1
0
mirror of https://github.com/venthur/blag.git synced 2025-11-26 05:02:58 +00:00

Compare commits

..

6 Commits

Author SHA1 Message Date
Bastian Venthur
618327adf5 reformatted 2023-11-12 17:24:48 +01:00
Bastian Venthur
f9f27953c6 Merge branch 'master' into delete_extra_files 2023-11-12 17:23:48 +01:00
Bastian Venthur
9168720ede Merge branch 'master' into delete_extra_files 2022-08-07 21:12:35 +02:00
Bastian Venthur
94ad898a86 added logger messanges on deletion 2022-08-07 21:10:51 +02:00
Bastian Venthur
ff3f8101ad added test 2022-08-06 22:29:48 +02:00
Bastian Venthur
8c0e69b2f4 Delete files that should not be in dest-dir 2022-08-06 21:55:09 +02:00
32 changed files with 185 additions and 275 deletions

View File

@@ -4,10 +4,6 @@ updates:
directory: "/" directory: "/"
schedule: schedule:
interval: "weekly" interval: "weekly"
groups:
all:
patterns:
- "*"
- package-ecosystem: "github-actions" - package-ecosystem: "github-actions"
directory: "/" directory: "/"

View File

@@ -17,14 +17,19 @@ jobs:
- macos-latest - macos-latest
- windows-latest - windows-latest
python-version: python-version:
- "3.8"
- "3.9"
- "3.10" - "3.10"
- "3.11" - "3.11"
- "3.12" - "3.12"
- "3.13" exclude:
# 3.8 on windows fails due to some pip issue
- os: windows-latest
python-version: "3.8"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-python@v5 - uses: actions/setup-python@v4
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
@@ -38,7 +43,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-python@v5 - uses: actions/setup-python@v4
with: with:
python-version: "3.x" python-version: "3.x"
@@ -52,7 +57,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-python@v5 - uses: actions/setup-python@v4
with: with:
python-version: "3.x" python-version: "3.x"
@@ -67,7 +72,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-python@v5 - uses: actions/setup-python@v4
with: with:
python-version: "3.x" python-version: "3.x"

View File

@@ -1,34 +1,5 @@
# Changelog # Changelog
## [unreleased]
* Added Python 3.13 to github actions
## [2.3.2] -- 2024-10-13
* Ignore FileNotFoundError when trying to get the last modified time of files
in directories. This happens for example with temporary emacs files.
* Added changelog to docs
* removed ruff's target-version from pyproject.toml, this value defaults to the
projects requires-python
## [2.3.1] -- 2024-07-06
* added manpage
* added makefile target for generating blog's manpage
* added makefile target for serving blags docs locally
* mkdocs: disabled loading of google fonts, using locally installed system
fonts instead
* Debian: simplified html docs directory for blag-doc package
* Debian: changed section from Python to Web
* updated dependencies
## [2.3.0] -- 2024-04-24
* fixed devsever so it does not crash anymore when the (re-)build fails
* dropped support for Python 3.8 and 3.9
* updated dependencies
## [2.2.1] -- 2023-11-11 ## [2.2.1] -- 2023-11-11
* fixed `suggests` to blag-doc * fixed `suggests` to blag-doc

View File

@@ -57,18 +57,6 @@ update-pygmentize: $(VENV)
docs: $(VENV) docs: $(VENV)
$(BIN)/mkdocs build $(BIN)/mkdocs build
.PHONY: serve-docs
serve-docs: $(VENV)
$(BIN)/mkdocs serve
.PHONY: manpage
manpage: $(VENV)
help2man $(BIN)/blag --no-info -n "blog-aware, static site generator" -o debian/blag.1
.PHONY: benchmark
benchmark: $(VENV)
$(BIN)/pytest --no-cov -capture=no -rA tests/benchmark.py
.PHONY: clean .PHONY: clean
clean: clean:
rm -rf build dist *.egg-info rm -rf build dist *.egg-info

View File

