diff options
Diffstat (limited to 'src/smp/macro_processor.py')
-rw-r--r-- | src/smp/macro_processor.py | 145 |
1 files changed, 116 insertions, 29 deletions
diff --git a/src/smp/macro_processor.py b/src/smp/macro_processor.py index 68fd726..2af26dd 100644 --- a/src/smp/macro_processor.py +++ b/src/smp/macro_processor.py @@ -56,15 +56,18 @@ def seek(input: str, start: int, target: str) -> int | None: class MacroProcessor: - """All currently defined macros in this 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]]] - """ Emitted warnings """ warnings: list[Any] + """ Global environment for python execution """ + py_global_env: dict + py_local_env_alt: dict py_local_env_current: dict @@ -72,6 +75,7 @@ class MacroProcessor: start_quote: str = '%"' end_quote: str = '"%' + prefix: str = "" def __init__(self, prefix=""): self.macros = dict() @@ -81,31 +85,46 @@ class MacroProcessor: self.py_local_env_alt = dict() self.py_local_env_current = self.macros self.indent_level = "" - - self._define_builtins(self.macros, prefix=prefix) - self._define_builtins(self.py_local_env_alt, prefix=prefix) - - def _define_builtins(self, env, prefix=""): - env[f"{prefix}macro_processor"] = self - env[f"{prefix}define"] = smp.builtins.smp_builtin_define - env[f"{prefix}undefine"] = smp.builtins.smp_builtin_undefine - env[f"{prefix}define_array"] = smp.builtins.smp_builtin_define_array - env[f"{prefix}ifdef"] = smp.builtins.smp_builtin_ifdef - env[f"{prefix}ifndef"] = smp.builtins.smp_builtin_ifndef - env[f"{prefix}ifeq"] = smp.builtins.smp_builtin_ifeq - env[f"{prefix}ifneq"] = smp.builtins.smp_builtin_ifneq - env[f"{prefix}include"] = smp.builtins.smp_builtin_include - env[f"{prefix}include_verbatim"] = smp.builtins.smp_builtin_include_verbatim - env[f"{prefix}shell"] = smp.builtins.smp_builtin_shell - env[f"{prefix}dumpenv"] = smp.builtins.smp_builtin_dumpenv - env[f"{prefix}eval"] = smp.builtins.smp_builtin_eval - env[f"{prefix}array_push"] = smp.builtins.smp_builtin_array_push - env[f"{prefix}array_each"] = smp.builtins.smp_builtin_array_each - env[f"{prefix}array_size"] = smp.builtins.smp_builtin_array_size - env[f"{prefix}explode"] = smp.builtins.smp_builtin_explode - env[f"{prefix}format_time"] = smp.builtins.smp_builtin_format_time - env[f"{prefix}html_from_markdown"] = smp.builtins.smp_builtin_html_from_markdown - env[f"{prefix}wodl"] = smp.builtins.smp_builtin_wodl + self.prefix = prefix + + self._define_builtins(self.macros) + self._define_builtins(self.py_local_env_alt) + + def _define_builtins(self, env): + 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 + env[f"{self.prefix}parse_file_yaml"] = True + # If true, some macros will run in a draft-mode, + # meaning they will skip steps that are slow. + env[f"{self.prefix}draft"] = False + env[f"{self.prefix}metadata_prefix"] = "METADATA_" def define_macro_string(self, macro_name, macro_value): self.define_macro(macro_name, str(macro_value)) @@ -113,6 +132,18 @@ class MacroProcessor: def define_macro(self, macro_name, macro_value): self.macros[macro_name] = macro_value + def _define_macro_with_prefix(self, macro_name, macro_value, sub_prefix: str = ""): + self.macros[f"{self.prefix}{sub_prefix}{macro_name}"] = macro_value + + 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 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_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. @@ -152,7 +183,9 @@ class MacroProcessor: return str(macro(*macro_args)) except Exception as e: s = f"{macro_name}({','.join([repr(x) for x in args])})" - self.warnings.append(f"Error expanding macro {s} ({e})") + self.log_warning( + f"Error expanding macro {s} ({e})\n{traceback.format_exc()}" + ) return s if isinstance(macro, str): expanded = macro @@ -175,6 +208,10 @@ class MacroProcessor: @for <python-expression> @endfor + + Note: Consider writing a new implementation that does it the same way M4 does, + by pushing the expanded macros back to the input string, this may be more confusing, + but may also be faster (stream or mutable string) """ output = "" state = ParserState.NORMAL @@ -322,8 +359,58 @@ class MacroProcessor: # Handle cases where the text ends with a macro without arguments if macro_name != "": if macro_is_whitespace_deleting(macro_name): - if output[-1] == " ": + if len(output) > 0 and output[-1] == " ": output = output[:-1] macro_name = macro_name_clean(macro_name) output += self.expand_macro(macro_name) return output + + def store(self, **xargs): + requested_keys = self.macros.get("METADATA_keep_states", self.macros.keys()) + for key in self.macros.keys(): + if key.startswith("METADATA_") 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] + ) + + target_filename = self._get_macro_with_prefix( + "target_filename", sub_prefix="METADATA_" + ) + + self.py_global_env["macro_processor_state"][self.source_file_path] = dict( + { + # "content": "", + "stored_data": { + k: v for k, v in self.macros.items() if k in requested_keys + }, + "extension": self._get_macro_with_prefix("target_file_extension"), + "source_path": self.source_file_path, + "needs_recompilation": needs_recompilation, + "target_filename": target_filename, + **xargs, + } + ) + return self.py_global_env["macro_processor_state"][self.source_file_path] + + +class MacroProcessorState: + global_state: dict + + def __init__(self): + self.global_state = dict() + + def macro_processor(self, macro_processor=None): + if macro_processor is None: + macro_processor = MacroProcessor() + + macro_processor.py_global_env["macro_processor_state"] = self.global_state + return macro_processor + + def print_state(self): + for key, val in self.global_state.items(): + print(f"{key[-20:]:20} {val}") |