From ad0ab1a0feee702c3dbfe3322c576736fbef7641 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Aug 2022 14:09:45 +0000 Subject: [PATCH 001/123] Bump flake8 from 5.0.2 to 5.0.4 Bumps [flake8](https://github.com/pycqa/flake8) from 5.0.2 to 5.0.4. - [Release notes](https://github.com/pycqa/flake8/releases) - [Commits](https://github.com/pycqa/flake8/compare/5.0.2...5.0.4) --- updated-dependencies: - dependency-name: flake8 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 1292db9..7d6b6ab 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,4 +3,4 @@ twine==4.0.1 wheel==0.37.1 pytest==7.1.2 pytest-cov==3.0.0 -flake8==5.0.2 +flake8==5.0.4 From f8cd915ac23bf4a9f59f782812b4ad071aeff605 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Aug 2022 14:09:03 +0000 Subject: [PATCH 002/123] Bump pygments from 2.12.0 to 2.13.0 Bumps [pygments](https://github.com/pygments/pygments) from 2.12.0 to 2.13.0. - [Release notes](https://github.com/pygments/pygments/releases) - [Changelog](https://github.com/pygments/pygments/blob/master/CHANGES) - [Commits](https://github.com/pygments/pygments/compare/2.12.0...2.13.0) --- updated-dependencies: - dependency-name: pygments dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c964cc4..58367ba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ markdown==3.4.1 feedgenerator==2.0.0 jinja2==3.1.2 -pygments==2.12.0 +pygments==2.13.0 From 322154041ad6ac48d26e409512445993c59fd452 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Wed, 31 Aug 2022 17:17:02 +0200 Subject: [PATCH 003/123] 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 004/123] 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 005/123] 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 006/123] 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 007/123] 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 008/123] 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 009/123] 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 010/123] 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 011/123] 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 012/123] 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 From a224840b28d3b64ae0699df7e9540de0cf1aa94a Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Thu, 1 Sep 2022 19:03:07 +0200 Subject: [PATCH 013/123] added .mypy_cache to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4780a71..1f8d906 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,6 @@ docs/api/ htmlcov/ .coverage +.mypy_cache venv/ From fc4eb0f463a457fb7983f93bae3d8fe20b27081b Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Thu, 1 Sep 2022 19:03:27 +0200 Subject: [PATCH 014/123] delete .mypy_cache on clean --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 87cec44..d6242d0 100644 --- a/Makefile +++ b/Makefile @@ -55,3 +55,4 @@ clean: find . -type d -name __pycache__ -delete # coverage rm -rf htmlcov .coverage + rm -rf .mypy_cache From 73672598e5f143dfaddf8b8c419daaf4e2a2ca9a Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Thu, 1 Sep 2022 19:03:43 +0200 Subject: [PATCH 015/123] bumped version to prepare a new release --- CHANGELOG.md | 2 +- blag/version.py | 2 +- debian/changelog | 7 +++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7b4dcf..82294ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## unreleased +## [1.4.0] - 2022-09-01 * added type hints and mypy --strict to test suite * improved default template diff --git a/blag/version.py b/blag/version.py index 41c78c8..dec3ec6 100644 --- a/blag/version.py +++ b/blag/version.py @@ -1 +1 @@ -__VERSION__ = '1.3.2' +__VERSION__ = '1.4.0' diff --git a/debian/changelog b/debian/changelog index 8f252c0..81c5e65 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +blag (1.4.0) unstable; urgency=medium + + * added type hints and mypy --strict to test suite + * improved default template + + -- Bastian Venthur Thu, 01 Sep 2022 18:59:11 +0200 + blag (1.3.2) unstable; urgency=medium * Added --version option From a4aa8045ebc4fa54ae3e0a3b96e07d97807edda7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Sep 2022 14:15:31 +0000 Subject: [PATCH 016/123] Bump pytest from 7.1.2 to 7.1.3 Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.1.2 to 7.1.3. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.1.2...7.1.3) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 86eea69..3b8eed5 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,7 +1,7 @@ sphinx==5.1.1 twine==4.0.1 wheel==0.37.1 -pytest==7.1.2 +pytest==7.1.3 pytest-cov==3.0.0 flake8==5.0.4 mypy==0.971 From 118b20ef33113ffb6079ddba02d30ea628d8082c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Sep 2022 14:10:38 +0000 Subject: [PATCH 017/123] Bump types-markdown from 3.4.1 to 3.4.2 Bumps [types-markdown](https://github.com/python/typeshed) from 3.4.1 to 3.4.2. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-markdown dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 86eea69..35123b9 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -5,4 +5,4 @@ pytest==7.1.2 pytest-cov==3.0.0 flake8==5.0.4 mypy==0.971 -types-markdown==3.4.1 +types-markdown==3.4.2 From 2d4f3334cf5e0b3b9c6a13e50b68241cc938bd57 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Sep 2022 14:15:49 +0000 Subject: [PATCH 018/123] Bump sphinx from 5.1.1 to 5.2.1 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 5.1.1 to 5.2.1. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/5.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v5.1.1...v5.2.1) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 86eea69..2aec96e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,4 @@ -sphinx==5.1.1 +sphinx==5.2.1 twine==4.0.1 wheel==0.37.1 pytest==7.1.2 From 31fc3d688bffc779b9dd338ec7fa32a4ced57c15 Mon Sep 17 00:00:00 2001 From: Debian Janitor Date: Thu, 29 Sep 2022 01:12:53 +0000 Subject: [PATCH 019/123] Apply multi-arch hints. + blag-doc: Add Multi-Arch: foreign. Changes-By: apply-multiarch-hints --- debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/control b/debian/control index 390a92a..485140f 100644 --- a/debian/control +++ b/debian/control @@ -46,6 +46,7 @@ Architecture: all Depends: ${sphinxdoc:Depends}, ${misc:Depends}, +Multi-Arch: foreign Description: Blog-aware, static site generator (documentation) Blag is a blog-aware, static site generator, written in Python. It supports the following features: From c06424cfdf9cd331f567b2acc8c9860bc6a04b05 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Thu, 29 Sep 2022 20:43:40 +0200 Subject: [PATCH 020/123] bumped version, updated changelogs --- CHANGELOG.md | 4 ++++ blag/version.py | 2 +- debian/changelog | 6 ++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82294ed..52b622a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [1.4.1] - 2022-09-29 + +* applied multi-arch fix by debian-janitor + ## [1.4.0] - 2022-09-01 * added type hints and mypy --strict to test suite diff --git a/blag/version.py b/blag/version.py index dec3ec6..6c9ba3b 100644 --- a/blag/version.py +++ b/blag/version.py @@ -1 +1 @@ -__VERSION__ = '1.4.0' +__VERSION__ = '1.4.1' diff --git a/debian/changelog b/debian/changelog index 81c5e65..e840a6d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +blag (1.4.1) unstable; urgency=medium + + * Applied multi-arch fix by debian-janitor + + -- Bastian Venthur Thu, 29 Sep 2022 20:41:28 +0200 + blag (1.4.0) unstable; urgency=medium * added type hints and mypy --strict to test suite From 34eb413434d83b249d20df326f02a932993959af Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Thu, 29 Sep 2022 20:47:14 +0200 Subject: [PATCH 021/123] updated changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52b622a..36db670 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## [1.4.1] - 2022-09-29 * applied multi-arch fix by debian-janitor +* updated dependencies: + * pytest 7.1.3 ## [1.4.0] - 2022-09-01 From a960db495297c641fe7986b163813881405a0d91 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Oct 2022 14:13:26 +0000 Subject: [PATCH 022/123] Bump pytest-cov from 3.0.0 to 4.0.0 Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 3.0.0 to 4.0.0. - [Release notes](https://github.com/pytest-dev/pytest-cov/releases) - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v3.0.0...v4.0.0) --- updated-dependencies: - dependency-name: pytest-cov dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 4d08133..6390bdd 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,7 +2,7 @@ sphinx==5.2.1 twine==4.0.1 wheel==0.37.1 pytest==7.1.3 -pytest-cov==3.0.0 +pytest-cov==4.0.0 flake8==5.0.4 mypy==0.971 types-markdown==3.4.2 From e317f80d1cfd96b55e8bc0bcb1e1e0f837008d4a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Oct 2022 15:07:12 +0000 Subject: [PATCH 023/123] Bump types-markdown from 3.4.2 to 3.4.2.1 Bumps [types-markdown](https://github.com/python/typeshed) from 3.4.2 to 3.4.2.1. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-markdown dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 4d08133..c51b0e1 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -5,4 +5,4 @@ pytest==7.1.3 pytest-cov==3.0.0 flake8==5.0.4 mypy==0.971 -types-markdown==3.4.2 +types-markdown==3.4.2.1 From 642e31c357c363f94e1376b22ff00d82891c4d34 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Oct 2022 15:07:18 +0000 Subject: [PATCH 024/123] Bump mypy from 0.971 to 0.982 Bumps [mypy](https://github.com/python/mypy) from 0.971 to 0.982. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.971...v0.982) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 4d08133..e834b41 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,5 +4,5 @@ wheel==0.37.1 pytest==7.1.3 pytest-cov==3.0.0 flake8==5.0.4 -mypy==0.971 +mypy==0.982 types-markdown==3.4.2 From e45e59d5684512319b857b5fdb7170c964556cd8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Oct 2022 14:38:34 +0000 Subject: [PATCH 025/123] Bump sphinx from 5.2.1 to 5.3.0 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 5.2.1 to 5.3.0. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/master/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v5.2.1...v5.3.0) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 4d08133..375a4f4 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,4 @@ -sphinx==5.2.1 +sphinx==5.3.0 twine==4.0.1 wheel==0.37.1 pytest==7.1.3 From d8d2f4f5d64964ab193885f05ec5029e58a454e0 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Sat, 29 Oct 2022 13:26:42 +0200 Subject: [PATCH 026/123] added python 3.11 to test suite --- .github/workflows/python-package.yaml | 1 + CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/python-package.yaml b/.github/workflows/python-package.yaml index cb0b346..3a4ed80 100644 --- a/.github/workflows/python-package.yaml +++ b/.github/workflows/python-package.yaml @@ -20,6 +20,7 @@ jobs: - "3.8" - "3.9" - "3.10" + - "3.11" steps: - uses: actions/checkout@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 454493d..240b9ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [unreleased] +* added python 3.11 to test suite * updated dependencies: * mypy 0.982 * types-markdown 3.4.2.1 From c3993f5111459d9e078b07d78e35616067db1008 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Tue, 15 Nov 2022 21:11:15 +0100 Subject: [PATCH 027/123] split mypy and lint from tests and run them only on linux/latest stable python --- .github/workflows/python-package.yaml | 32 ++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yaml b/.github/workflows/python-package.yaml index 3a4ed80..58ec755 100644 --- a/.github/workflows/python-package.yaml +++ b/.github/workflows/python-package.yaml @@ -26,7 +26,7 @@ jobs: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -34,10 +34,40 @@ jobs: run: | make test + lint: + name: Lint + runs-on: ubuntu-latest + + strategy: + fail-fast: false + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.x" + - name: Run linter run: | make lint + mypy: + name: mypy + runs-on: ubuntu-latest + + strategy: + fail-fast: false + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.x" + - name: Run mypy run: | make mypy From e168a0e0d3f6c1a239727dc35cf028fd5f9ae313 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Tue, 15 Nov 2022 21:21:03 +0100 Subject: [PATCH 028/123] remove fail-fast for non-matrix, seperate setup venv --- .github/workflows/python-package.yaml | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/python-package.yaml b/.github/workflows/python-package.yaml index 58ec755..1e9ea0c 100644 --- a/.github/workflows/python-package.yaml +++ b/.github/workflows/python-package.yaml @@ -30,17 +30,17 @@ jobs: with: python-version: ${{ matrix.python-version }} + - name: Setup virtual environment + run: | + make venv + - name: Run tests run: | make test lint: - name: Lint runs-on: ubuntu-latest - strategy: - fail-fast: false - steps: - uses: actions/checkout@v2 @@ -49,17 +49,17 @@ jobs: with: python-version: "3.x" + - name: Setup virtual environment + run: | + make venv + - name: Run linter run: | make lint mypy: - name: mypy runs-on: ubuntu-latest - strategy: - fail-fast: false - steps: - uses: actions/checkout@v2 @@ -68,6 +68,10 @@ jobs: with: python-version: "3.x" + - name: Setup virtual environment + run: | + make venv + - name: Run mypy run: | make mypy From 5902582c471efc28cf1e19eb70d0e8dd1890f4b3 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Tue, 15 Nov 2022 21:30:27 +0100 Subject: [PATCH 029/123] added gh-actions check for dependabot --- .github/dependabot.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6a7695c..4776211 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,3 +4,8 @@ updates: directory: "/" schedule: interval: "weekly" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" From 470bfba2bf2c013b53146e83869c6af960ffb4ce Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Tue, 15 Nov 2022 21:39:24 +0100 Subject: [PATCH 030/123] test --- .github/workflows/python-package.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/python-package.yaml b/.github/workflows/python-package.yaml index 1e9ea0c..83c22d5 100644 --- a/.github/workflows/python-package.yaml +++ b/.github/workflows/python-package.yaml @@ -75,3 +75,6 @@ jobs: - name: Run mypy run: | make mypy + + - run: | + make mypy From fcfa1bf06e60be5c632755d2ffd8ec8e466c4c8e Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Tue, 15 Nov 2022 21:42:58 +0100 Subject: [PATCH 031/123] removed most names --- .github/workflows/python-package.yaml | 34 +++++++-------------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/.github/workflows/python-package.yaml b/.github/workflows/python-package.yaml index 83c22d5..b5356ea 100644 --- a/.github/workflows/python-package.yaml +++ b/.github/workflows/python-package.yaml @@ -24,18 +24,13 @@ jobs: steps: - uses: actions/checkout@v2 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - - name: Setup virtual environment - run: | + - run: | make venv - - - name: Run tests - run: | + - run: | make test lint: @@ -43,18 +38,13 @@ jobs: steps: - uses: actions/checkout@v2 - - - name: Set up Python - uses: actions/setup-python@v4 + - name: actions/setup-python@v4 with: python-version: "3.x" - - name: Setup virtual environment - run: | + - run: | make venv - - - name: Run linter - run: | + - run: | make lint mypy: @@ -62,19 +52,11 @@ jobs: steps: - uses: actions/checkout@v2 - - - name: Set up Python - uses: actions/setup-python@v4 + - uses: actions/setup-python@v4 with: python-version: "3.x" - - name: Setup virtual environment - run: | + - run: | make venv - - - name: Run mypy - run: | - make mypy - - run: | make mypy From f1e122cd239c39572700611ab58a87d74a6010bb Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Tue, 15 Nov 2022 21:43:43 +0100 Subject: [PATCH 032/123] fix --- .github/workflows/python-package.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yaml b/.github/workflows/python-package.yaml index b5356ea..b4f0250 100644 --- a/.github/workflows/python-package.yaml +++ b/.github/workflows/python-package.yaml @@ -38,7 +38,7 @@ jobs: steps: - uses: actions/checkout@v2 - - name: actions/setup-python@v4 + - uses: actions/setup-python@v4 with: python-version: "3.x" From 4c41d1429fe7e5d2c5b217a27272af1bce97cdf2 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Tue, 15 Nov 2022 21:47:52 +0100 Subject: [PATCH 033/123] added changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 240b9ea..b82e388 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ ## [unreleased] * added python 3.11 to test suite +* break out lint and mypy from test matrix and only run on linux- and latest + stable python to make it a bit more efficient +* added dependabot check for github actions * updated dependencies: * mypy 0.982 * types-markdown 3.4.2.1 From 33728ce713583a8b28190f261ab971bd9266d440 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Nov 2022 20:48:40 +0000 Subject: [PATCH 034/123] Bump actions/checkout from 2 to 3 Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/python-package.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-package.yaml b/.github/workflows/python-package.yaml index b4f0250..ac4c312 100644 --- a/.github/workflows/python-package.yaml +++ b/.github/workflows/python-package.yaml @@ -23,7 +23,7 @@ jobs: - "3.11" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -37,7 +37,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: "3.x" @@ -51,7 +51,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: "3.x" From 6243552de11873b6a11fa13371acae1cd2332dce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Nov 2022 14:02:34 +0000 Subject: [PATCH 035/123] Bump flake8 from 5.0.4 to 6.0.0 Bumps [flake8](https://github.com/pycqa/flake8) from 5.0.4 to 6.0.0. - [Release notes](https://github.com/pycqa/flake8/releases) - [Commits](https://github.com/pycqa/flake8/compare/5.0.4...6.0.0) --- updated-dependencies: - dependency-name: flake8 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index cb925c4..ae5ab28 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,6 +3,6 @@ twine==4.0.1 wheel==0.37.1 pytest==7.1.3 pytest-cov==4.0.0 -flake8==5.0.4 +flake8==6.0.0 mypy==0.982 types-markdown==3.4.2.1 From 0ca248ede3012e1f8a9a8287bcc7a0587d1d5533 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Dec 2022 14:02:57 +0000 Subject: [PATCH 036/123] Bump twine from 4.0.1 to 4.0.2 Bumps [twine](https://github.com/pypa/twine) from 4.0.1 to 4.0.2. - [Release notes](https://github.com/pypa/twine/releases) - [Changelog](https://github.com/pypa/twine/blob/main/docs/changelog.rst) - [Commits](https://github.com/pypa/twine/compare/4.0.1...4.0.2) --- updated-dependencies: - dependency-name: twine dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index cb925c4..3a74eb3 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,5 @@ sphinx==5.3.0 -twine==4.0.1 +twine==4.0.2 wheel==0.37.1 pytest==7.1.3 pytest-cov==4.0.0 From 352c37045a7080e4aee73b37db87fd4ed83a4912 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Fri, 9 Dec 2022 20:57:11 +0100 Subject: [PATCH 037/123] moved to pyproject toml --- .flake8 | 2 ++ CHANGELOG.md | 1 + Makefile | 19 +++++++++---- pyproject.toml | 67 ++++++++++++++++++++++++++++++++++++++++++++ requirements-dev.txt | 1 + setup.cfg | 16 ----------- setup.py | 42 --------------------------- 7 files changed, 84 insertions(+), 64 deletions(-) create mode 100644 .flake8 create mode 100644 pyproject.toml delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..2882809 --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +exclude = venv,build,docs diff --git a/CHANGELOG.md b/CHANGELOG.md index b82e388..18951a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [unreleased] +* moved to pyproject.toml * added python 3.11 to test suite * break out lint and mypy from test matrix and only run on linux- and latest stable python to make it a bit more efficient diff --git a/Makefile b/Makefile index d6242d0..5731772 100644 --- a/Makefile +++ b/Makefile @@ -14,13 +14,13 @@ endif .PHONY: all -all: lint mypy test +all: lint mypy test test-release -$(VENV): requirements.txt requirements-dev.txt setup.py +$(VENV): requirements.txt requirements-dev.txt pyproject.toml $(PY) -m venv $(VENV) $(BIN)/pip install --upgrade -r requirements.txt $(BIN)/pip install --upgrade -r requirements-dev.txt - $(BIN)/pip install -e . + $(BIN)/pip install -e .['dev'] touch $(VENV) .PHONY: test @@ -35,10 +35,17 @@ mypy: $(VENV) lint: $(VENV) $(BIN)/flake8 -.PHONY: release -release: $(VENV) +.PHONY: build +build: $(VENV) rm -rf dist - $(BIN)/python setup.py sdist bdist_wheel + $(BIN)/python3 -m build + +.PHONY: test-release +test-release: $(VENV) build + $(BIN)/twine check dist/* + +.PHONY: release +release: $(VENV) build $(BIN)/twine upload dist/* .PHONY: docs diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..836afe1 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,67 @@ +[build-system] +requires = ["setuptools>=64.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "blag" +authors = [ + { name="Bastian Venthur", email="mail@venthur.de" }, +] +description = "blog-aware, static site generator" +keywords = ["markdown", "blag", "blog", "static site generator", "cli"] +readme = "README.md" +license = { file="LICENSE" } +requires-python = ">=3.8" +dynamic = ["version"] +dependencies = [ + "markdown", + "feedgenerator", + "jinja2", + "pygments", +] + +[project.scripts] +blag = "blag.blag:main" + +[project.urls] +'Documentation' = 'https://blag.readthedocs.io/' +'Source' = 'https://github.com/venthur/blag' +'Changelog' = 'https://github.com/venthur/blag/blob/master/CHANGELOG.md' + +[project.optional-dependencies] +dev = [ + "build", + "sphinx", + "twine", + "wheel", + "pytest", + "pytest-cov", + "flake8", + "mypy", + "types-markdown", +] + +[tool.setuptools.dynamic] +version = {attr = "blag.__VERSION__" } + +[tool.setuptools] +packages = ["blag"] + +[tool.setuptools.package-data] +blag = ["templates/*"] + +[tool.pytest.ini_options] +addopts = """ + --cov=blag + --cov=tests + --cov-report=html + --cov-report=term-missing:skip-covered +""" + +[tool.mypy] +files = "blag,tests" +strict = true + +[[tool.mypy.overrides]] +module = "feedgenerator.*" +ignore_missing_imports = true diff --git a/requirements-dev.txt b/requirements-dev.txt index cb925c4..16ac3e3 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,3 +1,4 @@ +build==0.9.0 sphinx==5.3.0 twine==4.0.1 wheel==0.37.1 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 94d778c..0000000 --- a/setup.cfg +++ /dev/null @@ -1,16 +0,0 @@ -[tool:pytest] -addopts = - --cov=blag - --cov=tests - --cov-report=html - --cov-report=term-missing:skip-covered - -[flake8] -exclude = venv,build,docs - -[mypy] -files = blag,tests -strict = True - -[mypy-feedgenerator.*] -ignore_missing_imports = True diff --git a/setup.py b/setup.py deleted file mode 100644 index 2f275fa..0000000 --- a/setup.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python - -from setuptools import setup - -meta = {} -exec(open('./blag/version.py').read(), meta) -meta['long_description'] = open('./README.md').read() - -setup( - name='blag', - version=meta['__VERSION__'], - description='blog-aware, static site generator', - long_description=meta['long_description'], - long_description_content_type='text/markdown', - keywords='markdown blag blog static site generator cli', - author='Bastian Venthur', - author_email='mail@venthur.de', - url='https://github.com/venthur/blag', - project_urls={ - 'Documentation': 'https://blag.readthedocs.io/', - 'Source': 'https://github.com/venthur/blag', - 'Changelog': - 'https://github.com/venthur/blag/blob/master/CHANGELOG.md', - }, - python_requires='>=3.8', - package_data={ - 'blag': ['templates/*'], - }, - install_requires=[ - 'markdown', - 'feedgenerator', - 'jinja2', - 'pygments', - ], - packages=['blag'], - entry_points={ - 'console_scripts': [ - 'blag = blag.blag:main' - ] - }, - license='MIT', -) From fe43288c8c6f8cfa6af83c14ae2af105bbccd14f Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Fri, 9 Dec 2022 21:03:46 +0100 Subject: [PATCH 038/123] added debian dependency and test-release target for gh action --- .github/workflows/python-package.yaml | 15 +++++++++++++++ debian/control | 1 + 2 files changed, 16 insertions(+) diff --git a/.github/workflows/python-package.yaml b/.github/workflows/python-package.yaml index ac4c312..6191d28 100644 --- a/.github/workflows/python-package.yaml +++ b/.github/workflows/python-package.yaml @@ -60,3 +60,18 @@ jobs: make venv - run: | make mypy + + + test-release: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: "3.x" + + - run: | + make venv + - run: | + make test-release diff --git a/debian/control b/debian/control index 485140f..32a11f6 100644 --- a/debian/control +++ b/debian/control @@ -8,6 +8,7 @@ Build-Depends: dh-sequence-sphinxdoc, dh-sequence-python3, dh-python, + pybuild-plugin-pyproject, python3-setuptools, python3-all, python3-markdown, From f6c18d381983e787aa58a652f31d91843b94d577 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Mar 2023 14:58:58 +0000 Subject: [PATCH 039/123] Bump wheel from 0.37.1 to 0.40.0 Bumps [wheel](https://github.com/pypa/wheel) from 0.37.1 to 0.40.0. - [Release notes](https://github.com/pypa/wheel/releases) - [Changelog](https://github.com/pypa/wheel/blob/main/docs/news.rst) - [Commits](https://github.com/pypa/wheel/compare/0.37.1...0.40.0) --- updated-dependencies: - dependency-name: wheel dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index cb925c4..c7978eb 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,6 @@ sphinx==5.3.0 twine==4.0.1 -wheel==0.37.1 +wheel==0.40.0 pytest==7.1.3 pytest-cov==4.0.0 flake8==5.0.4 From c3f0dffd11147f25d28b6f901c1c703e7f04ea25 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Apr 2023 14:57:48 +0000 Subject: [PATCH 040/123] Bump mypy from 0.982 to 1.2.0 Bumps [mypy](https://github.com/python/mypy) from 0.982 to 1.2.0. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.982...v1.2.0) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index cb925c4..77404a2 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,5 +4,5 @@ wheel==0.37.1 pytest==7.1.3 pytest-cov==4.0.0 flake8==5.0.4 -mypy==0.982 +mypy==1.2.0 types-markdown==3.4.2.1 From 06d8623dd7dae992c3cdefec1267e5f4fed7b4f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Apr 2023 14:58:07 +0000 Subject: [PATCH 041/123] Bump pytest from 7.1.3 to 7.3.0 Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.1.3 to 7.3.0. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.1.3...7.3.0) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index cb925c4..de71638 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,7 +1,7 @@ sphinx==5.3.0 twine==4.0.1 wheel==0.37.1 -pytest==7.1.3 +pytest==7.3.0 pytest-cov==4.0.0 flake8==5.0.4 mypy==0.982 From 169f0f2e0bb98fd7bc969e32f9278e7322f1ed47 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Sun, 16 Apr 2023 10:30:53 +0200 Subject: [PATCH 042/123] WIP --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 5731772..13ccdc8 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,7 @@ all: lint mypy test test-release $(VENV): requirements.txt requirements-dev.txt pyproject.toml $(PY) -m venv $(VENV) + $(BIN)/pip install --upgrade pip $(BIN)/pip install --upgrade -r requirements.txt $(BIN)/pip install --upgrade -r requirements-dev.txt $(BIN)/pip install -e .['dev'] From d8ff4afddbf3a273d0914f5536dc09619339cc14 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Sun, 16 Apr 2023 10:32:04 +0200 Subject: [PATCH 043/123] Revert "WIP" This reverts commit 169f0f2e0bb98fd7bc969e32f9278e7322f1ed47. --- Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile b/Makefile index 13ccdc8..5731772 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,6 @@ all: lint mypy test test-release $(VENV): requirements.txt requirements-dev.txt pyproject.toml $(PY) -m venv $(VENV) - $(BIN)/pip install --upgrade pip $(BIN)/pip install --upgrade -r requirements.txt $(BIN)/pip install --upgrade -r requirements-dev.txt $(BIN)/pip install -e .['dev'] From e765d6b7172677960760dbd2ad9961848559fb52 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Sun, 16 Apr 2023 10:37:06 +0200 Subject: [PATCH 044/123] eclude py38 on windows --- .github/workflows/python-package.yaml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-package.yaml b/.github/workflows/python-package.yaml index 6191d28..b10540f 100644 --- a/.github/workflows/python-package.yaml +++ b/.github/workflows/python-package.yaml @@ -21,6 +21,10 @@ jobs: - "3.9" - "3.10" - "3.11" + exclude: + # 3.8 on windows fails due to some pip issue + - os: windows-latest + python-version: "3.8" steps: - uses: actions/checkout@v3 @@ -40,7 +44,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: - python-version: "3.x" + python-version: "3.x" - run: | make venv @@ -54,7 +58,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: - python-version: "3.x" + python-version: "3.x" - run: | make venv @@ -69,7 +73,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: - python-version: "3.x" + python-version: "3.x" - run: | make venv From 128d3f032d75807bfea995e36e7c425e2a0dbb0f Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Sun, 16 Apr 2023 10:49:06 +0200 Subject: [PATCH 045/123] updated changelog --- CHANGELOG.md | 2 +- blag/version.py | 2 +- debian/changelog | 20 ++++++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12361db..21bb927 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [unreleased] +## [1.5.0] - 2023-04-16 * moved to pyproject.toml * added python 3.11 to test suite diff --git a/blag/version.py b/blag/version.py index 6c9ba3b..3fe4bea 100644 --- a/blag/version.py +++ b/blag/version.py @@ -1 +1 @@ -__VERSION__ = '1.4.1' +__VERSION__ = '1.5.0' diff --git a/debian/changelog b/debian/changelog index e840a6d..fcf6839 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,23 @@ +blag (1.5.0) unstable; urgency=medium + + * new upstream version + * moved to pyproject.toml + * added python 3.11 to test suite + * break out lint and mypy from test matrix and only run on linux- and latest + stable python to make it a bit more efficient + * added dependabot check for github actions + * updated dependencies: + * mypy 1.2.0 + * types-markdown 3.4.2.1 + * pytest-cov 4.0.0 + * sphinx 5.3.0 + * pytest 7.3.0 + * flake8 6.0.0 + * twine 4.0.2 + * wheel 0.40.0 + + -- Bastian Venthur Sun, 16 Apr 2023 10:48:18 +0200 + blag (1.4.1) unstable; urgency=medium * Applied multi-arch fix by debian-janitor From a3572d414fdcd61df47da540ce5b6ff8321cbf7f Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Sun, 16 Apr 2023 10:57:44 +0200 Subject: [PATCH 046/123] added blag.templates to packages --- pyproject.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 836afe1..85dbb8e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,10 @@ dev = [ version = {attr = "blag.__VERSION__" } [tool.setuptools] -packages = ["blag"] +packages = [ + "blag", + "blag.templates", +] [tool.setuptools.package-data] blag = ["templates/*"] From 362e721d8820978cc96a3b6d803666d4a3f81cb7 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Wed, 31 May 2023 15:53:49 +0200 Subject: [PATCH 047/123] Split Archive into Index and Archive --- CHANGELOG.md | 13 +++++++++++ blag/blag.py | 43 +++++++++++++++++++++++++++++++++---- blag/templates/archive.html | 2 +- blag/templates/base.html | 1 + blag/templates/index.html | 23 ++++++++++++++++++++ docs/blag.rst | 3 ++- tests/conftest.py | 5 +++++ tests/test_blag.py | 4 +++- tests/test_templates.py | 22 ++++++++++++++++++- 9 files changed, 108 insertions(+), 8 deletions(-) create mode 100644 blag/templates/index.html diff --git a/CHANGELOG.md b/CHANGELOG.md index 21bb927..80015bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [unreleased] + +* **Breaking Change**: Split former archive page which served as index.html + into "index" and "archive", each with their own template, respectively. Index + is the landing page and shows by default only the latest 10 articles. Archive + shows the full list of articles. + + If you used custom templates, + * you should create an "index.html"-template (take blag's default one as a + starting point) + * you may want to include the new "/archive.html" link somewhere in your + navigation + ## [1.5.0] - 2023-04-16 * moved to pyproject.toml diff --git a/blag/blag.py b/blag/blag.py index 490348b..f402357 100644 --- a/blag/blag.py +++ b/blag/blag.py @@ -263,6 +263,7 @@ def build(args: argparse.Namespace) -> None: page_template = env.get_template('page.html') article_template = env.get_template('article.html') + index_template = env.get_template('index.html') archive_template = env.get_template('archive.html') tags_template = env.get_template('tags.html') tag_template = env.get_template('tag.html') @@ -283,6 +284,7 @@ def build(args: argparse.Namespace) -> None: blog_description=config['description'], blog_author=config['author'], ) + generate_index(articles, index_template, args.output_dir) generate_archive(articles, archive_template, args.output_dir) generate_tags(articles, tags_template, tag_template, args.output_dir) @@ -302,6 +304,8 @@ def process_markdown( If a markdown file has a `date` metadata field it will be recognized as article otherwise as page. + Articles are sorted by date in descending order. + Parameters ---------- convertibles @@ -309,12 +313,12 @@ def process_markdown( input_dir output_dir page_template, archive_template - templats for pages and articles + templates for pages and articles Returns ------- list[tuple[str, dict[str, Any]]], list[tuple[str, dict[str, Any]]] - articles and pages + articles and pages, articles are sorted by date in descending order. """ logger.info("Converting Markdown files...") @@ -401,12 +405,14 @@ def generate_feed( feed.write(fh, encoding='utf8') -def generate_archive( +def generate_index( articles: list[tuple[str, dict[str, Any]]], template: Template, output_dir: str, ) -> None: - """Generate the archive page. + """Generate the index page. + + This is used for the index (i.e. landing) page. Parameters ---------- @@ -428,6 +434,35 @@ def generate_archive( fh.write(result) +def generate_archive( + articles: list[tuple[str, dict[str, Any]]], + template: Template, + output_dir: str, +) -> None: + """Generate the archive page. + + This is used for the full archive. + + Parameters + ---------- + articles + List of articles. Each article has the destination path and a + dictionary with the content. + template + output_dir + + """ + archive = [] + for dst, context in articles: + entry = context.copy() + entry['dst'] = dst + archive.append(entry) + + result = template.render(dict(archive=archive)) + with open(f'{output_dir}/archive.html', 'w') as fh: + fh.write(result) + + def generate_tags( articles: list[tuple[str, dict[str, Any]]], tags_template: Template, diff --git a/blag/templates/archive.html b/blag/templates/archive.html index e8f9cca..af8c122 100644 --- a/blag/templates/archive.html +++ b/blag/templates/archive.html @@ -1,6 +1,6 @@ {% extends "base.html" %} -{% block title %}{{ site.title }}{% endblock %} +{% block title %}Archive{% endblock %} {% block content %} {% for entry in archive %} diff --git a/blag/templates/base.html b/blag/templates/base.html index fc6f822..5e952b3 100644 --- a/blag/templates/base.html +++ b/blag/templates/base.html @@ -20,6 +20,7 @@

{{ site.description }}

diff --git a/blag/templates/index.html b/blag/templates/index.html new file mode 100644 index 0000000..a88a011 --- /dev/null +++ b/blag/templates/index.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} + +{% block title %}{{ site.title }}{% endblock %} + +{% block content %} +{% for entry in archive[:10] %} + + {% if entry.title %} +

{{entry.title}}

+ + {% if entry.description %} +

— {{ entry.description }}

+ {% endif %} + + {% endif %} + +

Written on {{ entry.date.date() }}.

+ +{% endfor %} + +

all articles...

