# import smp.exceptions import os import subprocess import urllib.request import urllib.error import urllib.parse import datetime import markdown from skaldpress.metadata_parser import extract_parse_yaml_metadata from gfm import AutolinkExtension, TaskListExtension # type: ignore from typing import Any def smp_builtin_define(macro_processor, macro_name, macro_value=None): macro_name = macro_processor.process_input(macro_name) if macro_value is not None: macro_value = macro_processor.process_input(macro_value) macro_processor.macros[macro_name] = macro_value else: macro_processor.macros[macro_name] = "" return "" def smp_builtin_undefine(macro_processor, macro_name): if macro_name in macro_processor.macros: del macro_processor.macros[macro_name] return "" def smp_builtin_define_array(macro_processor, macro_name): macro_name = macro_processor.process_input(macro_name) macro_processor.macros[macro_name] = list() return "" def smp_builtin_ifdef(macro_processor, macro_name, iftrue, iffalse=None): if macro_name in macro_processor.macros: return macro_processor.process_input(iftrue) if iffalse is not None: return macro_processor.process_input(iffalse) return "" def smp_builtin_ifndef(macro_processor, macro_name, iftrue, iffalse=None): if macro_name not in macro_processor.macros: return macro_processor.process_input(iftrue) if iffalse is not None: return macro_processor.process_input(iffalse) return "" def smp_builtin_ifeq(macro_processor, a, b, iftrue, iffalse=None): a = macro_processor.process_input(a) b = macro_processor.process_input(b) if a == b: return macro_processor.process_input(iftrue) if iffalse is not None: return macro_processor.process_input(iffalse) return "" def smp_builtin_ifneq(macro_processor, a, b, iftrue, iffalse=None): a = macro_processor.process_input(a) b = macro_processor.process_input(b) if a != b: return macro_processor.process_input(iftrue) if iffalse is not None: return macro_processor.process_input(iffalse) return "" def smp_builtin_add_metadata(macro_processor, metadata: dict[str, Any], overwrite=True): """ Not added to macro_processor as macro """ for macro_name, value in metadata.items(): if not macro_name.startswith( macro_processor._get_macro_with_prefix("metadata_prefix") ): macro_name = f"{macro_processor._get_macro_with_prefix('metadata_prefix')}{macro_name}" macro_value = str(value) if isinstance(value, list): macro_value = [str(el) for el in value] if macro_name in macro_processor.macros: macro_value.extend(macro_processor.macros[macro_name]) if overwrite or macro_name not in macro_processor.macros: macro_processor.define_macro(macro_name, macro_value) def smp_builtin_include(macro_processor, filename): return smp_builtin_read(macro_processor, filename, template_content=None) def smp_builtin_parse_leading_yaml(macro_processor, content): """ Not added to macro_processor as macro """ metadata, content = extract_parse_yaml_metadata(content) smp_builtin_add_metadata(macro_processor, metadata, overwrite=True) return content def smp_builtin_include_verbatim(macro_processor, filename): filename = macro_processor.process_input(filename) with open(filename, "r") as f: file_content = f.read() return file_content def smp_builtin_shell(macro_processor, cmd_args): cmd_args = macro_processor.process_input(cmd_args) return subprocess.check_output(cmd_args, shell=True).decode() def smp_builtin_eval(macro_processor, expression): r = eval(expression, macro_processor.py_global_env, macro_processor.macros) return r def smp_builtin_array_push(macro_processor, array_name, *values): if array_name not in macro_processor.macros: raise Exception(f"{array_name} is not a macro") if not isinstance(macro_processor.macros[array_name], list): raise Exception(f"{array_name} is not a array") for value in values: macro_processor.macros[array_name].append(value) return "" def smp_builtin_array_size(macro_processor, array_name): if array_name not in macro_processor.macros: raise Exception(f"{array_name} is not a macro") if not isinstance(macro_processor.macros[array_name], list): raise Exception(f"{array_name} is not a array") return str(len(macro_processor.macros[array_name])) def smp_builtin_array_each(macro_processor, array_name, template): if array_name not in macro_processor.macros: raise Exception(f"{array_name} is not a macro") if not isinstance(macro_processor.macros[array_name], list): raise Exception(f"{array_name} is not a array") out = "" for el in macro_processor.macros[array_name]: if isinstance(el, str): el = [el] out += macro_processor.process_input(macro_processor.expand_macro(template, el)) return out def smp_builtin_explode(macro_processor, array_name, delimiter, input): if array_name not in macro_processor.macros: raise Exception(f"{array_name} is not a macro") if not isinstance(macro_processor.macros[array_name], list): raise Exception(f"{array_name} is not a array") delimiter = macro_processor.process_input(delimiter) for el in macro_processor.process_input(input).split(delimiter): macro_processor.macros[array_name].append(el) return "" def smp_builtin_format_time(macro_processor, format, time): timestamp = macro_processor.process_input(time) dobj = datetime.datetime.fromisoformat(timestamp) return dobj.strftime(format) def smp_builtin_html_from_markdown(macro_processor, text, extensions=list()): # Get rid of quoting, I don't remember why, but the rust implementation does it like this. for _ in range(2): text = macro_processor.process_input(text) extensions.append(AutolinkExtension()) extensions.append(TaskListExtension(max_depth=2)) return markdown.markdown(text, extensions=extensions) def _smp_builtin_template_content(content): def inner(macro_processor): """ This should do some kind of stack thing, so we can track which file we are processing. entering the CONTENT is fine, the question is how to handle exiting it. could have a "once" macro or something, that is added to the end of the content. """ return content return inner def smp_builtin_template(macro_processor, template, content): return smp_builtin_read(macro_processor, template, template_content=content) def smp_builtin_read(macro_processor, filename, template_content=None): with open(filename, "r") as f: file_content = f.read() metadata = {} if macro_processor._get_macro_with_prefix("parse_file_yaml"): metadata, file_content = extract_parse_yaml_metadata(file_content) smp_builtin_add_metadata(macro_processor, metadata, overwrite=False) extension = os.path.splitext(filename)[1][1:] or "" macro_processor._define_macro_with_prefix("target_file_extension", extension) if template_content is not None: macro_processor._get_macro_with_prefix("template_stack").append(filename) macro_processor.macros["CONTENT"] = template_content content = macro_processor.process_input(file_content) if extension == "md": content = smp_builtin_html_from_markdown(macro_processor, content) if (template := macro_processor.macros.get("METADATA_template")) is not None: if template not in macro_processor._get_macro_with_prefix("template_stack"): return smp_builtin_read(macro_processor, template, content) return content global LINK_CACHE LINK_CACHE: dict[str, tuple[bool, int, str]] = dict() def smp_builtin_wodl(macro_processor, link, timeout_seconds=5): url = urllib.parse.urlparse(link) link = ( url.scheme + "://" + url.netloc.encode("idna").decode("ascii") + urllib.parse.quote(url.path) ) if link in LINK_CACHE: return LINK_CACHE[link] try: r = urllib.request.urlopen(link, timeout=timeout_seconds) working_link = (r.status == 200) and (r.reason == "OK") LINK_CACHE[link] = (working_link, r.status, r.reason) if not working_link: macro_processor.log_warning(f"Dead link {link} ({r.status} {r.reason})!") except urllib.error.URLError as e: macro_processor.log_warning(f"Dead link {link} ({e})!") return "" def smp_builtin_once(macro_processor, content): if (cache := macro_processor._get_macro_with_prefix("once_cache")) is not None: if (exp := cache.get(content)) is not None: return exp else: macro_processor._define_macro_with_prefix("once_cache", {}) expanded_content = macro_processor.process_input(content) macro_processor._get_macro_with_prefix("once_cache", expanded_content) return expanded_content def smp_builtin_dumpenv(macro_processor): out = "" out += "━ Macros ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" for key, val in macro_processor.macros.items(): out += f"{repr(key)}: {repr(val)}\n" out += "━ Globals ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" for key, val in macro_processor.py_global_env.items(): if key == "__builtins__": continue out += f"{repr(key)}: {repr(val)}\n" out += "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" return out