{"id":21068940,"url":"https://github.com/kamchatka-volcano/hypertextcpp","last_synced_at":"2025-05-16T03:33:41.539Z","repository":{"id":103982993,"uuid":"381475416","full_name":"kamchatka-volcano/hypertextcpp","owner":"kamchatka-volcano","description":"An HTML template engine using C++ code generation","archived":false,"fork":false,"pushed_at":"2024-10-06T20:00:48.000Z","size":151,"stargazers_count":32,"open_issues_count":0,"forks_count":2,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-10-30T02:03:27.800Z","etag":null,"topics":["cpp17","html-template","html-template-engine","html-template-system"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"ms-pl","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kamchatka-volcano.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-06-29T19:27:13.000Z","updated_at":"2024-10-06T20:00:51.000Z","dependencies_parsed_at":"2024-09-08T14:33:52.577Z","dependency_job_id":"d9863d5a-4fdf-422f-b35b-fee27811afdc","html_url":"https://github.com/kamchatka-volcano/hypertextcpp","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kamchatka-volcano%2Fhypertextcpp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kamchatka-volcano%2Fhypertextcpp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kamchatka-volcano%2Fhypertextcpp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kamchatka-volcano%2Fhypertextcpp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kamchatka-volcano","download_url":"https://codeload.github.com/kamchatka-volcano/hypertextcpp/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225405262,"owners_count":17469317,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cpp17","html-template","html-template-engine","html-template-system"],"created_at":"2024-11-19T18:29:32.900Z","updated_at":"2025-05-16T03:33:41.532Z","avatar_url":"https://github.com/kamchatka-volcano.png","language":"C++","readme":"\u003cp align=\"center\"\u003e\n  \u003cimg height=\"96\" src=\"doc/logo.png\"/\u003e  \n\u003c/p\u003e\n\n**hypertextcpp** is an HTML templating system for C++ applications.\nIt provides an [.htcpp](https://marketplace.visualstudio.com/items?itemName=kamchatka-volcano.htcpp) template file format  and a command-line utility that transpiles it to C++ HTML rendering code. Include the generated C++ header file in your project, set up a build system to update it when necessary, and you're all set.\n\nA quick example:\n\n[`examples/01/todolist.htcpp`](examples/01/todolist.htcpp)\n```html\n\u003chtml\u003e\t\n    \u003cbody\u003e\n        \u003ch1\u003e$(cfg.name)'s todo list:\u003c/h1\u003e\n        \u003cp\u003e No tasks found \u003c/p\u003e?(cfg.tasks.empty())\n        \u003cul\u003e\n            \u003cli\u003e$(task.name)\u003c/li\u003e@(auto task : cfg.tasks)\n        \u003c/ul\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\n\n```\n\nNow let's generate the C++ header:\n```console\nkamchatka-volcano@home:~$ hypertextcpp todolist.htcpp\n```\n\nThis command creates the `todolist.h` file in the current working directory. All that's left is to use it in our program.\n\n[`examples/01/todolist_printer.cpp`](examples/01/todolist_printer.cpp)\n```cpp\n#include \"todolist.h\"\n#include \u003cstring\u003e\n#include \u003cvector\u003e\n\nint main()\n{\t\n    struct PageParams{\n        struct Task{\n            std::string name;\n        };\n        std::string name = \"Bob\";\n        std::vector\u003cTask\u003e tasks = {{\"laundry\"}, {\"cooking\"}};\n    } pageParams;\n    auto page = todolist{};\n    page.print(pageParams);\n    return 0;\n}\n\n```\n\n  Compile it with your preferred method, launch it, and you will get this output:\n```console\nkamchatka-volcano@home:~$ ./todolist_printer\n\u003chtml\u003e\t\n    \u003cbody\u003e\n        \u003ch1\u003eBob's todo list:\u003c/h1\u003e\n        \n        \u003cul\u003e\n            \u003cli\u003elaundry\u003c/li\u003e\u003cli\u003ecooking\u003c/li\u003e\n        \u003c/ul\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\n\n```\n\n\n## Table of contents\n* [Template file syntax](#template-file-syntax)\n    * [Expressions](#expressions)\n    * [Statements](#statements)\n    * [Global statements](#global-statements)\n    * [Control flow extensions](#control-flow-extensions)\n    * [Sections](#sections)\n    * [Procedures and partial rendering](#procedures-and-partial-rendering)\n* [Code generation](#code-generation)\n    * [Command line parameters](#command-line-parameters)\n    * [Single header renderer](#single-header-renderer)\n    * [Header and source renderer](#header-and-source-renderer)\n    * [Shared library renderer](#shared-library-renderer)\n* [Installation](#installation)\n* [Running tests](#running-tests)\n* [License](#license)\n\n## Template file syntax\n\n### Expressions\n**$(**`c++ expression`**)**\nExpressions are used to add the application's data to the template. It can be any valid C++ expression, with the only condition that its result must be streamable to the default output stream `std::ostream`. Expressions can be placed anywhere in the HTML template, except in tag names.\nIn our todolist example, `$(cfg.name)` is an expression that adds the template config variable `name` to the result page.\n\n### Statements\n**${**`c++ statement(s)`**}**\nStatements are used to add any valid C++ code to the template rendering function. For example, you can add variables, class declarations, or lambdas. Let's say you don't like the default name `cfg` used for passing data to the template. You can create a reference to it with any name you like and use it later:\n\n```html\n    ${ auto\u0026 param = cfg;}\n    \u003ch1\u003e$(param.name)'s todo list:\u003c/h1\u003e\n```\n\nNote, that `cfg` and `out` are reserved names used for parameters in the generated\nrendering function. Also, don't put anything in the `htcpp` namespace.\n\n### Global statements\n**#{**`c++ statement(s)`**}**  \nThese statements are used to add any valid C++ code outside the template rendering function. Unlike regular statements, with global ones you can add include directives or function definitions. Global statements can only be placed at the top level of the `htcpp` template, outside any HTML element. Please don't put anything in the `htcpp` namespace.\n\n### Control flow extensions\nIf **hypertextcpp** used a common approach for control flow in HTML template engines, our todolist example would look something like this:\n```html\n\u003chtml\u003e\t\n    \u003cbody\u003e\n        \u003ch1\u003e%%cfg.name%%'s todo list:\u003c/h1\u003e\n        %%if cfg.tasks.empty()%%\n        \u003cp\u003e No tasks found \u003c/p\u003e\t\t\t\t\n        %%end%%\n        \u003cul\u003e\n            %%for auto task : cfg.tasks%%\n                \u003cli\u003e$(task.name)\u003c/li\u003e\n            %%end%%\n        \u003c/ul\u003e        \n    \u003c/body\u003e\n\u003c/html\u003e\n\n```\n\nIn our opinion, it significantly hurts the readability of the document tree and makes it hard to choose indentation and keep it consistent — notice how different approaches are used for if and for blocks in the example.\n\n**hypertextcpp** solves this problem by applying control flow to the HTML elements themselves without adding logic block scopes to the document. It uses just two extensions for tags to make this work:\n\n* Conditional extension  \n  **?(**`c++ condition`**)**  \n  HTML elements with this extension are added to the document only when condition is fulfilled.\n  Example of usage from our todolist template:\n  ```html\n      \u003cp\u003e No tasks found \u003c/p\u003e?(cfg.tasks.empty())\n  ```\n\n* Loop extension  \n  **@(**`c++ init-statement; condition; iteration_expression`**)**  \n  or  \n  **@(**`c++ range_declaration : range_expression`**)**  \n  HTML elements with this extension are added to the document multiple times, on each step of the loop or for each element of the iterated range.\n  Example of usage from our todolist template:\n  ```html\n      \u003cli\u003e$(task.name)\u003c/li\u003e@(auto task : cfg.tasks)\n  ```\n  Let's add another example for another type of `for` loop:\n  ```html\n      \u003cli\u003eTask#$(i)\u003c/li\u003e@(auto i = 0 ; i \u003c 3; ++i)\n  ```\n  which evaluates to\n  ```html\n      \u003cli\u003eTask#0\u003c/li\u003e\n      \u003cli\u003eTask#1\u003c/li\u003e\n      \u003cli\u003eTask#2\u003c/li\u003e\n  ```\n\nBoth extensions can be added to the opening or closing tag, but each tag and HTML element can only have one extension.\n\nIt's recommended to add the extension to the opening tag for multiline HTML elements:\n```html\n\u003cdiv\u003e?(cfg.greet)\n    \u003cp\u003eHello world!\u003c/p\u003e\n\u003c/div\u003e\n```\nand to the closing tag for the single-line ones:\n```html\n\u003cdiv\u003e\u003cp\u003eHello world!\u003c/p\u003e\u003c/div\u003e?(cfg.greet)\n```\n\nNote that while extensions hide control flow block scopes from the template document, they're still present in the generated C++ code and implemented with regular `if` and `for` control structures. Therefore, a template like this:\n\n```html\n    \u003cdiv\u003e@(auto i = 0; i\u003c3; ++i)\n        ${ auto num = i*2;}\n        \u003cp\u003e Item #$(num)\u003cp\u003e\n    \u003c/div\u003e\n    \u003cp\u003e Last num is $(num) \u003c/p\u003e\n```\nwon't compile because the `num` variable isn't visible outside the `for` block scope generated by the loop extension on the `div` tag.\n\n### Sections\n**\\[\\[** `text, html elements, statements, expressions or other sections` **]]**  \nSections can contain a part of the template document, and it's possible to attach control flow extensions to them. Their main usage is adding attributes to HTML elements conditionally.\n\nLet's update the todolist example by adding a line-through text style to completed tasks:\n[`examples/02/todolist.htcpp`](examples/02/todolist.htcpp)\n```html\n\u003chtml\u003e\t\n    \u003cbody\u003e\n        \u003ch1\u003e$(cfg.name)'s todo list:\u003c/h1\u003e\n        \u003cp\u003e No tasks found \u003c/p\u003e?(cfg.tasks.empty())\n        \u003cul\u003e\n            \u003cli [[style=\"text-decoration: line-through;\"]]?(task.isCompleted)\u003e$(task.name)\u003c/li\u003e@(auto task : cfg.tasks)\n        \u003c/ul\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\n```\n\nDon't forget to update the C++ template config structure!  \n[`examples/02/todolist_printer.cpp`](examples/02/todolist_printer.cpp)\n```cpp\n//...\nstruct PageParams{\n        struct Task{\n            std::string name;\n            bool isCompleted = false;\n        };\n        std::string name = \"Bob\";\n        std::vector\u003cTask\u003e tasks = {{\"laundry\", true}, {\"cooking\", false}};\n    } pageParams;\n//...\n```\n\nTip of the day: Keep your template tidy and don't introduce sections when it's possible to attach extensions to HTML elements.\n\n\n### Procedures and partial rendering\n**#**`procedureName`**(){**`html elements, statements, expressions or sections`**}**\n\nParts of the `htcpp` template can be placed inside procedures—parameterless functions capturing the `cfg` variable. They are available for call from the C++ application, so if any part of the page needs to be rendered separately from the whole template, procedures are a big help.\nProcedures can only be placed at the top level of the `htcpp` template, outside any HTML element.\nLet's put the list of tasks from the todolist example in the procedure:\n\n[`examples/03/todolist.htcpp`](examples/03/todolist.htcpp)\n```html\n#taskList(){\n    \u003cli [[style=\"text-decoration: line-through;\"]]?(task.isCompleted)\u003e$(task.name)\u003c/li\u003e@(auto task : cfg.tasks)\n}\n\u003chtml\u003e\t\n    \u003cbody\u003e\n        \u003ch1\u003e$(cfg.name)'s todo list:\u003c/h1\u003e\n        \u003cp\u003e No tasks found \u003c/p\u003e?(cfg.tasks.empty())\n        \u003cul\u003e\n            $(taskList())\n        \u003c/ul\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\n```\n\nNow the tasks list can be output to stdout by itself like this:  \n[`examples/03/todolist_printer.cpp`](examples/03/todolist_printer.cpp)\n```cpp\n    //...\n    auto page = todolist{};\n    page.print(\"taskList\", pageParams); \n    //...\n```\n\n## Code generation\n### Command line parameters\n ```console\nkamchatka-volcano@home:~$ hypertextcpp --help\nUsage: hypertextcpp [commands] [flags] \nFlags:\n  --help                                      show usage info and exit\nCommands:\n    generateHeaderOnly [options]              generate header only file\n    generateSharedLibrarySource [options]     generate shared library source \n                                                file\n    generateHeaderAndSource [options]         generate header and source files\n ```\n\nSingle header renderer generation:\n ```console\nkamchatka-volcano@home:~$ hypertextcpp generateHeaderOnly --help\nUsage: hypertextcpp generateHeaderOnly \u003cinput\u003e [params] [flags] \nArguments:\n    \u003cinput\u003e (path)         .htcpp file to transpile\n   -outputDir=\u003cpath\u003e       output dir\n                             (if empty, current working directory is used)\n                             (optional, default: \"\")\n   -className=\u003cstring\u003e     generated class name\n                             (if empty, input file name is used)\n                             (optional, default: \"\")\nFlags:\n  --help                   show usage info and exit\n ```\n\nHeader and source renderer generation:\n ```console\nkamchatka-volcano@home:~$ hypertextcpp generateHeaderAndSource --help\nUsage: hypertextcpp generateHeaderAndSource \u003cinput\u003e -configClassName=\u003cstring\u003e [params] [flags] \nArguments:\n    \u003cinput\u003e (path)               .htcpp file to transpile\nParameters:\n   -configClassName=\u003cstring\u003e     config class name\n   -outputDir=\u003cpath\u003e             output dir\n                                   (if empty, current working directory is used)\n                                   (optional, default: \"\")\n   -className=\u003cstring\u003e           generated class name\n                                   (if empty, input file name is used)\n                                   (optional, default: \"\")\nFlags:\n  --help                         show usage info and exit\n ```\n\nShared library renderer generation:\n ```console\nkamchatka-volcano@home:~$ hypertextcpp generateSharedLibrarySource --help\nUsage: hypertextcpp generateSharedLibrarySource \u003cinput\u003e [params] [flags] \nArguments:\n    \u003cinput\u003e (path)       .htcpp file to transpile\n   -outputDir=\u003cpath\u003e     output dir\n                           (if empty, current working directory is used)\n                           (optional, default: \"\")\nFlags:\n  --help                 show usage info and exit\n ```\n\n\n### Single header renderer\nIn this mode, the **hypertextcpp** generates a C++ header file that you're supposed to simply include in your project. A generated renderer class has the name of the `.htcpp` template file.\nConverting the template to C++ code each time you modify it is a laborious task, so it makes sense to add this step to your build process. To do this with CMake you can use `hypertextcpp_GenerateHeader` function from the `hypertextcpp.cmake` file.\nNote that this function launches the `hypertextcpp` executable, so it should be installed on your system first. \n\n[`examples/04/CMakeLists.txt`](examples/04/CMakeLists.txt)\n```\ncmake_minimum_required(VERSION 3.18)\n\n\ninclude(../../hypertextcpp.cmake)\nhypertextcpp_GenerateHeader(\n  TEMPLATE_FILE todolist.htcpp \n  CLASS_NAME TodoList\n)\n\nset(SRC\n    todolist_printer.cpp\n    todolist.h)\n\n#Please note that to generate the header todolist.h, it must pe passed to the target sources    \nadd_executable(todolist_printer ${SRC})\n\ntarget_compile_features(todolist_printer PUBLIC cxx_std_17)\nset_target_properties(todolist_printer PROPERTIES CXX_EXTENSIONS OFF)\n```\n\nNow, every time you change the template `todolist.htcpp`, the corresponding header will be regenerated on the next build.\n\n### Header and source renderer\nIt can feel quite wasteful to rebuild all object files that include the renderer header each time the template file is changed, so `hypertextcpp` supports the generation of the renderer as a header and implementation file. In this mode, the generated rendering methods aren't function templates, and you need to provide the config name as a command-line parameter and either define the config structure inside the template or include it from there (check `examples/ex_06` and `examples/ex_07`).\nTo do this with CMake, you can use the `hypertextcpp_GenerateHeaderAndSource` function from the `hypertextcpp.cmake` file.\n\n```\nhypertextcpp_GenerateHeaderAndSource(\n        TEMPLATE_FILE todolist.htcpp\n        CONFIG_CLASS_NAME PageParams)\n```\n\n### Shared library renderer\n **hypertextcpp** also supports the generation of a C++ source file for building templates in the form of shared libraries and linking them dynamically from your application. This way it's possible to rebuild the template and update it without restarting the application.\nIt requires duplicating the config declaration in the .htcpp template, registering it with the `HTCPP_CONFIG` macro in both the template and the application source, generating the renderer code with the `generateSharedLibrarySource` command, building the library, and loading it using the tiny API installed from the `shared_lib_api/` directory. It sounds scarier than it is, so let's quickly update the todolist example to see how it works.\n\nFirst we need to copy the config structure declaration in the template:  \n[`examples/05/todolist.htcpp`](examples/05/todolist.htcpp)\n```html\n#{\n    #include \u003cvector\u003e\n    struct PageParams{\n        struct Task{\n            std::string name;\n            bool isCompleted = false;\n        };\n        std::string name = \"Bob\";\n        std::vector\u003cTask\u003e tasks = {{\"laundry\", true}, {\"cooking\", false}};\n    } pageParams;\n    HTCPP_CONFIG(PageParams);\n}\n\n#taskList(){\n    \u003cli [[style=\"text-decoration: line-through;\"]]?(task.isCompleted)\u003e$(task.name)\u003c/li\u003e@(auto task : cfg.tasks)\n}\n\u003chtml\u003e\t\n    \u003cbody\u003e\n        \u003ch1\u003e$(cfg.name)'s todo list:\u003c/h1\u003e\n        \u003cp\u003e No tasks found \u003c/p\u003e?(cfg.tasks.empty())\n        \u003cul\u003e\n            $(taskList())\n        \u003c/ul\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\n```\n\nBe sure to use an exact copy; any mismatch in the config structure between the template and the application can't be handled gracefully. So, if you try to load a template library with a different structure, your application will abort with a runtime error. This means that by using a htcpp template in the form of shared libraries, you lose one of the main advantages of hypertextcpp—compile-time type safety. Because of this, it's recommended to use this mode only after your template config has stabilized and doesn't change often.\n\nNext, we need to build our template renderer as a library. It's not possible to bundle multiple template files in one library, so we can build a library from a single `.htcpp` file by using the `hypertextcpp_BuildSharedLibrary` CMake function from `hypertextcpp.cmake`:\n\n```\ncmake_minimum_required(VERSION 3.18)\n\nhypertextcpp_BuildSharedLibrary(\n        TEMPLATE_FILE todolist.htcpp\n)\nadd_dependencies(${PROJECT_NAME} todolist)\n```\n\nIf everything goes right, you'll get `libtodolist.so` in the build directory. That's our `todolist.htcpp` template compiled as a shared library.\nNext, let's modify `todolist_printer.cpp` to be able to load it:\n[`examples/05/todolist_printer.cpp`](examples/05/todolist_printer.cpp)\n```cpp\n#include \u003chypertextcpp/templateloader.h\u003e\n#include \u003cstring\u003e\n#include \u003cvector\u003e\n\nstruct PageParams{\nstruct Task{\nstd::string name;\nbool isCompleted = false;\n};\nstd::string name = \"Bob\";\nstd::vector\u003cTask\u003e tasks = {{\"laundry\", true}, {\"cooking\", false}};\n};\nHTCPP_CONFIG(PageParams);\n\nint main()\n{\nauto pageParams = PageParams{};\nauto page = htcpp::loadTemplate\u003cPageParams\u003e(\"libtodolist.so\");\npage-\u003eprint(pageParams);\nreturn 0;\n}\n```\n\nOn Linux, it may be necessary to link the system library `dl` to the build config, as we load the template library dynamically.\n`todolist_printer` should compile and work the same as the previous example using the single-header approach.\n\n## Installation\n\n```console\ngit clone https://github.com/kamchatka-volcano/hypertextcpp.git\ncd hypertextcpp\ncmake -S . -B build\ncmake --build build\ncmake --install build\ncmake --install build --component shared_lib_api\n```\nYou can skip the installation of `shared_lib_api` component if you don't need to load templates in shared libraries form. \n\n## Running tests\n```console\ncd hypertextcpp\ncmake -S . -B build -DENABLE_TESTS=ON\ncmake --build build \ncd build/tests \u0026\u0026 ctest\n```\n\n## License\n**hypertextcpp** is licensed under the [MS-PL license](/LICENSE.md)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkamchatka-volcano%2Fhypertextcpp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkamchatka-volcano%2Fhypertextcpp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkamchatka-volcano%2Fhypertextcpp/lists"}