Generate Files from Directories
authorGeorgios Atheridis <georgios@atheridis.org>
Sun, 12 Feb 2023 00:10:33 +0000 (00:10 +0000)
committerGeorgios Atheridis <georgios@atheridis.org>
Sun, 12 Feb 2023 03:31:02 +0000 (03:31 +0000)
Can compile output files with the same name as a directory.  This
allows for easier control over variable definitions, as it keeps the
data.toml file smaller and can much more easily define multiline
variables--such as scripts.

Updated README, added a TODO list and added some more example pages that
show sigma's functionality.

README
TODO [new file with mode: 0644]
pages/about.html/_article.md [new file with mode: 0644]
pages/about.html/_test.py [new file with mode: 0644]
pages/about.html/_title.txt [new file with mode: 0644]
pages/about.html/_value.md [new file with mode: 0644]
pages/posts/example.md
pages/posts/python-code.html/_code.py [new file with mode: 0644]
pages/posts/python-code.html/_value.md [new file with mode: 0644]
sigma
templates/scripts/build_nav.py

diff --git a/README b/README
index 180c63aedc7bcc28a3ce4a72d230a11eeb4fe362..4df619599fcd24b1552c5d96a28b22bfba70bf5b 100644 (file)
--- a/README
+++ b/README
@@ -1,21 +1,52 @@
 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.
