diff options
Diffstat (limited to 'src/smp/macro_processor.py')
-rw-r--r-- | src/smp/macro_processor.py | 171 |
1 files changed, 79 insertions, 92 deletions
diff --git a/src/smp/macro_processor.py b/src/smp/macro_processor.py index 2af26dd..a31f040 100644 --- a/src/smp/macro_processor.py +++ b/src/smp/macro_processor.py @@ -31,48 +31,20 @@ def macro_name_clean(macro_name: str) -> str: return macro_name -def seek(input: str, start: int, target: str) -> int | None: - """Seek for a value in a string, consider using startswith instead""" - from warnings import warn - - warn( - "seek should be considered replaced with str.startswith", - DeprecationWarning, - stacklevel=2, - ) - input_end = len(input) - target_end = len(target) - - if input_end < start + target_end: - return None - - i = 0 - while i < len(target): - if input[start + i] != target[i]: - return None - i += 1 - - return start + target_end - - class MacroProcessor: source_file_path: str - """All currently defined macros in this MacroProcessor""" macros: dict[str, Any] - """ All macro invocations that has happened """ - macro_invocations: list[tuple[str, list[str]]] - warnings: list[Any] - + """ Macros which are @directives """ + special_macros: dict[str, tuple[Any, Any]] """ Global environment for python execution """ - py_global_env: dict - + """ Local environment for python execution """ py_local_env_alt: dict py_local_env_current: dict - - special_macros: dict[str, tuple[Any, Any]] - + """ All macro invocations that has happened """ + macro_invocations: list[tuple[str, list[str]]] + warnings: list[Any] start_quote: str = '%"' end_quote: str = '"%' prefix: str = "" @@ -84,39 +56,24 @@ class MacroProcessor: self.py_global_env = dict() self.py_local_env_alt = dict() self.py_local_env_current = self.macros - self.indent_level = "" self.prefix = prefix self._define_builtins(self.macros) self._define_builtins(self.py_local_env_alt) + def _import_symbols(self, module, env, function_prefix=""): + for name, fun in inspect.getmembers(module, inspect.isfunction): + if name.startswith("_"): + continue + if not name.startswith(function_prefix): + continue + name = name.replace(function_prefix, "") + env[f"{self.prefix}{name}"] = fun + def _define_builtins(self, env): + self._import_symbols(smp.builtins, env, function_prefix="smp_builtin_") + env[f"{self.prefix}macro_processor"] = self - env[f"{self.prefix}define"] = smp.builtins.smp_builtin_define - env[f"{self.prefix}undefine"] = smp.builtins.smp_builtin_undefine - env[f"{self.prefix}define_array"] = smp.builtins.smp_builtin_define_array - env[f"{self.prefix}ifdef"] = smp.builtins.smp_builtin_ifdef - env[f"{self.prefix}ifndef"] = smp.builtins.smp_builtin_ifndef - env[f"{self.prefix}ifeq"] = smp.builtins.smp_builtin_ifeq - env[f"{self.prefix}ifneq"] = smp.builtins.smp_builtin_ifneq - env[f"{self.prefix}once"] = smp.builtins.smp_builtin_once - env[f"{self.prefix}include"] = smp.builtins.smp_builtin_include - env[f"{self.prefix}include_verbatim"] = ( - smp.builtins.smp_builtin_include_verbatim - ) - env[f"{self.prefix}shell"] = smp.builtins.smp_builtin_shell - env[f"{self.prefix}dumpenv"] = smp.builtins.smp_builtin_dumpenv - env[f"{self.prefix}eval"] = smp.builtins.smp_builtin_eval - env[f"{self.prefix}array_push"] = smp.builtins.smp_builtin_array_push - env[f"{self.prefix}array_each"] = smp.builtins.smp_builtin_array_each - env[f"{self.prefix}array_size"] = smp.builtins.smp_builtin_array_size - env[f"{self.prefix}explode"] = smp.builtins.smp_builtin_explode - env[f"{self.prefix}format_time"] = smp.builtins.smp_builtin_format_time - env[f"{self.prefix}html_from_markdown"] = ( - smp.builtins.smp_builtin_html_from_markdown - ) - env[f"{self.prefix}wodl"] = smp.builtins.smp_builtin_wodl - env[f"{self.prefix}template"] = smp.builtins.smp_builtin_template env[f"{self.prefix}template_stack"] = [] # If true, include-macros will parse yaml in beginning of content @@ -138,26 +95,64 @@ class MacroProcessor: def _get_macro_with_prefix(self, macro_name, sub_prefix: str = "", default=None): return self.macros.get(f"{self.prefix}{sub_prefix}{macro_name}", default) + def _get_metadata(self, macro_name, default=None): + return self._get_macro_with_prefix( + macro_name, + sub_prefix=self._get_macro_with_prefix("metadata_prefix"), + default=default, + ) + + def _define_metadata(self, macro_name, macro_value): + return self._define_macro_with_prefix( + macro_name, + macro_value, + sub_prefix=self._get_macro_with_prefix("metadata_prefix"), + ) + def log_warning(self, message): """ Here we should add some more information, line number, file etc, when that is available """ self.warnings.append(message) + def _expand_callable_macro(self, macro: Any, args: list[str] = list()) -> str: + signature = inspect.signature(macro) + macro_args: list[Any] = [] + if "macro_processor" in signature.parameters or "smp" in signature.parameters: + macro_args.append(self) + macro_args.extend(args) + # This should maybe be processed as well(?) + return str(macro(*macro_args)) + + def _expand_string_macro(self, macro: Any, args: list[str] = list()) -> str: + expanded = macro + for i, arg in enumerate(args): + placeholder = f"${i}" + expanded = macro.replace(placeholder, arg) + return self.process_input(expanded) + + def _expand_unknown_macro( + self, macro_name: str, args: list[str] = list(), process_args: bool = True + ) -> str: + if len(args) == 0: + return macro_name + out = f"{macro_name}(" + for i, arg in enumerate(args): + if process_args: + out += self.process_input(arg) + else: + out += arg + if i < (len(args) - 1): + out += "," + out += ")" + return out + def expand_macro(self, macro_name: str, args: list[str] = list()) -> str: # Ignore trailing underscore in macro name, the parser will pop a space in front if # present, but we should ignore it for finding the macro. macro_name = macro_name_clean(macro_name) if macro_name not in self.macros: - if len(args) == 0: - return macro_name - out = f"{macro_name}(" - for i, arg in enumerate(args): - out += self.process_input(arg) - if i < (len(args) - 1): - out += "," - out += ")" - return out + return self._expand_unknown_macro(macro_name, args, process_args=True) # Strip leading whitespace from arguments for arg in args: @@ -171,28 +166,18 @@ class MacroProcessor: macro = self.macros.get(macro_name) if callable(macro): - signature = inspect.signature(macro) - macro_args: list[Any] = [] - if ( - "macro_processor" in signature.parameters - or "smp" in signature.parameters - ): - macro_args.append(self) - macro_args.extend(args) try: - return str(macro(*macro_args)) + return self._expand_callable_macro(macro, args) except Exception as e: - s = f"{macro_name}({','.join([repr(x) for x in args])})" + s = self._expand_unknown_macro(macro_name, args, process_args=False) self.log_warning( f"Error expanding macro {s} ({e})\n{traceback.format_exc()}" ) return s + if isinstance(macro, str): - expanded = macro - for i, arg in enumerate(args): - placeholder = f"${i}" - expanded = macro.replace(placeholder, arg) - return self.process_input(expanded) + return self._expand_string_macro(macro, args) + return f"{repr(macro)}" def process_input(self, input: str): @@ -366,21 +351,23 @@ class MacroProcessor: return output def store(self, **xargs): - requested_keys = self.macros.get("METADATA_keep_states", self.macros.keys()) + requested_keys = self._get_metadata("keep_states", []) for key in self.macros.keys(): - if key.startswith("METADATA_") and key not in requested_keys: + if ( + key.startswith(self._get_macro_with_prefix("metadata_prefix")) + and key not in requested_keys + ): requested_keys.append(key) if isinstance(requested_keys, str): requested_keys = [str(requested_keys)] - needs_recompilation = ("METADATA_keep_states" in self.macros) or ( - "all_tagged_by" in [x[0] for x in self.macro_invocations] - ) + needs_recompilation = ( + f"{self._get_macro_with_prefix('metadata_prefix')}keep_states" + in self.macros + ) or ("all_tagged_by" in [x[0] for x in self.macro_invocations]) - target_filename = self._get_macro_with_prefix( - "target_filename", sub_prefix="METADATA_" - ) + target_filename = self._get_metadata("target_filename") self.py_global_env["macro_processor_state"][self.source_file_path] = dict( { |