diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index bab53f5d0..e496907f9 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -137,4 +137,4 @@ community.
-This document was generated from [gen/root/CONTRIBUTING_doc.yaml](https://github.com/lmorg/murex/blob/master/gen/root/CONTRIBUTING_doc.yaml).
+This document was generated from [gen/root/CONTRIBUTING_doc.yaml](https://github.com/lmorg/murex/blob/master/gen/root/CONTRIBUTING_doc.yaml).
\ No newline at end of file
diff --git a/app/app.go b/app/app.go
index 869f09722..8cf373bf5 100644
--- a/app/app.go
+++ b/app/app.go
@@ -16,8 +16,8 @@ const Name = "murex"
// Format of version string should be "$(Major).$(Minor).$(Revision) ($Branch)"
const (
Major = 7
- Minor = 2
- Revision = 1001
+ Minor = 3
+ Revision = 1008
)
var (
diff --git a/config/defaults/profile_preload.mx b/config/defaults/profile_preload.mx
index 6d5cb6357..88e3af275 100644
--- a/config/defaults/profile_preload.mx
+++ b/config/defaults/profile_preload.mx
@@ -14,18 +14,20 @@ if { os darwin && %[ /opt/homebrew/bin/brew ] -> f +x } then {
function define-force-tty-func (CMD: str) {
# Fixes programs which expect STDERR to be a TTY
- if { which $CMD } then {
- function $CMD "{
- # Wrapper script around `$CMD`
+ !if { which $CMD } then {
+ return
+ }
+
+ function $CMD "{
+ # Wrapper script around `$CMD`
- config set proc strict-arrays false
- config set proc force-tty true
+ config set proc strict-arrays false
+ config set proc force-tty true
- exec $CMD \@PARAMS
- }"
+ exec $CMD \@PARAMS
+ }"
- trypipe {
- summary $CMD ${man-summary $CMD}
- }
+ trypipe {
+ summary $CMD man-summary($CMD)
}
}
\ No newline at end of file
diff --git a/docs/apis/lang.UnmarshalData.md b/docs/apis/lang.UnmarshalData.md
index 7a959ee0e..15b2d6dfc 100644
--- a/docs/apis/lang.UnmarshalData.md
+++ b/docs/apis/lang.UnmarshalData.md
@@ -85,11 +85,13 @@ func UnmarshalDataBuffered(parent *Process, b []byte, dataType string) (any, err
fork := parent.Fork(F_BACKGROUND | F_CREATE_STDIN | F_NO_STDOUT | F_NO_STDERR)
defer fork.Kill()
+ fork.Stdin.Open()
_, err := fork.Stdin.Write(b)
- defer fork.Stdin.Close()
+ fork.Stdin.Close()
if err != nil {
return nil, fmt.Errorf("cannot write value to unmarshaller's buffer: %s", err.Error())
}
+
v, err := UnmarshalData(fork.Process, dataType)
if err != nil {
return nil, fmt.Errorf("cannot unmarshal buffer: %s", err.Error())
diff --git a/docs/blog/split_personalities.md b/docs/blog/split_personalities.md
index 5da6b8fb3..e41a727c3 100644
--- a/docs/blog/split_personalities.md
+++ b/docs/blog/split_personalities.md
@@ -217,7 +217,7 @@ The write-many read-once tendencies of the interactive terminal and the
write-once read-many demands of scripting might be difficult to consolidate
but I do think it is achievable and I'm not convinced the current heavy weights
do a good job at addressing those conflicting concerns. Whereas alternative
-shells like [Oil](https://www.oilshell.org/), [Elfish](https://elv.sh/) and
+shells like [Oil](https://www.oilshell.org/), [Elvish](https://elv.sh/) and
[Murex](https://github.com/lmorg/murex) seem to be putting a lot more thought
into this problem and it is really exciting seeing the different ideas that are
being produced.
diff --git a/docs/commands/while.md b/docs/commands/while.md
index 162d97559..85c5865e1 100644
--- a/docs/commands/while.md
+++ b/docs/commands/while.md
@@ -4,15 +4,15 @@
## Description
-`while` loops until loops until **condition** is false.
+`while` loops until **condition** is false.
Normally the **conditional** and executed code block are 2 separate parameters
however you can call `while` with just 1 parameter where the code block acts
-as both the conditional and the code to be ran.
+as both the conditional and the code to be run.
## Usage
-### Until true
+### While true (until false)
```
while { condition } { code-block } ->
@@ -20,7 +20,7 @@ while { condition } { code-block } ->
while { code-block } ->
```
-### Until false
+### While false (until true)
```
!while { condition } { code-block } ->
@@ -54,7 +54,7 @@ true
false
```
-### Until false
+### While false (until true)
`while` **$i** is _NOT_ greater than or equal to **5**
diff --git a/docs/integrations/chatgpt.md b/docs/integrations/chatgpt.md
index d3ccfa91c..0b6d676a0 100644
--- a/docs/integrations/chatgpt.md
+++ b/docs/integrations/chatgpt.md
@@ -21,7 +21,7 @@ To enable, you must add your OpenAI key to your profile, which is typically
`~/.murex_profile` (see [profile](/docs/user-guide/profile.md) for more details):
```
-config set preview-exec-api xxxxx
+config set openai preview-exec-api xxxxx
```
(where **xxxxx** is replaced with your OpenAI API key)
@@ -77,7 +77,7 @@ The **gpt-3.5-turbo** model is used by default because it is quick and cheap.
However you can change that via **preview-exec-model**. For example
```
-config set preview-exec-model gpt-4-turbo
+config set openai preview-exec-model gpt-4-turbo
```
You may need to clear the cache to see any changes:
@@ -86,7 +86,7 @@ You may need to clear the cache to see any changes:
runtime --clear-cache
```
-## Customising the prompt
+## Customizing the prompt
The default prompt passed to ChatGPT can be viewed via `config get openai
preview-exec-prompt`.
diff --git a/docs/parser/README.md b/docs/parser/README.md
index e53db1cd8..3a7201f09 100644
--- a/docs/parser/README.md
+++ b/docs/parser/README.md
@@ -56,15 +56,16 @@ parsed by the following rules of operation
Order of operations:
1. expression or statement discovery
2. sub-shells / sub-expressions
-3. multiplication / division (expressions only)
-4. addition / subtraction (expressions only)
-5. immutable merge
-6. comparisons, eg greater than (expressions only)
-7. logical and (sub-expressions only)
-8. logical or (sub-expressions only)
-9. elvis (expressions only)
-10. assign (expressions only)
-11. _left_ to _right_
+3. variable stage (expressions only)
+4. multiplication / division (expressions only)
+5. addition / subtraction (expressions only)
+6. immutable merge
+7. comparisons, eg greater than (expressions only)
+8. logical and (sub-expressions only)
+9. logical or (sub-expressions only)
+10. elvis (expressions only)
+11. assign (expressions only)
+12. _left_ to _right_
### Expression Or Statement Discovery
@@ -261,7 +262,7 @@ They are supported in both expressions and statements.
### Terminators
-"LF" refers to the life feed character, which is a new line.
+"LF" refers to the line feed character, which is a new line.
| Token | Name | Operation |
|----------|-------------------|-------------------------------------------|
@@ -273,7 +274,7 @@ They are supported in both expressions and statements.
Any character can be escaped via `\` to signal it isn't a token. However some
characters have special meanings when escaped.
-"LF" refers to the life feed character, which is a new line.
+"LF" refers to the line feed character, which is a new line.
| Token | Name | Operation |
|----------|-------------------|--------------------------------------------|
diff --git a/docs/parser/scalar.md b/docs/parser/scalar.md
index f0d9e5f35..b2aefc721 100644
--- a/docs/parser/scalar.md
+++ b/docs/parser/scalar.md
@@ -39,6 +39,17 @@ $(scalar)
_Enclosed_ syntax supports any unicode characters however the variable name
needs to be surrounded by parenthesis. See examples below.
+### Logical-Not Syntax (expressions)
+
+You can negate a variable's logical value in expressions with:
+
+```
+!$(variable)
+```
+
+This syntax resolves the variable's truthy / falsy value and then returns
+the inverse (`true` becomes `false`, and `false` becomes `true`).
+
## Examples
diff --git a/docs/user-guide/integrations.md b/docs/user-guide/integrations.md
index 9204d58be..eefecc866 100644
--- a/docs/user-guide/integrations.md
+++ b/docs/user-guide/integrations.md
@@ -5,7 +5,7 @@
## Description
Murex is highly customizable, as every good shell should be. However the Murex
-community believe that extendability and support for customizations are no
+community believes that extendability and support for customizations are no
substitute for good defaults.
Murex aims to provide you with the best support for your wider command line
diff --git a/docs/user-guide/operators-and-tokens.md b/docs/user-guide/operators-and-tokens.md
index e61a6ed18..46c5fe576 100644
--- a/docs/user-guide/operators-and-tokens.md
+++ b/docs/user-guide/operators-and-tokens.md
@@ -58,15 +58,16 @@ parsed by the following rules of operation
Order of operations:
1. expression or statement discovery
2. sub-shells / sub-expressions
-3. multiplication / division (expressions only)
-4. addition / subtraction (expressions only)
-5. immutable merge
-6. comparisons, eg greater than (expressions only)
-7. logical and (sub-expressions only)
-8. logical or (sub-expressions only)
-9. elvis (expressions only)
-10. assign (expressions only)
-11. _left_ to _right_
+3. variable stage (expressions only)
+4. multiplication / division (expressions only)
+5. addition / subtraction (expressions only)
+6. immutable merge
+7. comparisons, eg greater than (expressions only)
+8. logical and (sub-expressions only)
+9. logical or (sub-expressions only)
+10. elvis (expressions only)
+11. assign (expressions only)
+12. _left_ to _right_
### Expression Or Statement Discovery
@@ -294,4 +295,4 @@ characters have special meanings when escaped.
-This document was generated from [gen/user-guide/operators-tokens_doc.yaml](https://github.com/lmorg/murex/blob/master/gen/user-guide/operators-tokens_doc.yaml).
+This document was generated from [gen/user-guide/operators-tokens_doc.yaml](https://github.com/lmorg/murex/blob/master/gen/user-guide/operators-tokens_doc.yaml).
\ No newline at end of file
diff --git a/docs/user-guide/rosetta-stone.md b/docs/user-guide/rosetta-stone.md
index 843e598c1..97148d14a 100644
--- a/docs/user-guide/rosetta-stone.md
+++ b/docs/user-guide/rosetta-stone.md
@@ -174,4 +174,4 @@ if you want to learn more about shell scripting in Murex.
-This document was generated from [gen/user-guide/rosetta-stone_doc.yaml](https://github.com/lmorg/murex/blob/master/gen/user-guide/rosetta-stone_doc.yaml).
+This document was generated from [gen/user-guide/rosetta-stone_doc.yaml](https://github.com/lmorg/murex/blob/master/gen/user-guide/rosetta-stone_doc.yaml).
\ No newline at end of file
diff --git a/gen/includes/expr-operators-tokens.inc.md b/gen/includes/expr-operators-tokens.inc.md
index c8c95271a..5fd1581d3 100644
--- a/gen/includes/expr-operators-tokens.inc.md
+++ b/gen/includes/expr-operators-tokens.inc.md
@@ -54,15 +54,16 @@ parsed by the following rules of operation
Order of operations:
1. expression or statement discovery
2. sub-shells / sub-expressions
-3. multiplication / division (expressions only)
-4. addition / subtraction (expressions only)
-5. immutable merge
-6. comparisons, eg greater than (expressions only)
-7. logical and (sub-expressions only)
-8. logical or (sub-expressions only)
-9. elvis (expressions only)
-10. assign (expressions only)
-11. _left_ to _right_
+3. variable stage (expressions only)
+4. multiplication / division (expressions only)
+5. addition / subtraction (expressions only)
+6. immutable merge
+7. comparisons, eg greater than (expressions only)
+8. logical and (sub-expressions only)
+9. logical or (sub-expressions only)
+10. elvis (expressions only)
+11. assign (expressions only)
+12. _left_ to _right_
### Expression Or Statement Discovery
diff --git a/gen/parser/variables_doc.yaml b/gen/parser/variables_doc.yaml
index 01baad60d..9328f59cd 100644
--- a/gen/parser/variables_doc.yaml
+++ b/gen/parser/variables_doc.yaml
@@ -40,6 +40,17 @@
_Enclosed_ syntax supports any unicode characters however the variable name
needs to be surrounded by parenthesis. See examples below.
+
+ ### Logical-Not Syntax (expressions)
+
+ You can negate a variable's logical value in expressions with:
+
+ ```
+ !$(variable)
+ ```
+
+ This syntax resolves the variable's truthy / falsy value and then returns
+ the inverse (`true` becomes `false`, and `false` becomes `true`).
Examples: |-
{{ include "gen/includes/parser-var-tokens.inc.md" }}
diff --git a/go.mod b/go.mod
index 2ae9f64f9..1d73fe895 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module github.com/lmorg/murex
-go 1.24.0
+go 1.25.0
require (
github.com/Knetic/govaluate v3.0.0+incompatible
@@ -13,33 +13,32 @@ require (
github.com/hashicorp/hcl v1.0.0
github.com/lmorg/apachelogs v0.0.0-20161115121556-e5f3eae677ad
github.com/lmorg/readline/v4 v4.2.2
- github.com/mattn/go-runewidth v0.0.19
- github.com/mattn/go-sqlite3 v1.14.33
+ github.com/mattn/go-runewidth v0.0.23
+ github.com/mattn/go-sqlite3 v1.14.42
github.com/pelletier/go-toml v1.9.5
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee
- golang.org/x/sys v0.40.0
- golang.org/x/text v0.33.0
+ golang.org/x/sys v0.43.0
+ golang.org/x/text v0.36.0
gopkg.in/yaml.v3 v3.0.1
- modernc.org/sqlite v1.44.3
+ modernc.org/sqlite v1.48.2
)
require (
- github.com/clipperhouse/stringish v0.1.1 // indirect
- github.com/clipperhouse/uax29/v2 v2.3.1 // indirect
+ github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/disintegration/imaging v1.6.2 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
+ github.com/google/go-cmp v0.6.0 // indirect
github.com/google/uuid v1.6.0 // indirect
- github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
- github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
+ github.com/mattn/go-isatty v0.0.21 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/stretchr/testify v1.11.1 // indirect
- golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect
- golang.org/x/image v0.35.0 // indirect
- golang.org/x/mod v0.32.0 // indirect
- golang.org/x/sync v0.19.0 // indirect
- golang.org/x/tools v0.41.0 // indirect
- modernc.org/libc v1.67.6 // indirect
+ golang.org/x/image v0.39.0 // indirect
+ golang.org/x/tools v0.44.0 // indirect
+ modernc.org/libc v1.72.0 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
)
+
+replace github.com/lmorg/readline/v4 => ./local/readline
diff --git a/go.sum b/go.sum
index 574e673a4..ac2925d18 100644
--- a/go.sum
+++ b/go.sum
@@ -6,10 +6,8 @@ github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVk
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
-github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
-github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
-github.com/clipperhouse/uax29/v2 v2.3.1 h1:RjM8gnVbFbgI67SBekIC7ihFpyXwRPYWXn9BZActHbw=
-github.com/clipperhouse/uax29/v2 v2.3.1/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
+github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
+github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -34,16 +32,14 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/lmorg/apachelogs v0.0.0-20161115121556-e5f3eae677ad h1:TQcz4T52CwRmLT4KexBSPAL2XxAWKlsbpPyQ1hg9T50=
github.com/lmorg/apachelogs v0.0.0-20161115121556-e5f3eae677ad/go.mod h1:ZCbRp0gZDkH+2t/flBuXpT4+FyrVodEONI0l5jUwYH0=
-github.com/lmorg/readline/v4 v4.2.2 h1:aR1buzcMO525Omo7fS3H4ecUuONPv3nIENKHcTgrESg=
-github.com/lmorg/readline/v4 v4.2.2/go.mod h1:Zdp/tnRZl0eTssyikMGSp61wOZwxJLw/CtK2sE2f9e8=
-github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
-github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
-github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
-github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
-github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
-github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0=
-github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
+github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
+github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
+github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
+github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
+github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw=
+github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
+github.com/mattn/go-sqlite3 v1.14.42 h1:MigqEP4ZmHw3aIdIT7T+9TLa90Z6smwcthx+Azv4Cgo=
+github.com/mattn/go-sqlite3 v1.14.42/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
@@ -56,41 +52,38 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
-golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
-golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/image v0.35.0 h1:LKjiHdgMtO8z7Fh18nGY6KDcoEtVfsgLDPeLyguqb7I=
-golang.org/x/image v0.35.0/go.mod h1:MwPLTVgvxSASsxdLzKrl8BRFuyqMyGhLwmC+TO1Sybk=
-golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
-golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
-golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
-golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
-golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
-golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/image v0.39.0 h1:skVYidAEVKgn8lZ602XO75asgXBgLj9G/FE3RbuPFww=
+golang.org/x/image v0.39.0/go.mod h1:sIbmppfU+xFLPIG0FoVUTvyBMmgng1/XAMhQ2ft0hpA=
+golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
+golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
+golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
+golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
+golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
+golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
-golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
-golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
-golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
+golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
+golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
+golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
+golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
-modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
-modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
-modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
-modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
-modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
+modernc.org/cc/v4 v4.27.3 h1:uNCgn37E5U09mTv1XgskEVUJ8ADKpmFMPxzGJ0TSo+U=
+modernc.org/cc/v4 v4.27.3/go.mod h1:3YjcbCqhoTTHPycJDRl2WZKKFj0nwcOIPBfEZK0Hdk8=
+modernc.org/ccgo/v4 v4.32.4 h1:L5OB8rpEX4ZsXEQwGozRfJyJSFHbbNVOoQ59DU9/KuU=
+modernc.org/ccgo/v4 v4.32.4/go.mod h1:lY7f+fiTDHfcv6YlRgSkxYfhs+UvOEEzj49jAn2TOx0=
+modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
+modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
-modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
-modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
+modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
+modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
-modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI=
-modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE=
+modernc.org/libc v1.72.0 h1:IEu559v9a0XWjw0DPoVKtXpO2qt5NVLAnFaBbjq+n8c=
+modernc.org/libc v1.72.0/go.mod h1:tTU8DL8A+XLVkEY3x5E/tO7s2Q/q42EtnNWda/L5QhQ=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
@@ -99,8 +92,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
-modernc.org/sqlite v1.44.3 h1:+39JvV/HWMcYslAwRxHb8067w+2zowvFOUrOWIy9PjY=
-modernc.org/sqlite v1.44.3/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
+modernc.org/sqlite v1.48.2 h1:5CnW4uP8joZtA0LedVqLbZV5GD7F/0x91AXeSyjoh5c=
+modernc.org/sqlite v1.48.2/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
diff --git a/integrations/aspell_any.mx b/integrations/aspell_any.mx
index 55ccb9079..3514cc860 100644
--- a/integrations/aspell_any.mx
+++ b/integrations/aspell_any.mx
@@ -1,3 +1,3 @@
-if { which aspell } then {
+if { which aspell && !$(MXTTY) } then {
config set shell spellcheck-enabled true
-}
\ No newline at end of file
+}
diff --git a/integrations/dnf_linux.mx b/integrations/dnf_linux.mx
new file mode 100644
index 000000000..f34bc0d47
--- /dev/null
+++ b/integrations/dnf_linux.mx
@@ -0,0 +1 @@
+define-force-tty-func dnf
\ No newline at end of file
diff --git a/integrations/su_linux.mx b/integrations/su_linux.mx
index a4c832f66..7eea09f2c 100644
--- a/integrations/su_linux.mx
+++ b/integrations/su_linux.mx
@@ -20,3 +20,4 @@ autocomplete set su %[{
IncManPage: true
}]
+define-force-tty-func su
diff --git a/integrations/sudo_posix.mx b/integrations/sudo_posix.mx
new file mode 100644
index 000000000..9e7ecc9df
--- /dev/null
+++ b/integrations/sudo_posix.mx
@@ -0,0 +1 @@
+define-force-tty-func sudo
\ No newline at end of file
diff --git a/lang/define_unmarshal.go b/lang/define_unmarshal.go
index 89012ce50..ee8ee252d 100644
--- a/lang/define_unmarshal.go
+++ b/lang/define_unmarshal.go
@@ -47,11 +47,13 @@ func UnmarshalDataBuffered(parent *Process, b []byte, dataType string) (any, err
fork := parent.Fork(F_BACKGROUND | F_CREATE_STDIN | F_NO_STDOUT | F_NO_STDERR)
defer fork.Kill()
+ fork.Stdin.Open()
_, err := fork.Stdin.Write(b)
- defer fork.Stdin.Close()
+ fork.Stdin.Close()
if err != nil {
return nil, fmt.Errorf("cannot write value to unmarshaller's buffer: %s", err.Error())
}
+
v, err := UnmarshalData(fork.Process, dataType)
if err != nil {
return nil, fmt.Errorf("cannot unmarshal buffer: %s", err.Error())
diff --git a/lang/define_unmarshal_test.go b/lang/define_unmarshal_test.go
index b649f24b0..81ea6fd55 100644
--- a/lang/define_unmarshal_test.go
+++ b/lang/define_unmarshal_test.go
@@ -5,11 +5,36 @@ import (
"testing"
_ "github.com/lmorg/murex/builtins"
+ "github.com/lmorg/murex/debug"
"github.com/lmorg/murex/lang"
"github.com/lmorg/murex/lang/types"
"github.com/lmorg/murex/test/count"
)
+func TestUnmarshalDataBufferedDebug(t *testing.T) {
+ count.Tests(t, 1)
+
+ input := `["a","b","c"]`
+
+ lang.InitEnv()
+
+ debug.Enabled = true
+ t.Cleanup(func() {
+ lang.WaitForProcessCleanup()
+ debug.Enabled = false
+ })
+
+ v, err := lang.UnmarshalDataBuffered(lang.ShellProcess, []byte(input), types.Json)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ if fmt.Sprintf("%v", v) != "[a b c]" {
+ t.Errorf("unexpected output: %v", v)
+ }
+}
+
func TestUnmarshalArrayJsonString(t *testing.T) {
count.Tests(t, 1)
diff --git a/lang/expressions/exp06.go b/lang/expressions/exp06.go
index 7efe2eb3d..189a9d421 100644
--- a/lang/expressions/exp06.go
+++ b/lang/expressions/exp06.go
@@ -31,7 +31,7 @@ func expGtLt(tree *ParserT, compareFloat ltGtFT, compareString ltGtST) error {
value = compareFloat(convertNumber(lv), convertNumber(rv))
case string:
- value = compareString(lv.(string), rv.(string))
+ value = compareString(convertString(lv), convertString(rv))
default:
return raiseError(tree.expression, tree.currentSymbol(), 0, fmt.Sprintf(
@@ -65,3 +65,14 @@ func convertNumber(v any) float64 {
}
return f.(float64)
}
+
+func convertString(v any) string {
+ if s, ok := v.(string); ok {
+ return s
+ }
+ s, err := types.ConvertGoType(v, types.String)
+ if err != nil {
+ return fmt.Sprint(v)
+ }
+ return s.(string)
+}
diff --git a/lang/expressions/exp06_test.go b/lang/expressions/exp06_test.go
index 607b91e96..f38c464e3 100644
--- a/lang/expressions/exp06_test.go
+++ b/lang/expressions/exp06_test.go
@@ -181,3 +181,78 @@ func TestExpLessThanOrEqual(t *testing.T) {
testExpression(t, tests, true)
}
+
+// TestExpCompareNonComparableStrict is a regression test for
+// https://github.com/lmorg/murex/issues/982
+// Comparing a non-comparable type (e.g. array) with a number
+// should return an error, not panic.
+func TestExpCompareNonComparableStrict(t *testing.T) {
+ tests := []expressionTestT{
+ {
+ Expression: `%[1,2,3] < 2`,
+ Error: true,
+ },
+ {
+ Expression: `2 < %[1,2,3]`,
+ Error: true,
+ },
+ {
+ Expression: `%[1,2,3] > 2`,
+ Error: true,
+ },
+ {
+ Expression: `%[1,2,3] <= 2`,
+ Error: true,
+ },
+ {
+ Expression: `%[1,2,3] >= 2`,
+ Error: true,
+ },
+ }
+
+ testExpression(t, tests, true)
+}
+
+// TestExpCompareNonComparableNonStrict is a regression test for
+// https://github.com/lmorg/murex/issues/982
+// In non-strict mode, comparing a non-comparable type with a number
+// previously panicked with "interface conversion: interface {} is float64,
+// not string". After the fix, this should not panic.
+func TestExpCompareNonComparableNonStrict(t *testing.T) {
+ tests := []expressionTestT{
+ {
+ Expression: `%[1,2,3] < 2`,
+ Expected: false,
+ },
+ {
+ Expression: `2 > %[1,2,3]`,
+ Expected: true,
+ },
+ {
+ Expression: `%[1,2,3] > 2`,
+ Expected: true,
+ },
+ {
+ Expression: `2 < %[1,2,3]`,
+ Expected: false,
+ },
+ {
+ Expression: `%[1,2,3] <= 2`,
+ Expected: false,
+ },
+ {
+ Expression: `2 >= %[1,2,3]`,
+ Expected: true,
+ },
+ {
+ Expression: `%[1,2,3] >= 2`,
+ Expected: true,
+ },
+ {
+ Expression: `2 <= %[1,2,3]`,
+ Expected: false,
+ },
+ }
+
+ testExpression(t, tests, false)
+}
diff --git a/lang/expressions/parse_array.go b/lang/expressions/parse_array.go
index 0d8deaf57..3a283b16f 100644
--- a/lang/expressions/parse_array.go
+++ b/lang/expressions/parse_array.go
@@ -146,6 +146,29 @@ func (tree *ParserT) parseArray(exec bool) ([]rune, *primitives.DataType, error)
slice = append(slice, v)
}
+ case '!':
+ if tree.nextChar() == '$' {
+ r, _, _, fn, err := tree.parseVarLogicalNotExpr(exec)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if exec {
+ v, err := fn()
+ if err != nil {
+ return nil, nil, err
+ }
+ slice = append(slice, v.Value)
+ } else {
+ slice = append(slice, string(r))
+ }
+
+ continue
+ }
+
+ value := tree.parseArrayBareword()
+ slice = append(slice, formatArrayValue(value))
+
case '~':
// tilde
home, err := tree.parseVarTilde(exec)
diff --git a/lang/expressions/parse_array_test.go b/lang/expressions/parse_array_test.go
index cbf510605..5341faa45 100644
--- a/lang/expressions/parse_array_test.go
+++ b/lang/expressions/parse_array_test.go
@@ -90,6 +90,11 @@ func TestParseArray(t *testing.T) {
expected: `[1,2,null]`,
pos: 20,
},
+ {
+ input: "%[!$(TestParseArray)]",
+ expected: `[true]`,
+ pos: 19,
+ },
{
input: "%[@TestParseArray]",
expected: `[]`,
@@ -261,3 +266,21 @@ func TestParseArrayNestedExpr(t *testing.T) {
testParserObject(t, tests)
}
+
+func TestParseArrayNot(t *testing.T) {
+ tests := []test.MurexTest{
+ {
+ Block: `TestParseArrayNotTrue = true; %[!$(TestParseArrayNotTrue), !$(TestParseArrayNotTrue)]`,
+ Stdout: `[false,false]`,
+ },
+ {
+ Block: `TestParseArrayNotFalse = false; %[!$(TestParseArrayNotFalse), !$(TestParseArrayNotFalse)]`,
+ Stdout: `[true,true]`,
+ },
+ {
+ Block: `TestParseArrayNotTrue = true; TestParseArrayNotFalse = false; %[!$(TestParseArrayNotTrue), !$(TestParseArrayNotFalse)]`,
+ Stdout: `[false,true]`,
+ },
+ }
+ test.RunMurexTests(tests, t)
+}
diff --git a/lang/expressions/parse_expression.go b/lang/expressions/parse_expression.go
index 2641a9242..e1a3bfcc0 100644
--- a/lang/expressions/parse_expression.go
+++ b/lang/expressions/parse_expression.go
@@ -123,6 +123,19 @@ func (tree *ParserT) parseExpression(exec, incLogicalOps bool) error {
// not like
tree.appendAst(symbols.NotLike)
tree.charPos++
+ case '$':
+ runes, v, mxDt, fn, err := tree.parseVarLogicalNotExpr(exec)
+ if err != nil {
+ return err
+ }
+
+ if exec {
+ dt := primitives.NewFunction(fn)
+ tree.appendAstWithPrimitive(symbols.Scalar, dt, runes...)
+ } else {
+ dt := primitives.NewScalar(mxDt, v)
+ tree.appendAstWithPrimitive(symbols.Scalar, dt, runes...)
+ }
default:
// might be a function
if !isBareChar(tree.nextChar()) {
diff --git a/lang/expressions/parse_object.go b/lang/expressions/parse_object.go
index 71eed8387..58f49f899 100644
--- a/lang/expressions/parse_object.go
+++ b/lang/expressions/parse_object.go
@@ -171,6 +171,37 @@ func (tree *ParserT) parseObject(exec bool) ([]rune, *primitives.DataType, error
}
}
+ case '!':
+ if tree.nextChar() == '$' {
+ r, _, _, fn, err := tree.parseVarLogicalNotExpr(exec)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if exec {
+ val, err := fn()
+ if err != nil {
+ return nil, nil, err
+ }
+ err = o.UpdateInterface(val.Value)
+ if err != nil {
+ return nil, nil, err
+ }
+ } else {
+ err = o.UpdateInterface(string(r))
+ if err != nil {
+ return nil, nil, err
+ }
+ }
+
+ continue
+ }
+
+ err := o.AppendRune(r)
+ if err != nil {
+ return nil, nil, err
+ }
+
case '~':
// tilde
home, err := tree.parseVarTilde(exec)
diff --git a/lang/expressions/parse_object_test.go b/lang/expressions/parse_object_test.go
index b2f1a4b50..013d508e0 100644
--- a/lang/expressions/parse_object_test.go
+++ b/lang/expressions/parse_object_test.go
@@ -77,6 +77,16 @@ func TestParseObject(t *testing.T) {
expected: `{"a":null,"b":[]}`,
pos: 13,
},
+ {
+ input: `%{a:!$(a)}`,
+ expected: `{"a":true}`,
+ pos: 8,
+ },
+ {
+ input: `%{!$(a):b}`,
+ expected: `{"true":"b"}`,
+ pos: 8,
+ },
{
input: `%{nan:-}`,
expected: `{"NaN":"-"}`,
diff --git a/lang/expressions/parse_quote%_test.go b/lang/expressions/parse_quote%_test.go
index caddd48cd..4c3dec202 100644
--- a/lang/expressions/parse_quote%_test.go
+++ b/lang/expressions/parse_quote%_test.go
@@ -89,6 +89,11 @@ func TestParseQuoteParen(t *testing.T) {
expected: `foo--bar`,
pos: 3,
},
+ {
+ input: `%(foo-!$(b)-bar)`,
+ expected: `foo-true-bar`,
+ pos: 2,
+ },
{
input: `%(foo-\$bar-bar)`,
expected: `foo-\-bar`,
diff --git a/lang/expressions/parse_quotes.go b/lang/expressions/parse_quotes.go
index d07c811b5..33fb16276 100644
--- a/lang/expressions/parse_quotes.go
+++ b/lang/expressions/parse_quotes.go
@@ -5,6 +5,7 @@ import (
"github.com/lmorg/murex/lang"
"github.com/lmorg/murex/lang/expressions/symbols"
+ "github.com/lmorg/murex/lang/types"
"github.com/lmorg/murex/utils/ansi"
)
@@ -148,6 +149,26 @@ func (tree *ParserT) parseStringInfix(qEnd rune, exec bool) ([]rune, error) {
}
}
+ case r == '!' && tree.nextChar() == '$':
+ r, _, _, fn, err := tree.parseVarLogicalNotExpr(exec)
+ if err != nil {
+ return nil, err
+ }
+
+ if exec {
+ val, err := fn()
+ if err != nil {
+ return nil, err
+ }
+ s, err := types.ConvertGoType(val.Value, types.String)
+ if err != nil {
+ return nil, err
+ }
+ value = append(value, []rune(s.(string))...)
+ } else {
+ value = append(value, r...)
+ }
+
case r == '~':
// tilde
home, err := tree.parseVarTilde(exec)
diff --git a/lang/expressions/parse_statement.go b/lang/expressions/parse_statement.go
index 7beabf745..eb2b6005e 100644
--- a/lang/expressions/parse_statement.go
+++ b/lang/expressions/parse_statement.go
@@ -444,6 +444,37 @@ func (tree *ParserT) parseStatement(exec bool) error {
}
}
+ case '!':
+ tree.statement.validFunction = false
+ switch {
+ case tree.nextChar() == '$':
+ var tokenise bool
+ tokenise = tree.tokeniseScalar()
+ execScalar := exec && tokenise
+ value, _, _, fn, err := tree.parseVarLogicalNotExpr(execScalar)
+ if err != nil {
+ return raiseError(tree.expression, nil, tree.charPos, err.Error())
+ }
+
+ if execScalar {
+ val, err := fn()
+ if err != nil {
+ return err
+ }
+ s, err := types.ConvertGoType(val.Value, types.String)
+ if err != nil {
+ return err
+ }
+ appendToParam(tree, []rune(s.(string))...)
+ tree.statement.canHaveZeroLenStr = true
+ } else {
+ appendToParam(tree, value...)
+ }
+
+ default:
+ appendToParam(tree, r)
+ }
+
case '@':
tree.statement.validFunction = false
prev := tree.prevChar()
diff --git a/lang/expressions/parse_statement_test.go b/lang/expressions/parse_statement_test.go
index fed2b5c47..4c9a73291 100644
--- a/lang/expressions/parse_statement_test.go
+++ b/lang/expressions/parse_statement_test.go
@@ -252,6 +252,13 @@ func TestParseStatement(t *testing.T) {
},
Exec: false,
},
+ {
+ Statement: `echo !$(bob)`,
+ Args: []string{
+ "echo", "!$(bob)",
+ },
+ Exec: false,
+ },
{
Statement: `echo $bob`,
Args: []string{
@@ -259,6 +266,13 @@ func TestParseStatement(t *testing.T) {
},
Exec: true,
},
+ {
+ Statement: `echo !$(bob)`,
+ Args: []string{
+ "echo", "true",
+ },
+ Exec: true,
+ },
{
Statement: `echo "$bob"`,
Args: []string{
diff --git a/lang/expressions/parse_vars.go b/lang/expressions/parse_vars.go
index a70b3cf9a..67a0fca96 100644
--- a/lang/expressions/parse_vars.go
+++ b/lang/expressions/parse_vars.go
@@ -35,6 +35,49 @@ func (tree *ParserT) parseVarScalarExpr(exec, execScalars bool) ([]rune, any, st
return runes, v, mxDt, nil, err
}
+func (tree *ParserT) parseVarLogicalNotExpr(exec bool) ([]rune, any, string, primitives.FunctionT, error) {
+ startPos := tree.charPos
+
+ if tree.nextChar() != '$' || tree.charPos+2 >= len(tree.expression) || tree.expression[tree.charPos+2] != '(' {
+ return nil, nil, "", nil, raiseError(
+ tree.expression, nil, tree.charPos, "expecting variable token '$(' after logical-not token '!'")
+ }
+
+ tree.charPos += 2
+ start := tree.charPos
+
+ for tree.charPos++; tree.charPos < len(tree.expression); tree.charPos++ {
+ r := tree.expression[tree.charPos]
+ if r == ')' {
+ path := tree.expression[start+1 : tree.charPos]
+ runes := tree.expression[startPos : tree.charPos+1]
+
+ if !exec {
+ return runes, nil, types.Boolean, nil, nil
+ }
+
+ fn := func() (*primitives.Value, error) {
+ value, _, err := tree.getVar(path, varAsValue)
+ if err != nil {
+ return nil, err
+ }
+
+ s, err := types.ConvertGoType(value, types.String)
+ if err != nil {
+ return nil, err
+ }
+
+ return &primitives.Value{Value: !types.IsTrueString(s.(string), 0), DataType: types.Boolean}, nil
+ }
+
+ return runes, nil, types.Boolean, fn, nil
+ }
+ }
+
+ return nil, nil, "", nil, raiseError(
+ tree.expression, nil, tree.charPos, "expecting closing parenthesis, ')', after logical-not variable reference")
+}
+
func (tree *ParserT) parseVarScalar(exec, execScalars bool, strOrVal varFormatting) ([]rune, any, string, error) {
if tree.nextChar() == '(' {
tree.charPos++
diff --git a/lang/expressions/parse_vars_exec_test.go b/lang/expressions/parse_vars_exec_test.go
index 8da0cf9c1..fb3ed339d 100644
--- a/lang/expressions/parse_vars_exec_test.go
+++ b/lang/expressions/parse_vars_exec_test.go
@@ -60,6 +60,85 @@ func TestParseVarsScalar(t *testing.T) {
test.RunMurexTests(tests, t)
}
+func TestParseVarsLogicalNot(t *testing.T) {
+ tests := []test.MurexTest{
+ {
+ Block: `TestParseVarsLogicalNot0=true;!$(TestParseVarsLogicalNot0)`,
+ Stdout: `false`,
+ ExitNum: 1,
+ },
+ {
+ Block: `TestParseVarsLogicalNot1=false;!$(TestParseVarsLogicalNot1)`,
+ Stdout: `true`,
+ },
+ {
+ Block: `TestParseVarsLogicalNot2=1;!$(TestParseVarsLogicalNot2)`,
+ Stdout: `false`,
+ ExitNum: 1,
+ },
+ {
+ Block: `TestParseVarsLogicalNot3=0;!$(TestParseVarsLogicalNot3)`,
+ Stdout: `true`,
+ },
+ }
+
+ test.RunMurexTests(tests, t)
+}
+
+func TestParseVarsLogicalNotStatement(t *testing.T) {
+ tests := []test.MurexTest{
+ {
+ Block: `TestParseVarsLogicalNotStatement0=true;out !$(TestParseVarsLogicalNotStatement0)`,
+ Stdout: "false\n",
+ },
+ {
+ Block: `TestParseVarsLogicalNotStatement1=false;out !$(TestParseVarsLogicalNotStatement1)`,
+ Stdout: "true\n",
+ },
+ {
+ Block: `TestParseVarsLogicalNotStatement2=1;out !$(TestParseVarsLogicalNotStatement2)`,
+ Stdout: "false\n",
+ },
+ {
+ Block: `TestParseVarsLogicalNotStatement3=0;out !$(TestParseVarsLogicalNotStatement3)`,
+ Stdout: "true\n",
+ },
+ }
+
+ test.RunMurexTests(tests, t)
+}
+
+func TestParseVarsLogicalNotBuilders(t *testing.T) {
+ tests := []test.MurexTest{
+ {
+ Block: `TestParseVarsLogicalNotBuilders0=true;%(!$(TestParseVarsLogicalNotBuilders0))`,
+ Stdout: `false`,
+ },
+ {
+ Block: `TestParseVarsLogicalNotBuilders1=false;%(!$(TestParseVarsLogicalNotBuilders1))`,
+ Stdout: `true`,
+ },
+ {
+ Block: `TestParseVarsLogicalNotBuilders2=true;%[!$(TestParseVarsLogicalNotBuilders2)]`,
+ Stdout: `[false]`,
+ },
+ {
+ Block: `TestParseVarsLogicalNotBuilders3=false;%[!$(TestParseVarsLogicalNotBuilders3)]`,
+ Stdout: `[true]`,
+ },
+ {
+ Block: `TestParseVarsLogicalNotBuilders4=true;%{a:!$(TestParseVarsLogicalNotBuilders4)}`,
+ Stdout: `{"a":false}`,
+ },
+ {
+ Block: `TestParseVarsLogicalNotBuilders5=false;%{a:!$(TestParseVarsLogicalNotBuilders5)}`,
+ Stdout: `{"a":true}`,
+ },
+ }
+
+ test.RunMurexTests(tests, t)
+}
+
func TestParseVarsArray(t *testing.T) {
tests := []test.MurexTest{
{
diff --git a/lang/expressions/parse_vars_test.go b/lang/expressions/parse_vars_test.go
index 48ba918ab..cb0289498 100644
--- a/lang/expressions/parse_vars_test.go
+++ b/lang/expressions/parse_vars_test.go
@@ -20,6 +20,11 @@ func TestParseVarsScalarSymbol(t *testing.T) {
expected: `$foo[[/1]]`,
pos: -1,
},
+ {
+ input: `!$(foo)`,
+ expected: `!$(foo)`,
+ pos: -1,
+ },
},
}
diff --git a/lang/process.go b/lang/process.go
index 5c47ccf1d..7166abd57 100644
--- a/lang/process.go
+++ b/lang/process.go
@@ -6,6 +6,7 @@ import (
"io"
"os"
"strings"
+ "sync"
"time"
"github.com/lmorg/murex/app"
@@ -67,8 +68,16 @@ var (
// ShellExitNum is for when running murex in interactive shell mode
ShellExitNum int
+
+ // processCleanupWG tracks asynchronous process cleanup work.
+ processCleanupWG sync.WaitGroup
)
+// WaitForProcessCleanup waits for asynchronous process cleanup goroutines to finish.
+func WaitForProcessCleanup() {
+ processCleanupWG.Wait()
+}
+
func DefineFunction(name string, fn func(*Process) error, StdoutDataType string) {
GoFunctions[name] = fn
MethodStdout.Define(name, StdoutDataType)
@@ -453,7 +462,9 @@ func deregisterProcess(p *Process) {
ForegroundProc.Set(p.Next)
}
+ processCleanupWG.Add(1)
go func() {
+ defer processCleanupWG.Done()
p.State.Set(state.AwaitingGC)
GlobalFIDs.Deregister(p.Id)
if p.HasJobId.Get() {
diff --git a/utils/which/which.go b/utils/which/which.go
index caaefb617..4af354bcc 100644
--- a/utils/which/which.go
+++ b/utils/which/which.go
@@ -2,29 +2,32 @@ package which
import (
"os"
-
- "github.com/lmorg/murex/utils/consts"
+ "os/exec"
+ "path/filepath"
+ "strings"
)
// Which works similarly to the UNIX command with the same name.
-// If the executable is now found in $PATH then a zero length string is returned.
+// If the executable is not found in $PATH then a zero length string is returned.
func Which(cmd string) string {
- _, err := os.Stat(cmd)
- if !os.IsNotExist(err) {
- return cmd
- }
-
- envPath := os.Getenv("PATH")
-
- for _, path := range SplitPath(envPath) {
- filepath := path + consts.PathSlash + cmd
- _, err := os.Stat(filepath)
- if !os.IsNotExist(err) {
- return filepath
+ if strings.ContainsRune(cmd, '/') || strings.ContainsRune(cmd, filepath.Separator) {
+ // cmd is explicitly a path, resolve and return the absolute path if possible
+ fi, err := os.Stat(cmd)
+ if err == nil && fi.Mode().IsRegular() {
+ if abs, err := filepath.Abs(cmd); err == nil {
+ return abs
+ }
+ return cmd
}
+ return ""
}
- return ""
+ // cmd is not explicitly a path, just search $PATH
+ path, err := exec.LookPath(cmd)
+ if err != nil {
+ return ""
+ }
+ return path
}
// WhichIgnoreFail will always return a best guess of the executable
diff --git a/utils/which/which_test.go b/utils/which/which_test.go
index 793950e81..2c552f463 100644
--- a/utils/which/which_test.go
+++ b/utils/which/which_test.go
@@ -23,3 +23,29 @@ func TestWhich(t *testing.T) {
t.Log("$PATH: " + os.Getenv("PATH"))
}
}
+
+func TestWhichDirInCwd(t *testing.T) {
+ count.Tests(t, 1)
+
+ tmp := t.TempDir()
+ // make a dir with the same name as a real executable
+ if err := os.Mkdir(tmp+"/go", 0755); err != nil {
+ t.Fatal(err)
+ }
+
+ old, err := os.Getwd()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Chdir(old)
+
+ if err := os.Chdir(tmp); err != nil {
+ t.Fatal(err)
+ }
+
+ result := Which("go")
+
+ if result == "go" || result == "" {
+ t.Errorf("Which(\"go\") returned %q; expected the resultolved $PATH path", result)
+ }
+}
diff --git a/version.svg b/version.svg
index 5f65855f6..ee7244217 100644
--- a/version.svg
+++ b/version.svg
@@ -1 +1 @@
-
+