Skip to content

Commit 821f8a2

Browse files
committed
checkpoint
1 parent 1374c1b commit 821f8a2

22 files changed

+16
-2019
lines changed

Makefile

Lines changed: 3 additions & 22 deletions
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/* andrew@let-them.cyou:/srv/www/lt/andrew/tutorials/python
12+
echo "rsync -rvz --progress -e 'ssh -p 57018' ./dist/* andrew@let-them.cyou:/srv/www/lt/andrew/tutorials/python"

app/__init__.py

Lines changed: 1 addition & 8 deletions
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

Lines changed: 11 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,16 @@
11
"""Module for building and build-time QA of the python tutorial website"""
2-
import argparse
2+
import json
33
from os import makedirs
4-
from pathlib import Path
54
import sys
65

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
6+
from app.codeblocks import codeblocks
7+
from app.qa import CodeblocksTester, BuildTestFailure
118

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-
)
199

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",
33-
)
34-
35-
def run_build_checks(env, ctx, args):
10+
def run_build_checks():
3611
"""QA for build process"""
3712
testers = (
38-
LinkTester(warn=args.warn_links),
3913
CodeblocksTester(),
40-
UnresolvedTemplateVariablesTester(env, ctx),
4114
)
4215
fail = False
4316
for tester in testers:
@@ -49,53 +22,15 @@ def run_build_checks(env, ctx, args):
4922
sys.exit(1)
5023

5124

52-
def render_templates(env, ctx):
25+
def main():
5326
"""
54-
Generator
55-
Render templates in correct order to produce a final render
27+
Perform all of the actions necessary to output the python tutorial codeblocks
5628
"""
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):
83-
"""
84-
Perform all of the actions necessary to output the python tutorial webpages
85-
"""
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)
29+
run_build_checks()
30+
makedirs('dist', exist_ok=True)
31+
with open('dist/codeblocks.json', 'w', encoding='utf-8') as f:
32+
json.dump(codeblocks, f)
9733

9834

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

app/qa.py

Lines changed: 1 addition & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
"""Testers and test helpers for QA of tutorial"""
22
from abc import ABC, abstractmethod
3-
import asyncio
43
from dataclasses import dataclass, field
54
import doctest
65

7-
import aiohttp
8-
96

107
TIMEOUT_SEC = 10
118

@@ -54,115 +51,11 @@ class CodeblocksTester(BuildTest):
5451
name = 'Codeblocks'
5552

5653
def _test(self):
57-
from app.templates import codeblocks # pylint: disable=import-outside-toplevel
54+
from app import codeblocks # pylint: disable=import-outside-toplevel
5855
failure_count, _ = doctest.testmod(
5956
codeblocks,
6057
verbose=None,
6158
report=False
6259
)
6360
return BuildTestResult(not bool(failure_count))
64-
65-
66-
class LinkTester(BuildTest):
67-
"""Make sure all templated links are alive"""
68-
69-
name = 'Links'
70-
def __init__(self, concurrency=16, warn=False):
71-
"""
72-
Semaphore limits number of concurrent requests for performance
73-
Is still nonblocking
74-
"""
75-
self.concurrency = concurrency
76-
self.warn_only = warn
77-
78-
async def fetch(self, session, url, lock):
79-
"""Locked async HTTP GET"""
80-
async with lock:
81-
try:
82-
return await session.get(url, allow_redirects=False, timeout=TIMEOUT_SEC)
83-
except (
84-
aiohttp.ClientConnectorSSLError,
85-
aiohttp.ClientConnectorCertificateError
86-
) as err:
87-
if self.warn_only:
88-
print(f"SSL error when fetching {url}")
89-
return await session.get(
90-
url,
91-
allow_redirects=False,
92-
timeout=TIMEOUT_SEC,
93-
ssl=False
94-
)
95-
raise err
96-
except aiohttp.ClientResponseError as err:
97-
if self.warn_only:
98-
print(f"Error fetching {url}, status code: {err.status}")
99-
else:
100-
raise err
101-
except asyncio.exceptions.TimeoutError as err:
102-
if self.warn_only:
103-
print(f"Took too long trying to fetch {url}")
104-
else:
105-
raise err
106-
107-
async def get_failures(self, links):
108-
"""
109-
Send GET requests for link in links, return responses of requests
110-
which do not return 200 OK.
111-
"""
112-
async with aiohttp.ClientSession() as session:
113-
lock = asyncio.Semaphore(self.concurrency)
114-
responses = await asyncio.gather(
115-
*[self.fetch(session, url, lock) for url in links]
116-
)
117-
failures = [r for r in responses if r is not None and r.status != 200]
118-
return failures
119-
120-
def _test(self):
121-
from app.templates.links import links # pylint: disable=import-outside-toplevel
122-
123-
def strip_fragment_identifiers(link):
124-
return link.split('#')[0]
125-
126-
# formatting
127-
print()
128-
129-
links = set(strip_fragment_identifiers(link) for link in links.values())
130-
131-
fails = asyncio.run(self.get_failures(links))
132-
if self.warn_only:
133-
print('\nAdditional Warnings:')
134-
for fail in fails:
135-
print(f'{fail.url}: {fail.status}')
136-
return BuildTestResult(True)
137-
return BuildTestResult(
138-
not bool(fails),
139-
[f'{fail.url}: {fail.status}' for fail in fails]
140-
)
141-
142-
143-
class UnresolvedTemplateVariablesTester(BuildTest):
144-
"""Make sure all variables in the template have a match in context"""
145-
name = 'Unresolved Variables'
146-
147-
def __init__(self, env, ctx):
148-
self.env = env
149-
self.ctx = ctx
150-
super().__init__()
151-
152-
def _test(self):
153-
# pylint: disable=import-outside-toplevel
154-
from jinja2 import meta
155-
156-
from app.templates import TEMPLATES
157-
# pylint: enable=import-outside-toplevel
158-
159-
passed = True
160-
output = []
161-
for template in TEMPLATES:
162-
ast = self.env.parse(self.env.get_template(template).render())
163-
vars_in_template = meta.find_undeclared_variables(ast)
164-
if mismatch := vars_in_template.difference(set(self.ctx.keys())):
165-
passed = False
166-
output.append(f'{template}: {mismatch}')
167-
return BuildTestResult(passed, output)
16861
# pylint: enable=too-few-public-methods

app/templates/00_installation.md

Lines changed: 0 additions & 16 deletions
This file was deleted.

app/templates/01_how_to_run_code.md

Lines changed: 0 additions & 35 deletions
This file was deleted.

app/templates/02_programming_overview.md

Lines changed: 0 additions & 13 deletions
This file was deleted.

0 commit comments

Comments
 (0)