about summary refs log tree commit diff
diff options
context:
space:
mode:
authorsternenseemann <0rpkxez4ksa01gb3typccl0i@systemli.org>2020-08-25 11:56:30 +0200
committersternenseemann <0rpkxez4ksa01gb3typccl0i@systemli.org>2020-08-25 11:56:30 +0200
commitedd4a7e4d5f4d5be6a5fc9db3f7173fc8dc81616 (patch)
treee30c66c6c7de62c555768fdad5798d849988975d
parent09809f4b19e25c6f479bb43fd1a50b407c07d815 (diff)
feat(template): pass context to template, simplify api
New struct template_data is passed to all template functions, allowing
to e. g. reflect the current entry in template_header and to unify
template_single_entry, template_index_entry, template_error into
template_main which switches behavior depending on data.page_type.

BREAKING CHANGES:

* remove template_single_entry, template_index_entry,
  template_error; replaced by template_main
* template_header, template_footer: type change

refactor(templates/simple): use xml_escape where applicable
feat(templates/simple): navigate using header / entry titles
feat(templates/simple): change <title> depending on subpage
-rw-r--r--TODO1
-rw-r--r--main.c41
-rw-r--r--template.h104
-rw-r--r--templates/simple.c131
4 files changed, 142 insertions, 135 deletions
diff --git a/TODO b/TODO
index 1470d43..a361d9c 100644
--- a/TODO
+++ b/TODO
@@ -6,6 +6,5 @@ add tests for timeutil | id:b6cd0665e5c376f52cf9702698330308aa722031
 tests for xml.c | id:b9e91d022be6bdc70e7ab082743826370b713c72
 improve man3 situation | id:bf918930f6361c30f288c42894cbf9b0541c3340
 use errno instead of returning HTTP status codes in make_index | id:bfa35c48dcedbecd3a951eba243c4840178289ed
-templating: unify single_entry and index_entry | id:e6dcb195d890bb0b5186716808d806101d81e4f6
 sandboxing, at least chroot | id:f209aec893fe310c8276f64f6ff4a3208a2a4f28
 document cgiutil, timeutil and stringutil | id:fd05b97115848a8b22231a477b8252373bd87264
