{"id":18354631,"url":"https://github.com/birchb1024/goyamp","last_synced_at":"2025-08-04T21:42:12.827Z","repository":{"id":57745779,"uuid":"192136786","full_name":"birchb1024/goyamp","owner":"birchb1024","description":"The Macro-processor for YAML and JSON","archived":false,"fork":false,"pushed_at":"2023-12-18T08:39:40.000Z","size":337,"stargazers_count":21,"open_issues_count":7,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-07T04:37:57.296Z","etag":null,"topics":["arm-templates","automation","devops-tools","json","kubernetes-manifests","macro-processor","template-language","yaml","yaml-configuration"],"latest_commit_sha":null,"homepage":"https://github.com/birchb1024/goyamp","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/birchb1024.png","metadata":{"files":{"readme":"README.asciidoc","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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-06-16T00:59:00.000Z","updated_at":"2025-04-11T10:02:49.000Z","dependencies_parsed_at":"2024-06-20T09:27:22.228Z","dependency_job_id":"4851d513-2c3a-4afe-a846-18b5951a81b1","html_url":"https://github.com/birchb1024/goyamp","commit_stats":null,"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/birchb1024/goyamp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/birchb1024%2Fgoyamp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/birchb1024%2Fgoyamp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/birchb1024%2Fgoyamp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/birchb1024%2Fgoyamp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/birchb1024","download_url":"https://codeload.github.com/birchb1024/goyamp/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/birchb1024%2Fgoyamp/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268782139,"owners_count":24306626,"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","status":"online","status_checked_at":"2025-08-04T02:00:09.867Z","response_time":79,"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":["arm-templates","automation","devops-tools","json","kubernetes-manifests","macro-processor","template-language","yaml","yaml-configuration"],"created_at":"2024-11-05T22:04:42.614Z","updated_at":"2025-08-04T21:42:12.766Z","avatar_url":"https://github.com/birchb1024.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"= Goyamp - The Go Macro Processor for YAML and JSON\nPeter Birch \u003cbirchb1024 at gmail.com\u003e\n@@@VERSION@@@, @@@DATE@@@\n:toc: macro\n:toclevels: 4\nGoyamp is a general-purpose macroprocessor for YAML files.  Both its input and output are YAML. It scans the input for symbols and makes substitutions and expansions on the output. Goyamp is 100% YAML so the syntax for defining and calling macros is YAML also. It can also process JSON in and out.\n\n== TL;DR\n\n.Input\n[source, YAML]\n----\n- defmacro:\n    name: foo\n    args: [who]\n    value:\n        Hello: who\n- foo:\n    who: World\n----\n\n.Output\n[source, YAML]\n----\n- Hello: World\n----\n\n== Get Started with a Pre-compiled Binary\n\nGoYamp is a Go program contained in the goyamp module. This has been compiled and uploaded to Github and dockerhub.\n\n=== Download\n\nYou can download a binary copy of the Goyamp program. Copies are available on GitHub in https://github.com/birchb1024/goyamp/releases[Releases] in the repository. For example \n\n[source, bash]\n----\n$ curl -L -o goyamp https://github.com/birchb1024/goyamp/releases/download/@@@VERSION@@@/goyamp\n$ chmod a+x goyamp\n----\n\n=== Installation\n\nSimply place the binary in your $PATH or update your path to include the directory where you placed it.\n\n=== Execution\n\nThe program  is run from the command-line giving the input file to parse as the first argument followed by optional arguments to the expansion. The expansion is written to the standard output, which you normally redirect to another file.:\n\n.Usage\n[source,bash]\n----\n$ goyamp [-d|-debug] [-h|-help] [-o|-output yaml|json|lines] [Filename | - ] [arg1..argn]\n----\n\nThe `-debug` option causes Goyamp to trace its internal execution on stderr and print a backtrace on errors.\n\nPut your first Goyamp file in `hello.yaml`:\n\n.hello.yaml\n[source, YAML]\n----\n\"Hello {{env.USER}} from Goyamp v{{__VERSION__}}\"\n----\n\nAnd run it\n\n[source, bash]\n----\n$ goyamp hello.yaml\n----\n\n=== Working with JSON\n\nJSON is a superset of YAML, so goyamp can read JSON files too. You could define variables and macros in JSON, but most people prefer to code in YAML because it's easier \nto read. If you want to output JSON, specify the `-output json` command option. \n\nWe use this for generating Azure DevOps pipeline definitions - edit in YAML and generate JSON. You could use this for Azure ARM files or any other JSON.\n\nHowever remember that YAML is a super-set of JSON, so  you can express things in YAML which are not valid JSON. For example `{ 1: Monday, 2: Tuesday }` \nhas integer map keys and is valid YAML but JSON accepts only strings in map keys. Hence goyamp automatically converts map keys to strings when outputting JSON. Example:\n\n\n.hello.yaml\n[source, YAML]\n----\n$ echo '[{ null : Monday, 2: Tuesday }, null]' | ./goyamp -o json\n[\n    {\n        \"2\": \"Tuesday\",\n        \"null\": \"Monday\"\n    },\n    null\n]\n----     \n\nJSON only accepts a single top-level object. If you have multiple YAML docs in your expanded code, goyamp will output each one in turn, which is not strictly valid.\n\n== Running from Docker\n\nYou just want to use the docker image. \n[source, bash]\n----\n$ docker run --rm -u $(id -u):$(id -g) -v \"$PWD\":/work docker.io/birchb1024/goyamp /work/hello.yaml\n----\n\n== An example - Pipelines as Code.\n\nSupposing we are building some https://github.com/tomzo/gocd-yaml-config-plugin[GoCD] pipeline definitions in YAML each of which uses the same Git repository.  The YAML we have to write looks like this:\n\n.output.yaml\n[source,YAML]\n----\npipelines:\n  mypipe1:\n    group: mygroup\n    label_template: ${COUNT}\n    materials: # \u003c1\u003e\n      mygit:\n        branch: master\n        git: http://my.example.org/mygit.git\n    stages: null\n  mypipe2:\n    group: mygroup\n    label_template: ${COUNT}\n    materials: # \u003c1\u003e\n      mygit:\n        branch: ci\n        git: http://my.example.org/mygit.git\n    stages: null\n----\n\u003c1\u003e Duplicated\n\nWe don't want re-key duplicated code so we define a macro which Goyamp expands whenever it is invoked. Our source code now looks like this:\n\n.YAML source\n[source,YAML]\n----\ndefine: # \u003c1\u003e\n    name: mygit_repo_url\n    value: http://my.example.org/mygit.git\n\ndefmacro: # \u003c2\u003e\n    name: mygit_materials\n    args: [branch_name]\n    value:\n      mygit:\n        git: mygit_repo_url # \u003c3\u003e\n        branch: branch_name\n---\npipelines:\n  mypipe1:\n    group: mygroup\n    label_template: \"${COUNT}\"\n    materials: {mygit_materials: {branch_name: master}} # \u003c4\u003e\n    stages:\n\n  mypipe2:\n    group: mygroup\n    label_template: \"${COUNT}\"\n    materials:\n        mygit_materials:\n            branch_name: ci # \u003c5\u003e\n    stages:\n----\n\u003c1\u003e simple variable definition\n\u003c2\u003e a macro Definition\n\u003c3\u003e variable used\n\u003c4\u003e a macro call - flow style\n\u003c5\u003e a macro call - block style\n\nWhen run through Goyamp, the output is as above. Now we have a single place where the git repository is defined, if we need to change it we can change it once.\n\n== More Examples\n\nThe source repository has a directory of examples which you can run to observe the behaviour of the features. They are located in https://github.com/birchb1024/goyamp[the Github goyamp repository]. You can clone the soure repo to download them or browse them https://github.com/birchb1024/goyamp/tree/master/examples[here].\n\n== Applications\n\nThis program is general-purpose, it can be used wherever YAML is required. Its first uses were for GoCd pipelines and Ansible playbooks. These are human-readable source code which is a subset of YAML. Hence Goyamp may not be applied to all aspects of YAML especially those which result from data transmission.  We will not be attempting to exercise Goyamp with such inputs.\n\nSince YAML is a superset of JSON it can also be used to generate JSON for, say, Azure ARM files.\n\n== Similar Tools\n\nYamp - This is the progenitor of Goyamp, a Python YAML macro processor. Goyamp and Yamp are compatible, however there are some differences due to their respective execution environments. Being a Python program itself, Yamp can call Python functions directly.   \n\nThere are many great general-purpose macro-processors available, starting with the venerable `GPM`, through `m4`, cpp, and lately, Jinja2. However these are predominantly character-based and the programmer has to compute the indentation required by YAML by counting spaces. Like previous authors we started on this course of writing yet another macro-processor primarily for reasons of laziness. Since Goyamp transforms maps and sequences not character strings, indentation is automatic.\n\n\n== Reference\n\nThis section describes the operation of the processor and the macros available.\n\n=== The Command Line\n\nThe command to run Goyamp is a single binary executable filename followed by optional arguments. Assuming that `goyamp` is in the `$PATH`:\n\n.Usage\n[source,bash]\n----\n$ goyamp [-d|-debug] [-h|-help] [-o|-output yaml|json|lines] [Filename | - ] [arg1..argn]\n----\n\nIf the filename is the minus sign `-` or if there are no arguments, Goamp reads YAML from the standard input, so it serves as a filter. As in\n\n[source,bash]\n----\n$ echo \"[define: {data: {load: test/fixtures/blade-runner.json}}, data.directory]\" | goyamp \n- ' Ridley Scott'\n----\n\nIf the -output option is given, this specifies the output format required. The default is YAML. When `json` is selected , JSON is output subject to the constraints mentioned above. When `lines` is selected, any top level list is printed with no surrounding syntax. Top-level map objects are printed in JSON format on one line. 'line' mode suits downstream Unix programs which expect simple lines, we use it to generate `bash` scripts or data for `awk`.\n\n==== File Suffixes\n\nAny file suffix can be used - it is assumed to be YAML/JSON.\n\nIn practice `yaml` or `json` sufffixes will be recognised by most text editors editing modes. You will need to configure your text editor if you use a non-standard suffix.\n\n==== Docker\n\nA docker image is provided in docker.io (Docker Hub) https://cloud.docker.com/repository/docker/birchb1024/goyamp[here]. This image uses a slim Debian base. To use it you need to map your workspace into the container and use your current user id. In general:\n\n[source, bash]\n----\n$ docker run --rm -u $(id -u):$(id -g) -v \"$HOME\":/work docker.io/birchb1024/goyamp [options] /work/{path to your code}.yaml [arg1, arg2...] \u003e outputfile.yaml\n----\n\n=== Processing\n\n\nWhen Goyamp starts, it collects the command-line arguments and assigns the list to the variable `argv`. It collects the process environment and assigns it to the map variable `env`. Goyamp then reads the input file, attempts to parse the YAML and holds the resulting data as objects in memory. (If the YAML does not parse Goyamp exits). It recursively scans the objects looking for strings which are the same as defined variables or which contain variables inside the string in curly braces. If it finds a match, it substitutes the object with the variable's value.\n\nGoyamp is a substitution engine. It looks for things in its input an when it sees them replaces them with the substitution. The things to look for and the substitutions we call variables and bindings. For example:\n\n.Variables Bindings\n[options=\"header,footer\",width=\"50%\"]\n|=======================\n|Variable Name|Value to substitute\n|mygit_repo_url\n\na|\n[source,YAML]\n----\nhttp://my.example.org/mygit.git\n----\n\n|mygit_materials\n\na|\n[source,YAML]\n----\nargs: [branch_name]\nmygit:\n        git: mygit_repo_url\n        branch: branch_name\n----\n\n|=======================\n\n\nWhen scanning maps, Goyamp does not expand map keys unless either the map key is explicitly identified as a variable with the `^` caret character, or the map key is a string with embedded curly braces. In these two special cases Goyamp looks up variables or interpolates the string.  \n\nSome special variables contain 'macros' - these must be within a map of their own, with a value containing a map of arguments which can contain anything. Normally a macro will contain more than the original, so we call this 'macro expansion' footnote:[But it could actually be a reduction!]. \n\nGoyamp is looking for macro calls with this structure:\n\n[source,YAML]\n----\n\u003cMacro\u003e:\n   \u003cArgument1\u003e: \u003cvalue\u003e\n   \u003cArgument2\u003e: \u003cvalue\u003e\n    . . .\n----\n\nSome macros have special functions and are built-in to Goyamp. Those are described in the reference section.\n\nHere's examples of three kinds of things Goyamp is scanning for replacement:\n\n.Simple Variables\n[source,YAML]\n----\n- Username\n- 'directory'\n----\n\n.Embeded Variables\n[source,YAML]\n----\n- 'The username is {{Username}}'\n----\n\n.Macro Calls\n[source,YAML]\n----\n- add_user:\n    name: Kevin\n    phone: (555) 098 880\n----\n\nWhen all the objects in the data have been scanned and in some cases, substituted, Goyamp outputs the new object tree on the standard output\nin YAML or JSON format. Because YAML maps are unordered, the order of the keys and their corresponding values on output maybe be different from \nthe input footnote:[Order-preservation may happen in a future version, but it's complicated].\n\nWhen the processor sees a null item in an input sequence, these are preserved, however if the empty value is the result of a `define:`, `defmacro:` or other expansion which produces empty values, the value is stripped from the output. \n\n=== Variables\n\nDuring processing goyamp maintains a hierarchy of bindings of variable names to variable values. The top level of bindings is the gobal environment. As each macro is applied the application creates a unique environment for the macro variables which is popped when the macro finishes.\n\n==== `define` - Definition of Variables\n\nYou can define new variable bindings or update existing variables with the `define` macro. The value can be any YAML expansion. Variable names are expected to be strings.\n\n[source, YAML]\n----\n- define: {name: age, value: 32}\n- age\n- define: {name: age2, value: [age, age]}\n- age2\n- define: {name: age2, value: [{define: {name: age, value: 99}}, age]}\n- age2\n----\n\nProduces:\n\n[source, YAML]\n----\n- 32\n- - 32\n  - 32\n- - 99\n----\n\nThe result of expanding a `define`, `undefine`, `if` and `include` is a 'magic' value `goyamp.EMPTY`. This value is removed automatically from sequences, and maps if a `define` or `if` has been used there. So it's better to use `define` etc in sequences. When placed in their own document, they disappear completely:\n\n[source, YAML]\n----\n- define: {name: age, value: 32}\n- if: true\n  else: 23\n---\n- age\n----\n\nProduces:\n\n[source, YAML]\n----\n- 32\n---- \n\nThis provides a simple way to have conditional map keys, or list items. For example, if we only want a key to appear sometimes, we can use:\n\n[source, YAML]\n----\nsome_map:\n  this_key_is_always_here: 42\n  this_key_only_appears_if_$var_is_true:\n    if: $var\n    then: 23\n----\n\n\n==== Scalars\n\nVariables can contain any YAML scalar, int float, string, True, False and null.\n\n==== Collections\n\nVariables can contain any YAML collection ie, maps and lists.\n\n==== Variable Expansion\n\nWhen Goyamp scans YAML it looks for variables in the lists and map values. When one is found it is replaced with the current value of variable binding. It searches the stack of macro bindings until the global environment is reached. If no bindng is found the string is output unchanged.\n\n===== Variables Embedded in Strings\n\nInside strings, Goyamp will insert expansions delimited by the double-curlies `{{` and `}}`. It's looking for variable names.\n\n[source, YAML]\n----\n- define: {name: X, value: Christopher}\n- define: {name: AXA, value: 'A{{ X }}A'}\n---\n- AXA\n# Produces AChristopherA\n----\n\nThis processing is also done in map keys so that map keys can be computed during the expansion. For example:\n\n[source, YAML]\n----\nrepeat:\n  for: loop_variable\n  in : {range: [1,3] }\n  body:\n    'KEY_{{loop_variable}}': some step\n----\n\n===== Interpolation with dot syntax\n\nIf a string contains periods, such as `data.height` Goyamp looks for a exactly matching variable name, which is expanded with the value. Otherwise the first item (ie `data`) is assumed to be a variable name.\n\nIf a binding for the first part is found the value of the variable is assumed to be a collection. The other items which we call sub-variables are used to index the collection (ie `height`). If the collection is a map, the sub-variable name is used as the key. If it is a list the subvariable must evaluate to an integer which is zero-indexed into the list. These subvariable names are also expanded before use so other variables can be used to index the collection. \n\n[source, YAML]\n----\n- define: { zero: 0 }\n- define:\n    name: data\n    value:\n        - type: webserver\n          hostname: web01\n          ip: 1.1.2.3\n        - type: database\n          hostname: db01\n          ip: 1.1.2.2\n- define: {data.1 : Wednesday}\n---\n- data.1\n- data.1.hostname\n- data.zero.hostname\n----\n\nProduces\n\n[source, YAML]\n----\n- Wednesday\n- db01\n- web01\n----\n\n===== Variable Map Keys with the Caret\n\nNormally map keys are not expanded, but with a preceding caret character Goyamp looks up the variable name in the current binding and uses its value. For example:\n\n[source, YAML]\n----\n- defmacro:\n    name: my-macro\n    args: [ param ]\n    value:\n      ^param:\n        LtUaE : RU\n---\n- my-macro: { param: 42 }\n----\n\nEvaluates to:\n\n[source, YAML]\n----\n- 42:\n    LtUaE: 42\n----\n\nThis facility even allows macros to be called indirectly since the macro being called is provided by the variable rather than in the code itself. Here's an example, although the practical value of this is yet to surface. This code applies four different macros to the same arguments in turn:\n\n[source, YAML]\n----\nrepeat:\n  for: macro\n  in: [+, range, flatten, quote]\n  body:\n    ^macro: [1, 5]\n----\n\n\n===== Defining Multiple Variables\n\nDeclarations don't need the 'name' and 'value' keys, and multiple variables are simultaneously bound.\n\n[source,YAML]\n----\n- define: { quick: 'shorthand' }\n- define:\n    name: Sara\n    age: 34\n    height: 123\n----\n\n==== Refactoring Goyamp with `undefine`\n\nSometimes a variable needs to be renamed or removed. For example if a Goyamp macro name conflicts with a name used in the\noutput format required. The `undefine` macro removes a variable binding from the current environment. Usage:\n\n[source,YAML]\n----\nundefine: variablename\n----\n\nUsed at the top level\n(outside of a macro) `undefine` can be used to change the definitions of Goyamp built-in macros themselves. This is done by first assigning a new name with the currently used macro, then undefining the original name. If this is done before any files are included, it can be used to redefine Goyamp syntax. For example we can use `plus` instead of the `+` symbol as follows\n\n\n[source,YAML]\n----\n- define: \n    plus: +\n- undefine: +\n- {plus: [1,2,3]}\n----\n\n=== Macros\n\nMacros are re-usable templates of YAML objects that can be called up almost anywhere in the expansion. They differ from variables becuase they have parameters which are used to fill holes in the template. The are similar to functions, but unlike functions their entire text is always the result. By defining oft-repeated YAML fragments in macros repetitive work is avoided. Also a singular macro definition makes maintainance easy since there is a single defintion for a concept which can be easily changed.\n\n==== Defining with `defmacro`\n\nMacros are defined with the `define` macro which gives the macro a name and specifies the arguments it has and the expansion to return, the body.  A macro definition looks like this:\n\n[source,YAML]\n----\n- defmacro:\n    name: \u003cthe name of the macro\u003e\n    args: [\u003clist of argument names\u003e, ...]\n    value:\n      \u003cSome YAML to be expanded\u003e\n----\n\nExample - Database upgrade steps:\n\n[source,YAML]\n----\ndefmacro:\n  name: app-upgrade\n  args: [appname, dbname]\n  value:\n      Database upgrade for {{ appname }}:\n        - stop application {{ appname }}\n        - backup app database {{ dbname }}\n        - upgrade the database {{ dbname }}\n        - restart the application {{ appname }}\n        - smoke test {{ appname }}\n---\n- {app-upgrade: { appname: Netflix, dbname: db8812}}\n- app-upgrade:\n    appname: Stan\n    dbname: postgres123123\n----\n\nProduces:\n\n[source,YAML]\n----\n- Database upgrade for Netflix:\n  - stop application Netflix\n  - backup app database db8812\n  - upgrade the database db8812\n  - restart the application Netflix\n  - smoke test Netflix\n- Database upgrade for Stan:\n  - stop application Stan\n  - backup app database postgres123123\n  - upgrade the database postgres123123\n  - restart the application Stan\n  - smoke test Stan\n----\n\n==== Invoking/calling Macros\n\nAs above, macro calls are just maps with a particular structure:\n[source, YAML]\n----\n\u003cmacro name\u003e: \n   \u003carg1\u003e : \u003carg 1 value\u003e\n   ...\n   \u003cargN\u003e : \u003carg N value\u003e\n----\n\n==== Macros with no arguments\n\nYou can define macros with no arguments at all. Macros can be shorthand for expressions where you compose variables together, run conditions or other processing. The macro has access to all variables in scope where it was defined. For example here is a macro to concatenete variables to make a URL. In this example the macro uses the global (top-level) variables 'base-url' and 'module'.  \n\nExample:\n\n[source,YAML]\n----\n# Definition\n- defmacro:\n    name: api-url\n    value: \"{{base-url}}/{{module}}/list\"\n---\n# Call\napi-get:\n  url: {api-url: } # must have a space after the ':' !\n----\n\nProduces\n\n[source,YAML]\n----\n- api-get:\n    url: https://foo.org/api/users/list\n----\n\n==== Macros with variable arguments\n\nIf the arguments in the definition are specified as a string, not a list, the string is the single argument. All the actual arguments at call-time are collected and bound to the variable in a map.\n\n[source,YAML]\n----\n- defmacro:\n    name: \u003cthe name of the macro\u003e\n    args: \u003cargument_variable_name\u003e\n    value:\n      \u003cSome YAML to be expanded\u003e\n----\n\nExample:\n\n[source,YAML]\n----\n# Definition\n- defmacro:\n    name: package\n    args: all\n    value:\n      name: all.doc\n      yum:\n        name: apache\n        state: all.state\n\n---\n# Call\npackage:\n  doc: Install apache\n  name: httpd\n  state: latest\n----\n\nProduces\n\n[source,YAML]\n----\nname: Install apache\nyum:\n  name: apache\n  state: latest\n----\n\nThe disadvantage of vararg macros is that Goyamp cannot ensure that all the required arguments have been supplied in the call.  \n\n==== Nesting Macros\n\nMacro calls can be nested i.e. a macro can can contain a call to another in its arguments. Likewise macro definitions can be nested. The macro arguments are lexically scoped, a closure is collected at the time of definition. The macro call executes in the environment in the define-time closure. Macros can call themselves directly or indirectly.\n\n\n\n=== Conditional Expansion with `if then else`\n\nThe `if` macro renders one value from a choice of two based on whether the condition argument is true. Where true means it's `true` or not `false` or `null`. The `then` argument is expanded if so, otherwise the `else` argument. It's not required to have both `then` and `else` arguments - when the condition requires the missing one, it expands to `null`.\n\n[source,YAML]\n----\nif: \u003cBooleanish (true, false or null)\u003e\nthen: \u003cvalue if true\u003e\nelse: \u003cvalue if false or null\u003e\n----\n\nExample:\n\n[source,YAML]\n----\n# Some variable\ndefine:\n  application:\n    name: CSIRAC\n    has_database: true\n    arch: valves\n---\nif: application.has_database\nthen:\n  - shutdown database\nelse:\n  - shutdown not required\n----\n\nProduces:\n\n[source,YAML]\n----\n- shutdown database\n----\n\nExample - short form\n\n[source,YAML]\n----\nif: true\nelse: 'This value if false or Null'\n----\n\nProduces `null`\n\n=== Testing equality with `==`\n\nMacros can have almost any name, this one is the symbol '=='. It expands to `true` or `false` if the items in the list are equal. Most often used inside an enclosing `if` macro.\n\n[source,YAML]\n----\n{ ==: [arg1, arg2, ...] }\n----\n\nExample:\n\n[source,YAML]\n----\n{ ==: [1, 1, 10] }\n----\n\nProduces the value `false`.\n\n=== Preventing Expansion with `quote`\n\nThe `quote` macro does not expand its input arguments returning them unexpanded.\n\nExample:\n\n[source,YAML]\n----\n- define: { data1: { sub: 2}}\n- data1.sub\n- quote: data1.sub\n----\n\nProduces\n\n[source,YAML]\n----\n- 2\n- data1.sub\n----\n\n=== Looping with `repeat`\n\nThis macro repeatedly expands the same object, either returning a list or a map. If the `key` argument is present it returns a map, using the `key` argument as the item's key. This must have embedded variables derived from the looping execution otherwise there will be a key collision error. With no `key` argument, it returns a list.\n\n[source,YAML]\n----\nrepeat:\n  for: \u003cloop variable name\u003e\n  in: [list of items]\n  key: \u003cstring key with embedded varaibles in {{}}\u003e # Optional\n  body: \u003cany value\u003e\n----\n\n\nExample - returning a dictionary:\n\n[source,YAML]\n----\nrepeat:\n  for: environment_name\n  in:\n    - DEV1\n    - SVT\n    - PROD\n  key: 'Deploy_App_{{environment_name}}'\n  body:\n    stage: step\n----\n\nProduces:\n\n[source,YAML]\n----\nDeploy_App_DEV1:\n  stage: step\nDeploy_App_PROD:\n  stage: step\nDeploy_App_SVT:\n  stage: step\n----\n\nExample - returning a list:\n\n[source,YAML]\n----\nrepeat:\n  for: loop_variable\n  in: {range: [1,3]}\n  body:\n    loop_variable: 'KEY_{{loop_variable}}'\n    some: step\n    another:\n----\n\nProduces:\n\n[source,YAML]\n----\n- another: null\n  loop_variable: KEY_1\n  some: step\n- another: null\n  loop_variable: KEY_2\n  some: step\n- another: null\n  loop_variable: KEY_3\n  some: step\n----\n\nExample - looped list with changing keys. Here the keys and values of a child map are changed. :\n\n[source,YAML]\n----\nrepeat:\n  for: loop_variable\n  in: {range: [12,13]}\n  body:\n    'index_{{loop_variable}}': { +:  [100, loop_variable] }\n    some: step\n----\n\nProduces:\n\n[source,YAML]\n----\n- index_12: 112\n  some: step\n- index_13: 113\n  some: step\n----\n\n=== Looping with `range`\n\nThe `range` macro substitutes a list of numbers that can be used in `repeat` macros. (Or anywhere else a list of numbers is needed). The start and end values are passed as a list argument. The range can count up or down, always by one. \n\n[source, YAML]\n----\nrange: [3,5]\n----\n\nProduces `[3,4,5]`\n\n`range` also accepts a map object, in which case it expands the sequence of map keys. For example\n\n[source, YAML]\n----\n- define: {map: {ra: 879, rb: 662}}\n- range: map\n----\n\nProduces `[ra, rb]`. This can then be used in repeat to loop over the items in a map. Dot notation is used to expand individual members of the map. \nFor example here the loop variable is set to `ra` then `rb` which `map.keyz` resolves to `879` and `662`:\n\n[source, YAML]\n----\nrepeat:\n  for: keyz\n  in: {range: map}\n  body:\n    map.keyz\n----\n\nBe aware that map keys in data (such as `ra`) might conflict with already defined variables.   \n\n=== Combining Lists with `flatten`\n\nSometimes you need to combine lists, perhaps from different macro expansions. The `flatten` macro combines multiple lists into a single, flat, list. The flattening is recursive. Syntax:\n\n[source,YAML]\n----\nflatten: \u003c list of objects \u003e\n----\n\nFor example:\n\n[source,YAML]\n----\ndefine: {home-directories: [/home/elvis, /home/madonna]}\n---\nflatten: [[home-directories], /var, /log]\n---\nflatten: [1, 2, [3], [[4, 5]], [[[ 6,7]]] ]\n----\n\nProduces:\n\n[source,YAML]\n----\n- /home/elvis\n- /home/madonna\n- /var\n- /log\n---\n- 1\n- 2\n- 3\n- 4\n- 5\n- 6\n- 7\n----\n\n=== Combining One Level of Lists with `flatone`\n\nThe `flatone` macro combines multiple lists into a single, flat, list. The flattening is *not* recursive, only the first level is flattened. Syntax:\n\n[source,YAML]\n----\nflatone: \u003c list of objects \u003e\n----\n\nFor example:\n\n[source,YAML]\n----\nflatone: [1, 2, [3], [[4, 5]], [[[ 6,7]]] ]\n----\n\nProduces:\n\n[source,YAML]\n----\n- 1\n- 2\n- 3\n- - 4\n  - 5\n- - - 6\n    - 7\n----\n\n=== Combining Maps with `merge`\n\nThe `merge` macro takes a list of maps and merges them togther to make a single map. When there are keys shared between the supplied maps, the program uses the last one seen, it over-writes the earlier value. Hence the order in the list dictates the priority. Merge is NOT recursive, it merges one level of the maps provided. Syntax:\n\n[source,YAML]\n----\nmerge: \u003c list of maps \u003e\n----\n\nFor example:\n\n[source,YAML]\n----\nmerge:\n  - { a : 1 }\n  - { b : 2 }\n  - { c : 3 , a : -1}\n----\n\nProduces:\n\n[source,YAML]\n----\na: -1\nb: 2\nc: 3\n----\n\nA more complex example shows combining data from multiple sources:\n\n[source,YAML]\n----\n- define:\n    network-data:\n      hostname: tetris.games.org\n- defmacro:\n    name: mymacro\n    args: [arg1]\n    value:\n      hostname: arg1\n      ip: 1.1.1.1\n      app: tetris\n- merge:\n  - { hostname: tetris.home.org }\n  - { site: Kansas }\n  - mymacro:\n      arg1: tetris\n  - network-data\n----\n\nWhich boils down to:\n\n[source,YAML]\n----\n- app: tetris\n  hostname: tetris.games.org\n  ip: 1.1.1.1\n  site: Kansas\n----\n\n=== Arithmetic with `+`\n\nThe `+` macro adds a list of numbers, int or float.\n\n[source, YAML]\n----\n+: [1,2,4,8]\n----\n\nProduces `15`\n\n=== Reading files with `include`\n\n`include` reads and expands the list of Goyamp YAML files in order. The filenames can be the result of prior macro expansion. So derived filenames like \"{{ROOT_DIR}}/{{arch}}/config.yaml\" are possible.\n\n[source, YAML]\n----\ninclude:\n- \u003cfilename\u003e\n- \u003cfilename\u003e\n----\n\n=== Reading Data Files\n\nSometimes you want to use raw data for parameters and variable values. For example you may have an inventory or database of facts. Goyamp can load YAML or JSON data. \n\n==== Reading Data with `load`\n\nThe `load` macro reads a single file of YAML or JSON data and returns the result. No variable substitutions or macro expansions are performed on the data. YAML data is returned as a list, one object for each 'doc'. footnote:[YAML files are subdivided into 'docs' separated by '---']\n\n[source, YAML]\n----\n{load: \u003cfilename\u003e}\n----\n\nExamples:\n\n[source, YAML]\n----\n- define: {name: file, value: 'load_data.yaml'}\n- define:\n    name: somedata\n    value: {load: file}\n- define:\n    movie1: {load: '../test/fixtures/blade-runner.json'}\n----\n\n==== Loading Shell Script Data\n\nWhen you have shell variables in files which you want to use as input to expansion, you can load them into the environment of the Goyamp execution. For example here's a script with some dynamic data:\n\n.data.sh\n[source,bash]\n----\nexport VARIABLE1=value1\nexport VARIABLE2=\"${VARIABLE1}_value2\"\nexport VARIABLE3=\"${VARIABLE2}_value3\"\n----\n\nThe shell script must executed to determine the values. To load this into the Goyamp environment, use shell wrappers like this:\n\n[source,bash]\n----\n$ env -i bash --noprofile --norc -c '. data.sh ; echo env | goyamp'\n----\n\nHow does this work?\n\n* `env -i bash` creates a bash process with an empty environment.\n* `--noprofile --norc` prevent bash from reading profile files on startup\n* `-c '. data.sh` sources the shell script in the current (empty) environment\n* `echo env | goyamp` runs Goyamp with an input of just `env` - this will output all the environment variables\n\nThe YAML output contains the variables we want plus a couple of variables `bash` always needs:\n\n[source, Shell]\n----\nPWD: /home/birchb/workspace/goyamp\nSHLVL: '1'\nVARIABLE1: value1\nVARIABLE2: value1_value2\nVARIABLE3: value1_value2_value3\n_: /usr/bin/python\n----\n\n\n=== Executing External Programs with `execute:`\n\nThe `execute` builtin runs subprocesses and sends data to and from them. The syntax has two forms, \nthe first takes a string argument: \n\n[source, yaml]\n----\nexecute: \u003ccommand string\u003e\n----\n\nThe result is expanded as a string.  \n\nThe second form allows full control over the execution:\n\n[source, yaml]\n----\nexecute:\n\tcommand: \u003cpath to executable\u003e\n\targs: \u003ca sequence of strings\u003e\n\tenvironment: \u003c a map of strings containing an environment additions for the process\u003e \n\tdirectory: \u003ca path string\u003e\n\tresponse-type: \"string\"|\"lines\"|\"json\"|\"yaml\" - default \"lines\"\n\trequest-type: \"string\"|\"lines\"|\"json\"|\"yaml\" - default \"lines\"\n\trequest: \u003cany yaml\u003e\n----\n\nAfter execution, the stdout of the process is returned as the result processed according to the response-type value. If there is\nan error during execution the goyamp process stops with status '2'.\n\nEach argument is used as follows\n\n*command*\n\nThis is the name of the file to be run, which should on the `$PATH` or be an absolute path.\n\n*args*\n\nThese are the command-line arguments in a seqence of strings. \n\n*environment*\n\nBy default, the environment of the subprocess is inherited from the goyamp process. Additional environment variables for the command can be set with `environment`. If the variable already exists the values overwrite existing ones.\n\n[source, yaml]\n----\nexecute:\n    command: some-script.sh\n    environment:\n        USER: overwrites old USER\n        X: A new variable\n----\n \n*directory*\n\nThe command is run from the directory specified. The default is the users's current directory. Example:\n\n[source, yaml]\n----\nexecute:\n    command: cat\n    directory: \"{{__DIR__}}/../test/fixtures\"\n    args: [ blade-runner.json ]\n    response-type: json\n----\n\n*response-type*\n\nWhen the process runs, output is sent to it's standard output, we'll call that the 'response'. Goyamp reads the response and parses it. `response-type` specifies how goyamp should handle the response from the sub-process. The default is `lines`. The values are:\n\n* `string` - all the response is returned as a single string. Useful for programs like`date`,\n* `lines` - a sequence is returned, containing one item for each line of the response,\n* `json` - the response is expected to be JSON, it is parsed and returned,\n* `yaml` - the response is YAML, the first 'document' in the response is parsed and returned.\n\n*request-type*\n\nBefore the process runs, goyamp serialises the `request` data ready to send on the standard input. We'call this data the 'request'.  `request-type` specifies how goyamp should print the data. The default is `lines`, the options are:\n\n* `string` - the request is serialised as a single string. Useful for programs like 'bash' which can execute a multi-line string. This provides a way to embed scripts in goyamp files.\n* `lines` - a sequence is expected, each item is printed on a seperate line. \n* `json` - the request is converted to JSON,\n* `yaml` - the request is converted to YAML.\n\n\n==== Examples of `execute:`\n===== An empty environment\nTo build an empty environment use the Linux `env -i` command in a subshell. For example:\n\n[source, yaml]\n----\ndefine:\n    some_int_variable1: 2342\n    some_string_variable1: Hello World\n---\nexecute:\n    command: bash\n    args: [ -c , '/usr/bin/env -i - inherit1=$some_int_variable1 inherit2=\"$some_string_variable1\" env' ]\n    response-type: lines\n    environment:\n        some_int_variable1: 2342\n        some_string_variable1: Hello World\n----\n\nProduces\n\n[source, yaml]\n----\n- inherit1=2342\n- inherit2=Hello World\n----\n\n===== Examples of `response-type`s \n_string_\n\n[source, shell]\n----\n$ echo '{execute: {command: date, args: [+%d.%m.%Y], response-type: string}}' | ./goyamp\n---\n23.06.2019\n----\n\n_lines_ here we get a sequence of ip addresses:\n\n[source, YAML]\n----\nexecute:\n    command: bash\n    request-type: string\n    request: nmap -n -sL 192.168.0.0/30 | grep 'Nmap scan report for' | awk '{print $5}'\n    response-type: lines\n----\n\nWhich produces:\n\n[source, YAML]\n----\n- 192.168.0.0\n- 192.168.0.1\n- 192.168.0.2\n- 192.168.0.3\n----\n\n_json_ in this example we extract information about the CPUS on the machine\n\n[source, YAML]\n----\nexecute:\n    command: facter\n    args: [--json, processors]\n    response-type: json\n----\n\nProduces:\n\n[source, YAML]\n----\nprocessors:\n  count: 2\n  models:\n  - Intel(R) Core(TM)2 Duo CPU     P8400  @ 2.26GHz\n  - Intel(R) Core(TM)2 Duo CPU     P8400  @ 2.26GHz\n  physicalcount: 1\n----\n\n_yaml_ example:\n\n[source, YAML]\n----\nexecute:\n    command: cat\n    directory: $fixtures\n    args: [ variety.yaml ]\n    response-type: yaml\n----\n\n===== Examples of `response-type`s \n\n_string_ Here's a multipline Python script to print a list of dates embedded in YAML\n\n[source, YAML]\n----\nexecute:\n    command: python\n    response-type: lines\n    request-type: string\n    request: |\n        from datetime import timedelta, date\n        \n        def daterange(start_date, end_date):\n            for n in range(int ((end_date - start_date).days)):\n                yield start_date + timedelta(n)\n        \n        start_date = date(2019, 1, 1)\n        end_date = date(2019, 1, 5)\n        for single_date in daterange(start_date, end_date):\n            print single_date.strftime(\"%Y-%m-%d\")\n----\n\nProduces:\n\n[source, YAML]\n----\n- \"2019-01-01\"\n- \"2019-01-02\"\n- \"2019-01-03\"\n- \"2019-01-04\"\n----\n\n_lines_ Here we sort a list of hostnames in a sequence, and get that back as a sequence:\n\n[source, YAML]\n----\ndefmacro:\n    name: $sort\n    args: $items\n    value:    \n        execute:\n            command: sort\n            response-type: lines\n            request-type: lines\n            request: $items\n---\n$sort:\n    - ip-12-34-56-78.us-west-2.compute.internal\n    - ec2-12-43-56-78.ap-southeast-2.compute.amazonaws.com\n    - ip-12-34-56-78.us-east-2.compute.internal\n    - ip-12-34-65-99.us-west-2.compute.internal\n    - ec2-12-34-56-78.ap-southeast-2.compute.amazonaws.com\n----\n\nProduces:\n\n[source, YAML]\n----\n- ec2-12-34-56-78.ap-southeast-2.compute.amazonaws.com\n- ec2-12-43-56-78.ap-southeast-2.compute.amazonaws.com\n- ip-12-34-56-78.us-east-2.compute.internal\n- ip-12-34-56-78.us-west-2.compute.internal\n- ip-12-34-65-99.us-west-2.compute.internal\n----\n\n_json_ In this example we use curl to get JSON data from the GitHub API - a set of commit messages. \nThen we send the data as JSON to 'jq' which filters it. \n\n[source, YAML]\n----\nexecute:\n    command: jq\n    args: [\"[.[] | {message: .commit.message, name: .commit.committer.name}]\"]\n    request-type: json\n    response-type: json\n    request:\n        execute:\n            command: curl\n            args: [\"https://api.github.com/repos/birchb1024/goyamp/commits?per_page=3\"]\n            response-type: json\n----\n\nProduces:\n\n[source, YAML]\n----\n- message: Add execute. Change from __PATH__ to __DIR__. Add pwd as __DIR__\n  name: Peter William Birch\n- message: Additions to execute (still in progress)\n  name: Peter William Birch\n- message: Add Stringer() to yamly. Fail on undefined in {{}}\n  name: Bill Birch\n----\n\n=== Executing Lua Scripts with the Embedded Interpreter\n\nYou can make complex manipulations of the YAML data with the Lua 5.1 interpreter embedded in Goyamp. 'Gopher Lua' is written in 100% Go language. You can read about https://github.com/yuin/gopher-lua:[gopherlua here], and https://www.lua.org/manual/5.1/:[Lua 5.1 here].\n\nTo use Lua you invoke the interpreter with the `gopherlua:` key and pass it a YAML structure. The YAML structure is converted into Lua tables and set in the `args` global variable where your script can access it. At the end of execution you pass data back to Goyamp in the `results` Lua global variable. This becomes the value of the gopherlua map which is substituted in the output.\n\nEach time you invoke `gopherlua:`, a new interpreter is created, and destroyed at the end. The Lua initialisation process is:\n\n1. The location of the goyamp binary file is determined and saved to the global variable `executable_directory`\n\n2. The package.path variable is set to the value of environment variable `+__DIR__/?.lua;LUA_PATH+` if `LUA_PATH` is present, \notherwise the package.path variable is set to the default `+__DIR__/?.lua;./?.lua;./?.lc;\u003cexecutable_directory\u003e/lib/?.lua;\u003cexecutable_directory\u003e/lib/?.lc+`. This means Lua will pick up files in `require()` calls from the `lib/` directory wherever goyamp is installed. It will also pick up scripts relative to the current YAML file.    \n\n3. The Lua interpreter attempts to require `init.lua` from the package.path. If it isn't present there is no error or warning message unless you run with `-d`. \n\n4. Then the global variable `args` is set to the value of the YAML args: element. The string in script: is executed, and the value of `result` is returned to Goyamp.\n\nGoyamp uses these global variables inside the Lua interpreter:\n\n* `+__DIR__+` - Directory containing the current enclosing YAML script\n* `args` - Holds the input args: argument\n* `executable_directory` - Directory holding the gymap binary, useful for path manipulations,\n* `result` - where the result of the Lua execution is placed for return to Goyamp\n* `seqy` - the metatable attached to YAML sequence (list) tables\n* `mapy` - the metatable for YAML map tables\n* `nily` - variable contains the userdata object used for YAML null values\n\nThe gopherlua: syntax is as follows:\n\n[source, YAML]\n----\ngopherlua:\n  args: # this is where you pass a YAML structure to Lua\n  script: # This a Lua script which is executed.\n----\n\nHere are some examples:\n\nTo return an uppercase version of a string we use the Lua string.upper() function.\n\n[source, YAML]\n----\ngopherlua:\n  args: we are groot\n  script: \"result = string.upper(args)\"\n----\n\nTo sort a list we can use the Lua table.sort() function.\n\n[source, YAML]\n----\ngopherlua:\n  args: [X,K,A]\n  script: \"table.sort(args); result = args\"\n----\n\nHere is a more complex example.  We want to turn all the elements in a YAML structure\nto uppercase. Granted this could be done with shell tools, but this example shows are recursive\ntree walk function. YAML allows multi-line strings which are convenient for medium length\nscripts. Longer scripts can be put into source files and loaded by Lua with `require()`.\n\n[source, YAML]\n----\ngopherlua:\n    args: \n      a:3 : 22\n      str: \"a lower case string\"\n      arr: { x: , y: 2 }\n      list: [1,2,3]\n    script: |-\n\n      -- Uppercase all strings in a YAML tree\n      function uppertree(t)\n          local tt = type(t)\n          if tt == \"string\" then\n            return string.upper(t)\n          elseif tt == \"table\" then\n            local k, v = next(t, nil)\n            local result = {}\n            while k do\n              if type(k) == \"string\" then\n                result[string.upper(k)] = uppertree(v)\n              else\n                result[k] = uppertree(v)\n              end\n                k, v = next(t, k)\n            end\n            return result\n          else\n            return t\n          end\n      end\n      result = uppertree(args)\n----\n\nThis example shows how to load a standalone Lua file using `require()`. Having Lua code in separate files is handy since your favourite editor will give you syntax highlighting and formatting. You can also run your Lua scripts 'offline' with the gopher-lua standalone executable, `glua` which can be gotten from https://github.com/yuin/gopher-lua#standalone-interpreter:[here].\n\nIn this example we have stashed the script in a YAML variable, `$deepmerge`. This allows us to use it in many different `gopherlua:` calls. The file `deepmerge.lua` is in the goyamp release in the lib/ directory.\n\n[source, YAML]\n----\ndefine:\n  $deepmerge: |-\n    dm = require('deepmerge')\n    result = dm.deep_merge(args[1], args[2])\n---\ngopherlua:\n  script: $deepmerge\n  args: etc, etc\n----\n\nIn Lua you can add more to the path with `package.path = package.path ... \";my/directory/?.lua\"`.\n\n==== Lua Conversion\n\nLua tables use `nil` to signal absence of an entry rather than holding the value `nil`. To work around this, Goyamp converts YAML `null` to a `userdata` object which is stored in the global Lua variable `nily`.\n\nLua does not have a separate data types for arrays and maps, it uses the `table` type for both of these. Hence issues arise when working with YAML data which _does_ differentiate. This is handled by the custom metatables `mapy` and `seqy`. When collections are transfered to Gopher Lua their metatables are set to either `mapy` or `seqy`. Likewise when a Lua table is ambiguous in a result you can clarify this with `setmetatable`. For example `setmetatable(x, mapy)` ensures that Goymap sees this result item as a map.\n\n\n=== Quitting Early with `exit`\n\nSometimes you will want the script simply stop processing.  The `exit:` builtin halts execution by calling the operating system exit() function. You can provide the status for the process as an argument. If the argument is empty, null or 0 or the string `\"0\"`, the process status is zero. If an integer or a string containing an integer is provided this becomes the status of the process termination.    \n\nExample:\n\n[source, YAML]\n----\nif:\n  ==: [p1, p2]\nelse:\n  exit: 3\n----\n\nThis quietly exits with code 3. \n\n=== Enforcing Safety with `panic`\n\nWhen processing becomes more complex you may want to implement checks on input data. The `panic:` macro \nhalts execution and prints a message supplied. With this combined with ==: you can code a variety\nof check macros. For example here is a macro that ends processing if two things do not match:  \n\n[source, YAML]\n----\ndefmacro:\n    name: assert_equal\n    args: [p1, p2]\n    value:\n      if:\n        ==: [p1, p2]\n      else:\n        panic: \"ASSERT FAILED {{p1}} != {{p2}} {{__SOURCE__}}\"\n---\nassert_equal:\n    p1: 12\n    p2: 23\n----\n\nProduces this on stderr: \n\n[source, shell]\n----\npanic: ASSERT FAILED 12 != 23 { assert_equal : { p1 : 12 , p2 : 23  }  }\n----\n\nWith the `-d` command-line option, a backtrace is also printed.\n\n=== Builtin Variables\n\nGoyamp automatically populates some variables as it executes. These are:\n\n* `env` - the process environment\n\n* `argv` - the command line arguments\n\n* `+__VERSION__+` - the Goyamp version number\n\n* `+__FILE__+` - the current source filename\n\n* `+__DIR__+` - the directory pathname of the current source file\n\n* `+__SOURCE__+` - the expression passed into the currently executing macro - useful for debugging your macros.\n\n== Using the Goyamp Go Module\n\n\nThe goymap Go module can be used as a component to other programs. The 'main' of goyamp itself uses the modules API and can be used as an example. Here is a simplified version:\n\n[source, Go]\n----\n\n    // Import the module\n    import (\n    \t\"github.com/birchb1024/goyamp\"\n    )\n\n    // Create an instance of the macro-processor engine\n    // providing a list of command arguments, an environment, an output writer and an output format flag. \n    \n    engine := goyamp.NewExpander(commandArgs, os.Environ(), os.Stdout, outFormat)\n\n    // either process a stream, giving a Reader\n    err := engine.ExpandStream(os.Stdin, \"-\") \n    if err != nil {\n            panic(err)\n    }\n\n    // or process a file\n    err := engine.ExpandFile(\"examples/macros.yaml\")\n    if err != nil {\n            panic(err)\n    }\n\n----\n== Maintenance of Goyamp\n\n=== Build from Source\n\nSource code is in GitHub https://github.com/:birchb1024/goyamp:[here].\n\nFirst install dependencies (Ubuntu)\n\n[source, Shell]\n----\n\n$ sudo apt install asciidoctor\n$ sudo apt install source-highlight\n: Install the source-highlighter for YAML - Following these instructions https://gist.github.com/AlexZeitler/48813447f253360ccc431ae22d6939fd\n\n$ sudo -H bash\n$ curl https://gist.githubusercontent.com/AlexZeitler/48813447f253360ccc431ae22d6939fd/raw/1c1d9372cce5fb2b568b2dd953d334ef8fe3f33d/yaml.lang \u003e /usr/share/source-highlight/yaml.lang\n$ for X in yml yaml\ndo\n  echo \"$X = yaml.lang\" \u003e\u003e /usr/share/source-highlight/lang.map\ndone\n----\n\nBuild\n\n[source, Shell]\n----\n$ git clone https://github.com/birchb1024/goyamp\n$ cd goyamp\n$ build.sh                        # For executables\n\n$ build.sh coverage               # Test coverage detailed report\n\n$ build.sh doc                    # For Asciidoc to HTML\n\n$ build.sh package                # To make a releasable tar file with document, examples and executables.\n----\n\n=== Code\n\nRun the unit tests with `cd test; go test` \n\n=== Updating This Document\n\nThis document is in http://www.methods.co.nz/asciidoc/:[AsciiDoc] format. Use the Linux `asciidoc` packages. To Highlight the YAML syntax also install `source-highlight` and the https://gist.github.com/AlexZeitler/48813447f253360ccc431ae22d6939fd[YAML syntax module]. Save the HTML version in `doc/README.html`.\n\n=== Known Issues\n\nSee the `Issues` in the https://github.com/birchb1024/goyamp:[Goyamp GitHub project]\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbirchb1024%2Fgoyamp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbirchb1024%2Fgoyamp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbirchb1024%2Fgoyamp/lists"}