From 96a80b98a9d2b052f509d33637ad770aa4984871 Mon Sep 17 00:00:00 2001 From: Laurence Morgan Date: Tue, 16 Mar 2021 21:41:41 +0000 Subject: [PATCH] #286 Fix panic caused by bad envvar parsing + `export` autocompletion shouldn't be envvars --- builtins/core/runtime/runtime.go | 31 +-- builtins/core/typemgmt/variables.go | 4 +- builtins/core/typemgmt/variables_doc.yaml | 23 ++ builtins/docs/export_commands_docgen.go | 2 +- config/app.go | 2 +- config/defaults/auto_generated_all.go | 2 +- config/defaults/profile_all.mx | 34 ++- docs/commands/export.md | 21 ++ utils/envvars/envvars.go | 39 +++ utils/envvars/envvars_test.go | 277 ++++++++++++++++++++++ utils/envvars/godoc.go | 2 + 11 files changed, 385 insertions(+), 52 deletions(-) create mode 100644 utils/envvars/envvars.go create mode 100644 utils/envvars/envvars_test.go create mode 100644 utils/envvars/godoc.go diff --git a/builtins/core/runtime/runtime.go b/builtins/core/runtime/runtime.go index 04e3b14f6..b7573e919 100644 --- a/builtins/core/runtime/runtime.go +++ b/builtins/core/runtime/runtime.go @@ -2,9 +2,6 @@ package cmdruntime import ( "errors" - "fmt" - "os" - "regexp" "runtime" "sort" @@ -20,6 +17,7 @@ import ( "github.com/lmorg/murex/lang/types" "github.com/lmorg/murex/shell" "github.com/lmorg/murex/shell/autocomplete" + "github.com/lmorg/murex/utils/envvars" "github.com/lmorg/murex/utils/json" ) @@ -133,7 +131,7 @@ func cmdRuntime(p *lang.Process) error { case fGlobals: ret[fGlobals[2:]] = lang.GlobalVariables.Dump() case fExports: - m, err := dumpExports() + m, err := envvars.All() if err != nil { return err } @@ -229,28 +227,3 @@ func dumpTestResults(p *lang.Process) interface{} { "process": p.Tests.Results.Dump(), } } - -var rxMatchEnvs = regexp.MustCompile(`^(.*?)=(.*)$`) - -func dumpExports() (map[string]string, error) { - envs := os.Environ() - - m := make(map[string]string) - - for _, e := range envs { - split := rxMatchEnvs.FindAllStringSubmatch(e, -1) - - if len(split) != 1 || len(split[0]) != 3 { - // this should never happen! - b, err := json.Marshal(split, false) - if err != nil { - b = []byte(fmt.Sprint("!!Unable to marshal `", split, "`!!")) - } - return nil, fmt.Errorf("Unexpected result using regexp to split env string; This should never happen so please log an issue on https://github.com/lmorg/murex/issues/new with this message and the output of `env`. Debug info: len(split)==%d (expected 1), len(split[0])==%d (expected 3), split==%s", len(split), len(split[0]), string(b)) - } - - m[split[0][1]] = split[0][2] - } - - return m, nil -} diff --git a/builtins/core/typemgmt/variables.go b/builtins/core/typemgmt/variables.go index 171466894..633f49494 100644 --- a/builtins/core/typemgmt/variables.go +++ b/builtins/core/typemgmt/variables.go @@ -128,7 +128,9 @@ func cmdExport(p *lang.Process) error { // Set env as parameters: if rxVarName.MatchString(params) { - return os.Setenv(params, "") + v := p.Variables.GetString(params) + + return os.Setenv(params, v) } match := rxSet.FindAllStringSubmatch(params, -1) diff --git a/builtins/core/typemgmt/variables_doc.yaml b/builtins/core/typemgmt/variables_doc.yaml index 0992f6f55..1c6bffc52 100644 --- a/builtins/core/typemgmt/variables_doc.yaml +++ b/builtins/core/typemgmt/variables_doc.yaml @@ -174,6 +174,29 @@ not an idiomatic method of deallocation since it's name is misleading and suggests it is a deallocator for local _murex_ variables defined via `set`. + ### Exporting a local or global variable + + You can also export a local or global variable of the same name by specifying + that variable name without a following value. For example + + ``` + # Create a local variable called 'foo': + » set: foo=bar + » env -> grep: foo + + # Export that local variable as an environmental variable: + » export: foo + » env -> grep: foo + foo=bar + + # Changing the value of the local variable doesn't alter the value of the environmental variable: + » set: foo=rab + » env -> grep: foo + foo=bar + » out: $foo + rab + ``` + {{ include "gen/includes/variables.inc.md" }} Synonyms: - export diff --git a/builtins/docs/export_commands_docgen.go b/builtins/docs/export_commands_docgen.go index 633f571e7..f45b8be25 100644 --- a/builtins/docs/export_commands_docgen.go +++ b/builtins/docs/export_commands_docgen.go @@ -2,6 +2,6 @@ package docs func init() { - Definition["export"] = "# _murex_ Shell Docs\n\n## Command Reference: `export`\n\n> Define a local variable and set it's value\n\n## Description\n\nDefines, updates or deallocates an environmental variable.\n\n## Usage\n\n -> export var_name\n \n export var_name=data\n\n## Examples\n\nAs a method:\n\n » out \"Hello, world!\" -> export hw\n » out \"$hw\"\n Hello, World!\n \nAs a function:\n\n » export hw=\"Hello, world!\"\n » out \"$hw\"\n Hello, World!\n\n## Detail\n\n### Deallocation\n\nYou can unset variable names with the bang prefix:\n\n !export var_name\n \nFor compatibility with other shells, `unset` is also supported but it's really\nnot an idiomatic method of deallocation since it's name is misleading and\nsuggests it is a deallocator for local _murex_ variables defined via `set`.\n\n### Scoping\n\nVariable scoping is simplified to three layers:\n\n1. Local variables (`set`, `!set`, `let`)\n2. Global variables (`global`, `!global`)\n3. Environmental variables (`export`, `!export`, `unset`)\n\nVariables are looked up in that order of too. For example a the following\ncode where `set` overrides both the global and environmental variable:\n\n » set: foobar=1\n » global: foobar=2\n » export: foobar=3\n » out: $foobar\n 1\n \n#### Local variables\n\nThese are defined via `set` and `let`. They're variables that are persistent\nacross any blocks within a function. Functions will typically be blocks\nencapsulated like so:\n\n function example {\n # variables scoped inside here\n }\n \n...or...\n\n private example {\n # variables scoped inside here\n }\n \n \n...however dynamic autocompletes, events, unit tests and any blocks defined in\n`config` will also be triggered as functions.\n\nCode running inside any control flow or error handing structures will be\ntreated as part of the same part of the same scope as the parent function:\n\n » function example {\n » try {\n » # set 'foobar' inside a `try` block\n » set: foobar=example\n » }\n » # 'foobar' exists outside of `try` because it is scoped to `function`\n » out: $foobar\n » }\n example\n \nWhere this behavior might catch you out is with iteration blocks which create\nvariables, eg `for`, `foreach` and `formap`. Any variables created inside them\nare still shared with any code outside of those structures but still inside the\nfunction block.\n\nAny local variables are only available to that function. If a variable is\ndefined in a parent function that goes on to call child functions, then those\nlocal variables are not inherited but the child functions:\n\n » function parent {\n » # set a local variable\n » set: foobar=example\n » child\n » }\n » \n » function child {\n » # returns the `global` value, \"not set\", because the local `set` isn't inherited\n » out: $foobar\n » }\n » \n » global: $foobar=\"not set\"\n » parent\n not set\n \nIt's also worth remembering that any variable defined using `set` in the shells\nFID (ie in the interactive shell) is localised to structures running in the\ninteractive, REPL, shell and are not inherited by any called functions.\n\n#### Global variables\n\nWhere `global` differs from `set` is that the variables defined with `global`\nwill be scoped at the global shell level (please note this is not the same as\nenvironmental variables!) so will cascade down through all scoped code-blocks\nincluding those running in other threads.\n\n#### Environmental variables\n\nExported variables (defined via `export`) are system environmental variables.\nInside _murex_ environmental variables behave much like `global` variables\nhowever their real purpose is passing data to external processes. For example\n`env` is an external process on Linux (eg `/usr/bin/env` on ArchLinux):\n\n » export foo=bar\n » env -> grep foo\n foo=bar\n \n### Function Names\n\nAs a security feature function names cannot include variables. This is done to\nreduce the risk of code executing by mistake due to executables being hidden\nbehind variable names.\n\nInstead _murex_ will assume you want the output of the variable printed:\n\n » out \"Hello, world!\" -> set hw\n » $hw\n Hello, world!\n \nOn the rare occasions you want to force variables to be expanded inside a\nfunction name, then call that function via `exec`:\n\n » set cmd=grep\n » ls -> exec: $cmd main.go\n main.go\n \nThis only works for external executables. There is currently no way to call\naliases, functions nor builtins from a variable and even the above `exec` trick\nis considered bad form because it reduces the readability of your shell scripts.\n\n### Usage Inside Quotation Marks\n\nLike with Bash, Perl and PHP: _murex_ will expand the variable when it is used\ninside a double quotes but will escape the variable name when used inside single\nquotes:\n\n » out \"$foo\"\n bar\n \n » out '$foo'\n $foo\n \n » out ($foo)\n bar\n\n## Synonyms\n\n* `export`\n* `!export`\n* `unset`\n\n\n## See Also\n\n* [commands/`(` (brace quote)](../commands/brace-quote.md):\n Write a string to the STDOUT without new line\n* [commands/`=` (arithmetic evaluation)](../commands/equ.md):\n Evaluate a mathematical function\n* [commands/`global`](../commands/global.md):\n Define a global variable and set it's value\n* [commands/`let`](../commands/let.md):\n Evaluate a mathematical function and assign to variable\n* [commands/`set`](../commands/set.md):\n Define a local variable and set it's value" + Definition["export"] = "# _murex_ Shell Docs\n\n## Command Reference: `export`\n\n> Define a local variable and set it's value\n\n## Description\n\nDefines, updates or deallocates an environmental variable.\n\n## Usage\n\n -> export var_name\n \n export var_name=data\n\n## Examples\n\nAs a method:\n\n » out \"Hello, world!\" -> export hw\n » out \"$hw\"\n Hello, World!\n \nAs a function:\n\n » export hw=\"Hello, world!\"\n » out \"$hw\"\n Hello, World!\n\n## Detail\n\n### Deallocation\n\nYou can unset variable names with the bang prefix:\n\n !export var_name\n \nFor compatibility with other shells, `unset` is also supported but it's really\nnot an idiomatic method of deallocation since it's name is misleading and\nsuggests it is a deallocator for local _murex_ variables defined via `set`.\n\n### Exporting a local or global variable\n\nYou can also export a local or global variable of the same name by specifying\nthat variable name without a following value. For example\n\n # Create a local variable called 'foo':\n » set: foo=bar\n » env -> grep: foo\n \n # Export that local variable as an environmental variable:\n » export: foo\n » env -> grep: foo\n foo=bar\n \n # Changing the value of the local variable doesn't alter the value of the environmental variable:\n » set: foo=rab\n » env -> grep: foo\n foo=bar\n » out: $foo\n rab\n \n### Scoping\n\nVariable scoping is simplified to three layers:\n\n1. Local variables (`set`, `!set`, `let`)\n2. Global variables (`global`, `!global`)\n3. Environmental variables (`export`, `!export`, `unset`)\n\nVariables are looked up in that order of too. For example a the following\ncode where `set` overrides both the global and environmental variable:\n\n » set: foobar=1\n » global: foobar=2\n » export: foobar=3\n » out: $foobar\n 1\n \n#### Local variables\n\nThese are defined via `set` and `let`. They're variables that are persistent\nacross any blocks within a function. Functions will typically be blocks\nencapsulated like so:\n\n function example {\n # variables scoped inside here\n }\n \n...or...\n\n private example {\n # variables scoped inside here\n }\n \n \n...however dynamic autocompletes, events, unit tests and any blocks defined in\n`config` will also be triggered as functions.\n\nCode running inside any control flow or error handing structures will be\ntreated as part of the same part of the same scope as the parent function:\n\n » function example {\n » try {\n » # set 'foobar' inside a `try` block\n » set: foobar=example\n » }\n » # 'foobar' exists outside of `try` because it is scoped to `function`\n » out: $foobar\n » }\n example\n \nWhere this behavior might catch you out is with iteration blocks which create\nvariables, eg `for`, `foreach` and `formap`. Any variables created inside them\nare still shared with any code outside of those structures but still inside the\nfunction block.\n\nAny local variables are only available to that function. If a variable is\ndefined in a parent function that goes on to call child functions, then those\nlocal variables are not inherited but the child functions:\n\n » function parent {\n » # set a local variable\n » set: foobar=example\n » child\n » }\n » \n » function child {\n » # returns the `global` value, \"not set\", because the local `set` isn't inherited\n » out: $foobar\n » }\n » \n » global: $foobar=\"not set\"\n » parent\n not set\n \nIt's also worth remembering that any variable defined using `set` in the shells\nFID (ie in the interactive shell) is localised to structures running in the\ninteractive, REPL, shell and are not inherited by any called functions.\n\n#### Global variables\n\nWhere `global` differs from `set` is that the variables defined with `global`\nwill be scoped at the global shell level (please note this is not the same as\nenvironmental variables!) so will cascade down through all scoped code-blocks\nincluding those running in other threads.\n\n#### Environmental variables\n\nExported variables (defined via `export`) are system environmental variables.\nInside _murex_ environmental variables behave much like `global` variables\nhowever their real purpose is passing data to external processes. For example\n`env` is an external process on Linux (eg `/usr/bin/env` on ArchLinux):\n\n » export foo=bar\n » env -> grep foo\n foo=bar\n \n### Function Names\n\nAs a security feature function names cannot include variables. This is done to\nreduce the risk of code executing by mistake due to executables being hidden\nbehind variable names.\n\nInstead _murex_ will assume you want the output of the variable printed:\n\n » out \"Hello, world!\" -> set hw\n » $hw\n Hello, world!\n \nOn the rare occasions you want to force variables to be expanded inside a\nfunction name, then call that function via `exec`:\n\n » set cmd=grep\n » ls -> exec: $cmd main.go\n main.go\n \nThis only works for external executables. There is currently no way to call\naliases, functions nor builtins from a variable and even the above `exec` trick\nis considered bad form because it reduces the readability of your shell scripts.\n\n### Usage Inside Quotation Marks\n\nLike with Bash, Perl and PHP: _murex_ will expand the variable when it is used\ninside a double quotes but will escape the variable name when used inside single\nquotes:\n\n » out \"$foo\"\n bar\n \n » out '$foo'\n $foo\n \n » out ($foo)\n bar\n\n## Synonyms\n\n* `export`\n* `!export`\n* `unset`\n\n\n## See Also\n\n* [commands/`(` (brace quote)](../commands/brace-quote.md):\n Write a string to the STDOUT without new line\n* [commands/`=` (arithmetic evaluation)](../commands/equ.md):\n Evaluate a mathematical function\n* [commands/`global`](../commands/global.md):\n Define a global variable and set it's value\n* [commands/`let`](../commands/let.md):\n Evaluate a mathematical function and assign to variable\n* [commands/`set`](../commands/set.md):\n Define a local variable and set it's value" } diff --git a/config/app.go b/config/app.go index 35c32c525..16088639a 100644 --- a/config/app.go +++ b/config/app.go @@ -5,4 +5,4 @@ const AppName = "murex" // Version number of $SHELL // Format of version string should be "(major).(minor).(revision) DESCRIPTION" -const Version = "1.6.5100 BETA" +const Version = "1.7.0000 BETA" diff --git a/config/defaults/auto_generated_all.go b/config/defaults/auto_generated_all.go index 860b3149d..6176138f9 100644 --- a/config/defaults/auto_generated_all.go +++ b/config/defaults/auto_generated_all.go @@ -13,5 +13,5 @@ package defaults */ func init() { - murexProfile = append(murexProfile, "function h {\n # Output the murex history log in a human readable format\n history -> foreach { -> set json line; out \"$line[Index]: $line[Block]\" } -> cast *\n}\n\nfunction aliases {\n\t# Output the aliases in human readable format\n\truntime: --aliases -> formap name alias {\n $name -> sprintf: \"%10s => ${esccli @alias}\\n\"\n\t} -> cast: str\n}\n\ntest unit function aliases {\n \"PreBlock\": ({\n alias ALIAS_UNIT_TEST=example param1 param2 param3\n }),\n \"StdoutRegex\": \"([- _0-9a-zA-Z]+ => .*?\\n)+\",\n \"StdoutType\": \"str\",\n \"PostBlock\": ({\n !alias ALIAS_UNIT_TEST\n })\n}\n\nautocomplete: set cd { [{\n \"IncDirs\": true\n}] }\n\nautocomplete: set mkdir { [{\n \"IncDirs\": true,\n \"AllowMultiple\": true\n}] }\n\nautocomplete: set rmdir { [{\n \"IncDirs\": true,\n \"AllowMultiple\": true\n}] }\n\nautocomplete: set exec { [\n {\n \"IncFiles\": true,\n \"IncDirs\": true,\n \"IncExePath\": true\n },\n {\n \"NestedCommand\": true\n }\n] }\n\nautocomplete: set format { [{\n \"Dynamic\": ({ runtime: --marshallers })\n}] }\n\nautocomplete: set swivel-datatype { [{\n \"Dynamic\": ({ runtime: --marshallers })\n}] }\n\nprivate autocomplete.data-types {\n # Returns all murex data-types compiled\n runtime: --readarray -> format: str\n runtime: --writearray -> format: str\n runtime: --readmap -> format: str\n runtime: --marshallers -> format: str\n runtime: --unmarshallers -> format: str\n}\n\ntest unit private autocomplete.data-types {\n \"StdoutRegex\": (^(([a-z0-9]+|\\*)\\n)+),\n \"StdoutType\": \"str\",\n \"StdoutBlock\": ({\n -> len -> set len;\n if { = len>0 } then {\n out \"Len greater than 0\"\n } else {\n err \"No elements returned\"\n }\n }),\n \"StdoutIsArray\": true\n}\n\nautocomplete: set cast { [{\n \"Dynamic\": ({ autocomplete.data-types })\n}] }\n\nautocomplete: set tout { [{\n \"Dynamic\": ({ autocomplete.data-types })\n}] }\n\nautocomplete: set set { [{\n \"Dynamic\": ({ autocomplete.data-types })\n}] }\n\nautocomplete: set export { [{\n \"DynamicDesc\": ({ runtime --exports })\n}] }\n\nautocomplete: set !export { [{\n \"DynamicDesc\": ({ runtime --exports })\n}] }\n\nautocomplete: set unset { [{\n \"DynamicDesc\": ({ runtime --exports })\n}] }\n\nprivate autocomplete.variables {\n # Returns all global and local variable names\n runtime: --globals -> formap k v { out $k } -> cast: str\n runtime: --variables -> formap k v { out $k } -> cast: str\n}\n\ntest unit private autocomplete.variables {\n \"PreBlock\": ({ global MUREX_UNIT_TEST=foobar }),\n \"PostBlock\": ({ !global MUREX_UNIT_TEST }),\n \"StdoutRegex\": (^([_a-zA-Z0-9]+\\n)+),\n \"StdoutType\": \"str\",\n \"StdoutBlock\": ({\n -> len -> set len;\n if { = len>0 } then {\n out \"Len greater than 0\"\n } else {\n err \"No elements returned\"\n }\n }),\n \"StdoutIsArray\": true\n}\n\nautocomplete: set global { [{\n \"Dynamic\": ({ autocomplete.variables })\n}] }\n\nautocomplete: set !global { [{\n \"Dynamic\": ({ autocomplete.variables })\n}] }\n\nautocomplete: set set { [{\n \"Dynamic\": ({ autocomplete.variables })\n}] }\n\nautocomplete: set !set { [{\n \"Dynamic\": ({ autocomplete.variables })\n}] }\n\nautocomplete: set \"[\" { [{\n \"AnyValue\": true,\n \"AllowMultiple\": true,\n \"ExecCmdline\": true,\n \"Dynamic\": ({\n switch ${ get-type: stdin } {\n case * {\n # -> tabulate -> [ 0: ] -> format json -> [ 0 ]\n -> [ 0: ] -> format json -> [ 0 ] -> append \"]\"\n }\n\n case csv {\n -> [ 0: ] -> format json -> [ 0 ] -> append \"]\"\n }\n \n case jsonl {\n -> [ 0 ] -> set header\n $header -> cast utf8 -> [ 0 -1 ] -> set jsonl_format\n if { = jsonl_format==`[]` } then {\n tout json $header -> append \"]\"\n }\n }\n\n catch {\n -> formap k v { out $k } -> cast str -> append \"]\"\n }\n }\n })\n}] }\n\nautocomplete: set \"[[\" { [{\n \"AnyValue\": true,\n \"ExecCmdline\": true,\n \"AutoBranch\": true,\n \"Dynamic\": ({ -> struct-keys -> append \"]]\" })\n} ]}\n\nprivate autocomplete.config.get.apps {\n # Returns all app names for the 'app' field in `config`\n config: -> formap k v { out $k } -> cast: str -> msort\n}\n\ntest unit private autocomplete.config.get.apps {\n \"StdoutRegex\": (shell),\n \"StdoutType\": \"str\",\n \"StdoutBlock\": ({\n -> len -> set len;\n if { = len>0 } then {\n out \"Len greater than 0\"\n } else {\n err \"No elements returned\"\n }\n }),\n \"StdoutIsArray\": true\n}\n\nprivate autocomplete.config.get.keys {\n # Returns all keys for the 'app' field in `config`\n config -> [ $ARGS[1] ] -> formap k v { out $k } -> cast: str -> msort\n}\n\ntest unit private autocomplete.config.get.keys {\n \"Parameters\": [ \"shell\" ],\n \"StdoutRegex\": (prompt),\n \"StdoutType\": \"str\",\n \"StdoutBlock\": ({\n -> len -> set len;\n if { = len>0 } then {\n out \"Len greater than 0\"\n } else {\n err \"No elements returned\"\n }\n }),\n \"StdoutIsArray\": true\n}\n\nautocomplete: set config { [{\n \"Flags\": [ \"get\", \"set\", \"eval\", \"define\", \"default\" ],\n \"FlagValues\": {\n \"get\": [\n { \"Dynamic\": ({ autocomplete.config.get.apps }) },\n { \"Dynamic\": ({ autocomplete.config.get.keys $ARGS[2] }) }\n ], \n \"set\": [\n { \"Dynamic\": ({ autocomplete.config.get.apps }) },\n { \"Dynamic\": ({ autocomplete.config.get.keys $ARGS[2] }) },\n { \"Dynamic\": ({\n\t\t\t\tswitch {\n\t\t\t\t\tcase { = `${ config -> [ $ARGS[2] ] -> [ $ARGS[3] ] -> [ Data-Type ]}`==`bool` } {\n\t\t\t\t\t\tja [true,false]\n\t\t\t\t\t}\n\n\t\t\t\t\tcase { config -> [ $ARGS[2] ] -> [ $ARGS[3] ] -> [ Options ] } {\n\t\t\t\t\t\tconfig -> [ $ARGS[2] ] -> [ $ARGS[3] ] -> [ Options ]\n\t\t\t\t\t}\n\t\t\t\t\t\n \tcatch {\n\t\t\t\t\t\tout ${ config -> [ $ARGS[2] ] -> [ $ARGS[3] ] -> [ Default ]}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}) }\n ],\n \"eval\": [\n { \"Dynamic\": ({ autocomplete.config.get.apps }) },\n { \"Dynamic\": ({ autocomplete.config.get.keys $ARGS[2] }) }\n ],\n \"default\": [\n { \"Dynamic\": ({ autocomplete.config.get.apps }) },\n { \"Dynamic\": ({ autocomplete.config.get.keys $ARGS[2] }) }\n ]\n }\n}] }\n\nautocomplete: set !config { [\n { \"Dynamic\": ({ autocomplete.config.get.apps }) },\n { \"Dynamic\": ({ autocomplete.config.get.keys $ARGS[1] }) }\n] }\n\n\nautocomplete: set event { [\n {\n \"Dynamic\": ({ runtime: --events -> formap k v { out $k } })\n }\n] }\n\nautocomplete: set !event { [\n {\n \"Dynamic\": ({ runtime: --events -> formap k v { out $k } -> msort })\n },\n {\n \"Dynamic\": ({ runtime: --events -> [ $ARGS[1] ] -> formap k v { out $k } -> msort })\n }\n] }\n\nprivate autocomplete.alias {\n # Returns a map of all alises and the code they execute\n runtime: --aliases -> formap --jmap name value { $name } { out @value }\n}\n\ntest unit private autocomplete.alias {\n \"StdoutRegex\": (jobs),\n\t\"StdoutType\": \"json\",\n \"StdoutBlock\": ({\n -> len -> set len;\n if { = len>0 } then {\n out \"Len greater than 0\"\n } else {\n err \"No elements returned\"\n }\n }),\n \"StdoutIsMap\": true\n}\n\nautocomplete: set !alias { [{\n \"DynamicDesc\": ({ autocomplete.alias }),\n \"ListView\": true\n}] }\n\nprivate autocomplete.functions {\n # Returns a map of all murex public functions\n runtime: --functions -> formap --jmap k v { $k } { out: $v[summary] }\n}\n\ntest unit private autocomplete.functions {\n \"PreBlock\": ({\n function unit.test.autocomplete.functions {\n out \"This is only a dummy function for testing\"\n }\n }),\n \"PostBlock\": ({\n !function unit.test.autocomplete.functions\n }),\n \"StdoutRegex\": (unit.test.autocomplete.functions),\n\t\"StdoutType\": \"json\",\n \"StdoutBlock\": ({\n -> len -> set len;\n if { = len>0 } then {\n out \"Len greater than 0\"\n } else {\n err \"No elements returned\"\n }\n }),\n \"StdoutIsMap\": true\n}\n\nautocomplete: set !function { [{\n \"DynamicDesc\": ({ autocomplete.functions }),\n \"ListView\": true\n} ]}\n\nprivate autocomplete.privates {\n # Returns a map of all murex private functions\n runtime: --privates -> struct-keys: 3 -> regexp m,/.*?/.*?/, -> foreach --jmap private { $private } { runtime: --privates -> [[ $private/Summary ]] }\n}\n\ntest unit private autocomplete.privates {\n\t\"StdoutType\": \"json\",\n \"StdoutBlock\": ({\n -> len -> set len;\n if { = len>0 } then {\n out \"Len greater than 0\"\n } else {\n err \"No elements returned\"\n }\n }),\n \"StdoutIsMap\": true\n}\n\nprivate autocomplete.builtins {\n # Returns a map of all murex builtins\n runtime --builtins -> foreach --jmap builtin { $builtin } { murex-docs --summary $builtin }\n}\n\ntest unit private autocomplete.builtins {\n\t\"StdoutType\": \"json\",\n \"StdoutBlock\": ({\n -> len -> set len;\n if { = len>0 } then {\n out \"Len greater than 0\"\n } else {\n err \"No elements returned\"\n }\n }),\n \"StdoutIsMap\": true\n}\n\nautocomplete: set autocomplete { [{\n \"Flags\": [ \"get\", \"set\" ],\n \"FlagValues\": {\n \"get\": [{\n \"Dynamic\": ({\n runtime: --autocomplete -> formap: cmd ! { out $cmd } -> cast: str\n })\n }]\n }\n}] }\n\nprivate git-branch {\n # Returns a list of branches excluding currect checked-out branch in the current git repository\n git branch -> [ :0 ] -> !match *\n}\n\nautocomplete: set git { [{\n #\"Flags\": [ \"clone\", \"init\", \"add\", \"mv\", \"reset\", \"rm\", \"bisect\", \"grep\", \"log\", \"show\", \"status\", \"branch\", \"checkout\", \"commit\", \"diff\", \"merge\", \"rebase\", \"tag\", \"fetch\", \"pull\", \"push\", \"stash\" ],\n \"DynamicDesc\": ({\n git: help -a -> @[..^Ancillary]re -> tabulate: --map\n }),\n \"ListView\": true,\n \"FlagValues\": {\n \"init\": [{\n \"Flags\": [\"--bare\"]\n }],\n \"add\": [{\n #\"IncFiles\": true,\n \"AllowMultiple\": true,\n \"Dynamic\": ({\n git status -s -> regexp 'f/^.[^\\s] [\"]?(.*?)[\"]?$/' -> cast str\n })\n }],\n \"diff\": [{\n #\"IncFiles\": true,\n \"AllowMultiple\": true,\n \"Dynamic\": ({\n git status -s -> [:1]\n })\n }],\n \"mv\": [{ \n \"IncFiles\": true\n }],\n \"rm\": [{\n \"IncFiles\": true,\n \"AllowMultiple\": true\n }],\n \"checkout\": [{\n \"Dynamic\": ({ git-branch }),\n \"Flags\": [ \"-b\" ]\n }],\n \"merge\": [{\n \"Dynamic\": ({ git-branch })\n }],\n \"commit\": [{\n \"Flags\": [\"-a\", \"-m\", \"--amend\"],\n \"FlagValues\": {\n \"--amend\": [{ \"AnyValue\": true }]\n },\n \"AllowMultiple\": true\n }]\n }\n}] }\n\nautocomplete: set docker { [\n {\n \"DynamicDesc\": ({\n docker help -> @[^Usage:..]re -> tabulate: --split-comma --map\n }),\n\n #\"AllowMultiple\": true,\n #\"AnyValue\": true,\n \"ListView\": true,\n\n \"FlagValues\": {\n \"builder\": [{\n \"DynamicDesc\": ({\n docker help builder -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"config\": [{\n \"DynamicDesc\": ({\n docker help config -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"container\": [{\n \"DynamicDesc\": ({\n docker help container -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"context\": [{\n \"DynamicDesc\": ({\n docker help context -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"image\": [{\n \"DynamicDesc\": ({\n docker help image -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"network\": [{\n \"DynamicDesc\": ({\n docker help network -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"node\": [{\n \"DynamicDesc\": ({\n docker help node -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"plugin\": [{\n \"DynamicDesc\": ({\n docker help plugin -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"secret\": [{\n \"DynamicDesc\": ({\n docker help secret -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"service\": [{\n \"DynamicDesc\": ({\n docker help service -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"stack\": [{\n \"DynamicDesc\": ({\n docker help stack -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"swarm\": [{\n \"DynamicDesc\": ({\n docker help swarm -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"system\": [{\n \"DynamicDesc\": ({\n docker help system -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"trust\": [{\n \"DynamicDesc\": ({\n docker help trust -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"volume\": [{\n \"DynamicDesc\": ({\n docker help volume -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }]\n }\n },\n {\n \"IncFiles\": true\n }\n] }\n\nprivate autocomplete.docker-compose.services {\n # Returns a list of services described in docker-compose.yaml\n open docker-compose.yaml -> [ services ] -> formap k v { out \"$k\" } -> cast str\n}\n\nautocomplete: set docker-compose { [{\n \"Flags\": [\"build\",\"bundle\",\"config\",\"create\",\"down\",\"events\",\"exec\",\"help\",\"images\",\"kill\",\"logs\",\"pause\",\"port\",\"ps\",\"pull\",\"push\",\"restart\",\"rm\",\"run\",\"scale\",\"start\",\"stop\",\"top\",\"unpause\",\"up\",\"version\"],\n \"FlagValues\": {\n \"build\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"create\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"events\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"exec\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"kill\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"logs\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"pause\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"pull\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"push\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"restart\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"run\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"scale\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"start\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"stop\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"top\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"unpause\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"up\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }]\n }\n}] }\n\nautocomplete: set terraform { [{\n \"Flags\": [\"apply\",\"console\",\"destroy\",\"env\",\"fmt\",\"get\",\"graph\",\"import\",\"init\",\"output\",\"plan\",\"providers\",\"push\",\"refresh\",\"show\",\"taint\",\"untaint\",\"validate\",\"version\",\"workspace\"],\n \"FlagValues\": {\n \"workspace\": [\n {\n \"Flags\": [ \"new\", \"delete\", \"select\", \"list\", \"show\" ]\n }\n ]\n }\n}] }\n\nautocomplete: set gopass { [{\n \"Flags\": [\"--yes\",\"--clip\",\"-c\",\"--help\",\"-h\",\"--version\",\"-v\"],\n \"AllowMultiple\": true,\n \"Dynamic\": ({ exec: @ARGS --generate-bash-completion }),\n \"AutoBranch\": true\n}] }\n\nautocomplete: set debug { [{\n \"Flags\": [\"on\", \"off\"]\n}] }\n\nautocomplete: set murex-package {\n [{\n \"Flags\": [ \"install\", \"update\", \"import\", \"enable\", \"disable\", \"reload\", \"status\", \"list\", \"cd\" ],\n \"FlagValues\": {\n \"import\": [{\n \"IncFiles\": true\n }],\n \"enable\": [{\n \"DynamicDesc\": ({ murex-package: list disabled }),\n \"ListView\": true,\n \"AutoBranch\": true\n }],\n \"disable\": [{\n \"DynamicDesc\": ({ murex-package: list enabled }),\n \"ListView\": true,\n \"AutoBranch\": true\n }],\n \"list\": [{\n \"Flags\": [ \"enabled\", \"disabled\", \"packages\" ]\n }],\n \"cd\": [{\n \"Dynamic\": ({ murex-package: list packages })\n }]\n }\n }]\n}\n\nalias: builtins=runtime --builtins\n\nprivate: test.alias.builtins {\n # Wrapper function around the alias for `builtins` for unit testing\n builtins\n}\n\ntest: unit private test.alias.builtins {\n \"StdoutRegex\": (\"[a-z0-9]+\",),\n \"StdoutType\": \"json\",\n \"StdoutBlock\": ({\n -> len -> set len;\n if { = len>0 } then {\n out \"Len greater than 0\"\n } else {\n err \"No elements returned\"\n }\n }),\n \"StdoutIsArray\": true\n}\n\nautocomplete: set murex-docs { [{\n \"Dynamic\": ({ builtins }),\n\t\"Flags\": [ \"--summary\" ],\n\t\"FlagValues\": {\n\t\t\"--summary\": [{\n\t\t\t\"Dynamic\": ({ builtins })\n\t\t}]\n\t}\n}] }\n\nprivate: autocomplete.aliases.and.builtins {\n # Returns a list of aliases and builtins\n runtime: --aliases -> formap k ! { out: $k } -> cast str\n builtins -> format str\n}\n\ntest: unit private autocomplete.aliases.and.builtins {\n \"StdoutType\": \"str\",\n \"StdoutBlock\": ({\n -> len -> set len;\n if { = len>0 } then {\n out \"Len greater than 0\"\n } else {\n err \"No elements returned\"\n }\n }),\n \"StdoutIsArray\": true\n}\n\nautocomplete: set summary { [{\n \"IncExePath\": true,\n \"Dynamic\": ({\n autocomplete.aliases.and.builtins\n })\n}] }\n\nconfig: eval shell safe-commands {\n -> alter --merge / ([\n \"builtins\", \"jobs\"\n ])\n}\n\n!if { man-summary terraform } then {\n summary terraform \"Write, Plan, and Create Infrastructure as Code\"\n}\n\nautocomplete: set terraform {[\n {\n \"DynamicDesc\": ({\n terraform -help @{ $ARGS -> @[1..] } -> tabulate: --map --column-wraps --key-inc-hint\n }),\n \"AllowMultiple\": true,\n \"ListView\": true\n }\n]}\n\n!if { man-summary go } then {\n summary go \"Go is a tool for managing Go source code\"\n}\n\n!if { man-summary atom } then {\n summary atom \"Github Atom - Text editor / IDE\"\n}\n\n!if { man-summary code } then {\n summary code \"Microsoft Visual Studio Code - Text editor / IDE\"\n}\n\nautocomplete: set zfs {\n [{\n \"Dynamic\": ({\n zfs ? egrep \"^\\t[a-z]+\" -> regexp 'f/\\t+([a-z]+)/' -> uniq \n })\n }]\n}\n\nautocomplete: set zpool {\n [{\n \"Dynamic\": ({\n zpool ? egrep \"^\\t[a-z]+\" -> regexp 'f/\\t+([a-z]+)/' -> uniq \n })\n }]\n}") + murexProfile = append(murexProfile, "function h {\n # Output the murex history log in a human readable format\n history -> foreach { -> set json line; out \"$line[Index]: $line[Block]\" } -> cast *\n}\n\nfunction aliases {\n\t# Output the aliases in human readable format\n\truntime: --aliases -> formap name alias {\n $name -> sprintf: \"%10s => ${esccli @alias}\\n\"\n\t} -> cast: str\n}\n\ntest unit function aliases {\n \"PreBlock\": ({\n alias ALIAS_UNIT_TEST=example param1 param2 param3\n }),\n \"StdoutRegex\": \"([- _0-9a-zA-Z]+ => .*?\\n)+\",\n \"StdoutType\": \"str\",\n \"PostBlock\": ({\n !alias ALIAS_UNIT_TEST\n })\n}\n\nautocomplete: set cd { [{\n \"IncDirs\": true\n}] }\n\nautocomplete: set mkdir { [{\n \"IncDirs\": true,\n \"AllowMultiple\": true\n}] }\n\nautocomplete: set rmdir { [{\n \"IncDirs\": true,\n \"AllowMultiple\": true\n}] }\n\nautocomplete: set exec { [\n {\n \"IncFiles\": true,\n \"IncDirs\": true,\n \"IncExePath\": true\n },\n {\n \"NestedCommand\": true\n }\n] }\n\nautocomplete: set format { [{\n \"Dynamic\": ({ runtime: --marshallers })\n}] }\n\nautocomplete: set swivel-datatype { [{\n \"Dynamic\": ({ runtime: --marshallers })\n}] }\n\nprivate autocomplete.data-types {\n # Returns all murex data-types compiled\n runtime: --readarray -> format: str\n runtime: --writearray -> format: str\n runtime: --readmap -> format: str\n runtime: --marshallers -> format: str\n runtime: --unmarshallers -> format: str\n}\n\ntest unit private autocomplete.data-types {\n \"StdoutRegex\": (^(([a-z0-9]+|\\*)\\n)+),\n \"StdoutType\": \"str\",\n \"StdoutBlock\": ({\n -> len -> set len;\n if { = len>0 } then {\n out \"Len greater than 0\"\n } else {\n err \"No elements returned\"\n }\n }),\n \"StdoutIsArray\": true\n}\n\nautocomplete: set cast { [{\n \"Dynamic\": ({ autocomplete.data-types })\n}] }\n\nautocomplete: set tout { [{\n \"Dynamic\": ({ autocomplete.data-types })\n}] }\n\nprivate autocomplete.variables {\n # Returns all global and local variable names\n runtime: --globals -> formap k v { out $k } -> cast: str\n runtime: --variables -> formap k v { out $k } -> cast: str\n}\n\ntest unit private autocomplete.variables {\n \"PreBlock\": ({ global MUREX_UNIT_TEST=foobar }),\n \"PostBlock\": ({ !global MUREX_UNIT_TEST }),\n \"StdoutRegex\": (^([_a-zA-Z0-9]+\\n)+),\n \"StdoutType\": \"str\",\n \"StdoutBlock\": ({\n -> len -> set len;\n if { = len>0 } then {\n out \"Len greater than 0\"\n } else {\n err \"No elements returned\"\n }\n }),\n \"StdoutIsArray\": true\n}\n\nautocomplete: set set { [{\n \"Dynamic\": ({ autocomplete.variables })\n}] }\n\nautocomplete: set !set { [{\n \"Dynamic\": ({ autocomplete.variables })\n}] }\n\nautocomplete: set global { [{\n \"Dynamic\": ({ autocomplete.variables })\n}] }\n\nautocomplete: set !global { [{\n \"Dynamic\": ({ autocomplete.variables })\n}] }\n\nautocomplete: set export { [{\n \"Dynamic\": ({ autocomplete.variables })\n}] }\n\nautocomplete: set !export { [{\n \"DynamicDesc\": ({ runtime --exports })\n}] }\n\nautocomplete: set unset { [{\n \"DynamicDesc\": ({ runtime --exports })\n}] }\n\nautocomplete: set \"[\" { [{\n \"AnyValue\": true,\n \"AllowMultiple\": true,\n \"ExecCmdline\": true,\n \"Dynamic\": ({\n switch ${ get-type: stdin } {\n case * {\n # -> tabulate -> [ 0: ] -> format json -> [ 0 ]\n -> [ 0: ] -> format json -> [ 0 ] -> append \"]\"\n }\n\n case csv {\n -> [ 0: ] -> format json -> [ 0 ] -> append \"]\"\n }\n \n case jsonl {\n -> [ 0 ] -> set header\n $header -> cast utf8 -> [ 0 -1 ] -> set jsonl_format\n if { = jsonl_format==`[]` } then {\n tout json $header -> append \"]\"\n }\n }\n\n catch {\n -> formap k v { out $k } -> cast str -> append \"]\"\n }\n }\n })\n}] }\n\nautocomplete: set \"[[\" { [{\n \"AnyValue\": true,\n \"ExecCmdline\": true,\n \"AutoBranch\": true,\n \"Dynamic\": ({ -> struct-keys -> append \"]]\" })\n} ]}\n\nprivate autocomplete.config.get.apps {\n # Returns all app names for the 'app' field in `config`\n config: -> formap k v { out $k } -> cast: str -> msort\n}\n\ntest unit private autocomplete.config.get.apps {\n \"StdoutRegex\": (shell),\n \"StdoutType\": \"str\",\n \"StdoutBlock\": ({\n -> len -> set len;\n if { = len>0 } then {\n out \"Len greater than 0\"\n } else {\n err \"No elements returned\"\n }\n }),\n \"StdoutIsArray\": true\n}\n\nprivate autocomplete.config.get.keys {\n # Returns all keys for the 'app' field in `config`\n config -> [ $ARGS[1] ] -> formap k v { out $k } -> cast: str -> msort\n}\n\ntest unit private autocomplete.config.get.keys {\n \"Parameters\": [ \"shell\" ],\n \"StdoutRegex\": (prompt),\n \"StdoutType\": \"str\",\n \"StdoutBlock\": ({\n -> len -> set len;\n if { = len>0 } then {\n out \"Len greater than 0\"\n } else {\n err \"No elements returned\"\n }\n }),\n \"StdoutIsArray\": true\n}\n\nautocomplete: set config { [{\n \"Flags\": [ \"get\", \"set\", \"eval\", \"define\", \"default\" ],\n \"FlagValues\": {\n \"get\": [\n { \"Dynamic\": ({ autocomplete.config.get.apps }) },\n { \"Dynamic\": ({ autocomplete.config.get.keys $ARGS[2] }) }\n ], \n \"set\": [\n { \"Dynamic\": ({ autocomplete.config.get.apps }) },\n { \"Dynamic\": ({ autocomplete.config.get.keys $ARGS[2] }) },\n { \"Dynamic\": ({\n\t\t\t\tswitch {\n\t\t\t\t\tcase { = `${ config -> [ $ARGS[2] ] -> [ $ARGS[3] ] -> [ Data-Type ]}`==`bool` } {\n\t\t\t\t\t\tja [true,false]\n\t\t\t\t\t}\n\n\t\t\t\t\tcase { config -> [ $ARGS[2] ] -> [ $ARGS[3] ] -> [ Options ] } {\n\t\t\t\t\t\tconfig -> [ $ARGS[2] ] -> [ $ARGS[3] ] -> [ Options ]\n\t\t\t\t\t}\n\t\t\t\t\t\n \tcatch {\n\t\t\t\t\t\tout ${ config -> [ $ARGS[2] ] -> [ $ARGS[3] ] -> [ Default ]}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}) }\n ],\n \"eval\": [\n { \"Dynamic\": ({ autocomplete.config.get.apps }) },\n { \"Dynamic\": ({ autocomplete.config.get.keys $ARGS[2] }) }\n ],\n \"default\": [\n { \"Dynamic\": ({ autocomplete.config.get.apps }) },\n { \"Dynamic\": ({ autocomplete.config.get.keys $ARGS[2] }) }\n ]\n }\n}] }\n\nautocomplete: set !config { [\n { \"Dynamic\": ({ autocomplete.config.get.apps }) },\n { \"Dynamic\": ({ autocomplete.config.get.keys $ARGS[1] }) }\n] }\n\n\nautocomplete: set event { [\n {\n \"Dynamic\": ({ runtime: --events -> formap k v { out $k } })\n }\n] }\n\nautocomplete: set !event { [\n {\n \"Dynamic\": ({ runtime: --events -> formap k v { out $k } -> msort })\n },\n {\n \"Dynamic\": ({ runtime: --events -> [ $ARGS[1] ] -> formap k v { out $k } -> msort })\n }\n] }\n\nprivate autocomplete.alias {\n # Returns a map of all alises and the code they execute\n runtime: --aliases -> formap --jmap name value { $name } { out @value }\n}\n\ntest unit private autocomplete.alias {\n \"StdoutRegex\": (jobs),\n\t\"StdoutType\": \"json\",\n \"StdoutBlock\": ({\n -> len -> set len;\n if { = len>0 } then {\n out \"Len greater than 0\"\n } else {\n err \"No elements returned\"\n }\n }),\n \"StdoutIsMap\": true\n}\n\nautocomplete: set !alias { [{\n \"DynamicDesc\": ({ autocomplete.alias }),\n \"ListView\": true\n}] }\n\nprivate autocomplete.functions {\n # Returns a map of all murex public functions\n runtime: --functions -> formap --jmap k v { $k } { out: $v[summary] }\n}\n\ntest unit private autocomplete.functions {\n \"PreBlock\": ({\n function unit.test.autocomplete.functions {\n out \"This is only a dummy function for testing\"\n }\n }),\n \"PostBlock\": ({\n !function unit.test.autocomplete.functions\n }),\n \"StdoutRegex\": (unit.test.autocomplete.functions),\n\t\"StdoutType\": \"json\",\n \"StdoutBlock\": ({\n -> len -> set len;\n if { = len>0 } then {\n out \"Len greater than 0\"\n } else {\n err \"No elements returned\"\n }\n }),\n \"StdoutIsMap\": true\n}\n\nautocomplete: set !function { [{\n \"DynamicDesc\": ({ autocomplete.functions }),\n \"ListView\": true\n} ]}\n\nprivate autocomplete.privates {\n # Returns a map of all murex private functions\n runtime: --privates -> struct-keys: 3 -> regexp m,/.*?/.*?/, -> foreach --jmap private { $private } { runtime: --privates -> [[ $private/Summary ]] }\n}\n\ntest unit private autocomplete.privates {\n\t\"StdoutType\": \"json\",\n \"StdoutBlock\": ({\n -> len -> set len;\n if { = len>0 } then {\n out \"Len greater than 0\"\n } else {\n err \"No elements returned\"\n }\n }),\n \"StdoutIsMap\": true\n}\n\nprivate autocomplete.builtins {\n # Returns a map of all murex builtins\n runtime --builtins -> foreach --jmap builtin { $builtin } { murex-docs --summary $builtin }\n}\n\ntest unit private autocomplete.builtins {\n\t\"StdoutType\": \"json\",\n \"StdoutBlock\": ({\n -> len -> set len;\n if { = len>0 } then {\n out \"Len greater than 0\"\n } else {\n err \"No elements returned\"\n }\n }),\n \"StdoutIsMap\": true\n}\n\nautocomplete: set autocomplete { [{\n \"Flags\": [ \"get\", \"set\" ],\n \"FlagValues\": {\n \"get\": [{\n \"Dynamic\": ({\n runtime: --autocomplete -> formap: cmd ! { out $cmd } -> cast: str\n })\n }]\n }\n}] }\n\nprivate git-branch {\n # Returns a list of branches excluding currect checked-out branch in the current git repository\n git branch -> [ :0 ] -> !match *\n}\n\nautocomplete: set git { [{\n #\"Flags\": [ \"clone\", \"init\", \"add\", \"mv\", \"reset\", \"rm\", \"bisect\", \"grep\", \"log\", \"show\", \"status\", \"branch\", \"checkout\", \"commit\", \"diff\", \"merge\", \"rebase\", \"tag\", \"fetch\", \"pull\", \"push\", \"stash\" ],\n \"DynamicDesc\": ({\n git: help -a -> @[..^Ancillary]re -> tabulate: --map\n }),\n \"ListView\": true,\n \"FlagValues\": {\n \"init\": [{\n \"Flags\": [\"--bare\"]\n }],\n \"add\": [{\n #\"IncFiles\": true,\n \"AllowMultiple\": true,\n \"Dynamic\": ({\n git status -s -> regexp 'f/^.[^\\s] [\"]?(.*?)[\"]?$/' -> cast str\n })\n }],\n \"diff\": [{\n #\"IncFiles\": true,\n \"AllowMultiple\": true,\n \"Dynamic\": ({\n git status -s -> [:1]\n })\n }],\n \"mv\": [{ \n \"IncFiles\": true\n }],\n \"rm\": [{\n \"IncFiles\": true,\n \"AllowMultiple\": true\n }],\n \"checkout\": [{\n \"Dynamic\": ({ git-branch }),\n \"Flags\": [ \"-b\" ]\n }],\n \"merge\": [{\n \"Dynamic\": ({ git-branch })\n }],\n \"commit\": [{\n \"Flags\": [\"-a\", \"-m\", \"--amend\"],\n \"FlagValues\": {\n \"--amend\": [{ \"AnyValue\": true }]\n },\n \"AllowMultiple\": true\n }]\n }\n}] }\n\nautocomplete: set docker { [\n {\n \"DynamicDesc\": ({\n docker help -> @[^Usage:..]re -> tabulate: --split-comma --map\n }),\n\n #\"AllowMultiple\": true,\n #\"AnyValue\": true,\n \"ListView\": true,\n\n \"FlagValues\": {\n \"builder\": [{\n \"DynamicDesc\": ({\n docker help builder -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"config\": [{\n \"DynamicDesc\": ({\n docker help config -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"container\": [{\n \"DynamicDesc\": ({\n docker help container -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"context\": [{\n \"DynamicDesc\": ({\n docker help context -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"image\": [{\n \"DynamicDesc\": ({\n docker help image -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"network\": [{\n \"DynamicDesc\": ({\n docker help network -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"node\": [{\n \"DynamicDesc\": ({\n docker help node -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"plugin\": [{\n \"DynamicDesc\": ({\n docker help plugin -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"secret\": [{\n \"DynamicDesc\": ({\n docker help secret -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"service\": [{\n \"DynamicDesc\": ({\n docker help service -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"stack\": [{\n \"DynamicDesc\": ({\n docker help stack -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"swarm\": [{\n \"DynamicDesc\": ({\n docker help swarm -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"system\": [{\n \"DynamicDesc\": ({\n docker help system -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"trust\": [{\n \"DynamicDesc\": ({\n docker help trust -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }],\n\n \"volume\": [{\n \"DynamicDesc\": ({\n docker help volume -> @[^Usage:..]re -> tabulate: --split-comma --map\n })\n }]\n }\n },\n {\n \"IncFiles\": true\n }\n] }\n\nprivate autocomplete.docker-compose.services {\n # Returns a list of services described in docker-compose.yaml\n open docker-compose.yaml -> [ services ] -> formap k v { out \"$k\" } -> cast str\n}\n\nautocomplete: set docker-compose { [{\n \"Flags\": [\"build\",\"bundle\",\"config\",\"create\",\"down\",\"events\",\"exec\",\"help\",\"images\",\"kill\",\"logs\",\"pause\",\"port\",\"ps\",\"pull\",\"push\",\"restart\",\"rm\",\"run\",\"scale\",\"start\",\"stop\",\"top\",\"unpause\",\"up\",\"version\"],\n \"FlagValues\": {\n \"build\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"create\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"events\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"exec\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"kill\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"logs\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"pause\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"pull\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"push\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"restart\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"run\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"scale\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"start\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"stop\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"top\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"unpause\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }],\n \"up\": [{\n \"Dynamic\": ({ autocomplete.docker-compose.services })\n }]\n }\n}] }\n\nautocomplete: set terraform { [{\n \"Flags\": [\"apply\",\"console\",\"destroy\",\"env\",\"fmt\",\"get\",\"graph\",\"import\",\"init\",\"output\",\"plan\",\"providers\",\"push\",\"refresh\",\"show\",\"taint\",\"untaint\",\"validate\",\"version\",\"workspace\"],\n \"FlagValues\": {\n \"workspace\": [\n {\n \"Flags\": [ \"new\", \"delete\", \"select\", \"list\", \"show\" ]\n }\n ]\n }\n}] }\n\nautocomplete: set gopass { [{\n \"Flags\": [\"--yes\",\"--clip\",\"-c\",\"--help\",\"-h\",\"--version\",\"-v\"],\n \"AllowMultiple\": true,\n \"Dynamic\": ({ exec: @ARGS --generate-bash-completion }),\n \"AutoBranch\": true\n}] }\n\nautocomplete: set debug { [{\n \"Flags\": [\"on\", \"off\"]\n}] }\n\nautocomplete: set murex-package {\n [{\n \"Flags\": [ \"install\", \"update\", \"import\", \"enable\", \"disable\", \"reload\", \"status\", \"list\", \"cd\" ],\n \"FlagValues\": {\n \"import\": [{\n \"IncFiles\": true\n }],\n \"enable\": [{\n \"DynamicDesc\": ({ murex-package: list disabled }),\n \"ListView\": true,\n \"AutoBranch\": true\n }],\n \"disable\": [{\n \"DynamicDesc\": ({ murex-package: list enabled }),\n \"ListView\": true,\n \"AutoBranch\": true\n }],\n \"list\": [{\n \"Flags\": [ \"enabled\", \"disabled\", \"packages\" ]\n }],\n \"cd\": [{\n \"Dynamic\": ({ murex-package: list packages })\n }]\n }\n }]\n}\n\nalias: builtins=runtime --builtins\n\nprivate: test.alias.builtins {\n # Wrapper function around the alias for `builtins` for unit testing\n builtins\n}\n\ntest: unit private test.alias.builtins {\n \"StdoutRegex\": (\"[a-z0-9]+\",),\n \"StdoutType\": \"json\",\n \"StdoutBlock\": ({\n -> len -> set len;\n if { = len>0 } then {\n out \"Len greater than 0\"\n } else {\n err \"No elements returned\"\n }\n }),\n \"StdoutIsArray\": true\n}\n\nautocomplete: set murex-docs { [{\n \"Dynamic\": ({ builtins }),\n\t\"Flags\": [ \"--summary\" ],\n\t\"FlagValues\": {\n\t\t\"--summary\": [{\n\t\t\t\"Dynamic\": ({ builtins })\n\t\t}]\n\t}\n}] }\n\nprivate: autocomplete.aliases.and.builtins {\n # Returns a list of aliases and builtins\n runtime: --aliases -> formap k ! { out: $k } -> cast str\n builtins -> format str\n}\n\ntest: unit private autocomplete.aliases.and.builtins {\n \"StdoutType\": \"str\",\n \"StdoutBlock\": ({\n -> len -> set len;\n if { = len>0 } then {\n out \"Len greater than 0\"\n } else {\n err \"No elements returned\"\n }\n }),\n \"StdoutIsArray\": true\n}\n\nautocomplete: set summary { [{\n \"IncExePath\": true,\n \"Dynamic\": ({\n autocomplete.aliases.and.builtins\n })\n}] }\n\nconfig: eval shell safe-commands {\n -> alter --merge / ([\n \"builtins\", \"jobs\"\n ])\n}\n\n!if { man-summary terraform } then {\n summary terraform \"Write, Plan, and Create Infrastructure as Code\"\n}\n\nautocomplete: set terraform {[\n {\n \"DynamicDesc\": ({\n terraform -help @{ $ARGS -> @[1..] } -> tabulate: --map --column-wraps --key-inc-hint\n }),\n \"AllowMultiple\": true,\n \"ListView\": true\n }\n]}\n\n!if { man-summary go } then {\n summary go \"Go is a tool for managing Go source code\"\n}\n\n!if { man-summary atom } then {\n summary atom \"Github Atom - Text editor / IDE\"\n}\n\n!if { man-summary code } then {\n summary code \"Microsoft Visual Studio Code - Text editor / IDE\"\n}\n\nautocomplete: set zfs {\n [{\n \"Dynamic\": ({\n zfs ? egrep \"^\\t[a-z]+\" -> regexp 'f/\\t+([a-z]+)/' -> uniq \n })\n }]\n}\n\nautocomplete: set zpool {\n [{\n \"Dynamic\": ({\n zpool ? egrep \"^\\t[a-z]+\" -> regexp 'f/\\t+([a-z]+)/' -> uniq \n })\n }]\n}") } diff --git a/config/defaults/profile_all.mx b/config/defaults/profile_all.mx index ae4a47eb7..7051a8c57 100644 --- a/config/defaults/profile_all.mx +++ b/config/defaults/profile_all.mx @@ -85,22 +85,6 @@ autocomplete: set tout { [{ "Dynamic": ({ autocomplete.data-types }) }] } -autocomplete: set set { [{ - "Dynamic": ({ autocomplete.data-types }) -}] } - -autocomplete: set export { [{ - "DynamicDesc": ({ runtime --exports }) -}] } - -autocomplete: set !export { [{ - "DynamicDesc": ({ runtime --exports }) -}] } - -autocomplete: set unset { [{ - "DynamicDesc": ({ runtime --exports }) -}] } - private autocomplete.variables { # Returns all global and local variable names runtime: --globals -> formap k v { out $k } -> cast: str @@ -123,6 +107,14 @@ test unit private autocomplete.variables { "StdoutIsArray": true } +autocomplete: set set { [{ + "Dynamic": ({ autocomplete.variables }) +}] } + +autocomplete: set !set { [{ + "Dynamic": ({ autocomplete.variables }) +}] } + autocomplete: set global { [{ "Dynamic": ({ autocomplete.variables }) }] } @@ -131,12 +123,16 @@ autocomplete: set !global { [{ "Dynamic": ({ autocomplete.variables }) }] } -autocomplete: set set { [{ +autocomplete: set export { [{ "Dynamic": ({ autocomplete.variables }) }] } -autocomplete: set !set { [{ - "Dynamic": ({ autocomplete.variables }) +autocomplete: set !export { [{ + "DynamicDesc": ({ runtime --exports }) +}] } + +autocomplete: set unset { [{ + "DynamicDesc": ({ runtime --exports }) }] } autocomplete: set "[" { [{ diff --git a/docs/commands/export.md b/docs/commands/export.md index fc5ce280b..38f747243 100644 --- a/docs/commands/export.md +++ b/docs/commands/export.md @@ -40,6 +40,27 @@ For compatibility with other shells, `unset` is also supported but it's really not an idiomatic method of deallocation since it's name is misleading and suggests it is a deallocator for local _murex_ variables defined via `set`. +### Exporting a local or global variable + +You can also export a local or global variable of the same name by specifying +that variable name without a following value. For example + + # Create a local variable called 'foo': + » set: foo=bar + » env -> grep: foo + + # Export that local variable as an environmental variable: + » export: foo + » env -> grep: foo + foo=bar + + # Changing the value of the local variable doesn't alter the value of the environmental variable: + » set: foo=rab + » env -> grep: foo + foo=bar + » out: $foo + rab + ### Scoping Variable scoping is simplified to three layers: diff --git a/utils/envvars/envvars.go b/utils/envvars/envvars.go new file mode 100644 index 000000000..5c794978b --- /dev/null +++ b/utils/envvars/envvars.go @@ -0,0 +1,39 @@ +package envvars + +import ( + "os" +) + +// All returns a map of environmental variables +func All() (map[string]string, error) { + return all(os.Environ()) +} + +func all(envs []string) (map[string]string, error) { + m := make(map[string]string) + + for _, s := range envs { + if s == "" || s == "=" { + m[""] = "" + continue + } + + var i int + for ; i < len(s); i++ { + if s[i] == '=' { + break + } + } + + switch i { + case 0: + m[""] = s[1:] + case len(s): + m[s] = "" + default: + m[s[:i]] = s[i+1:] + } + } + + return m, nil +} diff --git a/utils/envvars/envvars_test.go b/utils/envvars/envvars_test.go new file mode 100644 index 000000000..d43c52555 --- /dev/null +++ b/utils/envvars/envvars_test.go @@ -0,0 +1,277 @@ +package envvars + +import ( + "encoding/json" + "testing" + + "github.com/lmorg/murex/test/count" +) + +type envAllTestT struct { + Slice []string + Expected map[string]string + Error bool +} + +func TestEnvVarsAll(t *testing.T) { + tests := []envAllTestT{ + { + Slice: []string{}, + Expected: map[string]string{}, + Error: false, + }, + { + Slice: []string{ + `a=a`, + `b=b`, + `c=c`, + }, + Expected: map[string]string{ + "a": "a", + "b": "b", + "c": "c", + }, + Error: false, + }, + { + Slice: []string{ + `a=`, + `b=b`, + `c=c`, + }, + Expected: map[string]string{ + "a": "", + "b": "b", + "c": "c", + }, + Error: false, + }, + { + Slice: []string{ + `a`, + `b=b`, + `c=c`, + }, + Expected: map[string]string{ + "a": "", + "b": "b", + "c": "c", + }, + Error: false, + }, + { + Slice: []string{ + `a=a=a`, + `b=b`, + `c=c`, + }, + Expected: map[string]string{ + "a": "a=a", + "b": "b", + "c": "c", + }, + Error: false, + }, + { + Slice: []string{ + `a=a`, + `foo=bar`, + `c=c`, + }, + Expected: map[string]string{ + "a": "a", + "foo": "bar", + "c": "c", + }, + Error: false, + }, + { + Slice: []string{ + `a=a`, + `=foobar`, + `c=c`, + }, + Expected: map[string]string{ + "a": "a", + "": "foobar", + "c": "c", + }, + Error: false, + }, + { + Slice: []string{ + `a=a`, + `foobar=`, + `c=c`, + }, + Expected: map[string]string{ + "a": "a", + "foobar": "", + "c": "c", + }, + Error: false, + }, + { + Slice: []string{ + `a=a`, + `=`, + `c=c`, + }, + Expected: map[string]string{ + "a": "a", + "": "", + "c": "c", + }, + Error: false, + }, + { + Slice: []string{ + `a=a`, + `foobar`, + `c=c`, + }, + Expected: map[string]string{ + "a": "a", + "foobar": "", + "c": "c", + }, + Error: false, + }, + { + Slice: []string{ + `a=a`, + ``, + `c=c`, + }, + Expected: map[string]string{ + "a": "a", + "": "", + "c": "c", + }, + Error: false, + }, + { + Slice: []string{ + `a=a`, + `hello=世界`, + `c=c`, + }, + Expected: map[string]string{ + "a": "a", + "hello": "世界", + "c": "c", + }, + Error: false, + }, + { + Slice: []string{ + `a=a`, + `世界=hello`, + `c=c`, + }, + Expected: map[string]string{ + "a": "a", + "世界": "hello", + "c": "c", + }, + Error: false, + }, + { + Slice: []string{ + `a=a`, + `世界=`, + `c=c`, + }, + Expected: map[string]string{ + "a": "a", + "世界": "", + "c": "c", + }, + Error: false, + }, + { + Slice: []string{ + `a=a`, + `世界`, + `c=c`, + }, + Expected: map[string]string{ + "a": "a", + "世界": "", + "c": "c", + }, + Error: false, + }, + { + Slice: []string{ + `a=a`, + `=世界`, + `c=c`, + }, + Expected: map[string]string{ + "a": "a", + "": "世界", + "c": "c", + }, + Error: false, + }, + { + Slice: []string{ + "a=a", + "TIMEFMT=\n================\nCPU %P\nuser %*U\nsystem %*S\ntotal %*E", + `c=c`, + }, + Expected: map[string]string{ + "a": "a", + "TIMEFMT": "\n================\nCPU %P\nuser %*U\nsystem %*S\ntotal %*E", + "c": "c", + }, + Error: false, + }, + } + + testEnvVarsAll(t, tests) +} + +func testEnvVarsAll(t *testing.T, tests []envAllTestT) { + count.Tests(t, len(tests)) + t.Helper() + + for i, test := range tests { + if test.Expected == nil { + test.Expected = make(map[string]string) + } + + actual, err := all(test.Slice) + + if (err != nil) != test.Error { + t.Errorf("Error expectation mismatch in test %d", i) + t.Logf(" Expected: %v", test.Error) + t.Logf(" Actual: %v", err) + } + + if len(test.Expected) != len(actual) { + t.Errorf("Output count mistmatch in test %d", i) + t.Logf(" Exp Count: %d", len(test.Expected)) + t.Logf(" Act Count: %d", len(actual)) + t.Logf(" Expected:\n%s", testJsonEncode(test.Expected)) + t.Logf(" Actual:\n%s", testJsonEncode(actual)) + } + + for k := range actual { + if actual[k] != test.Expected[k] { + t.Errorf("Key/value mistmatch in test %d", i) + t.Logf(" Key: `%s`", k) + t.Logf(" Exp Value: `%s`", test.Expected[k]) + t.Logf(" Act Value: `%s`", actual[k]) + t.Logf(" Expected:\n%s", testJsonEncode(test.Expected)) + t.Logf(" Actual:\n%s", testJsonEncode(actual)) + } + } + } +} + +func testJsonEncode(v interface{}) string { + b, _ := json.MarshalIndent(v, " ", " ") + return string(b) +} diff --git a/utils/envvars/godoc.go b/utils/envvars/godoc.go new file mode 100644 index 000000000..00db4b535 --- /dev/null +++ b/utils/envvars/godoc.go @@ -0,0 +1,2 @@ +// Package envvars provides a more pleasant framework around Go's stdlibs for working with environmental variables +package envvars