+ +{% endblock %} diff --git a/docs/blag.rst b/docs/blag.rst index b00450e..05b5263 100644 --- a/docs/blag.rst +++ b/docs/blag.rst @@ -195,7 +195,8 @@ Template Used For Variables ============ ====================================== =================== page.html pages (i.e. non-articles) site, content, meta article.html articles (i.e. blog posts) site, content, meta -archive.html archive- and landing page of the blog site, archive +index.html landing page of the blog site, archive +archive.html archive page of the blog site, archive tags.html list of tags site, tags tag.html archive of Articles with a certain tag site, archive, tag ============ ====================================== =================== diff --git a/tests/conftest.py b/tests/conftest.py index da40e05..00a0c11 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -33,6 +33,11 @@ def article_template(environment: Environment) -> Iterator[Template]: yield environment.get_template('article.html') +@pytest.fixture +def index_template(environment: Environment) -> Iterator[Template]: + yield environment.get_template('index.html') + + @pytest.fixture def archive_template(environment: Environment) -> Iterator[Template]: yield environment.get_template('archive.html') diff --git a/tests/test_blag.py b/tests/test_blag.py index 74e8512..17f840b 100644 --- a/tests/test_blag.py +++ b/tests/test_blag.py @@ -291,8 +291,10 @@ foo bar assert os.path.exists(f'{args.output_dir}/testdir/test') # ... feed assert os.path.exists(f'{args.output_dir}/atom.xml') - # ... archive + # ... index assert os.path.exists(f'{args.output_dir}/index.html') + # ... archive + assert os.path.exists(f'{args.output_dir}/archive.html') # ... tags assert os.path.exists(f'{args.output_dir}/tags/index.html') assert os.path.exists(f'{args.output_dir}/tags/foo.html') diff --git a/tests/test_templates.py b/tests/test_templates.py index b25388e..99a3890 100644 --- a/tests/test_templates.py +++ b/tests/test_templates.py @@ -27,6 +27,26 @@ def test_article(article_template: Template) -> None: assert '1980-05-09' in result +def test_index(index_template: Template) -> None: + entry = { + 'title': 'this is a title', + 'dst': 'https://example.com/link', + 'date': datetime.datetime(1980, 5, 9), + } + archive = [entry] + ctx = { + 'archive': archive, + } + result = index_template.render(ctx) + assert 'site title' in result + + assert 'this is a title' in result + assert '1980-05-09' in result + assert 'https://example.com/link' in result + + assert '/archive.html' in result + + def test_archive(archive_template: Template) -> None: entry = { 'title': 'this is a title', @@ -38,7 +58,7 @@ def test_archive(archive_template: Template) -> None: 'archive': archive, } result = archive_template.render(ctx) - assert 'site title' in result + assert 'Archive' in result assert 'this is a title' in result assert '1980-05-09' in result From c952c574b71dbcc872f6b017f962e58007aea960 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Thu, 1 Jun 2023 13:35:33 +0200 Subject: [PATCH 048/123] Don't use internal templates anymore. Blag will throw an error if a template is not found locally. The error message contains an explanation for the user on where to get the missing templates. Quickstart will generate templates for the user in the working directory. Updated tests appropriately. --- CHANGELOG.md | 15 +++++++++++++++ blag/blag.py | 43 +++++++++++++++++++++++-------------------- blag/quickstart.py | 22 ++++++++++++++++++++++ docs/blag.rst | 10 ++++------ tests/conftest.py | 11 ++++++----- tests/test_blag.py | 20 ++++++++++++++++++-- 6 files changed, 88 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21bb927..eb9cf8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## [unreleased] + +### Breaking + +* blag does not use default fallback templates anymore and will return an error + if it is unable to find required templates, e.g. in `templates/`. + + Users upgrading from older versions can either run `blag quickstart` (don't + forget to backup your `config.ini` or copy the templates from blag's + resources (the resource path is shown in the error message). + + New users are not affected as `blag quickstart` will generate the needed + templates. + + ## [1.5.0] - 2023-04-16 * moved to pyproject.toml diff --git a/blag/blag.py b/blag/blag.py index 490348b..8ef4b9b 100644 --- a/blag/blag.py +++ b/blag/blag.py @@ -16,13 +16,13 @@ import sys from jinja2 import ( Environment, - ChoiceLoader, FileSystemLoader, - PackageLoader, Template, + TemplateNotFound, ) import feedgenerator +import blag from blag.markdown import markdown_factory, convert_markdown from blag.devserver import serve from blag.version import __VERSION__ @@ -185,15 +185,14 @@ def get_config(configfile: str) -> configparser.SectionProxy: def environment_factory( - template_dir: str | None = None, + template_dir: str, globals_: dict[str, object] | None = None, ) -> Environment: """Environment factory. - Creates a Jinja2 Environment with the default templates and - additional templates from `template_dir` loaded. If `globals` are - provided, they are attached to the environment and thus available to - all contexts. + Creates a Jinja2 Environment with the templates from `template_dir` loaded. + If `globals` are provided, they are attached to the environment and thus + available to all contexts. Parameters ---------- @@ -206,13 +205,7 @@ def environment_factory( jinja2.Environment """ - # first we try the custom templates, and fall back the ones provided - # by blag - loaders: list[FileSystemLoader | PackageLoader] = [] - if template_dir: - loaders.append(FileSystemLoader([template_dir])) - loaders.append(PackageLoader('blag', 'templates')) - env = Environment(loader=ChoiceLoader(loaders)) + env = Environment(loader=FileSystemLoader(template_dir)) if globals_: env.globals = globals_ return env @@ -261,11 +254,21 @@ def build(args: argparse.Namespace) -> None: env = environment_factory(args.template_dir, dict(site=config)) - page_template = env.get_template('page.html') - article_template = env.get_template('article.html') - archive_template = env.get_template('archive.html') - tags_template = env.get_template('tags.html') - tag_template = env.get_template('tag.html') + try: + page_template = env.get_template('page.html') + article_template = env.get_template('article.html') + archive_template = env.get_template('archive.html') + tags_template = env.get_template('tags.html') + tag_template = env.get_template('tag.html') + except TemplateNotFound as exc: + tmpl = os.path.join(blag.__path__[0], 'templates') + logger.error( + f'Template "{exc.name}" not found in {args.template_dir}! ' + 'Consider running `blag quickstart` or copying the ' + f'missing template from {tmpl}.' + ) + + sys.exit(1) articles, pages = process_markdown( convertibles, @@ -309,7 +312,7 @@ def process_markdown( input_dir output_dir page_template, archive_template - templats for pages and articles + templates for pages and articles Returns ------- diff --git a/blag/quickstart.py b/blag/quickstart.py index c27e473..aee73e1 100644 --- a/blag/quickstart.py +++ b/blag/quickstart.py @@ -6,6 +6,10 @@ from __future__ import annotations import configparser import argparse +import shutil +import os + +import blag def get_input(question: str, default: str) -> str: @@ -33,6 +37,22 @@ def get_input(question: str, default: str) -> str: return reply +def copy_templates() -> None: + """Copy templates into current directory. + + It will not overwrite existing files. + + """ + print("Copying templates...") + try: + shutil.copytree( + os.path.join(blag.__path__[0], 'templates'), + 'templates', + ) + except FileExistsError: + print("Templates already exist. Skipping.") + + def quickstart(args: argparse.Namespace | None) -> None: """Quickstart. @@ -71,3 +91,5 @@ def quickstart(args: argparse.Namespace | None) -> None: } with open('config.ini', 'w') as fh: config.write(fh) + + copy_templates() diff --git a/docs/blag.rst b/docs/blag.rst index b00450e..2c7ac0c 100644 --- a/docs/blag.rst +++ b/docs/blag.rst @@ -13,7 +13,7 @@ Install blag from PyPI_ .. _pypi: https://pypi.org/project/blag/ -Run blag's quickstart command to create the configuration needed +Run blag's quickstart command to create the configuration and templates needed .. code-block:: sh @@ -40,8 +40,8 @@ If you want more separation between the static files and the markdown content, you can put all static files into the ``static`` directory. Blag will copy them over to the ``build`` directory. -If you want to customize the looks of the generated site, create a -``template`` directory and put your jinja2 templates here. +If you want to customize the look of the generated site, visit the ``template`` +directory. It contains jinja2 templates and can be modified as needed. Those directories can be changed via command line arguments. See @@ -186,9 +186,7 @@ becomes Templating ---------- -Custom templates are **optional** and stored by default in the ``templates`` -directory. blag will search the ``templates`` directory first, and fall back -to blag's default built-in templates. +Templates are stored by default in the ``templates`` directory. ============ ====================================== =================== Template Used For Variables diff --git a/tests/conftest.py b/tests/conftest.py index da40e05..4f49918 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,18 +8,18 @@ import os import pytest from jinja2 import Environment, Template -from blag import blag +from blag import blag, quickstart @pytest.fixture -def environment() -> Iterator[Environment]: +def environment(cleandir: str) -> Iterator[Environment]: site = { 'base_url': 'site base_url', 'title': 'site title', 'description': 'site description', 'author': 'site author', } - env = blag.environment_factory(globals_=dict(site=site)) + env = blag.environment_factory('templates', globals_=dict(site=site)) yield env @@ -50,7 +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 working directory and cwd.""" config = """ [main] base_url = https://example.com/ @@ -60,13 +60,14 @@ author = a. u. thor """ with TemporaryDirectory() as dir: - for d in 'content', 'build', 'static', 'templates': + for d in 'content', 'build', 'static': os.mkdir(f'{dir}/{d}') with open(f'{dir}/config.ini', 'w') as fh: fh.write(config) # change directory old_cwd = os.getcwd() os.chdir(dir) + quickstart.copy_templates() yield dir # and change back afterwards os.chdir(old_cwd) diff --git a/tests/test_blag.py b/tests/test_blag.py index 74e8512..56cd153 100644 --- a/tests/test_blag.py +++ b/tests/test_blag.py @@ -179,9 +179,9 @@ author = a. u. thor assert config_parsed['base_url'] == 'https://example.com/' -def test_environment_factory() -> None: +def test_environment_factory(cleandir: str) -> None: globals_: dict[str, object] = {'foo': 'bar', 'test': 'me'} - env = blag.environment_factory(globals_=globals_) + env = blag.environment_factory("templates", globals_=globals_) assert env.globals['foo'] == 'bar' assert env.globals['test'] == 'me' @@ -299,6 +299,22 @@ foo bar assert os.path.exists(f'{args.output_dir}/tags/bar.html') +@pytest.mark.parametrize( + 'template', + [ + 'page.html', + 'article.html', + 'archive.html', + 'tags.html', + 'tag.html', + ] +) +def test_missing_template_raises(template: str, args: Namespace) -> None: + os.remove(f'templates/{template}') + with pytest.raises(SystemExit): + blag.build(args) + + def test_main(cleandir: str) -> None: blag.main(['build']) From f97e641bd8ad8f7ba8ecef654252a8d68aca616a Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Thu, 1 Jun 2023 14:02:04 +0200 Subject: [PATCH 049/123] added test for quickstart creating the templates --- tests/test_quickstart.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_quickstart.py b/tests/test_quickstart.py index ac6ccaf..95b54c1 100644 --- a/tests/test_quickstart.py +++ b/tests/test_quickstart.py @@ -1,5 +1,6 @@ # remove when we don't support py38 anymore from __future__ import annotations +import os from pytest import MonkeyPatch @@ -27,3 +28,13 @@ def test_quickstart(cleandir: str, monkeypatch: MonkeyPatch) -> None: assert 'title = foo' in data assert 'description = foo' in data assert 'author = foo' in data + + for template in ( + "archive.html", + "article.html", + "base.html", + "page.html", + "tag.html", + "tags.html", + ): + assert os.path.exists(f'templates/{template}') From fe268516e30310a2a9abb439af9f9bb1da53bf71 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Tue, 13 Jun 2023 16:29:44 +0200 Subject: [PATCH 050/123] Improved the default theme. Closes: #48 --- CHANGELOG.md | 5 ++ blag/content/about.md | 8 ++ blag/content/hello-world.md | 51 +++++++++++ blag/content/second-post.md | 9 ++ blag/content/testpage.md | 46 ++++++++++ blag/quickstart.py | 27 +++--- blag/static/code-dark.css | 83 ++++++++++++++++++ blag/static/code-light.css | 73 ++++++++++++++++ blag/static/favicon.ico | Bin 0 -> 15406 bytes blag/static/style.css | 166 ++++++++++++++++++++++++++++++++++++ blag/templates/archive.html | 23 ++--- blag/templates/article.html | 1 - blag/templates/base.html | 20 ++++- blag/templates/page.html | 4 +- blag/templates/tag.html | 27 +++--- pyproject.toml | 8 +- tests/conftest.py | 5 +- tests/test_templates.py | 2 +- 18 files changed, 515 insertions(+), 43 deletions(-) create mode 100644 blag/content/about.md create mode 100644 blag/content/hello-world.md create mode 100644 blag/content/second-post.md create mode 100644 blag/content/testpage.md create mode 100644 blag/static/code-dark.css create mode 100644 blag/static/code-light.css create mode 100644 blag/static/favicon.ico create mode 100644 blag/static/style.css diff --git a/CHANGELOG.md b/CHANGELOG.md index eb9cf8d..adbbe72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,11 @@ New users are not affected as `blag quickstart` will generate the needed templates. +### Changed + +* blag comes now with a simple yet good looking default theme that supports + syntax highlighting and a light- and dark theme. + ## [1.5.0] - 2023-04-16 diff --git a/blag/content/about.md b/blag/content/about.md new file mode 100644 index 0000000..4f01ccf --- /dev/null +++ b/blag/content/about.md @@ -0,0 +1,8 @@ +title: About Me +description: Short description of this page. + + +## About Me + +This is a regular page, i.e. not a blog post. Feel free to delete this page, +populate it with more content or generate more [pages like this](testpage.md). diff --git a/blag/content/hello-world.md b/blag/content/hello-world.md new file mode 100644 index 0000000..7039dd9 --- /dev/null +++ b/blag/content/hello-world.md @@ -0,0 +1,51 @@ +Title: Hello World! +Description: Hello there, this is the first blog post. You should read me first. +Date: 2023-01-01 12:00 +Tags: blag, pygments + + +## Hello World + +This is an example blog post. Internally, blag differentiates between **pages** +and **articles**. Intuitively, pages are simple pages and articles are blog +posts. The decision whether a document is a page or an article is made +depending on the presence of the `date` metadata element: Any document that +contains the `date` metadata element is an article, everything else a page. + +This differentiation has consequences: + +* blag uses different templates: `page.html` and `article.html` +* only articles are collected in the Atom feed +* only articles are aggregated in the tag pages + +For more detailed information, please refer to the [documentation][doc] + +[doc]: https://blag.readthedocs.io + + +### Syntax Highlighting + +```python +def foo(bar): + """This is a docstring. + + """ + # comment + return bar +``` + +Syntax highlighting is done via [Pygments][pygments]. For code blocks, blag +generates the necessary CSS classes by default, which you can use to style your +code using CSS. It provides you with a default light- and dark theme, for more +information on how to generate a different theme, please refer to [Pygments' +documentation][pygments]. + +[pygments]: https://pygments.org + + +### Next Steps + +* Adapt the files in `templates` to your needs +* Check out the files in `static` and modify as needed +* Add some content +* Change the [favicon.ico](favicon.ico) diff --git a/blag/content/second-post.md b/blag/content/second-post.md new file mode 100644 index 0000000..5a35d4b --- /dev/null +++ b/blag/content/second-post.md @@ -0,0 +1,9 @@ +Title: Second Post +Description: This is the second blog post, so you can see how it looks like on the front page. +Date: 2023-01-02 12:00 +Tags: blag + + +## Second Post + +This page serves no purpose :) diff --git a/blag/content/testpage.md b/blag/content/testpage.md new file mode 100644 index 0000000..24fe02b --- /dev/null +++ b/blag/content/testpage.md @@ -0,0 +1,46 @@ +# This Is A Headline + +This is some **bold text** with some `code` inside. This is _some_underlined_ +text with some `code` inside. This is some text with some `code` inside. This +is some text with some `code` inside. This is some text with some `code` +inside. This is some text with some `code` inside. This is some text with some +`code` inside. This is some text with some `code` inside. + +This is some [link](https://example.com) inside the text -- it does not really +lead anywhere! This is some [link](https://example.com) inside the text -- it +does not really lead anywhere! This is some [link](https://example.com) inside +the text -- it does not really lead anywhere! + + +* some bullets +* some other + * bullets + * foo + +```python +# this is some python code + +class Foo: + + def __init__(self, foo, bar): + self.foo = foo + self.bar = bar + + def do_something(): + """This is the docstring of this method. + + """ + return foo +``` + + +## Some other headline + +This is some other text + +```makefile + +# some comment +foo: + ls -lh +``` diff --git a/blag/quickstart.py b/blag/quickstart.py index aee73e1..4d12af8 100644 --- a/blag/quickstart.py +++ b/blag/quickstart.py @@ -37,20 +37,25 @@ def get_input(question: str, default: str) -> str: return reply -def copy_templates() -> None: - """Copy templates into current directory. +def copy_default_theme() -> None: + """Copy default theme into current directory. + + The default theme contains the 'templates', 'content' and 'static' + directories shipped with blag. It will not overwrite existing files. """ - print("Copying templates...") - try: - shutil.copytree( - os.path.join(blag.__path__[0], 'templates'), - 'templates', - ) - except FileExistsError: - print("Templates already exist. Skipping.") + print("Copying default theme...") + for dir_ in 'templates', 'content', 'static': + print(f" Copying {dir_}...") + try: + shutil.copytree( + os.path.join(blag.__path__[0], dir_), + dir_, + ) + except FileExistsError: + print(f" {dir_} already exist. Skipping.") def quickstart(args: argparse.Namespace | None) -> None: @@ -92,4 +97,4 @@ def quickstart(args: argparse.Namespace | None) -> None: with open('config.ini', 'w') as fh: config.write(fh) - copy_templates() + copy_default_theme() diff --git a/blag/static/code-dark.css b/blag/static/code-dark.css new file mode 100644 index 0000000..a2af57e --- /dev/null +++ b/blag/static/code-dark.css @@ -0,0 +1,83 @@ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.hll { background-color: #49483e } +.c { color: #75715e } /* Comment */ +.err { color: #960050; background-color: #1e0010 } /* Error */ +.esc { color: #f8f8f2 } /* Escape */ +.g { color: #f8f8f2 } /* Generic */ +.k { color: #66d9ef } /* Keyword */ +.l { color: #ae81ff } /* Literal */ +.n { color: #f8f8f2 } /* Name */ +.o { color: #f92672 } /* Operator */ +.x { color: #f8f8f2 } /* Other */ +.p { color: #f8f8f2 } /* Punctuation */ +.ch { color: #75715e } /* Comment.Hashbang */ +.cm { color: #75715e } /* Comment.Multiline */ +.cp { color: #75715e } /* Comment.Preproc */ +.cpf { color: #75715e } /* Comment.PreprocFile */ +.c1 { color: #75715e } /* Comment.Single */ +.cs { color: #75715e } /* Comment.Special */ +.gd { color: #f92672 } /* Generic.Deleted */ +.ge { color: #f8f8f2; font-style: italic } /* Generic.Emph */ +.gr { color: #f8f8f2 } /* Generic.Error */ +.gh { color: #f8f8f2 } /* Generic.Heading */ +.gi { color: #a6e22e } /* Generic.Inserted */ +.go { color: #66d9ef } /* Generic.Output */ +.gp { color: #f92672; font-weight: bold } /* Generic.Prompt */ +.gs { color: #f8f8f2; font-weight: bold } /* Generic.Strong */ +.gu { color: #75715e } /* Generic.Subheading */ +.gt { color: #f8f8f2 } /* Generic.Traceback */ +.kc { color: #66d9ef } /* Keyword.Constant */ +.kd { color: #66d9ef } /* Keyword.Declaration */ +.kn { color: #f92672 } /* Keyword.Namespace */ +.kp { color: #66d9ef } /* Keyword.Pseudo */ +.kr { color: #66d9ef } /* Keyword.Reserved */ +.kt { color: #66d9ef } /* Keyword.Type */ +.ld { color: #e6db74 } /* Literal.Date */ +.m { color: #ae81ff } /* Literal.Number */ +.s { color: #e6db74 } /* Literal.String */ +.na { color: #a6e22e } /* Name.Attribute */ +.nb { color: #f8f8f2 } /* Name.Builtin */ +.nc { color: #a6e22e } /* Name.Class */ +.no { color: #66d9ef } /* Name.Constant */ +.nd { color: #a6e22e } /* Name.Decorator */ +.ni { color: #f8f8f2 } /* Name.Entity */ +.ne { color: #a6e22e } /* Name.Exception */ +.nf { color: #a6e22e } /* Name.Function */ +.nl { color: #f8f8f2 } /* Name.Label */ +.nn { color: #f8f8f2 } /* Name.Namespace */ +.nx { color: #a6e22e } /* Name.Other */ +.py { color: #f8f8f2 } /* Name.Property */ +.nt { color: #f92672 } /* Name.Tag */ +.nv { color: #f8f8f2 } /* Name.Variable */ +.ow { color: #f92672 } /* Operator.Word */ +.pm { color: #f8f8f2 } /* Punctuation.Marker */ +.w { color: #f8f8f2 } /* Text.Whitespace */ +.mb { color: #ae81ff } /* Literal.Number.Bin */ +.mf { color: #ae81ff } /* Literal.Number.Float */ +.mh { color: #ae81ff } /* Literal.Number.Hex */ +.mi { color: #ae81ff } /* Literal.Number.Integer */ +.mo { color: #ae81ff } /* Literal.Number.Oct */ +.sa { color: #e6db74 } /* Literal.String.Affix */ +.sb { color: #e6db74 } /* Literal.String.Backtick */ +.sc { color: #e6db74 } /* Literal.String.Char */ +.dl { color: #e6db74 } /* Literal.String.Delimiter */ +.sd { color: #e6db74 } /* Literal.String.Doc */ +.s2 { color: #e6db74 } /* Literal.String.Double */ +.se { color: #ae81ff } /* Literal.String.Escape */ +.sh { color: #e6db74 } /* Literal.String.Heredoc */ +.si { color: #e6db74 } /* Literal.String.Interpol */ +.sx { color: #e6db74 } /* Literal.String.Other */ +.sr { color: #e6db74 } /* Literal.String.Regex */ +.s1 { color: #e6db74 } /* Literal.String.Single */ +.ss { color: #e6db74 } /* Literal.String.Symbol */ +.bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ +.fm { color: #a6e22e } /* Name.Function.Magic */ +.vc { color: #f8f8f2 } /* Name.Variable.Class */ +.vg { color: #f8f8f2 } /* Name.Variable.Global */ +.vi { color: #f8f8f2 } /* Name.Variable.Instance */ +.vm { color: #f8f8f2 } /* Name.Variable.Magic */ +.il { color: #ae81ff } /* Literal.Number.Integer.Long */ diff --git a/blag/static/code-light.css b/blag/static/code-light.css new file mode 100644 index 0000000..e2cc7b8 --- /dev/null +++ b/blag/static/code-light.css @@ -0,0 +1,73 @@ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.hll { background-color: #ffffcc } +.c { color: #3D7B7B; font-style: italic } /* Comment */ +.err { border: 1px solid #FF0000 } /* Error */ +.k { color: #008000; font-weight: bold } /* Keyword */ +.o { color: #666666 } /* Operator */ +.ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ +.cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ +.cp { color: #9C6500 } /* Comment.Preproc */ +.cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ +.c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ +.cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ +.gd { color: #A00000 } /* Generic.Deleted */ +.ge { font-style: italic } /* Generic.Emph */ +.gr { color: #E40000 } /* Generic.Error */ +.gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.gi { color: #008400 } /* Generic.Inserted */ +.go { color: #717171 } /* Generic.Output */ +.gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +.gs { font-weight: bold } /* Generic.Strong */ +.gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.gt { color: #0044DD } /* Generic.Traceback */ +.kc { color: #008000; font-weight: bold } /* Keyword.Constant */ +.kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ +.kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ +.kp { color: #008000 } /* Keyword.Pseudo */ +.kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ +.kt { color: #B00040 } /* Keyword.Type */ +.m { color: #666666 } /* Literal.Number */ +.s { color: #BA2121 } /* Literal.String */ +.na { color: #687822 } /* Name.Attribute */ +.nb { color: #008000 } /* Name.Builtin */ +.nc { color: #0000FF; font-weight: bold } /* Name.Class */ +.no { color: #880000 } /* Name.Constant */ +.nd { color: #AA22FF } /* Name.Decorator */ +.ni { color: #717171; font-weight: bold } /* Name.Entity */ +.ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ +.nf { color: #0000FF } /* Name.Function */ +.nl { color: #767600 } /* Name.Label */ +.nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ +.nt { color: #008000; font-weight: bold } /* Name.Tag */ +.nv { color: #19177C } /* Name.Variable */ +.ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ +.w { color: #bbbbbb } /* Text.Whitespace */ +.mb { color: #666666 } /* Literal.Number.Bin */ +.mf { color: #666666 } /* Literal.Number.Float */ +.mh { color: #666666 } /* Literal.Number.Hex */ +.mi { color: #666666 } /* Literal.Number.Integer */ +.mo { color: #666666 } /* Literal.Number.Oct */ +.sa { color: #BA2121 } /* Literal.String.Affix */ +.sb { color: #BA2121 } /* Literal.String.Backtick */ +.sc { color: #BA2121 } /* Literal.String.Char */ +.dl { color: #BA2121 } /* Literal.String.Delimiter */ +.sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ +.s2 { color: #BA2121 } /* Literal.String.Double */ +.se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ +.sh { color: #BA2121 } /* Literal.String.Heredoc */ +.si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ +.sx { color: #008000 } /* Literal.String.Other */ +.sr { color: #A45A77 } /* Literal.String.Regex */ +.s1 { color: #BA2121 } /* Literal.String.Single */ +.ss { color: #19177C } /* Literal.String.Symbol */ +.bp { color: #008000 } /* Name.Builtin.Pseudo */ +.fm { color: #0000FF } /* Name.Function.Magic */ +.vc { color: #19177C } /* Name.Variable.Class */ +.vg { color: #19177C } /* Name.Variable.Global */ +.vi { color: #19177C } /* Name.Variable.Instance */ +.vm { color: #19177C } /* Name.Variable.Magic */ +.il { color: #666666 } /* Literal.Number.Integer.Long */ diff --git a/blag/static/favicon.ico b/blag/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..6175ffc0c98d3eb8736a322197f8d335bd4c669e GIT binary patch literal 15406 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 literal 0 HcmV?d00001 diff --git a/blag/static/style.css b/blag/static/style.css new file mode 100644 index 0000000..416284a --- /dev/null +++ b/blag/static/style.css @@ -0,0 +1,166 @@ +@import "code-light.css" (prefers-color-scheme: light); +@import "code-dark.css" (prefers-color-scheme: dark); + +@media (prefers-color-scheme: light) { + :root { + --background: #FFFFFF; + --background-dim: #f5f7f9; + + --foreground: #2B303A; + --foreground-dim: #576379; + --foreground-heavy: #191C22; + + --primary-color: #375287; + } +} + +@media (prefers-color-scheme: dark) { + :root { + --background: #2B363B; + --background-dim: #2F3C42; + + --foreground: #f0f2f3; + --foreground-dim: #d5d5d5; + --foreground-heavy: #f2f4f5; + + --primary-color: #A1C5FF; + } +} + + +html { + font-size: 18px; + font-family: serif; +} + +body { + margin: 0 auto; + max-width: 50rem; + background: var(--background); + color: var(--foreground); + line-height: 1.5; + padding: 0rem 0.5rem; +} + +aside { + font-size: smaller; + font-style: italic; + color: var(--foreground-dim); +} + +h1, +h2, +h3, +h4, +h5, +h6, +strong { + color: var(--foreground-heavy); +} + +a { + color: var(--primary-color); +} + +h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { + text-decoration: none; +} + +h1 a:hover, h2 a:hover, h3 a:hover, h4 a:hover, h5 a:hover, h6 a:hover { + text-decoration: underline; +} + +nav ul { + list-style: none; +} + +nav li { + display: inline; +} + +nav li + li:before { + content: " · "; + margin: 0 0.5ex; +} + +article header { + display: flex; + flex-direction: row; + margin: 1rem 0; +} + +article header time { + white-space: nowrap; + color: var(--foreground-dim); + font-style: italic; + flex: 0 0 12ex; +} + +article header h2, +article header p { + font-size: 1rem; + display: inline; +} + +code, +pre { + background: var(--background-dim); + border-radius: 0.3rem; + font-family: monospace; +} + +pre { + padding: 1rem; + border-left: 2px solid var(--primary-color); + overflow: auto; +} + +code { + padding: 0.1rem 0.2rem; +} + +/* reset the padding for code inside pre */ +pre code { + padding: 0; +} + +blockquote { + background: var(--background-dim); + border-radius: 0 0.3rem 0.3rem 0; + font-style: italic; + border-left: 2px solid var(--primary-color); + margin: 0; + padding: 1rem; +} + +/* reset the margin for p inside blockquotes */ +blockquote p { + margin: 0; +} + +body > header { + padding: 2rem 0; +} + +body footer { + margin: 3rem 0; + color: var(--foreground-dim); + font-size: smaller; + text-align: center; +} + +header nav { + display: flex; + flex-direction: row; + justify-content: space-between; +} + +header h1 { + margin: 0 auto; + color: var(--primary-color); +} + +header h2 { + display: inline; + font-size: 1.2rem; +} diff --git a/blag/templates/archive.html b/blag/templates/archive.html index e8f9cca..1c1e62c 100644 --- a/blag/templates/archive.html +++ b/blag/templates/archive.html @@ -3,18 +3,21 @@ {% block title %}{{ site.title }}{% endblock %} {% block content %} + {% for entry in archive %} - {% if entry.title %} -

{{entry.title}}

- - {% if entry.description %} -

— {{ entry.description }}

- {% endif %} - - {% endif %} - -

Written on {{ entry.date.date() }}.

+
+
+ +
+

{{ entry.title }}

+ {% if entry.description %} +

— {{ entry.description }}

+ {% endif %} +
+
+
{% endfor %} + {% endblock %} diff --git a/blag/templates/article.html b/blag/templates/article.html index 4080c36..889186d 100644 --- a/blag/templates/article.html +++ b/blag/templates/article.html @@ -22,7 +22,6 @@

- {{ content }} {% endblock %} diff --git a/blag/templates/base.html b/blag/templates/base.html index fc6f822..de1f6af 100644 --- a/blag/templates/base.html +++ b/blag/templates/base.html @@ -4,12 +4,15 @@ + {%- if description %} {%- else %} {%- endif %} + + {% block title %}{% endblock %} | {{ site.description }} @@ -19,9 +22,10 @@ @@ -31,7 +35,15 @@ {% endblock %} + - diff --git a/blag/templates/page.html b/blag/templates/page.html index 5507a42..bdeab2b 100644 --- a/blag/templates/page.html +++ b/blag/templates/page.html @@ -3,5 +3,7 @@ {% block title %}{{ title }}{% endblock %} {% block content %} -{{ content }} + + {{ content }} + {% endblock %} diff --git a/blag/templates/tag.html b/blag/templates/tag.html index 7abb4df..84bbe2c 100644 --- a/blag/templates/tag.html +++ b/blag/templates/tag.html @@ -1,20 +1,25 @@ {% extends "base.html" %} -{% block title %}Tag {{ tag }}{% endblock %} +{% block title %}Tag: {{ tag }}{% endblock %} {% block content %} + +

Articles tagged "{{ tag }}"

+ {% for entry in archive %} - {% if entry.title %} -

{{entry.title}}

- - {% if entry.description %} -

— {{ entry.description }}

- {% endif %} - - {% endif %} - -

Written on {{ entry.date.date() }}.

+
+
+ +
+

{{ entry.title }}

+ {% if entry.description %} +

— {{ entry.description }}

+ {% endif %} +
+
+
{% endfor %} + {% endblock %} diff --git a/pyproject.toml b/pyproject.toml index 85dbb8e..8cdde32 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,10 +48,16 @@ version = {attr = "blag.__VERSION__" } packages = [ "blag", "blag.templates", + "blag.static", + "blag.content", ] [tool.setuptools.package-data] -blag = ["templates/*"] +blag = [ + "templates/*", + "static/*", + "content/*", +] [tool.pytest.ini_options] addopts = """ diff --git a/tests/conftest.py b/tests/conftest.py index 4f49918..c012b12 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -60,14 +60,13 @@ author = a. u. thor """ with TemporaryDirectory() as dir: - for d in 'content', 'build', 'static': - os.mkdir(f'{dir}/{d}') + os.mkdir(f'{dir}/build') with open(f'{dir}/config.ini', 'w') as fh: fh.write(config) # change directory old_cwd = os.getcwd() os.chdir(dir) - quickstart.copy_templates() + quickstart.copy_default_theme() yield dir # and change back afterwards os.chdir(old_cwd) diff --git a/tests/test_templates.py b/tests/test_templates.py index b25388e..8f43778 100644 --- a/tests/test_templates.py +++ b/tests/test_templates.py @@ -70,7 +70,7 @@ def test_tag(tag_template: Template) -> None: 'archive': archive, } result = tag_template.render(ctx) - assert 'Tag foo' in result + assert 'foo' in result assert 'this is a title' in result assert '1980-05-09' in result From 6b928e4953bf521321e21ecfe074bbd1fb42014e Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Fri, 16 Jun 2023 09:24:27 +0200 Subject: [PATCH 051/123] WIP --- docs/api.md | 9 +++++++++ docs/index.md | 17 +++++++++++++++++ mkdocs.yml | 19 +++++++++++++++++++ requirements-dev.txt | 4 +++- 4 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 docs/api.md create mode 100644 docs/index.md create mode 100644 mkdocs.yml diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..874b638 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,9 @@ +::: blag.version + +::: blag.blag + +::: blag.markdown + +::: blag.devserver + +::: blag.quickstart diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..000ea34 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,17 @@ +# Welcome to MkDocs + +For full documentation visit [mkdocs.org](https://www.mkdocs.org). + +## Commands + +* `mkdocs new [dir-name]` - Create a new project. +* `mkdocs serve` - Start the live-reloading docs server. +* `mkdocs build` - Build the documentation site. +* `mkdocs -h` - Print help message and exit. + +## Project layout + + mkdocs.yml # The configuration file. + docs/ + index.md # The documentation homepage. + ... # Other markdown pages, images and other files. diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..0d34f3f --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,19 @@ +site_name: blag +site_url: https://blag.readthedocs.io/ +nav: + - Home: index.md + - Manual: manual.md + - API: api.md +theme: material +repo_url: https://github.com/venthur/blag +repo_name: venthur/blag +plugins: + - search: + - mkdocstrings: + handlers: + python: + options: + docstring_style: numpy + allow_inspection: true + show_root_heading: true + show_if_no_docstring: true diff --git a/requirements-dev.txt b/requirements-dev.txt index 95c079a..2c806f0 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,7 @@ build==0.9.0 -sphinx==5.3.0 +mkdocs==1.4.3 +mkdocs-material==9.1.15 +mkdocstrings[python]==0.20.0 twine==4.0.2 wheel==0.40.0 pytest==7.3.0 From 7e22f00ac59748c0735ab14f1598607cfb9b7266 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Fri, 16 Jun 2023 09:45:05 +0200 Subject: [PATCH 052/123] updated default theme --- blag/templates/base.html | 9 +++++---- blag/templates/index.html | 22 ++++++++++++---------- blag/templates/tag.html | 2 +- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/blag/templates/base.html b/blag/templates/base.html index de1f6af..2dad0d7 100644 --- a/blag/templates/base.html +++ b/blag/templates/base.html @@ -23,9 +23,9 @@

{{ site.description }}

@@ -38,10 +38,11 @@ diff --git a/blag/templates/index.html b/blag/templates/index.html index a88a011..002e185 100644 --- a/blag/templates/index.html +++ b/blag/templates/index.html @@ -3,18 +3,20 @@ {% block title %}{{ site.title }}{% endblock %} {% block content %} -{% for entry in archive[:10] %} - {% if entry.title %} -

{{entry.title}}

+{% for entry in archive[:15] %} - {% if entry.description %} -

— {{ entry.description }}

- {% endif %} - - {% endif %} - -

Written on {{ entry.date.date() }}.