@@ -24,7 +24,7 @@ blag is named after [the blag of the webcomic xkcd][blagxkcd].
* Integrated devserver * Integrated devserver
* Available on [PyPI][] * Available on [PyPI][]
blag runs on Linux, Mac and Windows and requires Python >= 3.10 blag runs on Linux, Mac and Windows and requires Python >= 3.8
[markdown]: https://daringfireball.net/projects/markdown/ [markdown]: https://daringfireball.net/projects/markdown/
[jinja2]: https://palletsprojects.com/p/jinja/ [jinja2]: https://palletsprojects.com/p/jinja/

View File

@@ -2,6 +2,9 @@
"""blag's core methods.""" """blag's core methods."""
# remove when we don't support py38 anymore
from __future__ import annotations
import argparse import argparse
import configparser import configparser
import logging import logging
@@ -215,6 +218,7 @@ def build(args: argparse.Namespace) -> None:
""" """
os.makedirs(f"{args.output_dir}", exist_ok=True) os.makedirs(f"{args.output_dir}", exist_ok=True)
convertibles = [] convertibles = []
known_targets = []
for root, dirnames, filenames in os.walk(args.input_dir): for root, dirnames, filenames in os.walk(args.input_dir):
for filename in filenames: for filename in filenames:
rel_src = os.path.relpath( rel_src = os.path.relpath(
@@ -226,15 +230,22 @@ def build(args: argparse.Namespace) -> None:
rel_dst = rel_src rel_dst = rel_src
rel_dst = rel_dst[:-3] + ".html" rel_dst = rel_dst[:-3] + ".html"
convertibles.append((rel_src, rel_dst)) convertibles.append((rel_src, rel_dst))
known_targets.append(
os.path.abspath(f"{args.output_dir}/{rel_dst}")
)
else: else:
shutil.copy( shutil.copy(
f"{args.input_dir}/{rel_src}", f"{args.input_dir}/{rel_src}",
f"{args.output_dir}/{rel_src}", f"{args.output_dir}/{rel_src}",
) )
known_targets.append(
os.path.abspath(f"{args.output_dir}/{rel_src}")
)
for dirname in dirnames: for dirname in dirnames:
# all directories are copied into the output directory # all directories are copied into the output directory
path = os.path.relpath(f"{root}/{dirname}", start=args.input_dir) path = os.path.relpath(f"{root}/{dirname}", start=args.input_dir)
os.makedirs(f"{args.output_dir}/{path}", exist_ok=True) os.makedirs(f"{args.output_dir}/{path}", exist_ok=True)
known_targets.append(os.path.abspath(f"{args.output_dir}/{path}"))
# copy static files over # copy static files over
logger.info("Copying static files.") logger.info("Copying static files.")
@@ -270,6 +281,24 @@ def build(args: argparse.Namespace) -> None:
article_template, article_template,
) )
# clean up files that should not be there
for root, dirnames, filenames in os.walk(args.output_dir):
for filename in filenames:
dst = os.path.abspath(f"{root}/{filename}")
if dst not in known_targets:
logger.info(f"deleting {dst}")
os.remove(dst)
else:
known_targets.remove(dst)
for dirname in dirnames:
dst = os.path.abspath(f"{root}/{dirname}")
if dst not in known_targets:
logger.info(f"deleting {dst}")
shutil.rmtree(dst)
else:
known_targets.remove(dst)
logger.debug(known_targets)
generate_feed( generate_feed(
articles, articles,
args.output_dir, args.output_dir,
@@ -323,18 +352,6 @@ def process_markdown(
for src, dst in convertibles: for src, dst in convertibles:
logger.debug(f"Processing {src}") logger.debug(f"Processing {src}")
# see first if the dst actually needs re-building. for that we compare
# the mtimes and assume mtime_dst > mtime_src means that it needs not
# to be rebuilt
if os.path.exists(f"{output_dir}/{dst}"):
mtime_src = os.stat(f"{input_dir}/{src}").st_mtime
mtime_dst = os.stat(f"{output_dir}/{dst}").st_mtime
if mtime_dst >= mtime_src:
logger.debug(
"Skipping, as target exists and is newer than source."
)
continue
with open(f"{input_dir}/{src}") as fh: with open(f"{input_dir}/{src}") as fh:
body = fh.read() body = fh.read()

View File

@@ -6,6 +6,9 @@ site if necessary.
""" """
# remove when we don't support py38 anymore
from __future__ import annotations
import argparse import argparse
import logging import logging
import multiprocessing import multiprocessing
@@ -42,19 +45,14 @@ def get_last_modified(dirs: list[str]) -> float:
for dir in dirs: for dir in dirs:
for root, dirs, files in os.walk(dir): for root, dirs, files in os.walk(dir):
for f in files: for f in files:
try: mtime = os.stat(os.path.join(root, f)).st_mtime
mtime = os.stat(os.path.join(root, f)).st_mtime
except FileNotFoundError:
# ignore files that have been deleted since the os.walk
# call (for example temporary emacs files)
continue
if mtime > last_mtime: if mtime > last_mtime:
last_mtime = mtime last_mtime = mtime
return last_mtime return last_mtime
def autoreload(args: argparse.Namespace, wait: int=1) -> NoReturn: def autoreload(args: argparse.Namespace) -> NoReturn:
"""Start the autoreloader. """Start the autoreloader.
This method monitors the given directories for changes (i.e. the This method monitors the given directories for changes (i.e. the
@@ -68,9 +66,6 @@ def autoreload(args: argparse.Namespace, wait: int=1) -> NoReturn:
---------- ----------
args args
contains the input-, template- and static dir contains the input-, template- and static dir
wait
number of seconds the devsever waits before checking for updated
content
""" """
dirs = [args.input_dir, args.template_dir, args.static_dir] dirs = [args.input_dir, args.template_dir, args.static_dir]
@@ -79,18 +74,12 @@ def autoreload(args: argparse.Namespace, wait: int=1) -> NoReturn:
# loop to avoid serving stale contents # loop to avoid serving stale contents
last_mtime = 0.0 last_mtime = 0.0
while True: while True:
# make sure the devsever does not crash when the build fails with an mtime = get_last_modified(dirs)
# exception if mtime > last_mtime:
try: last_mtime = mtime
mtime = get_last_modified(dirs) logger.info("Change detected, rebuilding...")
if mtime > last_mtime: blag.build(args)
last_mtime = mtime time.sleep(1)
logger.info("Change detected, rebuilding...")
blag.build(args)
time.sleep(wait)
except Exception:
logger.exception("Error occurred during rebuild:")
logger.info("Devserver did not crash, you may continue editing.")
def serve(args: argparse.Namespace) -> None: def serve(args: argparse.Namespace) -> None:

View File

@@ -5,6 +5,9 @@ processing.
""" """
# remove when we don't support py38 anymore
from __future__ import annotations
import logging import logging
from datetime import datetime from datetime import datetime
from urllib.parse import urlsplit, urlunsplit from urllib.parse import urlsplit, urlunsplit

View File

@@ -1,5 +1,8 @@
"""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
import argparse import argparse
import configparser import configparser
import os import os

View File

@@ -4,36 +4,35 @@ span.linenos { color: inherit; background-color: transparent; padding-left: 5px;
td.linenos .special { color: #000000; background-color: #ffffc0; 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; } span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.hll { background-color: #49483e } .hll { background-color: #49483e }
.c { color: #959077 } /* Comment */ .c { color: #75715e } /* Comment */
.err { color: #ed007e; background-color: #1e0010 } /* Error */ .err { color: #960050; background-color: #1e0010 } /* Error */
.esc { color: #f8f8f2 } /* Escape */ .esc { color: #f8f8f2 } /* Escape */
.g { color: #f8f8f2 } /* Generic */ .g { color: #f8f8f2 } /* Generic */
.k { color: #66d9ef } /* Keyword */ .k { color: #66d9ef } /* Keyword */
.l { color: #ae81ff } /* Literal */ .l { color: #ae81ff } /* Literal */
.n { color: #f8f8f2 } /* Name */ .n { color: #f8f8f2 } /* Name */
.o { color: #ff4689 } /* Operator */ .o { color: #f92672 } /* Operator */
.x { color: #f8f8f2 } /* Other */ .x { color: #f8f8f2 } /* Other */
.p { color: #f8f8f2 } /* Punctuation */ .p { color: #f8f8f2 } /* Punctuation */
.ch { color: #959077 } /* Comment.Hashbang */ .ch { color: #75715e } /* Comment.Hashbang */
.cm { color: #959077 } /* Comment.Multiline */ .cm { color: #75715e } /* Comment.Multiline */
.cp { color: #959077 } /* Comment.Preproc */ .cp { color: #75715e } /* Comment.Preproc */
.cpf { color: #959077 } /* Comment.PreprocFile */ .cpf { color: #75715e } /* Comment.PreprocFile */
.c1 { color: #959077 } /* Comment.Single */ .c1 { color: #75715e } /* Comment.Single */
.cs { color: #959077 } /* Comment.Special */ .cs { color: #75715e } /* Comment.Special */
.gd { color: #ff4689 } /* Generic.Deleted */ .gd { color: #f92672 } /* Generic.Deleted */
.ge { color: #f8f8f2; font-style: italic } /* Generic.Emph */ .ge { color: #f8f8f2; font-style: italic } /* Generic.Emph */
.ges { color: #f8f8f2; font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.gr { color: #f8f8f2 } /* Generic.Error */ .gr { color: #f8f8f2 } /* Generic.Error */
.gh { color: #f8f8f2 } /* Generic.Heading */ .gh { color: #f8f8f2 } /* Generic.Heading */
.gi { color: #a6e22e } /* Generic.Inserted */ .gi { color: #a6e22e } /* Generic.Inserted */
.go { color: #66d9ef } /* Generic.Output */ .go { color: #66d9ef } /* Generic.Output */
.gp { color: #ff4689; font-weight: bold } /* Generic.Prompt */ .gp { color: #f92672; font-weight: bold } /* Generic.Prompt */
.gs { color: #f8f8f2; font-weight: bold } /* Generic.Strong */ .gs { color: #f8f8f2; font-weight: bold } /* Generic.Strong */
.gu { color: #959077 } /* Generic.Subheading */ .gu { color: #75715e } /* Generic.Subheading */
.gt { color: #f8f8f2 } /* Generic.Traceback */ .gt { color: #f8f8f2 } /* Generic.Traceback */
.kc { color: #66d9ef } /* Keyword.Constant */ .kc { color: #66d9ef } /* Keyword.Constant */
.kd { color: #66d9ef } /* Keyword.Declaration */ .kd { color: #66d9ef } /* Keyword.Declaration */
.kn { color: #ff4689 } /* Keyword.Namespace */ .kn { color: #f92672 } /* Keyword.Namespace */
.kp { color: #66d9ef } /* Keyword.Pseudo */ .kp { color: #66d9ef } /* Keyword.Pseudo */
.kr { color: #66d9ef } /* Keyword.Reserved */ .kr { color: #66d9ef } /* Keyword.Reserved */
.kt { color: #66d9ef } /* Keyword.Type */ .kt { color: #66d9ef } /* Keyword.Type */
@@ -52,9 +51,9 @@ span.linenos.special { color: #000000; background-color: #ffffc0; padding-left:
.nn { color: #f8f8f2 } /* Name.Namespace */ .nn { color: #f8f8f2 } /* Name.Namespace */
.nx { color: #a6e22e } /* Name.Other */ .nx { color: #a6e22e } /* Name.Other */
.py { color: #f8f8f2 } /* Name.Property */ .py { color: #f8f8f2 } /* Name.Property */
.nt { color: #ff4689 } /* Name.Tag */ .nt { color: #f92672 } /* Name.Tag */
.nv { color: #f8f8f2 } /* Name.Variable */ .nv { color: #f8f8f2 } /* Name.Variable */
.ow { color: #ff4689 } /* Operator.Word */ .ow { color: #f92672 } /* Operator.Word */
.pm { color: #f8f8f2 } /* Punctuation.Marker */ .pm { color: #f8f8f2 } /* Punctuation.Marker */
.w { color: #f8f8f2 } /* Text.Whitespace */ .w { color: #f8f8f2 } /* Text.Whitespace */
.mb { color: #ae81ff } /* Literal.Number.Bin */ .mb { color: #ae81ff } /* Literal.Number.Bin */

View File

@@ -16,7 +16,6 @@ span.linenos.special { color: #000000; background-color: #ffffc0; padding-left:
.cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */
.gd { color: #A00000 } /* Generic.Deleted */ .gd { color: #A00000 } /* Generic.Deleted */
.ge { font-style: italic } /* Generic.Emph */ .ge { font-style: italic } /* Generic.Emph */
.ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.gr { color: #E40000 } /* Generic.Error */ .gr { color: #E40000 } /* Generic.Error */
.gh { color: #000080; font-weight: bold } /* Generic.Heading */ .gh { color: #000080; font-weight: bold } /* Generic.Heading */
.gi { color: #008400 } /* Generic.Inserted */ .gi { color: #008400 } /* Generic.Inserted */

View File

@@ -1,16 +1,33 @@
@import "code-light.css" (prefers-color-scheme: light); @import "code-light.css" (prefers-color-scheme: light);
@import "code-dark.css" (prefers-color-scheme: dark); @import "code-dark.css" (prefers-color-scheme: dark);
:root { @media (prefers-color-scheme: light) {
color-scheme: light dark; :root {
--background: light-dark(#FFFFFF, #2B363B); --background: #FFFFFF;
--background-dim: light-dark(#f5f7f9, #2F3C42); --background-dim: #f5f7f9;
--foreground: light-dark(#2B303A, #f0f2f3);
--foreground-dim: light-dark(#576379, #d5d5d5); --foreground: #2B303A;
--foreground-heavy: light-dark(#191C22, #f2f4f5); --foreground-dim: #576379;
--primary-color: light-dark(#375287, #A1C5FF); --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 { html {
font-size: 18px; font-size: 18px;
font-family: serif; font-family: serif;

View File

@@ -1,3 +1,3 @@
"""Version information for the blag package.""" """Version information for the blag package."""
__VERSION__ = "2.3.2" __VERSION__ = "2.2.1"

View File

@@ -1 +1 @@
site/* site/

28
debian/blag.1 vendored
View File

@@ -1,28 +0,0 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH BLAG "1" "July 2024" "blag 2.3.0" "User Commands"
.SH NAME
blag \- blog-aware, static site generator
.SH DESCRIPTION
usage: blag [\-h] [\-\-version] [\-v] {build,quickstart,serve} ...
.SS "positional arguments:"
.IP
{build,quickstart,serve}
.TP
build
Build website.
.TP
quickstart
Quickstart blag, creating necessary configuration.
.TP
serve
Start development server.
.SS "options:"
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-\-version\fR
show program's version number and exit
.TP
\fB\-v\fR, \fB\-\-verbose\fR
Verbose output.

31
debian/changelog vendored
View File

@@ -1,34 +1,3 @@
blag (2.3.2) unstable; urgency=medium
* Ignore FileNotFoundError when trying to get the last modified time of
files in directories. This happens for example with temporary emacs files.
* Added changelog to docs
* removed ruff's target-version from pyproject.toml, this value defaults to
the projects requires-python
-- Bastian Venthur <venthur@debian.org> Sun, 13 Oct 2024 20:12:29 +0200
blag (2.3.1) unstable; urgency=medium
* added manpage
* added makefile target for generating blog's manpage
* added makefile target for serving blags docs locally
* mkdocs: disabled loading of google fonts, using locally installed system
fonts instead
* Debian: simplified html docs directory for blag-doc package
* Debian: changed section in debian/control from Python to Web
* updated dependencies
-- Bastian Venthur <venthur@debian.org> Sat, 06 Jul 2024 15:33:36 +0200
blag (2.3.0) unstable; urgency=medium
* fixed devsever so it does not crash anymore when the (re-)build fails
* dropped support for Python 3.8 and 3.9
* updated dependencies
-- Bastian Venthur <venthur@debian.org> Wed, 24 Apr 2024 22:25:31 +0200
blag (2.2.1) unstable; urgency=medium blag (2.2.1) unstable; urgency=medium
* fixed suggests field to blag-doc (Closes: #1055769) * fixed suggests field to blag-doc (Closes: #1055769)

16
debian/control vendored
View File

@@ -1,24 +1,24 @@
Source: blag Source: blag
Section: web Section: python
Priority: optional Priority: optional
Maintainer: Bastian Venthur <venthur@debian.org> Maintainer: Bastian Venthur <venthur@debian.org>
Rules-Requires-Root: no Rules-Requires-Root: no
Build-Depends: Build-Depends:
debhelper-compat (= 13), debhelper-compat (= 13),
dh-python,
dh-sequence-python3, dh-sequence-python3,
mkdocs, dh-python,
mkdocs-material,
mkdocstrings-python-handlers,
pybuild-plugin-pyproject, pybuild-plugin-pyproject,
python3-setuptools,
python3-all, python3-all,
python3-markdown,
python3-feedgenerator, python3-feedgenerator,
python3-jinja2, python3-jinja2,
python3-markdown,
python3-pygments, python3-pygments,
python3-pytest, python3-pytest,
python3-pytest-cov, python3-pytest-cov,
python3-setuptools, mkdocs,
mkdocs-material,
mkdocstrings-python-handlers,
#Testsuite: autopkgtest-pkg-python #Testsuite: autopkgtest-pkg-python
Standards-Version: 4.6.0.1 Standards-Version: 4.6.0.1
Homepage: https://github.com/venthur/blag Homepage: https://github.com/venthur/blag
@@ -28,8 +28,8 @@ Vcs-Git: https://github.com/venthur/blag.git
Package: blag Package: blag
Architecture: all Architecture: all
Depends: Depends:
${misc:Depends},
${python3:Depends}, ${python3:Depends},
${misc:Depends},
Suggests: Suggests:
blag-doc, blag-doc,
Description: Blog-aware, static site generator Description: Blog-aware, static site generator

1
debian/manpages vendored
View File

@@ -1 +0,0 @@
debian/blag.1

View File

@@ -1 +0,0 @@
../CHANGELOG.md

View File

@@ -16,7 +16,7 @@ blag is named after [the blag of the webcomic xkcd][blagxkcd].
## Features ## Features
* Write content in [Markdown][] * Write content in [Markdown][]
* Good looking default theme: * Good looking default theme
![Blag Screenshot](blag.png) ![Blag Screenshot](blag.png)
* Theming support using [Jinja2][] templates * Theming support using [Jinja2][] templates
* Generation of Atom feeds for blog content * Generation of Atom feeds for blog content
@@ -24,7 +24,7 @@ blag is named after [the blag of the webcomic xkcd][blagxkcd].
* Integrated devserver * Integrated devserver
* Available on [PyPI][] * Available on [PyPI][]
blag runs on Linux, Mac and Windows and requires Python >= 3.10 blag runs on Linux, Mac and Windows and requires Python >= 3.8
[markdown]: https://daringfireball.net/projects/markdown/ [markdown]: https://daringfireball.net/projects/markdown/
[jinja2]: https://palletsprojects.com/p/jinja/ [jinja2]: https://palletsprojects.com/p/jinja/

View File

@@ -13,13 +13,10 @@ nav:
- blag.markdown: markdown.md - blag.markdown: markdown.md
- blag.devserver: devserver.md - blag.devserver: devserver.md
- blag.quickstart: quickstart.md - blag.quickstart: quickstart.md
- Changelog: CHANGELOG.md
theme: theme:
name: material name: material
highlightjs: true highlightjs: true
# disable google fonts, use system fonts
font: false
markdown_extensions: markdown_extensions:
- pymdownx.superfences - pymdownx.superfences

View File

@@ -11,7 +11,7 @@ description = "blog-aware, static site generator"
keywords = ["markdown", "blag", "blog", "static site generator", "cli"] keywords = ["markdown", "blag", "blog", "static site generator", "cli"]
readme = "README.md" readme = "README.md"
license = { file="LICENSE" } license = { file="LICENSE" }
requires-python = ">=3.10" requires-python = ">=3.8"
dynamic = ["version"] dynamic = ["version"]
dependencies = [ dependencies = [
"markdown", "markdown",
@@ -64,9 +64,6 @@ addopts = """
""" """
[tool.ruff] [tool.ruff]
line-length = 79
[tool.ruff.lint]
select = [ select = [
"F", # pyflakes "F", # pyflakes
"E", "W", # pycodestyle "E", "W", # pycodestyle
@@ -75,7 +72,11 @@ select = [
"D", # pydocstyle "D", # pydocstyle
"UP" # pyupgrade "UP" # pyupgrade
] ]
pydocstyle.convention = "numpy" line-length = 79
target-version = "py38"
[tool.ruff.pydocstyle]
convention = "numpy"
[tool.mypy] [tool.mypy]
files = "blag,tests" files = "blag,tests"

View File

@@ -1,11 +1,11 @@
build==1.2.2.post1 build==1.0.3
mkdocs==1.6.1 mkdocs==1.5.3
mkdocs-material==9.5.49 mkdocs-material==9.4.8
mkdocstrings[python]==0.27.0 mkdocstrings[python]==0.23.0
twine==6.0.1 twine==4.0.2
wheel==0.45.1 wheel==0.41.3
pytest==8.3.4 pytest==7.4.3
pytest-cov==6.0.0 pytest-cov==4.1.0
ruff==0.8.4 ruff==0.1.5
mypy==1.14.0 mypy==1.6.1
types-markdown==3.7.0.20241204 types-markdown==3.5.0.1

View File

@@ -1,4 +1,4 @@
markdown==3.7 markdown==3.5.1
feedgenerator==2.1.0 feedgenerator==2.1.0
jinja2==3.1.5 jinja2==3.1.2
pygments==2.18.0 pygments==2.16.1

View File

@@ -1,37 +0,0 @@
"""Benchmark the performance of the blag build command."""
import logging
import os
from argparse import Namespace
from pytest import LogCaptureFixture
import blag
from blag.blag import build
def test_performance(args: Namespace, caplog: LogCaptureFixture) -> None:
"""Test performance of the build command."""
caplog.set_level(logging.ERROR)
FILES = 10000
print(f"Generating {FILES} markdown files")
# create random markdown files in the content directory
with open(os.path.join(blag.__path__[0], "content", "testpage.md")) as fh:
markdown = fh.read()
for i in range(FILES):
with open(f"content/{i}.md", "w") as f:
f.write(markdown)
f.write(str(i))
from time import time
t = time()
build(args)
t_first = time() - t
t = time()
build(args)
t_second = time() - t
print(f"First run: {t_first:.2f}s, second run: {t_second:.2f}s")
print(f"Speedup: {t_first/t_second:.2f}")

View File

@@ -1,9 +1,13 @@
"""Pytest fixtures.""" """Pytest fixtures."""
# remove when we don't support py38 anymore
from __future__ import annotations
import os import os
from argparse import Namespace from argparse import Namespace
from collections.abc import Callable, Iterator
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
from typing import Callable, Iterator
import pytest import pytest
from jinja2 import Environment, Template from jinja2 import Environment, Template

View File

@@ -1,5 +1,9 @@
"""Test blag.""" """Test blag."""
# remove when we don't support py38 anymore
from __future__ import annotations
import os import os
from argparse import Namespace from argparse import Namespace
from datetime import datetime from datetime import datetime
@@ -308,6 +312,22 @@ foo bar
assert os.path.exists(f"{args.output_dir}/tags/bar.html") assert os.path.exists(f"{args.output_dir}/tags/bar.html")
def test_remove_extra_files(args):
"""Test that extra files are removed."""
# create a file and directory in output dir that have no corresponding
# source
file_path = f'{args.output_dir}/a'
dir_path = f'{args.output_dir}/b'
fh = open(file_path, 'w')
fh.close()
os.mkdir(dir_path)
blag.build(args)
assert not os.path.exists(file_path)
assert not os.path.exists(dir_path)
@pytest.mark.parametrize( @pytest.mark.parametrize(
"template", "template",
[ [

View File

@@ -1,12 +1,16 @@
"""Tests for the devserver module.""" """Tests for the devserver module."""
# remove when we don't support py38 anymore
from __future__ import annotations
import threading import threading
import time import time
from argparse import Namespace from argparse import Namespace
from blag import devserver import pytest
WAITTIME = 0.1 from blag import devserver
def test_get_last_modified(cleandir: str) -> None: def test_get_last_modified(cleandir: str) -> None:
@@ -15,13 +19,13 @@ def test_get_last_modified(cleandir: str) -> None:
t1 = devserver.get_last_modified(["content"]) t1 = devserver.get_last_modified(["content"])
# wait a bit, create a file and measure again # wait a bit, create a file and measure again
time.sleep(WAITTIME) time.sleep(0.1)
with open("content/test", "w") as fh: with open("content/test", "w") as fh:
fh.write("boo") fh.write("boo")
t2 = devserver.get_last_modified(["content"]) t2 = devserver.get_last_modified(["content"])
# wait a bit and take time again # wait a bit and take time again
time.sleep(WAITTIME) time.sleep(0.1)
t3 = devserver.get_last_modified(["content"]) t3 = devserver.get_last_modified(["content"])
assert t2 > t1 assert t2 > t1
@@ -36,14 +40,14 @@ def test_autoreload_builds_immediately(args: Namespace) -> None:
t = threading.Thread( t = threading.Thread(
target=devserver.autoreload, target=devserver.autoreload,
args=(args, WAITTIME), args=(args,),
daemon=True, daemon=True,
) )
t0 = devserver.get_last_modified(["build"]) t0 = devserver.get_last_modified(["build"])
t.start() t.start()
# try for 5 seconds... # try for 5 seconds...
for i in range(5): for i in range(5):
time.sleep(WAITTIME) time.sleep(1)
t1 = devserver.get_last_modified(["build"]) t1 = devserver.get_last_modified(["build"])
print(t1) print(t1)
if t1 > t0: if t1 > t0:
@@ -51,11 +55,14 @@ def test_autoreload_builds_immediately(args: Namespace) -> None:
assert t1 > t0 assert t1 > t0
@pytest.mark.filterwarnings(
"ignore::pytest.PytestUnhandledThreadExceptionWarning"
)
def test_autoreload(args: Namespace) -> None: def test_autoreload(args: Namespace) -> None:
"""Test autoreload.""" """Test autoreload."""
t = threading.Thread( t = threading.Thread(
target=devserver.autoreload, target=devserver.autoreload,
args=(args, WAITTIME), args=(args,),
daemon=True, daemon=True,
) )
t.start() t.start()
@@ -68,32 +75,8 @@ def test_autoreload(args: Namespace) -> None:
# try for 5 seconds to see if we rebuild once... # try for 5 seconds to see if we rebuild once...
for i in range(5): for i in range(5):
time.sleep(WAITTIME) time.sleep(1)
t1 = devserver.get_last_modified(["build"]) t1 = devserver.get_last_modified(["build"])
if t1 > t0: if t1 > t0:
break break
assert t1 > t0 assert t1 > t0
def test_autoreload_does_not_crash(args: Namespace) -> None:
"""Test autoreload does not crash if build fails."""
t = threading.Thread(
target=devserver.autoreload,
args=(args, WAITTIME),
daemon=True,
)
t.start()
t0 = devserver.get_last_modified(["build"])
# create a file that causes build to crash
with open("content/test.md", "w") as fh:
fh.write("date: ")
# try for 5 seconds to see if we rebuild once...
for i in range(5):
time.sleep(WAITTIME)
t1 = devserver.get_last_modified(["build"])
if t1 > t0:
break
assert t.is_alive()

View File

@@ -1,5 +1,9 @@
"""Test markdown module.""" """Test markdown module."""
# remove when we don't support py38 anymore
from __future__ import annotations
from datetime import datetime from datetime import datetime
from typing import Any from typing import Any

View File

@@ -1,5 +1,9 @@
"""Tests for the quickstart module.""" """Tests for the quickstart module."""
# remove when we don't support py38 anymore
from __future__ import annotations
import os import os
from pytest import MonkeyPatch from pytest import MonkeyPatch

View File

@@ -1,5 +1,9 @@
"""Test the templates.""" """Test the templates."""
# remove when we don't support py38 anymore
from __future__ import annotations
import datetime import datetime
from jinja2 import Template from jinja2 import Template

View File

@@ -1,5 +1,9 @@
"""Test the version module.""" """Test the version module."""
# remove when we don't support py38 anymore
from __future__ import annotations
import blag import blag