Skip to content

Commit 18b93f3

Browse files
authored
Let Zola do the SSG (#25)
* Large codeblocks restructure * Remove unnecessary requirements * Break Pylint3.x! * More codeblocks * Add error_handling page * Update README.md
1 parent 1374c1b commit 18b93f3

31 files changed

+1333
-2746
lines changed

Makefile

+3-22
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,12 @@
11
.PHONY: default
2-
default: build
2+
default: clean lint
3+
python3 -m app
34

45
.PHONY: clean
56
clean:
67
rm -rf dist
78
.PHONY: lint
89
lint:
910
pylint app
10-
.PHONY: init_dist_dir
11-
init_dist_dir:
12-
mkdir -p dist
13-
.PHONY: basic_build
14-
basic_build: clean init_dist_dir
15-
python -m app
16-
.PHONY: basic_build_new_internal_links
17-
basic_build_warn_links: clean init_dist_dir
18-
python -m app --warn-links
19-
.PHONY: pygmentize_css
20-
pygmentize_css:
21-
pygmentize -S solarized-light -f html -a .codehilite > dist/styles.css
22-
.PHONY: zip
23-
zip:
24-
gzip -k -9 dist/*
25-
.PHONY: build
26-
build: lint basic_build pygmentize_css zip
27-
.PHONY: build_warn_links
28-
build_warn_links: lint basic_build_warn_links pygmentize_css zip
29-
.PHONY: rsync
3011
rsync:
31-
rsync -rvz --progress -e 'ssh -p 57018' ./dist/* [email protected]:/srv/www/lt/andrew/tutorials/python
12+
echo "rsync -rvz --progress -e 'ssh -p 57018' ./dist/* [email protected]:/srv/www/lt/andrew/tutorials/python"

README.md

+4-9
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
# python-tutorial
22

33
## Brief summary
4-
A tutorial with eventual content for both absolute beginners and experienced programmers.
5-
6-
## Libraries
7-
[Python-Markdown](https://python-markdown.github.io/) - Converts Markdown to HTML
84

9-
[Jinja](https://jinja.palletsprojects.com/en/3.0.x/) - Templating, used for links, codeblocks, and HTML
10-
11-
## Usage
12-
After cloning the repository, create a virtual environment `python3 -m venv venv` and install all dependencies in requirements.txt `pip install -r requirements.txt`.
5+
A tutorial with eventual content for both absolute beginners and experienced programmers.
6+
Codeblocks that are automatically tested before being exported for use on my personal page's Python tutorial.
137

14-
Refer to the Makefile for actions to take
8+
Once, this repository held both a site generator and the markdown content of my Python tutorial.
9+
It is due for a name change...

app/__init__.py

+1-8
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,3 @@
11
"""
2-
Package builds and performs QA of a python tutorial website
3-
Module provides config dictionary to other modules
2+
Package for providing tested codeblock data for templating in another program
43
"""
5-
__HOMEPAGE_URL = 'https://andrew.let-them.cyou'
6-
config = {
7-
'DIST_PATH': 'dist',
8-
'HOMEPAGE_URL': __HOMEPAGE_URL,
9-
'TUTORIAL_BASE_URL': f'{__HOMEPAGE_URL}/tutorials/python',
10-
}

app/__main__.py

+34-78
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,28 @@
1-
"""Module for building and build-time QA of the python tutorial website"""
2-
import argparse
1+
"""Module for building and build-time QA of the python tutorial codeblocks"""
2+
import inspect
3+
import json
34
from os import makedirs
4-
from pathlib import Path
55
import sys
66

7-
from jinja2 import Environment, PackageLoader, DebugUndefined
8-
from markdown import markdown
9-
from markdown.extensions.toc import TocExtension
10-
from markdown.extensions.codehilite import CodeHiliteExtension
7+
from app.codeblocks import get_func_body
8+
from app.pages import first_steps, first_projects, variables, classes, error_handling
9+
from app.qa import CodeblocksTester, BuildTestFailure
1110

12-
from app import config
13-
from app.templates import TEMPLATES, BASE_HTML
14-
from app.templates.codeblocks import codeblocks
15-
from app.templates.links import links
16-
from app.qa import (
17-
LinkTester, CodeblocksTester, UnresolvedTemplateVariablesTester, BuildTestFailure
18-
)
1911

20-
parser = argparse.ArgumentParser()
21-
parser.add_argument(
22-
'--dry-run',
23-
action="store_true",
24-
help="""
25-
Do not write any files or directories.
26-
Still perform network requests for link checks
27-
"""
28-
)
29-
parser.add_argument(
30-
'--warn-links',
31-
action="store_true",
32-
help="Do not error on link checker failures",
12+
PAGE_MODULES = (
13+
first_steps,
14+
first_projects,
15+
variables,
16+
classes,
17+
error_handling,
3318
)
3419

35-
def run_build_checks(env, ctx, args):
20+
OUTPUT_DIR = "dist"
21+
22+
23+
def run_build_checks():
3624
"""QA for build process"""
37-
testers = (
38-
LinkTester(warn=args.warn_links),
39-
CodeblocksTester(),
40-
UnresolvedTemplateVariablesTester(env, ctx),
41-
)
25+
testers = (CodeblocksTester(PAGE_MODULES),)
4226
fail = False
4327
for tester in testers:
4428
try:
@@ -49,53 +33,25 @@ def run_build_checks(env, ctx, args):
4933
sys.exit(1)
5034

5135

52-
def render_templates(env, ctx):
53-
"""
54-
Generator
55-
Render templates in correct order to produce a final render
56-
"""
57-
py_md_extensions = (
58-
'attr_list',
59-
'fenced_code',
60-
CodeHiliteExtension(guess_lang=False),
61-
TocExtension(toc_depth='2-6', anchorlink=True),
62-
)
63-
base_html_template = env.get_template(BASE_HTML)
64-
65-
for name in TEMPLATES:
66-
template = env.get_template(name)
67-
html = markdown(
68-
template.render(ctx),
69-
extensions=py_md_extensions
70-
)
71-
final_render = base_html_template.render({'body_content': html})
72-
path = Path(config['DIST_PATH'], Path(name).stem).with_suffix('.html')
73-
yield final_render, path
74-
75-
76-
def write_render(render, path):
77-
"""Write final render to appropriate path"""
78-
with open(path, 'w', encoding='utf-8') as f:
79-
f.write(render)
80-
81-
82-
def main(args):
36+
def main():
8337
"""
84-
Perform all of the actions necessary to output the python tutorial webpages
38+
Perform all of the actions necessary to output the python tutorial codeblocks
8539
"""
86-
env = Environment(
87-
loader=PackageLoader('app'),
88-
autoescape=False,
89-
undefined=DebugUndefined,
90-
)
91-
ctx = {**codeblocks, **links}
92-
run_build_checks(env, ctx, args)
93-
if not args.dry_run:
94-
makedirs('dist', exist_ok=True)
95-
for render, path in render_templates(env, ctx):
96-
write_render(render, path)
40+
print()
41+
run_build_checks()
42+
print()
43+
makedirs(OUTPUT_DIR, exist_ok=True)
44+
with open(f'{OUTPUT_DIR}/codeblocks.json', 'w', encoding='utf-8') as f:
45+
codeblock_map = {}
46+
for module in PAGE_MODULES:
47+
nonlocals = getattr(module, "NONLOCALS", ())
48+
print(f"extracting from {module.__name__}:")
49+
for member_name, member in inspect.getmembers(module, inspect.isfunction):
50+
if member_name not in nonlocals:
51+
print(" ", member_name)
52+
codeblock_map[member_name] = get_func_body(member)
53+
json.dump(codeblock_map, f)
9754

9855

9956
if __name__ == '__main__':
100-
args_ = parser.parse_args()
101-
main(args_)
57+
main()

app/codeblocks.py

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"""
2+
Exports doctested, minimal codeblocks for templating
3+
Docstrings MUST be double quotes!
4+
"""
5+
6+
import inspect
7+
8+
9+
def normalize_indentation(line, set_level, level):
10+
"""Remove unnecessary indentation resulting from doctesting of code"""
11+
if line == '\n':
12+
return line, level
13+
counter = 0
14+
length = len(line)
15+
while counter < length - 1 and line[counter] == ' ':
16+
counter += 1
17+
if set_level:
18+
level = int(counter / 4)
19+
result = line[4 * level:], level
20+
else:
21+
index = 4 * level
22+
result = line[index:], level
23+
return ''.join(result[0]) + '\n', result[1]
24+
25+
26+
def get_func_body(name):
27+
"""
28+
Remove docstrings, indentation, and testing artifacts from function
29+
indicated by name for use with templating as a codeblock
30+
31+
Functions can be marked with # START and # END comments for special
32+
trimming of testing code
33+
"""
34+
raw = inspect.getsource(name)
35+
lines = raw.split('\n')[1:]
36+
set_level = True
37+
level = None
38+
consume_whitespace = True
39+
result = []
40+
if '# START' in raw and '# END' in raw:
41+
take = False
42+
for line in lines:
43+
if '# END' in line:
44+
take = False
45+
if take:
46+
if not line.strip() and consume_whitespace: # DRY :\
47+
continue
48+
consume_whitespace = False
49+
line, level = normalize_indentation(line, set_level, level)
50+
set_level = False
51+
result.append(line)
52+
if '# START' in line:
53+
take = True
54+
return ''.join(result).strip() # confused myself with early return lol
55+
docstring_occurences = 0
56+
for line in lines:
57+
if docstring_occurences >= 2:
58+
if not line.strip() and consume_whitespace: # DRY :\
59+
continue
60+
consume_whitespace = False
61+
line, level = normalize_indentation(line, set_level, level)
62+
set_level = False
63+
result.append(line)
64+
docstring_occurences += line.count('"""')
65+
66+
return ''.join(result).strip()

app/pages/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)