From d48f27122a3940849fea05d38700279738819820 Mon Sep 17 00:00:00 2001 From: holly sparkles Date: Tue, 7 Feb 2023 13:32:04 +0100 Subject: [PATCH] feat: add script source files --- requirements.txt | 13 +++++++++++++ src/filters.py | 47 +++++++++++++++++++++++++++++++++++++++++++++++ src/livejrnl.py | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 183 insertions(+) create mode 100644 requirements.txt create mode 100644 src/filters.py create mode 100644 src/livejrnl.py diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..552bd07 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,13 @@ +arrow==1.2.3 +beautifulsoup4==4.11.2 +jinja-markdown==1.210911 +Jinja2==3.1.2 +jinja2-time==0.2.0 +Markdown==3.4.1 +MarkupSafe==2.1.2 +Pygments==2.14.0 +pymdown-extensions==9.9.2 +python-dateutil==2.8.2 +six==1.16.0 +soupsieve==2.3.2.post1 +strip-markdown==1.3 diff --git a/src/filters.py b/src/filters.py new file mode 100644 index 0000000..a3be80a --- /dev/null +++ b/src/filters.py @@ -0,0 +1,47 @@ +import strip_markdown +from datetime import datetime +import markdown + + +class TemplateFilters(): + """A collection of filters to use with Jinja2 templates""" + + def str_to_datetime(value: str, format: str = "%a, %d %b %Y %H:%M:%S %z", dt_offset="+0100") -> str: + """ + Formats a Jrnl date to the desired date string. + + Keyword arguments: + value -- the string to format + format -- the format to format the string to + dt_offset -- the timezone offset to use for DateTime formatting + """ + extracted_date: datetime = datetime.strptime( + value + dt_offset, "%Y-%m-%d %H:%M%z") + return extracted_date.strftime(format) + + def strip_markdown(value: str) -> str: + """ + Strips Markdown formatting from a string + + Keyword arguments: + value -- the string to strip formatting from + """ + return strip_markdown.strip_markdown(value) + + def strip_entry_tag(value: str) -> str: + """ + Removes the first character from a tag string. + + Keyword arguments: + value -- the string to strip the tag from + """ + return value[1:] + + def markdown_to_html(value: str) -> str: + """ + Formats Markdown as HTML + + Keyword arguments: + value -- the string to format as HTML + """ + return markdown.markdown(value) diff --git a/src/livejrnl.py b/src/livejrnl.py new file mode 100644 index 0000000..0ae28c1 --- /dev/null +++ b/src/livejrnl.py @@ -0,0 +1,123 @@ +from argparse import ArgumentParser +import subprocess +import json +from jinja2 import Environment, Template, FileSystemLoader +from filters import TemplateFilters +from pathlib import Path + + +def parse_args() -> dict: + """Parse arguments passed to the script and add it to a dictionary""" + parser: ArgumentParser = ArgumentParser( + prog="LiveJrnl", + description="Renders a Jrnl journal as a static site." + ) + + parser.add_argument("-t", "--template", type=str, + help="the template file to use to build the output", required=True) + parser.add_argument("-o", "--output", type=str, + help="the output file to write to", required=True) + parser.add_argument("-x", "--cutoff", type=int, + help="the maximum number of items to render", default=-1) + parser.add_argument("-c", "--config", type=str, + help="the configuration file to use for building your journal") + + args: dict = parser.parse_args() + return args + + +def get_journal() -> dict: + """Load a jrnl journal and return it as JSON""" + json_data: str = subprocess.getoutput("jrnl --format json") + return json.loads(json_data) + + +def get_default_config() -> dict: + """Create a default configuration for use with the included default template""" + default_config: dict = { + "title": "Ashley Robin's Journal", + "base_url": "https://localhost", + "description": "Write a bit about your website here.", + "author": "Ashley Robin", + "author_link": "https://localhost/arobin", + "year": "2023", + "language": "en", + "rss_language": "en-gb" + } + return default_config + + +def load_config(args: dict) -> str: + """Load JSON configuration from file or create a default one. + + Keyword arguments: + args -- arguments passed from parse_args() + """ + if args.config is None: + return get_default_config() + else: + with open(args.config, 'r') as config_file: + data = config_file.read() + return json.loads(data) + + +def generate_from_template(template: str, loaded_config: str) -> str: + """Generate output from a supplied template + + Keyword arguments: + template -- the path to the template to use for output + loaded_config -- a dictionary of extra information to render + """ + template_path: Path = Path(template) + if template_path.exists(): + jrnl_json: dict = get_journal() + + environment: Environment = Environment(loader=FileSystemLoader(template_path.parent), extensions=[ + 'jinja2_time.TimeExtension', 'jinja_markdown.MarkdownExtension']) + environment.filters['datetime'] = TemplateFilters.str_to_datetime + environment.filters['md2html'] = TemplateFilters.markdown_to_html + environment.filters['tagstrip'] = TemplateFilters.strip_entry_tag + + loaded_template: Template = environment.get_template( + template_path.name) + return loaded_template.render(jrnl_json, config=loaded_config) + return str() + + +def remove_empty_lines(string: str) -> str: + """Strip empty/blank lines from a string. + + Keyword arguments: + string -- the string to clean up + """ + + # https://stackoverflow.com/a/46416167 + return "".join([s for s in string.splitlines(True) if s.strip()]) + + +def write_output_file(filename: str, contents: str) -> None: + """Write data to file + + Keyword arguments: + filename -- the full path to the file to write to + contents -- the contents to write to file + """ + output_path: Path = Path(filename) + # create the output directory structure + output_path.parent.mkdir(parents=True, exist_ok=True) + # write the file, stripping empty lines + with open(output_path, "w") as output_file: + output_file.write(remove_empty_lines(contents)) + + +if __name__ == '__main__': + # get args + args: dict = parse_args() + # load config from file or default + config = load_config(args) + # add feed cutoff to the config if defined + if not args.cutoff is None: + config["cutoff"] = int(args.cutoff) + # generate the file and write to disk + contents = generate_from_template(args.template, config) + write_output_file(args.output, contents) -- libgit2 1.7.2