{"id":17684652,"url":"https://github.com/tarampampam/mustpl","last_synced_at":"2025-04-30T14:41:41.131Z","repository":{"id":145925610,"uuid":"525275882","full_name":"tarampampam/mustpl","owner":"tarampampam","description":"🧰 Logic-less CLI templating tool","archived":false,"fork":false,"pushed_at":"2024-11-01T07:48:46.000Z","size":582,"stargazers_count":27,"open_issues_count":0,"forks_count":4,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-25T19:18:38.478Z","etag":null,"topics":["c","cli","mustache","templating"],"latest_commit_sha":null,"homepage":"","language":"C","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/tarampampam.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":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-08-16T07:32:48.000Z","updated_at":"2025-04-09T17:49:15.000Z","dependencies_parsed_at":"2024-03-19T07:10:36.788Z","dependency_job_id":"25182f3f-c80c-4bbf-84b8-56bdbb7f1f40","html_url":"https://github.com/tarampampam/mustpl","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tarampampam%2Fmustpl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tarampampam%2Fmustpl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tarampampam%2Fmustpl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tarampampam%2Fmustpl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tarampampam","download_url":"https://codeload.github.com/tarampampam/mustpl/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251722825,"owners_count":21633023,"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":["c","cli","mustache","templating"],"created_at":"2024-10-24T10:24:35.822Z","updated_at":"2025-04-30T14:41:41.092Z","avatar_url":"https://github.com/tarampampam.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n[![banner][banner]][repo]\n\n[![badge-version][badge-version]][release-latest]\n[![badge-tests][badge-tests]][actions]\n[![badge-release][badge-release]][actions]\n[![badge-image-size][badge-image-size]][actions]\n[![badge-license][badge-license]][docker-hub]\n\n[banner]:https://user-images.githubusercontent.com/7326800/185954094-bdc16f92-4554-4e41-97e9-82e5b9e47b53.png\n[badge-tests]:https://img.shields.io/github/actions/workflow/status/tarampampam/mustpl/tests.yml?branch=master\u0026maxAge=30\u0026label=tests\u0026logo=github\u0026style=flat-square\n[badge-release]:https://img.shields.io/github/actions/workflow/status/tarampampam/mustpl/release.yml?maxAge=30\u0026label=release\u0026logo=github\u0026style=flat-square\n[badge-image-size]:https://img.shields.io/docker/image-size/tarampampam/mustpl/latest?maxAge=30\u0026label=size\u0026logo=docker\u0026logoColor=white\u0026style=flat-square\n[badge-license]:https://img.shields.io/github/license/tarampampam/mustpl.svg?maxAge=30\u0026style=flat-square\n[badge-version]:https://img.shields.io/github/v/release/tarampampam/mustpl?maxAge=30\u0026label=version\u0026style=flat-square\n\u003c/div\u003e\n\nSometimes, you might need to generate data using templates, and this tool allows you to do it in the simplest way. All it takes is the template itself, the data for it (the values that are inserted in the template), and this tool.\n\n\u003cdetails\u003e\n  \u003csummary\u003e👉 The simplest example\u003c/summary\u003e\n\nLet's imagine, that you have such a Nginx template:\n\n```nginx\n# File `nginx.tpl`\n\nserver {\n  listen      {{ port }};\n  server_name {{ server_name }};\n\n  location / {\n    root  /usr/share/nginx/html;\n    index index.html index.htm;\n  }\n}\n```\n\nAll that is required for its rendering is:\n\n```shell\n$ PORT_NUM=\"8080\"\n$ mustpl -d '{\"port\": \"${PORT_NUM:-8888}\", \"server_name\": \"example.com\"}' ./nginx.tpl\n\nserver {\n  listen      8080;\n  server_name example.com;\n\n  location / {\n    root  /usr/share/nginx/html;\n    index index.html index.htm;\n  }\n}\n```\n\n\u003c/details\u003e\n\n## 🔥 Features List\n\n- Zero external dependencies\n- [Mustache][mustache] templating engine under the hood\n- Can be used in a `scratch` Docker image (empty file system)\n- Distributed, and compiled for many architectures, including a **Docker** image\n- Extremely lightweight (_**~55KB** compressed, statically linked_) and fast (written in pure C)\n- Supports substitution from environment variables into the template, with default values fallback (`${ENV_NAME:-default value}`)\n- Can be used as a Docker entrypoint (can start another application without PID changing - `mustpl ... -- nginx -g 'daemon off;'`)\n\n## 🧩 Installation\n\nDownload the latest binary file for your architecture (_only Linux-like platforms are supported at the moment_) from the [releases page][release-latest]. For example, let's install it on the amd64 architecture (e.g., Debian, Ubuntu, etc):\n\n```bash\n$ curl -SsL -o ./mustpl https://github.com/tarampampam/mustpl/releases/latest/download/mustpl-linux-amd64\n$ chmod +x ./mustpl\n\n# optionally, install the binary file globally:\n$ sudo install -g root -o root -t /usr/local/bin -v ./mustpl\n$ rm ./mustpl\n$ mustpl --help\n```\n\n\u003cdetails\u003e\n  \u003csummary\u003e🛸 Compilation from sources\u003c/summary\u003e\n\nAll you need to compile is gcc:\n\n```shell\n$ sudo apt install gcc\n\n# on linux alpine:\n$ apk add make gcc musl-dev\n```\n\nGet the sources and switch to the latest version:\n\n```shell\n$ git clone https://github.com/tarampampam/mustpl.git ./mustpl\n$ cd ./mustpl\n$ git fetch --tags\n$ git checkout $(git describe --tags `git rev-list --tags --max-count=1`)\n```\n\nAnd compile:\n\n```shell\n$ make version=1.1.1 # set your version\n$ ./mustpl --help\n```\n\n\u003c/details\u003e\n\n### 🐋 Docker image\n\nAdditionally, you can use our docker image:\n\n| Registry                          | Image                        |\n|-----------------------------------|------------------------------|\n| [GitHub Container Registry][ghcr] | `ghcr.io/tarampampam/mustpl` |\n| [Docker Hub][docker-hub]          | `tarampampam/mustpl`         |\n\n\u003e ⚠ Using the `latest` tag for the docker image is highly discouraged because of possible backward-incompatible changes during **major** upgrades. Please, use tags in `X.Y.Z` format\n\n\u003cdetails\u003e\n  \u003csummary\u003e🔍 What's inside the Docker image?\u003c/summary\u003e\n\nTo watch the docker image content you can use the [dive](https://github.com/wagoodman/dive):\n\n```shell\n$ docker run --rm -it \\\n    -v \"/var/run/docker.sock:/var/run/docker.sock:ro\" \\\n    wagoodman/dive:latest \\\n      ghcr.io/tarampampam/mustpl:latest\n```\n\n\u003cdiv align=\"center\"\u003e\n\n![dive](https://user-images.githubusercontent.com/7326800/186757406-fdff74ca-dd3d-4be8-9323-65787289db9b.png)\n\n\u003c/div\u003e\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e🔍 Which platforms are supported?\u003c/summary\u003e\n\nThe following platforms for this image are available:\n\n```shell\n$ docker run --rm mplatform/mquery ghcr.io/tarampampam/mustpl:latest\nImage: ghcr.io/tarampampam/mustpl:latest\n * Manifest List: Yes (Image type: application/vnd.docker.distribution.manifest.list.v2+json)\n * Supported platforms:\n   - linux/386\n   - linux/amd64\n   - linux/arm/v6\n   - linux/arm/v7\n   - linux/arm64\n   - linux/ppc64le\n   - linux/s390x\n```\n\n\u003c/details\u003e\n\nTo run locally:\n\n```shell\n$ docker run --rm -ti \\\n    -u \"$(id -u):$(id -g)\" \\\n    -v \"$(pwd):/rootfs:rw\" \\\n    -w /rootfs \\\n      ghcr.io/tarampampam/mustpl --help\n```\n\nOr add it to another image:\n\n```dockerfile\nFROM nginx:alpine\n\nCOPY --from=ghcr.io/tarampampam/mustpl /bin/mustpl /bin/mustpl\n\nRUN mustpl --help\n```\n\n## 🛠 CLI Overview\n\n```shell\nUsage: mustpl [OPTION...] \u003ctemplate-file\u003e [-- \u003cexec-command\u003e]\n\n{{ mustach }} templating tool. For more details about the templating engine and\nrules, please, follow this link: https://mustache.github.io/mustache.5.html\n\nYou can use environment variables in your template data using the following\nformat: ${ENV_NAME} or ${ENV_NAME:-default value}. Only those formats are\nallowed (not $ENV_NAME).\n\n Template data:\n  -d, --data=\u003cjson-string\u003e   Template data in JSON-string format (has higher\n                             priority than --data-file flag)\n  -f, --data-file=\u003cfile\u003e     Read template data from the JSON file\n\n Output:\n  -o, --out=\u003cout-file\u003e       Write output to the file instead of standard\n                             output\n\n  -?, --help                 Give this help list\n      --usage                Give a short usage message\n  -V, --version              Print program version\n```\n\n### 🦾 What the `mustpl` can do\n\n\u003e 🌟 For detailed information about the templating engine please refer to the following links - [mustache manual][mustache] and the [library repository](https://gitlab.com/jobol/mustach)\n\nFor example, you have the following template data (`data.json`):\n\n```json\n{\n  \"name\": \"Chris\",\n  \"value\": 10000,\n  \"taxed_value\": 6000,\n  \"in_ca\": true\n}\n```\n\nAnd template (`template.txt`):\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003chead\u003e\u003ctitle\u003eHello {{name}}\u003c/title\u003e\u003c/head\u003e\n\u003cbody\u003e\n  \u003cp\u003eYou have just won \u003cstrong\u003e{{value}}\u003c/strong\u003e dollars!\u003c/p\u003e\n\n{{#in_ca}}\n  \u003cp style=\"font-size: .7em\"\u003eWell, \u003ci\u003e{{taxed_value}} dollars\u003c/i\u003e, after taxes.\u003c/p\u003e\n{{/in_ca}}\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\nLet's do the magic!\n\n```shell\n$ mustpl -f ./data.json ./template.txt\n\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003chead\u003e\u003ctitle\u003eHello Chris\u003c/title\u003e\u003c/head\u003e\n\u003cbody\u003e\n  \u003cp\u003eYou have just won \u003cstrong\u003e10000\u003c/strong\u003e dollars!\u003c/p\u003e\n\n  \u003cp style=\"font-size: .7em\"\u003eWell, \u003ci\u003e6000 dollars\u003c/i\u003e, after taxes.\u003c/p\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\n### ✅ Conditions\n\nYou can test the value of the selected key using the following operators:\n\n- `key=value` (matching test)\n- `key=!value` (not matching test)\n- `key\u003evalue` (greater)\n- `key\u003e=value` (greater or equal)\n- `key\u003cvalue` (lesser)\n- `key\u003c=value` (lesser or equal)\n\n```json\n{\n  \"person\": {\n    \"name\": \"Harry\",\n    \"age\": 37\n  },\n  \"lang\": \"fr\",\n  \"l10n\": {\n    \"en\": \"Hello\",\n    \"fr\": \"Salut\"\n  }\n}\n```\n\n```mustach\n{{#person.name=Harry}}\nHello Harry!\n{{/person.name=Harry}}\n\n{{^person.name=John}}\nThe person's name is not John.\n{{/person.name=John}}\n\n{{#person.age\u003e40}}\nHe's over 40 years old.\n{{/person.age\u003e40}}{{^person.age\u003e40}}\nHe's definitely not more than 40 years old.\n{{/person.age\u003e40}}\n\n{{#lang=fr}}{{ l10n.fr }}{{/lang=fr}}{{#lang=!fr}}{{ l10n.en }}{{/lang=!fr}} {{ person.name }}!\n\nRender only if equals:\n- {{ person.age=36 }}\n- {{ person.age=37 }}\n- {{ person.age=38 }}\n```\n\nIt will be rendered as follows:\n\n```text\nHello Harry!\n\nThe person's name is not John.\n\n\nHe's definitely not more than 40 years old.\n\nSalut Harry!\n\nRender only if equals:\n-\n- 37\n-\n```\n\n### 🔄 Loops\n\nOkay, but what about the **loops**? Here you go (the value of the current field can be accessed using the single dot `{{ . }}`):\n\n```json\n{\n  \"servers\": [\n    {\n      \"listen\": 8080,\n      \"names\": [\n        \"example.com\"\n      ],\n      \"is_default\": true,\n      \"home\": \"/www/example.com\"\n    },\n    {\n      \"listen\": 1088,\n      \"names\": [\n        \"127-0-0-1.nip.io\",\n        \"127-0-0-2.nip.io\"\n      ],\n      \"home\": \"/www/local\"\n    }\n  ]\n}\n```\n\n```nginx\n{{#servers}}\nserver { {{! just a comment, will not be rendered }}\n  listen      {{ listen }};\n  server_name{{#names}} {{ . }}{{/names}}{{#is_default}} default_server{{/is_default}};\n\n  location / {\n    root  {{ home }};\n    index index.html index.htm;\n  }\n}\n{{/servers}}\n```\n\n```shell\n$ mustpl -f ./data.json ./template.txt\nserver {\n  listen      8080;\n  server_name example.com default_server;\n\n  location / {\n    root  /www/example.com;\n    index index.html index.htm;\n  }\n}\nserver {\n  listen      1088;\n  server_name 127-0-0-1.nip.io 127-0-0-2.nip.io;\n\n  location / {\n    root  /www/local;\n    index index.html index.htm;\n  }\n}\n```\n\nIn addition, you can use the pattern `{{#X.*}} ... {{/X.*}}` to iterate on fields of `X` :\n\n```json\n{\n  \"people\": {\n    \"John\": 27,\n    \"Mark\": \"32\"\n  }\n}\n```\n\n```mustach\n{{#people.*}}\n- {{ * }} is {{ . }} years old\n{{/people.*}}\n```\n\nProduces:\n\n```text\n- John is 27 years old\n- Mark is 32 years old\n```\n\nHere the single star `{{ * }}` is replaced by the iterated key and the single dot `{{ . }}` is replaced by its value.\n\n### 🚩 Template data provided using options\n\nYou can provide your template data from cli using the `-d` (`--data`) flag:\n\n```nginx\nserver {\n  listen      8080;\n  server_name{{#names}} {{ . }}{{/names}};\n\n  location / {\n    root  /var/www/data;\n    index index.html index.htm;\n  }\n}\n```\n\n```shell\n$ mustpl -d '{\"names\": [\"example.com\", \"google.com\"]}' ./template.txt\nserver {\n  listen      8080;\n  server_name example.com google.com;\n\n  location / {\n    root  /var/www/data;\n    index index.html index.htm;\n  }\n}\n```\n\n### 📎 Environment variables\n\nEnvironment variables can be used in the following format: `${ENV_NAME:-default value}` (inside template data file too; _template from the example above is used_):\n\n```shell\n$ SERVER_NAME_1=example.com ./mustpl -d '{\"names\": [{\"name\": \"${SERVER_NAME_1:-fallback.com}\"}, {\"name\": \"${SERVER_NAME_X:-unset.com}\"}]}' ./tmp/template.txt\nserver {\n  listen      8080;\n  server_name example.com unset.com;\n\n  location / {\n    root  /var/www/data;\n    index index.html index.htm;\n  }\n}\n```\n\n### 🛰 `exec` and the PID magic\n\nAs you probably know, the main process inside the docker container should have PID == 1 for the correct signals processing from the docker daemon. That's why basically entry-point scripts have the following code:\n\n```shell\n#!/bin/sh\nset -e\n\nif [ -n \"$MY_OPTION\" ]; then\n  sed -i \"s~foo~bar ${MY_OPTION}~\" /etc/app.cfg # modify the config\nfi;\n\nexec \"$@\" # \u003c-- that's it!\n```\n\n```dockerfile\n# ...\n\nCOPY docker-entrypoint.sh ./docker-entrypoint.sh\n\nENTRYPOINT [\"/docker-entrypoint.sh\"]\n\nCMD [\"/bin/app\", \"--another\", \"flags\"]\n```\n\nFrom the `man exec`:\n\n\u003e The `exec()` family  of functions replaces the current process image with a new process image\n\nSo, the application `/bin/app` will have a PID == 1 that was previously assigned to the `bash` (because the `bash` is the image entrypoint). `mustpl` uses the same technique, let's create the following files for the example:\n\n`data.json`:\n\n```json\n{\n  \"my_option\": \"${MY_OPTION:-default value}\"\n}\n```\n\n`template.txt`:\n\n```mustache\nHello {{ my_option }}!\n```\n\n`Dockerfile`:\n\n```dockerfile\nFROM alpine:latest\n\nCOPY --from=ghcr.io/tarampampam/mustpl /bin/mustpl /bin/mustpl\n\nCOPY ./data.json /data.json\nCOPY ./template.txt /template.txt\n\nENTRYPOINT [\"mustpl\", \"-f\", \"/data.json\", \"-o\", \"/rendered.txt\", \"/template.txt\", \"--\"]\n\nCMD [\"sleep\", \"infinity\"]\n```\n\nNext, build the image:\n\n```shell\n$ docker build --tag test:local .\n```\n\nAnd then run it:\n\n```shell\n$ docker run --rm --name mustpl_example -e \"MY_OPTION=foobar\" test:local\n```\n\nIn the separate terminal run:\n\n```shell\n$ docker exec mustpl_example ps aux\nPID   USER     TIME  COMMAND\n    1 root      0:00 sleep infinity # \u003c-- our CMD with a PID == 1\n    7 root      0:00 ps aux\n\n$ docker exec mustpl_example cat /rendered.txt\nHello foobar! # \u003c-- our environment variable value\n\n$ docker kill mustpl_example\n```\n\nThis approach is easier than using `sed`, `awk`, and other tools to modify the configuration files before running the main application. But despite this, no one is restricting you from using the entrypoint scripts 😉\n\n## 📰 Changes log\n\n[![Release date][badge-release-date]][releases]\n[![Commits since latest release][badge-commits]][commits]\n\nChanges log can be [found here][changelog].\n\n## 👾 Support\n\n[![Issues][badge-issues]][issues]\n[![Issues][badge-prs]][prs]\n\nIf you find any bugs in the project, please [create an issue][new-issue] in the current repository.\n\n## 📖 License\n\nThis is open-sourced software licensed under the [MIT License][license].\n\n[badge-release-date]:https://img.shields.io/github/release-date/tarampampam/mustpl.svg?maxAge=180\n[badge-commits]:https://img.shields.io/github/commits-since/tarampampam/mustpl/latest.svg?maxAge=45\n[badge-issues]:https://img.shields.io/github/issues/tarampampam/mustpl.svg?maxAge=45\n[badge-prs]:https://img.shields.io/github/issues-pr/tarampampam/mustpl.svg?maxAge=45\n\n[repo]:https://github.com/tarampampam/mustpl\n[docker-hub]:https://hub.docker.com/r/tarampampam/mustpl\n[ghcr]:https://github.com/users/tarampampam/packages/container/package/mustpl\n[actions]:https://github.com/tarampampam/mustpl/actions\n[releases]:https://github.com/tarampampam/mustpl/releases\n[release-latest]:https://github.com/tarampampam/mustpl/releases/latest\n[commits]:https://github.com/tarampampam/mustpl/commits\n[issues]:https://github.com/tarampampam/mustpl/issues\n[new-issue]:https://github.com/tarampampam/mustpl/issues/new/choose\n[prs]:https://github.com/tarampampam/mustpl/pulls\n[changelog]:https://github.com/tarampampam/mustpl/blob/master/CHANGELOG.md\n[license]:https://github.com/tarampampam/mustpl/blob/master/LICENSE\n\n[mustache]:https://mustache.github.io/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftarampampam%2Fmustpl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftarampampam%2Fmustpl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftarampampam%2Fmustpl/lists"}