Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Functions
- `longestCommonPrefix`: Longest leading substring shared by every string in a list. `([str] -- str)`
- `setenv`: Set an environment variable by name, taking the value then the name.
Use when the name is not known statically; otherwise prefer `$NAME!`. `(str str -- )`
- `stdinIsTerminal`, `stdoutIsTerminal`, and `stderrIsTerminal`: Report whether
the current effective standard stream is connected to a terminal or Windows
console. Regular files, pipes, captures, and non-file streams return false.
Expand Down
1 change: 1 addition & 0 deletions doc/functions.inc.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ <h1 id="functions-built-ins">Built-ins <a class="section-link" href="#functions-
<tr> <td><code>defs</code></td> <td>Print available definitions at the current location.</td> <td><code>(--)</code></td> </tr>
<tr> <td><code>completionDefs</code></td> <td>Push a dictionary of completion definitions. Keys are command names, values are lists of quotations.</td> <td><code>(-- dict)</code></td> </tr>
<tr> <td><code>env</code></td> <td>Write all environment variables to stderr in sorted order.</td> <td><code>(--)</code></td> </tr>
<tr> <td><code>setenv</code></td> <td>Set an environment variable by name, taking the value then the name. Use when the name is not known statically; otherwise prefer <code>$NAME!</code>.</td> <td><code>(<span class="sig-type sig-type-str">str</span> <span class="sig-type sig-type-str">str</span> -- )</code></td> </tr>
<tr> <td><code>unsetenv</code></td> <td>Remove an environment variable by name. Unsetting a variable that does not exist is not an error.</td> <td><code>(<span class="sig-type sig-type-str">str</span> -- )</code></td> </tr>
<tr> <td><code>dup</code></td> <td>Duplicate the top stack item.</td> <td><code>(a -- a a)</code></td> </tr>
<tr> <td><code>swap</code></td> <td>Swap the top two stack items.</td> <td><code>(a b -- b a)</code></td> </tr>
Expand Down
8 changes: 8 additions & 0 deletions doc/mshell.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion doc/variables.inc.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,12 @@ <h1 id="variables-environment">Environment Variables <a class="section-link" hre
Environment variables are always exported to subprocesses, so setting them affects subsequent command executions.
Remove one with the <code>unsetenv</code> 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 <code>setenv</code> built-in, which takes the value then the name.
</p>

<pre>
<code><span class="mshellSTRING">"LOG_LEVEL"</span> <span class="mshellLITERAL">unsetenv</span></code>
<code><span class="mshellSTRING">"LOG_LEVEL"</span> <span class="mshellLITERAL">unsetenv</span>
<span class="mshellSTRING">"info"</span> <span class="mshellSTRING">"LOG_LEVEL"</span> <span class="mshellLITERAL">setenv</span></code>
</pre>

<pre>
Expand Down
1 change: 1 addition & 0 deletions mshell/BuiltInList.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ var BuiltInList = map[string]struct{}{
"set": {},
"setAt": {},
"setd": {},
"setenv": {},
"sha256sum": {},
"skip": {},
"sleep": {},
Expand Down
30 changes: 30 additions & 0 deletions mshell/Evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions mshell/TypeBuiltins.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions tests/success/setenv.msh
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions tests/success/setenv.msh.stdout
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
true
hello
world
false