+
+
+ +
+

{{ entry.title }}

+ {% if entry.description %} +

— {{ entry.description }}

+ {% endif %} +
+
+
{% endfor %} diff --git a/blag/templates/tag.html b/blag/templates/tag.html index 84bbe2c..4abd3bc 100644 --- a/blag/templates/tag.html +++ b/blag/templates/tag.html @@ -1,6 +1,6 @@ {% extends "base.html" %} -{% block title %}Tag: {{ tag }}{% endblock %} +{% block title %}#{{ tag }}{% endblock %} {% block content %} From d6ed2d71d576876001bac242a328d047ab7137a2 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Fri, 16 Jun 2023 09:55:17 +0200 Subject: [PATCH 053/123] small fixed to blag quickstart --- CHANGELOG.md | 4 ++++ README.md | 1 + blag/quickstart.py | 5 +++-- docs/blag.rst | 6 +++--- tests/test_quickstart.py | 4 ++++ 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5c0c19..35a1c1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,10 @@ * blag comes now with a simple yet good looking default theme that supports syntax highlighting and a light- and dark theme. +* apart from the generated configuration, `blag quickstart` will now also + create the initial directory structure, with the default template, the static + directory with the CSS files and the content directory with some initial + content to get the user started ## [1.5.0] - 2023-04-16 diff --git a/README.md b/README.md index 77848f4..3249765 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ blag is named after [the blag of the webcomic xkcd][blagxkcd]. ## Features * Write content in [Markdown][] +* Good looking default theme * Theming support using [Jinja2][] templates * Generation of Atom feeds for blog content * Fenced code blocks and syntax highlighting using [Pygments][] diff --git a/blag/quickstart.py b/blag/quickstart.py index 4d12af8..ac40112 100644 --- a/blag/quickstart.py +++ b/blag/quickstart.py @@ -61,8 +61,9 @@ def copy_default_theme() -> None: def quickstart(args: argparse.Namespace | None) -> None: """Quickstart. - This method asks the user some questions and generates a - configuration file that is needed in order to run blag. + This method asks the user some questions and generates a configuration file + that is needed in order to run blag. Additionally, it creates the content + and static directories with some initial content, to get the user started. Parameters ---------- diff --git a/docs/blag.rst b/docs/blag.rst index 47cd5ad..92239c9 100644 --- a/docs/blag.rst +++ b/docs/blag.rst @@ -13,7 +13,8 @@ Install blag from PyPI_ .. _pypi: https://pypi.org/project/blag/ -Run blag's quickstart command to create the configuration and templates needed +Run blag's quickstart command to create the configuration, templates and some +initial content. .. code-block:: sh @@ -23,7 +24,6 @@ Create some content .. code-block:: sh - $ mkdir content $ edit content/hello-world.md Generate the website @@ -121,7 +121,7 @@ Static Files Static files can be put into the ``content`` directory and will be copied over to the ``build`` directory as well. If you want better separation between -content and static files, you can create a ``static`` directory and put the +content and static files, you can use the ``static`` directory and put the files there. All files and directories found in the ``static`` directory will be copied over to ``build``. diff --git a/tests/test_quickstart.py b/tests/test_quickstart.py index 95b54c1..d2b1dff 100644 --- a/tests/test_quickstart.py +++ b/tests/test_quickstart.py @@ -33,8 +33,12 @@ def test_quickstart(cleandir: str, monkeypatch: MonkeyPatch) -> None: "archive.html", "article.html", "base.html", + "index.html", "page.html", "tag.html", "tags.html", ): assert os.path.exists(f'templates/{template}') + + for directory in "build", "content", "static": + assert os.path.exists(directory) From 19b41f73f8c32c2cfa888ff4bf20c9d212f0b8dc Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Fri, 16 Jun 2023 10:06:47 +0200 Subject: [PATCH 054/123] updated deps --- CHANGELOG.md | 9 +++++++++ Makefile | 5 +++++ requirements-dev.txt | 6 +++--- requirements.txt | 4 ++-- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35a1c1d..2943c73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,15 @@ directory with the CSS files and the content directory with some initial content to get the user started +* Added a make target to update the pygments themes + +* updated dependencies: + * markdown 3.4.3 + * pygments 2.15.1 + * pytest 7.3.2 + * types-markdown 3.4.2.9 + * build 0.10.0 + ## [1.5.0] - 2023-04-16 * moved to pyproject.toml diff --git a/Makefile b/Makefile index 5731772..fce9102 100644 --- a/Makefile +++ b/Makefile @@ -48,6 +48,11 @@ test-release: $(VENV) build release: $(VENV) build $(BIN)/twine upload dist/* +.PHONY: update-pygmentize +update-pygmentize: $(VENV) + $(BIN)/pygmentize -f html -S default > blag/static/code-light.css + $(BIN)/pygmentize -f html -S monokai > blag/static/code-dark.css + .PHONY: docs docs: $(VENV) $(BIN)/sphinx-build $(DOCS_SRC) $(DOCS_OUT) diff --git a/requirements-dev.txt b/requirements-dev.txt index 95c079a..098232f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,9 +1,9 @@ -build==0.9.0 +build==0.10.0 sphinx==5.3.0 twine==4.0.2 wheel==0.40.0 -pytest==7.3.0 +pytest==7.3.2 pytest-cov==4.0.0 flake8==6.0.0 mypy==1.2.0 -types-markdown==3.4.2.1 +types-markdown==3.4.2.9 diff --git a/requirements.txt b/requirements.txt index 58367ba..6607ece 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -markdown==3.4.1 +markdown==3.4.3 feedgenerator==2.0.0 jinja2==3.1.2 -pygments==2.13.0 +pygments==2.15.1 From f6c5eaf375cb657ac11ed854e1d0af46df423992 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Fri, 16 Jun 2023 10:19:45 +0200 Subject: [PATCH 055/123] added tests/conftest.py to dist closes: #103 --- CHANGELOG.md | 5 +++++ pyproject.toml | 4 +--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2943c73..be2f7eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,11 @@ * types-markdown 3.4.2.9 * build 0.10.0 +### Fixed + +* fixed pyproject.toml to include tests/conftest.py + + ## [1.5.0] - 2023-04-16 * moved to pyproject.toml diff --git a/pyproject.toml b/pyproject.toml index 8cdde32..e6b5dda 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,9 +47,7 @@ version = {attr = "blag.__VERSION__" } [tool.setuptools] packages = [ "blag", - "blag.templates", - "blag.static", - "blag.content", + "tests", ] [tool.setuptools.package-data] From 0349bd3359715d1a763176f23791a21f9ae3d9c9 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Fri, 16 Jun 2023 10:25:21 +0200 Subject: [PATCH 056/123] reformatted code --- blag/blag.py | 216 +++++++++++++++++++-------------------- blag/devserver.py | 22 ++-- blag/markdown.py | 42 ++++---- blag/quickstart.py | 19 ++-- blag/version.py | 2 +- tests/conftest.py | 42 ++++---- tests/test_blag.py | 216 +++++++++++++++++++-------------------- tests/test_devserver.py | 29 +++--- tests/test_markdown.py | 65 ++++++------ tests/test_quickstart.py | 23 +++-- tests/test_templates.py | 85 +++++++-------- 11 files changed, 381 insertions(+), 380 deletions(-) diff --git a/blag/blag.py b/blag/blag.py index c59ae0a..28fe983 100644 --- a/blag/blag.py +++ b/blag/blag.py @@ -6,32 +6,28 @@ # remove when we don't support py38 anymore from __future__ import annotations -from typing import Any + import argparse +import configparser +import logging import os import shutil -import logging -import configparser import sys +from typing import Any -from jinja2 import ( - Environment, - FileSystemLoader, - Template, - TemplateNotFound, -) import feedgenerator +from jinja2 import Environment, FileSystemLoader, Template, TemplateNotFound import blag -from blag.markdown import markdown_factory, convert_markdown from blag.devserver import serve -from blag.version import __VERSION__ +from blag.markdown import convert_markdown, markdown_factory from blag.quickstart import quickstart +from blag.version import __VERSION__ logger = logging.getLogger(__name__) logging.basicConfig( level=logging.INFO, - format='%(asctime)s %(levelname)s %(name)s %(message)s', + format="%(asctime)s %(levelname)s %(name)s %(message)s", ) @@ -70,84 +66,84 @@ def parse_args(args: list[str] | None = None) -> argparse.Namespace: """ parser = argparse.ArgumentParser() parser.add_argument( - '--version', - action='version', - version='%(prog)s ' + __VERSION__, + "--version", + action="version", + version="%(prog)s " + __VERSION__, ) parser.add_argument( - '-v', - '--verbose', - action='store_true', - help='Verbose output.', + "-v", + "--verbose", + action="store_true", + help="Verbose output.", ) - commands = parser.add_subparsers(dest='command') + commands = parser.add_subparsers(dest="command") 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', + "quickstart", help="Quickstart blag, creating necessary configuration.", ) quickstart_parser.set_defaults(func=quickstart) serve_parser = commands.add_parser( - 'serve', + "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) @@ -170,18 +166,18 @@ def get_config(configfile: str) -> configparser.SectionProxy: config = configparser.ConfigParser() config.read(configfile) # check for the mandatory options - for value in 'base_url', 'title', 'description', 'author': + for value in "base_url", "title", "description", "author": try: - config['main'][value] + config["main"][value] except Exception: - print(f'{value} is missing in {configfile}!') + print(f"{value} is missing in {configfile}!") sys.exit(1) - if not config['main']['base_url'].endswith('/'): - logger.warning('base_url does not end with a slash, adding it.') - config['main']['base_url'] += '/' + if not config["main"]["base_url"].endswith("/"): + logger.warning("base_url does not end with a slash, adding it.") + config["main"]["base_url"] += "/" - return config['main'] + return config["main"] def environment_factory( @@ -222,51 +218,51 @@ def build(args: argparse.Namespace) -> None: args """ - os.makedirs(f'{args.output_dir}', exist_ok=True) + os.makedirs(f"{args.output_dir}", exist_ok=True) 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 + 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'): + if rel_src.endswith(".md"): rel_dst = rel_src - rel_dst = rel_dst[:-3] + '.html' + 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}', + 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) - os.makedirs(f'{args.output_dir}/{path}', exist_ok=True) + path = os.path.relpath(f"{root}/{dirname}", start=args.input_dir) + os.makedirs(f"{args.output_dir}/{path}", exist_ok=True) # copy static files over - logger.info('Copying static files.') + logger.info("Copying static files.") if os.path.exists(args.static_dir): shutil.copytree(args.static_dir, args.output_dir, dirs_exist_ok=True) - config = get_config('config.ini') + config = get_config("config.ini") env = environment_factory(args.template_dir, dict(site=config)) try: - page_template = env.get_template('page.html') - article_template = env.get_template('article.html') - index_template = env.get_template('index.html') - archive_template = env.get_template('archive.html') - tags_template = env.get_template('tags.html') - tag_template = env.get_template('tag.html') + page_template = env.get_template("page.html") + article_template = env.get_template("article.html") + index_template = env.get_template("index.html") + archive_template = env.get_template("archive.html") + tags_template = env.get_template("tags.html") + tag_template = env.get_template("tag.html") except TemplateNotFound as exc: - tmpl = os.path.join(blag.__path__[0], 'templates') + tmpl = os.path.join(blag.__path__[0], "templates") logger.error( f'Template "{exc.name}" not found in {args.template_dir}! ' - 'Consider running `blag quickstart` or copying the ' - f'missing template from {tmpl}.' + "Consider running `blag quickstart` or copying the " + f"missing template from {tmpl}." ) sys.exit(1) @@ -282,10 +278,10 @@ def build(args: argparse.Namespace) -> None: generate_feed( articles, args.output_dir, - base_url=config['base_url'], - blog_title=config['title'], - blog_description=config['description'], - blog_author=config['author'], + base_url=config["base_url"], + blog_title=config["title"], + blog_description=config["description"], + blog_author=config["author"], ) generate_index(articles, index_template, args.output_dir) generate_archive(articles, archive_template, args.output_dir) @@ -330,9 +326,9 @@ def process_markdown( articles = [] pages = [] for src, dst in convertibles: - logger.debug(f'Processing {src}') + logger.debug(f"Processing {src}") - with open(f'{input_dir}/{src}', 'r') as fh: + with open(f"{input_dir}/{src}", "r") as fh: body = fh.read() content, meta = convert_markdown(md, body) @@ -342,17 +338,17 @@ def process_markdown( # if markdown has date in meta, we treat it as a blog article, # everything else are just pages - if meta and 'date' in meta: + if meta and "date" in meta: articles.append((dst, context)) result = article_template.render(context) else: pages.append((dst, context)) result = page_template.render(context) - with open(f'{output_dir}/{dst}', 'w') as fh_dest: + with open(f"{output_dir}/{dst}", "w") as fh_dest: fh_dest.write(result) # sort articles by date, descending - articles = sorted(articles, key=lambda x: x[1]['date'], reverse=True) + articles = sorted(articles, key=lambda x: x[1]["date"], reverse=True) return articles, pages @@ -382,30 +378,30 @@ def generate_feed( blog author """ - logger.info('Generating Atom feed.') + logger.info("Generating Atom feed.") feed = feedgenerator.Atom1Feed( link=base_url, title=blog_title, description=blog_description, - feed_url=base_url + 'atom.xml', + feed_url=base_url + "atom.xml", ) for dst, context in articles: # if article has a description, use that. otherwise fall back to # the title - description = context.get('description', context['title']) + description = context.get("description", context["title"]) feed.add_item( - title=context['title'], + title=context["title"], author_name=blog_author, link=base_url + dst, description=description, - content=context['content'], - pubdate=context['date'], + content=context["content"], + pubdate=context["date"], ) - with open(f'{output_dir}/atom.xml', 'w') as fh: - feed.write(fh, encoding='utf8') + with open(f"{output_dir}/atom.xml", "w") as fh: + feed.write(fh, encoding="utf8") def generate_index( @@ -429,11 +425,11 @@ def generate_index( archive = [] for dst, context in articles: entry = context.copy() - entry['dst'] = dst + entry["dst"] = dst archive.append(entry) result = template.render(dict(archive=archive)) - with open(f'{output_dir}/index.html', 'w') as fh: + with open(f"{output_dir}/index.html", "w") as fh: fh.write(result) @@ -458,11 +454,11 @@ def generate_archive( archive = [] for dst, context in articles: entry = context.copy() - entry['dst'] = dst + entry["dst"] = dst archive.append(entry) result = template.render(dict(archive=archive)) - with open(f'{output_dir}/archive.html', 'w') as fh: + with open(f"{output_dir}/archive.html", "w") as fh: fh.write(result) @@ -484,11 +480,11 @@ def generate_tags( """ logger.info("Generating Tag-pages.") - os.makedirs(f'{output_dir}/tags', exist_ok=True) + os.makedirs(f"{output_dir}/tags", exist_ok=True) # get tags number of occurrences all_tags: dict[str, int] = {} for _, context in articles: - tags: list[str] = 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 @@ -497,25 +493,25 @@ def generate_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) # get tags and archive per tag all_tags2: dict[str, list[dict[str, Any]]] = {} for dst, context in articles: - tags = context.get('tags', []) + tags = context.get("tags", []) for tag in tags: archive: list[dict[str, Any]] = all_tags2.get(tag, []) entry = context.copy() - entry['dst'] = dst + entry["dst"] = dst archive.append(entry) all_tags2[tag] = archive 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: + with open(f"{output_dir}/tags/{tag}.html", "w") as fh: fh.write(result) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/blag/devserver.py b/blag/devserver.py index a57ad71..4f3ec50 100644 --- a/blag/devserver.py +++ b/blag/devserver.py @@ -8,18 +8,18 @@ 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 -import multiprocessing -from http.server import SimpleHTTPRequestHandler, HTTPServer -from functools import partial + import argparse +import logging +import multiprocessing +import os +import time +from functools import partial +from http.server import HTTPServer, SimpleHTTPRequestHandler +from typing import NoReturn from blag import blag - logger = logging.getLogger(__name__) @@ -69,7 +69,7 @@ def autoreload(args: argparse.Namespace) -> NoReturn: """ dirs = [args.input_dir, args.template_dir, args.static_dir] - 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 # loop to avoid serving stale contents last_mtime = 0.0 @@ -77,7 +77,7 @@ def autoreload(args: argparse.Namespace) -> NoReturn: mtime = get_last_modified(dirs) if mtime > last_mtime: last_mtime = mtime - logger.info('Change detected, rebuilding...') + logger.info("Change detected, rebuilding...") blag.build(args) time.sleep(1) @@ -92,7 +92,7 @@ def serve(args: argparse.Namespace) -> None: """ httpd = HTTPServer( - ('', 8000), + ("", 8000), partial(SimpleHTTPRequestHandler, directory=args.output_dir), ) proc = multiprocessing.Process(target=autoreload, args=(args,)) diff --git a/blag/markdown.py b/blag/markdown.py index 6b055ed..e23d1e9 100644 --- a/blag/markdown.py +++ b/blag/markdown.py @@ -7,8 +7,9 @@ processing. # remove when we don't support py38 anymore from __future__ import annotations -from datetime import datetime + import logging +from datetime import datetime from urllib.parse import urlsplit, urlunsplit from xml.etree.ElementTree import Element @@ -16,7 +17,6 @@ from markdown import Markdown from markdown.extensions import Extension from markdown.treeprocessors import Treeprocessor - logger = logging.getLogger(__name__) @@ -33,13 +33,13 @@ def markdown_factory() -> Markdown: """ md = Markdown( extensions=[ - 'meta', - 'fenced_code', - 'codehilite', - 'smarty', + "meta", + "fenced_code", + "codehilite", + "smarty", MarkdownLinkExtension(), ], - output_format='html', + output_format="html", ) return md @@ -75,20 +75,20 @@ def convert_markdown( # markdowns metadata consists as list of strings -- one item per # line. let's convert into single strings. for key, value in meta.items(): - value = '\n'.join(value) + value = "\n".join(value) meta[key] = value # convert known metadata # date: datetime - if 'date' in meta: - meta['date'] = datetime.fromisoformat(meta['date']) - meta['date'] = meta['date'].astimezone() + if "date" in meta: + meta["date"] = datetime.fromisoformat(meta["date"]) + meta["date"] = meta["date"].astimezone() # tags: list[str] and lower case - if 'tags' in meta: - tags = meta['tags'].split(',') + if "tags" in meta: + tags = meta["tags"].split(",") tags = [t.lower() for t in tags] tags = [t.strip() for t in tags] - meta['tags'] = tags + meta["tags"] = tags return content, meta @@ -98,25 +98,25 @@ class MarkdownLinkTreeprocessor(Treeprocessor): def run(self, root: Element) -> Element: for element in root.iter(): - if element.tag == 'a': - url = element.get('href') + 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) + element.set("href", converted) return root def convert(self, url: str) -> str: scheme, netloc, path, query, fragment = urlsplit(url) logger.debug( - f'{url}: {scheme=} {netloc=} {path=} {query=} {fragment=}' + f"{url}: {scheme=} {netloc=} {path=} {query=} {fragment=}" ) if scheme or netloc or not path: return url - if path.endswith('.md'): - path = path[:-3] + '.html' + if path.endswith(".md"): + path = path[:-3] + ".html" url = urlunsplit((scheme, netloc, path, query, fragment)) return url @@ -128,6 +128,6 @@ class MarkdownLinkExtension(Extension): def extendMarkdown(self, md: Markdown) -> None: md.treeprocessors.register( MarkdownLinkTreeprocessor(md), - 'mdlink', + "mdlink", 0, ) diff --git a/blag/quickstart.py b/blag/quickstart.py index ac40112..42e8425 100644 --- a/blag/quickstart.py +++ b/blag/quickstart.py @@ -4,10 +4,11 @@ # remove when we don't support py38 anymore from __future__ import annotations -import configparser + import argparse -import shutil +import configparser import os +import shutil import blag @@ -47,7 +48,7 @@ def copy_default_theme() -> None: """ print("Copying default theme...") - for dir_ in 'templates', 'content', 'static': + for dir_ in "templates", "content", "static": print(f" Copying {dir_}...") try: shutil.copytree( @@ -89,13 +90,13 @@ def quickstart(args: argparse.Namespace | None) -> None: ) config = configparser.ConfigParser() - config['main'] = { - 'base_url': base_url, - 'title': title, - 'description': description, - 'author': author, + config["main"] = { + "base_url": base_url, + "title": title, + "description": description, + "author": author, } - with open('config.ini', 'w') as fh: + with open("config.ini", "w") as fh: config.write(fh) copy_default_theme() diff --git a/blag/version.py b/blag/version.py index 3fe4bea..7e6a9cc 100644 --- a/blag/version.py +++ b/blag/version.py @@ -1 +1 @@ -__VERSION__ = '1.5.0' +__VERSION__ = "1.5.0" diff --git a/tests/conftest.py b/tests/conftest.py index 81215ec..f852463 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,9 +1,10 @@ # 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 + import os +from argparse import Namespace +from tempfile import TemporaryDirectory +from typing import Callable, Iterator import pytest from jinja2 import Environment, Template @@ -14,43 +15,43 @@ from blag import blag, quickstart @pytest.fixture def environment(cleandir: str) -> Iterator[Environment]: site = { - 'base_url': 'site base_url', - 'title': 'site title', - 'description': 'site description', - 'author': 'site author', + "base_url": "site base_url", + "title": "site title", + "description": "site description", + "author": "site author", } - env = blag.environment_factory('templates', globals_=dict(site=site)) + env = blag.environment_factory("templates", globals_=dict(site=site)) yield env @pytest.fixture def page_template(environment: Environment) -> Iterator[Template]: - yield environment.get_template('page.html') + yield environment.get_template("page.html") @pytest.fixture def article_template(environment: Environment) -> Iterator[Template]: - yield environment.get_template('article.html') + yield environment.get_template("article.html") @pytest.fixture def index_template(environment: Environment) -> Iterator[Template]: - yield environment.get_template('index.html') + yield environment.get_template("index.html") @pytest.fixture def archive_template(environment: Environment) -> Iterator[Template]: - yield environment.get_template('archive.html') + yield environment.get_template("archive.html") @pytest.fixture def tags_template(environment: Environment) -> Iterator[Template]: - yield environment.get_template('tags.html') + yield environment.get_template("tags.html") @pytest.fixture def tag_template(environment: Environment) -> Iterator[Template]: - yield environment.get_template('tag.html') + yield environment.get_template("tag.html") @pytest.fixture @@ -65,8 +66,8 @@ author = a. u. thor """ with TemporaryDirectory() as dir: - os.mkdir(f'{dir}/build') - with open(f'{dir}/config.ini', 'w') as fh: + os.mkdir(f"{dir}/build") + with open(f"{dir}/config.ini", "w") as fh: fh.write(config) # change directory old_cwd = os.getcwd() @@ -79,11 +80,10 @@ author = a. u. thor @pytest.fixture 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 ce2dd4a..e454ca1 100644 --- a/tests/test_blag.py +++ b/tests/test_blag.py @@ -1,73 +1,73 @@ # remove when we don't support py38 anymore from __future__ import annotations -from tempfile import TemporaryDirectory + import os -from datetime import datetime -from typing import Any from argparse import Namespace +from datetime import datetime +from tempfile import TemporaryDirectory +from typing import Any import pytest -from pytest import CaptureFixture, LogCaptureFixture from jinja2 import Template +from pytest import CaptureFixture, LogCaptureFixture -from blag import __VERSION__ -from blag import blag +from blag import __VERSION__, blag 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') + blag.generate_feed(articles, "build", " ", " ", " ", " ") + assert os.path.exists("build/atom.xml") def test_feed(cleandir: str) -> None: articles: list[tuple[str, dict[str, Any]]] = [ ( - 'dest1.html', + "dest1.html", { - 'title': 'title1', - 'date': datetime(2019, 6, 6), - 'content': 'content1', + "title": "title1", + "date": datetime(2019, 6, 6), + "content": "content1", }, ), ( - 'dest2.html', + "dest2.html", { - 'title': 'title2', - 'date': datetime(1980, 5, 9), - 'content': 'content2', + "title": "title2", + "date": datetime(1980, 5, 9), + "content": "content2", }, ), ] blag.generate_feed( articles, - 'build', - 'https://example.com/', - 'blog title', - 'blog description', - 'blog author', + "build", + "https://example.com/", + "blog title", + "blog description", + "blog author", ) - with open('build/atom.xml') as fh: + with open("build/atom.xml") as fh: feed = fh.read() - assert 'blog title' in feed + assert "blog title" in feed # enable when https://github.com/getpelican/feedgenerator/issues/22 # is fixed # assert 'blog description' in feed - assert 'blog author' in feed + assert "blog author" in feed # article 1 - assert 'title1' in feed + assert "title1" in feed assert 'title1' in feed - assert '2019-06-06' in feed + assert "2019-06-06" in feed assert 'content1' in feed assert 'title2' in feed + assert "title2" in feed assert 'title2' in feed - assert '1980-05-09' in feed + assert "1980-05-09" in feed assert 'content2' in feed assert ' None: # the feed, otherwise we simply use the title of the article articles: list[tuple[str, dict[str, Any]]] = [ ( - 'dest.html', + "dest.html", { - 'title': 'title', - 'description': 'description', - 'date': datetime(2019, 6, 6), - 'content': 'content', + "title": "title", + "description": "description", + "date": datetime(2019, 6, 6), + "content": "content", }, ) ] - blag.generate_feed(articles, 'build', ' ', ' ', ' ', ' ') + blag.generate_feed(articles, "build", " ", " ", " ", " ") - with open('build/atom.xml') as fh: + with open("build/atom.xml") as fh: feed = fh.read() - assert 'title' in feed + assert "title" in feed assert 'description' in feed - assert '2019-06-06' in feed + assert "2019-06-06" in feed assert 'content' in feed def test_parse_args_build() -> None: # test default args - args = blag.parse_args(['build']) - assert args.input_dir == 'content' - assert args.output_dir == 'build' - assert args.template_dir == 'templates' - assert args.static_dir == 'static' + args = blag.parse_args(["build"]) + assert args.input_dir == "content" + assert args.output_dir == "build" + assert args.template_dir == "templates" + assert args.static_dir == "static" # input dir - args = blag.parse_args(['build', '-i', 'foo']) - assert args.input_dir == 'foo' - args = blag.parse_args(['build', '--input-dir', 'foo']) - assert args.input_dir == 'foo' + args = blag.parse_args(["build", "-i", "foo"]) + assert args.input_dir == "foo" + args = blag.parse_args(["build", "--input-dir", "foo"]) + assert args.input_dir == "foo" # output dir - args = blag.parse_args(['build', '-o', 'foo']) - assert args.output_dir == 'foo' - args = blag.parse_args(['build', '--output-dir', 'foo']) - assert args.output_dir == 'foo' + args = blag.parse_args(["build", "-o", "foo"]) + assert args.output_dir == "foo" + args = blag.parse_args(["build", "--output-dir", "foo"]) + assert args.output_dir == "foo" # template dir - args = blag.parse_args(['build', '-t', 'foo']) - assert args.template_dir == 'foo' - args = blag.parse_args(['build', '--template-dir', 'foo']) - assert args.template_dir == 'foo' + args = blag.parse_args(["build", "-t", "foo"]) + assert args.template_dir == "foo" + args = blag.parse_args(["build", "--template-dir", "foo"]) + assert args.template_dir == "foo" # static dir - args = blag.parse_args(['build', '-s', 'foo']) - assert args.static_dir == 'foo' - args = blag.parse_args(['build', '--static-dir', 'foo']) - assert args.static_dir == 'foo' + args = blag.parse_args(["build", "-s", "foo"]) + assert args.static_dir == "foo" + args = blag.parse_args(["build", "--static-dir", "foo"]) + assert args.static_dir == "foo" def test_get_config() -> None: @@ -140,24 +140,24 @@ author = a. u. thor """ # happy path with TemporaryDirectory() as dir: - configfile = f'{dir}/config.ini' - with open(configfile, 'w') as fh: + configfile = f"{dir}/config.ini" + with open(configfile, "w") as fh: fh.write(config) config_parsed = blag.get_config(configfile) - assert config_parsed['base_url'] == 'https://example.com/' - assert config_parsed['title'] == 'title' - assert config_parsed['description'] == 'description' - assert config_parsed['author'] == 'a. u. thor' + assert config_parsed["base_url"] == "https://example.com/" + assert config_parsed["title"] == "title" + assert config_parsed["description"] == "description" + assert config_parsed["author"] == "a. u. thor" # a missing required config causes a sys.exit - for x in 'base_url', 'title', 'description', 'author': - config2 = '\n'.join( + for x in "base_url", "title", "description", "author": + 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: + configfile = f"{dir}/config.ini" + with open(configfile, "w") as fh: fh.write(config2) with pytest.raises(SystemExit): config_parsed = blag.get_config(configfile) @@ -171,19 +171,19 @@ description = description author = a. u. thor """ with TemporaryDirectory() as dir: - configfile = f'{dir}/config.ini' - with open(configfile, 'w') as fh: + configfile = f"{dir}/config.ini" + with open(configfile, "w") as fh: fh.write(config) config_parsed = blag.get_config(configfile) - assert config_parsed['base_url'] == 'https://example.com/' + assert config_parsed["base_url"] == "https://example.com/" def test_environment_factory(cleandir: str) -> None: - globals_: dict[str, object] = {'foo': 'bar', 'test': 'me'} + globals_: dict[str, object] = {"foo": "bar", "test": "me"} env = blag.environment_factory("templates", globals_=globals_) - assert env.globals['foo'] == 'bar' - assert env.globals['test'] == 'me' + assert env.globals["foo"] == "bar" + assert env.globals["test"] == "me" def test_process_markdown( @@ -216,12 +216,12 @@ foo bar convertibles = [] for i, txt in enumerate((page1, article1, article2)): - with open(f'content/{str(i)}', 'w') as fh: + with open(f"content/{str(i)}", "w") as fh: fh.write(txt) 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) @@ -229,14 +229,14 @@ foo bar for dst, context in articles: assert isinstance(dst, str) assert isinstance(context, dict) - assert 'content' in context + assert "content" in context assert isinstance(pages, list) assert len(pages) == 1 for dst, context in pages: assert isinstance(dst, str) assert isinstance(context, dict) - assert 'content' in context + assert "content" in context def test_build(args: Namespace) -> None: @@ -268,63 +268,63 @@ foo bar # write some convertibles convertibles = [] for i, txt in enumerate((page1, article1, article2)): - with open(f'{args.input_dir}/{str(i)}.md', 'w') as fh: + with open(f"{args.input_dir}/{str(i)}.md", "w") as fh: fh.write(txt) convertibles.append((str(i), str(i))) # some static files - with open(f'{args.static_dir}/test', 'w') as fh: - fh.write('hello') + with open(f"{args.static_dir}/test", "w") as fh: + fh.write("hello") - os.mkdir(f'{args.input_dir}/testdir') - with open(f'{args.input_dir}/testdir/test', 'w') as fh: - fh.write('hello') + os.mkdir(f"{args.input_dir}/testdir") + with open(f"{args.input_dir}/testdir/test", "w") as fh: + fh.write("hello") blag.build(args) # test existence of the three converted files for i in range(3): - assert os.path.exists(f'{args.output_dir}/{i}.html') + assert os.path.exists(f"{args.output_dir}/{i}.html") # ... static file - assert os.path.exists(f'{args.output_dir}/test') + assert os.path.exists(f"{args.output_dir}/test") # ... directory - assert os.path.exists(f'{args.output_dir}/testdir/test') + assert os.path.exists(f"{args.output_dir}/testdir/test") # ... feed - assert os.path.exists(f'{args.output_dir}/atom.xml') + assert os.path.exists(f"{args.output_dir}/atom.xml") # ... index - assert os.path.exists(f'{args.output_dir}/index.html') + assert os.path.exists(f"{args.output_dir}/index.html") # ... archive - assert os.path.exists(f'{args.output_dir}/archive.html') + assert os.path.exists(f"{args.output_dir}/archive.html") # ... tags - assert os.path.exists(f'{args.output_dir}/tags/index.html') - assert os.path.exists(f'{args.output_dir}/tags/foo.html') - assert os.path.exists(f'{args.output_dir}/tags/bar.html') + assert os.path.exists(f"{args.output_dir}/tags/index.html") + assert os.path.exists(f"{args.output_dir}/tags/foo.html") + assert os.path.exists(f"{args.output_dir}/tags/bar.html") @pytest.mark.parametrize( - 'template', + "template", [ - 'page.html', - 'article.html', - 'index.html', - 'archive.html', - 'tags.html', - 'tag.html', - ] + "page.html", + "article.html", + "index.html", + "archive.html", + "tags.html", + "tag.html", + ], ) def test_missing_template_raises(template: str, args: Namespace) -> None: - os.remove(f'templates/{template}') + os.remove(f"templates/{template}") with pytest.raises(SystemExit): blag.build(args) def test_main(cleandir: str) -> None: - blag.main(['build']) + blag.main(["build"]) def test_cli_version(capsys: CaptureFixture[str]) -> None: with pytest.raises(SystemExit) as ex: - blag.main(['--version']) + blag.main(["--version"]) # normal system exit assert ex.value.code == 0 # proper version reported @@ -333,8 +333,8 @@ def test_cli_version(capsys: CaptureFixture[str]) -> None: def test_cli_verbose(cleandir: str, caplog: LogCaptureFixture) -> None: - blag.main(['build']) - assert 'DEBUG' not in caplog.text + blag.main(["build"]) + assert "DEBUG" not in caplog.text - blag.main(['--verbose', 'build']) - assert 'DEBUG' in caplog.text + blag.main(["--verbose", "build"]) + assert "DEBUG" in caplog.text diff --git a/tests/test_devserver.py b/tests/test_devserver.py index 4b66b38..29477fb 100644 --- a/tests/test_devserver.py +++ b/tests/test_devserver.py @@ -1,7 +1,8 @@ # remove when we don't support py38 anymore from __future__ import annotations -import time + import threading +import time from argparse import Namespace import pytest @@ -11,17 +12,17 @@ from blag import devserver def test_get_last_modified(cleandir: str) -> None: # take initial time - t1 = devserver.get_last_modified(['content']) + t1 = devserver.get_last_modified(["content"]) # wait a bit, create a file and measure again time.sleep(0.1) - with open('content/test', 'w') as fh: - fh.write('boo') - t2 = devserver.get_last_modified(['content']) + with open("content/test", "w") as fh: + fh.write("boo") + t2 = devserver.get_last_modified(["content"]) # wait a bit and take time again time.sleep(0.1) - t3 = devserver.get_last_modified(['content']) + t3 = devserver.get_last_modified(["content"]) assert t2 > t1 assert t2 == t3 @@ -29,20 +30,20 @@ def test_get_last_modified(cleandir: str) -> None: 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') + with open("content/test.md", "w") as fh: + fh.write("boo") t = threading.Thread( target=devserver.autoreload, args=(args,), daemon=True, ) - t0 = devserver.get_last_modified(['build']) + t0 = devserver.get_last_modified(["build"]) t.start() # try for 5 seconds... for i in range(5): time.sleep(1) - t1 = devserver.get_last_modified(['build']) + t1 = devserver.get_last_modified(["build"]) print(t1) if t1 > t0: break @@ -60,16 +61,16 @@ def test_autoreload(args: Namespace) -> None: ) t.start() - t0 = devserver.get_last_modified(['build']) + t0 = devserver.get_last_modified(["build"]) # create a dummy file that can be build - with open('content/test.md', 'w') as fh: - fh.write('boo') + with open("content/test.md", "w") as fh: + fh.write("boo") # try for 5 seconds to see if we rebuild once... for i in range(5): time.sleep(1) - t1 = devserver.get_last_modified(['build']) + t1 = devserver.get_last_modified(["build"]) if t1 > t0: break assert t1 > t0 diff --git a/tests/test_markdown.py b/tests/test_markdown.py index 816f310..3035608 100644 --- a/tests/test_markdown.py +++ b/tests/test_markdown.py @@ -1,10 +1,11 @@ # remove when we don't support py38 anymore from __future__ import annotations + from datetime import datetime from typing import Any -import pytest import markdown +import pytest from blag.markdown import convert_markdown, markdown_factory @@ -13,23 +14,23 @@ from blag.markdown import convert_markdown, markdown_factory "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'), + ("[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'), + ("[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: @@ -42,11 +43,11 @@ def test_convert_markdown_links(input_: str, expected: str) -> None: "input_, expected", [ # scheme - ('[test](https://)', 'https://'), + ("[test](https://)", "https://"), # netloc - ('[test](//test.md)', '//test.md'), + ("[test](//test.md)", "//test.md"), # no path - ('[test]()', ''), + ("[test]()", ""), ], ) def test_dont_convert_normal_links(input_: str, expected: str) -> None: @@ -58,13 +59,13 @@ def test_dont_convert_normal_links(input_: str, expected: str) -> None: @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']}), + ("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()}, + "date: 2020-01-01 12:10", + {"date": datetime(2020, 1, 1, 12, 10).astimezone()}, ), ], ) @@ -88,9 +89,9 @@ this --- is -- a test ... """ html, meta = convert_markdown(md, md1) - assert 'mdash' in html - assert 'ndash' in html - assert 'hellip' in html + assert "mdash" in html + assert "ndash" in html + assert "hellip" in html def test_smarty_code() -> None: @@ -102,6 +103,6 @@ this --- is -- a test ... ``` """ html, meta = convert_markdown(md, md1) - assert 'mdash' not in html - assert 'ndash' not in html - assert 'hellip' not in html + assert "mdash" not in html + assert "ndash" not in html + assert "hellip" not in html diff --git a/tests/test_quickstart.py b/tests/test_quickstart.py index d2b1dff..c976a29 100644 --- a/tests/test_quickstart.py +++ b/tests/test_quickstart.py @@ -1,5 +1,6 @@ # remove when we don't support py38 anymore from __future__ import annotations + import os from pytest import MonkeyPatch @@ -8,26 +9,26 @@ from blag.quickstart import get_input, quickstart def test_get_input_default_answer(monkeypatch: MonkeyPatch) -> None: - monkeypatch.setattr('builtins.input', lambda x: '') + monkeypatch.setattr("builtins.input", lambda x: "") answer = get_input("foo", "bar") - assert answer == 'bar' + assert answer == "bar" def test_get_input(monkeypatch: MonkeyPatch) -> None: - monkeypatch.setattr('builtins.input', lambda x: 'baz') + monkeypatch.setattr("builtins.input", lambda x: "baz") answer = get_input("foo", "bar") - assert answer == 'baz' + assert answer == "baz" def test_quickstart(cleandir: str, monkeypatch: MonkeyPatch) -> None: - monkeypatch.setattr('builtins.input', lambda x: 'foo') + monkeypatch.setattr("builtins.input", lambda x: "foo") quickstart(None) - with open('config.ini', 'r') as fh: + with open("config.ini", "r") as fh: data = fh.read() - assert 'base_url = foo' in data - assert 'title = foo' in data - assert 'description = foo' in data - assert 'author = foo' in data + assert "base_url = foo" in data + assert "title = foo" in data + assert "description = foo" in data + assert "author = foo" in data for template in ( "archive.html", @@ -38,7 +39,7 @@ def test_quickstart(cleandir: str, monkeypatch: MonkeyPatch) -> None: "tag.html", "tags.html", ): - assert os.path.exists(f'templates/{template}') + assert os.path.exists(f"templates/{template}") for directory in "build", "content", "static": assert os.path.exists(directory) diff --git a/tests/test_templates.py b/tests/test_templates.py index 53efa09..8fd0265 100644 --- a/tests/test_templates.py +++ b/tests/test_templates.py @@ -1,5 +1,6 @@ # remove when we don't support py38 anymore from __future__ import annotations + import datetime from jinja2 import Template @@ -7,91 +8,91 @@ from jinja2 import Template def test_page(page_template: Template) -> None: ctx = { - 'content': 'this is the content', - 'title': 'this is the title', + "content": "this is the content", + "title": "this is the title", } result = page_template.render(ctx) - assert 'this is the content' in result - assert 'this is the title' in result + assert "this is the content" in result + assert "this is the title" in result def test_article(article_template: Template) -> None: ctx = { - 'content': 'this is the content', - 'title': 'this is the title', - 'date': datetime.datetime(1980, 5, 9), + "content": "this is the content", + "title": "this is the title", + "date": datetime.datetime(1980, 5, 9), } result = article_template.render(ctx) - assert 'this is the content' in result - assert 'this is the title' in result - assert '1980-05-09' in result + assert "this is the content" in result + assert "this is the title" in result + assert "1980-05-09" in result def test_index(index_template: Template) -> None: entry = { - 'title': 'this is a title', - 'dst': 'https://example.com/link', - 'date': datetime.datetime(1980, 5, 9), + "title": "this is a title", + "dst": "https://example.com/link", + "date": datetime.datetime(1980, 5, 9), } archive = [entry] ctx = { - 'archive': archive, + "archive": archive, } result = index_template.render(ctx) - assert 'site title' in result + assert "site title" in result - assert 'this is a title' in result - assert '1980-05-09' in result - assert 'https://example.com/link' in result + assert "this is a title" in result + assert "1980-05-09" in result + assert "https://example.com/link" in result - assert '/archive.html' in result + assert "/archive.html" in result def test_archive(archive_template: Template) -> None: entry = { - 'title': 'this is a title', - 'dst': 'https://example.com/link', - 'date': datetime.datetime(1980, 5, 9), + "title": "this is a title", + "dst": "https://example.com/link", + "date": datetime.datetime(1980, 5, 9), } archive = [entry] ctx = { - 'archive': archive, + "archive": archive, } result = archive_template.render(ctx) - assert 'Archive' in result + assert "Archive" in result - assert 'this is a title' in result - assert '1980-05-09' in result - assert 'https://example.com/link' in result + assert "this is a title" in result + assert "1980-05-09" in result + assert "https://example.com/link" in result def test_tags(tags_template: Template) -> None: - tags = [('foo', 42)] + tags = [("foo", 42)] ctx = { - 'tags': tags, + "tags": tags, } result = tags_template.render(ctx) - assert 'Tags' in result + assert "Tags" in result - assert 'foo.html' in result - assert 'foo' in result - assert '42' in result + assert "foo.html" in result + assert "foo" in result + assert "42" in result def test_tag(tag_template: Template) -> None: entry = { - 'title': 'this is a title', - 'dst': 'https://example.com/link', - 'date': datetime.datetime(1980, 5, 9), + "title": "this is a title", + "dst": "https://example.com/link", + "date": datetime.datetime(1980, 5, 9), } archive = [entry] ctx = { - 'tag': 'foo', - 'archive': archive, + "tag": "foo", + "archive": archive, } result = tag_template.render(ctx) - assert 'foo' in result + assert "foo" in result - assert 'this is a title' in result - assert '1980-05-09' in result - assert 'https://example.com/link' in result + assert "this is a title" in result + assert "1980-05-09" in result + assert "https://example.com/link" in result From b90a54d0946d28bb9e14d58b24c5126bc4aaa111 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Fri, 16 Jun 2023 11:53:17 +0200 Subject: [PATCH 057/123] replaced sphinx with mkdocs --- .gitignore | 3 +- Makefile | 5 +- docs/CHANGELOG.md | 1 + docs/Makefile | 20 ---- docs/README.md | 1 + docs/api.md | 4 + docs/api.rst | 12 -- docs/blag.md | 1 + docs/blag.rst | 282 --------------------------------------------- docs/blag_.md | 1 + docs/conf.py | 69 ----------- docs/devserver.md | 1 + docs/index.md | 17 --- docs/index.rst | 53 --------- docs/make.bat | 35 ------ docs/manual.md | 264 ++++++++++++++++++++++++++++++++++++++++++ docs/markdown.md | 1 + docs/quickstart.md | 1 + docs/version.md | 1 + mkdocs.yml | 23 ++-- 20 files changed, 294 insertions(+), 501 deletions(-) create mode 120000 docs/CHANGELOG.md delete mode 100644 docs/Makefile create mode 120000 docs/README.md delete mode 100644 docs/api.rst create mode 100644 docs/blag.md delete mode 100644 docs/blag.rst create mode 100644 docs/blag_.md delete mode 100644 docs/conf.py create mode 100644 docs/devserver.md delete mode 100644 docs/index.md delete mode 100644 docs/index.rst delete mode 100644 docs/make.bat create mode 100644 docs/manual.md create mode 100644 docs/markdown.md create mode 100644 docs/quickstart.md create mode 100644 docs/version.md diff --git a/.gitignore b/.gitignore index 1f8d906..65fa4d1 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,7 @@ build/ dist/ *.egg-info/ -docs/_build/ -docs/api/ +site/ htmlcov/ .coverage diff --git a/Makefile b/Makefile index fce9102..28272c4 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ VENV = venv BIN=$(VENV)/bin DOCS_SRC = docs -DOCS_OUT = $(DOCS_SRC)/_build +DOCS_OUT = site ifeq ($(OS), Windows_NT) @@ -55,14 +55,13 @@ update-pygmentize: $(VENV) .PHONY: docs docs: $(VENV) - $(BIN)/sphinx-build $(DOCS_SRC) $(DOCS_OUT) + $(BIN)/mkdocs build .PHONY: clean clean: rm -rf build dist *.egg-info rm -rf $(VENV) rm -rf $(DOCS_OUT) - rm -rf $(DOCS_SRC)/api find . -type f -name *.pyc -delete find . -type d -name __pycache__ -delete # coverage diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md new file mode 120000 index 0000000..75c555f --- /dev/null +++ b/docs/CHANGELOG.md @@ -0,0 +1 @@ +/home/venthur/git/blag/CHANGELOG.md \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index d4bb2cb..0000000 --- a/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = . -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/README.md b/docs/README.md new file mode 120000 index 0000000..b52422b --- /dev/null +++ b/docs/README.md @@ -0,0 +1 @@ +/home/venthur/git/blag/README.md \ No newline at end of file diff --git a/docs/api.md b/docs/api.md index 874b638..7ddd345 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1,3 +1,7 @@ +# API + +::: blag + ::: blag.version ::: blag.blag diff --git a/docs/api.rst b/docs/api.rst deleted file mode 100644 index f856c26..0000000 --- a/docs/api.rst +++ /dev/null @@ -1,12 +0,0 @@ -API -=== - -.. autosummary:: - :toctree: api - - blag.__init__ - blag.version - blag.blag - blag.markdown - blag.devserver - blag.quickstart diff --git a/docs/blag.md b/docs/blag.md new file mode 100644 index 0000000..adae5eb --- /dev/null +++ b/docs/blag.md @@ -0,0 +1 @@ +::: blag.blag diff --git a/docs/blag.rst b/docs/blag.rst deleted file mode 100644 index 92239c9..0000000 --- a/docs/blag.rst +++ /dev/null @@ -1,282 +0,0 @@ -Manual -====== - - -Quickstart ----------- - -Install blag from PyPI_ - -.. code-block:: sh - - $ pip install blag - -.. _pypi: https://pypi.org/project/blag/ - -Run blag's quickstart command to create the configuration, templates and some -initial content. - -.. code-block:: sh - - $ blag quickstart - -Create some content - -.. code-block:: sh - - $ edit content/hello-world.md - -Generate the website - -.. code-block:: sh - - $ blag build - -By default, blag will search for content in ``content`` and the output will be -generated in ``build``. All markdown files in ``content`` will be converted to -html, all other files (i.e. static files) will be copied over). - -If you want more separation between the static files and the markdown content, -you can put all static files into the ``static`` directory. Blag will copy -them over to the ``build`` directory. - -If you want to customize the look of the generated site, visit the ``template`` -directory. It contains jinja2 templates and can be modified as needed. - -Those directories can be changed via command line arguments. See - -.. code-block:: sh - - $ blag --help - - -Manual ------- - - -Pages and Articles -^^^^^^^^^^^^^^^^^^ - -Internally, blag differentiates between **pages** and **articles**. -Intuitively, pages are simple pages and articles are blog posts. The decision -whether a document is a page or an article is made depending on the presence -of the ``date`` metadata element: Any document that contains the ``date`` -metadata element is an article, everything else a page. - -This differentiation has consequences: - -* blag uses different templates: ``page.html`` and ``article.html`` -* only articles are collected in the Atom feed -* only articles are aggregated in the tag pages - -blag does **not** enforce a certain directory structure for pages and -articles. You can mix and match them freely or structure them in different -directories. blag will mirror the structure found in the ``content`` directory - -:: - - content/ - article1.md - article2.md - page1.md - -results in: - -:: - - build/ - article1.html - article2.html - page1.html - -Arbitrary complex structures are possible too: - -:: - - content/ - posts/ - 2020/ - 2020-01-01-foo.md - 2020-02-01-foo.md - pages/ - foo.md - bar.md - -results in: - -:: - - build/ - posts/ - 2020/ - 2020-01-01-foo.html - 2020-02-01-foo.html - pages/ - foo.html - bar.html - - -Static Files -^^^^^^^^^^^^ - -Static files can be put into the ``content`` directory and will be copied over -to the ``build`` directory as well. If you want better separation between -content and static files, you can use the ``static`` directory and put the -files there. All files and directories found in the ``static`` directory will -be copied over to ``build``. - -:: - - content/ - foo.md - bar.md - kitty.jpg - -results in: - -:: - - build/ - foo.html - bar.html - kitty.jpg - -Alternatively: - -:: - - content/ - foo.md - bar.md - static/ - kitty.jpg - -results in: - -:: - - build/ - foo.html - bar.html - kitty.jpg - - -Internal Links --------------- - -In contrast to most other static blog generators, blag will automatically -convert **relative** markdown links. That means you can link you content using -relative markdown links and blag will convert them to html automatically. The -advantage is that your content tree in markdown is consistent and -self-contained even if you don't generate html from it. - - -.. code-block:: markdown - - [...] - this is a [link](foo.md) to an internal page foo. - -becomes - -.. code-block:: html - -

