From 521eade920d9760d3d09d1211ddf354139933fd8 Mon Sep 17 00:00:00 2001 From: Rickard Armiento Date: Fri, 27 Mar 2026 00:33:12 +0000 Subject: [PATCH 1/2] Add examples --- examples/README.md | 17 + examples/legacy/README.md | 14 + examples/legacy/blog/publish_legacy_blog.py | 6 + examples/legacy/blog/serve_legacy_blog.py | 6 + examples/legacy/blog/src/config.httkweb | 4 + .../legacy/blog/src/config_dynamic.httkweb | 5 + examples/legacy/blog/src/content/404.md | 6 + examples/legacy/blog/src/content/blog.md | 12 + .../src/content/blogposts/another_post.md | 13 + .../src/content/blogposts/hello-everyone.md | 13 + .../blog/src/content/blogposts/third_post.md | 13 + examples/legacy/blog/src/content/contact.md | 12 + examples/legacy/blog/src/content/index.md | 33 ++ examples/legacy/blog/src/functions/init.py | 39 ++ examples/legacy/blog/src/static/favicon.ico | Bin 0 -> 15086 bytes .../blog/src/static/img/.gitignore} | 0 .../legacy/blog/src/static/js/paginate.js | 104 ++++ .../blog/src/static/resources/css/httk.css | 342 ++++++++++++ .../blog/src/templates/404.httkweb.html | 2 + .../src/templates/base_default.httkweb.html | 43 ++ .../blog/src/templates/blog.httkweb.html | 46 ++ .../blog/src/templates/blogpost.httkweb.html | 26 + .../blog/src/templates/default.httkweb.html | 23 + .../publish_legacy_hello_world.py | 6 + .../serve_legacy_hello_world.py | 6 + .../legacy/hello_world_app/src/config.httkweb | 3 + .../hello_world_app/src/content/404.httkweb | 10 + .../hello_world_app/src/content/index.httkweb | 13 + .../src/functions/hello_world.py | 3 + .../hello_world_app/src/functions/init.py | 3 + .../src/static/img/Example.png | Bin 0 -> 2335 bytes .../src/static/resources/css/httkdemo.css | 40 ++ .../src/templates/base_default.httkweb.html | 45 ++ .../src/templates/default.httkweb.html | 2 + .../templates/hello_world_result.httkweb.html | 1 + .../publish_legacy_rst_templator.py | 6 + .../serve_legacy_rst_templator.py | 6 + examples/legacy/rst_templator/src/config.rst | 6 + .../rst_templator/src/config_dynamic.rst | 6 + .../legacy/rst_templator/src/content/404.rst | 12 + .../legacy/rst_templator/src/content/bare.rst | 13 + .../rst_templator/src/content/contact.rst | 11 + .../rst_templator/src/content/index.rst | 509 ++++++++++++++++++ .../rst_templator/src/static/img/Example.png | Bin 0 -> 2335 bytes .../src/static/resources/css/httkdemo.css | 40 ++ .../src/templates/bare.httkweb.html | 25 + .../src/templates/bare.templator.html | 26 + .../src/templates/base_default.httkweb.html | 52 ++ .../src/templates/base_default.templator.html | 52 ++ .../src/templates/default.httkweb.html | 1 + .../src/templates/default.templator.html | 2 + examples/legacy/search_app/example.sqlite | Bin 0 -> 98304 bytes .../search_app/publish_legacy_search_app.py | 6 + .../search_app/serve_legacy_search_app.py | 6 + examples/legacy/search_app/src/config.httkweb | 6 + .../legacy/search_app/src/content/404.httkweb | 10 + .../search_app/src/content/index.httkweb | 10 + .../search_app/src/functions/details.py | 29 + .../legacy/search_app/src/functions/init.py | 13 + .../legacy/search_app/src/functions/search.py | 49 ++ .../search_app/src/static/img/Example.png | Bin 0 -> 2335 bytes .../src/static/resources/css/httkdemo.css | 40 ++ .../src/templates/base_app.httkweb.html | 22 + .../src/templates/base_default.httkweb.html | 60 +++ .../src/templates/default.httkweb.html | 1 + .../templates/material_details.httkweb.html | 16 + .../src/templates/search_page.httkweb.html | 15 + .../src/templates/search_result.httkweb.html | 19 + .../publish_legacy_static_simple.py | 6 + .../serve_legacy_static_simple.py | 6 + .../legacy/static_simple/src/config.httkweb | 4 + .../static_simple/src/config_dynamic.httkweb | 4 + .../static_simple/src/content/404.httkweb | 10 + .../static_simple/src/content/bare.httkweb | 15 + .../static_simple/src/content/contact.httkweb | 13 + .../static_simple/src/content/index.httkweb | 34 ++ .../static_simple/src/static/img/Example.png | Bin 0 -> 2335 bytes .../src/static/resources/css/httkdemo.css | 40 ++ .../src/templates/404.httkweb.html | 2 + .../src/templates/bare.httkweb.html | 25 + .../src/templates/base_default.httkweb.html | 52 ++ .../src/templates/default.httkweb.html | 1 + examples/modern/blog/README.md | 19 + examples/modern/blog/publish.py | 6 + examples/modern/blog/serve.py | 6 + examples/modern/blog/src/content/blog.md | 6 + .../src/content/blogposts/hello-modern.md | 8 + .../src/content/blogposts/migration-notes.md | 8 + examples/modern/blog/src/content/contact.md | 6 + examples/modern/blog/src/content/index.md | 6 + examples/modern/blog/src/functions/init.py | 14 + .../blog/src/templates/base_default.html.j2 | 21 + .../blog/src/templates/blog_home.html.j2 | 13 + .../blog/src/templates/blog_index.html.j2 | 12 + .../blog/src/templates/blog_post.html.j2 | 5 + .../modern/blog/src/templates/default.html.j2 | 4 + examples/modern/minimal/README.md | 13 + examples/modern/minimal/public/index.html | 17 + examples/modern/minimal/publish.py | 6 + examples/modern/minimal/serve.py | 6 + examples/modern/minimal/src/content/index.md | 8 + .../src/templates/base_default.html.j2 | 13 + .../minimal/src/templates/default.html.j2 | 4 + examples/modern/rst_site/README.md | 19 + examples/modern/rst_site/publish.py | 6 + examples/modern/rst_site/serve.py | 6 + .../modern/rst_site/src/content/about.rst | 7 + .../modern/rst_site/src/content/index.rst | 14 + .../src/templates/base_default.html.j2 | 19 + .../rst_site/src/templates/default.html.j2 | 4 + examples/modern/search_app/README.md | 15 + examples/modern/search_app/publish.py | 6 + examples/modern/search_app/serve.py | 6 + .../modern/search_app/src/content/index.md | 8 + .../search_app/src/functions/details.py | 5 + .../modern/search_app/src/functions/init.py | 6 + .../modern/search_app/src/functions/search.py | 12 + .../src/templates/base_default.html.j2 | 13 + .../src/templates/material_details.html.j2 | 10 + .../src/templates/search_page.html.j2 | 21 + .../src/templates/search_results.html.j2 | 24 + src/httk/web/api.py | 25 +- src/httk/web/engine/site_engine.py | 106 +++- src/httk/web/model/config.py | 18 +- src/httk/web/renderers/_frontmatter.py | 2 + src/httk/web/templating/_legacy_formatter.py | 179 ++++++ src/httk/web/templating/httk_compat.py | 43 ++ tests/test_api.py | 108 ++++ 128 files changed, 3060 insertions(+), 15 deletions(-) create mode 100644 examples/README.md create mode 100644 examples/legacy/README.md create mode 100644 examples/legacy/blog/publish_legacy_blog.py create mode 100644 examples/legacy/blog/serve_legacy_blog.py create mode 100644 examples/legacy/blog/src/config.httkweb create mode 100644 examples/legacy/blog/src/config_dynamic.httkweb create mode 100644 examples/legacy/blog/src/content/404.md create mode 100644 examples/legacy/blog/src/content/blog.md create mode 100644 examples/legacy/blog/src/content/blogposts/another_post.md create mode 100644 examples/legacy/blog/src/content/blogposts/hello-everyone.md create mode 100644 examples/legacy/blog/src/content/blogposts/third_post.md create mode 100644 examples/legacy/blog/src/content/contact.md create mode 100644 examples/legacy/blog/src/content/index.md create mode 100644 examples/legacy/blog/src/functions/init.py create mode 100644 examples/legacy/blog/src/static/favicon.ico rename examples/{dummy.py => legacy/blog/src/static/img/.gitignore} (100%) create mode 100644 examples/legacy/blog/src/static/js/paginate.js create mode 100644 examples/legacy/blog/src/static/resources/css/httk.css create mode 100644 examples/legacy/blog/src/templates/404.httkweb.html create mode 100644 examples/legacy/blog/src/templates/base_default.httkweb.html create mode 100644 examples/legacy/blog/src/templates/blog.httkweb.html create mode 100644 examples/legacy/blog/src/templates/blogpost.httkweb.html create mode 100644 examples/legacy/blog/src/templates/default.httkweb.html create mode 100755 examples/legacy/hello_world_app/publish_legacy_hello_world.py create mode 100755 examples/legacy/hello_world_app/serve_legacy_hello_world.py create mode 100644 examples/legacy/hello_world_app/src/config.httkweb create mode 100644 examples/legacy/hello_world_app/src/content/404.httkweb create mode 100644 examples/legacy/hello_world_app/src/content/index.httkweb create mode 100644 examples/legacy/hello_world_app/src/functions/hello_world.py create mode 100644 examples/legacy/hello_world_app/src/functions/init.py create mode 100644 examples/legacy/hello_world_app/src/static/img/Example.png create mode 100644 examples/legacy/hello_world_app/src/static/resources/css/httkdemo.css create mode 100644 examples/legacy/hello_world_app/src/templates/base_default.httkweb.html create mode 100644 examples/legacy/hello_world_app/src/templates/default.httkweb.html create mode 100644 examples/legacy/hello_world_app/src/templates/hello_world_result.httkweb.html create mode 100644 examples/legacy/rst_templator/publish_legacy_rst_templator.py create mode 100644 examples/legacy/rst_templator/serve_legacy_rst_templator.py create mode 100644 examples/legacy/rst_templator/src/config.rst create mode 100644 examples/legacy/rst_templator/src/config_dynamic.rst create mode 100644 examples/legacy/rst_templator/src/content/404.rst create mode 100644 examples/legacy/rst_templator/src/content/bare.rst create mode 100644 examples/legacy/rst_templator/src/content/contact.rst create mode 100644 examples/legacy/rst_templator/src/content/index.rst create mode 100644 examples/legacy/rst_templator/src/static/img/Example.png create mode 100644 examples/legacy/rst_templator/src/static/resources/css/httkdemo.css create mode 100644 examples/legacy/rst_templator/src/templates/bare.httkweb.html create mode 100644 examples/legacy/rst_templator/src/templates/bare.templator.html create mode 100644 examples/legacy/rst_templator/src/templates/base_default.httkweb.html create mode 100644 examples/legacy/rst_templator/src/templates/base_default.templator.html create mode 100644 examples/legacy/rst_templator/src/templates/default.httkweb.html create mode 100644 examples/legacy/rst_templator/src/templates/default.templator.html create mode 100644 examples/legacy/search_app/example.sqlite create mode 100644 examples/legacy/search_app/publish_legacy_search_app.py create mode 100644 examples/legacy/search_app/serve_legacy_search_app.py create mode 100644 examples/legacy/search_app/src/config.httkweb create mode 100644 examples/legacy/search_app/src/content/404.httkweb create mode 100644 examples/legacy/search_app/src/content/index.httkweb create mode 100644 examples/legacy/search_app/src/functions/details.py create mode 100644 examples/legacy/search_app/src/functions/init.py create mode 100644 examples/legacy/search_app/src/functions/search.py create mode 100644 examples/legacy/search_app/src/static/img/Example.png create mode 100644 examples/legacy/search_app/src/static/resources/css/httkdemo.css create mode 100644 examples/legacy/search_app/src/templates/base_app.httkweb.html create mode 100644 examples/legacy/search_app/src/templates/base_default.httkweb.html create mode 100644 examples/legacy/search_app/src/templates/default.httkweb.html create mode 100644 examples/legacy/search_app/src/templates/material_details.httkweb.html create mode 100644 examples/legacy/search_app/src/templates/search_page.httkweb.html create mode 100644 examples/legacy/search_app/src/templates/search_result.httkweb.html create mode 100755 examples/legacy/static_simple/publish_legacy_static_simple.py create mode 100755 examples/legacy/static_simple/serve_legacy_static_simple.py create mode 100644 examples/legacy/static_simple/src/config.httkweb create mode 100644 examples/legacy/static_simple/src/config_dynamic.httkweb create mode 100644 examples/legacy/static_simple/src/content/404.httkweb create mode 100644 examples/legacy/static_simple/src/content/bare.httkweb create mode 100644 examples/legacy/static_simple/src/content/contact.httkweb create mode 100644 examples/legacy/static_simple/src/content/index.httkweb create mode 100644 examples/legacy/static_simple/src/static/img/Example.png create mode 100644 examples/legacy/static_simple/src/static/resources/css/httkdemo.css create mode 100644 examples/legacy/static_simple/src/templates/404.httkweb.html create mode 100644 examples/legacy/static_simple/src/templates/bare.httkweb.html create mode 100644 examples/legacy/static_simple/src/templates/base_default.httkweb.html create mode 100644 examples/legacy/static_simple/src/templates/default.httkweb.html create mode 100644 examples/modern/blog/README.md create mode 100644 examples/modern/blog/publish.py create mode 100644 examples/modern/blog/serve.py create mode 100644 examples/modern/blog/src/content/blog.md create mode 100644 examples/modern/blog/src/content/blogposts/hello-modern.md create mode 100644 examples/modern/blog/src/content/blogposts/migration-notes.md create mode 100644 examples/modern/blog/src/content/contact.md create mode 100644 examples/modern/blog/src/content/index.md create mode 100644 examples/modern/blog/src/functions/init.py create mode 100644 examples/modern/blog/src/templates/base_default.html.j2 create mode 100644 examples/modern/blog/src/templates/blog_home.html.j2 create mode 100644 examples/modern/blog/src/templates/blog_index.html.j2 create mode 100644 examples/modern/blog/src/templates/blog_post.html.j2 create mode 100644 examples/modern/blog/src/templates/default.html.j2 create mode 100644 examples/modern/minimal/README.md create mode 100644 examples/modern/minimal/public/index.html create mode 100644 examples/modern/minimal/publish.py create mode 100644 examples/modern/minimal/serve.py create mode 100644 examples/modern/minimal/src/content/index.md create mode 100644 examples/modern/minimal/src/templates/base_default.html.j2 create mode 100644 examples/modern/minimal/src/templates/default.html.j2 create mode 100644 examples/modern/rst_site/README.md create mode 100644 examples/modern/rst_site/publish.py create mode 100644 examples/modern/rst_site/serve.py create mode 100644 examples/modern/rst_site/src/content/about.rst create mode 100644 examples/modern/rst_site/src/content/index.rst create mode 100644 examples/modern/rst_site/src/templates/base_default.html.j2 create mode 100644 examples/modern/rst_site/src/templates/default.html.j2 create mode 100644 examples/modern/search_app/README.md create mode 100644 examples/modern/search_app/publish.py create mode 100644 examples/modern/search_app/serve.py create mode 100644 examples/modern/search_app/src/content/index.md create mode 100644 examples/modern/search_app/src/functions/details.py create mode 100644 examples/modern/search_app/src/functions/init.py create mode 100644 examples/modern/search_app/src/functions/search.py create mode 100644 examples/modern/search_app/src/templates/base_default.html.j2 create mode 100644 examples/modern/search_app/src/templates/material_details.html.j2 create mode 100644 examples/modern/search_app/src/templates/search_page.html.j2 create mode 100644 examples/modern/search_app/src/templates/search_results.html.j2 create mode 100644 src/httk/web/templating/_legacy_formatter.py diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..1bf3cdb --- /dev/null +++ b/examples/README.md @@ -0,0 +1,17 @@ +# Examples + +This directory contains runnable `httk-web` examples. + +- `modern/`: recommended v2-first examples (Jinja2 templates, modern metadata). +- `legacy/`: compatibility-mode examples that use deprecated legacy syntax/features. + +Modern includes: +- `minimal/` +- `rst_site/` (modern replacement for `rst_templator`) +- `blog/` (modern replacement for legacy blog example) +- `search_app/` (modern replacement for legacy search example) + +Legacy now includes migrated variants of the original `6_website` examples, +including blog/search-style examples. + +Legacy examples are kept to validate migration behavior and should not be used as the default style for new projects. diff --git a/examples/legacy/README.md b/examples/legacy/README.md new file mode 100644 index 0000000..9624a04 --- /dev/null +++ b/examples/legacy/README.md @@ -0,0 +1,14 @@ +# Legacy Examples + +These examples intentionally use deprecated legacy features and require compatibility mode. + +- `static_simple/` +- `hello_world_app/` +- `rst_templator/` +- `blog/` +- `search_app/` + +Use the provided `serve_legacy_*.py` and `publish_legacy_*.py` scripts. + +`search_app/` is included as a migrated legacy example, but it depends on legacy +`httk` database modules that are not part of `httk-web`. diff --git a/examples/legacy/blog/publish_legacy_blog.py b/examples/legacy/blog/publish_legacy_blog.py new file mode 100644 index 0000000..c654cfc --- /dev/null +++ b/examples/legacy/blog/publish_legacy_blog.py @@ -0,0 +1,6 @@ +from pathlib import Path + +from httk.web import publish + +ROOT = Path(__file__).parent +publish(ROOT / "src", ROOT / "public", "http://127.0.0.1/", compatibility_mode=True) diff --git a/examples/legacy/blog/serve_legacy_blog.py b/examples/legacy/blog/serve_legacy_blog.py new file mode 100644 index 0000000..e3f984f --- /dev/null +++ b/examples/legacy/blog/serve_legacy_blog.py @@ -0,0 +1,6 @@ +from pathlib import Path + +from httk.web import serve + +ROOT = Path(__file__).parent +serve(ROOT / "src", port=8080, compatibility_mode=True, config_name="config_dynamic") diff --git a/examples/legacy/blog/src/config.httkweb b/examples/legacy/blog/src/config.httkweb new file mode 100644 index 0000000..7a7c446 --- /dev/null +++ b/examples/legacy/blog/src/config.httkweb @@ -0,0 +1,4 @@ +--- +maintitle: My formidable website +menuitems-list: index, blog, contact +--- diff --git a/examples/legacy/blog/src/config_dynamic.httkweb b/examples/legacy/blog/src/config_dynamic.httkweb new file mode 100644 index 0000000..6ace46b --- /dev/null +++ b/examples/legacy/blog/src/config_dynamic.httkweb @@ -0,0 +1,5 @@ +--- +maintitle: My formidable website +menuitems-list: index, blog, contact +urls_without_ext: true +--- diff --git a/examples/legacy/blog/src/content/404.md b/examples/legacy/blog/src/content/404.md new file mode 100644 index 0000000..313b364 --- /dev/null +++ b/examples/legacy/blog/src/content/404.md @@ -0,0 +1,6 @@ +--- +Title: My Website - Missing page +Name: "404" +Template: "404" +--- +The page could not be found. diff --git a/examples/legacy/blog/src/content/blog.md b/examples/legacy/blog/src/content/blog.md new file mode 100644 index 0000000..3bd0295 --- /dev/null +++ b/examples/legacy/blog/src/content/blog.md @@ -0,0 +1,12 @@ +-------- +Title: My Website - Blog posts +Name: Blog +Template: blog +Base_template: base_default +-------- + +Blog +==== + +Below is a list of all blog posts available here. + diff --git a/examples/legacy/blog/src/content/blogposts/another_post.md b/examples/legacy/blog/src/content/blogposts/another_post.md new file mode 100644 index 0000000..f8acc9e --- /dev/null +++ b/examples/legacy/blog/src/content/blogposts/another_post.md @@ -0,0 +1,13 @@ +-------- +Title: My Website - Another post +Author: Rickard Armiento +Date: 2023-10-04 +Name: Another post +Template: blogpost +Base_template: base_default +-------- + +Another post +============ + +This is another post diff --git a/examples/legacy/blog/src/content/blogposts/hello-everyone.md b/examples/legacy/blog/src/content/blogposts/hello-everyone.md new file mode 100644 index 0000000..a4608d5 --- /dev/null +++ b/examples/legacy/blog/src/content/blogposts/hello-everyone.md @@ -0,0 +1,13 @@ +-------- +Title: My Website - Hello Everyone +Author: Rickard Armiento +Date: 2023-10-03 +Name: Hello Everyone +Template: blogpost +Base_template: base_default +-------- + +Hello everyone +============== + +Hello everyone, this is the first blog post. diff --git a/examples/legacy/blog/src/content/blogposts/third_post.md b/examples/legacy/blog/src/content/blogposts/third_post.md new file mode 100644 index 0000000..7b424b5 --- /dev/null +++ b/examples/legacy/blog/src/content/blogposts/third_post.md @@ -0,0 +1,13 @@ +-------- +Title: My Website - Third post +Author: Rickard Armiento +Date: 2023-10-05 +Name: Third post +Template: blogpost +Base_template: base_default +-------- + +Third post +========== + +This is the third post diff --git a/examples/legacy/blog/src/content/contact.md b/examples/legacy/blog/src/content/contact.md new file mode 100644 index 0000000..74eef13 --- /dev/null +++ b/examples/legacy/blog/src/content/contact.md @@ -0,0 +1,12 @@ +------- +Title: My Website - Contact +Name: Contact +Template: default +Base_template: base_default +------ + +Contact +======= + +Contact me here: someone@example.com + diff --git a/examples/legacy/blog/src/content/index.md b/examples/legacy/blog/src/content/index.md new file mode 100644 index 0000000..702570e --- /dev/null +++ b/examples/legacy/blog/src/content/index.md @@ -0,0 +1,33 @@ +----------- +Title: My Website +Name: Main +Date: 2023-09-27 +Version: 1 +Author: Rickard Armiento +Template: default +Base_template: base_default +----------- + +My website +========== + +This is my formidable website. It contains the following sections: + +* [Blog](blog) +* [Contact](contact) + +Subheading +---------- + +Here is some nice math: + +\(\int (x+y) dx\) + +And a code segment: + +``` python +x = 1 +if x == 1: + # indented four spaces + print("x is 1.") +``` diff --git a/examples/legacy/blog/src/functions/init.py b/examples/legacy/blog/src/functions/init.py new file mode 100644 index 0000000..3fb9ea3 --- /dev/null +++ b/examples/legacy/blog/src/functions/init.py @@ -0,0 +1,39 @@ +import datetime +import os +from pathlib import Path + + +def execute(global_data, **kargs): + prefix = Path(__file__).resolve().parents[1] / "content" + filterlist = [".md", ".rst", ".html"] + path = "blogposts" + + # Chicken-or-egg problem of having to partially render blog posts to sort them, but their renders refer to the other blogposts + global_data['blogposts'] = [] + global_data['blogposts_latest'] = [] + + def listdirsorted(path): + return [ + x[0] + for x in sorted( + [ + ( + fn, + datetime.datetime.strptime( + str(global_data['pages'](os.path.join(path, fn), 'date')), "%Y-%m-%d" + ), + ) + for fn in os.listdir(prefix / path) + ], + key=lambda x: x[1], + reverse=True, + ) + ] + + global_data['blogposts'] = [ + os.path.join(path, f) + for f in listdirsorted(path) + if os.path.isfile(prefix / path / f) and any([f.endswith(t) for t in filterlist]) + ] + + global_data['blogposts_latest'] = global_data['blogposts'][:5] diff --git a/examples/legacy/blog/src/static/favicon.ico b/examples/legacy/blog/src/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..89273a94a6a1f3b17f118a1427e33da7caf7517d GIT binary patch literal 15086 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 TfqQ0xYS(H{7`z|+qtE{XkXLaA literal 0 HcmV?d00001 diff --git a/examples/dummy.py b/examples/legacy/blog/src/static/img/.gitignore similarity index 100% rename from examples/dummy.py rename to examples/legacy/blog/src/static/img/.gitignore diff --git a/examples/legacy/blog/src/static/js/paginate.js b/examples/legacy/blog/src/static/js/paginate.js new file mode 100644 index 0000000..fc77e63 --- /dev/null +++ b/examples/legacy/blog/src/static/js/paginate.js @@ -0,0 +1,104 @@ +// Based on https://stackoverflow.com/a/72308579 by rootShiv + +function setup_pagination(table) { + var filter = $("#filter").val().toLowerCase(); + var lastPage = 1; + $("#maxRows").on("change", function (evt) { + lastPage = 1; + $(".pagination").find("li").slice(1, -1).remove(); + var trnum = 0; + var maxRows = parseInt($(this).val()); + $(table + " tr").each(function (el) { + a = $(this); + txtValue = a.text().toLowerCase(); + if (!filter || txtValue.indexOf(filter) > -1) { + trnum++; + } + }); + var pagenum = Math.ceil(trnum / maxRows); + for (var i = 1; i <= pagenum; i++) { + $(".pagination #insert-pages-here") + .before( + '
  • '+(i)+ + '(current)
  • ') + .show(); + } + $('.pagination [data-page="1"]').addClass("active"); + $(".pagination li").on("click", function (evt) { + evt.stopImmediatePropagation(); + evt.preventDefault(); + var pageNum = $(this).attr("data-page"); + activate_page(pageNum, table, filter); + }); + pagination_limits(); + }).val(10).change(); + activate_page(1, table, filter); +} + +function activate_page(pageNum, table, filter) { + var maxRows = parseInt($("#maxRows").val()); + if (pageNum == "prev") { + if (lastPage == 1) { + return; + } + pageNum = --lastPage; + } + if (pageNum == "next") { + if (lastPage == $(".pagination li").length - 2) { + return; + } + pageNum = ++lastPage; + } + lastPage = pageNum; + var trIndex = 0; + $(".pagination li").removeClass("active"); + $('.pagination [data-page="' + lastPage + '"]').addClass("active"); + pagination_limits(); + $(table + " tr").each(function () { + + a = $(this); + txtValue = a.text().toLowerCase(); + if (!filter || txtValue.indexOf(filter) > -1) { + trIndex++; + if (trIndex > maxRows * pageNum || trIndex <= maxRows * pageNum - maxRows) { + $(this).hide(); + } else { + $(this).show(); + } + } else { + $(this).hide(); + } + }); +} + +function pagination_limits() { + if ($(".pagination li").length > 7) { + if ($(".pagination li.active").attr("data-page") <= 3) { + $(".pagination li:gt(5)").hide(); + $(".pagination li:lt(5)").show(); + $('.pagination [data-page="next"]').show(); + } + if ($(".pagination li.active").attr("data-page") > 3) { + $(".pagination li:gt(0)").hide(); + $('.pagination [data-page="next"]').show(); + for ( + let i = parseInt($(".pagination li.active").attr("data-page")) - 2; + i <= parseInt($(".pagination li.active").attr("data-page")) + 2; + i++ + ) { + $('.pagination [data-page="' + i + '"]').show(); + } + } + } +} + +$('#filterform').on('reset', function(e) +{ + setTimeout(function() { setup_pagination("#table-id"); }); +}); + +$('#filterform').on('submit',function(event) { + event.preventDefault(); +}); + +setup_pagination("#table-id"); diff --git a/examples/legacy/blog/src/static/resources/css/httk.css b/examples/legacy/blog/src/static/resources/css/httk.css new file mode 100644 index 0000000..a3dd0e3 --- /dev/null +++ b/examples/legacy/blog/src/static/resources/css/httk.css @@ -0,0 +1,342 @@ +body { + padding-top: 0rem; +} +.navbar-custom { +} +.navbar-custom .navbar-nav .nav-link { + color: grey; +} +.navbar-custom .nav-link.active, +.navbar-custom .nav-link:hover { + color: black; +} +.border-3 { + border-width:3px !important; +} +.border-orange { + border-color: #f80 !important; +} +.border-darkgrey { + border-color: #888 !important; +} +.orange { + color: #f80 !important; +} +.dropdown .dropdown-menu .dropdown-item:active, .dropdown .dropdown-menu .dropdown-item:hover{background-color: white; color: black;} +.dropdown .dropdown-menu .dropdown-item {background-color: white; color: grey;} + +h1, h2, h3 { + padding-top: 1em; +} + +.publist a { + font-style: italic; +} + +* { + box-sizing: border-box; } + +body { + padding: 0; + margin: 0; + font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 1.5; + color: #606c71; } + +a { + color: #1e6bb8; + text-decoration: none; } + a:hover { + text-decoration: underline; } + +.page-header .btn { + display: inline-block; + margin-bottom: 1rem; + color: rgba(255, 255, 255, 0.7); + background-color: rgba(255, 255, 255, 0.08); + border-color: rgba(255, 255, 255, 0.2); + border-style: solid; + border-width: 1px; + border-radius: 0.3rem; + transition: color 0.2s, background-color 0.2s, border-color 0.2s; } +.page-header .btn:hover { + color: rgba(255, 255, 255, 0.8); + text-decoration: none; + background-color: rgba(255, 255, 255, 0.2); + border-color: rgba(255, 255, 255, 0.3); } +.page-header .btn + .btn { + margin-left: 1rem; } + @media screen and (min-width: 64em) { + .page-header .btn { + padding: 0.75rem 1rem; } } + @media screen and (min-width: 42em) and (max-width: 64em) { + .page-header .btn { + padding: 0.6rem 0.9rem; + font-size: 0.9rem; } } + @media screen and (max-width: 42em) { + .page-header .btn { + display: block; + width: 100%; + padding: 0.75rem; + font-size: 0.9rem; } + .btn + .btn { + margin-top: 1rem; + margin-left: 0; } } + +.page-header { + color: #fff; + text-align: center; + #background-color: #155799; + background-color: #114a85; + #background-image: linear-gradient(120deg, #80b8df, #155799); + #background-image: linear-gradient(120deg, #80b8df, #103481); + #background-image: linear-gradient(120deg, #103481, #80b8df); + #background-image: linear-gradient(120deg, #A03481, #FA380f); + background-image: linear-gradient(120deg, #703481, #6A0000); + } + .page-header img { + height:96px; + } + @media screen and (min-width: 64em) { + .page-header { + padding: 3rem 4rem; } } + @media screen and (min-width: 42em) and (max-width: 64em) { + .page-header { + padding: 3rem 4rem; } } + @media screen and (max-width: 42em) { + .page-header { + padding: 2rem 1rem; } } + +.project-name { + margin-top: 0; + margin-bottom: 0.1rem; } + @media screen and (min-width: 64em) { + .project-name { + font-size: 3.25rem; } } + @media screen and (min-width: 42em) and (max-width: 64em) { + .project-name { + font-size: 2.25rem; } } + @media screen and (max-width: 42em) { + .project-name { + font-size: 1.75rem; } } + +.project-tagline { + margin-bottom: 2rem; + font-weight: normal; + opacity: 0.7; } + @media screen and (min-width: 64em) { + .project-tagline { + font-size: 1.25rem; } } + @media screen and (min-width: 42em) and (max-width: 64em) { + .project-tagline { + font-size: 1.15rem; } } + @media screen and (max-width: 42em) { + .project-tagline { + font-size: 1rem; } } + +.main-content { + word-wrap: break-word; } + .main-content :first-child { + margin-top: 0; } + @media screen and (min-width: 64em) { + .main-content { + max-width: 64rem; + padding: 2rem 6rem; + margin: 0 auto; + font-size: 1.1rem; } } + @media screen and (min-width: 42em) and (max-width: 64em) { + .main-content { + padding: 2rem 4rem; + font-size: 1.1rem; } } + @media screen and (max-width: 42em) { + .main-content { + padding: 2rem 1rem; + font-size: 1rem; } } + .main-content img { + max-width: 100%; } + .main-content h1, + .main-content h2, + .main-content h3, + .main-content h4, + .main-content h5, + .main-content h6 { + margin-top: 2rem; + margin-bottom: 1rem; + font-weight: normal; + color: #2255AA; } + .main-content p { + margin-bottom: 1em; } + .main-content code { + padding: 2px 4px; + font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; + font-size: 0.9rem; + color: #567482; + background-color: #f3f6fa; + border-radius: 0.3rem; } + .main-content pre { + padding: 0.8rem; + margin-top: 0; + margin-bottom: 1rem; + font: 1rem Consolas, "Liberation Mono", Menlo, Courier, monospace; + color: #567482; + word-wrap: normal; + background-color: #f3f6fa; + border: solid 1px #dce6f0; + border-radius: 0.3rem; } + .main-content pre > code { + padding: 0; + margin: 0; + font-size: 0.9rem; + color: #567482; + word-break: normal; + white-space: pre; + background: transparent; + border: 0; } + .main-content .highlight { + margin-bottom: 1rem; } + .main-content .highlight pre { + margin-bottom: 0; + word-break: normal; } + .main-content .highlight pre, + .main-content pre { + padding: 0.8rem; + overflow: auto; + font-size: 0.9rem; + line-height: 1.45; + border-radius: 0.3rem; + -webkit-overflow-scrolling: touch; } + .main-content pre code, + .main-content pre tt { + display: inline; + max-width: initial; + padding: 0; + margin: 0; + overflow: initial; + line-height: inherit; + word-wrap: normal; + background-color: transparent; + border: 0; } + .main-content pre code:before, .main-content pre code:after, + .main-content pre tt:before, + .main-content pre tt:after { + content: normal; } + .main-content ul, + .main-content ol { + margin-top: 0; } + .main-content blockquote { + padding: 0 1rem; + margin-left: 0; + color: #819198; + border-left: 0.3rem solid #dce6f0; } + .main-content blockquote > :first-child { + margin-top: 0; } + .main-content blockquote > :last-child { + margin-bottom: 0; } + .main-content table { + display: block; + width: 100%; + overflow: auto; + word-break: normal; + word-break: keep-all; + -webkit-overflow-scrolling: touch; } + .main-content table th { + font-weight: bold; } + .main-content table th, + .main-content table td { + padding: 0.5rem 1rem; + border: 1px solid #e9ebec; } + .main-content dl { + padding: 0; } + .main-content dl dt { + padding: 0; + margin-top: 1rem; + font-size: 1rem; + font-weight: bold; } + .main-content dl dd { + padding: 0; + margin-bottom: 1rem; } + .main-content hr { + height: 2px; + padding: 0; + margin: 1rem 0; + background-color: #eff0f1; + border: 0; } + +.site-footer { + padding-top: 2rem; + margin-top: 2rem; + border-top: solid 1px #eff0f1; } + @media screen and (min-width: 64em) { + .site-footer { + font-size: 1rem; } } + @media screen and (min-width: 42em) and (max-width: 64em) { + .site-footer { + font-size: 1rem; } } + @media screen and (max-width: 42em) { + .site-footer { + font-size: 0.9rem; } } + +.site-footer-owner { + display: block; + font-weight: bold; } + +.site-footer-credits { + color: #819198; } + +.project-name { + font-weight: bold; + display: inline; +} +.top-left-dropdown { + background-color: #eff0f1; + border-color: #dfe0e1; + color: grey; +} +.dropdown-toggle { + background-color: #ffffff; + border-color: #dfe0e1; + color: grey; +} +@media all and (min-width: 992px) { + .navbar .nav-item .dropdown-menu{ display: none; } + .navbar .nav-item:hover .dropdown-menu{ display: block; } + .navbar .nav-item .dropdown-menu{ margin-top:0; } +} +blockquote { + margin: 1em 0em 1em 5em; + color: grey; +} + +.main-content img { + display: block; + margin: 2em; + margin-left: auto; + margin-right: auto; + width: 100%; +} + +.main-content img.align-left { + float: left; + margin-top: 0.5ex; + margin-bottom: 0.5ex; + margin-right: 2em; + margin-left: -5em; +} + +.main-content img.align-right { + float: right; + margin-top: 0.5ex; + margin-bottom: 0.5ex; + margin-right: -5em; + margin-left: 2em; +} + +.main-content img.oversize { + max-width: none; + width: 135%; + margin-top: 0.5ex; + margin-bottom: 0.5ex; + margin-left: -5em; + margin-right: -5em; +} diff --git a/examples/legacy/blog/src/templates/404.httkweb.html b/examples/legacy/blog/src/templates/404.httkweb.html new file mode 100644 index 0000000..912ade8 --- /dev/null +++ b/examples/legacy/blog/src/templates/404.httkweb.html @@ -0,0 +1,2 @@ +{content} +{error_404_reason:unquoted:if:Reason given from webserver: {error_404_reason}} diff --git a/examples/legacy/blog/src/templates/base_default.httkweb.html b/examples/legacy/blog/src/templates/base_default.httkweb.html new file mode 100644 index 0000000..a8f839b --- /dev/null +++ b/examples/legacy/blog/src/templates/base_default.httkweb.html @@ -0,0 +1,43 @@ + + + + + {page.title} + + + + + + + + + + + +
    +{content} +
    + + + + + + + diff --git a/examples/legacy/blog/src/templates/blog.httkweb.html b/examples/legacy/blog/src/templates/blog.httkweb.html new file mode 100644 index 0000000..c9e5c08 --- /dev/null +++ b/examples/legacy/blog/src/templates/blog.httkweb.html @@ -0,0 +1,46 @@ +
    + +
    +
    +
    + {content} + + + {blogposts:repeat:: + + } +
    {{pages:call:{{item}}:name}} - {{pages:call:{{item}}:author}} ({{pages:call:{{item}}:date}})
    +
    + +
    +
    + +
    +
    +
    +
    +
    + diff --git a/examples/legacy/blog/src/templates/blogpost.httkweb.html b/examples/legacy/blog/src/templates/blogpost.httkweb.html new file mode 100644 index 0000000..f41c654 --- /dev/null +++ b/examples/legacy/blog/src/templates/blogpost.httkweb.html @@ -0,0 +1,26 @@ +
    + +
    +
    +
    + + ← blog / {page.name} + + {content} +
    +
    +
    +
    diff --git a/examples/legacy/blog/src/templates/default.httkweb.html b/examples/legacy/blog/src/templates/default.httkweb.html new file mode 100644 index 0000000..8b13b37 --- /dev/null +++ b/examples/legacy/blog/src/templates/default.httkweb.html @@ -0,0 +1,23 @@ +
    + +
    +
    +
    + {content} +
    +
    +
    +
    diff --git a/examples/legacy/hello_world_app/publish_legacy_hello_world.py b/examples/legacy/hello_world_app/publish_legacy_hello_world.py new file mode 100755 index 0000000..5b6218d --- /dev/null +++ b/examples/legacy/hello_world_app/publish_legacy_hello_world.py @@ -0,0 +1,6 @@ +from pathlib import Path + +from httk.web import publish + +ROOT = Path(__file__).parent +publish(ROOT / "src", ROOT / "public", "./", compatibility_mode=True) diff --git a/examples/legacy/hello_world_app/serve_legacy_hello_world.py b/examples/legacy/hello_world_app/serve_legacy_hello_world.py new file mode 100755 index 0000000..a59305e --- /dev/null +++ b/examples/legacy/hello_world_app/serve_legacy_hello_world.py @@ -0,0 +1,6 @@ +from pathlib import Path + +from httk.web import serve + +ROOT = Path(__file__).parent +serve(ROOT / "src", port=8080, compatibility_mode=True) diff --git a/examples/legacy/hello_world_app/src/config.httkweb b/examples/legacy/hello_world_app/src/config.httkweb new file mode 100644 index 0000000..519c199 --- /dev/null +++ b/examples/legacy/hello_world_app/src/config.httkweb @@ -0,0 +1,3 @@ +--- +website_name: Httk hello world demo +--- diff --git a/examples/legacy/hello_world_app/src/content/404.httkweb b/examples/legacy/hello_world_app/src/content/404.httkweb new file mode 100644 index 0000000..eb5118f --- /dev/null +++ b/examples/legacy/hello_world_app/src/content/404.httkweb @@ -0,0 +1,10 @@ +--- +Title: 404 +Date: 2018-08-16 +Version: 1 +Author: Rickard Armiento +Template: default +--- +The page could not be found. + + diff --git a/examples/legacy/hello_world_app/src/content/index.httkweb b/examples/legacy/hello_world_app/src/content/index.httkweb new file mode 100644 index 0000000..c4950ca --- /dev/null +++ b/examples/legacy/hello_world_app/src/content/index.httkweb @@ -0,0 +1,13 @@ +--- +Title: Search +Date: 2018-08-16 +Version: 1 +Author: Rickard Armiento +hello_world-function: hello_world::hello_world_result +--- + +============================================ +This example runs a function that greets you +============================================ + +The output is shown below. diff --git a/examples/legacy/hello_world_app/src/functions/hello_world.py b/examples/legacy/hello_world_app/src/functions/hello_world.py new file mode 100644 index 0000000..16f7e7a --- /dev/null +++ b/examples/legacy/hello_world_app/src/functions/hello_world.py @@ -0,0 +1,3 @@ +def execute(global_data, **kargs): + print("Debug: running hello_world function.") + return "Hello " + global_data['greeter'] diff --git a/examples/legacy/hello_world_app/src/functions/init.py b/examples/legacy/hello_world_app/src/functions/init.py new file mode 100644 index 0000000..da86307 --- /dev/null +++ b/examples/legacy/hello_world_app/src/functions/init.py @@ -0,0 +1,3 @@ +def execute(global_data, **kargs): + print("Debug: running website initalization function.") + global_data['greeter'] = "world" diff --git a/examples/legacy/hello_world_app/src/static/img/Example.png b/examples/legacy/hello_world_app/src/static/img/Example.png new file mode 100644 index 0000000000000000000000000000000000000000..aa613599201f8ac36246f65c18e760f6f3c82903 GIT binary patch literal 2335 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 + + + + + + + + +{page.title} + + + + + + + +
    +
    + {content} +
    +
    + + + + + diff --git a/examples/legacy/hello_world_app/src/templates/default.httkweb.html b/examples/legacy/hello_world_app/src/templates/default.httkweb.html new file mode 100644 index 0000000..390c2fc --- /dev/null +++ b/examples/legacy/hello_world_app/src/templates/default.httkweb.html @@ -0,0 +1,2 @@ +{content} +{page.hello_world} diff --git a/examples/legacy/hello_world_app/src/templates/hello_world_result.httkweb.html b/examples/legacy/hello_world_app/src/templates/hello_world_result.httkweb.html new file mode 100644 index 0000000..a9ed31a --- /dev/null +++ b/examples/legacy/hello_world_app/src/templates/hello_world_result.httkweb.html @@ -0,0 +1 @@ +Result of hello world function: {result} diff --git a/examples/legacy/rst_templator/publish_legacy_rst_templator.py b/examples/legacy/rst_templator/publish_legacy_rst_templator.py new file mode 100644 index 0000000..c654cfc --- /dev/null +++ b/examples/legacy/rst_templator/publish_legacy_rst_templator.py @@ -0,0 +1,6 @@ +from pathlib import Path + +from httk.web import publish + +ROOT = Path(__file__).parent +publish(ROOT / "src", ROOT / "public", "http://127.0.0.1/", compatibility_mode=True) diff --git a/examples/legacy/rst_templator/serve_legacy_rst_templator.py b/examples/legacy/rst_templator/serve_legacy_rst_templator.py new file mode 100644 index 0000000..e3f984f --- /dev/null +++ b/examples/legacy/rst_templator/serve_legacy_rst_templator.py @@ -0,0 +1,6 @@ +from pathlib import Path + +from httk.web import serve + +ROOT = Path(__file__).parent +serve(ROOT / "src", port=8080, compatibility_mode=True, config_name="config_dynamic") diff --git a/examples/legacy/rst_templator/src/config.rst b/examples/legacy/rst_templator/src/config.rst new file mode 100644 index 0000000..b1ba0d0 --- /dev/null +++ b/examples/legacy/rst_templator/src/config.rst @@ -0,0 +1,6 @@ +:menuitems-list: + - index + - contact + - bare + + diff --git a/examples/legacy/rst_templator/src/config_dynamic.rst b/examples/legacy/rst_templator/src/config_dynamic.rst new file mode 100644 index 0000000..5821c3d --- /dev/null +++ b/examples/legacy/rst_templator/src/config_dynamic.rst @@ -0,0 +1,6 @@ +:menuitems-list: + - index + - contact + - bare +:urls_without_ext: true + diff --git a/examples/legacy/rst_templator/src/content/404.rst b/examples/legacy/rst_templator/src/content/404.rst new file mode 100644 index 0000000..733dd19 --- /dev/null +++ b/examples/legacy/rst_templator/src/content/404.rst @@ -0,0 +1,12 @@ +:Title: Contact +:Date: 2018-08-16 +:Version: 1 +:Author: Rickard Armiento +:Layout: default + +Not found +========= + +The page could not be found. + + diff --git a/examples/legacy/rst_templator/src/content/bare.rst b/examples/legacy/rst_templator/src/content/bare.rst new file mode 100644 index 0000000..faac02a --- /dev/null +++ b/examples/legacy/rst_templator/src/content/bare.rst @@ -0,0 +1,13 @@ +:Title: Bare +:Date: 2018-08-16 +:Version: 1 +:Author: Rickard Armiento +:Layout: default +:Base_template: bare + +Bare +==== + +This page has no header, etc. + + diff --git a/examples/legacy/rst_templator/src/content/contact.rst b/examples/legacy/rst_templator/src/content/contact.rst new file mode 100644 index 0000000..9e9047f --- /dev/null +++ b/examples/legacy/rst_templator/src/content/contact.rst @@ -0,0 +1,11 @@ +:Title: Contact +:Date: 2018-08-16 +:Version: 1 +:Author: Rickard Armiento +:Layout: default + +Contact +======= + +You should contact us + diff --git a/examples/legacy/rst_templator/src/content/index.rst b/examples/legacy/rst_templator/src/content/index.rst new file mode 100644 index 0000000..bacedab --- /dev/null +++ b/examples/legacy/rst_templator/src/content/index.rst @@ -0,0 +1,509 @@ +:Title: Front page +:Date: 2018-08-16 +:Version: 1 +:Author: Rickard Armiento +:Layout: default + +:Address: Example Street 123 + Sweden +:Contact: example@example.com +:organization: httk +:field name: This is a generic bibliographic field. +:field name 2: + Generic bibliographic fields may contain multiple body elements. + + Like this. + +====================== + httk example website +====================== + +Below follows some example content from the docutils demo.txt file. + +.. Above is the document title, and below is the subtitle. + They are transformed from section titles after parsing. + +-------------------------------- + Examples of Syntax Constructs +-------------------------------- + +.. meta:: + :keywords: reStructuredText, demonstration, demo, parser + :description lang=en: A demonstration of the reStructuredText + markup language, containing examples of all basic + constructs and many advanced constructs. + +.. To get numbered headings, use this: + .. contents:: Table of Contents + .. section-numbering:: + +Structural Elements +=================== + +Section Title +------------- + +That's it, the text just above this line. + +Transitions +----------- + +Here's a transition: + +--------- + +It divides the section. + +Body Elements +============= + +Paragraphs +---------- + +A paragraph. + +Inline Markup +````````````` + +Paragraphs contain text and may contain inline markup: *emphasis*, +**strong emphasis**, ``inline literals``, standalone hyperlinks +(http://www.python.org), external hyperlinks (Python_), internal +cross-references (example_), external hyperlinks with embedded URIs +(`Python web site `__), footnote references +(manually numbered [1]_, anonymous auto-numbered [#]_, labeled +auto-numbered [#label]_, or symbolic [*]_), citation references +([CIT2002]_), substitution references, and _`inline +hyperlink targets` (see Targets_ below for a reference back to here). +Character-level inline markup is also possible (although exceedingly +ugly!) in *re*\ ``Structured``\ *Text*. + +The default role for interpreted text is `Title Reference`. Here are +some explicit interpreted text roles: a PEP reference (:PEP:`287`); an +RFC reference (:RFC:`2822`); a :sub:`subscript`; a :sup:`superscript`; +and explicit roles for :emphasis:`standard` :strong:`inline` +:literal:`markup`. + +.. DO NOT RE-WRAP THE FOLLOWING PARAGRAPH! + +Let's test wrapping and whitespace significance in inline literals: +``This is an example of --inline-literal --text, --including some-- +strangely--hyphenated-words. Adjust-the-width-of-your-browser-window +to see how the text is wrapped. -- ---- -------- Now note the +spacing between the words of this sentence (words +should be grouped in pairs).`` + +If the ``--pep-references`` option was supplied, there should be a +live link to PEP 258 here. + +Bullet Lists +------------ + +- A bullet list + + + Nested bullet list. + + Nested item 2. + +- Item 2. + + Paragraph 2 of item 2. + + * Nested bullet list. + * Nested item 2. + + - Third level. + - Item 2. + + * Nested item 3. + +Enumerated Lists +---------------- + +1. Arabic numerals. + + a) lower alpha) + + (i) (lower roman) + + A. upper alpha. + + I) upper roman) + +2. Lists that don't start at 1: + + 3. Three + + 4. Four + + C. C + + D. D + + iii. iii + + iv. iv + +#. List items may also be auto-enumerated. + +Definition Lists +---------------- + +Term + Definition +Term : classifier + Definition paragraph 1. + + Definition paragraph 2. +Term + Definition + +Field Lists +----------- + +:what: Field lists map field names to field bodies, like database + records. They are often part of an extension syntax. They are + an unambiguous variant of RFC 2822 fields. + +:how arg1 arg2: + + The field marker is a colon, the field name, and a colon. + + The field body may contain one or more body elements, indented + relative to the field marker. + +Option Lists +------------ + +For listing command-line options: + +-a command-line option "a" +-b file options can have arguments + and long descriptions +--long options can be long also +--input=file long options can also have + arguments + +--very-long-option + The description can also start on the next line. + + The description may contain multiple body elements, + regardless of where it starts. + +-x, -y, -z Multiple options are an "option group". +-v, --verbose Commonly-seen: short & long options. +-1 file, --one=file, --two file + Multiple options with arguments. +/V DOS/VMS-style options too + +There must be at least two spaces between the option and the +description. + +Literal Blocks +-------------- + +Literal blocks are indicated with a double-colon ("::") at the end of +the preceding paragraph (over there ``-->``). They can be indented:: + + if literal_block: + text = 'is left as-is' + spaces_and_linebreaks = 'are preserved' + markup_processing = None + +Or they can be quoted without indentation:: + +>> Great idea! +> +> Why didn't I think of that? + +Line Blocks +----------- + +| This is a line block. It ends with a blank line. +| Each new line begins with a vertical bar ("|"). +| Line breaks and initial indents are preserved. +| Continuation lines are wrapped portions of long lines; + they begin with a space in place of the vertical bar. +| The left edge of a continuation line need not be aligned with + the left edge of the text above it. + +| This is a second line block. +| +| Blank lines are permitted internally, but they must begin with a "|". + +Take it away, Eric the Orchestra Leader! + + | A one, two, a one two three four + | + | Half a bee, philosophically, + | must, *ipso facto*, half not be. + | But half the bee has got to be, + | *vis a vis* its entity. D'you see? + | + | But can a bee be said to be + | or not to be an entire bee, + | when half the bee is not a bee, + | due to some ancient injury? + | + | Singing... + +Block Quotes +------------ + +Block quotes consist of indented body elements: + + My theory by A. Elk. Brackets Miss, brackets. This theory goes + as follows and begins now. All brontosauruses are thin at one + end, much much thicker in the middle and then thin again at the + far end. That is my theory, it is mine, and belongs to me and I + own it, and what it is too. + + -- Anne Elk (Miss) + +Doctest Blocks +-------------- + +>>> print 'Python-specific usage examples; begun with ">>>"' +Python-specific usage examples; begun with ">>>" +>>> print '(cut and pasted from interactive Python sessions)' +(cut and pasted from interactive Python sessions) + +Tables +------ + +Here's a grid table followed by a simple table: + ++------------------------+------------+----------+----------+ +| Header row, column 1 | Header 2 | Header 3 | Header 4 | +| (header rows optional) | | | | ++========================+============+==========+==========+ +| body row 1, column 1 | column 2 | column 3 | column 4 | ++------------------------+------------+----------+----------+ +| body row 2 | Cells may span columns. | ++------------------------+------------+---------------------+ +| body row 3 | Cells may | - Table cells | ++------------------------+ span rows. | - contain | +| body row 4 | | - body elements. | ++------------------------+------------+----------+----------+ +| body row 5 | Cells may also be | | +| | empty: ``-->`` | | ++------------------------+-----------------------+----------+ + +===== ===== ====== + Inputs Output +------------ ------ + A B A or B +===== ===== ====== +False False False +True False True +False True True +True True True +===== ===== ====== + +Footnotes +--------- + +.. [1] A footnote contains body elements, consistently indented by at + least 3 spaces. + + This is the footnote's second paragraph. + +.. [#label] Footnotes may be numbered, either manually (as in [1]_) or + automatically using a "#"-prefixed label. This footnote has a + label so it can be referred to from multiple places, both as a + footnote reference ([#label]_) and as a hyperlink reference + (label_). + +.. [#] This footnote is numbered automatically and anonymously using a + label of "#" only. + +(Remark: one example was removed here, because it caused unicode error.) + +.. [*] This footnote shows the next symbol in the sequence. + +Citations +--------- + +.. [CIT2002] Citations are text-labeled footnotes. They may be + rendered separately and differently from footnotes. + +Here's a reference to the above, [CIT2002]_. + +Targets +------- + +.. _example: + +This paragraph is pointed to by the explicit "example" target. A +reference can be found under `Inline Markup`_, above. `Inline +hyperlink targets`_ are also possible. + +Section headers are implicit targets, referred to by name. See +Targets_, which is a subsection of `Body Elements`_. + +Explicit external targets are interpolated into references such as +"Python_". + +.. _Python: http://www.python.org/ + +Targets may be indirect and anonymous. Thus `this phrase`__ may also +refer to the Targets_ section. + +__ Targets_ + +Duplicate Target Names +`````````````````````` + +Duplicate names in section headers or other implicit targets will +generate "info" (level-1) system messages. Duplicate names in +explicit targets will generate "warning" (level-2) system messages. + +Duplicate Target Names +`````````````````````` + +Since there are two "Duplicate Target Names" section headers, we +cannot uniquely refer to either of them by name. + +Directives +---------- + +.. contents:: :local: + +These are just a sample of the many reStructuredText Directives. For +others, please see +http://docutils.sourceforge.net/docs/ref/rst/directives.html. + +Document Parts +`````````````` + +An example of the "contents" directive can be seen above this section +(a local, untitled table of contents_). + +Images +`````` + +An image directive (also clickable -- a hyperlink reference): + +.. image:: img/Example.png + :target: directives_ + +A figure directive: + +.. figure:: img/Example.png + :alt: reStructuredText, the markup syntax + + A figure is an image with a caption and/or a legend: + + +------------+-----------------------------------------------+ + | re | Revised, revisited, based on 're' module. | + +------------+-----------------------------------------------+ + | Structured | Structure-enhanced text, structuredtext. | + +------------+-----------------------------------------------+ + | Text | Well it is, isn't it? | + +------------+-----------------------------------------------+ + + This paragraph is also part of the legend. + +Admonitions +``````````` + +.. Attention:: Directives at large. + +.. Caution:: + + Don't take any wooden nickels. + +.. DANGER:: Mad scientist at work! + +.. Error:: Does not compute. + +.. Hint:: It's bigger than a bread box. + +.. Important:: + - Wash behind your ears. + - Clean up your room. + - Call your mother. + - Back up your data. + +.. Note:: This is a note. + +.. Tip:: 15% if the service is good. + +.. WARNING:: Strong prose may provoke extreme mental exertion. + Reader discretion is strongly advised. + +.. admonition:: And, by the way... + + You can make up your own admonition too. + +Topics, Sidebars, and Rubrics +````````````````````````````` + +.. sidebar:: Sidebar Title + :subtitle: Optional Subtitle + + This is a sidebar. It is for text outside the flow of the main + text. + + .. rubric:: This is a rubric inside a sidebar + + Sidebars often appears beside the main text with a border and + background color. + +.. topic:: Topic Title + + This is a topic. + +.. rubric:: This is a rubric + +Target Footnotes +```````````````` + +.. target-notes:: + +Replacement Text +```````````````` + +I recommend you try |Python|_. + +.. |Python| replace:: Python, *the* best language around + +Compound Paragraph +`````````````````` + +.. compound:: + + This paragraph contains a literal block:: + + Connecting... OK + Transmitting data... OK + Disconnecting... OK + + and thus consists of a simple paragraph, a literal block, and + another simple paragraph. Nonetheless it is semantically *one* + paragraph. + +This construct is called a *compound paragraph* and can be produced +with the "compound" directive. + +Substitution Definitions +------------------------ + +An inline image (|example|) example: + +.. |EXAMPLE| image:: img/Example.png + +(Substitution definitions are not visible in the HTML source.) + +Comments +-------- + +Here's one: + +.. Comments begin with two dots and a space. Anything may + follow, except for the syntax of footnotes, hyperlink + targets, directives, or substitution definitions. + + Double-dashes -- "--" -- must be escaped somehow in HTML output. + +(View the HTML source to see the comment.) + + diff --git a/examples/legacy/rst_templator/src/static/img/Example.png b/examples/legacy/rst_templator/src/static/img/Example.png new file mode 100644 index 0000000000000000000000000000000000000000..aa613599201f8ac36246f65c18e760f6f3c82903 GIT binary patch literal 2335 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 + + + + + + + + + {page.title} + + + + + + +
    +
    + {content} +
    +
    + + + + diff --git a/examples/legacy/rst_templator/src/templates/bare.templator.html b/examples/legacy/rst_templator/src/templates/bare.templator.html new file mode 100644 index 0000000..8ec27be --- /dev/null +++ b/examples/legacy/rst_templator/src/templates/bare.templator.html @@ -0,0 +1,26 @@ +$def with (content) + + + + + + + + + + $page.title + + + + + + +
    +
    + $:content +
    +
    + + + + diff --git a/examples/legacy/rst_templator/src/templates/base_default.httkweb.html b/examples/legacy/rst_templator/src/templates/base_default.httkweb.html new file mode 100644 index 0000000..c0b6804 --- /dev/null +++ b/examples/legacy/rst_templator/src/templates/base_default.httkweb.html @@ -0,0 +1,52 @@ + + + + + + + + + + {page.title} + + + + + + + + +
    +
    + {content} +
    +
    + + + + diff --git a/examples/legacy/rst_templator/src/templates/base_default.templator.html b/examples/legacy/rst_templator/src/templates/base_default.templator.html new file mode 100644 index 0000000..e721fe6 --- /dev/null +++ b/examples/legacy/rst_templator/src/templates/base_default.templator.html @@ -0,0 +1,52 @@ +$def with (content) + + + + + + + + + + $page.title + + + + + + + + +
    +
    + $:content +
    +
    + + + + diff --git a/examples/legacy/rst_templator/src/templates/default.httkweb.html b/examples/legacy/rst_templator/src/templates/default.httkweb.html new file mode 100644 index 0000000..24db9fc --- /dev/null +++ b/examples/legacy/rst_templator/src/templates/default.httkweb.html @@ -0,0 +1 @@ +{content} diff --git a/examples/legacy/rst_templator/src/templates/default.templator.html b/examples/legacy/rst_templator/src/templates/default.templator.html new file mode 100644 index 0000000..9ad37b9 --- /dev/null +++ b/examples/legacy/rst_templator/src/templates/default.templator.html @@ -0,0 +1,2 @@ +$def with(content) +$:content diff --git a/examples/legacy/search_app/example.sqlite b/examples/legacy/search_app/example.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..532e3bdcb3c40c3b43495389811fea89ee9188ee GIT binary patch literal 98304 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 + + + + + + + + + {page.title} + + + + + +
    + {content} +
    + + + + diff --git a/examples/legacy/search_app/src/templates/base_default.httkweb.html b/examples/legacy/search_app/src/templates/base_default.httkweb.html new file mode 100644 index 0000000..bd3fad4 --- /dev/null +++ b/examples/legacy/search_app/src/templates/base_default.httkweb.html @@ -0,0 +1,60 @@ + + + + + + + + + +{page.title} + + + + + + + +
    + {content} +
    + + + + diff --git a/examples/legacy/search_app/src/templates/default.httkweb.html b/examples/legacy/search_app/src/templates/default.httkweb.html new file mode 100644 index 0000000..24db9fc --- /dev/null +++ b/examples/legacy/search_app/src/templates/default.httkweb.html @@ -0,0 +1 @@ +{content} diff --git a/examples/legacy/search_app/src/templates/material_details.httkweb.html b/examples/legacy/search_app/src/templates/material_details.httkweb.html new file mode 100644 index 0000000..d17d10d --- /dev/null +++ b/examples/legacy/search_app/src/templates/material_details.httkweb.html @@ -0,0 +1,16 @@ +Materials info +
      +
    • Formula: {result[formula]}
    • +
    • Anonymous formula: {result[anonymous_formula]}
    • +
    • Spacegroup number: {result[spacegroup_number]}
    • +
    • Names: {result[names]}
    • +
    +Tags: + + {result[tags]:repeat: + + + + + } +
    {{item[tag]}}{{item[value]}}
    diff --git a/examples/legacy/search_app/src/templates/search_page.httkweb.html b/examples/legacy/search_app/src/templates/search_page.httkweb.html new file mode 100644 index 0000000..c09cc78 --- /dev/null +++ b/examples/legacy/search_app/src/templates/search_page.httkweb.html @@ -0,0 +1,15 @@ +
    +
    + +

    Search for Materials

    + (Can enter names and symbols)
    +
    +
    +
    +
    +
    +
    +
    +{page.search_result} +{page.material_details} +
    diff --git a/examples/legacy/search_app/src/templates/search_result.httkweb.html b/examples/legacy/search_app/src/templates/search_result.httkweb.html new file mode 100644 index 0000000..2d6d9ca --- /dev/null +++ b/examples/legacy/search_app/src/templates/search_result.httkweb.html @@ -0,0 +1,19 @@ +

    Search results:

    + + + + + + + +{result:repeat:: + + + + + + + + +} +
    #Compound idFormulaAnonumous formulaNamesSpacegroup
    {{item[index]}}{{item[id]}}{{item[formula]}}{{item[anonymous_formula]}}{{item[names]}}{{item[spacegroup]}}
    diff --git a/examples/legacy/static_simple/publish_legacy_static_simple.py b/examples/legacy/static_simple/publish_legacy_static_simple.py new file mode 100755 index 0000000..c654cfc --- /dev/null +++ b/examples/legacy/static_simple/publish_legacy_static_simple.py @@ -0,0 +1,6 @@ +from pathlib import Path + +from httk.web import publish + +ROOT = Path(__file__).parent +publish(ROOT / "src", ROOT / "public", "http://127.0.0.1/", compatibility_mode=True) diff --git a/examples/legacy/static_simple/serve_legacy_static_simple.py b/examples/legacy/static_simple/serve_legacy_static_simple.py new file mode 100755 index 0000000..e3f984f --- /dev/null +++ b/examples/legacy/static_simple/serve_legacy_static_simple.py @@ -0,0 +1,6 @@ +from pathlib import Path + +from httk.web import serve + +ROOT = Path(__file__).parent +serve(ROOT / "src", port=8080, compatibility_mode=True, config_name="config_dynamic") diff --git a/examples/legacy/static_simple/src/config.httkweb b/examples/legacy/static_simple/src/config.httkweb new file mode 100644 index 0000000..d97f29d --- /dev/null +++ b/examples/legacy/static_simple/src/config.httkweb @@ -0,0 +1,4 @@ +--- +menuitems-list: index, contact, bare +--- + diff --git a/examples/legacy/static_simple/src/config_dynamic.httkweb b/examples/legacy/static_simple/src/config_dynamic.httkweb new file mode 100644 index 0000000..dcc358d --- /dev/null +++ b/examples/legacy/static_simple/src/config_dynamic.httkweb @@ -0,0 +1,4 @@ +--- +menuitems-list: index, contact, bare +urls_without_ext: true +--- diff --git a/examples/legacy/static_simple/src/content/404.httkweb b/examples/legacy/static_simple/src/content/404.httkweb new file mode 100644 index 0000000..9d62814 --- /dev/null +++ b/examples/legacy/static_simple/src/content/404.httkweb @@ -0,0 +1,10 @@ +--- +Title: Contact +Date: 2018-08-16 +Version: 1 +Author: Rickard Armiento +Template: 404 +--- +The page could not be found. + + diff --git a/examples/legacy/static_simple/src/content/bare.httkweb b/examples/legacy/static_simple/src/content/bare.httkweb new file mode 100644 index 0000000..11495ac --- /dev/null +++ b/examples/legacy/static_simple/src/content/bare.httkweb @@ -0,0 +1,15 @@ +--- +Title: Bare +Date: 2018-08-16 +Version: 1 +Author: Rickard Armiento +Template: default +Base_template: bare +--- + +Bare +==== + +This page has no header, etc. + + diff --git a/examples/legacy/static_simple/src/content/contact.httkweb b/examples/legacy/static_simple/src/content/contact.httkweb new file mode 100644 index 0000000..e32967a --- /dev/null +++ b/examples/legacy/static_simple/src/content/contact.httkweb @@ -0,0 +1,13 @@ +--- +Title: Contact +Date: 2018-08-16 +Version: 1 +Author: Rickard Armiento +Template: default +--- + +Contact +======= + +You should contact us + diff --git a/examples/legacy/static_simple/src/content/index.httkweb b/examples/legacy/static_simple/src/content/index.httkweb new file mode 100644 index 0000000..6608dc5 --- /dev/null +++ b/examples/legacy/static_simple/src/content/index.httkweb @@ -0,0 +1,34 @@ +--- +Title: Front page +Date: 2018-08-16 +Version: 1 +Author: Rickard Armiento +Template: default + +Address: Example Street 123 + Sweden +Contact: example@example.com +organization: httk +field name: This is a generic bibliographic field. +field name 2: + Generic bibliographic fields may contain multiple body elements. + + Like this. +--- + +This is the front page +====================== + +You can write with *emphasis*, **strong emphasis**, and as `interpreted text`. + +This is an anchor link: `You can do subtitles`_ + +You escape, e.g., \* and \`` with \\ + + +You can do subtitles +-------------------- +Welcome to this page. + +You can do external hyperlinks, like `Python `_. + diff --git a/examples/legacy/static_simple/src/static/img/Example.png b/examples/legacy/static_simple/src/static/img/Example.png new file mode 100644 index 0000000000000000000000000000000000000000..aa613599201f8ac36246f65c18e760f6f3c82903 GIT binary patch literal 2335 zcmV+)3E=jLP)73W`Ip=h?7hZ#|CUP!NCzCJuJa%@>lvgzW$SnOi5xXtuTQ zbVj0_xJ^HOT{=Py<6n`#7kgk2?14S72ll`o*aLfD5A1Sx9O@S><0@o{QNkk^VV;HbPw(U)R{`f|$#>k{;n$m=F~3|wMov?JE9)o7*+d_#4NujV|T1YQB;?waY-yek$+{{(g=4!hqCOel_$v!wdYwf-;d zjB@|06()jpUo0o~TCI^pJ7YPhW-*gmlQdcQ)^5+V zd7imw9V?&}>S;=UhQQb?hMloG8L6pWN}-FjcD?<&{X(z-d~Me}jxk5X?+DY`7u0NL z26bPo1u1%g^kTJXDf8`mgA@7r^ViRXW?onsF=AFV5rJ6vECJSiv9O}g%PV?OC3xG- z#}#AH=Bv$BG$*wzlqX>Ynv^F6b!V*PteT+w6VvizLAmoYt$_xqQL06w6%`^fx9*LV zFi6Vk*e~2m0j)GurfOxbWHT)|HTz*!NV4w!FCyn5Bq~gF`5LuL(MsA%;{v1M(0OYfP1>D&<;O$%efDlY;aCfmH6i<=8_K zQFH4Bt7#eQtVJ5J6_cV`a3#fh>bqkl4k3ovPxs}%ZIAm26(c#uB*w&4*s7V=32l|a zOkC*^1=gLh$sbbu*{VOBx;(b~_CWmiJ{}oR)rE_il8=ZeXJZ*`{|83&+T$>U^mv?3 zrxjnA@AqZ7KYAbfIAD!=%ry~|r7)?i%wyDXcWj2!D@?rH9r+Zmj$TnVK10ZZgTpxB zlar{j#u&KojrCg=0&tfTsQ2yuHb2(B_TLG;J3I|O-8o+5eglV2wBdo4)UjO!u!QRQ7~{y*^;|qJ3=`jTKe|(tvjD+g~4nj6Bxh;`KSX zxG}u5oz@q17=~!!quzB%>S1;MiacavO&!kQN!rJc!uH^x?Xf~Uj6Tq>2>>|qC%^eN z$xtE#5@qb4jD6|UvWpWgJ{SAdO#NSW&1lDU6bPcbl>7-*CFSA%|7Wh&KuUzMa_wj| z*Sq*3*mQMsz&$8w=Kps9vS1hNf?co+cEK*#1-oDu?1Eje3wFUS*af>_7wm$Stv?@@ zU?~Cs+yPd_kYu$EYZ6_yd=5)Wl1P?P1RDIed#hFhlgCBN=ddzz0Vd(W4h{Otmd{}c zSR@IEb#47R(pGX^wtNnoB_MJKmhZkO@GLB?CpB{S{ji2ludFP*0hY9#Oat&WQ3X)7p`OX*T^OQ6OM$lCqGwP*gD_p^ zU=7VFYvnJ103*!x#uB7S7~iw7h0}HgI0Gi%rtpWBZSM(^;8&rp!G4*t377N$9Qiv- zpN6;b?RoIM3Tt=^cES-*@6Q0omJYR*P)@>hU4mu2{h9^rhTI-o=9JA#i;w^>!OkNq zX_t|P<#=m(L_xrVFuuG7>!d8Q9Bq4|g!#+ZvBhwvL+=3@VOP+)1WUkMmZhP{bt#}a z0fPhVvBl3;7QmtiyRWP(X+RMvOWAh6tqo^Q>uU-F`r4b;F01Bt(>E)?L&OuX z*`uL;TN{reTeVMBD!`h(;a)>F05-kLV^<|*6Y(6Zte#0S0T+6LZJK>qy-PTG(*jJ( zK(X9H$`By|V3#K1IasDF$y(J=Z3b3!jkZFdfU~zB1&~<)n|*G*vk+iXHWAOjLa8F| z5wld8&@KX4qB-ye+h;NKl(TeLRB0lffi;HT&w8!=#cD9y`k^;_S`uJ7D{Qq55zoL9 zdaa^-T`;7@Oi)MOt}*7$o<+bA@f56(%4R4z!n$Q$o_*{cWcN;HiiU@X=TB=AnE>Z3 zFwDQlQ}jdchMf6w5CcxcbFg_Ui9DYYWZd@HASZ7D9eEQl{x(;pz(hO`O9L#b8%w97 z2H2eQnmuV}Z@1oduOxVR+BRW`Nc}#nTESO-MixL?TVP#^Y-Tg}?46YD_flZ>%rBXV zn5B3RO8_v19DX$ja7$wEm&&695IFYkpLUZvyOaSt5p#s|7PfoupUP?5G})bo6;2JG zlXrGmjt4VHom-J&Iv;-vTlnaO;atbhy%jWjZ(ko>dLz?2k3fF&mu~{UsWkMnMUF6- zCn}&n0vlRGCtktN!4|`YPt?JG61Gs{ZxuA|QkWy)at~Mv0ASq$wq5H#lRv)}?1Eje z3wFUS*af>_7wkXn-N66=004u)k>roLM;b+t!KJcVvj + + + + + + + + + {page.title} + + + + + + +
    +
    + {content} +
    +
    + + + + diff --git a/examples/legacy/static_simple/src/templates/base_default.httkweb.html b/examples/legacy/static_simple/src/templates/base_default.httkweb.html new file mode 100644 index 0000000..e14e145 --- /dev/null +++ b/examples/legacy/static_simple/src/templates/base_default.httkweb.html @@ -0,0 +1,52 @@ + + + + + + + + + + {page.title} + + + + + + + + +
    +
    + {content} +
    +
    + + + + diff --git a/examples/legacy/static_simple/src/templates/default.httkweb.html b/examples/legacy/static_simple/src/templates/default.httkweb.html new file mode 100644 index 0000000..24db9fc --- /dev/null +++ b/examples/legacy/static_simple/src/templates/default.httkweb.html @@ -0,0 +1 @@ +{content} diff --git a/examples/modern/blog/README.md b/examples/modern/blog/README.md new file mode 100644 index 0000000..23ceb15 --- /dev/null +++ b/examples/modern/blog/README.md @@ -0,0 +1,19 @@ +# Modern Blog Example + +Modernized replacement for the old legacy blog example. + +- Markdown content +- Jinja2 templates +- Python function bootstrap via `src/functions/init.py` + +Run: + +```bash +python serve.py +``` + +Publish: + +```bash +python publish.py +``` diff --git a/examples/modern/blog/publish.py b/examples/modern/blog/publish.py new file mode 100644 index 0000000..2b958af --- /dev/null +++ b/examples/modern/blog/publish.py @@ -0,0 +1,6 @@ +from pathlib import Path + +from httk.web import publish + +ROOT = Path(__file__).parent +publish(ROOT / "src", ROOT / "public", "http://127.0.0.1/") diff --git a/examples/modern/blog/serve.py b/examples/modern/blog/serve.py new file mode 100644 index 0000000..ce3cd1a --- /dev/null +++ b/examples/modern/blog/serve.py @@ -0,0 +1,6 @@ +from pathlib import Path + +from httk.web import serve + +ROOT = Path(__file__).parent +serve(ROOT / "src", port=8080) diff --git a/examples/modern/blog/src/content/blog.md b/examples/modern/blog/src/content/blog.md new file mode 100644 index 0000000..fc1672c --- /dev/null +++ b/examples/modern/blog/src/content/blog.md @@ -0,0 +1,6 @@ +--- +title: All Blog Posts +template: blog_index +--- + +Browse all posts below. diff --git a/examples/modern/blog/src/content/blogposts/hello-modern.md b/examples/modern/blog/src/content/blogposts/hello-modern.md new file mode 100644 index 0000000..6c59aa5 --- /dev/null +++ b/examples/modern/blog/src/content/blogposts/hello-modern.md @@ -0,0 +1,8 @@ +--- +title: Hello Modern Blog +author: httk-web +date: 2026-03-10 +template: blog_post +--- + +This is the first modern blog post. diff --git a/examples/modern/blog/src/content/blogposts/migration-notes.md b/examples/modern/blog/src/content/blogposts/migration-notes.md new file mode 100644 index 0000000..2c21baa --- /dev/null +++ b/examples/modern/blog/src/content/blogposts/migration-notes.md @@ -0,0 +1,8 @@ +--- +title: Migration Notes +author: httk-web +date: 2026-03-20 +template: blog_post +--- + +This post summarizes migration from legacy templates to Jinja2. diff --git a/examples/modern/blog/src/content/contact.md b/examples/modern/blog/src/content/contact.md new file mode 100644 index 0000000..750a90b --- /dev/null +++ b/examples/modern/blog/src/content/contact.md @@ -0,0 +1,6 @@ +--- +title: Contact +template: default +--- + +Contact us at `contact@example.com`. diff --git a/examples/modern/blog/src/content/index.md b/examples/modern/blog/src/content/index.md new file mode 100644 index 0000000..934a1de --- /dev/null +++ b/examples/modern/blog/src/content/index.md @@ -0,0 +1,6 @@ +--- +title: Modern Blog Home +template: blog_home +--- + +Welcome to the modern blog example. diff --git a/examples/modern/blog/src/functions/init.py b/examples/modern/blog/src/functions/init.py new file mode 100644 index 0000000..0be7c01 --- /dev/null +++ b/examples/modern/blog/src/functions/init.py @@ -0,0 +1,14 @@ +from pathlib import Path + + +def execute(global_data, **kwargs): + content_root = Path(__file__).resolve().parents[1] / "content" / "blogposts" + posts = [] + for path in sorted(content_root.glob("*.md")): + rel = f"blogposts/{path.stem}" + date = global_data["pages"](rel, "date") + posts.append((str(date or ""), rel)) + + posts.sort(reverse=True) + global_data["blogposts"] = [rel for _, rel in posts] + global_data["blogposts_latest"] = global_data["blogposts"][:5] diff --git a/examples/modern/blog/src/templates/base_default.html.j2 b/examples/modern/blog/src/templates/base_default.html.j2 new file mode 100644 index 0000000..cc46843 --- /dev/null +++ b/examples/modern/blog/src/templates/base_default.html.j2 @@ -0,0 +1,21 @@ + + + + + + {{ title or "Modern Blog" }} + + +
    +

    Modern Blog

    + +
    +
    + {{ content }} +
    + + diff --git a/examples/modern/blog/src/templates/blog_home.html.j2 b/examples/modern/blog/src/templates/blog_home.html.j2 new file mode 100644 index 0000000..eea2349 --- /dev/null +++ b/examples/modern/blog/src/templates/blog_home.html.j2 @@ -0,0 +1,13 @@ +
    +

    {{ title }}

    + {{ content }} +

    Latest posts

    + +
    diff --git a/examples/modern/blog/src/templates/blog_index.html.j2 b/examples/modern/blog/src/templates/blog_index.html.j2 new file mode 100644 index 0000000..b429883 --- /dev/null +++ b/examples/modern/blog/src/templates/blog_index.html.j2 @@ -0,0 +1,12 @@ +
    +

    {{ title }}

    + {{ content }} +
      + {% for rel in blogposts %} +
    • + {{ pages(rel, 'title') }} + by {{ pages(rel, 'author') }} ({{ pages(rel, 'date') }}) +
    • + {% endfor %} +
    +
    diff --git a/examples/modern/blog/src/templates/blog_post.html.j2 b/examples/modern/blog/src/templates/blog_post.html.j2 new file mode 100644 index 0000000..0e92ad1 --- /dev/null +++ b/examples/modern/blog/src/templates/blog_post.html.j2 @@ -0,0 +1,5 @@ +
    +

    {{ title }}

    +

    {{ date }} by {{ author }}

    + {{ content }} +
    diff --git a/examples/modern/blog/src/templates/default.html.j2 b/examples/modern/blog/src/templates/default.html.j2 new file mode 100644 index 0000000..6711c79 --- /dev/null +++ b/examples/modern/blog/src/templates/default.html.j2 @@ -0,0 +1,4 @@ +
    +

    {{ title }}

    + {{ content }} +
    diff --git a/examples/modern/minimal/README.md b/examples/modern/minimal/README.md new file mode 100644 index 0000000..b958aaa --- /dev/null +++ b/examples/modern/minimal/README.md @@ -0,0 +1,13 @@ +# Minimal Modern Example + +Serve: + +```bash +python serve.py +``` + +Publish: + +```bash +python publish.py +``` diff --git a/examples/modern/minimal/public/index.html b/examples/modern/minimal/public/index.html new file mode 100644 index 0000000..4e16bba --- /dev/null +++ b/examples/modern/minimal/public/index.html @@ -0,0 +1,17 @@ + + + + + + Minimal Modern Example + + +
    +
    +

    Minimal Modern Example

    +

    Hello from httk-web

    +

    This is the modern example using Jinja2 templates.

    +
    +
    + + \ No newline at end of file diff --git a/examples/modern/minimal/publish.py b/examples/modern/minimal/publish.py new file mode 100644 index 0000000..2b958af --- /dev/null +++ b/examples/modern/minimal/publish.py @@ -0,0 +1,6 @@ +from pathlib import Path + +from httk.web import publish + +ROOT = Path(__file__).parent +publish(ROOT / "src", ROOT / "public", "http://127.0.0.1/") diff --git a/examples/modern/minimal/serve.py b/examples/modern/minimal/serve.py new file mode 100644 index 0000000..ce3cd1a --- /dev/null +++ b/examples/modern/minimal/serve.py @@ -0,0 +1,6 @@ +from pathlib import Path + +from httk.web import serve + +ROOT = Path(__file__).parent +serve(ROOT / "src", port=8080) diff --git a/examples/modern/minimal/src/content/index.md b/examples/modern/minimal/src/content/index.md new file mode 100644 index 0000000..a0542ac --- /dev/null +++ b/examples/modern/minimal/src/content/index.md @@ -0,0 +1,8 @@ +--- +title: Minimal Modern Example +template: default +--- + +# Hello from httk-web + +This is the modern example using Jinja2 templates. diff --git a/examples/modern/minimal/src/templates/base_default.html.j2 b/examples/modern/minimal/src/templates/base_default.html.j2 new file mode 100644 index 0000000..f92656a --- /dev/null +++ b/examples/modern/minimal/src/templates/base_default.html.j2 @@ -0,0 +1,13 @@ + + + + + + {{ title }} + + +
    + {{ content }} +
    + + diff --git a/examples/modern/minimal/src/templates/default.html.j2 b/examples/modern/minimal/src/templates/default.html.j2 new file mode 100644 index 0000000..1050544 --- /dev/null +++ b/examples/modern/minimal/src/templates/default.html.j2 @@ -0,0 +1,4 @@ +
    +

    {{ title }}

    + {{ content }} +
    diff --git a/examples/modern/rst_site/README.md b/examples/modern/rst_site/README.md new file mode 100644 index 0000000..93f36e4 --- /dev/null +++ b/examples/modern/rst_site/README.md @@ -0,0 +1,19 @@ +# Modern RST Site + +Modernized replacement for the old `rst_templator` example. + +- Uses `.rst` content. +- Uses Jinja2 templates (`.html.j2`). +- No legacy templator/web.py syntax. + +Run: + +```bash +python serve.py +``` + +Publish: + +```bash +python publish.py +``` diff --git a/examples/modern/rst_site/publish.py b/examples/modern/rst_site/publish.py new file mode 100644 index 0000000..2b958af --- /dev/null +++ b/examples/modern/rst_site/publish.py @@ -0,0 +1,6 @@ +from pathlib import Path + +from httk.web import publish + +ROOT = Path(__file__).parent +publish(ROOT / "src", ROOT / "public", "http://127.0.0.1/") diff --git a/examples/modern/rst_site/serve.py b/examples/modern/rst_site/serve.py new file mode 100644 index 0000000..ce3cd1a --- /dev/null +++ b/examples/modern/rst_site/serve.py @@ -0,0 +1,6 @@ +from pathlib import Path + +from httk.web import serve + +ROOT = Path(__file__).parent +serve(ROOT / "src", port=8080) diff --git a/examples/modern/rst_site/src/content/about.rst b/examples/modern/rst_site/src/content/about.rst new file mode 100644 index 0000000..d3c19b0 --- /dev/null +++ b/examples/modern/rst_site/src/content/about.rst @@ -0,0 +1,7 @@ +:Title: About This Example +:Template: default + +About +===== + +This is a small multi-page RST site. diff --git a/examples/modern/rst_site/src/content/index.rst b/examples/modern/rst_site/src/content/index.rst new file mode 100644 index 0000000..70e1455 --- /dev/null +++ b/examples/modern/rst_site/src/content/index.rst @@ -0,0 +1,14 @@ +:Title: Modern RST Front Page +:Author: httk-web +:Date: 2026-03-27 +:Template: default + +Modern RST Example +================== + +This page is written in **reStructuredText** and rendered through the modern +Jinja2 template layer. + +- No web.py templator syntax +- No `.templator.html` templates +- Python 3.11+ only diff --git a/examples/modern/rst_site/src/templates/base_default.html.j2 b/examples/modern/rst_site/src/templates/base_default.html.j2 new file mode 100644 index 0000000..266e78b --- /dev/null +++ b/examples/modern/rst_site/src/templates/base_default.html.j2 @@ -0,0 +1,19 @@ + + + + + + {{ title or "Modern RST Site" }} + + +
    + +
    +
    + {{ content }} +
    + + diff --git a/examples/modern/rst_site/src/templates/default.html.j2 b/examples/modern/rst_site/src/templates/default.html.j2 new file mode 100644 index 0000000..1050544 --- /dev/null +++ b/examples/modern/rst_site/src/templates/default.html.j2 @@ -0,0 +1,4 @@ +
    +

    {{ title }}

    + {{ content }} +
    diff --git a/examples/modern/search_app/README.md b/examples/modern/search_app/README.md new file mode 100644 index 0000000..333458f --- /dev/null +++ b/examples/modern/search_app/README.md @@ -0,0 +1,15 @@ +# Modern Search Example + +Modernized replacement for the old search app example. + +- Dynamic function injection from query parameters +- Jinja2 templates for page and fragment rendering +- In-memory sample dataset (no legacy `httk.db` dependency) + +Run: + +```bash +python serve.py +``` + +Then open `http://127.0.0.1:8080/` and search for terms like `si` or `oxide`. diff --git a/examples/modern/search_app/publish.py b/examples/modern/search_app/publish.py new file mode 100644 index 0000000..2b958af --- /dev/null +++ b/examples/modern/search_app/publish.py @@ -0,0 +1,6 @@ +from pathlib import Path + +from httk.web import publish + +ROOT = Path(__file__).parent +publish(ROOT / "src", ROOT / "public", "http://127.0.0.1/") diff --git a/examples/modern/search_app/serve.py b/examples/modern/search_app/serve.py new file mode 100644 index 0000000..ce3cd1a --- /dev/null +++ b/examples/modern/search_app/serve.py @@ -0,0 +1,6 @@ +from pathlib import Path + +from httk.web import serve + +ROOT = Path(__file__).parent +serve(ROOT / "src", port=8080) diff --git a/examples/modern/search_app/src/content/index.md b/examples/modern/search_app/src/content/index.md new file mode 100644 index 0000000..6434025 --- /dev/null +++ b/examples/modern/search_app/src/content/index.md @@ -0,0 +1,8 @@ +--- +title: Modern Search Demo +template: search_page +results-function: search:q:search_results +details-function: details:material_id:material_details +--- + +Use the search box to query the sample material dataset. diff --git a/examples/modern/search_app/src/functions/details.py b/examples/modern/search_app/src/functions/details.py new file mode 100644 index 0000000..943bc78 --- /dev/null +++ b/examples/modern/search_app/src/functions/details.py @@ -0,0 +1,5 @@ +def execute(material_id, global_data, **kwargs): + for row in global_data.get("materials", []): + if row.get("id") == material_id: + return row + return None diff --git a/examples/modern/search_app/src/functions/init.py b/examples/modern/search_app/src/functions/init.py new file mode 100644 index 0000000..e0c9304 --- /dev/null +++ b/examples/modern/search_app/src/functions/init.py @@ -0,0 +1,6 @@ +def execute(global_data, **kwargs): + global_data["materials"] = [ + {"id": "mp-149", "formula": "Si", "name": "Silicon", "spacegroup": "Fd-3m"}, + {"id": "mp-13", "formula": "Fe", "name": "Iron", "spacegroup": "Im-3m"}, + {"id": "mp-22862", "formula": "Al2O3", "name": "Aluminum Oxide", "spacegroup": "R-3c"}, + ] diff --git a/examples/modern/search_app/src/functions/search.py b/examples/modern/search_app/src/functions/search.py new file mode 100644 index 0000000..282a0b7 --- /dev/null +++ b/examples/modern/search_app/src/functions/search.py @@ -0,0 +1,12 @@ +def execute(q, global_data, **kwargs): + needle = str(q).strip().lower() + if not needle: + return [] + + materials = global_data.get("materials", []) + out = [] + for row in materials: + text = " ".join([str(row.get("id", "")), str(row.get("formula", "")), str(row.get("name", ""))]).lower() + if needle in text: + out.append(row) + return out diff --git a/examples/modern/search_app/src/templates/base_default.html.j2 b/examples/modern/search_app/src/templates/base_default.html.j2 new file mode 100644 index 0000000..ce6296a --- /dev/null +++ b/examples/modern/search_app/src/templates/base_default.html.j2 @@ -0,0 +1,13 @@ + + + + + + {{ title or "Modern Search" }} + + +
    + {{ content }} +
    + + diff --git a/examples/modern/search_app/src/templates/material_details.html.j2 b/examples/modern/search_app/src/templates/material_details.html.j2 new file mode 100644 index 0000000..f353aa9 --- /dev/null +++ b/examples/modern/search_app/src/templates/material_details.html.j2 @@ -0,0 +1,10 @@ +{% if result %} +
      +
    • ID: {{ result['id'] }}
    • +
    • Formula: {{ result['formula'] }}
    • +
    • Name: {{ result['name'] }}
    • +
    • Spacegroup: {{ result['spacegroup'] }}
    • +
    +{% else %} +

    No details found.

    +{% endif %} diff --git a/examples/modern/search_app/src/templates/search_page.html.j2 b/examples/modern/search_app/src/templates/search_page.html.j2 new file mode 100644 index 0000000..42cb258 --- /dev/null +++ b/examples/modern/search_app/src/templates/search_page.html.j2 @@ -0,0 +1,21 @@ +
    +

    {{ title }}

    + {{ content }} + +
    + + +
    + + {% if results %} + {{ results }} + {% endif %} + + {% if details %} +

    Material details

    + {{ details }} + {% endif %} +
    diff --git a/examples/modern/search_app/src/templates/search_results.html.j2 b/examples/modern/search_app/src/templates/search_results.html.j2 new file mode 100644 index 0000000..d925a6a --- /dev/null +++ b/examples/modern/search_app/src/templates/search_results.html.j2 @@ -0,0 +1,24 @@ + + + + + + + + + + + {% for row in result %} + + + + + + + {% endfor %} + +
    IDFormulaNameSpacegroup
    + + {{ row['id'] }} + + {{ row['formula'] }}{{ row['name'] }}{{ row['spacegroup'] }}
    diff --git a/src/httk/web/api.py b/src/httk/web/api.py index efa64f8..0b5472c 100644 --- a/src/httk/web/api.py +++ b/src/httk/web/api.py @@ -15,9 +15,15 @@ def create_asgi_app( *, baseurl: str | None = None, compatibility_mode: bool = False, + config_name: str = "config", debug: bool = False, ) -> Starlette: - config = SiteConfig.from_srcdir(srcdir=srcdir, baseurl=baseurl, compatibility_mode=compatibility_mode) + config = SiteConfig.from_srcdir( + srcdir=srcdir, + baseurl=baseurl, + compatibility_mode=compatibility_mode, + config_name=config_name, + ) engine = SiteEngine(config) return create_app(engine=engine, debug=debug) @@ -29,9 +35,16 @@ def serve( port: int = 8080, baseurl: str | None = None, compatibility_mode: bool = False, + config_name: str = "config", debug: bool = False, ) -> None: - app = create_asgi_app(srcdir=srcdir, baseurl=baseurl, compatibility_mode=compatibility_mode, debug=debug) + app = create_asgi_app( + srcdir=srcdir, + baseurl=baseurl, + compatibility_mode=compatibility_mode, + config_name=config_name, + debug=debug, + ) run_dev_server(app=app, host=host, port=port) @@ -41,7 +54,13 @@ def publish( baseurl: str, *, compatibility_mode: bool = False, + config_name: str = "config", ) -> PublishReport: - config = SiteConfig.from_srcdir(srcdir=srcdir, baseurl=baseurl, compatibility_mode=compatibility_mode) + config = SiteConfig.from_srcdir( + srcdir=srcdir, + baseurl=baseurl, + compatibility_mode=compatibility_mode, + config_name=config_name, + ) engine = SiteEngine(config) return publish_site(engine=engine, outdir=outdir) diff --git a/src/httk/web/engine/site_engine.py b/src/httk/web/engine/site_engine.py index 2352de1..ca7fc2a 100644 --- a/src/httk/web/engine/site_engine.py +++ b/src/httk/web/engine/site_engine.py @@ -26,6 +26,8 @@ def __init__(self, config: SiteConfig) -> None: else: self.template_engine = JinjaTemplateEngine(template_dir=config.template_dir) self.function_handler = PythonFunctionHandler(functions_dir=config.functions_dir) + self.global_data: dict[str, object] = self._load_global_config_metadata() + self._run_init_function() def resolve(self, route: str) -> ResolvedRoute: return resolve_route(config=self.config, route=route) @@ -65,8 +67,17 @@ def render( route_key = normalize_route(route) warnings: list[str] = [] - template_name = self._metadata_string(metadata, "template", default="default") - base_template_name = self._metadata_string(metadata, "base_template", default="base_default") + render_mode = "serve" if request is not None else "publish" + template_name = self._metadata_string( + metadata, + f"template_{render_mode}", + default=self._metadata_string(metadata, "template", default="default"), + ) + base_template_name = self._metadata_string( + metadata, + f"base_template_{render_mode}", + default=self._metadata_string(metadata, "base_template", default="base_default"), + ) context = self._build_template_context( route_key=route_key, @@ -121,7 +132,8 @@ def _build_template_context( postvars: dict[str, str], request: HttpRequestContext, ) -> dict[str, object]: - context: dict[str, object] = dict(metadata) + context: dict[str, object] = dict(self.global_data) + context.update(metadata) page_cache: dict[str, tuple[str, dict[str, object]]] = {} def first_value(*values: object) -> object: @@ -170,8 +182,9 @@ def pages(path: str, field: str) -> object: page_html, page_metadata = self._render_content_without_templates(target) page_cache[normalized] = (page_html, page_metadata) - if field in page_metadata: - return page_metadata[field] + metadata_value = self._metadata_field_value(page_metadata, field) + if metadata_value is not None: + return metadata_value if field in {"content", "html"}: return page_html if field == "relurl": @@ -184,11 +197,20 @@ def pages(path: str, field: str) -> object: context["query"] = dict(query) context["postvars"] = dict(postvars) context["request"] = request - context["page"] = { - "relurl": route_key, - "absurl": self._absolute_url(route_key), - "relbaseurl": self._relative_base(route_key), + page_data = { + key: value + for key, value in metadata.items() + if isinstance(key, str) and key and not key.startswith("_") and not key.endswith("-function") } + page_data.update( + { + "relurl": route_key, + "absurl": self._absolute_url(route_key), + "relbaseurl": self._relative_base(route_key), + "functionurl": self._absolute_url(route_key), + } + ) + context["page"] = page_data return context def _apply_function_injections( @@ -200,7 +222,7 @@ def _apply_function_injections( route_key: str, warnings: list[str], ) -> None: - function_keys = [key for key in metadata if key.endswith("-function")] + function_keys = [key for key in metadata if isinstance(key, str) and key.lower().endswith("-function")] for function_key in function_keys: try: @@ -291,3 +313,67 @@ def _metadata_string(self, metadata: dict[str, object], key: str, *, default: st stripped = value.strip() return stripped if stripped else default return default + + def _load_global_config_metadata(self) -> dict[str, object]: + config_name = self.config.config_name.strip() + if not config_name: + return {} + + for suffix, renderer in RENDERERS_BY_SUFFIX.items(): + candidate = self.config.srcdir / f"{config_name}{suffix}" + if not candidate.exists() or not candidate.is_file(): + continue + rendered = renderer.render(candidate) + metadata = dict(rendered.metadata) + return self._normalize_legacy_list_keys(metadata) + + return {} + + def _run_init_function(self) -> None: + init_file = self.config.functions_dir / "init.py" + if not init_file.exists() or not init_file.is_file(): + return + + init_context = dict(self.global_data) + init_context["pages"] = self._global_pages_helper + self.function_handler.execute(function_name="init", params={}, global_data=init_context) + self.global_data.update(init_context) + + def _normalize_legacy_list_keys(self, metadata: dict[str, object]) -> dict[str, object]: + normalized = dict(metadata) + for key, value in list(metadata.items()): + if not isinstance(key, str) or not key.endswith("-list"): + continue + base_key = key[: -len("-list")] + if isinstance(value, list): + normalized[base_key] = value + elif isinstance(value, str): + normalized[base_key] = [x.strip() for x in value.split(",") if x.strip()] + elif value is None: + normalized[base_key] = [] + else: + normalized[base_key] = [value] + return normalized + + def _global_pages_helper(self, path: str, field: str) -> object: + target = self.resolve(path) + if target.kind != "content" or target.source_path is None: + return None + + page_html, page_metadata = self._render_content_without_templates(target) + metadata_value = self._metadata_field_value(page_metadata, field) + if metadata_value is not None: + return metadata_value + if field in {"content", "html"}: + return page_html + if field == "relurl": + return normalize_route(path) + return None + + def _metadata_field_value(self, metadata: dict[str, object], field: str) -> object: + if field in metadata: + return metadata[field] + for key, value in metadata.items(): + if isinstance(key, str) and key.lower() == field.lower(): + return value + return None diff --git a/src/httk/web/model/config.py b/src/httk/web/model/config.py index ee1fcec..221aa10 100644 --- a/src/httk/web/model/config.py +++ b/src/httk/web/model/config.py @@ -12,6 +12,7 @@ class SiteConfig: functions_subdir: str = "functions" baseurl: str | None = None compatibility_mode: bool = False + config_name: str = "config" @classmethod def from_srcdir( @@ -20,8 +21,14 @@ def from_srcdir( *, baseurl: str | None = None, compatibility_mode: bool = False, + config_name: str = "config", ) -> Self: - return cls(srcdir=Path(srcdir).resolve(), baseurl=baseurl, compatibility_mode=compatibility_mode) + return cls( + srcdir=Path(srcdir).resolve(), + baseurl=baseurl, + compatibility_mode=compatibility_mode, + config_name=config_name, + ) @property def content_dir(self) -> Path: @@ -37,4 +44,11 @@ def template_dir(self) -> Path: @property def functions_dir(self) -> Path: - return self.srcdir / self.functions_subdir + primary = self.srcdir / self.functions_subdir + if primary.exists(): + return primary + if self.compatibility_mode: + legacy = self.srcdir / "_functions" + if legacy.exists(): + return legacy + return primary diff --git a/src/httk/web/renderers/_frontmatter.py b/src/httk/web/renderers/_frontmatter.py index 3d886ca..0307356 100644 --- a/src/httk/web/renderers/_frontmatter.py +++ b/src/httk/web/renderers/_frontmatter.py @@ -39,6 +39,8 @@ def split_front_matter(text: str) -> tuple[dict[str, Any], str]: target_list = normalized[base_key] if isinstance(value, list): target_list.extend(value) + elif isinstance(value, str): + target_list.extend([x.strip() for x in value.split(",") if x.strip()]) elif value is not None: target_list.append(value) else: diff --git a/src/httk/web/templating/_legacy_formatter.py b/src/httk/web/templating/_legacy_formatter.py new file mode 100644 index 0000000..461d789 --- /dev/null +++ b/src/httk/web/templating/_legacy_formatter.py @@ -0,0 +1,179 @@ +import ast +import string +from html import escape +from typing import Any, Mapping, Sequence + +from markupsafe import Markup + + +class UnquotedText(str): + """Marker type for values that should bypass auto-escaping.""" + + +class HttkTemplateFormatter(string.Formatter): + def __init__(self) -> None: + super().__init__() + self._current_args: Sequence[Any] = () + self._current_kwargs: Mapping[str, Any] = {} + + def format_field(self, value: Any, spec: str) -> str: + return self._format_field(value, spec, quote=None) + + def _format_field(self, value: Any, spec: str, quote: bool | None) -> str: + if spec == "unquoted" or spec.startswith("unquoted:"): + return self._format_field(value, spec[len("unquoted::") :], quote=False) + if spec == "quote" or spec.startswith("quote:"): + return self._format_field(value, spec[len("quote::") :], quote=True) + + if spec.startswith("repeat:"): + template = spec.partition("::")[-1] + new_kwargs: dict[str, Any] = dict(self._current_kwargs) + if "item" in new_kwargs: + prior_items = new_kwargs.get("items") + if isinstance(prior_items, list): + new_kwargs["items"] = [new_kwargs["item"]] + prior_items + else: + new_kwargs["items"] = [new_kwargs["item"]] + if "index" in new_kwargs: + prior_indices = new_kwargs.get("indices") + if isinstance(prior_indices, list): + new_kwargs["indices"] = [new_kwargs["index"]] + prior_indices + else: + new_kwargs["indices"] = [new_kwargs["index"]] + if "index1" in new_kwargs: + prior_indices = new_kwargs.get("indices") + if isinstance(prior_indices, list): + new_kwargs["indices"] = [new_kwargs["index1"]] + prior_indices + else: + new_kwargs["indices"] = [new_kwargs["index1"]] + + def update_and_return(update: dict[str, Any]) -> dict[str, Any]: + new_kwargs.update(update) + return new_kwargs + + if value is None: + raise ValueError(f"HttkTemplateFormatter: asked to loop over None for spec: {spec}") + if isinstance(value, dict): + return "".join( + [ + self.format( + template, + **(update_and_return({"item": value[key], "index": key, "index1": key})), + ) + for key in value + ] + ) + if not isinstance(value, Sequence): + return "" + return "".join( + [ + self.format( + template, + **(update_and_return({"item": value[i], "index": i, "index1": i + 1})), + ) + for i in range(len(value)) + ] + ) + + if spec == "call" or spec.startswith("call:"): + callargs, _sep, newspec = spec.partition("::") + callargs_list = callargs.split(":") + parsed_callargs: list[Any] = [] + for arg in callargs_list: + if arg.startswith("{") and arg.endswith("}"): + parsed_callargs.append(self.get_field(arg[1:-1], self._current_args, self._current_kwargs)[0]) + else: + parsed_callargs.append(arg) + + if hasattr(value, "__repr__") and ("of float object" in repr(value) or "of int object" in repr(value)): + for callarg_idx in range(1, len(parsed_callargs)): + try: + if parsed_callargs[callarg_idx] == "nan": + parsed_callargs[callarg_idx] = float("nan") + else: + parsed_callargs[callarg_idx] = ast.literal_eval(str(parsed_callargs[callarg_idx])) + except (ValueError, SyntaxError): + pass + + if not callable(value): + raise TypeError(f"Templateengine_httk: tried to call non-callable value: {value!r}") + result = value(*parsed_callargs[1:]) + return self._format_field(result, newspec, quote=quote) + + if spec.startswith("getitem:") or spec.startswith("getattr:"): + x, _dummy, newspec = spec.partition(":")[2].partition("::") + call_func: str | None = None + if ":call" in x: + call_func = x.partition(".")[-1].split(":")[0] + x = x.partition(".")[0] + idx: Any + if x.startswith("{") and x.endswith("}"): + idx = self.get_field(x[1:-1], self._current_args, self._current_kwargs)[0] + else: + idx = x + try: + val = value[idx] if spec.startswith("getitem:") else getattr(value, str(idx)) + except (TypeError, KeyError, AttributeError, IndexError): + try: + int_idx = int(str(idx)) + val = value[int_idx] if spec.startswith("getitem:") else "" + except (TypeError, ValueError, KeyError, IndexError): + return "" + + if newspec == "" and call_func is None: + return str(val) + + if call_func is not None: + val = getattr(val, call_func) + newspec = "call" + spec.partition(":call")[-1] + return self._format_field(val, newspec, quote=quote) + + if ( + spec.startswith("if:") + or spec.startswith("if-not:") + or spec.startswith("if-set:") + or spec.startswith("if-unset:") + ): + outcome = ( + (spec.startswith("if:") and bool(value)) + or (spec.startswith("if-not:") and not bool(value)) + or (spec.startswith("if-set:") and value is not None) + or (spec.startswith("if-unset:") and value is None) + ) + + if "::else::" in spec: + if not outcome: + template = spec.partition("::else::")[-1] + else: + template = spec.partition("::else::")[0].partition("::")[-1] + else: + if not outcome: + return "" + template = spec.partition("::")[-1] + return self.format(template, **dict(self._current_kwargs)) + + if value is None: + return "" + + output = super().format_field(value, spec) + if quote is None: + quote = not isinstance(value, (UnquotedText, Markup)) + if quote: + output = escape(output, quote=True).replace("'", "'") + return output + + def get_field( + self, + field_name: str, + args: Sequence[Any], + kwargs: Mapping[str, Any], + ) -> tuple[Any, Any]: + try: + return super().get_field(field_name, args, kwargs) + except (KeyError, AttributeError): + return None, field_name + + def vformat(self, format_string: str, args: Sequence[Any], kwargs: Mapping[str, Any]) -> str: + self._current_args = args + self._current_kwargs = kwargs + return super().vformat(format_string, args, kwargs) diff --git a/src/httk/web/templating/httk_compat.py b/src/httk/web/templating/httk_compat.py index f69ea1d..0cfecdb 100644 --- a/src/httk/web/templating/httk_compat.py +++ b/src/httk/web/templating/httk_compat.py @@ -1,5 +1,9 @@ from pathlib import Path +from markupsafe import Markup + +from ._legacy_formatter import HttkTemplateFormatter, UnquotedText +from .base import TemplateRenderInput from .jinja2_engine import JinjaTemplateEngine LEGACY_TEMPLATE_SUFFIXES: tuple[str, ...] = ( @@ -21,3 +25,42 @@ class HttkCompatTemplateEngine(JinjaTemplateEngine): def __init__(self, template_dir: Path) -> None: super().__init__(template_dir, template_suffixes=LEGACY_TEMPLATE_SUFFIXES) + self._legacy_formatter = HttkTemplateFormatter() + + def render(self, render_input: TemplateRenderInput) -> str: + template_key = self._resolve_template(render_input.template_name) + base_key = self._resolve_template(render_input.base_template_name) + + context = dict(render_input.context) + content = render_input.content_html + if template_key is not None: + content = self._render_with_resolved_template(template_key, content=content, context=context) + + if base_key is not None: + content = self._render_with_resolved_template(base_key, content=content, context=context) + + return content + + def render_fragment(self, *, template_name: str, context: dict[str, object]) -> str | None: + template_key = self._resolve_fragment_template(template_name) + if template_key is None: + return None + + if template_key.endswith(".httkweb.html"): + template_text = (self.template_dir / template_key).read_text(encoding="utf-8") + return self._legacy_formatter.format(template_text, **dict(context)) + + template = self._environment.get_template(template_key) + return template.render(**context) + + def _render_with_resolved_template(self, template_key: str, *, content: str, context: dict[str, object]) -> str: + if not template_key.endswith(".httkweb.html"): + template = self._environment.get_template(template_key) + working = dict(context) + working["content"] = Markup(content) + return template.render(**working) + + template_text = (self.template_dir / template_key).read_text(encoding="utf-8") + working = dict(context) + working["content"] = UnquotedText(content) + return self._legacy_formatter.format(template_text, **working) diff --git a/tests/test_api.py b/tests/test_api.py index 33540ba..1caf399 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -72,3 +72,111 @@ def test_publish_compatibility_mode_prefers_httkweb_templates(tmp_path: Path) -> rendered = (out / "index.html").read_text(encoding="utf-8") assert "legacy=" in rendered assert "modern=" not in rendered + + +def test_create_asgi_app_compatibility_mode_supports_legacy_repeat(tmp_path: Path) -> None: + src = tmp_path / "src" + (src / "content").mkdir(parents=True) + (src / "static").mkdir(parents=True) + (src / "templates").mkdir(parents=True) + + (src / "content" / "index.md").write_text( + "---\n" + "template: default\n" + "menuitems:\n" + " - alpha\n" + " - beta\n" + "---\n\n" + "hello", + encoding="utf-8", + ) + (src / "templates" / "default.httkweb.html").write_text( + "
      {menuitems:repeat::
    • {{item}}
    • }
    {content}", + encoding="utf-8", + ) + + app = create_asgi_app(src, compatibility_mode=True) + + with TestClient(app) as client: + response = client.get("/") + + assert response.status_code == 200 + assert "
  • alpha
  • " in response.text + assert "
  • beta
  • " in response.text + assert "hello" in response.text + + +def test_create_asgi_app_compatibility_mode_supports_legacy_pages_call(tmp_path: Path) -> None: + src = tmp_path / "src" + (src / "content").mkdir(parents=True) + (src / "static").mkdir(parents=True) + (src / "templates").mkdir(parents=True) + + (src / "content" / "about.md").write_text("---\ntitle: About Page\n---\n\nAbout body", encoding="utf-8") + (src / "content" / "index.md").write_text( + "---\n" + "template: default\n" + "menuitems:\n" + " - about\n" + "---\n\n" + "home", + encoding="utf-8", + ) + (src / "templates" / "default.httkweb.html").write_text( + "{menuitems:repeat::{{pages:call:{{item}}:title}}}{content}", + encoding="utf-8", + ) + + app = create_asgi_app(src, compatibility_mode=True) + + with TestClient(app) as client: + response = client.get("/") + + assert response.status_code == 200 + assert "About Page" in response.text + + +def test_create_asgi_app_compatibility_mode_loads_config_frontmatter(tmp_path: Path) -> None: + src = tmp_path / "src" + (src / "content").mkdir(parents=True) + (src / "static").mkdir(parents=True) + (src / "templates").mkdir(parents=True) + + (src / "config.httkweb").write_text("---\nmenuitems-list: index, contact, bare\n---\n", encoding="utf-8") + (src / "content" / "index.md").write_text("---\ntemplate: default\n---\n\nhello", encoding="utf-8") + (src / "templates" / "default.httkweb.html").write_text( + "
      {menuitems:repeat::
    • {{item}}
    • }
    {content}", + encoding="utf-8", + ) + + app = create_asgi_app(src, compatibility_mode=True) + with TestClient(app) as client: + response = client.get("/") + + assert response.status_code == 200 + assert "
  • index
  • " in response.text + assert "
  • contact
  • " in response.text + assert "
  • bare
  • " in response.text + + +def test_create_asgi_app_compatibility_mode_runs_init_function(tmp_path: Path) -> None: + src = tmp_path / "src" + (src / "content").mkdir(parents=True) + (src / "static").mkdir(parents=True) + (src / "templates").mkdir(parents=True) + (src / "functions").mkdir(parents=True) + + (src / "functions" / "init.py").write_text( + "def execute(global_data, **kwargs):\n" + " global_data['website_name'] = 'Legacy Site'\n", + encoding="utf-8", + ) + (src / "content" / "index.md").write_text("---\ntemplate: default\n---\n\nhello", encoding="utf-8") + (src / "templates" / "default.httkweb.html").write_text("{website_name}:{content}", encoding="utf-8") + + app = create_asgi_app(src, compatibility_mode=True) + with TestClient(app) as client: + response = client.get("/") + + assert response.status_code == 200 + assert "Legacy Site:" in response.text From aba2e8abea78fd4ecdef8993eb10b921b93e5f29 Mon Sep 17 00:00:00 2001 From: Rickard Armiento Date: Fri, 27 Mar 2026 00:45:51 +0000 Subject: [PATCH 2/2] Fix spelling errors and other errors in rendering the examples --- .gitignore | 1 + .../hello_world_app/src/functions/init.py | 2 +- .../src/templates/search_result.httkweb.html | 2 +- examples/modern/minimal/public/index.html | 17 ----------------- src/httk/web/engine/site_engine.py | 2 +- src/httk/web/templating/_legacy_formatter.py | 12 +++++++++--- 6 files changed, 13 insertions(+), 23 deletions(-) delete mode 100644 examples/modern/minimal/public/index.html diff --git a/.gitignore b/.gitignore index 95ab1bd..edf4096 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *~ \#*\# .\#* +examples/*/*/public # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/examples/legacy/hello_world_app/src/functions/init.py b/examples/legacy/hello_world_app/src/functions/init.py index da86307..0a65fbe 100644 --- a/examples/legacy/hello_world_app/src/functions/init.py +++ b/examples/legacy/hello_world_app/src/functions/init.py @@ -1,3 +1,3 @@ def execute(global_data, **kargs): - print("Debug: running website initalization function.") + print("Debug: running website initialization function.") global_data['greeter'] = "world" diff --git a/examples/legacy/search_app/src/templates/search_result.httkweb.html b/examples/legacy/search_app/src/templates/search_result.httkweb.html index 2d6d9ca..6eed70a 100644 --- a/examples/legacy/search_app/src/templates/search_result.httkweb.html +++ b/examples/legacy/search_app/src/templates/search_result.httkweb.html @@ -3,7 +3,7 @@

    Search results:

    # Compound id Formula -Anonumous formula +Anonymous formula Names Spacegroup {result:repeat:: diff --git a/examples/modern/minimal/public/index.html b/examples/modern/minimal/public/index.html deleted file mode 100644 index 4e16bba..0000000 --- a/examples/modern/minimal/public/index.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - Minimal Modern Example - - -
    -
    -

    Minimal Modern Example

    -

    Hello from httk-web

    -

    This is the modern example using Jinja2 templates.

    -
    -
    - - \ No newline at end of file diff --git a/src/httk/web/engine/site_engine.py b/src/httk/web/engine/site_engine.py index ca7fc2a..5537234 100644 --- a/src/httk/web/engine/site_engine.py +++ b/src/httk/web/engine/site_engine.py @@ -200,7 +200,7 @@ def pages(path: str, field: str) -> object: page_data = { key: value for key, value in metadata.items() - if isinstance(key, str) and key and not key.startswith("_") and not key.endswith("-function") + if isinstance(key, str) and key and not key.startswith("_") and not key.lower().endswith("-function") } page_data.update( { diff --git a/src/httk/web/templating/_legacy_formatter.py b/src/httk/web/templating/_legacy_formatter.py index 461d789..b2a7e69 100644 --- a/src/httk/web/templating/_legacy_formatter.py +++ b/src/httk/web/templating/_legacy_formatter.py @@ -21,9 +21,9 @@ def format_field(self, value: Any, spec: str) -> str: def _format_field(self, value: Any, spec: str, quote: bool | None) -> str: if spec == "unquoted" or spec.startswith("unquoted:"): - return self._format_field(value, spec[len("unquoted::") :], quote=False) + return self._format_field(value, spec[len("unquoted:") :], quote=False) if spec == "quote" or spec.startswith("quote:"): - return self._format_field(value, spec[len("quote::") :], quote=True) + return self._format_field(value, spec[len("quote:") :], quote=True) if spec.startswith("repeat:"): template = spec.partition("::")[-1] @@ -174,6 +174,12 @@ def get_field( return None, field_name def vformat(self, format_string: str, args: Sequence[Any], kwargs: Mapping[str, Any]) -> str: + previous_args = self._current_args + previous_kwargs = self._current_kwargs self._current_args = args self._current_kwargs = kwargs - return super().vformat(format_string, args, kwargs) + try: + return super().vformat(format_string, args, kwargs) + finally: + self._current_args = previous_args + self._current_kwargs = previous_kwargs