{"id":14156368,"url":"https://github.com/gilbert/zaml","last_synced_at":"2025-09-02T08:33:26.001Z","repository":{"id":57405156,"uuid":"157765917","full_name":"gilbert/zaml","owner":"gilbert","description":"The Final Form of configuration files","archived":false,"fork":false,"pushed_at":"2021-10-24T01:56:14.000Z","size":159,"stargazers_count":46,"open_issues_count":2,"forks_count":2,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-11-16T19:37:00.368Z","etag":null,"topics":["configuration","schema","validation","yaml"],"latest_commit_sha":null,"homepage":"https://gilbert.github.io/zaml/editor.html","language":"TypeScript","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/gilbert.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-11-15T20:04:15.000Z","updated_at":"2024-05-11T15:22:01.000Z","dependencies_parsed_at":"2022-09-17T14:42:13.121Z","dependency_job_id":null,"html_url":"https://github.com/gilbert/zaml","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gilbert%2Fzaml","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gilbert%2Fzaml/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gilbert%2Fzaml/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gilbert%2Fzaml/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gilbert","download_url":"https://codeload.github.com/gilbert/zaml/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225489435,"owners_count":17482378,"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":["configuration","schema","validation","yaml"],"created_at":"2024-08-17T08:05:24.759Z","updated_at":"2024-11-20T08:16:55.554Z","avatar_url":"https://github.com/gilbert.png","language":"TypeScript","funding_links":[],"categories":["yaml"],"sub_categories":[],"readme":"[![npm](https://img.shields.io/npm/v/zaml.svg)](https://www.npmjs.com/package/zaml)\n[![Build Status](https://travis-ci.org/gilbert/zaml.svg?branch=master)](https://travis-ci.org/gilbert/zaml)\n\n# Zaml – The Final Form of Configuration Files\n\nJSON is tedious to type for humans. YAML is [error-prone and hard to parse](https://arp242.net/weblog/yaml_probably_not_so_great_after_all.html). TOML is verbose for nested data structures.\n\nEnter Zaml.\n\nZaml is the [final form](https://youtu.be/zGdFXUJ1o1U?t=4m17s) of configuration files. It's a zero-dependency, type-checking machine that points out your errors as graceful as a dove. It takes the pain out of your gain.\n\nNever again deal with the boilerplate of validating config data structures. Make config easier for you *and* your users.\n\nZaml isn't even an ancronym. That's how good it is.\n\n## Install\n\n    npm install zaml\n\nor\n\n    yarn add zaml\n\nor\n\n[Check out the online editor](https://gilbert.github.io/zaml/editor.html)!\n\n## Table of Contents\n\n- [Introduction](#introduction)\n- [Syntax \u0026 Features](#features)\n- [Using the JavaScript API](#javascript-api)\n- [Spec](#spec)\n- [Roadmap](#roadmap)\n- [Contributing](#contributing)\n\n## Introduction\n\nZaml's syntax is clean, effective, and not obsessed with your shift key:\n\n```js\nimport {parse} from 'zaml'\n\nvar schema = '{ fileRoots: { dev:list, prod:list } }'\n\nvar zamlContent = `\n\n  # This is Zaml!\n  # You'd probably load this from a file instead.\n\n  fileRoots {\n    dev {\n      $HOME/dev/services\n      $HOME/dev/states\n    }\n    prod {\n      $HOME/prod/services-2\n      $HOME/prod/states\n    }\n  }\n`\n\nvar result = parse(zamlContent, schema, { vars: process.env })\nconsole.log(\"Got result:\", result)\n```\n\n(You can [run this example in your browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvAHkIGYA+FjAAkIBOMMAF4AOiBLEADnEQB6eQHMIxQgFcARvjpZl0TTAHF5AL2xQJnAFoWW8jJ14B5dTLeJ7HTmLQtpQrwQACbiIEJw6lDEcFb2ATCclCBwMLDUxBD0CIggAIyIAEwALCAAvhTo2Li5+ABWCFR0DEzEeABuGAK85mQAwvSMDLwivAAGvry8AMS8ACqEEHBBy7ZkAISTvJCwAEq0tDG8wFtTwTDtx6dTvAAkABLOALIAovLn7fKpAu0Q1PDXKYPZ5vD5fYgYRixNA3Xhla4BWjBK4w2F3R6veSI4JfIy-f5wAC0hUB6JBWIESPBkIBqKm8NRDImaE63Tg1EIMCwfFGAHJgNtoDB9oc5MdeB9EFAlsQKLxsVKZWU4bzfKzeKzlqMBRiXoheLz5IRaDgHNL-ry4b5iAIAJ4oqbq2huEa8NZQfDSLqpAAUvSgAxaDDl7M53LlAs1cIAlFsIlEYvgIGg0EZ7nMngAZV0AKQAys4AHL4OA25MqMC2n3O2VoKJQCiW3mxtAM6iQjk+owCaMO3jx6JwJMptMZ7Ojbv4HBwOAYJQwXwM5K6aRCgR4TQYQzkKipdKZbJ4QqFRAABnKlRAmBweB0M+Xg1aeHKAF0qNK0ABrHKoK-VPDJucAAe+CaJSADu3z1I0IDqAI5C5FIsgKPI6hoNIn5KDoJpmBYAACp74HkADs+Cnu8MryIBMAgWBtCQUY0HJMQtrSDUKTUAIEDSG0ZQvmUQA))\n\nParsing the above will result in this data structure:\n\n```json\n{\n  \"fileRoots\": {\n    \"dev\": [\n      \"/home/alice/dev/services\",\n      \"/home/alice/dev/states\"\n    ],\n    \"prod\": [\n      \"/home/alice/prod/services-2\",\n      \"/home/alice/prod/states\"\n    ]\n  }\n}\n```\n\nNo quotes, no commas, no colons. Only what you need and nothing else.\n\nYour users also get nice, accurate error messages if they make a mistake writing their config. It's a win-win!\n\n### More Examples\n\nSee the [examples/](./examples) folder for more complete syntax \u0026 schema examples like the above.\n\n## Features\n\nHere are Zaml's features, each with an example use, from simplest to most complex.\n\n### Comments\n\nA comment is any line that **begins** with `#`\n\n```zaml\n# I am a comment\n# title This is a comment\ntitle This is # not a comment\n```\n\n### bool\n\nA `bool` accepts `true` or `false`\n\n```zaml\n# if your schema is {autoCleanup:bool}\n\nautoCleanup true\n\n#=\u003e { \"autoCleanup\": true }\n```\n\n[View this example in the online editor](https://gilbert.github.io/zaml/editor.html#s=N4IgzgxgFgpgtgQxALhMBBXALgewMIA2MCAdhgA7IBGOOBAviADTg4YBOEMKImuhxMuQAEWdhm70gA)\n\n### num\n\nA `num` accepts a single numerical value.\n\n```zaml\n# if your schema is {port:num}\n\nport 3000\n\n#=\u003e { \"port\": 3000 }\n```\n\n[View this example in the online editor](https://gilbert.github.io/zaml/editor.html#s=N4IgzgxgFgpgtgQxALhMADgewE4BdkB2ArnAL4gA04mR2EMKIWeABAMwAMXAOgSKUA)\n\n### str\n\nA `str` is the default type of any unspecified schema key. It accepts any character until it reaches a newline.\n\nIf you need multiline strings, you can use triple quotes (`\"\"\"`). Zaml will also un-indent your string automatically, just like [Ruby squiggly heredoc](https://infinum.com/the-capsized-eight/multiline-strings-ruby-2-3-0-the-squiggly-heredoc).\n\n```zaml\n# if your schema is {title,description} OR {title:str,description} OR etc.\n\ntitle You, Yourself, and U\ndescription \"\"\"\n  A story\n    ...\n  about your mirror.\n\"\"\"\n\n#=\u003e { \"title\": \"You, Yourself, and U\", \"description\": \"A story\\n  ...\\nabout your mirror.\" }\n```\n\n[View this example in the online editor](https://gilbert.github.io/zaml/editor.html#s=N4IgzgxgFgpgtgQxALhMALgS3QGxgGgBMZIAnTABywHsA7AXxH3GoFdSIYUQtcYACAJpt8QtqTAwcAM1EJahfgFUAOgpIRyVTHX4qQ+w7X78AgvzDpqpAJ57jJ-gDoXakwgBGbdPxvj+cJikpNZOaoYGBrQg9EA)\n\n### enum\n\nAn `enum` is a [str](#str) with restricted options. It's useful for a \"choose one\" situation.\n\n```zaml\n# if your schema is { paymentMode: enum(test,live) }\n\npaymentMode test\n\n#=\u003e { \"paymentMode\": \"test\" }\n```\n\nNaturally, if the user provides a value outside the schema, Zaml will reject it and report a readable error message.\n\n[View this example in the online editor](https://gilbert.github.io/zaml/editor.html#s=N4IgzgxgFgpgtgQxALhMADggnnGA7AFwFkB7AExmXwFc4AKAmMAgGgBsBLANxgEoBfEC3AlqAJwgwUITDnzFyMAASNmAHTwh+QA)\n\n### kv\n\nA `kv` is a set of key-value pairs. It requires a block.\n\n```zaml\n# if your schema is {redirects:kv}\n\nredirects {\n  /contact       /contact-us\n  /profile/:user /u/:user\n}\n\n#=\u003e { \"redirects\": { \"/contact\": \"/contact-us\", \"/profile/:user\": \"/u/:user\" } }\n```\n\nPlease note Zaml **is not** indentation sensitive.\n\n[View this example in the online editor](https://gilbert.github.io/zaml/editor.html#s=N4IgzgxgFgpgtgQxALhMATjAJgS0xAFzGQGsA3AXxABpwB7AV3QhhRE13yIAJgAdAHbduAegh0BBBIWGzhYiVMIBaBmEHyADujoAzHABsYI5GpjpRDE2fSCKgkBSA)\n\n### hash block\n\nA `{}` block is a specified inner schema. It translates to a hash that only allows your specified keys.\n\n```zaml\n# if your schema is { project: { title, private:bool } }\n\nproject {\n  title My Sweet App\n  private true\n}\n\n#=\u003e { \"project\": { \"title\": \"My Sweet App\", \"private\": true } }\n```\n\n[View this example in the online editor](https://gilbert.github.io/zaml/editor.html#s=N4IgzgxgFgpgtgQxALhMADgJwPYCsYQAuywhAloQDYwA0WZAbgoTMgEbbaUC+3IN4bAFdMEGChBY8BQgAJgAHQB2s2eSoxZAWQCesgMoB3GDDkBBdOmWr6TFmsxCYy7spDcgA)\n\nYou can also enhance basic types with blocks. This lets you specify cleaner config:\n\n```zaml\n# if your schema is { page|multi: str {hide:bool} }\n\npage index.html\npage wip.html {\n  hide true\n}\n\n#=\u003e { \"page\": [\n#     [\"index.html\", {}],\n#     [\"index.html\", { \"hide\": true }],\n#   ] }\n```\n\n[View this example in the online editor](https://gilbert.github.io/zaml/editor.html#s=N4IgzgxgFgpgtgQxALhMABABwQcxgHzgFcAbAFwEtl0wyAndYKCgExmQCMB7LkgX3R8QAGnBcidCDBQhsedBQB2bAB4A6KGTgkAOorkx0AdwqYNWkoz3p0zNunpEYevnpB8gA)\n\n### list\n\nA `list` is a sequence of values. A user can write lists either inline or with a block (but not both).\n\n```zaml\n# if your schema is {tags:list} OR {tags:list(str)}\n\n# Inline example\ntags library, npm, with spaces, js\n\n# Block example\ntags {\n  library\n  npm\n  with spaces\n  js\n}\n\n#=\u003e { \"tags\": [\"library\", \"npm\", \"with spaces\", \"js\"] }\n```\n\n[View this example in the online editor](https://gilbert.github.io/zaml/editor.html#s=N4IgzgxgFgpgtgQxALhMALgg5mZAbASzHQF8QAacAewFcAnCGFEAYgAIBJAO0K5jZgAPBHAAOeGAB0u7TDjaEARnQR0Anmy6i4bSSADuBdFDZhRCRmD1sAVla7T2AITxUIAawHCxE6XLBswNJsCgTKqmrBmtpRhsam5pZRdtIk0iAkQA)\n\nYou can specify the type of items in your list by specifying it in parethesis. When no item type is specified, it is defaulted to `str`, resulting in `list(str)`.\n\n```zaml\n# if your schema is { fav_nums:list(num) }\nfav_nums 10, 20\n\n#=\u003e { \"fav-nums\": [10, 20] }\n```\n\n[View this example in the online editor](https://gilbert.github.io/zaml/editor.html#s=N4IgzgxgFgpgtgQxALhMABAMwQNwPoB2ArnGMgDYCWYALgBTFwCU6AviADTgD2RAThBgoQ2fIzDoAjAAYO6AEzSAOgRCsgA)\n\nThe list item type may also be enhanced with a block. Note that you **must** specify an item type when you do!\n\n```zaml\n# if your schema is { users:list(str { admin:bool }) }\n\nusers {\n  andy\n  beth {\n    admin true\n  }\n  carl\n}\n\n#=\u003e { \"users\": [[\"andy\", {}], [\"beth\", {admin: true}], [\"carl\", {}]] }\n```\n\n[View this example in the online editor](https://gilbert.github.io/zaml/editor.html#s=N4IgzgxgFgpgtgQxALhMABAVzDATmZAGwEswAXACnN2AQBM5iA7ZAIwHt3CBfASnW4gANOHaZcEGChDY8YdMAA6TdOgRM6AT2WrWMMlAU7Vahs3RlcmGMe7GICXIWV2mIbkA)\n\nNote how a block changes the shape of the above parsed result. This allows you to use destructuring for each list item:\n\n```js\nvar result = parse(source, schema)\nfor (let [user, options] of result.users) {\n  //\n  // `options` will be {admin: true} for beth,\n  //  and undefined for andy \u0026 carl\n  //\n}\n```\n\n### inline-list\n\nNOTE: THIS FEATURE IS NOT IMPLEMENTED YET\n\nAn inline list a [list](#list) that can only be written inline. This is useful when you want to extend your list with a block:\n\n```zaml\n# if your schema is { env|multi: inline-list{require|multi} }\n\nenv development, test {\n  require lib-1\n  require lib-2\n}\nenv production {\n  require lib-3\n}\n\n# =\u003e { \"env\": [\n#      [[\"development\", \"test\"], { \"require\": [\"lib-1\",\"lib-2\"] }],\n#      [[\"production\"], { \"require\": [\"lib-3\"] }]\n#    ]}\n```\n\n### key|req\n\nAppending the `|req` attribute to a key will make that key requried.\n\n```zaml\n# if your schema is { access_token|req:str }\n\naccess_token abc123\n```\n\n[Open that in your browser](https://gilbert.github.io/zaml/editor.html#s=N4IgzgxgFgpgtgQxALhMABAiEZjAfQBcB7AaxgDsAfAJxgEdkxCb0BfEAGnGIFcacKEFhx4iZSpgBGEAIwAmAMwAdCiDZA) and see what happens when you remove the `access_token` line.\n\nIf you specify a `|req` within a basic-type hash block, it will make that block required.\n\n```zaml\n# if your schema is { table: str{ id|req: enum(INT,VARCHAR) } }\n\n# This is invalid! It requires a block\n# table users\n\n# This works\ntable users {\n  id INT\n}\n```\n\n[View this example in the online editor](https://gilbert.github.io/zaml/editor.html#s=N4IgzgxgFgpgtgQxALhMABAFwQIwDYzLpiYBOGAlgCYA+pMAjkTAHYCucAFAJIByAKgBoAagEEASgGEAEhICU6AL5KQg8AHs2pCDBQgAxOn5QKYdKfMsAbgjzUAhOm6Z09Bmwr0zCdPnUQAawAdFkNsfBh0NjAYUjAQkMNjCwB3dVIA+JZwgiiYuPRgEPRzKicBEMUQkEUgA)\n\n### key|multi\n\nAppending the `|multi` attribute to a key allows your users to specify it more than once.\n\n```zaml\n# if your schema is {project|multi:{title,type}}\n\nproject {\n  title A\n}\nproject {\n  title B\n  type personal\n}\n\n#=\u003e { \"project\": [{ \"title\": \"A\" }, { \"title\": \"B\", \"type\": \"personal\" }] }\n```\n\n[View this example in the online editor](https://gilbert.github.io/zaml/editor.html#s=N4IgzgxgFgpgtgQxALhMADgJwPYCsYQAuAPnAK4A2hAlssDYRTADSECe6MAvlyM+NjKYIMFCCx4ChAATAAOgDtp0hk2kBBRV0UT8RWYuWqY0gEKGVHE50xhsChBS2KQXIA)\n\n`|multi` will also ensure your key is always present, even if the user does not provide any.\n\n```zaml\n# if your schema is {project|multi:{title,type}}\n\n# (intentionally left blank)\n\n#=\u003e { \"project\": [] }\n```\n\n[View this example in the online editor](https://gilbert.github.io/zaml/editor.html#s=N4IgzgxgFgpgtgQxALhMADgJwPYCsYQAuAPnAK4A2hAlssDYRTADSECe6MAvlyM+NjKYIMFCBBcgA)\n\n### tuple\n\nA tuple captures two or more values for a given key, separated by commas. You can specify one in the schema using parenthesis:\n\n```zaml\n# if your schema is {redirect:(num,str,str)}\n\nredirect 302, /old, /new\n\n#=\u003e { \"redirect\": [302, \"old\", \"new\"] }\n```\n\n[View this example in the online editor](https://gilbert.github.io/zaml/editor.html#s=N4IgzgxgFgpgtgQxALhMATjAJgS0xAF2QAoA7AVzgBowD0a6BKAXxCvAHtz0IYURMufAQAEAZgAMAJhEB6DgBssc0jADuAHVIhmQA)\n\nPlease note that tuples may only contain basic types (`str`, `num`, `bool`, and [enum](#enum)). However, you're free to mix tuples with other features:\n\n```zaml\n# if your schema is {redirect|multi:(num,str,str){enableAt}}\n\nredirect 301, /x, /y\n\nredirect 302, /old, /new {\n  enableAt 2020-10-10\n}\n\n#=\u003e { \"redirect\": [[301, \"x\", \"y\"], [302, \"/old\", \"/new\", { \"enableAt\": \"2020-10-10\" }]] }\n```\n\n[View this example in the online editor](https://gilbert.github.io/zaml/editor.html#s=N4IgzgxgFgpgtgQxALhMABAJxgEwJbYQAuAPnAK4A2ReyAFAHblwA0YRmbHAlMDAwgBGlGAEEiAX3QSQLcAHtymCDBQhs+QkXQBmAAwBGdAHoAHiYCeAHQY2NBGMV16ATCfmUcJhjADu6YBt0dH4hEXF0F1c9AFoDWPibCRsQCSA)\n\n### array block\n\nA `[]` block is an array of items from a specified schema. It translates to an array of key-value tuples.\n\n```zaml\n# if your schema is {sidebar:[header,link:(str,str)]}\n\nsidebar {\n  header Site\n  link Home /\n  link About Us, /about\n\n  header Account\n  link Settings, /account/settings\n}\n\n#=\u003e { \"sidebar\": [\n#       [\"header\", \"Site\"], [\"link\", [\"Home\", \"/\"]], [\"link\", [\"About Us\", \"/about\"]],\n#       [\"header\", \"Account\"], [\"link\", [\"Settings\", \"/account/settings\"]]\n#     ] }\n```\n\nNote how a block changes the shape of the above parsed result. This allows you to use destructuring for each item:\n\n[View this example in the online editor](https://gilbert.github.io/zaml/editor.html#s=N4IgzgxgFgpgtgQxALhMMBLAJjARggJ2QG1YEcCAaAGwwDsBrZACjABcr2CBKAXQF8QlcAHsArgQgwU4bHkIACYAB06ChWQoKAyhjYxV62owUAJEXBiUFAekMLjDBQEFc4tgoCqYazYRuxNlV7TRgCFwgIcTogtQd6J20YNjZ6AHMfWwRI6LYbMGTUugzVflUQfiA)\n\n```js\nvar result = parse(source, schema)\nfor (let [type, value] of result.sidebar) {\n  //\n  // `type` will be \"header\" or \"link\",\n  //  whereas `value` will be a string (for header) or an array of size 2 (for link).\n  //\n}\n```\n\n## JavaScript API\n\n- [parse(source, schema [, parseOptions])](#parse)\n\n### parse\n\nThe primary way you interact with Zaml is through its `parse` function. This is how you convert Zaml source to a JavaScript data structure.\n\n`parse` takes two, maybe three arguments:\n\n1. The Zaml source code (as a string)\n2. A schema to parse with (as a string)\n3. [Options to enable extra features](#parse-options) (optional parameter)\n\nHere's a full example that includes reading from the file system. Assuming you have the following `my-config.zaml`:\n\n```zaml\nhost www.example.com\nport 443\ndisallow /admin\ndisallow /dashboard\n```\n\nYou can parse the above with this node.js code:\n\n```js\nvar fs = require('fs')\nvar zaml = require('zaml')\n\nvar source = fs.readFileSync(__dirname + '/my-config.zaml')\nvar result = zaml.parse(source, 'host,port:num,disallow|multi')\nresult\n//=\u003e { \"host\": \"www.example.com\", \"port\": 443, \"disallow\": [\"/admin\", \"/dashboard\"] }\n```\n\n## Parse Options\n\nparseOptions, the third parameter to `parse()`, has the following options:\n\n### vars\n\nType: `Record\u003cstring,string\u003e`\n\nEach key-value in the provided `vars` object will be made available for users to interpolate using the `$` operator. Example:\n\n```js\nlet source = `\n  title Welcome to $APP_NAME\n`\nzaml.parse(source, 'title:str', {\n  vars: { APP_NAME: 'My App' }\n})\n//=\u003e { \"title\": \"Welcome to My App\" }\n```\n\nNote that you can easily use this feature to provide the user access to their own environment variables in a node.js environment:\n\n```js\nzaml.parse(source, 'title:str', {\n  vars: process.env\n})\n```\n\n### failOnUndefinedVars\n\nIf `vars` is set, then setting `failOnUndefinedVars` to `true` will ensure users cannot use variables that are not defined. This is useful if you want to e.g. ensure a key is always defined:\n\n```js\nlet source = `\n  title Welcome to $iDontExist\n`\n//\n// This will throw an error!\n//\nzaml.parse(source, 'title:str', {\n  vars: { anyOtherKey: '' },\n  failOnUndefinedVars: true,\n})\n```\n\n### caseInsensitiveKeys\n\nSetting this to `true` will allow users to write their config keys in a case-insensitive manner.\n\n### caseInsensitiveEnums\n\nSetting this to `true` will allow users to write [enum](#enum) values in any case.\n\n## Spec\n\nZaml has not yet reached 1.0, so there is no spec as of yet. However, here's a rough ABNF grammar for the lexer:\n\n```\nline = statement | comment\nstatement = key rest [\"{\\n\" *statement \"}\"] \"\\n\"\n\nkey = string-with-no-whitespace\nrest = string-with-no-newlines\n\ncomment = \"#\" rest (\"\\n\" | EOF)\n```\n\nAfter lexing, the parser uses the schema to determine how to parse the `rest` and `*statement` for each statement.\n\n## Roadmap\n\n- New regular expression type\n- Allow inline `kv`?\n- Allow blocks on `kv`\n- New `json` type for arbitrarily-nested json-like data?\n- Multiline strings? `text` type?\n- Split `num` into `int` and `float`?\n- Pluggable validation?\n- Default values for tuple types?\n- Command line interface?\n\nRegarding the [online editor](https://gilbert.github.io/zaml/editor.html):\n\n- `Get code` button\n- Fancy explanations of schema on hover\n\n## Contributing\n\nInterested in contributing? There are several ways you can help:\n\n- Open or discuss an issue for an item on the roadmap\n- Implement Zaml in another programming language\n- Report any bugs you come across\n- Report a behavior that you think *should* be bug\n- Help start a spec!\n\n## Developing\n\n```\nnpm install\nnpm start # In a separate terminal\nnpm test\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgilbert%2Fzaml","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgilbert%2Fzaml","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgilbert%2Fzaml/lists"}