From 36c5ee34f7bbc903fa97451dfd42b2b915ea83ab Mon Sep 17 00:00:00 2001 From: David Luevano Alvarado Date: Tue, 29 Aug 2023 00:27:19 -0600 Subject: feat: add essential templates, refactor --- src/pyssg/builder.py | 6 +- src/pyssg/md/__init__.py | 0 src/pyssg/md/page.py | 81 +++++++++++++++++++++++++ src/pyssg/md/parser.py | 135 +++++++++++++++++++++++++++++++++++++++++ src/pyssg/md_parser.py | 137 ------------------------------------------ src/pyssg/page.py | 83 ------------------------- src/pyssg/plt/base.html | 10 +++ src/pyssg/plt/default.yaml | 1 + src/pyssg/plt/index.html | 39 +++--------- src/pyssg/plt/page.html | 38 +++++------- src/pyssg/plt/page_index.html | 15 +++++ src/pyssg/plt/page_list.html | 15 +++++ src/pyssg/plt/tag.html | 26 -------- src/pyssg/plt/tag_index.html | 11 ++++ src/pyssg/plt/tag_list.html | 7 +++ src/pyssg/pyssg.py | 12 +++- 16 files changed, 310 insertions(+), 306 deletions(-) create mode 100644 src/pyssg/md/__init__.py create mode 100644 src/pyssg/md/page.py create mode 100644 src/pyssg/md/parser.py delete mode 100644 src/pyssg/md_parser.py delete mode 100644 src/pyssg/page.py create mode 100644 src/pyssg/plt/base.html create mode 100644 src/pyssg/plt/page_index.html create mode 100644 src/pyssg/plt/page_list.html delete mode 100644 src/pyssg/plt/tag.html create mode 100644 src/pyssg/plt/tag_index.html create mode 100644 src/pyssg/plt/tag_list.html diff --git a/src/pyssg/builder.py b/src/pyssg/builder.py index 1eec345..0cb1728 100644 --- a/src/pyssg/builder.py +++ b/src/pyssg/builder.py @@ -1,15 +1,14 @@ import os import sys from copy import deepcopy -from operator import itemgetter from logging import Logger, getLogger from jinja2 import Environment, Template, FileSystemLoader as FSLoader from pyssg.utils import get_file_list, get_dir_structure, create_dir, copy_file from pyssg.db.database import Database -from pyssg.md_parser import MDParser -from pyssg.page import Page +from pyssg.md.parser import MDParser +from pyssg.md.page import Page log: Logger = getLogger(__name__) @@ -28,7 +27,6 @@ class Builder: log.error('dir path "%s" cannot be absolute', self.dir_cfg['dir']) sys.exit(1) - if self.dir_cfg['dir'].strip() == '/': log.debug('dir path is "/", copying src/dst directly') self.dir_cfg['src'] = self.config['path']['src'] diff --git a/src/pyssg/md/__init__.py b/src/pyssg/md/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/pyssg/md/page.py b/src/pyssg/md/page.py new file mode 100644 index 0000000..6a393d1 --- /dev/null +++ b/src/pyssg/md/page.py @@ -0,0 +1,81 @@ +from datetime import datetime, timezone +from logging import Logger, getLogger +from typing import Any + +log: Logger = getLogger(__name__) + + +class Page: + def __init__(self, name: str, + cts: float, + mts: float, + html: str, + toc: str, + toc_tokens: list[str], + meta: dict[str, Any], + config: dict[str, Any]) -> None: + # initial data + self.name: str = name.replace(".md", ".html") + self.cts: float = cts + self.mts: float = mts + self.content: str = html + self.toc: str = toc + self.toc_tokens: list[str] = toc_tokens + self.meta: dict[str, Any] = meta + self.config: dict[str, Any] = config + + # data from self.meta + self.title: str + self.author: list[str] + self.summary: str + self.lang: str + self.tags: tuple[str] + + # constructed + self.cdate_rss: str + self.cdate_sitemap: str + self.mdate_rss: str | None = None + self.mdate_sitemap: str | None = None + + self.next: Page | None = None + self.previous: Page | None = None + + def __lt__(self, other): + return self.cts < other.cts + + def __get_meta(self, var: str, + or_else: str | list[str] = ['']) -> str | list[str] | Any: + if var in self.meta: + return self.meta[var] + else: + log.debug('getting metadata "%s" failed, using optional value "%s"', + var, or_else) + return or_else + + def date(self, ts: float, format: str) -> str: + dt: datetime = datetime.fromtimestamp(ts, tz=timezone.utc) + + if format in self.config['fmt']: + return dt.strftime(self.config['fmt'][format]) + else: + log.warning('format "%s" not found in config, returning ' + 'empty string', format) + return '' + + # parses meta from self.meta + def parse_metadata(self): + self.title = str(self.__get_meta('title')[0]) + self.author = list(self.__get_meta('author')) + self.summary = str(self.__get_meta('summary')[0]) + self.lang = str(self.__get_meta('lang', ['en'])[0]) + + # probably add a way to sanitize tags + self.tags = tuple(set(sorted(self.__get_meta('tags', [])))) + log.debug('parsed tags %s', self.tags) + + self.cdate_rss = self.date(self.cts, 'rss_date') + self.cdate_sitemap = self.date(self.cts, 'sitemap_date') + if self.mts != 0.0: + self.mdate_rss = self.date(self.mts, 'rss_date') + self.mdate_sitemap = self.date(self.mts, 'sitemap_date') + diff --git a/src/pyssg/md/parser.py b/src/pyssg/md/parser.py new file mode 100644 index 0000000..34a8922 --- /dev/null +++ b/src/pyssg/md/parser.py @@ -0,0 +1,135 @@ +import os +from logging import Logger, getLogger +from typing import Any + +from markdown import Markdown +from yafg import YafgExtension +from pymdvar import VariableExtension +from markdown_checklist.extension import ChecklistExtension +from markdown.extensions.toc import TocExtension + +from pyssg.db.database import Database +from pyssg.md.page import Page +from pyssg.utils import get_file_stats + +log: Logger = getLogger(__name__) + + +# TODO: add configuration testing for extensions config (pymdvar for ex) +def get_md_obj(variables: dict[str, str], + enable_env: bool) -> Markdown: + exts: list = ['extra', + 'meta', + 'sane_lists', + 'smarty', + 'wikilinks', + TocExtension(permalink=True, + baselevel=2), + VariableExtension(variables=variables, + enable_env=enable_env), + # stripTitle generates an error when True, + # if there is no title attr + YafgExtension(stripTitle=False, + figureClass='', + figcaptionClass='', + figureNumbering=False, + figureNumberClass='number', + figureNumberText='Figure'), + ChecklistExtension(), + 'pymdownx.mark', + 'pymdownx.caret', + 'pymdownx.tilde'] + log.debug('list of md extensions: (%s)', + ', '.join([e if isinstance(e, str) else type(e).__name__ + for e in exts])) + # for some reason, the definition for output_format doesn't include html5 + # even though it is listed in the documentation, ignoring + return Markdown(extensions=exts, output_format='html5') # type: ignore + + +# page and file is basically a synonym +class MDParser: + def __init__(self, files: list[str], + config: dict, + dir_config: dict, + db: Database): + log.debug('initializing the md parser with %d files', len(files)) + self.files: list[str] = files + self.config: dict = config + self.dir_config: dict = dir_config + self.db: Database = db + # TODO: actually add extensions support, for now only pymdvar is configured + self.pymdvar_vars: dict[str, str] = dict() + self.pymdvar_enable_env: bool = False + if 'exts' in config and 'pymdvar' in config['exts']: + pymdvar: dict[str, Any] = config['exts']['pymdvar'] + if 'variables' in pymdvar and type(pymdvar['variables']) == dict: + self.pymdvar_vars = pymdvar['variables'] + if 'enable_env' in pymdvar and type(pymdvar['enable_env']) == bool: + self.pymdvar_enable_env = pymdvar['enable_env'] + log.debug('pymdvar_variables: %s', self.pymdvar_vars) + log.debug('pymdvar_enable_env: %s', self.pymdvar_enable_env) + + self.md: Markdown = get_md_obj(self.pymdvar_vars, self.pymdvar_enable_env) + + self.all_files: list[Page] = [] + self.all_tags: list[str] = [] + + def parse_files(self) -> None: + for i, f in enumerate(self.files): + log.debug('parsing file "%s"', f) + path: str = os.path.join(self.dir_config['src'], f) + content: str = self.md.reset().convert(open(path).read()) + fstats = get_file_stats(path) + chksm: str = fstats[0] + time: float = fstats[1] + + entry: tuple + # old entry + oentry: tuple | None = self.db.select(f) + if not oentry: + entry = self.db.insert(f, time, chksm) + else: + oe_chksm: str = oentry[3] + if chksm != oe_chksm: + entry = self.db.update(f, time, chksm) + else: + entry = oentry + + # ignoring md.Meta type as it is not yet defined + # (because it is from an extension) + page: Page = Page(f, + entry[1], + entry[2], + content, + self.md.toc, # type: ignore + self.md.toc_tokens, # type: ignore + self.md.Meta, # type: ignore + self.config) + page.parse_metadata() + self.all_files.append(page) + + if self.dir_config['tags']: + if entry[4] is not None: + if set(page.tags) != set(entry[4]): + self.db.update_tags(f, page.tags) + + for t in page.tags: + if t not in self.all_tags: + self.all_tags.append(t) + log.debug('added tag "%s" to all tags', t) + + self.all_files.sort(reverse=True) + self.all_tags.sort() + + pages_amount: int = len(self.all_files) + # note that prev and next are switched because of the + # reverse ordering of all_pages + for i, p in enumerate(self.all_files): + if i != 0: + next_page: Page = self.all_files[i - 1] + p.next = next_page + + if i != pages_amount - 1: + prev_page: Page = self.all_files[i + 1] + p.previous = prev_page diff --git a/src/pyssg/md_parser.py b/src/pyssg/md_parser.py deleted file mode 100644 index 0c0dd5b..0000000 --- a/src/pyssg/md_parser.py +++ /dev/null @@ -1,137 +0,0 @@ -import os -from operator import itemgetter -from logging import Logger, getLogger -import sys -from typing import Any - -from markdown import Markdown -from yafg import YafgExtension -from pymdvar import VariableExtension -from markdown_checklist.extension import ChecklistExtension -from markdown.extensions.toc import TocExtension - -from pyssg.db.database import Database -from pyssg.page import Page -from pyssg.utils import get_file_stats - -log: Logger = getLogger(__name__) - - -# TODO: add configuration testing for extensions config (pymdvar for ex) -def get_md_obj(variables: dict[str, str], - enable_env: bool) -> Markdown: - exts: list = ['extra', - 'meta', - 'sane_lists', - 'smarty', - 'wikilinks', - TocExtension(permalink=True, - baselevel=2), - VariableExtension(variables=variables, - enable_env=enable_env), - # stripTitle generates an error when True, - # if there is no title attr - YafgExtension(stripTitle=False, - figureClass='', - figcaptionClass='', - figureNumbering=False, - figureNumberClass='number', - figureNumberText='Figure'), - ChecklistExtension(), - 'pymdownx.mark', - 'pymdownx.caret', - 'pymdownx.tilde'] - log.debug('list of md extensions: (%s)', - ', '.join([e if isinstance(e, str) else type(e).__name__ - for e in exts])) - # for some reason, the definition for output_format doesn't include html5 - # even though it is listed in the documentation, ignoring - return Markdown(extensions=exts, output_format='html5') # type: ignore - - -# page and file is basically a synonym -class MDParser: - def __init__(self, files: list[str], - config: dict, - dir_config: dict, - db: Database): - log.debug('initializing the md parser with %d files', len(files)) - self.files: list[str] = files - self.config: dict = config - self.dir_config: dict = dir_config - self.db: Database = db - # TODO: actually add extensions support, for now only pymdvar is configured - self.pymdvar_vars: dict[str, str] = dict() - self.pymdvar_enable_env: bool = False - if 'exts' in config and 'pymdvar' in config['exts']: - pymdvar: dict[str, Any] = config['exts']['pymdvar'] - if 'variables' in pymdvar and type(pymdvar['variables']) == dict: - self.pymdvar_vars = pymdvar['variables'] - if 'enable_env' in pymdvar and type(pymdvar['enable_env']) == bool: - self.pymdvar_enable_env = pymdvar['enable_env'] - log.debug('pymdvar_variables: %s', self.pymdvar_vars) - log.debug('pymdvar_enable_env: %s', self.pymdvar_enable_env) - - self.md: Markdown = get_md_obj(self.pymdvar_vars, self.pymdvar_enable_env) - - self.all_files: list[Page] = [] - self.all_tags: list[str] = [] - - def parse_files(self) -> None: - for i, f in enumerate(self.files): - log.debug('parsing file "%s"', f) - path: str = os.path.join(self.dir_config['src'], f) - content: str = self.md.reset().convert(open(path).read()) - fstats = get_file_stats(path) - chksm: str = fstats[0] - time: float = fstats[1] - - entry: tuple - # old entry - oentry: tuple | None = self.db.select(f) - if not oentry: - entry = self.db.insert(f, time, chksm) - else: - oe_chksm: str = oentry[3] - if chksm != oe_chksm: - entry = self.db.update(f, time, chksm) - else: - entry = oentry - - # ignoring md.Meta type as it is not yet defined - # (because it is from an extension) - page: Page = Page(f, - entry[1], - entry[2], - content, - self.md.toc, # type: ignore - self.md.toc_tokens, # type: ignore - self.md.Meta, # type: ignore - self.config) - page.parse_metadata() - self.all_files.append(page) - - if self.dir_config['tags']: - if entry[4] is not None: - if set(page.tags) != set(entry[4]): - self.db.update_tags(f, page.tags) - - for t in page.tags: - if t not in self.all_tags: - self.all_tags.append(t) - log.debug('added tag "%s" to all tags', t) - - self.all_files.sort(reverse=True) - self.all_tags.sort() - - pages_amount: int = len(self.all_files) - # note that prev and next are switched because of the - # reverse ordering of all_pages - for i, p in enumerate(self.all_files): - if i != 0: - next_page: Page = self.all_files[i - 1] - p.next = next_page - - if i != pages_amount - 1: - prev_page: Page = self.all_files[i + 1] - p.previous = prev_page diff --git a/src/pyssg/page.py b/src/pyssg/page.py deleted file mode 100644 index ae88a67..0000000 --- a/src/pyssg/page.py +++ /dev/null @@ -1,83 +0,0 @@ -from datetime import datetime, timezone -from logging import Logger, getLogger -from typing import Any - -log: Logger = getLogger(__name__) - - -class Page: - def __init__(self, name: str, - cts: float, - mts: float, - html: str, - toc: str, - toc_tokens: list[str], - meta: dict[str, Any], - config: dict[str, Any]) -> None: - # initial data - self.name: str = name.replace(".md", ".html") - self.cts: float = cts - self.mts: float = mts - self.content: str = html - self.toc: str = toc - self.toc_tokens: list[str] = toc_tokens - self.meta: dict[str, Any] = meta - self.config: dict[str, Any] = config - - # data from self.meta - self.title: str - self.author: list[str] - self.summary: str - self.lang: str - self.tags: tuple[str] - - # constructed - self.cdate_rss: str - self.cdate_sitemap: str - self.mdate_rss: str | None = None - self.mdate_sitemap: str | None = None - - self.next: Page | None = None - self.previous: Page | None = None - - def __lt__(self, other): - return self.cts < other.cts - - def __get_meta(self, var: str, - or_else: str | list[str] = ['']) -> str | list[str] | Any: - if var in self.meta: - return self.meta[var] - else: - log.debug('getting metadata "%s" failed, using optional value "%s"', - var, or_else) - return or_else - - # these date/cdate/mdate might be a bit overcomplicated - - def date(self, ts: float, format: str) -> str: - dt: datetime = datetime.fromtimestamp(ts, tz=timezone.utc) - - if format in self.config['fmt']: - return dt.strftime(self.config['fmt'][format]) - else: - log.warning('format "%s" not found in config, returning ' - 'empty string', format) - return '' - - # parses meta from self.meta - def parse_metadata(self): - self.title = str(self.__get_meta('title')[0]) - self.author = list(self.__get_meta('author')) - self.summary = str(self.__get_meta('summary')[0]) - self.lang = str(self.__get_meta('lang', ['en'])[0]) - - # probably add a way to sanitize tags - self.tags = tuple(set(sorted(self.__get_meta('tags', [])))) - log.debug('parsed tags %s', self.tags) - - self.cdate_rss = self.date(self.cts, 'rss_date') - self.cdate_sitemap = self.date(self.cts, 'sitemap_date') - if self.mts != 0.0: - self.mdate_rss = self.date(self.mts, 'rss_date') - self.mdate_sitemap = self.date(self.mts, 'sitemap_date') - diff --git a/src/pyssg/plt/base.html b/src/pyssg/plt/base.html new file mode 100644 index 0000000..5207d0f --- /dev/null +++ b/src/pyssg/plt/base.html @@ -0,0 +1,10 @@ + + + + + {%block head_title%}{%endblock head_title%} + + + {%block body_content%}{%endblock body_content%} + + diff --git a/src/pyssg/plt/default.yaml b/src/pyssg/plt/default.yaml index b458b75..cca0fbc 100644 --- a/src/pyssg/plt/default.yaml +++ b/src/pyssg/plt/default.yaml @@ -21,6 +21,7 @@ info: dir: "/" plt: "page.html" tags: False +tags_prefix: '' index: False rss: False sitemap: False diff --git a/src/pyssg/plt/index.html b/src/pyssg/plt/index.html index 23d890a..4409ba4 100644 --- a/src/pyssg/plt/index.html +++ b/src/pyssg/plt/index.html @@ -1,32 +1,9 @@ - - - - - - Index -- {{config['title']}} - - -

