{metadata.title}
+{article.metadata.title}
By Jim Shepich III -First published: -
Last modified: +
First published: +
Last modified: - {content} + {article.content}
-
{blog_tags}
+Tags: {blog_tags}{templates.components.rss_icon}
diff --git a/config.yaml b/config.yaml index 01715d9..6a9c028 100644 --- a/config.yaml +++ b/config.yaml @@ -24,7 +24,8 @@ sites: dogma_jimfinium: title: Dogma Jimfinium - base_url: http://localhost:8080/dogma-jimfinium + description: May it bolster the skills of all who read it. + base_url: http://localhost:8000/dogma-jimfinium git_repo: ssh://gitea/jim/dogma-jimfinium.git build_cache: ./build/dogma-jimfinium web_root: ./dist/dogma-jimfinium @@ -32,4 +33,6 @@ sites: - assets articles: - '*.md' + addons: + - rss \ No newline at end of file diff --git a/jimsite/__init__.py b/jimsite/__init__.py index b07106c..2ed9366 100644 --- a/jimsite/__init__.py +++ b/jimsite/__init__.py @@ -9,6 +9,10 @@ import pydantic from typing import Optional from datetime import datetime, date from dotmap import DotMap +import logging + +logger = logging.getLogger(__name__) +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') from .common import filepath_or_string, GlobalVars, SiteConfig, dotmap_access from .templating import format_html_template, map_templates, TemplateSelections @@ -25,45 +29,62 @@ from .blog import build_blog_archive, build_rss_feed def build_site(site: SiteConfig, templates: DotMap): + logger.info(f'Building site "{site.title}".') + # Do not build a site marked as unpublished. if not site.published: + logger.info(f'"{site.title}" is not published. Skipping.') return None # Initialize the build cache and web root, in case they do not exist. + logger.debug(f'Creating build cache: {site.build_cache}') os.makedirs(site.build_cache, exist_ok = True) + logger.debug(f'Creating web root: {site.web_root}') os.makedirs(site.web_root, exist_ok = True) # If the site is built from a git repo, pull that repo into the build cache. if site.git_repo: + logger.info(f'Cloning/pulling git repo from {site.git_repo}') pull_git_repo(site.git_repo, site.build_cache) # Copy the sites assets into the web root. + logger.info(f'Copying static assets.') copy_assets(site) # Load the site's articles into an index. + logger.info('Building index of articles.') index = build_index(site) # Determine which templates are to be used for explicit applications, e.g. # the tag component. + logger.info('Loading selected templates.') template_selections = TemplateSelections(site, templates) # Generate HTML pages for the articles. + logger.info('Building articles.') build_articles(site, index, templates, template_selections) if len(site.articles or []): + logger.info('Building archive of articles.') build_blog_archive(site, index, template_selections, templates = templates) + if 'rss' in (site.addons or []): + logger.info('Building RSS feed.') + build_rss_feed(site, index) + else: + logger.debug('Addon "rss" not elected.') def main(): + logger.info('Loading config.') with open('/home/jim/projects/shepich.com/config.yaml', 'r') as config_file: config = yaml.safe_load(config_file.read()) + logger.info('Loading global templates.') templates = map_templates(config['templates_folder']) for site in config['sites'].values(): build_site(SiteConfig(**site), templates) - if __name__ == '__main__': main() diff --git a/jimsite/articles.py b/jimsite/articles.py index 9ee38d3..e6074a1 100644 --- a/jimsite/articles.py +++ b/jimsite/articles.py @@ -18,7 +18,13 @@ class ArticleMetadata(pydantic.BaseModel): author: Optional[str] = None lastmod: Optional[date] = None thumbnail: Optional[str] = None + description: Optional[str] = None +class Article(pydantic.BaseModel): + path: str + content: str + metadata: Optional[ArticleMetadata] = None + def load_markdown(md: str) -> tuple[ArticleMetadata|None, str]: '''Loads a Markdown file into a (metadata: ArticleMetadata, content: str) pair.''' @@ -42,9 +48,9 @@ def load_markdown(md: str) -> tuple[ArticleMetadata|None, str]: return ArticleMetadata(**metadata), content -def build_index(site: SiteConfig) -> dict: - '''Loads the sites articles into an index mapping the filename stem - to a (metadata: dict, content: str) tuple.''' +def build_index(site: SiteConfig) -> dict[str, Article]: + '''Loads the sites articles into an index mapping the filename + to an Article object.''' index = {} @@ -53,19 +59,31 @@ def build_index(site: SiteConfig) -> dict: 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("/")}') + glob.glob(f'{site.build_cache}/{a.removeprefix('./').lstrip("/")}') ) - for article in expanded_article_list: - metadata, content = load_markdown(article) + for article_full_path in expanded_article_list: + metadata, content = load_markdown(article_full_path) # Skip unpublished articles. if not metadata.published: continue - article_filestem = os.path.splitext(os.path.basename(article))[0] - index[article_filestem] = (metadata, content) + # Construct the article's path for the index by discarding the build cache + # and replacing .md with .html. + # article_path = article_full_path\ + # .removeprefix(site.build_cache)\ + # .lstrip('/')\ + # .replace('.md','.html') + # TODO: add tolerance for a hierarchical article directory structure. + article_path = os.path.basename(article_full_path).replace('.md', '.html') + + index[article_path] = Article( + path = article_path, + content = content, + metadata = metadata + ) return index @@ -79,34 +97,33 @@ def format_article_tags(tags: list[str], tag_template, **kwargs) -> list[str]: def build_articles( site: SiteConfig, - index: dict[str, tuple[ArticleMetadata, str]], + index: dict[str, Article], templates: DotMap, template_selections: TemplateSelections ): '''Generates HTML files for all of a given site's Markdown articles by interpolating the contents and metadata into the HTML templates.''' - for filestem, (metadata, content) in index.items(): - article = format_html_template( + for article in index.values(): + article_html = format_html_template( template_selections['article'], - content = content, + article = article, blog_tags = ' '.join(format_article_tags( - metadata.tags, template_selections['tag'], site = site + article.metadata.tags, template_selections['tag'], site = site )), - metadata = metadata, templates = templates, site = site ) - page = format_html_template( + page_html = format_html_template( template_selections['page'], - content = article, + content = article_html, templates = templates, site = site ) - with open(f'{site.web_root.rstrip('/')}/{filestem}.html', 'w') as f: - f.write(page) + with open(f'{site.web_root.rstrip('/')}/{article.path}', 'w') as f: + f.write(page_html) \ No newline at end of file diff --git a/jimsite/blog.py b/jimsite/blog.py index bbefe98..2e4403e 100644 --- a/jimsite/blog.py +++ b/jimsite/blog.py @@ -1,15 +1,15 @@ import rfeed -import datetime +from datetime import datetime from .common import SiteConfig -from .articles import ArticleMetadata, format_article_tags +from .articles import ArticleMetadata, Article, format_article_tags from .templating import format_html_template, TemplateSelections def build_blog_archive( site: SiteConfig, - index: dict[str, tuple[str, str]], + index: dict[str, Article], template_selections: TemplateSelections, **kwargs ) -> str: @@ -22,14 +22,14 @@ def build_blog_archive( # Add each article as a list item to an unordered list. archive_html_list = '
{blog_tags}
+Tags: {blog_tags}{templates.components.rss_icon}
{blog_tags}
+