From 0a88e37e14ac07f0f725fabad58d4b694922eb2b Mon Sep 17 00:00:00 2001
From: Tim Neumann <git@neumann-tim.de>
Date: Tue, 26 Nov 2024 19:59:14 +0100
Subject: [PATCH] feat(templating): Add support for user defined variables

Also make program more resilient and handle errors appropriatly
---
 README.md        | 20 +++++++++++++
 pad_initiator.py | 73 +++++++++++++++++++++++++++++++-----------------
 2 files changed, 68 insertions(+), 25 deletions(-)

diff --git a/README.md b/README.md
index 761f886..bd835bb 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,26 @@ All whitespace is ignored. If a filename appears more then once in the file, the
 Example line: `README.md : my_reamde`
 This will result in the link to the pad being `my.server.com/my_readme`
 
+### Templating
+
+You can use [jinja2 templates](https://jinja.palletsprojects.com/en/stable/templates/) inside the markdown files.
+They will be templated before being added to the respective pad.
+
+The following variables are defined in the templating context:
+
+- `file_name` the "name" of the file being templated. This actually is the path relative to the cwd.
+- `links` a mapping of the file names (actually is the path relative to the cwd) to the link of the pad.
+  - These links are pre-populated with the human readable links if such a file is provided
+  - For pages where no human-readable link is defined, a placeholder link will be assigned intialliy
+  - Every time a pad is created the link actually returned by CodiMD is stored in the map (overriding any previous value)
+  - However, for links to another pad that is not yet created and does not have a human readable link, the placeholder will be used
+  - In a second round of templating we compare the templating result of the first round with the "final" result.
+  - If these results differ, the pad content needs to be updated.
+  - As there is no API for updating pad content we inform the user, that that needs to be done manually.
+- `user_vars` a map of user defined variables
+  - To set such a variable, set the environment variable `PI_USER_VAR_<MY_VAR_NAME>`
+  - The value of `user_vars[MY_VAR_NAME]` will then be set to the value of that environment variable
+
 ### Obtaining the connectsid
 To obtain the connectsid login to your HedgeDoc Server using your browser,
 open the Developer Tools, select the Network Tab and then open some note in HedgeDoc.
diff --git a/pad_initiator.py b/pad_initiator.py
index 8bed1bf..d5bae85 100755
--- a/pad_initiator.py
+++ b/pad_initiator.py
@@ -12,6 +12,9 @@ import argparse
 import requests
 
 
+USER_DEFINED_VARIABLES_PREFIX = "PI_USER_VAR_"
+
+
 class EnvDefault(argparse.Action):
     def __init__(self, envvar, required=True, default=None, **kwargs):
         if not default and envvar:
@@ -35,9 +38,17 @@ def templatePad(file_name: str, raw_data: str, links: Dict[str, str]) -> str:
     links: The links dict
     """
 
+    user_vars = {}
+
+    for key, value in environ.items():
+        if key.startswith(USER_DEFINED_VARIABLES_PREFIX):
+            var_name = key[len(USER_DEFINED_VARIABLES_PREFIX):]
+            user_vars[var_name] = value
+
     variables_for_template = {
         "file_name": file_name,
         "links": links,
+        "user_vars": user_vars
     }
     template = Template(source=raw_data, keep_trailing_newline=True)
     return template.render(variables_for_template)
@@ -78,41 +89,53 @@ def initPads(files: List[str],
     pad_content_hashes = {}
 
     for file_name in files:
-        if file_name in pre_configured_links:
-            link_name: str = pre_configured_links[file_name]
-            url += link_name
         with open(file_name) as f:
             data = f.read()
         pad_content = templatePad(file_name, data, all_the_links)
 
-        pad_content_hashes[file_name] = md5(pad_content.encode('utf-8')).digest()
+    for file_name in files:
+        try:
+            url_for_this_file = url
+            if file_name in pre_configured_links:
+                link_name: str = pre_configured_links[file_name]
+                url_for_this_file = url + link_name
+            with open(file_name) as f:
+                data = f.read()
+            pad_content = templatePad(file_name, data, all_the_links)
+
+            pad_content_hashes[file_name] = md5(pad_content.encode('utf-8')).digest()
 
-        r: requests.Response = requests.post(url, headers=headers, cookies=cookies, data=pad_content.encode('utf-8'), allow_redirects=False)
-        if not r.ok:
-            print(f"Failed to create pad for {file_name}. Status: {r.status_code}. Response: {r.content.decode('utf-8')}")
-        if not r.is_redirect:
-            print(f"Failed to create pad for {file_name}. Response was not a well formed redirect. Response: {r.content.decode('utf-8')}")
+            r: requests.Response = requests.post(url_for_this_file, headers=headers, cookies=cookies, data=pad_content.encode('utf-8'), allow_redirects=False)
+            if not r.ok:
+                print(f"Failed to create pad for {file_name}. Status: {r.status_code}. Response: {r.content.decode('utf-8')}")
+            if not r.is_redirect:
+                print(f"Failed to create pad for {file_name}. Response was not a well formed redirect. Response: {r.content.decode('utf-8')}")
 
-        created_page_link = r.next.url
+            created_page_link = r.next.url
 
-        all_the_links[file_name] = created_page_link
+            all_the_links[file_name] = created_page_link
 
-        result[file_name] = created_page_link
+            result[file_name] = created_page_link
+        except Exception as e:
+            print(f"Exception while templating first round for {file_name}", e)
 
     for file_name in files:
-        with open(file_name) as f:
-            data = f.read()
-        new_pad_content = templatePad(file_name, data, all_the_links)
-        new_pad_content_hash = md5(new_pad_content.encode('utf-8')).digest()
-
-        if new_pad_content_hash != pad_content_hashes[file_name]:
-            outfile = outdir + "/" + file_name
-            dir_of_outfile = path.dirname(outfile)
-            if not path.exists(dir_of_outfile):
-                makedirs(dir_of_outfile)
-            with open(outfile, 'w') as f:
-                f.write(new_pad_content)
-            print(f"The pad for {file_name} must be updated from {outfile}. It's URL is {result[file_name]}?edit. -- xclip -in -selection clipboard {outfile}")
+        try:
+            with open(file_name) as f:
+                data = f.read()
+            new_pad_content = templatePad(file_name, data, all_the_links)
+            new_pad_content_hash = md5(new_pad_content.encode('utf-8')).digest()
+
+            if new_pad_content_hash != pad_content_hashes[file_name]:
+                outfile = outdir + "/" + file_name
+                dir_of_outfile = path.dirname(outfile)
+                if not path.exists(dir_of_outfile):
+                    makedirs(dir_of_outfile)
+                with open(outfile, 'w') as f:
+                    f.write(new_pad_content)
+                print(f"The pad for {file_name} must be updated from {outfile}. It's URL is {result[file_name]}?edit. -- xclip -in -selection clipboard {outfile}")
+        except Exception as e:
+            print(f"Exception while templating second round for {file_name}", e)
 
     return result
 
-- 
GitLab