# import smp.exceptions import os import subprocess import urllib.request import urllib.error import urllib.parse import datetime from markdown_it import MarkdownIt from skaldpress.metadata_parser import extract_parse_yaml_metadata 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 | list[str] = 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_file(macro_processor, filename): return _smp_builtin_read( macro_processor, filename, template_content=None, inline=True ) 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()): text = macro_processor.process_input(text) md = ( MarkdownIt("commonmark", {"breaks": True, "html": True}) .enable("table") .enable("list") ) for extension in extensions: extension(md) return md.render(text) def _smp_builtin_template_content(): def inner(macro_processor): filename, content, extension = macro_processor._get_macro_with_prefix( "template_stack_content" ).pop() macro_processor._enter_file_frame(f"[part]{filename}", 0, "") res = macro_processor.process_input(content, file=filename) if extension == "md": res = smp_builtin_html_from_markdown(macro_processor, res) macro_processor._pop_file_frame() return res 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, inline=False): macro_processor._enter_file_frame(filename, 0, template_content) 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=True) extension = os.path.splitext(filename)[1][1:] or "" if not inline: 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 = file_content if (template := macro_processor._get_metadata("template")) is not None: template_prefix = macro_processor._get_macro_with_prefix("template_prefix") if not os.path.exists(template): template = os.path.join(template_prefix, template) if template not in macro_processor._get_macro_with_prefix("template_stack"): macro_processor._get_macro_with_prefix("template_stack_content").append( (filename, content, extension) ) return _smp_builtin_read( macro_processor, template, _smp_builtin_template_content() ) content = macro_processor.process_input(file_content, file=filename) if extension == "md": content = smp_builtin_html_from_markdown(macro_processor, content) return content def smp_builtin_indent(macro_processor, indent: int, content: str): indent = int(indent) content = macro_processor.process_input(content) return "\n".join( f"{' ' * (indent if i > 0 else 0)}{x}" for i, x in enumerate(content.split("\n")) ) def smp_builtin_wodl(macro_processor, link, timeout_seconds=5): if (macro_processor._get_macro_with_prefix("wodl_cache")) is None: macro_processor._define_macro_with_prefix("wodl_cache", {}) cache = macro_processor._get_macro_with_prefix("wodl_cache") url = urllib.parse.urlparse(link) link = ( url.scheme + "://" + url.netloc.encode("idna").decode("ascii") + urllib.parse.quote(url.path) ) if link in cache: return cache[link] try: r = urllib.request.urlopen(link, timeout=timeout_seconds) working_link = (r.status == 200) and (r.reason == "OK") 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 Exception as e: macro_processor.log_warning(f"Dead link {link} ({e})!") return "" def smp_builtin_expand_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