Skip to content

Commit

Permalink
#286 Fix panic caused by bad envvar parsing + export autocompletion…
Browse files Browse the repository at this point in the history
… shouldn't be envvars
  • Loading branch information
Laurence Morgan committed Mar 16, 2021
1 parent 31ed1dd commit 96a80b9
Show file tree
Hide file tree
Showing 11 changed files with 385 additions and 52 deletions.
31 changes: 2 additions & 29 deletions builtins/core/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ package cmdruntime

import (
"errors"
"fmt"
"os"
"regexp"
"runtime"
"sort"

Expand All @@ -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"
)

Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
4 changes: 3 additions & 1 deletion builtins/core/typemgmt/variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
23 changes: 23 additions & 0 deletions builtins/core/typemgmt/variables_doc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion builtins/docs/export_commands_docgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 <stdin> -> 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 <stdin> -> 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"

}
2 changes: 1 addition & 1 deletion config/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
2 changes: 1 addition & 1 deletion config/defaults/auto_generated_all.go

Large diffs are not rendered by default.

34 changes: 15 additions & 19 deletions config/defaults/profile_all.mx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 })
}] }
Expand All @@ -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 "[" { [{
Expand Down
21 changes: 21 additions & 0 deletions docs/commands/export.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
39 changes: 39 additions & 0 deletions utils/envvars/envvars.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit 96a80b9

Please sign in to comment.