{"id":22750083,"url":"https://github.com/pwall567/mustache-k","last_synced_at":"2026-04-29T04:35:08.603Z","repository":{"id":57735721,"uuid":"402955105","full_name":"pwall567/mustache-k","owner":"pwall567","description":"Mustache template processor for Kotlin","archived":false,"fork":false,"pushed_at":"2025-06-08T14:45:42.000Z","size":65,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-06-08T15:32:32.518Z","etag":null,"topics":["json","kotlin","mustache"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pwall567.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","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,"zenodo":null}},"created_at":"2021-09-04T03:33:28.000Z","updated_at":"2025-06-08T14:45:24.000Z","dependencies_parsed_at":"2023-12-11T11:52:03.957Z","dependency_job_id":"e18be81e-8917-4f1e-86d5-4595cafee1a7","html_url":"https://github.com/pwall567/mustache-k","commit_stats":null,"previous_names":[],"tags_count":31,"template":false,"template_full_name":null,"purl":"pkg:github/pwall567/mustache-k","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pwall567%2Fmustache-k","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pwall567%2Fmustache-k/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pwall567%2Fmustache-k/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pwall567%2Fmustache-k/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pwall567","download_url":"https://codeload.github.com/pwall567/mustache-k/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pwall567%2Fmustache-k/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262762459,"owners_count":23360329,"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":["json","kotlin","mustache"],"created_at":"2024-12-11T04:12:17.400Z","updated_at":"2026-04-29T04:35:08.590Z","avatar_url":"https://github.com/pwall567.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mustache-k\n\n[![Build Status](https://github.com/pwall567/mustache-k/actions/workflows/build.yml/badge.svg)](https://github.com/pwall567/mustache-k/actions/workflows/build.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Kotlin](https://img.shields.io/static/v1?label=Kotlin\u0026message=v2.0.21\u0026color=7f52ff\u0026logo=kotlin\u0026logoColor=7f52ff)](https://github.com/JetBrains/kotlin/releases/tag/v2.0.21)\n[![Maven Central](https://img.shields.io/maven-central/v/io.kjson/mustache-k?label=Maven%20Central)](https://central.sonatype.com/artifact/io.kjson/mustache-k)\n\n[Mustache](https://mustache.github.io/mustache.5.html) template processor for Kotlin\n\nWhy is this included among a set of Kotlin JSON libraries?\nMustache is often used in conjunction with JSON \u0026ndash; even if only for debugging \u0026ndash; and it seemed like a good\nfit.\n\nThis library also operates on structures created by the [`kjson-core`](https://github.com/pwall567/kjson-core) library,\nand that library is therefore a dependency of this one.\n\nThe `mustache-k` library is derived from the earlier [`kotlin-mustache`](https://github.com/pwall567/kotlin-mustache)\nlibrary; most of the template functionality is the same but the handling of partial resolution is much improved.\n\n- [Usage](#usage)\n- [Reference](#reference)\n- [Dependency Specification](#dependency-specification)\n\n\n## Usage\n\nTemplates are loaded into memory using the `Parser` class, and there are several option for constructing a `Parser`,\ndepending on how the templates are stored.\nTemplates may invoke other templates (the term used by Mustache is \u0026ldquo;Partial\u0026rdquo;), and the Parser needs to have\na means of resolving the Partial by name.\n(For simplicity, the term template is used here to refer to a template or a partial \u0026ndash; in effect they are the\nsame.)\n\nThe `Parser` class has four alternative public constructors.\n\n### Directory Specified by File\n\nIf the templates are held in a directory in the main file system, the Parser may be constructed with a `File` describing\nthe directory, as follows:\n```kotlin\n    val parser = Parser(File(\"/path/to/directory\"))\n```\nPartial references will cause the parser to look for a file in the nominated directory.\n\n### Directory Specified by Path\n\nThe `java.nio.file.Path` class may be used to specify the directory containing the templates:\n```kotlin\n    val parser = Parser(FileSystems.getDefault().getPath(\"/path/to/directory\"))\n```\n\n### Directory Specified by URL\n\nThe `Parser` may also take a URL as a directory specifier.\nThe most familiar form of a URL is the web reference, for example `https://github.com/pwall567/mustache-k`, and the\nparser will take a URL of this form and use it to create a specific locator for the template.\n\n```kotlin\n    val parser = Parser(URL(\"https://raw.githubusercontent.com/pwall567/mustache-k/main/src/test/resources/templates/\"))\n```\n(Note the \u0026lsquo;/\u0026rsquo; on the end of the URL \u0026ndash; this will cause this location to be regarded as a directory.)\n\nThere is also the `file:` form of URL, and the parser will take this type of URL instead of a file or path reference as\ndescribed above.\n\n```kotlin\n    val parser = Parser(URL(\"file:/path/to/directory/\"))\n```\n\nBut perhaps the most useful form of URL is the type returned by the `Class.getResource()` function \u0026ndash; this will\nlook up the specified directory on the classpath, and return either a `file:` URL for a directory in the main file\nsystem, or a `jar:` URL for the directory within the JAR file where the templates have been combined with the classes.\nThis means that a combined JAR can be created containing the program classes and the templates, and the same code will\nread the templates regardless of whether they are in the main file system or in a JAR.\n\nTo use this type of URL:\n```kotlin\n    val parser = Parser(Resource.classPathURL(\"/templates\") ?: throw RuntimeException(\"templates not found\"))\n```\nThe `throw` is required because the `getResource()` function may return null.\n\n### Directory Specified by Custom Lambda\n\nFinally, if none of the above lookup mechanisms are suitable, the `Parser` may be constructed with a lambda to resolve a\npartial name to a `Reader` which is used to read the partial:\n```kotlin\n    val parser = Parser { name -\u003e\n        when (name) {\n            \"alpha\" -\u003e File(\"alpha.template\").reader()\n            \"beta\" -\u003e File(\"beta.template\").reader()\n            else -\u003e throw IllegalArgumentException(\"Unrecognised name - $name\")\n        }\n    }\n```\n\n### Parser Functions\n\nHaving constructed a `Parser`, there are several functions to parse a template, taking a `String`, a `Reader`, an\n`InputStream` or a `File`.\n\n```kotlin\n    val template = parser.parse(string)\n```\n\n```kotlin\n    val template = parser.parse(reader)\n```\n\n```kotlin\n    val template = parser.parse(inputStream)\n```\n\n```kotlin\n    val template = parser.parse(file)\n```\n\nAnd to parse a template using the same naming system as for partials, using the directory specified in the constructor:\n```kotlin\n    val template = parser.parseByName(name)\n```\n\nWhen searching for partial definitions, regardless of how the directory is specified, the parser will add the extension\n`mustache`.\nTo change this to use a different extension, use:\n```kotlin\n    parser.extension = \"hbs\"\n```\n\nThe functions which read from an `InputStream` take an optional `Charset` parameter.\nThe parser will use a default of `Charsets.UTF_8` if the parameter is not supplied; to change this default:\n```kotlin\n    parser.charset = Charsets.ISO_8859_1\n```\n\n### Template functions\n\nTo render a template to a `String`:\n```kotlin\n    val resultString = template.render(contextObject)\n```\nThe context object is the object that will be used to resolve variables and the controlling values of sections.\n\nTo render to an `Appendable` (this is often more efficient than rendering to a string since it avoids the creation of an\nintermediate output object):\n```kotlin\n    template.renderTo(appendable, contextObject)\n```\n\nAnd from version 1.4, you can render to a non-blocking data stream:\n```kotlin\n    template.coRender(contextObject) { /* output to non-blocking channel */ }\n```\n\nThe `Template` companion object also has functions to simplify template parsing, avoiding the need to create a `Parser`.\nTo parse a template from a `String`:\n```kotlin\n    val template = Template.parse(\"This is a template.\\n\")\n```\nFrom a `Reader`:\n```kotlin\n    val template = Template.parse(reader)\n```\nFrom an `InputStream`:\n```kotlin\n    val template = Template.parse(inputStream)\n```\nOr from a `File`:\n```kotlin\n    val template = Template.parse(File(\"/path/to/template\"))\n```\nIn this last case, a `Parser` will be created with the parent directory of the file as its location for partials.\n\n\n## Reference\n\nThe reference [Mustache Specification](https://github.com/mustache/spec) contains the definitive description of the\nMustache template system.\nThis implementation does not include all features covered by the specification; the differences are detailed below.\n\nA Mustache template is a block of text to be copied to the output, interspersed with \u0026ldquo;tags\u0026rdquo; that control the\nsubstitution of values from a supplied object \u0026ndash; the \u0026ldquo;context object\u0026rdquo;.\nAll text that is not part of a tag is copied without modification to the generated output.\nThe tags are described below.\n\n### Tags\n\nTo distinguish tags from general text, the original authors chose the open brace (mustache) character.\nTags are opened by two left brace characters (`{{`) and closed by two right brace characters (`}}`).\n(These delimiters may be changed if the use of these characters would cause a conflict \u0026ndash; see\n[below](#set-delimiter).)\n\n#### Variables\n\nThe simplest tag type is the variable, which is replaced in the output by the result of resolving the variable name in\nthe context object.\nIf the context object is a `Map`, the variable name is used as a key to locate an entry; otherwise the variable name is\nused to locate a property in the Kotlin object.\nSee [Name Resolution](#name-resolution) below for more detail.\n\nFor example, given the template:\n```handlebars\nHello {{who}}\n```\n\nThen either:\n```kotlin\n    val contextObject = mapOf(\"who\" to \"World\")\n    template.processToString(contextObject)\n```\nOr:\n```kotlin\n    data class ContextObject(val who: String)\n\n    val contextObject = ContextObject(\"World\")\n    template.processToString(contextObject)\n```\nWill cause the generated output to be:\n```text\nHello World\n```\n\nIf the name is not located \u0026ndash; the key is not found in the map or the class does not have a variable with the\nspecified name \u0026ndash; no text will be output.\nThe name `.` will select the entire context object.\n\nBy default, Mustache performs HTML escaping on the substituted text \u0026ndash; that is, HTML special characters are\nconverted to the HTML entity references for those characters.\nFor example, `\u003c` is converted to `\u0026lt;` and `\u0026` is converted to `\u0026amp;`.\n\nIf this HTML escaping is not required, the literal form of the variable tag may be used \u0026ndash; either `{{{name}}}`\n(three braces instead of two) or `{{\u0026name}}` (ampersand before the variable name).\n\nThe name may be a structured name \u0026ndash; see [Name Resolution](#name-resolution) below.\n\n#### Sections\n\nA section is a block of text to be processed zero or more times depending on the value nominated by the section name.\nSections are introduced by a opening tag which has a `#` following the two left braces, and closed by a tag with a `/`\nfollowing the left braces, for example `{{#person}}...{{/person}}`.\nSections may be nested, and closing tags must match the most recent unmatched opening tag.\n\nThe content of the section is processed conditionally, depending on the type of the value (the value is determined using\n[the rules detailed below](#name-resolution)):\n\n- `Iterable` (e.g. `List`), `Array`: the content of the section is processed for each entry in the collection, with the\n  individual entry used as the context object for the nested content\n- `Map`: the content of the section is processed for each entry in the map, with the `Map.Entry` used as the context\n  object for the nested content\n- `Enum`: the content of the section is processed with the enum as the context object, allowing the individual enum\n  values to be used to control nested sections (see [Enums](#enums) below)\n- `Enum` values: the content of the section is processed if the context object is an enum with the specified value (see\n  [Enums](#enums) below)\n- `String` (and other forms of `CharSequence`): the content of the section is processed if the string is not empty\n- `Int`, `Long`, `Short`, `Byte`, `Double`, `Float`, `BigInteger` and `BigDecimal`: the content of the section is\n  processed if the value is non-zero\n- `Boolean`: the content of the section is processed if the value is `true`\n- anything else: the content of the section is processed if the value is not `null`, with the value used as the context\n  object for the nested content (this is how properties of nested objects may be accessed)\n\nWhen processing an `Iterable`, additional names are available in the context for each item processed:\n- `first` (`Boolean`) \u0026ndash; `true` if the item is the first in the collection\n- `last` (`Boolean`) \u0026ndash; `true` if the item is the last in the collection\n- `index` (`Int`) \u0026ndash; the index (zero-based) of the item in the collection\n- `index1` (`Int`) \u0026ndash; the index (one-based) of the item in the collection\n\nWhen rendering to a non-blocking data stream, the section tag can iterate over a `Channel` or a `Flow`.\nIn this case, the rendering function will suspend until the next element becomes available, and will terminate only when\nthe `Channel` or `Flow` is closed.\nThe additional names in the context are available as for `Iterable`, but the `last` property will never be set to `true`\n(since that would require lookahead to see if another element was going to be available).\n\n#### Inverted Sections\n\nAn inverted section is a block of text to be processed once, if the value is null or the following:\n\n- `Iterable` (`List` _etc._), `Array`, `Map`: if the collection or map is empty\n- `Enum` values: if the enum does not have the specified value\n- `String` _etc._: if the string is empty\n- number types: if the number is zero\n- `Boolean`: if the value is `false`\n\nInverted sections are introduced by a opening tag which has a `^` following the two left braces, and closed by a tag\nwith a `/` following the left braces.\nSections and inverted sections may nest within each other.\n\n#### Partials\n\n\u0026ldquo;Partial\u0026rdquo; is the term given to a nested section \u0026ndash; for example a template in a separate file.\nPartials are introduced by a tag with '\u003e' following the two left braces \u0026ndash; the remainder of the tag is the name of\nan external file.\n\nIn the most common usage, the directory and extension of the first template will be stored and used as the directory and\nextension for any partials encountered in that template.\n\nRecursive data structures may be processed by recursive templates.\n\n#### Set Delimiter\n\nFor cases where the standard double-brace delimiters clash with the text being processed, the delimiters may be changed\nto some other combination of characters for the remainder of the current template.\nA \u0026ldquo;Set Delimiter\u0026rdquo; tag consists of the current opening delimiter, immediately followed by an equals sign '=',\nthen the new opening delimiter, one or more spaces, the new closing delimiter, an equals sign '=' again, and finally the\ncurrent closing delimiter.\n\nFor example, to change the delimiters to `\u003c%` and `%\u003e`:\n```\n{{=\u003c% %\u003e=}}\n```\n\nThe delimiters may not contain spaces or the equals sign.\n\n### Name Resolution\n\nThe variable and section (including inverted section) tags use the following rules to determine the value to be used for\nthe tag:\n- If the context object is `null`, the result value is `null`\n- If the context object is a `Map` containing a key with the tag name, the value associated with that key is used\n- If the context object has a property with the tag name, that property is used\n- If the current context is a nested context (part of a section or inverted section), the context object of the outer\n  context is searched using these same rules (repeatedly up to the outermost context)\n- If nothing is found, `null` is returned\n\nThe variable or section may be specified in a structured form, e.g. `person.firstName`.\nIn this case, the above rules are used for the first part of the name (the part before the first dot), but for the\nsubsequent parts only the result of the first part is searched and the enclosing contexts do not form part of the\nresolution process.\n\n### Whitespace\n\nThis implementation treats all whitespace as significant, and copies it to the output.\nThis includes the newline at the end of a file, so if, for example, you have a partial to substitute a word or phrase\ninto the middle of a line, then to preserve line formatting the partial must not have a newline at the end of the file.\n\n### Enums\n\nOne very powerful way in which this library differs from other Mustache implementations is in its handling of enums.\nIf an enum value is used in a [Section](#sections), the contents of the section are processed with the context object\nfor the nested content set to a special object that yields `true` for the name corresponding to the value of the enum,\nand `false` for all other values.\n\nFor example:\n```kotlin\n    enum class Type { CREDIT, DEBIT }\n    data class Transaction(val type: Type, val amount: Int)\n\n    val template = Template.parse(\"{{#type}}{{#CREDIT}}+{{/CREDIT}}{{#DEBIT}}-{{/DEBIT}}{{/type}}{{\u0026amount}}\")\n    println(template.render(Transaction(Type.DEBIT, 100))) // will print -100\n```\n\n\n## Dependency Specification\n\nThe latest version of the library is 3.14, and it may be obtained from the Maven Central repository.\n\n### Maven\n```xml\n    \u003cdependency\u003e\n      \u003cgroupId\u003eio.kjson\u003c/groupId\u003e\n      \u003cartifactId\u003emustache-k\u003c/artifactId\u003e\n      \u003cversion\u003e3.14\u003c/version\u003e\n    \u003c/dependency\u003e\n```\n### Gradle\n```groovy\n    implementation 'io.kjson:mustache-k:3.14'\n```\n### Gradle (kts)\n```kotlin\n    implementation(\"io.kjson:mustache-k:3.14\")\n```\n\nPeter Wall\n\n2025-11-13\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpwall567%2Fmustache-k","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpwall567%2Fmustache-k","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpwall567%2Fmustache-k/lists"}