refactor #1
@ -18,7 +18,7 @@ sites:
|
||||
git_repo: ssh://gitea/jim/resume.git
|
||||
build_cache: ./build/resume
|
||||
assets:
|
||||
- '{build_cache}/shepich_resume.pdf'
|
||||
- 'shepich_resume.pdf'
|
||||
dogma_jimfinium:
|
||||
base_url: http://localhost:8080/dogma-jimfinium
|
||||
git_repo: ssh://gitea/jim/dogma-jimfinium.git
|
||||
|
||||
92
jimsite.py
92
jimsite.py
@ -1,4 +1,5 @@
|
||||
import os
|
||||
import re
|
||||
import glob
|
||||
import shutil
|
||||
import subprocess
|
||||
@ -23,6 +24,59 @@ def filepath_or_string(s: str) -> str:
|
||||
return s
|
||||
|
||||
|
||||
def extract_placeholders(s: str) -> set:
|
||||
'''Extracts placeholder variables in the format `{variable}` from
|
||||
an unformatted template string.'''
|
||||
|
||||
# Regex pattern to match placeholders with alphanumerics, dots, and underscores.
|
||||
placeholder_pattern = r'\{([\w\.]+)\}'
|
||||
|
||||
# Find all matches in the string.
|
||||
matches = re.findall(placeholder_pattern, s)
|
||||
|
||||
# Return the set of distinct placeholders.
|
||||
return set(matches)
|
||||
|
||||
|
||||
def find_cyclical_placeholders(s: str, _parents: tuple = None, _cycles: set = None, **kwargs) -> set[tuple]:
|
||||
'''Recursively interpolates supplied kwargs into a template string to validate
|
||||
that there are no cyclical dependencies that would cause infinite recursion.
|
||||
|
||||
Returns a list of paths (expressed as tuples of nodes) of cyclical placeholders.
|
||||
'''
|
||||
|
||||
# Track the lineage of each placeholder so we can see if it is its own ancestor.
|
||||
if _parents is None:
|
||||
_parents = tuple()
|
||||
|
||||
# Keep track of any cycles encountered.
|
||||
if _cycles is None:
|
||||
_cycles = set()
|
||||
|
||||
# Extract the placeholders from the input.
|
||||
placeholders = extract_placeholders(s)
|
||||
|
||||
# Recursion will naturally end once there are no more nested placeholders.
|
||||
for p in placeholders:
|
||||
|
||||
# Any placeholder that has itself in its ancestry forms a cycle.
|
||||
if p in _parents:
|
||||
_cycles.add(_parents + (p,))
|
||||
|
||||
# For placeholders that are not their own ancestor, recursively
|
||||
# interpolate the kwargs into the nested placeholders until we reach
|
||||
# strings without placeholders.
|
||||
else:
|
||||
find_cyclical_placeholders(
|
||||
('{'+p+'}').format(**kwargs),
|
||||
_parents = _parents+(p,),
|
||||
_cycles = _cycles,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
return _cycles
|
||||
|
||||
|
||||
with open('config.yaml', 'r') as config_file:
|
||||
config = yaml.safe_load(config_file.read())
|
||||
|
||||
@ -69,20 +123,36 @@ def load_markdown(md: str) -> tuple[ArticleMetadata|None, str]:
|
||||
|
||||
def format_html_template(template: str, **kwargs) -> str:
|
||||
'''Interpolates variables specified as keyword arguments
|
||||
into the given HTML template.'''
|
||||
into the given HTML template.
|
||||
|
||||
# Example
|
||||
|
||||
```python
|
||||
kwargs = {'a': '1', 'b': '2', 'c': '{d}+{e}', 'd': '3', 'e': '{c}'}
|
||||
s = '{a} + {b} = {c}'
|
||||
find_cyclical_placeholders(s, **kwargs)
|
||||
|
||||
>>> {('c', 'e', 'c')}
|
||||
```
|
||||
'''
|
||||
|
||||
# Load the template if a filepath is given.
|
||||
template = filepath_or_string(template)
|
||||
|
||||
# Interpolate the kwargs into the HTML template.
|
||||
# Apply global variables twice in case a partial used
|
||||
# by the first call of .format() uses a variable.
|
||||
html = template.format(
|
||||
globalvars = GlobalVars(), **kwargs
|
||||
).format(globalvars = GlobalVars())
|
||||
# Ensure the template does not have cyclical placeholder references.
|
||||
cycles = find_cyclical_placeholders(template, globalvars = GlobalVars(), **kwargs)
|
||||
|
||||
if len(cycles) > 0:
|
||||
raise ValueError('Template has cyclical dependencies: {cycles}')
|
||||
|
||||
# Iteratively interpolate global variables and the kwargs into the template until
|
||||
# there are no more placeholders. The loop is used to account for nested template references.
|
||||
formatted_html = template
|
||||
while len(extract_placeholders(formatted_html)) > 0:
|
||||
formatted_html = formatted_html.format(globalvars = GlobalVars(), **kwargs)
|
||||
|
||||
# Return the formatted HTML.
|
||||
return html
|
||||
return formatted_html
|
||||
|
||||
|
||||
run = lambda cmd: subprocess.run(cmd.split(' '), stdout = subprocess.PIPE, stderr = subprocess.PIPE)
|
||||
@ -212,7 +282,7 @@ def build_index(site: SiteConfig) -> dict:
|
||||
|
||||
# Expand any globbed expressions.
|
||||
expanded_article_list = []
|
||||
for a in site.articles:
|
||||
for a in site.articles or {}:
|
||||
expanded_article_list.extend(
|
||||
# Article paths are defined relative to the build cache; construct the full path.
|
||||
glob.glob(f'{site.build_cache}/{a.lstrip("/")}')
|
||||
@ -259,10 +329,6 @@ def map_templates(dir: str, parent = '') -> DotMap:
|
||||
with open(full_path, 'r') as file:
|
||||
html = file.read()
|
||||
|
||||
# # Interpolate global variables into partials.
|
||||
# if 'partials' in full_path:
|
||||
# html = html.format(globalvars = GlobalVars())
|
||||
|
||||
output[filestem] = html
|
||||
|
||||
return DotMap(output)
|
||||
|
||||
512
testbench.ipynb
512
testbench.ipynb
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user