Author: {{ metadata.author }}
Published: {{ get_time_iso8601(metadata.stat.ctime) }}
{% if metadata.stat.mtime-metadata.stat.ctime > 512 %}
diff --git a/examples/pixywerk.com/src/recurse.thtml b/examples/pixywerk.com/src/recurse.thtml
deleted file mode 100644
index 44cd412..0000000
--- a/examples/pixywerk.com/src/recurse.thtml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-{% for i in get_hier('.', '*') %}
-- {{i}}
-{% endfor %}
-
\ No newline at end of file
diff --git a/pixywerk2/__init__.py b/pixywerk2/__init__.py
index ef7eb44..e69de29 100644
--- a/pixywerk2/__init__.py
+++ b/pixywerk2/__init__.py
@@ -1 +0,0 @@
-__version__ = '0.6.0'
diff --git a/pixywerk2/__main__.py b/pixywerk2/__main__.py
index bb5d037..ff2e88c 100644
--- a/pixywerk2/__main__.py
+++ b/pixywerk2/__main__.py
@@ -11,24 +11,14 @@ import os
import shutil
import sys
import time
+
from typing import Dict, List, cast
-from .metadata import MetaTree
from .processchain import ProcessorChains
from .processors.processors import PassthroughException
+from .metadata import MetaTree
+from .template_tools import date_iso8601, file_list, file_name, file_content, file_metadata, time_iso8601, file_raw
from .pygments import pygments_get_css, pygments_markup_contents_html
-from .template_tools import (
- date_iso8601,
- file_content,
- file_list,
- file_list_hier,
- file_json,
- file_metadata,
- file_name,
- file_raw,
- time_iso8601,
-)
-from .utils import deep_merge_dicts
logger = logging.getLogger()
@@ -37,12 +27,6 @@ def setup_logging(verbose: bool = False) -> None:
pass
-def parse_var(varspec: str) -> List:
- if (not ('=' in varspec)):
- return [varspec, True]
- return list(varspec.split('=', 2))
-
-
def get_args(args: List[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser("Compile a Pixywerk directory into an output directory.")
@@ -53,14 +37,14 @@ def get_args(args: List[str]) -> argparse.Namespace:
"-c", "--clean", help="Remove the target tree before proceeding (by renaming to .bak).", action="store_true"
)
parser.add_argument("-s", "--safe", help="Abort if the target directory already exists.", action="store_true")
- parser.add_argument("-f", "--follow-links", help="Follow symbolic links in the input tree.", action="store_true")
parser.add_argument("-t", "--template", help="The template directory (default: root/templates)", default=None)
parser.add_argument("-d", "--dry-run", help="Perform a dry-run.", action="store_true")
parser.add_argument("-v", "--verbose", help="Output verbosely.", action="store_true")
parser.add_argument("--processors", help="Specify a path to a processor configuration file.", default=None)
- parser.add_argument(
- "-D", "--define", help="Add a variable to the metadata.", nargs="+", action="extend", type=parse_var)
+ # parser.add_argument("--prescript", help="Specify one or more prescripts to run (in order specified) with context of the compile.", default=[], action="append")
+ # parser.add_argument("--postscript", help="Specify one or more postsscripts to run (in order specified) with context of the compile.", default=[], action="append")
result = parser.parse_args(args)
+
# validate arguments
if not os.path.isdir(result.root):
raise FileNotFoundError("can't find root folder {}".format(result.root))
@@ -98,31 +82,24 @@ def main() -> int:
"author": "",
"author_email": "",
}
- if args.define:
- for var in args.define:
- default_metadata[var[0]] = var[1]
meta_tree = MetaTree(args.root, default_metadata)
file_list_cache = cast(Dict, {})
file_cont_cache = cast(Dict, {})
file_name_cache = cast(Dict, {})
file_raw_cache = cast(Dict, {})
- flist = file_list(args.root, file_list_cache)
default_metadata["globals"] = {
- "get_file_list": flist,
- "get_hier": file_list_hier(args.root, flist),
+ "get_file_list": file_list(args.root, file_list_cache),
"get_file_name": file_name(args.root, meta_tree, process_chains, file_name_cache),
"get_file_content": file_content(args.root, meta_tree, process_chains, file_cont_cache),
- "get_json": file_json(args.root),
"get_raw": file_raw(args.root, file_raw_cache),
"get_file_metadata": file_metadata(meta_tree),
"get_time_iso8601": time_iso8601("UTC"),
"get_date_iso8601": date_iso8601("UTC"),
"pygments_get_css": pygments_get_css,
"pygments_markup_contents_html": pygments_markup_contents_html,
- "merge_dicts": deep_merge_dicts,
}
- for root, _, files in os.walk(args.root, followlinks=args.follow_links):
+ for root, _, files in os.walk(args.root):
workroot = os.path.relpath(root, args.root)
if workroot == ".":
workroot = ""
@@ -141,7 +118,7 @@ def main() -> int:
continue
metadata = meta_tree.get_metadata(os.path.join(workroot, f))
chain = process_chains.get_chain_for_filename(os.path.join(root, f), ctx=metadata)
- print("process {} -> {} -> {}".format(os.path.join(root, f), repr(chain), os.path.join(target_dir, chain.output_filename)))
+ print("process {} -> {}".format(os.path.join(root, f), os.path.join(target_dir, chain.output_filename)))
if not args.dry_run:
try:
with open(os.path.join(target_dir, chain.output_filename), "w") as outfile:
diff --git a/pixywerk2/defaults/chains.yaml b/pixywerk2/defaults/chains.yaml
index 459eae0..4683860 100644
--- a/pixywerk2/defaults/chains.yaml
+++ b/pixywerk2/defaults/chains.yaml
@@ -8,14 +8,7 @@ default:
templatable:
extension: null
chain:
- - jinja2
-
-# Any object that needs jinja and to be embedded in a parent template
-tembed:
- extension: null
- chain:
- - jinja2
- - jinja2_page_embed
+ - jinja2
# Markdown, BBCode and RST are first run through the templater, and then
# they are processed into HTML, and finally embedded in a page template.
@@ -69,24 +62,24 @@ template-html:
- jinja2
- jinja2_page_embed
-# # Smart CSS are simply converted to CSS.
-# sass:
-# extension:
-# - sass
-# - scss
-# chain:
-# - process_sass
-# less:
-# extension:
-# - less
-# chain:
-# - process_less
+# Smart CSS are simply converted to CSS.
+sass:
+ extension:
+ - sass
+ - scss
+ chain:
+ - process_sass
+less:
+ extension:
+ - less
+ chain:
+ - process_less
-# stylus:
-# extension:
-# - styl
-# chain:
-# - process_styl
+stylus:
+ extension:
+ - styl
+ chain:
+ - process_styl
# # Images are processed into thumbnails and sized in addition to being retained as their original
# FIXME implement split chain processor, implement processor arguments,
diff --git a/pixywerk2/metadata.py b/pixywerk2/metadata.py
index f92e534..9e45851 100644
--- a/pixywerk2/metadata.py
+++ b/pixywerk2/metadata.py
@@ -5,7 +5,8 @@ import logging
import mimetypes
import os
import uuid
-from typing import Any, Dict, List, Optional, Tuple, Union, cast
+
+from typing import Dict, Optional, Union, List, Tuple, Any, cast
import jstyleson
@@ -93,7 +94,7 @@ class MetaTree:
"""Retrieve the metadata for a given path
The general procedure is to iterate the tree, at each level
- load .meta (JSON formatted dictionary) for that level, and
+m load .meta (JSON formatted dictionary) for that level, and
then finally load the path.meta, and merge these dictionaries
in descendant order.
diff --git a/pixywerk2/processchain.py b/pixywerk2/processchain.py
index cc1e98c..d5952d0 100644
--- a/pixywerk2/processchain.py
+++ b/pixywerk2/processchain.py
@@ -3,7 +3,8 @@
import os
import os.path
import random
-from typing import Any, Dict, Iterable, List, Optional, Type, cast
+
+from typing import List, Iterable, Optional, Any, Dict, Type, cast
import yaml
@@ -90,9 +91,6 @@ class ProcessorChain:
fname = processor.filename(fname, self._ctx)
return fname
- def __repr__(self) -> str:
- return "[" + ",".join([x.__class__.__name__ for x in self._processors]) + "]"
-
class ProcessorChains:
"""Load a configuration for processor chains, and provide ability to process the chains given a particular input
diff --git a/pixywerk2/processors/jinja2.py b/pixywerk2/processors/jinja2.py
index 52535b6..80b072b 100644
--- a/pixywerk2/processors/jinja2.py
+++ b/pixywerk2/processors/jinja2.py
@@ -1,6 +1,6 @@
"""Define a Jinja2 Processor which applies programmable templating to the input stream."""
-from typing import Dict, Iterable, Optional, cast
+from typing import Iterable, Optional, Dict, cast
from jinja2 import Environment, FileSystemLoader
@@ -22,10 +22,11 @@ class Jinja2(PassThrough):
iterable: The post-processed output stream
"""
ctx = cast(Dict, ctx)
- template_env = Environment(loader=FileSystemLoader(ctx["templates"]), extensions=["jinja2.ext.do"])
+ template_env = Environment(loader=FileSystemLoader(ctx["templates"]), extensions=['jinja2.ext.do'])
template_env.globals.update(ctx["globals"])
template_env.filters.update(ctx["filters"])
tmpl = template_env.from_string("".join([x for x in input_file]))
return tmpl.render(metadata=ctx)
+
processor = Jinja2
diff --git a/pixywerk2/processors/jinja2_page_embed.py b/pixywerk2/processors/jinja2_page_embed.py
index 3be143c..21f6f3a 100644
--- a/pixywerk2/processors/jinja2_page_embed.py
+++ b/pixywerk2/processors/jinja2_page_embed.py
@@ -3,7 +3,8 @@
the target template is rendered)."""
import os
-from typing import Dict, Iterable, Optional, cast
+
+from typing import Iterable, Optional, Dict, cast
from jinja2 import Environment, FileSystemLoader
@@ -24,7 +25,8 @@ class Jinja2PageEmbed(Processor):
str: the new name for the file
"""
- return os.path.splitext(oldname)[0] + "." + self.extension(oldname, ctx)
+
+ return os.path.splitext(oldname)[0] + ".html"
def mime_type(self, oldname: str, ctx: Optional[Dict] = None) -> str:
"""Return the mimetype of the post-processed file.
@@ -37,7 +39,7 @@ class Jinja2PageEmbed(Processor):
str: the new mimetype of the file after processing
"""
- return ctx.get("mime", "text/html")
+ return "text/html"
def process(self, input_file: Iterable, ctx: Optional[Dict] = None) -> Iterable:
"""Return an iterable object of the post-processed file.
@@ -50,7 +52,7 @@ class Jinja2PageEmbed(Processor):
iterable: The post-processed output stream
"""
ctx = cast(Dict, ctx)
- template_env = Environment(loader=FileSystemLoader(ctx["templates"]), extensions=["jinja2.ext.do"])
+ template_env = Environment(loader=FileSystemLoader(ctx["templates"]), extensions=['jinja2.ext.do'])
template_env.globals.update(ctx["globals"])
template_env.filters.update(ctx["filters"])
tmpl = template_env.get_template(ctx["template"])
@@ -68,7 +70,7 @@ class Jinja2PageEmbed(Processor):
str: the new extension of the file after processing
"""
- return ctx.get("extension", "html")
+ return "html"
processor = Jinja2PageEmbed
diff --git a/pixywerk2/processors/passthrough.py b/pixywerk2/processors/passthrough.py
index c3f34ae..cc6511b 100644
--- a/pixywerk2/processors/passthrough.py
+++ b/pixywerk2/processors/passthrough.py
@@ -1,10 +1,10 @@
"""Passthrough progcessor which takes input and returns it."""
import os
-from typing import Dict, Iterable, Optional, cast
+from .processors import Processor, PassthroughException
from ..utils import guess_mime
-from .processors import PassthroughException, Processor
+from typing import Iterable, Optional, Dict, cast
class PassThrough(Processor):
diff --git a/pixywerk2/processors/process_md.py b/pixywerk2/processors/process_md.py
index 30a1b01..0687bc6 100644
--- a/pixywerk2/processors/process_md.py
+++ b/pixywerk2/processors/process_md.py
@@ -2,7 +2,8 @@
import io
import os
-from typing import Dict, Iterable, Optional
+
+from typing import Iterable, Optional, Dict
import markdown
diff --git a/pixywerk2/processors/processors.py b/pixywerk2/processors/processors.py
index f3312e7..0ff970e 100644
--- a/pixywerk2/processors/processors.py
+++ b/pixywerk2/processors/processors.py
@@ -1,5 +1,6 @@
import abc
-from typing import Dict, Iterable, Optional
+
+from typing import Iterable, Optional, Dict
class PassthroughException(Exception):
@@ -64,6 +65,3 @@ class Processor(abc.ABC): # pragma: no cover
Returns:
iterable: The post-processed output stream
"""
-
- def repr(self) -> str:
- return self.__class__.__name__
diff --git a/pixywerk2/pygments.py b/pixywerk2/pygments.py
index 90938f6..c96de3b 100644
--- a/pixywerk2/pygments.py
+++ b/pixywerk2/pygments.py
@@ -4,17 +4,18 @@ from typing import Optional
import pygments
import pygments.formatters
import pygments.lexers
-import pygments.styles
import pygments.util
+import pygments.styles
-def pygments_markup_contents_html(input_text: str, file_type: str, style: Optional[str] = None) -> str:
+
+def pygments_markup_contents_html(input_text: str, file_type: str, style: Optional[str]=None) -> str:
"""Format input string with Pygments and return HTML."""
if style is None:
- style = "default"
+ style = 'default'
style = pygments.styles.get_style_by_name(style)
- formatter = pygments.formatters.get_formatter_by_name("html", style=style)
+ formatter = pygments.formatters.get_formatter_by_name('html', style=style)
try:
lexer = pygments.lexers.get_lexer_for_filename(file_type)
except pygments.util.ClassNotFound:
@@ -25,12 +26,11 @@ def pygments_markup_contents_html(input_text: str, file_type: str, style: Option
return pygments.highlight(input_text, lexer, formatter)
-
-def pygments_get_css(style: Optional[str] = None) -> str:
+def pygments_get_css(style: Optional[str]=None) -> str:
"""Return the CSS styles associated with a particular style definition."""
if style is None:
- style = "default"
+ style = 'default'
style = pygments.styles.get_style_by_name(style)
- formatter = pygments.formatters.get_formatter_by_name("html", style=style)
+ formatter = pygments.formatters.get_formatter_by_name('html', style=style)
return formatter.get_style_defs()
diff --git a/pixywerk2/template_tools.py b/pixywerk2/template_tools.py
index 328145e..28e4249 100644
--- a/pixywerk2/template_tools.py
+++ b/pixywerk2/template_tools.py
@@ -1,51 +1,38 @@
-import copy
import datetime
import glob
import itertools
import os
-from typing import Callable, Dict, Iterable, List, Union, cast, Tuple
-
-import jstyleson
-
import pytz
+from typing import Callable, Dict, List, Iterable, Union, cast
from .metadata import MetaTree
from .processchain import ProcessorChains
-from .utils import deep_merge_dicts
def file_list(root: str, listcache: Dict) -> Callable:
- def get_file_list(
- path_glob: Union[str, List[str], Tuple[str]],
- *,
- sort_order: str = "ctime",
- reverse: bool = False,
- limit: int = 0) -> Iterable:
+ def get_file_list(path_glob: str, *, sort_order: str = "ctime", reverse: bool = False, limit: int = 0) -> Iterable:
stattable = cast(List, [])
- if isinstance(path_glob, str):
- path_glob = [path_glob]
- for pglob in path_glob:
- if pglob in listcache:
- stattable.extend(listcache[pglob])
- else:
- for fil in glob.glob(os.path.join(root, pglob)):
- if os.path.isdir(fil):
- continue
- if fil.endswith(".meta") or fil.endswith("~"):
- continue
- st = os.stat(fil)
- stattable.append(
- {
- "file_path": os.path.relpath(fil, root),
- "file_name": os.path.split(fil)[-1],
- "mtime": st.st_mtime,
- "ctime": st.st_ctime,
- "size": st.st_size,
- "ext": os.path.splitext(fil)[1],
- }
- )
- listcache[pglob] = stattable
- ret = sorted(stattable, key=lambda x: x[sort_order], reverse=reverse)
+ if path_glob in listcache:
+ stattable = listcache[path_glob]
+ else:
+ for fil in glob.glob(os.path.join(root, path_glob)):
+ if os.path.isdir(fil):
+ continue
+ if fil.endswith(".meta") or fil.endswith("~"):
+ continue
+ st = os.stat(fil)
+ stattable.append(
+ {
+ "file_path": os.path.relpath(fil, root),
+ "file_name": os.path.split(fil)[-1],
+ "mtime": st.st_mtime,
+ "ctime": st.st_ctime,
+ "size": st.st_size,
+ "ext": os.path.splitext(fil)[1],
+ }
+ )
+ listcache[path_glob] = stattable
+ ret = sorted(stattable, key=lambda x: x[sort_order], reverse=reverse)
if limit > 0:
return itertools.islice(ret, limit)
return ret
@@ -53,27 +40,6 @@ def file_list(root: str, listcache: Dict) -> Callable:
return get_file_list
-def file_list_hier(root: str, flist: Callable) -> Callable:
- """Return a callable which, given a directory, will walk the directory and return the files within
- it that match the glob passed."""
-
- def get_file_list_hier(path: str, glob: str, *, sort_order: str = "ctime", reverse: bool = False) -> Iterable:
- output = []
-
- for pth in os.walk(os.path.join(root, path)):
- output.extend(
- flist(
- os.path.join(os.path.relpath(os.path.realpath(pth[0]), root), glob),
- sort_order=sort_order,
- reverse=reverse,
- )
- )
-
- return output
-
- return get_file_list_hier
-
-
def file_name(root: str, metatree: MetaTree, processor_chains: ProcessorChains, namecache: Dict) -> Callable:
def get_file_name(file_name: str) -> Dict:
if file_name in namecache:
@@ -85,29 +51,15 @@ def file_name(root: str, metatree: MetaTree, processor_chains: ProcessorChains,
return get_file_name
-
def file_raw(root: str, contcache: Dict) -> Callable:
def get_raw(file_name: str) -> str:
if file_name in contcache:
return contcache[file_name]
- with open(os.path.join(root, file_name), "r", encoding="utf-8") as f:
+ with open(os.path.join(root, file_name), 'r', encoding="utf-8") as f:
return f.read()
return get_raw
-
-def file_json(root: str) -> Callable:
- def get_json(file_name: str, parent: Dict = None) -> Dict:
- outd = {}
- if parent is not None:
- outd = copy.deepcopy(parent)
-
- with open(os.path.join(root, file_name), "r", encoding="utf-8") as f:
- return deep_merge_dicts(outd, jstyleson.load(f))
-
- return get_json
-
-
def file_content(root: str, metatree: MetaTree, processor_chains: ProcessorChains, contcache: Dict) -> Callable:
def get_file_content(file_name: str) -> Iterable:
if file_name in contcache:
@@ -115,7 +67,7 @@ def file_content(root: str, metatree: MetaTree, processor_chains: ProcessorChain
metadata = metatree.get_metadata(file_name)
chain = processor_chains.get_chain_for_filename(os.path.join(root, file_name), ctx=metadata)
contcache[file_name] = chain.output
- return str(chain.output)
+ return unicode(chain.output)
return get_file_content
@@ -135,11 +87,10 @@ def time_iso8601(timezone: str) -> Callable:
return get_time_iso8601
-
def date_iso8601(timezone: str) -> Callable:
tz = pytz.timezone(timezone)
def get_date_iso8601(time_t: Union[int, float]) -> str:
- return datetime.datetime.fromtimestamp(time_t, tz).strftime("%Y-%m-%d")
+ return datetime.datetime.fromtimestamp(time_t, tz).strftime('%Y-%m-%d')
return get_date_iso8601
diff --git a/pixywerk2/utils.py b/pixywerk2/utils.py
index d12c490..962c9e2 100644
--- a/pixywerk2/utils.py
+++ b/pixywerk2/utils.py
@@ -1,11 +1,11 @@
-from typing import Dict, Optional
-import copy
import mimetypes
import os
+from typing import Dict, Optional
+
def merge_dicts(dict_a: Dict, dict_b: Dict) -> Dict:
- """Merge two dictionaries (shallow).
+ """Merge two dictionaries.
Arguments:
dict_a (dict): The dictionary to use as the base.
@@ -20,36 +20,6 @@ def merge_dicts(dict_a: Dict, dict_b: Dict) -> Dict:
return dict_z
-def deep_merge_dicts(dict_a: Dict, dict_b: Dict, _path=None, cpy=False) -> Dict:
- """Merge two dictionaries (deep).
- https://stackoverflow.com/questions/7204805/how-to-merge-dictionaries-of-dictionaries/7205107#7205107
-
- Arguments:
- dict_a (dict): The dictionary to use as the base.
- dict_b (dict): The dictionary to update the values with.
- _path (list): internal use.
-
- Returns:
- dict: A new merged dictionary.
-
- """
- if cpy:
- dict_a = copy.deepcopy(dict_a)
- if _path is None:
- _path = []
- for key in dict_b:
- if key in dict_a:
- if isinstance(dict_a[key], dict) and isinstance(dict_b[key], dict):
- deep_merge_dicts(dict_a[key], dict_b[key], _path + [str(key)])
- elif dict_a[key] == dict_b[key]:
- pass # same leaf value
- else:
- dict_a[key] = copy.deepcopy(dict_b[key])
- else:
- dict_a[key] = dict_b[key]
- return dict_a
-
-
def guess_mime(path: str) -> Optional[str]:
"""Guess the mime type for a given path.
diff --git a/pyproject.toml b/pyproject.toml
deleted file mode 100644
index e69de29..0000000
diff --git a/setup.py b/setup.py
index 6097c30..9e41013 100644
--- a/setup.py
+++ b/setup.py
@@ -1,8 +1,6 @@
"""Package configuration."""
from setuptools import find_packages, setup
-from pixywerk2 import __version__
-
LONG_DESCRIPTION = """Pixywerk 2 is a filesystem based static site generator."""
INSTALL_REQUIRES = ["yaml-1.3", "markdown", "jstyleson", "jinja2", "pygments"]
@@ -58,5 +56,4 @@ setup(
use_scm_version=True,
url="https://git.antpanethon.com/cas/pixywerk2",
zip_safe=False,
- version=__version__,
)
diff --git a/tox.ini b/tox.ini
index d6a9161..53ca9b0 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist=py{36,37,38,39}-{code-quality, unit} #, py37-sphinx
+envlist=py{36,37}-{code-quality, unit} #, py37-sphinx
skipsdist = true
[testenv]
@@ -17,8 +17,6 @@ commands =
basepython =
py36: python3.6
py37: python3.7
- py38: python3.8
- py39: python3.9
[flake8]
max-line-length = 120