Skip to content

Commit

Permalink
Add prettier & ruff formatting, delete requirements.txt
Browse files Browse the repository at this point in the history
  • Loading branch information
sondregronas committed Aug 20, 2024
1 parent 8a41511 commit b995948
Show file tree
Hide file tree
Showing 22 changed files with 527 additions and 372 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*.DS_Store
.DS_Store?
.cache
*.egg-info

tailwind.css

Expand All @@ -12,3 +13,7 @@ turtleconvert/
.idea/
venv/
__pycache__/


node_modules/
package-lock.json
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ WORKDIR /app

COPY requirements.txt /app

RUN pip install -U pip && pip install setuptools wheel && pip install -q -r requirements.txt
RUN pip install -U pip && pip install .

RUN pip install gunicorn

Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@

[![GitHub Pages](https://badgen.net/badge/visit/github%20pages/?icon=chrome)](https://piggy.iktim.no)

Install `pip install .`

Install dev `pip install .[dev]` + `npm install`

🚧 Construction in progress. 🚧
20 changes: 20 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"devDependencies": {
"prettier": "^3.3.3",
"prettier-plugin-jinja-template": "^1.4.1",
"tailwindcss": "^3.4.10"
},
"prettier": {
"plugins": [
"prettier-plugin-jinja-template"
],
"overrides": [
{
"files": "*.html",
"options": {
"parser": "jinja-template"
}
}
]
}
}
12 changes: 6 additions & 6 deletions piggy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@

from piggy.piggybank import generate_piggymap

PIGGYBANK_FOLDER = Path('piggybank')
PIGGYBANK_FOLDER = Path("piggybank")
PIGGYMAP = generate_piggymap(PIGGYBANK_FOLDER)

SUPPORTED_LANGUAGES = {
'': {'name': 'Norsk'}, # Default language
'eng': {'name': 'English'},
'ukr': {'name': 'Українська'},
"": {"name": "Norsk"}, # Default language
"eng": {"name": "English"},
"ukr": {"name": "Українська"},
}

# A prefix for the assignment URLs, to avoid conflicts with other routes
ASSIGNMENT_ROUTE = 'main'
ASSIGNMENT_ROUTE = "main"
# Media is on a different prefix to not compete with the assignment routes
MEDIA_ROUTE = 'img'
MEDIA_ROUTE = "img"
71 changes: 35 additions & 36 deletions piggy/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,83 +16,82 @@


def create_app():
app = Flask(__name__, static_folder='static')
app = Flask(__name__, static_folder="static")

assignment_routes = Blueprint(ASSIGNMENT_ROUTE, __name__, url_prefix=f'/{ASSIGNMENT_ROUTE}')
media_routes = Blueprint(MEDIA_ROUTE, __name__, url_prefix=f'/{MEDIA_ROUTE}')
assignment_routes = Blueprint(ASSIGNMENT_ROUTE, __name__, url_prefix=f"/{ASSIGNMENT_ROUTE}")
media_routes = Blueprint(MEDIA_ROUTE, __name__, url_prefix=f"/{MEDIA_ROUTE}")

generate_static_files_wrapper()

@app.context_processor
def context_processor():
"""Context variables for all templates in the app."""
return {'ASSIGNMENT_URL_PREFIX': ASSIGNMENT_ROUTE,
'MEDIA_URL_PREFIX': MEDIA_ROUTE,
'piggymap': PIGGYMAP,
'img_fmt': 'webp'}

@app.route('/')
return {
"ASSIGNMENT_URL_PREFIX": ASSIGNMENT_ROUTE,
"MEDIA_URL_PREFIX": MEDIA_ROUTE,
"piggymap": PIGGYMAP,
"img_fmt": "webp",
}

@app.route("/")
@lru_cache_wrapper
def index():
html = '<h1>Velkommen til Piggy!</h1>'
html = "<h1>Velkommen til Piggy!</h1>"
html += f'\n<a href="/{ASSIGNMENT_ROUTE}"><button>Oppgaver</button></a>'
return html

@assignment_routes.route(f'/<path:path>')
@assignment_routes.route(f'/')
@assignment_routes.route("/<path:path>")
@assignment_routes.route("/")
@lru_cache_wrapper
def get_assignment_directory(path=''):
def get_assignment_directory(path=""):
"""
Render the webpage for a given path.
Keeps track of template_type (assignments_root, year_level, class_name, subject, topic) based on the path.
Gets the relevant piggymap from the path. (key = child content, value = dict with relevant child data + meta)
"""
path = path.strip('/')
path = path.strip("/")

template_type = get_template_from_path(path)
metadata, segment = get_piggymap_segment_from_path(path, PIGGYMAP)

media_abspath = f'/{MEDIA_ROUTE}/{path}' if path else f'/{MEDIA_ROUTE}'
abspath = f'/{ASSIGNMENT_ROUTE}/{path}' if path else f'/{ASSIGNMENT_ROUTE}'
media_abspath = f"/{MEDIA_ROUTE}/{path}" if path else f"/{MEDIA_ROUTE}"
abspath = f"/{ASSIGNMENT_ROUTE}/{path}" if path else f"/{ASSIGNMENT_ROUTE}"

return render_template(template_type,
meta=metadata,
segment=segment,
path=path,
media_abspath=media_abspath,
abspath=abspath)
return render_template(
template_type, meta=metadata, segment=segment, path=path, media_abspath=media_abspath, abspath=abspath
)

@assignment_routes.route(f'/<year_level>/<class_name>/<subject>/<topic>/<assignment>')
def get_assignment(year_level, class_name, subject, topic, assignment, lang=''):
@assignment_routes.route("/<year_level>/<class_name>/<subject>/<topic>/<assignment>")
def get_assignment(year_level, class_name, subject, topic, assignment, lang=""):
"""
Render an assignment from the piggymap. Takes precedence over the wildcard route due to specificity.
"""
# TODO: Simplify path handling (less parameters)?
if request: # Caching doesn't work with request context
lang = request.cookies.get('lang', lang)
lang = request.cookies.get("lang", lang)
if lang:
assignment = f'translations/{lang}/{assignment}'
path = f'{PIGGYBANK_FOLDER}/{year_level}/{class_name}/{subject}/{topic}'
assignment = f"translations/{lang}/{assignment}"
path = f"{PIGGYBANK_FOLDER}/{year_level}/{class_name}/{subject}/{topic}"

return _render_assignment(Path(f'{path}/{assignment}.md'))
return _render_assignment(Path(f"{path}/{assignment}.md"))

@media_routes.route(f'/<path:wildcard>/media/<filename>')
@assignment_routes.route(f'/<path:wildcard>/attachments/<filename>')
@media_routes.route("/<path:wildcard>/media/<filename>")
@assignment_routes.route("/<path:wildcard>/attachments/<filename>")
def get_assignment_media_wildcard(wildcard, filename):
"""
Get a media file from either the media or attachments folder.
(only in MEDIA_URL_PREFIX or ASSIGNMENT_URL_PREFIX)
"""
if request.path.split('/')[1] == MEDIA_ROUTE:
wildcard = wildcard.replace(MEDIA_ROUTE, '', 1).strip('/')
folder = 'media'
if request.path.split("/")[1] == MEDIA_ROUTE:
wildcard = wildcard.replace(MEDIA_ROUTE, "", 1).strip("/")
folder = "media"
else:
folder = 'attachments'
folder = "attachments"
try:
return send_file(Path(f'{PIGGYBANK_FOLDER}/{wildcard}/{folder}/{filename}').absolute())
return send_file(Path(f"{PIGGYBANK_FOLDER}/{wildcard}/{folder}/{filename}").absolute())
except FileNotFoundError:
return send_file('static/img/placeholders/100x100.png')
return send_file("static/img/placeholders/100x100.png")

if not app.debug:
# Generate a cache of all assignment related pages
Expand Down
67 changes: 35 additions & 32 deletions piggy/caching.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,59 +11,62 @@


def lru_cache_wrapper(func):
if os.environ.get('USE_CACHE', '1') == '1':
if os.environ.get("USE_CACHE", "1") == "1":
return lru_cache()(func)
return func


def cache_directory(segment: dict,
directory_fn: Callable[[str], str],
assignment_fn: Callable[[Path], Response],
_path: str = ''):
def cache_directory(
segment: dict, directory_fn: Callable[[str], str], assignment_fn: Callable[[Path], Response], _path: str = ""
):
for key, value in segment.items():
print(f'Caching: {_path}/{key}')
directory_fn(f'{_path}/{key}'.strip('/'))
if _path.count('/') == 3:
for assignment, assignment_data in value.get('data', {}).items():
assignment_path = f'{_path}/{key}/{assignment}'.strip('/')
assignment_path = Path(f'{PIGGYBANK_FOLDER}/{assignment_path}.md')
print(f"Caching: {_path}/{key}")
directory_fn(f"{_path}/{key}".strip("/"))
if _path.count("/") == 3:
for assignment, assignment_data in value.get("data", {}).items():
assignment_path = f"{_path}/{key}/{assignment}".strip("/")
assignment_path = Path(f"{PIGGYBANK_FOLDER}/{assignment_path}.md")
assignment_fn(assignment_path)
[assignment_fn(Path(f'{assignment_path.parent}/translations/{lang}/{assignment}.md'))
for lang in SUPPORTED_LANGUAGES.keys()]
elif _path.count('/') > 3:
[
assignment_fn(Path(f"{assignment_path.parent}/translations/{lang}/{assignment}.md"))
for lang in SUPPORTED_LANGUAGES.keys()
]
elif _path.count("/") > 3:
return
else:
cache_directory(value.get('data', {}), f'{_path}/{key}')
cache_directory(value.get("data", {}), f"{_path}/{key}")


@lru_cache_wrapper
def _render_assignment(p: Path) -> Response:
"""Render an assignment from a Path object."""
if not p.exists():
# TODO: Raise a custom error
return Response('Error: Assignment not found', status=404)
return Response("Error: Assignment not found", status=404)
try:
sections = mdfile_to_sections(p)
print('Rendering:', p)
print("Rendering:", p)
except ConversionError:
# TODO: Raise a custom error
return Response('Error: Could not render assignment', status=500)
return Response("Error: Could not render assignment", status=500)

lang = ''
if p.parents[1].name == 'translations':
lang = ""
if p.parents[1].name == "translations":
lang = p.parent.name
current_language = SUPPORTED_LANGUAGES.get(lang, '')['name']
current_language = SUPPORTED_LANGUAGES.get(lang, "")["name"]

match = ASSIGNMENT_FILENAME_REGEX.match(p.name)

render = render_template('assignments/5-assignment.html',
content=sections,
current_language=current_language,
supported_languages=SUPPORTED_LANGUAGES,
path=p,
assignment_name=match.group(1).strip(),
level=match.group(2).strip(),
level_name=match.group(3).strip(),
media_abspath=f'/{MEDIA_ROUTE}/{p.parent}',
abspath=f'/{ASSIGNMENT_ROUTE}/{p}')
return Response(render, mimetype='text/html', status=200)
render = render_template(
"assignments/5-assignment.html",
content=sections,
current_language=current_language,
supported_languages=SUPPORTED_LANGUAGES,
path=p,
assignment_name=match.group(1).strip(),
level=match.group(2).strip(),
level_name=match.group(3).strip(),
media_abspath=f"/{MEDIA_ROUTE}/{p.parent}",
abspath=f"/{ASSIGNMENT_ROUTE}/{p}",
)
return Response(render, mimetype="text/html", status=200)
52 changes: 26 additions & 26 deletions piggy/piggybank.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,26 @@

def load_meta_json(path: Path):
try:
with open(path, 'r', encoding='utf-8') as f:
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
except FileNotFoundError:
data = {}
if 'name' not in data:
data['name'] = path.parent.name
if "name" not in data:
data["name"] = path.parent.name
return data


def get_piggymap_segment_from_path(path: str, piggymap: dict) -> tuple[dict, dict]:
"""Get the metadata and segment from a path."""
segment = dict(piggymap.copy())
meta = segment.get('meta', {})
for path in path.split('/'):
meta = segment.get("meta", {})
for path in path.split("/"):
if not path:
continue
if path not in segment:
return {}, {}
meta = segment.get(path, {}).get('meta', {})
segment = segment.get(path, {}).get('data', {})
meta = segment.get(path, {}).get("meta", {})
segment = segment.get(path, {}).get("data", {})

return meta, segment

Expand All @@ -37,14 +37,14 @@ def get_template_from_path(path: str) -> str:
"""Get the directory name from a path."""
# TODO: Use an enum?
nest_level_dirname = {
0: 'assignments/0-assignments_root',
1: 'assignments/1-year_level',
2: 'assignments/2-class_name',
3: 'assignments/3-subject',
4: 'assignments/4-topic',
0: "assignments/0-assignments_root",
1: "assignments/1-year_level",
2: "assignments/2-class_name",
3: "assignments/3-subject",
4: "assignments/4-topic",
}
path = path.split('/')
return nest_level_dirname.get(len([x for x in path if x]), 'unknown') + '.html'
path = path.split("/")
return nest_level_dirname.get(len([x for x in path if x]), "unknown") + ".html"


def generate_piggymap(path: Path, max_levels: int = 5, _current_level: int = 0):
Expand All @@ -68,27 +68,27 @@ def generate_piggymap(path: Path, max_levels: int = 5, _current_level: int = 0):
return None
for item in os.listdir(path):
# If the item is a directory, we want to go deeper
if os.path.isdir(f'{path}/{item}'):
new_item = generate_piggymap(Path(f'{path}/{item}'), _current_level=_current_level + 1)
if os.path.isdir(f"{path}/{item}"):
new_item = generate_piggymap(Path(f"{path}/{item}"), _current_level=_current_level + 1)
if new_item:
piggymap[item] = {'data': new_item}
piggymap[item] = {"data": new_item}
# If the folder contains a 'meta.json' file, we should add that as metadata to the folder
piggymap[item]['meta'] = load_meta_json(Path(f'{path}/{item}/meta.json'))
piggymap[item]["meta"] = load_meta_json(Path(f"{path}/{item}/meta.json"))
continue

# If the item is a file, we want to check if it's a valid assignment file
match = ASSIGNMENT_FILENAME_REGEX.match(item)
if not match:
continue
assignment_path = Path(f'{path}/{item}')
assignment_path = Path(f"{path}/{item}")
sections = mdfile_to_sections(assignment_path)

piggymap[item.replace('.md', '')] = {
'path': assignment_path,
'assignment_name': match.group(1).strip(),
'level': match.group(2).strip(),
'level_name': match.group(3).strip(),
'heading': sections['heading'],
'meta': sections['meta'],
piggymap[item.replace(".md", "")] = {
"path": assignment_path,
"assignment_name": match.group(1).strip(),
"level": match.group(2).strip(),
"level_name": match.group(3).strip(),
"heading": sections["heading"],
"meta": sections["meta"],
}
return piggymap
Loading

0 comments on commit b995948

Please sign in to comment.