Skip to content

Commit a516412

Browse files
committed
feat: refactored static page generation
Issue #16
1 parent ae8aae4 commit a516412

File tree

12 files changed

+327
-327
lines changed

12 files changed

+327
-327
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
debug
22
deploy
33
venv
4-
src/promo.jpg
4+
static/promo.jpg
5+
6+
*.pyc
7+

Makefile

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,31 @@
1-
SOURCE_DIR = src
21
DEBUG_DIR = debug
32
DEPLOY_DIR = deploy
3+
STATIC_DIR = static
44

5+
all: clear debug deploy
56

6-
all: debug deploy
7-
8-
debug: $(SOURCE_DIR)/index.html $(SOURCE_DIR)/script.js $(SOURCE_DIR)/app.css $(SOURCE_DIR)/promo.css $(SOURCE_DIR)/examples.html
7+
debug:
98
mkdir -p $(DEBUG_DIR)
10-
cp -r $(SOURCE_DIR)/* $(DEBUG_DIR)
11-
python3 html_builder.py -m debug -f $(SOURCE_DIR)/index.html > $(DEBUG_DIR)/index.html
12-
python3 examples_page_builder.py -m debug -f $(SOURCE_DIR)/examples.html > $(DEBUG_DIR)/examples.html
9+
cp -r $(STATIC_DIR)/* $(DEBUG_DIR)
10+
python3 build.py --debug -d $(DEBUG_DIR)
1311

1412
deploy: debug
1513
mkdir -p $(DEPLOY_DIR)
16-
cp -r $(DEBUG_DIR)/* $(DEPLOY_DIR)
17-
python3 html_builder.py -m deploy -f $(SOURCE_DIR)/index.html > $(DEPLOY_DIR)/index.html
18-
python3 examples_page_builder.py -m deploy -f $(SOURCE_DIR)/examples.html > $(DEPLOY_DIR)/examples.html
19-
uglifyjs $(DEBUG_DIR)/script.js > $(DEPLOY_DIR)/script.js
20-
uglifyjs $(DEBUG_DIR)/examples.js > $(DEPLOY_DIR)/examples.js
21-
22-
pages: deploy
23-
ghp-import -n -f -p $(DEPLOY_DIR)
14+
cp -r $(STATIC_DIR)/* $(DEPLOY_DIR)
15+
python3 build.py -d $(DEPLOY_DIR)
16+
uglifyjs $(STATIC_DIR)/js/index.js > $(DEPLOY_DIR)/js/index.js
17+
uglifyjs $(STATIC_DIR)/js/examples.js > $(DEPLOY_DIR)/js/examples.js
2418

2519
clear:
2620
rm -rf $(DEBUG_DIR)
2721
rm -rf $(DEPLOY_DIR)
2822

23+
pages: deploy
24+
ghp-import -n -f -p $(DEPLOY_DIR)
25+
2926
newpromo:
3027
ifdef i
31-
cp $(i) 'src/promo.jpg'
28+
cp $(i) 'static/promo.jpg'
3229
else
3330
@echo "No picture specified. Usage: make i=path newpromo"
3431
endif

build.py

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import argparse
2+
from dataclasses import dataclass, field
3+
import jinja2
4+
import json
5+
import pathlib
6+
from typing import Optional
7+
8+
from builder.icons import Icons
9+
from builder.contact import Contact
10+
from builder.recipe import RecipeExample
11+
12+
13+
@dataclass
14+
class JqueryCDN:
15+
src: str
16+
integrity: str
17+
18+
19+
TEMPLATES_PATH = pathlib.Path(__file__).parent / 'templates'
20+
STATICS_PATH = pathlib.Path(__file__).parent / 'static'
21+
ICONS = Icons.from_icon_folder(STATICS_PATH / 'icons')
22+
EXTERNAL_LINKS = {
23+
'payment': 'https://www.tinkoff.ru/cf/7T9naweBKz8',
24+
'repository': 'https://github.com/stepanzh/Proportio',
25+
}
26+
PROJECT_CONTACTS = {
27+
'Stepan': Contact(
28+
firstname='Степан',
29+
secondname='Захаров',
30+
telegram='https://t.me/red_deer',
31+
github='https://github.com/stepanzh',
32+
),
33+
'Alexandra': Contact(
34+
firstname='Александра',
35+
secondname='Кобец',
36+
telegram='https://t.me/kobets_dez',
37+
),
38+
'Mariya': Contact(
39+
firstname='Мария',
40+
secondname='Лукьянова',
41+
telegram='https://t.me/Marnia_made'
42+
),
43+
}
44+
45+
JQUERY = {
46+
'debug': JqueryCDN(
47+
src="https://code.jquery.com/jquery-3.6.2.js",
48+
integrity="sha256-pkn2CUZmheSeyssYw3vMp1+xyub4m+e+QK4sQskvuo4="
49+
),
50+
'deploy': JqueryCDN(
51+
src="https://code.jquery.com/jquery-3.6.3.slim.min.js",
52+
integrity="sha256-ZwqZIVdD3iXNyGHbSYdsmWP//UBokj2FHAxKuSBKDSo="
53+
),
54+
}
55+
56+
57+
class Page:
58+
name: str
59+
template_variables: dict
60+
61+
def extend_template_variables(self, variables: dict):
62+
self.template_variables |= variables
63+
64+
65+
class PageWriter:
66+
def __init__(self, environment: jinja2.Environment, rootdir: pathlib.Path):
67+
self.environment = environment
68+
self.rootdir = rootdir
69+
70+
def write(self, page: Page):
71+
filename = page.name + '.html'
72+
template = self.environment.get_template(filename)
73+
text = template.render(page.template_variables)
74+
with open(self.rootdir / filename, 'w') as io:
75+
print(text, file=io)
76+
77+
78+
class IndexPage(Page):
79+
name = 'index'
80+
template_variables = {
81+
'contacts': PROJECT_CONTACTS,
82+
'icons': ICONS,
83+
'links': EXTERNAL_LINKS,
84+
'jquery': JQUERY,
85+
}
86+
87+
88+
class PromoPage(Page):
89+
name = 'promo'
90+
template_variables = dict()
91+
92+
93+
class ExamplePage(Page):
94+
name = 'examples'
95+
96+
def __init__(self):
97+
recipe_dir = STATICS_PATH / 'example_recipes'
98+
99+
recipes = sorted(
100+
map(
101+
lambda path: RecipeExample.from_json_path(path),
102+
filter(lambda x: x.is_file(), recipe_dir.iterdir())
103+
),
104+
key=lambda recipe: recipe.title.lower(),
105+
)
106+
107+
self.template_variables = {
108+
'recipe_examples': recipes,
109+
}
110+
111+
112+
PAGES = [IndexPage(), PromoPage(), ExamplePage()]
113+
114+
115+
def parse_commandline():
116+
parser = argparse.ArgumentParser()
117+
parser.add_argument('-d', '--output_dir',
118+
required=True,
119+
help='output directory for site',
120+
)
121+
parser.add_argument('--debug',
122+
help='generate debuggable version of site',
123+
action=argparse.BooleanOptionalAction,
124+
default=False,
125+
)
126+
args = parser.parse_args()
127+
return args
128+
129+
def main():
130+
args = parse_commandline()
131+
132+
environment = jinja2.Environment(loader=jinja2.FileSystemLoader(TEMPLATES_PATH))
133+
134+
pagewriter = PageWriter(environment, pathlib.Path(args.output_dir))
135+
for page in PAGES:
136+
page.extend_template_variables({
137+
'isdebug': args.debug,
138+
})
139+
pagewriter.write(page)
140+
141+
142+
if __name__ == '__main__':
143+
main()

builder/contact.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from dataclasses import dataclass
2+
import typing
3+
4+
5+
@dataclass
6+
class Contact:
7+
firstname: str = ""
8+
secondname: str = ""
9+
telegram: str = ""
10+
github: str = ""

builder/icons.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import pathlib
2+
import typing
3+
4+
5+
class Icons:
6+
def __init__(self):
7+
self.inner = dict()
8+
9+
def __getattr__(self, icon_name):
10+
if icon_name not in self.inner.keys():
11+
raise AttributeError()
12+
return self.inner[icon_name]
13+
14+
@classmethod
15+
def from_icon_folder(cls, folder_path: pathlib.Path):
16+
assert folder_path.is_dir(), f"Icon folder does not exist: {folder_path}"
17+
18+
icons = Icons()
19+
20+
for child in folder_path.iterdir():
21+
if not child.is_file():
22+
continue
23+
with open(child) as io:
24+
icons.inner[child.stem] = io.read().strip()
25+
return icons

builder/recipe.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from dataclasses import dataclass
2+
import json
3+
import pathlib
4+
import typing
5+
6+
7+
@dataclass
8+
class RecipeCredits:
9+
author: str = ''
10+
recipe_url: str = ''
11+
12+
@classmethod
13+
def from_dict(cls, d):
14+
if d is None:
15+
return RecipeCredits()
16+
17+
author = d.get('author', '')
18+
recipe_url = d.get('recipe_url', '')
19+
return RecipeCredits(author, recipe_url)
20+
21+
@property
22+
def is_empty(self):
23+
return (not self.author) and (not self.recipe_url)
24+
25+
26+
@dataclass
27+
class RecipeExample:
28+
title: str
29+
ingredients: typing.List[str]
30+
relative_url: str
31+
credits: RecipeCredits
32+
33+
@property
34+
def ingredients_as_string(self):
35+
ingredients = list(map(lambda x: str(x).lower(), self.ingredients))
36+
ingredients[0] = ingredients[0].capitalize()
37+
s = ', '.join(ingredients)
38+
return s
39+
40+
@classmethod
41+
def from_json_path(cls, json_path):
42+
with open(json_path) as io:
43+
recipe = json.loads(io.read())
44+
45+
title = recipe.get('title', '').replace(' - ', ' — ')
46+
ingredients = [item['name'] for item in recipe.get('original_items')]
47+
# TODO: Should be refactored
48+
relative_url = json_path.relative_to(
49+
pathlib.Path(__file__).parent.parent / 'static'
50+
)
51+
credits = RecipeCredits.from_dict(recipe.get('credits'))
52+
53+
return RecipeExample(title=title, ingredients=list(ingredients), relative_url=relative_url, credits=credits)

examples_page_builder.py

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

0 commit comments

Comments
 (0)