From 36f887f48639c5b918b6343059b1b1ab861ba709 Mon Sep 17 00:00:00 2001 From: Birdee <85372418+BirdeeHub@users.noreply.github.com> Date: Fri, 15 May 2026 19:36:15 -0700 Subject: [PATCH] feat(nested settings): better handling for setting nested settings swapped sh._x_key for sh[{'key'}] = { nested = { idk } } instead Added sh[{'key','nested','idk','something'}] = true as well --- README.md | 10 ++++++++-- lua/sh.lua | 38 ++++++++++++++++++++++++++++++++++---- tests/test.lua | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a820c26..44e2e6b 100644 --- a/README.md +++ b/README.md @@ -223,10 +223,16 @@ sh.repr = { newshell = { ... } } ``` This would overwrite the entire `repr` table, erasing any already there. -You may prefix the setting name with `_x_` to deep extend the tables together instead. +There are 2 other methods designed for more easily setting nested values. ```lua local sh = require('sh') -sh._x_repr = { newshell = { ... } } +sh.proper_pipes = true +-- setting a top level value like this will perform a key-based deep merge +sh[{"repr"}] = { posix = { transforms = { function(v) print(v) return v end } } } +-- or provide a list of names to directly set a nested value! (does not merge) +sh[{"repr", "posix", "transforms"}] = { function(v) print(v) return v end } +-- The above examples add a print to the posix repr without overwriting the other representation functions +-- (so you can see what the final command looks like!) ``` ## For nix users diff --git a/lua/sh.lua b/lua/sh.lua index c14622d..0262ad2 100644 --- a/lua/sh.lua +++ b/lua/sh.lua @@ -136,6 +136,21 @@ local function tbl_get(t, default, ...) return t or default end +---@param t table +---@param value any +local function setNested(t, value, ...) + local keys = {...} + local cur = t + for i = 1, #keys - 1 do + local k = keys[i] + if type(cur[k]) ~= "table" then + cur[k] = {} + end + cur = cur[k] + end + cur[keys[#keys]] = value +end + local warned_run_cmd_shim = false ---@param opts Shelua.Opts @@ -419,6 +434,7 @@ local cmd_mt = { end, } +local has_warned_about_x_ = false local MT = { ---@type Shelua.Opts __metatable = { @@ -441,13 +457,27 @@ local MT = { -- change settings by assigning them to table __newindex = function(self, key, value) if type(key) == "string" and key:sub(1, 3) == "_x_" then - local fkey = key:sub(4) + if not has_warned_about_x_ then + io.stderr:write("shelua: Using `sh._x_name = { nested = { settings = { to_be = \"merged\" } } }` to deep extend settings has been deprecated.\n") + io.stderr:write("Use `sh[{\"name\"}] = { nested = { settings = { to_be = \"merged\" } } }` instead.\n") + end + key = key:sub(4) + local opts = getmetatable(self) + if type(rawget(opts, key)) ~= "table" then + opts[key] = value + else + recUpdate(opts[key], value) + end + elseif type(key) == "table" and key[1] and #key == 1 then + key = key[1] local opts = getmetatable(self) - if type(rawget(opts, fkey)) ~= "table" then - opts[fkey] = value + if type(rawget(opts, key)) ~= "table" then + opts[key] = value else - recUpdate(opts[fkey], value) + recUpdate(opts[key], value) end + elseif type(key) == "table" and #key > 1 then + setNested(getmetatable(self), value, (unpack or table.unpack)(key)) else getmetatable(self)[key] = value end diff --git a/tests/test.lua b/tests/test.lua index c6a10a8..f8adfe5 100644 --- a/tests/test.lua +++ b/tests/test.lua @@ -65,6 +65,42 @@ test('Check command with table args', function() local r = sh.stat('/bin', { format = '%a %n' }) ok(tostring(r) == '755 /bin', 'stat --format "%a %n" /bin') end) +test('Check settings deep merge via sh[{"key"}]', function() + local s = sh() + s[{"repr"}] = { posix = { foo = "bar" } } + local opts = getmetatable(s) + ok(opts.repr.posix.foo == "bar", "merge adds new nested key") + ok(opts.repr.posix.escape ~= nil, "merge preserves existing keys") +end) + +test('Check settings nested set via multi-key table', function() + local s = sh() + s[{"repr", "posix", "foo"}] = "bar" + local opts = getmetatable(s) + ok(opts.repr.posix.foo == "bar", "nested set via multi-key table") +end) + +test('Check table key merge preserves keys set via nested set', function() + local s = sh() + s[{"repr", "posix", "foo"}] = "bar" + s[{"repr"}] = { posix = { bar = "baz" } } + local opts = getmetatable(s) + ok(opts.repr.posix.foo == "bar", "merge preserves keys set via nested set") + ok(opts.repr.posix.bar == "baz", "merge adds new keys") +end) + +test('Check sh[{"key"}] overwrites non-table settings', function() + local s = sh() + s[{"escape_args"}] = true + ok(getmetatable(s).escape_args == true, "sh[{key}] with non-table value overwrites") +end) + +test('Check normal setting still works', function() + local s = sh() + s.escape_args = true + ok(getmetatable(s).escape_args == true, "direct setting works") +end) + test('Check concat command results', function() local r = sh.echo 'Hello World' :sed("s/Hello/Goodbye/g") .. " " .. sh.echo 'Hello Lua' :sed "s/Hello/Goodbye/g" ok(tostring(r) == 'Goodbye World Goodbye Lua', 'concat commands with string')