Add Script and Basic Template Layout
authorGeorgios Atheridis <georgios@atheridis.org>
Mon, 6 Feb 2023 03:02:02 +0000 (03:02 +0000)
committerGeorgios Atheridis <georgios@atheridis.org>
Mon, 6 Feb 2023 03:02:02 +0000 (03:02 +0000)
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.

18 files changed:
.flake8 [new file with mode: 0644]
.gitignore [new file with mode: 0644]
README
data.toml [new file with mode: 0644]
pages/index.html [new file with mode: 0644]
pages/posts/example.md [new file with mode: 0644]
pages/posts/other.md [new file with mode: 0644]
pages/posts/test.md [new file with mode: 0644]
pages/textfile.txt [new file with mode: 0644]
sigma [new file with mode: 0755]
static/main.css [new file with mode: 0644]
templates/base.html [new file with mode: 0644]
templates/base.txt [new file with mode: 0644]
templates/footer.html [new file with mode: 0644]
templates/post.html [new file with mode: 0644]
templates/scripts/build_nav.py [new file with mode: 0644]
templates/scripts/build_posts.py [new file with mode: 0644]
templates/scripts/build_table.py [new file with mode: 0644]

diff --git a/.flake8 b/.flake8
new file mode 100644 (file)
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 (file)
index 0000000..3fd0cad
--- /dev/null
@@ -0,0 +1,5 @@
+out/
+venv/
+env/
+.venv
+.env
diff --git a/README b/README
index 1ae5289b0a159f139ca39b4f070401806864a41f..180c63aedc7bcc28a3ce4a72d230a11eeb4fe362 100644 (file)
--- 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 (file)
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 (file)
index 0000000..6c835c9
--- /dev/null
@@ -0,0 +1,2 @@
+<h1>These are my posts</h1>
+{% file 'scripts/build_posts.py' %}
diff --git a/pages/posts/example.md b/pages/posts/example.md
new file mode 100644 (file)
index 0000000..5a8afd0
--- /dev/null
@@ -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 (file)
index 0000000..86cef3c
--- /dev/null
@@ -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 (file)
index 0000000..74377fc
--- /dev/null
@@ -0,0 +1,3 @@
+# Testing
+
+This is a test
diff --git a/pages/textfile.txt b/pages/textfile.txt
new file mode 100644 (file)
index 0000000..e9691ea
--- /dev/null
@@ -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 (executable)
index 0000000..d17f8b4
--- /dev/null
+++ b/sigma
@@ -0,0 +1,161 @@
+#!/bin/python3
+# BSD 2-Clause License
+#
+# Copyright (c) 2023, Georgios Atheridis <georgios@atheridis.org>
+#
+# 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 (file)
index 0000000..58de5f9
--- /dev/null
@@ -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 (file)
index 0000000..dc079ad
--- /dev/null
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>{{ self._title }}</title>
+        <link rel="stylesheet" href="/static/main.css" />
+    </head>
+    <body>
+        {% file 'scripts/build_nav.py' %}
+        <hr />
+        {{ self._value }}
+        <hr />
+        {{ self._footer }}
+    </body>
+</html>
diff --git a/templates/base.txt b/templates/base.txt
new file mode 100644 (file)
index 0000000..97b9ac3
--- /dev/null
@@ -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 (file)
index 0000000..8217545
--- /dev/null
@@ -0,0 +1,3 @@
+<footer>
+    This is my footer on page
+</footer>
diff --git a/templates/post.html b/templates/post.html
new file mode 100644 (file)
index 0000000..296e3af
--- /dev/null
@@ -0,0 +1,7 @@
+{% extend 'base.html' %}
+<h3>Here is my article</h3>
+<p>From: <a href="mailto:{{ self._email }}">{{ self._owner }}</a></p>
+<p>Date: {{ self._date }}</p>
+<article>
+    {{ self._value }}
+</article>
diff --git a/templates/scripts/build_nav.py b/templates/scripts/build_nav.py
new file mode 100644 (file)
index 0000000..a2e5d45
--- /dev/null
@@ -0,0 +1,21 @@
+import os
+
+_value = "<nav>%s</nav>"
+
+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 += '<a href="%(path)s">{{ pages.%(page)s._name }}</a>' % {
+        "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 (file)
index 0000000..0345712
--- /dev/null
@@ -0,0 +1,21 @@
+import os
+
+_value = "<ul>\n"
+
+posts = ""
+
+for post in os.listdir(os.path.join(data["_PAGE_ROOT"], "posts")):
+    path = "/posts/" + os.path.splitext(post)[0] + ".html"
+    post = os.path.splitext(post)[0]
+
+    posts += (
+        '<li> <span>{{ posts.%(post)s._date }}</span> <a href="%(path)s">'
+        "{{ posts.%(post)s._title }}</a> </li>\n"
+        % {
+            "path": path,
+            "post": post,
+        }
+    )
+
+_value += posts
+_value += "</ul>"
diff --git a/templates/scripts/build_table.py b/templates/scripts/build_table.py
new file mode 100644 (file)
index 0000000..30dc6f8
--- /dev/null
@@ -0,0 +1,24 @@
+_value = "<table><thead><tr>%(head)s</tr></thead><tbody>%(body)s</tbody></table>"
+posts = ""
+
+table = get_value(data, namespace, "_table")
+head = ""
+body = "<tr>"
+
+max_index = 0
+for head_item in table:
+    head += f"<th>{head_item}</th>"
+
+    body += f"<td>{table[head_item][0]}</td>"
+    max_index = max(max_index, len(table[head_item]))
+body += "</tr>"
+for i in range(1, max_index):
+    body += "<tr>"
+    for head_item in table:
+        try:
+            body += f"<td>{table[head_item][i]}</td>"
+        except IndexError:
+            body += "<td>None</td>"
+    body += "</tr>"
+
+_value = _value % {"head": head, "body": body}