Index -- {{config['title']}}

-

Some text here.

- -

Tags: - {%for t in all_tags%} - {{t[0]}}{{", " if not loop.last else ""}} - {%endfor%} -

+{%extends "base.html"%} +{%block head_title%} + Index -- {{config['title']}} +{%endblock head_title%} -

Articles

- - - +{%block body_content%} +

Index -- {{config['title']}}

+ {{page.content}} +{%endblock body_content%} diff --git a/src/pyssg/plt/page.html b/src/pyssg/plt/page.html index b61ad03..395b0f0 100644 --- a/src/pyssg/plt/page.html +++ b/src/pyssg/plt/page.html @@ -1,25 +1,19 @@ - - - - - - {{page.title}} -- {{config['title']}} - - -

{{page.title}}

-

By {{page.author}}

-

Created: {{page.date(page.cts, 'date')}}

- {%if page.mtimestamp != 0.0%} -

Modified: {{page.date(page.mts, 'date')}}

- {%endif%} +{%extends "base.html"%} +{%block head_title%} + {{page.title}} -- {{config['title']}} +{%endblock head_title%} - {{page.content}} +{%block body_content%} +

{{page.title}} -- {{config['title']}}

-

Tags: - {%for t in page.tags%} - {{t[0]}}{{", " if not loop.last else ""}} - {%endfor%} -

