diff --git a/.github/prompts/createChangeLog.prompt.md b/.github/prompts/createChangeLog.prompt.md new file mode 100644 index 000000000..cf0cb5809 --- /dev/null +++ b/.github/prompts/createChangeLog.prompt.md @@ -0,0 +1,109 @@ +--- +name: createChangeLog +description: Works through the release PR to compile a change long +argument-hint: versionNumber +--- +# Instructions +- Follow EVERY step +- Do NOT start the following step until the previous step has finished +- Do NOT skip any steps +- This project is `Murex`, found on Github at https://github.com/lmorg/murex +- changelogs are found in `gen/changelog/` +- changelogs consist of 2 (two) files with the naming convention: + 1. `${VERSION}_doc.yaml` + 2. `${VERSION}.inc.md` +- `${VERSION}` is a version number and should follow the format: `v${MAJOR}.{MINOR}` where: + 1. `${MAJOR}` is a numeric value + 2. `${MINOR}` is a numeric value + + +# Step 1: Find Github Pull Request (PR) +- Check for open PRs in Github for only the Murex repository. We are only interested in a PR that is named like `v7.1`. Ignore all other PRs. +- There should only be one PR. If there is more than one PRs, or you are unsure which PR to check, then stop all steps and ask which PR to read. +- Do not read multiple PRs +- Do not read other repositories other than `murex` + +# Step 2: Read all git commits, then summarize changes +From the selected PR: +- Read every git commit and create a list of changes. +- Use git commit messages as a clue for the change. +- If a commit message has a value like `#123` (`#` followed by a number) then this is a Github Issue. You should also read that Github issue to understand the change. +- Multiple git commits might be part of the same change. +- A git commit might contain multiple changes. + +You should output a bullet point list of each change, with a short summary in the following format: +``` +- ${COMPONENT}: ${SUMMARY} ([issue](${ISSUE})) +``` + 1. `${COMPONENT}` is a one word title for the system that is being changed + 2. `${SUMMARY}` is the summary you generate + 3. `${ISSUE}` is a URL to the Github issue -- if an change has a related Github Issue + +Some issues might be links to Github discussions. + +Also include a reference to any contributors either creating related Github issues, github discussions, commits or pull requests. These users should be related to this version and who are not `@lmorg`. + +# Step 3: Grouping changes + +The changes should be grouped into the following groups: +1. **Breaking changes**: these are changes that might impact the users upgrading from previous versions of this project +2. **Features**: these are new features added to the project in this version +3. **Bug Fixes**: these are changes that do not introduce new features but that fix bugs or issues with existing features + +# Step 5: Update changelog +This step refers only to `${VERSION}.inc.md`: +- This file should be in `gen/changelog/`. +- If a file does not exist, then you can create one. +- If a file does exist then review the contents of it and add any changes you've identified if they don't already exist in this document +- This is a markdown document. +- It's contents should match the following: +``` +## Breaking Changes + +- ${COMPONENT}: ${SUMMARY} ([issue](${ISSUE})) + +## v${VERSION}.xxxx + +### Features + +- ${COMPONENT}: ${SUMMARY} ([issue](${ISSUE})) + +### Bug Fixes + +- ${COMPONENT}: ${SUMMARY} ([issue](${ISSUE})) + +## Special Thanks + +Thank yous for this release go to ${LIST_OF_USERS_WHO_CONTRIBUTED} for your code, testing and feedback. + +Also thank you to everyone in the [discussions group](https://github.com/lmorg/murex/discussions) and all who raise bug reports. + +You rock! +``` + +# Step 6: Update summary + +This step refers only to `${VERSION}_doc.yaml`: +- This file should be in `gen/changelog/`. +- If a file does not exist, then you can create one. +- If a file does exist then review the contents of it and add any changes if you think it alters the nature of the version summary. +- This is a YAML document. +- It's contents should match the following: + +``` +- DocumentID: ${VERSION} + Title: >- + ${VERSION} + CategoryID: changelog + DateTime: ${VERSION} + Summary: >- + ${VERSION_SUMMARY} + Description: |- + {{ include "gen/changelog/${VERSION}.inc.md" }} + Related: + - CONTRIBUTING +``` +- ${VERSION} should be replaced with the version number. +- ${DATE} should be replaced with the current date and time, formatted like the following example: 2025-10-23 22:35 +- ${VERSION_SUMMARY} will be a summary of all the changes in this version. + diff --git a/Makefile b/Makefile index 5b2520c56..3c0c955a3 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,9 @@ BUILD_TAGS?=$(shell cat builtins/optional/standard-opts.txt || echo "") # Default target .PHONY: all +all: generate all: build +all: build-wasm # Build the binary .PHONY: build @@ -39,16 +41,29 @@ run: build-dev # Build with dev flags .PHONY: build-dev -build-dev: generate build-dev: GO_FLAGS += -gcflags="-N -l" -race build-dev: BUILD_TAGS := "$(BUILD_TAGS),pprof,trace,no_crash_handler" build-dev: build +# Build with TinyGo instead of default Go binary +.PHONY: build-tinygo +build-tinygo: LDFLAGS=-ldflags "-X github.com/lmorg/murex/app.branch=${BRANCH} -X github.com/lmorg/murex/app.buildDate=${BUILD_DATE}" +build-tinygo: BUILD_TAGS := "$(BUILD_TAGS),tinygo,no_cachedb,no_cmd_select,no_pty" +build-tinygo: + @echo "Building ${BINARY_NAME} using TinyGo..." + tinygo build -x -tags ${BUILD_TAGS} + +# Build WASM +.PHONY: build-wasm +build-wasm: export GOOS=js +build-wasm: export GOARCH=wasm +build-wasm: build-tinygo + # Test .PHONY: test -test: build +test: @mkdir -p ./test/tmp - go test ./... -count 1 -race -covermode=atomic + go test ./... -count 1 -race -covermode=atomic -tags "$(BUILD_TAGS),no_crash_handler" ${BUILD_DIR}/${BINARY_NAME} -c 'g behavioural/*.mx -> foreach f { source $$f }; test run *' # Benchmark diff --git a/app/app.go b/app/app.go index 959f00ef8..5f61d211c 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 = 1 - Revision = 4143 + Minor = 2 + Revision = 79 ) var ( @@ -29,7 +29,7 @@ func Branch() string { return branch } func BuildDate() string { return strings.ReplaceAll(buildDate, "_", " ") } // Copyright is the copyright owner string -const Copyright = "2018-2025 Laurence Morgan" +const Copyright = "2018-2026 Laurence Morgan" // License is the projects software license const License = "GPL v2" diff --git a/builtins/core/autocomplete/autocomplete.go b/builtins/core/autocomplete/autocomplete.go index 2565ff981..b88c3ca9c 100644 --- a/builtins/core/autocomplete/autocomplete.go +++ b/builtins/core/autocomplete/autocomplete.go @@ -28,9 +28,6 @@ func cmdAutocomplete(p *lang.Process) error { case "set": return set(p) - //case "cache-dynamic": - // return cacheDynamic(p) - default: p.Stdout.SetDataType(types.Null) return errors.New("Not a valid mode. Please use `get` or `set`") @@ -57,6 +54,8 @@ func get(p *lang.Process) error { return err } +var ErrEmptyAutocomplete = errors.New("empty autocomplete") + func set(p *lang.Process) error { p.Stdout.SetDataType(types.Null) @@ -93,6 +92,10 @@ func set(p *lang.Process) error { return err } + if len(flags) == 0 { + return ErrEmptyAutocomplete + } + // So we don't have nil values in JSON if flags[0].CacheTTL == 0 { flags[0].CacheTTL = 5 diff --git a/builtins/core/autocomplete/autocomplete_test.go b/builtins/core/autocomplete/autocomplete_test.go index 32bccc40c..8a1abe276 100644 --- a/builtins/core/autocomplete/autocomplete_test.go +++ b/builtins/core/autocomplete/autocomplete_test.go @@ -4,7 +4,9 @@ import ( "testing" _ "github.com/lmorg/murex/builtins" + cmdautocomplete "github.com/lmorg/murex/builtins/core/autocomplete" "github.com/lmorg/murex/lang" + "github.com/lmorg/murex/test" "github.com/lmorg/murex/test/count" ) @@ -88,3 +90,19 @@ func TestAutocomplete(t *testing.T) { t.Logf(" Actual: %s", string(stdout)) } } + +// Bugfix: panic in autocomplete: +// https://github.com/lmorg/murex/issues/953 +func TestAutocompleteIssue953(t *testing.T) { + count.Tests(t, 1) + + tests := []test.MurexTest{ + { + Block: `autocomplete set foobar %[]`, + Stderr: cmdautocomplete.ErrEmptyAutocomplete.Error(), + ExitNum: 1, + }, + } + + test.RunMurexTestsRx(tests, t) +} diff --git a/builtins/core/dag/fanout.go b/builtins/core/dag/fanout.go index 190cff355..5ca6fdf28 100644 --- a/builtins/core/dag/fanout.go +++ b/builtins/core/dag/fanout.go @@ -52,7 +52,7 @@ func cmdFanout(p *lang.Process) error { return err } - dt := flags[fDataType] + dt := flags.GetValue(fDataType).String() if dt == "" { if p.IsMethod { dt = p.Stdin.GetDataType() @@ -65,7 +65,7 @@ func cmdFanout(p *lang.Process) error { } p.Stdout.SetDataType(dt) - parse := flags[fParse] == types.TrueString + parse := flags.GetValue(fParse).Boolean() switch { case len(sBlocks) == 0: @@ -141,7 +141,7 @@ func cmdFanout(p *lang.Process) error { wg.Wait() - if flags[fConcatenate] == types.TrueString { + if flags.GetValue(fConcatenate).Boolean() { for i := range rBlocks { if errs[i] != nil { return fmt.Errorf("error returned from vertex %d:\n%v", i+1, err) diff --git a/builtins/core/datatools/count.go b/builtins/core/datatools/count.go index 6ae1b7462..af898ce5d 100644 --- a/builtins/core/datatools/count.go +++ b/builtins/core/datatools/count.go @@ -74,83 +74,78 @@ func cmdCount(p *lang.Process) error { return err } - if len(flags) == 0 { - flags[argTotal] = types.TrueString - } - - for f := range flags { - switch f { - case argDuplications: - v, err := countDuplications(p) - if err != nil { - p.Stdout.SetDataType(types.Null) - return err - } - - b, err := json.Marshal(v, p.Stdout.IsTTY()) - if err != nil { - p.Stdout.SetDataType(types.Null) - return err - } + switch { + case flags.GetValue(argDuplications).Boolean(): + v, err := countDuplications(p) + if err != nil { + p.Stdout.SetDataType(types.Null) + return err + } - p.Stdout.SetDataType(types.Json) - _, err = p.Stdout.Write(b) + b, err := json.Marshal(v, p.Stdout.IsTTY()) + if err != nil { + p.Stdout.SetDataType(types.Null) return err + } - case argUnique: - v, err := countUnique(p) - if err != nil { - p.Stdout.SetDataType(types.Null) - return err - } + p.Stdout.SetDataType(types.Json) + _, err = p.Stdout.Write(b) + return err - p.Stdout.SetDataType(types.Integer) - _, err = p.Stdout.Write([]byte(strconv.Itoa(v))) + case flags.GetValue(argUnique).Boolean(): + v, err := countUnique(p) + if err != nil { + p.Stdout.SetDataType(types.Null) return err + } - case argTotal: - v, err := countTotal(p) - if err != nil { - p.Stdout.SetDataType(types.Null) - return err - } + p.Stdout.SetDataType(types.Integer) + _, err = p.Stdout.Write([]byte(strconv.Itoa(v))) + return err - p.Stdout.SetDataType(types.Integer) - _, err = p.Stdout.Write([]byte(strconv.Itoa(v))) + case flags.GetValue(argSum).Boolean(), flags.GetValue(argSumStrict).Boolean(): + f, err := countSum(p, flags.GetValue(argSumStrict).Boolean()) + if err != nil { + p.Stdout.SetDataType(types.Null) return err + } - case argSum, argSumStrict: - f, err := countSum(p, flags[argSumStrict] == types.TrueString) - if err != nil { - p.Stdout.SetDataType(types.Null) - return err - } + p.Stdout.SetDataType(types.Number) + _, err = p.Stdout.Write([]byte(types.FloatToString(f))) + return err - p.Stdout.SetDataType(types.Number) - _, err = p.Stdout.Write([]byte(types.FloatToString(f))) + case flags.GetValue(argBytes).Boolean(): + p.Stdout.SetDataType(types.Integer) + b, err := p.Stdin.ReadAll() + if err != nil { return err + } + _, err = p.Stdout.Write([]byte(fmt.Sprintf("%d", len(b)))) + return err - case argBytes: - p.Stdout.SetDataType(types.Integer) - b, err := p.Stdin.ReadAll() - if err != nil { - return err - } - _, err = p.Stdout.Write([]byte(fmt.Sprintf("%d", len(b)))) + case flags.GetValue(argRunes).Boolean(): + p.Stdout.SetDataType(types.Integer) + b, err := p.Stdin.ReadAll() + if err != nil { return err + } + _, err = p.Stdout.Write([]byte(fmt.Sprintf("%d", utf8.RuneCount(b)))) + return err - case argRunes: - p.Stdout.SetDataType(types.Integer) - b, err := p.Stdin.ReadAll() - if err != nil { - return err - } - _, err = p.Stdout.Write([]byte(fmt.Sprintf("%d", utf8.RuneCount(b)))) + case flags.GetValue(argTotal).Boolean(): + fallthrough + + default: + v, err := countTotal(p) + if err != nil { + p.Stdout.SetDataType(types.Null) return err } - } - return nil + p.Stdout.SetDataType(types.Integer) + _, err = p.Stdout.Write([]byte(strconv.Itoa(v))) + return err + } } func countDuplications(p *lang.Process) (map[string]int, error) { diff --git a/builtins/core/datatools/structkeys.go b/builtins/core/datatools/structkeys.go index 9cafbf6bf..84f8b4db9 100644 --- a/builtins/core/datatools/structkeys.go +++ b/builtins/core/datatools/structkeys.go @@ -32,19 +32,18 @@ func cmdStructKeys(p *lang.Process) error { return err } - separator := flags["--separator"] + separator := flags.GetValue("--separator").String() if separator == "" { separator = "/" } - depth := flags["--depth"] - if depth == "" && len(additional) == 1 { - depth = additional[0] + depth := flags.GetValue("--depth").Integer() + if depth == 0 && len(additional) == 1 { + depth, _ = strconv.Atoi(additional[0]) } - nDeep, _ := strconv.Atoi(depth) - if nDeep < 1 { - nDeep = -1 + if depth == 0 { + depth = -1 } dt := p.Stdin.GetDataType() @@ -60,7 +59,7 @@ func cmdStructKeys(p *lang.Process) error { return err } - err = objectkeys.Recursive(p.Context, "", v, dt, separator, aw.WriteString, nDeep) + err = objectkeys.Recursive(p.Context, "", v, dt, separator, aw.WriteString, depth) if err != nil { return err } diff --git a/builtins/core/escape/escape.go b/builtins/core/escape/escape.go index bef16dc06..da40a4250 100644 --- a/builtins/core/escape/escape.go +++ b/builtins/core/escape/escape.go @@ -25,7 +25,7 @@ func cmdEscape(p *lang.Process) error { p.Stdout.SetDataType(types.String) var str string - if p.Parameters.Len() == 0 { + if p.IsMethod { b, err := p.Stdin.ReadAll() if err != nil { return err @@ -56,7 +56,7 @@ func cmdHtml(p *lang.Process) error { p.Stdout.SetDataType(types.String) var str string - if p.Parameters.Len() == 0 { + if p.IsMethod { b, err := p.Stdin.ReadAll() if err != nil { return err @@ -83,7 +83,7 @@ func cmdUrl(p *lang.Process) (err error) { p.Stdout.SetDataType(types.String) var str string - if p.Parameters.Len() == 0 { + if p.IsMethod { b, err := p.Stdin.ReadAll() if err != nil { return err diff --git a/builtins/core/escape/escape_test.go b/builtins/core/escape/escape_test.go index 14cf4aa09..5f5b514bf 100644 --- a/builtins/core/escape/escape_test.go +++ b/builtins/core/escape/escape_test.go @@ -1,43 +1,42 @@ -package escape +package escape_test import ( "testing" - _ "github.com/lmorg/murex/builtins/core/expressions" - _ "github.com/lmorg/murex/builtins/types/string" - "github.com/lmorg/murex/lang/types" + _ "github.com/lmorg/murex/builtins" "github.com/lmorg/murex/test" ) func TestEscape(t *testing.T) { - // as stdin - - test.RunMethodTest( - t, cmdEscape, "escape", - `hello world`, types.String, nil, - `"hello world"`, nil) - - test.RunMethodTest( - t, cmdEscape, "escape", - `"hello world"`, types.String, nil, - `"\"hello world\""`, nil) - - // as a parameter - - test.RunMethodTest( - t, cmdEscape, "escape", - ``, types.String, []string{`hello`, `world`}, - `"hello world"`, nil) - - test.RunMethodTest( - t, cmdEscape, "escape", - ``, types.String, []string{"hello world"}, - `"hello world"`, nil) - - test.RunMethodTest( - t, cmdEscape, "escape", - ``, types.String, []string{`"hello world"`}, - `"\"hello world\""`, nil) + tests := []test.MurexTest{ + // method + + { + Block: `tout str hello world -> escape`, + Stdout: `"hello world"`, + }, + { + Block: `tout str '"hello world"' -> escape`, + Stdout: `"\"hello world\""`, + }, + + // parameter + + { + Block: `escape hello world`, + Stdout: `"hello world"`, + }, + { + Block: `escape "hello world"`, + Stdout: `"hello world"`, + }, + { + Block: `escape '"hello world"'`, + Stdout: `"\"hello world\""`, + }, + } + + test.RunMurexTests(tests, t) } func TestEscapeBang(t *testing.T) { @@ -67,35 +66,36 @@ func TestEscapeBang(t *testing.T) { test.RunMurexTests(tests, t) } -func TestHtml(t *testing.T) { - // as stdin - - test.RunMethodTest( - t, cmdHtml, "eschtml", - `foo & bar`, types.String, nil, - `foo & bar`, nil) - - test.RunMethodTest( - t, cmdHtml, "eschtml", - `"foo & bar"`, types.String, nil, - `"foo & bar"`, nil) +func TestHTML(t *testing.T) { + tests := []test.MurexTest{ + // method - // as a parameter + { + Block: `tout str foo & bar -> eschtml`, + Stdout: `foo & bar`, + }, + { + Block: `tout str '"foo & bar"' -> eschtml`, + Stdout: `"foo & bar"`, + }, - test.RunMethodTest( - t, cmdHtml, "escape", - ``, types.String, []string{`foo`, `&`, `bar`}, - `foo & bar`, nil) + // parameter - test.RunMethodTest( - t, cmdHtml, "escape", - ``, types.String, []string{"foo & bar"}, - `foo & bar`, nil) + { + Block: `eschtml foo & bar`, + Stdout: `foo & bar`, + }, + { + Block: `eschtml "foo & bar"`, + Stdout: `foo & bar`, + }, + { + Block: `eschtml '"foo & bar"'`, + Stdout: `"foo & bar"`, + }, + } - test.RunMethodTest( - t, cmdHtml, "escape", - ``, types.String, []string{`"foo & bar"`}, - `"foo & bar"`, nil) + test.RunMurexTests(tests, t) } func TestHtmlBang(t *testing.T) { @@ -110,25 +110,27 @@ func TestHtmlBang(t *testing.T) { } func TestUrl(t *testing.T) { - // as stdin - - test.RunMethodTest( - t, cmdUrl, "escurl", - `foo bar`, types.String, nil, - `foo%20bar`, nil) + tests := []test.MurexTest{ + // method - // as a parameter + { + Block: `tout str foo bar -> escurl`, + Stdout: `foo%20bar`, + }, - test.RunMethodTest( - t, cmdUrl, "escurl", - ``, types.String, []string{`foo`, `bar`}, - `foo%20bar`, nil) + // parameter - test.RunMethodTest( - t, cmdUrl, "escurl", - ``, types.String, []string{"foo bar"}, - `foo%20bar`, nil) + { + Block: `escurl foo bar`, + Stdout: `foo%20bar`, + }, + { + Block: `escurl "foo bar"`, + Stdout: `foo%20bar`, + }, + } + test.RunMurexTests(tests, t) } func TestUrlBang(t *testing.T) { @@ -143,16 +145,20 @@ func TestUrlBang(t *testing.T) { } func TestCli(t *testing.T) { - // as stdin + tests := []test.MurexTest{ + // method - test.RunMethodTest( - t, cmdEscapeCli, "esccli", - `foo bar`, types.String, nil, - "foo\\ bar\n", nil) -} + { + Block: `tout str foo bar -> esccli`, + Stdout: "foo\\ bar\n", + }, + { + Block: `tout str '"foo bar"' -> esccli`, + Stdout: "\\\"foo\\ bar\\\"\n", + }, + + // parameter -func TestCli2(t *testing.T) { - tests := []test.MurexTest{ { Block: `esccli foo bar`, Stdout: "foo bar\n", diff --git a/builtins/core/httpclient/client.go b/builtins/core/httpclient/client.go index 313cd6313..e0af59920 100644 --- a/builtins/core/httpclient/client.go +++ b/builtins/core/httpclient/client.go @@ -2,7 +2,6 @@ package httpclient import ( "context" - "crypto/tls" "net" "net/http" neturl "net/url" @@ -24,58 +23,27 @@ const ( var rxHttpProto = regexp.MustCompile(`(?i)^http(s)?://`) // Request generates a HTTP request -func Request(ctx context.Context, method, url string, body stdio.Io, conf *config.Config, setTimeout bool) (response *http.Response, err error) { - toStr, err := conf.Get("http", "timeout", types.String) +func Request(ctx context.Context, method, url string, body stdio.Io, conf *config.Config, setTimeout bool) (*http.Response, error) { + client, err := createClient(conf, setTimeout) if err != nil { - return - } - toDur, err := time.ParseDuration(toStr.(string) + "s") - if err != nil { - return - } - - insecure, err := conf.Get("http", "insecure", types.Boolean) - if err != nil { - return - } - - var client *http.Client - if setTimeout { - tr := http.Transport{ - Dial: dialTimeout(toDur, toDur), - TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure.(bool)}, - } - - client = &http.Client{ - Timeout: toDur, - Transport: &tr, - } - - } else { - tr := http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure.(bool)}, - } - - client = &http.Client{ - Transport: &tr, - } + return nil, err } userAgent, err := conf.Get("http", "user-agent", types.String) if err != nil { - return + return nil, err } request, err := http.NewRequest(method, url, body) if err != nil { - return + return nil, err } request = request.WithContext(ctx) urlParsed, err := neturl.Parse(url) if err != nil { - return + return nil, err } if body != nil { @@ -92,16 +60,14 @@ func Request(ctx context.Context, method, url string, body stdio.Io, conf *confi redirects, err := conf.Get("http", "redirect", types.Boolean) if err != nil { - return + return nil, err } if redirects.(bool) { - response, err = client.Do(request) - } else { - response, err = client.Transport.RoundTrip(request) + return client.Do(request) } - return + return client.Transport.RoundTrip(request) } // dialTimeout function unashamedly copy and pasted from: diff --git a/builtins/core/httpclient/client_std.go b/builtins/core/httpclient/client_std.go new file mode 100644 index 000000000..3d399f99a --- /dev/null +++ b/builtins/core/httpclient/client_std.go @@ -0,0 +1,44 @@ +//go:build !tinygo +// +build !tinygo + +package httpclient + +import ( + "crypto/tls" + "net/http" + "time" + + "github.com/lmorg/murex/config" + "github.com/lmorg/murex/lang/types" +) + +func createClient(conf *config.Config, setTimeout bool) (*http.Client, error) { + toStr, err := conf.Get("http", "timeout", types.String) + if err != nil { + return nil, err + } + toDur, err := time.ParseDuration(toStr.(string) + "s") + if err != nil { + return nil, err + } + + insecure, err := conf.Get("http", "insecure", types.Boolean) + if err != nil { + return nil, err + } + + if setTimeout { + tr := http.Transport{ + Dial: dialTimeout(toDur, toDur), + TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure.(bool)}, + } + + return &http.Client{Timeout: toDur, Transport: &tr}, nil + } + + tr := http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure.(bool)}, + } + + return &http.Client{Transport: &tr}, nil +} diff --git a/builtins/core/httpclient/client_tinygo.go b/builtins/core/httpclient/client_tinygo.go new file mode 100644 index 000000000..d201c36aa --- /dev/null +++ b/builtins/core/httpclient/client_tinygo.go @@ -0,0 +1,14 @@ +//go:build tinygo +// +build tinygo + +package httpclient + +import ( + "net/http" + + "github.com/lmorg/murex/config" +) + +func createClient(conf *config.Config, setTimeout bool) (*http.Client, error) { + return &http.Client{}, nil +} diff --git a/builtins/core/io/lockfile.go b/builtins/core/io/lockfile.go index 934b2636e..c7fc7bb16 100644 --- a/builtins/core/io/lockfile.go +++ b/builtins/core/io/lockfile.go @@ -77,5 +77,5 @@ func fileExists(path string) bool { } func lockFilePath(key string) string { - return consts.TempDir + key + ".lockfile" + return consts.TmpDir() + key + ".lockfile" } diff --git a/builtins/core/io/read.go b/builtins/core/io/read.go index 55f6556d9..7589c7b6a 100644 --- a/builtins/core/io/read.go +++ b/builtins/core/io/read.go @@ -58,12 +58,12 @@ func read(p *lang.Process, dt string, paramAdjust int) error { } if len(additional) == 0 { - prompt = flags[flagReadPrompt] - varName = flags[flagReadVariable] - defaultVal = flags[flagReadDefault] - datatype := flags[flagReadDataType] - mask = flags[flagReadMask] - complete = flags[flagReadComplete] + prompt = flags.GetValue(flagReadPrompt).String() + varName = flags.GetValue(flagReadVariable).String() + defaultVal = flags.GetValue(flagReadDefault).String() + datatype := flags.GetValue(flagReadDataType).String() + mask = flags.GetValue(flagReadMask).String() + complete = flags.GetValue(flagReadComplete).String() if datatype != "" { dt = datatype diff --git a/builtins/core/io/tmp.go b/builtins/core/io/tmp.go index 54080d4de..c835aac32 100644 --- a/builtins/core/io/tmp.go +++ b/builtins/core/io/tmp.go @@ -33,7 +33,7 @@ func cmdTempFile(p *lang.Process) error { return err } - name := consts.TempDir + hex.EncodeToString(h.Sum(nil)) + "-" + strconv.Itoa(os.Getpid()) + ext + name := consts.TmpDir() + hex.EncodeToString(h.Sum(nil)) + "-" + strconv.Itoa(os.Getpid()) + ext file, err := os.Create(name) if err != nil { diff --git a/builtins/core/management/shell.go b/builtins/core/management/shell.go index 00f762639..f06d9a047 100644 --- a/builtins/core/management/shell.go +++ b/builtins/core/management/shell.go @@ -39,7 +39,7 @@ func cmdArgs(p *lang.Process) (err error) { type flags struct { Self string - Flags map[string]string + Flags map[string]any Additional []string Error string } @@ -57,11 +57,13 @@ func cmdArgs(p *lang.Process) (err error) { jObj.Self = p.Scope.Name.String() } - jObj.Flags, jObj.Additional, err = parameters.ParseFlags(params, &args) + var flagsT *parameters.FlagsT + flagsT, jObj.Additional, err = parameters.ParseFlags(params, &args) if err != nil { jObj.Error = err.Error() p.ExitNum = 1 } + jObj.Flags = flagsT.GetMap() b, err = json.Marshal(jObj, false) if err != nil { @@ -192,7 +194,7 @@ func cmdManParser(p *lang.Process) error { } var b []byte - if cmdFlags[manArgDescriptions] == types.TrueString { + if cmdFlags.GetValue(manArgDescriptions).Boolean() { b, err = json.Marshal(descriptions, p.Stdout.IsTTY()) } else { b, err = json.Marshal(flags, p.Stdout.IsTTY()) diff --git a/builtins/core/management/shell_doc.yaml b/builtins/core/management/shell_doc.yaml index 8990c9e8c..4c7a51c28 100644 --- a/builtins/core/management/shell_doc.yaml +++ b/builtins/core/management/shell_doc.yaml @@ -62,6 +62,8 @@ Title: >+ Parse Murex Source: `murex-parser` CategoryID: commands + SubCategoryIDs: + - commands.lang Summary: >- Runs the Murex parser against a block of code Description: |- @@ -113,10 +115,51 @@ {{ include "examples/flags.mx" }} ``` Flags: - Detail: + Detail: |- + ### Flags vs Parameters + + #### Flags + + Flags are any values pass via `-` or `--` prefixed labels. For example + `--datatype str` would assign the value `str` to the flag name `--datatype`. + + Flags can be `str`, `int` `num` `bool`. These values will be type checked and + `args` will return an error if a user passes (for example) alpha character to + a numeric flag. + + Boolean flags do not require `true` nor `false` values to be included; the + absence of a boolean flag automatically sets it to _false_, while the presence + automatically sets it to _true_. + + #### Parameters + + Parameters are any values that are not assigned to a flag. For example + `cat file1.txt file2.txt file3.txt`. + + #### Allowing Flag-like Parameters + + Sometimes you'll want parameters that look like flags. For example + `calculator -3 + 2` where `-3` might normally be considered a flag. + + There are two ways you can force `args` to read values as a parameter: + + 1. **User controlled**: the user can use the `--` flag to denote that everything + which follows is a parameter. eg `calculator -- -3 + 2`. + + 2. **Developer controlled**: A better option is to enable `StrictFlagPlacement` + and have the first parameter be non-flag option. Eg + `calculator print -3 + 2`. With `StrictFlagPlacement`, anything including + and proceeding a parameter will always be parsed as a parameter regardless + of whether it looks like a flag or not. + + (here, `calculator` is a hypothetical command) Synonyms: Related: - reserved-vars + - str + - int + - num + - bool @@ -173,4 +216,4 @@ Related: - murex-docs - summary - - man-summary \ No newline at end of file + - man-summary diff --git a/builtins/core/pipe/pipe.go b/builtins/core/pipe/pipe.go index a2e5e1f2d..5e0ab6605 100644 --- a/builtins/core/pipe/pipe.go +++ b/builtins/core/pipe/pipe.go @@ -42,12 +42,12 @@ func cmdPipe(p *lang.Process) error { return errors.New("no name specified for named pipe. Usage: `pipe name [ --pipe-type creation-data ]") } - if len(flags) > 1 { - return errors.New("too many types of pipe specified. Please use only one flag per") + if flags.Len() > 1 { + return errors.New("too many types of pipe specified. Please use only one flag per command invocation") } - for flag := range flags { - return lang.GlobalPipes.CreatePipe(additional[0], flag[2:], flags[flag]) + for flag := range flags.GetMap() { + return lang.GlobalPipes.CreatePipe(additional[0], flag[2:], flags.GetValue(flag).String()) } for _, name := range additional { diff --git a/builtins/core/pretty/pretty.go b/builtins/core/pretty/pretty.go index 6b09e0a71..c3f02d231 100644 --- a/builtins/core/pretty/pretty.go +++ b/builtins/core/pretty/pretty.go @@ -48,11 +48,11 @@ func cmdPretty(p *lang.Process) error { } switch { - case flags[fDataType] != "": - return prettyType(p, flags[fDataType], flags[fStrict] == types.TrueString) + case flags.GetValue(fDataType).String() != "": + return prettyType(p, flags.GetValue(fDataType).String(), flags.GetValue(fStrict).Boolean()) default: - return prettyType(p, p.Stdin.GetDataType(), flags[fStrict] == types.TrueString) + return prettyType(p, p.Stdin.GetDataType(), flags.GetValue(fStrict).Boolean()) } } diff --git a/builtins/core/processes/fid-list.go b/builtins/core/processes/fid-list.go index 3c7e68ea6..00c45169b 100644 --- a/builtins/core/processes/fid-list.go +++ b/builtins/core/processes/fid-list.go @@ -4,15 +4,14 @@ import ( "fmt" "strings" - "github.com/lmorg/murex/config/defaults" "github.com/lmorg/murex/lang" "github.com/lmorg/murex/lang/state" "github.com/lmorg/murex/lang/types" "github.com/lmorg/murex/utils/json" ) -func init() { - lang.DefineFunction("fid-list", cmdFidList, types.JsonLines) +/*func init() { + lang.DefineFunction("fid-list", cmdFidListOld, types.JsonLines) defaults.AppendProfile(` autocomplete set fid-list { [{ @@ -25,7 +24,7 @@ func init() { "out": "*" } config eval shell safe-commands { -> append jobs }`) -} +}*/ func getParams(p *lang.Process) string { s := string(p.Parameters.Raw()) @@ -35,7 +34,7 @@ func getParams(p *lang.Process) string { return s } -func cmdFidList(p *lang.Process) error { +func cmdFidListOld(p *lang.Process) error { flag, _ := p.Parameters.String(0) switch flag { case "": diff --git a/builtins/core/processes/fid-list_new.go b/builtins/core/processes/fid-list_new.go new file mode 100644 index 000000000..9443a60f5 --- /dev/null +++ b/builtins/core/processes/fid-list_new.go @@ -0,0 +1,284 @@ +package processes + +import ( + "fmt" + + "github.com/lmorg/murex/config/defaults" + "github.com/lmorg/murex/lang" + "github.com/lmorg/murex/lang/parameters" + "github.com/lmorg/murex/lang/state" + "github.com/lmorg/murex/lang/types" + "github.com/lmorg/murex/utils/json" +) + +func init() { + lang.DefineFunction("fid-list", cmdFidListNew, types.JsonLines) + + defaults.AppendProfile(` + autocomplete set fid-list { [{ + "DynamicDesc": ({ fid-list --help }) + }] } + + alias jobs=fid-list --jobs + method define jobs { + "in": "null", + "out": "*" + } + config eval shell safe-commands { -> append jobs }`) +} + +const ( + fCsv = "--csv" + fJsonL = "--jsonl" + fTty = "--tty" + fJobs = "--jobs" + fStopped = "--stopped" + fBackground = "--background" + fIncChildrenOf = "--children-of" + fExcChildrenOf = "--no-children-of" + fIncFnContainer = "--function-groups" + fExcFnContainer = "--no-function-groups" + fHelp = "--help" +) + +var argsFidList = ¶meters.Arguments{ + AllowAdditional: false, + IgnoreInvalidFlags: false, + StrictFlagPlacement: false, + Flags: map[string]string{ + fCsv: types.Boolean, + fJsonL: types.Boolean, + fTty: types.Boolean, + + fJobs: types.Boolean, + fStopped: types.Boolean, + fBackground: types.Boolean, + + fIncChildrenOf: types.Integer, + fExcChildrenOf: types.Integer, + fIncFnContainer: types.Boolean, + fExcFnContainer: types.Boolean, + + fHelp: types.Boolean, + }, +} + +func cmdFidListNew(p *lang.Process) error { + flags, _, err := p.Parameters.ParseFlags(argsFidList) + if err != nil { + return err + } + + options := []lang.OptFidList{} + + if v, ok := flags.GetNullable(fIncChildrenOf); ok { + options = append(options, lang.FidWithIsChildOf(uint32(v.Integer()), true)) + } + + if v, ok := flags.GetNullable(fExcChildrenOf); ok { + options = append(options, lang.FidWithIsChildOf(uint32(v.Integer()), false)) + } + + if _, ok := flags.GetNullable(fIncFnContainer); ok { + options = append(options, lang.FidWithIsFork(false)) + } + + if _, ok := flags.GetNullable(fExcFnContainer); ok { + options = append(options, lang.FidWithIsFork(false)) + } + + procs := lang.GlobalFIDs.List(options...) + + switch { + case flags.GetValue(fCsv).Boolean(): + return cmdFidListNewCSV(p, procs) + + case flags.GetValue(fJsonL).Boolean(): + return cmdFidListNewPipe(p, procs) + + case flags.GetValue(fTty).Boolean(): + return cmdFidListNewTTY(p, procs) + + case flags.GetValue(fJobs).Boolean(): + return cmdJobs(p) + + case flags.GetValue(fStopped).Boolean(): + return cmdJobsNewStopped(p, procs) + + case flags.GetValue(fBackground).Boolean(): + return cmdJobsNewBackground(p, procs) + + case flags.GetValue(fHelp).Boolean(): + return cmdFidListNewHelp(p) + + default: + if p.Stdout.IsTTY() { + return cmdFidListNewTTY(p, procs) + } + return cmdFidListNewPipe(p, procs) + } +} + +func cmdFidListNewHelp(p *lang.Process) error { + flags := map[string]string{ + fCsv: "Outputs as CSV table", + fJsonL: "Outputs as a jsonlines (a greppable array of JSON objects). This is the default mode when `fid-list` is piped", + fTty: "Outputs as a human readable table. This is the default mode when outputting to a TTY", + fStopped: "JSON map of all stopped processes running under murex", + fBackground: "JSON map of all background processes running under murex", + fJobs: "List stopped or background processes (similar to POSIX jobs)", + fIncChildrenOf: " Include only children of FID", + fExcChildrenOf: " Exclude all children of FID", + fIncFnContainer: "Include only function containers", + fExcFnContainer: "Exclude all function containers", + fHelp: "Displays a list of parameters", + } + p.Stdout.SetDataType(types.Json) + b, err := json.Marshal(flags, p.Stdout.IsTTY()) + if err != nil { + return err + } + + _, err = p.Stdout.Write(b) + return err +} + +func cmdFidListNewTTY(p *lang.Process, procs []*lang.Process) error { + p.Stdout.SetDataType(types.Generic) + p.Stdout.Writeln([]byte(fmt.Sprintf("%7s %7s %7s %-13s %-8s %-3s %-10s %-10s %-10s %s", + "FID", "Parent", "Scope", "State", "Run Mode", "BG", "Out Pipe", "Err Pipe", "Command", "Parameters"))) + + for _, process := range procs { + s := fmt.Sprintf("%7d %7d %7d %-13s %-8s %-3s %-10s %-10s %-10s %s", + process.Id, + process.Parent.Id, + process.Scope.Id, + process.State.String(), + process.RunMode.String(), + process.Background.String(), + process.NamedPipeOut, + process.NamedPipeErr, + process.Name.String(), + getParams(process), + ) + _, err := p.Stdout.Writeln([]byte(s)) + if err != nil { + return err + } + } + return nil +} + +func cmdFidListNewCSV(p *lang.Process, procs []*lang.Process) error { + p.Stdout.SetDataType("csv") + p.Stdout.Writeln([]byte(fmt.Sprintf("%s,%s,%s,%s,%s,%s,%s,%s,%s,%s", + "FID", "Parent", "Scope", "State", "Run Mode", "BG", "Out Pipe", "Err Pipe", "Command", "Parameters"))) + + for _, process := range procs { + s := fmt.Sprintf("%d,%d,%d,%s,%s,%s,%s,%s,%s,%s", + process.Id, + process.Parent.Id, + process.Scope.Id, + process.State.String(), + process.RunMode.String(), + process.Background.String(), + process.NamedPipeOut, + process.NamedPipeErr, + process.Name.String(), + getParams(process), + ) + _, err := p.Stdout.Writeln([]byte(s)) + if err != nil { + return err + } + } + return nil +} + +func cmdFidListNewPipe(p *lang.Process, procs []*lang.Process) error { + p.Stdout.SetDataType(types.JsonLines) + + b, err := lang.MarshalData(p, types.Json, []any{ + "FID", + "Parent", + "Scope", + "State", + "RunMode", + "BG", + "OutPipe", + "ErrPipe", + "Command", + "Parameters", + }) + if err != nil { + return err + } + _, err = p.Stdout.Writeln(b) + if err != nil { + return err + } + + for _, process := range procs { + b, err = lang.MarshalData(p, types.Json, []any{ + process.Id, + process.Parent.Id, + process.Scope.Id, + process.State.String(), + process.RunMode.String(), + process.Background.Get(), + process.NamedPipeOut, + process.NamedPipeErr, + process.Name.String(), + getParams(process), + }) + if err != nil { + return err + } + _, err = p.Stdout.Writeln(b) + if err != nil { + return err + } + } + + return nil +} + +func cmdJobsNewStopped(p *lang.Process, procs []*lang.Process) error { + m := make(map[uint32]string) + + for _, process := range procs { + if process.State.Get() != state.Stopped { + continue + } + m[process.Id] = process.Name.String() + " " + getParams(process) + } + + b, err := lang.MarshalData(p, types.Json, m) + if err != nil { + return err + } + + p.Stdout.SetDataType(types.Json) + _, err = p.Stdout.Write(b) + return err +} + +func cmdJobsNewBackground(p *lang.Process, procs []*lang.Process) error { + m := make(map[uint32]string) + + for _, process := range procs { + if !process.Background.Get() { + continue + } + m[process.Id] = process.Name.String() + " " + getParams(process) + } + + b, err := lang.MarshalData(p, types.Json, m) + if err != nil { + return err + } + + p.Stdout.SetDataType(types.Json) + _, err = p.Stdout.Write(b) + return err +} diff --git a/builtins/core/runtime/runtime.go b/builtins/core/runtime/runtime.go index 1dbf7efa2..59b3c187c 100644 --- a/builtins/core/runtime/runtime.go +++ b/builtins/core/runtime/runtime.go @@ -158,12 +158,12 @@ func cmdRuntime(p *lang.Process) error { return err } - if len(f) == 0 { + if f.Len() == 0 { return errors.New("please include one or more parameters") } ret := make(map[string]any) - for flag := range f { + for flag := range f.GetMap() { switch flag { case fVars: ret[flag[2:]] = p.Scope.Variables.Dump() @@ -352,8 +352,8 @@ func dumpAbout() any { "Alloc": humannumbers.Bytes(mem.Alloc), "SessionLifeTime": humannumbers.Bytes(mem.TotalAlloc), "Sys": humannumbers.Bytes(mem.Sys), - "NumGC": mem.NumGC, - "NumForcedGC": mem.NumForcedGC, + "NumGC": memNumGC(&mem), + "NumForcedGC": memNumForcedGC(&mem), } return m diff --git a/builtins/core/runtime/runtime_std.go b/builtins/core/runtime/runtime_std.go new file mode 100644 index 000000000..bd7fe75ad --- /dev/null +++ b/builtins/core/runtime/runtime_std.go @@ -0,0 +1,14 @@ +//go:build !tinygo +// +build !tinygo + +package cmdruntime + +import "runtime" + +func memNumGC(mem *runtime.MemStats) any { + return mem.NumGC +} + +func memNumForcedGC(mem *runtime.MemStats) any { + return mem.NumForcedGC +} diff --git a/builtins/core/runtime/runtime_tinygo.go b/builtins/core/runtime/runtime_tinygo.go new file mode 100644 index 000000000..7300fee5d --- /dev/null +++ b/builtins/core/runtime/runtime_tinygo.go @@ -0,0 +1,16 @@ +//go:build tinygo +// +build tinygo + +package cmdruntime + +import "runtime" + +const unsupportedTinyGo = "unsupported in TinyGo" + +func memNumGC(mem *runtime.MemStats) any { + return unsupportedTinyGo +} + +func memNumForcedGC(mem *runtime.MemStats) any { + return unsupportedTinyGo +} diff --git a/builtins/core/structs/alias.go b/builtins/core/structs/alias.go new file mode 100644 index 000000000..293a86664 --- /dev/null +++ b/builtins/core/structs/alias.go @@ -0,0 +1,171 @@ +package structs + +import ( + "errors" + "fmt" + "regexp" + + "github.com/lmorg/murex/lang" + "github.com/lmorg/murex/lang/parameters" + "github.com/lmorg/murex/lang/types" + "github.com/lmorg/murex/shell/autocomplete" + "github.com/lmorg/murex/shell/hintsummary" + "github.com/lmorg/murex/utils/json" +) + +func init() { + lang.DefineFunction("alias", cmdAlias, types.Null) + lang.DefineFunction("!alias", cmdUnalias, types.Null) +} + +const ( + usageAlias = ` +usage: alias [ --copy ] alias_name = command parameter1 parameter2 ... +please note: command and parameters should not be quoted as one parameter to alias` +) + +const ( + fAliasCopy = "--copy" +) + +var argsAlias = ¶meters.Arguments{ + Flags: map[string]string{ + fAliasCopy: types.Boolean, + "-c": fAliasCopy, + }, + AllowAdditional: true, + IgnoreInvalidFlags: false, + StrictFlagPlacement: true, +} + +func aliasErr(err error, name string) error { + if err == nil { + return nil + } + + if name == "" { + return fmt.Errorf("%v%s", err, usageAlias) + } + + return fmt.Errorf("%v\nalias name: %s%s", err, name, usageAlias) +} + +func cmdAlias(p *lang.Process) error { + if p.Parameters.Len() == 0 { + p.Stdout.SetDataType(types.Json) + b, err := json.Marshal(lang.GlobalAliases.Dump(), p.Stdout.IsTTY()) + if err != nil { + return err + } + _, err = p.Stdout.Writeln(b) + return err + + } + + p.Stdout.SetDataType(types.Null) + + flags, aliasParams, err := p.Parameters.ParseFlags(argsAlias) + if err != nil { + return aliasErr(err, "") + } + + name, params, err := aliasParseParams(aliasParams) + if err != nil { + return aliasErr(err, name) + } + + if flags.GetValue(fAliasCopy).Boolean() { + cmdAliasCopy(p, name, params) + } + + lang.GlobalAliases.Add(name, params, p.FileRef) + return nil +} + +var ( + rxAlias = regexp.MustCompile(`^([-_.a-zA-Z0-9]+)=(.*?)$`) + + errAliasMissingCommand = errors.New("missing command to alias") + errInvalidSyntax = errors.New("invalid syntax") + errAliasUnknown = errors.New("unknown error parsing alias") +) + +func aliasParseParams(aliasParams []string) (string, []string, error) { + if !rxAlias.MatchString(aliasParams[0]) && len(aliasParams) >= 2 && len(aliasParams[1]) >= 1 && aliasParams[1][0] != '=' { + return "", nil, errInvalidSyntax + } + + var ( + split = rxAlias.FindStringSubmatch(aliasParams[0]) + name string + params []string + ) + + if len(split) == 0 { + name = aliasParams[0] + params = aliasParams[1:] + switch { + case len(params) == 0: + return name, nil, errAliasMissingCommand + case len(params[0]) == 1 && params[0] == "=": + params = params[1:] + case len(params[0]) > 0 && params[0][0] == '=': + params[0] = params[0][1:] + default: + return name, nil, errAliasUnknown + } + + } else { + name = split[1] + params = append([]string{split[2]}, aliasParams[1:]...) + } + + if len(params) == 0 { + return name, nil, errAliasMissingCommand + } + + if params[0] == "" && len(params) > 0 { + params = params[1:] + } + + if len(params) == 0 || params[0] == "" { + return name, nil, errAliasMissingCommand + } + + return name, params, nil +} + +func cmdAliasCopy(p *lang.Process, name string, params []string) { + // summary + summary := hintsummary.Summary.Get(params[0]) + if summary != "" { + hintsummary.Summary.Set(name, summary) + } + + // method + dts := lang.MethodStdin.Types(params[0]) + for i := range dts { + lang.MethodStdin.Define(name, dts[i]) + } + + // autocomplete + if len(params) == 1 { + flags, ok := autocomplete.ExesFlags[params[0]] + if ok { + autocomplete.ExesFlags[name] = flags + autocomplete.ExesFlagsFileRef[name] = p.FileRef + } + } +} + +func cmdUnalias(p *lang.Process) error { + p.Stdout.SetDataType(types.Null) + + for _, name := range p.Parameters.StringArray() { + err := lang.GlobalAliases.Delete(name) + if err != nil { + return err + } + } + return nil +} diff --git a/builtins/core/structs/alias_doc.yaml b/builtins/core/structs/alias_doc.yaml new file mode 100644 index 000000000..58fa10e57 --- /dev/null +++ b/builtins/core/structs/alias_doc.yaml @@ -0,0 +1,103 @@ +- DocumentID: alias + Title: >+ + Alias "shortcut": `alias` + CategoryID: commands + SubCategoryIDs: + - commands.shell + - commands.posix + Summary: >- + Create an alias for a command + Description: |- + `alias` allows you to create a shortcut or abbreviation for a longer command. + + IMPORTANT: aliases in Murex are not macros and are therefore different than + other shells. if the shortcut requires any dynamics such as `piping`, + `command sequencing`, `variable evaluations` or `scripting`... + Prefer the **`function`** builtin. + Usage: |- + ``` + alias [ --copy | -c ] alias=command parameter parameter ... + + !alias command + ``` + Examples: |- + Because aliases are parsed into an array of parameters, you cannot put the + entire alias within quotes. For example: + + ``` + # bad :( + » alias hw="out Hello, World!" + » hw + exec "out\\ Hello,\\ World!": executable file not found in $PATH + + # good :) + » alias hw=out "Hello, World!" + » hw + Hello, World! + ``` + + Notice how only the command `out "Hello, World!"` is quoted in `alias` the + same way you would have done if you'd run that command "naked" in the command + line? This is how `alias` expects it's parameters and where `alias` on Murex + differs from `alias` in POSIX shells. + + To materialize those differences, pay attention to the examples below: + + ``` + # bad : the following statements generate errors, + # prefer function builtin to implent them + + » alias myalias=out "Hello, World!" | wc + » alias myalias=out $myvariable | wc + » alias myalias=out ${vmstat} | wc + » alias myalias=out "hello" && out "world" + » alias myalias=out "hello" ; out "world" + » alias myalias="out hello; out world" + ``` + + In some ways this makes `alias` a little less flexible than it might + otherwise be. However the design of this is to keep `alias` focused on it's + core objective. To implement the above aliasing, you can use `function` + instead. + Flags: + --copy: >- + copies `method`, `summary` and `autocomplete` of destination command. + `autocomplete` is only copied if alias does not include parameters. + -c: alias for `--copy` + Detail: |- + ### Allowed characters + + Alias names can only include alpha-numeric characters, hyphen and underscore. + The following regex is used to validate the `alias`'s parameters: + `^([-_a-zA-Z0-9]+)=(.*?)$` + + ### Undefining an alias + + Like all other definable states in Murex, you can delete an alias with the + bang prefix: + + ``` + » alias hw=out "Hello, World!" + » hw + Hello, World! + + » !alias hw + » hw + exec "hw": executable file not found in $PATH + ``` + + ### Order of preference + + {{ include "gen/includes/order-of-precedence.inc.md" }} + Synonyms: + - alias + - "!alias" + Related: + - function + - private + - source + - exec + - fexec + - method + - autocomplete + - summary diff --git a/builtins/core/structs/alias_test.go b/builtins/core/structs/alias_test.go index 87b8f4c6b..2661b779e 100644 --- a/builtins/core/structs/alias_test.go +++ b/builtins/core/structs/alias_test.go @@ -11,38 +11,39 @@ import ( func TestAliasParamParsing(t *testing.T) { alias := fmt.Sprintf("GoTest-alias-%d-", rand.Int()) + const errMissingCommand = "missing command to alias" tests := []test.MurexTest{ // errors { - Block: fmt.Sprintf(`alias %s%d`, alias, -1), - Stderr: "no command supplied", + Block: fmt.Sprintf(`alias %s%d`, alias, -1), + Stderr: errMissingCommand, ExitNum: 1, }, { - Block: fmt.Sprintf(`alias %s%d `, alias, -2), - Stderr: "no command supplied", + Block: fmt.Sprintf(`alias %s%d `, alias, -2), + Stderr: errMissingCommand, ExitNum: 1, }, { - Block: fmt.Sprintf(`alias %s%d=`, alias, -3), - Stderr: "no command supplied", + Block: fmt.Sprintf(`alias %s%d=`, alias, -3), + Stderr: errMissingCommand, ExitNum: 1, }, { - Block: fmt.Sprintf(`alias %s%d= `, alias, -4), - Stderr: "no command supplied", + Block: fmt.Sprintf(`alias %s%d= `, alias, -4), + Stderr: errMissingCommand, ExitNum: 1, }, { - Block: fmt.Sprintf(`alias %s%d =`, alias, -1), - Stderr: "no command supplied", + Block: fmt.Sprintf(`alias %s%d =`, alias, -5), + Stderr: errMissingCommand, ExitNum: 1, }, { - Block: fmt.Sprintf(`alias %s%d foobar`, alias, -1), - Stderr: "invalid syntax", + Block: fmt.Sprintf(`alias %s%d foobar`, alias, -6), + Stderr: "invalid syntax", ExitNum: 1, }, @@ -112,8 +113,40 @@ func TestAliasParamParsing(t *testing.T) { Block: fmt.Sprintf(`alias %s%d = foo bar; alias -> [%s%d]`, alias, 12, alias, 12), Stdout: "[\"foo\",\"bar\"]", }, + } + + test.RunMurexTestsRx(tests, t) +} +func TestAliasCopy(t *testing.T) { + token := fmt.Sprintf("GoTest-alias-copy-%d-", rand.Int()) + tests := []test.MurexTest{ + { + Block: fmt.Sprintf(` + summary %[1]s-foo-%[2]d foobar-%[2]d + alias %[1]s-bar-%[2]d = %[1]s-foo-%[2]d + runtime --summaries -> [%[1]s-bar-%[2]d] + `, token, 0), + Stderr: "not found", + ExitNum: 1, + }, + { + Block: fmt.Sprintf(` + summary %[1]s-foo-%[2]d foobar-%[2]d + alias --copy %[1]s-bar-%[2]d = %[1]s-foo-%[2]d + runtime --summaries -> [%[1]s-bar-%[2]d] + `, token, 1), + Stdout: "^foobar-1$", + }, + { + Block: fmt.Sprintf(` + autocomplete set %[1]s-foo-%[2]d %%[{Flags: [ "foobar-%[2]d" ]}] + alias --copy %[1]s-bar-%[2]d = %[1]s-foo-%[2]d + runtime --autocomplete -> [[ /%[1]s-foo-%[2]d/FlagValues/0/Flags/0 ]] + `, token, 2), + Stdout: "^foobar-2$", + }, } test.RunMurexTestsRx(tests, t) diff --git a/builtins/core/structs/foreach.go b/builtins/core/structs/foreach.go index 282588fc2..3ad5e29b2 100644 --- a/builtins/core/structs/foreach.go +++ b/builtins/core/structs/foreach.go @@ -42,15 +42,17 @@ func cmdForEach(p *lang.Process) error { return err } + foreachParallelVal, foreachParallelOk := flags.GetNullable(foreachParallel) + switch { - case flags[foreachJmap] == types.TrueString: + case flags.GetValue(foreachJmap).Boolean(): return cmdForEachJmap(p) - case flags[foreachParallel] != "": - return cmdForEachParallel(p, flags, additional) + case foreachParallelOk: + return cmdForEachParallel(p, foreachParallelVal.Integer(), additional) default: - return cmdForEachDefault(p, flags, additional) + return cmdForEachDefault(p, flags.GetValue(foreachStep).Integer(), additional) } } @@ -63,15 +65,6 @@ func convertToByte(v any) ([]byte, error) { return []byte(s.(string)), nil } -func getFlagValueInt(flags map[string]string, flagName string) (int, error) { - v, err := types.ConvertGoType(flags[flagName], types.Integer) - if err != nil { - return 0, fmt.Errorf(`expecting integer for %s, instead got "%s": %s`, flagName, flags[flagName], err.Error()) - } - - return v.(int), nil -} - func forEachInitializer(p *lang.Process, additional []string) (block []rune, varName string, err error) { dataType := p.Stdin.GetDataType() if dataType == types.Json { @@ -99,17 +92,12 @@ func forEachInitializer(p *lang.Process, additional []string) (block []rune, var return } -func cmdForEachDefault(p *lang.Process, flags map[string]string, additional []string) error { +func cmdForEachDefault(p *lang.Process, steps int, additional []string) error { block, varName, err := forEachInitializer(p, additional) if err != nil { return err } - steps, err := getFlagValueInt(flags, foreachStep) - if err != nil { - return err - } - var ( step int iteration int diff --git a/builtins/core/structs/foreach_parallel.go b/builtins/core/structs/foreach_parallel.go index a77c58584..029fe263c 100644 --- a/builtins/core/structs/foreach_parallel.go +++ b/builtins/core/structs/foreach_parallel.go @@ -9,17 +9,12 @@ import ( const MAX_INT = int(^uint(0) >> 1) -func cmdForEachParallel(p *lang.Process, flags map[string]string, additional []string) error { +func cmdForEachParallel(p *lang.Process, parallel int, additional []string) error { block, varName, err := forEachInitializer(p, additional) if err != nil { return err } - parallel, err := getFlagValueInt(flags, foreachParallel) - if err != nil { - return err - } - if parallel < 1 { parallel = MAX_INT } diff --git a/builtins/core/structs/function.go b/builtins/core/structs/function.go index 0aa6e03d8..4c6341d99 100644 --- a/builtins/core/structs/function.go +++ b/builtins/core/structs/function.go @@ -3,7 +3,6 @@ package structs import ( "errors" "fmt" - "regexp" "strings" "github.com/lmorg/murex/config/defaults" @@ -14,8 +13,6 @@ import ( ) func init() { - lang.DefineFunction("alias", cmdAlias, types.Null) - lang.DefineFunction("!alias", cmdUnalias, types.Null) lang.DefineFunction("function", cmdFunc, types.Null) lang.DefineFunction("!function", cmdUnfunc, types.Null) lang.DefineFunction("private", cmdPrivate, types.Null) @@ -33,82 +30,6 @@ func init() { `) } -var rxAlias = regexp.MustCompile(`^([-_.a-zA-Z0-9]+)=(.*?)$`) - -func cmdAlias(p *lang.Process) error { - if p.Parameters.Len() == 0 { - p.Stdout.SetDataType(types.Json) - b, err := json.Marshal(lang.GlobalAliases.Dump(), p.Stdout.IsTTY()) - if err != nil { - return err - } - _, err = p.Stdout.Writeln(b) - return err - - } - - p.Stdout.SetDataType(types.Null) - - s, _ := p.Parameters.String(0) - eq, _ := p.Parameters.String(1) - - if !rxAlias.MatchString(s) && len(eq) > 0 && eq[0] != '=' { - return errors.New("invalid syntax. Expecting `alias new_name=original_name parameter1 parameter2 ...`") - } - - var ( - split = rxAlias.FindStringSubmatch(s) - name string - params []string - ) - - if len(split) == 0 { - name = s - params = p.Parameters.StringArray()[1:] - switch { - case len(params) == 0: - return fmt.Errorf("no command supplied") - case len(params[0]) == 1 && params[0] == "=": - params = params[1:] - case len(params[0]) > 0 && params[0][0] == '=': - params[0] = params[0][1:] - default: - return fmt.Errorf("unknown error. Please check syntax follows `alias new_name=original_name parameter1 parameter2 ...`") - } - - } else { - name = split[1] - params = append([]string{split[2]}, p.Parameters.StringArray()[1:]...) - } - - if len(params) == 0 { - return fmt.Errorf("no command supplied") - } - - if params[0] == "" && len(params) > 0 { - params = params[1:] - } - - if len(params) == 0 || params[0] == "" { - return fmt.Errorf("no command supplied") - } - - lang.GlobalAliases.Add(name, params, p.FileRef) - return nil -} - -func cmdUnalias(p *lang.Process) error { - p.Stdout.SetDataType(types.Null) - - for _, name := range p.Parameters.StringArray() { - err := lang.GlobalAliases.Delete(name) - if err != nil { - return err - } - } - return nil -} - func cmdFunc(p *lang.Process) error { var dtParamsT []lang.MurexFuncParam diff --git a/builtins/core/structs/function_doc.yaml b/builtins/core/structs/function_doc.yaml index a99c7a034..5f12b5da3 100644 --- a/builtins/core/structs/function_doc.yaml +++ b/builtins/core/structs/function_doc.yaml @@ -1,108 +1,3 @@ -- DocumentID: alias - Title: >+ - Alias "shortcut": `alias` - CategoryID: commands - SubCategoryIDs: - - commands.shell - - commands.posix - Summary: >- - Create an alias for a command - Description: |- - `alias` allows you to create a shortcut or abbreviation for a longer command. - - IMPORTANT: aliases in Murex are not macros and are therefore different than - other shells. if the shortcut requires any dynamics such as `piping`, - `command sequencing`, `variable evaluations` or `scripting`... - Prefer the **`function`** builtin. - Usage: |- - ``` - alias alias=command parameter parameter - - !alias command - ``` - Examples: |- - Because aliases are parsed into an array of parameters, you cannot put the - entire alias within quotes. For example: - - ``` - # bad :( - » alias hw="out Hello, World!" - » hw - exec "out\\ Hello,\\ World!": executable file not found in $PATH - - # good :) - » alias hw=out "Hello, World!" - » hw - Hello, World! - ``` - - Notice how only the command `out "Hello, World!"` is quoted in `alias` the - same way you would have done if you'd run that command "naked" in the command - line? This is how `alias` expects it's parameters and where `alias` on Murex - differs from `alias` in POSIX shells. - - To materialize those differences, pay attention to the examples below: - - ``` - # bad : the following statements generate errors, - # prefer function builtin to implent them - - » alias myalias=out "Hello, World!" | wc - » alias myalias=out $myvariable | wc - » alias myalias=out ${vmstat} | wc - » alias myalias=out "hello" && out "world" - » alias myalias=out "hello" ; out "world" - » alias myalias="out hello; out world" - ``` - - In some ways this makes `alias` a little less flexible than it might - otherwise be. However the design of this is to keep `alias` focused on it's - core objective. To implement the above aliasing, you can use `function` - instead. - Flags: - Detail: |- - ### Allowed characters - - Alias names can only include alpha-numeric characters, hyphen and underscore. - The following regex is used to validate the `alias`'s parameters: - `^([-_a-zA-Z0-9]+)=(.*?)$` - - ### Undefining an alias - - Like all other definable states in Murex, you can delete an alias with the - bang prefix: - - ``` - » alias hw=out "Hello, World!" - » hw - Hello, World! - - » !alias hw - » hw - exec "hw": executable file not found in $PATH - ``` - - ### Order of preference - - {{ include "gen/includes/order-of-precedence.inc.md" }} - Synonyms: - - alias - - "!alias" - Related: - - function - - private - - source - - g - - let - - set - - global - - export - - exec - - fexec - - method - - - - DocumentID: function Title: >+ Public Function: `function` diff --git a/builtins/core/tabulate/tabulate.go b/builtins/core/tabulate/tabulate.go index c5558adf6..5d908ec52 100644 --- a/builtins/core/tabulate/tabulate.go +++ b/builtins/core/tabulate/tabulate.go @@ -102,10 +102,10 @@ func cmdTabulate(p *lang.Process) error { //iValStart int // where the value starts when column wraps and keyVal used ) - for flag, value := range f { + for flag, value := range f.GetMap() { switch flag { case fSeparator: - separator = value + separator = value.(string) case fSplitComma: splitComma = true case fSplitSpace: @@ -115,7 +115,7 @@ func cmdTabulate(p *lang.Process) error { case fKeyVal: keyVal = true case fJoiner: - joiner = value + joiner = value.(string) case fMap: keyVal = true case fColumnWraps: @@ -149,7 +149,7 @@ func cmdTabulate(p *lang.Process) error { p.Name.String(), types.String, types.Generic, dt) } - if f[fMap] == "" { + if !f.GetValue(fMap).Boolean() { p.Stdout.SetDataType("csv") w = csv.NewWriter(p.Stdout) } else { diff --git a/builtins/core/time/datetime.go b/builtins/core/time/datetime.go index 09ee8d122..0d68aa872 100644 --- a/builtins/core/time/datetime.go +++ b/builtins/core/time/datetime.go @@ -59,9 +59,9 @@ func cmdDateTime(p *lang.Process) error { } var ( - fIn = flags[_FLAG_IN] - fOut = flags[_FLAG_OUT] - fValue = flags[_FLAG_VAL] + fIn = flags.GetValue(_FLAG_IN).String() + fOut = flags.GetValue(_FLAG_OUT).String() + fValue = flags.GetValue(_FLAG_VAL).String() ) switch len(additional) { diff --git a/builtins/core/typemgmt/round.go b/builtins/core/typemgmt/round.go index 6583d7f29..ec46b3461 100644 --- a/builtins/core/typemgmt/round.go +++ b/builtins/core/typemgmt/round.go @@ -53,8 +53,8 @@ func cmdRound(p *lang.Process) error { } precision := v.(float64) - roundDown := flags[flagRoundDown] == types.TrueString - roundUp := flags[flagRoundUp] == types.TrueString + roundDown := flags.GetValue(flagRoundDown).Boolean() + roundUp := flags.GetValue(flagRoundUp).Boolean() if roundUp && roundDown { return fmt.Errorf("you cannot use both %s/-d and %s/-u flags together", flagRoundDown, flagRoundUp) diff --git a/builtins/docs/summaries.go b/builtins/docs/summaries.go index b53a043ba..f635edded 100644 --- a/builtins/docs/summaries.go +++ b/builtins/docs/summaries.go @@ -423,6 +423,7 @@ func init() { "changelog/v6.4": "This change brings a number of ergonomic improvements to job control, `datetime` and working with structures.", "changelog/v7.0": "Introducing experimental support for XML, new integrations, and several other quality-of-life improvements. Four deprecated builtins have been removed too, which is this release sees an increment of its major version number", "changelog/v7.1": "This release focuses mainly on bugfixes and quality-of-life with the exception of three **experimental** new major additions:\n* `foreach` now supports running processes in parallel\n* `fanout` is a new builtin that allows sending stdout to the stdin of many processes\n* `md` is a new datatype added. Currently only supports rendering markdown tables but more features will follow in future releases", + "changelog/v7.2": "This release brings several improvements in scripting environments for Javascript/Typescript and Python support. Core features include the new `--copy` flag for aliases to inherit shell configuration, and bugfixes to address autocomplete panics, expression error handling, and 3rd party integration issues.", "deprecated/equ": "Evaluate a mathematical function (removed 7.0)", "deprecated/die": "Terminate murex with an exit number of 1 (removed 7.0)", "deprecated/let": "Evaluate a mathematical function and assign to variable (removed 7.0)", @@ -1089,5 +1090,6 @@ func init() { "changelog/v6.4": "changelog/v6.4", "changelog/v7.0": "changelog/v7.0", "changelog/v7.1": "changelog/v7.1", + "changelog/v7.2": "changelog/v7.2", } } diff --git a/docs/changelog/README.md b/docs/changelog/README.md index ba3a2bed8..98acd2ba0 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 +### 01.02.2026 - [v7.2](../changelog/v7.2.md) + +This release brings several improvements for scripting environments in Javascript/Typescript and Python support. Core features include the new `--copy` flag for aliases to inherit shell configuration, and bugfixes to address autocomplete panics, expression error handling, and 3rd party integration issues. + + ### 23.10.2025 - [v7.1](../changelog/v7.1.md) This release focuses mainly on bugfixes and quality-of-life with the exception of three **experimental** new major additions: diff --git a/docs/changelog/v7.2.md b/docs/changelog/v7.2.md new file mode 100644 index 000000000..6d3088345 --- /dev/null +++ b/docs/changelog/v7.2.md @@ -0,0 +1,68 @@ +# v7.2 + +This release brings several improvements for scripting environments in Javascript/Typescript and Python support. Core features include the new `--copy` flag for aliases to inherit shell configuration, and bugfixes to address autocomplete panics, expression error handling, and 3rd party integration issues. + +## Breaking Changes + +None + +## v7.2.xxxx + +### Features + +* `alias`: new `--copy` flag to inherit shell configuration from aliased commands ([issue](https://github.com/lmorg/murex/issues/872)) +* `args`: `StrictFlagPlacement` and `--` separator +* `fid-list`: rewrite to support filtering +* core: allow filterable function table +* cache.db: don't cache anything with a TTL < 1hr +* integrations: `yarn` refactor +* integrations: Python `uv` support +* integrations: Python virtual environment improvements ([issue](https://github.com/lmorg/murex/issues/960)) +* integrations: `terraform-docs` summary +* makefile: add tags to make test +* wasm: support for builds via tinygo (EXPERIMENTAL) +* chore: update dependencies + +### Bug Fixes + +* autocomplete: fix panic when JSON struct is zero-length ([issue](https://github.com/lmorg/murex/issues/953)) +* `foreach`: fix regression bug in `--parallel` after args rewrite +* escape builtins: use `IsMethod()` instead of `Parameters.Len()` ([issue](https://github.com/lmorg/murex/issues/857)) +* integrations: `yarn` bug fix - resolve initialization hang with corepack ([issue](https://github.com/lmorg/murex/issues/956)) +* expressions: caught error wasn't being returned correctly +* core: temp directory value is now immutable ([issue](https://github.com/lmorg/murex/issues/864)) + +## Special Thanks + +Thank yous for this release go to [@priscira](https://github.com/priscira), [@lokalius](https://github.com/lokalius) and [@fyrak1s](https://github.com/fyrak1s), for your testing and feedback. + +Also thank you to everyone in the [discussions group](https://github.com/lmorg/murex/discussions) and all who raise bug reports. + +You rock! + +
+ +Published: 01.02.2026 at 12:00 + +## See Also + +* [Alias "shortcut": `alias`](../commands/alias.md): + Create an alias for a command +* [Define Function Arguments: `args`](../commands/args.md): + Command line flag parser for Murex shell scripting +* [Display Running Functions: `fid-list`](../commands/fid-list.md): + Lists all running functions within the current Murex session +* [How To Contribute](../Murex/CONTRIBUTING.md): + Murex is community project. We gratefully accept contributions +* [Integrations](../user-guide/integrations.md): + Default integrations shipped with Murex +* [Quote String: `escape`](../commands/escape.md): + Escape or unescape input +* [Tab Autocompletion: `autocomplete`](../commands/autocomplete.md): + Set definitions for tab-completion in the command line +* [expressions](../changelog/expressions.md): + + +
+ +This document was generated from [gen/changelog/v7.2_doc.yaml](https://github.com/lmorg/murex/blob/master/gen/changelog/v7.2_doc.yaml). \ No newline at end of file diff --git a/docs/commands/README.md b/docs/commands/README.md index 783855d22..b36d69969 100644 --- a/docs/commands/README.md +++ b/docs/commands/README.md @@ -395,6 +395,8 @@ Various commands that enable control flow, error handling and other important ch Reads the stdin and exit number from previous process and not's it's condition * [Null: `null`](../commands/devnull.md): null function. Similar to /dev/null +* [Parse Murex Source: `murex-parser`](../commands/murex-parser.md): + Runs the Murex parser against a block of code * [Private Function: `private`](../commands/private.md): Define a private function block * [Public Function: `function`](../commands/function.md): @@ -436,10 +438,7 @@ Tools for providing help and hints, useful when working inside the interactive s * [Set Command Summary Hint: `summary`](../commands/summary.md): Defines a summary help text for a command -### Uncategorised -* [Parse Murex Source: `murex-parser`](../commands/murex-parser.md): - Runs the Murex parser against a block of code ## Optional Builtins diff --git a/docs/commands/alias.md b/docs/commands/alias.md index 3a11667f5..99e3be69a 100644 --- a/docs/commands/alias.md +++ b/docs/commands/alias.md @@ -14,7 +14,7 @@ IMPORTANT: aliases in Murex are not macros and are therefore different than ## Usage ``` -alias alias=command parameter parameter +alias [ --copy | -c ] alias=command parameter parameter ... !alias command ``` @@ -60,6 +60,13 @@ otherwise be. However the design of this is to keep `alias` focused on it's core objective. To implement the above aliasing, you can use `function` instead. +## Flags + +* `--copy` + copies `method`, `summary` and `autocomplete` of destination command. `autocomplete` is only copied if alias does not include parameters. +* `-c` + alias for `--copy` + ## Detail ### Allowed characters @@ -123,29 +130,23 @@ You can override this order of precedence via the `fexec` and `exec` builtins. ## See Also -* [Define Environmental Variable: `export`](../commands/export.md): - Define an environmental variable and set it's value -* [Define Global: `global`](../commands/global.md): - Define a global variable and set it's value * [Define Method Relationships (`method`)](../commands/method.md): Define a methods supported data-types -* [Define Variable: `set`](../commands/set.md): - Define a variable (typically local) and set it's value * [Execute External Command: `exec`](../commands/exec.md): Runs an executable * [Execute Function or Builtin: `fexec`](../commands/fexec.md): Execute a command or function, bypassing the usual order of precedence. -* [Globbing: `g`](../commands/g.md): - Glob pattern matching for file system objects (eg `*.txt`) * [Include / Evaluate Murex Code: `source`](../commands/source.md): Import Murex code from another file or code block -* [Integer Operations: `let`](../deprecated/let.md): - Evaluate a mathematical function and assign to variable (removed 7.0) * [Private Function: `private`](../commands/private.md): Define a private function block * [Public Function: `function`](../commands/function.md): Define a function block +* [Set Command Summary Hint: `summary`](../commands/summary.md): + Defines a summary help text for a command +* [Tab Autocompletion: `autocomplete`](../commands/autocomplete.md): + Set definitions for tab-completion in the command line
-This document was generated from [builtins/core/structs/function_doc.yaml](https://github.com/lmorg/murex/blob/master/builtins/core/structs/function_doc.yaml). \ No newline at end of file +This document was generated from [builtins/core/structs/alias_doc.yaml](https://github.com/lmorg/murex/blob/master/builtins/core/structs/alias_doc.yaml). \ No newline at end of file diff --git a/docs/commands/args.md b/docs/commands/args.md index 6c8f58be5..5cd3ed676 100644 --- a/docs/commands/args.md +++ b/docs/commands/args.md @@ -78,10 +78,58 @@ $args[Additional] -> foreach flag { } ``` +## Detail + +### Flags vs Parameters + +#### Flags + +Flags are any values pass via `-` or `--` prefixed labels. For example +`--datatype str` would assign the value `str` to the flag name `--datatype`. + +Flags can be `str`, `int` `num` `bool`. These values will be type checked and +`args` will return an error if a user passes (for example) alpha character to +a numeric flag. + +Boolean flags do not require `true` nor `false` values to be included; the +absence of a boolean flag automatically sets it to _false_, while the presence +automatically sets it to _true_. + +#### Parameters + +Parameters are any values that are not assigned to a flag. For example +`cat file1.txt file2.txt file3.txt`. + +#### Allowing Flag-like Parameters + +Sometimes you'll want parameters that look like flags. For example +`calculator -3 + 2` where `-3` might normally be considered a flag. + +There are two ways you can force `args` to read values as a parameter: + +1. **User controlled**: the user can use the `--` flag to denote that everything + which follows is a parameter. eg `calculator -- -3 + 2`. + +2. **Developer controlled**: A better option is to enable `StrictFlagPlacement` + and have the first parameter be non-flag option. Eg + `calculator print -3 + 2`. With `StrictFlagPlacement`, anything including + and proceeding a parameter will always be parsed as a parameter regardless + of whether it looks like a flag or not. + +(here, `calculator` is a hypothetical command) + ## See Also * [Reserved Variables](../user-guide/reserved-vars.md): Special variables reserved by Murex +* [`bool`](../types/bool.md): + Boolean (primitive) +* [`int`](../types/int.md): + Whole number (primitive) +* [`num` (number)](../types/num.md): + Floating point number (primitive) +* [`str` (string)](../types/str.md): + string (primitive)
diff --git a/docs/commands/expr.md b/docs/commands/expr.md index fe615ac8e..0068e6e6c 100644 --- a/docs/commands/expr.md +++ b/docs/commands/expr.md @@ -219,7 +219,7 @@ func executeExpression(tree *ParserT, order symbols.Exp) (err error) { case symbols.AssignAndDivide: err = expAssignAndOperate(tree, _assDiv) case symbols.AssignAndMultiply: - err = expAssignAndOperate(tree, _assMulti) + err = expAssignAndOperate(tree, _assMul) case symbols.AssignOrMerge: err = expAssignMerge(tree) diff --git a/docs/commands/tmp.md b/docs/commands/tmp.md index d73cd4854..1c1ab2d4b 100644 --- a/docs/commands/tmp.md +++ b/docs/commands/tmp.md @@ -70,7 +70,7 @@ func cmdTempFile(p *lang.Process) error { return err } - name := consts.TempDir + hex.EncodeToString(h.Sum(nil)) + "-" + strconv.Itoa(os.Getpid()) + ext + name := consts.TmpDir() + hex.EncodeToString(h.Sum(nil)) + "-" + strconv.Itoa(os.Getpid()) + ext file, err := os.Create(name) if err != nil { diff --git a/gen/changelog/v7.2.inc.md b/gen/changelog/v7.2.inc.md new file mode 100644 index 000000000..a3c4269ea --- /dev/null +++ b/gen/changelog/v7.2.inc.md @@ -0,0 +1,37 @@ +## Breaking Changes + +None + +## v7.2.xxxx + +### Features + +* `alias`: new `--copy` flag to inherit shell configuration from aliased commands ([issue](https://github.com/lmorg/murex/issues/872)) +* `args`: `StrictFlagPlacement` and `--` separator +* `fid-list`: rewrite to support filtering +* core: allow filterable function table +* cache.db: don't cache anything with a TTL < 1hr +* integrations: `yarn` refactor +* integrations: Python `uv` support +* integrations: Python virtual environment improvements ([issue](https://github.com/lmorg/murex/issues/960)) +* integrations: `terraform-docs` summary +* makefile: add tags to make test +* wasm: support for builds via tinygo (EXPERIMENTAL) +* chore: update dependencies + +### Bug Fixes + +* autocomplete: fix panic when JSON struct is zero-length ([issue](https://github.com/lmorg/murex/issues/953)) +* `foreach`: fix regression bug in `--parallel` after args rewrite +* escape builtins: use `IsMethod()` instead of `Parameters.Len()` ([issue](https://github.com/lmorg/murex/issues/857)) +* integrations: `yarn` bug fix - resolve initialization hang with corepack ([issue](https://github.com/lmorg/murex/issues/956)) +* expressions: caught error wasn't being returned correctly +* core: temp directory value is now immutable ([issue](https://github.com/lmorg/murex/issues/864)) + +## Special Thanks + +Thank yous for this release go to [@priscira](https://github.com/priscira), [@lokalius](https://github.com/lokalius) and [@fyrak1s](https://github.com/fyrak1s), for your testing and feedback. + +Also thank you to everyone in the [discussions group](https://github.com/lmorg/murex/discussions) and all who raise bug reports. + +You rock! diff --git a/gen/changelog/v7.2_doc.yaml b/gen/changelog/v7.2_doc.yaml new file mode 100644 index 000000000..6f1f3b157 --- /dev/null +++ b/gen/changelog/v7.2_doc.yaml @@ -0,0 +1,19 @@ +- DocumentID: v7.2 + Title: >- + v7.2 + CategoryID: changelog + DateTime: 2026-02-01 12:00 + Summary: >- + This release brings several improvements for scripting environments in Javascript/Typescript and Python support. Core features include the new `--copy` flag for aliases to inherit shell configuration, and bugfixes to address autocomplete panics, expression error handling, and 3rd party integration issues. + Description: |- + {{ include "gen/changelog/v7.2.inc.md" }} + Related: + - CONTRIBUTING + - integrations + - alias + - args + - autocomplete + - fid-list + - escape + - expressions + diff --git a/gen/vuepress/commands_generated.json b/gen/vuepress/commands_generated.json index f3c68d9e1..00dea0e6b 100644 --- a/gen/vuepress/commands_generated.json +++ b/gen/vuepress/commands_generated.json @@ -818,6 +818,11 @@ "link": "commands/devnull.md", "text": "Null: null" }, + { + "icon": "file-lines", + "link": "commands/murex-parser.md", + "text": "Parse Murex Source: murex-parser" + }, { "icon": "file-lines", "link": "commands/private.md", @@ -911,10 +916,5 @@ "collapsible": true, "icon": "folder", "text": "Help and Hint Tools" - }, - { - "icon": "file-lines", - "link": "commands/murex-parser.md", - "text": "Parse Murex Source: murex-parser" } ] \ No newline at end of file diff --git a/go.mod b/go.mod index ede916768..2ae9f64f9 100644 --- a/go.mod +++ b/go.mod @@ -12,19 +12,20 @@ require ( github.com/fsnotify/fsnotify v1.9.0 github.com/hashicorp/hcl v1.0.0 github.com/lmorg/apachelogs v0.0.0-20161115121556-e5f3eae677ad - github.com/lmorg/readline/v4 v4.2.1 + github.com/lmorg/readline/v4 v4.2.2 github.com/mattn/go-runewidth v0.0.19 - github.com/mattn/go-sqlite3 v1.14.32 + github.com/mattn/go-sqlite3 v1.14.33 github.com/pelletier/go-toml v1.9.5 github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee - golang.org/x/sys v0.37.0 - golang.org/x/text v0.30.0 + golang.org/x/sys v0.40.0 + golang.org/x/text v0.33.0 gopkg.in/yaml.v3 v3.0.1 - modernc.org/sqlite v1.39.1 + modernc.org/sqlite v1.44.3 ) require ( - github.com/clipperhouse/uax29/v2 v2.2.0 // indirect + github.com/clipperhouse/stringish v0.1.1 // indirect + github.com/clipperhouse/uax29/v2 v2.3.1 // indirect github.com/disintegration/imaging v1.6.2 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/google/uuid v1.6.0 // indirect @@ -33,12 +34,12 @@ require ( 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-20251017212417-90e834f514db // indirect - golang.org/x/image v0.32.0 // indirect - golang.org/x/mod v0.29.0 // indirect - golang.org/x/sync v0.17.0 // indirect - golang.org/x/tools v0.38.0 // indirect - modernc.org/libc v1.66.10 // 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 modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect ) diff --git a/go.sum b/go.sum index 547887507..574e673a4 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,10 @@ 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/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY= -github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= +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/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= @@ -26,20 +28,22 @@ github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17k github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 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.1 h1:tUr0/j6CPEmjMd7QMWm3D1WTVwPlH8f/QtSLgbyvgYU= -github.com/lmorg/readline/v4 v4.2.1/go.mod h1:Zdp/tnRZl0eTssyikMGSp61wOZwxJLw/CtK2sE2f9e8= +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.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= -github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +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/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= @@ -52,39 +56,41 @@ 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-20251017212417-90e834f514db h1:by6IehL4BH5k3e3SJmcoNbOobMey2SLpAF79iPOEBvw= -golang.org/x/exp v0.0.0-20251017212417-90e834f514db/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= +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.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ= -golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc= -golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +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.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= -golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= -golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= -golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +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= 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.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4= -modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= -modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A= -modernc.org/ccgo/v4 v4.28.1/go.mod h1:uD+4RnfrVgE6ec9NGguUNdhqzNIeeomeXf6CL0GTE5Q= +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/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/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= -modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A= -modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I= +modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI= +modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE= 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= @@ -93,8 +99,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.39.1 h1:H+/wGFzuSCIEVCvXYVHX5RQglwhMOvtHSv+VtidL2r4= -modernc.org/sqlite v1.39.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE= +modernc.org/sqlite v1.44.3 h1:+39JvV/HWMcYslAwRxHb8067w+2zowvFOUrOWIy9PjY= +modernc.org/sqlite v1.44.3/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA= 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/python_posix.mx b/integrations/python_posix.mx new file mode 100644 index 000000000..9b0ce7a6e --- /dev/null +++ b/integrations/python_posix.mx @@ -0,0 +1,39 @@ +function venv.mx { + # Enter a Python virtual environment from a Murex shell + + venv = $1 ?? ".venv" + activate = "$(venv)/bin/activate" + + if { g $activate } else { + err "Cannot start Python virtual environment because" + err "file does not exist: $activate" + return + } + + out "Entering Python virtual environment: $(venv)..." + + export MUREX_OLD_PROMPT = ${ config get shell prompt -> base64 } + export MUREX_OLD_PROMPT_ML = ${ config get shell prompt-multiline -> base64 } + + out " + function _old_prompt \${ \$ENV.MUREX_OLD_PROMPT -> base64 -d } + function _old_prompt_ml \${ \$ENV.MUREX_OLD_PROMPT_ML -> base64 -d } + + config set shell prompt { + out %(($venv) \${_old_prompt}) + } + + config set shell prompt-multiline { + out %(($venv) \${_old_prompt_ml}) + } + + alias pydoc = python -m pydoc + + out 'Type `exit` or ctrl+d to return' + " -> tmp -> set murex_init + + bash -e -c %( + source $venv/bin/activate + $MUREX_EXE -load-modules -quiet -ignore-whatsnew -i -execute source $murex_init + ) +} diff --git a/integrations/python_uv_any.mx b/integrations/python_uv_any.mx new file mode 100644 index 000000000..63ec0be4f --- /dev/null +++ b/integrations/python_uv_any.mx @@ -0,0 +1,69 @@ +if { which uv } then { + private uv.summary { + # Automatically populate the command summary for `uv` + uv --help -> [..1] + } + + summary uv fexec(private /builtin/integrations_python_uv_any/uv.summary) + + private uv.autocomplete { + # Autocompletions for `uv` + + config set proc strict-arrays false + + parameters = ${ + trypipe { @PARAMS -> [...-]re } + } ?: %[] + + uv @parameters --help \ + -> tabulate --column-wraps --split-comma --map --key-inc-hint + } + + private uv.preview { + # Man page for `uv` preview + + config set proc strict-arrays false + + parameters = ${ + try { @PARAMS -> [...-]re } + } ?: %[] + + uv help @parameters + } + + autocomplete set uv %[ + { + DynamicDesc: '{ uv.autocomplete }' + DynamicPreview: '{ uv.preview }' + AllowMultiple: true + AllowAny: true + } + ] + + test unit private uv.summary %{ + StdoutType: * + StdoutRegex: Python + } + + test unit private uv.autocomplete %{ + StdoutType: json + StdoutIsMap: true + } + + test unit private uv.preview %{ + StdoutType: * + } +} + +if { which uvx } then { + summary uvx ${ uvx --help -> [..1] } + + autocomplete set uvx %[ + { + DynamicDesc: '{ uvx --help -> tabulate --column-wraps --split-comma --map --key-inc-hint }' + DynamicPreview: '{ uv --help }' + AllowMultiple: true + AllowAny: true + } + ] +} diff --git a/integrations/terraform-docs_any.mx b/integrations/terraform-docs_any.mx index f4c1294d8..3d8fab7cf 100644 --- a/integrations/terraform-docs_any.mx +++ b/integrations/terraform-docs_any.mx @@ -1,3 +1,5 @@ +summary terraform-docs "A utility to generate documentation from Terraform modules in various output formats" + autocomplete set terraform-docs %[ { DynamicDesc: '{ @@ -9,4 +11,4 @@ autocomplete set terraform-docs %[ AllowAny: true ListView: true } -] \ No newline at end of file +] diff --git a/integrations/yarn_any.mx b/integrations/yarn_any.mx index 11d760463..071707365 100644 --- a/integrations/yarn_any.mx +++ b/integrations/yarn_any.mx @@ -2,60 +2,87 @@ return } -autocomplete: set yarn ({[ +private yarn.autocomplete.1 { + cast json + autocomplete = %{} + + trypipe { + autocomplete <~ ${ + g ${yarn bin}/* \ + -> regexp s,^.*/,, \ + -> foreach --jmap bin { $bin } { out "yarn bin" } + } + } + + trypipe { + autocomplete <~ ${ yarn help -> tabulate: --key-value --split-comma --key-inc-hint --map } + } + + $autocomplete +} + +/#test unit private yarn.autocomplete.1 %{ + StdoutType: json + StdoutIsMap: true + StdoutGreaterThan: 10 +}#/ + +private yarn.autocomplete.commands { + yarn help \ + -> [Commands..Run]re \ + -> [:1] \ + -> cast str \ + -> foreach --jmap cmd { $cmd } { yarn help $cmd -> [Usage..Options]rebt } +} + +/#test unit private yarn.autocomplete.commands %{ + StdoutType: json + StdoutIsMap: true + StdoutGreaterThan: 10 +}#/ + +bg { + MODULE.yarn_autocomplete_commands = yarn.autocomplete.commands() +} + +autocomplete set yarn %[ { - "CacheTTL": 30, - "Dynamic": ({ - g: ${yarn bin}/* -> regexp: s,^.*/,, - }), - "FlagsDesc": ${ - trypipe { - yarn help -> tabulate: --key-value --split-comma --key-inc-hint --map - } - }, - "Optional": true, - "AllowMultiple": true, - "AllowNoFlagValue": true, - "FlagValues": {"*": [ - { "IncDirs": true }, - { "Goto": "/0" } - ]} - }, + CacheTTL: 120 + DynamicDesc: '{yarn.autocomplete.1}' + Optional: true + AllowMultiple: true + AllowNoFlagValue: true + FlagValues: { + "*": [ + { IncDirs: true } + { Goto: "/0" } + ] + } + } { - "DynamicDesc": ({ + DynamicDesc: '{ cast json - if { g: package.json } then { + if { g package.json } then { open package.json -> [ scripts ] } - }), - "Optional": true - }, - { - "Flags": ${ - trypipe { - yarn help -> [Commands..Run]re -> [:1] -> cast str -> format json - } - }, - "FlagValues": { - ${ - trypipe { - yarn help -> [Commands..Run]re -> [:1] -> foreach MOD_cmd { - out ("$MOD_cmd": - [{ - "DynamicDesc": ({ + }' + Optional: true + } + /#{ + DynamicDesc: '{ $MODULE.yarn_autocomplete_commands }' + FlagValues: { + "*": [{ + DynamicDesc: '{ yarn help $MOD_cmd -> tabulate: --key-value --split-comma --key-inc-hint --map - }), - "AllowMultiple": true, - "AllowNoFlagValue": true, - "FlagValues": {"*": [ - { "IncDirs": true }, - { "Goto": "/2/add/0" } + }) + AllowMultiple: true + AllowNoFlagValue: true + FlagValues: {"*": [ + { IncDirs: true } + { Goto: "/2/add/0" } ]} }],) - } - } - } - "": [{ }] + }] } - } -]}) \ No newline at end of file + }#/ +] \ No newline at end of file diff --git a/lang/expressions/exp14.go b/lang/expressions/exp14.go index 79228e9e8..086097a87 100644 --- a/lang/expressions/exp14.go +++ b/lang/expressions/exp14.go @@ -106,7 +106,7 @@ func expAssign(tree *ParserT, overwriteType bool) error { } } - // this is ugly but Go's JSON marshaller is better behaved than Murexes on with empty values + // this is ugly but Go's JSON marshaller is more correct than Murexes with empty values if dt == types.Json { b, err := right.Marshal() if err != nil { @@ -136,7 +136,7 @@ func expAssign(tree *ParserT, overwriteType bool) error { v, err = types.ConvertGoType(right.Value, dt) if err != nil { - raiseError(tree.expression, tree.currentSymbol(), 0, err.Error()) + return raiseError(tree.expression, tree.currentSymbol(), 0, err.Error()) } } } @@ -154,95 +154,12 @@ func expAssign(tree *ParserT, overwriteType bool) error { }) } -/*func expAssignAdd(tree *ParserT) error { - leftNode, rightNode, err := tree.getLeftAndRightSymbols() - if err != nil { - return err - } - - if err = convertAssigneeToBareword(tree, leftNode); err != nil { - return err - } - - right, err := rightNode.dt.GetValue() - if err != nil { - return err - } - - if leftNode.key != symbols.Bareword { - return raiseError(tree.expression, leftNode, 0, fmt.Sprintf( - "left side of %s should be a bareword, instead got %s", - tree.currentSymbol().key, leftNode.key)) - } - - v, dt, err := tree.getVar(leftNode.value, varAsValue) - if err != nil { - if !tree.StrictTypes() && strings.Contains(err.Error(), lang.ErrDoesNotExist) { - // var doesn't exist and we have strict types disabled so lets create var - v, dt, err = float64(0), types.Number, nil - } else { - return raiseError(tree.expression, tree.currentSymbol(), 0, err.Error()) - } - } - - var result any - - switch dt { - case types.Number, types.Float: - if right.Primitive != primitives.Number { - return raiseError(tree.expression, tree.currentSymbol(), 0, fmt.Sprintf( - "cannot %s %s to %s", tree.currentSymbol().key, right.Primitive, dt)) - } - result = v.(float64) + right.Value.(float64) - - case types.Integer: - if right.Primitive != primitives.Number { - return raiseError(tree.expression, tree.currentSymbol(), 0, fmt.Sprintf( - "cannot %s %s to %s", tree.currentSymbol().key, right.Primitive, dt)) - } - result = float64(v.(int)) + right.Value.(float64) - - case types.Boolean: - return raiseError(tree.expression, tree.currentSymbol(), 0, fmt.Sprintf( - "cannot %s %s", tree.currentSymbol().key, dt)) - - case types.Null: - switch right.Primitive { - case primitives.String: - result = right.Value.(string) - case primitives.Number: - result = right.Value.(float64) - default: - return raiseError(tree.expression, tree.currentSymbol(), 0, fmt.Sprintf( - "cannot %s %s to %s", tree.currentSymbol().key, right.Primitive, dt)) - } - - default: - if right.Primitive != primitives.String { - return raiseError(tree.expression, tree.currentSymbol(), 0, fmt.Sprintf( - "cannot %s %s to %s", tree.currentSymbol().key, right.Primitive, dt)) - } - result = v.(string) + right.Value.(string) - } - - err = tree.setVar(leftNode.value, result, right.DataType) - if err != nil { - return raiseError(tree.expression, tree.currentSymbol(), 0, err.Error()) - } - - return tree.foldAst(&astNodeT{ - key: symbols.Calculated, - pos: tree.ast[tree.astPos].pos, - dt: primitives.NewPrimitive(primitives.Null, nil), - }) -}*/ - type assFnT func(float64, float64) float64 -func _assAdd(lv float64, rv float64) float64 { return lv + rv } -func _assSub(lv float64, rv float64) float64 { return lv - rv } -func _assMulti(lv float64, rv float64) float64 { return lv * rv } -func _assDiv(lv float64, rv float64) float64 { return lv / rv } +func _assAdd(lv float64, rv float64) float64 { return lv + rv } +func _assSub(lv float64, rv float64) float64 { return lv - rv } +func _assMul(lv float64, rv float64) float64 { return lv * rv } +func _assDiv(lv float64, rv float64) float64 { return lv / rv } func expAssignAndOperate(tree *ParserT, operation assFnT) error { leftNode, rightNode, err := tree.getLeftAndRightSymbols() @@ -362,6 +279,13 @@ func expAssignMerge(tree *ParserT) error { err.Error())) } + /*if dt == "" || dt == types.Null { + dt = right.DataType + } + b, err := lang.MarshalData(tree.p, dt, merged) + if err != nil { + return raiseError(tree.expression, tree.currentSymbol(), 0, err.Error()) + }*/ err = tree.setVar(leftNode.value, merged, dt) if err != nil { return raiseError(tree.expression, tree.currentSymbol(), 0, err.Error()) diff --git a/lang/expressions/expression.go b/lang/expressions/expression.go index 0eaeaf84c..0cd463810 100644 --- a/lang/expressions/expression.go +++ b/lang/expressions/expression.go @@ -101,7 +101,7 @@ func executeExpression(tree *ParserT, order symbols.Exp) (err error) { case symbols.AssignAndDivide: err = expAssignAndOperate(tree, _assDiv) case symbols.AssignAndMultiply: - err = expAssignAndOperate(tree, _assMulti) + err = expAssignAndOperate(tree, _assMul) case symbols.AssignOrMerge: err = expAssignMerge(tree) diff --git a/lang/fork.go b/lang/fork.go index 5c4606e50..5823d02f8 100644 --- a/lang/fork.go +++ b/lang/fork.go @@ -95,7 +95,7 @@ type Fork struct { preview bool } -const ForkSuffix = " (fork)" +//const ForkSuffix = " (fork)" // Fork will create a new handle for executing a code block func (p *Process) Fork(flags int) *Fork { @@ -107,7 +107,7 @@ func (p *Process) Fork(flags int) *Fork { trace(fork.Process) fork.raw = p.raw - fork.State.Set(state.MemAllocated) + fork.State.Set(state.FunctionGroup) fork.Background.Set(flags&F_BACKGROUND != 0 || p.Background.Get()) fork.HasJobId.Set(flags&F_ASSIGN_JOBID != 0 || p.HasJobId.Get()) @@ -115,6 +115,8 @@ func (p *Process) Fork(flags int) *Fork { fork.OperatorLogicAnd = p.OperatorLogicAnd fork.OperatorLogicOr = p.OperatorLogicOr fork.IsNot = p.IsNot + fork.IsFork = true + fork.Parent = p //p.Parent fork.Previous = p.Previous fork.Next = p.Next @@ -131,7 +133,7 @@ func (p *Process) Fork(flags int) *Fork { if flags&F_FUNCTION != 0 { fork.Scope = fork.Process - fork.Parent = fork.Process + //fork.Parent = fork.Process fork.Context, fork.Done = context.WithCancel(context.Background()) fork.Kill = fork.Done @@ -159,27 +161,27 @@ func (p *Process) Fork(flags int) *Fork { switch { case flags&F_PARENT_VARTABLE != 0: - fork.Parent = p.Parent + //fork.Parent = p.Parent fork.Variables = p.Variables fork.Id = p.Id case flags&F_NEW_VARTABLE != 0: - fork.Parent = p.Parent + //fork.Parent = p.Parent fork.Variables = p.Variables - fork.Name.Append(ForkSuffix) + //.Name.Append(ForkSuffix) GlobalFIDs.Register(fork.Process) fork.fidRegistered = true - fork.IsFork = true + //fork.IsFork = true default: //panic("must include either F_PARENT_VARTABLE or F_NEW_VARTABLE") - fork.Parent = p.Parent + //fork.Parent = p.Parent fork.Variables = NewVariables(fork.Process) fork.Variables = p.Variables - fork.Name.Append(ForkSuffix) + //fork.Name.Append(ForkSuffix) GlobalFIDs.Register(fork.Process) fork.fidRegistered = true - fork.IsFork = true + //fork.IsFork = true } if flags&F_NEW_CONFIG != 0 { diff --git a/lang/funcid.go b/lang/funcid.go index 8b4cad81e..939760829 100644 --- a/lang/funcid.go +++ b/lang/funcid.go @@ -95,6 +95,28 @@ func (f *funcID) ListAll() fidList { return procs } +// List processes registered in the FID (Function ID) table - return as a ordered list +func (f *funcID) List(options ...OptFidList) fidList { + fids := f.ListAll() + + var procs fidList + + for _, p := range fids { + //fmt.Printf("start: %d %s\n", p.Id, p.Name.String()) + inc := true + for opt := range options { + //fmt.Printf("opt %d: %d %s\n", opt, p.Id, p.Name.String()) + inc = inc && options[opt](p) + } + if inc { + procs = append(procs, p) + } + //fmt.Printf("end: %d %s\n", p.Id, p.Name.String()) + } + + return procs +} + // Dump returns a list of FIDs in a format for `runtime` builtin func (f *funcID) Dump() any { fidList := f.ListAll() diff --git a/lang/funcid_options.go b/lang/funcid_options.go new file mode 100644 index 000000000..2b364af83 --- /dev/null +++ b/lang/funcid_options.go @@ -0,0 +1,45 @@ +package lang + +type OptFidList func(*Process) bool + +func FidWithIsBackground(include bool) OptFidList { + return func(p *Process) bool { + return p.Background.Get() == include + } +} + +func FidWithIsStopped(include bool) OptFidList { + return func(p *Process) bool { + select { + case <-p.HasStopped: + return include + default: + return !include + } + } +} + +func FidWithIsChildOf(fid uint32, include bool) OptFidList { + return func(p *Process) bool { + + proc := p.Parent + + for { + if proc.Id == fid { + return include + } + + proc = proc.Parent + + if proc.Id == ShellProcess.Id { + return !include + } + } + } +} + +func FidWithIsFork(include bool) OptFidList { + return func(p *Process) bool { + return p.IsFork == include + } +} diff --git a/lang/parameters/flags.go b/lang/parameters/flags.go index d1c67fe88..d38c45b52 100644 --- a/lang/parameters/flags.go +++ b/lang/parameters/flags.go @@ -7,11 +7,107 @@ import ( "github.com/lmorg/murex/lang/types" ) +type FlagValueT interface { + String() string + Integer() int + Number() float64 + Boolean() bool + Any() any +} + +type flagValue struct { + v any + dt string +} + +func (fv *flagValue) String() string { + if fv.dt == types.String { + return fv.v.(string) + } + panic(fmt.Sprintf("cannot String() on %s", fv.dt)) +} + +func (fv *flagValue) Integer() int { + if fv.dt == types.Integer { + return fv.v.(int) + } + panic(fmt.Sprintf("cannot Integer() on %s", fv.dt)) +} + +func (fv *flagValue) Number() float64 { + if fv.dt == types.Number || fv.dt == types.Float { + return fv.v.(float64) + } + panic(fmt.Sprintf("cannot Number() on %s", fv.dt)) +} + +func (fv *flagValue) Boolean() bool { + if fv.dt == types.Boolean { + return fv.v.(bool) + } + panic(fmt.Sprintf("cannot Boolean() on %s", fv.dt)) +} + +func (fv *flagValue) Any() any { + return fv.v +} + +type nullValue struct{} + +func (nv *nullValue) String() string { return "" } +func (nv *nullValue) Integer() int { return 0 } +func (nv *nullValue) Number() float64 { return 0 } +func (nv *nullValue) Boolean() bool { return false } +func (nv *nullValue) Any() any { return nil } + +type FlagsT struct { + flags map[string]FlagValueT +} + +func (f *FlagsT) set(flag string, v any, dt string) error { + v, err := types.ConvertGoType(v, dt) + if err != nil { + return fmt.Errorf("flag %s is not a %s\n%v", flag, dt, err) + } + f.flags[flag] = &flagValue{v: v, dt: dt} + return nil +} + +func (f *FlagsT) GetValue(flag string) FlagValueT { + v, ok := f.flags[flag] + if ok { + return v + } + return &nullValue{} +} + +func (f *FlagsT) GetNullable(flag string) (FlagValueT, bool) { + v, ok := f.flags[flag] + return v, ok +} + +func (f *FlagsT) GetMap() map[string]any { + m := make(map[string]any) + for k, v := range f.flags { + m[k] = v.Any() + } + return m +} + +func (f *FlagsT) Len() int { + return len(f.flags) +} + +func newFlagsT() *FlagsT { + return &FlagsT{flags: make(map[string]FlagValueT)} +} + // Arguments is a struct which holds the allowed flags supported when parsing the flags (with ParseFlags) type Arguments struct { - AllowAdditional bool - IgnoreInvalidFlags bool - Flags map[string]string + AllowAdditional bool + IgnoreInvalidFlags bool + StrictFlagPlacement bool + Flags map[string]string } const invalidParameters = "invalid parameters" @@ -27,36 +123,52 @@ const invalidParameters = "invalid parameters" // "--bool": "bool", // "-b": "--bool" // } -func ParseFlags(params []string, args *Arguments) (flags map[string]string, additional []string, err error) { - var previous string - flags = make(map[string]string) - additional = make([]string, 0) +// +// Returns: +// 1. map of flags, +// 2. additional parameters, +// 3. error +func ParseFlags(params []string, args *Arguments) (*FlagsT, []string, error) { + var ( + previous string + flags = newFlagsT() + additional = make([]string, 0) + ignoreFlags bool + i int + ) - for i := range params { + for i = range params { scanFlags: switch { + case ignoreFlags: + additional = append(additional, params[i]) + case strings.HasPrefix(params[i], "-"): switch { + case args.AllowAdditional && params[i] == "--": + ignoreFlags = true case strings.HasPrefix(args.Flags[params[i]], "-"): params[i] = args.Flags[params[i]] goto scanFlags - //case previous != "": - // return nil, nil, fmt.Errorf("%s: flag found without value: `%s`", invalidParameters, previous) case args.Flags[params[i]] == types.Boolean: - flags[params[i]] = types.TrueString + flags.set(params[i], true, types.Boolean) case args.Flags[params[i]] != "": previous = params[i] case previous != "": - flags[previous] = params[i] + if err := flags.set(previous, params[i], args.Flags[previous]); err != nil { + return nil, nil, err + } previous = "" case args.IgnoreInvalidFlags && args.AllowAdditional: additional = append(additional, params[i]) default: - return nil, nil, fmt.Errorf("%s: flag not recognised: `%s`", invalidParameters, params[i]) + return nil, nil, fmt.Errorf("%s: flag not recognized: `%s`", invalidParameters, params[i]) } case previous != "": - flags[previous] = params[i] + if err := flags.set(previous, params[i], args.Flags[previous]); err != nil { + return nil, nil, err + } previous = "" default: @@ -64,6 +176,9 @@ func ParseFlags(params []string, args *Arguments) (flags map[string]string, addi return nil, nil, fmt.Errorf("%s: parameter found without a flag: `%s`", invalidParameters, params[i]) } additional = append(additional, params[i]) + if args.StrictFlagPlacement { + ignoreFlags = true + } } } @@ -71,12 +186,12 @@ func ParseFlags(params []string, args *Arguments) (flags map[string]string, addi return nil, nil, fmt.Errorf("%s: flag found without value: `%s`", invalidParameters, previous) } - return + return flags, additional, nil } // ParseFlags - this instance of ParseFlags is a wrapper function for ParseFlags (above) so you can use inside your // lang.Process.Parameters object -func (p *Parameters) ParseFlags(args *Arguments) (flags map[string]string, additional []string, err error) { +func (p *Parameters) ParseFlags(args *Arguments) (flags *FlagsT, additional []string, err error) { p.mutex.RLock() defer p.mutex.RUnlock() diff --git a/lang/parameters/flags_test.go b/lang/parameters/flags_test.go index fd0822420..ca7aea64c 100644 --- a/lang/parameters/flags_test.go +++ b/lang/parameters/flags_test.go @@ -13,7 +13,7 @@ type flagTest struct { Parameters []string Arguments parameters.Arguments - ExpFlags map[string]string + ExpFlags map[string]any ExpAdditional []string Error bool @@ -30,30 +30,76 @@ func TestFlags(t *testing.T) { "--number": types.Number, }, }, - ExpFlags: map[string]string{ + ExpFlags: map[string]any{ "--string": "--test", - "--number": "-5", + "--number": -5, }, ExpAdditional: nil, Error: false, }, + { + Parameters: []string{"foobar", "--string", "--test", "--number", "-5"}, + Arguments: parameters.Arguments{ + StrictFlagPlacement: false, + AllowAdditional: true, + Flags: map[string]string{ + "--string": types.String, + "--number": types.Number, + }, + }, + ExpFlags: map[string]any{ + "--string": "--test", + "--number": -5, + }, + ExpAdditional: []string{"foobar"}, + Error: false, + }, + { + Parameters: []string{"foobar", "--string", "--test", "--number", "-5"}, + Arguments: parameters.Arguments{ + StrictFlagPlacement: true, + AllowAdditional: true, + Flags: map[string]string{ + "--string": types.String, + "--number": types.Number, + }, + }, + ExpFlags: map[string]any{}, + ExpAdditional: []string{"foobar", "--string", "--test", "--number", "-5"}, + Error: false, + }, + { + Parameters: []string{"--", "foobar", "--string", "--test", "--number", "-5"}, + Arguments: parameters.Arguments{ + StrictFlagPlacement: false, + AllowAdditional: true, + Flags: map[string]string{ + "--string": types.String, + "--number": types.Number, + }, + }, + ExpFlags: map[string]any{}, + ExpAdditional: []string{"foobar", "--string", "--test", "--number", "-5"}, + Error: false, + }, } count.Tests(t, len(tests)) for i, test := range tests { if test.ExpFlags == nil { - test.ExpFlags = make(map[string]string) + test.ExpFlags = make(map[string]any) } if test.ExpAdditional == nil { test.ExpAdditional = make([]string, 0) } - flags, additional, err := parameters.ParseFlags(test.Parameters, &test.Arguments) + flagsT, additional, err := parameters.ParseFlags(test.Parameters, &test.Arguments) + flags := flagsT.GetMap() - if flags == nil { - flags = make(map[string]string) - } + //if flags == nil { + // flags = make(map[string]*parameters.FlagValueT) + //} if additional == nil { additional = make([]string, 0) } diff --git a/lang/process_fallback.go b/lang/process_fallback.go index 8f6037b6b..941f403b6 100644 --- a/lang/process_fallback.go +++ b/lang/process_fallback.go @@ -1,5 +1,5 @@ -//go:build windows || plan9 || js -// +build windows plan9 js +//go:build windows || plan9 || js || no_pty +// +build windows plan9 js no_pty package lang diff --git a/lang/process_unix.go b/lang/process_unix.go index 1208f27d1..193b47aec 100644 --- a/lang/process_unix.go +++ b/lang/process_unix.go @@ -1,5 +1,5 @@ -//go:build !windows && !plan9 && !js -// +build !windows,!plan9,!js +//go:build !windows && !plan9 && !js && !no_pty +// +build !windows,!plan9,!js,!no_pty package lang diff --git a/lang/runmode/runmode_string.go b/lang/runmode/runmode_string.go index 1cd74324e..6f3f00593 100644 --- a/lang/runmode/runmode_string.go +++ b/lang/runmode/runmode_string.go @@ -32,8 +32,9 @@ const _RunMode_name = "DefaultNormalBlockUnsafeFunctionUnsafeModuleUnsafeBlockTr var _RunMode_index = [...]uint8{0, 7, 13, 24, 38, 50, 58, 70, 81, 96, 107, 122, 136, 154, 163, 176, 188, 204} func (i RunMode) String() string { - if i < 0 || i >= RunMode(len(_RunMode_index)-1) { + idx := int(i) - 0 + if i < 0 || idx >= len(_RunMode_index)-1 { return "RunMode(" + strconv.FormatInt(int64(i), 10) + ")" } - return _RunMode_name[_RunMode_index[i]:_RunMode_index[i+1]] + return _RunMode_name[_RunMode_index[idx]:_RunMode_index[idx+1]] } diff --git a/lang/state/functionstate.go b/lang/state/functionstate.go index fc7959e5b..cfde5f8e4 100644 --- a/lang/state/functionstate.go +++ b/lang/state/functionstate.go @@ -8,6 +8,7 @@ type FunctionState int32 // The different states available to FunctionState: const ( Undefined FunctionState = iota + FunctionGroup MemAllocated Assigned Starting diff --git a/lang/state/functionstate_string.go b/lang/state/functionstate_string.go index cee86c9ed..ea109aab8 100644 --- a/lang/state/functionstate_string.go +++ b/lang/state/functionstate_string.go @@ -9,23 +9,25 @@ func _() { // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[Undefined-0] - _ = x[MemAllocated-1] - _ = x[Assigned-2] - _ = x[Starting-3] - _ = x[Executing-4] - _ = x[Executed-5] - _ = x[Terminating-6] - _ = x[AwaitingGC-7] - _ = x[Stopped-8] + _ = x[FunctionGroup-1] + _ = x[MemAllocated-2] + _ = x[Assigned-3] + _ = x[Starting-4] + _ = x[Executing-5] + _ = x[Executed-6] + _ = x[Terminating-7] + _ = x[AwaitingGC-8] + _ = x[Stopped-9] } -const _FunctionState_name = "UndefinedMemAllocatedAssignedStartingExecutingExecutedTerminatingAwaitingGCStopped" +const _FunctionState_name = "UndefinedFunctionGroupMemAllocatedAssignedStartingExecutingExecutedTerminatingAwaitingGCStopped" -var _FunctionState_index = [...]uint8{0, 9, 21, 29, 37, 46, 54, 65, 75, 82} +var _FunctionState_index = [...]uint8{0, 9, 22, 34, 42, 50, 59, 67, 78, 88, 95} func (i FunctionState) String() string { - if i < 0 || i >= FunctionState(len(_FunctionState_index)-1) { + idx := int(i) - 0 + if i < 0 || idx >= len(_FunctionState_index)-1 { return "FunctionState(" + strconv.FormatInt(int64(i), 10) + ")" } - return _FunctionState_name[_FunctionState_index[i]:_FunctionState_index[i+1]] + return _FunctionState_name[_FunctionState_index[idx]:_FunctionState_index[idx+1]] } diff --git a/lang/test_messages.go b/lang/test_messages.go index f09d74490..dba950fa6 100644 --- a/lang/test_messages.go +++ b/lang/test_messages.go @@ -71,7 +71,7 @@ func tMsgDataTypeMatch(stdType string) string { } func tMsgStringMismatch(property string, std []byte) string { - return fmt.Sprintf("%s string mismatch. Got: '%s'", property, std) + return fmt.Sprintf("%s string mismatch. Got: %s", property, std) } func tMsgStringMatch(property string) string { return fmt.Sprintf("%s matches expected string", property) @@ -81,7 +81,7 @@ func tMsgRegexCompileErr(property string, err error) string { return fmt.Sprintf("%s could not compile: %s", property, err) } func tMsgRegexMismatch(property string, std []byte) string { - return fmt.Sprintf("%s expression did not match. Got: '%s'", property, std) + return fmt.Sprintf("%s expression did not match. Got: %s", property, std) } func tMsgRegexMatch(property string) string { return fmt.Sprintf("%s matches expected regex expression", property) diff --git a/lang/variables_reserved.go b/lang/variables_reserved.go index 80c791249..f2ad9f44c 100644 --- a/lang/variables_reserved.go +++ b/lang/variables_reserved.go @@ -20,6 +20,7 @@ var envDataTypes = map[string][]string{ func getVarSelf(p *Process) any { bg := p.Scope.Background.Get() return map[string]any{ + //"FID": int(p.Parent.Id), "Parent": int(p.Scope.Parent.Id), "Scope": int(p.Scope.Id), "TTY": p.Scope.Stdout.IsTTY(), diff --git a/main_js.go b/main_js.go index c07d2deaf..2557fb602 100644 --- a/main_js.go +++ b/main_js.go @@ -13,7 +13,6 @@ import ( "github.com/lmorg/murex/config/profile" "github.com/lmorg/murex/lang" "github.com/lmorg/murex/shell" - signalhandler "github.com/lmorg/murex/shell/signal_handler" "github.com/lmorg/murex/utils/ansi" "github.com/lmorg/readline/v4" ) @@ -124,7 +123,8 @@ func wasmKeyPress() js.Func { }) } +/* func registerSignalHandlers(interactiveMode bool) { signalhandler.Handlers = &signalhandler.SignalFunctionsT{} signalhandler.EventLoop(interactiveMode) -} +}*/ diff --git a/shell/session/session_fallback.go b/shell/session/session_fallback.go index 0c5bdc0a2..d66dc8066 100644 --- a/shell/session/session_fallback.go +++ b/shell/session/session_fallback.go @@ -1,9 +1,10 @@ -//go:build js || windows || plan9 -// +build js windows plan9 +//go:build js || windows || plan9 || tinygo +// +build js windows plan9 tinygo package session import ( + "os" "runtime" "github.com/lmorg/murex/debug" @@ -18,3 +19,7 @@ func UnixIsSession() bool { return false } func UnixCreateSession() { debug.Logf("!!! UnixCreateSession is not supported on %s", runtime.GOOS) } + +func UnixTTY() *os.File { + return os.Stdin +} diff --git a/shell/session/session_unix.go b/shell/session/session_unix.go index 93431ab55..b6268d6c5 100644 --- a/shell/session/session_unix.go +++ b/shell/session/session_unix.go @@ -1,5 +1,5 @@ -//go:build !js && !windows && !plan9 -// +build !js,!windows,!plan9 +//go:build !js && !windows && !plan9 && !tinygo +// +build !js,!windows,!plan9,!tinygo package session diff --git a/shell/shell.go b/shell/shell.go index 2c00d8d6f..e7a63fe6f 100644 --- a/shell/shell.go +++ b/shell/shell.go @@ -94,7 +94,7 @@ func Start() { } lang.Interactive = true - Prompt.TempDirectory = consts.TempDir + Prompt.TempDirectory = consts.TmpDir() definePromptHistory() Prompt.AutocompleteHistory = autocompleteHistoryLine diff --git a/main_plan9.go b/signals_plan9.go similarity index 100% rename from main_plan9.go rename to signals_plan9.go diff --git a/main_unix.go b/signals_unix.go similarity index 100% rename from main_unix.go rename to signals_unix.go diff --git a/main_windows.go b/signals_windows.go similarity index 100% rename from main_windows.go rename to signals_windows.go diff --git a/utils/cache/internal.go b/utils/cache/internal.go index b2c6ab144..6b8d77f9c 100644 --- a/utils/cache/internal.go +++ b/utils/cache/internal.go @@ -90,6 +90,11 @@ func (ic *internalCacheT) Write(key string, value *[]byte, ttl time.Time) { return } + if ttl.Before(time.Now().Add(59 * time.Minute)) { + // lets not bother writing anything to cache.db if the TTL < 1 hr + return + } + ic.mutex.Lock() ic.cache[key] = &cacheItemT{value, ttl} ic.mutex.Unlock() diff --git a/utils/consts/consts_test.go b/utils/consts/consts_test.go index f65cb47e5..add5bb386 100644 --- a/utils/consts/consts_test.go +++ b/utils/consts/consts_test.go @@ -10,11 +10,11 @@ import ( func TestTempDir(t *testing.T) { count.Tests(t, 1) - if TempDir == "" { + if TmpDir() == "" { t.Error("No temp directory specified") } - if TempDir == tempDir { - t.Log("ioutil.TempDir() failed so using fallback path:", tempDir) + if TmpDir() == _TMP_DIR { + t.Log("ioutil.TempDir() failed so using fallback path:", _TMP_DIR) } } diff --git a/utils/consts/consts_unix.go b/utils/consts/consts_unix.go index 55c9fdc56..e608b82e7 100644 --- a/utils/consts/consts_unix.go +++ b/utils/consts/consts_unix.go @@ -7,6 +7,6 @@ const ( // PathSlash is an OS specific directory separator PathSlash = "/" - // tempDir is the location of temp directory if it cannot be automatically determind - tempDir = "/tmp/murex/" + // _TMP_DIR is the location of temp directory if it cannot be automatically determined + _TMP_DIR = "/tmp/murex/" ) diff --git a/utils/consts/consts_windows.go b/utils/consts/consts_windows.go index f0a218697..716130a51 100644 --- a/utils/consts/consts_windows.go +++ b/utils/consts/consts_windows.go @@ -5,9 +5,9 @@ package consts const ( // PathSlash is an OS specific directory separator. - // Normally in Windows this would be a \ but lets standardise everything in murex to be / + // Normally in Windows this would be a \ but lets standardize everything in murex to be / PathSlash = "/" - // tempDir is the location of temp directory if it cannot be automatically determind - tempDir = `c:/temp/murex/` + // _TMP_DIR is the location of temp directory if it cannot be automatically determined + _TMP_DIR = `c:/temp/murex/` ) diff --git a/utils/consts/temp.go b/utils/consts/temp.go index 000dab6c4..781fa8686 100644 --- a/utils/consts/temp.go +++ b/utils/consts/temp.go @@ -6,22 +6,22 @@ import ( "github.com/lmorg/murex/app" ) -// TempDir is the location of temp directory -var TempDir string +// temporaryDirectory is the location of tmp directory +var temporaryDirectory string func init() { var err error - TempDir, err = os.MkdirTemp("", app.Name) - if err != nil || TempDir == "" { - TempDir = tempDir + temporaryDirectory, err = os.MkdirTemp("", app.Name) + if err != nil || temporaryDirectory == "" { + temporaryDirectory = _TMP_DIR } - if TempDir[len(TempDir)-1:] != PathSlash { - TempDir += PathSlash + if temporaryDirectory[len(temporaryDirectory)-1:] != PathSlash { + temporaryDirectory += PathSlash } - createDirIfNotExist(TempDir) + createDirIfNotExist(temporaryDirectory) } func createDirIfNotExist(dir string) { @@ -36,3 +36,7 @@ func createDirIfNotExist(dir string) { } } } + +func TmpDir() string { + return temporaryDirectory +} diff --git a/utils/docgen/main.go b/utils/docgen/main.go index d167d31de..f24164b58 100644 --- a/utils/docgen/main.go +++ b/utils/docgen/main.go @@ -14,7 +14,7 @@ const ( Version = "5.0.1" // Copyright is the copyright owner string - Copyright = "(c) 2018-2025 Laurence Morgan" + Copyright = "(c) 2018-2026 Laurence Morgan" // License is the projects software license License = "License GPL v2" diff --git a/utils/parser/pipetoken_string.go b/utils/parser/pipetoken_string.go index 558cae7d4..d62d81efb 100644 --- a/utils/parser/pipetoken_string.go +++ b/utils/parser/pipetoken_string.go @@ -21,8 +21,9 @@ const _PipeToken_name = "PipeTokenNonePipeTokenPosixPipeTokenArrowPipeTokenGener var _PipeToken_index = [...]uint8{0, 13, 27, 41, 57, 74, 89} func (i PipeToken) String() string { - if i < 0 || i >= PipeToken(len(_PipeToken_index)-1) { + idx := int(i) - 0 + if i < 0 || idx >= len(_PipeToken_index)-1 { return "PipeToken(" + strconv.FormatInt(int64(i), 10) + ")" } - return _PipeToken_name[_PipeToken_index[i]:_PipeToken_index[i+1]] + return _PipeToken_name[_PipeToken_index[idx]:_PipeToken_index[idx+1]] } diff --git a/utils/tmpfile.go b/utils/tmpfile.go index 089a2845b..47fa63eaf 100644 --- a/utils/tmpfile.go +++ b/utils/tmpfile.go @@ -34,7 +34,7 @@ func NewTempFile(reader io.Reader, ext string) (*TempFile, error) { return nil, err } - name := consts.TempDir + hex.EncodeToString(h.Sum(nil)) + "-" + strconv.Itoa(os.Getpid()) + ext + name := consts.TmpDir() + hex.EncodeToString(h.Sum(nil)) + "-" + strconv.Itoa(os.Getpid()) + ext file, err := os.Create(name) if err != nil { diff --git a/version.svg b/version.svg index 0098a0202..bc05e5f36 100644 --- a/version.svg +++ b/version.svg @@ -1 +1 @@ -Version: 7.1.4143Version7.1.4143 +Version: 7.2.0079Version7.2.0079