forked from github.com/blag
Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
713b53d0d9 | ||
|
|
2d1d0ab302 | ||
|
|
572710fab1 | ||
|
|
0b66c5fbf4 | ||
|
|
18ecd82c5a | ||
|
|
a74f36be8a | ||
|
|
dd7d6cdae2 | ||
|
|
39e6aa2676 | ||
|
|
93f6840168 | ||
|
|
e27d82ac2a | ||
|
|
88b4fc8233 | ||
|
|
8923b0bb6e | ||
|
|
2671239ac1 | ||
|
|
7aa6cebb63 | ||
|
|
4869ea0fd2 | ||
|
|
aeedd9b73a | ||
|
|
52412b8926 | ||
|
|
c0dae31b60 | ||
|
|
2366ee2def | ||
|
|
67f24642e5 | ||
|
|
f1020637e6 | ||
|
|
a4d596b79d | ||
|
|
79edd04ee8 | ||
|
|
9cdecdccf7 | ||
|
|
499b0dfe11 | ||
|
|
72971408b2 | ||
|
|
6445f31204 | ||
|
|
98b97fbbbd | ||
|
|
df65dee488 | ||
|
|
d227392c79 | ||
|
|
03663a855d | ||
|
|
0d2c54071e | ||
|
|
cffb4cf49d | ||
|
|
0d4052dbb5 | ||
|
|
eada12097d | ||
|
|
4c14cac499 | ||
|
|
637d57eb85 | ||
|
|
746841f05c | ||
|
|
a78d4238b6 | ||
|
|
a8e14e86d0 | ||
|
|
cdc6639447 | ||
|
|
9c228165e9 | ||
|
|
2b3651e7d5 | ||
|
|
7240b0a28b | ||
|
|
3c264966c0 | ||
|
|
b24241544b | ||
|
|
9a96fe5e81 | ||
|
|
c60c98c37c | ||
|
|
179100005c | ||
|
|
b2f32e84e4 | ||
|
|
14ebc2769c | ||
|
|
e2ebd950ae | ||
|
|
576121afac | ||
|
|
2d3bb0c0f3 | ||
|
|
01b203ff5c | ||
|
|
6f70f7ca93 | ||
|
|
7f832a1445 | ||
|
|
aac2d70fed | ||
|
|
dc6547290b | ||
|
|
b077e22984 | ||
|
|
af5825b412 | ||
|
|
7d69c37032 |
6
.github/dependabot.yml
vendored
Normal file
6
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
1
.github/workflows/python-package.yaml
vendored
1
.github/workflows/python-package.yaml
vendored
@@ -19,6 +19,7 @@ jobs:
|
||||
python-version:
|
||||
- 3.8
|
||||
- 3.9
|
||||
- "3.10"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
8
.readthedocs.yaml
Normal file
8
.readthedocs.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
version: 2
|
||||
|
||||
python:
|
||||
version: 3.8
|
||||
install:
|
||||
- requirements: requirements.txt
|
||||
- requirements: requirements-dev.txt
|
||||
- path: .
|
||||
22
CHANGELOG.md
22
CHANGELOG.md
@@ -1,6 +1,24 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.0] - YYYY-MM-DD
|
||||
## [1.1.0] - 2021-1006
|
||||
|
||||
*
|
||||
* added Python 3.10 to list of supported versions to test against
|
||||
* added dependabot to github workflows
|
||||
* updated various dependencies:
|
||||
* pygments 2.10.0
|
||||
* sphinx 4.2.0
|
||||
* twine 3.4.2
|
||||
* wheel 0.37.0
|
||||
* pytest 6.2.5
|
||||
|
||||
## [1.0.0] - 2021-08-18
|
||||
|
||||
* first 1.0 release!
|
||||
* bump requirements of feedgenerator to 1.9.2. this version uses the
|
||||
description to provide a subtitle for the feed
|
||||
|
||||
## [0.0.9] - 2021-06-22
|
||||
|
||||
* updated to jinja 3.0
|
||||
* updated to sphinx 4.0
|
||||
* added link to changelog
|
||||
|
||||
17
Makefile
17
Makefile
@@ -3,6 +3,10 @@ PY = python3
|
||||
VENV = venv
|
||||
BIN=$(VENV)/bin
|
||||
|
||||
DOCS_SRC = docs
|
||||
DOCS_OUT = $(DOCS_SRC)/_build
|
||||
|
||||
|
||||
ifeq ($(OS), Windows_NT)
|
||||
BIN=$(VENV)/Scripts
|
||||
PY=python
|
||||
@@ -18,25 +22,30 @@ $(VENV): requirements.txt requirements-dev.txt setup.py
|
||||
$(BIN)/pip install -e .
|
||||
touch $(VENV)
|
||||
|
||||
.PHONY: test
|
||||
test: $(VENV)
|
||||
$(BIN)/pytest
|
||||
.PHONY: test
|
||||
|
||||
.PHONY: lint
|
||||
lint: $(VENV)
|
||||
$(BIN)/flake8
|
||||
.PHONY: lint
|
||||
|
||||
.PHONY: release
|
||||
release: $(VENV)
|
||||
rm -rf dist
|
||||
$(BIN)/python setup.py sdist bdist_wheel
|
||||
$(BIN)/twine upload dist/*
|
||||
.PHONY: release
|
||||
|
||||
.PHONY: docs
|
||||
docs: $(VENV)
|
||||
$(BIN)/sphinx-build $(DOCS_SRC) $(DOCS_OUT)
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf build dist *.egg-info
|
||||
rm -rf $(VENV)
|
||||
rm -rf $(DOCS_OUT)
|
||||
find . -type f -name *.pyc -delete
|
||||
find . -type d -name __pycache__ -delete
|
||||
# coverage
|
||||
rm -rf htmlcov .coverage
|
||||
.PHONY: clean
|
||||
|
||||
@@ -2,10 +2,15 @@
|
||||
|
||||
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 readthedocs
|
||||
|
||||
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
|
||||
@@ -14,12 +19,15 @@ blag is named after [the blag of the webcomic xkcd][blagxkcd].
|
||||
* 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/
|
||||
|
||||
|
||||
## Quickstart
|
||||
|
||||
152
blag/blag.py
152
blag/blag.py
@@ -1,13 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Small static site generator.
|
||||
"""blag's core methods.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
__author__ = "Bastian Venthur <venthur@debian.org>"
|
||||
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import shutil
|
||||
@@ -19,15 +15,27 @@ from jinja2 import Environment, ChoiceLoader, FileSystemLoader, PackageLoader
|
||||
import feedgenerator
|
||||
|
||||
from blag.markdown import markdown_factory, convert_markdown
|
||||
from blag.devserver import serve
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s %(levelname)s %(name)s %(message)s',
|
||||
)
|
||||
|
||||
|
||||
def main(args=None):
|
||||
"""Main entrypoint for the CLI.
|
||||
|
||||
This method parses the CLI arguments and executes the respective
|
||||
commands.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
args : list[str]
|
||||
optional parameters, used for testing
|
||||
|
||||
"""
|
||||
args = parse_args(args)
|
||||
args.func(args)
|
||||
|
||||
@@ -35,14 +43,14 @@ def main(args=None):
|
||||
def parse_args(args=None):
|
||||
"""Parse command line arguments.
|
||||
|
||||
Paramters
|
||||
---------
|
||||
Parameters
|
||||
----------
|
||||
args : List[str]
|
||||
optional parameters, used for testing
|
||||
|
||||
Returns
|
||||
-------
|
||||
args
|
||||
arparse.Namespace
|
||||
|
||||
"""
|
||||
parser = argparse.ArgumentParser()
|
||||
@@ -50,7 +58,10 @@ def parse_args(args=None):
|
||||
commands = parser.add_subparsers(dest='command')
|
||||
commands.required = True
|
||||
|
||||
build_parser = commands.add_parser('build')
|
||||
build_parser = commands.add_parser(
|
||||
'build',
|
||||
help='Build website.',
|
||||
)
|
||||
build_parser.set_defaults(func=build)
|
||||
build_parser.add_argument(
|
||||
'-i', '--input-dir',
|
||||
@@ -73,13 +84,55 @@ def parse_args(args=None):
|
||||
help='Static directory (default: static)',
|
||||
)
|
||||
|
||||
quickstart_parser = commands.add_parser('quickstart')
|
||||
quickstart_parser = commands.add_parser(
|
||||
'quickstart',
|
||||
help="Quickstart blag, creating necessary configuration.",
|
||||
)
|
||||
quickstart_parser.set_defaults(func=quickstart)
|
||||
|
||||
serve_parser = commands.add_parser(
|
||||
'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)',
|
||||
)
|
||||
serve_parser.add_argument(
|
||||
'-o', '--output-dir',
|
||||
default='build',
|
||||
help='Ouptut directory (default: build)',
|
||||
)
|
||||
serve_parser.add_argument(
|
||||
'-t', '--template-dir',
|
||||
default='templates',
|
||||
help='Template directory (default: templates)',
|
||||
)
|
||||
serve_parser.add_argument(
|
||||
'-s', '--static-dir',
|
||||
default='static',
|
||||
help='Static directory (default: static)',
|
||||
)
|
||||
|
||||
return parser.parse_args(args)
|
||||
|
||||
|
||||
def get_config(configfile):
|
||||
"""Load site configuration from configfile.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
configfile : str
|
||||
path to configuration file
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
|
||||
"""
|
||||
config = configparser.ConfigParser()
|
||||
config.read(configfile)
|
||||
# check for the mandatory options
|
||||
@@ -128,6 +181,16 @@ def environment_factory(template_dir=None, globals_=None):
|
||||
|
||||
|
||||
def build(args):
|
||||
"""Build the site.
|
||||
|
||||
This is blag's main method that builds the site, generates the feed
|
||||
etc.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
args : argparse.Namespace
|
||||
|
||||
"""
|
||||
os.makedirs(f'{args.output_dir}', exist_ok=True)
|
||||
convertibles = []
|
||||
for root, dirnames, filenames in os.walk(args.input_dir):
|
||||
@@ -205,12 +268,13 @@ def process_markdown(convertibles, input_dir, output_dir,
|
||||
articles, pages : List[Tuple[str, Dict]]
|
||||
|
||||
"""
|
||||
logger.info("Converting Markdown files...")
|
||||
md = markdown_factory()
|
||||
|
||||
articles = []
|
||||
pages = []
|
||||
for src, dst in convertibles:
|
||||
logger.debug(f'Processing {src}')
|
||||
logger.info(f'Processing {src}')
|
||||
with open(f'{input_dir}/{src}', 'r') as fh:
|
||||
body = fh.read()
|
||||
|
||||
@@ -225,7 +289,7 @@ def process_markdown(convertibles, input_dir, output_dir,
|
||||
articles.append((dst, context))
|
||||
result = article_template.render(context)
|
||||
else:
|
||||
pages.append((dst, content))
|
||||
pages.append((dst, context))
|
||||
result = page_template.render(context)
|
||||
with open(f'{output_dir}/{dst}', 'w') as fh_dest:
|
||||
fh_dest.write(result)
|
||||
@@ -243,6 +307,25 @@ def generate_feed(
|
||||
blog_description,
|
||||
blog_author,
|
||||
):
|
||||
"""Generate Atom feed.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
articles : list[list[str, dict]]
|
||||
list of relative output path and article dictionary
|
||||
output_dir : str
|
||||
where the feed is stored
|
||||
base_url : str
|
||||
base url
|
||||
blog_title : str
|
||||
blog title
|
||||
blog_description : str
|
||||
blog description
|
||||
blog_author : str
|
||||
blog author
|
||||
|
||||
"""
|
||||
logger.info('Generating Atom feed.')
|
||||
feed = feedgenerator.Atom1Feed(
|
||||
link=base_url,
|
||||
title=blog_title,
|
||||
@@ -251,11 +334,15 @@ def generate_feed(
|
||||
)
|
||||
|
||||
for dst, context in articles:
|
||||
# if article has a description, use that. otherwise fall back to
|
||||
# the title
|
||||
description = context.get('description', context['title'])
|
||||
|
||||
feed.add_item(
|
||||
title=context['title'],
|
||||
author_name=blog_author,
|
||||
link=base_url + dst,
|
||||
description=context['title'],
|
||||
description=description,
|
||||
content=context['content'],
|
||||
pubdate=context['date'],
|
||||
)
|
||||
@@ -265,6 +352,17 @@ def generate_feed(
|
||||
|
||||
|
||||
def generate_archive(articles, template, output_dir):
|
||||
"""Generate the archive page.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
articles : list[list[str, dict]]
|
||||
List of articles. Each article has the destination path and a
|
||||
dictionary with the content.
|
||||
template : jinja2.Template instance
|
||||
output_dir : str
|
||||
|
||||
"""
|
||||
archive = []
|
||||
for dst, context in articles:
|
||||
entry = context.copy()
|
||||
@@ -277,12 +375,24 @@ def generate_archive(articles, template, output_dir):
|
||||
|
||||
|
||||
def generate_tags(articles, tags_template, tag_template, output_dir):
|
||||
"""Generate the tags page.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
articles : list[list[str, dict]]
|
||||
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
|
||||
|
||||
"""
|
||||
logger.info("Generating Tag-pages.")
|
||||
os.makedirs(f'{output_dir}/tags', exist_ok=True)
|
||||
|
||||
# get tags number of occurrences
|
||||
all_tags = {}
|
||||
for _, context in articles:
|
||||
tags = context.get('tags', None)
|
||||
tags = context.get('tags', [])
|
||||
for tag in tags:
|
||||
all_tags[tag] = all_tags.get(tag, 0) + 1
|
||||
# sort by occurrence
|
||||
@@ -295,7 +405,7 @@ def generate_tags(articles, tags_template, tag_template, output_dir):
|
||||
# get tags and archive per tag
|
||||
all_tags = {}
|
||||
for dst, context in articles:
|
||||
tags = context.get('tags', None)
|
||||
tags = context.get('tags', [])
|
||||
for tag in tags:
|
||||
archive = all_tags.get(tag, [])
|
||||
entry = context.copy()
|
||||
@@ -310,6 +420,16 @@ def generate_tags(articles, tags_template, tag_template, output_dir):
|
||||
|
||||
|
||||
def quickstart(args):
|
||||
"""Quickstart.
|
||||
|
||||
This method asks the user some questions and generates a
|
||||
configuration file that is needed in order to run blag.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
args : argparse.Namespace
|
||||
|
||||
"""
|
||||
base_url = input("Hostname (and path) to the root? "
|
||||
"[https://example.com/]: ")
|
||||
title = input("Title of your website? ")
|
||||
|
||||
87
blag/devserver.py
Normal file
87
blag/devserver.py
Normal file
@@ -0,0 +1,87 @@
|
||||
"""Development Server.
|
||||
|
||||
This module provides functionality for blag's development server. It
|
||||
automatically detects changes in certain directories and rebuilds the
|
||||
site if necessary.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
import time
|
||||
import multiprocessing
|
||||
from http.server import SimpleHTTPRequestHandler, HTTPServer
|
||||
from functools import partial
|
||||
|
||||
from blag import blag
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_last_modified(dirs):
|
||||
"""Get the last modified time.
|
||||
|
||||
This method recursively goes through `dirs` and returns the most
|
||||
recent modification time time found.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
dirs : list[str]
|
||||
list of directories to search
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
most recent modification time found in `dirs`
|
||||
|
||||
"""
|
||||
last_mtime = 0
|
||||
|
||||
for dir in dirs:
|
||||
for root, dirs, files in os.walk(dir):
|
||||
for f in files:
|
||||
mtime = os.stat(os.path.join(root, f)).st_mtime
|
||||
if mtime > last_mtime:
|
||||
last_mtime = mtime
|
||||
|
||||
return last_mtime
|
||||
|
||||
|
||||
def autoreload(args):
|
||||
"""Start the autoreloader.
|
||||
|
||||
This method monitors the given directories for changes (i.e. the
|
||||
last modified time). If the last modified time has changed, a
|
||||
rebuild is triggered.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
args : argparse.Namespace
|
||||
|
||||
"""
|
||||
dirs = [args.input_dir, args.template_dir, args.static_dir]
|
||||
logger.info(f'Monitoring {dirs} for changes...')
|
||||
last_mtime = get_last_modified(dirs)
|
||||
while True:
|
||||
mtime = get_last_modified(dirs)
|
||||
if mtime > last_mtime:
|
||||
last_mtime = mtime
|
||||
logger.debug('Change detected, rebuilding...')
|
||||
blag.build(args)
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
def serve(args):
|
||||
"""Start the webserver and the autoreloader.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
args : arparse.Namespace
|
||||
|
||||
"""
|
||||
httpd = HTTPServer(('', 8000), partial(SimpleHTTPRequestHandler,
|
||||
directory=args.output_dir))
|
||||
proc = multiprocessing.Process(target=autoreload, args=(args,))
|
||||
proc.start()
|
||||
httpd.serve_forever()
|
||||
@@ -1,3 +1,10 @@
|
||||
"""Markdown Processing.
|
||||
|
||||
This module contains the methods responsible for blag's markdown
|
||||
processing.
|
||||
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
import logging
|
||||
from urllib.parse import urlsplit, urlunsplit
|
||||
@@ -23,7 +30,7 @@ def markdown_factory():
|
||||
"""
|
||||
md = Markdown(
|
||||
extensions=[
|
||||
'meta', 'fenced_code', 'codehilite',
|
||||
'meta', 'fenced_code', 'codehilite', 'smarty',
|
||||
MarkdownLinkExtension()
|
||||
],
|
||||
output_format='html5',
|
||||
@@ -34,6 +41,11 @@ def markdown_factory():
|
||||
def convert_markdown(md, markdown):
|
||||
"""Convert markdown into html and extract meta data.
|
||||
|
||||
Some meta data is treated special:
|
||||
* `date` is converted into datetime with local timezone
|
||||
* `tags` is interpreted as a comma-separeted list of strings.
|
||||
All strings are stripped and converted to lower case.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
md : markdown.Markdown instance
|
||||
@@ -98,6 +110,9 @@ class MarkdownLinkTreeprocessor(Treeprocessor):
|
||||
|
||||
|
||||
class MarkdownLinkExtension(Extension):
|
||||
"""markdown.extension that converts relative .md- to .html-links.
|
||||
|
||||
"""
|
||||
def extendMarkdown(self, md):
|
||||
md.treeprocessors.register(
|
||||
MarkdownLinkTreeprocessor(md), 'mdlink', 0,
|
||||
|
||||
@@ -1 +1 @@
|
||||
__VERSION__ = '0.0.3'
|
||||
__VERSION__ = '1.1.0'
|
||||
|
||||
20
docs/Makefile
Normal file
20
docs/Makefile
Normal file
@@ -0,0 +1,20 @@
|
||||
# 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)
|
||||
11
docs/api.rst
Normal file
11
docs/api.rst
Normal file
@@ -0,0 +1,11 @@
|
||||
API
|
||||
===
|
||||
|
||||
.. autosummary::
|
||||
:toctree: api
|
||||
|
||||
blag.__init__
|
||||
blag.version
|
||||
blag.blag
|
||||
blag.markdown
|
||||
blag.devserver
|
||||
283
docs/blag.rst
Normal file
283
docs/blag.rst
Normal file
@@ -0,0 +1,283 @@
|
||||
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 needed
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
$ blag quickstart
|
||||
|
||||
Create some content
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
$ mkdir content
|
||||
$ 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 looks of the generated site, create a
|
||||
``template`` directory and put your jinja2 templates here.
|
||||
|
||||
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 create a ``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
|
||||
|
||||
<p>this is a <a href="foo.html">link</a> to an internal page foo.</p>
|
||||
|
||||
|
||||
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.
|
||||
|
||||
============ ====================================== ===================
|
||||
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
|
||||
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
|
||||
|
||||
69
docs/conf.py
Normal file
69
docs/conf.py
Normal file
@@ -0,0 +1,69 @@
|
||||
# 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
|
||||
53
docs/index.rst
Normal file
53
docs/index.rst
Normal file
@@ -0,0 +1,53 @@
|
||||
.. 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`
|
||||
35
docs/make.bat
Normal file
35
docs/make.bat
Normal file
@@ -0,0 +1,35 @@
|
||||
@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
|
||||
@@ -1,5 +1,6 @@
|
||||
twine==3.3.0
|
||||
wheel==0.36.2
|
||||
pytest==6.2.1
|
||||
pytest-cov==2.10.1
|
||||
flake8==3.8.4
|
||||
sphinx==4.2.0
|
||||
twine==3.4.2
|
||||
wheel==0.37.0
|
||||
pytest==6.2.5
|
||||
pytest-cov==2.12.1
|
||||
flake8==3.9.2
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
markdown==3.3.3
|
||||
feedgenerator==1.9.1
|
||||
jinja2==2.11.2
|
||||
pygments==2.7.3
|
||||
markdown==3.3.4
|
||||
feedgenerator==1.9.2
|
||||
jinja2==3.0.1
|
||||
pygments==2.10.0
|
||||
|
||||
@@ -6,4 +6,4 @@ addopts =
|
||||
--cov-report=term-missing:skip-covered
|
||||
|
||||
[flake8]
|
||||
exclude = venv,build
|
||||
exclude = venv,build,docs
|
||||
|
||||
7
setup.py
7
setup.py
@@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
meta = {}
|
||||
@@ -17,6 +16,12 @@ setup(
|
||||
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/*'],
|
||||
|
||||
86
tests/conftest.py
Normal file
86
tests/conftest.py
Normal file
@@ -0,0 +1,86 @@
|
||||
from tempfile import TemporaryDirectory
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from blag import blag
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def environment():
|
||||
site = {
|
||||
'base_url': 'site base_url',
|
||||
'title': 'site title',
|
||||
'description': 'site description',
|
||||
'author': 'site author',
|
||||
}
|
||||
env = blag.environment_factory(globals_=dict(site=site))
|
||||
yield env
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def page_template(environment):
|
||||
yield environment.get_template('page.html')
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def article_template(environment):
|
||||
yield environment.get_template('article.html')
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def archive_template(environment):
|
||||
yield environment.get_template('archive.html')
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tags_template(environment):
|
||||
yield environment.get_template('tags.html')
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tag_template(environment):
|
||||
yield environment.get_template('tag.html')
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def cleandir():
|
||||
"""Create a temporary workind directory and cwd.
|
||||
|
||||
"""
|
||||
config = """
|
||||
[main]
|
||||
base_url = https://example.com/
|
||||
title = title
|
||||
description = description
|
||||
author = a. u. thor
|
||||
"""
|
||||
|
||||
with TemporaryDirectory() as dir:
|
||||
for d in 'content', 'build', 'static', 'templates':
|
||||
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)
|
||||
yield dir
|
||||
# and change back afterwards
|
||||
os.chdir(old_cwd)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def args(cleandir):
|
||||
|
||||
class NameSpace:
|
||||
def __init__(self, **kwargs):
|
||||
for name in kwargs:
|
||||
setattr(self, name, kwargs[name])
|
||||
|
||||
args = NameSpace(
|
||||
input_dir='content',
|
||||
output_dir='build',
|
||||
static_dir='static',
|
||||
template_dir='templates',
|
||||
)
|
||||
yield args
|
||||
@@ -1,21 +1,86 @@
|
||||
from tempfile import TemporaryDirectory
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from blag import blag
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def outdir():
|
||||
with TemporaryDirectory() as dir:
|
||||
yield dir
|
||||
|
||||
|
||||
def test_generate_feed(outdir):
|
||||
def test_generate_feed(cleandir):
|
||||
articles = []
|
||||
blag.generate_feed(articles, outdir, ' ', ' ', ' ', ' ')
|
||||
assert os.path.exists(f'{outdir}/atom.xml')
|
||||
blag.generate_feed(articles, 'build', ' ', ' ', ' ', ' ')
|
||||
assert os.path.exists('build/atom.xml')
|
||||
|
||||
|
||||
def test_feed(cleandir):
|
||||
articles = [
|
||||
[
|
||||
'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/',
|
||||
'blog title', 'blog description', 'blog author')
|
||||
with open('build/atom.xml') as fh:
|
||||
feed = fh.read()
|
||||
|
||||
assert '<title>blog title</title>' in feed
|
||||
# enable when https://github.com/getpelican/feedgenerator/issues/22
|
||||
# is fixed
|
||||
# assert '<subtitle>blog description</subtitle>' in feed
|
||||
assert '<author><name>blog author</name></author>' in feed
|
||||
|
||||
# article 1
|
||||
assert '<title>title1</title>' in feed
|
||||
assert '<summary type="html">title1' in feed
|
||||
assert '<published>2019-06-06' in feed
|
||||
assert '<content type="html">content1' in feed
|
||||
assert '<link href="https://example.com/dest1.html"' in feed
|
||||
|
||||
# article 2
|
||||
assert '<title>title2</title>' in feed
|
||||
assert '<summary type="html">title2' in feed
|
||||
assert '<published>1980-05-09' in feed
|
||||
assert '<content type="html">content2' in feed
|
||||
assert '<link href="https://example.com/dest2.html"' in feed
|
||||
|
||||
|
||||
def test_generate_feed_with_description(cleandir):
|
||||
# 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 = [[
|
||||
'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:
|
||||
feed = fh.read()
|
||||
|
||||
assert '<title>title</title>' in feed
|
||||
assert '<summary type="html">description' in feed
|
||||
assert '<published>2019-06-06' in feed
|
||||
assert '<content type="html">content' in feed
|
||||
|
||||
|
||||
def test_parse_args_build():
|
||||
@@ -109,3 +174,106 @@ def test_environment_factory():
|
||||
env = blag.environment_factory(globals_=globals_)
|
||||
assert env.globals['foo'] == 'bar'
|
||||
assert env.globals['test'] == 'me'
|
||||
|
||||
|
||||
def test_process_markdown(cleandir, page_template, article_template):
|
||||
page1 = """\
|
||||
title: some page
|
||||
|
||||
some text
|
||||
foo bar
|
||||
"""
|
||||
|
||||
article1 = """\
|
||||
title: some article1
|
||||
date: 2020-01-01
|
||||
|
||||
some text
|
||||
foo bar
|
||||
"""
|
||||
|
||||
article2 = """\
|
||||
title: some article2
|
||||
date: 2021-01-01
|
||||
|
||||
some text
|
||||
foo bar
|
||||
"""
|
||||
|
||||
convertibles = []
|
||||
for i, txt in enumerate((page1, article1, article2)):
|
||||
i = str(i)
|
||||
with open(f'content/{i}', 'w') as fh:
|
||||
fh.write(txt)
|
||||
convertibles.append([i, i])
|
||||
|
||||
articles, pages = blag.process_markdown(
|
||||
convertibles,
|
||||
'content',
|
||||
'build',
|
||||
page_template,
|
||||
article_template
|
||||
)
|
||||
|
||||
assert isinstance(articles, list)
|
||||
assert len(articles) == 2
|
||||
for dst, context in articles:
|
||||
assert isinstance(dst, str)
|
||||
assert isinstance(context, dict)
|
||||
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
|
||||
|
||||
|
||||
def test_build(args):
|
||||
page1 = """\
|
||||
title: some page
|
||||
|
||||
some text
|
||||
foo bar
|
||||
"""
|
||||
|
||||
article1 = """\
|
||||
title: some article1
|
||||
date: 2020-01-01
|
||||
tags: foo, bar
|
||||
|
||||
some text
|
||||
foo bar
|
||||
"""
|
||||
|
||||
article2 = """\
|
||||
title: some article2
|
||||
date: 2021-01-01
|
||||
tags: baz
|
||||
|
||||
some text
|
||||
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:
|
||||
fh.write(txt)
|
||||
convertibles.append([i, i])
|
||||
|
||||
# some static files
|
||||
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')
|
||||
|
||||
blag.build(args)
|
||||
|
||||
|
||||
def test_main(cleandir):
|
||||
blag.main(['build'])
|
||||
|
||||
21
tests/test_devserver.py
Normal file
21
tests/test_devserver.py
Normal file
@@ -0,0 +1,21 @@
|
||||
import time
|
||||
|
||||
from blag import devserver
|
||||
|
||||
|
||||
def test_get_last_modified(cleandir):
|
||||
# take initial time
|
||||
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'])
|
||||
|
||||
# wait a bit and take time again
|
||||
time.sleep(0.1)
|
||||
t3 = devserver.get_last_modified(['content'])
|
||||
|
||||
assert t2 > t1
|
||||
assert t2 == t3
|
||||
@@ -49,3 +49,31 @@ def test_convert_metadata(input_, expected):
|
||||
def test_markdown_factory():
|
||||
md = markdown_factory()
|
||||
assert isinstance(md, markdown.Markdown)
|
||||
|
||||
|
||||
def test_smarty():
|
||||
md = markdown_factory()
|
||||
|
||||
md1 = """
|
||||
|
||||
this --- is -- a test ...
|
||||
|
||||
"""
|
||||
html, meta = convert_markdown(md, md1)
|
||||
assert 'mdash' in html
|
||||
assert 'ndash' in html
|
||||
assert 'hellip' in html
|
||||
|
||||
|
||||
def test_smarty_code():
|
||||
md = markdown_factory()
|
||||
|
||||
md1 = """
|
||||
```
|
||||
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
|
||||
|
||||
@@ -1,46 +1,5 @@
|
||||
import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from blag import blag
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def environment():
|
||||
site = {
|
||||
'base_url': 'site base_url',
|
||||
'title': 'site title',
|
||||
'description': 'site description',
|
||||
'author': 'site author',
|
||||
}
|
||||
env = blag.environment_factory(globals_=dict(site=site))
|
||||
yield env
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def page_template(environment):
|
||||
yield environment.get_template('page.html')
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def article_template(environment):
|
||||
yield environment.get_template('article.html')
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def archive_template(environment):
|
||||
yield environment.get_template('archive.html')
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tags_template(environment):
|
||||
yield environment.get_template('tags.html')
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tag_template(environment):
|
||||
yield environment.get_template('tag.html')
|
||||
|
||||
|
||||
def test_page(page_template):
|
||||
ctx = {
|
||||
|
||||
Reference in New Issue
Block a user