- - +

By {{page.author}}

+

Created: {{page.date(page.cts, 'date')}}

+ {%if page.mtimestamp != 0.0%} +

Modified: {{page.date(page.mts, 'date')}}

+ {%endif%} + {{page.content}} + + {%import "tag_list.html" as tag_list%} + {{tag_list.print(page.tags)}} +{%endblock body_content%} diff --git a/src/pyssg/plt/page_index.html b/src/pyssg/plt/page_index.html new file mode 100644 index 0000000..70639b2 --- /dev/null +++ b/src/pyssg/plt/page_index.html @@ -0,0 +1,15 @@ +{%extends "base.html"%} +{%block head_title%} + Index -- {{config['title']}} +{%endblock head_title%} + +{%block body_content%} +

Index -- {{config['title']}}

+ {{page.content}} + + {%import "tag_list.html" as tag_list%} + {{tag_list.print(all_tags)}} + + {%import "page_list.html" as page_list%} + {{page_list.print("Articles", all_pages)}} +{%endblock body_content%} diff --git a/src/pyssg/plt/page_list.html b/src/pyssg/plt/page_list.html new file mode 100644 index 0000000..93faf43 --- /dev/null +++ b/src/pyssg/plt/page_list.html @@ -0,0 +1,15 @@ +{%macro print(name, pages)%} +

