From df129896f3099c033ae9b937bd186c9a601909df Mon Sep 17 00:00:00 2001 From: Laurence Morgan Date: Sun, 7 Jan 2024 21:47:59 +0000 Subject: [PATCH 01/11] #783 rewrite object parser --- app/app.go | 4 +- config/defaults/profile_any.mx | 2 +- lang/expressions/parse_object.go | 185 +++++++++++-------------- lang/expressions/parse_object_test.go | 53 ++++++- lang/expressions/parse_object_types.go | 129 +++++++++++++++++ version.svg | 2 +- 6 files changed, 264 insertions(+), 111 deletions(-) create mode 100644 lang/expressions/parse_object_types.go diff --git a/app/app.go b/app/app.go index 8a44851d4..cd6b992ef 100644 --- a/app/app.go +++ b/app/app.go @@ -14,8 +14,8 @@ const Name = "murex" const ( version = "%d.%d.%d" Major = 5 - Minor = 3 - Revision = 7000 + Minor = 4 + Revision = 1000 ) // Copyright is the copyright owner string diff --git a/config/defaults/profile_any.mx b/config/defaults/profile_any.mx index 27224b125..683a5506d 100644 --- a/config/defaults/profile_any.mx +++ b/config/defaults/profile_any.mx @@ -674,7 +674,7 @@ autocomplete set docgen %[ ] config define open image %{ - Description: Which mode to render images to the terminal + Description: "Which mode to render images to the terminal" DataType: str Default: auto Options: [ auto compatible kitty iterm terminology sixel ] diff --git a/lang/expressions/parse_object.go b/lang/expressions/parse_object.go index db2d62895..b731d884b 100644 --- a/lang/expressions/parse_object.go +++ b/lang/expressions/parse_object.go @@ -16,45 +16,13 @@ func (tree *ParserT) createObjectAst(exec bool) error { } tree.appendAstWithPrimitive(symbols.ObjectBegin, dt) tree.charPos++ - return nil -} - -type parseObjectT struct { - keyValueR [2][]rune - keyValueI [2]interface{} - stage int - obj map[string]interface{} -} - -func newParseObjectT() *parseObjectT { - o := new(parseObjectT) - o.obj = make(map[string]interface{}) - return o -} - -func (o *parseObjectT) WriteKeyValuePair(pos int) error { - if o.keyValueI[0] == nil { - return fmt.Errorf("object key cannot be null before %d", pos) - } - if len(o.keyValueR[1]) != 0 { - o.keyValueI[1] = string(o.keyValueR[1]) - } - - s, err := types.ConvertGoType(o.keyValueI[0], types.String) - if err != nil { - return err - } - o.obj[s.(string)] = o.keyValueI[1] - o.keyValueR = [2][]rune{nil, nil} - o.keyValueI = [2]interface{}{nil, nil} - o.stage++ return nil } func (tree *ParserT) parseObject(exec bool) ([]rune, *primitives.DataType, error) { start := tree.charPos - o := newParseObjectT() + o := newParseObjectT(tree) for tree.charPos++; tree.charPos < len(tree.expression); tree.charPos++ { r := tree.expression[tree.charPos] @@ -69,16 +37,22 @@ func (tree *ParserT) parseObject(exec bool) ([]rune, *primitives.DataType, error return nil, nil, err } } else { - o.keyValueR[o.stage&1] = append(o.keyValueR[o.stage&1], r) + err := o.AppendRune(r) + if err != nil { + return nil, nil, err + } } case '\'', '"': // quoted string - str, err := tree.parseString(r, r, exec) + value, err := tree.parseString(r, r, exec) + if err != nil { + return nil, nil, err + } + err = o.UpdateInterface(string(value)) if err != nil { return nil, nil, err } - o.keyValueR[o.stage&1] = append(o.keyValueR[o.stage&1], str...) tree.charPos++ case '%': @@ -92,15 +66,21 @@ func (tree *ParserT) parseObject(exec bool) ([]rune, *primitives.DataType, error if err != nil { return nil, nil, err } - o.keyValueR[o.stage&1] = append(o.keyValueR[o.stage&1], value...) + err = o.UpdateInterface(string(value)) + if err != nil { + return nil, nil, err + } default: // string - o.keyValueR[o.stage&1] = append(o.keyValueR[o.stage&1], r) + err := o.AppendRune(r) + if err != nil { + return nil, nil, err + } } case '[': // start nested array - if o.stage&1 == 0 { + if o.stage == 0 { return nil, nil, fmt.Errorf("object keys cannot be an array") } _, dt, err := tree.parseArray(exec) @@ -111,12 +91,15 @@ func (tree *ParserT) parseObject(exec bool) ([]rune, *primitives.DataType, error if err != nil { return nil, nil, err } - o.keyValueI[1] = v.Value + err = o.UpdateInterface(v.Value) + if err != nil { + return nil, nil, err + } tree.charPos++ case '{': // start nested object - if o.stage&1 == 0 { + if o.stage == 0 { return nil, nil, raiseError( tree.expression, nil, tree.charPos, "object keys cannot be another object") } @@ -128,14 +111,17 @@ func (tree *ParserT) parseObject(exec bool) ([]rune, *primitives.DataType, error if err != nil { return nil, nil, err } - o.keyValueI[1] = v.Value + err = o.UpdateInterface(v.Value) + if err != nil { + return nil, nil, err + } tree.charPos++ case '$': switch { case tree.nextChar() == '{': // inline subshell - strOrVal := varFormatting(o.stage & 1) + strOrVal := varFormatting(o.stage) subshell, fn, err := tree.parseSubShell(exec, r, strOrVal) if err != nil { return nil, nil, err @@ -145,34 +131,44 @@ func (tree *ParserT) parseObject(exec bool) ([]rune, *primitives.DataType, error if err != nil { return nil, nil, err } - o.keyValueI[o.stage&1] = val.Value + err = o.UpdateInterface(val.Value) + if err != nil { + return nil, nil, err + } } else { - o.keyValueI[o.stage&1] = string(subshell) + err = o.UpdateInterface(string(subshell)) + if err != nil { + return nil, nil, err + } } default: // inline scalar - strOrVal := varFormatting(o.stage & 1) + strOrVal := varFormatting(o.stage) scalar, v, _, err := tree.parseVarScalar(exec, exec, strOrVal) if err != nil { return nil, nil, err } if exec { - o.keyValueI[o.stage&1] = v + err = o.UpdateInterface(v) + if err != nil { + return nil, nil, err + } } else { - o.keyValueI[o.stage&1] = string(scalar) + err = o.UpdateInterface(string(scalar)) + if err != nil { + return nil, nil, err + } } } case '~': // tilde - o.keyValueI[o.stage&1] = tree.parseVarTilde(exec) + err := o.AppendRune([]rune(tree.parseVarTilde(exec))...) + if err != nil { + return nil, nil, err + } case '@': - // inline array - if o.stage&1 == 0 { - return nil, nil, raiseError( - tree.expression, nil, tree.charPos, "arrays cannot be object keys") - } switch tree.nextChar() { case '{': subshell, fn, err := tree.parseSubShell(exec, r, varAsValue) @@ -184,81 +180,61 @@ func (tree *ParserT) parseObject(exec bool) ([]rune, *primitives.DataType, error if err != nil { return nil, nil, err } - o.keyValueI[o.stage&1] = val.Value + err = o.UpdateInterface(val.Value) + if err != nil { + return nil, nil, err + } } else { - o.keyValueI[o.stage&1] = string(subshell) + err = o.UpdateInterface(string(subshell)) + if err != nil { + return nil, nil, err + } } default: _, v, err := tree.parseVarArray(exec) if err != nil { return nil, nil, err } - o.keyValueI[1] = v + err = o.UpdateInterface(v) + if err != nil { + return nil, nil, err + } } case ':': - if o.stage&1 == 1 { + if o.stage != OBJ_STAGE_KEY { return nil, nil, raiseError( tree.expression, nil, tree.charPos, "invalid symbol ':' expecting ',' or '}' instead") } o.stage++ - if o.keyValueI[0] != nil { - continue - } - o.keyValueI[0] = string(o.keyValueR[0]) case '\n': - if o.stage&1 == 0 { - if len(o.keyValueR[0]) > 0 || o.keyValueI[0] != nil { - return nil, nil, raiseError( - tree.expression, nil, tree.charPos, - "unexpected new line, expecting ':' instead") - } - continue - } - - err := o.WriteKeyValuePair(tree.charPos) + err := o.WriteKeyValuePair() if err != nil { return nil, nil, err } tree.crLf() case ',': - if o.stage&1 == 0 && (len(o.keyValueR[0]) > 0 || o.keyValueI[0] != nil) { - return nil, nil, raiseError( - tree.expression, nil, tree.charPos, fmt.Sprintf( - "invalid symbol '%s', expecting ':' instead", - string(r))) - } - - err := o.WriteKeyValuePair(tree.charPos) + err := o.WriteKeyValuePair() if err != nil { return nil, nil, err } case '}': - if o.stage&1 == 0 { - if len(o.keyValueR[0]) > 0 || o.keyValueI[0] != nil { - return nil, nil, raiseError( - tree.expression, nil, tree.charPos, fmt.Sprintf( - "invalid symbol '%s', expecting ':' instead", - string(r))) - } else { - // empty object - goto endObject - } - } - - err := o.WriteKeyValuePair(tree.charPos) + err := o.WriteKeyValuePair() if err != nil { return nil, nil, err } + goto endObject - if r == '}' { - goto endObject - } + case '\r': + continue - case ' ', '\t', '\r': + case ' ', '\t': + if !o.IsValueUndefined() { + o.keyValue[o.stage].ValueSet = true + } continue default: @@ -266,26 +242,29 @@ func (tree *ParserT) parseObject(exec bool) ([]rune, *primitives.DataType, error v, err := types.ConvertGoType(value, types.Number) if err == nil { // is a number - o.keyValueI[o.stage&1] = v + err = o.UpdateInterface(v) } else { // is a string s := string(value) switch s { case "true": - o.keyValueI[o.stage&1] = true + err = o.UpdateInterface(true) case "false": - o.keyValueI[o.stage&1] = false + err = o.UpdateInterface(false) case "null": - o.keyValueI[o.stage&1] = nil + err = o.UpdateInterface(nil) default: - o.keyValueI[o.stage&1] = s + err = o.UpdateInterface(s) } } + if err != nil { + return nil, nil, err + } } } return nil, nil, raiseError( - tree.expression, nil, tree.charPos, "missing closing bracket (})") + tree.expression, nil, tree.charPos, "missing closing bracket '}'") endObject: value := tree.expression[start:tree.charPos] diff --git a/lang/expressions/parse_object_test.go b/lang/expressions/parse_object_test.go index 6ad9bf6c3..72e7787ca 100644 --- a/lang/expressions/parse_object_test.go +++ b/lang/expressions/parse_object_test.go @@ -14,9 +14,8 @@ func TestParseObject(t *testing.T) { symbol: symbols.ObjectBegin, tests: []expTestT{ { - input: `%{foo:}`, - expected: `{"foo":null}`, - pos: 5, + input: `%{foo:}`, + error: true, }, { input: `%{foo: bar}`, @@ -108,7 +107,9 @@ func TestParseObjectBadGrammar(t *testing.T) { }, { input: `%{foo:bar,,}`, - error: true, + //error: true, + expected: `{"foo":"bar"}`, + pos: 10, }, { input: `%{foo:bar`, @@ -180,3 +181,47 @@ func TestParseObjectNull(t *testing.T) { testParserObject(t, tests) } + +// https://github.com/lmorg/murex/issues/781 +func TestParseObjectZeroLengthString(t *testing.T) { + tests := expTestsT{ + symbol: symbols.ObjectBegin, + tests: []expTestT{ + { + input: `%{foo:''}`, + expected: `{"foo":""}`, + pos: 7, + }, + { + input: `%{foo:""}`, + expected: `{"foo":""}`, + pos: 7, + }, + }, + } + + testParserObject(t, tests) +} + +func TestParseObjectNestedString(t *testing.T) { + tests := expTestsT{ + symbol: symbols.ObjectBegin, + tests: []expTestT{ + { + input: `%{foo:%(hello world)}`, + expected: `{"foo":"hello world"}`, + pos: 19, + }, + { + input: ` +%{foo: %( + hello world +)}`, + expected: `{"foo":"\n\thello world\n"}`, + pos: 24, + }, + }, + } + + testParserObject(t, tests) +} diff --git a/lang/expressions/parse_object_types.go b/lang/expressions/parse_object_types.go new file mode 100644 index 000000000..a1546ce05 --- /dev/null +++ b/lang/expressions/parse_object_types.go @@ -0,0 +1,129 @@ +package expressions + +import ( + "fmt" + + "github.com/lmorg/murex/lang/types" +) + +type parseObjectKvT struct { + Interface any + IsNull bool + Runes []rune + ValueSet bool +} + +func (kv *parseObjectKvT) Value() any { + if kv.Interface != nil || kv.IsNull { + return kv.Interface + } + + return string(kv.Runes) +} + +type parseObjectT struct { + keyValue [2]parseObjectKvT + stage objStageT + obj map[string]interface{} + tree *ParserT +} + +type objStageT int + +const ( + OBJ_STAGE_KEY objStageT = 0 + OBJ_STAGE_VALUE objStageT = 1 +) + +func newParseObjectT(tree *ParserT) *parseObjectT { + o := new(parseObjectT) + o.obj = make(map[string]interface{}) + o.tree = tree + return o +} + +func (o *parseObjectT) WriteKeyValuePair() error { + ko, vo := o.IsKeyUndefined(), o.IsValueUndefined() + + if ko && vo { + return nil // empty + } + + if ko { + return raiseError(o.tree.expression, nil, o.tree.charPos, + fmt.Sprintf("object keys cannot be null no undefined. Key/Value expected before '%s' (pos: %d)", + string(o.tree.expression[o.tree.charPos]), o.tree.charPos)) + } + + if vo { + return raiseError(o.tree.expression, nil, o.tree.charPos, + fmt.Sprintf("object values cannot be undefined. Value expected before '%s' (pos: %d)", + string(o.tree.expression[o.tree.charPos]), o.tree.charPos)) + } + + key, err := types.ConvertGoType(o.keyValue[OBJ_STAGE_KEY].Value(), types.String) + if err != nil { + return err + } + + value := o.keyValue[OBJ_STAGE_VALUE].Value() + + o.obj[key.(string)] = value + + o.keyValue = [2]parseObjectKvT{} + o.stage = 0 + + return nil +} + +const ( + objErrUnexpectedKey = "unexpected object key:\n* new values should follow a colon\n* barewords cannot include whitespace" + objErrUnexpectedValue = "unexpected object value:\n* new keys should follow a comma, or new line.\n* objects should be terminated with a closing curly bracket '}'\n* barewords cannot include whitespace" +) + +func (o *parseObjectT) AppendRune(r ...rune) error { + if o.ExpectNextStage() { + if o.stage == OBJ_STAGE_KEY { + return raiseError(o.tree.expression, nil, o.tree.charPos, objErrUnexpectedKey) + } + + return raiseError(o.tree.expression, nil, o.tree.charPos, objErrUnexpectedValue) + } + + o.keyValue[o.stage].Runes = append(o.keyValue[o.stage].Runes, r...) + return nil +} + +func (o *parseObjectT) UpdateInterface(v interface{}) error { + if o.ExpectNextStage() { + if o.stage == OBJ_STAGE_KEY { + return raiseError(o.tree.expression, nil, o.tree.charPos, objErrUnexpectedKey) + } + + return raiseError(o.tree.expression, nil, o.tree.charPos, objErrUnexpectedValue) + } + + if v == nil { + o.keyValue[o.stage].IsNull = true + } else { + o.keyValue[o.stage].Interface = v + } + o.keyValue[o.stage].ValueSet = true + + return nil +} + +func (o *parseObjectT) IsKeyUndefined() bool { + return o.keyValue[OBJ_STAGE_KEY].Interface == nil && + len(o.keyValue[OBJ_STAGE_KEY].Runes) == 0 +} + +func (o *parseObjectT) IsValueUndefined() bool { + return o.keyValue[OBJ_STAGE_VALUE].Interface == nil && + !o.keyValue[OBJ_STAGE_VALUE].IsNull && + len(o.keyValue[OBJ_STAGE_VALUE].Runes) == 0 +} + +func (o *parseObjectT) ExpectNextStage() bool { + return o.keyValue[o.stage].ValueSet +} diff --git a/version.svg b/version.svg index ad4ce28a2..747f12221 100644 --- a/version.svg +++ b/version.svg @@ -1 +1 @@ -version: 5.3.7000version5.3.7000 +version: 5.4.1000version5.4.1000 From 1c1a2ce4bf4eea67f0fa3a2d6cabf35ab6ccf5bc Mon Sep 17 00:00:00 2001 From: Laurence Morgan Date: Tue, 9 Jan 2024 00:04:23 +0000 Subject: [PATCH 02/11] #786 zero length string env vars returning `null` data type --- lang/variables.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lang/variables.go b/lang/variables.go index f670c9215..2379353da 100644 --- a/lang/variables.go +++ b/lang/variables.go @@ -488,8 +488,8 @@ func (v *Variables) getDataType(name string) string { } // variable not found so lets fallback to the environmental variables - value := os.Getenv(name) - if value != "" { + _, exists = os.LookupEnv(name) + if exists { return getEnvVarDataType(name) } From bc4e2a14d32782b704c76eaa7557553c629e91f3 Mon Sep 17 00:00:00 2001 From: tiymat <138939221+tiymat@users.noreply.github.com> Date: Sun, 14 Jan 2024 01:25:23 -0330 Subject: [PATCH 03/11] docs: add exit number details to documentation of if command --- builtins/core/structs/if_doc.yaml | 20 ++++++++++++++++++++ docs/commands/if.md | 19 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/builtins/core/structs/if_doc.yaml b/builtins/core/structs/if_doc.yaml index d17205814..ae3b2145e 100644 --- a/builtins/core/structs/if_doc.yaml +++ b/builtins/core/structs/if_doc.yaml @@ -86,6 +86,8 @@ ``` Flags: Detail: |- + ### Pipelines and Output + The conditional block can contain entire pipelines - even multiple lines of code let alone a single pipeline - as well as solitary commands as demonstrated in the examples above. However the conditional block does not output STDOUT nor @@ -96,6 +98,24 @@ use either a Murex named pipe to redirect the output, or test or debug flags (depending on your use case) if you only need to occasionally inspect the conditionals output. + + ### Exit Numbers + + When evaluating a command or code block, `if` will treat an exit number less + than 0 as true, and one greater than 0 as false. When the exit number is 0, `if` + will examine the stdout of the command or code block. If there is no output, or + the output is one of the following strings, `if` will evaluate the command or + code block as false. Otherwise, it will be considered true. + + * `0` + * `disabled` + * `fail` + * `failed` + * `false` + * `no` + * `null` + * `off` + Synonyms: - if - "!if" diff --git a/docs/commands/if.md b/docs/commands/if.md index be4fe0c49..90ad5e03f 100644 --- a/docs/commands/if.md +++ b/docs/commands/if.md @@ -89,6 +89,8 @@ if { g somefile.txt } else { ## Detail +### Pipelines and Output + The conditional block can contain entire pipelines - even multiple lines of code let alone a single pipeline - as well as solitary commands as demonstrated in the examples above. However the conditional block does not output STDOUT nor @@ -100,6 +102,23 @@ use either a Murex named pipe to redirect the output, or test or debug flags (depending on your use case) if you only need to occasionally inspect the conditionals output. +### Exit Numbers + +When evaluating a command or code block, `if` will treat an exit number less +than 0 as true, and one greater than 0 as false. When the exit number is 0, `if` +will examine the stdout of the command or code block. If there is no output, or +the output is one of the following strings, `if` will evaluate the command or +code block as false. Otherwise, it will be considered true. + +* `0` +* `disabled` +* `fail` +* `failed` +* `false` +* `no` +* `null` +* `off` + ## Synonyms * `if` From 9cc3d27dec47fcb6a797c1dbfc6bd288b7f24644 Mon Sep 17 00:00:00 2001 From: Laurence Morgan Date: Thu, 18 Jan 2024 23:53:06 +0000 Subject: [PATCH 04/11] mxtty image support --- app/app.go | 2 +- builtins/types/csv/marshal.go | 26 ++++++++++++++++++-------- config/defaults/profile_any.mx | 12 +++++++++++- docs/user-guide/ansi.md | 3 +++ utils/ansi/consts.go | 3 +++ version.svg | 2 +- 6 files changed, 37 insertions(+), 11 deletions(-) diff --git a/app/app.go b/app/app.go index cd6b992ef..83c11740d 100644 --- a/app/app.go +++ b/app/app.go @@ -15,7 +15,7 @@ const ( version = "%d.%d.%d" Major = 5 Minor = 4 - Revision = 1000 + Revision = 2000 ) // Copyright is the copyright owner string diff --git a/builtins/types/csv/marshal.go b/builtins/types/csv/marshal.go index 4cc1e9068..de6b2c7a6 100644 --- a/builtins/types/csv/marshal.go +++ b/builtins/types/csv/marshal.go @@ -5,6 +5,7 @@ import ( enc "encoding/csv" "fmt" "io" + "os" "reflect" "github.com/lmorg/murex/lang" @@ -16,6 +17,8 @@ func marshal(p *lang.Process, iface interface{}) ([]byte, error) { buf := bytes.NewBuffer(b) w := enc.NewWriter(buf) + //if term. + separator, err := p.Config.Get("csv", "separator", types.String) if err != nil { return nil, err @@ -32,8 +35,7 @@ func marshal(p *lang.Process, iface interface{}) ([]byte, error) { return buf.Bytes(), err } } - w.Flush() - return buf.Bytes(), w.Error() + break case [][]string: for i := range v { @@ -42,8 +44,7 @@ func marshal(p *lang.Process, iface interface{}) ([]byte, error) { return buf.Bytes(), err } } - w.Flush() - return buf.Bytes(), w.Error() + break case []interface{}: if len(v) == 0 { @@ -58,21 +59,30 @@ func marshal(p *lang.Process, iface interface{}) ([]byte, error) { return buf.Bytes(), err } } - w.Flush() - return buf.Bytes(), w.Error() + break } err = types.MapToTable(v, func(s []string) error { return w.Write(s) }) if err != nil { return nil, err } - w.Flush() - return buf.Bytes(), w.Error() default: err = fmt.Errorf("cannot marshal %T data into a `%s`", v, typeName) return buf.Bytes(), err } + + var table []byte + if os.Getenv("MXTTY") == "true" { + table = []byte("\x1b_begin;table;{\"format\":\"csv\"}\x1b\\") + } + table = append(table, buf.Bytes()...) + if os.Getenv("MXTTY") == "true" { + table = append(table, []byte("\x1b_end;table\x1b\\")...) + } + + w.Flush() + return table, w.Error() } func unmarshal(p *lang.Process) (interface{}, error) { diff --git a/config/defaults/profile_any.mx b/config/defaults/profile_any.mx index 683a5506d..6fbd0fea6 100644 --- a/config/defaults/profile_any.mx +++ b/config/defaults/profile_any.mx @@ -677,7 +677,7 @@ config define open image %{ Description: "Which mode to render images to the terminal" DataType: str Default: auto - Options: [ auto compatible kitty iterm terminology sixel ] + Options: [ auto compatible mxtty kitty iterm terminology sixel ] } openagent set image { @@ -687,6 +687,7 @@ openagent set image { if { = mode==`auto` } { switch { case { $TMUX } { set mode=compatible } + case { $MXTTY } { set mode=mxtty } case { $ITERM_PROFILE } { set mode=iterm } case { $TERM_PROGRAM == "iTerm.app" } { set mode=iterm } case { $KITTY_WINDOW_ID } { set mode=kitty } @@ -707,6 +708,15 @@ openagent set image { open-image $file } + case mxtty { + if { $SSH_TTY } { + $json = %{ base64: ${base64 -i $file -o -} } + } else { + $json = %{ filename: $file } + } + out "{ESC}_insert;image;$(json){ESC}\\" + } + case kitty { try { kitty icat $file diff --git a/docs/user-guide/ansi.md b/docs/user-guide/ansi.md index db6954fb8..8ab8066fa 100644 --- a/docs/user-guide/ansi.md +++ b/docs/user-guide/ansi.md @@ -109,6 +109,9 @@ var constants = map[string][]byte{ "ALT-7": {27, 55}, "ALT-8": {27, 56}, "ALT-9": {27, 57}, + + // control seqs + "CSI": {27, 91}, } var sgr = map[string][]byte{ diff --git a/utils/ansi/consts.go b/utils/ansi/consts.go index c11d00d88..9fa38b89f 100644 --- a/utils/ansi/consts.go +++ b/utils/ansi/consts.go @@ -93,6 +93,9 @@ var constants = map[string][]byte{ "ALT-7": {27, 55}, "ALT-8": {27, 56}, "ALT-9": {27, 57}, + + // control seqs + "CSI": {27, 91}, } var sgr = map[string][]byte{ diff --git a/version.svg b/version.svg index 747f12221..76d9a9e94 100644 --- a/version.svg +++ b/version.svg @@ -1 +1 @@ -version: 5.4.1000version5.4.1000 +version: 5.4.2000version5.4.2000 From ab5d3759657440846377b4e97836409bcf36ba96 Mon Sep 17 00:00:00 2001 From: Laurence Morgan Date: Wed, 31 Jan 2024 07:27:44 -0800 Subject: [PATCH 05/11] updated integrations inc mxtty support --- builtins/types/csv/marshal.go | 2 +- config/defaults/profile_any.mx | 4 ++-- integrations/coreutils_any.mx | 10 ++++++++++ integrations/coreutils_linux.mx | 3 --- integrations/coreutils_posix.mx | 16 ++++++---------- integrations/mxtty_posix.mx | 28 ++++++++++++++++++++++++++++ 6 files changed, 47 insertions(+), 16 deletions(-) delete mode 100644 integrations/coreutils_linux.mx create mode 100644 integrations/mxtty_posix.mx diff --git a/builtins/types/csv/marshal.go b/builtins/types/csv/marshal.go index de6b2c7a6..b40ef519f 100644 --- a/builtins/types/csv/marshal.go +++ b/builtins/types/csv/marshal.go @@ -74,7 +74,7 @@ func marshal(p *lang.Process, iface interface{}) ([]byte, error) { var table []byte if os.Getenv("MXTTY") == "true" { - table = []byte("\x1b_begin;table;{\"format\":\"csv\"}\x1b\\") + table = []byte("\x1b_begin;table;{\"Format\":\"csv\"}\x1b\\") } table = append(table, buf.Bytes()...) if os.Getenv("MXTTY") == "true" { diff --git a/config/defaults/profile_any.mx b/config/defaults/profile_any.mx index 6fbd0fea6..ea0cd0f29 100644 --- a/config/defaults/profile_any.mx +++ b/config/defaults/profile_any.mx @@ -710,9 +710,9 @@ openagent set image { case mxtty { if { $SSH_TTY } { - $json = %{ base64: ${base64 -i $file -o -} } + $json = %{ Base64: ${base64 -i $file -o -} } } else { - $json = %{ filename: $file } + $json = %{ Filename: $file } } out "{ESC}_insert;image;$(json){ESC}\\" } diff --git a/integrations/coreutils_any.mx b/integrations/coreutils_any.mx index 634b60b3c..59405b3e2 100644 --- a/integrations/coreutils_any.mx +++ b/integrations/coreutils_any.mx @@ -12,4 +12,14 @@ autocomplete set rmdir %[{ IncDirs: true AllowMultiple: true IncManPage: true +}] + +autocomplete set which %[{ + IncExePath: true + AllowMultiple: true +}] + +autocomplete set whereis %[{ + IncExePath: true + AllowMultiple: true }] \ No newline at end of file diff --git a/integrations/coreutils_linux.mx b/integrations/coreutils_linux.mx deleted file mode 100644 index ec8497f69..000000000 --- a/integrations/coreutils_linux.mx +++ /dev/null @@ -1,3 +0,0 @@ -alias ls=ls --color=auto -alias grep=grep --color=auto -alias egrep=egrep --color=auto \ No newline at end of file diff --git a/integrations/coreutils_posix.mx b/integrations/coreutils_posix.mx index 9e626a5e6..7c8983909 100644 --- a/integrations/coreutils_posix.mx +++ b/integrations/coreutils_posix.mx @@ -7,16 +7,6 @@ autocomplete set man-summary %[{ AllowMultiple: true }] -autocomplete set which %[{ - IncExePath: true - AllowMultiple: true -}] - -autocomplete set whereis %[{ - IncExePath: true - AllowMultiple: true -}] - autocomplete set sudo %[ { IncFiles: true @@ -27,3 +17,9 @@ autocomplete set sudo %[ NestedCommand: true } ] + +#alias ls=ls --color=auto +alias grep=grep --color=auto +alias egrep=grep -E --color=auto + +$ENV.CLICOLOR = true \ No newline at end of file diff --git a/integrations/mxtty_posix.mx b/integrations/mxtty_posix.mx new file mode 100644 index 000000000..69122c602 --- /dev/null +++ b/integrations/mxtty_posix.mx @@ -0,0 +1,28 @@ +!if { $MXTTY } then { + echo "zzzzzzzzzzzzzzzzzzz" + return +} + +function ps { + config set proc strict-arrays false + + if { $SELF.TTY } then { + tout str '{ESC}_begin;table{ESC}\' + exec ps @PARAMS + tout str '{ESC}_end;table{ESC}\' + } else { + exec ps @PARAMS + } +} + +function last { + config set proc strict-arrays false + + if { $SELF.TTY } then { + tout str '{ESC}_begin;table;{ "HeadMissing": true }{ESC}\' + exec last @PARAMS + tout str '{ESC}_end;table{ESC}\' + } else { + exec last @PARAMS + } +} \ No newline at end of file From dbad74bff5ec0579704fce0710d7a4c3588605d1 Mon Sep 17 00:00:00 2001 From: Laurence Morgan Date: Wed, 31 Jan 2024 07:32:52 -0800 Subject: [PATCH 06/11] #789 zero length string shouldn't panic when normalising path --- integrations/mxtty_posix.mx | 1 - utils/path_posix.go | 5 +++++ utils/path_windows.go | 5 +++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/integrations/mxtty_posix.mx b/integrations/mxtty_posix.mx index 69122c602..61c37ab6b 100644 --- a/integrations/mxtty_posix.mx +++ b/integrations/mxtty_posix.mx @@ -1,5 +1,4 @@ !if { $MXTTY } then { - echo "zzzzzzzzzzzzzzzzzzz" return } diff --git a/utils/path_posix.go b/utils/path_posix.go index a1eb55dd8..c092dc20f 100644 --- a/utils/path_posix.go +++ b/utils/path_posix.go @@ -13,6 +13,11 @@ import ( // NormalisePath takes a string and returns an absolute path if the string is a // relative path func NormalisePath(path string) string { + // bugfix: https://github.com/lmorg/murex/issues/789 + if len(path) == 0 { + path = "." + } + pwd, err := os.Getwd() if err == nil && path[0] != consts.PathSlash[0] { path = pwd + consts.PathSlash + path diff --git a/utils/path_windows.go b/utils/path_windows.go index 811f7f303..f92791b0f 100644 --- a/utils/path_windows.go +++ b/utils/path_windows.go @@ -16,6 +16,11 @@ var rxPathPrefix *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z]:[\\/]`) // NormalisePath takes a string and returns an absolute path if the string is a // relative path func NormalisePath(path string) string { + // bugfix: https://github.com/lmorg/murex/issues/789 + if len(path) == 0 { + path = "." + } + pwd, err := os.Getwd() if err == nil && path[0] != consts.PathSlash[0] && !rxPathPrefix.MatchString(path) { path = pwd + consts.PathSlash + path From a2b2e4c47e0c888540145bb9f4cf946fb2b4eda2 Mon Sep 17 00:00:00 2001 From: Laurence Morgan Date: Wed, 31 Jan 2024 08:40:00 -0800 Subject: [PATCH 07/11] #788 suppress cache error (it's just a cache) --- app/app.go | 2 +- utils/cache/cachedb/cachedb.go | 6 ++++-- version.svg | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/app.go b/app/app.go index 83c11740d..0fa3897e4 100644 --- a/app/app.go +++ b/app/app.go @@ -15,7 +15,7 @@ const ( version = "%d.%d.%d" Major = 5 Minor = 4 - Revision = 2000 + Revision = 3000 ) // Copyright is the copyright owner string diff --git a/utils/cache/cachedb/cachedb.go b/utils/cache/cachedb/cachedb.go index 3466cca5b..7e9723b0b 100644 --- a/utils/cache/cachedb/cachedb.go +++ b/utils/cache/cachedb/cachedb.go @@ -58,8 +58,10 @@ func CreateTable(namespace string) { } func dbFailed(message string, err error) { - os.Stderr.WriteString(fmt.Sprintf("Error %s: %s: '%s'\n%s\n", message, err.Error(), Path, consts.IssueTrackerURL)) - os.Stderr.WriteString("!!! Disabling persistent cache !!!\n") + if debug.Enabled { + os.Stderr.WriteString(fmt.Sprintf("Error %s: %s: '%s'\n%s\n", message, err.Error(), Path, consts.IssueTrackerURL)) + os.Stderr.WriteString("!!! Disabling persistent cache !!!\n") + } disabled = true } diff --git a/version.svg b/version.svg index 76d9a9e94..5d47c9936 100644 --- a/version.svg +++ b/version.svg @@ -1 +1 @@ -version: 5.4.2000version5.4.2000 +version: 5.4.3000version5.4.3000 From e8398689ae86000a72aea2aeea58fab0a0b1b1f5 Mon Sep 17 00:00:00 2001 From: Laurence Morgan Date: Sun, 4 Feb 2024 22:39:43 +0000 Subject: [PATCH 08/11] deprecate `let` and `=` --- app/app.go | 2 +- builtins/core/typemgmt/let.go | 2 +- builtins/core/typemgmt/math.go | 2 ++ lang/deprecated.go | 12 ++++++++++++ version.svg | 2 +- 5 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 lang/deprecated.go diff --git a/app/app.go b/app/app.go index 0fa3897e4..a41737b3b 100644 --- a/app/app.go +++ b/app/app.go @@ -15,7 +15,7 @@ const ( version = "%d.%d.%d" Major = 5 Minor = 4 - Revision = 3000 + Revision = 3100 ) // Copyright is the copyright owner string diff --git a/builtins/core/typemgmt/let.go b/builtins/core/typemgmt/let.go index 40032ee4b..ed0f042fc 100644 --- a/builtins/core/typemgmt/let.go +++ b/builtins/core/typemgmt/let.go @@ -22,7 +22,7 @@ func init() { } func cmdLet(p *lang.Process) (err error) { - //p.Stdout.SetDataType(types.Null) + lang.Deprecated(p) if !debug.Enabled { defer func() { diff --git a/builtins/core/typemgmt/math.go b/builtins/core/typemgmt/math.go index f5c7f117e..046f26393 100644 --- a/builtins/core/typemgmt/math.go +++ b/builtins/core/typemgmt/math.go @@ -16,6 +16,8 @@ func init() { } func cmdEqu(p *lang.Process) (err error) { + lang.Deprecated(p) + if p.Parameters.Len() == 0 { return errors.New("missing expression") } diff --git a/lang/deprecated.go b/lang/deprecated.go new file mode 100644 index 000000000..34d442d1b --- /dev/null +++ b/lang/deprecated.go @@ -0,0 +1,12 @@ +package lang + +import ( + "fmt" + "os" +) + +func Deprecated(p *Process) { + message := fmt.Sprintf("!!! WARNING: The builtin `%s` has been deprecated and will be removed in the next release\n!!! : Module: %s\n!!! : File: %s\n!!! : Line: %d\n!!! : Column: %d\n", + p.Name.String(), p.FileRef.Source.Module, p.FileRef.Source.Filename, p.FileRef.Line, p.FileRef.Column) + os.Stderr.WriteString(message) +} diff --git a/version.svg b/version.svg index 5d47c9936..3fb1626c7 100644 --- a/version.svg +++ b/version.svg @@ -1 +1 @@ -version: 5.4.3000version5.4.3000 +version: 5.4.3100version5.4.3100 From 3963569a6bb182734f87136bfef1cc72fb84bb6c Mon Sep 17 00:00:00 2001 From: Laurence Morgan Date: Sat, 17 Feb 2024 20:23:51 +0000 Subject: [PATCH 09/11] tmux integration --- integrations/tmux_posix.mx | 50 ++++++++++++++++++++++++++++++++++++++ lang/exec_unix.go | 3 +++ 2 files changed, 53 insertions(+) create mode 100644 integrations/tmux_posix.mx diff --git a/integrations/tmux_posix.mx b/integrations/tmux_posix.mx new file mode 100644 index 000000000..7d1cae997 --- /dev/null +++ b/integrations/tmux_posix.mx @@ -0,0 +1,50 @@ +autocomplete set tmux %[ + { Dynamic: "{ tmux-commands }" } + { Dynamic: "{ tmux-subcommands }" } +] + +private tmux-commands { + # Parse `tmux` command line arguments + tmux start\; list-commands -> [:0] +} + +test unit private tmux-commands %{ + StdoutRegex: '([-a-z]+\n)+' +} + + +private tmux-subcommands { + # Parses `tmux` command line arguments + tmux start\; list-commands -> regexp m/$ARGS[1]/ -> regexp 'f#\[\-([-a-zA-Z0-9]+)#' -> foreach { -> jsplit "" -> format * } -> regexp m/[a-zA-Z0-9]/ -> prefix - +} + +test unit private tmux-subcommands %{ + Parameters: [ attach-session ] + StdoutRegex: '(-[a-zA-Z]\n)+' +} + +function tsplit { + # Splits the current tmux session horizontally and runs a murex code block + + test state cmd { $cmd } + test state pwd { $PWD } + + test state tmux { + (tmux split -h ($SHELL --load-modules -c (cd $PWD; clear; source $cmd))) + } + + test state shell { + ($SHELL --load-modules -c (cd $PWD; clear; source $cmd)) + } + + if { $TMUX } then { + trypipe { + $ARGS[1] -> set cmd + pwd -> set PWD + tmux \ + split -h ($SHELL --load-modules -c (cd $PWD; clear; sleep 4; echo $cmd; source $cmd; sleep 4)) + } + } else { + err "tmux is not running." + } +} \ No newline at end of file diff --git a/lang/exec_unix.go b/lang/exec_unix.go index a2540f3e4..d8cf39abb 100644 --- a/lang/exec_unix.go +++ b/lang/exec_unix.go @@ -22,5 +22,8 @@ func getCmdTokens(p *Process) (exe string, parameters []string, err error) { func osSyscalls(cmd *exec.Cmd, fd int) { cmd.SysProcAttr = &syscall.SysProcAttr{ Ctty: fd, + //Noctty: false, + //Setctty: true, + //Setsid: true, } } From 266cb6a29bb375f6a5babb844e1a286351408977 Mon Sep 17 00:00:00 2001 From: Laurence Morgan Date: Sat, 17 Feb 2024 20:47:49 +0000 Subject: [PATCH 10/11] 6.0 changelog --- app/app.go | 6 ++-- builtins/docs/summaries.go | 2 ++ docs/changelog/README.md | 5 +++ docs/changelog/v6.0.md | 62 +++++++++++++++++++++++++++++++++++++ gen/changelog/v6.0.inc.md | 35 +++++++++++++++++++++ gen/changelog/v6.0_doc.yaml | 18 +++++++++++ version.svg | 2 +- 7 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 docs/changelog/v6.0.md create mode 100644 gen/changelog/v6.0.inc.md create mode 100644 gen/changelog/v6.0_doc.yaml diff --git a/app/app.go b/app/app.go index a41737b3b..55e9eef16 100644 --- a/app/app.go +++ b/app/app.go @@ -13,9 +13,9 @@ const Name = "murex" // Format of version string should be "(major).(minor).(revision) DESCRIPTION" const ( version = "%d.%d.%d" - Major = 5 - Minor = 4 - Revision = 3100 + Major = 6 + Minor = 0 + Revision = 1000 ) // Copyright is the copyright owner string diff --git a/builtins/docs/summaries.go b/builtins/docs/summaries.go index 772b8156c..c6b009ec0 100644 --- a/builtins/docs/summaries.go +++ b/builtins/docs/summaries.go @@ -386,6 +386,7 @@ func init() { "changelog/v5.1": "This release brings new operators and a builtin, all for managing null types. There is also a substantial revamp to readline's responsiveness.", "changelog/v5.2": "The v5.2 release introduces significant new features and improvements for those using Murex as their interactive shell. Many of these features are unique to Murex.", "changelog/v5.3": "Caching has been vastly improved in this release due to a new sqlite3-backed persistent `cache.db`. There have also been some improvements to `[f1]` help pages", + "changelog/v6.0": "Despite this being a new major version release, it is a vary minor update. Aside from a handful of bugfixes, the most significant change is notice of deprecation for `=`, `let`, and `?`.", } Synonym = map[string]string{ @@ -952,5 +953,6 @@ func init() { "changelog/v5.1": "changelog/v5.1", "changelog/v5.2": "changelog/v5.2", "changelog/v5.3": "changelog/v5.3", + "changelog/v6.0": "changelog/v6.0", } } diff --git a/docs/changelog/README.md b/docs/changelog/README.md index 34faf44c2..bac995689 100644 --- a/docs/changelog/README.md +++ b/docs/changelog/README.md @@ -4,6 +4,11 @@ Track new features, any breaking changes, and the release history here. ## Articles +### 17.02.2024 - [v6.0](../changelog/v6.0.md) + +Despite this being a new major version release, it is a vary minor update. Aside from a handful of bugfixes, the most significant change is notice of deprecation for `=`, `let`, and `?`. + + ### 02.01.2024 - [v5.3](../changelog/v5.3.md) Caching has been vastly improved in this release due to a new sqlite3-backed persistent `cache.db`. There have also been some improvements to `[f1]` help pages diff --git a/docs/changelog/v6.0.md b/docs/changelog/v6.0.md new file mode 100644 index 000000000..a1266d3e5 --- /dev/null +++ b/docs/changelog/v6.0.md @@ -0,0 +1,62 @@ +# v6.0 + +Despite this being a new major version release, it is a vary minor update. Aside from a handful of bugfixes, the most significant change is notice of deprecation for `=`, `let`, and `?`. + +## Breaking Changes + +None + +## Deprecation Warnings + +Please read out [compatibility commitment](https://murex.rocks/compatibility.html) to understand how features are deprecated. + +* the `?` pipe will be deprecated to make way for a the ternary operator. You can achieve the same result with ` `, eg `command parameters... | next-command ...` + +* the `=` and `let` builtins are now officially deprecated. They've been marked as deprecated in the documentation for a couple of years but you'll now receive a deprecation warning when using them. This warning will not impact any functions that call them (they bypass the STDOUT and STDERR pipes and write directly to your TTY) but it is still recommended that you update any existing code not to use it. The change is very simple, Murex supported expressions as first class primitives, so you can simply drop the `=` and `let` command names from your expressions + +## Features + +Features marked as **EXPERIMENTAL** are provided without assurances of future breaking changes. All other features are considered stable as part of Murex's [compatibility commitment](https://murex.rocks/compatibility.html). + +* new integrations for the experimental `mxtty` terminal emulator ([Github repo](https://github.com/lmorg/mxtty)) + +## Bug Fixes + +* `which` and `whereis` autocompletions were Linux specific. Now they're enabled for all platforms + +* `grep` and `egrep` aliases were Linux specific. Now they're enabled for all POSIX platforms + +* zero length environment variables are no longer being reported as `null` by `is-null` ([issue #786](https://github.com/lmorg/murex/issues/786)) + +* fixed edge case where a zero length string could generate a panic when normalising paths ([issue #789](https://github.com/lmorg/murex/issues/789)) + +* suppress sqlite3 cache error message. The error doesn't impact the operation of Murex, it just spooks users ([issue #788](https://github.com/lmorg/murex/issues/788)) + +## Special Thanks + +Special thank yous for this release goes to everyone in the discussions group for raising bug reports and their design discussions. + +You rock! + +
+ +Published: 17.02.2024 at 20:47 + +## See Also + +* [Contributing](../Murex/CONTRIBUTING.md): + Guide to contributing to Murex +* [`autocomplete`](../commands/autocomplete.md): + Set definitions for tab-completion in the command line +* [`config`](../commands/config.md): + Query or define Murex runtime settings +* [`export`](../commands/export.md): + Define an environmental variable and set it's value +* [`is-null`](../commands/is-null.md): + Checks if a variable is null or undefined +* [`runtime`](../commands/runtime.md): + Returns runtime information on the internal state of Murex + +
+ +This document was generated from [gen/changelog/v6.0_doc.yaml](https://github.com/lmorg/murex/blob/master/gen/changelog/v6.0_doc.yaml). \ No newline at end of file diff --git a/gen/changelog/v6.0.inc.md b/gen/changelog/v6.0.inc.md new file mode 100644 index 000000000..e58058c63 --- /dev/null +++ b/gen/changelog/v6.0.inc.md @@ -0,0 +1,35 @@ +## Breaking Changes + +None + +## Deprecation Warnings + +Please read out [compatibility commitment](https://murex.rocks/compatibility.html) to understand how features are deprecated. + +* the `?` pipe will be deprecated to make way for a the ternary operator. You can achieve the same result with ` `, eg `command parameters... | next-command ...` + +* the `=` and `let` builtins are now officially deprecated. They've been marked as deprecated in the documentation for a couple of years but you'll now receive a deprecation warning when using them. This warning will not impact any functions that call them (they bypass the STDOUT and STDERR pipes and write directly to your TTY) but it is still recommended that you update any existing code not to use it. The change is very simple, Murex supported expressions as first class primitives, so you can simply drop the `=` and `let` command names from your expressions + +## Features + +Features marked as **EXPERIMENTAL** are provided without assurances of future breaking changes. All other features are considered stable as part of Murex's [compatibility commitment](https://murex.rocks/compatibility.html). + +* new integrations for the experimental `mxtty` terminal emulator ([Github repo](https://github.com/lmorg/mxtty)) + +## Bug Fixes + +* `which` and `whereis` autocompletions were Linux specific. Now they're enabled for all platforms + +* `grep` and `egrep` aliases were Linux specific. Now they're enabled for all POSIX platforms + +* zero length environment variables are no longer being reported as `null` by `is-null` ([issue #786](https://github.com/lmorg/murex/issues/786)) + +* fixed edge case where a zero length string could generate a panic when normalising paths ([issue #789](https://github.com/lmorg/murex/issues/789)) + +* suppress sqlite3 cache error message. The error doesn't impact the operation of Murex, it just spooks users ([issue #788](https://github.com/lmorg/murex/issues/788)) + +## Special Thanks + +Special thank yous for this release goes to everyone in the discussions group for raising bug reports and their design discussions. + +You rock! diff --git a/gen/changelog/v6.0_doc.yaml b/gen/changelog/v6.0_doc.yaml new file mode 100644 index 000000000..40ffbd333 --- /dev/null +++ b/gen/changelog/v6.0_doc.yaml @@ -0,0 +1,18 @@ +- DocumentID: v6.0 + Title: >- + v6.0 + CategoryID: changelog + DateTime: 2024-02-17 20:47 + Summary: >- + Despite this being a new major version release, it is a vary minor update. + Aside from a handful of bugfixes, the most significant change is notice of + deprecation for `=`, `let`, and `?`. + Description: |- + {{ include "gen/changelog/v6.0.inc.md" }} + Related: + - CONTRIBUTING + - autocomplete + - export + - config + - runtime + - is-null diff --git a/version.svg b/version.svg index 3fb1626c7..13c9fda7b 100644 --- a/version.svg +++ b/version.svg @@ -1 +1 @@ -version: 5.4.3100version5.4.3100 +version: 6.0.1000version6.0.1000 From fc8f70cc792c918df52e995c8181884210ec6249 Mon Sep 17 00:00:00 2001 From: Laurence Morgan Date: Sat, 17 Feb 2024 20:53:27 +0000 Subject: [PATCH 11/11] deprecation notice for `?` operator --- behavioural/murex_flags.mx | 17 ----------------- lang/expressions/parse_block.go | 5 +++++ 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/behavioural/murex_flags.mx b/behavioural/murex_flags.mx index 856f83242..c7d5b3524 100755 --- a/behavioural/murex_flags.mx +++ b/behavioural/murex_flags.mx @@ -18,14 +18,6 @@ test: unit function murex.flag.try.pipe { "StdoutRegex": "bar" } -function murex.flag.try.errpipe { - exec: $MUREX_EXE --try -c (err: "foo" ? out "bar") -} - -test: unit function murex.flag.try.errpipe { - "StdoutRegex": "bar" -} - function murex.flag.trypipe.semicolon { exec: $MUREX_EXE --trypipe -c (err: "foo"; out "bar") } @@ -43,12 +35,3 @@ test: unit function murex.flag.trypipe.pipe { "StderrRegex": "foo", "ExitNum": 1 } - -function murex.flag.trypipe.errpipe { - exec: $MUREX_EXE --trypipe -c (err: "foo" ? out "bar") -} - -test: unit function murex.flag.trypipe.errpipe { - "StderrRegex": "Error", - "ExitNum": 1 -} \ No newline at end of file diff --git a/lang/expressions/parse_block.go b/lang/expressions/parse_block.go index c143671e8..291483c67 100644 --- a/lang/expressions/parse_block.go +++ b/lang/expressions/parse_block.go @@ -3,6 +3,7 @@ package expressions import ( "errors" "fmt" + "os" "strings" "github.com/lmorg/murex/lang" @@ -251,6 +252,10 @@ func (blk *BlockT) ParseBlock() error { } case '?': + message := fmt.Sprintf("!!! WARNING: The operator `?` has been deprecated and will be removed in the next release\n!!! : Line: %d\n!!! : Column: %d\n", + blk.lineN, blk.charPos) + os.Stderr.WriteString(message) + if err := blk.append(tree, fn.P_PIPE_ERR, fn.P_FOLLOW_ON|fn.P_METHOD); err != nil { return err }