+
From ae4d30ade9f21691257c876a867be0702c2435a4 Mon Sep 17 00:00:00 2001
From: Jim Shepich III '
+ yield from source
+ yield 0, ''
+
+# Pick extensions to use for Markdown parsing.
+MARKDOWN_EXTENSIONS = [
+ 'footnotes',
+ 'fenced_code',
+ CodeHiliteExtension(linenums = True, pygments_formatter = CustomHiliteFormatter)
+]
+
class ArticleMetadata(pydantic.BaseModel):
'''A model for the YAML frontmatter included with Markdown articles.'''
title: str
@@ -51,7 +76,7 @@ def load_markdown(md: str) -> tuple[Optional[ArticleMetadata], str]:
metadata = yaml.safe_load(raw_metadata)
# Convert the contents to a HTML string.
- content = markdown.markdown(raw_article)
+ content = markdown.markdown(raw_article, extensions = MARKDOWN_EXTENSIONS)
return ArticleMetadata(**metadata), content
diff --git a/jimsite/blog.py b/jimsite/blog.py
index 59229a3..231c8c4 100644
--- a/jimsite/blog.py
+++ b/jimsite/blog.py
@@ -58,9 +58,9 @@ def build_blog_archive(
tag_selector_options = []
tag_selector_css_rules = [f'''
- body:has(input[name="tag-selector"][value="*"]:checked) li:has(.blog-tag){{{{
+ body:has(input[name="tag-selector"][value="*"]:checked) li:has(.blog-tag){{
display: list-item!important;
- }}}}
+ }}
''']
# Add tag selector options in descending order of article count.
@@ -75,12 +75,12 @@ def build_blog_archive(
if tag == '*':
continue
tag_selector_css_rules.append(f'''
- body:has(input[name="tag-selector"]:not([value="{tag}"]):checked) li:has(.blog-tag[data="{tag}"]){{{{
+ body:has(input[name="tag-selector"]:not([value="{tag}"]):checked) li:has(.blog-tag[data="{tag}"]){{
display: none;
- }}}}
- body:has(input[name="tag-selector"][value="{tag}"]:checked) li:has(.blog-tag[data="{tag}"]){{{{
+ }}
+ body:has(input[name="tag-selector"][value="{tag}"]:checked) li:has(.blog-tag[data="{tag}"]){{
display: list-item!important;
- }}}}
+ }}
''')
# For Python 3.9-friendliness.
diff --git a/jimsite/templating.py b/jimsite/templating.py
index edefc19..189dd17 100644
--- a/jimsite/templating.py
+++ b/jimsite/templating.py
@@ -1,18 +1,52 @@
import os
-import re
+
+# Use the community regex package instead of re for QoL
+# features like variable-length lookbacks.
+import regex as re
+import logging
from dotmap import DotMap
+
+logger = logging.getLogger(__name__)
+
from .common import filepath_or_string, GlobalVars, SiteConfig, dotmap_access
+def escape_braces(template) -> str:
+ '''Escapes any curly braces that are unclosed or enclose
+ anything other than a pattern for a valid variable
+ (alphanumerics, underscores, and periods ONLY;
+ must start with alpha or underscore).
+ '''
+
+ # Match any left braces that are not followed by a valid variable
+ # name (with no whitespace) and a closing right brace.
+ left_brace_pattern = r'{(?![A-Za-z_][\w.]+})'
+
+ # Match any right braces that are not preceded by an opening
+ # left brace and a valid variable name.
+ right_brace_pattern = r'(? 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\.]+)\}'
+ placeholder_pattern = r'\{([A-Za-z_][\w.]+)\}'
+ # Escape braces that are not used to enclose Python variables
+ # (e.g. those used in inline CSS or JavaScript)
+ escaped = escape_braces(s)
+
# Find all matches in the string.
- matches = re.findall(placeholder_pattern, s)
+ matches = re.findall(placeholder_pattern, escaped)
# Return the set of distinct placeholders.
return set(matches)
@@ -85,7 +119,19 @@ def format_html_template(template: str, **kwargs) -> str:
# 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)
+
+ # Escape non-variable-enclosing braces each iteration of the
+ # loop, because each time they'll be replaced with single braces.
+ try:
+ escaped = escape_braces(formatted_html)
+ formatted_html = escaped.format(
+ globalvars = GlobalVars(),
+ **kwargs
+ )
+ except Exception as e:
+ logger.error('Encountered an exception while formatting the following:')
+ logger.error(escaped)
+ raise e
# Return the formatted HTML.
return formatted_html
diff --git a/requirements.txt b/requirements.txt
index c4b3774..b112f5e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,4 +3,6 @@ markdown
pyyaml
rfeed
dotmap
-pydantic
\ No newline at end of file
+pydantic
+pygments
+pymdownextensions
\ No newline at end of file
diff --git a/site/assets/css/codehilite.css b/site/assets/css/codehilite.css
new file mode 100644
index 0000000..96c6007
--- /dev/null
+++ b/site/assets/css/codehilite.css
@@ -0,0 +1,75 @@
+pre { line-height: 125%; }
+td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
+span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
+td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
+span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
+.codehilite .hll { background-color: #ffffcc }
+.codehilite { background: #f8f8f8; }
+.codehilite .c { color: #3D7B7B; font-style: italic } /* Comment */
+.codehilite .err { border: 1px solid #F00 } /* Error */
+.codehilite .k { color: #008000; font-weight: bold } /* Keyword */
+.codehilite .o { color: #666 } /* Operator */
+.codehilite .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */
+.codehilite .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */
+.codehilite .cp { color: #9C6500 } /* Comment.Preproc */
+.codehilite .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */
+.codehilite .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */
+.codehilite .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */
+.codehilite .gd { color: #A00000 } /* Generic.Deleted */
+.codehilite .ge { font-style: italic } /* Generic.Emph */
+.codehilite .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
+.codehilite .gr { color: #E40000 } /* Generic.Error */
+.codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */
+.codehilite .gi { color: #008400 } /* Generic.Inserted */
+.codehilite .go { color: #717171 } /* Generic.Output */
+.codehilite .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
+.codehilite .gs { font-weight: bold } /* Generic.Strong */
+.codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
+.codehilite .gt { color: #04D } /* Generic.Traceback */
+.codehilite .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
+.codehilite .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
+.codehilite .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
+.codehilite .kp { color: #008000 } /* Keyword.Pseudo */
+.codehilite .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
+.codehilite .kt { color: #B00040 } /* Keyword.Type */
+.codehilite .m { color: #666 } /* Literal.Number */
+.codehilite .s { color: #BA2121 } /* Literal.String */
+.codehilite .na { color: #687822 } /* Name.Attribute */
+.codehilite .nb { color: #008000 } /* Name.Builtin */
+.codehilite .nc { color: #00F; font-weight: bold } /* Name.Class */
+.codehilite .no { color: #800 } /* Name.Constant */
+.codehilite .nd { color: #A2F } /* Name.Decorator */
+.codehilite .ni { color: #717171; font-weight: bold } /* Name.Entity */
+.codehilite .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */
+.codehilite .nf { color: #00F } /* Name.Function */
+.codehilite .nl { color: #767600 } /* Name.Label */
+.codehilite .nn { color: #00F; font-weight: bold } /* Name.Namespace */
+.codehilite .nt { color: #008000; font-weight: bold } /* Name.Tag */
+.codehilite .nv { color: #19177C } /* Name.Variable */
+.codehilite .ow { color: #A2F; font-weight: bold } /* Operator.Word */
+.codehilite .w { color: #BBB } /* Text.Whitespace */
+.codehilite .mb { color: #666 } /* Literal.Number.Bin */
+.codehilite .mf { color: #666 } /* Literal.Number.Float */
+.codehilite .mh { color: #666 } /* Literal.Number.Hex */
+.codehilite .mi { color: #666 } /* Literal.Number.Integer */
+.codehilite .mo { color: #666 } /* Literal.Number.Oct */
+.codehilite .sa { color: #BA2121 } /* Literal.String.Affix */
+.codehilite .sb { color: #BA2121 } /* Literal.String.Backtick */
+.codehilite .sc { color: #BA2121 } /* Literal.String.Char */
+.codehilite .dl { color: #BA2121 } /* Literal.String.Delimiter */
+.codehilite .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
+.codehilite .s2 { color: #BA2121 } /* Literal.String.Double */
+.codehilite .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */
+.codehilite .sh { color: #BA2121 } /* Literal.String.Heredoc */
+.codehilite .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */
+.codehilite .sx { color: #008000 } /* Literal.String.Other */
+.codehilite .sr { color: #A45A77 } /* Literal.String.Regex */
+.codehilite .s1 { color: #BA2121 } /* Literal.String.Single */
+.codehilite .ss { color: #19177C } /* Literal.String.Symbol */
+.codehilite .bp { color: #008000 } /* Name.Builtin.Pseudo */
+.codehilite .fm { color: #00F } /* Name.Function.Magic */
+.codehilite .vc { color: #19177C } /* Name.Variable.Class */
+.codehilite .vg { color: #19177C } /* Name.Variable.Global */
+.codehilite .vi { color: #19177C } /* Name.Variable.Instance */
+.codehilite .vm { color: #19177C } /* Name.Variable.Magic */
+.codehilite .il { color: #666 } /* Literal.Number.Integer.Long */
diff --git a/site/assets/css/theme.css b/site/assets/css/theme.css
index 31c38e2..2ce4a91 100644
--- a/site/assets/css/theme.css
+++ b/site/assets/css/theme.css
@@ -1,3 +1,5 @@
+/* TODO: Reorganize CSS */
+
body{
background: inherit;
background-color: white;
@@ -296,4 +298,10 @@ p.socials{
article ul, article ol{
margin-bottom: 1em;
-}
\ No newline at end of file
+}
+
+pre:has(> code), mark{
+ font-family: 'Oxygen Mono', sans-serif;
+ font-display: swap;
+ background-color: #d8d8d8;
+}
diff --git a/site/templates/components/jimoire-article.html b/site/templates/components/jimoire-article.html
new file mode 100644
index 0000000..304110d
--- /dev/null
+++ b/site/templates/components/jimoire-article.html
@@ -0,0 +1,20 @@
+
+ {article.metadata.title}
+ By Jim Shepich III
+
+ + < Previous + + Next > +
+Tags: {blog_tags}{templates.components.rss_icon}
+ \ No newline at end of file diff --git a/site/templates/partials/default_css.html b/site/templates/partials/default_css.html index c34eeeb..2e85391 100644 --- a/site/templates/partials/default_css.html +++ b/site/templates/partials/default_css.html @@ -1,5 +1,6 @@ - + +