1
0
mirror of https://github.com/venthur/blag.git synced 2025-11-25 20:52:43 +00:00
This commit is contained in:
Bastian Venthur
2022-08-31 17:17:02 +02:00
parent 641f0ed94e
commit 322154041a
7 changed files with 87 additions and 43 deletions

View File

@@ -27,6 +27,10 @@ $(VENV): requirements.txt requirements-dev.txt setup.py
test: $(VENV) test: $(VENV)
$(BIN)/pytest $(BIN)/pytest
.PHONY: mypy
mypy: $(VENV)
$(BIN)/mypy
.PHONY: lint .PHONY: lint
lint: $(VENV) lint: $(VENV)
$(BIN)/flake8 $(BIN)/flake8

View File

@@ -11,7 +11,13 @@ import logging
import configparser import configparser
import sys import sys
from jinja2 import Environment, ChoiceLoader, FileSystemLoader, PackageLoader from jinja2 import (
Environment,
ChoiceLoader,
FileSystemLoader,
PackageLoader,
Template,
)
import feedgenerator import feedgenerator
from blag.markdown import markdown_factory, convert_markdown from blag.markdown import markdown_factory, convert_markdown
@@ -26,7 +32,7 @@ logging.basicConfig(
) )
def main(args=None): def main(arguments: list[str] = None) -> None:
"""Main entrypoint for the CLI. """Main entrypoint for the CLI.
This method parses the CLI arguments and executes the respective This method parses the CLI arguments and executes the respective
@@ -34,11 +40,11 @@ def main(args=None):
Parameters Parameters
---------- ----------
args : list[str] arguments : list[str]
optional parameters, used for testing optional parameters, used for testing
""" """
args = parse_args(args) args = parse_args(arguments)
# set loglevel # set loglevel
if args.verbose: if args.verbose:
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
@@ -46,7 +52,7 @@ def main(args=None):
args.func(args) args.func(args)
def parse_args(args=None): def parse_args(args: list[str] = None) -> argparse.Namespace:
"""Parse command line arguments. """Parse command line arguments.
Parameters Parameters
@@ -135,7 +141,7 @@ def parse_args(args=None):
return parser.parse_args(args) return parser.parse_args(args)
def get_config(configfile): def get_config(configfile: str) -> configparser.SectionProxy:
"""Load site configuration from configfile. """Load site configuration from configfile.
Parameters Parameters
@@ -146,7 +152,7 @@ def get_config(configfile):
Returns Returns
------- -------
dict configparser.SectionProxy
""" """
config = configparser.ConfigParser() config = configparser.ConfigParser()
@@ -166,7 +172,10 @@ def get_config(configfile):
return config['main'] return config['main']
def environment_factory(template_dir=None, globals_=None): def environment_factory(
template_dir: str = None,
globals_: dict = None,
) -> Environment:
"""Environment factory. """Environment factory.
Creates a Jinja2 Environment with the default templates and Creates a Jinja2 Environment with the default templates and
@@ -186,7 +195,7 @@ def environment_factory(template_dir=None, globals_=None):
""" """
# first we try the custom templates, and fall back the ones provided # first we try the custom templates, and fall back the ones provided
# by blag # by blag
loaders = [] loaders: list[FileSystemLoader | PackageLoader] = []
if template_dir: if template_dir:
loaders.append(FileSystemLoader([template_dir])) loaders.append(FileSystemLoader([template_dir]))
loaders.append(PackageLoader('blag', 'templates')) loaders.append(PackageLoader('blag', 'templates'))
@@ -196,7 +205,7 @@ def environment_factory(template_dir=None, globals_=None):
return env return env
def build(args): def build(args: argparse.Namespace) -> None:
"""Build the site. """Build the site.
This is blag's main method that builds the site, generates the feed This is blag's main method that builds the site, generates the feed
@@ -261,8 +270,13 @@ def build(args):
generate_tags(articles, tags_template, tag_template, args.output_dir) generate_tags(articles, tags_template, tag_template, args.output_dir)
def process_markdown(convertibles, input_dir, output_dir, def process_markdown(
page_template, article_template): convertibles: list[tuple[str, str]],
input_dir: str,
output_dir: str,
page_template: Template,
article_template: Template,
) -> tuple[list[tuple[str, dict]], list[tuple[str, dict]]]:
"""Process markdown files. """Process markdown files.
This method processes the convertibles, converts them to html and This method processes the convertibles, converts them to html and
@@ -273,7 +287,7 @@ def process_markdown(convertibles, input_dir, output_dir,
Parameters Parameters
---------- ----------
convertibles : List[Tuple[str, str]] convertibles : list[tuple[str, str]]
relative paths to markdown- (src) html- (dest) files relative paths to markdown- (src) html- (dest) files
input_dir : str input_dir : str
output_dir : str output_dir : str
@@ -282,7 +296,7 @@ def process_markdown(convertibles, input_dir, output_dir,
Returns Returns
------- -------
articles, pages : List[Tuple[str, Dict]] articles, pages : list[tuple[str, dict]]
""" """
logger.info("Converting Markdown files...") logger.info("Converting Markdown files...")
@@ -318,18 +332,18 @@ def process_markdown(convertibles, input_dir, output_dir,
def generate_feed( def generate_feed(
articles, articles: list[tuple[str, dict]],
output_dir, output_dir: str,
base_url, base_url: str,
blog_title, blog_title: str,
blog_description, blog_description: str,
blog_author, blog_author: str,
): ) -> None:
"""Generate Atom feed. """Generate Atom feed.
Parameters Parameters
---------- ----------
articles : list[list[str, dict]] articles : list[tuple[str, dict]]
list of relative output path and article dictionary list of relative output path and article dictionary
output_dir : str output_dir : str
where the feed is stored where the feed is stored
@@ -369,12 +383,16 @@ def generate_feed(
feed.write(fh, encoding='utf8') feed.write(fh, encoding='utf8')
def generate_archive(articles, template, output_dir): def generate_archive(
articles: list[tuple[str, dict]],
template: Template,
output_dir: str,
) -> None:
"""Generate the archive page. """Generate the archive page.
Parameters Parameters
---------- ----------
articles : list[list[str, dict]] articles : list[tuple[str, dict]]
List of articles. Each article has the destination path and a List of articles. Each article has the destination path and a
dictionary with the content. dictionary with the content.
template : jinja2.Template instance template : jinja2.Template instance
@@ -392,12 +410,17 @@ def generate_archive(articles, template, output_dir):
fh.write(result) fh.write(result)
def generate_tags(articles, tags_template, tag_template, output_dir): def generate_tags(
articles: list[tuple[str, dict]],
tags_template: Template,
tag_template: Template,
output_dir: str,
) -> None:
"""Generate the tags page. """Generate the tags page.
Parameters Parameters
---------- ----------
articles : list[list[str, dict]] articles : list[tuple[str, dict]]
List of articles. Each article has the destination path and a List of articles. Each article has the destination path and a
dictionary with the content. dictionary with the content.
tags_template, tag_template : jinja2.Template instance tags_template, tag_template : jinja2.Template instance
@@ -408,15 +431,17 @@ def generate_tags(articles, tags_template, tag_template, output_dir):
os.makedirs(f'{output_dir}/tags', exist_ok=True) os.makedirs(f'{output_dir}/tags', exist_ok=True)
# get tags number of occurrences # get tags number of occurrences
all_tags = {} all_tags: dict = {}
for _, context in articles: for _, context in articles:
tags = context.get('tags', []) tags = context.get('tags', [])
for tag in tags: for tag in tags:
all_tags[tag] = all_tags.get(tag, 0) + 1 all_tags[tag] = all_tags.get(tag, 0) + 1
# sort by occurrence # sort by occurrence
all_tags = sorted(all_tags.items(), key=lambda x: x[1], reverse=True) taglist: list[tuple[str, int]] = sorted(
all_tags.items(), key=lambda x: x[1], reverse=True
)
result = tags_template.render(dict(tags=all_tags)) result = tags_template.render(dict(tags=taglist))
with open(f'{output_dir}/tags/index.html', 'w') as fh: with open(f'{output_dir}/tags/index.html', 'w') as fh:
fh.write(result) fh.write(result)

View File

@@ -12,6 +12,7 @@ import time
import multiprocessing import multiprocessing
from http.server import SimpleHTTPRequestHandler, HTTPServer from http.server import SimpleHTTPRequestHandler, HTTPServer
from functools import partial from functools import partial
import argparse
from blag import blag from blag import blag
@@ -19,7 +20,7 @@ from blag import blag
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def get_last_modified(dirs): def get_last_modified(dirs: list[str]) -> float:
"""Get the last modified time. """Get the last modified time.
This method recursively goes through `dirs` and returns the most This method recursively goes through `dirs` and returns the most
@@ -32,11 +33,11 @@ def get_last_modified(dirs):
Returns Returns
------- -------
int float
most recent modification time found in `dirs` most recent modification time found in `dirs`
""" """
last_mtime = 0 last_mtime = 0.0
for dir in dirs: for dir in dirs:
for root, dirs, files in os.walk(dir): for root, dirs, files in os.walk(dir):
@@ -48,7 +49,7 @@ def get_last_modified(dirs):
return last_mtime return last_mtime
def autoreload(args): def autoreload(args: argparse.Namespace) -> None:
"""Start the autoreloader. """Start the autoreloader.
This method monitors the given directories for changes (i.e. the This method monitors the given directories for changes (i.e. the
@@ -67,7 +68,7 @@ def autoreload(args):
logger.info(f'Monitoring {dirs} for changes...') logger.info(f'Monitoring {dirs} for changes...')
# make sure we trigger the rebuild immediately when we enter the # make sure we trigger the rebuild immediately when we enter the
# loop to avoid serving stale contents # loop to avoid serving stale contents
last_mtime = 0 last_mtime = 0.0
while True: while True:
mtime = get_last_modified(dirs) mtime = get_last_modified(dirs)
if mtime > last_mtime: if mtime > last_mtime:
@@ -77,7 +78,7 @@ def autoreload(args):
time.sleep(1) time.sleep(1)
def serve(args): def serve(args: argparse.Namespace) -> None:
"""Start the webserver and the autoreloader. """Start the webserver and the autoreloader.
Parameters Parameters

View File

@@ -8,6 +8,7 @@ processing.
from datetime import datetime from datetime import datetime
import logging import logging
from urllib.parse import urlsplit, urlunsplit from urllib.parse import urlsplit, urlunsplit
from xml.etree.ElementTree import Element
from markdown import Markdown from markdown import Markdown
from markdown.extensions import Extension from markdown.extensions import Extension
@@ -17,7 +18,7 @@ from markdown.treeprocessors import Treeprocessor
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def markdown_factory(): def markdown_factory() -> Markdown:
"""Create a Markdown instance. """Create a Markdown instance.
This method exists only to ensure we use the same Markdown instance This method exists only to ensure we use the same Markdown instance
@@ -33,12 +34,12 @@ def markdown_factory():
'meta', 'fenced_code', 'codehilite', 'smarty', 'meta', 'fenced_code', 'codehilite', 'smarty',
MarkdownLinkExtension() MarkdownLinkExtension()
], ],
output_format='html5', output_format='html',
) )
return md return md
def convert_markdown(md, markdown): def convert_markdown(md: Markdown, markdown: str) -> tuple[str, dict]:
"""Convert markdown into html and extract meta data. """Convert markdown into html and extract meta data.
Some meta data is treated special: Some meta data is treated special:
@@ -87,15 +88,19 @@ class MarkdownLinkTreeprocessor(Treeprocessor):
""" """
def run(self, root): def run(self, root: Element):
for element in root.iter(): for element in root.iter():
if element.tag == 'a': if element.tag == 'a':
url = element.get('href') url = element.get('href')
# element.get could also return None, we haven't seen this so
# far, so lets wait if we raise this
assert url is not None
url = str(url)
converted = self.convert(url) converted = self.convert(url)
element.set('href', converted) element.set('href', converted)
return root return root
def convert(self, url): def convert(self, url: str):
scheme, netloc, path, query, fragment = urlsplit(url) scheme, netloc, path, query, fragment = urlsplit(url)
logger.debug( logger.debug(
f'{url}: {scheme=} {netloc=} {path=} {query=} {fragment=}' f'{url}: {scheme=} {netloc=} {path=} {query=} {fragment=}'
@@ -113,7 +118,7 @@ class MarkdownLinkExtension(Extension):
"""markdown.extension that converts relative .md- to .html-links. """markdown.extension that converts relative .md- to .html-links.
""" """
def extendMarkdown(self, md): def extendMarkdown(self, md: Markdown):
md.treeprocessors.register( md.treeprocessors.register(
MarkdownLinkTreeprocessor(md), 'mdlink', 0, MarkdownLinkTreeprocessor(md), 'mdlink', 0,
) )

View File

@@ -3,9 +3,10 @@
""" """
import configparser import configparser
import argparse
def get_input(question, default): def get_input(question: str, default: str) -> str:
"""Prompt for user input. """Prompt for user input.
This is a wrapper around the input-builtin. It will show the default answer This is a wrapper around the input-builtin. It will show the default answer
@@ -29,7 +30,7 @@ def get_input(question, default):
return reply return reply
def quickstart(args): def quickstart(args: argparse.Namespace) -> None:
"""Quickstart. """Quickstart.
This method asks the user some questions and generates a This method asks the user some questions and generates a

View File

@@ -4,3 +4,5 @@ wheel==0.37.1
pytest==7.1.2 pytest==7.1.2
pytest-cov==3.0.0 pytest-cov==3.0.0
flake8==5.0.2 flake8==5.0.2
mypy==0.971
types-markdown==3.4.1

View File

@@ -7,3 +7,9 @@ addopts =
[flake8] [flake8]
exclude = venv,build,docs exclude = venv,build,docs
[mypy]
files = blag,tests
[mypy-feedgenerator.*]
ignore_missing_imports = True