aboutsummaryrefslogtreecommitdiff
path: root/src/smp/macro_processor.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/smp/macro_processor.py')
-rw-r--r--src/smp/macro_processor.py171
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(
{