~eightytwo/idspispopd

c7d896707d6316d55f9406a0926bcb82c8779fa5 — eightytwo 4 years ago f204aa7
Add support for images in blog posts
1 files changed, 57 insertions(+), 39 deletions(-)

M build.py
M build.py => build.py +57 -39
@@ 1,9 1,6 @@
import os
from collections import defaultdict
from dataclasses import dataclass
from datetime import datetime
from os.path import isfile
from os.path import join
from pathlib import Path
from shutil import copytree
from typing import Dict


@@ 15,6 12,7 @@ import markdown
from jinja2 import Environment
from jinja2 import FileSystemLoader
from markdown.extensions.codehilite import CodeHiliteExtension
from markdown.extensions.footnotes import FootnoteExtension
from markdown.extensions.toc import TocExtension
from slugify import slugify



@@ 26,7 24,7 @@ class Page:
    listing: bool = False
    create_detail_pages: bool = False
    detail_page_template: Optional[str] = None
    source_dir: Optional[str] = None
    source_dir: Optional[Path] = None


BUILD_PATH = 'build'


@@ 42,40 40,41 @@ PAGES = [
        listing=True,
        create_detail_pages=True,
        detail_page_template='post',
        source_dir='content/blog',
        source_dir=Path('content/blog'),
    ),
    Page(
        category='projects',
        template='projects',
        listing=True,
        source_dir='content/projects',
        source_dir=Path('content/projects'),
    ),
]


def write_file(name: str, content: str):
def write_file(name: Path, content: str, suffix: str = ".html"):
    """Write an HTML file to disk.

    :param name: The name of the file, excluding extension.
    :param name: The path and name of the file, excluding extension.
    :param content: The content of the file.
    :param suffix: The extension of the file.
    """
    filepath = Path(f'{BUILD_PATH}/{name}.html')
    with open(filepath, "w+", encoding="utf-8", errors="xmlcharrefreplace") as f:
        f.write(content)
    file = BUILD_PATH / name.with_suffix(suffix)
    file.write_text(content, encoding="utf-8", errors="xmlcharrefreplace")


def parse_markdown(filepath: str) -> Dict:
def parse_markdown(filepath: Path) -> Dict:
    """Parse a Markdown file.

    :param filepath: The path and filename of the Markdown file to be parsed.
    :return: A dictionary containing the metadata and content of the Markdown file.
    """
    data = Path(filepath).read_text(encoding='utf-8')
    data = filepath.read_text(encoding='utf-8')
    md = markdown.Markdown(
        extensions=[
            'meta',
            TocExtension(title='Contents', permalink=True, toc_depth=2),
            CodeHiliteExtension(guess_lang=False),
            FootnoteExtension(),
        ],
        output_format='html5',
    )


@@ 86,7 85,7 @@ def parse_markdown(filepath: str) -> Dict:
    }


def get_page_vars(filepath: str) -> Dict:
def get_page_vars(filepath: Path) -> Dict:
    """Extract variables from a Markdown file, such as the metadata fields and the
    content of the file.



@@ 97,7 96,7 @@ def get_page_vars(filepath: str) -> Dict:
    template_vars = {'content': data['html']}

    for field, value in data['metadata'].items():
        if field == 'date_published':
        if field.startswith('date_'):
            template_vars[field] = datetime.strptime(value[0], '%d %b %Y')
        else:
            template_vars[field] = value[0] if len(value) == 1 else value


@@ 120,53 119,72 @@ def build_tag_page(page_category: str, tag: str, pages: List[Dict]):

    template = env.get_template(f'{page_category}.html')
    write_file(
        f'{page_category}/tag/{tag}',
        Path(page_category, "tag", tag),
        template.render(category=page_category, tag=tag, items=items),
    )


def build_detail_pages(parent_page, detail_pages: List[Dict]):
    """Create an HTML page for each item that appears in a parent list page.

    :param parent_page: The page that displays a list of items.
    :param detail_pages: The data for the list items that will have pages
     created for them.
    """
    for detail_page in detail_pages:
        output_file = Path(parent_page.category, detail_page['slug'])
        template = env.get_template(f'{parent_page.detail_page_template}.html')
        write_file(
            f"{parent_page.category}/{detail_page['slug']}",
            output_file,
            template.render(category=parent_page.category, page=detail_page),
        )

        # Check if this detail page (e.g. a blog post page or project page) has any
        # assets, such as images or videos. If so, copy these assets to the build
        # directory.
        assets_dir = parent_page.source_dir / detail_page['filename'].stem
        if assets_dir.is_dir():
            copytree(assets_dir, Path(BUILD_PATH) / output_file, dirs_exist_ok=True)


def build_list_page(page: Page) -> Tuple[List[Dict], Dict]:
    """Create an HTML page that lists items, such as blog posts or projects.

    :param page: The page to be created.
    :return:
    Items that appear on the list page are gathered into a list so individual
    pages can be created. While processing these items, a map of tags is also
    built.

    :param page: The list page to be created.
    :return: The items that appear in the list along with a map of tags.
    """
    items = []
    tags: Dict[str, List] = defaultdict(list)
    source_dir = str(page.source_dir)

    for f in os.listdir(source_dir):
        filepath = join(source_dir, f)
    if page.source_dir:
        for file in page.source_dir.iterdir():
            if not file.is_file():
                continue

        if not isfile(filepath):
            continue
            page_vars = get_page_vars(file)
            page_vars['filename'] = file

        page_vars = get_page_vars(filepath)
            if 'title' in page_vars:
                page_vars['slug'] = slugify(page_vars['title'])

        if 'title' in page_vars:
            page_vars['slug'] = slugify(page_vars['title'])
            items.append(page_vars)

        items.append(page_vars)

        if 'tags' in page_vars:
            for tag in page_vars['tags']:
                tags[tag].append(page_vars)
            if 'tags' in page_vars:
                for tag in page_vars['tags']:
                    tags[tag].append(page_vars)

    # Items on a page, such as blog posts or projects, are listed from most to
    # least recent.
    items = sorted(items, key=lambda x: x['date_published'], reverse=True)

    template = env.get_template(f'{page.template}.html')
    write_file(page.category, template.render(category=page.category, items=items))
    write_file(
        Path(page.category), template.render(category=page.category, items=items)
    )

    return items, tags



@@ 174,20 192,23 @@ def build_list_page(page: Page) -> Tuple[List[Dict], Dict]:
def build_simple_page(page: Page):
    """Create an HTML page from a template.

    :param page_name: The name of the page to build, excluding the extension.
    :param page: The page to build.
    """
    template = env.get_template(f'{page.template}.html')
    write_file(page.category, template.render(category=page.category))
    write_file(Path(page.category), template.render(category=page.category))


if __name__ == "__main__":
    env = Environment(
        autoescape=True,
        loader=FileSystemLoader(searchpath=TEMPLATES_PATH),
        autoescape=True,
        trim_blocks=True,
        lstrip_blocks=True,
    )

    # Copy the static files to the build directory
    copytree(STATIC_ASSETS_PATH, BUILD_PATH, dirs_exist_ok=True)

    # Ensure some build directories exist
    Path(f'{BUILD_PATH}/blog/tag/').mkdir(parents=True, exist_ok=True)



@@ 210,6 231,3 @@ if __name__ == "__main__":
    # Build the tag pages
    for (page_category, tag), page_vars in all_tags.items():
        build_tag_page(page_category, tag, page_vars)

    # Copy the static files to the build directory
    copytree(STATIC_ASSETS_PATH, BUILD_PATH, dirs_exist_ok=True)