jack/jack/models.py

160 lines
4.7 KiB
Python

import logging
import os
import re
from datetime import datetime
from typing import List
import markdown
import yaml
LOGGER = logging.getLogger(__name__)
def extract_metadata_from_content(content):
BLOCK_RE = re.compile(
r"^-{3}[ \t]*\n(.*?\n)(?:\.{3}|-{3})[ \t]*\n", re.UNICODE | re.DOTALL
)
metadata = {}
if match := BLOCK_RE.match(content):
try:
metadata = yaml.safe_load(match.group(1))
content = content[match.end() :].lstrip("\n")
except Exception as exc:
LOGGER.warning("Unable to extract metadata: {}".format(exc))
return metadata, content
class Article:
"""An article is composed by a content, associated to facultative tags and a category"""
def __init__(self, file_content: str, relative_file_path: str):
self.meta, self.content = extract_metadata_from_content(file_content)
self.title = self.meta.get("title")
self.description = self.meta.get("description")
self.tags = self.meta.get("tags", [])
self.relative_file_path = relative_file_path
if self.relative_file_path:
self.sections = categories = [
x for x in relative_file_path.split(os.sep) if x
][:-1]
else:
self.sections = []
try:
self.published_date = datetime.strptime(self.sections[-1][0:10], "%Y-%m-%d")
except (IndexError, TypeError, ValueError):
self.published_date = None
md = markdown.Markdown(extensions=["fenced_code"])
self.html = md.convert(self.content)
def to_prose(self):
return self.html
def write(self, root_output_path):
output_file_path = os.path.join(root_output_path, "index.html")
LOGGER.warning("Writing file to {}".format(output_file_path))
with open(output_file_path, "w") as output_file:
output_file.write(self.to_prose())
class Section:
def __init__(self, key, parent=None):
self.key = key
self.parent = parent
self.sections = {}
self.articles = []
def add(self, article):
self.articles.append(article)
def write(self, root_path):
section_path = os.path.join(root_path, self.key)
LOGGER.warning("Writing section to {}".format(section_path))
with open(os.path.join(section_path, "index.html"), "w") as html_file:
html_file.write("""<html><body><p>{}</p></body></html>""".format(self.articles))
for child in self.sections.values():
child.write(section_path)
class Tag:
def __init__(self, name):
self.name = name
self.articles = []
def write(self, output_path):
absolute_output_path = os.path.join(output_path, "tags", self.name)
os.mkdir(absolute_output_path)
with open(os.path.join(absolute_output_path, "index.html"), "w") as html_file:
html_file.write("""<html><body><p>Test</p></body></html>""")
def __eq__(self, o):
if isinstance(o, type(Tag)) and o.name == self.name:
return True
return False
class Site:
"""A site contains articles, categories and all related data."""
def __init__(self, root_directory: str):
self.root_directory = root_directory
self.articles = []
self.sections = {}
self.tags = {}
def add(self, article: Article):
"""Add a new article to the current site
Returns:
The newly created article
"""
self.articles.append(article)
for tag in article.tags:
tag_key = self.tags.setdefault(tag, [])
tag_key.append(article)
self._fill_sections(article)
return article
def _fill_sections(self, article):
if article.sections:
section = article.sections[0]
section_key = self.sections.setdefault(section, Section(section))
section_key.add(article)
if len(article.sections) > 1:
for section in article.sections[1:]:
section_key = section_key.sections.setdefault(section, Section(section))
section_key.add(article)
def write(self):
LOGGER.info("Writing site articles")
for article in self.articles:
with open(os.path.join(self.root_directory, "index.html"), "w") as html_file:
html_file.write("""<html><body><p>{}</p></body></html>""".format(self.articles))
LOGGER.info("Writing site categories")
for section in self.sections.values():
section.write(self.root_directory)
LOGGER.info("Writing site tags")
for tag in self.tags:
tag.write(self.root_directory)
print(len(self.articles))