diff --git a/main.c b/main.c
index 8571bdc..dfbfd5f 100644
--- a/main.c
+++ b/main.c
@@ -208,34 +208,55 @@ int main(void) {
 
     // confirm status and page_type match
     assert(status == 200 || page_type == PAGE_TYPE_ERROR);
+    assert(page_type != PAGE_TYPE_ERROR || status != 200);
+
+    // initial contents of data, changed in loop for PAGE_TYPE_INDEX
+    struct template_data data;
+    data.page_type = page_type;
+    data.status = status;
+    data.script_name = script_name;
+    data.path_info = path_info;
+
+    // confirm that we have SCRIPT_NAME and PATH_INFO unless an error occurred
+    assert(data.page_type != PAGE_TYPE_ERROR ||
+           (data.path_info != NULL && data.script_name != NULL));
+
+    // make sure that PAGE_TYPE_ENTRY will have an entry set in template_header
+    if(count > 0) {
+        data.entry = entries;
+    } else {
+        data.entry = NULL;
+    }
+    assert(page_type != PAGE_TYPE_ENTRY || data.entry != NULL);
+
     // render response
     if(page_type == PAGE_TYPE_ERROR) {
         send_standard_headers(status, "text/html");
 
-        template_header();
-        template_error(status);
-        template_footer();
+        template_header(data);
+        template_main(data);
+        template_footer(data);
     } else if(is_feed == FEED_TYPE_NONE) {
         // either PAGE_TYPE_INDEX or PAGE_TYPE_ENTRY
         send_standard_headers(200, "text/html");
 
-        template_header();
+        template_header(data);
 
         // confirm that PAGE_TYPE_ENTRY → count == 1
         assert(page_type != PAGE_TYPE_ENTRY || count == 1);
 
         for(int i = 0; i < count; i++) {
             if(entries[i].text != NULL || entry_get_text(&entries[i]) != -1) {
-                if(page_type == PAGE_TYPE_INDEX) {
-                    template_index_entry(entries[i]);
-                } else {
-                    template_single_entry(entries[i]);
-                }
+                data.entry = &entries[i];
+                assert(data.entry != NULL);
+
+                template_main(data);
+
                 entry_unget_text(&entries[i]);
             }
         }
 
-        template_footer();
+        template_footer(data);
     } else if(is_feed == FEED_TYPE_RSS) {
         blog_rss(script_name, entries, count);
     } else if(is_feed == FEED_TYPE_ATOM) {
diff --git a/template.h b/template.h
index 572f09f..043f59e 100644
--- a/template.h
+++ b/template.h
@@ -21,6 +21,28 @@
  */
 
 /*!
+ * @brief (Meta) data about the page being served
+ *
+ * `struct template_data` is used to pass information about
+ * the current page to the template. It is received as the
+ * single argument by all template functions.
+ *
+ * The following assumptions about its contents can be made:
+ *
+ * * `page_type == PAGE_TYPE_ENTRY` → `entry != NULL`
+ * * `page_type == PAGE_TYPE_ERROR` ⟷ `status != 200`
+ * * `page_type != PAGE_TYPE_ERROR` → `script_name != NULL && path_info != NULL`
+ * * `page_type == PAGE_TYPE_ERROR` → `entry == NULL`
+ */
+struct template_data {
+  enum page_type page_type;       //! type of page to render
+  int status;                     //! HTTP status of the response
+  struct entry *entry;            //! Pointer to entry if applicable, else `NULL`
+  char *script_name;              //! value of `SCRIPT_NAME` environment variable
+  char *path_info;                //! value of `PATH_INFO` environment variable
+};
+
+/*!
  * @brief Prints beginning of HTML source
  *
  * template_header() is expected to print out the common beginning of
@@ -29,11 +51,16 @@
  * first template function).
  *
  * Typically it will print the HTML `<head>` and the header part
- * of the `<body>` element which is common for all pages.
+ * of the `<body>` element which is common for all pages. It may
+ * adjust some parts of it (like headings, title, navigations, …)
+ * depending on the `data` that is passed.
  *
- * If you are using `xml.h`, this is a good place to call new_xml_context().
+ * If `data.page_type == PAGE_TYPE_INDEX`, `data.entry` will point
+ * to the first entry or be `NULL` if there are no entries.
+ *
+ * @see struct template_data
  */
-void template_header(void);
+void template_header(struct template_data data);
 
 /*!
  * @brief Prints end of HTML source
@@ -44,54 +71,31 @@ void template_header(void);
  * Usually this involves printing a footer part of the web page and
  * closing the `<body>` and `<html>` elements.
  *
- * If you are using `xml.h`, this is a good place to call del_xml_context().
- */
-void template_footer(void);
-
-/*!
- * @brief Prints HTML snippet for a single entry on the index page
- *
- * template_index_entry() is called for every entry that is part of
- * the index page. It should print out the repeating content structure
- * filled with the respective values for each entry.
- *
- * It gets passed a fully constructed `struct entry` with its `text`
- * field populated, i. e. entry_get_text() has been called
- *
- * This function is essentially like template_single_entry(), but:
- *
- * * It must make sure to output appropriate and correct HTML
- *   if called repeatedly
- * * Should reflect that it's an index page, i. e. link to
- *   the entries' individual pages and maybe display less
- *   detail than single pages.
- *
- * @see template_index_entry
- * @see struct entry
- * @see entry_get_text
+ * If `data.page_type == PAGE_TYPE_INDEX`, `data.entry` will point
+ * to the last entry or be `NULL` if there are no entries.
  */
-void template_index_entry(struct entry entry);
+void template_footer(struct template_data data);
 
 /*!
- * @brief Prints HTML snippet for an entry's own page
- *
- * template_single_entry() is like template_index_entry()
- * and receives a fully populated `struct entry` as well,
- * but it's a single page of an entry, so it may display
- * more details and link back to the index.
- *
- * @see struct entry
- * @see entry_get_text
- */
-void template_single_entry(struct entry entry);
-
-/*!
- * @brief Prints HTML snippet for an error page's main entry
- *
- * This should print main part of the page informing the user
- * about an error. It may display different messages depending
- * on the type of error.
- *
- * @param code HTTP status code of the error
+ * @brief Prints HTML snippet for the main part of the page
+ *
+ * template_main() should print the main part of the HTML source
+ * which is located between template_header() and template_footer().
+ *
+ * Depending on `data.page_type` the following applies:
+ *
+ * * For `PAGE_TYPE_ENTRY` template_main() is called once and
+ *   should print the main part of a single entry page.
+ * * For `PAGE_TYPE_ERROR` template_main() is called once and
+ *   should print the main part of a page informing the user
+ *   about an occurred HTTP error (reflecting `data.status`).
+ * * For `PAGE_TYPE_INDEX` template_main() is called 0 to n
+ *   times where n is the number of total entries. Each time
+ *   it's called it should print a HTML snippet which is
+ *   suitable as an index entry. Furthermore it should be
+ *   valid HTML regardless how many times it has been called
+ *   before and will be called afterwards.
+ *
+ * @see struct template_data
  */
-void template_error(int code);
+void template_main(struct template_data data);
diff --git a/templates/simple.c b/templates/simple.c
index eeee6db..9c9f20f 100644
--- a/templates/simple.c
+++ b/templates/simple.c
@@ -18,12 +18,12 @@ void output_entry_time(struct xml_context *ctx, struct entry entry) {
 
     if(flocaltime(strtime, HTML_TIME_FORMAT_READABLE, MAX_TIMESTR_SIZE, &entry.time) > 0) {
         xml_open_tag_attrs(ctx, "time", 1, "datetime", strtime);
-        xml_raw(ctx, strtime);
+        xml_escaped(ctx, strtime);
         xml_close_tag(ctx, "time");
     }
 }
 
-void template_header(void) {
+void template_header(struct template_data data) {
     new_xml_context(&ctx);
     ctx.warn = stderr;
     ctx.closing_slash = 0;
@@ -43,7 +43,13 @@ void template_header(void) {
    #endif
 
     xml_open_tag(&ctx, "title");
-    xml_raw(&ctx, BLOG_TITLE);
+    xml_escaped(&ctx, BLOG_TITLE);
+    if(data.page_type == PAGE_TYPE_ENTRY) {
+       xml_escaped(&ctx, ": ");
+       xml_escaped(&ctx, data.entry->title);
+    } else if(data.page_type == PAGE_TYPE_ERROR) {
+       xml_escaped(&ctx, ": error");
+    }
     xml_close_tag(&ctx, "title");
 
     xml_close_tag(&ctx, "head");
@@ -51,20 +57,22 @@ void template_header(void) {
     xml_open_tag(&ctx, "body");
     xml_open_tag(&ctx, "header");
     xml_open_tag(&ctx, "h1");
-    xml_raw(&ctx, BLOG_TITLE);
+    if(data.page_type != PAGE_TYPE_INDEX && data.script_name != NULL) {
+       xml_open_tag_attrs(&ctx, "a", 1, "href", data.script_name);
+    }
+    xml_escaped(&ctx, BLOG_TITLE);
     xml_close_including(&ctx, "header");
 
     xml_open_tag(&ctx, "main");
 }
 
-void template_footer(void) {
+void template_footer(struct template_data data) {
     xml_close_tag(&ctx, "main");
 
     xml_open_tag(&ctx, "footer");
 
-    char *script_name = getenv("SCRIPT_NAME");
-    char *rss_link = catn_alloc(2, script_name, "/rss.xml");
-    char *atom_link = catn_alloc(2, script_name, "/atom.xml");
+    char *rss_link = catn_alloc(2, data.script_name, "/rss.xml");
+    char *atom_link = catn_alloc(2, data.script_name, "/atom.xml");
 
     if(rss_link != NULL) {
         xml_open_tag_attrs(&ctx, "a", 1, "href", rss_link);
@@ -89,77 +97,52 @@ void template_footer(void) {
     del_xml_context(&ctx);
 }
 
-void template_single_entry(struct entry entry) {
-    xml_open_tag(&ctx, "article");
-
-    if(entry.text_size > 0) {
-        xml_open_tag_attrs(&ctx, "div", 1, "class", "content");
-        xml_raw(&ctx, entry.text);
-        xml_close_tag(&ctx, "div");
-    }
-
-    char *script_name = getenv("SCRIPT_NAME");
-
-    xml_open_tag_attrs(&ctx, "div", 1, "class", "meta");
-    xml_open_tag(&ctx, "ul");
-
-    // time of publishing
-    xml_open_tag(&ctx, "li");
-    output_entry_time(&ctx, entry);
-    xml_close_tag(&ctx, "li");
-
-    // link back to index
-    xml_open_tag(&ctx, "li");
-    xml_open_tag_attrs(&ctx, "a", 1, "href", script_name);
-    xml_raw(&ctx, "Back");
-    xml_close_tag(&ctx, "a");
-    xml_close_tag(&ctx, "li");
-
-    xml_close_including(&ctx, "article");
-}
+void template_main(struct template_data data) {
+    if(data.page_type == PAGE_TYPE_ERROR) {
+       xml_open_tag_attrs(&ctx, "div", 1, "class", "error-page");
+       xml_open_tag(&ctx, "h2");
+       xml_escaped(&ctx, "An error occured while handling your request");
+       xml_close_tag(&ctx, "h2");
+
+       xml_open_tag_attrs(&ctx, "div", 1, "class", "content");
+       xml_open_tag(&ctx, "p");
+
+       if(data.status == 500) {
+          xml_escaped(&ctx, "Something is wrong with this application and/or its server (error 500).");
+       } else if(data.status == 404) {
+          xml_escaped(&ctx, "What you requested doesn't exist (error 404).");
+       } else {
+          xml_escaped(&ctx, "The error encoutered is: ");
+          xml_escaped(&ctx, http_status_line(data.status));
+       }
+
+       xml_close_tag(&ctx, "p");
+       xml_close_tag(&ctx, "div");
+       xml_close_tag(&ctx, "div");
+    } else {
 
-void template_index_entry(struct entry entry) {
-    // TODO time?
-    xml_open_tag_attrs(&ctx, "article", 1, "id", entry.title);
-    if(entry.text_size > 0) {
-        xml_open_tag_attrs(&ctx, "div", 1, "class", "content index");
-        xml_raw(&ctx, entry.text);
-        xml_close_tag(&ctx, "div");
-    }
+       xml_open_tag(&ctx, "article");
 
-    xml_open_tag_attrs(&ctx, "div", 1, "class", "meta index");
-    xml_open_tag(&ctx, "p");
-    xml_open_tag_attrs(&ctx, "a", 1, "href", entry.link);
-    xml_raw(&ctx, "Permalink");
+       xml_open_tag(&ctx, "h2");
+       if(data.page_type == PAGE_TYPE_INDEX) {
+          xml_open_tag_attrs(&ctx, "a", 1, "href", data.entry->link);
+       }
+       xml_escaped(&ctx, data.entry->title);
+       xml_close_including(&ctx, "h2");
 
-    xml_close_including(&ctx, "article");
-}
+       if(data.entry->text_size > 0) {
+          xml_open_tag_attrs(&ctx, "div", 1, "class", "content");
+          xml_raw(&ctx, data.entry->text);
+          xml_close_tag(&ctx, "div");
+       }
 
-void template_error(int status) {
-    xml_open_tag_attrs(&ctx, "div", 1, "class", "error-page");
-    xml_open_tag(&ctx, "h2");
-    xml_raw(&ctx, "An error occured while handling your request");
-    xml_close_tag(&ctx, "h2");
+       xml_open_tag_attrs(&ctx, "div", 1, "class", "meta");
 
-    xml_open_tag(&ctx, "p");
+       // modification time
+       xml_open_tag_attrs(&ctx, "p", 1, "class", "mtime");
+       output_entry_time(&ctx, *data.entry);
+       xml_close_tag(&ctx, "p");
 
-    if(status == 500) {
-        xml_raw(&ctx, "Something is wrong with this application and/or its server (error 500).");
-    } else if(status == 404) {
-        xml_raw(&ctx, "What you requested doesn't exist (error 404).");
-    } else {
-       xml_raw(&ctx, "The error encoutered is: ");
-       xml_raw(&ctx, http_status_line(status));
+       xml_close_including(&ctx, "article");
     }
-
-    xml_close_tag(&ctx, "p");
-
-    char *script_name = getenv("SCRIPT_NAME");
-
-    xml_open_tag(&ctx, "nav");
-    xml_open_tag(&ctx, "p");
-    xml_open_tag_attrs(&ctx, "a", 1, "href", script_name);
-    xml_raw(&ctx, "Back");
-
-    xml_close_including(&ctx, "div");
 }