aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoruser <user@node5.net>2024-03-11 15:19:35 +0100
committeruser <user@node5.net>2024-03-11 15:19:35 +0100
commitb692ac3bafb0559a3d2c132187bafda1ce008ee7 (patch)
treed18510b28226a0c0f3466f80060f3a727a7310cb
parent8c956c553f36d9de7061ab27efb44d7e69ba52b7 (diff)
rename article_generator.py module -> article, make functions into article generator class
-rw-r--r--src/article.py177
-rw-r--r--src/article_generator.py177
-rw-r--r--src/blog_node5_net.py17
3 files changed, 186 insertions, 185 deletions
diff --git a/src/article.py b/src/article.py
new file mode 100644
index 0000000..afca0df
--- /dev/null
+++ b/src/article.py
@@ -0,0 +1,177 @@
+import dataclasses
+import datetime
+import os
+import logging
+import pathlib
+import glob
+import typing
+import markdown
+import yaml
+
+
+# Known exceptions, these are raised when generating on program exiting error, and are displayed to the user nicely
+class ArticleHandlerException(Exception):
+ pass
+
+
+class ArticleMetaDataMalformed(ArticleHandlerException):
+ pass
+
+
+class ArticleNoMetaData(ArticleHandlerException):
+ pass
+
+
+@dataclasses.dataclass
+class WebPage:
+ url: str
+ name: str
+
+
+@dataclasses.dataclass
+class MetaData():
+ description: str
+ created: datetime.date
+
+ @property
+ def pretty_print(self) -> str:
+ return f'''
+ Description: {self.description}
+ Created: {self.created}'''
+
+
+def truncate(text: str, max_length: int = 50):
+ if len(text) < max_length:
+ return text
+ else:
+ return text[0:max_length - 1]
+
+
+@dataclasses.dataclass
+class Article(WebPage):
+ metadata: MetaData
+ web_dir: tuple
+ source_path: str
+ source: str
+ html: str
+ modified: datetime.datetime
+ folder_path: typing.Union[None, str] = None
+
+ @property
+ def pretty_print(self) -> str:
+ return f'''
+Name: {self.name}
+Metadata: {self.metadata.pretty_print}
+Web dir: {self.web_dir}
+URL: {self.url}
+Source path: {self.source_path}
+Folder path: {self.folder_path}
+Modified: {self.modified}
+HTML: {truncate(self.html)}
+Source: {truncate(self.source)}'''
+
+
+@dataclasses.dataclass
+class Folder(WebPage):
+ articles: typing.List[Article] = dataclasses.field(default_factory=list)
+ sub_folders: typing.Dict[str, object] = dataclasses.field(default_factory=dict)
+
+
+class ArticleGenerator:
+ def __init__(self, articles_path: str):
+ self.articles_path = articles_path
+
+ def get_web_dir(self, path, name) -> tuple[str, ...]:
+ dir_structure = path.split(name)[0]
+ # Split into tuple, remove first part, assemble to path again, all to remove the first source dir agnostically
+ dir_structure_prefix_striped = pathlib.Path(dir_structure).parts[2:]
+ return dir_structure_prefix_striped
+
+ def parse_article_meta_data(self, source: str) -> typing.Tuple[str, MetaData]:
+ if source.startswith('---'):
+ meta_data_yml_end_char_index = source.find('---', 3)
+ meta_data_yml = source[3:meta_data_yml_end_char_index]
+ # Strip metadata text from source, before feeding it to the markdown reader
+ source = source[meta_data_yml_end_char_index + 3:]
+ meta_data = yaml.safe_load(meta_data_yml)
+
+ try:
+ meta_data = MetaData(**meta_data)
+ except TypeError as type_error_exception:
+ raise ArticleMetaDataMalformed(str(type_error_exception))
+ return source, meta_data
+ else:
+ raise ArticleNoMetaData(f'No metadata found')
+
+ def get_article(self, path: str) -> Article:
+ logging.debug(path)
+ article_args = {}
+ filename, file_extension = os.path.splitext(path)
+
+ basename = os.path.basename(filename)
+ if basename == 'index':
+ # Article is the folder
+ article_folder_name = os.path.dirname(path)
+ article_args['folder_path'] = article_folder_name
+ dir_basename = os.path.basename(article_folder_name)
+ article_args['name'] = dir_basename
+ else:
+ # Article one file
+ article_args['name'] = os.path.basename(filename)
+ article_args['web_dir'] = self.get_web_dir(path, article_args['name'])
+ article_args['source_path'] = path
+
+ article_args[
+ 'url'] = f'{"/" if article_args["web_dir"] else ""}{"/".join(article_args["web_dir"])}/{article_args["name"]}'
+
+ article_args['modified'] = datetime.datetime.utcfromtimestamp(os.path.getmtime(path)).replace(
+ tzinfo=datetime.datetime.now().astimezone().tzinfo)
+
+ with open(path, 'r') as file:
+ source = file.read()
+ article_args['source'], article_args['metadata'] = self.parse_article_meta_data(source)
+ article_args['html'] = markdown.markdown(article_args['source'], extensions=
+ [
+ 'fenced_code',
+ 'codehilite',
+ 'tables',
+ 'toc' # Automatically generates unique IDs for headers allowing for ID URL referral (Anchor)
+ ])
+
+ article = Article(**article_args)
+ return article
+
+ def discover_folder_structure(self, article: Article, articles: Folder):
+ previous_folder = articles
+ for folder_name in article.web_dir:
+ logging.debug(folder_name)
+
+ if folder_name not in previous_folder.sub_folders:
+ logging.debug('new')
+ current_folder = Folder(url=f'{previous_folder.url}{folder_name}/', name=folder_name)
+ previous_folder.sub_folders[folder_name] = current_folder
+ else:
+ logging.debug('reuse')
+ current_folder = previous_folder.sub_folders[folder_name]
+ previous_folder = current_folder
+ logging.debug('')
+
+ previous_folder.articles.append(article)
+
+ def sort_articles(self, folder: Folder):
+ folder.articles = sorted(folder.articles,
+ key=lambda
+ x: x.metadata.created if x.metadata.created is not None else datetime.date.min,
+ reverse=True)
+ for folder in folder.sub_folders.values():
+ self.sort_articles(folder)
+
+ def discover_articles(self):
+ articles_paths = glob.glob(f'{self.articles_path}/**/*.md', recursive=True) # Equivalent to ls ./**.md
+ articles = Folder(url='/', name='index')
+ for article_path in articles_paths:
+ article = self.get_article(article_path)
+ logging.debug(article.pretty_print)
+ self.discover_folder_structure(articles=articles, article=article)
+ self.sort_articles(articles)
+ return articles
diff --git a/src/article_generator.py b/src/article_generator.py
deleted file mode 100644
index 828f31a..0000000
--- a/src/article_generator.py
+++ /dev/null
@@ -1,177 +0,0 @@
-import dataclasses
-import datetime
-import os
-import logging
-import pathlib
-import glob
-import typing
-import markdown
-import yaml
-
-
-# Known exceptions, these are raised when generating on program exiting error, and are displayed to the user nicely
-class ArticleHandlerException(Exception):
- pass
-
-
-class ArticleMetaDataMalformed(ArticleHandlerException):
- pass
-
-
-class ArticleNoMetaData(ArticleHandlerException):
- pass
-
-
-@dataclasses.dataclass
-class WebPage:
- url: str
- name: str
-
-
-@dataclasses.dataclass
-class MetaData():
- description: str
- created: datetime.date
-
- @property
- def pretty_print(self) -> str:
- return f'''
- Description: {self.description}
- Created: {self.created}'''
-
-
-def truncate(text: str, max_length: int = 50):
- if len(text) < max_length:
- return text
- else:
- return text[0:max_length - 1]
-
-
-@dataclasses.dataclass
-class Article(WebPage):
- metadata: MetaData
- web_dir: tuple
- source_path: str
- source: str
- html: str
- modified: datetime.datetime
- folder_path: typing.Union[None, str] = None
-
- @property
- def pretty_print(self) -> str:
- return f'''
-Name: {self.name}
-Metadata: {self.metadata.pretty_print}
-Web dir: {self.web_dir}
-URL: {self.url}
-Source path: {self.source_path}
-Folder path: {self.folder_path}
-Modified: {self.modified}
-HTML: {truncate(self.html)}
-Source: {truncate(self.source)}'''
-
-
-@dataclasses.dataclass
-class Folder(WebPage):
- articles: typing.List[Article] = dataclasses.field(default_factory=list)
- sub_folders: typing.Dict[str, object] = dataclasses.field(default_factory=dict)
-
-
-def get_web_dir(path, name) -> tuple[str, ...]:
- dir_structure = path.split(name)[0]
- # Split into tuple, remove first part, assemble to path again, all to remove the first source dir agnostically
- dir_structure_prefix_striped = pathlib.Path(dir_structure).parts[2:]
- return dir_structure_prefix_striped
-
-
-def parse_article_meta_data(source: str) -> typing.Tuple[str, MetaData]:
- if source.startswith('---'):
- meta_data_yml_end_char_index = source.find('---', 3)
- meta_data_yml = source[3:meta_data_yml_end_char_index]
- # Strip metadata text from source, before feeding it to the markdown reader
- source = source[meta_data_yml_end_char_index + 3:]
- meta_data = yaml.safe_load(meta_data_yml)
-
- try:
- meta_data = MetaData(**meta_data)
- except TypeError as type_error_exception:
- raise ArticleMetaDataMalformed(str(type_error_exception))
- return source, meta_data
- else:
- raise ArticleNoMetaData(f'No metadata found')
-
-
-def get_article(path: str) -> Article:
- logging.debug(path)
- article_args = {}
- filename, file_extension = os.path.splitext(path)
-
- basename = os.path.basename(filename)
- if basename == 'index':
- # Article is the folder
- article_folder_name = os.path.dirname(path)
- article_args['folder_path'] = article_folder_name
- dir_basename = os.path.basename(article_folder_name)
- article_args['name'] = dir_basename
- else:
- # Article one file
- article_args['name'] = os.path.basename(filename)
- article_args['web_dir'] = get_web_dir(path, article_args['name'])
- article_args['source_path'] = path
-
- article_args[
- 'url'] = f'{"/" if article_args["web_dir"] else ""}{"/".join(article_args["web_dir"])}/{article_args["name"]}'
-
- article_args['modified'] = datetime.datetime.utcfromtimestamp(os.path.getmtime(path)).replace(
- tzinfo=datetime.datetime.now().astimezone().tzinfo)
-
- with open(path, 'r') as file:
- source = file.read()
- article_args['source'], article_args['metadata'] = parse_article_meta_data(source)
- article_args['html'] = markdown.markdown(article_args['source'], extensions=
- [
- 'fenced_code',
- 'codehilite',
- 'tables',
- 'toc' # Automatically generates unique IDs for headers allowing for ID URL referral (Anchor)
- ])
-
- article = Article(**article_args)
- return article
-
-
-def discover_folder_structure(article: Article, articles: Folder):
- previous_folder = articles
- for folder_name in article.web_dir:
- logging.debug(folder_name)
-
- if folder_name not in previous_folder.sub_folders:
- logging.debug('new')
- current_folder = Folder(url=f'{previous_folder.url}{folder_name}/', name=folder_name)
- previous_folder.sub_folders[folder_name] = current_folder
- else:
- logging.debug('reuse')
- current_folder = previous_folder.sub_folders[folder_name]
- previous_folder = current_folder
- logging.debug('')
-
- previous_folder.articles.append(article)
-
-
-def sort_articles(folder: Folder):
- folder.articles = sorted(folder.articles,
- key=lambda x: x.metadata.created if x.metadata.created is not None else datetime.date.min,
- reverse=True)
- for folder in folder.sub_folders.values():
- sort_articles(folder)
-
-
-def discover_articles(path):
- articles_paths = glob.glob(f'{path}/**/*.md', recursive=True) # Equivalent to ls ./**.md
- articles = Folder(url='/', name='index')
- for article_path in articles_paths:
- article = get_article(article_path)
- logging.debug(article.pretty_print)
- discover_folder_structure(articles=articles, article=article)
- sort_articles(articles)
- return articles
diff --git a/src/blog_node5_net.py b/src/blog_node5_net.py
index d4a2871..0782810 100644
--- a/src/blog_node5_net.py
+++ b/src/blog_node5_net.py
@@ -9,7 +9,7 @@ import urllib
import flask
import yaml
-import article_generator
+import article
import db_handler
import telegram_handler
@@ -19,12 +19,14 @@ logger = logging.getLogger(__name__)
site_root_folder_path = 'blog.node5.net'
+article_generator = article.ArticleGenerator(os.path.join(site_root_folder_path, 'articles'))
-def get_articles() -> article_generator.Folder:
+
+def get_articles() -> article.Folder:
try:
- articles = article_generator.discover_articles(os.path.join(site_root_folder_path, 'articles'))
+ articles = article_generator.discover_articles()
return articles
- except article_generator.ArticleHandlerException as exception: # Known exceptions
+ except article.ArticleHandlerException as exception: # Known exceptions
logger.error(exception)
exit(1) # Exit code 1: Code for generic error
@@ -40,8 +42,8 @@ app = flask.Flask(__name__, template_folder=os.path.join('..', site_root_folder_
with open(os.path.join(site_root_folder_path, 'motd.yml'), 'r') as file:
motd_list = yaml.safe_load(file.read())
-folders_by_url: typing.Dict[str, article_generator.Folder] = {}
-articles_by_url: typing.Dict[str, article_generator.Article] = {}
+folders_by_url: typing.Dict[str, article.Folder] = {}
+articles_by_url: typing.Dict[str, article.Article] = {}
@app.context_processor # Always inject site title to all render_templates
@@ -100,7 +102,7 @@ def view_article():
return flask.render_template('article.html', article=article)
-def register_urls(folder: article_generator.Folder):
+def register_urls(folder: article.Folder):
# Use recursion to traverse folder tree structure
if strip_trailing_slash(folder.url) not in folders_by_url.keys():
# If statement is because it can be called multiple times, which would cause an error
@@ -129,7 +131,6 @@ def load_articles():
register_urls(articles)
-
load_articles()