from dataclasses import dataclass
import json
from pathlib import Path
from mimetypes import guess_type
import flask
from markupsafe import Markup
import src.constants as const
PROJECT_PATH = Path(__file__).parent.parent
def test_all_suffixes(path: Path, suffixes: list[str]) -> Path | None:
"""
Tests different options of file suffix and check its validity.
"""
if path.suffix: # if suffix is present
if path.suffix in suffixes and path.exists(): # check its validity
return path
return None
if path.exists(): # if suffix not present and file exists
return path
for suffix in suffixes: # trying to find a suffix by brute force
if (spath := path.with_suffix(suffix)).exists():
return spath
return None
def is_hidden(file: Path) -> bool:
return file.name.startswith("__") or file.name.startswith(".")
def walk_subpath(path: Path, subpath: str, suffixes: list[str], hidden_func=is_hidden) -> Path:
"""
Iterates over directory names in path to find file.
"""
subpath_names = filter(bool, subpath.split("/"))
for name in subpath_names:
flask.g.rpath.add(name)
file = path / name
if hidden_func(file):
flask.abort(404)
file = test_all_suffixes(file, suffixes)
if not file:
flask.abort(404)
path = file
return path
def guess_type_and_load_media(path: Path, file_link: str) -> str:
"""
Loads media template for any media file.
"""
mime_type, _ = guess_type(path)
if not mime_type:
mime_type = "invalid/invalid"
return flask.render_template(
"source/media.html",
tag=mime_type.split("/")[0],
file_link=file_link,
mime_type=mime_type,
)
def load_content_file_template(path: Path) -> str:
with open(path) as file:
source = file.read()
# Place for the code for processing content files other than HTML
return flask.render_template_string(source)
def load_source_file_template(path: Path) -> str:
"""
Loads source (text or media) file template.
"""
if path.suffix in const.TEXT_SOURCE_SUFFIXES:
with open(path) as file:
source = file.read()
return flask.render_template("source/file.html", source=source, filename=path.name)
file_link = "/raw/" + str(path.relative_to(PROJECT_PATH))
return guess_type_and_load_media(path, file_link)
def is_hidden_source(file: Path) -> bool:
return file.name.startswith("__") or (file.name.startswith(".") and file.is_dir())
def content_filter(file: Path) -> bool:
return not is_hidden(file) and file.suffix in const.CONTENT_SUFFIXES
def source_filter(file: Path) -> bool:
return not is_hidden_source(file) and file.suffix in ("", *const.SOURCE_SUFFIXES)
def content_file_finder(subpath: str = "") -> str:
"""
Finds and loads content file template.
"""
path = walk_subpath(PROJECT_PATH / "content", subpath, const.CONTENT_SUFFIXES)
if path.is_dir():
flask.g.rpath.scan_dir(path, content_filter)
path /= const.DIR_DESCR_FILENAME
if not path.exists():
flask.abort(404)
return load_content_file_template(path)
def source_file_finder(subpath: str = "") -> str:
"""
Finds and loads source file template.
"""
flask.g.rpath.add("source")
path = walk_subpath(PROJECT_PATH, subpath, const.SOURCE_SUFFIXES, is_hidden_source)
if path.is_dir():
flask.g.rpath.scan_dir(path, source_filter)
return flask.render_template("source/folder.html")
else:
return load_source_file_template(path)
def raw_file_finder(subpath: str):
"""
Find and loads raw files directly from directories.
"""
flask.g.rpath.add("raw")
path = walk_subpath(PROJECT_PATH, subpath, const.SOURCE_SUFFIXES, is_hidden_source)
if not path.is_file():
flask.abort(404)
mimetype = None
if path.suffix in const.TEXT_SOURCE_SUFFIXES:
mimetype = "text/plain"
return flask.send_from_directory(
PROJECT_PATH,
path.relative_to(PROJECT_PATH),
mimetype=mimetype,
)
@dataclass
class Article:
"""
Dataclass representing meta information about the article.
"""
path: Path
link: str
file_content: str
date: str = "YYYY.MM.DD"
number: int = 0
def __lt__(self, other: "Article") -> bool:
return (self.date, self.number) < (other.date, other.number)
@staticmethod
def _load_meta_block(article: str):
return json.loads(flask.render_template_string(article, article_mode="meta"))
@classmethod
def parse(cls, article_path: Path, link: Path | str) -> "Article":
with article_path.open() as file:
file_content = file.read()
meta_info = cls._load_meta_block(file_content)
return cls(article_path, "/" + str(link), file_content, **meta_info)
def include(self, mode="preview") -> Markup:
template = flask.render_template_string(
self.file_content,
link=self.link,
article_mode=mode,
)
return Markup(template)
def get_articles(subpath: str = "/articles") -> list[Article]:
"""
Finds all articles in directory and loads information about it.
"""
suffixes = const.CONTENT_SUFFIXES
root = PROJECT_PATH / "content"
dir_path = walk_subpath(root, subpath, suffixes)
article_list: list[Article] = []
for file in dir_path.iterdir():
if is_hidden(file):
continue
if file.is_file() and file.suffix in suffixes:
article = Article.parse(file, file.relative_to(root))
elif (about := file / const.DIR_DESCR_FILENAME).exists():
article = Article.parse(about, file.relative_to(root))
else:
continue
article_list.append(article)
return sorted(article_list, reverse=True)