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 @@ -Version: 7.2.1001Version7.2.1001 +Version: 7.3.1008Version7.3.1008