---------------------
+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
+---------
+- `_PAGE_ROOT` specifies the default page root
+- `_TEMPLATES` specifies the template directory
+- `_OUT` designates the directory where the output files go
+- `_DATE_FORMAT` designates the date format to use when using `_date`
+
++ `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, in case of a directory,
+  the latest mtime file within the directory is used.
 
-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
---------------------
+Functionality:
+--------------
+- Ability to use directories as page outputs.
+  ```
+  pages/
+  ├─ index.html
+  ├─ textfile.txt
+  ├─ posts/
+  │  ├─ example.md
+  │  ├─ other.md
+  │  ├─ test.md
+  │  ├─ python-code.html/
+  │  │  ├─ _value.md
+  │  │  ├─ _code.py
+  ├─ about.html/
+  │  ├─ _value.md
+  │  ├─ _title.txt
+  │  ├─ _article.md
+  │  ├─ _test.py
+  ```
+  Where the file names inside the directory become the variables of the
+  file.  Python files are not run here, as they will often be used to
+  write code which can then be imported into the _value variable.  Files
+  that are meant to be ran should go into the templates directory.
diff --git a/TODO b/TODO
new file mode 100644 (file)
index 0000000..d37fbc1
--- /dev/null
+++ b/TODO
@@ -0,0 +1,19 @@
+TODO
+====
+
+- Allow for a _build variable which will produce html files through
+  key-value pairs.  Directories will also be able to be created by
+  having another key-value pair inside of the value.  This will allow
+  for recursive creation of html files and directories.
+
+  If the _build variable is a python file inside of the pages directory
+  it will run the python file and the _value variable inside the file
+  will result in the value of the _build variable.
+
+- Do not update files that haven't changed.  Include data.toml values.
+
+- Allow any executable to be executed as a template.  Its standard
+  output becomes the _value of that template.  Make sure to transfer
+  appropriate data to the executable.
+
+- Add logging.
diff --git a/pages/about.html/_article.md b/pages/about.html/_article.md
new file mode 100644 (file)
index 0000000..43d7414
--- /dev/null
@@ -0,0 +1,7 @@
+This is my **article**
+
+#### We have lists
+
+- many good reasons
+- to choose lists
+- they're awesome
diff --git a/pages/about.html/_test.py b/pages/about.html/_test.py
new file mode 100644 (file)
index 0000000..46a4fdd
--- /dev/null
@@ -0,0 +1,6 @@
+def hello_world():
+    print("hello world")
+
+
+if __name__ == "__main__":
+    hello_world()
diff --git a/pages/about.html/_title.txt b/pages/about.html/_title.txt
new file mode 100644 (file)
index 0000000..6965192
--- /dev/null
@@ -0,0 +1 @@
+Hello Title
diff --git a/pages/about.html/_value.md b/pages/about.html/_value.md
new file mode 100644 (file)
index 0000000..9f9bfd7
--- /dev/null
@@ -0,0 +1,15 @@
+# This is a test
+
+## The title should be: {{ self._title }}
+
+with the following article:
+
+{{ self._article }}
+
+It was last edited on {{ self._date }}
+
+### Here we see the a python file
+
+```python
+{{ self._test }}
+```
index 5a8afd024a1c7cf6f7eeccbd3eb75ceddba5f345..5a0ba5484febca1edcd6faad96e46948c3c71d98 100644 (file)
@@ -1,3 +1,6 @@
+This *is a header* for
+===
+
 # Hello
 ## Hello
 ### Hello
 Using a variable: {{ self._owner }}
 
 1. This
-1. Is
-1. An
-1. Ordered
-1. List
-
+0. Is
+0. An
+0. Ordered
+3. List
 
 * This
 * Is
-* Not
-* Ordered
+- Not
++ Ordered
+
+> block
+> 
+> quote
 
 ---
 
@@ -25,13 +31,20 @@ Above me you will see a horizontal line
 
 as well as below me
 
----
+***
+
+<https://mylink.com>
+
+<email@example.com>
+
 
 _this is how you write in italics_
 
-**this is how you write in bold**
+*this is how you also write in italics*
+
+__this is how you write in bold__
 
-~~And this is how you strikethrough~~
+**this is how you also write in bold**
 
 `this is a codeblock`
 
diff --git a/pages/posts/python-code.html/_code.py b/pages/posts/python-code.html/_code.py
new file mode 100644 (file)
index 0000000..ce6ddb5
--- /dev/null
@@ -0,0 +1,5 @@
+print("hello world")
+
+print("{{ self._title }}")
+
+print("The above won't get changed if you called this file with {! !}")
diff --git a/pages/posts/python-code.html/_value.md b/pages/posts/python-code.html/_value.md
new file mode 100644 (file)
index 0000000..8968ece
--- /dev/null
@@ -0,0 +1,7 @@
+# Here is some python code inside my page
+
+<h1> test </h1>
+```python
+<h1> test </h1>
+{! self._code !}
+```
diff --git a/sigma b/sigma
index e03b4ee8853871402d7b400a30862f2280ed26bf..1f72c08e2da9855b69f3513cdae7f50cbf4af141 100755 (executable)
--- a/sigma
+++ b/sigma
@@ -35,15 +35,43 @@ import markdown
 
 regex_extend = re.compile(r"^{%\s?extend (\"|')(.+)\1\s?%}$")
 regex_variable = re.compile(r"{{\s?(\S+)\s?}}")
+regex_variable_no_interpret = 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"])
+    return markdown.markdown(md, extensions=["extra", "sane_lists"])
+
+
+def get_max_mtime(dir: str) -> float:
+    mtime = 0
+    for f in os.walk(dir):
+        mtime = max(
+            map(
+                os.path.getmtime,
+                map(lambda file: os.path.join(f[0], file), f[2]),
+            )
+        )
+    return mtime
 
 
 def initialize_values(data: dict):
     for dir in os.walk(data["_PAGE_ROOT"]):
+        _, ext = os.path.splitext(dir[0])
+        if ext:
+            rel_path = os.path.relpath(dir[0], data["_PAGE_ROOT"])
+            mtime = get_max_mtime(dir[0])
+            rel_path_no_type, _ = os.path.splitext(rel_path)
+            namespace = split_path(rel_path_no_type)
+            update_value(data, namespace, "_name", namespace[-1], False)
+            update_value(
+                data,
+                namespace,
+                "_date",
+                time.strftime(data["_DATE_FORMAT"], time.localtime(mtime)),
+                False,
+            )
+            continue
         for file in dir[2]:
             path = os.path.join(dir[0], file)
             rel_path = os.path.relpath(path, data["_PAGE_ROOT"])
@@ -60,7 +88,38 @@ def initialize_values(data: dict):
             )
 
 
-def get_value(data: dict, namespace: tuple, key: str):
+def get_value_from_file(
+    data: dict, namespace: tuple, key: str, interpret_ok: bool
+) -> str | None:
+    path = os.path.join(data["_PAGE_ROOT"], *namespace[:-1])
+    for dir in os.listdir(path):
+        dir_name, dir_ext = os.path.splitext(dir)
+        if namespace[-1] == dir_name:
+            break
+    else:
+        return
+
+    if not os.path.isdir(os.path.join(path, dir_name + dir_ext)):
+        return
+
+    for file in os.listdir(os.path.join(path, dir_name + dir_ext)):
+        file_name, file_ext = os.path.splitext(file)
+        if key == file_name:
+            with open(os.path.join(path, dir_name + dir_ext, file), "r") as file_in:
+                if interpret_ok:
+                    _value = interpret(file_in.read(), data, namespace)
+                else:
+                    _value = file_in.read()
+                if file_ext == ".md":
+                    _value = md_to_html(_value)
+            return _value
+
+
+def get_value(data: dict, namespace: tuple, key: str, interpret_ok=True):
+    if key != "_value" and (
+        value := get_value_from_file(data, namespace, key, interpret_ok)
+    ):
+        return value
     value = data.get(key)
     for namespace_item in namespace:
         data = data.get(namespace_item, data)
@@ -83,6 +142,27 @@ def split_path(path: str) -> tuple:
     return split_path(rest) + (tail,)
 
 
+def interpret_no_recursion(file_value: str, data: dict, namespace: tuple) -> str:
+    start_pos = 0
+    while variable := regex_variable_no_interpret.search(file_value, start_pos):
+        varspace = variable.group(1).split(".")
+        try:
+            varspace.remove("self")
+        except ValueError:
+            varspace = tuple(varspace)
+        else:
+            varspace = namespace + tuple(varspace)
+        repl_value = str(get_value(data, varspace[:-1], varspace[-1], False))
+        start_pos = variable.start() + len(repl_value)
+        regex_variable_no_interpret.search(file_value, start_pos)
+        file_value = file_value.replace(
+            variable.group(0),
+            str(get_value(data, varspace[:-1], varspace[-1], False)),
+            1,
+        )
+    return file_value
+
+
 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):
@@ -110,24 +190,45 @@ def interpret(file_value: str, data: dict, namespace: tuple) -> str:
             file_value = regex_variable.sub(
                 str(get_value(data, varspace[:-1], varspace[-1])), file_value, 1
             )
+
     return file_value
 
 
+def dir_to_file(file: str) -> dict[str, str]:
+    if not os.path.isdir(file):
+        return {"_value": file}
+    files = {}
+    for f in os.listdir(file):
+        files[os.path.splitext(f)[0]] = os.path.join(file, f)
+    return files
+
+
 def generate_output(file: str, data: dict, namespace: tuple) -> str:
-    _, file_type = os.path.splitext(file)
-    with open(file, "r") as in_file:
+    files = dir_to_file(file)
+    _, _value_file_type = os.path.splitext(files["_value"])
+    with open(files["_value"], "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":
+    if _value_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)
+
+    update_value(
+        data,
+        namespace,
+        "_value",
+        interpret_no_recursion(
+            str(get_value(data, namespace, "_value")), data, namespace
+        ),
+    )
+
     return str(get_value(data, namespace, "_value"))
 
 
@@ -162,6 +263,10 @@ def main(args):
     if not args.pages:
         pages = []
         for dir in os.walk(data["_PAGE_ROOT"]):
+            _, ext = os.path.splitext(dir[0])
+            if ext:
+                pages.append(dir[0])
+                continue
             for file in dir[2]:
                 pages.append(os.path.join(dir[0], file))
     else:
index a2e5d450cd7e13d67e9896089ffc64b0715b969c..46644f67be53fc24c3c8ac5e9acb32efac9e0c88 100644 (file)
@@ -13,7 +13,7 @@ for page in os.listdir(data["_PAGE_ROOT"]):
         path = "/" + page
     page = os.path.splitext(page)[0]
 
-    links += '<a href="%(path)s">{{ pages.%(page)s._name }}</a>' % {
+    links += '<a href="%(path)s">{{ %(page)s._name }}</a>' % {
         "path": path,
         "page": page,
     }