str]] -- )
runtime | Get the current OS runtime (GOOS). | (-- str) |
hostname | Get the current hostname, or unknown on failure. | (-- str) |
+ uuid | Generate a random (version 4) UUID as a canonical lowercase hyphenated string. | (-- str) |
+ uuid7 | Generate a time-ordered (version 7) UUID. The leading bits encode a Unix millisecond timestamp, so values sort chronologically. | (-- str) |
parseCsv | Parse CSV input (path or string) into a list of rows. | (path|str -- [[str]]) |
toGrid | Build a Grid from a table of string rows. The first row supplies column headers and remaining rows become string-valued data rows. | ([[str]] -- Grid) |
gridValues | Extract Grid or GridView cell values as row-major lists, without a header row and without coercing cell types. | (Grid|GridView -- [[a]]) |
diff --git a/doc/mshell.md b/doc/mshell.md
index 0b2411e..ef9d5c8 100644
--- a/doc/mshell.md
+++ b/doc/mshell.md
@@ -973,6 +973,8 @@ end wl # Output: 11
- `tuw`: Shorthand for `(tjoin) map uw` `([[str]] -- )`
- `runtime`: Get the current OS runtime. This is the output of the GOOS environment variable. Common possible values are `linux`, `windows`, and `darwin`. `( -- str)`
- `hostname`: Get the current OS hostname. On failure to get, puts 'unknown' on the stack. `( -- str)`
+- `uuid`: Generate a random (version 4) UUID per RFC 9562, as a canonical lowercase hyphenated string (e.g. `9a9fc320-8284-440d-a740-d038cf95b667`). `( -- str)`
+- `uuid7`: Generate a time-ordered (version 7) UUID per RFC 9562. The first 48 bits are a Unix millisecond timestamp, so the values sort chronologically; the rest is random. `( -- str)`
- `parseCsv`: Parse a CSV file into a list of lists of strings. Input can be a path/literal file name, or the string contents itself. (`path|str -- [[str]])`
- `toGrid`: Build a Grid from a list of string rows. The first row supplies column headers and remaining rows become string-valued data rows. (`[[str]] -- Grid`)
- `gridValues`: Extract Grid or GridView cell values as row-major lists, without a header row and without coercing cell types. (`Grid|GridView -- [[a]]`)
diff --git a/mshell/BuiltInList.go b/mshell/BuiltInList.go
index 7d0eae8..ab67151 100644
--- a/mshell/BuiltInList.go
+++ b/mshell/BuiltInList.go
@@ -199,6 +199,8 @@ var BuiltInList = map[string]struct{}{
"updateCol": {},
"uniq": {},
"upper": {},
+ "uuid": {},
+ "uuid7": {},
"urlEncode": {},
"utcToCst": {},
"utf8Bytes": {},
diff --git a/mshell/Evaluator.go b/mshell/Evaluator.go
index de73fd4..e106e76 100644
--- a/mshell/Evaluator.go
+++ b/mshell/Evaluator.go
@@ -5,6 +5,7 @@ import (
"archive/zip"
"bufio"
"bytes"
+ crand "crypto/rand"
"crypto/sha256"
"encoding/csv"
"encoding/base64"
@@ -4363,6 +4364,43 @@ func ParseJsonObjToMshell(jsonObj any) MShellObject {
// "\\\\?\\UNC\\server\\share\\dir" -> "dir"
// "\\\\?\\Volume{GUID}\\Windows\\Temp" -> "Windows\\Temp"
// "\\\\.\\COM1" -> "" (device path, no remainder)
+// formatUuid renders 16 bytes as the canonical lowercase hyphenated UUID string
+// (8-4-4-4-12), e.g. "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx".
+func formatUuid(b [16]byte) string {
+ return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16])
+}
+
+// NewUuidV4 generates a random (version 4) UUID as defined by RFC 9562.
+func NewUuidV4() (string, error) {
+ var b [16]byte
+ if _, err := crand.Read(b[:]); err != nil {
+ return "", err
+ }
+ b[6] = (b[6] & 0x0f) | 0x40 // Version 4
+ b[8] = (b[8] & 0x3f) | 0x80 // Variant 10
+ return formatUuid(b), nil
+}
+
+// NewUuidV7 generates a time-ordered (version 7) UUID as defined by RFC 9562.
+// The first 48 bits are a Unix timestamp in milliseconds, making the values
+// sort chronologically; the remaining bits are random.
+func NewUuidV7() (string, error) {
+ var b [16]byte
+ if _, err := crand.Read(b[:]); err != nil {
+ return "", err
+ }
+ ms := time.Now().UnixMilli()
+ b[0] = byte(ms >> 40)
+ b[1] = byte(ms >> 32)
+ b[2] = byte(ms >> 24)
+ b[3] = byte(ms >> 16)
+ b[4] = byte(ms >> 8)
+ b[5] = byte(ms)
+ b[6] = (b[6] & 0x0f) | 0x70 // Version 7
+ b[8] = (b[8] & 0x3f) | 0x80 // Variant 10
+ return formatUuid(b), nil
+}
+
func StripVolumePrefix(p string) string {
if runtime.GOOS != "windows" {
return p
@@ -10233,6 +10271,18 @@ func (state *EvalState) evaluateToken(t Token, stack *MShellStack, context Execu
} else {
stack.Push(MShellString{host})
}
+ } else if t.Lexeme == "uuid" {
+ s, err := NewUuidV4()
+ if err != nil {
+ return state.FailWithMessage(fmt.Sprintf("%d:%d: Failed to generate a UUID: %s\n", t.Line, t.Column, err.Error()))
+ }
+ stack.Push(MShellString{s})
+ } else if t.Lexeme == "uuid7" {
+ s, err := NewUuidV7()
+ if err != nil {
+ return state.FailWithMessage(fmt.Sprintf("%d:%d: Failed to generate a UUID: %s\n", t.Line, t.Column, err.Error()))
+ }
+ stack.Push(MShellString{s})
} else if t.Lexeme == "removeWindowsVolumePrefix" {
obj, err := stack.Pop()
if err != nil {
diff --git a/mshell/TypeBuiltins.go b/mshell/TypeBuiltins.go
index c3d3f12..1e6e244 100644
--- a/mshell/TypeBuiltins.go
+++ b/mshell/TypeBuiltins.go
@@ -506,6 +506,8 @@ func builtinSigsByName(arena *TypeArena, names *NameTable) map[NameId][]QuoteSig
r.reg(name, "(str str -- int)")
}
r.reg("hostname", "( -- str)")
+ r.reg("uuid", "( -- str)")
+ r.reg("uuid7", "( -- str)")
r.reg("pwd", "( -- path)")
r.reg("args", "( -- [str])")
r.reg("md5", "(str | path | bytes -- str)")
diff --git a/tests/success/uuid.msh b/tests/success/uuid.msh
new file mode 100644
index 0000000..0e98ae6
--- /dev/null
+++ b/tests/success/uuid.msh
@@ -0,0 +1,2 @@
+uuid '^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' reMatch ("uuid ok") ("uuid BAD") iff wl
+uuid7 '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' reMatch ("uuid7 ok") ("uuid7 BAD") iff wl
diff --git a/tests/success/uuid.msh.stdout b/tests/success/uuid.msh.stdout
new file mode 100644
index 0000000..3464c77
--- /dev/null
+++ b/tests/success/uuid.msh.stdout
@@ -0,0 +1,2 @@
+uuid ok
+uuid7 ok