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)
$(BIN)/pytest
.PHONY: mypy
mypy: $(VENV)
$(BIN)/mypy
.PHONY: lint
lint: $(VENV)
$(BIN)/flake8

View File

@@ -11,7 +11,13 @@ import logging
import configparser
import sys
from jinja2 import Environment, ChoiceLoader, FileSystemLoader, PackageLoader
from jinja2 import (
Environment,
ChoiceLoader,
FileSystemLoader,
PackageLoader,
Template,
)
import feedgenerator
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.
This method parses the CLI arguments and executes the respective
@@ -34,11 +40,11 @@ def main(args=None):
Parameters
----------
args : list[str]
arguments : list[str]
optional parameters, used for testing
"""
args = parse_args(args)
args = parse_args(arguments)
# set loglevel
if args.verbose:
logger.setLevel(logging.DEBUG)
@@ -46,7 +52,7 @@ def main(args=None):
args.func(args)
def parse_args(args=None):
def parse_args(args: list[str] = None) -> argparse.Namespace:
"""Parse command line arguments.
Parameters
@@ -135,7 +141,7 @@ def parse_args(args=None):
return parser.parse_args(args)
def get_config(configfile):
def get_config(configfile: str) -> configparser.SectionProxy:
"""Load site configuration from configfile.
Parameters
@@ -146,7 +152,7 @@ def get_config(configfile):
Returns
-------
dict
configparser.SectionProxy
"""
config = configparser.ConfigParser()
@@ -166,7 +172,10 @@ def get_config(configfile):
return config['main']
def environment_factory(template_dir=None, globals_=None):
def environment_factory(
template_dir: str = None,
globals_: dict = None,
) -> Environment:
"""Environment factory.
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
# by blag
loaders = []
loaders: list[FileSystemLoader | PackageLoader] = []
if template_dir:
loaders.append(FileSystemLoader([template_dir]))
loaders.append(PackageLoader('blag', 'templates'))
@@ -196,7 +205,7 @@ def environment_factory(template_dir=None, globals_=None):
return env
def build(args):
def build(args: argparse.Namespace) -> None:
"""Build the site.
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)
def process_markdown(convertibles, input_dir, output_dir,
page_template, article_template):
def process_markdown(
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.
This method processes the convertibles, converts them to html and
@@ -273,7 +287,7 @@ def process_markdown(convertibles, input_dir, output_dir,
Parameters
----------
convertibles : List[Tuple[str, str]]
convertibles : list[tuple[str, str]]
relative paths to markdown- (src) html- (dest) files
input_dir : str
output_dir : str
@@ -282,7 +296,7 @@ def process_markdown(convertibles, input_dir, output_dir,
Returns
-------
articles, pages : List[Tuple[str, Dict]]
articles, pages : list[tuple[str, dict]]
"""
logger.info("Converting Markdown files...")
@@ -318,18 +332,18 @@ def process_markdown(convertibles, input_dir, output_dir,
def generate_feed(
articles,
output_dir,
base_url,
blog_title,
blog_description,
blog_author,
):
articles: list[tuple[str, dict]],
output_dir: str,
base_url: str,
blog_title: str,
blog_description: str,
blog_author: str,
) -> None:
"""Generate Atom feed.
Parameters
----------
articles : list[list[str, dict]]
articles : list[tuple[str, dict]]
list of relative output path and article dictionary
output_dir : str
where the feed is stored
@@ -369,12 +383,16 @@ def generate_feed(
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.
Parameters
----------
articles : list[list[str, dict]]
articles : list[tuple[str, dict]]
List of articles. Each article has the destination path and a
dictionary with the content.
template : jinja2.Template instance
@@ -392,12 +410,17 @@ def generate_archive(articles, template, output_dir):
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.
Parameters
----------
articles : list[list[str, dict]]
articles : list[tuple[str, dict]]
List of articles. Each article has the destination path and a
dictionary with the content.
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)
# get tags number of occurrences
all_tags = {}
all_tags: dict = {}
for _, context in articles:
tags = context.get('tags', [])
for tag in tags:
all_tags[tag] = all_tags.get(tag, 0) + 1
# 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:
fh.write(result)

View File

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

View File

@@ -8,6 +8,7 @@ processing.
from datetime import datetime
import logging
from urllib.parse import urlsplit, urlunsplit
from xml.etree.ElementTree import Element
from markdown import Markdown
from markdown.extensions import Extension
@@ -17,7 +18,7 @@ from markdown.treeprocessors import Treeprocessor
logger = logging.getLogger(__name__)
def markdown_factory():
def markdown_factory() -> Markdown:
"""Create a 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',
MarkdownLinkExtension()
],
output_format='html5',
output_format='html',
)
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.
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():
if element.tag == 'a':
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)
element.set('href', converted)
return root
def convert(self, url):
def convert(self, url: str):
scheme, netloc, path, query, fragment = urlsplit(url)
logger.debug(
f'{url}: {scheme=} {netloc=} {path=} {query=} {fragment=}'
@@ -113,7 +118,7 @@ class MarkdownLinkExtension(Extension):
"""markdown.extension that converts relative .md- to .html-links.
"""
def extendMarkdown(self, md):
def extendMarkdown(self, md: Markdown):
md.treeprocessors.register(
MarkdownLinkTreeprocessor(md), 'mdlink', 0,
)

View File

@@ -3,9 +3,10 @@
"""
import configparser
import argparse
def get_input(question, default):
def get_input(question: str, default: str) -> str:
"""Prompt for user input.
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
def quickstart(args):
def quickstart(args: argparse.Namespace) -> None:
"""Quickstart.
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-cov==3.0.0
flake8==5.0.2
mypy==0.971
types-markdown==3.4.1

View File

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