From 322154041ad6ac48d26e409512445993c59fd452 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Wed, 31 Aug 2022 17:17:02 +0200 Subject: [PATCH 01/10] WIP --- Makefile | 4 +++ blag/blag.py | 83 ++++++++++++++++++++++++++++---------------- blag/devserver.py | 13 +++---- blag/markdown.py | 17 +++++---- blag/quickstart.py | 5 +-- requirements-dev.txt | 2 ++ setup.cfg | 6 ++++ 7 files changed, 87 insertions(+), 43 deletions(-) diff --git a/Makefile b/Makefile index d973043..3199902 100644 --- a/Makefile +++ b/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 diff --git a/blag/blag.py b/blag/blag.py index e84bc2b..bf70e3d 100644 --- a/blag/blag.py +++ b/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) diff --git a/blag/devserver.py b/blag/devserver.py index dbc7386..c49cdfe 100644 --- a/blag/devserver.py +++ b/blag/devserver.py @@ -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 diff --git a/blag/markdown.py b/blag/markdown.py index 328b0d0..640bb3b 100644 --- a/blag/markdown.py +++ b/blag/markdown.py @@ -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, ) diff --git a/blag/quickstart.py b/blag/quickstart.py index 00b4725..7bffc2c 100644 --- a/blag/quickstart.py +++ b/blag/quickstart.py @@ -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 diff --git a/requirements-dev.txt b/requirements-dev.txt index 1292db9..fd43f91 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -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 diff --git a/setup.cfg b/setup.cfg index 681fad8..71030af 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,3 +7,9 @@ addopts = [flake8] exclude = venv,build,docs + +[mypy] +files = blag,tests + +[mypy-feedgenerator.*] +ignore_missing_imports = True From 451fb1b2609fb0099b475ef74787786f158151b1 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Wed, 31 Aug 2022 19:54:32 +0200 Subject: [PATCH 02/10] try supporting py38 --- blag/blag.py | 2 ++ blag/devserver.py | 2 ++ blag/markdown.py | 2 ++ blag/quickstart.py | 2 ++ 4 files changed, 8 insertions(+) diff --git a/blag/blag.py b/blag/blag.py index bf70e3d..f1c1b1a 100644 --- a/blag/blag.py +++ b/blag/blag.py @@ -4,6 +4,8 @@ """ +# remove when we don't support py38 anymore +from __future__ import annotations import argparse import os import shutil diff --git a/blag/devserver.py b/blag/devserver.py index c49cdfe..1f2a8be 100644 --- a/blag/devserver.py +++ b/blag/devserver.py @@ -6,6 +6,8 @@ site if necessary. """ +# remove when we don't support py38 anymore +from __future__ import annotations import os import logging import time diff --git a/blag/markdown.py b/blag/markdown.py index 640bb3b..b71df4b 100644 --- a/blag/markdown.py +++ b/blag/markdown.py @@ -5,6 +5,8 @@ processing. """ +# remove when we don't support py38 anymore +from __future__ import annotations from datetime import datetime import logging from urllib.parse import urlsplit, urlunsplit diff --git a/blag/quickstart.py b/blag/quickstart.py index 7bffc2c..dc82380 100644 --- a/blag/quickstart.py +++ b/blag/quickstart.py @@ -2,6 +2,8 @@ """ +# remove when we don't support py38 anymore +from __future__ import annotations import configparser import argparse From 10f84ebb160521237a15e5fdea59829cae57366e Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Wed, 31 Aug 2022 22:57:02 +0200 Subject: [PATCH 03/10] added mypy to github action --- .github/workflows/python-package.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/python-package.yaml b/.github/workflows/python-package.yaml index fdccf0d..cb0b346 100644 --- a/.github/workflows/python-package.yaml +++ b/.github/workflows/python-package.yaml @@ -36,3 +36,7 @@ jobs: - name: Run linter run: | make lint + + - name: Run mypy + run: | + make mypy From 2adc7b3bd4d51cba9b3946267fc4cc1d3c85e011 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Wed, 31 Aug 2022 22:57:30 +0200 Subject: [PATCH 04/10] added mypy to all target --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3199902..87cec44 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ endif .PHONY: all -all: lint test +all: lint mypy test $(VENV): requirements.txt requirements-dev.txt setup.py $(PY) -m venv $(VENV) From ebac0a8fc4ba07bdf07e3e7be7d45d69e8cca0de Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Wed, 31 Aug 2022 22:59:55 +0200 Subject: [PATCH 05/10] fixed for mypy --strict testing --- blag/__init__.py | 2 +- blag/blag.py | 40 +++++++++++++------------- blag/markdown.py | 15 ++++++---- blag/quickstart.py | 2 +- setup.cfg | 1 + tests/conftest.py | 26 ++++++++--------- tests/test_blag.py | 62 ++++++++++++++++++++++------------------ tests/test_devserver.py | 7 +++-- tests/test_markdown.py | 13 +++++---- tests/test_quickstart.py | 8 ++++-- tests/test_templates.py | 12 ++++---- tests/test_version.py | 2 +- 12 files changed, 102 insertions(+), 88 deletions(-) diff --git a/blag/__init__.py b/blag/__init__.py index 6527216..318e4f3 100644 --- a/blag/__init__.py +++ b/blag/__init__.py @@ -1 +1 @@ -from blag.version import __VERSION__ # noqa +from blag.version import __VERSION__ as __VERSION__ # noqa diff --git a/blag/blag.py b/blag/blag.py index f1c1b1a..8a9ce81 100644 --- a/blag/blag.py +++ b/blag/blag.py @@ -6,6 +6,7 @@ # remove when we don't support py38 anymore from __future__ import annotations +from typing import Any import argparse import os import shutil @@ -34,7 +35,7 @@ logging.basicConfig( ) -def main(arguments: list[str] = None) -> None: +def main(arguments: list[str] | None = None) -> None: """Main entrypoint for the CLI. This method parses the CLI arguments and executes the respective @@ -54,7 +55,7 @@ def main(arguments: list[str] = None) -> None: args.func(args) -def parse_args(args: list[str] = None) -> argparse.Namespace: +def parse_args(args: list[str] | None = None) -> argparse.Namespace: """Parse command line arguments. Parameters @@ -175,8 +176,8 @@ def get_config(configfile: str) -> configparser.SectionProxy: def environment_factory( - template_dir: str = None, - globals_: dict = None, + template_dir: str | None = None, + globals_: dict[str, object] | None = None, ) -> Environment: """Environment factory. @@ -188,7 +189,7 @@ def environment_factory( Parameters ---------- template_dir : str - globals_ : dict + globals_ : dict[str, object] Returns ------- @@ -278,7 +279,7 @@ def process_markdown( output_dir: str, page_template: Template, article_template: Template, -) -> tuple[list[tuple[str, dict]], list[tuple[str, dict]]]: +) -> tuple[list[tuple[str, dict[str, Any]]], list[tuple[str, dict[str, Any]]]]: """Process markdown files. This method processes the convertibles, converts them to html and @@ -298,7 +299,7 @@ def process_markdown( Returns ------- - articles, pages : list[tuple[str, dict]] + articles, pages : list[tuple[str, dict[str, Any]]] """ logger.info("Converting Markdown files...") @@ -334,7 +335,7 @@ def process_markdown( def generate_feed( - articles: list[tuple[str, dict]], + articles: list[tuple[str, dict[str, Any]]], output_dir: str, base_url: str, blog_title: str, @@ -345,7 +346,7 @@ def generate_feed( Parameters ---------- - articles : list[tuple[str, dict]] + articles : list[tuple[str, dict[str, Any]]] list of relative output path and article dictionary output_dir : str where the feed is stored @@ -386,7 +387,7 @@ def generate_feed( def generate_archive( - articles: list[tuple[str, dict]], + articles: list[tuple[str, dict[str, Any]]], template: Template, output_dir: str, ) -> None: @@ -394,7 +395,7 @@ def generate_archive( Parameters ---------- - articles : list[tuple[str, dict]] + articles : list[tuple[str, dict[str, Any]]] List of articles. Each article has the destination path and a dictionary with the content. template : jinja2.Template instance @@ -413,7 +414,7 @@ def generate_archive( def generate_tags( - articles: list[tuple[str, dict]], + articles: list[tuple[str, dict[str, Any]]], tags_template: Template, tag_template: Template, output_dir: str, @@ -422,7 +423,7 @@ def generate_tags( Parameters ---------- - articles : list[tuple[str, dict]] + articles : list[tuple[str, dict[str, Any]]] List of articles. Each article has the destination path and a dictionary with the content. tags_template, tag_template : jinja2.Template instance @@ -431,11 +432,10 @@ def generate_tags( """ logger.info("Generating Tag-pages.") os.makedirs(f'{output_dir}/tags', exist_ok=True) - # get tags number of occurrences - all_tags: dict = {} + all_tags: dict[str, int] = {} for _, context in articles: - tags = context.get('tags', []) + tags: list[str] = context.get('tags', []) for tag in tags: all_tags[tag] = all_tags.get(tag, 0) + 1 # sort by occurrence @@ -448,17 +448,17 @@ def generate_tags( fh.write(result) # get tags and archive per tag - all_tags = {} + all_tags2: dict[str, list[dict[str, Any]]] = {} for dst, context in articles: tags = context.get('tags', []) for tag in tags: - archive = all_tags.get(tag, []) + archive: list[dict[str, Any]] = all_tags2.get(tag, []) entry = context.copy() entry['dst'] = dst archive.append(entry) - all_tags[tag] = archive + all_tags2[tag] = archive - for tag, archive in all_tags.items(): + for tag, archive in all_tags2.items(): result = tag_template.render(dict(archive=archive, tag=tag)) with open(f'{output_dir}/tags/{tag}.html', 'w') as fh: fh.write(result) diff --git a/blag/markdown.py b/blag/markdown.py index b71df4b..efe0926 100644 --- a/blag/markdown.py +++ b/blag/markdown.py @@ -41,7 +41,10 @@ def markdown_factory() -> Markdown: return md -def convert_markdown(md: Markdown, markdown: str) -> tuple[str, dict]: +def convert_markdown( + md: Markdown, + markdown: str, +) -> tuple[str, dict[str, str]]: """Convert markdown into html and extract meta data. Some meta data is treated special: @@ -56,13 +59,13 @@ def convert_markdown(md: Markdown, markdown: str) -> tuple[str, dict]: Returns ------- - str, dict : + str, dict[str, str] : html and metadata """ md.reset() content = md.convert(markdown) - meta = md.Meta + meta = md.Meta # type: ignore # markdowns metadata consists as list of strings -- one item per # line. let's convert into single strings. @@ -90,7 +93,7 @@ class MarkdownLinkTreeprocessor(Treeprocessor): """ - def run(self, root: Element): + def run(self, root: Element) -> Element: for element in root.iter(): if element.tag == 'a': url = element.get('href') @@ -102,7 +105,7 @@ class MarkdownLinkTreeprocessor(Treeprocessor): element.set('href', converted) return root - def convert(self, url: str): + def convert(self, url: str) -> str: scheme, netloc, path, query, fragment = urlsplit(url) logger.debug( f'{url}: {scheme=} {netloc=} {path=} {query=} {fragment=}' @@ -120,7 +123,7 @@ class MarkdownLinkExtension(Extension): """markdown.extension that converts relative .md- to .html-links. """ - def extendMarkdown(self, md: Markdown): + def extendMarkdown(self, md: Markdown) -> None: md.treeprocessors.register( MarkdownLinkTreeprocessor(md), 'mdlink', 0, ) diff --git a/blag/quickstart.py b/blag/quickstart.py index dc82380..cbdcaac 100644 --- a/blag/quickstart.py +++ b/blag/quickstart.py @@ -32,7 +32,7 @@ def get_input(question: str, default: str) -> str: return reply -def quickstart(args: argparse.Namespace) -> None: +def quickstart(args: argparse.Namespace | None) -> None: """Quickstart. This method asks the user some questions and generates a diff --git a/setup.cfg b/setup.cfg index 71030af..94d778c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -10,6 +10,7 @@ exclude = venv,build,docs [mypy] files = blag,tests +strict = True [mypy-feedgenerator.*] ignore_missing_imports = True diff --git a/tests/conftest.py b/tests/conftest.py index 221078c..f774841 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,13 +1,16 @@ +from argparse import Namespace +from typing import Iterator, Callable from tempfile import TemporaryDirectory import os import pytest +from jinja2 import Environment, Template from blag import blag @pytest.fixture -def environment(): +def environment() -> Iterator[Environment]: site = { 'base_url': 'site base_url', 'title': 'site title', @@ -19,32 +22,32 @@ def environment(): @pytest.fixture -def page_template(environment): +def page_template(environment: Environment) -> Iterator[Template]: yield environment.get_template('page.html') @pytest.fixture -def article_template(environment): +def article_template(environment: Environment) -> Iterator[Template]: yield environment.get_template('article.html') @pytest.fixture -def archive_template(environment): +def archive_template(environment: Environment) -> Iterator[Template]: yield environment.get_template('archive.html') @pytest.fixture -def tags_template(environment): +def tags_template(environment: Environment) -> Iterator[Template]: yield environment.get_template('tags.html') @pytest.fixture -def tag_template(environment): +def tag_template(environment: Environment) -> Iterator[Template]: yield environment.get_template('tag.html') @pytest.fixture -def cleandir(): +def cleandir() -> Iterator[str]: """Create a temporary workind directory and cwd. """ @@ -70,14 +73,9 @@ author = a. u. thor @pytest.fixture -def args(cleandir): +def args(cleandir: Callable[[], Iterator[str]]) -> Iterator[Namespace]: - class NameSpace: - def __init__(self, **kwargs): - for name in kwargs: - setattr(self, name, kwargs[name]) - - args = NameSpace( + args = Namespace( input_dir='content', output_dir='build', static_dir='static', diff --git a/tests/test_blag.py b/tests/test_blag.py index eeafbae..d4f40ae 100644 --- a/tests/test_blag.py +++ b/tests/test_blag.py @@ -1,37 +1,41 @@ from tempfile import TemporaryDirectory import os from datetime import datetime +from typing import Any +from argparse import Namespace import pytest +from pytest import CaptureFixture, LogCaptureFixture +from jinja2 import Template +from blag import __VERSION__ from blag import blag -def test_generate_feed(cleandir): - articles = [] +def test_generate_feed(cleandir: str) -> None: + articles: list[tuple[str, dict[str, Any]]] = [] blag.generate_feed(articles, 'build', ' ', ' ', ' ', ' ') assert os.path.exists('build/atom.xml') -def test_feed(cleandir): - articles = [ - [ +def test_feed(cleandir: str) -> None: + articles: list[tuple[str, dict[str, Any]]] = [ + ( 'dest1.html', { 'title': 'title1', 'date': datetime(2019, 6, 6), 'content': 'content1', } - ], - [ + ), + ( 'dest2.html', { 'title': 'title2', 'date': datetime(1980, 5, 9), 'content': 'content2', } - ], - + ), ] blag.generate_feed(articles, 'build', 'https://example.com/', @@ -60,10 +64,10 @@ def test_feed(cleandir): assert ' None: # if a description is provided, it will be used as the summary in # the feed, otherwise we simply use the title of the article - articles = [[ + articles: list[tuple[str, dict[str, Any]]] = [( 'dest.html', { 'title': 'title', @@ -71,7 +75,7 @@ def test_generate_feed_with_description(cleandir): 'date': datetime(2019, 6, 6), 'content': 'content', } - ]] + )] blag.generate_feed(articles, 'build', ' ', ' ', ' ', ' ') with open('build/atom.xml') as fh: @@ -83,7 +87,7 @@ def test_generate_feed_with_description(cleandir): assert 'content' in feed -def test_parse_args_build(): +def test_parse_args_build() -> None: # test default args args = blag.parse_args(['build']) assert args.input_dir == 'content' @@ -116,7 +120,7 @@ def test_parse_args_build(): assert args.static_dir == 'foo' -def test_get_config(): +def test_get_config() -> None: config = """ [main] base_url = https://example.com/ @@ -166,8 +170,8 @@ author = a. u. thor assert config_parsed['base_url'] == 'https://example.com/' -def test_environment_factory(): - globals_ = { +def test_environment_factory() -> None: + globals_: dict[str, object] = { 'foo': 'bar', 'test': 'me' } @@ -176,7 +180,11 @@ def test_environment_factory(): assert env.globals['test'] == 'me' -def test_process_markdown(cleandir, page_template, article_template): +def test_process_markdown( + cleandir: str, + page_template: Template, + article_template: Template, +) -> None: page1 = """\ title: some page @@ -202,10 +210,9 @@ foo bar convertibles = [] for i, txt in enumerate((page1, article1, article2)): - i = str(i) - with open(f'content/{i}', 'w') as fh: + with open(f'content/{str(i)}', 'w') as fh: fh.write(txt) - convertibles.append([i, i]) + convertibles.append((str(i), str(i))) articles, pages = blag.process_markdown( convertibles, @@ -230,7 +237,7 @@ foo bar assert 'content' in context -def test_build(args): +def test_build(args: Namespace) -> None: page1 = """\ title: some page @@ -259,10 +266,9 @@ foo bar # write some convertibles convertibles = [] for i, txt in enumerate((page1, article1, article2)): - i = str(i) - with open(f'{args.input_dir}/{i}.md', 'w') as fh: + with open(f'{args.input_dir}/{str(i)}.md', 'w') as fh: fh.write(txt) - convertibles.append([i, i]) + convertibles.append((str(i), str(i))) # some static files with open(f'{args.static_dir}/test', 'w') as fh: @@ -291,21 +297,21 @@ foo bar assert os.path.exists(f'{args.output_dir}/tags/bar.html') -def test_main(cleandir): +def test_main(cleandir: str) -> None: blag.main(['build']) -def test_cli_version(capsys): +def test_cli_version(capsys: CaptureFixture[str]) -> None: with pytest.raises(SystemExit) as ex: blag.main(['--version']) # normal system exit assert ex.value.code == 0 # proper version reported out, _ = capsys.readouterr() - assert blag.__VERSION__ in out + assert __VERSION__ in out -def test_cli_verbose(cleandir, caplog): +def test_cli_verbose(cleandir: str, caplog: LogCaptureFixture) -> None: blag.main(['build']) assert 'DEBUG' not in caplog.text diff --git a/tests/test_devserver.py b/tests/test_devserver.py index 7f56a40..cdbb842 100644 --- a/tests/test_devserver.py +++ b/tests/test_devserver.py @@ -1,12 +1,13 @@ import time import threading +from argparse import Namespace import pytest from blag import devserver -def test_get_last_modified(cleandir): +def test_get_last_modified(cleandir: str) -> None: # take initial time t1 = devserver.get_last_modified(['content']) @@ -24,7 +25,7 @@ def test_get_last_modified(cleandir): assert t2 == t3 -def test_autoreload_builds_immediately(args): +def test_autoreload_builds_immediately(args: Namespace) -> None: # create a dummy file that can be build with open('content/test.md', 'w') as fh: fh.write('boo') @@ -45,7 +46,7 @@ def test_autoreload_builds_immediately(args): @pytest.mark.filterwarnings("ignore::pytest.PytestUnhandledThreadExceptionWarning") # noqa -def test_autoreload(args): +def test_autoreload(args: Namespace) -> None: t = threading.Thread(target=devserver.autoreload, args=(args, ), daemon=True,) diff --git a/tests/test_markdown.py b/tests/test_markdown.py index 9103ec1..4cee8c2 100644 --- a/tests/test_markdown.py +++ b/tests/test_markdown.py @@ -1,4 +1,5 @@ from datetime import datetime +from typing import Any import pytest import markdown @@ -26,7 +27,7 @@ from blag.markdown import convert_markdown, markdown_factory ('[test][]\n[test]: /a/test.md', '/a/test.html'), ('[test][]\n[test]: /a/test.md "test"', '/a/test.html'), ]) -def test_convert_markdown_links(input_, expected): +def test_convert_markdown_links(input_: str, expected: str) -> None: md = markdown_factory() html, _ = convert_markdown(md, input_) assert expected in html @@ -40,7 +41,7 @@ def test_convert_markdown_links(input_, expected): # no path ('[test]()', ''), ]) -def test_dont_convert_normal_links(input_, expected): +def test_dont_convert_normal_links(input_: str, expected: str) -> None: md = markdown_factory() html, _ = convert_markdown(md, input_) assert expected in html @@ -54,18 +55,18 @@ def test_dont_convert_normal_links(input_, expected): ('date: 2020-01-01 12:10', {'date': datetime(2020, 1, 1, 12, 10).astimezone()}), ]) -def test_convert_metadata(input_, expected): +def test_convert_metadata(input_: str, expected: dict[str, Any]) -> None: md = markdown_factory() _, meta = convert_markdown(md, input_) assert expected == meta -def test_markdown_factory(): +def test_markdown_factory() -> None: md = markdown_factory() assert isinstance(md, markdown.Markdown) -def test_smarty(): +def test_smarty() -> None: md = markdown_factory() md1 = """ @@ -79,7 +80,7 @@ this --- is -- a test ... assert 'hellip' in html -def test_smarty_code(): +def test_smarty_code() -> None: md = markdown_factory() md1 = """ diff --git a/tests/test_quickstart.py b/tests/test_quickstart.py index 4594497..ada2844 100644 --- a/tests/test_quickstart.py +++ b/tests/test_quickstart.py @@ -1,19 +1,21 @@ +from pytest import MonkeyPatch + from blag.quickstart import get_input, quickstart -def test_get_input_default_answer(monkeypatch): +def test_get_input_default_answer(monkeypatch: MonkeyPatch) -> None: monkeypatch.setattr('builtins.input', lambda x: '') answer = get_input("foo", "bar") assert answer == 'bar' -def test_get_input(monkeypatch): +def test_get_input(monkeypatch: MonkeyPatch) -> None: monkeypatch.setattr('builtins.input', lambda x: 'baz') answer = get_input("foo", "bar") assert answer == 'baz' -def test_quickstart(cleandir, monkeypatch): +def test_quickstart(cleandir: str, monkeypatch: MonkeyPatch) -> None: monkeypatch.setattr('builtins.input', lambda x: 'foo') quickstart(None) with open('config.ini', 'r') as fh: diff --git a/tests/test_templates.py b/tests/test_templates.py index e420891..dc3395a 100644 --- a/tests/test_templates.py +++ b/tests/test_templates.py @@ -1,7 +1,9 @@ import datetime +from jinja2 import Template -def test_page(page_template): + +def test_page(page_template: Template) -> None: ctx = { 'content': 'this is the content', 'title': 'this is the title', @@ -11,7 +13,7 @@ def test_page(page_template): assert 'this is the title' in result -def test_article(article_template): +def test_article(article_template: Template) -> None: ctx = { 'content': 'this is the content', 'title': 'this is the title', @@ -23,7 +25,7 @@ def test_article(article_template): assert '1980-05-09' in result -def test_archive(archive_template): +def test_archive(archive_template: Template) -> None: entry = { 'title': 'this is a title', 'dst': 'https://example.com/link', @@ -41,7 +43,7 @@ def test_archive(archive_template): assert 'https://example.com/link' in result -def test_tags(tags_template): +def test_tags(tags_template: Template) -> None: tags = [('foo', 42)] ctx = { 'tags': tags, @@ -54,7 +56,7 @@ def test_tags(tags_template): assert '42' in result -def test_tag(tag_template): +def test_tag(tag_template: Template) -> None: entry = { 'title': 'this is a title', 'dst': 'https://example.com/link', diff --git a/tests/test_version.py b/tests/test_version.py index 04f8d9a..d978f67 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -1,5 +1,5 @@ import blag -def test_version(): +def test_version() -> None: assert isinstance(blag.__VERSION__, str) From f59a6487796d3a5a42dcdc6e3e1f00c114aaa7c2 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Wed, 31 Aug 2022 23:04:30 +0200 Subject: [PATCH 06/10] import future annotations to make mypy work on py38 --- tests/conftest.py | 2 ++ tests/test_blag.py | 2 ++ tests/test_devserver.py | 2 ++ tests/test_markdown.py | 2 ++ tests/test_quickstart.py | 3 +++ tests/test_templates.py | 2 ++ tests/test_version.py | 3 +++ 7 files changed, 16 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index f774841..0b530e7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,5 @@ +# remove when we don't support py38 anymore +from __future__ import annotations from argparse import Namespace from typing import Iterator, Callable from tempfile import TemporaryDirectory diff --git a/tests/test_blag.py b/tests/test_blag.py index d4f40ae..cb200f4 100644 --- a/tests/test_blag.py +++ b/tests/test_blag.py @@ -1,3 +1,5 @@ +# remove when we don't support py38 anymore +from __future__ import annotations from tempfile import TemporaryDirectory import os from datetime import datetime diff --git a/tests/test_devserver.py b/tests/test_devserver.py index cdbb842..9699f91 100644 --- a/tests/test_devserver.py +++ b/tests/test_devserver.py @@ -1,3 +1,5 @@ +# remove when we don't support py38 anymore +from __future__ import annotations import time import threading from argparse import Namespace diff --git a/tests/test_markdown.py b/tests/test_markdown.py index 4cee8c2..670a4e1 100644 --- a/tests/test_markdown.py +++ b/tests/test_markdown.py @@ -1,3 +1,5 @@ +# remove when we don't support py38 anymore +from __future__ import annotations from datetime import datetime from typing import Any diff --git a/tests/test_quickstart.py b/tests/test_quickstart.py index ada2844..ac6ccaf 100644 --- a/tests/test_quickstart.py +++ b/tests/test_quickstart.py @@ -1,3 +1,6 @@ +# remove when we don't support py38 anymore +from __future__ import annotations + from pytest import MonkeyPatch from blag.quickstart import get_input, quickstart diff --git a/tests/test_templates.py b/tests/test_templates.py index dc3395a..b25388e 100644 --- a/tests/test_templates.py +++ b/tests/test_templates.py @@ -1,3 +1,5 @@ +# remove when we don't support py38 anymore +from __future__ import annotations import datetime from jinja2 import Template diff --git a/tests/test_version.py b/tests/test_version.py index d978f67..b772f4b 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -1,3 +1,6 @@ +# remove when we don't support py38 anymore +from __future__ import annotations + import blag From 875fd85d659fb10d978419ccad4c2584589f96f0 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Thu, 1 Sep 2022 10:51:32 +0200 Subject: [PATCH 07/10] removed types from docstrings now that we have type annotations. note, the return value still needs to be in there. --- blag/blag.py | 48 ++++++++++++++++++++++++---------------------- blag/devserver.py | 8 +++++--- blag/markdown.py | 8 +++++--- blag/quickstart.py | 8 +++++--- 4 files changed, 40 insertions(+), 32 deletions(-) diff --git a/blag/blag.py b/blag/blag.py index 8a9ce81..60fdb33 100644 --- a/blag/blag.py +++ b/blag/blag.py @@ -43,7 +43,7 @@ def main(arguments: list[str] | None = None) -> None: Parameters ---------- - arguments : list[str] + arguments optional parameters, used for testing """ @@ -60,7 +60,7 @@ def parse_args(args: list[str] | None = None) -> argparse.Namespace: Parameters ---------- - args : List[str] + args optional parameters, used for testing Returns @@ -149,7 +149,7 @@ def get_config(configfile: str) -> configparser.SectionProxy: Parameters ---------- - configfile : str + configfile path to configuration file @@ -188,8 +188,9 @@ def environment_factory( Parameters ---------- - template_dir : str - globals_ : dict[str, object] + template_dir + directory containing the templates + globals_ Returns ------- @@ -216,7 +217,7 @@ def build(args: argparse.Namespace) -> None: Parameters ---------- - args : argparse.Namespace + args """ os.makedirs(f'{args.output_dir}', exist_ok=True) @@ -290,16 +291,17 @@ def process_markdown( Parameters ---------- - convertibles : list[tuple[str, str]] + convertibles relative paths to markdown- (src) html- (dest) files - input_dir : str - output_dir : str - page_template, archive_template : jinja2 template + input_dir + output_dir + page_template, archive_template templats for pages and articles Returns ------- - articles, pages : list[tuple[str, dict[str, Any]]] + list[tuple[str, dict[str, Any]]], list[tuple[str, dict[str, Any]]] + articles and pages """ logger.info("Converting Markdown files...") @@ -346,17 +348,17 @@ def generate_feed( Parameters ---------- - articles : list[tuple[str, dict[str, Any]]] + articles list of relative output path and article dictionary - output_dir : str + output_dir where the feed is stored - base_url : str + base_url base url - blog_title : str + blog_title blog title - blog_description : str + blog_description blog description - blog_author : str + blog_author blog author """ @@ -395,11 +397,11 @@ def generate_archive( Parameters ---------- - articles : list[tuple[str, dict[str, Any]]] + articles List of articles. Each article has the destination path and a dictionary with the content. - template : jinja2.Template instance - output_dir : str + template + output_dir """ archive = [] @@ -423,11 +425,11 @@ def generate_tags( Parameters ---------- - articles : list[tuple[str, dict[str, Any]]] + articles List of articles. Each article has the destination path and a dictionary with the content. - tags_template, tag_template : jinja2.Template instance - output_dir : str + tags_template, tag_template + output_dir """ logger.info("Generating Tag-pages.") diff --git a/blag/devserver.py b/blag/devserver.py index 1f2a8be..2c31046 100644 --- a/blag/devserver.py +++ b/blag/devserver.py @@ -30,7 +30,7 @@ def get_last_modified(dirs: list[str]) -> float: Parameters ---------- - dirs : list[str] + dirs list of directories to search Returns @@ -63,7 +63,8 @@ def autoreload(args: argparse.Namespace) -> None: Parameters ---------- - args : argparse.Namespace + args + contains the input-, template- and static dir """ dirs = [args.input_dir, args.template_dir, args.static_dir] @@ -85,7 +86,8 @@ def serve(args: argparse.Namespace) -> None: Parameters ---------- - args : arparse.Namespace + args + contains the input-, template- and static dir """ httpd = HTTPServer(('', 8000), partial(SimpleHTTPRequestHandler, diff --git a/blag/markdown.py b/blag/markdown.py index efe0926..fa6d908 100644 --- a/blag/markdown.py +++ b/blag/markdown.py @@ -54,12 +54,14 @@ def convert_markdown( Parameters ---------- - md : markdown.Markdown instance - markdown : str + md + the Markdown instance + markdown + the markdown text that should be converted Returns ------- - str, dict[str, str] : + str, dict[str, str] html and metadata """ diff --git a/blag/quickstart.py b/blag/quickstart.py index cbdcaac..4f4678d 100644 --- a/blag/quickstart.py +++ b/blag/quickstart.py @@ -16,14 +16,15 @@ def get_input(question: str, default: str) -> str: Parameters ---------- - question : str + question the question the user is presented - default : str + default the default value that will be used if no answer was given Returns ------- str + the answer """ reply = input(f"{question} [{default}]: ") @@ -40,7 +41,8 @@ def quickstart(args: argparse.Namespace | None) -> None: Parameters ---------- - args : argparse.Namespace + args + not used """ base_url = get_input( From 7b6b219cdf52b003f2d5d089927a02e3eb425571 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Thu, 1 Sep 2022 11:11:53 +0200 Subject: [PATCH 08/10] mark autoreload as NoReturn --- blag/devserver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/blag/devserver.py b/blag/devserver.py index 2c31046..42feac0 100644 --- a/blag/devserver.py +++ b/blag/devserver.py @@ -8,6 +8,7 @@ site if necessary. # remove when we don't support py38 anymore from __future__ import annotations +from typing import NoReturn import os import logging import time @@ -51,7 +52,7 @@ def get_last_modified(dirs: list[str]) -> float: return last_mtime -def autoreload(args: argparse.Namespace) -> None: +def autoreload(args: argparse.Namespace) -> NoReturn: """Start the autoreloader. This method monitors the given directories for changes (i.e. the From 87d619cc1c057e39c1a534840f4889a8eb0aa6c9 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Thu, 1 Sep 2022 12:41:25 +0200 Subject: [PATCH 09/10] Re-formatted the code --- blag/__init__.py | 2 +- blag/blag.py | 99 +++++++++++++++++++++++------------------ blag/devserver.py | 6 ++- blag/markdown.py | 20 +++++---- blag/quickstart.py | 8 ++-- tests/conftest.py | 12 +++-- tests/test_blag.py | 52 +++++++++++----------- tests/test_devserver.py | 20 ++++++--- tests/test_markdown.py | 83 +++++++++++++++++++--------------- 9 files changed, 167 insertions(+), 135 deletions(-) diff --git a/blag/__init__.py b/blag/__init__.py index 318e4f3..189ce64 100644 --- a/blag/__init__.py +++ b/blag/__init__.py @@ -1 +1 @@ -from blag.version import __VERSION__ as __VERSION__ # noqa +from blag.version import __VERSION__ as __VERSION__ # noqa diff --git a/blag/blag.py b/blag/blag.py index 60fdb33..490348b 100644 --- a/blag/blag.py +++ b/blag/blag.py @@ -30,8 +30,8 @@ from blag.quickstart import quickstart logger = logging.getLogger(__name__) logging.basicConfig( - level=logging.INFO, - format='%(asctime)s %(levelname)s %(name)s %(message)s', + level=logging.INFO, + format='%(asctime)s %(levelname)s %(name)s %(message)s', ) @@ -72,10 +72,11 @@ def parse_args(args: list[str] | None = None) -> argparse.Namespace: parser.add_argument( '--version', action='version', - version='%(prog)s '+__VERSION__, + version='%(prog)s ' + __VERSION__, ) parser.add_argument( - '-v', '--verbose', + '-v', + '--verbose', action='store_true', help='Verbose output.', ) @@ -84,61 +85,69 @@ def parse_args(args: list[str] | None = None) -> argparse.Namespace: commands.required = True build_parser = commands.add_parser( - 'build', - help='Build website.', + 'build', + help='Build website.', ) build_parser.set_defaults(func=build) build_parser.add_argument( - '-i', '--input-dir', - default='content', - help='Input directory (default: content)', + '-i', + '--input-dir', + default='content', + help='Input directory (default: content)', ) build_parser.add_argument( - '-o', '--output-dir', - default='build', - help='Ouptut directory (default: build)', + '-o', + '--output-dir', + default='build', + help='Ouptut directory (default: build)', ) build_parser.add_argument( - '-t', '--template-dir', - default='templates', - help='Template directory (default: templates)', + '-t', + '--template-dir', + default='templates', + help='Template directory (default: templates)', ) build_parser.add_argument( - '-s', '--static-dir', - default='static', - help='Static directory (default: static)', + '-s', + '--static-dir', + default='static', + help='Static directory (default: static)', ) quickstart_parser = commands.add_parser( - 'quickstart', - help="Quickstart blag, creating necessary configuration.", + 'quickstart', + help="Quickstart blag, creating necessary configuration.", ) quickstart_parser.set_defaults(func=quickstart) serve_parser = commands.add_parser( - 'serve', - help="Start development server.", + 'serve', + help="Start development server.", ) serve_parser.set_defaults(func=serve) serve_parser.add_argument( - '-i', '--input-dir', - default='content', - help='Input directory (default: content)', + '-i', + '--input-dir', + default='content', + help='Input directory (default: content)', ) serve_parser.add_argument( - '-o', '--output-dir', - default='build', - help='Ouptut directory (default: build)', + '-o', + '--output-dir', + default='build', + help='Ouptut directory (default: build)', ) serve_parser.add_argument( - '-t', '--template-dir', - default='templates', - help='Template directory (default: templates)', + '-t', + '--template-dir', + default='templates', + help='Template directory (default: templates)', ) serve_parser.add_argument( - '-s', '--static-dir', - default='static', - help='Static directory (default: static)', + '-s', + '--static-dir', + default='static', + help='Static directory (default: static)', ) return parser.parse_args(args) @@ -224,8 +233,9 @@ def build(args: argparse.Namespace) -> None: convertibles = [] for root, dirnames, filenames in os.walk(args.input_dir): for filename in filenames: - rel_src = os.path.relpath(f'{root}/{filename}', - start=args.input_dir) + rel_src = os.path.relpath( + f'{root}/{filename}', start=args.input_dir + ) # all non-markdown files are just copied over, the markdown # files are converted to html if rel_src.endswith('.md'): @@ -233,8 +243,10 @@ def build(args: argparse.Namespace) -> None: rel_dst = rel_dst[:-3] + '.html' convertibles.append((rel_src, rel_dst)) else: - shutil.copy(f'{args.input_dir}/{rel_src}', - f'{args.output_dir}/{rel_src}') + shutil.copy( + f'{args.input_dir}/{rel_src}', + f'{args.output_dir}/{rel_src}', + ) for dirname in dirnames: # all directories are copied into the output directory path = os.path.relpath(f'{root}/{dirname}', start=args.input_dir) @@ -264,7 +276,8 @@ def build(args: argparse.Namespace) -> None: ) generate_feed( - articles, args.output_dir, + articles, + args.output_dir, base_url=config['base_url'], blog_title=config['title'], blog_description=config['description'], @@ -364,10 +377,10 @@ def generate_feed( """ logger.info('Generating Atom feed.') feed = feedgenerator.Atom1Feed( - link=base_url, - title=blog_title, - description=blog_description, - feed_url=base_url + 'atom.xml', + link=base_url, + title=blog_title, + description=blog_description, + feed_url=base_url + 'atom.xml', ) for dst, context in articles: diff --git a/blag/devserver.py b/blag/devserver.py index 42feac0..a57ad71 100644 --- a/blag/devserver.py +++ b/blag/devserver.py @@ -91,8 +91,10 @@ def serve(args: argparse.Namespace) -> None: contains the input-, template- and static dir """ - httpd = HTTPServer(('', 8000), partial(SimpleHTTPRequestHandler, - directory=args.output_dir)) + httpd = HTTPServer( + ('', 8000), + partial(SimpleHTTPRequestHandler, directory=args.output_dir), + ) proc = multiprocessing.Process(target=autoreload, args=(args,)) proc.start() logger.info("\n\n Devserver Started -- visit http://localhost:8000\n") diff --git a/blag/markdown.py b/blag/markdown.py index fa6d908..6b055ed 100644 --- a/blag/markdown.py +++ b/blag/markdown.py @@ -33,8 +33,11 @@ def markdown_factory() -> Markdown: """ md = Markdown( extensions=[ - 'meta', 'fenced_code', 'codehilite', 'smarty', - MarkdownLinkExtension() + 'meta', + 'fenced_code', + 'codehilite', + 'smarty', + MarkdownLinkExtension(), ], output_format='html', ) @@ -91,9 +94,7 @@ def convert_markdown( class MarkdownLinkTreeprocessor(Treeprocessor): - """Converts relative links to .md files to .html - - """ + """Converts relative links to .md files to .html""" def run(self, root: Element) -> Element: for element in root.iter(): @@ -112,7 +113,7 @@ class MarkdownLinkTreeprocessor(Treeprocessor): logger.debug( f'{url}: {scheme=} {netloc=} {path=} {query=} {fragment=}' ) - if (scheme or netloc or not path): + if scheme or netloc or not path: return url if path.endswith('.md'): path = path[:-3] + '.html' @@ -122,10 +123,11 @@ class MarkdownLinkTreeprocessor(Treeprocessor): 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: Markdown) -> None: md.treeprocessors.register( - MarkdownLinkTreeprocessor(md), 'mdlink', 0, + MarkdownLinkTreeprocessor(md), + 'mdlink', + 0, ) diff --git a/blag/quickstart.py b/blag/quickstart.py index 4f4678d..c27e473 100644 --- a/blag/quickstart.py +++ b/blag/quickstart.py @@ -64,10 +64,10 @@ def quickstart(args: argparse.Namespace | None) -> None: config = configparser.ConfigParser() config['main'] = { - 'base_url': base_url, - 'title': title, - 'description': description, - 'author': author, + 'base_url': base_url, + 'title': title, + 'description': description, + 'author': author, } with open('config.ini', 'w') as fh: config.write(fh) diff --git a/tests/conftest.py b/tests/conftest.py index 0b530e7..da40e05 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -50,9 +50,7 @@ def tag_template(environment: Environment) -> Iterator[Template]: @pytest.fixture def cleandir() -> Iterator[str]: - """Create a temporary workind directory and cwd. - - """ + """Create a temporary workind directory and cwd.""" config = """ [main] base_url = https://example.com/ @@ -78,9 +76,9 @@ author = a. u. thor def args(cleandir: Callable[[], Iterator[str]]) -> Iterator[Namespace]: args = Namespace( - input_dir='content', - output_dir='build', - static_dir='static', - template_dir='templates', + input_dir='content', + output_dir='build', + static_dir='static', + template_dir='templates', ) yield args diff --git a/tests/test_blag.py b/tests/test_blag.py index cb200f4..74e8512 100644 --- a/tests/test_blag.py +++ b/tests/test_blag.py @@ -28,7 +28,7 @@ def test_feed(cleandir: str) -> None: 'title': 'title1', 'date': datetime(2019, 6, 6), 'content': 'content1', - } + }, ), ( 'dest2.html', @@ -36,12 +36,18 @@ def test_feed(cleandir: str) -> None: 'title': 'title2', 'date': datetime(1980, 5, 9), 'content': 'content2', - } + }, ), ] - blag.generate_feed(articles, 'build', 'https://example.com/', - 'blog title', 'blog description', 'blog author') + blag.generate_feed( + articles, + 'build', + 'https://example.com/', + 'blog title', + 'blog description', + 'blog author', + ) with open('build/atom.xml') as fh: feed = fh.read() @@ -69,15 +75,17 @@ def test_feed(cleandir: str) -> None: def test_generate_feed_with_description(cleandir: str) -> None: # if a description is provided, it will be used as the summary in # the feed, otherwise we simply use the title of the article - articles: list[tuple[str, dict[str, Any]]] = [( - 'dest.html', - { - 'title': 'title', - 'description': 'description', - 'date': datetime(2019, 6, 6), - 'content': 'content', - } - )] + articles: list[tuple[str, dict[str, Any]]] = [ + ( + 'dest.html', + { + 'title': 'title', + 'description': 'description', + 'date': datetime(2019, 6, 6), + 'content': 'content', + }, + ) + ] blag.generate_feed(articles, 'build', ' ', ' ', ' ', ' ') with open('build/atom.xml') as fh: @@ -144,10 +152,9 @@ author = a. u. thor # a missing required config causes a sys.exit for x in 'base_url', 'title', 'description', 'author': - config2 = '\n'.join([line - for line - in config.splitlines() - if not line.startswith(x)]) + config2 = '\n'.join( + [line for line in config.splitlines() if not line.startswith(x)] + ) with TemporaryDirectory() as dir: configfile = f'{dir}/config.ini' with open(configfile, 'w') as fh: @@ -173,10 +180,7 @@ author = a. u. thor def test_environment_factory() -> None: - globals_: dict[str, object] = { - 'foo': 'bar', - 'test': 'me' - } + globals_: dict[str, object] = {'foo': 'bar', 'test': 'me'} env = blag.environment_factory(globals_=globals_) assert env.globals['foo'] == 'bar' assert env.globals['test'] == 'me' @@ -217,11 +221,7 @@ foo bar convertibles.append((str(i), str(i))) articles, pages = blag.process_markdown( - convertibles, - 'content', - 'build', - page_template, - article_template + convertibles, 'content', 'build', page_template, article_template ) assert isinstance(articles, list) diff --git a/tests/test_devserver.py b/tests/test_devserver.py index 9699f91..4b66b38 100644 --- a/tests/test_devserver.py +++ b/tests/test_devserver.py @@ -32,9 +32,11 @@ def test_autoreload_builds_immediately(args: Namespace) -> None: with open('content/test.md', 'w') as fh: fh.write('boo') - t = threading.Thread(target=devserver.autoreload, - args=(args, ), - daemon=True,) + t = threading.Thread( + target=devserver.autoreload, + args=(args,), + daemon=True, + ) t0 = devserver.get_last_modified(['build']) t.start() # try for 5 seconds... @@ -47,11 +49,15 @@ def test_autoreload_builds_immediately(args: Namespace) -> None: assert t1 > t0 -@pytest.mark.filterwarnings("ignore::pytest.PytestUnhandledThreadExceptionWarning") # noqa +@pytest.mark.filterwarnings( + "ignore::pytest.PytestUnhandledThreadExceptionWarning" +) def test_autoreload(args: Namespace) -> None: - t = threading.Thread(target=devserver.autoreload, - args=(args, ), - daemon=True,) + t = threading.Thread( + target=devserver.autoreload, + args=(args,), + daemon=True, + ) t.start() t0 = devserver.get_last_modified(['build']) diff --git a/tests/test_markdown.py b/tests/test_markdown.py index 670a4e1..816f310 100644 --- a/tests/test_markdown.py +++ b/tests/test_markdown.py @@ -9,54 +9,65 @@ import markdown from blag.markdown import convert_markdown, markdown_factory -@pytest.mark.parametrize("input_, expected", [ - # inline - ('[test](test.md)', 'test.html'), - ('[test](test.md "test")', 'test.html'), - ('[test](a/test.md)', 'a/test.html'), - ('[test](a/test.md "test")', 'a/test.html'), - ('[test](/test.md)', '/test.html'), - ('[test](/test.md "test")', '/test.html'), - ('[test](/a/test.md)', '/a/test.html'), - ('[test](/a/test.md "test")', '/a/test.html'), - # reference - ('[test][]\n[test]: test.md ''', 'test.html'), - ('[test][]\n[test]: test.md "test"', 'test.html'), - ('[test][]\n[test]: a/test.md', 'a/test.html'), - ('[test][]\n[test]: a/test.md "test"', 'a/test.html'), - ('[test][]\n[test]: /test.md', '/test.html'), - ('[test][]\n[test]: /test.md "test"', '/test.html'), - ('[test][]\n[test]: /a/test.md', '/a/test.html'), - ('[test][]\n[test]: /a/test.md "test"', '/a/test.html'), -]) +@pytest.mark.parametrize( + "input_, expected", + [ + # inline + ('[test](test.md)', 'test.html'), + ('[test](test.md "test")', 'test.html'), + ('[test](a/test.md)', 'a/test.html'), + ('[test](a/test.md "test")', 'a/test.html'), + ('[test](/test.md)', '/test.html'), + ('[test](/test.md "test")', '/test.html'), + ('[test](/a/test.md)', '/a/test.html'), + ('[test](/a/test.md "test")', '/a/test.html'), + # reference + ('[test][]\n[test]: test.md ' '', 'test.html'), + ('[test][]\n[test]: test.md "test"', 'test.html'), + ('[test][]\n[test]: a/test.md', 'a/test.html'), + ('[test][]\n[test]: a/test.md "test"', 'a/test.html'), + ('[test][]\n[test]: /test.md', '/test.html'), + ('[test][]\n[test]: /test.md "test"', '/test.html'), + ('[test][]\n[test]: /a/test.md', '/a/test.html'), + ('[test][]\n[test]: /a/test.md "test"', '/a/test.html'), + ], +) def test_convert_markdown_links(input_: str, expected: str) -> None: md = markdown_factory() html, _ = convert_markdown(md, input_) assert expected in html -@pytest.mark.parametrize("input_, expected", [ - # scheme - ('[test](https://)', 'https://'), - # netloc - ('[test](//test.md)', '//test.md'), - # no path - ('[test]()', ''), -]) +@pytest.mark.parametrize( + "input_, expected", + [ + # scheme + ('[test](https://)', 'https://'), + # netloc + ('[test](//test.md)', '//test.md'), + # no path + ('[test]()', ''), + ], +) def test_dont_convert_normal_links(input_: str, expected: str) -> None: md = markdown_factory() html, _ = convert_markdown(md, input_) assert expected in html -@pytest.mark.parametrize("input_, expected", [ - ('foo: bar', {'foo': 'bar'}), - ('foo: those are several words', {'foo': 'those are several words'}), - ('tags: this, is, a, test\n', {'tags': ['this', 'is', 'a', 'test']}), - ('tags: this, IS, a, test', {'tags': ['this', 'is', 'a', 'test']}), - ('date: 2020-01-01 12:10', {'date': - datetime(2020, 1, 1, 12, 10).astimezone()}), -]) +@pytest.mark.parametrize( + "input_, expected", + [ + ('foo: bar', {'foo': 'bar'}), + ('foo: those are several words', {'foo': 'those are several words'}), + ('tags: this, is, a, test\n', {'tags': ['this', 'is', 'a', 'test']}), + ('tags: this, IS, a, test', {'tags': ['this', 'is', 'a', 'test']}), + ( + 'date: 2020-01-01 12:10', + {'date': datetime(2020, 1, 1, 12, 10).astimezone()}, + ), + ], +) def test_convert_metadata(input_: str, expected: dict[str, Any]) -> None: md = markdown_factory() _, meta = convert_markdown(md, input_) From 01507e9de6e56ad3b59861f581aaf95702d40966 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Thu, 1 Sep 2022 18:51:53 +0200 Subject: [PATCH 10/10] updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3009093..307bf2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## unreleased +* added type hints and mypy --strict to test suite * improved default template * updated dependencies: * markdown 3.4.1