From f72f4ca8367afe7e3ab3a8725359cc32ce5a6318 Mon Sep 17 00:00:00 2001 From: Georgios Atheridis Date: Mon, 6 Feb 2023 03:02:02 +0000 Subject: [PATCH] Add Script and Basic Template Layout Structure a basic template layout that sigma can use to generate the static webpage. This will be used as a basis for creating the python program. Markdown files are converted to html files. Bracket syntax will be used to call variables, scripts or other templates. Python files will be executed. --- .flake8 | 6 ++ .gitignore | 5 + README | 19 ++++ data.toml | 45 +++++++++ pages/index.html | 2 + pages/posts/example.md | 43 +++++++++ pages/posts/other.md | 21 ++++ pages/posts/test.md | 3 + pages/textfile.txt | 3 + sigma | 161 +++++++++++++++++++++++++++++++ static/main.css | 15 +++ templates/base.html | 14 +++ templates/base.txt | 7 ++ templates/footer.html | 3 + templates/post.html | 7 ++ templates/scripts/build_nav.py | 21 ++++ templates/scripts/build_posts.py | 21 ++++ templates/scripts/build_table.py | 24 +++++ 18 files changed, 420 insertions(+) create mode 100644 .flake8 create mode 100644 .gitignore create mode 100644 data.toml create mode 100644 pages/index.html create mode 100644 pages/posts/example.md create mode 100644 pages/posts/other.md create mode 100644 pages/posts/test.md create mode 100644 pages/textfile.txt create mode 100755 sigma create mode 100644 static/main.css create mode 100644 templates/base.html create mode 100644 templates/base.txt create mode 100644 templates/footer.html create mode 100644 templates/post.html create mode 100644 templates/scripts/build_nav.py create mode 100644 templates/scripts/build_posts.py create mode 100644 templates/scripts/build_table.py diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..fa5e7f0 --- /dev/null +++ b/.flake8 @@ -0,0 +1,6 @@ +[flake8] +ignore = E203, W503 +max-line-length = 88 + +per-file-ignores = + templates/*: F821 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3fd0cad --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +out/ +venv/ +env/ +.venv +.env diff --git a/README b/README index 1ae5289..180c63a 100644 --- a/README +++ b/README @@ -1,2 +1,21 @@ sigma is a static site generator written in python ==================== + +data.toml: +It is used to define variables for each file. Variables in the root are +global and are inherited by their children. If a child has a variable of +the same name, then the child's variable is used instead. +-------------------- + +Keywords: +_PAGE_ROOT: specifies the default page root +_TEMPLATES: specifies the template directory +_OUT: designates the directory where the output files go + +self: the file which invoked the template +_extend: the template which the file should invoke +_value: value of a file + +_name: defaults to file name without extension +_date: defaults to the mtime of the file +-------------------- diff --git a/data.toml b/data.toml new file mode 100644 index 0000000..2ce5b4d --- /dev/null +++ b/data.toml @@ -0,0 +1,45 @@ +########################## +# Arguments # +########################## +_PAGE_ROOT = "pages" +_TEMPLATES = "templates" +_OUT = "out" + +########################## +# Variables # +########################## +_extend = "base.html" +_footer = "{% file 'footer.html' %}" +_title = "Atheridis' Page" +_owner = "Georgios Atheridis" +_email = "georgios@atheridis.org" + +[index] +_name = "Home" +_title = "My Blog" + +[posts] +_extend = "post.html" + +[posts.example] +_owner = "John Doe" +_email = "john_doe@example.com" +_date = "2023-02-04" +_title = "An example post" +_description = "This is a description for the example post" +_tags = ["example", "post", "sigma"] + +[posts.test] +_date = "2023-02-02" +_title = "Test" +_description = "This is a test" +_tags = ["test", "post"] + +[posts.other] +_title = "Another Post" +_description = "The description of another post" +_tags = ["other", "post"] +[posts.other._table] +name = ["Georgios Atheridis", "John Doe", "Jane Doe"] +email = ["georgios@atheridis.org", "johndoe@example.com", "janedoe@example.com"] +"phone number" = ["1234567890", "0777777777"] diff --git a/pages/index.html b/pages/index.html new file mode 100644 index 0000000..6c835c9 --- /dev/null +++ b/pages/index.html @@ -0,0 +1,2 @@ +

These are my posts

+{% file 'scripts/build_posts.py' %} diff --git a/pages/posts/example.md b/pages/posts/example.md new file mode 100644 index 0000000..5a8afd0 --- /dev/null +++ b/pages/posts/example.md @@ -0,0 +1,43 @@ +# Hello +## Hello +### Hello +#### Hello +##### Hello +###### Hello + +Using a variable: {{ self._owner }} + +1. This +1. Is +1. An +1. Ordered +1. List + + +* This +* Is +* Not +* Ordered + +--- + +Above me you will see a horizontal line + +as well as below me + +--- + +_this is how you write in italics_ + +**this is how you write in bold** + +~~And this is how you strikethrough~~ + +`this is a codeblock` + +``` +this is also a codeblock +and will output in monospaced font +``` + +[This is how you write a link](https://example.com) diff --git a/pages/posts/other.md b/pages/posts/other.md new file mode 100644 index 0000000..86cef3c --- /dev/null +++ b/pages/posts/other.md @@ -0,0 +1,21 @@ +# {{ self._title }} + +{{ self._description }} + +This is another file +this was posted on: {{ self._date }} + +--- + +### Manual Table +| name | email | +|---|---| +| {{ self._owner }} | {{ self._email }} | +| John Doe | johndoe@example.com | + +--- + +### Table generated in python +{% file 'scripts/build_table.py' %} + +--- diff --git a/pages/posts/test.md b/pages/posts/test.md new file mode 100644 index 0000000..74377fc --- /dev/null +++ b/pages/posts/test.md @@ -0,0 +1,3 @@ +# Testing + +This is a test diff --git a/pages/textfile.txt b/pages/textfile.txt new file mode 100644 index 0000000..e9691ea --- /dev/null +++ b/pages/textfile.txt @@ -0,0 +1,3 @@ +{% extend 'base.txt' %} +This is a text file +and should not be turned to html diff --git a/sigma b/sigma new file mode 100755 index 0000000..d17f8b4 --- /dev/null +++ b/sigma @@ -0,0 +1,161 @@ +#!/bin/python3 +# BSD 2-Clause License +# +# Copyright (c) 2023, Georgios Atheridis +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import argparse +import os +import re +import tomllib + +import markdown + + +regex_extend = re.compile(r"^{%\s?extend (\"|')(.+)\1\s?%}$") +regex_variable = re.compile(r"{{\s?(\S+)\s?}}") +regex_execute = re.compile(r"{%\s?file (\"|')(.+)\1\s?%}") + + +def md_to_html(md: str) -> str: + return markdown.markdown(md, extensions=["tables"]) + + +def initialize_values(data: dict): + pass + + +def get_value(data: dict, namespace: tuple, key: str): + value = data.get(key) + for namespace_item in namespace: + data = data.get(namespace_item, data) + value = data.get(key, value) + if value: + return value + if key == "_name": + return namespace[-1] + + +def update_value(data: dict, namespace: tuple, key: str, value): + for namespace_item in namespace: + data = data.setdefault(namespace_item, {}) + data[key] = value + + +def split_path(path: str) -> tuple: + rest, tail = os.path.split(path) + if rest in ("", os.path.sep): + return (tail,) + return split_path(rest) + (tail,) + + +def interpret(file_value: str, data: dict, namespace: tuple) -> str: + while regex_execute.search(file_value) or regex_variable.search(file_value): + while file_to_run := regex_execute.search(file_value): + file_to_run = file_to_run.group(2) + _, ext = os.path.splitext(file_to_run) + _value = "" + with open(os.path.join(data["_TEMPLATES"], file_to_run), "r") as f: + if ext == ".py": + d = {"data": data, "namespace": namespace, "get_value": get_value} + exec(f.read(), d) + _value = d["_value"] + elif ext == ".md": + _value = md_to_html(f.read()) + else: + _value = f.read() + file_value = regex_execute.sub(_value, file_value, 1) + while variable := regex_variable.search(file_value): + varspace = variable.group(1).split(".") + try: + varspace.remove("self") + except ValueError: + varspace = tuple(varspace) + else: + varspace = namespace + tuple(varspace) + file_value = regex_variable.sub( + str(get_value(data, varspace[:-1], varspace[-1])), file_value, 1 + ) + return file_value + + +def generate_output(file: str, data: dict, namespace: tuple) -> str: + _, file_type = os.path.splitext(file) + with open(file, "r") as in_file: + _value = in_file.read() + if result := regex_extend.search(_value.splitlines()[0]): + update_value(data, namespace, "_extend", result.group(2)) + _value = _value.removeprefix(result.group(0) + "\n") + _value = interpret(_value, data, namespace) + if file_type == ".md": + _value = md_to_html(_value) + update_value(data, namespace, "_value", _value) + + if _extend := get_value(data, namespace, "_extend"): + update_value(data, namespace, "_extend", "") + generate_output( + os.path.join(data["_TEMPLATES"], _extend), data, namespace + ) + return str(get_value(data, namespace, "_value")) + + +def main(args): + # Load toml file + data = tomllib.load(args.data) + args.data.close() + + # Assign default values if not set + if args.templates: + data["_TEMPLATES"] = args.templates + elif not data.get("_TEMPLATES"): + data["_TEMPLATES"] = "templates" + + if args.page_root: + data["_PAGE_ROOT"] = args.page_root + elif not data.get("_PAGE_ROOT"): + data["_PAGE_ROOT"] = "pages" + + if args.out: + data["_OUT"] = args.out + elif not data.get("_OUT"): + data["_OUT"] = "out" + + rel_path = os.path.relpath(args.page, data["_PAGE_ROOT"]) + rel_path_no_type, file_type = os.path.splitext(rel_path) + namespace = split_path(rel_path_no_type) + os.makedirs(os.path.join(data["_OUT"], os.path.split(rel_path)[0]), exist_ok=True) + if file_type == ".md": + rel_path = rel_path_no_type + ".html" + with open(os.path.join(data["_OUT"], rel_path), "w") as out_file: + out_file.write(generate_output(args.page, data, namespace)) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--page", type=str) + parser.add_argument("--page-root", type=str) + parser.add_argument("--templates", type=str) + parser.add_argument("--data", default="data.toml", type=argparse.FileType("rb")) + parser.add_argument("--out", type=str) + args = parser.parse_args() + main(args) diff --git a/static/main.css b/static/main.css new file mode 100644 index 0000000..58de5f9 --- /dev/null +++ b/static/main.css @@ -0,0 +1,15 @@ +body { + background: #DDDDDD; + color: #333333; + max-width: 900px; + margin: auto; + padding-top: 1rem; +} +nav a { + padding-left: 1rem; + padding-right: 1rem; +} +nav a:hover { + background: #350; + color: #ddd; +} diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..dc079ad --- /dev/null +++ b/templates/base.html @@ -0,0 +1,14 @@ + + + + {{ self._title }} + + + + {% file 'scripts/build_nav.py' %} +
+ {{ self._value }} +
+ {{ self._footer }} + + diff --git a/templates/base.txt b/templates/base.txt new file mode 100644 index 0000000..97b9ac3 --- /dev/null +++ b/templates/base.txt @@ -0,0 +1,7 @@ +This file was written by +{{ self._owner }} +on +{{ self._date }} +========================= + +{{ self._value }} diff --git a/templates/footer.html b/templates/footer.html new file mode 100644 index 0000000..8217545 --- /dev/null +++ b/templates/footer.html @@ -0,0 +1,3 @@ +
+ This is my footer on page +
diff --git a/templates/post.html b/templates/post.html new file mode 100644 index 0000000..296e3af --- /dev/null +++ b/templates/post.html @@ -0,0 +1,7 @@ +{% extend 'base.html' %} +

Here is my article

+

From: {{ self._owner }}

+

Date: {{ self._date }}

+
+ {{ self._value }} +
diff --git a/templates/scripts/build_nav.py b/templates/scripts/build_nav.py new file mode 100644 index 0000000..a2e5d45 --- /dev/null +++ b/templates/scripts/build_nav.py @@ -0,0 +1,21 @@ +import os + +_value = "" + +links = "" + +for page in os.listdir(data["_PAGE_ROOT"]): + if page.startswith(".") or page == "posts": + continue + if page == "index.html": + path = "/" + else: + path = "/" + page + page = os.path.splitext(page)[0] + + links += '{{ pages.%(page)s._name }}' % { + "path": path, + "page": page, + } + +_value = _value % links diff --git a/templates/scripts/build_posts.py b/templates/scripts/build_posts.py new file mode 100644 index 0000000..0345712 --- /dev/null +++ b/templates/scripts/build_posts.py @@ -0,0 +1,21 @@ +import os + +_value = "" diff --git a/templates/scripts/build_table.py b/templates/scripts/build_table.py new file mode 100644 index 0000000..30dc6f8 --- /dev/null +++ b/templates/scripts/build_table.py @@ -0,0 +1,24 @@ +_value = "%(head)s%(body)s
" +posts = "" + +table = get_value(data, namespace, "_table") +head = "" +body = "" + +max_index = 0 +for head_item in table: + head += f"{head_item}" + + body += f"{table[head_item][0]}" + max_index = max(max_index, len(table[head_item])) +body += "" +for i in range(1, max_index): + body += "" + for head_item in table: + try: + body += f"{table[head_item][i]}" + except IndexError: + body += "None" + body += "" + +_value = _value % {"head": head, "body": body} -- 2.30.2