{{name}}

+ +{%endmacro%} diff --git a/src/pyssg/plt/tag.html b/src/pyssg/plt/tag.html deleted file mode 100644 index bf15c0d..0000000 --- a/src/pyssg/plt/tag.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - Posts filtered by {{tag[0]}} -- {{config['title']}} - - -

Posts filtered by {{tag[0]}}

-

Some text here.

- -

Articles

- - - diff --git a/src/pyssg/plt/tag_index.html b/src/pyssg/plt/tag_index.html new file mode 100644 index 0000000..0d80265 --- /dev/null +++ b/src/pyssg/plt/tag_index.html @@ -0,0 +1,11 @@ +{%extends "base.html"%} +{%block head_title%} + Posts filtered by {{tag}} -- {{config['title']}} +{%endblock head_title%} + +{%block body_content%} +

Posts filtered by {{tag}}

+ + {%import "page_list.html" as page_list%} + {{page_list.print("Articles", tag_pages)}} +{%endblock body_content%} diff --git a/src/pyssg/plt/tag_list.html b/src/pyssg/plt/tag_list.html new file mode 100644 index 0000000..ebc864e --- /dev/null +++ b/src/pyssg/plt/tag_list.html @@ -0,0 +1,7 @@ +{%macro print(tags)%} +

