82 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			Python
		
	
	
	
		
		
			
		
	
	
			82 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			Python
		
	
	
	
| 
								 | 
							
								# ===----------------- directive.py - Directive Base Class ----------------===//
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Copyright 2019-2020 The IBM Research Authors.
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# =============================================================================
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# ===----------------------------------------------------------------------===//
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import re
							 | 
						||
| 
								 | 
							
								import ast
							 | 
						||
| 
								 | 
							
								from typing import List, Dict, Callable, Any, Pattern, Tuple
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from doc_parser import failure, success, succeeded
							 | 
						||
| 
								 | 
							
								from utils import DocCheckerCtx
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								DirectiveConfigList = List[Dict[str, Any]]
							 | 
						||
| 
								 | 
							
								ConfigParseResult = Tuple[str, Dict[str, Any]]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Directive(object):
							 | 
						||
| 
								 | 
							
								    """"""
							 | 
						||
| 
								 | 
							
								    def __init__(self, ext_to_regexes: Dict[str, str],
							 | 
						||
| 
								 | 
							
								                 config_parsers: List[Callable[[str, DirectiveConfigList],
							 | 
						||
| 
								 | 
							
								                                               ConfigParseResult]],
							 | 
						||
| 
								 | 
							
								                 handler: Callable[[Dict[str, Any], DocCheckerCtx], None]):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        :param ext_to_regexes: specify a regex expression to match the directive (for each file extension type).
							 | 
						||
| 
								 | 
							
								        :param config_parsers: specify a list of parsers to parse configuration. They will be invoked in order until one indicates parsing is successful.
							 | 
						||
| 
								 | 
							
								        :param handler: a function to perform the invariance check specified by the directive.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        self.ext_to_patterns: Dict[str, Pattern] = {}
							 | 
						||
| 
								 | 
							
								        for ext, pattern in ext_to_regexes.items():
							 | 
						||
| 
								 | 
							
								            self.ext_to_patterns[ext] = re.compile(pattern)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        self.config_parsers: List[Callable[[str, DirectiveConfigList],
							 | 
						||
| 
								 | 
							
								                                           ConfigParseResult]] = config_parsers
							 | 
						||
| 
								 | 
							
								        self.handler = handler
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def try_parse_directive(
							 | 
						||
| 
								 | 
							
								            self, ctx: DocCheckerCtx,
							 | 
						||
| 
								 | 
							
								            directive_config: DirectiveConfigList) -> Tuple[str, Any]:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        :param ctx: parser context.
							 | 
						||
| 
								 | 
							
								        :param directive_config: a list used to output parsed directive configuration.
							 | 
						||
| 
								 | 
							
								        :return: parse result.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        line = ctx.doc_file.next_non_empty_line()
							 | 
						||
| 
								 | 
							
								        matches = self.ext_to_patterns[ctx.doc_file_ext()].findall(line)
							 | 
						||
| 
								 | 
							
								        if len(matches) > 1:
							 | 
						||
| 
								 | 
							
								            raise ValueError("more than one directives in a line")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        match = matches[0] if len(matches) else None
							 | 
						||
| 
								 | 
							
								        if match:
							 | 
						||
| 
								 | 
							
								            for parser in self.config_parsers:
							 | 
						||
| 
								 | 
							
								                if succeeded(parser(match, directive_config)):
							 | 
						||
| 
								 | 
							
								                    return success()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            raise ValueError("Failed to parse configuration.")
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            return failure()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def handle(self, config, ctx):
							 | 
						||
| 
								 | 
							
								        self.handler(config, ctx)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def generic_config_parser(
							 | 
						||
| 
								 | 
							
								        match: str, directive_config: DirectiveConfigList) -> Tuple[str, Any]:
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    Generic configuration parser.
							 | 
						||
| 
								 | 
							
								    Will return success if and only if configuration is specified as a python dictionary literal.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @param match: the content from which to parse the directive configuration.
							 | 
						||
| 
								 | 
							
								    @param directive_config: a list to output the parsed directive_config.
							 | 
						||
| 
								 | 
							
								    @return: parsing result.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        directive_config.append(ast.literal_eval(match))
							 | 
						||
| 
								 | 
							
								        return success()
							 | 
						||
| 
								 | 
							
								    except (SyntaxError, ValueError):
							 | 
						||
| 
								 | 
							
								        # If literal_eval failed, return parsing failure.
							 | 
						||
| 
								 | 
							
								        return failure()
							 |