this is a link to an internal page foo.

- - -Templating ----------- - -Templates are stored by default in the ``templates`` directory. - -============ ====================================== =================== -Template Used For Variables -============ ====================================== =================== -page.html pages (i.e. non-articles) site, content, meta -article.html articles (i.e. blog posts) site, content, meta -index.html landing page of the blog site, archive -archive.html archive page of the blog site, archive -tags.html list of tags site, tags -tag.html archive of Articles with a certain tag site, archive, tag -============ ====================================== =================== - -If you make use of Jinja2's template inheritance, you can of course have more -template files in the ``templates`` directory. - -``site`` - This dictionary contains the site configuration, namely: ``base_url``, - ``title``, ``description`` and ``author``. Don't confuse the site-title - and -description with the title and description of individual pages or - articles. - -``content`` - HTML, converted from markdown. - -``meta`` - ``meta`` stands for all metadata elements available in the article or - page. Please be aware that those are not wrapped in a dictionary, but - **directly** available as variables. - -``archive`` - A list of ``[destination path, context]`` tuples, where the context are - the respective variables that would be provided to the individual page or - article. - -``tags`` - List of tags. - -``tag`` - A tag. - - -Metadata ---------- - -blag supports metadata elements in the markdown files. They must come before -the content and should be separated from the content with a blank line: - -.. code-block:: markdown - - title: foo - date: 2020-02-02 - tags: this, is, a, test - description: some subtitle - - this is my content. - [...] - -blag supports *arbitrary* metadata in your documents, and you can use them -freely in you templates. However, some metadata elements are treated special: - -``date`` - If a document contains the ``date`` element, it is treated as an - **article**, otherwise as a **page**. Additionally, ``date`` elements are - expected to be in ISO format (e.g. ``1980-05-05 21:58``). They are - automatically converted into ``datetime`` objects with the local timezone - attached. - -``tags`` - Tags are interpreted as a comma separated list. All elements are stripped - and converted to lower-case: ``tags: foo, Foo Bar, BAZ`` becomes: ``[foo, - foo bar, baz]``. - - Tags in **articles** are also used to generate the tag-pages, that - aggregate all articles per tag. - -``title`` and ``description`` - The title and description are used in the html header and in the atom - feed. - - -Devserver ---------- - -blag provides a devserver which you can use for local web-development. The -devserver provides a simple web server, serving your site in -http://localhost:8000 and will automatically rebuild the project when it -detects modifications in one of the ``content``, ``static`` and ``templates`` -directories. - -.. code-block:: sh - - $ blag serve - diff --git a/docs/blag_.md b/docs/blag_.md new file mode 100644 index 0000000..317c29f --- /dev/null +++ b/docs/blag_.md @@ -0,0 +1 @@ +::: blag diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 4cb1944..0000000 --- a/docs/conf.py +++ /dev/null @@ -1,69 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. - -import os -import sys -sys.path.insert(0, os.path.abspath('..')) - -import blag - - -# -- Project information ----------------------------------------------------- - -project = 'blag' -copyright = '2021, Bastian Venthur' -author = 'Bastian Venthur' - -# The full version, including alpha/beta/rc tags -release = blag.__VERSION__ - - -# -- General configuration --------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.autosummary', - 'sphinx.ext.autodoc', - 'sphinx.ext.napoleon', -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'alabaster' - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -autodoc_default_options = { - 'members': True, - 'undoc-members': True, - 'private-members': True, - 'special-members': True, -} - -autosummary_generate = True diff --git a/docs/devserver.md b/docs/devserver.md new file mode 100644 index 0000000..e62835e --- /dev/null +++ b/docs/devserver.md @@ -0,0 +1 @@ +::: blag.devserver diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index 000ea34..0000000 --- a/docs/index.md +++ /dev/null @@ -1,17 +0,0 @@ -# Welcome to MkDocs - -For full documentation visit [mkdocs.org](https://www.mkdocs.org). - -## Commands - -* `mkdocs new [dir-name]` - Create a new project. -* `mkdocs serve` - Start the live-reloading docs server. -* `mkdocs build` - Build the documentation site. -* `mkdocs -h` - Print help message and exit. - -## Project layout - - mkdocs.yml # The configuration file. - docs/ - index.md # The documentation homepage. - ... # Other markdown pages, images and other files. diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 731814f..0000000 --- a/docs/index.rst +++ /dev/null @@ -1,53 +0,0 @@ -.. blag documentation master file, created by - sphinx-quickstart on Sun Mar 21 13:39:00 2021. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to blag! -================ - -blag is a blog-aware, static site generator, written in Python_. An example -"deployment" can be found here_. - -blag is named after the blag of the webcomic xkcd_. - -.. _python: https://python.org -.. _xkcd: https://blog.xkcd.com -.. _here: https://venthur.de - - -Features --------- - -* Write content in Markdown_ -* Theming support using Jinja2_ templates -* Generation of Atom feeds for blog content -* Fenced code blocks and syntax highlighting using Pygments_ -* Integrated devserver -* Available on PyPI_ - -blag runs on Linux, Mac and Windows and requires Python >= 3.8 - -.. _markdown: https://daringfireball.net/projects/markdown/ -.. _jinja2: https://palletsprojects.com/p/jinja/ -.. _pygments: https://pygments.org/ -.. _pypi: https://pypi.org/project/blag/ - - -Documentation -============= - -.. toctree:: - :maxdepth: 2 - :caption: Contents: - - - blag.rst - api.rst - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 2119f51..0000000 --- a/docs/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=. -set BUILDDIR=_build - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/docs/manual.md b/docs/manual.md new file mode 100644 index 0000000..072e508 --- /dev/null +++ b/docs/manual.md @@ -0,0 +1,264 @@ +# Manual + + +## Quickstart + +Install blag from [PyPI][] + +```sh +$ pip install blag +``` + +[pypi]: https://pypi.org/project/blag/ + +Run blag's quickstart command to create the configuration, templates and some +initial content. + +```sh +$ blag quickstart +``` + +Create some content + +```sh +$ edit content/hello-world.md +``` + +Generate the website + +```sh +$ blag build +``` + +By default, blag will search for content in `content` and the output will be +generated in `build`. All markdown files in `content` will be converted to +html, all other files (i.e. static files) will be copied over). + +If you want more separation between the static files and the markdown content, +you can put all static files into the `static` directory. Blag will copy them +over to the `build` directory. + +If you want to customize the look of the generated site, visit the `template` +directory. It contains jinja2 templates and can be modified as needed. + +Those directories can be changed via command line arguments. See + +```sh +$ blag --help +``` + + +## Manual + +### Pages and Articles + +Internally, blag differentiates between **pages** and **articles**. +Intuitively, pages are simple pages and articles are blog posts. The decision +whether a document is a page or an article is made depending on the presence of +the `date` metadata element: Any document that contains the `date` metadata +element is an article, everything else a page. + +This differentiation has consequences: + +* blag uses different templates: `page.html` and `article.html` +* only articles are collected in the Atom feed +* only articles are aggregated in the tag pages + +blag does **not** enforce a certain directory structure for pages and articles. +You can mix and match them freely or structure them in different directories. +blag will mirror the structure found in the `content` directory + +``` +content/ + article1.md + article2.md + page1.md +``` + +results in: + +``` +build/ + article1.html + article2.html + page1.html +``` + +Arbitrary complex structures are possible too: + +``` +content/ + posts/ + 2020/ + 2020-01-01-foo.md + 2020-02-01-foo.md + pages/ + foo.md + bar.md +``` + +results in: + +``` +build/ + posts/ + 2020/ + 2020-01-01-foo.html + 2020-02-01-foo.html + pages/ + foo.html + bar.html +``` + + +### Static Files + +Static files can be put into the `content` directory and will be copied over to +the `build` directory as well. If you want better separation between content +and static files, you can use the `static` directory and put the files there. +All files and directories found in the `static` directory will be copied over +to `build`. + +``` +content/ + foo.md + bar.md + kitty.jpg +``` + +results in: + +``` +build/ + foo.html + bar.html + kitty.jpg +``` + +Alternatively: + +``` +content/ + foo.md + bar.md +static/ + kitty.jpg +``` + +results in: + +``` +build/ + foo.html + bar.html + kitty.jpg +``` + + +### Internal Links + +In contrast to most other static blog generators, blag will automatically +convert **relative** markdown links. That means you can link you content using +relative markdown links and blag will convert them to html automatically. The +advantage is that your content tree in markdown is consistent and +self-contained even if you don't generate html from it. + + +```markdown +[...] +this is a [link](foo.md) to an internal page foo. +``` + +becomes + +```html +

this is a link to an internal page foo.

