From 50a697757994c6683525a1d42b2206c920b0425f Mon Sep 17 00:00:00 2001 From: Mitchell Paulus Date: Thu, 29 Jan 2026 09:57:23 -0600 Subject: [PATCH] Add tempFileExt, improve temp file docs and handling --- CHANGELOG.md | 1 + doc/functions.inc.html | 5 +++-- doc/mshell.md | 5 +++-- mshell/BuiltInList.go | 1 + mshell/Evaluator.go | 27 +++++++++++++++++++++++++++ 5 files changed, 35 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28f4840..1edbfa0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `randomNorm` - `sqrt` - `randomTri` + - `tempFileExt` ### Fixed diff --git a/doc/functions.inc.html b/doc/functions.inc.html index b820255..f90dbe5 100644 --- a/doc/functions.inc.html +++ b/doc/functions.inc.html @@ -85,8 +85,9 @@

File and Directory isFile Test whether a path is a file. (path -- bool) fileExists Check whether a filesystem entry exists. Uses go os.Lstat for determination. This means that broken symlink will return true. (str|path -- bool) hardLink Create a hard link. (path:source path:dest -- ) - tempFile Create a temporary file and push its path. (-- str) - tempDir Return the OS-specific temporary directory via os.TempDir (for example, $TMPDIR on Unix or %TMP% on Windows). (-- str) + tempFile Create a temporary file and push its path. Uses go os.CreateTemp with dir="" (defaults to os.TempDir) and pattern msh-, so the filename starts with msh- plus a random suffix. The file is not removed automatically; use rm/rmf when you are done. (-- path) + tempFileExt Create a temporary file with a required extension and push its path. The extension input is canonicalized to start with . if needed. Uses go os.CreateTemp with dir="" (defaults to os.TempDir) and pattern msh-* plus the canonicalized extension, so the filename ends with the extension. The file is not removed automatically; use rm/rmf when you are done. (str|path -- path) + tempDir Return the OS-specific temporary directory via os.TempDir (for example, $TMPDIR on Unix or %TMP% on Windows). (-- path) rm Remove a file; fails on IO errors. (str -- ) rmf Remove a file; ignore IO errors. (str -- ) cp Copy a file or directory. (str:source str:dest -- ) diff --git a/doc/mshell.md b/doc/mshell.md index a22f95c..6bfa6c1 100644 --- a/doc/mshell.md +++ b/doc/mshell.md @@ -227,8 +227,9 @@ Metadata values must be static: strings (single or double quoted), integers, flo - `isFile`: Check if path is a file. `(path -- bool)` - `fileExists`: Check whether a file or directory exists. `(str|path -- bool)` - `hardLink`: Create a hard link. `(existingSourcePath newTargetPath -- )` -- `tempFile`: Create a temporary file, and put the full path on the stack. `( -- str)` -- `tempDir`: Return path to the OS specific temporary directory. No checks on permission or existence, so never fails. See [`os.TempDir`](https://pkg.go.dev/os#TempDir) in golang. `$TMPDIR` or `/tmp` for Unix, `%TMP%`, `%TEMP%, %USERPROFILE%`, or the Windows directory (`C:\Windows`). `( -- str)` +- `tempFile`: Create a temporary file with go [`os.CreateTemp`](https://pkg.go.dev/os#CreateTemp) using `dir=""` (defaults to [`os.TempDir`](https://pkg.go.dev/os#TempDir)) and pattern `msh-`, so the filename starts with `msh-` plus a random suffix. Pushes the full path. The file is not removed automatically; use `rm`/`rmf` when you are done. `( -- path)` +- `tempFileExt`: Create a temporary file with a required extension. The extension input is canonicalized to start with `.` if needed. Uses go [`os.CreateTemp`](https://pkg.go.dev/os#CreateTemp) with `dir=""` (defaults to [`os.TempDir`](https://pkg.go.dev/os#TempDir)) and pattern `msh-*` plus the canonicalized extension, so the filename ends with the extension. Pushes the full path. The file is not removed automatically; use `rm`/`rmf` when you are done. `(str|path -- path)` +- `tempDir`: Return path to the OS specific temporary directory. No checks on permission or existence, so never fails. See [`os.TempDir`](https://pkg.go.dev/os#TempDir) in golang. `$TMPDIR` or `/tmp` for Unix, `%TMP%`, `%TEMP%, %USERPROFILE%`, or the Windows directory (`C:\Windows`). `( -- path)` - `rm`: Remove file. Will stop execution on IO error, including file not found. `(str -- )` - `rmf`: Remove file. Will not stop execution on IO error, including file not found. `(str -- )` - `cp`: Copy file or directory. `(str:source str:dest -- )` diff --git a/mshell/BuiltInList.go b/mshell/BuiltInList.go index 18bf4f9..41afb6c 100644 --- a/mshell/BuiltInList.go +++ b/mshell/BuiltInList.go @@ -137,6 +137,7 @@ var BuiltInList = map[string]struct{}{ "take": {}, "tempDir": {}, "tempFile": {}, + "tempFileExt": {}, "title": {}, "toDt": {}, "toFixed": {}, diff --git a/mshell/Evaluator.go b/mshell/Evaluator.go index db411bd..b3d1a63 100644 --- a/mshell/Evaluator.go +++ b/mshell/Evaluator.go @@ -2080,6 +2080,33 @@ MainLoop: if err != nil { return state.FailWithMessage(fmt.Sprintf("%d:%d: Error creating temporary file: %s\n", t.Line, t.Column, err.Error())) } + if err := tmpfile.Close(); err != nil { + return state.FailWithMessage(fmt.Sprintf("%d:%d: Error closing temporary file: %s\n", t.Line, t.Column, err.Error())) + } + // Dump the full path to the stack + stack.Push(MShellPath{tmpfile.Name()}) + } else if t.Lexeme == "tempFileExt" { + extValue, err := stack.Pop() + if err != nil { + return state.FailWithMessage(fmt.Sprintf("%d:%d: Cannot do 'tempFileExt' operation on an empty stack.\n", t.Line, t.Column)) + } + + extStr, err := extValue.CastString() + if err != nil { + return state.FailWithMessage(fmt.Sprintf("%d:%d: Cannot create tempFileExt with a %s.\n", t.Line, t.Column, extValue.TypeName())) + } + + if extStr != "" && !strings.HasPrefix(extStr, ".") { + extStr = "." + extStr + } + + tmpfile, err := os.CreateTemp("", "msh-*"+extStr) + if err != nil { + return state.FailWithMessage(fmt.Sprintf("%d:%d: Error creating temporary file: %s\n", t.Line, t.Column, err.Error())) + } + if err := tmpfile.Close(); err != nil { + return state.FailWithMessage(fmt.Sprintf("%d:%d: Error closing temporary file: %s\n", t.Line, t.Column, err.Error())) + } // Dump the full path to the stack stack.Push(MShellPath{tmpfile.Name()}) } else if t.Lexeme == "tempDir" {