{"id":18253587,"url":"https://github.com/krk/carender","last_synced_at":"2026-06-30T12:32:03.314Z","repository":{"id":146935062,"uuid":"188617972","full_name":"krk/carender","owner":"krk","description":"carender - car text template renderer","archived":false,"fork":false,"pushed_at":"2019-06-30T14:38:41.000Z","size":402,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-04-08T21:37:03.969Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/krk.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2019-05-25T22:29:30.000Z","updated_at":"2019-06-30T14:56:23.000Z","dependencies_parsed_at":null,"dependency_job_id":"8dcdf1c6-89bf-4bec-80ee-5713eb5f7207","html_url":"https://github.com/krk/carender","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/krk/carender","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/krk%2Fcarender","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/krk%2Fcarender/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/krk%2Fcarender/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/krk%2Fcarender/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/krk","download_url":"https://codeload.github.com/krk/carender/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/krk%2Fcarender/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34967614,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-30T02:00:05.919Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":[],"created_at":"2024-11-05T10:07:23.721Z","updated_at":"2026-06-30T12:32:03.303Z","avatar_url":"https://github.com/krk.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# carender - car template renderer\n\n`carender` renders a `car` template with provided variables to create a text output.\n\n## Quick Start\n\nOn a system that can run binaries built on `debian:stretch`, run the following commands to see a rendered template:\n```sh\n./build_in_docker.sh\ncd from_docker/bin\nRANGE_SYMBOLS=../../examples/fruits/ranges.txt SYMBOLS=../../examples/fruits/symbols.txt ./carender ../../examples/fruits/template.car\n```\n\n## Design\n\n### Requirements\n* No third-party library dependencies except libc/libc++/STL.\n* No lexer/parser generators.\n* Target C++14 standard.\n* Having fun throughout the development process.\n\n### Principles\n\nOOP design patterns, SOLID and DRY principles are observed as far as possible in the limited time that was allocated to this project. That is, except `test` and `cmd` folders. There is a lot of code duplication in `tests` and there are no tests in `cmd`.\n\n### Decisions\n\n* No unused code.\n* No commented-out code.\n* Target recent g++ and clang++ versions.\n* Target 100% unit test **branch** coverage.\n* There are two types of template symbols, string (`Renderer::symbols`) and strings (`Renderer::rangeSymbols`).\n\n## Implementation\n\n`carender` consists of a lexer, a parser, a renderer and a command line tool that uses these components as an example.\n\n**Lexer** reads an `istream` and ignores whitespace in non-text context, e.g. between START_DIRECTIVE and START_BLOCK in `{{   #loop range element}}`. Inside text content, lexer does not ignore whitespace. For this purpose, lexer keeps track of the state as \"inside START_DIRECTIVE or not\" and \"inside START_BLOCK/END_BLOCK or not\".\n\nEntry point is the public `lex` method:\n```c++\nbool lex(std::istream \u0026input, std::vector\u003cToken\u003e \u0026output, std::ostream \u0026error);\n```\n\n**Parser** is a hand-written recursive-descent parser that generates four types of nodes:\n`TextNode`, `PrintNode`, `LoopNode`, `IfEqNode`.\n\nEntry point is the public `parse` method:\n\n```c++\nstd::vector\u003cstd::unique_ptr\u003cNode\u003e\u003e parse(const std::vector\u003cToken\u003e \u0026tokens, std::ostream \u0026error)\n```\n\n\nSimplified `Parser` method call graph:\n\n![parser methods](doc/parser.png)\n\nNotes:\n* `parseBlock` method choses the parser method to dispatch for a block based on the keyword (`loop` and `ifeq`) from the `keywordParser` map defined in the `Parser`.\n* To reduce code duplication, common functionality of `parseLoop` and `parseIfEq` are moved to the template member method, `parseBlockWithTwoSymbols`.\n* Parser is tested with the Lexer's output and separately with manually crafted Token streams that the lexer may not generate, in `test_parser.cpp`.\n\n**Renderer** implements the `Visitor Pattern` to consume a stream of nodes from the parser.\n\nEntry point is the public `visit` methods called from the `accept` methods on nodes:\n\n```c++\nnode-\u003eaccept(renderer);\n```\n\nData flow using `lexer`, `parser` and `renderer`:\n\n![parser methods](doc/dataflow.png)\n\n### Building\n\n`make all`\n\nA [Makefile](Makefile) is used for building, testing and coverage reporting.\n\n`make` targets:\n\n* `lib`: To build the static library.\n* `test`: To build unit tests.\n* `cmd`: To build example command-line tool.\n* `docs`: To build doxygen documentation.\n* `clean`: Remove build artifacts.\n* `cover`: Build unit tests with coverage instrumentation, run tests to collect coverage information and create html reports in `test_coverage` folder.\n* `cover-show`: Build `cover` and display index with `firefox`.\n\nSet `CXX` environment variable to use a different compiler. Tested with recent clang++, afl-g++, afl-clang++ versions.\n\n### Testing\n\n`make test \u0026\u0026 bin/test`\n\n* [catch2](https://github.com/catchorg/Catch2) chosen for unit testing. It is a header-only library and easy to add to a project.\n\n### Coverage\n\n`make cover`\n\n* `g++` and [lcov](http://ltp.sourceforge.net/coverage/lcov.php) for coverage reports. Coverage reporting is not supported when building with `clang++`.\n* To increase branch coverage, compiler-generated unreachable destructor branches are excluded with `LCOV_EXCL_START` and `LCOV_EXCL_STOP` markers.\n  * Related: [\"G++ emits two copies of constructors and destructors.\"](https://gcc.gnu.org/bugs/#nonbugs_cxx) and [What is the branch in the destructor reported by gcov?](https://stackoverflow.com/a/7199706/128002).\n\n![coverage](doc/coverage.png)\n\n## Usage\n\n`carender` can be linked as a static library. A command line tool is provided as an example of using the library.\n\n**Warning:** Only tested on Ubuntu 18.04 and Debian Stretch.\n\n* A `Dockerfile` added to document the build steps in a reproducible manner. Run `build_in_docker.sh` to build, test and create coverage reports.\n\n### High-level Driver API\n\nDriver API is designed to be batteries-included, for consumers that do not want to configure subcomponents (`lexer`, `parser`, `renderer`) separately.\n\n```c++\n#include \"driver.hpp\"\n#include \u003cfstream\u003e\n\n// symbols and rangeSymbols are unordered_map instances defining valid symbols and their values for the template.\nauto driver = car::driver::Driver(symbols, rangeSymbols, std::cout, std::cerr);\nauto templ = std::ifstream(\"template.car\");\n\nif(driver.Render(templ)) {\n    // We have the template rendered to the `output` stream, `std::cout` in this case.\n    return;\n}\n// There were some errors, they are written to the `error` stream, `std::cerr` in this case.\n```\n\nSee the sample application for an example usage of the Driver API at [main.cpp](cmd/main.cpp)\n\n### Low-level API\n\nLow-level API allows for configuration of the subcomponents. New options may be added in new versions.\n\n#### Lexer\n\n`Lexer` consumes an input stream and emits a stream of tokens as a `std::vector\u003ccar::lexer::Token\u003e`. See [test_lexer.cpp](test/test_lexer.cpp) and [driver.cpp](cmd/driver.cpp) for usage examples.\n\n```c++\nauto lexer = car::lexer::Lexer();\nauto tokens = std::vector\u003ccar::lexer::Token\u003e();\n\nlexer.lex(input, tokens, error);\n```\n\n#### Parser\n\n`Parser` consumes a token stream and emits a stream of nodes as a `std::vector\u003cstd::unique_ptr\u003cNode\u003e\u003e`. See [test_parser.cpp](test/test_parser.cpp) and [driver.cpp](cmd/driver.cpp) for usage examples.\n\n```c++\nauto options = ParserOptions(symbolNames);\nauto parser = Parser(options);\n\nauto nodes = parser.parse(tokens, error);\n```\n\n#### Renderer\n\n`Renderer` consumes a node stream and emits text. See [test_renderer.cpp](test/test_renderer.cpp) and [driver.cpp](cmd/driver.cpp) for usage examples.\n\n```c++\nauto renderer = Renderer(symbols, rangeSymbols, output, error);\nfor (auto const \u0026n : nodes)\n{\n    n-\u003eaccept(renderer);\n}\n```\n\n# `car` template language\n\n## An example to get a taste:\n\n### Template\n```\nHello {{name}},\n\n{{#loop items item}}\nI like {{item}}{{#ifeq item favorite}} very much{{/ifeq}}.\n{{/loop}}\n\nCheers!\n```\n\n### Variables\n```\nname: \"Donald\"\nitems: [\"apples\", \"pineapples\", \"oranges\"]\nfavorite: \"pineapples\"\n```\n\n### Rendered output\n```\nHello Donald,\n\nI like apples.\nI like pineapplesvery much.\nI like oranges.\n\nCheers!\n```\n\n## Grammar\nIn an approximate E-BNF:\n```\nTEMPLATE = TEXT | PRINT | LOOP | IFEQ\n\nCHILDREN = TEMPLATE\n\nSTART_DIRECTIVE = \"{{\"\nEND_DIRECTIVE = \"}}\"\nSTART_BLOCK = \"#\"\nEND_BLOCK = \"/\"\nSYMBOL = TEXT\n\nTEXT = a string not containing START_DIRECTIVE\n\nPRINT = START_DIRECTIVE, SYMBOL, END_DIRECTIVE\n\nLOOP = START_DIRECTIVE, START_BLOCK, \"loop\", END_DIRECTIVE, CHILDREN, START_DIRECTIVE, END_BLOCK, \"loop\", END_DIRECTIVE\n\nIFEQ = START_DIRECTIVE, START_BLOCK, \"ifeq\", END_DIRECTIVE, CHILDREN, START_DIRECTIVE, END_BLOCK, \"ifeq\", END_DIRECTIVE\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkrk%2Fcarender","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkrk%2Fcarender","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkrk%2Fcarender/lists"}