+``` + +```python +def this_is_a(test): + pass +``` + +### Templating + +Templates are stored by default in the `templates` directory. + +Template | Used For | Variables +------------ | -------------------------------------- | ------------------- +page.html | pages (i.e. non-articles) | site, content, meta +article.html | articles (i.e. blog posts) | site, content, meta +index.html | landing page of the blog | site, archive +archive.html | archive page of the blog | site, archive +tags.html | list of tags | site, tags +tag.html | archive of Articles with a certain tag | site, archive, tag + +If you make use of Jinja2's template inheritance, you can of course have more +template files in the `templates` directory. + + +#### Variables + +* `site`: This dictionary contains the site configuration, namely: `base_url`, + `title`, `description` and `author`. Don't confuse the site-title and + -description with the title and description of individual pages or articles. + +* `content`: HTML, converted from markdown. + +* `meta`: stands for all metadata elements available in the article or page. + Please be aware that those are not wrapped in a dictionary, but **directly** + available as variables. + +* `archive`: A list of `[destination path, context]` tuples, where the context + are the respective variables that would be provided to the individual page or + article. + +* `tags`: List of tags. + +* `tag`: A tag. + + +### Metadata + +blag supports metadata elements in the markdown files. They must come before +the content and should be separated from the content with a blank line: + +```markdown +title: foo +date: 2020-02-02 +tags: this, is, a, test +description: some subtitle + +this is my content. +[...] +``` + +blag supports *arbitrary* metadata in your documents, and you can use them +freely in you templates. However, some metadata elements are treated special: + +* `date`: If a document contains the `date` element, it is treated as an + **article**, otherwise as a **page**. Additionally, `date` elements are + expected to be in ISO format (e.g. `1980-05-09 21:58`). They are + automatically converted into `datetime` objects with the local timezone + attached. + +* `tags`: Tags are interpreted as a comma separated list. All elements are + stripped and converted to lower-case: `tags: foo, Foo Bar, BAZ` becomes: + `[foo, foo bar, baz]`. Tags in **articles** are also used to generate the + tag-pages, that aggregate all articles per tag. + +* `title` and `description`: The title and description are used in the html + header and in the atom feed. + + +## Devserver + +blag provides a devserver which you can use for local web-development. The +devserver provides a simple web server, serving your site in +http://localhost:8000 and will automatically rebuild the project when it +detects modifications in one of the `content`, `static` and `templates` +directories. + +```sh +$ blag serve +``` diff --git a/docs/markdown.md b/docs/markdown.md new file mode 100644 index 0000000..9310fa0 --- /dev/null +++ b/docs/markdown.md @@ -0,0 +1 @@ +::: blag.markdown diff --git a/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 0000000..a67addd --- /dev/null +++ b/docs/quickstart.md @@ -0,0 +1 @@ +::: blag.quickstart diff --git a/docs/version.md b/docs/version.md new file mode 100644 index 0000000..326829d --- /dev/null +++ b/docs/version.md @@ -0,0 +1 @@ +::: blag.version diff --git a/mkdocs.yml b/mkdocs.yml index 0d34f3f..2ad3b2c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,12 +1,22 @@ site_name: blag site_url: https://blag.readthedocs.io/ -nav: - - Home: index.md - - Manual: manual.md - - API: api.md -theme: material repo_url: https://github.com/venthur/blag repo_name: venthur/blag + +nav: + - Home: README.md + - Manual: manual.md + - API: + - blag: blag_.md + - blag.version: version.md + - blag.blag: blag.md + - blag.markdown: markdown.md + - blag.devserver: devserver.md + - blag.quickstart: quickstart.md + - Changelog: CHANGELOG.md + +theme: material + plugins: - search: - mkdocstrings: @@ -14,6 +24,3 @@ plugins: python: options: docstring_style: numpy - allow_inspection: true - show_root_heading: true - show_if_no_docstring: true From 92ad157e310a73c2f35346969c1662e14cdc8a65 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Fri, 16 Jun 2023 12:26:09 +0200 Subject: [PATCH 058/123] fixed highlighting --- mkdocs.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 2ad3b2c..b461bd3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -15,7 +15,12 @@ nav: - blag.quickstart: quickstart.md - Changelog: CHANGELOG.md -theme: material +theme: + name: material + highlightjs: true + +markdown_extensions: + - pymdownx.superfences plugins: - search: From 86868c7c5276febe1418ef199a87a5102b1fbe3f Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Fri, 16 Jun 2023 12:26:41 +0200 Subject: [PATCH 059/123] updated changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index be2f7eb..eaef0b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,8 @@ * types-markdown 3.4.2.9 * build 0.10.0 +* Switched from sphinx to mkdocs + ### Fixed * fixed pyproject.toml to include tests/conftest.py From d918e8e0f9e88d37e556877ce02871131c503342 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Fri, 16 Jun 2023 22:14:03 +0200 Subject: [PATCH 060/123] configure readthedocs --- .readthedocs.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index f076753..eda0cab 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,7 +1,9 @@ version: 2 +mkdocs: + configuration: mkdocs.yml + python: - version: 3.8 install: - requirements: requirements.txt - requirements: requirements-dev.txt From da364efd6c6230c4db421e477dcd7a8478b99e00 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Fri, 16 Jun 2023 22:15:41 +0200 Subject: [PATCH 061/123] more rtd config --- .readthedocs.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index eda0cab..150d739 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,5 +1,10 @@ version: 2 +build: + os: ubuntu-22.04 + tools: + python: "3.11" + mkdocs: configuration: mkdocs.yml From a6e0a7168cc193f0dc1dc70a6e7dc15dd9e56367 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Fri, 16 Jun 2023 22:20:11 +0200 Subject: [PATCH 062/123] removed docs/api.md --- docs/api.md | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 docs/api.md diff --git a/docs/api.md b/docs/api.md deleted file mode 100644 index 7ddd345..0000000 --- a/docs/api.md +++ /dev/null @@ -1,13 +0,0 @@ -# API - -::: blag - -::: blag.version - -::: blag.blag - -::: blag.markdown - -::: blag.devserver - -::: blag.quickstart From 08af02b9d1c52e822e70e0d457489da49fb3fb1e Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Fri, 16 Jun 2023 22:30:18 +0200 Subject: [PATCH 063/123] more fixes for rtd --- docs/CHANGELOG.md | 1 - docs/README.md | 1 - docs/index.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++ mkdocs.yml | 3 +-- 4 files changed, 57 insertions(+), 4 deletions(-) delete mode 120000 docs/CHANGELOG.md delete mode 120000 docs/README.md create mode 100644 docs/index.md diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md deleted file mode 120000 index 75c555f..0000000 --- a/docs/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -/home/venthur/git/blag/CHANGELOG.md \ No newline at end of file diff --git a/docs/README.md b/docs/README.md deleted file mode 120000 index b52422b..0000000 --- a/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -/home/venthur/git/blag/README.md \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..daea224 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,56 @@ +# Welcome to blag! + +blag is a blog-aware, static site generator, written in [Python][]. + +* an example "deployment" can be found [here][venthur.de] +* online [documentation][] is available on https://readthedocs.org. + +blag is named after [the blag of the webcomic xkcd][blagxkcd]. + +[python]: https://python.org +[blagxkcd]: https://blog.xkcd.com +[venthur.de]: https://venthur.de +[documentation]: https://blag.readthedocs.io/en/latest/ + + +## Features + +* Write content in [Markdown][] +* Good looking default theme +* Theming support using [Jinja2][] templates +* Generation of Atom feeds for blog content +* Fenced code blocks and syntax highlighting using [Pygments][] +* Integrated devserver +* Available on [PyPI][] + +blag runs on Linux, Mac and Windows and requires Python >= 3.8 + +[markdown]: https://daringfireball.net/projects/markdown/ +[jinja2]: https://palletsprojects.com/p/jinja/ +[pygments]: https://pygments.org/ +[pypi]: https://pypi.org/project/blag/ + + +## Install + +blag is available on [PyPI][], you can install it via: + +```bash +$ pip install blag +``` + +On Debian or Ubuntu, you can also just install the Debian package: + +```bash +$ sudo aptitude install blag +``` + + +## Quickstart + +```bash +$ pip install blag # 1. install blag +$ blag quickstart # 2. create a new site +$ vim content/hello-world.md # 3. create some content +$ blag build # 4. build the website +``` diff --git a/mkdocs.yml b/mkdocs.yml index b461bd3..e891eaf 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -4,7 +4,7 @@ repo_url: https://github.com/venthur/blag repo_name: venthur/blag nav: - - Home: README.md + - Home: index.md - Manual: manual.md - API: - blag: blag_.md @@ -13,7 +13,6 @@ nav: - blag.markdown: markdown.md - blag.devserver: devserver.md - blag.quickstart: quickstart.md - - Changelog: CHANGELOG.md theme: name: material From 175812e18cf7ad51cde865f8b15171f69e9f4b04 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Fri, 16 Jun 2023 23:06:47 +0200 Subject: [PATCH 064/123] prepared version 2.0.0 --- CHANGELOG.md | 2 +- blag/version.py | 2 +- debian/blag-doc.docs | 2 +- debian/blag.install | 1 - debian/changelog | 41 +++++++++++++++++++++++++++++++++++++++++ debian/control | 8 +++++--- debian/rules | 12 ++---------- 7 files changed, 51 insertions(+), 17 deletions(-) delete mode 100644 debian/blag.install diff --git a/CHANGELOG.md b/CHANGELOG.md index eaef0b0..0f918f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [unreleased] +## [2.0.0] - 2023-06-16 ### Breaking diff --git a/blag/version.py b/blag/version.py index 7e6a9cc..3896400 100644 --- a/blag/version.py +++ b/blag/version.py @@ -1 +1 @@ -__VERSION__ = "1.5.0" +__VERSION__ = "2.0.0" diff --git a/debian/blag-doc.docs b/debian/blag-doc.docs index 344fcaa..45ddf0a 100644 --- a/debian/blag-doc.docs +++ b/debian/blag-doc.docs @@ -1 +1 @@ -build/html/ +site/ diff --git a/debian/blag.install b/debian/blag.install deleted file mode 100644 index 28d4e43..0000000 --- a/debian/blag.install +++ /dev/null @@ -1 +0,0 @@ -build/man/blag.1 /usr/share/man/man1 diff --git a/debian/changelog b/debian/changelog index fcf6839..8c41d4d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,44 @@ +blag (2.0.0) unstable; urgency=medium + + * new upstream version + + * breaking: + * blag does not use default fallback templates anymore and will return an + error if it is unable to find required templates, e.g. in `templates/`. + Users upgrading from older versions can either run `blag quickstart` + (don't forget to backup your `config.ini` or copy the templates from + blag's resources (the resource path is shown in the error message). + New users are not affected as `blag quickstart` will generate the needed + templates. + * Split former archive page which served as index.html into "index" and + "archive", each with their own template, respectively. Index is the + landing page and shows by default only the latest 10 articles. Archive + shows the full list of articles. + If you used custom templates, + * you should create an "index.html"-template (take blag's default one as + a starting point) + * you may want to include the new "/archive.html" link somewhere in your + navigation + + * Changes: + * blag comes now with a simple yet good looking default theme that + supports syntax highlighting and a light- and dark theme. + * apart from the generated configuration, `blag quickstart` will now also + create the initial directory structure, with the default template, the + static directory with the CSS files and the content directory with some + initial content to get the user started + * Added a make target to update the pygments themes + * updated dependencies: + * markdown 3.4.3 + * pygments 2.15.1 + * pytest 7.3.2 + * types-markdown 3.4.2.9 + * build 0.10.0 + * Switched from sphinx to mkdocs + * fixed pyproject.toml to include tests/conftest.py + + -- Bastian Venthur Fri, 16 Jun 2023 22:34:29 +0200 + blag (1.5.0) unstable; urgency=medium * new upstream version diff --git a/debian/control b/debian/control index 32a11f6..44ae262 100644 --- a/debian/control +++ b/debian/control @@ -5,7 +5,6 @@ Maintainer: Bastian Venthur Rules-Requires-Root: no Build-Depends: debhelper-compat (= 13), - dh-sequence-sphinxdoc, dh-sequence-python3, dh-python, pybuild-plugin-pyproject, @@ -17,7 +16,9 @@ Build-Depends: python3-pygments, python3-pytest, python3-pytest-cov, - python3-sphinx, + mkdocs, + mkdocs-material, + mkdocstrings-python-handlers, #Testsuite: autopkgtest-pkg-python Standards-Version: 4.6.0.1 Homepage: https://github.com/venthur/blag @@ -35,6 +36,7 @@ Description: Blog-aware, static site generator Blag is a blog-aware, static site generator, written in Python. It supports the following features: * Write content in Markdown + * Good looking default theme * Theming support using Jinja2 templates * Generation of Atom feeds for blog content * Fenced code blocks and syntax highlighting using Pygments @@ -45,13 +47,13 @@ Package: blag-doc Section: doc Architecture: all Depends: - ${sphinxdoc:Depends}, ${misc:Depends}, Multi-Arch: foreign Description: Blog-aware, static site generator (documentation) Blag is a blog-aware, static site generator, written in Python. It supports the following features: * Write content in Markdown + * Good looking default theme * Theming support using Jinja2 templates * Generation of Atom feeds for blog content * Fenced code blocks and syntax highlighting using Pygments diff --git a/debian/rules b/debian/rules index d61e888..9368a86 100755 --- a/debian/rules +++ b/debian/rules @@ -9,17 +9,9 @@ export PYBUILD_TEST_ARGS=--no-cov export PYBUILD_NAME=blag %: - dh $@ --with python3,sphinxdoc --buildsystem=pybuild + dh $@ --with python3 --buildsystem=pybuild - -# If you need to rebuild the Sphinx documentation: -# Add sphinxdoc to the dh --with line. -# -# And uncomment the following lines. execute_after_dh_auto_build-indep: export http_proxy=127.0.0.1:9 execute_after_dh_auto_build-indep: export https_proxy=127.0.0.1:9 execute_after_dh_auto_build-indep: - PYTHONPATH=. python3 -m sphinx -N -bhtml \ - docs/ build/html # HTML generator - PYTHONPATH=. python3 -m sphinx -N -bman \ - docs/ build/man # Manpage generator + PYTHONPATH=. mkdocs build From 1ebae15cda04b46e22b04739ddf3d4fdb037ce71 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Sat, 17 Jun 2023 15:24:00 +0200 Subject: [PATCH 065/123] added screenshot --- README.md | 3 ++- blag/content/second-post.md | 2 ++ blag/static/blag.png | Bin 0 -> 96105 bytes docs/index.md | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 blag/static/blag.png diff --git a/README.md b/README.md index 3249765..1038bb8 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,8 @@ blag is named after [the blag of the webcomic xkcd][blagxkcd]. ## Features * Write content in [Markdown][] -* Good looking default theme +* Good looking default theme: + ![Blag Screenshot](blag/static/blag.png) * Theming support using [Jinja2][] templates * Generation of Atom feeds for blog content * Fenced code blocks and syntax highlighting using [Pygments][] diff --git a/blag/content/second-post.md b/blag/content/second-post.md index 5a35d4b..354fac5 100644 --- a/blag/content/second-post.md +++ b/blag/content/second-post.md @@ -7,3 +7,5 @@ Tags: blag ## Second Post This page serves no purpose :) + +![Blag Screenshot](blag.png) diff --git a/blag/static/blag.png b/blag/static/blag.png new file mode 100644 index 0000000000000000000000000000000000000000..06445a281edffe20daa07389159a1c746be91339 GIT binary patch literal 96105 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%0DCC`yP|^qb1owzZ?m@)&fD3 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 Date: Sat, 17 Jun 2023 15:29:46 +0200 Subject: [PATCH 066/123] corrected screenshot path --- docs/blag.png | Bin 0 -> 96105 bytes docs/index.md | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 docs/blag.png diff --git a/docs/blag.png b/docs/blag.png new file mode 100644 index 0000000000000000000000000000000000000000..06445a281edffe20daa07389159a1c746be91339 GIT binary patch literal 96105 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%0DCC`yP|^qb1owzZ?m@)&fD3 zE>9%391%4HJR0UWDPeICgs{Yp?-GF<21a$>{r6lQqX~q22)zi4h5TAFvwub$C)KKi z7Oz6~`K*qUWMpK%LzdsJ{sV;njl5eG-*ZnJK7Cp$Aqsg74T7aso}eX^iL+XBd!LWU z(fJ#M!Sb8P;KaY5;(wwGUSp#Y=iwbk4dAnMvP|;Sr~BVy^Rx&SeJ`(3pXMcY{Ebkp z7Bu`n(;ANk)AI7nla}kYpfWEH?PHyOnAD(C!crErCCMnb=9vaE#GZLi@A=Q@SRey( zqZHhDDDgPR9>U_9HIPDOArM!v3Lvmmjkd_xbyM@RSk^!gQJ)s>|3c~4Xiqc34ugk4 zd>{j3E#R6UCUB`x$4O-{s`GJDa@LO;sL<*^*5Lnk*1M4wVolFHoh`rY?GI{v1bci$ zD5w%Ikb=zUaq;sq-L`XxW@#^n(iKOT$FjfL-HX({|e9k6ZOM_^IeHAq2WoS6j}$w*MbLF@tMQP zU%?Mtf?+37f1K_xofR>g_28avN6R@i8Tg$AtCxhm6zgkcfE zI5RXvXJDeJQAy5=sLlre$}s%<1RljkDT0T9ULHB5gcVxxTbg5{L3erF~;y zfmL|6&u^rB5Io4lDf7XG``@9qx2>`$7nwq`sZ<18M)VX;orZad&bCVoAYe}n#v2{` z;vg3||6{iP^OX`J2p+(EVRaaQ2lx}5TY&3Db6PAw{_ahs2hY#iC2Kf83hr&9RTdGm z|9L0;)tCjs#6^Qai35`nzQk-WYHcB+NLi-p*{rPQHxU!hyqb>I9_o~G&k|tgO z`#hmn?#_!>?ToCM3n3R`sj6vZK9sUWNQ+6^{w^Gr_y2wVXw0$IHR_zTYCmVzSlC#^ zl`Y%KFI|UstCV=mz?tRbn)!riUiLqeqj)+$p+l_Glxk|4^eBZP1__S(uko$Saoo?i z8@BriHRy=3GN}J`(0TDgtl$J)9Z=p>!HTfKd{2(JGLiGYQlb0=*3hXZ|GRUY^KgkAgWW^k}CBrSYoABvIfb?+3fI>Q0hQV8szG-B9uTw#w@7>t|K!rDZjYW$Jf2u9dWD`usl1wS5_ z12`tbzp_eHy6j!YK&GZIv8<`e^HI5=JND($J#F2Nk1-c4s7EBSnZlmpFs#hh?7FV^ z2J?^Bqxp|Qn?nbS8D)7aKrA35xZSQZ^ zs|FX!2xLkZOe_spX!gUt(-9h-=;gZ@qJZ4@3tK5J_?AAGw`MO(-5S-I%<;A&5332w z2HfSV%oziBF04}9@^S?p_KicDyd#1_ySi;Dsd()O{B7IJoZI>syY*c+ZWt9n!4Kx{ z&R)}|OH80*4N%daV(38qFSzuZ4H**ZY@r>b0v`r%jI6NY4VHZQo&S(Gvwe z5;VClxC2u4AnxPkh#}P8a`q>z$0gD7wte5V(b5A1#dX)3|5~rz=O9(1K*+5(mIjnC zih_c}@B4ORqEjVWOP)tZW016U*m1w4)BCRVA1**0Mm^vlV9w}*RBYBI!Lyx54H6I~ zoqwt;tEyrFf&|oyI&L$+y(lZ}IgW?6Li5GaDM@LJw5N*agTj6s`f|Ks&LjbO8BiKx zU1=fZ->$I;yuZEtz|9`-vgD50J-o{D3Vz}PzoIgoGTH?QjN;jmMMeOAT5W6oz<+wO zQADTtD;a;HNUq6hM@!aabI)zo+3s$zvN`x*Iv2M~Hg)u2YAY_`&2;n7qor@eg+EM$nX=;k%9gaq>WhI&h?c*9%R25UCV zE+dC~vp&8^#*(K8-@gKKT|;P@XV|stdt=FXz5P6TI`s-9P(KKGwHKH#wmD!v27i8K zw9W&lOO=ij`0-^rVqZNUP)N?;AH17pT}zVLR&rbx0aUG6zBDeLsZSbi^}n*aLf6uE z$VZ$42X2IH5d;+`Bb9Mh^R;OXxsuG)KAV`kAgeC*U_*EK>doi;+96Q5BrzvA-ll!lYoALAj^_V=CNB@Empjs+4is$m$EvG}ygts0gI z#5>h{+){L1mc1k2;fJYPGvu^4gIqlNE=SjI9>c#ac3kKWy`#YQ*a;SvdD1UI5Jo{4JkSl``a2a(02`s!$ z%)9+Y`j-c3YIJn#10=ALuSTZcDb$+Bg>Ukl>broiDya4<^I8OV&tWi<&vYxl` zH1bc{CMNyqecg|@ezx9s!B5}bx|G0oap^w%d-jPe3UZF^^I>1`Y>J%Ay_RS~gxodo zU#l|4q^Aptii!eg8n1#3N!!je0nl4xL+^0sh%t1i5p>`R$R_q?35$`K|NhqURf_nL z9#F{=2TVZ8kBmG7ckJPDV~b=guO=5`H%%Y1bF+&47&6p6W0fbd5t10z`8dP;M06P* z{vH^yb+9Qq@K^cad~5u%*8%RX!ky0~CV0t2Mt{A*_QTEE<(2iO(V04i4KV^TE~DY- z`Wpvfu$%D;TwPJBEoKunCJImXuf(mE(%i_lw$`L~FglKpPe_@$@s*X|urE6u%tN~x za`?%qdmIeCKxf)b<9s;Sc$N0x&TuT$EYqpM;~-5qK{4)&j*9iJDR^l#9NB zCJ$iQ-@l(KXgB$bW7zk?EELFO|p+IoGU(EqA}C@V@!`rBH=# z=!`2qWf1u05sziL-%b{?&}25^-+Pe|o|hl2bRICtY1ljUm|nY`W6eEw&!bK;5^c!{ zu^JcFJYPPGk|tcN+0%8pTZo7>y6%^+-Z`fhk(heNegfxncae1QILz$C?8B%Y%EnFf z#F*o$4fk-dQ7AVv4*h(xD>fhDBgMh<*rKow*l0oZ-VmgX-b&L)*la_CFIZ@x-alPy znUH$r_sF}Ljw85b@u|Jp`OUe?icZ;>r8UOIanhSNJK&C09#?K7Q40-27B+ApMo!j` z#X&iRvMKX`Xjw_myuw1IA`Orp^%9nk;cF~0mJf%X)IWkzVJht7 z^LgMQ9mtvLIX8ggXEgY&BjQw2^2M@=C!F5S3!Nr+dP%^Uz#B62;zlR9JZf5wu-1IW zWPM?a+Gah5j}ouqTYct;VY84Y*P`dE6$BJ_s5A99cI$ zc@h5yh)3UaaseMYFq5PC==fVQ|5E-3;Q8ay`OD-}@+~N26RK1?=TU8J3O;%^6^Em!F9rFY6AvQC$Ynp#{7YUj7_BlYfPmJ23*_Xd`nmTja{mvw{4 zg5BdqhNpiL$Ah$ijh5?jrHb`%+)2%xp@Z|*-7(Q8ZJyfi-eAV6Py<|Lt=|l7q3FkojVRM$z@hZ&O;am!+ zVVw3Ab4ew$FW5KZFb?{9djVO{ccA9Hw8?0+(<@LpP^O*};J7pX6%H1*y}k1(-S3^o z5_xv!n9OeV2jNiT?iIVuVlSu>Y>(|41JMk(QZF%3+Lg!hPiJ(>b&C;Qw|CiE?U1~? znbTso_PR19v)t{AC877~h6FVCH9bQhG56vpK4u92t4ZTk>CPKVr4qKt*!-ygqXT>f z=)JCR?2z!*tq6&YjkW5V$eZ|rLcj|17QoHA1Y9o7Jq3xu3{5Cq9nF;lIc5XqU&BRWxQmZ=tbi}z=jV4apqfL}9%w#h%Ho`wOSHd{ zz-`EkuU_d$e*E$!7Nd$^-6G+~UzHK`%zzT?d3*0#YrWI~y$!~qS4afZ0`ZdMiE4Y` z!jd?R;rmpJH${!!mzI_Sl4~$OR2Ogn^>sXVlp_K(X+}FEZlfyJygk?BgKV_9nQU7y zI%QKuQzq$|Qs~HcWAX-{5}AO^$sJN57J1wz8v|H6C{P{Q4S>X1m)Vf8tE)R^>df|* zYF_7zIJ{D9lHl7vtunpUNKGU0T&o8J$o|A@f-OcZA;4*gr|Run;8q0H3ad3@Zo0-4=_I5PG(|LxVk|4Iv4;z`P{tR zR~3#082#5y7%+j6eq-mxp<6P+t0GAp9)E@FwxV-s6M3B|UQd-`YSg3KLT4Vc#sC7$ zmanRf-$jVzmAX2%)&8>8T4@4>H1C@S+NOsKN+8|#6{vp@ z>6i&OH$m0F(^AYESf4#703C2lCkt7z)Y{B*{BF}X(^Xa-7Jvanb4sY&WKwJ2`N$Nn z!B)f-Hdimt_jjsfz(qjnY2B>}RP~wP)T~_%N7;efT-34jo{S8_5diHA%<$S_njdR}0IGY%<+45oCcW{UlcmbmHFBY)Y9g7WnEQ7N}0FEE1p_2P;^s@29z0!Ir z-Rco^7WJ@a@E(9Bf$sd0wdOZ2vI4(R(?16zh1=Pme*BP#NTn8CTjnXVg2DDM$}1eA znsZ99r1(=!S&G31Z(tCMN-5bH%AeyoP>Y6545O6uW(iIN=I8`@vJult$k(eu3`l5nTeq3fUg}qaRkY%gn}Dee9omU$=pb z+I&c)XZiMUIf0WVG@>Dp5grn3uz@dEB|h}H$CW8n5bgOmf$Ms^ZrmHDUaa2ybyFJu zctKUy?1OJ)Z$3%$5bZ*PJ?9-BW}eKNMY}TmrB50VH{-KQJ_elc1a+LHxH!uxW`l7v zz-e7)lF^ZseK>@?Cgkys{)LQV$eiTr(XEF+Ub4ot7)qqGeD4zQnt-4SFz0|u5S^F% zg&f`{wvedK2;iEM-G$}_KAO2C0N25oEa)ir?%zTI=a-%Z_v+8&9VPQRU| zZ$VSSR@QC?fTcn5IWv`B2rvTD$^GDGAi{e`RyGv7)68tWx%dC5`s%2vx~*@dB@Q7V z-5}B^B`Gb^NE|u^q>+>q4&5Lkh}1#phC?^f(%q$`lyrX!pZnhDe*efAj=lF;Yt1$5 zH_LWUjhiKwrZYOzxRn;zmhFLgqrkm_IGpUsZDLe{9*yNuc;e@gM@L7tlEZjLeiH4T z$54a?$Kth!fBW)s!_+d`48s2U)9J&O^(?WUzUuVOBkh6X5%HWI!i&>9B*XnQvPB*7bAYy zHRC##tD7uP)TLlQ$;XzpK+_6Rf|K|h#6TJTk>8G{^?3dce)^jS)EWTz$v1U#%~C%v z*RLn&m_A&u^||C_M!d&s565IG)*oE*)`Ak;m9`E==ee^p)H;{F_Rp>< zR^#PlC8s+*Am`n~r$z6o%)%%8Ilm_kNR-Y}Opb4O`dbNf;T%{i5(bTm%a9tL?ei7g zHn{d#c#B%_a(ou>{CXIkpN~T}7c(^_XfaT(LB3E!&=Z;L{ARI=Sf&ljW|dmdBk7aT zJ6OaguF(QHZNuMF(bpJwqX;h=PJ^m1r{bQscHt!V`%lC^_)^P(Z1UPVC%?Vx+X@XB zWK*St+Tbo}{06o-a6%Rl7WKNjlpulQRs8O}kn`=0kJv>s7iAS)O9tKE!C_z?KwyGf zQ7#B6iMrQ~qE|-;o6*^KC|3PQBl3kV*oZj@JMk8yhrfRaDSJDvE9-mq7-ZHrjAXI> z>P@uh-jBme9hmKlc>JIVB)4*dLK@PuHQ~FoJ5b{lNWy78J?Ri-yppgvmS+I-cYuD6 z6$ps}Q@34byn}HlT7js}dsUAvh2E$PTnC*>^Jfk?l(L^wxq6KZlvPylvX!r*kD|l8x`78@4S6JTW$-0ll`o7h8XrXIjh6%H6Rfd&1 z?Ypcd5qiT+ME&XRQ=b!kBz*&Y769-FpYFP!sUxwdPyKoAchUw+$9K1%3LuDDoPRf1G_{)ZtcY@BO~^(?1GNEb7{KsQP6+S66hgys{?oc16^} zZ!sn?MZoHKL=Kcm$wo1Y3c7oDWi{4*c%USN1B%KXJa`xO`l%Q#(}7rjeS@6w^4l@5 zChAKR`4|%hJ}+Z4g9G_onlElYV~T zvF=1Y&)NCX`cL^RT;E5pv*1;k#pHB|B~iwHJjAw1|JM;OXV)x zAJYx&z(8=N5IAfVjvl`!)82;p!|44Y3H#3+`BzBLv&$#FiL61>U!46s?N1)I{50}? z{cDR4>24YCnw7KU+b<%ocSD+8adtsSxgjm_QPv}sG&fSqpWIyusQ`R(-a69x%xB&G zM_kLM>swT?>-JkKrxlk z67)bQ{4sra=Ck?sGq1fOuxZN?x<8MPzWRzP5bHRq;)_RK zO6paE8dXEzZ!{lr29`dkC8pG;gEq3AQ7?RNZrH3R^X<>Y_J$JOsEpP=3^r$*Tnm9T znf&qX$A*QNW@6Vb^lk$KQ3t*tLhUqJwHcp1>$ngLdNWdUVd3Q}O$uF4dIpH^r!HrgKa#6MK|>v&$_T8-3lo)MpC(6+6})ECf67pi++v76%~qc7H+Yqg7|jb;*uGRy$nl-m^**|pO>T>ab6YOZ52V#+*yS|WeerBe zWBtfR7(1A5)}`|2mnF>uLUsK2ZFE6av&&3XdsF>?n4DQiDb45QHX|>7D)8IRz~2S` zbo2S$^Jy@~`~2$cPs*O!QFwb9wNw8vMc;gX)WtEosX2%yI9n-QG}p6)&3|%zX`Rmu zO<>UtJPSmnFJ5%~^69uy#k`ig3|IVytHfhy^C&Q}+&e5)s9fHLSg^?tm#(8=nMPwS zbDmD%Cl?e8SinebMXALIUR&Lhryw}=KA-%temV29mzJ0#^~1s6SuVfUfZdx2>TOiN zR{lahKU!A$a(_|w!;y3#9$9taDRFl+iPPp##(qG_=nXFi_2p7aNZ zgXkY$kc-Wp_TkdwJIAd1pWP5V0kkh?*8+G~={{jzARh_TqW-v0(i__R!b|Y;N3HXW z<(gVhh$n)hYaF08cEpDdIWkQeg+NjKQ@Le2f$!fttOYia0BX8?QZ##ZdGQ*{8o~Z> z5e$uUWSJIy_lFmeubE{9B^5q3#|j43qjw0A?D5vUQr9kJ43iCzL8wupOZH*-h3Uwm zKumxCg-B>Oi-L{X{0twMUxW#|_T91ijQftVw|5%@hn$Nd@8;%a0@o{aKR- z*f)?uwqLDy@9C2#f^XUNBL)>!lwN~KR>M#g-@C>JdHI*pnnWWbqoA|SSGYijeNiL@A9vjaxu;BtlU`5#T?^k;IHRYjDM1`&b8+14M! zZRe{#05lphN#Yj-7?A`1vzHHe+}&d6sy|8xEZ z4znY>$7xvir+fQQ5EF}isWc7g-h`R$PH?|QK9m|vU;Bgc)2zQY$ivgKTPXqni~EaA zG4;ZGIJ+u_DtKZSD{P!bQQdA**BL2Spe?jzO1fbVXd=Hmmai4G_ei-%i^{m3+w=AV zmnu=v9V$GHczSKP*_vbIhy5JLhT*E$hd)w6o?4zSjq=nIp;4xDlT;@vQwuwdn4EQ2 zqrc+it&nyG5s#}XtNTF6qGFI3uPLtGeors^Ef*I=spxs^9`fOCkN1=Xwez`g%XZ_ls>b=Zs&o}Y5EErU(h|HkLL+t208?(?;J_lrF&$0Z(S2>I;ez98W9iVyMrr!Epqbp{SxofBzCm!)wV0@< zUVH&iyRx(E=~=ePT{L;Ra1S-zYU_bL1#NX&Kcsm>mj<FdoDZvSADXWhH%iOM zJfI{xt4;*1;kAYweC7{v2?>fOdw|1#SX98= z6YP?ACb#!Yy|$={pi5ABepMB{b zn%J1r1Vyoke#(j@4uSMRQlK7AxhmyKy@Nv&Rj%Sp3}k-?Hyx0JP8#P&+k2RU zZ!1cCTEFJl95%T8VvX7w&rC8%UyBy?qRK`Xpi)H124MXh)EYO|$u6IX}mOkKd9=bzA@aedLh~ zN8*UA^-I&L0~fb#uWbv)YI}=Q-WLigmtCJ0*A9){+;U!Ca|%9t7KiT+qtIsWYDs3$ z<6Q=k6S(W=#@nesn@7nxUlJ&*3PhdaJ_$MvjWpXt2={NEEjD?bni&mThd^vpigrLQ zXd`Uj*z7!Ce5#zo3tQd%Xf;OKWhLEd^49NUSGcJM`4;8u(csHdYM1BJ--uXJB&T1Y z5=eK5DyxVNx^BH&s6D=_9dz{^aE3e9sOpgPEqD>>x;_5pH8jus#@A1TL1iR?i8f*I z^0=3c$*zKY=I4h!kHh)Xxb~4gEf+zpGYd?>&k^qaa$x?&a#7w z$h+O#7l4J@r{r~5mfz+!Ob8yJu6zHubkGvrsyO5ZWky~77B284DFi4pyT7g$R5 zqnW`t@r7D~F9<24nFUjowq&tog0u4OyVX-^?Hpck_b}%?;7`FF$Yu@=K&o$#mepf0 zm+|i~^{1`Lr?8!gs2SBse=}^zJt@HZMWZ9+l~>$Ui31kNl^_L~f;O#L57rN(8q!7Y zQ-tpQ2|^{{@etf&a##2+bvhRWB7Mupw+%6Mb!%Ml zre-r9O0yVvzhkyOlV^z#Beh9M9h_q9!N&8K!q$zfNwn@%o#iRxU5`RJY^*uMp%7Gz z%X%|;JK5D8BfhlsfgL69**fd0>p*2aQB~tWypS$p9WjWkW z8X|YY3~pq>iR=Ax&LKJQ3+R1HHwtNa*_-A0q|43K=5rCYm-@s{GS%#& zN@>}9kBeWu>xp|{STfSqju~SgvLKb6&qd_U{!}cz(T7Y{r5;bMEES)SWR*mVbTUh$ zk-B{R;f(}AZk`5=+2_T#byrbhKF#8X&Y1)Uvch2km+nPk$Ir;tp-oysGgcMpWk`(T z%}Ap4?sC!|HNz!%NISFndz@>;L|?{dzkX^@Oh`;SvFqhxm;5x4TFNHew=ElT2zo%! zfX3V)3{w160&mf?s<$1&PmO{<-TFU->%%Q3) zp|j?ZHHME*_UNVt3Wfytgr#g?Nb9Dpq?AD;xfqmKkP@QZ0b|U9mt!r{(d8DD$UT2A zF0F=3^Cz05&7y*?MH~svLeIjD9~H$ZSYCb+?)tYxM{;wL}X-3 z>zB200}KcyGSc_1j;+d-Qc@!S5FsIu9q3j;E|5T7a)YTRK`Sa&d zY{kl|gsgMR8+RvLMGAAvJ()r3`4qbx?trEp;j-7W5XE#ivYWTJ>5-$=dsFWKXCya> zTE0?J;-G|J5Z1fNnobVXtgBS7*3~N=gWkAp2Z4h#4%ho}$9!tXYDr?R2@AE@ znl9fNR;XrtBO~srCF1x|T4%5u$;R1L*30^pOJ|LG$!h1JFxUGi5=ebNGQe6N&kxXL;GmDpg`v*g?{E#2(s+WRJIJNTr8Vap%J-nyq0-MdsVRmw4J zrR~`obQ`St*14X~iWTPv#pNZU5uYO8igoY4?&)3F>5B|o$>MCrxbe>njou6QZ6A9( zchz2>g3$SLWG7Pd0Ak1Sd&xiV_kM6uYKRS#o`T0 zxA5l}Ck8oE*oDi^ib_d?rjfw2>$j(NnA*ykw?z(~`?BfcbbUFu%FE$=He+EouX1WW z3hS|xeDCN?8+*rYl(!IEP*T&j9of3~mEI`vnKhwv+N3`j3*%DwiS$@q6f^LO`3|&f2vqSill!4Se z@{U5Oxf6!8Y%_~CDf4bxe%beTX5b3z(a?l6fAv?#Yv7Zm_U+6#RPojcdG14=SMED+ zOA6S&q(Yjjosp)uYXy`7n^UEh^c%tGV#2K40j@ z55laK8PWdwOPM3`)Z4MPFSTZuX z`DgXq3u*TsyXSMzl~UYQ{9E*F<1iT<&BnCo{TT5pBuXX7550uw?oda@$|yO>Sr5fz zL23lzxEtC;7|&?(yL&k_!VVVDW(u0#7)$H2+9x?Nx$O?L>@VrAaRFCIV_BT*nwnfQjC;MR)J^s|!k4$ImxJVBvXI!^nxl;q(3stCcc(3X6 z_MlLotKnzQaP!RUpEna|XPT+AdSAoJybetx>#oEYkEu{}4*X^C@h6)eVjY(A9L)I9 zX4}z&QiOCrQhGBXW5DSwY`qeud&8KRSaORdqddkx`7rNm8~uLmearmz)Is#X&sttW zoyN7-{kV+K{AxdOcKsSzDt72wI*GcimN z&e|tbLu%7S{kM7v?{;IBJtjtCJcmQooT*T@j`6Q8Nv2@adQX(vr2Bbf#%=yaBQ z%+Mm=(CwjDjZso1&(kaOV$D1Fo|?6QWr{>jo%AFLuBzRAOi%mvedonY#Rc@mOU3WG zCT~Kc!de%6FjTd)qJ`{UMT2MRgpOYBMwc;+@6CgjqGlgI-aV8O9{@g-RorwBV87CD z-jG4oe$v+J(Q~G9L+s#L_*BF}+m`xKJPaYLm+5QEIp^5A4Y?b67B8E7Sp1!l9|{h_Y8el^A zx{VJb*yH1TES!%eOw@SPF;2RcBv3$+Qow$xD&YL^di!Q#wYWHs+f|jQ1+VJu2b7Ux z5?eb}!nT5*l}H^-OrbGR?MZPALrn(4sOYE}K9b1A^;<8Gc2Waq);U^&Npv|Ht{dLQ z{LIvjF^?n{BN|py6Le(3`1Ol8x1{9M(-SbkeHz$;uaW!a+X|oWPrc#6vs+Dq08nR8AAxJL{2o7s=8Kq{D^v1yiA#{Q#+q_mcgtCNPVIe z<0r|I!|5UhlZ(9u4ZTIzXGt8kO^WCT^>=8)k!2w?!QG*lN86(mt#ouClHXC;#|7ks z;E&X5B*eI8tT`Hq#GU=7`bsYZzG51Piblu!1UUpkBVi6@aob&pfUgkVjc|1oqV3g+ zq$m;-15I;FUte-k0$PE7H&P43-dAU%l0UBBQQJoGAN`JtY|TSyxV5u+yV8g!F5#-g zr)7om=g#2oD`^bM&St(kobDpX(!4r;q1<+bwt0529+X?0cXz4xSNa^5T3V$%PI{WN z^h_V}^75`Urw@E@l?qYQ(~bZ(84$}Yg||L2yYgXY?fsJlrwI1dh~!%-H8mbU_Bby_ zJgS*Tv|fW^b8cNe@6k4rzqqt5x}pWiKx?h3w2BQQVq=XNDlwy)Jocs|dAcrS*%gM?b4^)dAAAdg1EzED*8s9$f<7$Ke6jDQj z5?08vG0f1DByY;^HE=rBv=u(j`AG~UP(MEl)Rr+Fqz!Aj854eco^h|A6h zSsa0hm2Kra(MFq315-^551jqJ#AKZZb#t+__=%^6$!sb(9)Eo0G=9`K&v`o;`5Hv$ zsa`7dfeaol5Oyx1l_#{ewk9AXjQnbEP$q1HM`Fp|euEMcJ5TcJ)hK{LByz{tpxN{96KrKPG2 z8QU}89`M^O1gEcYw8b@Kv9Lbwh-YHk|NfMV_VfjHCz#oqk@5V;xdkV`!*uit+!VC# zfDAUDaY0eBdwY8cmiA)OL_dH(w-m)WC^i#z(ibHPYWnKoC--;oK~_Gq+ygLQFT%*T z=RL63X@u1XK*MCo&T?XCy*&faJj2XFgicOnk6gV(G_@yh6#NG=HKNUM~^Kk!_HtQghvSD?0c9!9nPE>t}gM-;JqFE3JI!ONsH?y*`m0mOy zc9Ib=1s ze|p$nU(y&#@CHVJzl0|r&+Yw!)!WCXG%x5OgQYsu1^FVz5qF{-%D#_{*-;nCHT<(Q z>(yIwAy;=?^arQ}%8yE2$4{*aOImk1A~MfVTf}JrsE|=tjykRP$+)8}M%73!jAwLL z&JkMaw2(>QH1(;Lu$_4UBB7dYt56;pyALfixI*hyh+f)XP{PCve_vw6{rNK`7W*G3|SIx ze2n|t%m)`@QPS0rJebL%k~$qX*A9SlLWA=7*7bX-<%k2eA5&SjgOR?rl9T7a(O|S+w=fod!mC&O(fIm zZscQ+qeYpZPT_#qT%mz=uBya`C z!g4}|#FD@c)7H|~JW?KZ+;aNmisXueYCr1yXsXG$wX5)-hL&X|BO@HJPY{ZS>*1o%I%!{Y0 z3Jp|i4_=vmbaPKW9-kEdy_1=&N&f{d3;SDKhHFhd%Vd5lTA+oaPj+TZL8G;^vqLU5 z0|VIE6sf+DiOAiqKupu4Q8dIkCPPVsaYP-%q`T#wpq49+R>5(C{b1zgD7Vkn>rUZX zhT!bwKAl@B2@|^C5DfIAO&AYDFaw7xe!h+M!9rL1-CP92l0l}`u9dLnYhu90trD$Z zDn0L$#FCa96b!Xjda&;UsXV4r_N&_f()?|!R+rtbhph@^RnRWMQ9 zHR|Vw>Z3k3UN8EgKFh#cgv8qK5s)}o5#jg`sDdiTG$JXUV%-_Q$1}$s%ZN#}0<`W0 z`)#<<)5^sH>(@~k{ko*j^yfPddnp{=h^P2F3R0fExsm)yEre-gM017_Wc=s*LFw$y z{&5+-2sYsYCJE9%+|nMC(?K!d^DMk?t$LU zDliUsbvr`Op$>9~wSCjZ!=ZM$S~A(sxJN4DL2Kd5hXHC&BtuT_dz|Don+?79?VP;0 zxG-)z8)jhzv$&6#fE?t2!|4xxuEvn=4fT}=Ch-poel~jlx=-8erp@K|tRFr@@VDjk zK@p1vmgCb?DWE{KigiPdIuw9?8QjNg5YW(&b+++=Ylz|5eU!^n(dE7RhlajXG=6Y< z6d{)l?zNjrB=_qBlIjxBI=eMP;yUhQ<3nr`&dzA6`k@>Zu7*EtqZ?D1jR&ronRH+p z()S5F;WC%$mkA#~t@>Z?2E@eRfh^ZBT#D+YUbbx7?!MSjx1ANWSFa+73nq+j0y?HD z-%-@LZ3bGV-(U#2|9%htdnxUbMncMrVQXt^|FA)3WAYK&vk>RTv^1avnIkFCavt2Z z;v>1O!vV&r`<&a;-OEENdt!F^`8vQXrq@6tBP>J#e@EPUBUA9(OYFvpn7`g@Rhk@Z zc|BU(vCDpZ-U|{u)$|LC^?#?1JhmZ3LBT|!9PnAwJbx3|F4kh#*1o&@PPSs& zl&!O*n$!K_rW38Kct*3Hx4`ryPf=ti!RAcnCUHatjZy(Ma3}>6V3|_r%Y%M(R1*IF zrL0)$p2yF%PN+eW1+|O})7faMUrXrn8)&H&kK4{b=e`My;30+c%w$DjA*HsVa|F*% z4^URM59p++$w%$^^ZwM1+t}Ec=}lY5-wScQCv4XAJ&r++=QIbc=CXKJdF#YO7X%Y_ zeU==otW^&QZ7B9efhboEAASXVpnOU zhMCz*ecQ;3YVT`V_A4e7unSq^_u|wwdn%peT1YPxe+L-USCq2&+~;?gk2Y+qW5_hn z^A_Evu$_$ytKX}O;_?n1KvMYA!aG5nDiuet9Lq!&BqJlcT{h7 zpLbfqp#hjWiL?rY-aduEdjT8<#2_A+$GCq8rgfxqfgY;M>+2XsG&E<{U1Bq>)|z8{ zgWNL<0@`HFv3uLM=(w1^_uZ~~Vd+?Cb_a4q%}x0RhOu(( zZnTaU7aF=@ezSJSDOf%5Gs3HW9t;C-TXdn0&StK@_t8>HN(xps5f+Mx)EPq-brm34 zcwBD(iPO;fECV+oL7Jr;RYr?e0Zzi0E5&%sEum#@)&#Th$tHPX^${1&Lf#50DvC2C z@W<#9Zf<1vOC9589xYbL5&DrzOH1QWgWr}+tQE$l@jF4Y<)_$-j5xj@`U&8kuGx(8 zo!MCO@nb_0*rZ3T@v=-Xerff@qj24G@iIx(v{XorV9c-4?VhmWXTbrR9zg;EfQ-}9!xx9WX#CoQ z{pU8uA#h_g6jZfYtIQf!@*m@nwU-#qFEXn&YJwo}33WP5eAEcFW$w4%RSONO3D~qt zWK^Fc71w$S?KZDRlL+YAPE`_@G#}F-JqJF+ow2>KP{am!UebRaL$ zLKsSnrX>?xQiA7QbVtvik{G#L>h^#bb43+tTAw#iBDl%0$L7`KHmFPH{{`;kam zjlz8M^Nu5YJQ*C@(JW>IHu&0xeh9=Ot%8bCjhB^*NM_jLSq-|()<-ET+v6`-A$2R@Tpfw3wZ8IgvolB{>I)A-en%Z(4eK)BWdQbwxux zTEg8IpA~h~3aHIxu$jlEA@Bu5x|dt*7>Cd0TUV$Btt3$N*ufr1MZn6`*pBq_!9m&O z#4;7lEPLp&ANj{$Fk?rck&GJ4gY)^%XQwtbgp*+CIwK)3!;7skdUz*W;abl&{9zrY zJQJ4XGk271MFCd`A(;lQ4Q<+$ErwlccGIvV9$n;ka(uyvM2QF_*^Fh_eX80vjGb*p5yPh#MlSLK zj+&>lVeC}mhSZ=X3aC#2ySr@GXv&r!f;@i$JRFC%Tho$>*lWM<9jHbd5=n4*Y3%O1nX#d3-9j8oA5yTX2O?9vjr z7-QDKiI&1i1yqb?RWNGaBuJ+|hBjDNt_iH#4LcbBzxyX1DWF0kh0!#E9t;&!Ky;Pc z7Q5jCHn>n!QwQG~R3hiN%|V(4=&a)S|~A|rOsNm%-KnnTuN$_`R**VY$?mx)4&t=bL3c=0Kf1&2%8!7 z^YD(1e;GOdNc_uqqW(1ND|d*v6+JxS3k#O)#u8{gsFnb3rF0J_(^y6;u~;sQNr@OD zI}SS~{)|Hh&S1c(&_l3q%R1V`Z>`rNM>~PKBMxPDz-~X*lk5&M-2pvUl^qI8I%Z>3 zgnk`5PuU4JxIeHi=*1@?7^LAR816ViZBsy?WK`*Rr0>wy+LgyLo?2s6?@JVX;U08T zt%la1D?YITTvF#)x^j2w%4{w}$>840kD<%B?&6d$IpkPFr2@^ zC zGNjV8dI5YQ!XQIfPR4#WbzxspSizomzhJYRp*9=0p^p`+=QqRP18q?pBtuIP#?$oo zHw+fE>_Yddf`xwmo{t=FzIyl#EkxO)@x!G`OUv*i1EUD__(!IxvK!07%VPz>l+R<2 z^5gH$KhvxEJ|$n;w4nrAh1+(VP^rS?pKa8DoQtVVTXSWC97bBvb?Wj>a1`oM(TE z>LVXct*^YDxcYOyHv(ezGoe0?e_2aIi^ya$ScXL~K4_&u^lW*wi^qE;6%!}Av!Nsn z4G9E+VjJxqZ*Avup9klBHn88CqPTm2rFS+&6I(3U$e2YPT}2}2tA_X0$Z>8yi~WHR z9;qvbtkU^Yh=pBCG(wyKAmKR8M-WtWIlAitYkn~4{F&+ageshTUl z8cE;#k6}5+$M6`nSDMk^7tB;+Q=-zFy(i8Ljhc-Et2bLP9uf?{8vru3wnOrE3X=wC zkH~P0QGE<~LOSbvfz*PeJ0#gA@r7=GRlB3w`FVt;afLt)GO~oUjuR&mEer2jBpP0h z=QBCyC-agaqr-V#K$(DcQ`+$2`cZl)XH6duHpB)6 ziG|Z89igh_LTc3Lb9%o?0D{ErIpaery3=HlUVVXn4~gW5FTtn9BlhC@Q!0chgAG+x zNlM7o7e%oeAY;scK~cPgCO>aZ$CjNLzeTPj^p zf0H}43JZzo&~$GIpE?mOHR;h7j72~IhcaeIc4qKQ8pM7wTXPXQ##6o&dD7&E{fkNN zbE;udH3m|wyRop?RkO`d?K-!MGIg+hV;BAFeIoYs+j|q)eghb!1jFY?fvtTPil<5> z)(9A59v$e-vsFedNxJ>?^{Gj`r&rJ$pKNlW(Bh%iteJb+W`E76A?prCMusLcE?d3J z2Z`5LX|tOTSAKcqvvZ+>7jr#>Z6k0^i!V9){ZN`BF%6)l%tc`NRazu!;?VQ_32Wury46leUu)g3AF( zOkjL94%4N}bSNJxphAtY#$a&a))eNxGZgDefvK!~D4$e_gp?msi_YGwWyE+)=Dc7S zJ->h{AyQlT=C*hoAR8M`UC7x3woOKyhs@{BS}sH1Og47x&8O5ND?u2)#(6sFhmV^z?NA|hR{AbK0S zhtzDmN_J!m`Bc(iX%WD=0Y_EWk|rhHf$;k5-kxUzKC;*PieNGGA#wo25Zz;9vm9~E zoYDKYE!;q5(cbQJV#Y_hrC!k2-g-=tZ>xQ2rs3;)iBC!%r92-(o2ei^rtvWg{u9$W zzgPM4?G;M<(6@E&%DowyC|pFYkXB@GIdb)(^(g0kryZ{rddz*i@aI4lORLFt?dHAs zElREg5oUb}Qcemnj}aeB1^v@t%ydm=ScPIYFSdk9U|kD=BXCOg>jNq%DuEM~Cw<-^ z=^1+9(#EnuZo#wQ&%IBesP{zC6{YJreKMifL&XY4k;$Io>gYz%N^DL3ENm~RoKf1~ zGZJMN^o;b7dCwTx^vX9ZKl(d^nT$}7I*rb`gH>)GR25%PBY~T7|LM&U2|E(j+X^Hk z0nsxa)7jIOrc}1G7DjrZc3|8D@NcM1Xj!o8^1;Tff%8PHRX{8zCbrKbrWFGMI9p|= z;}&989|_8lWfJM$pgd-h4`7kkl?iGxeTB{RgX7_sLtr_6Us^U@DxHmWw|WF$t%v(m zO8}M7vSlRJ>TggGH|EZM7u3A2pUf~@p;c^JzK`_Y&zJ@1nm~h;Q6B4F(GB4vtm6(B zMLCyEzB!LFAE8g9mK!cO&Uz^ zbK3HCnS+egq3AgkveVo*xX9E?FT;$m4rvrlG+FpzIXtBr z5}FiUv&lu%#zl(?aKxVLM;YaXNNBY>-SgFGUA=!m*wz8Wi6fE%%{3B!n)b|9tLYfN zXAYtI_$vq#Vgm#B1jLyIr7JhzU)N6VdX+R3CSsjn7Ayb|nac;p z#1Jy$J##%tjS&y64{-Z32*Ld|Qv*7`d55^lAhm>g-&2-+*+Gc9K%IzYtD`m74{_C(VVe2=wFacYzB@F!~YdB7UVWY8O`r{OI1?%O& zuT%v#yof5S9b?;yQcPJdFb~KJO$zhQj`lwYeJvWNUL&pi%8#4Z3 z?}$w$ZRV5xbJ!v?7(xOC9^IYT_}7uhCFZGycF)u)=*(J}XcDoMgP!LL)^u#)dK*hR zATUbEs20HTuCRA(_RbrWz=!Vqz4Cu8`IH9;=ms&QcmiwkrPAbc`5wEBuk7{t3`gM@ zgRSuaEJ~X!eP$(Qz?MV>C{*Vgwl<%{l7qH=~IDUuQP=V<1GxfrI3N z1iP`LEgvu8Xv$*Qmm8t;BL0#YTaA}f3V&|>nMW}0zfPqZ8zxosq-o~g?XZL+cQo|3 z5ee2vVw44)V3kdC%MTtW7v4Fl|3Lx*pSkQ$n9}vdmpvKH*A}s&q2M6Vy_BgS!9MU9 zhXAMqJP%CMf1D&v$24auBt*X?9?lVMt98D`5QhBcYA8!5c+47O)E2|1vq( zR$f*0ef5L?c(A^s4-5yJOzG)`sxx8F9EbGS8RY{sk-fw*)6RI$yeM}I*u8Pzyp%`GY50OcTKJ0wgbV~!vUyPG9W-AlUXs0G0eIf z+*`**eUl&WhQxC4k`rD}S9X9toZPsHF6rB}HJZV*axXfhU;Y9GoTlX?*A<}z+ zz&B=;*L_bbm54}&glkeU=7H^CnP<(n5evPDN0m<;bN!tC5<|kK48JlzWEF7-|EO)Z zYhepnk_zDyq|)5&N7a1V&NbUM-wJ>~o&amQwWDTvTwAn2B)TRP8sITUf{oN49R}Q; zzmSSSMrQkgmL)5I72WI#ht`E^s-WHdtW-aiVZVnhOcoM`)g#cP{)LTRez9`R)(e8M z3(Oh+aeQdNG~IScK7kf!a`X;Tx!)I4qBjdsS@F(lYNoiNjjh8t1f!>jQ^;Bp->ONIY^ zfz7dj1lzS3W@=kj%Bpp2)(F&PekN>1H#4tzRAbV1i%sCT79xv)x&0QKlvq;y_XNm+ z8@QBF4NU?YjJGZ9s{C99Wmz-ps2GvJrZPv&Nbu62-V3m_0pJ~Gz~w09Uoo zi^zEH08Zh)mRU;B?|l2AH9^u@j>r@2NqvlJVCNxV8U3#KfvW?q#vOkR&cEi%d&HP& zX2}3A*q_~l@WOt_SfwK3i!DVC7w$ng|fIGMTKWNSC=;{thLYZn3Y%{MQ023`* zn;esZ>ti7dV4zuM?3i=NLOvy_*nhve<111vJ`p0Rk?`Qh-J4)siv78qgWva9!vHp|qNMh`c8{ zJ|(mK|KTmV4+s6@*Gyu{8Tlz7k8Q^B0MeQrMY1)?3iOj}73{GK_rPM-N@kFM{J*kc ze@NmHnC@drN4Cw$)Ls4)s0S7xKY*;%I;UkT2fmTeY-^?1D-BB-QYj|HzW?7t_zRj@ zcj#J|sTPoqv0^lSZf)oQ%m*E(l`L?HHAk?n1UgeiSF{E!-<{dt$LroBfOr8UEg%AT zsOM}TwHV7xhCDcAvl|$RP!cOuqz=S$KZ8oIrZoW2<}Wmgum|?(5f<9v^BxPZn+a^4 zbd=1J{xJ)Q8Z8U!c1AP_T-r)pk^ngb%3tRDD<8+OzF1{^WefuX&Ic9*5Gp+m^H*qr z>?z2Y&K)q8cQnjzbW?hU?$pjdKcWuLy^$ASr4m7Znco_~KPqus4Ig-`8gs0LW0wfC zHB8CFjXNS`&eMLz?Q&-=px~D_APq_i(*a+(+(B{FiVFr zd;|&hA-BAJ*?(Voc<=B+Q379|7>5HVffo{P^D+B(ZEKsx4sB?_EVsP#|F?-=j{-OD z641^xLI__yp4RKfyV%#G&*gRaeUzOF-g1`=XcRowZ)eom{ExT}4do;RTww*k3TLTm0C- z|LqU~lZEbLo2q$?(!*ldjgRa%*igfNu+6_5tH$QeIu5y@#L$UT%Bc;&oE(w| z9*q15x_o&5nh=ul#=j>*(pBUZiwMVPL`bU9_JylmXN2GZVI>z4)YK7BL=4F2!pwcA zScL`wqBXXk}vGXalxU# z^9o`N+d2&|4#zlD#E3ZIY1z5^_R;J=-;Q}#;5MX3r_4Jm!BaFmRt+oxn<`lKP^&3~ zMr7^I*8AsZtvl|}c3@o)c7TPvupU~~rP--2DeX=VZPE9rmBU1&60H86y9Znt~nt;OTL4HWt>azc{-~_TMWh zGzdbIEahMOz3}P!E*#}KLb>fKhT1Vf!(@PIW6EYM1D>vDANULPKaILm=)>=%9V!IX z5CECac9d{K=qIfS*C3!)#CV&rZJYLU$V)5| z=wF@iSBtGO5(Is-Twc$r@>`>}l)s#z4Qr-T$*-rv^Y5W^88!%(hY|bc?GP}##rQ}f_E45@6I9u z!T#}4vu0W=8YLa_VvnOpX1c5+f<8?{NFsEPk+4Hv5`r^*#&Ez1f|nKj)`*pe(*?2J zNlKwF5uX;kF-k-ZQIV0acr15Sk`CKdIDCCvG#D3rH{UfJ4nKlcS@-XGUP?+y?r%A( z&~wuwE#C0rRWzq~{qYM`8i%w8e_e?YSbX0sdQ7=xZPWO}DK;t=1pW~7l4|;yutGFQ zxe#>3IpeF?sKmULlf8wI$jy~s>7Mcbqw1~0qUxir;ZamjBvhnBL`CWD5D*cN?go+W z?oLVR?#=;`ZibL?aSx;lHwmWuHC%cOyw z1+sy!6HUa(>l_j7iW#dN-oX}g2ko(;+nSSD>VYdWMlE-7z9a-Qu3oXQoZKsYw^I*F zyQXU1D*~qT^(>`o4%b6uXg}YAp^bJQ#+`Tnev6*2^HO;|{TlsifSaWM2aOLTz*b)Dp9&O*sa z__2BYxq!Ta(p~OJH*aJB86J&yN%?TTJy2Y-|M<7aLg6TlGYUAzw$`kNf;e+CV8lF2 zzr7)!^s%Ww_MPjl6#gXpWk|Bp!n`Pu0b@fx$G&sD-DeIAduDSD)~z}$C(hh?Ek_qMJhZn8B|44RwU`Oul4+J=8`np{?7H&CKZ4Vge!dWIcE>+56TB4n z1+nGZef^PHF|cI02w$Tvzgr|{VPVUZM8^eEUl|3BP?CCkn6ZN77Eh6h@!>f8g4U#m zFSvIQOO|E`y1UAAl}K%cpNJDlgbAR7zWpgK9gO}7zxv)i{v!h`8s`!EqhQS=%casG zVy5)Cndb4c7fTNHNE=a{F)?s?|0m*V0_jD=UH1m9J0ImanzeO!P0IStiqG|a1ICR@ zUV2?FE?BMCo*9w@@mw|k<+yEu^}tiDNVvK*#R@jAlqpnS1=<28f%ZDVrb$TC-D}1P zi--s9eBO!(l3omY0eSOd2bz(6uks5^J8hs1n2?K$Jr8nVj*!DXaVR-6HBnS&ct`e; zE;cr{rw?bq(Uww$6~jiE)%>ly`^8S914WJqQx6pt!_Le>x%16eL`T-g<*TC7mZ32( zw8L{CttJ4QbG(FunHlVkbK==9Wg(J0mq>AlC*|E4L$V}gX4czbr~^ICw?WT$Dxh2@ z{`~?U=ULL6S8~?-&1md^&!W~dKV?)@e4x!5Xi5EHUz14A=bcEFe`FqxR|l6F&nW`) zZWfUF*>IZ%Ez{|x<4BSG-fYT}tIkBsPp2LR%pg5Ko48fr0e->+)+jxZ*&|#P7tEzz z3tX_SE$jFDr=q_|EN^1s-q3yM*%_RiT5X3f(6!Nv!$GbaLcoX3oEr+r=GGezkJo-3 zdNt159O{ftIG`uW@+L*9Wy_W@kN4&n_20A&+KVM}+A1!j!+Q9IUbiZwr7^t8JAMg7V__E<76ar6Fv0)K_I7*x1_?F=St zQ=iPT9(ixWq32BxAr0r<)!R#z%@prAhH8p2)a;jfIZtF{Wc=Fc_N+GgmEQ2s)O6gT zN=QgHT(8Nx)*GU~lW7^VYP5peQ4Gm`jn;B~PS&saOM=@*+PxUpOjpoqMnQ7=bs5XF()2eP zfAn)TT+Rmo)%nHD=->m$kIbxyAvRJ`(2}@#fCX_pEYDlCJzVRqin8PUApYkiuIRi=*_5kF;OVe2xNpwlCR^kI~D(1Xx#g2Ra%_ZXA`b z(%(1)g}m>j#M9HNA0tED0>Xeq!kY^&fCJRI0gDqvxot~^ZDZZKK(+UP(zAc&&(Nec z?5wX5;@OPc;%e{uqO+YtVKu&{Yjt=i<9dGbQk~b1Skv`bQ{ATH5t6~<6H}}O1Tr?x zzSzOeFZ|OHP1AXskLRj|_Fpf3+f{$PD$;46*AhRJjfm9aknMXC%QG818rWd4A_Su8 zw*TN`(5|!fTeOSal)O1fm*b@wMrY-_6+V6QM8>BcO?y-bo-{WIJ)fOL1@sphR%<;J z*Q_r?&lx>E zngvh(Ip_ELH!IIqSJzjfpdMWNh=tmXkA#GKj@JbymOC**haFfZj5hmqxXtdZe^Msv zsPUv`nV+ULT2qf)*>$emT|Gdx5g(J@Q>NYbFlT#+bUQqggD>YA`5aB;5g02U&m#U4 zW89}D8nd``nm_&;fCq;LJiN9$(;XMC8Y$PrX!n|`tE)FAgPN;rcyC-5++F!xFu@wJ zMFg^X|DP}DF4o|7cnEn;FiAG$a-LC^kB@S7_5=dn;jtY80W9^gXr$$3&MO9o*0;r~ z^`zHWYl|!NO<$$8-#FVJ=qDclzj-p+xUITMO8e!G zPZZ+0nQu93b;1Is^rKiYM&NUOe7rZ)@#B16Xd6*%@!3s?qRwv1vnKxAI}qVRl{A9d z2ljxyR9WEpBta8GBuSR7wrW_OlMcPw5+BDK|MRZ(M8ZutVovhaqb1)xj>znJ3kZlu z$`Y-9Du&;%xjwX$i`Vl`x!6c6X!v9lG?q&iSsyk?@z85wKe|xXTd{U47OOam2{@>I zFjx0i6l8=M(sMQcp&q$iq2$HC5zQYa{nE)FDKq zne?e48Clp&Dd{tjs8`%)s|9xfv3p;Hd@u(2Szdnq3tXiq0Og3m zq%lZ@H{Odx!7e7vt3iVhDQMmWc@xMK)(5nF)k>4RGJH^Vde2CdEA3Iht!Z?3lQ5$N zg8fV-YX{TGhSxrz(I)bB(S2th7&GysJ4gYv^q^Ml$)g0-unphK(@%RpECqG%t34L< z)>u2i2o3G*l8})2*Ujr~e`E#mbZVyg3SHg5IH#u9?hPZEY_Q{Y*DU)+g!ZZBVawCJ ztMbu@Dv=?v5S7@Be@t(vELm8|t0bNSxtwuN;i4@|>+qQG)f1w3a$wioLmF>S;GWBQ zZLq*^2sNhR5);&Hj9otu4;J95B_^wEu)KX94iz-2%Df~b4$S>Pd3)A3^YJ59_IYa$I=t<5SE~es+wD8QDfF~p`m%$Kedlr^y)#qpf zx$cco;Gb-emc-DRVd`3Re@wAfHL^Z`*pi`rS~8|g($WKnfY&YPg5lkVUn|e0#oF!Q zhn~jNOD||M3u0D;?Y%u(+I{VK>7ymBsu`O50Z~3Oq>T#MD7)E=|I;iHayqsrpl3_D z(Ve1AP>8_^seJ!nzvp~f(R??&!qfztAmN^qEaFg{TDB`JDG_qauM2%c-weS*u85xQ zM*s<-^J18qIcOK*X*fGLeiosac1$(8zC7_8y`zgmF7{S=*Y2)6@S zOI2Uv<|y-UjYk}GJ_L*Zj?`h+^|61>nLmxV<8jCNoH-n!B+)+k!lz6}{KJG63P|;UtEg8!qw^MbaT8M-r5{y#kAy#ut)TEZV;JaKDto34E6~ObaybV1j(o( zIS&mENXZ0rte(1SMu$W zf;h=9%7!1=EM^`6R{WEIn;UN%tnGK4)}noWfn8&i8N$(^*{#0nBbAKH{-`YKThLv7 zAK|6+2imAD8hzU7QijXjysI)=ALfk~RH=U34*GthVXAAF`>|!oP%!Eh8WzS^5Y95+ zwqcVHJn1N`QQ$_yMSmpl`MRq7@Z=nJeiEAwMpWF$MZO7odv(4aW|w5IJu}d_nY3(- zH&yP!KiyH?x~X$R-fKKA9J)1kHFB&QAJ_D9SVkwFM+ZOkx=Z8R_Vk03QKd>pE%rnz z0m%I_Y9LYeOT+#A2=#ILpPtIFzZt{&vuASXd6N4(Tz`Xwl?}0P&xPt{+?dj(zzXGT zKXLA?@FnLGS%K;eZF292uGsmHu~R23_L_y5c)qlH>nhlpr-fU*@`hdNLzaRXp4(M_wTDw^}vi`S0X^KNeakKJqKt%~Az5HP-1|Ceq?hLc5+Gyt0Tp zIM_fDeF;jFk05dn4W;{2vn9>#)#Qxw?Dc+XBT7RCX;Ra*HGBrqtX z1=XSf(BL2l;DM(5#m|zMe2y!xm0x#oloZwxLV2#=LDHq5slcRR{{3o8D=R+0Z~nN_ zi()9#(%S8r_mOH&#YpO(SVn-suT>3lkd) zdImUmfuCPK;v(ywXmD5B%uEhtXwZZa)3!HgDgIDfC;g5e^9=N(-&q_qWo1zf@r8M8 zR3(T$c=(Ro_vM>Y$xUHrkG=Y2$fm`iP7(>Qn%JkUHSVY2|9x za(t*v0;lzj0lKK&%OTd#!R8w;GwyRMQMkRjLx(m_2a@Pp))TvLy&Ro?6T~OW^zekP z95$q{WPo!Osw|8F<5?5Hp3jub%pbX~&4Eq4+?LmFcONL()5iW%fI(K_nsajbxN->; zU6KrlfDRhfrm+2#=|y{`S@T6{V$f&&%LIyVI+({Xs(XBjeh{E>m)@jd$%UaVNy)rH zzY$Q|TOe2TWYmCu?V7U}5`PSP%a!wYaYwXb4};WerN_J_{%%E*@I!|VJ|Wbt&eKi} zi}A0L_vt5Kt2u1<$X7xxMb-5F&V!hjV=mrb*HFZujX;`b$O@7QQO62G+GroweY zK#pJDe}dLE$v4u6Wt>N{Ecg zpQIbNK0_6LYF=T_*-wbz#K=Ie(HLC!VdmW zR)R04AR75QNAPLnbn+SFm|kL!Bu1rKE3O2A|bd+A24w0MbyeoEYitt|oyf+(n9+uBft9EG#J@jEPEY{9_0PgBO=TMHuN zqD$sgMM`?0-LL__7x0v80!z1uA_= z4eQG!nW>3cSmsRor7M5$y~cbjlF%aX{~Qo;QDBTJq0p;mRI%CG#%GYqofdjx% z-wxk^pX%BV&2V2UaQpgW=*Re|>}lhFwTu#e6NrXE_HZ22eiOUc{&%{-{Q)m|?tkKy zGb?b%D`2)-k~7S-md((D@0=err~ksV5{)!_`v3pRnl!))D9{#HzGe74Vwni;ngp)b zq5z!5<@ZnRB;m4Ua(uQo0{2&+{MN7;uUiP5&$8<}%kxfkK5sgbX}MZimb^bw73M4Y zA3XS@;1sW?${AoEGeD~%0$*ukKu-O zMGLMLak74j6+^aa_2JMY5eMG9IFA|Kv}1NWo1{uFlNLG+d}+wQxUV6jv&KxcGUSyilp=YnQ#*EkiZ4 z^j^A&v2vx4&Zc7!`ln&yFEtblwkbcLC}|*%a2jG6H?0_w$Jw#QQ4qHJ*h&@f+Zgq; zJ~uM#S+E$VQ=MmO3gIvdrvNGTy_tEV-dhb56dpXdK|xpLJb45@@aM)pFQkEOpTM}i zi&1Ap{)6Nlsj$q&aU;8o!Z;t@8>YA3s0X!C6SRpoOx#IFuP9Gewksj>MTD?WrSJ^9 z6Me%(F=JJ(1&O5AD~WZjd6S9!)=(ua(b2Enj&6Kx=2M+u-;;=_3?nqe1vjs^+xvYl zgRM{WM9GDI4~y({)rSImGs&MRKNgzi3=_W=42BPGtSBpDs@gwrZ%%9bfu%81{qC(` z1vMCHIQ!3Eg8S1)s!Zc?{8HlKG-o>#e?QP%dpT>rPyxv%;~R)1gWDYJDGgWZQiG{Y z7t2Xx%9ZBSIXR@js0KroFevy+jcdCuy(l%wZw{>>rB|c6r+ZToGo@rkX{lBmQAbf5 z){ELtp0XC&oUq1$4a~gPrywjH9x07Z)o#3rFvn|j{%dfW<0dnHJWh~gpL=pt&Hu;b zP9{j4m&NRNiyQlSj*cuby5jC?`Q7&iiwW#a8~1;%u@1n}=Wion$;)fu>wk1y2qlHG zEa17bHbNHak||G_CytN1P9Tm~P2!h_No&M>sS-ZZFwRZ^?5XYq(&W6KV;t$QdvA#7u#gk=e?EG5o88DTx5NI21Ry||7NJSa+ zreKt^f4-&bmB%~E>fg6>iL&alR11sXS*OD{R{xQbx2kjRcKN6fj`^}VOt2@Xz&N-- zkA53@I;2+7db$qY5mIx0qb{)gL92&RBC^PCfN@a8BjXgi!Om=M4w){ z^}WW7&So9&d8-lrbD~KfR-&rmIwzsq%Sq+!u|ls*4#(R{sVR?-KHL_y-nWfh6;9Wr zx&5kwX{39@h{I;_)9qM6Npn8V+RKKUG!Y|sA!A{tB^7tvrTB#9sUxl~-Hy}(DamBh z4&)@RSVww+E#)D=gn7zU0;sIwERaa zHBdUi>#xiL@F%l4X$i-(Wf~pV=@z_l3wh2L*Pd@rd3W2eG1$$2n59d>BPEciU$=9- zgw@K2|H>8hAzU+8>%X81Asq{&&nsEKO}aihKoxl@_bIZUd$jgEZH0g{o%Vcw5;XwU z3OsR^(9Sz!d1!ks3BTLb2&;?J_V|`MUm5NB>`;Jr=F1QL^PgQ2V(Qb?m9AOnz#SiS zhur&{jY@zQ1cf&fOIhDIBSGu2<3S7_KJO3wLe`y@l_SpuvNP8A?{n6|P~sc4U!364 z>n(WsG~8|BCHD>FY7LTlsvP~cT1C6Y%(ec=>+>`!?~iHD)ckP02s@aD;L*daqN-A^dn=V{lAmA(pa?5i zabn0TEPdYAVY681l~B?W3(m#JSE~L1Xiop#(OO{F8M7-t@Yj={f>gcNDBWbKwY?vX znWs_ofyF4g{WZn46&GcAR@Wa$eDhCMKR@AFw;B9x%J(B#;%apc#{J<%Yzh~ynw;6Z z4w~@b4;aQn`I0S7UIZmYiJ2JZ2x8_YPPKV7o zqR}WexdlPNJ(?x-$JGYh>>BNFMSl08IC0^?;LKaNPqQrWVp6%zVgFxXhEHH{$$%P} z>v1q$QB8LH?d--$L}ILDis~of@Dlqo zqaBRGw&k2&m8KuB6ImaIL%}++c_6Z*v7@;iSO>de^>;_US~j zE*D0`x<{|5OkG$lenOPl-{2~iP6c+YWR2}~Y@A0$C3z^?kjRdPL5AKG#JZ`=|9k`v zu{s=z(7NsY@=Z-JC;uD7`s5je!*P?L<9*Jo^8x?fiDTpa^c|{(VsK%cc6x=2=Ujg` zc>ieNYevjE4mjSpV3Ow93qBu?pu4Wo$LPOAug44 zz%;K$Pa6z?Z@@A>f-miCYbMpJ-+iWt9sm>g9wP?v_?;2i+Qq|HMTIrzqB~jex?lS6 z3ORdn3YQPEKe@GG|BbKZH=cxLeK5LmS6_`(YS6s0H|r@;vPbuDt4lGkb2ci7+x(X2 zD)gV-i2je=D8}@})NCa($``0}?p};?)E+qodS-J~ zf~micAQSOLL-8J0u>6!Y8!vE_gF*Y5j43DHFjFUq4M*rySAJEmwBF!N zbV~e7kyqpX)8OSwQG7LZ^`U`0K; zOQ~5Zqy{9`E0EYenEY$Ut-)25XI6yEVjkb8)j97E(-|_R!|NuQxaZRs#e5gXWO5gvUrYy$($}t{7e%XCoyvdHd=|E5%+0dm);oG zN$#~fg6dY0HnGfng)O#Zwmm|Z5}Yn72=}a{&}vXS^~7X-<97{7o^?GwK`+o^kHD26 zIy{1pPBoeEqhgUPRi1as<;dd9aaWgctQO$(Zzc19onYPIubqA?qgwWEz3?3uQPf#; zN_sHD1dLA8Ke%PNFTp$bOX(d*#&t6-2?;CW%ymb@Y(EUB4w|IVK^EIZPq?z)YHTL< zl-`JR4}8il%a7J+RFmcG>3BsirF-tBTRfB41L9qdN&S_5lCZl#Ibzr}@8~)sz3dUc zJAm?v;Zfn4FR|X|Xo`t7zUIU9f)3KtbQ)B8se7nU+LLf}Qj-m?XglMYRA(Qb-l16#plP41PdC-yA;{5(Vgl_|`Ep++Oy^dnX#1<F z?u36|9zYXW#Pq-2xw9C>e+QmwD$nM^3PISE!ZepUg-t-vnr6|E3a+t?wDiBY1&JSH zIYI>r%wFgG*I+9a-pSGqJ)_%%dLqmBT;b6VfV*T`yH4m+F>IP5Vs-tPB0DWZL5G{R zFnEhn;L3GNBs)4XEFa^0!BN`0Ek;Pm-m-!yGauF0?@RXaPU|9bLKA`?UAlsWXUPgh zN-qcqMEl#h#sY&ofST*^%_21PF)|IueDC=*{{ z_?lYLVts5&9n?IaP-CSCIAtpO*aMbJ@q_Y*HqYh+f2^7suE7}JmidD(?GU91Zjtr( zZ}f-I$eOKfanV4i=F*Dmwk;if!!1F9kw^guy(rEO>m^vtl~T=!=@%RtUu#zYnEqvY zN!OL{*2R90`_bo>)jD;%DF_I&vGu8b)b>Z68|a)?RN4oRS81aB|Y#2-_Slcu_z=>9mzq3lN^LjH1)6X){}$B~t-95l{+7^wrfRomv8s!&`fd)y*7;j_C4@$^ zG6}0mrL*;v<}JnZx}0}5aJ$BQ)%V8+Qs>u8E5CX#&DWie(Sx>TDxXHYkZiiqNpWlT zh_6o`{NOrxfptr}a0;^b{|myfc?o!Wv7y5XjV%ee09aJr>o>FZfr-f*&0(|a+uK?5 z*%#~0bLQ-ld9UJne~oUnZL#$AGCi+&Ed(EW$o#fABgX3z)9b(5znyH~;k^?Yu)9DT zdWIPR1y*?l;fglz&q;o{Nz5cHpn4xHnogpTR}fIZ!Qs}nOy?k8-@rsw_Y@G?kXc#v zlk)<&Q3NW8TkTE~|CO7|)yOxra(n4itzwUaWxREKa#g*2{Bw;JAsGn*KDdM&kFYZ0 z7Aksd|v{6yn`=Y_dC_^qJ5Jbk>)xD`#2D_-GMSeaPB zZ#MMz4Q&Gd5A>4WQ>F4~Q*p;Pw=h3QVNKibd0nYr04DhfN#EJC*Y6n?GSn-5R=iE1 z_YjHiBdq(M9O8wA>1sQ4y_hj$57SJ%bRr8Cp6XQU<`Wjm=5}O$2M0VPp}PMXf#^y9 z9n#K;J+1$usx`Auy8H8D`eW~3L*Sqvqz^NEShbmKOJRN-^0MHa|F7523<;X1krx(F zApRIc!BHVF+Jg}zzb74c(^H=Xngy|9495!r~YO@&M?`u52S_C@|4m z=Xx|vU2KNCrf9%LtmE^ppAMbizcWI6dJjGX2GiT-r}qmgyf(?tHb=#7C65f!*h^R4 z!?7%a`OeuaJ-mcSSI49zFs*H z&gAwdOX%oC@O<8woT0xZQ>!{yECEaQ?D)}GeHv!vT(9?UR`tm?!YCR+l?M}VnAW^?<;b$|Z}h9K*d^Oo zYq0_mBcrG9h5girCA#)73vA2Qxf&3NXNsoZv;K|=g&Ie=Ud&d8)LpwVRahThBF_-0 zAA@|zn%&}w=nuq_ax0xKE=4a~Z>`UDx)XVMSj^u-=<)_au=YPtwW;j8g1r?TIMgkM z(v-3ET1^P7qhxxOihwSyL@}#h0r1p@AduMK9zx}s7o=pYKAkVjhBwV6GML?dxV@R^ zV@|yTh2wp~QQEM|B_;p`bu2^e~l~J8-zdd=g_wQ3_&9%m@)w#4k!dzuZ zkGQ{Y>8jc7i@E!YYR##XnNHg~g+bxvTZS{8lN`mYU5?c0>NobMWwY}@fkxRQG)LxSQyu%>%ei}BH1?kBY zI&Qi9N$i3x0UtDA_Z5L7Z%)HGl=|CTMe3Z2!g_~})TAfl z;|^*Y75OyK|8sk$Ix5b_J7vc}_d+t$gm9Sd-z3*7DXSM>C*BfH*p7Z=jP#c=x@h2Q zX{{rn?DLA8nEu|8A+5fw2Mf!(aOG2Fms~8#>jKjrFV@HThYPLVmJm`h?KRf#!lKSG zD=f5P#LSlqh0~61HAgbY$?g_HAvmw&j$xT4YXfr1@;%8pGd>$0x3di&aEm8Jfh*;( zMxH{OLd3aPRw;JQ(ku!Uf2BKr$AMi}ts^L*Oem=VbXYEQ_0{YPA@Bl~n5i`6V+pw$ zeMga7yE{urZj%dM%Jf*4=HB)!TW)~c)SZ(lOVIM-QGLKMT3J zhj}y|E!gH{Bb{!#i!RT7;v&@9V^O=@x~k>^9^~V!Enh{Xh?c%0YT!Q@)LA95n7S){ zq4ujbZ7!g<;4XLLO_ec@4PRrpm<-)z#(Q7vyZ5q30CwIOGGo{5{Z&kJd)e0O$g{2m zIVa)x)~hceQ*!KTWiD~)zRuamlQtMUs1~$vK*7#E%GZ?7kVZnZzJiGM9F($>A$3px z%YYR(`dNW^@ssx6ZuJ)1u?`!_f`2BC%<^ddd>JKYK&%Y)aM?7Sd?p1eGJ)@Kz5Kci zCazXo!xYn1V>jNO|uo*Oq^GMk`AKECi!!u z7@^Mj?)~m5XK&RC;_6!|QSgkO_U7>wFMO5`#WP;)h0dTJR5EneGu`?x5g@qVrr4{6 zj}AF5r|PKNETA^<>wMCM?)~aoS(#$@NH@s)`W&MGpTRZ;ty-XB^^yG^e@~l2{pLc6 z(Z8L!!PCb{M)Ua}X6(swt^dyO(YEasAD-En%cc2F_88cav71Wiww;?e+3sq8JvA{S zTgcRr_~6k zIV^l$SNx;$an9(})|~^7FTo!|2|yy7mGSEl}sV!jRMTWR~Y3V z^A<4dQM6DF6*e?wG1oly_jKVD8Hn+BaE<=f9Um1%>B;a z#ELtUq_l2=58!JkGsmYU%*xdW>`%1btsT*|L|gEZPXXb20V#Li){Xe$%R=nb zJg1*B&N>S_I6D{-Bd0luF0H4jFv<8O);{9#aDJ8Izp}i zIjqiRDg`q#Jl&g)b}`=!?JZ!|=~UIcbxggSb~>igy#C1tDq9(-y&Y+j+Z&0fR|6OL zW?)D6)2NN#saDDHRKw5r;i0WJbq^1bCTYMyOB%yN@|41Hnv&SkQZ?j2Qm@l|RmoN? zRg}gnRY?_ryeTE;IYzz%DRG7K$pa>-_2WbZVVV8Ay!emHTrBs`n@%gNC-j;#F#(oV zx@)dPhD_FaiTypP`j;x}k2XQn6PMY=UoQR;%305Ls&cL2QCcXICAdlp=MPXvB;y6T z@~uLMhx|oudH&sAA-#X3I_eRMD;{K6|4rJV-|5>F*n{BPZEyo(cm1r$#jFut zDDNEYi4jrT)`<{t`6eq6iO-7OA%(-G#4vz8!qUtG+p+;PHE8M3xVUqz3AW$1QH*d|_QXYlw5c zpHn^KvkVgrjTxm4rW~ZuI4fwwK-6I4XHn5lSPKs42USlqciGS-Mn4WHi(0iBWhRB> z%6>O1j5Zg6v&c(SRI7Xp@*doIpP~9rvNC>6Eo{bw1uHvk)li>Qh|cKQ#DcE})C^bj z*SO)_U))y)kJEN|QNt^A4_?p_4OPuv8qm%6bnY4PxSvb&xh?4f+d2u4%bm1%3Af;G zuW>M7Y|t|4Y<4CJpJj~yWW!!|H}v$@+D0q20ZzDj*^_p$%g>HdW+cMp;dVxJpRbPM znXkuE*Bz~Ipo1sR7=mgdtPB$-!;3@!^8APW^{jr*&cy2T0U1oUhK}={JwKR$f>fL{Iijo z?O%&KTPixxXVg8D-MQX!esZ=YcongdP0H1_u-_1taZ`VzbURp^EhOZBZqm=Mnb_8~ zaPtpVjrF#(@aL!A6$GQRg8#*KjPl`{*51Sm?xFbRU-tbwhwG#8p&T=}+ml;jm)H(C zbY5Qx?LmH!kDt~p?QYGGcD70VlZG4U&Nm~O07}hN=ZOa)pkBByG?ts`m`0F({QS*} zl!NR)4k2fGq{~GIAEK--(1PQj+JG5)PR29juB)OhD=4G$A}#bT7U}(PgbB04`Q}L? z3zJK8qbB|PoE%7WbhNa(?8BDwm4t@t?u}yAYOfFq5tSxuR#eQK;lDCM%)|w8UqH6t z1U4K`mN`)u7;W{%v%)p+l-1Qk7V418%gbeAe$pKs>j;U&TV61k7x0FCU}e9RAuDn|HJ$uCMI?iT^q{0LA$tf@S);Po9g~7lG)R?6^dS-qZ{aqiJEt5 zrDQkSF2f?bM&= z^1W+U%moDL*E0*D&T`W~e@>`eUGJ&4MlRM`=dsVm;~}8?3g-&3-Tqfa{P6k)FlfpC zVZC~mUHw^x!+#%?kL$V*+gQTowETGE2=ofSyZ2yFaNF=u$JG=LpZ?Y7Ox|)bU-&}@ zoJ2f_3krl%a;@BTPLmG$M*FH@#$ofYyQ0aB)5%YsFIeT7UO*BT28M)!CC8ggZ^bm~ zCwUBiJctt6zb`_o&$^ZvP-qxt#C!a0C54I@YKU0#Nhna`<4*pO_!MZeCU)Df_h7Mr zqNeMAikb){(v{m2?a_nRAe#uNBx3g?sXl0V;E%B@G9FL$Gll1|XWAiuEAH#pFJ2Q| zMpctvpBmu=_-v%PClgwgpQ(05P3TP&=#?tW*61bOum+4U*IY^I4&pNTFvGFqnoS*KHN11!L|AA%T6VZ=EGa5@^b@*r7i z+k$^yV=SeO%S<2(6!hhOD9pHEitm3EygwKrG?Q?NX!g+&mvMi*aU4*o@`#EQ86>c` z?!%E<T!eQmehoDk1VPWlmM(%pyN{Zyz9D5t_cqgnvd;9_2!GgCPcNkNlmfT5b zfkZKsn9*VFy6c%{q@M!Ws5r3CX4D&ke3HK&n^$K!^p z{5~xnY-SHAmUB9jZd?9Q(U@=0_4VGSB(U7S2Plf#ii_$Srp3NC??;=oIjrH8W=wao zmiW-8-1|$;X}985a3R~>^$-tV;gbVF<*OfoVb7bo&Ga9$|3}LF8)_S3Pk&(!bM{gH zf{4$zuBK((L)%jg06UQo#*8-e){^=g(zLmMUVkpo*^_T>Uva?7vAQ8VYhEc*;3|Ti zT>XW#&;chHlsxg5-?{*P@n&x5*cZ9Va*WY|#V;7n1v2&SjBmCh;TFjQtA zz$^qH$ba%64-Sxht5}F_mtT$fmyJFwc?8CpH*aydmyR_7c4*dp6mjnnX)5Q_?@oqM zdsC#~EJ49O7EjzwHV>n!Yx|m;k@hc%s!eHM#jna=RC3!Nkn^rEG`iU8P9XhQ%qKm= zn~+x_@SKm&qe%sO_t&2j5~O?EG)tc!oEh)IU<`;&dA!^VcvM z&7FU))Rk}7zVY&u)pRPR^-rbC<&SMDkQYZgq&233q_}zegR@va+(3TypxUp+BUdx{ z&YN(Y`mS)}cvO`CkKMUWlj9by{VsP8fckTUin5;l=L2C2DfI{$=l99*)&zw+=xaqo~LB-vVaR18CYeg*K- z@2UW=S|;X|?PaVE#(uHy3gq`1$30 zS1k*U)@Kd0Bx-idg`qCctT-y}L+d=^FbsRU<(wLBrv#taTIef1jIMM(5qNTD_;u$) zrSxmmJR2D7uLSp~N{uxhEfFOXfS+6u$0gCx&U*>ZF?$yBR_n@5=VD%*Mb0-+`+yQz zKKNIre#0f+*&J&4W1ozXL-yisQLAhc&W?iQMH8c5$%>mSnZ_Een+AAEOv}8Rk069n|Wf8-pkDH8oo2ymV>wr zmmP1z^wy$@t{mCe%%zGfADajRjYXo*mI{q}c`sin;BSGmpmL@4y~JRaC{*~hy1KP! zl96A7lLkdn4QDPNZWn-alwodq2I}a0_&t!n_?-X7o6yRN=kjo2GkPTKO4M#EdL;J4 zM95>$E#{5i@_B6VZ~_V7ex2?21n=hK7oFH!W7YeFm-ssiMj92d#Q+S2y zlZOfqyAj({CC@~{$@0Fs5~pxy-xGeMH3l@Gh_z+Lv}#=X~!awm7N_; zwoa!l-8_G~Js;EVOK2-*)U~kRgaXab`c3G!YFd4x3l>N}_J#huzo z^3_@!W=mE7nnj{RZ?m2I?8g_%KUN+OFarqGabg?+orHtq0(J;*Gs>{!Yy66Qi6!L1 z3dGs~Tg%7>RlC(wQt0J0z+a+cosS&des17Q2V^C1x-u;7`UE5+-$Lqncma?UXKVb$ zU4StT!_=ht6S}&`BK;`1mCB8mZvg4`vo$D%hhv}n#4bt=z+JkqDxrp}Zr7?kPC)=g zDw^|r){%Ky`KnNL(1+lW*^Ev6tGwgG>!rGglqt7Ee5gm2ComD$W=r%aedxyYixgnw zW=U{z@=~V3X6_^fBCIm=9rDgb=HBPWBNbqyjK1VzV_} z+62UzjzXzg*rtAkn>A(Z13u1{40~u=uG~=}6kRIF73Xyt^^P1A7XU;6F9@~}A>Gq> zuUAdiR$u$M6@LRTsylKOFM6?ZS{1;xPRN{s)6JpZb(_0Mc{%(*ty@=Ra<9;tF#Aa3p!O`d?>b6Jn)pmLsoE74kfhv2*OP#s zDkYhZpwvp~6j`7%=q-H?xGU}BWDKC>GtmrkB7r>id#|(93Ix;HWV{`sCb!>23-ACi zEh=hhBQ4xzXQ~nI)LVVBa^-ffi3Zh<_X1Xz)T*pBPD9xLRU%dG@%8nR^{;%Me(HbD z3X31#sV+Ai|8Uv*0o2i+gmKCnpYEB@HKv=|xO6uWE_X;u zXWPA>r0k{)Kw?C3bNJ%r@=A^AB(}5NLC>*55ooxnmf+jzFE$gSn{W(OS^%5%4pOt%w$=K`2;{GmKbkhEKGzKJbS@4w?kU~N;5=NNx~{p9=r7JXm})))U|=(eU4yxd$eIEX{iSYpKM>~qBfa6U-fWz4Rz8&1UZD7>01Gkpuu zO^g3#D(FVl(LP#uf;K^e>}leIbS$MWybd3#+d%`nDJ8f=S6jkzzI1{Sm!9=42YIsU zLSlca0D87;Tl3>^WESD;)VxkN19U!=IeHq#9ZAeDA6n?whdX)oMLA{bC;#l;<_#en z3j<22GUF9@AcpJp`umEIuaPMQ7PhI)Fkn{91>2h;sZFkloI_ay!THTA3UF;&8q^sg7 z>)+7ak{>q{LLrT%ls0jD_o&*JuN+&Tww{|||GCzA0$3kCG9~tM<4GDc&q-rOAUXRv{HOAJV)<_rROT8}Lkl_* z+ozDb^J#F$4KE)3j<>qS3Ps>dMm&S1Jh zf+e4*H&%my=ZwsT;vXO}V45Z~=cE~mq4@PA24^FCz;F1U8mlawTWXth7Qa`!G`=!! z(yH+b(VYq*nD8p1-}~1|Vl;c$(&bsJ$Vv(QO}9rV%}wrKsV3`WfqXe(A(bW6Tlc&c zVaG#CmgJCY^XBG}1ksrGzY?gN>~>}N4j{-uj`8PQ`a_zOW_S3`xz%_M$z+mqGVsdS zT8%FOCVAo7BWB*vfL_G5zj5Vm)BB>o(OEg~ z9sh`ZqNUCFo9#OiDJUjxv+EHT9liL8Hiu<(@yPMevB9ws96{#ZJUCB!WW&s%DCz1O z$DjItsyg?$Cax@wE5RfZm;l>EP@)7yZ3Pw~U`WuCjIcm?DM^-RH?b5YJQ^^KU3F2& zi^5isLWKa8DDs9z1cIoQ5ZO{Cnok4BL)XNv5DTe~S_^G=yLS@ohvtv`HIq5_+;h%M z=6-+Yeswc>-tsMg+PB5ONKOfPe&*qiFU}p!MT7gr+}o#5?Rk3cELeJy@=hE@oxb-@ zblJeVv24pvg(UDYbU4Dwx2KJHU3gD0pd%MG$8Q{-fAeAUrj+N1XSz%j$NPTU^3IlF z)ny|ik(Z~*-?SZ7K`wc_zQ^tY-*L@2@PuTd_&aI`e12${uHDH(Ab095c4%Gar&CL> z9OYo$gX*+I*Z!usH@D3)61B=J4V^!J{?9GH{73ZNt82(g`uY1$_rGvP=Ck_o+~;0z z|1?%}B7vgh{5MGf9)sFS33~vmlsI#QFwgZJ5vFj@JpE(q_Cz5U)=sq$2 z))`i(>znc4Zz^e{-qHP>cqJZLt%SRgMN7$|_XmBf;m>b4Pi88!6ulZ4zDE&00Q&{S z!s9H~4v9Zr^M$&7{lYC-=|LK`P@BR4R@q*Q0o2D^m{1IF3c4D^yacb&ZFFs|fxUtV zI%wM%chR{8Xg01+YdVgvD&(aCyb^eIQ@sr&aIed)4L?)`tF8kaBBvFLBiUI9oL&?; zm$=$B3>mnM_J8EHzfb4o9BdCAZFoGy|3be&Qoz>fqY&RSLMq zQI>~ZtJH}3QoMq%JA$pBZ)n@jubG10mq$m}T8;1vvq5Qu#nxb)Z$^{2E42#GnQX{f zuhbdgA|}6v8P=Tv6bW&XrED`7na+q-DO1__qa&)XjR{}tCmg_j z9A}gj$l?9QD61N#j?pkpzNTY_C$f)yE>_M@q+uRFo_0!DHSPr{3HBRwK|+uG9wp} zQ%i7Cn@{7hEczt8#WDqD_KHIc47EB%0z%_uSgI`OA!FadGmVQYmC7teuk}E#n6I?) zCfI$VmJCk{)sPUCZs_#)>Srbz#*8_bVZ~D}$>UUcCvn6f7MT#6%4YHYY^=a^WA? zzNG3MlwOsxV%7n4*CAy{n-EVg^FRef1O=!xus4bUb6Ya}g?fvg+%wKxn|dJ&4QcLr zA3_ekDUG-CIp^(6J19YpyN?SRyXMU@o)jMuISem4=mr`2_PbIdd#zy5@=pg4oT?mq zlFb7^9^)oE#d8B+c{-uqE_WSgSe(9;klE7MrZ9=8_(k?k$RL&@E2#>ovRDLo*H9)M zh?@Evo1~ys*P*tMl5g@ZW7|an3FvFbM`-caQmAKldYFlL$I7uFdIp#l`%L;|;3mqr z;%S4jCHkZGaG;1mZLto-2IJfuelx-19DJLB!+SVxb`Evplu1jQYUUEzmziEdRfhSy z(LsJJkc}Z$*`_<(7&8vFJyNxkMo%&>qvZN9CT{8wuW+M>PpiaZ;UuFNy~3b6W;@AI z3v9*yI-?h#t8Gs8UB~H#4_$<(p8ZiH<`Pmn1p#5vYy|GY=y7?use&)4YTBK&f2ER) ztklNqL|8;C>X}RoJ5dLZBt+4L*wMeMn25w}g{={6!eRDoJZPU$6kIlVBZw4h zun@~i+eizqO7*v_o+~LUu@20ScCbx^Wrb4f(XzbiO`WeCnq7XquZL%Rnds=xTEX5b z-fq;Bqbq-Yczc4yNGV2b);SRYxzi<50e|}v`H@$p=PPS1&R|!VoE`_iRjbe+N9|>> ztkJ)-0->#LhF?6-$6Os|ChmM`{3oW_L}@40MU2#8HfOXno@#34ta9m&xE#|iWzm7- ze=kZn(&%aaB&RftEjDBs3uwA2NYo1pzJ$#YxO9V7om`n*^RZ-Q2rX8opL3sN@yxV4 z<%|mC3V|hhZK^NjaCM_Qt+G{EK4ncC8#>ibt<(J+)2#efh@RO|M~1y&Q0sLm&8>V5 zA2VNoGKt~fWUE2eqp==Y1MW0xy<3qxo>%MB0pPe?;>2-G_&y!4 zqE!2{+rJCQrj=PC5cP`3511&Urt&*s8>BoVr4IZ}-%*17+XeT@k6cCnk#IIOS_VmA}fk+*zEBx9A$j-t2 zbzSA35aW(%r4KTd^JWEcPeMRrrT44o!R)(`*>D2u-3Zf{^bOM}m;f(|+yK8vhx;jyBBU z+HgUim^hii1A@!dV#1!+>0_;LH-}wm_Od8iXNVJE0Q0U6Y3d#udoVF6=QhL%A7IgV z#l{Yyp2G6JHcb{bG%jD)7I=0Lk_k&r#E6ku2OtEVk=E+@xHCPu3m=E|XoQM5Y9KpuMhFOsS6w_J=f=c?53t}<(~NWA+VW9J;L zr4;o1KR`x#TW|cfsm+%{il!;h;Py%Pj1QtuR=ZOHWoReonw{$`&i$dFx9{c5za$+- z9!UriC)YhIthU3F>~6`RyllF3CQIhvG7%~-3A-q!Cp|Zc*2v#kFZ&a`t9XNtkQ4;) z(?rV^=oJy?z7=68Uq{C-AvQ;~eFF{LgaR9NLyrrllLgD9MJ zvBVVahfU)t&l6L9!j?;~Z2%e>6qoJUIHP#N<74Q4iP5g>!CeYjn5{U1K z5Q33qwkNhP7SrRblI%Iq$1&vxA``|O8R5sMAH2(x;WeH^xlW%t=5=E`?v61z2lNXK04+M?M)wNjlOx?V(AlX7d%X){IYNmWJ zEXud>I+M-zfWlLJtd1*0W%U9FK9>^)f-mQojg*&0Ma=wrlIz!p97$dsdNTE)ChLPs zg7tBSo8ZXXvbIV*$qx3u&7npbtq)iEpWQGlYl7F3SnNwMpkj170;e{dD-yfY`a1~< z7m*pfXB32_yTzfvUEm;1lBd%57(Jto Date: Mon, 19 Jun 2023 14:57:50 +0000 Subject: [PATCH 067/123] Bump feedgenerator from 2.0.0 to 2.1.0 Bumps [feedgenerator](https://github.com/getpelican/feedgenerator) from 2.0.0 to 2.1.0. - [Release notes](https://github.com/getpelican/feedgenerator/releases) - [Changelog](https://github.com/getpelican/feedgenerator/blob/main/CHANGELOG.md) - [Commits](https://github.com/getpelican/feedgenerator/compare/2.0.0...2.1.0) --- updated-dependencies: - dependency-name: feedgenerator dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6607ece..76798c0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ markdown==3.4.3 -feedgenerator==2.0.0 +feedgenerator==2.1.0 jinja2==3.1.2 pygments==2.15.1 From 1db80df0ce0a9762e95caf8fbbb4f67776957e86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jun 2023 14:57:56 +0000 Subject: [PATCH 068/123] Bump pytest-cov from 4.0.0 to 4.1.0 Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 4.0.0 to 4.1.0. - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v4.0.0...v4.1.0) --- updated-dependencies: - dependency-name: pytest-cov dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 0cf243c..a434022 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -5,7 +5,7 @@ mkdocstrings[python]==0.20.0 twine==4.0.2 wheel==0.40.0 pytest==7.3.2 -pytest-cov==4.0.0 +pytest-cov==4.1.0 flake8==6.0.0 mypy==1.2.0 types-markdown==3.4.2.9 From f646c89152de654bcd69091b8fe630cea5b8d715 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Sat, 24 Jun 2023 13:25:17 +0200 Subject: [PATCH 069/123] img's have max-width: 100% to avoid overflowing of big images --- CHANGELOG.md | 5 +++++ blag/static/style.css | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f918f1..e4e849a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## [unreleased] + +* default theme: `img` have now `max-width: 100%` by default to avoid very + large images overflowing + ## [2.0.0] - 2023-06-16 ### Breaking diff --git a/blag/static/style.css b/blag/static/style.css index 416284a..f00afd6 100644 --- a/blag/static/style.css +++ b/blag/static/style.css @@ -48,6 +48,10 @@ aside { color: var(--foreground-dim); } +img { + max-width: 100%; +} + h1, h2, h3, From 86826c0c03389c4563d22cc816999a9b907b8d05 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Sat, 24 Jun 2023 13:27:11 +0200 Subject: [PATCH 070/123] list default template as package explicitily --- CHANGELOG.md | 3 +++ pyproject.toml | 10 +++------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4e849a..271acd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ * default theme: `img` have now `max-width: 100%` by default to avoid very large images overflowing +* packaging: explicitly list `templates`, `static` and `content` as packages + instead of using package-data for setuptools + ## [2.0.0] - 2023-06-16 diff --git a/pyproject.toml b/pyproject.toml index e6b5dda..141b900 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,16 +47,12 @@ version = {attr = "blag.__VERSION__" } [tool.setuptools] packages = [ "blag", + "blag.templates", + "blag.static", + "blag.content", "tests", ] -[tool.setuptools.package-data] -blag = [ - "templates/*", - "static/*", - "content/*", -] - [tool.pytest.ini_options] addopts = """ --cov=blag From 32fff6dabbd0ebf19abf008f22a1645357aca2c6 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Thu, 13 Jul 2023 09:52:30 +0200 Subject: [PATCH 071/123] treat templates, static, and content as namespace packages --- CHANGELOG.md | 2 +- pyproject.toml | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 271acd5..cc3f916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ * default theme: `img` have now `max-width: 100%` by default to avoid very large images overflowing -* packaging: explicitly list `templates`, `static` and `content` as packages +* packaging: treat `templates`, `static` and `content` as namespace packages instead of using package-data for setuptools diff --git a/pyproject.toml b/pyproject.toml index 141b900..5f5e093 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,9 +47,6 @@ version = {attr = "blag.__VERSION__" } [tool.setuptools] packages = [ "blag", - "blag.templates", - "blag.static", - "blag.content", "tests", ] From 3cfd756ac1360e2120ed1e7b60dfa9d7587c3014 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Thu, 13 Jul 2023 10:21:35 +0200 Subject: [PATCH 072/123] added data dirs to MANIFEST.in --- CHANGELOG.md | 2 +- pyproject.toml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc3f916..271acd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ * default theme: `img` have now `max-width: 100%` by default to avoid very large images overflowing -* packaging: treat `templates`, `static` and `content` as namespace packages +* packaging: explicitly list `templates`, `static` and `content` as packages instead of using package-data for setuptools diff --git a/pyproject.toml b/pyproject.toml index 5f5e093..141b900 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,9 @@ version = {attr = "blag.__VERSION__" } [tool.setuptools] packages = [ "blag", + "blag.templates", + "blag.static", + "blag.content", "tests", ] From a1cfe01373b2386ada237686b7265a2934ff6aeb Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Thu, 13 Jul 2023 10:21:54 +0200 Subject: [PATCH 073/123] added MANIFEST.in --- MANIFEST.in | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..dbb52ef --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +recursive-include blag/content * +recursive-include blag/static * +recursive-include blag/templates * From 111957883ee9bc11e8a5a04674a32bd33d2b96fe Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Thu, 13 Jul 2023 12:07:50 +0200 Subject: [PATCH 074/123] updated changelog --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 271acd5..83a8fa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,10 @@ * default theme: `img` have now `max-width: 100%` by default to avoid very large images overflowing * packaging: explicitly list `templates`, `static` and `content` as packages - instead of using package-data for setuptools + instead of relying on package-data for setuptools. additionally, created a + MANIFEST.in to add the contents of these directories here as well. the + automatic finding of namespace packages and packaga-data, currently does not + work as advertised in setuptools' docs ## [2.0.0] - 2023-06-16 From e4786eca74716674fc01bf53059708a48eb1fa83 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jul 2023 10:13:49 +0000 Subject: [PATCH 075/123] Bump mkdocs-material from 9.1.15 to 9.1.18 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.1.15 to 9.1.18. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.1.15...9.1.18) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 0cf243c..f7e0ff5 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,6 @@ build==0.10.0 mkdocs==1.4.3 -mkdocs-material==9.1.15 +mkdocs-material==9.1.18 mkdocstrings[python]==0.20.0 twine==4.0.2 wheel==0.40.0 From 75b926de6718dc6a3b4c8782b2918e62510ea695 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jul 2023 10:20:22 +0000 Subject: [PATCH 076/123] Bump mypy from 1.2.0 to 1.4.1 Bumps [mypy](https://github.com/python/mypy) from 1.2.0 to 1.4.1. - [Commits](https://github.com/python/mypy/compare/v1.2.0...v1.4.1) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index a434022..4760fa9 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,5 +7,5 @@ wheel==0.40.0 pytest==7.3.2 pytest-cov==4.1.0 flake8==6.0.0 -mypy==1.2.0 +mypy==1.4.1 types-markdown==3.4.2.9 From db1d81713cb9809cff5858997f57240f5e8425d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jul 2023 10:20:57 +0000 Subject: [PATCH 077/123] Bump mkdocstrings[python] from 0.20.0 to 0.22.0 Bumps [mkdocstrings[python]](https://github.com/mkdocstrings/mkdocstrings) from 0.20.0 to 0.22.0. - [Release notes](https://github.com/mkdocstrings/mkdocstrings/releases) - [Changelog](https://github.com/mkdocstrings/mkdocstrings/blob/master/CHANGELOG.md) - [Commits](https://github.com/mkdocstrings/mkdocstrings/compare/0.20.0...0.22.0) --- updated-dependencies: - dependency-name: mkdocstrings[python] dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 20d1c70..d80957a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,7 +1,7 @@ build==0.10.0 mkdocs==1.4.3 mkdocs-material==9.1.18 -mkdocstrings[python]==0.20.0 +mkdocstrings[python]==0.22.0 twine==4.0.2 wheel==0.40.0 pytest==7.3.2 From 609b49b6f2470a8921c286b9b10d1c89e3130bd6 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Thu, 13 Jul 2023 14:01:02 +0200 Subject: [PATCH 078/123] updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83a8fa3..2f36b32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ MANIFEST.in to add the contents of these directories here as well. the automatic finding of namespace packages and packaga-data, currently does not work as advertised in setuptools' docs +* updated dependencies ## [2.0.0] - 2023-06-16 From df84fbb96a4a7f8692ab6084ecf7796265d21a9a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 14:35:36 +0000 Subject: [PATCH 079/123] Bump pytest from 7.3.2 to 7.4.0 Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.3.2 to 7.4.0. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.3.2...7.4.0) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index b97caec..84f0c9e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,7 +4,7 @@ mkdocs-material==9.1.18 mkdocstrings[python]==0.22.0 twine==4.0.2 wheel==0.40.0 -pytest==7.3.2 +pytest==7.4.0 pytest-cov==4.1.0 flake8==6.0.0 mypy==1.4.1 From 35d5ab93cda599f5f0c72e3a974e9d614e6925ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jul 2023 14:18:37 +0000 Subject: [PATCH 080/123] Bump types-markdown from 3.4.2.9 to 3.4.2.10 Bumps [types-markdown](https://github.com/python/typeshed) from 3.4.2.9 to 3.4.2.10. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-markdown dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 84f0c9e..42e857a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,4 +8,4 @@ pytest==7.4.0 pytest-cov==4.1.0 flake8==6.0.0 mypy==1.4.1 -types-markdown==3.4.2.9 +types-markdown==3.4.2.10 From 7b14d7dcbda7ba873dffeaa2774d77590e342f41 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 14:14:54 +0000 Subject: [PATCH 081/123] Bump markdown from 3.4.3 to 3.4.4 Bumps [markdown](https://github.com/Python-Markdown/markdown) from 3.4.3 to 3.4.4. - [Changelog](https://github.com/Python-Markdown/markdown/blob/master/docs/change_log/release-2.6.md) - [Commits](https://github.com/Python-Markdown/markdown/compare/3.4.3...3.4.4) --- updated-dependencies: - dependency-name: markdown dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 76798c0..27254ba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -markdown==3.4.3 +markdown==3.4.4 feedgenerator==2.1.0 jinja2==3.1.2 pygments==2.15.1 From 37c5a4eca6544e98bee610dd1cb2e57c976ee37f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 14:15:07 +0000 Subject: [PATCH 082/123] Bump flake8 from 6.0.0 to 6.1.0 Bumps [flake8](https://github.com/pycqa/flake8) from 6.0.0 to 6.1.0. - [Commits](https://github.com/pycqa/flake8/compare/6.0.0...6.1.0) --- updated-dependencies: - dependency-name: flake8 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 84f0c9e..f9e5280 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,6 +6,6 @@ twine==4.0.2 wheel==0.40.0 pytest==7.4.0 pytest-cov==4.1.0 -flake8==6.0.0 +flake8==6.1.0 mypy==1.4.1 types-markdown==3.4.2.9 From 298a828ecd903eb69a21acc63b55d3081b324579 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Aug 2023 19:14:22 +0000 Subject: [PATCH 083/123] Bump wheel from 0.40.0 to 0.41.1 Bumps [wheel](https://github.com/pypa/wheel) from 0.40.0 to 0.41.1. - [Changelog](https://github.com/pypa/wheel/blob/main/docs/news.rst) - [Commits](https://github.com/pypa/wheel/compare/0.40.0...0.41.1) --- updated-dependencies: - dependency-name: wheel dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index b28ff08..d4d748a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,7 +3,7 @@ mkdocs==1.4.3 mkdocs-material==9.1.18 mkdocstrings[python]==0.22.0 twine==4.0.2 -wheel==0.40.0 +wheel==0.41.1 pytest==7.4.0 pytest-cov==4.1.0 flake8==6.1.0 From b44c303d7a6a0259dab2385708f88b2697477e42 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 14:49:26 +0000 Subject: [PATCH 084/123] Bump mypy from 1.4.1 to 1.5.0 Bumps [mypy](https://github.com/python/mypy) from 1.4.1 to 1.5.0. - [Commits](https://github.com/python/mypy/compare/v1.4.1...v1.5.0) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index d4d748a..b6bc84f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,5 +7,5 @@ wheel==0.41.1 pytest==7.4.0 pytest-cov==4.1.0 flake8==6.1.0 -mypy==1.4.1 +mypy==1.5.0 types-markdown==3.4.2.10 From 392181e728a479fa320cd501f8c34490d01efc52 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 14:49:30 +0000 Subject: [PATCH 085/123] Bump mkdocs from 1.4.3 to 1.5.2 Bumps [mkdocs](https://github.com/mkdocs/mkdocs) from 1.4.3 to 1.5.2. - [Release notes](https://github.com/mkdocs/mkdocs/releases) - [Commits](https://github.com/mkdocs/mkdocs/compare/1.4.3...1.5.2) --- updated-dependencies: - dependency-name: mkdocs dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index d4d748a..47b8c22 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,5 @@ build==0.10.0 -mkdocs==1.4.3 +mkdocs==1.5.2 mkdocs-material==9.1.18 mkdocstrings[python]==0.22.0 twine==4.0.2 From a3637621d7155d520ad1cf1dc68a712c43d2f13c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 14:49:37 +0000 Subject: [PATCH 086/123] Bump pygments from 2.15.1 to 2.16.1 Bumps [pygments](https://github.com/pygments/pygments) from 2.15.1 to 2.16.1. - [Release notes](https://github.com/pygments/pygments/releases) - [Changelog](https://github.com/pygments/pygments/blob/master/CHANGES) - [Commits](https://github.com/pygments/pygments/compare/2.15.1...2.16.1) --- updated-dependencies: - dependency-name: pygments dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 27254ba..5f253e1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ markdown==3.4.4 feedgenerator==2.1.0 jinja2==3.1.2 -pygments==2.15.1 +pygments==2.16.1 From ad41be706d1a953dd65d66e2a11dc25528f47267 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Aug 2023 08:03:29 +0000 Subject: [PATCH 087/123] Bump mkdocs-material from 9.1.18 to 9.1.21 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.1.18 to 9.1.21. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.1.18...9.1.21) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 3ce8181..03bf00c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,6 @@ build==0.10.0 mkdocs==1.5.2 -mkdocs-material==9.1.18 +mkdocs-material==9.1.21 mkdocstrings[python]==0.22.0 twine==4.0.2 wheel==0.41.1 From ea345c6464d018f110915934c8dada374ed83954 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Aug 2023 14:35:52 +0000 Subject: [PATCH 088/123] Bump mypy from 1.5.0 to 1.5.1 Bumps [mypy](https://github.com/python/mypy) from 1.5.0 to 1.5.1. - [Commits](https://github.com/python/mypy/compare/v1.5.0...v1.5.1) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 03bf00c..0f6dc9f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,5 +7,5 @@ wheel==0.41.1 pytest==7.4.0 pytest-cov==4.1.0 flake8==6.1.0 -mypy==1.5.0 +mypy==1.5.1 types-markdown==3.4.2.10 From f09b30d069a36b04d67575dfc7ea8a27d3fd625b Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Sun, 27 Aug 2023 15:28:39 +0200 Subject: [PATCH 089/123] bump version --- CHANGELOG.md | 4 ++-- blag/version.py | 2 +- debian/changelog | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f36b32..4ac4c73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [unreleased] +## [2.1.0] -- 2023-08-27 * default theme: `img` have now `max-width: 100%` by default to avoid very large images overflowing @@ -10,7 +10,7 @@ automatic finding of namespace packages and packaga-data, currently does not work as advertised in setuptools' docs * updated dependencies - +* created debian/watch ## [2.0.0] - 2023-06-16 diff --git a/blag/version.py b/blag/version.py index 3896400..a45506a 100644 --- a/blag/version.py +++ b/blag/version.py @@ -1 +1 @@ -__VERSION__ = "2.0.0" +__VERSION__ = "2.1.0" diff --git a/debian/changelog b/debian/changelog index 8c41d4d..ad6fb96 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,17 @@ +blag (2.1.0) unstable; urgency=medium + + * default theme: `img` have now `max-width: 100%` by default to avoid very + large images overflowing + * packaging: explicitly list `templates`, `static` and `content` as packages + instead of relying on package-data for setuptools. additionally, created a + MANIFEST.in to add the contents of these directories here as well. the + automatic finding of namespace packages and packaga-data, currently does + not work as advertised in setuptools' docs + * updated dependencies + * created debian/watch + + -- Bastian Venthur Sun, 27 Aug 2023 15:27:39 +0200 + blag (2.0.0) unstable; urgency=medium * new upstream version From c45fb44c9fde1f7054184cb63bc535849205e21d Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Sun, 27 Aug 2023 15:28:50 +0200 Subject: [PATCH 090/123] added watchfile --- debian/watch | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 debian/watch diff --git a/debian/watch b/debian/watch new file mode 100644 index 0000000..a906a51 --- /dev/null +++ b/debian/watch @@ -0,0 +1,9 @@ +# You can run the "uscan" command to check for upstream updates and more. +# See uscan(1) for format + +# Compulsory line, this is a version 4 file +version=4 + +# Direct Git +opts="mode=git" https://github.com/venthur/blag.git \ + refs/tags/v([\d\.]+) debian uupdate From cfe7f36868d4b7fe648b993444a0654702ca3476 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 14:08:55 +0000 Subject: [PATCH 091/123] Bump actions/checkout from 3 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/python-package.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/python-package.yaml b/.github/workflows/python-package.yaml index b10540f..6484dbe 100644 --- a/.github/workflows/python-package.yaml +++ b/.github/workflows/python-package.yaml @@ -27,7 +27,7 @@ jobs: python-version: "3.8" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -41,7 +41,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: "3.x" @@ -55,7 +55,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: "3.x" @@ -70,7 +70,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: "3.x" From 4382dff70399acd30476abf338a8299acf8fcc19 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Sep 2023 14:16:45 +0000 Subject: [PATCH 092/123] Bump pytest from 7.4.0 to 7.4.2 Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.4.0 to 7.4.2. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.4.0...7.4.2) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 0f6dc9f..9ca3376 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,7 +4,7 @@ mkdocs-material==9.1.21 mkdocstrings[python]==0.22.0 twine==4.0.2 wheel==0.41.1 -pytest==7.4.0 +pytest==7.4.2 pytest-cov==4.1.0 flake8==6.1.0 mypy==1.5.1 From ceb6de574f41b97c64249c0a4145b34995bc74ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Sep 2023 14:16:49 +0000 Subject: [PATCH 093/123] Bump build from 0.10.0 to 1.0.3 Bumps [build](https://github.com/pypa/build) from 0.10.0 to 1.0.3. - [Release notes](https://github.com/pypa/build/releases) - [Changelog](https://github.com/pypa/build/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pypa/build/compare/0.10.0...1.0.3) --- updated-dependencies: - dependency-name: build dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 0f6dc9f..04ef287 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,4 @@ -build==0.10.0 +build==1.0.3 mkdocs==1.5.2 mkdocs-material==9.1.21 mkdocstrings[python]==0.22.0 From f0e6cd2144ae7d587b61cafcf632a718bfd9296f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Sep 2023 19:12:10 +0000 Subject: [PATCH 094/123] Bump mkdocs-material from 9.1.21 to 9.3.1 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.1.21 to 9.3.1. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.1.21...9.3.1) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index fe37e66..e93bcd5 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,6 @@ build==1.0.3 mkdocs==1.5.2 -mkdocs-material==9.1.21 +mkdocs-material==9.3.1 mkdocstrings[python]==0.22.0 twine==4.0.2 wheel==0.41.1 From ca5405f7aa63f6e8d9baf486fb3c6a44be23f887 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Sep 2023 19:12:21 +0000 Subject: [PATCH 095/123] Bump wheel from 0.41.1 to 0.41.2 Bumps [wheel](https://github.com/pypa/wheel) from 0.41.1 to 0.41.2. - [Changelog](https://github.com/pypa/wheel/blob/main/docs/news.rst) - [Commits](https://github.com/pypa/wheel/compare/0.41.1...0.41.2) --- updated-dependencies: - dependency-name: wheel dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index fe37e66..6702299 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,7 +3,7 @@ mkdocs==1.5.2 mkdocs-material==9.1.21 mkdocstrings[python]==0.22.0 twine==4.0.2 -wheel==0.41.1 +wheel==0.41.2 pytest==7.4.2 pytest-cov==4.1.0 flake8==6.1.0 From 33b5c282f987c611923b9ef1b93f87dc48a272c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 06:15:35 +0000 Subject: [PATCH 096/123] Bump mkdocstrings[python] from 0.22.0 to 0.23.0 Bumps [mkdocstrings[python]](https://github.com/mkdocstrings/mkdocstrings) from 0.22.0 to 0.23.0. - [Release notes](https://github.com/mkdocstrings/mkdocstrings/releases) - [Changelog](https://github.com/mkdocstrings/mkdocstrings/blob/main/CHANGELOG.md) - [Commits](https://github.com/mkdocstrings/mkdocstrings/compare/0.22.0...0.23.0) --- updated-dependencies: - dependency-name: mkdocstrings[python] dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index ad2f36f..c929c54 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,7 +1,7 @@ build==1.0.3 mkdocs==1.5.2 mkdocs-material==9.3.1 -mkdocstrings[python]==0.22.0 +mkdocstrings[python]==0.23.0 twine==4.0.2 wheel==0.41.2 pytest==7.4.2 From ead8c518c33f4406c8445c876e035a64bb10d1b8 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Wed, 20 Sep 2023 12:58:50 +0200 Subject: [PATCH 097/123] replaced flake8 with ruff --- .flake8 | 2 -- Makefile | 2 +- pyproject.toml | 18 +++++++++++++++++- requirements-dev.txt | 2 +- 4 files changed, 19 insertions(+), 5 deletions(-) delete mode 100644 .flake8 diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 2882809..0000000 --- a/.flake8 +++ /dev/null @@ -1,2 +0,0 @@ -[flake8] -exclude = venv,build,docs diff --git a/Makefile b/Makefile index 28272c4..63d744e 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ mypy: $(VENV) .PHONY: lint lint: $(VENV) - $(BIN)/flake8 + $(BIN)/ruff . .PHONY: build build: $(VENV) diff --git a/pyproject.toml b/pyproject.toml index 141b900..b395797 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ dev = [ "wheel", "pytest", "pytest-cov", - "flake8", + "ruff", "mypy", "types-markdown", ] @@ -61,6 +61,22 @@ addopts = """ --cov-report=term-missing:skip-covered """ +[tool.ruff] +select = [ + "F", # pyflakes + "E", "W", # pycodestyle + "C90", # mccabe + "I", # isort + "D", # pydocstyle + "UP" # pyupgrade +] +line-length = 79 +src = ["blag", "tests"] +target-version = "py38" + +[tool.ruff.pydocstyle] +convention = "numpy" + [tool.mypy] files = "blag,tests" strict = true diff --git a/requirements-dev.txt b/requirements-dev.txt index c929c54..4afb1ff 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,6 +6,6 @@ twine==4.0.2 wheel==0.41.2 pytest==7.4.2 pytest-cov==4.1.0 -flake8==6.1.0 +ruff==0.0.290 mypy==1.5.1 types-markdown==3.4.2.10 From 4f3516fb196b3ce2c58c1a3c3fb6aa5f230fe4a6 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Wed, 20 Sep 2023 13:03:55 +0200 Subject: [PATCH 098/123] fixed src --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b395797..04421bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,7 +71,6 @@ select = [ "UP" # pyupgrade ] line-length = 79 -src = ["blag", "tests"] target-version = "py38" [tool.ruff.pydocstyle] From c92130559cedba07f884ba3d9e6a5e59f572a87d Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Wed, 20 Sep 2023 15:09:28 +0200 Subject: [PATCH 099/123] add missing docstrings --- blag/blag.py | 8 +++----- blag/markdown.py | 5 ++++- blag/quickstart.py | 4 +--- blag/version.py | 2 ++ tests/__init__.py | 1 + tests/conftest.py | 11 +++++++++++ tests/test_blag.py | 15 +++++++++++++++ tests/test_devserver.py | 6 ++++++ tests/test_markdown.py | 9 +++++++++ tests/test_quickstart.py | 8 +++++++- tests/test_templates.py | 9 +++++++++ tests/test_version.py | 4 ++++ 12 files changed, 72 insertions(+), 10 deletions(-) diff --git a/blag/blag.py b/blag/blag.py index 28fe983..c770489 100644 --- a/blag/blag.py +++ b/blag/blag.py @@ -1,8 +1,6 @@ #!/usr/bin/env python3 -"""blag's core methods. - -""" +"""blag's core methods.""" # remove when we don't support py38 anymore from __future__ import annotations @@ -32,7 +30,7 @@ logging.basicConfig( def main(arguments: list[str] | None = None) -> None: - """Main entrypoint for the CLI. + """Run the CLI. This method parses the CLI arguments and executes the respective commands. @@ -328,7 +326,7 @@ def process_markdown( for src, dst in convertibles: logger.debug(f"Processing {src}") - with open(f"{input_dir}/{src}", "r") as fh: + with open(f"{input_dir}/{src}") as fh: body = fh.read() content, meta = convert_markdown(md, body) diff --git a/blag/markdown.py b/blag/markdown.py index e23d1e9..bc7aa6b 100644 --- a/blag/markdown.py +++ b/blag/markdown.py @@ -94,9 +94,10 @@ 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: + """Process the ElementTree.""" for element in root.iter(): if element.tag == "a": url = element.get("href") @@ -109,6 +110,7 @@ class MarkdownLinkTreeprocessor(Treeprocessor): return root def convert(self, url: str) -> str: + """Convert relative .md-links to .html-links.""" scheme, netloc, path, query, fragment = urlsplit(url) logger.debug( f"{url}: {scheme=} {netloc=} {path=} {query=} {fragment=}" @@ -126,6 +128,7 @@ class MarkdownLinkExtension(Extension): """markdown.extension that converts relative .md- to .html-links.""" def extendMarkdown(self, md: Markdown) -> None: + """Register the MarkdownLinkTreeprocessor.""" md.treeprocessors.register( MarkdownLinkTreeprocessor(md), "mdlink", diff --git a/blag/quickstart.py b/blag/quickstart.py index 42e8425..fdd34d8 100644 --- a/blag/quickstart.py +++ b/blag/quickstart.py @@ -1,6 +1,4 @@ -"""Helper methods for blag's quickstart command. - -""" +"""Helper methods for blag's quickstart command.""" # remove when we don't support py38 anymore from __future__ import annotations diff --git a/blag/version.py b/blag/version.py index a45506a..7311e69 100644 --- a/blag/version.py +++ b/blag/version.py @@ -1 +1,3 @@ +"""Version information for the blag package.""" + __VERSION__ = "2.1.0" diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..808384b 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Tests for blag.""" diff --git a/tests/conftest.py b/tests/conftest.py index f852463..9ee4239 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,6 @@ +"""Pytest fixtures.""" + + # remove when we don't support py38 anymore from __future__ import annotations @@ -14,6 +17,7 @@ from blag import blag, quickstart @pytest.fixture def environment(cleandir: str) -> Iterator[Environment]: + """Create a Jinja2 environment.""" site = { "base_url": "site base_url", "title": "site title", @@ -26,31 +30,37 @@ def environment(cleandir: str) -> Iterator[Environment]: @pytest.fixture def page_template(environment: Environment) -> Iterator[Template]: + """Create a Jinja2 page-template.""" yield environment.get_template("page.html") @pytest.fixture def article_template(environment: Environment) -> Iterator[Template]: + """Create a Jinja2 article-template.""" yield environment.get_template("article.html") @pytest.fixture def index_template(environment: Environment) -> Iterator[Template]: + """Create a Jinja2 index-template.""" yield environment.get_template("index.html") @pytest.fixture def archive_template(environment: Environment) -> Iterator[Template]: + """Create a Jinja2 archive-template.""" yield environment.get_template("archive.html") @pytest.fixture def tags_template(environment: Environment) -> Iterator[Template]: + """Create a Jinja2 tags-template.""" yield environment.get_template("tags.html") @pytest.fixture def tag_template(environment: Environment) -> Iterator[Template]: + """Create a Jinja2 tag-template.""" yield environment.get_template("tag.html") @@ -80,6 +90,7 @@ author = a. u. thor @pytest.fixture def args(cleandir: Callable[[], Iterator[str]]) -> Iterator[Namespace]: + """Create a Namespace with default arguments.""" args = Namespace( input_dir="content", output_dir="build", diff --git a/tests/test_blag.py b/tests/test_blag.py index e454ca1..155e483 100644 --- a/tests/test_blag.py +++ b/tests/test_blag.py @@ -1,3 +1,6 @@ +"""Test blag.""" + + # remove when we don't support py38 anymore from __future__ import annotations @@ -15,12 +18,14 @@ from blag import __VERSION__, blag def test_generate_feed(cleandir: str) -> None: + """Test generate_feed.""" articles: list[tuple[str, dict[str, Any]]] = [] blag.generate_feed(articles, "build", " ", " ", " ", " ") assert os.path.exists("build/atom.xml") def test_feed(cleandir: str) -> None: + """Test feed.""" articles: list[tuple[str, dict[str, Any]]] = [ ( "dest1.html", @@ -73,6 +78,7 @@ def test_feed(cleandir: str) -> None: def test_generate_feed_with_description(cleandir: str) -> None: + """Test generate_feed with description.""" # 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]]] = [ @@ -98,6 +104,7 @@ def test_generate_feed_with_description(cleandir: str) -> None: def test_parse_args_build() -> None: + """Test parse_args with build.""" # test default args args = blag.parse_args(["build"]) assert args.input_dir == "content" @@ -131,6 +138,7 @@ def test_parse_args_build() -> None: def test_get_config() -> None: + """Test get_config.""" config = """ [main] base_url = https://example.com/ @@ -180,6 +188,7 @@ author = a. u. thor def test_environment_factory(cleandir: str) -> None: + """Test environment_factory.""" globals_: dict[str, object] = {"foo": "bar", "test": "me"} env = blag.environment_factory("templates", globals_=globals_) assert env.globals["foo"] == "bar" @@ -191,6 +200,7 @@ def test_process_markdown( page_template: Template, article_template: Template, ) -> None: + """Test process_markdown.""" page1 = """\ title: some page @@ -240,6 +250,7 @@ foo bar def test_build(args: Namespace) -> None: + """Test build.""" page1 = """\ title: some page @@ -313,16 +324,19 @@ foo bar ], ) def test_missing_template_raises(template: str, args: Namespace) -> None: + """Test that missing templates raise SystemExit.""" os.remove(f"templates/{template}") with pytest.raises(SystemExit): blag.build(args) def test_main(cleandir: str) -> None: + """Test main.""" blag.main(["build"]) def test_cli_version(capsys: CaptureFixture[str]) -> None: + """Test --version.""" with pytest.raises(SystemExit) as ex: blag.main(["--version"]) # normal system exit @@ -333,6 +347,7 @@ def test_cli_version(capsys: CaptureFixture[str]) -> None: def test_cli_verbose(cleandir: str, caplog: LogCaptureFixture) -> None: + """Test --verbose.""" blag.main(["build"]) assert "DEBUG" not in caplog.text diff --git a/tests/test_devserver.py b/tests/test_devserver.py index 29477fb..3652f19 100644 --- a/tests/test_devserver.py +++ b/tests/test_devserver.py @@ -1,3 +1,6 @@ +"""Tests for the devserver module.""" + + # remove when we don't support py38 anymore from __future__ import annotations @@ -11,6 +14,7 @@ from blag import devserver def test_get_last_modified(cleandir: str) -> None: + """Test get_last_modified.""" # take initial time t1 = devserver.get_last_modified(["content"]) @@ -29,6 +33,7 @@ def test_get_last_modified(cleandir: str) -> None: def test_autoreload_builds_immediately(args: Namespace) -> None: + """Test autoreload builds immediately.""" # create a dummy file that can be build with open("content/test.md", "w") as fh: fh.write("boo") @@ -54,6 +59,7 @@ def test_autoreload_builds_immediately(args: Namespace) -> None: "ignore::pytest.PytestUnhandledThreadExceptionWarning" ) def test_autoreload(args: Namespace) -> None: + """Test autoreload.""" t = threading.Thread( target=devserver.autoreload, args=(args,), diff --git a/tests/test_markdown.py b/tests/test_markdown.py index 3035608..56cb742 100644 --- a/tests/test_markdown.py +++ b/tests/test_markdown.py @@ -1,3 +1,6 @@ +"""Test markdown module.""" + + # remove when we don't support py38 anymore from __future__ import annotations @@ -34,6 +37,7 @@ from blag.markdown import convert_markdown, markdown_factory ], ) def test_convert_markdown_links(input_: str, expected: str) -> None: + """Test convert_markdown.""" md = markdown_factory() html, _ = convert_markdown(md, input_) assert expected in html @@ -51,6 +55,7 @@ def test_convert_markdown_links(input_: str, expected: str) -> None: ], ) def test_dont_convert_normal_links(input_: str, expected: str) -> None: + """Test convert_markdown doesn't convert normal links.""" md = markdown_factory() html, _ = convert_markdown(md, input_) assert expected in html @@ -70,17 +75,20 @@ def test_dont_convert_normal_links(input_: str, expected: str) -> None: ], ) def test_convert_metadata(input_: str, expected: dict[str, Any]) -> None: + """Test convert_markdown converts metadata correctly.""" md = markdown_factory() _, meta = convert_markdown(md, input_) assert expected == meta def test_markdown_factory() -> None: + """Test markdown_factory.""" md = markdown_factory() assert isinstance(md, markdown.Markdown) def test_smarty() -> None: + """Test smarty.""" md = markdown_factory() md1 = """ @@ -95,6 +103,7 @@ this --- is -- a test ... def test_smarty_code() -> None: + """Test smarty doesn't touch code.""" md = markdown_factory() md1 = """ diff --git a/tests/test_quickstart.py b/tests/test_quickstart.py index c976a29..4467fc9 100644 --- a/tests/test_quickstart.py +++ b/tests/test_quickstart.py @@ -1,3 +1,6 @@ +"""Tests for the quickstart module.""" + + # remove when we don't support py38 anymore from __future__ import annotations @@ -9,21 +12,24 @@ from blag.quickstart import get_input, quickstart def test_get_input_default_answer(monkeypatch: MonkeyPatch) -> None: + """Test get_input with default answer.""" monkeypatch.setattr("builtins.input", lambda x: "") answer = get_input("foo", "bar") assert answer == "bar" def test_get_input(monkeypatch: MonkeyPatch) -> None: + """Test get_input.""" monkeypatch.setattr("builtins.input", lambda x: "baz") answer = get_input("foo", "bar") assert answer == "baz" def test_quickstart(cleandir: str, monkeypatch: MonkeyPatch) -> None: + """Test quickstart.""" monkeypatch.setattr("builtins.input", lambda x: "foo") quickstart(None) - with open("config.ini", "r") as fh: + with open("config.ini") as fh: data = fh.read() assert "base_url = foo" in data assert "title = foo" in data diff --git a/tests/test_templates.py b/tests/test_templates.py index 8fd0265..4d96f66 100644 --- a/tests/test_templates.py +++ b/tests/test_templates.py @@ -1,3 +1,6 @@ +"""Test the templates.""" + + # remove when we don't support py38 anymore from __future__ import annotations @@ -7,6 +10,7 @@ from jinja2 import Template def test_page(page_template: Template) -> None: + """Test the page template.""" ctx = { "content": "this is the content", "title": "this is the title", @@ -17,6 +21,7 @@ def test_page(page_template: Template) -> None: def test_article(article_template: Template) -> None: + """Test the article template.""" ctx = { "content": "this is the content", "title": "this is the title", @@ -29,6 +34,7 @@ def test_article(article_template: Template) -> None: def test_index(index_template: Template) -> None: + """Test the index template.""" entry = { "title": "this is a title", "dst": "https://example.com/link", @@ -49,6 +55,7 @@ def test_index(index_template: Template) -> None: def test_archive(archive_template: Template) -> None: + """Test the archive template.""" entry = { "title": "this is a title", "dst": "https://example.com/link", @@ -67,6 +74,7 @@ def test_archive(archive_template: Template) -> None: def test_tags(tags_template: Template) -> None: + """Test the tags template.""" tags = [("foo", 42)] ctx = { "tags": tags, @@ -80,6 +88,7 @@ def test_tags(tags_template: Template) -> None: def test_tag(tag_template: Template) -> None: + """Test the tag template.""" entry = { "title": "this is a title", "dst": "https://example.com/link", diff --git a/tests/test_version.py b/tests/test_version.py index b772f4b..fbe1d02 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -1,3 +1,6 @@ +"""Test the version module.""" + + # remove when we don't support py38 anymore from __future__ import annotations @@ -5,4 +8,5 @@ import blag def test_version() -> None: + """Test the version of the package.""" assert isinstance(blag.__VERSION__, str) From a7c221f34579c2a11aa36049b2c10123fed147d6 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Wed, 20 Sep 2023 15:10:21 +0200 Subject: [PATCH 100/123] updated changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ac4c73..ec8ce41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## [unreleased] -- XXXXXXX + +* switched from flake8 to ruff +* added missing docstrings + ## [2.1.0] -- 2023-08-27 * default theme: `img` have now `max-width: 100%` by default to avoid very From f2900fdb4ba6fcb4bd1f7a7fd6e751257e5ad972 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Wed, 20 Sep 2023 15:16:58 +0200 Subject: [PATCH 101/123] run ruff check explicitely --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 63d744e..13e2bfd 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ mypy: $(VENV) .PHONY: lint lint: $(VENV) - $(BIN)/ruff . + $(BIN)/ruff check . .PHONY: build build: $(VENV) From 65e5fd8728857c26c727ba4a8755b52566842be0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Sep 2023 14:59:33 +0000 Subject: [PATCH 102/123] Bump mkdocs from 1.5.2 to 1.5.3 Bumps [mkdocs](https://github.com/mkdocs/mkdocs) from 1.5.2 to 1.5.3. - [Release notes](https://github.com/mkdocs/mkdocs/releases) - [Commits](https://github.com/mkdocs/mkdocs/compare/1.5.2...1.5.3) --- updated-dependencies: - dependency-name: mkdocs dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 4afb1ff..8ef3585 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,5 @@ build==1.0.3 -mkdocs==1.5.2 +mkdocs==1.5.3 mkdocs-material==9.3.1 mkdocstrings[python]==0.23.0 twine==4.0.2 From a416095cd0f6b224852845dcdecc3115d56462e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Sep 2023 14:59:52 +0000 Subject: [PATCH 103/123] Bump ruff from 0.0.290 to 0.0.291 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.290 to 0.0.291. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.0.290...v0.0.291) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 4afb1ff..41e3bcc 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,6 +6,6 @@ twine==4.0.2 wheel==0.41.2 pytest==7.4.2 pytest-cov==4.1.0 -ruff==0.0.290 +ruff==0.0.291 mypy==1.5.1 types-markdown==3.4.2.10 From 81a64019ee05bc863e9f58795a56128600904f4b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Oct 2023 14:22:23 +0000 Subject: [PATCH 104/123] Bump mkdocs-material from 9.3.1 to 9.4.3 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.3.1 to 9.4.3. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.3.1...9.4.3) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 8501a82..385b58b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,6 @@ build==1.0.3 mkdocs==1.5.3 -mkdocs-material==9.3.1 +mkdocs-material==9.4.3 mkdocstrings[python]==0.23.0 twine==4.0.2 wheel==0.41.2 From a550d7b194c1acacfac5eef0101431920b976446 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Oct 2023 14:59:10 +0000 Subject: [PATCH 105/123] Bump mypy from 1.5.1 to 1.6.0 Bumps [mypy](https://github.com/python/mypy) from 1.5.1 to 1.6.0. - [Commits](https://github.com/python/mypy/compare/v1.5.1...v1.6.0) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 385b58b..560ea36 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,5 +7,5 @@ wheel==0.41.2 pytest==7.4.2 pytest-cov==4.1.0 ruff==0.0.291 -mypy==1.5.1 +mypy==1.6.0 types-markdown==3.4.2.10 From 7d7723219be74801f56d5b8960bbd3344f4472b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Oct 2023 14:59:18 +0000 Subject: [PATCH 106/123] Bump mkdocs-material from 9.4.3 to 9.4.6 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.4.3 to 9.4.6. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.4.3...9.4.6) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 385b58b..fd04562 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,6 @@ build==1.0.3 mkdocs==1.5.3 -mkdocs-material==9.4.3 +mkdocs-material==9.4.6 mkdocstrings[python]==0.23.0 twine==4.0.2 wheel==0.41.2 From d6e049bebb4911f8477ca2c516c34793f97aa563 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Oct 2023 10:00:09 +0000 Subject: [PATCH 107/123] Bump ruff from 0.0.291 to 0.1.0 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.291 to 0.1.0. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.0.291...v0.1.0) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 970cc98..d6a5c3d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,6 +6,6 @@ twine==4.0.2 wheel==0.41.2 pytest==7.4.2 pytest-cov==4.1.0 -ruff==0.0.291 +ruff==0.1.0 mypy==1.6.0 types-markdown==3.4.2.10 From 2a6c7d0b0ea5bb8f575cd08e4f5962514eadd2cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Oct 2023 14:48:40 +0000 Subject: [PATCH 108/123] Bump ruff from 0.1.0 to 0.1.1 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.1.0 to 0.1.1. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.1.0...v0.1.1) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index d6a5c3d..9af2f2f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,6 +6,6 @@ twine==4.0.2 wheel==0.41.2 pytest==7.4.2 pytest-cov==4.1.0 -ruff==0.1.0 +ruff==0.1.1 mypy==1.6.0 types-markdown==3.4.2.10 From d66b2955a57d2d655fb0a6cd6b25d7c9d9059ff9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Oct 2023 09:00:52 +0000 Subject: [PATCH 109/123] Bump types-markdown from 3.4.2.10 to 3.5.0.0 Bumps [types-markdown](https://github.com/python/typeshed) from 3.4.2.10 to 3.5.0.0. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-markdown dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 9af2f2f..d9adb59 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,4 +8,4 @@ pytest==7.4.2 pytest-cov==4.1.0 ruff==0.1.1 mypy==1.6.0 -types-markdown==3.4.2.10 +types-markdown==3.5.0.0 From 9734386397b6182f7c50b9fb83bec04b92fbe9e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Oct 2023 09:00:54 +0000 Subject: [PATCH 110/123] Bump markdown from 3.4.4 to 3.5 Bumps [markdown](https://github.com/Python-Markdown/markdown) from 3.4.4 to 3.5. - [Changelog](https://github.com/Python-Markdown/markdown/blob/master/docs/changelog.md) - [Commits](https://github.com/Python-Markdown/markdown/compare/3.4.4...3.5) --- updated-dependencies: - dependency-name: markdown dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5f253e1..5542399 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -markdown==3.4.4 +markdown==3.5 feedgenerator==2.1.0 jinja2==3.1.2 pygments==2.16.1 From 86356a5e1d03e00598d9c80e17a2eec1aa93f74c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Oct 2023 09:17:43 +0000 Subject: [PATCH 111/123] Bump mypy from 1.6.0 to 1.6.1 Bumps [mypy](https://github.com/python/mypy) from 1.6.0 to 1.6.1. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.6.0...v1.6.1) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index d9adb59..72fd236 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,5 +7,5 @@ wheel==0.41.2 pytest==7.4.2 pytest-cov==4.1.0 ruff==0.1.1 -mypy==1.6.0 +mypy==1.6.1 types-markdown==3.5.0.0 From 5314a7d260aa7162fbb6cc8fbf56ec48fdea175f Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Mon, 30 Oct 2023 08:48:52 +0100 Subject: [PATCH 112/123] fixed requirements in pyproject.toml still pointing to sphinx --- CHANGELOG.md | 1 + pyproject.toml | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec8ce41..df5df40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * switched from flake8 to ruff * added missing docstrings +* fixed dev requirements in pyproject, still pointing to sphinx ## [2.1.0] -- 2023-08-27 diff --git a/pyproject.toml b/pyproject.toml index 04421bb..59f0d1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,9 @@ blag = "blag.blag:main" [project.optional-dependencies] dev = [ "build", - "sphinx", + "mkdocs", + "mkdocs-material", + "mkdocstrings[python]", "twine", "wheel", "pytest", From dfcbaec6f9a938fac588f7a295fb486bc3b88679 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Mon, 30 Oct 2023 08:51:23 +0100 Subject: [PATCH 113/123] added python 3.12 to test suite --- .github/workflows/python-package.yaml | 1 + CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/python-package.yaml b/.github/workflows/python-package.yaml index 6484dbe..a938dac 100644 --- a/.github/workflows/python-package.yaml +++ b/.github/workflows/python-package.yaml @@ -21,6 +21,7 @@ jobs: - "3.9" - "3.10" - "3.11" + - "3.12" exclude: # 3.8 on windows fails due to some pip issue - os: windows-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index df5df40..fe30f5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * switched from flake8 to ruff * added missing docstrings * fixed dev requirements in pyproject, still pointing to sphinx +* added Python3.12 to test suite ## [2.1.0] -- 2023-08-27 From 082f1e04732c4b123faa71adec1a5c071e43021a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 14:07:01 +0000 Subject: [PATCH 114/123] Bump ruff from 0.1.1 to 0.1.3 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.1.1 to 0.1.3. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.1.1...v0.1.3) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 72fd236..5b2a2d8 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,6 +6,6 @@ twine==4.0.2 wheel==0.41.2 pytest==7.4.2 pytest-cov==4.1.0 -ruff==0.1.1 +ruff==0.1.3 mypy==1.6.1 types-markdown==3.5.0.0 From 6ec70ba61bfd0b30a1e1cf30975685fc06372ebb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 14:07:08 +0000 Subject: [PATCH 115/123] Bump pytest from 7.4.2 to 7.4.3 Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.4.2 to 7.4.3. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.4.2...7.4.3) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 72fd236..bf41999 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,7 +4,7 @@ mkdocs-material==9.4.6 mkdocstrings[python]==0.23.0 twine==4.0.2 wheel==0.41.2 -pytest==7.4.2 +pytest==7.4.3 pytest-cov==4.1.0 ruff==0.1.1 mypy==1.6.1 From 8f287c5b8db269aa67fe9ed70ea1d5fe030cd32a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 5 Nov 2023 16:03:50 +0000 Subject: [PATCH 116/123] Bump wheel from 0.41.2 to 0.41.3 Bumps [wheel](https://github.com/pypa/wheel) from 0.41.2 to 0.41.3. - [Release notes](https://github.com/pypa/wheel/releases) - [Changelog](https://github.com/pypa/wheel/blob/main/docs/news.rst) - [Commits](https://github.com/pypa/wheel/compare/0.41.2...0.41.3) --- updated-dependencies: - dependency-name: wheel dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 057ff6d..cfd55d0 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,7 +3,7 @@ mkdocs==1.5.3 mkdocs-material==9.4.6 mkdocstrings[python]==0.23.0 twine==4.0.2 -wheel==0.41.2 +wheel==0.41.3 pytest==7.4.3 pytest-cov==4.1.0 ruff==0.1.3 From 9378bb5103f143729fd031e5f6ee1feb2d296fc9 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Sun, 5 Nov 2023 17:15:15 +0100 Subject: [PATCH 117/123] removed watchfile, bumped version --- CHANGELOG.md | 3 ++- blag/version.py | 2 +- debian/changelog | 10 ++++++++++ debian/watch | 9 --------- 4 files changed, 13 insertions(+), 11 deletions(-) delete mode 100644 debian/watch diff --git a/CHANGELOG.md b/CHANGELOG.md index fe30f5b..18ae7de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,12 @@ # Changelog -## [unreleased] -- XXXXXXX +## [2.2.0] -- 2023-11-05 * switched from flake8 to ruff * added missing docstrings * fixed dev requirements in pyproject, still pointing to sphinx * added Python3.12 to test suite +* removed debian/watch ## [2.1.0] -- 2023-08-27 diff --git a/blag/version.py b/blag/version.py index 7311e69..08f9cb0 100644 --- a/blag/version.py +++ b/blag/version.py @@ -1,3 +1,3 @@ """Version information for the blag package.""" -__VERSION__ = "2.1.0" +__VERSION__ = "2.2.0" diff --git a/debian/changelog b/debian/changelog index ad6fb96..eceaf07 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,13 @@ +blag (2.2.0) unstable; urgency=medium + + * switched from flake8 to ruff + * added missing docstrings + * fixed dev requirements in pyproject, still pointing to sphinx + * added Python3.12 to test suite + * removed watch file again + + -- Bastian Venthur Sun, 05 Nov 2023 17:08:09 +0100 + blag (2.1.0) unstable; urgency=medium * default theme: `img` have now `max-width: 100%` by default to avoid very diff --git a/debian/watch b/debian/watch deleted file mode 100644 index a906a51..0000000 --- a/debian/watch +++ /dev/null @@ -1,9 +0,0 @@ -# You can run the "uscan" command to check for upstream updates and more. -# See uscan(1) for format - -# Compulsory line, this is a version 4 file -version=4 - -# Direct Git -opts="mode=git" https://github.com/venthur/blag.git \ - refs/tags/v([\d\.]+) debian uupdate From 9b0bd2814a6249b4f64cb3e5893638fa49f1524a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 14:42:36 +0000 Subject: [PATCH 118/123] Bump markdown from 3.5 to 3.5.1 Bumps [markdown](https://github.com/Python-Markdown/markdown) from 3.5 to 3.5.1. - [Release notes](https://github.com/Python-Markdown/markdown/releases) - [Changelog](https://github.com/Python-Markdown/markdown/blob/master/docs/changelog.md) - [Commits](https://github.com/Python-Markdown/markdown/compare/3.5...3.5.1) --- updated-dependencies: - dependency-name: markdown dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5542399..62bba80 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -markdown==3.5 +markdown==3.5.1 feedgenerator==2.1.0 jinja2==3.1.2 pygments==2.16.1 From 056ddbdbcb7e49f365b2a06f5c733382d0084781 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 14:42:44 +0000 Subject: [PATCH 119/123] Bump types-markdown from 3.5.0.0 to 3.5.0.1 Bumps [types-markdown](https://github.com/python/typeshed) from 3.5.0.0 to 3.5.0.1. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-markdown dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 057ff6d..50f2222 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,4 +8,4 @@ pytest==7.4.3 pytest-cov==4.1.0 ruff==0.1.3 mypy==1.6.1 -types-markdown==3.5.0.0 +types-markdown==3.5.0.1 From caf6217221aeb3df423b86fb86b2bdd15903431b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 11 Nov 2023 09:55:02 +0000 Subject: [PATCH 120/123] Bump mkdocs-material from 9.4.6 to 9.4.8 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.4.6 to 9.4.8. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.4.6...9.4.8) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index efee210..87670e2 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,6 @@ build==1.0.3 mkdocs==1.5.3 -mkdocs-material==9.4.6 +mkdocs-material==9.4.8 mkdocstrings[python]==0.23.0 twine==4.0.2 wheel==0.41.3 From 12a7fb95686c60b2dae8e5a8bf4ad24089ac8db6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 11 Nov 2023 09:55:13 +0000 Subject: [PATCH 121/123] Bump ruff from 0.1.3 to 0.1.5 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.1.3 to 0.1.5. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.1.3...v0.1.5) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index efee210..dafab8e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,6 +6,6 @@ twine==4.0.2 wheel==0.41.3 pytest==7.4.3 pytest-cov==4.1.0 -ruff==0.1.3 +ruff==0.1.5 mypy==1.6.1 types-markdown==3.5.0.1 From 8c6161429598e3a043dbab9c2b3a247a9dba658b Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Sat, 11 Nov 2023 10:59:07 +0100 Subject: [PATCH 122/123] fixed suggests to blag-doc, bumped version --- CHANGELOG.md | 4 ++++ blag/version.py | 2 +- debian/changelog | 6 ++++++ debian/control | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18ae7de..f4a94ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [2.2.1] -- 2023-11-11 + +* fixed `suggests` to blag-doc + ## [2.2.0] -- 2023-11-05 * switched from flake8 to ruff diff --git a/blag/version.py b/blag/version.py index 08f9cb0..30d8088 100644 --- a/blag/version.py +++ b/blag/version.py @@ -1,3 +1,3 @@ """Version information for the blag package.""" -__VERSION__ = "2.2.0" +__VERSION__ = "2.2.1" diff --git a/debian/changelog b/debian/changelog index eceaf07..aff404e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +blag (2.2.1) UNRELEASED; urgency=medium + + * fixed suggests field to blag-doc (Closes: #1055769) + + -- Bastian Venthur Sat, 11 Nov 2023 10:57:06 +0100 + blag (2.2.0) unstable; urgency=medium * switched from flake8 to ruff diff --git a/debian/control b/debian/control index 44ae262..2011e23 100644 --- a/debian/control +++ b/debian/control @@ -31,7 +31,7 @@ Depends: ${python3:Depends}, ${misc:Depends}, Suggests: - python-blag-doc, + blag-doc, Description: Blog-aware, static site generator Blag is a blog-aware, static site generator, written in Python. It supports the following features: From 55e2f41b88b1ec4a6dd96e8299f57b878652de6b Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Sat, 11 Nov 2023 11:01:28 +0100 Subject: [PATCH 123/123] correct release --- debian/changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index aff404e..3ec7e20 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -blag (2.2.1) UNRELEASED; urgency=medium +blag (2.2.1) unstable; urgency=medium * fixed suggests field to blag-doc (Closes: #1055769)