From 3086bb0cbc14a9f42084efadaf666acff9bccb47 Mon Sep 17 00:00:00 2001 From: Birdee <85372418+BirdeeHub@users.noreply.github.com> Date: Mon, 11 May 2026 22:13:25 -0700 Subject: [PATCH] feat(run_cmd): post_5_2_run and pre_5_2_run deprecated for run_cmd it made the API far more complex to read than was necessary, and generally when defining a new backend, both versions end up recieving the same function anyway --- README.md | 21 +++------ REPR.md | 85 +++++++++++++++++----------------- lua/sh.lua | 133 +++++++++++++++++++++++++++++------------------------ 3 files changed, 119 insertions(+), 120 deletions(-) diff --git a/README.md b/README.md index 16a8248..859075e 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Lua][lua-shield]][lua-url] [![LuaRocks][luarocks-shield]][luarocks-url] -Tiny library for shell scripting with Lua (inspired by zserge/luash). +Tiny library for shell scripting with Lua (inspired by [zserge/luash](https://github.com/zserge/luash)). `luash` is interesting, but it modifies `_G` in an extreme way. @@ -20,15 +20,6 @@ It works with any "posix-enough" shell by default such as `bash`, `zsh`, and `da But it will not work by default with `fish`, `nushell`, `cmd` or `powershell` unless you define a [representation](./REPR.md) for that shell. -It also exports a [small nix helper](#in-addition-to-the-library) that allows you -to use `shelua` to write `nix` derivations in `lua` instead of `bash`. - -It is `pkgs.runCommand` except it is `pkgs.runLuaCommand` because the command is in `lua`. - -It is useful when you have a short build or wrapper script that needs to deal with a lot of structured data. - -Especially when you have a lot of `json` and would rather use `cjson` and deal with tables than use `jq` and bash arrays - ## Install via luarocks: `luarocks install shelua` @@ -39,7 +30,7 @@ Or just clone this repo and copy `lua/sh.lua` into your project. ## Simple usage -Every command that can be called via os.execute can be called via the sh table. +Every command that can be called via `os.execute` can be called via the sh table. All the arguments passed into the function become command arguments. ``` lua @@ -56,7 +47,7 @@ end ## Command input and pipelines If command argument is a table which has a `__input` field - it will be used as -a command input (stdin). Multiple arguments with input are allowed, they will +a command input (`stdin`). Multiple arguments with input are allowed, they will be concatenated. The each command function returns a structure that contains the `__input` @@ -84,8 +75,8 @@ sh.ls '/bin' : grep "$filter" : wc '-l' ``` Note that the commands are not running in parallel (because Lua can only handle -one I/O loop at a time). So the inner-most command is executed, its output is -read, the the outer command is execute with the output redirected etc. +one `I/O` loop at a time). So the inner-most command is executed, its output is +read, the outer command is execute with the output redirected etc. However, `shelua` also offers a `proper_pipes` [setting](#settings). @@ -116,7 +107,7 @@ arguments without manually changing the arguments list. ## Partial commands and commands with tricky names or characters You can call `sh` with a string as the first argument to construct a command function, optionally -pre-setting the arguments: +presetting the arguments: ``` lua local sh = require('sh') diff --git a/REPR.md b/REPR.md index ef1185f..dee14ad 100644 --- a/REPR.md +++ b/REPR.md @@ -78,14 +78,14 @@ Its first return value will then be passed through any defined `transforms`. the result, in addition to its optional second return value will then be passed to one of the 2 following run functions based on current lua version. -`post_5_2_run` and `pre_5_2_run` are what call the actual final shell command when needed. +`run_cmd` is what calls the actual final shell command when needed. -Their job is to run the command and report the result, exit and signal codes. +Its job is to run the command and report the result, exit and signal codes. -Prior to 5.2 the io.popen command does not return exit code or signal. You can decide to support older than 5.2 or not. +Prior to 5.2 the `io.popen` command does not return exit code or signal. You can decide to support older than 5.2 or not. ```lua - ---returns cmd and an optional item such as path to a tempfile to be passed to post_5_2_run or pre_5_2_run + ---returns cmd and an optional item such as path to a tempfile to be passed to run_cmd ---called only when proper_pipes is false ---cmd is the result of add_args ---codes is the list of codes that correspond with each input such as `__exitcode`, empty if none @@ -105,53 +105,50 @@ Prior to 5.2 the io.popen command does not return exit code or signal. You can d end, ---runs the command and returns the result and exit code and signal -- cmd is the result of single_stdin or concat_cmd, after being passed through any defined transforms - ---@field post_5_2_run fun(opts: Shelua.Opts, cmd: string|any, msg: any?): { __input: string, __exitcode: number, __signal: number } - post_5_2_run = function(opts, cmd, tmp) - local p = io.popen(cmd, 'r') - local output, exit, status - if p then - output = p:read('*a') - _, exit, status = p:close() - end - pcall(os.remove, tmp) + ---@field run_cmd fun(opts: Shelua.Opts, cmd: string|any, msg: any?): { __input: string, __exitcode: number, __signal: number } + run_cmd = function(opts, cmd, tmp) + if is_5_2_plus then + local p = io.popen(cmd, 'r') + local output, _, exit, status + if p then + output = p:read('*a') + _, exit, status = p:close() + end + pcall(os.remove, tmp) - return { - __input = output, - __exitcode = exit == 'exit' and status or 127, - __signal = exit == 'signal' and status or 0, - } - end, - ---runs the command and returns the result and exit code and signal - ---Should return the flags using the same format as io.popen does in 5.2+ - -- cmd is the result of single_stdin or concat_cmd, after being passed through any defined transforms - ---@field pre_5_2_run fun(opts: Shelua.Opts, cmd: string|any, msg: any?): { __input: string, __exitcode: number, __signal: number } - pre_5_2_run = function(opts, cmd, tmp) - local p = io.popen(cmd .. "\necho __EXITCODE__$?", 'r') - local output - if p then - output = p:read('*a') - p:close() + return { + __input = output, + __exitcode = exit == 'exit' and status or 127, + __signal = exit == 'signal' and status or 0, + } + else + local p = io.popen(cmd .. "\necho __EXITCODE__$?", 'r') + local output + if p then + output = p:read('*a') + p:close() + end + pcall(os.remove, tmp) + local exit + output = (output or ""):gsub("__EXITCODE__(%d*)\r?\n?$", function(code) + exit = tonumber(code) + return "" + end) + return { + __input = output, + __exitcode = exit or 127, + __signal = (exit and exit > 128) and (exit - 128) or 0 + } end - pcall(os.remove, tmp) - local exit - output = (output or ""):gsub("__EXITCODE__(%d*)\r?\n?$", function(code) - exit = tonumber(code) - return "" - end) - return { - __input = output, - __exitcode = exit or 127, - __signal = (exit and exit > 128) and (exit - 128) or 0 - } end, - ---if your pre_5_2_run or post_5_2_run returns a table with extra keys, e.g. `__stderr` + ---if your run_cmd returns a table with extra keys, e.g. `__stderr` ---proper_pipes will need to know that accessing them should be a trigger to resolve the pipe. ---each string in this table must begin with '__' or it will be ignored ---@field extra_cmd_results string[]|fun(opts: Shelua.Opts): string[] extra_cmd_results = {}, ---a list of functions to run in order on the command before running it. ---each one recieves the previous value and returns a new one. - ---they are ran after concat_cmd or single_stdin and before the post_5_2_run and pre_5_2_run functions + ---they are ran after concat_cmd or single_stdin and before the run_cmd functions ---@field transforms? (fun(cmd: string|any): string|any)[] transforms = {}, ``` @@ -172,7 +169,7 @@ which is the optional second return value of the call to `concat_cmd` Your goal in this function is to construct a string from the prior inputs, that pipes them into the command, and then return that string, if there are any prior inputs to pipe. -Its result will be provided to the same run function as `single_stdin` would have, either `pre_5_2_run` or `post_5_2_run`, +Its result will be provided to the same run function as `single_stdin` would have, `run_cmd`, after adding the newly resolved values to the command result being resolved. ```lua @@ -192,7 +189,7 @@ after adding the newly resolved values to the command result being resolved. ---strategy to combine piped inputs, 0, 1, or many, return resolved command to run ---called only when proper_pipes is true - ---may return an optional second value to be placed in another PipeInput, or returned to post_5_2_run or pre_5_2_run + ---may return an optional second value to be placed in another PipeInput, or returned to run_cmd -- cmd is the same type as the result of add_args ---@field concat_cmd fun(opts: Shelua.Opts, cmd: string|any, input: Shelua.PipeInput[]): (string|any, any?) concat_cmd = function(opts, cmd, input) diff --git a/lua/sh.lua b/lua/sh.lua index 8f33771..eefaed4 100644 --- a/lua/sh.lua +++ b/lua/sh.lua @@ -1,17 +1,21 @@ ---Will contain either `s`, a plain string, ---or `c`, an input command string ----@class Shelua.PipeInput +---@class Shelua.PipeInputStdin ---string stdin to combine ---@field s? string|any ---if string input came from a command, ---`e` will contain a table of all other command result fields ---such as `__exitcode` ---@field e? table + +---@class Shelua.PipeInputClass ---cmd to combine ---@field c? string|any ---optional 2nd return of concat_cmd ---@field m? any +---@alias Shelua.PipeInput Shelua.PipeInputStdin | Shelua.PipeInputClass + ---@class Shelua.Repr ---escapes a string for the shell ---@field escape fun(arg: any, opts: Shelua.Opts?): string @@ -20,7 +24,7 @@ ---@field arg_tbl fun(opts: Shelua.Opts, k: string, a: any): string|string[]? ---adds args to the command ---@field add_args fun(opts: Shelua.Opts, cmd: string, args: string[]): string|any ----returns cmd and an optional item such as path to a tempfile to be passed to post_5_2_run or pre_5_2_run +---returns cmd and an optional item such as path to a tempfile to be passed to run_cmd ---called when proper_pipes is false ---cmd is the result of add_args ---codes is the list of codes that correspond with each input such as `__exitcode`, empty if none @@ -30,13 +34,11 @@ ---@field concat_cmd fun(opts: Shelua.Opts, cmd: string|any, input: Shelua.PipeInput[]): (string|any, any?) ---a list of functions to run in order on the command before running it. ---each one recieves the previous value and returns a new one. ----they are ran after concat_cmd or single_stdin and before the post_5_2_run and pre_5_2_run functions +---they are ran after concat_cmd or single_stdin and before the run_cmd functions ---@field transforms? (fun(cmd: string|any): string|any)[] ---runs the command and returns the result and exit code and signal ----@field post_5_2_run fun(opts: Shelua.Opts, cmd: string|any, msg: any?): { __input: string, __exitcode: number, __signal: number } ----runs the command and returns the result and exit code and signal ----@field pre_5_2_run fun(opts: Shelua.Opts, cmd: string|any, msg: any?): { __input: string, __exitcode: number, __signal: number } ----if your pre_5_2_run or post_5_2_run returns a table with extra keys, e.g. `__stderr` +---@field run_cmd fun(opts: Shelua.Opts, cmd: string|any, msg: any?): { __input: string, __exitcode: number, __signal: number } +---if your run_cmd returns a table with extra keys, e.g. `__stderr` ---proper_pipes will need to know that accessing them should be a trigger to resolve the pipe. ---each string in this table must begin with '__' or it will be ignored ---@field extra_cmd_results string[]|fun(opts: Shelua.Opts): string[] @@ -134,10 +136,34 @@ local function tbl_get(t, default, ...) return t or default end +local warned_run_cmd_shim = false + ---@param opts Shelua.Opts ---@param attr string ---@return function local get_repr_fn = function(opts, attr) + if attr == "run_cmd" then + local shell = opts.shell or "posix" + local shell_repr = tbl_get(opts, nil, "repr", shell) + if shell_repr and not shell_repr.run_cmd then + local old_post = shell_repr.post_5_2_run + local old_pre = shell_repr.pre_5_2_run + if old_post or old_pre then + if not warned_run_cmd_shim then + warned_run_cmd_shim = true + io.stderr:write("shelua: post_5_2_run/pre_5_2_run are deprecated. ") + io.stderr:write("Use run_cmd(opts, cmd, msg) instead.\n") + end + shell_repr.run_cmd = function(o, cmd, msg) + if is_5_2_plus and old_post then + return old_post(o, cmd, msg) + else + return (old_pre or old_post)(o, cmd, msg) + end + end + end + end + end return tbl_get(opts, tbl_get(opts, function() error("Shelua Repr Error: " .. tostring(attr) .. " function required for shell: " .. tostring(opts.shell or "posix")) @@ -222,39 +248,40 @@ local posix = { return cmd end end, - post_5_2_run = function(opts, cmd, tmp) - local p = io.popen(cmd, 'r') - local output, _, exit, status - if p then - output = p:read('*a') - _, exit, status = p:close() - end - pcall(os.remove, tmp) + run_cmd = function(opts, cmd, tmp) + if is_5_2_plus then + local p = io.popen(cmd, 'r') + local output, _, exit, status + if p then + output = p:read('*a') + _, exit, status = p:close() + end + pcall(os.remove, tmp) - return { - __input = output, - __exitcode = exit == 'exit' and status or 127, - __signal = exit == 'signal' and status or 0, - } - end, - pre_5_2_run = function(opts, cmd, tmp) - local p = io.popen(cmd .. "\necho __EXITCODE__$?", 'r') - local output - if p then - output = p:read('*a') - p:close() + return { + __input = output, + __exitcode = exit == 'exit' and status or 127, + __signal = exit == 'signal' and status or 0, + } + else + local p = io.popen(cmd .. "\necho __EXITCODE__$?", 'r') + local output + if p then + output = p:read('*a') + p:close() + end + pcall(os.remove, tmp) + local exit + output = (output or ""):gsub("__EXITCODE__(%d*)\r?\n?$", function(code) + exit = tonumber(code) + return "" + end) + return { + __input = output, + __exitcode = exit or 127, + __signal = (exit and exit > 128) and (exit - 128) or 0 + } end - pcall(os.remove, tmp) - local exit - output = (output or ""):gsub("__EXITCODE__(%d*)\r?\n?$", function(code) - exit = tonumber(code) - return "" - end) - return { - __input = output, - __exitcode = exit or 127, - __signal = (exit and exit > 128) and (exit - 128) or 0 - } end, extra_cmd_results = {}, transforms = {}, @@ -362,21 +389,14 @@ local cmd_mt = { end if check_if_cmd_result(opts, c) then local apply = function(com) - local transforms = opts.transforms - if transforms then print("Shelua Deprecation: transforms option moved to be a repr-specific option") end - transforms = tbl_get(opts, transforms or {}, "repr", opts.shell or "posix", "transforms") + local transforms = tbl_get(opts, {}, "repr", opts.shell or "posix", "transforms") for _, f in ipairs(transforms) do com = f(com) end return com end local cmd, msg = resolve(self, opts) - local res - if is_5_2_plus then - res = get_repr_fn(opts, "post_5_2_run")(opts, apply(cmd), msg) - else - res = get_repr_fn(opts, "pre_5_2_run")(opts, apply(cmd), msg) - end + local res = get_repr_fn(opts, "run_cmd")(opts, apply(cmd), msg) for k, v in pairs(res or {}) do rawset(self, k, v) end @@ -494,25 +514,16 @@ command = function(self, cmdstr, ...) unresolved[t] = { cmd = cmd, unres = unres, input = input, codes = codes } else local apply = function(com) - local transforms = shmt.transforms - if transforms then print("Shelua Deprecation: transforms option moved to be a repr-specific option") end - transforms = tbl_get(shmt, transforms or {}, "repr", shmt.shell or "posix", "transforms") + local transforms = tbl_get(shmt, {}, "repr", shmt.shell or "posix", "transforms") for _, f in ipairs(transforms) do com = f(com) end return com end - if is_5_2_plus then - local msg - cmd, msg = get_repr_fn(shmt, "single_stdin")(shmt, cmd, #input > 0 and input or nil, - #codes > 0 and codes or nil) - t = get_repr_fn(shmt, "post_5_2_run")(shmt, apply(cmd), msg) - else - local msg - cmd, msg = get_repr_fn(shmt, "single_stdin")(shmt, cmd, #input > 0 and input or nil, - #codes > 0 and codes or nil) - t = get_repr_fn(shmt, "pre_5_2_run")(shmt, apply(cmd), msg) - end + local msg + cmd, msg = get_repr_fn(shmt, "single_stdin")(shmt, cmd, #input > 0 and input or nil, + #codes > 0 and codes or nil) + t = get_repr_fn(shmt, "run_cmd")(shmt, apply(cmd), msg) if shmt.assert_zero and t.__exitcode ~= 0 then error("Command " .. tostring(cmd) .. " exited with non-zero status: " .. tostring(t.__exitcode)) end