mirror of
https://github.com/venthur/blag.git
synced 2025-11-25 20:52:43 +00:00
WIP
This commit is contained in:
4
Makefile
4
Makefile
@@ -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
|
||||
|
||||
83
blag/blag.py
83
blag/blag.py
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user