Tags: + {%for t in tags-%} + {{t}}{{", " if not loop.last else ""}} + {%-endfor%} +

+{%endmacro%} diff --git a/src/pyssg/pyssg.py b/src/pyssg/pyssg.py index fd136d9..26c94d6 100644 --- a/src/pyssg/pyssg.py +++ b/src/pyssg/pyssg.py @@ -54,17 +54,23 @@ def main() -> None: log.debug('changed logging level to DEBUG') if args['init']: - idir: str = os.path.normpath(get_expanded_path(str(args['init']))) log.info('initializing directory structure and copying templates') + idir: str = os.path.normpath(get_expanded_path(str(args['init']))) + create_dir(idir) with rpath('pyssg.plt', 'default.yaml') as p: copy_file(str(p), os.path.join(idir, 'config.yaml')) + create_dir(os.path.join(idir, 'src')) create_dir(os.path.join(idir, 'dst')) create_dir(os.path.join(idir, 'plt')) - files: list[str] = ['index.html', + files: list[str] = ['base.html', + 'index.html', + 'page_index.html', 'page.html', - 'tag.html', + 'page_list.html', + 'tag_index.html', + 'tag_list.html', 'rss.xml', 'sitemap.xml', 'entry.md'] -- cgit v1.2.3-54-g00ecf