{"id":13521313,"url":"https://github.com/jamboree/bustache","last_synced_at":"2026-01-20T22:37:43.228Z","repository":{"id":17646265,"uuid":"20450752","full_name":"jamboree/bustache","owner":"jamboree","description":"C++20 implementation of {{ mustache }}","archived":false,"fork":false,"pushed_at":"2024-11-18T01:43:43.000Z","size":266,"stargazers_count":84,"open_issues_count":1,"forks_count":10,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-03-31T20:37:55.359Z","etag":null,"topics":["c-plus-plus","c-plus-plus-20","mustache","template-engine"],"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/jamboree.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":"2014-06-03T16:40:34.000Z","updated_at":"2024-12-14T20:56:38.000Z","dependencies_parsed_at":"2024-11-02T05:31:52.176Z","dependency_job_id":"665fd1bc-da8d-46c5-acf5-2347825ba649","html_url":"https://github.com/jamboree/bustache","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/jamboree/bustache","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamboree%2Fbustache","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamboree%2Fbustache/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamboree%2Fbustache/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamboree%2Fbustache/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jamboree","download_url":"https://codeload.github.com/jamboree/bustache/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamboree%2Fbustache/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28617001,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-20T22:24:05.405Z","status":"ssl_error","status_checked_at":"2026-01-20T22:20:31.342Z","response_time":117,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["c-plus-plus","c-plus-plus-20","mustache","template-engine"],"created_at":"2024-08-01T06:00:32.603Z","updated_at":"2026-01-20T22:37:43.212Z","avatar_url":"https://github.com/jamboree.png","language":"C++","funding_links":[],"categories":["模板引擎"],"sub_categories":[],"readme":"{{ bustache }} \u003c!-- [![Try it online][badge.wandbox]](https://wandbox.org/permlink/HC4GG9QxCw6dsygF) --\u003e\n========\n\nC++20 implementation of [{{ mustache }}](http://mustache.github.io/), compliant with [spec](https://github.com/mustache/spec) v1.1.3.\n\n### Dependencies\n* [fmt](https://github.com/fmtlib/fmt) (or C++20 `\u003cformat\u003e`)\n  \u003e __Note__ \\\n  \u003e This can be controlled by `BUSTACHE_USE_FMT`\n\n### Optional Dependencies\n* [Google.Benchmark](https://github.com/google/benchmark) - for benchmark\n* [Catch2](https://github.com/catchorg/Catch2) - for test\n\n## Supported Features\n* Variables\n* Sections\n* Inverted Sections\n* Comments\n* Partials\n* Set Delimiter\n* Lambdas\n* HTML escaping *(configurable)*\n* [Inheritance](https://github.com/mustache/spec/blob/master/specs/~inheritance.yml) *(extension)*\n* [Dynamic Names](https://github.com/mustache/spec/blob/master/specs/~dynamic-names.yml) *(extension)*\n\n## Other Features\n* Customizable behavior on unresolved variable\n* Trait-based user-defined model\n* Variable [format string](https://fmt.dev/latest/syntax.html), e.g. \\\n  `{{var:*^10}}`.\n* List expansion section, e.g. \\\n  `{{*map}}({{key}} -\u003e {{value}}){{/map}}`.\n* Filter section, e.g. \\\n  `{{?filter}}...{{/filter}}`.\n\n## Basics\n{{ mustache }} is a template language for text-replacing.\nWhen it comes to formatting, there are 2 essential things -- _Format_ and _Data_.\n{{ mustache }} also allows an extra lookup-context for _Partials_.\nIn {{ bustache }}, we represent the _Format_ as a `bustache::format` object, and _Data_ and _Partials_ can be anything that implements the required traits.\n\n### Quick Example\n```c++\nbustache::format format{\"{{mustache}} templating\"};\nstd::unordered_map\u003cstd::string, std::string\u003e data{{\"mustache\", \"bustache\"}};\nstd::cout \u003c\u003c format(data); // should print \"bustache templating\"\n```\n\n## Manual\n\n### Data Model\n{{ bustache }} doesn't required a fixed set of predefined data types to be used as data model. Instead, any type can be used as data model.\nMost STL-compatible containers will work out-of-the-box, including the ones that you defined yourself!\n\n#### Header\n`#include \u003cbustache/model.hpp\u003e`\n\n#### Adapted Model\n* [Boost.Describe](https://boost.org/libs/describe) \\\n  `#include \u003cbustache/adapted/boost_describe.hpp\u003e`\n* [Boost.JSON](https://boost.org/libs/json) \\\n  `#include \u003cbustache/adapted/boost_json.hpp\u003e`\n\n#### Model Traits\nTo meet the `Model` concept, you have to implement the traits:\n```c++\ntemplate\u003c\u003e\nstruct bustache::impl_model\u003cT\u003e\n{\n    static constexpr model kind;\n};\n```\nwhere model can be one of the following:\n* `model::atom`\n* `model::object`\n* `model::list`\n\n```c++\n// Required by model::atom.\ntemplate\u003c\u003e\nstruct bustache::impl_test\u003cT\u003e\n{\n    static bool test(T const\u0026 self);\n};\n\n// Required by model::atom.\ntemplate\u003c\u003e\nstruct bustache::impl_print\u003cT\u003e\n{\n    static void print(T const\u0026 self, output_handler os, char const* fmt);\n};\n\n// Required by model::object.\ntemplate\u003c\u003e\nstruct bustache::impl_object\u003cT\u003e\n{\n    static void get(T const\u0026 self, std::string const\u0026 key, value_handler visit);\n};\n\n// Required by model::list.\ntemplate\u003c\u003e\nstruct bustache::impl_list\u003cT\u003e\n{\n    static bool empty(T const\u0026 self);\n    static void iterate(T const\u0026 self, value_handler visit);\n};\n```\nSee [udt.cpp](test/udt.cpp) for more examples.\n\n#### Compatible Trait\nSome types cannot be categorized into a single model (e.g. `variant`), to make it compatible, you can implement the trait:\n```c++\ntemplate\u003c\u003e\nstruct bustache::impl_compatible\u003cT\u003e\n{\n    static value_ptr get_value_ptr(T const\u0026 self);\n};\n```\nSee [model.hpp](test/model.hpp) for example.\n\n### Format Object\n`bustache::format` parses in-memory string into AST.\n\n#### Header\n`#include \u003cbustache/format.hpp\u003e`\n\n#### Synopsis\n*Constructors*\n```c++\nexplicit format(std::string_view source); // [1]\nformat(std::string_view source, bool copytext); // [2]\nformat(ast::document doc, bool copytext); // [3]\n```\n* Version 1 doesn't hold the text, you must ensure the source is valid and not modified during its use.\n* Version 2~3, if `copytext == true` the text will be copied into the internal buffer.\n\n*Manipulator*\n\nA manipulator combines the format \u0026 data and allows you to specify some options.\n```c++\ntemplate\u003cclass T\u003e\nmanipulator\u003c/*unspecified*/\u003e format::operator()(T const\u0026 data) const;\n\n// Specify the context for partials.\ntemplate\u003cclass T\u003e\nmanipulator\u003c/*unspecified*/\u003e manipulator::context(T const\u0026) const noexcept;\n\n// Specify the escape action.\ntemplate\u003cclass T\u003e\nmanipulator\u003c/*unspecified*/\u003e manipulator::escape(T const\u0026) const noexcept;\n```\n\n### Render API\n`render` can be used for customized output.\n\n#### Header\n`#include \u003cbustache/render.hpp\u003e`\n\n```c++\ntemplate\u003cclass Sink, class Escape = no_escape_t\u003e\nvoid render\n(\n    Sink const\u0026 os, format const\u0026 fmt, value_ref data,\n    context_handler context = no_context_t{}, Escape escape = {},\n    unresolved_handler f = nullptr\n);\n```\n\n#### Context Handler\nThe context for partials can be any callable that meets the signature:\n```c++\n(std::string const\u0026 key) -\u003e format const*;\n```\n\n#### Unresolved Handler\nThe unresolved handler can be any callable that meets the signature:\n```c++\n(std::string const\u0026 key) -\u003e value_ptr;\n```\n\n#### Sink (Output Handler)\nThe sink can be any callable that meets the signature:\n```c++\n(char const* data, std::size_t count) -\u003e void;\n```\n\n#### Escape Action\nThe escape action can be any callable that meets the signature:\n```c++\ntemplate\u003cclass OldSink\u003e\n(OldSink const\u0026 sink) -\u003e NewSink;\n```\nThere're 2 predefined actions: `no_escape` (default) and `escape_html`, if `no_escape` is chosen, there's no difference between `{{Tag}}` and `{{{Tag}}}`, the text won't be escaped in both cases.\n\n### Stream-based Output\nOutput directly to the `std::basic_ostream`.\n\n#### Header\n`#include \u003cbustache/render/ostream.hpp\u003e`\n\n#### Synopsis\n```c++\ntemplate\u003cclass CharT, class Traits, class Escape = no_escape_t\u003e\nvoid render_ostream\n(\n    std::basic_ostream\u003cCharT, Traits\u003e\u0026 out, format const\u0026 fmt,\n    value_ref data, context_handler context = no_context_t{},\n    Escape escape = {}, unresolved_handler f = nullptr\n);\n\ntemplate\u003cclass CharT, class Traits, class... Opts\u003e\nstd::basic_ostream\u003cCharT, Traits\u003e\u0026 operator\u003c\u003c(std::basic_ostream\u003cCharT, Traits\u003e\u0026 out, manipulator\u003cOpts...\u003e const\u0026 manip)\n```\n\n#### Example\n```c++\n// Create format from source.\nbustache::format format(...);\n// Create the data we want to output.\nmy_data data{...};\n// Create the context for Partials.\nmy_context context{...};\n// Output the result.\nstd::cout \u003c\u003c format(data).context(context).escape(bustache::escape_html);\n```\n\n### String Output\nGenerate a `std::string`.\n\n#### Header\n`#include \u003cbustache/render/string.hpp\u003e`\n\n#### Synopsis\n```c++\ntemplate\u003cclass String, class Escape = no_escape_t\u003e\nvoid render_string\n(\n    String\u0026 out, format const\u0026 fmt,\n    value_ref data, context_handler context = no_context_t{},\n    Escape escape = {}, unresolved_handler f = nullptr\n);\n\ntemplate\u003cclass... Opts\u003e\nstd::string to_string(manipulator\u003cOpts...\u003e const\u0026 manip);\n```\n#### Example\n```c++\nbustache::format format(...);\nstd::string txt = to_string(format(data).context(context).escape(bustache::escape_html));\n```\n\n## Advanced Topics\n### Lambdas\nThe lambdas in {{ bustache }} accept signatures below:\n* `(ast::view const* view) -\u003e format`\n* `(ast::view const* view) -\u003e Value`\n\nA `ast::view` is a parsed list of AST nodes, you can make a new `ast::view` out of the old one and give it to a `format`. Note that `view` will be null if the lambda is used as variable.\n\n### Error Handling\nThe constructor of `bustache::format` may throw `bustache::format_error` if the parsing fails.\n```c++\nclass format_error : public std::runtime_error\n{\npublic:\n    explicit format_error(error_type err, std::ptrdiff_t position);\n\n    error_type code() const noexcept;\n    std::ptrdiff_t position() const noexcept;\n};\n```\n`error_type` has these values:\n* error_set_delim\n* error_baddelim\n* error_delim\n* error_section\n* error_badkey\n\nYou can also use `what()` for a descriptive text.\n\n## Performance\nCompare with 2 other libs - [mstch](https://github.com/no1msd/mstch/tree/0fde1cf94c26ede7fa267f4b64c0efe5da81a77a) and [Kainjow.Mustache](https://github.com/kainjow/Mustache/tree/a7eebc9bec92676c1931eddfff7637d7e819f2d2).\nSee [benchmark.cpp](test/benchmark.cpp). \n\nSample run (VS2019 16.7.6, boost 1.73.0, 64-bit release build):\n```\n2020-10-27T16:10:49+08:00\nRunning F:\\code\\bus\\x64\\Release\\bus.exe\nRun on (8 X 3600 MHz CPU s)\nCPU Caches:\n  L1 Data 32 KiB (x8)\n  L1 Instruction 32 KiB (x8)\n  L2 Unified 256 KiB (x8)\n  L3 Unified 12288 KiB (x1)\n---------------------------------------------------------\nBenchmark               Time             CPU   Iterations\n---------------------------------------------------------\nbustache_usage       4675 ns         4708 ns       149333\nmstch_usage         80919 ns        81961 ns         8960\nkainjow_usage       23993 ns        24065 ns        29867\n```\nLower is better.\n\n![benchmark](doc/benchmark.png?raw=true)\n\n## License\n\n    Copyright (c) 2014-2023 Jamboree\n\n    Distributed under the Boost Software License, Version 1.0. (See accompanying\n    file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n\n\u003c!-- Links --\u003e\n[badge.Wandbox]: https://img.shields.io/badge/try%20it-online-green.svg\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamboree%2Fbustache","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjamboree%2Fbustache","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamboree%2Fbustache/lists"}