From 64919f3a3a17a68c1fb58d4a0b70f0f5416c20b4 Mon Sep 17 00:00:00 2001
From: Mitchell Paulus Built-ins str str -- )
unsetenvRemove an environment variable by name. Unsetting a variable that does not exist is not an error. (str -- )
dupDuplicate the top stack item. (a -- a a)
diff --git a/doc/mshell.md b/doc/mshell.md
index 78ad384..64a8247 100644
--- a/doc/mshell.md
+++ b/doc/mshell.md
@@ -327,6 +327,10 @@ An environment variable can be removed with the `unsetenv` built-in,
which takes the variable name as a string.
Unsetting a variable that does not exist is not an error.
+When the variable name is not known statically,
+an environment variable can be set with the `setenv` built-in,
+which takes the value then the name as strings.
+
```mshell
$HOME cd
@@ -338,6 +342,9 @@ $HOME cd
# Removing an environment variable
"MSHELL_VAR" unsetenv
+
+# Setting with a dynamic name, value then name
+"Hello, World!" "MSHELL_VAR" setenv
```
## Indexing
@@ -1057,6 +1064,7 @@ end wl # Output: 11
- `defs`: Print available definitions at the current location (--)
- `env`: Write all environment variables to stderr in sorted order (--)
- `completionDefs`: Push a dictionary of completion definitions. Keys are command names, values are lists of quotations. `( -- dict)`
+- `setenv`: Set an environment variable by name, value then name. Use when the name is not known statically; otherwise prefer `$NAME!`. `(str str -- )`
- `unsetenv`: Remove an environment variable by name. Unsetting a variable that does not exist is not an error. `(str -- )`
- `dup`: Duplicate (a -- a a)
- `swap`: Swap (a b -- b a)
diff --git a/doc/variables.inc.html b/doc/variables.inc.html
index 405bdae..4884929 100644
--- a/doc/variables.inc.html
+++ b/doc/variables.inc.html
@@ -57,10 +57,12 @@ swapSwap the top two stack items. (a b -- b a)Environment Variables unsetenv built-in, which takes the name as a string.
Unsetting a variable that does not exist is not an error.
+When the variable name is not known statically, set one with the
setenv built-in, which takes the value then the name.
-"LOG_LEVEL" unsetenv+"LOG_LEVEL" unsetenv +"info" "LOG_LEVEL" setenv
diff --git a/mshell/BuiltInList.go b/mshell/BuiltInList.go
index ca48227..56cca1e 100644
--- a/mshell/BuiltInList.go
+++ b/mshell/BuiltInList.go
@@ -162,6 +162,7 @@ var BuiltInList = map[string]struct{}{
"set": {},
"setAt": {},
"setd": {},
+ "setenv": {},
"sha256sum": {},
"skip": {},
"sleep": {},
diff --git a/mshell/Evaluator.go b/mshell/Evaluator.go
index 1adcb79..c05a8a5 100644
--- a/mshell/Evaluator.go
+++ b/mshell/Evaluator.go
@@ -7216,6 +7216,36 @@ func (state *EvalState) evaluateToken(t Token, stack *MShellStack, context Execu
}
stack.Push(MShellPath{path})
+ } else if t.Lexeme == "setenv" {
+ obj1, err := stack.Pop()
+ if err != nil {
+ return state.FailWithMessage(fmt.Sprintf("%d:%d: Cannot do 'setenv' operation on an empty stack.\n", t.Line, t.Column))
+ }
+
+ varName, err := obj1.CastString()
+ if err != nil {
+ return state.FailWithMessage(fmt.Sprintf("%d:%d: Cannot use a %s as an environment variable name.\n", t.Line, t.Column, obj1.TypeName()))
+ }
+
+ obj2, err := stack.Pop()
+ if err != nil {
+ return state.FailWithMessage(fmt.Sprintf("%d:%d: Cannot do 'setenv' operation on a stack with less than two items.\n", t.Line, t.Column))
+ }
+
+ varValue, err := obj2.CastString()
+ if err != nil {
+ return state.FailWithMessage(fmt.Sprintf("%d:%d: Cannot use a %s as an environment variable value.\n", t.Line, t.Column, obj2.TypeName()))
+ }
+
+ err = os.Setenv(varName, varValue)
+ if err != nil {
+ return state.FailWithMessage(fmt.Sprintf("%d:%d: Could not set the environment variable '%s' to '%s'.\n", t.Line, t.Column, varName, varValue))
+ }
+
+ // If it was the PATH, refresh all the binaries
+ if varName == "PATH" {
+ context.Pbm.Update()
+ }
} else if t.Lexeme == "unsetenv" {
obj1, err := stack.Pop()
if err != nil {
diff --git a/mshell/TypeBuiltins.go b/mshell/TypeBuiltins.go
index 51607fe..671796c 100644
--- a/mshell/TypeBuiltins.go
+++ b/mshell/TypeBuiltins.go
@@ -449,6 +449,8 @@ func builtinSigsByName(arena *TypeArena, names *NameTable) map[NameId][]QuoteSig
r.reg(name, "(str | path -- )")
}
r.reg("cd", "(str | path -- )")
+ // setenv : set an environment variable, value then name
+ r.reg("setenv", "(str str -- )")
// unsetenv : remove an environment variable by name
r.reg("unsetenv", "(str -- )")
// cdh / cdp : interactive directory history / pop navigation
diff --git a/tests/success/setenv.msh b/tests/success/setenv.msh
new file mode 100644
index 0000000..cbd4785
--- /dev/null
+++ b/tests/success/setenv.msh
@@ -0,0 +1,9 @@
+# Set an environment variable with a dynamic name, value then name.
+"hello" "MSH_SETENV_TEST" setenv
+$MSH_SETENV_TEST? str wl
+$MSH_SETENV_TEST wl
+# Overwriting an existing variable.
+"world" "MSH_SETENV_TEST" setenv
+$MSH_SETENV_TEST wl
+"MSH_SETENV_TEST" unsetenv
+$MSH_SETENV_TEST? str wl
diff --git a/tests/success/setenv.msh.stdout b/tests/success/setenv.msh.stdout
new file mode 100644
index 0000000..c2602b9
--- /dev/null
+++ b/tests/success/setenv.msh.stdout
@@ -0,0 +1,4 @@
+true
+hello
+world
+false