From 514384deb9392a6a88e14ee7471c215cf89f5958 Mon Sep 17 00:00:00 2001 From: Daniel Kongsgaard Date: Mon, 25 Sep 2023 02:49:47 +0200 Subject: [PATCH 01/42] First draft of some table functions. --- Cargo.lock | 10 +++++ env-bootstrap/Cargo.toml | 1 + env-bootstrap/src/lib.rs | 1 + lua-api-crates/table-funcs/Cargo.toml | 11 +++++ lua-api-crates/table-funcs/src/lib.rs | 60 +++++++++++++++++++++++++++ 5 files changed, 83 insertions(+) create mode 100644 lua-api-crates/table-funcs/Cargo.toml create mode 100644 lua-api-crates/table-funcs/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index bcc3f9f7589..4c09fa489da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1501,6 +1501,7 @@ dependencies = [ "share-data", "spawn-funcs", "ssh-funcs", + "table-funcs", "termwiz", "termwiz-funcs", "time-funcs", @@ -5115,6 +5116,15 @@ dependencies = [ "libc", ] +[[package]] +name = "table-funcs" +version = "0.1.0" +dependencies = [ + "anyhow", + "config", + "luahelper", +] + [[package]] name = "tabout" version = "0.3.0" diff --git a/env-bootstrap/Cargo.toml b/env-bootstrap/Cargo.toml index f2bb155a00f..f8a4ce3f59f 100644 --- a/env-bootstrap/Cargo.toml +++ b/env-bootstrap/Cargo.toml @@ -30,6 +30,7 @@ ssh-funcs = { path = "../lua-api-crates/ssh-funcs" } spawn-funcs = { path = "../lua-api-crates/spawn-funcs" } time-funcs = { path = "../lua-api-crates/time-funcs" } url-funcs = { path = "../lua-api-crates/url-funcs" } +table-funcs = { path = "../lua-api-crates/table-funcs" } wezterm-version = { path = "../wezterm-version" } [target."cfg(windows)".dependencies] diff --git a/env-bootstrap/src/lib.rs b/env-bootstrap/src/lib.rs index ebe6751d62d..7a16ab753b8 100644 --- a/env-bootstrap/src/lib.rs +++ b/env-bootstrap/src/lib.rs @@ -206,6 +206,7 @@ fn register_lua_modules() { share_data::register, time_funcs::register, url_funcs::register, + table_funcs::register, ] { config::lua::add_context_setup_func(func); } diff --git a/lua-api-crates/table-funcs/Cargo.toml b/lua-api-crates/table-funcs/Cargo.toml new file mode 100644 index 00000000000..6afb14bf51b --- /dev/null +++ b/lua-api-crates/table-funcs/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "table-funcs" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0" +config = { path = "../../config" } +luahelper = { path = "../../luahelper" } diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs new file mode 100644 index 00000000000..0c8bb7d9675 --- /dev/null +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -0,0 +1,60 @@ +use config::lua::get_or_create_sub_module; +use config::lua::mlua::{self, Lua, Table, Value as LuaValue}; + +pub fn register(lua: &Lua) -> anyhow::Result<()> { + let table = get_or_create_sub_module(lua, "table")?; + table.set("merge", lua.create_function(merge)?)?; + table.set("clone", lua.create_function(clone)?)?; + table.set("flatten", lua.create_function(flatten)?)?; + + Ok(()) +} + +// merge tables +// (in case of overlap of the tables, we take the key-value pair from the last table) +// +fn merge<'lua>(lua: &'lua Lua, tables: Vec>) -> mlua::Result> { + let mut tbl_vec: Vec<(LuaValue, LuaValue)> = vec![]; + for table in tables { + for inner_pair in table.pairs::() { + let (key, value) = inner_pair.map_err(mlua::Error::external)?; + tbl_vec.push((key, value)); + } + } + let tbl_len = tbl_vec.len(); + let tbl: Table<'lua> = lua + .create_table_with_capacity(0, tbl_len) + .map_err(mlua::Error::external)?; + for (key, value) in tbl_vec { + // Note that we override previously set key values if we have + // the same key showing up more than once + tbl.set(key, value).map_err(mlua::Error::external)?; + } + Ok(tbl) +} + +fn clone<'lua>(_: &'lua Lua, table: Table<'lua>) -> mlua::Result> { + Ok(table.clone()) +} + +fn flatten<'lua>(lua: &'lua Lua, table: Vec>) -> mlua::Result> { + let flat_vec: Vec = table.into_iter().flatten().collect(); + let flat_table = lua + .create_table_with_capacity(flat_vec.len(), 0) + .map_err(mlua::Error::external)?; + for value in flat_vec { + flat_table.push(value).map_err(mlua::Error::external)?; + } + Ok(flat_table) +} + +// #[cfg(test)] +// mod tests { +// use super::*; +// +// #[test] +// fn it_works() { +// let result = add(2, 2); +// assert_eq!(result, 4); +// } +// } From f3a67541623774afd6b0610928515866885afae7 Mon Sep 17 00:00:00 2001 From: Daniel Kongsgaard Date: Tue, 26 Sep 2023 00:43:18 +0200 Subject: [PATCH 02/42] Added more table functions + docs. --- ci/generate-docs.py | 4 + docs/config/lua/wezterm.table/clone.md | 20 +++ docs/config/lua/wezterm.table/flatten.md | 20 +++ docs/config/lua/wezterm.table/has_key.md | 24 +++ docs/config/lua/wezterm.table/has_value.md | 24 +++ docs/config/lua/wezterm.table/index.markdown | 7 + docs/config/lua/wezterm.table/index.md | 16 ++ docs/config/lua/wezterm.table/length.md | 26 ++++ docs/config/lua/wezterm.table/merge.md | 36 +++++ docs/config/lua/wezterm.table/to_string.md | 35 +++++ lua-api-crates/table-funcs/src/lib.rs | 146 ++++++++++++++++--- 11 files changed, 335 insertions(+), 23 deletions(-) create mode 100644 docs/config/lua/wezterm.table/clone.md create mode 100644 docs/config/lua/wezterm.table/flatten.md create mode 100644 docs/config/lua/wezterm.table/has_key.md create mode 100644 docs/config/lua/wezterm.table/has_value.md create mode 100644 docs/config/lua/wezterm.table/index.markdown create mode 100644 docs/config/lua/wezterm.table/index.md create mode 100644 docs/config/lua/wezterm.table/length.md create mode 100644 docs/config/lua/wezterm.table/merge.md create mode 100644 docs/config/lua/wezterm.table/to_string.md diff --git a/ci/generate-docs.py b/ci/generate-docs.py index a4d15fc96e4..51f5b42738b 100644 --- a/ci/generate-docs.py +++ b/ci/generate-docs.py @@ -382,6 +382,10 @@ def render(self, output, depth=0, mode="mdbook"): "module: wezterm.color", "config/lua/wezterm.color", ), + Gen( + "module: wezterm.table", + "config/lua/wezterm.table", + ), Gen( "module: wezterm.gui", "config/lua/wezterm.gui", diff --git a/docs/config/lua/wezterm.table/clone.md b/docs/config/lua/wezterm.table/clone.md new file mode 100644 index 00000000000..c2144cac620 --- /dev/null +++ b/docs/config/lua/wezterm.table/clone.md @@ -0,0 +1,20 @@ +# `wezterm.table.clone(table)` + +{{since('nightly')}} + +This function clones the Lua table (or array) passed to it. + +```lua +local wezterm = require 'wezterm' +local clone = wezterm.table.clone + +local tbl1 = { + a = 1, + b = '2', +} + +local tbl2 = clone(tbl1) + +assert(tbl1 == tbl2) +``` + diff --git a/docs/config/lua/wezterm.table/flatten.md b/docs/config/lua/wezterm.table/flatten.md new file mode 100644 index 00000000000..657505f0abf --- /dev/null +++ b/docs/config/lua/wezterm.table/flatten.md @@ -0,0 +1,20 @@ +# `wezterm.table.flatten(array_of_arrays)` + +{{since('nightly')}} + +This function flattens Lua arrays passed to it in the form of an array. +I.e., to flatten the Lua arrays `arr1` and `arr2` into one array, +we can pass them to the function as `{ arr1, arr2 }`. (See below.) + +```lua +local wezterm = require 'wezterm' +local flatten = wezterm.table.flatten + +local arr1 = { 1, 2, 3 } + +local arr2 = { 'a', 'b', 'c' } + +wezterm.log_error(flatten { arr1, arr2 }) +``` + +See also [merge](merge.md). diff --git a/docs/config/lua/wezterm.table/has_key.md b/docs/config/lua/wezterm.table/has_key.md new file mode 100644 index 00000000000..2e81e0c8eb1 --- /dev/null +++ b/docs/config/lua/wezterm.table/has_key.md @@ -0,0 +1,24 @@ +# `wezterm.table.has_key(table, key)` + +{{since('nightly')}} + +This function accepts a Lua table (or array) `table` and a key `key`. +It returns `true` if `table` contains a key equal to `key` (with non-nill value) +and false otherwise. + +```lua +local wezterm = require 'wezterm' +local has_key = wezterm.table.has_key + +local tbl1 = { + a = 1, + b = '2', +} +local arr1 = { 'a', 'b', 'c' } + +assert(has_key(tbl1, 'a')) +assert(not has_key(tbl1, 'c')) + +assert(has_key(arr1, 3)) +assert(not has_key(arr1, 4)) +``` diff --git a/docs/config/lua/wezterm.table/has_value.md b/docs/config/lua/wezterm.table/has_value.md new file mode 100644 index 00000000000..fb3ebb86af7 --- /dev/null +++ b/docs/config/lua/wezterm.table/has_value.md @@ -0,0 +1,24 @@ +# `wezterm.table.has_value(table, value)` + +{{since('nightly')}} + +This function accepts a Lua table (or array) `table` and a value `value`. +It returns `true` if `table` contains an entry with value equal to `value` +and false otherwise. + +```lua +local wezterm = require 'wezterm' +local has_value = wezterm.table.has_value + +local tbl1 = { + a = 1, + b = '2', +} +local arr1 = { 'a', 'b', 'c' } + +assert(has_value(tbl1, 1)) +assert(not has_value(tbl1, 'a')) + +assert(has_value(arr1, 'a')) +assert(not has_value(arr1, '1')) +``` diff --git a/docs/config/lua/wezterm.table/index.markdown b/docs/config/lua/wezterm.table/index.markdown new file mode 100644 index 00000000000..237f5860607 --- /dev/null +++ b/docs/config/lua/wezterm.table/index.markdown @@ -0,0 +1,7 @@ +# `wezterm.table` module + +{{since('nightly')}} + +The `wezterm.table` module exposes functions that work with lua tables and arrays. + +## Available functions diff --git a/docs/config/lua/wezterm.table/index.md b/docs/config/lua/wezterm.table/index.md new file mode 100644 index 00000000000..59b606ce524 --- /dev/null +++ b/docs/config/lua/wezterm.table/index.md @@ -0,0 +1,16 @@ +# `wezterm.table` module + +{{since('nightly')}} + +The `wezterm.table` module exposes functions that work with lua tables and arrays. + +## Available functions + + + - [clone](clone.md) + - [flatten](flatten.md) + - [has_key](has_key.md) + - [has_value](has_value.md) + - [length](length.md) + - [merge](merge.md) + - [to_string](to_string.md) diff --git a/docs/config/lua/wezterm.table/length.md b/docs/config/lua/wezterm.table/length.md new file mode 100644 index 00000000000..4dfdfc57813 --- /dev/null +++ b/docs/config/lua/wezterm.table/length.md @@ -0,0 +1,26 @@ +# `wezterm.table.length(table)` + +{{since('nightly')}} + +This function returns the length of a Lua table (or array) passed to it. + +Note: The Lua function `#` also returns the length of an array, but +`#` only works for array and not tables (with non-integer keys). + +```lua +local wezterm = require 'wezterm' +local length = wezterm.table.length + +local tbl1 = { + a = 1, + b = '2', +} +local arr1 = { 1, 'a', 2, 'abc' } + +assert(2 == length(tbl1)) +assert(4 == length(arr1)) + +assert(0 == #tbl1) +assert(4 == #arr1) +``` + diff --git a/docs/config/lua/wezterm.table/merge.md b/docs/config/lua/wezterm.table/merge.md new file mode 100644 index 00000000000..31ee3e3eadc --- /dev/null +++ b/docs/config/lua/wezterm.table/merge.md @@ -0,0 +1,36 @@ +# `wezterm.table.merge(array_of_tables [, keep_first])` + +{{since('nightly')}} + +This function merges Lua tables passed to it in the form of an array. +I.e., to merge the Lua tables `tbl1` and `tbl2`, we can pass them to +the function as `{ tbl1, tbl2 }`. (See below.) + +By default this function merges tables with identical keys by taking +the value from the last table in the array with each given key. + +The optional `keep_first` allows us to instead prefer values from the +first table in the array where we see the key by passing `true` after the array. +The default behavior is identical to what we get by passing `false`. + +```lua +local wezterm = require 'wezterm' +local merge = wezterm.table.merge + +local tbl1 = { + a = 1, + b = '2', +} + +local tbl2 = { + a = '1', + c = 3, +} + +wezterm.log_error(merge { tbl1, tbl2 }) +assert(merge { tbl1, tbl2 } == merge({ tbl1, tbl2 }, false)) + +wezterm.log_error(merge({ tbl1, tbl2 }, true)) +``` + +See also [flatten](flatten.md). diff --git a/docs/config/lua/wezterm.table/to_string.md b/docs/config/lua/wezterm.table/to_string.md new file mode 100644 index 00000000000..1c7987536a3 --- /dev/null +++ b/docs/config/lua/wezterm.table/to_string.md @@ -0,0 +1,35 @@ +# `wezterm.table.to_string(table [, indent])` + +{{since('nightly')}} + +This function takes a Lua table and returns a string with the data of +the table. E.g., passing in the table `{ a=1, b=2 }` the function +will return the string: +``` +{ + a = 1, + b = 2, +} +``` + +By default this function constructs the string with 4 spaces for indentation. + +The optional `indent` allows us to instead prefer other (positive) integer values +of spaces for the indentation. + +```lua +local wezterm = require 'wezterm' +local to_string = wezterm.table.to_string + +local tbl1 = { + a = 1, + b = 2, +} +local str1 = [[{ + a = 1, + b = 2, +}]] + +assert(str1 == to_string(tbl1)) +``` + diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index 0c8bb7d9675..ffb1c042d98 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -1,34 +1,58 @@ +use anyhow::anyhow; use config::lua::get_or_create_sub_module; -use config::lua::mlua::{self, Lua, Table, Value as LuaValue}; +use config::lua::mlua::{self, Integer, Lua, Table, Value as LuaValue}; pub fn register(lua: &Lua) -> anyhow::Result<()> { let table = get_or_create_sub_module(lua, "table")?; table.set("merge", lua.create_function(merge)?)?; table.set("clone", lua.create_function(clone)?)?; table.set("flatten", lua.create_function(flatten)?)?; + table.set("length", lua.create_function(length)?)?; + table.set("has_key", lua.create_function(has_key)?)?; + table.set("has_value", lua.create_function(has_value)?)?; + table.set("to_string", lua.create_function(to_string)?)?; Ok(()) } // merge tables -// (in case of overlap of the tables, we take the key-value pair from the last table) -// -fn merge<'lua>(lua: &'lua Lua, tables: Vec>) -> mlua::Result> { +// (in case of overlap of the tables, we default to taking the key-value pair from the last table) +// Note that we don't use a HashMap since we want to keep the order of the tables, which +// can be useful in some cases +fn merge<'lua>( + lua: &'lua Lua, + (array_of_tables, keep_first): (Vec>, Option), +) -> mlua::Result> { let mut tbl_vec: Vec<(LuaValue, LuaValue)> = vec![]; - for table in tables { - for inner_pair in table.pairs::() { - let (key, value) = inner_pair.map_err(mlua::Error::external)?; + for table in array_of_tables { + for pair in table.pairs::() { + let (key, value) = pair.map_err(mlua::Error::external)?; tbl_vec.push((key, value)); } } let tbl_len = tbl_vec.len(); + // note we might allocate a bit too much here, but in many usecases we will be correct let tbl: Table<'lua> = lua .create_table_with_capacity(0, tbl_len) .map_err(mlua::Error::external)?; + + let keep_first = match keep_first { + Some(b) => b, + None => false, // default behavior is to keep_last set value + }; for (key, value) in tbl_vec { // Note that we override previously set key values if we have // the same key showing up more than once - tbl.set(key, value).map_err(mlua::Error::external)?; + if keep_first { + if !tbl + .contains_key(key.clone()) + .map_err(mlua::Error::external)? + { + tbl.set(key, value).map_err(mlua::Error::external)?; + } + } else { + tbl.set(key, value).map_err(mlua::Error::external)?; + } } Ok(tbl) } @@ -37,24 +61,100 @@ fn clone<'lua>(_: &'lua Lua, table: Table<'lua>) -> mlua::Result> { Ok(table.clone()) } -fn flatten<'lua>(lua: &'lua Lua, table: Vec>) -> mlua::Result> { - let flat_vec: Vec = table.into_iter().flatten().collect(); - let flat_table = lua +fn flatten<'lua>(lua: &'lua Lua, array_of_arrays: Vec>) -> mlua::Result> { + let flat_vec: Vec = array_of_arrays.into_iter().flatten().collect(); + let flat_array = lua .create_table_with_capacity(flat_vec.len(), 0) .map_err(mlua::Error::external)?; for value in flat_vec { - flat_table.push(value).map_err(mlua::Error::external)?; + flat_array.push(value).map_err(mlua::Error::external)?; + } + Ok(flat_array) +} + +fn length<'lua>(_: &'lua Lua, table: Table<'lua>) -> mlua::Result { + // note that # only works correctly on arrays in lua + let mut len: i64 = 0; + for _ in table.pairs::() { + len += 1 + } + Ok(len) +} + +fn has_key<'lua>(_: &'lua Lua, (table, key): (Table<'lua>, LuaValue)) -> mlua::Result { + Ok(table.contains_key(key).map_err(mlua::Error::external)?) +} + +fn has_value<'lua>(_: &'lua Lua, (table, value): (Table<'lua>, LuaValue)) -> mlua::Result { + for pair in table.pairs::() { + let (_, tbl_value) = pair.map_err(mlua::Error::external)?; + if tbl_value == value { + return Ok(true); + } + } + Ok(false) +} + +fn to_string<'lua>( + lua: &'lua Lua, + (table, indent): (Table<'lua>, Option), +) -> mlua::Result { + if let Some(ind) = indent { + if ind < 0 { + return Err(mlua::Error::external(anyhow!( + "Indent set to {ind}. Please us an indent ≥ 0." + ))); + } } - Ok(flat_table) + to_string_impl(lua, (table, indent, 0)) } -// #[cfg(test)] -// mod tests { -// use super::*; -// -// #[test] -// fn it_works() { -// let result = add(2, 2); -// assert_eq!(result, 4); -// } -// } +fn to_string_impl<'lua>( + lua: &'lua Lua, + (table, indent, depth): (Table<'lua>, Option, usize), +) -> mlua::Result { + let mut string = String::new(); + let bracket_spaces = match indent { + Some(ind) => " ".repeat((ind as usize) * depth), + None => " ".repeat(4 * depth), + }; + let content_spaces = match indent { + Some(ind) => " ".repeat((ind as usize) * (depth + 1)), + None => " ".repeat(4 * (depth + 1)), + }; + string.push_str("{\n"); + for pair in table.pairs::() { + string.push_str(&content_spaces); + + let (key, value) = pair.map_err(mlua::Error::external)?; + match value.clone() { + LuaValue::Table(tbl) => { + string.push_str(&to_string_impl(lua, (tbl, indent, depth + 1))?) + } + _ => { + let nice_key = match key { + LuaValue::String(s) => s.to_str().map_err(mlua::Error::external)?.to_string(), + LuaValue::Number(f) => f.to_string(), + LuaValue::Integer(i) => i.to_string(), + LuaValue::Boolean(b) => b.to_string(), + other => format!("{other:?}"), + }; + let nice_value = match value { + LuaValue::String(s) => s.to_str().map_err(mlua::Error::external)?.to_string(), + LuaValue::Number(f) => f.to_string(), + LuaValue::Integer(i) => i.to_string(), + LuaValue::Boolean(b) => b.to_string(), + other => format!("{other:?}"), + }; + string.push_str(&format!("{nice_key} = {nice_value},\n")); + } + } + } + string.push_str(&bracket_spaces); + if depth != 0 { + string.push_str("},\n") + } else { + string.push_str("}"); + } + Ok(string) +} From ec10ee2a0e500cc2a2d71be5aa3bb3df6f8756a2 Mon Sep 17 00:00:00 2001 From: Daniel Kongsgaard Date: Wed, 27 Sep 2023 01:43:14 +0200 Subject: [PATCH 03/42] Fixed some typos. --- docs/config/lua/wezterm.table/index.markdown | 2 +- docs/config/lua/wezterm.table/index.md | 2 +- lua-api-crates/table-funcs/src/lib.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/config/lua/wezterm.table/index.markdown b/docs/config/lua/wezterm.table/index.markdown index 237f5860607..291281d51d5 100644 --- a/docs/config/lua/wezterm.table/index.markdown +++ b/docs/config/lua/wezterm.table/index.markdown @@ -2,6 +2,6 @@ {{since('nightly')}} -The `wezterm.table` module exposes functions that work with lua tables and arrays. +The `wezterm.table` module exposes functions that work with Lua tables and arrays. ## Available functions diff --git a/docs/config/lua/wezterm.table/index.md b/docs/config/lua/wezterm.table/index.md index 59b606ce524..4e777e388ba 100644 --- a/docs/config/lua/wezterm.table/index.md +++ b/docs/config/lua/wezterm.table/index.md @@ -2,7 +2,7 @@ {{since('nightly')}} -The `wezterm.table` module exposes functions that work with lua tables and arrays. +The `wezterm.table` module exposes functions that work with Lua tables and arrays. ## Available functions diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index ffb1c042d98..9eb881cd9dc 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -31,7 +31,7 @@ fn merge<'lua>( } } let tbl_len = tbl_vec.len(); - // note we might allocate a bit too much here, but in many usecases we will be correct + // note we might allocate a bit too much here, but in many use cases we will be correct let tbl: Table<'lua> = lua .create_table_with_capacity(0, tbl_len) .map_err(mlua::Error::external)?; @@ -73,7 +73,7 @@ fn flatten<'lua>(lua: &'lua Lua, array_of_arrays: Vec>) -> mlua::R } fn length<'lua>(_: &'lua Lua, table: Table<'lua>) -> mlua::Result { - // note that # only works correctly on arrays in lua + // note that # only works correctly on arrays in Lua let mut len: i64 = 0; for _ in table.pairs::() { len += 1 From 373386a1005ca811e1f237577c9cc6a2b485ecad Mon Sep 17 00:00:00 2001 From: Daniel Kongsgaard Date: Thu, 28 Sep 2023 02:06:03 +0200 Subject: [PATCH 04/42] Improved flatten and added an option to to_string. --- docs/config/lua/wezterm.table/flatten.md | 8 +-- docs/config/lua/wezterm.table/length.md | 2 +- docs/config/lua/wezterm.table/to_string.md | 42 +++++++++--- lua-api-crates/table-funcs/src/lib.rs | 74 +++++++++++++++++----- 4 files changed, 96 insertions(+), 30 deletions(-) diff --git a/docs/config/lua/wezterm.table/flatten.md b/docs/config/lua/wezterm.table/flatten.md index 657505f0abf..16b54275c7e 100644 --- a/docs/config/lua/wezterm.table/flatten.md +++ b/docs/config/lua/wezterm.table/flatten.md @@ -2,7 +2,7 @@ {{since('nightly')}} -This function flattens Lua arrays passed to it in the form of an array. +This function recursively flattens Lua arrays passed to it in the form of an array. I.e., to flatten the Lua arrays `arr1` and `arr2` into one array, we can pass them to the function as `{ arr1, arr2 }`. (See below.) @@ -10,11 +10,11 @@ we can pass them to the function as `{ arr1, arr2 }`. (See below.) local wezterm = require 'wezterm' local flatten = wezterm.table.flatten -local arr1 = { 1, 2, 3 } +local arr1 = { { 1, 2 }, 3 } -local arr2 = { 'a', 'b', 'c' } +local arr2 = { 'a', { 'b', { 'c' } } } -wezterm.log_error(flatten { arr1, arr2 }) +assert(flatten { arr1, arr2 } == { 1, 2, 3, 'a', 'b', 'c' }) ``` See also [merge](merge.md). diff --git a/docs/config/lua/wezterm.table/length.md b/docs/config/lua/wezterm.table/length.md index 4dfdfc57813..c2fa7cb8c79 100644 --- a/docs/config/lua/wezterm.table/length.md +++ b/docs/config/lua/wezterm.table/length.md @@ -5,7 +5,7 @@ This function returns the length of a Lua table (or array) passed to it. Note: The Lua function `#` also returns the length of an array, but -`#` only works for array and not tables (with non-integer keys). +`#` only works for arrays and not tables (with non-integer keys). ```lua local wezterm = require 'wezterm' diff --git a/docs/config/lua/wezterm.table/to_string.md b/docs/config/lua/wezterm.table/to_string.md index 1c7987536a3..e7d44bfd42e 100644 --- a/docs/config/lua/wezterm.table/to_string.md +++ b/docs/config/lua/wezterm.table/to_string.md @@ -1,4 +1,4 @@ -# `wezterm.table.to_string(table [, indent])` +# `wezterm.table.to_string(table [, indent [, skip_outer_bracket]])` {{since('nightly')}} @@ -7,29 +7,55 @@ the table. E.g., passing in the table `{ a=1, b=2 }` the function will return the string: ``` { - a = 1, - b = 2, + a = 1, + b = 2, } ``` -By default this function constructs the string with 4 spaces for indentation. +By default this function constructs the string with 2 spaces for indentation. The optional `indent` allows us to instead prefer other (positive) integer values of spaces for the indentation. ```lua local wezterm = require 'wezterm' -local to_string = wezterm.table.to_string +local tbl_to_string = wezterm.table.to_string local tbl1 = { a = 1, - b = 2, + { + b = 2, + }, } local str1 = [[{ - a = 1, + a = 1, + { b = 2, + }, }]] -assert(str1 == to_string(tbl1)) +assert(str1 == tbl_to_string(tbl1)) +``` + +The optional `skip_outer_bracket` (which can only be used together with `indent`) is +a boolean, which defaults to `false`. If you set it to `true`, the outer brackets are +not included in the string (and thus everything is `indent` fewer spaces indented too). + +```lua +local wezterm = require 'wezterm' +local tbl_to_string = wezterm.table.to_string + +local tbl1 = { + a = 1, + { + b = 2, + }, +} +local str1 = [[a = 1, +{ +b = 2, +},]] + +assert(str1 == tbl_to_string(tbl1, 0, true)) ``` diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index 9eb881cd9dc..414c9915cdb 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -61,15 +61,31 @@ fn clone<'lua>(_: &'lua Lua, table: Table<'lua>) -> mlua::Result> { Ok(table.clone()) } -fn flatten<'lua>(lua: &'lua Lua, array_of_arrays: Vec>) -> mlua::Result> { - let flat_vec: Vec = array_of_arrays.into_iter().flatten().collect(); - let flat_array = lua - .create_table_with_capacity(flat_vec.len(), 0) - .map_err(mlua::Error::external)?; - for value in flat_vec { - flat_array.push(value).map_err(mlua::Error::external)?; +fn flatten<'lua>(lua: &'lua Lua, arrays: Vec>) -> mlua::Result>> { + let mut flat_vec: Vec = vec![]; + for item in arrays { + match item { + LuaValue::Table(tbl) => { + let tbl_as_vec = tbl + .sequence_values() + .filter_map(|x| x.map_err(mlua::Error::external).ok()) + .collect(); + let flat = flatten(lua, tbl_as_vec)?; + for j in flat { + flat_vec.push(j); + } + } + LuaValue::Nil => (), + LuaValue::Thread(_) => (), + LuaValue::Error(err) => { + return Err(mlua::Error::external(err)); + } + other => { + flat_vec.push(other); + } + } } - Ok(flat_array) + Ok(flat_vec) } fn length<'lua>(_: &'lua Lua, table: Table<'lua>) -> mlua::Result { @@ -97,39 +113,61 @@ fn has_value<'lua>(_: &'lua Lua, (table, value): (Table<'lua>, LuaValue)) -> mlu fn to_string<'lua>( lua: &'lua Lua, - (table, indent): (Table<'lua>, Option), + (table, indent, skip_outer_bracket): (Table<'lua>, Option, Option), ) -> mlua::Result { if let Some(ind) = indent { if ind < 0 { return Err(mlua::Error::external(anyhow!( - "Indent set to {ind}. Please us an indent ≥ 0." + "Indent set to {ind}. Please use an indent ≥ 0." ))); } } - to_string_impl(lua, (table, indent, 0)) + let result = to_string_impl(lua, (table, indent, skip_outer_bracket, 0)); + match result { + Ok(res) => { + if skip_outer_bracket == Some(true) { + // we added indent too many spaces on each line + let extra_spaces = match indent { + Some(ind) => " ".repeat(ind as usize), + None => " ".repeat(2), + }; + let old = ["\n", &extra_spaces].concat(); + return Ok(res.replace(&old, "\n")); + } + Ok(res) + } + Err(err) => Err(mlua::Error::external(err)), + } } fn to_string_impl<'lua>( lua: &'lua Lua, - (table, indent, depth): (Table<'lua>, Option, usize), + (table, indent, skip_outer_bracket, depth): (Table<'lua>, Option, Option, usize), ) -> mlua::Result { let mut string = String::new(); + let skip_outer_bracket = match skip_outer_bracket { + Some(b) => b, + None => false, // defaults to keeping the outer brackets + }; + if !skip_outer_bracket || depth != 0 { + string.push_str("{\n"); + } + let bracket_spaces = match indent { Some(ind) => " ".repeat((ind as usize) * depth), - None => " ".repeat(4 * depth), + None => " ".repeat(2 * depth), }; let content_spaces = match indent { Some(ind) => " ".repeat((ind as usize) * (depth + 1)), - None => " ".repeat(4 * (depth + 1)), + None => " ".repeat(2 * (depth + 1)), }; - string.push_str("{\n"); for pair in table.pairs::() { string.push_str(&content_spaces); let (key, value) = pair.map_err(mlua::Error::external)?; match value.clone() { LuaValue::Table(tbl) => { - string.push_str(&to_string_impl(lua, (tbl, indent, depth + 1))?) + string.push_str(&to_string_impl(lua, (tbl, indent, None, depth + 1))?) } _ => { let nice_key = match key { @@ -150,9 +188,11 @@ fn to_string_impl<'lua>( } } } - string.push_str(&bracket_spaces); if depth != 0 { + string.push_str(&bracket_spaces); string.push_str("},\n") + } else if skip_outer_bracket { + string.pop(); // remove the last newline in this case } else { string.push_str("}"); } From 9292fe0b07eac30d8e831bf170b429b51dc319e7 Mon Sep 17 00:00:00 2001 From: Daniel Kongsgaard Date: Mon, 23 Oct 2023 23:18:13 +0200 Subject: [PATCH 05/42] Added to_string_fallback --- docs/config/lua/wezterm.table/index.md | 1 + docs/config/lua/wezterm.table/to_string.md | 17 ++++++--- .../lua/wezterm.table/to_string_fallback.md | 37 +++++++++++++++++++ lua-api-crates/table-funcs/src/lib.rs | 8 ++++ 4 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 docs/config/lua/wezterm.table/to_string_fallback.md diff --git a/docs/config/lua/wezterm.table/index.md b/docs/config/lua/wezterm.table/index.md index 4e777e388ba..47b01c7d137 100644 --- a/docs/config/lua/wezterm.table/index.md +++ b/docs/config/lua/wezterm.table/index.md @@ -14,3 +14,4 @@ The `wezterm.table` module exposes functions that work with Lua tables and array - [length](length.md) - [merge](merge.md) - [to_string](to_string.md) + - [to_string_fallback](to_string_fallback.md) diff --git a/docs/config/lua/wezterm.table/to_string.md b/docs/config/lua/wezterm.table/to_string.md index e7d44bfd42e..8ff1b933789 100644 --- a/docs/config/lua/wezterm.table/to_string.md +++ b/docs/config/lua/wezterm.table/to_string.md @@ -12,9 +12,13 @@ will return the string: } ``` +*Note:* This function is not careful about checking for recursive tables, so it can't +be used to print e.g. `_G`. To print a recursive (or in general a very big) table, +it is recommended that you use [to_string_fallback](to_string_fallback.md). + By default this function constructs the string with 2 spaces for indentation. -The optional `indent` allows us to instead prefer other (positive) integer values +The optional `indent` allows us to instead prefer other (non-negative) integer values of spaces for the indentation. ```lua @@ -28,13 +32,13 @@ local tbl1 = { }, } local str1 = [[{ - a = 1, - { - b = 2, - }, + a = 1, + { + b = 2, + }, }]] -assert(str1 == tbl_to_string(tbl1)) +assert(str1 == tbl_to_string(tbl1, 4)) ``` The optional `skip_outer_bracket` (which can only be used together with `indent`) is @@ -59,3 +63,4 @@ b = 2, assert(str1 == tbl_to_string(tbl1, 0, true)) ``` +See also [to_string_fallback](to_string_fallback.md). diff --git a/docs/config/lua/wezterm.table/to_string_fallback.md b/docs/config/lua/wezterm.table/to_string_fallback.md new file mode 100644 index 00000000000..903da9e5c54 --- /dev/null +++ b/docs/config/lua/wezterm.table/to_string_fallback.md @@ -0,0 +1,37 @@ +# `wezterm.table.to_string_fallback(table)` + +{{since('nightly')}} + +This function takes a Lua table and returns a string with the data of +the table. E.g., passing in the table `{ a=1, b=2 }` the function +will return the string: +``` +{ + ["a"] = 1, + ["b"] = 2, +} +``` + +For nested tables, this function always prints a label (even for arrays). +This can make the string look different than you might expect. +```lua +local wezterm = require 'wezterm' +local tbl_to_string_fb = wezterm.table.to_string_fallback + +local tbl1 = { + a = 1, + { + b = 2, + }, +} +local str1 = [[{ + [1] = { + ["b"] = 2, + }, + ["a"] = 1, +}]] + +assert(str1 == tbl_to_string_fb(tbl1)) +``` + +See also [to_string](to_string.md). diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index 414c9915cdb..74e5d7a9e07 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -11,6 +11,7 @@ pub fn register(lua: &Lua) -> anyhow::Result<()> { table.set("has_key", lua.create_function(has_key)?)?; table.set("has_value", lua.create_function(has_value)?)?; table.set("to_string", lua.create_function(to_string)?)?; + table.set("to_string_fallback", lua.create_function(to_string_fallback)?)?; Ok(()) } @@ -111,6 +112,13 @@ fn has_value<'lua>(_: &'lua Lua, (table, value): (Table<'lua>, LuaValue)) -> mlu Ok(false) } +fn to_string_fallback<'lua>( + _: &'lua Lua, + table: Table<'lua> +) -> mlua::Result { + Ok(format!("{:#?}", table)) +} + fn to_string<'lua>( lua: &'lua Lua, (table, indent, skip_outer_bracket): (Table<'lua>, Option, Option), From 1a00a870c8e7d4b6371b217e334292f61bef1c51 Mon Sep 17 00:00:00 2001 From: Daniel Kongsgaard Date: Mon, 23 Oct 2023 23:20:36 +0200 Subject: [PATCH 06/42] Cargo.lock update --- Cargo.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 789cc5eae51..9ef131a21a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5159,15 +5159,6 @@ dependencies = [ "libc", ] -[[package]] -name = "table-funcs" -version = "0.1.0" -dependencies = [ - "anyhow", - "config", - "luahelper", -] - [[package]] name = "system-configuration" version = "0.5.1" @@ -5189,6 +5180,15 @@ dependencies = [ "libc", ] +[[package]] +name = "table-funcs" +version = "0.1.0" +dependencies = [ + "anyhow", + "config", + "luahelper", +] + [[package]] name = "tabout" version = "0.3.0" From bfc61a539ecf4dec7d75c05976a6a0990bac102e Mon Sep 17 00:00:00 2001 From: Daniel Kongsgaard Date: Mon, 23 Oct 2023 23:23:30 +0200 Subject: [PATCH 07/42] Fixed formatting --- lua-api-crates/table-funcs/src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index 74e5d7a9e07..e988848294b 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -11,7 +11,10 @@ pub fn register(lua: &Lua) -> anyhow::Result<()> { table.set("has_key", lua.create_function(has_key)?)?; table.set("has_value", lua.create_function(has_value)?)?; table.set("to_string", lua.create_function(to_string)?)?; - table.set("to_string_fallback", lua.create_function(to_string_fallback)?)?; + table.set( + "to_string_fallback", + lua.create_function(to_string_fallback)?, + )?; Ok(()) } @@ -112,10 +115,7 @@ fn has_value<'lua>(_: &'lua Lua, (table, value): (Table<'lua>, LuaValue)) -> mlu Ok(false) } -fn to_string_fallback<'lua>( - _: &'lua Lua, - table: Table<'lua> -) -> mlua::Result { +fn to_string_fallback<'lua>(_: &'lua Lua, table: Table<'lua>) -> mlua::Result { Ok(format!("{:#?}", table)) } From 87eb457db8710746682f58279db51cf87c7defc2 Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Wed, 8 Nov 2023 00:03:04 +0100 Subject: [PATCH 08/42] Simplify flatten --- lua-api-crates/table-funcs/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index e988848294b..ea5c4392b22 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -72,7 +72,7 @@ fn flatten<'lua>(lua: &'lua Lua, arrays: Vec>) -> mlua::Result { let tbl_as_vec = tbl .sequence_values() - .filter_map(|x| x.map_err(mlua::Error::external).ok()) + .filter_map(|x| x.ok()) .collect(); let flat = flatten(lua, tbl_as_vec)?; for j in flat { From 476e5e2817182d60f2ce1b549df1288fa5ed6225 Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Fri, 24 Nov 2023 17:11:53 +0100 Subject: [PATCH 09/42] Update formatting --- lua-api-crates/table-funcs/src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index ea5c4392b22..4907e602a48 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -70,10 +70,7 @@ fn flatten<'lua>(lua: &'lua Lua, arrays: Vec>) -> mlua::Result { - let tbl_as_vec = tbl - .sequence_values() - .filter_map(|x| x.ok()) - .collect(); + let tbl_as_vec = tbl.sequence_values().filter_map(|x| x.ok()).collect(); let flat = flatten(lua, tbl_as_vec)?; for j in flat { flat_vec.push(j); From b16c57300b1562f37c1be5d470ff04c63ca64ffb Mon Sep 17 00:00:00 2001 From: Daniel Kongsgaard Date: Fri, 1 Dec 2023 17:28:59 +0100 Subject: [PATCH 10/42] Apply some suggestions from code review Co-authored-by: Wez Furlong --- docs/config/lua/wezterm.table/length.md | 2 +- docs/config/lua/wezterm.table/merge.md | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/config/lua/wezterm.table/length.md b/docs/config/lua/wezterm.table/length.md index c2fa7cb8c79..2c216a2dd7f 100644 --- a/docs/config/lua/wezterm.table/length.md +++ b/docs/config/lua/wezterm.table/length.md @@ -5,7 +5,7 @@ This function returns the length of a Lua table (or array) passed to it. Note: The Lua function `#` also returns the length of an array, but -`#` only works for arrays and not tables (with non-integer keys). +`#` only works for array-style tables with contiguous integer keys starting with index `1`, and not sparse arrays (with gaps in their integer keys), or object or other style of tables with non-integer keys. ```lua local wezterm = require 'wezterm' diff --git a/docs/config/lua/wezterm.table/merge.md b/docs/config/lua/wezterm.table/merge.md index 31ee3e3eadc..00a7e5fd112 100644 --- a/docs/config/lua/wezterm.table/merge.md +++ b/docs/config/lua/wezterm.table/merge.md @@ -2,7 +2,9 @@ {{since('nightly')}} -This function merges Lua tables passed to it in the form of an array. +This function merges a list of Lua object-style tables based on their keys. + +The tables are passed to it in the form of an array. I.e., to merge the Lua tables `tbl1` and `tbl2`, we can pass them to the function as `{ tbl1, tbl2 }`. (See below.) From 0acacad4f5eecd7bae77f4597159547215cb3985 Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Fri, 1 Dec 2023 17:35:41 +0100 Subject: [PATCH 11/42] Simplify `length` --- lua-api-crates/table-funcs/src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index 4907e602a48..257ad2c36b8 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -91,10 +91,7 @@ fn flatten<'lua>(lua: &'lua Lua, arrays: Vec>) -> mlua::Result(_: &'lua Lua, table: Table<'lua>) -> mlua::Result { // note that # only works correctly on arrays in Lua - let mut len: i64 = 0; - for _ in table.pairs::() { - len += 1 - } + let len = table.pairs::().count() as i64; Ok(len) } From e42b523c22c553dd63b34dc79fa947726c4aee48 Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Fri, 1 Dec 2023 18:14:21 +0100 Subject: [PATCH 12/42] simple `to_string` with `ValuePrinter` --- lua-api-crates/table-funcs/src/lib.rs | 90 ++------------------------- 1 file changed, 5 insertions(+), 85 deletions(-) diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index 257ad2c36b8..e4df2652b03 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -1,6 +1,6 @@ -use anyhow::anyhow; use config::lua::get_or_create_sub_module; use config::lua::mlua::{self, Integer, Lua, Table, Value as LuaValue}; +use luahelper::ValuePrinter; pub fn register(lua: &Lua) -> anyhow::Result<()> { let table = get_or_create_sub_module(lua, "table")?; @@ -114,89 +114,9 @@ fn to_string_fallback<'lua>(_: &'lua Lua, table: Table<'lua>) -> mlua::Result( - lua: &'lua Lua, - (table, indent, skip_outer_bracket): (Table<'lua>, Option, Option), -) -> mlua::Result { - if let Some(ind) = indent { - if ind < 0 { - return Err(mlua::Error::external(anyhow!( - "Indent set to {ind}. Please use an indent ≥ 0." - ))); - } - } - let result = to_string_impl(lua, (table, indent, skip_outer_bracket, 0)); - match result { - Ok(res) => { - if skip_outer_bracket == Some(true) { - // we added indent too many spaces on each line - let extra_spaces = match indent { - Some(ind) => " ".repeat(ind as usize), - None => " ".repeat(2), - }; - let old = ["\n", &extra_spaces].concat(); - return Ok(res.replace(&old, "\n")); - } - Ok(res) - } - Err(err) => Err(mlua::Error::external(err)), - } -} - -fn to_string_impl<'lua>( - lua: &'lua Lua, - (table, indent, skip_outer_bracket, depth): (Table<'lua>, Option, Option, usize), + _: &'lua Lua, + table: Table<'lua>, ) -> mlua::Result { - let mut string = String::new(); - let skip_outer_bracket = match skip_outer_bracket { - Some(b) => b, - None => false, // defaults to keeping the outer brackets - }; - if !skip_outer_bracket || depth != 0 { - string.push_str("{\n"); - } - - let bracket_spaces = match indent { - Some(ind) => " ".repeat((ind as usize) * depth), - None => " ".repeat(2 * depth), - }; - let content_spaces = match indent { - Some(ind) => " ".repeat((ind as usize) * (depth + 1)), - None => " ".repeat(2 * (depth + 1)), - }; - for pair in table.pairs::() { - string.push_str(&content_spaces); - - let (key, value) = pair.map_err(mlua::Error::external)?; - match value.clone() { - LuaValue::Table(tbl) => { - string.push_str(&to_string_impl(lua, (tbl, indent, None, depth + 1))?) - } - _ => { - let nice_key = match key { - LuaValue::String(s) => s.to_str().map_err(mlua::Error::external)?.to_string(), - LuaValue::Number(f) => f.to_string(), - LuaValue::Integer(i) => i.to_string(), - LuaValue::Boolean(b) => b.to_string(), - other => format!("{other:?}"), - }; - let nice_value = match value { - LuaValue::String(s) => s.to_str().map_err(mlua::Error::external)?.to_string(), - LuaValue::Number(f) => f.to_string(), - LuaValue::Integer(i) => i.to_string(), - LuaValue::Boolean(b) => b.to_string(), - other => format!("{other:?}"), - }; - string.push_str(&format!("{nice_key} = {nice_value},\n")); - } - } - } - if depth != 0 { - string.push_str(&bracket_spaces); - string.push_str("},\n") - } else if skip_outer_bracket { - string.pop(); // remove the last newline in this case - } else { - string.push_str("}"); - } - Ok(string) + let res = ValuePrinter(LuaValue::Table(table)); + Ok(format!("{:#?}", res).to_string()) } From bd127554d8967bce9f4a2ad4f0b7fad83e012cdc Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Fri, 1 Dec 2023 18:22:28 +0100 Subject: [PATCH 13/42] Cleanup errors --- lua-api-crates/table-funcs/src/lib.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index e4df2652b03..5ef646b0093 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -30,15 +30,14 @@ fn merge<'lua>( let mut tbl_vec: Vec<(LuaValue, LuaValue)> = vec![]; for table in array_of_tables { for pair in table.pairs::() { - let (key, value) = pair.map_err(mlua::Error::external)?; + let (key, value) = pair?; tbl_vec.push((key, value)); } } let tbl_len = tbl_vec.len(); // note we might allocate a bit too much here, but in many use cases we will be correct let tbl: Table<'lua> = lua - .create_table_with_capacity(0, tbl_len) - .map_err(mlua::Error::external)?; + .create_table_with_capacity(0, tbl_len)?; let keep_first = match keep_first { Some(b) => b, @@ -49,13 +48,12 @@ fn merge<'lua>( // the same key showing up more than once if keep_first { if !tbl - .contains_key(key.clone()) - .map_err(mlua::Error::external)? + .contains_key(key.clone())? { - tbl.set(key, value).map_err(mlua::Error::external)?; + tbl.set(key, value)?; } } else { - tbl.set(key, value).map_err(mlua::Error::external)?; + tbl.set(key, value)?; } } Ok(tbl) @@ -79,7 +77,7 @@ fn flatten<'lua>(lua: &'lua Lua, arrays: Vec>) -> mlua::Result (), LuaValue::Thread(_) => (), LuaValue::Error(err) => { - return Err(mlua::Error::external(err)); + return Err(err); } other => { flat_vec.push(other); @@ -96,12 +94,12 @@ fn length<'lua>(_: &'lua Lua, table: Table<'lua>) -> mlua::Result { } fn has_key<'lua>(_: &'lua Lua, (table, key): (Table<'lua>, LuaValue)) -> mlua::Result { - Ok(table.contains_key(key).map_err(mlua::Error::external)?) + Ok(table.contains_key(key)?) } fn has_value<'lua>(_: &'lua Lua, (table, value): (Table<'lua>, LuaValue)) -> mlua::Result { for pair in table.pairs::() { - let (_, tbl_value) = pair.map_err(mlua::Error::external)?; + let (_, tbl_value) = pair?; if tbl_value == value { return Ok(true); } From 38f0c02accfe66d829a3aaf7c58c42fcff711741 Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Fri, 1 Dec 2023 18:23:38 +0100 Subject: [PATCH 14/42] format --- lua-api-crates/table-funcs/src/lib.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index 5ef646b0093..befcd519ca7 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -36,8 +36,7 @@ fn merge<'lua>( } let tbl_len = tbl_vec.len(); // note we might allocate a bit too much here, but in many use cases we will be correct - let tbl: Table<'lua> = lua - .create_table_with_capacity(0, tbl_len)?; + let tbl: Table<'lua> = lua.create_table_with_capacity(0, tbl_len)?; let keep_first = match keep_first { Some(b) => b, @@ -47,9 +46,7 @@ fn merge<'lua>( // Note that we override previously set key values if we have // the same key showing up more than once if keep_first { - if !tbl - .contains_key(key.clone())? - { + if !tbl.contains_key(key.clone())? { tbl.set(key, value)?; } } else { @@ -111,10 +108,7 @@ fn to_string_fallback<'lua>(_: &'lua Lua, table: Table<'lua>) -> mlua::Result( - _: &'lua Lua, - table: Table<'lua>, -) -> mlua::Result { +fn to_string<'lua>(_: &'lua Lua, table: Table<'lua>) -> mlua::Result { let res = ValuePrinter(LuaValue::Table(table)); Ok(format!("{:#?}", res).to_string()) } From 8567fc60dc9be6d219108ba79fa920ec556bb62e Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Fri, 1 Dec 2023 22:57:41 +0100 Subject: [PATCH 15/42] Rename `merge` to `extend` + add `deep_extend` --- lua-api-crates/table-funcs/src/lib.rs | 127 +++++++++++++++++++++++--- 1 file changed, 113 insertions(+), 14 deletions(-) diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index befcd519ca7..fdfc00e2f41 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -4,7 +4,8 @@ use luahelper::ValuePrinter; pub fn register(lua: &Lua) -> anyhow::Result<()> { let table = get_or_create_sub_module(lua, "table")?; - table.set("merge", lua.create_function(merge)?)?; + table.set("extend", lua.create_function(extend)?)?; + table.set("deep_extend", lua.create_function(deep_extend)?)?; table.set("clone", lua.create_function(clone)?)?; table.set("flatten", lua.create_function(flatten)?)?; table.set("length", lua.create_function(length)?)?; @@ -19,13 +20,40 @@ pub fn register(lua: &Lua) -> anyhow::Result<()> { Ok(()) } +#[derive(Debug, Clone, PartialEq, Eq)] +enum ConflictMode { + Keep, + Force, + Error, +} + +impl<'lua> mlua::FromLua<'lua> for ConflictMode { + fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> mlua::Result { + match value { + LuaValue::String(s) => { + match s.to_str() { + Ok("Keep") => Ok(ConflictMode::Keep), + Ok("keep") => Ok(ConflictMode::Keep), + Ok("Force") => Ok(ConflictMode::Force), + Ok("force") => Ok(ConflictMode::Force), + Ok("Error") => Ok(ConflictMode::Error), + Ok("error") => Ok(ConflictMode::Error), + _ => Err(mlua::Error::runtime("Unknown string. Expected 'Keep', 'Force' or 'Error'".to_string())) + } + } + LuaValue::Error(err) => Err(err), + other => Err(mlua::Error::runtime(format!("Expected a Lua string. Got something of type: {}", other.type_name()))), + } + } +} + // merge tables // (in case of overlap of the tables, we default to taking the key-value pair from the last table) // Note that we don't use a HashMap since we want to keep the order of the tables, which // can be useful in some cases -fn merge<'lua>( +fn extend<'lua>( lua: &'lua Lua, - (array_of_tables, keep_first): (Vec>, Option), + (array_of_tables, behavior): (Vec>, Option), ) -> mlua::Result> { let mut tbl_vec: Vec<(LuaValue, LuaValue)> = vec![]; for table in array_of_tables { @@ -38,24 +66,95 @@ fn merge<'lua>( // note we might allocate a bit too much here, but in many use cases we will be correct let tbl: Table<'lua> = lua.create_table_with_capacity(0, tbl_len)?; - let keep_first = match keep_first { - Some(b) => b, - None => false, // default behavior is to keep_last set value - }; - for (key, value) in tbl_vec { - // Note that we override previously set key values if we have - // the same key showing up more than once - if keep_first { - if !tbl.contains_key(key.clone())? { + match behavior { + Some(ConflictMode::Keep) => { + for (key,value) in tbl_vec { + if !tbl.contains_key(key.clone())? { + tbl.set(key, value)?; + } + } + } + // default behavior is to keep last set value + Some(ConflictMode::Force) | None => { + for (key, value) in tbl_vec { tbl.set(key, value)?; } - } else { - tbl.set(key, value)?; + } + Some(ConflictMode::Error) => { + for (key, value) in tbl_vec { + if tbl.contains_key(key.clone())? { + return Err(mlua::Error::runtime(format!("The key {} is in more than one of the tables.", key.to_string()?))); + } + tbl.set(key, value)?; + } + } + }; + + Ok(tbl) +} + + +// merge tables entrywise recursively +// (in case of overlap of the tables, we default to taking the key-value pair from the last table) +// Note that we don't use a HashMap since we want to keep the order of the tables, which +// can be useful in some cases +fn deep_extend<'lua>( + lua: &'lua Lua, + (array_of_tables, behavior): (Vec>, Option), +) -> mlua::Result> { + let mut tbl_vec: Vec<(LuaValue, LuaValue)> = vec![]; + for table in array_of_tables { + for pair in table.pairs::() { + let (key, value) = pair?; + tbl_vec.push((key, value)); } } + let tbl_len = tbl_vec.len(); + // note we might allocate a bit too much here, but in many use cases we will be correct + let tbl: Table<'lua> = lua.create_table_with_capacity(0, tbl_len)?; + + match behavior { + Some(ConflictMode::Keep) => { + for (key,value) in tbl_vec { + if !tbl.contains_key(key.clone())? { + tbl.set(key, value)?; + } else if let LuaValue::Table(t) = value { + let inner_tbl = deep_extend(lua, (vec![tbl.get(key.clone())?, t], Some(ConflictMode::Keep)))?; + tbl.set(key, inner_tbl)?; + } + } + } + // default behavior is to keep last set value + Some(ConflictMode::Force) | None => { + for (key, value) in tbl_vec { + if !tbl.contains_key(key.clone())? { + tbl.set(key, value)?; + } else if let LuaValue::Table(t) = value { + let inner_tbl = deep_extend(lua, (vec![tbl.get(key.clone())?, t], Some(ConflictMode::Force)))?; + tbl.set(key, inner_tbl)?; + } else { + tbl.set(key, value)?; + } + } + } + Some(ConflictMode::Error) => { + for (key, value) in tbl_vec { + if !tbl.contains_key(key.clone())? { + tbl.set(key, value)?; + } else if let LuaValue::Table(t) = value { + let inner_tbl = deep_extend(lua, (vec![tbl.get(key.clone())?, t], Some(ConflictMode::Keep)))?; + tbl.set(key, inner_tbl)?; + } else { + return Err(mlua::Error::runtime(format!("The key {} is in more than one of the tables.", key.to_string()?))); + } + } + } + }; + Ok(tbl) } + fn clone<'lua>(_: &'lua Lua, table: Table<'lua>) -> mlua::Result> { Ok(table.clone()) } From cfe4705ebba5155707d4d48babf11e0c05d81049 Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Sat, 2 Dec 2023 00:42:23 +0100 Subject: [PATCH 16/42] `clone` fix + add `equal` --- lua-api-crates/table-funcs/src/lib.rs | 107 ++++++++++++++++++++------ 1 file changed, 82 insertions(+), 25 deletions(-) diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index fdfc00e2f41..f1e628fa59d 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -11,6 +11,7 @@ pub fn register(lua: &Lua) -> anyhow::Result<()> { table.set("length", lua.create_function(length)?)?; table.set("has_key", lua.create_function(has_key)?)?; table.set("has_value", lua.create_function(has_value)?)?; + table.set("equal", lua.create_function(equal)?)?; table.set("to_string", lua.create_function(to_string)?)?; table.set( "to_string_fallback", @@ -22,27 +23,30 @@ pub fn register(lua: &Lua) -> anyhow::Result<()> { #[derive(Debug, Clone, PartialEq, Eq)] enum ConflictMode { - Keep, - Force, - Error, + Keep, + Force, + Error, } impl<'lua> mlua::FromLua<'lua> for ConflictMode { fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> mlua::Result { match value { - LuaValue::String(s) => { - match s.to_str() { - Ok("Keep") => Ok(ConflictMode::Keep), - Ok("keep") => Ok(ConflictMode::Keep), - Ok("Force") => Ok(ConflictMode::Force), - Ok("force") => Ok(ConflictMode::Force), - Ok("Error") => Ok(ConflictMode::Error), - Ok("error") => Ok(ConflictMode::Error), - _ => Err(mlua::Error::runtime("Unknown string. Expected 'Keep', 'Force' or 'Error'".to_string())) - } - } + LuaValue::String(s) => match s.to_str() { + Ok("Keep") => Ok(ConflictMode::Keep), + Ok("keep") => Ok(ConflictMode::Keep), + Ok("Force") => Ok(ConflictMode::Force), + Ok("force") => Ok(ConflictMode::Force), + Ok("Error") => Ok(ConflictMode::Error), + Ok("error") => Ok(ConflictMode::Error), + _ => Err(mlua::Error::runtime( + "Unknown string. Expected 'Keep', 'Force' or 'Error'".to_string(), + )), + }, LuaValue::Error(err) => Err(err), - other => Err(mlua::Error::runtime(format!("Expected a Lua string. Got something of type: {}", other.type_name()))), + other => Err(mlua::Error::runtime(format!( + "Expected a Lua string. Got something of type: {}", + other.type_name() + ))), } } } @@ -68,7 +72,7 @@ fn extend<'lua>( match behavior { Some(ConflictMode::Keep) => { - for (key,value) in tbl_vec { + for (key, value) in tbl_vec { if !tbl.contains_key(key.clone())? { tbl.set(key, value)?; } @@ -83,7 +87,10 @@ fn extend<'lua>( Some(ConflictMode::Error) => { for (key, value) in tbl_vec { if tbl.contains_key(key.clone())? { - return Err(mlua::Error::runtime(format!("The key {} is in more than one of the tables.", key.to_string()?))); + return Err(mlua::Error::runtime(format!( + "The key {} is in more than one of the tables.", + key.to_string()? + ))); } tbl.set(key, value)?; } @@ -93,7 +100,6 @@ fn extend<'lua>( Ok(tbl) } - // merge tables entrywise recursively // (in case of overlap of the tables, we default to taking the key-value pair from the last table) // Note that we don't use a HashMap since we want to keep the order of the tables, which @@ -115,11 +121,14 @@ fn deep_extend<'lua>( match behavior { Some(ConflictMode::Keep) => { - for (key,value) in tbl_vec { + for (key, value) in tbl_vec { if !tbl.contains_key(key.clone())? { tbl.set(key, value)?; } else if let LuaValue::Table(t) = value { - let inner_tbl = deep_extend(lua, (vec![tbl.get(key.clone())?, t], Some(ConflictMode::Keep)))?; + let inner_tbl = deep_extend( + lua, + (vec![tbl.get(key.clone())?, t], Some(ConflictMode::Keep)), + )?; tbl.set(key, inner_tbl)?; } } @@ -130,7 +139,10 @@ fn deep_extend<'lua>( if !tbl.contains_key(key.clone())? { tbl.set(key, value)?; } else if let LuaValue::Table(t) = value { - let inner_tbl = deep_extend(lua, (vec![tbl.get(key.clone())?, t], Some(ConflictMode::Force)))?; + let inner_tbl = deep_extend( + lua, + (vec![tbl.get(key.clone())?, t], Some(ConflictMode::Force)), + )?; tbl.set(key, inner_tbl)?; } else { tbl.set(key, value)?; @@ -142,10 +154,16 @@ fn deep_extend<'lua>( if !tbl.contains_key(key.clone())? { tbl.set(key, value)?; } else if let LuaValue::Table(t) = value { - let inner_tbl = deep_extend(lua, (vec![tbl.get(key.clone())?, t], Some(ConflictMode::Keep)))?; + let inner_tbl = deep_extend( + lua, + (vec![tbl.get(key.clone())?, t], Some(ConflictMode::Keep)), + )?; tbl.set(key, inner_tbl)?; } else { - return Err(mlua::Error::runtime(format!("The key {} is in more than one of the tables.", key.to_string()?))); + return Err(mlua::Error::runtime(format!( + "The key {} is in more than one of the tables.", + key.to_string()? + ))); } } } @@ -154,9 +172,20 @@ fn deep_extend<'lua>( Ok(tbl) } +fn clone<'lua>(lua: &'lua Lua, table: Table<'lua>) -> mlua::Result> { + let table_len = table.clone().pairs::().count(); + let res: Table<'lua> = lua.create_table_with_capacity(0, table_len)?; -fn clone<'lua>(_: &'lua Lua, table: Table<'lua>) -> mlua::Result> { - Ok(table.clone()) + for pair in table.pairs::() { + let (key, value) = pair?; + if let LuaValue::Table(tbl) = value { + let inner_res = clone(lua, tbl)?; + res.set(key, inner_res)?; + } else { + res.set(key, value)?; + } + } + Ok(res) } fn flatten<'lua>(lua: &'lua Lua, arrays: Vec>) -> mlua::Result>> { @@ -203,6 +232,34 @@ fn has_value<'lua>(_: &'lua Lua, (table, value): (Table<'lua>, LuaValue)) -> mlu Ok(false) } +fn equal<'lua>(lua: &'lua Lua, (table1, table2): (Table<'lua>, Table<'lua>)) -> mlua::Result { + let mut res = true; + + // check if the tables are the same length to ensure we don't miss anything in table2 + // when we only loop through table1 + let table1_len = table2.clone().pairs::().count(); + let table2_len = table2.clone().pairs::().count(); + if table1_len != table2_len { + return Ok(false); + } + + for pair in table1.pairs::() { + let (key, value1) = pair?; + let value2 = table2.get(key.clone())?; + if let LuaValue::Table(tbl1) = value1.clone() { + if let LuaValue::Table(tbl2) = value2 { + res = equal(lua, (tbl1, tbl2))?; + } else { + return Ok(false); + } + } else { + res = value1.eq(&value2); + } + } + + Ok(res) +} + fn to_string_fallback<'lua>(_: &'lua Lua, table: Table<'lua>) -> mlua::Result { Ok(format!("{:#?}", table)) } From ee2ecbc7d70949587a91c6def6f4497cf4b6d40f Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Sat, 2 Dec 2023 00:43:55 +0100 Subject: [PATCH 17/42] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index bb9982eb07e..5a2e07aa5f2 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ dhat-heap.json /docs/config/lua/wezterm.time/index.md /docs/config/lua/wezterm.time/Time/index.md /docs/config/lua/wezterm.url/index.md +/docs/config/lua/wezterm.table/index.md /docs/config/lua/wezterm/index.md /docs/config/lua/window-events/index.md /docs/config/lua/window/index.md From 0a61ba0dfbec76bde2fb498b48a413b24e510b17 Mon Sep 17 00:00:00 2001 From: Daniel Kongsgaard Date: Sat, 2 Dec 2023 00:46:01 +0100 Subject: [PATCH 18/42] Delete docs/config/lua/wezterm.table/index.md --- docs/config/lua/wezterm.table/index.md | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 docs/config/lua/wezterm.table/index.md diff --git a/docs/config/lua/wezterm.table/index.md b/docs/config/lua/wezterm.table/index.md deleted file mode 100644 index 47b01c7d137..00000000000 --- a/docs/config/lua/wezterm.table/index.md +++ /dev/null @@ -1,17 +0,0 @@ -# `wezterm.table` module - -{{since('nightly')}} - -The `wezterm.table` module exposes functions that work with Lua tables and arrays. - -## Available functions - - - - [clone](clone.md) - - [flatten](flatten.md) - - [has_key](has_key.md) - - [has_value](has_value.md) - - [length](length.md) - - [merge](merge.md) - - [to_string](to_string.md) - - [to_string_fallback](to_string_fallback.md) From 716b20a5f52aa75df41e7658c327b7cc48d4d6a6 Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Sat, 2 Dec 2023 01:33:06 +0100 Subject: [PATCH 19/42] Improve `equal` --- lua-api-crates/table-funcs/src/lib.rs | 45 +++++++++++++++------------ 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index f1e628fa59d..08afc885976 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -232,32 +232,37 @@ fn has_value<'lua>(_: &'lua Lua, (table, value): (Table<'lua>, LuaValue)) -> mlu Ok(false) } -fn equal<'lua>(lua: &'lua Lua, (table1, table2): (Table<'lua>, Table<'lua>)) -> mlua::Result { - let mut res = true; - - // check if the tables are the same length to ensure we don't miss anything in table2 - // when we only loop through table1 - let table1_len = table2.clone().pairs::().count(); - let table2_len = table2.clone().pairs::().count(); - if table1_len != table2_len { - return Ok(false); +fn lua_value_eq(value1: &LuaValue, value2: &LuaValue) -> mlua::Result { + match (value1, value2) { + (LuaValue::Table(a), LuaValue::Table(b)) => lua_table_eq(a, b), + (a, b) => Ok(a.eq(b)), } +} - for pair in table1.pairs::() { - let (key, value1) = pair?; - let value2 = table2.get(key.clone())?; - if let LuaValue::Table(tbl1) = value1.clone() { - if let LuaValue::Table(tbl2) = value2 { - res = equal(lua, (tbl1, tbl2))?; - } else { - return Ok(false); +fn lua_table_eq(table1: &Table, table2: &Table) -> mlua::Result { + let mut table1_len = 0; + for pair in table1.clone().pairs::() { + match pair { + Ok((key, value)) => { + table1_len += 1; + match table2.get(key.clone()) { + Ok(value2) => { + if !lua_value_eq(&value, &value2)? { + return Ok(false); + } + } + Err(_) => return Ok(false), + } } - } else { - res = value1.eq(&value2); + Err(_) => return Ok(false), } } + let table2_len = table2.clone().pairs::().count(); + Ok(table1_len == table2_len) +} - Ok(res) +fn equal<'lua>(_: &'lua Lua, (table1, table2): (Table<'lua>, Table<'lua>)) -> mlua::Result { + lua_table_eq(&table1, &table2) } fn to_string_fallback<'lua>(_: &'lua Lua, table: Table<'lua>) -> mlua::Result { From 7e10b6609ee90a41720ce19d363a47694fffb767 Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Sat, 2 Dec 2023 17:14:05 +0100 Subject: [PATCH 20/42] Cleanup `ConflictMode` --- Cargo.lock | 1 + lua-api-crates/table-funcs/Cargo.toml | 1 + lua-api-crates/table-funcs/src/lib.rs | 29 ++++----------------------- 3 files changed, 6 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b0f203c5db8..2e03eb1f0e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5327,6 +5327,7 @@ dependencies = [ "anyhow", "config", "luahelper", + "wezterm-dynamic", ] [[package]] diff --git a/lua-api-crates/table-funcs/Cargo.toml b/lua-api-crates/table-funcs/Cargo.toml index 6afb14bf51b..e95c79a6694 100644 --- a/lua-api-crates/table-funcs/Cargo.toml +++ b/lua-api-crates/table-funcs/Cargo.toml @@ -9,3 +9,4 @@ edition = "2021" anyhow = "1.0" config = { path = "../../config" } luahelper = { path = "../../luahelper" } +wezterm-dynamic = { path = "../../wezterm-dynamic" } diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index 08afc885976..4b433f9405a 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -1,6 +1,7 @@ use config::lua::get_or_create_sub_module; use config::lua::mlua::{self, Integer, Lua, Table, Value as LuaValue}; -use luahelper::ValuePrinter; +use luahelper::{impl_lua_conversion_dynamic, ValuePrinter}; +use wezterm_dynamic::{FromDynamic, ToDynamic}; pub fn register(lua: &Lua) -> anyhow::Result<()> { let table = get_or_create_sub_module(lua, "table")?; @@ -21,35 +22,13 @@ pub fn register(lua: &Lua) -> anyhow::Result<()> { Ok(()) } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, FromDynamic, ToDynamic, Clone, PartialEq, Eq)] enum ConflictMode { Keep, Force, Error, } - -impl<'lua> mlua::FromLua<'lua> for ConflictMode { - fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> mlua::Result { - match value { - LuaValue::String(s) => match s.to_str() { - Ok("Keep") => Ok(ConflictMode::Keep), - Ok("keep") => Ok(ConflictMode::Keep), - Ok("Force") => Ok(ConflictMode::Force), - Ok("force") => Ok(ConflictMode::Force), - Ok("Error") => Ok(ConflictMode::Error), - Ok("error") => Ok(ConflictMode::Error), - _ => Err(mlua::Error::runtime( - "Unknown string. Expected 'Keep', 'Force' or 'Error'".to_string(), - )), - }, - LuaValue::Error(err) => Err(err), - other => Err(mlua::Error::runtime(format!( - "Expected a Lua string. Got something of type: {}", - other.type_name() - ))), - } - } -} +impl_lua_conversion_dynamic!(ConflictMode); // merge tables // (in case of overlap of the tables, we default to taking the key-value pair from the last table) From 77e0b3f954b83a61d3c6f9654b1c6069e03049fd Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Sat, 2 Dec 2023 17:20:48 +0100 Subject: [PATCH 21/42] Small changes to remove `.clone()` calls --- lua-api-crates/table-funcs/src/lib.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index 4b433f9405a..265d58be86e 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -211,22 +211,22 @@ fn has_value<'lua>(_: &'lua Lua, (table, value): (Table<'lua>, LuaValue)) -> mlu Ok(false) } -fn lua_value_eq(value1: &LuaValue, value2: &LuaValue) -> mlua::Result { +fn lua_value_eq(value1: LuaValue, value2: LuaValue) -> mlua::Result { match (value1, value2) { (LuaValue::Table(a), LuaValue::Table(b)) => lua_table_eq(a, b), - (a, b) => Ok(a.eq(b)), + (a, b) => Ok(a.eq(&b)), } } -fn lua_table_eq(table1: &Table, table2: &Table) -> mlua::Result { +fn lua_table_eq(table1: Table, table2: Table) -> mlua::Result { let mut table1_len = 0; - for pair in table1.clone().pairs::() { + for pair in table1.pairs::() { match pair { Ok((key, value)) => { table1_len += 1; match table2.get(key.clone()) { Ok(value2) => { - if !lua_value_eq(&value, &value2)? { + if !lua_value_eq(value, value2)? { return Ok(false); } } @@ -236,12 +236,12 @@ fn lua_table_eq(table1: &Table, table2: &Table) -> mlua::Result { Err(_) => return Ok(false), } } - let table2_len = table2.clone().pairs::().count(); + let table2_len = table2.pairs::().count(); Ok(table1_len == table2_len) } fn equal<'lua>(_: &'lua Lua, (table1, table2): (Table<'lua>, Table<'lua>)) -> mlua::Result { - lua_table_eq(&table1, &table2) + lua_table_eq(table1, table2) } fn to_string_fallback<'lua>(_: &'lua Lua, table: Table<'lua>) -> mlua::Result { From a1d53c65445b09c16c2376e89c0890ea4c59dba1 Mon Sep 17 00:00:00 2001 From: Daniel Kongsgaard Date: Sat, 2 Dec 2023 21:18:53 +0100 Subject: [PATCH 22/42] Apply some suggestions from code review Co-authored-by: Wez Furlong --- lua-api-crates/table-funcs/src/lib.rs | 29 +++++++++++---------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index 265d58be86e..6b76deb10c4 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -22,10 +22,14 @@ pub fn register(lua: &Lua) -> anyhow::Result<()> { Ok(()) } -#[derive(Debug, FromDynamic, ToDynamic, Clone, PartialEq, Eq)] +#[derive(Default, Debug, FromDynamic, ToDynamic, Clone, PartialEq, Eq)] enum ConflictMode { + /// Retain the existing value Keep, + /// Take the latest value + #[default] Force, + /// Raise an error Error, } impl_lua_conversion_dynamic!(ConflictMode); @@ -152,14 +156,12 @@ fn deep_extend<'lua>( } fn clone<'lua>(lua: &'lua Lua, table: Table<'lua>) -> mlua::Result> { - let table_len = table.clone().pairs::().count(); - let res: Table<'lua> = lua.create_table_with_capacity(0, table_len)?; + let res: Table<'lua> = lua.create_table()?; for pair in table.pairs::() { let (key, value) = pair?; if let LuaValue::Table(tbl) = value { - let inner_res = clone(lua, tbl)?; - res.set(key, inner_res)?; + res.set(key, clone(lua, tbl)?)?; } else { res.set(key, value)?; } @@ -173,16 +175,10 @@ fn flatten<'lua>(lua: &'lua Lua, arrays: Vec>) -> mlua::Result { let tbl_as_vec = tbl.sequence_values().filter_map(|x| x.ok()).collect(); - let flat = flatten(lua, tbl_as_vec)?; - for j in flat { - flat_vec.push(j); - } + let mut flat = flatten(lua, tbl_as_vec)?; + flat_vec.append(&mut flat); } LuaValue::Nil => (), - LuaValue::Thread(_) => (), - LuaValue::Error(err) => { - return Err(err); - } other => { flat_vec.push(other); } @@ -191,10 +187,9 @@ fn flatten<'lua>(lua: &'lua Lua, arrays: Vec>) -> mlua::Result(_: &'lua Lua, table: Table<'lua>) -> mlua::Result { - // note that # only works correctly on arrays in Lua - let len = table.pairs::().count() as i64; - Ok(len) +/// note that the `#` operator only works correctly on arrays in Lua +fn length<'lua>(_: &'lua Lua, table: Table<'lua>) -> mlua::Result { + Ok(table.pairs::().count()) } fn has_key<'lua>(_: &'lua Lua, (table, key): (Table<'lua>, LuaValue)) -> mlua::Result { From aff4221f2c8a82755012fd854c26634ee0d57077 Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Sat, 2 Dec 2023 22:17:28 +0100 Subject: [PATCH 23/42] Cleanup of to_string and extend functions Also removed the to_string docs and renamed length to count. --- docs/config/lua/wezterm.table/count.md | 27 ++++ docs/config/lua/wezterm.table/length.md | 26 ---- docs/config/lua/wezterm.table/to_string.md | 66 ---------- .../lua/wezterm.table/to_string_fallback.md | 37 ------ lua-api-crates/table-funcs/src/lib.rs | 124 +++++------------- 5 files changed, 59 insertions(+), 221 deletions(-) create mode 100644 docs/config/lua/wezterm.table/count.md delete mode 100644 docs/config/lua/wezterm.table/length.md delete mode 100644 docs/config/lua/wezterm.table/to_string.md delete mode 100644 docs/config/lua/wezterm.table/to_string_fallback.md diff --git a/docs/config/lua/wezterm.table/count.md b/docs/config/lua/wezterm.table/count.md new file mode 100644 index 00000000000..6efa831a02e --- /dev/null +++ b/docs/config/lua/wezterm.table/count.md @@ -0,0 +1,27 @@ +# `wezterm.table.count(table)` + +{{since('nightly')}} + +This function returns the number of non-nil elements of a Lua table (or array) passed to it. + +Note: The Lua function `#` also returns the length of an array, but `#` only works for array-style +tables with contiguous integer keys starting with index `1`, and not sparse arrays (with gaps in +their integer keys), or object or other style of tables with non-integer keys. + +```lua +local wezterm = require 'wezterm' +local count = wezterm.table.count + +local tbl1 = { + a = 1, + b = '2', +} +local arr1 = { 1, 'a', 2, 'abc' } + +assert(2 == count(tbl1)) +assert(4 == count(arr1)) + +assert(0 == #tbl1) +assert(4 == #arr1) +``` + diff --git a/docs/config/lua/wezterm.table/length.md b/docs/config/lua/wezterm.table/length.md deleted file mode 100644 index 2c216a2dd7f..00000000000 --- a/docs/config/lua/wezterm.table/length.md +++ /dev/null @@ -1,26 +0,0 @@ -# `wezterm.table.length(table)` - -{{since('nightly')}} - -This function returns the length of a Lua table (or array) passed to it. - -Note: The Lua function `#` also returns the length of an array, but -`#` only works for array-style tables with contiguous integer keys starting with index `1`, and not sparse arrays (with gaps in their integer keys), or object or other style of tables with non-integer keys. - -```lua -local wezterm = require 'wezterm' -local length = wezterm.table.length - -local tbl1 = { - a = 1, - b = '2', -} -local arr1 = { 1, 'a', 2, 'abc' } - -assert(2 == length(tbl1)) -assert(4 == length(arr1)) - -assert(0 == #tbl1) -assert(4 == #arr1) -``` - diff --git a/docs/config/lua/wezterm.table/to_string.md b/docs/config/lua/wezterm.table/to_string.md deleted file mode 100644 index 8ff1b933789..00000000000 --- a/docs/config/lua/wezterm.table/to_string.md +++ /dev/null @@ -1,66 +0,0 @@ -# `wezterm.table.to_string(table [, indent [, skip_outer_bracket]])` - -{{since('nightly')}} - -This function takes a Lua table and returns a string with the data of -the table. E.g., passing in the table `{ a=1, b=2 }` the function -will return the string: -``` -{ - a = 1, - b = 2, -} -``` - -*Note:* This function is not careful about checking for recursive tables, so it can't -be used to print e.g. `_G`. To print a recursive (or in general a very big) table, -it is recommended that you use [to_string_fallback](to_string_fallback.md). - -By default this function constructs the string with 2 spaces for indentation. - -The optional `indent` allows us to instead prefer other (non-negative) integer values -of spaces for the indentation. - -```lua -local wezterm = require 'wezterm' -local tbl_to_string = wezterm.table.to_string - -local tbl1 = { - a = 1, - { - b = 2, - }, -} -local str1 = [[{ - a = 1, - { - b = 2, - }, -}]] - -assert(str1 == tbl_to_string(tbl1, 4)) -``` - -The optional `skip_outer_bracket` (which can only be used together with `indent`) is -a boolean, which defaults to `false`. If you set it to `true`, the outer brackets are -not included in the string (and thus everything is `indent` fewer spaces indented too). - -```lua -local wezterm = require 'wezterm' -local tbl_to_string = wezterm.table.to_string - -local tbl1 = { - a = 1, - { - b = 2, - }, -} -local str1 = [[a = 1, -{ -b = 2, -},]] - -assert(str1 == tbl_to_string(tbl1, 0, true)) -``` - -See also [to_string_fallback](to_string_fallback.md). diff --git a/docs/config/lua/wezterm.table/to_string_fallback.md b/docs/config/lua/wezterm.table/to_string_fallback.md deleted file mode 100644 index 903da9e5c54..00000000000 --- a/docs/config/lua/wezterm.table/to_string_fallback.md +++ /dev/null @@ -1,37 +0,0 @@ -# `wezterm.table.to_string_fallback(table)` - -{{since('nightly')}} - -This function takes a Lua table and returns a string with the data of -the table. E.g., passing in the table `{ a=1, b=2 }` the function -will return the string: -``` -{ - ["a"] = 1, - ["b"] = 2, -} -``` - -For nested tables, this function always prints a label (even for arrays). -This can make the string look different than you might expect. -```lua -local wezterm = require 'wezterm' -local tbl_to_string_fb = wezterm.table.to_string_fallback - -local tbl1 = { - a = 1, - { - b = 2, - }, -} -local str1 = [[{ - [1] = { - ["b"] = 2, - }, - ["a"] = 1, -}]] - -assert(str1 == tbl_to_string_fb(tbl1)) -``` - -See also [to_string](to_string.md). diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index 6b76deb10c4..230439f4fdb 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -1,6 +1,6 @@ use config::lua::get_or_create_sub_module; -use config::lua::mlua::{self, Integer, Lua, Table, Value as LuaValue}; -use luahelper::{impl_lua_conversion_dynamic, ValuePrinter}; +use config::lua::mlua::{self, Lua, Table, Value as LuaValue}; +use luahelper::impl_lua_conversion_dynamic; use wezterm_dynamic::{FromDynamic, ToDynamic}; pub fn register(lua: &Lua) -> anyhow::Result<()> { @@ -9,20 +9,15 @@ pub fn register(lua: &Lua) -> anyhow::Result<()> { table.set("deep_extend", lua.create_function(deep_extend)?)?; table.set("clone", lua.create_function(clone)?)?; table.set("flatten", lua.create_function(flatten)?)?; - table.set("length", lua.create_function(length)?)?; + table.set("count", lua.create_function(count)?)?; table.set("has_key", lua.create_function(has_key)?)?; table.set("has_value", lua.create_function(has_value)?)?; table.set("equal", lua.create_function(equal)?)?; - table.set("to_string", lua.create_function(to_string)?)?; - table.set( - "to_string_fallback", - lua.create_function(to_string_fallback)?, - )?; Ok(()) } -#[derive(Default, Debug, FromDynamic, ToDynamic, Clone, PartialEq, Eq)] +#[derive(Default, Debug, FromDynamic, ToDynamic, Clone, PartialEq, Eq, Copy)] enum ConflictMode { /// Retain the existing value Keep, @@ -53,32 +48,19 @@ fn extend<'lua>( // note we might allocate a bit too much here, but in many use cases we will be correct let tbl: Table<'lua> = lua.create_table_with_capacity(0, tbl_len)?; - match behavior { - Some(ConflictMode::Keep) => { - for (key, value) in tbl_vec { - if !tbl.contains_key(key.clone())? { - tbl.set(key, value)?; - } - } - } - // default behavior is to keep last set value - Some(ConflictMode::Force) | None => { - for (key, value) in tbl_vec { - tbl.set(key, value)?; - } + let behavior = behavior.unwrap_or_default(); + for (key, value) in tbl_vec { + if !tbl.contains_key(key.clone())? { + tbl.set(key, value)?; + } else if behavior == ConflictMode::Force { + tbl.set(key, value)?; + } else if behavior == ConflictMode::Error { + return Err(mlua::Error::runtime(format!( + "The key {} is in more than one of the tables.", + key.to_string()? + ))); } - Some(ConflictMode::Error) => { - for (key, value) in tbl_vec { - if tbl.contains_key(key.clone())? { - return Err(mlua::Error::runtime(format!( - "The key {} is in more than one of the tables.", - key.to_string()? - ))); - } - tbl.set(key, value)?; - } - } - }; + } Ok(tbl) } @@ -102,55 +84,22 @@ fn deep_extend<'lua>( // note we might allocate a bit too much here, but in many use cases we will be correct let tbl: Table<'lua> = lua.create_table_with_capacity(0, tbl_len)?; - match behavior { - Some(ConflictMode::Keep) => { - for (key, value) in tbl_vec { - if !tbl.contains_key(key.clone())? { - tbl.set(key, value)?; - } else if let LuaValue::Table(t) = value { - let inner_tbl = deep_extend( - lua, - (vec![tbl.get(key.clone())?, t], Some(ConflictMode::Keep)), - )?; - tbl.set(key, inner_tbl)?; - } - } + let behavior = behavior.unwrap_or_default(); + for (key, value) in tbl_vec { + if !tbl.contains_key(key.clone())? { + tbl.set(key, value)?; + } else if let LuaValue::Table(t) = value { + let inner_tbl = deep_extend(lua, (vec![tbl.get(key.clone())?, t], Some(behavior)))?; + tbl.set(key, inner_tbl)?; + } else if behavior == ConflictMode::Force { + tbl.set(key, value)?; + } else if behavior == ConflictMode::Error { + return Err(mlua::Error::runtime(format!( + "The key {} is in more than one of the tables.", + key.to_string()? + ))); } - // default behavior is to keep last set value - Some(ConflictMode::Force) | None => { - for (key, value) in tbl_vec { - if !tbl.contains_key(key.clone())? { - tbl.set(key, value)?; - } else if let LuaValue::Table(t) = value { - let inner_tbl = deep_extend( - lua, - (vec![tbl.get(key.clone())?, t], Some(ConflictMode::Force)), - )?; - tbl.set(key, inner_tbl)?; - } else { - tbl.set(key, value)?; - } - } - } - Some(ConflictMode::Error) => { - for (key, value) in tbl_vec { - if !tbl.contains_key(key.clone())? { - tbl.set(key, value)?; - } else if let LuaValue::Table(t) = value { - let inner_tbl = deep_extend( - lua, - (vec![tbl.get(key.clone())?, t], Some(ConflictMode::Keep)), - )?; - tbl.set(key, inner_tbl)?; - } else { - return Err(mlua::Error::runtime(format!( - "The key {} is in more than one of the tables.", - key.to_string()? - ))); - } - } - } - }; + } Ok(tbl) } @@ -188,7 +137,7 @@ fn flatten<'lua>(lua: &'lua Lua, arrays: Vec>) -> mlua::Result(_: &'lua Lua, table: Table<'lua>) -> mlua::Result { +fn count<'lua>(_: &'lua Lua, table: Table<'lua>) -> mlua::Result { Ok(table.pairs::().count()) } @@ -238,12 +187,3 @@ fn lua_table_eq(table1: Table, table2: Table) -> mlua::Result { fn equal<'lua>(_: &'lua Lua, (table1, table2): (Table<'lua>, Table<'lua>)) -> mlua::Result { lua_table_eq(table1, table2) } - -fn to_string_fallback<'lua>(_: &'lua Lua, table: Table<'lua>) -> mlua::Result { - Ok(format!("{:#?}", table)) -} - -fn to_string<'lua>(_: &'lua Lua, table: Table<'lua>) -> mlua::Result { - let res = ValuePrinter(LuaValue::Table(table)); - Ok(format!("{:#?}", res).to_string()) -} From 358d8b99f1cf15b267c91f084ef270ac501f86f5 Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Sat, 2 Dec 2023 22:56:07 +0100 Subject: [PATCH 24/42] Add optional behavior to flatten and clone Note: I haven't tested this code yet. --- docs/config/lua/wezterm.table/count.md | 5 +-- lua-api-crates/table-funcs/src/lib.rs | 43 ++++++++++++++++++++------ 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/docs/config/lua/wezterm.table/count.md b/docs/config/lua/wezterm.table/count.md index 6efa831a02e..36de9e30bba 100644 --- a/docs/config/lua/wezterm.table/count.md +++ b/docs/config/lua/wezterm.table/count.md @@ -2,11 +2,12 @@ {{since('nightly')}} -This function returns the number of non-nil elements of a Lua table (or array) passed to it. +This function returns the number of non-nil elements of any Lua table passed to it. Note: The Lua function `#` also returns the length of an array, but `#` only works for array-style tables with contiguous integer keys starting with index `1`, and not sparse arrays (with gaps in -their integer keys), or object or other style of tables with non-integer keys. +their integer keys), or object or other style of tables with non-integer keys. `wezterm.table.count` +can instead be used for such tables. ```lua local wezterm = require 'wezterm' diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index 230439f4fdb..449e201cef3 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -1,5 +1,5 @@ use config::lua::get_or_create_sub_module; -use config::lua::mlua::{self, Lua, Table, Value as LuaValue}; +use config::lua::mlua::{self, Lua, Table, Value as LuaValue, MultiValue as LuaMultiValue}; use luahelper::impl_lua_conversion_dynamic; use wezterm_dynamic::{FromDynamic, ToDynamic}; @@ -29,6 +29,16 @@ enum ConflictMode { } impl_lua_conversion_dynamic!(ConflictMode); +#[derive(Default, Debug, FromDynamic, ToDynamic, Clone, PartialEq, Eq, Copy)] +enum DepthMode { + /// Take the latest value + #[default] + Top, + /// Raise an error + Deep, +} +impl_lua_conversion_dynamic!(DepthMode); + // merge tables // (in case of overlap of the tables, we default to taking the key-value pair from the last table) // Note that we don't use a HashMap since we want to keep the order of the tables, which @@ -104,28 +114,41 @@ fn deep_extend<'lua>( Ok(tbl) } -fn clone<'lua>(lua: &'lua Lua, table: Table<'lua>) -> mlua::Result> { +fn clone<'lua>(lua: &'lua Lua, (table, behavior): (Table<'lua>, Option)) -> mlua::Result> { let res: Table<'lua> = lua.create_table()?; + let behavior = behavior.unwrap_or_default(); for pair in table.pairs::() { let (key, value) = pair?; - if let LuaValue::Table(tbl) = value { - res.set(key, clone(lua, tbl)?)?; - } else { - res.set(key, value)?; + match behavior { + DepthMode::Top => { + res.set(key, value)? + } + DepthMode::Deep => { + if let LuaValue::Table(tbl) = value { + res.set(key, clone(lua, (tbl, Some(behavior)))?)? + } else { + res.set(key, value)?; + } + } } } Ok(res) } -fn flatten<'lua>(lua: &'lua Lua, arrays: Vec>) -> mlua::Result>> { +fn flatten<'lua>(lua: &'lua Lua, (arrays, behavior): (Vec>, Option)) -> mlua::Result>> { let mut flat_vec: Vec = vec![]; + let behavior = behavior.unwrap_or_default(); for item in arrays { match item { LuaValue::Table(tbl) => { - let tbl_as_vec = tbl.sequence_values().filter_map(|x| x.ok()).collect(); - let mut flat = flatten(lua, tbl_as_vec)?; - flat_vec.append(&mut flat); + if behavior == DepthMode::Deep { + let tbl_as_vec = tbl.sequence_values().filter_map(|x| x.ok()).collect(); + let mut flat = flatten(lua, (tbl_as_vec, Some(behavior)))?; + flat_vec.append(&mut flat); + } else { + flat_vec.push(LuaValue::Table(tbl)); + } } LuaValue::Nil => (), other => { From 4be77dca97968d23b42d95f79c8f91b8a35d0075 Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Sun, 3 Dec 2023 02:38:54 +0100 Subject: [PATCH 25/42] Added optional extra keys to has_key --- lua-api-crates/table-funcs/src/lib.rs | 43 ++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index 449e201cef3..22c3d0a7f2f 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -1,5 +1,5 @@ use config::lua::get_or_create_sub_module; -use config::lua::mlua::{self, Lua, Table, Value as LuaValue, MultiValue as LuaMultiValue}; +use config::lua::mlua::{self, Lua, MultiValue as LuaMultiValue, Table, Value as LuaValue}; use luahelper::impl_lua_conversion_dynamic; use wezterm_dynamic::{FromDynamic, ToDynamic}; @@ -114,16 +114,17 @@ fn deep_extend<'lua>( Ok(tbl) } -fn clone<'lua>(lua: &'lua Lua, (table, behavior): (Table<'lua>, Option)) -> mlua::Result> { +fn clone<'lua>( + lua: &'lua Lua, + (table, behavior): (Table<'lua>, Option), +) -> mlua::Result> { let res: Table<'lua> = lua.create_table()?; let behavior = behavior.unwrap_or_default(); for pair in table.pairs::() { let (key, value) = pair?; match behavior { - DepthMode::Top => { - res.set(key, value)? - } + DepthMode::Top => res.set(key, value)?, DepthMode::Deep => { if let LuaValue::Table(tbl) = value { res.set(key, clone(lua, (tbl, Some(behavior)))?)? @@ -136,7 +137,10 @@ fn clone<'lua>(lua: &'lua Lua, (table, behavior): (Table<'lua>, Option(lua: &'lua Lua, (arrays, behavior): (Vec>, Option)) -> mlua::Result>> { +fn flatten<'lua>( + lua: &'lua Lua, + (arrays, behavior): (Vec>, Option), +) -> mlua::Result>> { let mut flat_vec: Vec = vec![]; let behavior = behavior.unwrap_or_default(); for item in arrays { @@ -164,8 +168,31 @@ fn count<'lua>(_: &'lua Lua, table: Table<'lua>) -> mlua::Result { Ok(table.pairs::().count()) } -fn has_key<'lua>(_: &'lua Lua, (table, key): (Table<'lua>, LuaValue)) -> mlua::Result { - Ok(table.contains_key(key)?) +fn has_key<'lua>( + _: &'lua Lua, + (table, key, mut extra_keys): (Table<'lua>, LuaValue, LuaMultiValue), +) -> mlua::Result { + if extra_keys.is_empty() { + return Ok(table.contains_key(key)?); + } + + let mut value_has_key = table.contains_key(key.clone())?; + + let mut value = match table.get::<_, Table>(key) { + Ok(t) => t, + Err(_) => return Ok(value_has_key && extra_keys.len() == 0), + }; + + while let Some(next_key) = extra_keys.pop_front() { + value_has_key = value.contains_key(next_key.clone())?; + let new_val = value.get::<_, Table>(next_key); + value = match new_val { + Ok(t) => t, + Err(_) => return Ok(value_has_key && extra_keys.len() == 0), + }; + } + + Ok(value_has_key) } fn has_value<'lua>(_: &'lua Lua, (table, value): (Table<'lua>, LuaValue)) -> mlua::Result { From 9a509e7fa0431b3d9dc0c7a05728a3ab1a7f3cd7 Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Sun, 3 Dec 2023 02:49:29 +0100 Subject: [PATCH 26/42] Minor cleanup --- lua-api-crates/table-funcs/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index 22c3d0a7f2f..2ea658e9d90 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -180,7 +180,7 @@ fn has_key<'lua>( let mut value = match table.get::<_, Table>(key) { Ok(t) => t, - Err(_) => return Ok(value_has_key && extra_keys.len() == 0), + Err(_) => return Ok(false), // if extra_keys were empty, we wouldn't get here }; while let Some(next_key) = extra_keys.pop_front() { @@ -188,7 +188,7 @@ fn has_key<'lua>( let new_val = value.get::<_, Table>(next_key); value = match new_val { Ok(t) => t, - Err(_) => return Ok(value_has_key && extra_keys.len() == 0), + Err(_) => return Ok(value_has_key && extra_keys.is_empty()), }; } From f06582d840094a28322b4ee9db70e8e631bc6465 Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Sun, 3 Dec 2023 14:38:42 +0100 Subject: [PATCH 27/42] Added `get` function --- lua-api-crates/table-funcs/src/lib.rs | 34 +++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index 2ea658e9d90..5b06f430025 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -10,6 +10,7 @@ pub fn register(lua: &Lua) -> anyhow::Result<()> { table.set("clone", lua.create_function(clone)?)?; table.set("flatten", lua.create_function(flatten)?)?; table.set("count", lua.create_function(count)?)?; + table.set("get", lua.create_function(get)?)?; table.set("has_key", lua.create_function(has_key)?)?; table.set("has_value", lua.create_function(has_value)?)?; table.set("equal", lua.create_function(equal)?)?; @@ -168,6 +169,39 @@ fn count<'lua>(_: &'lua Lua, table: Table<'lua>) -> mlua::Result { Ok(table.pairs::().count()) } +fn get<'lua>( + _: &'lua Lua, + (table, key, mut extra_keys): (Table<'lua>, LuaValue<'lua>, LuaMultiValue<'lua>), +) -> mlua::Result> { + if extra_keys.is_empty() { + return Ok(table.get::<_, LuaValue>(key)?); + } + + let mut value: LuaValue = table.get(key.clone())?; + + let mut value_tbl = match table.get::<_, Table>(key) { + Ok(t) => t, + Err(_) => return Ok(LuaValue::Nil), // if extra_keys were empty, we wouldn't get here + }; + + while let Some(next_key) = extra_keys.pop_front() { + value = value_tbl.get(next_key.clone())?; + let new_val_tbl = value_tbl.get::<_, Table>(next_key); + value_tbl = match new_val_tbl { + Ok(t) => t, + Err(_) => { + if extra_keys.is_empty() { + return Ok(value); + } else { + return Ok(LuaValue::Nil); + } + } + } + } + + Ok(value) +} + fn has_key<'lua>( _: &'lua Lua, (table, key, mut extra_keys): (Table<'lua>, LuaValue, LuaMultiValue), From f76d8e2fa19886541b281db137de6050228da192 Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Sun, 3 Dec 2023 16:24:01 +0100 Subject: [PATCH 28/42] Flatten fix + docs updates --- docs/config/lua/wezterm.table/clone.md | 47 ++++++++++++-- docs/config/lua/wezterm.table/deep_extend.md | 66 ++++++++++++++++++++ docs/config/lua/wezterm.table/equal.md | 31 +++++++++ docs/config/lua/wezterm.table/extend.md | 63 +++++++++++++++++++ docs/config/lua/wezterm.table/flatten.md | 27 ++++++-- docs/config/lua/wezterm.table/get.md | 39 ++++++++++++ docs/config/lua/wezterm.table/has_key.md | 26 ++++++-- docs/config/lua/wezterm.table/has_value.md | 4 +- docs/config/lua/wezterm.table/merge.md | 38 ----------- lua-api-crates/table-funcs/src/lib.rs | 8 ++- 10 files changed, 292 insertions(+), 57 deletions(-) create mode 100644 docs/config/lua/wezterm.table/deep_extend.md create mode 100644 docs/config/lua/wezterm.table/equal.md create mode 100644 docs/config/lua/wezterm.table/extend.md create mode 100644 docs/config/lua/wezterm.table/get.md delete mode 100644 docs/config/lua/wezterm.table/merge.md diff --git a/docs/config/lua/wezterm.table/clone.md b/docs/config/lua/wezterm.table/clone.md index c2144cac620..6431260fab6 100644 --- a/docs/config/lua/wezterm.table/clone.md +++ b/docs/config/lua/wezterm.table/clone.md @@ -1,20 +1,57 @@ -# `wezterm.table.clone(table)` +# `wezterm.table.clone(table [, behavior])` {{since('nightly')}} -This function clones the Lua table (or array) passed to it. +This function clones the Lua table passed to it. + +The function accepts an optional string of the form `'Top'` or `'Deep'` +describing its behavior. Any other string passed to the function will result +in an error. The default behavior is equavalent to passing the string `'Top'` +as the behavior. + +When `clone` is run with the `'Top'` behavior, it will only go through the +top-level of the table and clone the values. In particular this means that +for any tables at the top-level, it will just clone the table address, and +thus any changes in these nested tables will affect the clone. + +When `clone` is run with the `'Deep'` behavior, it will recursively go through +all nested tables and clone the non-table values. Thus any changes to the +original table won't affect the clone. + ```lua local wezterm = require 'wezterm' local clone = wezterm.table.clone +local equal = wezterm.table.equal -local tbl1 = { +local tbl = { a = 1, b = '2', + c = { + d = 1, + }, } +local tbl_copy = tbl -- copy the table address +local tbl_top_clone = clone(tbl) -- same as clone(tbl1, 'Top') +local tbl_deep_clone = clone(tbl, 'Deep') + +assert(tbl == tbl_copy) +assert(not (tbl == tbl_top_clone)) +assert(not (tbl == tbl_deep_clone)) +assert(equal(tbl, tbl_top_clone)) +assert(equal(tbl, tbl_deep_clone)) -local tbl2 = clone(tbl1) +tbl.a = 2 +assert(not equal(tbl, tbl_top_clone)) +assert(tbl_top_clone.a == 1) +assert(not equal(tbl, tbl_deep_clone)) +assert(tbl_deep_clone.a == 1) +tbl.a = 1 -assert(tbl1 == tbl2) +tbl.c.d = 2 +assert(equal(tbl, tbl_top_clone)) +assert(tbl_deep_clone.c.d == 2) +assert(not equal(tbl, tbl_deep_clone)) +assert(tbl_deep_clone.c.d == 1) ``` diff --git a/docs/config/lua/wezterm.table/deep_extend.md b/docs/config/lua/wezterm.table/deep_extend.md new file mode 100644 index 00000000000..ffbeaaef413 --- /dev/null +++ b/docs/config/lua/wezterm.table/deep_extend.md @@ -0,0 +1,66 @@ +# `wezterm.table.deep_extend(array_of_tables [, behavior])` + +{{since('nightly')}} + +This function merges a list of Lua object-style tables based on their keys in +each nested table. + +The tables are passed to it in the form of an array. +I.e., to merge the Lua tables `tbl1` and `tbl2`, we can pass them to +the function as `{ tbl1, tbl2 }`. (See below.) + +By default this function merges tables with identical keys for non-table values +by taking the value from the last table in the array with each given key. + +The function accepts an optional string of the form `'Keep'`, `'Force'` or +`'Error` describing its behavior. Any other string passed to the function will +result in an error. The default behavior is equavalent to passing the string +`'Force'`as the behavior. + +When `deep_extend` is run with the `'Keep'` behavior, it will prefer values from the +first table in the array where we see the key. (In contrast to `'Force'` that +prefers later values.) + +When `extend` is run with the `'Error'` behavior, it will return an error if +any of the tables passed to it contain the same key for a non-table value, and it +will not try to merge the tables in this case. Otherwise, it will cleanly merge the +tables with no ambiguity, since there are no duplicate keys with non-table values. + +```lua +local wezterm = require 'wezterm' +local deep_extend = wezterm.table.deep_extend +local equal = wezterm.table.equal + +local tbl1 = { + a = 1, + b = { + d = 4, + }, + c = 3, +} + +local tbl2 = { + a = 2, + b = { + e = 5, + }, + d = 4, +} + +local tbl3 = { + b = { + a = 1, + b = 2, + } +} + +assert(equal(deep_extend({tbl1, tbl2}), { a = 2, b = { d = 4, e = 5 }, c = 3, d = 4 })) +assert(equal(deep_extend({tbl1, tbl2}, 'Keep'), { a = 1, b = { d = 4, e = 5 }, c = 3, d = 4 })) +-- This will return an error: deep_extend({tbl1, tbl2}, 'Error') + +assert(equal(deep_extend({tbl2, tbl3}), { a = 2, b = { a = 1, b = 2, e = 5 }, d = 4 })) +assert(equal(deep_extend({tbl2, tbl3}, 'Keep'), { a = 2, b = { a = 1, b = 2, e = 5 }, d = 4 })) +assert(equal(deep_extend({tbl2, tbl3}, 'Error'), { a = 2, b = { a = 1, b = 2, e = 5 }, d = 4 })) +``` + +See also [flatten](flatten.md) and [deep_extend](deep_extend.md). diff --git a/docs/config/lua/wezterm.table/equal.md b/docs/config/lua/wezterm.table/equal.md new file mode 100644 index 00000000000..685c9a52646 --- /dev/null +++ b/docs/config/lua/wezterm.table/equal.md @@ -0,0 +1,31 @@ +# `wezterm.table.equal(table1, table2)` + +{{since('nightly')}} + +This function checks if two tables are equal by checking for equality of their values. The +function returns `true` if `table1` and `table2` are equal and `false` otherwise. + +Note: Lua can also check equality of tables via `==`, but `==` checks if the table addresses +are equal, and thus it cannot be used to check equality of values like this function. E.g., +`{1} == {1}` will return false, whereas `wezterm.table.equal({1}, {1})` returns true. + +```lua +local wezterm = require 'wezterm' +local equal = wezterm.table.equal + +local tbl1 = { + a = 1, + b = '2', +} +local arr1 = { 1, 'a', 2, 'abc' } + +assert(not tbl1 == arr1) +assert(not equal(tbl1, arr1)) + +assert(equal(tbl1, { a = 1, b = '2' })) +assert(not (tbl1 == { a = 1, b = '2' })) + +assert(equal(arr1, { 1, 'a', 2, 'abc' })) +assert(not (arr1 == { 1, 'a', 2, 'abc' })) +``` + diff --git a/docs/config/lua/wezterm.table/extend.md b/docs/config/lua/wezterm.table/extend.md new file mode 100644 index 00000000000..9021ff83a06 --- /dev/null +++ b/docs/config/lua/wezterm.table/extend.md @@ -0,0 +1,63 @@ +# `wezterm.table.extend(array_of_tables [, behavior])` + +{{since('nightly')}} + +This function merges a list of Lua object-style tables based on their top-level +keys. To go through all nested tables use [deep_extend](deep_extend.md). + +The tables are passed to it in the form of an array. +I.e., to merge the Lua tables `tbl1` and `tbl2`, we can pass them to +the function as `{ tbl1, tbl2 }`. (See below.) + +By default this function merges tables with identical keys by taking +the value from the last table in the array with each given key. + +The function accepts an optional string of the form `'Keep'`, `'Force'` or +`'Error` describing its behavior. Any other string passed to the function will +result in an error. The default behavior is equavalent to passing the string +`'Force'`as the behavior. + +When `extend` is run with the `'Keep'` behavior, it will prefer values from the +first table in the array where we see the key. (In contrast to `'Force'` that +prefers later values.) + +When `extend` is run with the `'Error'` behavior, it will return an error if +any of the tables passed to it contain the same key, and it will not try to +merge the tables in this case. Otherwise, it will cleanly merge the tables +with no ambiguity, since there are no duplicate keys. + +```lua +local wezterm = require 'wezterm' +local extend = wezterm.table.extend +local equal = wezterm.table.equal + +local tbl1 = { + a = 1, + b = { + d = 4, + }, + c = 3, +} + +local tbl2 = { + a = 2, + b = { + e = 5, + }, + d = 4, +} + +local tbl3 = { + e = 5, +} + +assert(equal(extend({tbl1, tbl2}), { a = 2, b = { e = 5 }, c = 3, d = 4 })) +assert(equal(extend({tbl1, tbl2}, 'Keep'), { a = 1, b = { d = 4 }, c = 3, d = 4 })) +-- This will return an error: extend({tbl1, tbl2}, 'Error') + +assert(equal(extend({tbl2, tbl3}), { a = 2, b = { e = 5 }, d = 4, e = 5 })) +assert(equal(extend({tbl2, tbl3}, 'Keep'), { a = 2, b = { e = 5 }, d = 4, e = 5 })) +assert(equal(extend({tbl2, tbl3}, 'Error'), { a = 2, b = { e = 5 }, d = 4, e = 5 })) +``` + +See also [flatten](flatten.md) and [deep_extend](deep_extend.md). diff --git a/docs/config/lua/wezterm.table/flatten.md b/docs/config/lua/wezterm.table/flatten.md index 16b54275c7e..1bd719f5033 100644 --- a/docs/config/lua/wezterm.table/flatten.md +++ b/docs/config/lua/wezterm.table/flatten.md @@ -1,20 +1,39 @@ -# `wezterm.table.flatten(array_of_arrays)` +# `wezterm.table.flatten(array_of_arrays [, behavior])` {{since('nightly')}} -This function recursively flattens Lua arrays passed to it in the form of an array. +This function flattens Lua arrays passed to it in the form of an array. I.e., to flatten the Lua arrays `arr1` and `arr2` into one array, we can pass them to the function as `{ arr1, arr2 }`. (See below.) +The function accepts an optional string of the form `'Top'` or `'Deep'` +describing its behavior. Any other string passed to the function will result +in an error. The default behavior is equavalent to passing the string `'Top'` +as the behavior. + +When `flatten` is run with the `'Top'` behavior, it will only go through the +top-level of the arrays and flatten the values into a new array. In particular +this means that for any table at the top-level in one of the arrays, it will +not try to flatten the table and instead will just add the table to the top-level +of the flattened array. + +When `flatten` is run with the `'Deep'` behavior, it will recursively go through +all nested tables and treat them like array-like tables that it then flattens. + ```lua local wezterm = require 'wezterm' local flatten = wezterm.table.flatten +local equal = wezterm.table.equal local arr1 = { { 1, 2 }, 3 } - local arr2 = { 'a', { 'b', { 'c' } } } +local arr3 = { 1, { a = 1, 2 }, { b = 2 } } + +assert(equal(flatten({ arr1, arr2 }), { { 1, 2 }, 3, 'a', { 'b', { 'c' } } })) +assert(equal(flatten({ arr1, arr2 }, 'Deep'), { 1, 2, 3, 'a', 'b', 'c' })) -assert(flatten { arr1, arr2 } == { 1, 2, 3, 'a', 'b', 'c' }) +assert(equal(flatten({arr1, arr3}), { { 1, 2 }, 3, 1, { a = 1, 2 }, { b = 2 } })) +assert(equal(flatten({arr1, arr3}, 'Deep'), { 1, 2, 3, 1, 2 })) ``` See also [merge](merge.md). diff --git a/docs/config/lua/wezterm.table/get.md b/docs/config/lua/wezterm.table/get.md new file mode 100644 index 00000000000..347cb65a674 --- /dev/null +++ b/docs/config/lua/wezterm.table/get.md @@ -0,0 +1,39 @@ +# `wezterm.table.get(table, key [, ...])` + +{{since('nightly')}} + +This function accepts a Lua table `table` and a key `key`. +It returns the value at the table entry `key` if `table` contains a key equal +to `key` (with non-nil value) and it returns `nil` otherwise. + +The function accepts an optional arbitrary number of extra arguments, that will +all be intepreted as extra keys to check for recursively in the table. I.e., to +get the value of `table` at `table.a.b.c`, we can use +`wezterm.table.get(table, 'a', 'b', 'c')`. + +```lua +local wezterm = require 'wezterm' +local has_key = wezterm.table.has_key + +local tbl1 = { + a = 1, + b = { + c = { + d = 4 + } + } +} + +local arr1 = { 'a', 'b', 'c' } + +assert(get(tbl1, 'a') == 1) +assert(get(tbl1, 'b') == tbl1.b) -- note: we get the table address of tbl1.b here +assert(get(tbl1, 'b', 'c', 'd') == 4) +assert(get(tbl1, 'c') == nil) + +assert(get(arr1, 3) == 'c') +assert(get(arr1, 4) == nil) +assert(get(arr1, 1, 2) == nil) +``` + +See also [has_key](has_key.md) and [has_value](has_value.md). diff --git a/docs/config/lua/wezterm.table/has_key.md b/docs/config/lua/wezterm.table/has_key.md index 2e81e0c8eb1..188f8f63678 100644 --- a/docs/config/lua/wezterm.table/has_key.md +++ b/docs/config/lua/wezterm.table/has_key.md @@ -1,10 +1,15 @@ -# `wezterm.table.has_key(table, key)` +# `wezterm.table.has_key(table, key [, ...])` {{since('nightly')}} -This function accepts a Lua table (or array) `table` and a key `key`. -It returns `true` if `table` contains a key equal to `key` (with non-nill value) -and false otherwise. +This function accepts a Lua table `table` and a key `key`. +It returns `true` if `table` contains a key equal to `key` (with non-nil value) +and `false` otherwise. + +The function accepts an optional arbitrary number of extra arguments, that will +all be intepreted as extra keys to check for recursively in the table. I.e., to +check whether `table` has any non-nil value at `table.a.b.c`, we can use +`wezterm.table.has_key(table, 'a', 'b', 'c')`. ```lua local wezterm = require 'wezterm' @@ -12,13 +17,22 @@ local has_key = wezterm.table.has_key local tbl1 = { a = 1, - b = '2', + b = { + c = { + d = 4 + } + } } + local arr1 = { 'a', 'b', 'c' } assert(has_key(tbl1, 'a')) -assert(not has_key(tbl1, 'c')) +assert(has_key(tbl1, 'b')) +assert(has_key(tbl1, 'b', 'c', 'd')) assert(has_key(arr1, 3)) assert(not has_key(arr1, 4)) +assert(not has_key(arr1, 1, 2)) ``` + +See also [has_value](has_value.md) and [get](get.md). diff --git a/docs/config/lua/wezterm.table/has_value.md b/docs/config/lua/wezterm.table/has_value.md index fb3ebb86af7..bbb3bb8ba54 100644 --- a/docs/config/lua/wezterm.table/has_value.md +++ b/docs/config/lua/wezterm.table/has_value.md @@ -2,7 +2,7 @@ {{since('nightly')}} -This function accepts a Lua table (or array) `table` and a value `value`. +This function accepts a Lua table `table` and a value `value`. It returns `true` if `table` contains an entry with value equal to `value` and false otherwise. @@ -22,3 +22,5 @@ assert(not has_value(tbl1, 'a')) assert(has_value(arr1, 'a')) assert(not has_value(arr1, '1')) ``` + +See also [has_key](has_key.md) and [get](get.md). diff --git a/docs/config/lua/wezterm.table/merge.md b/docs/config/lua/wezterm.table/merge.md deleted file mode 100644 index 00a7e5fd112..00000000000 --- a/docs/config/lua/wezterm.table/merge.md +++ /dev/null @@ -1,38 +0,0 @@ -# `wezterm.table.merge(array_of_tables [, keep_first])` - -{{since('nightly')}} - -This function merges a list of Lua object-style tables based on their keys. - -The tables are passed to it in the form of an array. -I.e., to merge the Lua tables `tbl1` and `tbl2`, we can pass them to -the function as `{ tbl1, tbl2 }`. (See below.) - -By default this function merges tables with identical keys by taking -the value from the last table in the array with each given key. - -The optional `keep_first` allows us to instead prefer values from the -first table in the array where we see the key by passing `true` after the array. -The default behavior is identical to what we get by passing `false`. - -```lua -local wezterm = require 'wezterm' -local merge = wezterm.table.merge - -local tbl1 = { - a = 1, - b = '2', -} - -local tbl2 = { - a = '1', - c = 3, -} - -wezterm.log_error(merge { tbl1, tbl2 }) -assert(merge { tbl1, tbl2 } == merge({ tbl1, tbl2 }, false)) - -wezterm.log_error(merge({ tbl1, tbl2 }, true)) -``` - -See also [flatten](flatten.md). diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index 5b06f430025..447aaad68c4 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -32,10 +32,10 @@ impl_lua_conversion_dynamic!(ConflictMode); #[derive(Default, Debug, FromDynamic, ToDynamic, Clone, PartialEq, Eq, Copy)] enum DepthMode { - /// Take the latest value + /// Only look at the top level of tables #[default] Top, - /// Raise an error + /// Recursively go through tables Deep, } impl_lua_conversion_dynamic!(DepthMode); @@ -152,7 +152,9 @@ fn flatten<'lua>( let mut flat = flatten(lua, (tbl_as_vec, Some(behavior)))?; flat_vec.append(&mut flat); } else { - flat_vec.push(LuaValue::Table(tbl)); + for elem in tbl.sequence_values::() { + flat_vec.push(elem?); + } } } LuaValue::Nil => (), From a0cdfbea59867bd6e956afd1ff7ef8056d609b87 Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Sun, 3 Dec 2023 16:48:13 +0100 Subject: [PATCH 29/42] Add behavior to `has_value` --- docs/config/lua/wezterm.table/has_value.md | 28 +++++++++++++++++--- lua-api-crates/table-funcs/src/lib.rs | 30 ++++++++++++++++++---- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/docs/config/lua/wezterm.table/has_value.md b/docs/config/lua/wezterm.table/has_value.md index bbb3bb8ba54..e03ae8a6fa1 100644 --- a/docs/config/lua/wezterm.table/has_value.md +++ b/docs/config/lua/wezterm.table/has_value.md @@ -1,10 +1,24 @@ -# `wezterm.table.has_value(table, value)` +# `wezterm.table.has_value(table, value [, behavior])` {{since('nightly')}} This function accepts a Lua table `table` and a value `value`. It returns `true` if `table` contains an entry with value equal to `value` -and false otherwise. +and false otherwise. By default the function only searches for the value at +the top-level of the table. + +The function accepts an optional string of the form `'Top'` or `'Deep'` +describing its behavior. Any other string passed to the function will result +in an error. The default behavior is equavalent to passing the string `'Top'` +as the behavior. + +When `has_value` is run with the `'Top'` behavior, it will only go through the +top-level of the table to look for `value`. This is equavalent to going through +the table in Lua with `pairs` (not `ipairs`). + +When `has_value` is run with the `'Deep'` behavior, it will recursively go through +all nested tables and look for `value` in each of them. It will return `true` if it +find `value` in any nested table, and otherwise it will return `false`. ```lua local wezterm = require 'wezterm' @@ -12,12 +26,18 @@ local has_value = wezterm.table.has_value local tbl1 = { a = 1, - b = '2', + b = { + c = { + d = 4 + } + }, } local arr1 = { 'a', 'b', 'c' } assert(has_value(tbl1, 1)) -assert(not has_value(tbl1, 'a')) +assert(not has_value(tbl1, 4)) +assert(has_value(tbl1, 4, 'Deep')) +assert(not has_value(tbl1, 'a', 'Deep')) assert(has_value(arr1, 'a')) assert(not has_value(arr1, '1')) diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index 447aaad68c4..f48367de301 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -231,11 +231,31 @@ fn has_key<'lua>( Ok(value_has_key) } -fn has_value<'lua>(_: &'lua Lua, (table, value): (Table<'lua>, LuaValue)) -> mlua::Result { - for pair in table.pairs::() { - let (_, tbl_value) = pair?; - if tbl_value == value { - return Ok(true); +fn has_value<'lua>(lua: &'lua Lua, (table, value, behavior): (Table<'lua>, LuaValue, Option)) -> mlua::Result { + let behavior = behavior.unwrap_or_default(); + match behavior { + DepthMode::Top => { + // we don't need a clone in this case + for pair in table.pairs::() { + let (_, tbl_value) = pair?; + if tbl_value == value { + return Ok(true); + } + } + } + DepthMode::Deep => { + for pair in table.clone().pairs::() { + let (key, tbl_value) = pair?; + if tbl_value == value { + return Ok(true); + } + if tbl_value.is_table() { + let tbl = table.get::<_, Table>(key)?; + if let Ok(true) = has_value(lua, (tbl, value.clone(), Some(behavior))) { + return Ok(true); + } + } + } } } Ok(false) From 4917e3da84fc489331c6a7e5e257a0e31a6fa139 Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Sun, 3 Dec 2023 16:51:26 +0100 Subject: [PATCH 30/42] Cleanup --- docs/config/lua/wezterm.table/deep_extend.md | 37 ++++++++++++++++---- docs/config/lua/wezterm.table/extend.md | 25 ++++++++++--- docs/config/lua/wezterm.table/flatten.md | 10 +++--- docs/config/lua/wezterm.table/get.md | 6 ++-- docs/config/lua/wezterm.table/has_key.md | 6 ++-- docs/config/lua/wezterm.table/has_value.md | 4 +-- lua-api-crates/table-funcs/src/lib.rs | 5 ++- 7 files changed, 69 insertions(+), 24 deletions(-) diff --git a/docs/config/lua/wezterm.table/deep_extend.md b/docs/config/lua/wezterm.table/deep_extend.md index ffbeaaef413..d97c84e5ef3 100644 --- a/docs/config/lua/wezterm.table/deep_extend.md +++ b/docs/config/lua/wezterm.table/deep_extend.md @@ -51,16 +51,41 @@ local tbl3 = { b = { a = 1, b = 2, - } + }, } -assert(equal(deep_extend({tbl1, tbl2}), { a = 2, b = { d = 4, e = 5 }, c = 3, d = 4 })) -assert(equal(deep_extend({tbl1, tbl2}, 'Keep'), { a = 1, b = { d = 4, e = 5 }, c = 3, d = 4 })) +assert( + equal( + deep_extend { tbl1, tbl2 }, + { a = 2, b = { d = 4, e = 5 }, c = 3, d = 4 } + ) +) +assert( + equal( + deep_extend({ tbl1, tbl2 }, 'Keep'), + { a = 1, b = { d = 4, e = 5 }, c = 3, d = 4 } + ) +) -- This will return an error: deep_extend({tbl1, tbl2}, 'Error') -assert(equal(deep_extend({tbl2, tbl3}), { a = 2, b = { a = 1, b = 2, e = 5 }, d = 4 })) -assert(equal(deep_extend({tbl2, tbl3}, 'Keep'), { a = 2, b = { a = 1, b = 2, e = 5 }, d = 4 })) -assert(equal(deep_extend({tbl2, tbl3}, 'Error'), { a = 2, b = { a = 1, b = 2, e = 5 }, d = 4 })) +assert( + equal( + deep_extend { tbl2, tbl3 }, + { a = 2, b = { a = 1, b = 2, e = 5 }, d = 4 } + ) +) +assert( + equal( + deep_extend({ tbl2, tbl3 }, 'Keep'), + { a = 2, b = { a = 1, b = 2, e = 5 }, d = 4 } + ) +) +assert( + equal( + deep_extend({ tbl2, tbl3 }, 'Error'), + { a = 2, b = { a = 1, b = 2, e = 5 }, d = 4 } + ) +) ``` See also [flatten](flatten.md) and [deep_extend](deep_extend.md). diff --git a/docs/config/lua/wezterm.table/extend.md b/docs/config/lua/wezterm.table/extend.md index 9021ff83a06..aecac8fb6a9 100644 --- a/docs/config/lua/wezterm.table/extend.md +++ b/docs/config/lua/wezterm.table/extend.md @@ -51,13 +51,28 @@ local tbl3 = { e = 5, } -assert(equal(extend({tbl1, tbl2}), { a = 2, b = { e = 5 }, c = 3, d = 4 })) -assert(equal(extend({tbl1, tbl2}, 'Keep'), { a = 1, b = { d = 4 }, c = 3, d = 4 })) +assert(equal(extend { tbl1, tbl2 }, { a = 2, b = { e = 5 }, c = 3, d = 4 })) +assert( + equal( + extend({ tbl1, tbl2 }, 'Keep'), + { a = 1, b = { d = 4 }, c = 3, d = 4 } + ) +) -- This will return an error: extend({tbl1, tbl2}, 'Error') -assert(equal(extend({tbl2, tbl3}), { a = 2, b = { e = 5 }, d = 4, e = 5 })) -assert(equal(extend({tbl2, tbl3}, 'Keep'), { a = 2, b = { e = 5 }, d = 4, e = 5 })) -assert(equal(extend({tbl2, tbl3}, 'Error'), { a = 2, b = { e = 5 }, d = 4, e = 5 })) +assert(equal(extend { tbl2, tbl3 }, { a = 2, b = { e = 5 }, d = 4, e = 5 })) +assert( + equal( + extend({ tbl2, tbl3 }, 'Keep'), + { a = 2, b = { e = 5 }, d = 4, e = 5 } + ) +) +assert( + equal( + extend({ tbl2, tbl3 }, 'Error'), + { a = 2, b = { e = 5 }, d = 4, e = 5 } + ) +) ``` See also [flatten](flatten.md) and [deep_extend](deep_extend.md). diff --git a/docs/config/lua/wezterm.table/flatten.md b/docs/config/lua/wezterm.table/flatten.md index 1bd719f5033..a71ef772f24 100644 --- a/docs/config/lua/wezterm.table/flatten.md +++ b/docs/config/lua/wezterm.table/flatten.md @@ -29,11 +29,13 @@ local arr1 = { { 1, 2 }, 3 } local arr2 = { 'a', { 'b', { 'c' } } } local arr3 = { 1, { a = 1, 2 }, { b = 2 } } -assert(equal(flatten({ arr1, arr2 }), { { 1, 2 }, 3, 'a', { 'b', { 'c' } } })) +assert(equal(flatten { arr1, arr2 }, { { 1, 2 }, 3, 'a', { 'b', { 'c' } } })) assert(equal(flatten({ arr1, arr2 }, 'Deep'), { 1, 2, 3, 'a', 'b', 'c' })) -assert(equal(flatten({arr1, arr3}), { { 1, 2 }, 3, 1, { a = 1, 2 }, { b = 2 } })) -assert(equal(flatten({arr1, arr3}, 'Deep'), { 1, 2, 3, 1, 2 })) +assert( + equal(flatten { arr1, arr3 }, { { 1, 2 }, 3, 1, { a = 1, 2 }, { b = 2 } }) +) +assert(equal(flatten({ arr1, arr3 }, 'Deep'), { 1, 2, 3, 1, 2 })) ``` -See also [merge](merge.md). +See also [extend](extend.md). diff --git a/docs/config/lua/wezterm.table/get.md b/docs/config/lua/wezterm.table/get.md index 347cb65a674..9c2c767b248 100644 --- a/docs/config/lua/wezterm.table/get.md +++ b/docs/config/lua/wezterm.table/get.md @@ -19,9 +19,9 @@ local tbl1 = { a = 1, b = { c = { - d = 4 - } - } + d = 4, + }, + }, } local arr1 = { 'a', 'b', 'c' } diff --git a/docs/config/lua/wezterm.table/has_key.md b/docs/config/lua/wezterm.table/has_key.md index 188f8f63678..78f44e347bb 100644 --- a/docs/config/lua/wezterm.table/has_key.md +++ b/docs/config/lua/wezterm.table/has_key.md @@ -19,9 +19,9 @@ local tbl1 = { a = 1, b = { c = { - d = 4 - } - } + d = 4, + }, + }, } local arr1 = { 'a', 'b', 'c' } diff --git a/docs/config/lua/wezterm.table/has_value.md b/docs/config/lua/wezterm.table/has_value.md index e03ae8a6fa1..c862b956d2c 100644 --- a/docs/config/lua/wezterm.table/has_value.md +++ b/docs/config/lua/wezterm.table/has_value.md @@ -28,8 +28,8 @@ local tbl1 = { a = 1, b = { c = { - d = 4 - } + d = 4, + }, }, } local arr1 = { 'a', 'b', 'c' } diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index f48367de301..515586fcfee 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -231,7 +231,10 @@ fn has_key<'lua>( Ok(value_has_key) } -fn has_value<'lua>(lua: &'lua Lua, (table, value, behavior): (Table<'lua>, LuaValue, Option)) -> mlua::Result { +fn has_value<'lua>( + lua: &'lua Lua, + (table, value, behavior): (Table<'lua>, LuaValue, Option), +) -> mlua::Result { let behavior = behavior.unwrap_or_default(); match behavior { DepthMode::Top => { From 764bbe0b4d0688fb2bdc2f2ad92f6a24aaf39772 Mon Sep 17 00:00:00 2001 From: Daniel Kongsgaard Date: Mon, 4 Dec 2023 00:47:02 +0100 Subject: [PATCH 31/42] Apply some suggestions from code review Co-authored-by: Wez Furlong --- docs/config/lua/wezterm.table/clone.md | 2 +- docs/config/lua/wezterm.table/deep_extend.md | 35 +++++++++----------- lua-api-crates/table-funcs/src/lib.rs | 2 +- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/docs/config/lua/wezterm.table/clone.md b/docs/config/lua/wezterm.table/clone.md index 6431260fab6..c9eb12782db 100644 --- a/docs/config/lua/wezterm.table/clone.md +++ b/docs/config/lua/wezterm.table/clone.md @@ -31,7 +31,7 @@ local tbl = { d = 1, }, } -local tbl_copy = tbl -- copy the table address +local tbl_ref = tbl -- reference the table; no copy is made local tbl_top_clone = clone(tbl) -- same as clone(tbl1, 'Top') local tbl_deep_clone = clone(tbl, 'Deep') diff --git a/docs/config/lua/wezterm.table/deep_extend.md b/docs/config/lua/wezterm.table/deep_extend.md index d97c84e5ef3..be6ade49e94 100644 --- a/docs/config/lua/wezterm.table/deep_extend.md +++ b/docs/config/lua/wezterm.table/deep_extend.md @@ -2,29 +2,24 @@ {{since('nightly')}} -This function merges a list of Lua object-style tables based on their keys in -each nested table. +This function merges a list of Lua object-style tables, producing a single object-style table comprised of the keys of each of the tables in the input list, making a deep, recursive copy of the corresponding value. For a shallow copy, see [wezterm.table.extend](extend.md). -The tables are passed to it in the form of an array. -I.e., to merge the Lua tables `tbl1` and `tbl2`, we can pass them to -the function as `{ tbl1, tbl2 }`. (See below.) +For each table in the `array_of_tables` parameter, the keys are iterated and set in +the return value. -By default this function merges tables with identical keys for non-table values -by taking the value from the last table in the array with each given key. -The function accepts an optional string of the form `'Keep'`, `'Force'` or -`'Error` describing its behavior. Any other string passed to the function will -result in an error. The default behavior is equavalent to passing the string -`'Force'`as the behavior. +The optional `behavior` parameter controls how repeated keys are handled; the +values are accepted: -When `deep_extend` is run with the `'Keep'` behavior, it will prefer values from the -first table in the array where we see the key. (In contrast to `'Force'` that -prefers later values.) - -When `extend` is run with the `'Error'` behavior, it will return an error if -any of the tables passed to it contain the same key for a non-table value, and it -will not try to merge the tables in this case. Otherwise, it will cleanly merge the -tables with no ambiguity, since there are no duplicate keys with non-table values. +* `"Force"` (this is the default) - always take the latest value for a key, even if + the same key has already been populated into the return value, forcing the + existing value to be updated with a later value. + +* `"Keep"` - keep the first value of the key. Subsequent values for that same key + are ignored. + +* `"Error"` - when a key is seen more than once, raise an error. This mode will + only return if no keys are in conflict across the set of input tables. ```lua local wezterm = require 'wezterm' @@ -88,4 +83,4 @@ assert( ) ``` -See also [flatten](flatten.md) and [deep_extend](deep_extend.md). +See also [wezterm.table.flatten](flatten.md) and [wezterm.table.deep_extend](deep_extend.md). diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index 515586fcfee..25d5b0635a4 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -34,7 +34,7 @@ impl_lua_conversion_dynamic!(ConflictMode); enum DepthMode { /// Only look at the top level of tables #[default] - Top, + Shallow, /// Recursively go through tables Deep, } From f9e7e5258213437a4b04caa8192192dbbcffee89 Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Mon, 4 Dec 2023 00:59:41 +0100 Subject: [PATCH 32/42] Rename Top to Shallow and simplify `has_value` --- lua-api-crates/table-funcs/src/lib.rs | 30 ++++++++------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index 25d5b0635a4..343bade1f00 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -125,7 +125,7 @@ fn clone<'lua>( for pair in table.pairs::() { let (key, value) = pair?; match behavior { - DepthMode::Top => res.set(key, value)?, + DepthMode::Shallow => res.set(key, value)?, DepthMode::Deep => { if let LuaValue::Table(tbl) = value { res.set(key, clone(lua, (tbl, Some(behavior)))?)? @@ -235,32 +235,20 @@ fn has_value<'lua>( lua: &'lua Lua, (table, value, behavior): (Table<'lua>, LuaValue, Option), ) -> mlua::Result { - let behavior = behavior.unwrap_or_default(); - match behavior { - DepthMode::Top => { - // we don't need a clone in this case - for pair in table.pairs::() { - let (_, tbl_value) = pair?; - if tbl_value == value { - return Ok(true); - } - } + for pair in table.pairs::() { + let (_, tbl_value) = pair?; + if tbl_value.eq(&value) { + return Ok(true); } - DepthMode::Deep => { - for pair in table.clone().pairs::() { - let (key, tbl_value) = pair?; - if tbl_value == value { + if behavior == Some(DepthMode::Deep) { + if let LuaValue::Table(tbl) = tbl_value { + if has_value(lua, (tbl, value.clone(), behavior))? { return Ok(true); } - if tbl_value.is_table() { - let tbl = table.get::<_, Table>(key)?; - if let Ok(true) = has_value(lua, (tbl, value.clone(), Some(behavior))) { - return Ok(true); - } - } } } } + Ok(false) } From 63fe57b445a7c0394a959c9a221e04d322e99d23 Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Mon, 4 Dec 2023 01:38:33 +0100 Subject: [PATCH 33/42] Cleanup --- docs/config/lua/wezterm.table/clone.md | 2 ++ docs/config/lua/wezterm.table/extend.md | 10 ++++++---- docs/config/lua/wezterm.table/get.md | 24 +++++++++++++++--------- lua-api-crates/table-funcs/src/lib.rs | 4 ++-- 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/docs/config/lua/wezterm.table/clone.md b/docs/config/lua/wezterm.table/clone.md index c9eb12782db..49a5a8c6daf 100644 --- a/docs/config/lua/wezterm.table/clone.md +++ b/docs/config/lua/wezterm.table/clone.md @@ -42,6 +42,8 @@ assert(equal(tbl, tbl_top_clone)) assert(equal(tbl, tbl_deep_clone)) tbl.a = 2 +assert(equal(tbl, tbl_ref)) +assert(tbl_ref.a == 2) assert(not equal(tbl, tbl_top_clone)) assert(tbl_top_clone.a == 1) assert(not equal(tbl, tbl_deep_clone)) diff --git a/docs/config/lua/wezterm.table/extend.md b/docs/config/lua/wezterm.table/extend.md index aecac8fb6a9..a230a7bd97b 100644 --- a/docs/config/lua/wezterm.table/extend.md +++ b/docs/config/lua/wezterm.table/extend.md @@ -36,7 +36,7 @@ local tbl1 = { b = { d = 4, }, - c = 3, + e = 3, } local tbl2 = { @@ -51,14 +51,16 @@ local tbl3 = { e = 5, } -assert(equal(extend { tbl1, tbl2 }, { a = 2, b = { e = 5 }, c = 3, d = 4 })) +assert(equal(extend { tbl1, tbl2 }, { a = 2, b = { e = 5 }, e = 3, d = 4 })) assert( equal( extend({ tbl1, tbl2 }, 'Keep'), - { a = 1, b = { d = 4 }, c = 3, d = 4 } + { a = 1, b = { d = 4 }, e = 3, d = 4 } ) ) --- This will return an error: extend({tbl1, tbl2}, 'Error') +local ok, msg = pcall(function() extend({tbl1, tbl3}, 'Error') end) +assert(not ok and + msg == "error runtime error: The key 'e' is in more than one of the tables.") assert(equal(extend { tbl2, tbl3 }, { a = 2, b = { e = 5 }, d = 4, e = 5 })) assert( diff --git a/docs/config/lua/wezterm.table/get.md b/docs/config/lua/wezterm.table/get.md index 9c2c767b248..3fc60d4a830 100644 --- a/docs/config/lua/wezterm.table/get.md +++ b/docs/config/lua/wezterm.table/get.md @@ -2,18 +2,24 @@ {{since('nightly')}} -This function accepts a Lua table `table` and a key `key`. -It returns the value at the table entry `key` if `table` contains a key equal -to `key` (with non-nil value) and it returns `nil` otherwise. +This function can be used to resolve the value for a key in a table. In its most basic form +it is equivalent to the built-in table indexing operator: +```lua +assert(wezterm.table.get(tbl, key) == tbl[key]) +``` +You may pass a sequence of keys that will be used to successively resolve +nested tables: +```lua +wezterm.table.get(tbl, 'a', 'b', 'c') == tbl['a']['b']['c'] +``` + +*Note:* In the above `tbl['a']['b']['c']` might cause an error, since we might be indexing a nil value, +but `wezterm.table.get(tbl, 'a', 'b', 'c')` won't error in this case; instead it will return nil. -The function accepts an optional arbitrary number of extra arguments, that will -all be intepreted as extra keys to check for recursively in the table. I.e., to -get the value of `table` at `table.a.b.c`, we can use -`wezterm.table.get(table, 'a', 'b', 'c')`. ```lua local wezterm = require 'wezterm' -local has_key = wezterm.table.has_key +local get = wezterm.table.get local tbl1 = { a = 1, @@ -27,7 +33,7 @@ local tbl1 = { local arr1 = { 'a', 'b', 'c' } assert(get(tbl1, 'a') == 1) -assert(get(tbl1, 'b') == tbl1.b) -- note: we get the table address of tbl1.b here +assert(get(tbl1, 'b') == tbl1.b) -- note: we get the table reference of tbl1.b here assert(get(tbl1, 'b', 'c', 'd') == 4) assert(get(tbl1, 'c') == nil) diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index 343bade1f00..67478e50de3 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -67,7 +67,7 @@ fn extend<'lua>( tbl.set(key, value)?; } else if behavior == ConflictMode::Error { return Err(mlua::Error::runtime(format!( - "The key {} is in more than one of the tables.", + "The key '{}' is in more than one of the tables.", key.to_string()? ))); } @@ -106,7 +106,7 @@ fn deep_extend<'lua>( tbl.set(key, value)?; } else if behavior == ConflictMode::Error { return Err(mlua::Error::runtime(format!( - "The key {} is in more than one of the tables.", + "The key '{}' is in more than one of the tables.", key.to_string()? ))); } From 2d58f3cbaeaeb05c1c22dfee600af223fe148d29 Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Mon, 4 Dec 2023 02:02:16 +0100 Subject: [PATCH 34/42] Docs update --- docs/config/lua/wezterm.table/deep_extend.md | 16 +++++---- docs/config/lua/wezterm.table/extend.md | 38 ++++++++++---------- docs/config/lua/wezterm.table/flatten.md | 33 +++++++++-------- 3 files changed, 46 insertions(+), 41 deletions(-) diff --git a/docs/config/lua/wezterm.table/deep_extend.md b/docs/config/lua/wezterm.table/deep_extend.md index be6ade49e94..c6927963bdb 100644 --- a/docs/config/lua/wezterm.table/deep_extend.md +++ b/docs/config/lua/wezterm.table/deep_extend.md @@ -2,22 +2,23 @@ {{since('nightly')}} -This function merges a list of Lua object-style tables, producing a single object-style table comprised of the keys of each of the tables in the input list, making a deep, recursive copy of the corresponding value. For a shallow copy, see [wezterm.table.extend](extend.md). +This function merges a list of Lua object-style tables, producing a single object-style +table comprised of the keys of each of the tables in the input list, making a deep, recursive +copy of the corresponding value. For a shallow copy, see [wezterm.table.extend](extend.md). For each table in the `array_of_tables` parameter, the keys are iterated and set in the return value. - The optional `behavior` parameter controls how repeated keys are handled; the -values are accepted: +accepted values are: * `"Force"` (this is the default) - always take the latest value for a key, even if the same key has already been populated into the return value, forcing the existing value to be updated with a later value. - + * `"Keep"` - keep the first value of the key. Subsequent values for that same key are ignored. - + * `"Error"` - when a key is seen more than once, raise an error. This mode will only return if no keys are in conflict across the set of input tables. @@ -61,7 +62,10 @@ assert( { a = 1, b = { d = 4, e = 5 }, c = 3, d = 4 } ) ) --- This will return an error: deep_extend({tbl1, tbl2}, 'Error') + +local ok, msg = pcall(function() extend({tbl1, tbl2}, 'Error') end) +local msg_string = wezterm.to_string(msg) +wezterm.log_info(not ok and msg_string:find "The key 'a' is in more than one of the tables." ~= nil) assert( equal( diff --git a/docs/config/lua/wezterm.table/extend.md b/docs/config/lua/wezterm.table/extend.md index a230a7bd97b..493f49647c9 100644 --- a/docs/config/lua/wezterm.table/extend.md +++ b/docs/config/lua/wezterm.table/extend.md @@ -2,29 +2,26 @@ {{since('nightly')}} -This function merges a list of Lua object-style tables based on their top-level -keys. To go through all nested tables use [deep_extend](deep_extend.md). +This function merges a list of Lua object-style tables, producing a single object-style +table comprised of the keys of each of the tables in the input list, making a shallow +(non-recursive) copy of the corresponding value. For a deep copy, see +[wezterm.table.deep_extend](deep_extend.md). -The tables are passed to it in the form of an array. -I.e., to merge the Lua tables `tbl1` and `tbl2`, we can pass them to -the function as `{ tbl1, tbl2 }`. (See below.) +For each table in the `array_of_tables` parameter, the top-level keys are iterated and set in +the return value. -By default this function merges tables with identical keys by taking -the value from the last table in the array with each given key. +The optional `behavior` parameter controls how repeated keys are handled; the +accepted values are: -The function accepts an optional string of the form `'Keep'`, `'Force'` or -`'Error` describing its behavior. Any other string passed to the function will -result in an error. The default behavior is equavalent to passing the string -`'Force'`as the behavior. +* `"Force"` (this is the default) - always take the latest value for a key, even if + the same key has already been populated into the return value, forcing the + existing value to be updated with a later value. -When `extend` is run with the `'Keep'` behavior, it will prefer values from the -first table in the array where we see the key. (In contrast to `'Force'` that -prefers later values.) +* `"Keep"` - keep the first value of the key. Subsequent values for that same key + are ignored. -When `extend` is run with the `'Error'` behavior, it will return an error if -any of the tables passed to it contain the same key, and it will not try to -merge the tables in this case. Otherwise, it will cleanly merge the tables -with no ambiguity, since there are no duplicate keys. +* `"Error"` - when a key is seen more than once, raise an error. This mode will + only return if no keys are in conflict across the set of input tables. ```lua local wezterm = require 'wezterm' @@ -58,9 +55,10 @@ assert( { a = 1, b = { d = 4 }, e = 3, d = 4 } ) ) + local ok, msg = pcall(function() extend({tbl1, tbl3}, 'Error') end) -assert(not ok and - msg == "error runtime error: The key 'e' is in more than one of the tables.") +local msg_string = wezterm.to_string(msg) +wezterm.log_info(not ok and msg_string:find "The key 'e' is in more than one of the tables." ~= nil) assert(equal(extend { tbl2, tbl3 }, { a = 2, b = { e = 5 }, d = 4, e = 5 })) assert( diff --git a/docs/config/lua/wezterm.table/flatten.md b/docs/config/lua/wezterm.table/flatten.md index a71ef772f24..49e13df89a3 100644 --- a/docs/config/lua/wezterm.table/flatten.md +++ b/docs/config/lua/wezterm.table/flatten.md @@ -2,23 +2,26 @@ {{since('nightly')}} -This function flattens Lua arrays passed to it in the form of an array. -I.e., to flatten the Lua arrays `arr1` and `arr2` into one array, -we can pass them to the function as `{ arr1, arr2 }`. (See below.) +This function flattens a list of Lua arrya-style tables, producing a single array-style +table comprised of the values of each of the tables in the input list. -The function accepts an optional string of the form `'Top'` or `'Deep'` -describing its behavior. Any other string passed to the function will result -in an error. The default behavior is equavalent to passing the string `'Top'` -as the behavior. +For each table in the `array_of_arrays` parameter, the values are iterated over and put +into the return array. -When `flatten` is run with the `'Top'` behavior, it will only go through the -top-level of the arrays and flatten the values into a new array. In particular -this means that for any table at the top-level in one of the arrays, it will -not try to flatten the table and instead will just add the table to the top-level -of the flattened array. +The optional `behavior` parameter controls how deeply we flatten the array-like lists; +the accepted values are: -When `flatten` is run with the `'Deep'` behavior, it will recursively go through -all nested tables and treat them like array-like tables that it then flattens. +* `"Shallow"` (this is the default) - always take the latest value for a key, even if + the same key has already been populated into the return value, forcing the + existing value to be updated with a later value. + +* `"Deep"` - keep the first value of the key. Subsequent values for that same key + are ignored. + +*Note:* `flatten` will ignore non-array like keys when going through the tables. I.e., +it goes through contiguous integer keys starting with index `1`. This is similar to the +behavior of `ipairs`, and not `pairs`. For behavior like `pairs`, see +[wezterm.table.extend](extend.md). ```lua local wezterm = require 'wezterm' @@ -38,4 +41,4 @@ assert( assert(equal(flatten({ arr1, arr3 }, 'Deep'), { 1, 2, 3, 1, 2 })) ``` -See also [extend](extend.md). +See also [wezterm.table.extend](extend.md). From 46fc06bba04caac9b56569f86ade3f28a0c3b66e Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Mon, 4 Dec 2023 02:43:50 +0100 Subject: [PATCH 35/42] Update docs and simplify extend functions --- docs/config/lua/wezterm.table/deep_extend.md | 10 ++- docs/config/lua/wezterm.table/extend.md | 10 ++- docs/config/lua/wezterm.table/get.md | 2 +- lua-api-crates/table-funcs/src/lib.rs | 80 +++++++++----------- 4 files changed, 54 insertions(+), 48 deletions(-) diff --git a/docs/config/lua/wezterm.table/deep_extend.md b/docs/config/lua/wezterm.table/deep_extend.md index c6927963bdb..73f0da3b87f 100644 --- a/docs/config/lua/wezterm.table/deep_extend.md +++ b/docs/config/lua/wezterm.table/deep_extend.md @@ -63,9 +63,15 @@ assert( ) ) -local ok, msg = pcall(function() extend({tbl1, tbl2}, 'Error') end) +local ok, msg = pcall(function() + extend({ tbl1, tbl2 }, 'Error') +end) local msg_string = wezterm.to_string(msg) -wezterm.log_info(not ok and msg_string:find "The key 'a' is in more than one of the tables." ~= nil) +wezterm.log_info( + not ok + and msg_string:find "The key 'a' is in more than one of the tables." + ~= nil +) assert( equal( diff --git a/docs/config/lua/wezterm.table/extend.md b/docs/config/lua/wezterm.table/extend.md index 493f49647c9..410b83dc89c 100644 --- a/docs/config/lua/wezterm.table/extend.md +++ b/docs/config/lua/wezterm.table/extend.md @@ -56,9 +56,15 @@ assert( ) ) -local ok, msg = pcall(function() extend({tbl1, tbl3}, 'Error') end) +local ok, msg = pcall(function() + extend({ tbl1, tbl3 }, 'Error') +end) local msg_string = wezterm.to_string(msg) -wezterm.log_info(not ok and msg_string:find "The key 'e' is in more than one of the tables." ~= nil) +wezterm.log_info( + not ok + and msg_string:find "The key 'e' is in more than one of the tables." + ~= nil +) assert(equal(extend { tbl2, tbl3 }, { a = 2, b = { e = 5 }, d = 4, e = 5 })) assert( diff --git a/docs/config/lua/wezterm.table/get.md b/docs/config/lua/wezterm.table/get.md index 3fc60d4a830..eb123145cfb 100644 --- a/docs/config/lua/wezterm.table/get.md +++ b/docs/config/lua/wezterm.table/get.md @@ -9,7 +9,7 @@ assert(wezterm.table.get(tbl, key) == tbl[key]) ``` You may pass a sequence of keys that will be used to successively resolve nested tables: -```lua +``` wezterm.table.get(tbl, 'a', 'b', 'c') == tbl['a']['b']['c'] ``` diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index 67478e50de3..e0ecc6f7bbc 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -42,34 +42,26 @@ impl_lua_conversion_dynamic!(DepthMode); // merge tables // (in case of overlap of the tables, we default to taking the key-value pair from the last table) -// Note that we don't use a HashMap since we want to keep the order of the tables, which -// can be useful in some cases fn extend<'lua>( lua: &'lua Lua, (array_of_tables, behavior): (Vec>, Option), ) -> mlua::Result> { - let mut tbl_vec: Vec<(LuaValue, LuaValue)> = vec![]; + let behavior = behavior.unwrap_or_default(); + let tbl: Table<'lua> = lua.create_table()?; for table in array_of_tables { for pair in table.pairs::() { let (key, value) = pair?; - tbl_vec.push((key, value)); - } - } - let tbl_len = tbl_vec.len(); - // note we might allocate a bit too much here, but in many use cases we will be correct - let tbl: Table<'lua> = lua.create_table_with_capacity(0, tbl_len)?; - let behavior = behavior.unwrap_or_default(); - for (key, value) in tbl_vec { - if !tbl.contains_key(key.clone())? { - tbl.set(key, value)?; - } else if behavior == ConflictMode::Force { - tbl.set(key, value)?; - } else if behavior == ConflictMode::Error { - return Err(mlua::Error::runtime(format!( - "The key '{}' is in more than one of the tables.", - key.to_string()? - ))); + if !tbl.contains_key(key.clone())? { + tbl.set(key, value)?; + } else if behavior == ConflictMode::Force { + tbl.set(key, value)?; + } else if behavior == ConflictMode::Error { + return Err(mlua::Error::runtime(format!( + "The key '{}' is in more than one of the tables.", + key.to_string()? + ))); + } } } @@ -78,37 +70,39 @@ fn extend<'lua>( // merge tables entrywise recursively // (in case of overlap of the tables, we default to taking the key-value pair from the last table) -// Note that we don't use a HashMap since we want to keep the order of the tables, which -// can be useful in some cases fn deep_extend<'lua>( lua: &'lua Lua, (array_of_tables, behavior): (Vec>, Option), ) -> mlua::Result> { - let mut tbl_vec: Vec<(LuaValue, LuaValue)> = vec![]; + let behavior = behavior.unwrap_or_default(); + let tbl: Table<'lua> = lua.create_table()?; for table in array_of_tables { for pair in table.pairs::() { let (key, value) = pair?; - tbl_vec.push((key, value)); - } - } - let tbl_len = tbl_vec.len(); - // note we might allocate a bit too much here, but in many use cases we will be correct - let tbl: Table<'lua> = lua.create_table_with_capacity(0, tbl_len)?; + let contains_key = tbl.contains_key(key.clone())?; - let behavior = behavior.unwrap_or_default(); - for (key, value) in tbl_vec { - if !tbl.contains_key(key.clone())? { - tbl.set(key, value)?; - } else if let LuaValue::Table(t) = value { - let inner_tbl = deep_extend(lua, (vec![tbl.get(key.clone())?, t], Some(behavior)))?; - tbl.set(key, inner_tbl)?; - } else if behavior == ConflictMode::Force { - tbl.set(key, value)?; - } else if behavior == ConflictMode::Error { - return Err(mlua::Error::runtime(format!( - "The key '{}' is in more than one of the tables.", - key.to_string()? - ))); + if behavior == ConflictMode::Error && contains_key { + return Err(mlua::Error::runtime(format!( + "The key '{}' is in more than one of the tables.", + key.to_string()? + ))); + } + + if !contains_key && !value.is_table() { + tbl.set(key, value)?; + } else if let LuaValue::Table(t) = value { + let cur_val_as_tbl = tbl.get::<_, Table>(key.clone()); + let inner_tbl = if cur_val_as_tbl.is_ok() { + deep_extend(lua, (vec![cur_val_as_tbl?, t], Some(behavior)))? + } else { + // if the currently set value is not a table, we will replace it + // with the table we just received (prefering nesting) + t + }; + tbl.set(key, inner_tbl)?; + } else if behavior == ConflictMode::Force { + tbl.set(key, value)?; + } } } From 4a4e3ac7561ed0e374872ada02aa7230be6c06c9 Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Mon, 4 Dec 2023 03:00:59 +0100 Subject: [PATCH 36/42] Minor bug fix Note: This can probably be written a bit nicer later. --- lua-api-crates/table-funcs/src/lib.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index e0ecc6f7bbc..2f18f060829 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -92,14 +92,19 @@ fn deep_extend<'lua>( tbl.set(key, value)?; } else if let LuaValue::Table(t) = value { let cur_val_as_tbl = tbl.get::<_, Table>(key.clone()); + let mut update_bool = true; let inner_tbl = if cur_val_as_tbl.is_ok() { deep_extend(lua, (vec![cur_val_as_tbl?, t], Some(behavior)))? } else { - // if the currently set value is not a table, we will replace it - // with the table we just received (prefering nesting) + // Note: we can get here in modes Force and Keep + if behavior == ConflictMode::Keep { + update_bool = false; + } t }; - tbl.set(key, inner_tbl)?; + if update_bool { + tbl.set(key, inner_tbl)?; + } } else if behavior == ConflictMode::Force { tbl.set(key, value)?; } From 67673255b53f8c626a56fe1f066422ffa28055ea Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Mon, 4 Dec 2023 17:18:16 +0100 Subject: [PATCH 37/42] Some cleanup --- lua-api-crates/table-funcs/src/lib.rs | 87 +++++++++++++-------------- 1 file changed, 41 insertions(+), 46 deletions(-) diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index 2f18f060829..0352344c82d 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -1,6 +1,7 @@ use config::lua::get_or_create_sub_module; use config::lua::mlua::{self, Lua, MultiValue as LuaMultiValue, Table, Value as LuaValue}; use luahelper::impl_lua_conversion_dynamic; +use luahelper::mlua::AnyUserDataExt; use wezterm_dynamic::{FromDynamic, ToDynamic}; pub fn register(lua: &Lua) -> anyhow::Result<()> { @@ -172,30 +173,29 @@ fn count<'lua>(_: &'lua Lua, table: Table<'lua>) -> mlua::Result { fn get<'lua>( _: &'lua Lua, - (table, key, mut extra_keys): (Table<'lua>, LuaValue<'lua>, LuaMultiValue<'lua>), + (table, keys): (Table<'lua>, LuaMultiValue<'lua>), ) -> mlua::Result> { - if extra_keys.is_empty() { - return Ok(table.get::<_, LuaValue>(key)?); + if keys.is_empty() { + return Err(mlua::Error::runtime( + "wezterm.table.get(, ) expects at least one key, but it was called with no keys." + )); } - let mut value: LuaValue = table.get(key.clone())?; - - let mut value_tbl = match table.get::<_, Table>(key) { - Ok(t) => t, - Err(_) => return Ok(LuaValue::Nil), // if extra_keys were empty, we wouldn't get here - }; - - while let Some(next_key) = extra_keys.pop_front() { - value = value_tbl.get(next_key.clone())?; - let new_val_tbl = value_tbl.get::<_, Table>(next_key); - value_tbl = match new_val_tbl { - Ok(t) => t, - Err(_) => { - if extra_keys.is_empty() { - return Ok(value); - } else { - return Ok(LuaValue::Nil); - } + let mut value = LuaValue::Table(table); + for key in keys { + match value { + LuaValue::Table(tbl) => { + value = tbl.get(key)?; + } + LuaValue::UserData(ud) => { + value = match ud.get(key) { + Ok(v) => v, + Err(_) => return Ok(LuaValue::Nil), + }; + } + _ => { + // cannot index non-table structures + return Ok(LuaValue::Nil); } } } @@ -204,30 +204,16 @@ fn get<'lua>( } fn has_key<'lua>( - _: &'lua Lua, - (table, key, mut extra_keys): (Table<'lua>, LuaValue, LuaMultiValue), + lua: &'lua Lua, + (table, keys): (Table<'lua>, LuaMultiValue), ) -> mlua::Result { - if extra_keys.is_empty() { - return Ok(table.contains_key(key)?); + if keys.is_empty() { + return Err(mlua::Error::runtime( + "wezterm.table.has_key(
, ) expects at least one key, but it was called with no keys." + )); } - let mut value_has_key = table.contains_key(key.clone())?; - - let mut value = match table.get::<_, Table>(key) { - Ok(t) => t, - Err(_) => return Ok(false), // if extra_keys were empty, we wouldn't get here - }; - - while let Some(next_key) = extra_keys.pop_front() { - value_has_key = value.contains_key(next_key.clone())?; - let new_val = value.get::<_, Table>(next_key); - value = match new_val { - Ok(t) => t, - Err(_) => return Ok(value_has_key && extra_keys.is_empty()), - }; - } - - Ok(value_has_key) + Ok(!get(lua, (table, keys))?.is_nil()) } fn has_value<'lua>( @@ -235,13 +221,22 @@ fn has_value<'lua>( (table, value, behavior): (Table<'lua>, LuaValue, Option), ) -> mlua::Result { for pair in table.pairs::() { - let (_, tbl_value) = pair?; - if tbl_value.eq(&value) { + let (_, table_val) = pair?; + let table_has_value = match (table_val.clone(), value.clone()) { + // for tables, compare by values using our equal function + (LuaValue::Table(table_val_tbl), LuaValue::Table(value_tbl)) => { + lua_table_eq(table_val_tbl, value_tbl)? + } + // oterwise, compare using Lua '==' + _ => table_val.eq(&value), + }; + if table_has_value { return Ok(true); } + if behavior == Some(DepthMode::Deep) { - if let LuaValue::Table(tbl) = tbl_value { - if has_value(lua, (tbl, value.clone(), behavior))? { + if let LuaValue::Table(new_tbl) = table_val { + if has_value(lua, (new_tbl, value.clone(), behavior))? { return Ok(true); } } From c0c3df484da46cdf837d39551880e43af78fe692 Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Mon, 4 Dec 2023 17:30:04 +0100 Subject: [PATCH 38/42] Docs cleanup --- docs/config/lua/wezterm.table/extend.md | 2 +- docs/config/lua/wezterm.table/get.md | 12 +++++++---- docs/config/lua/wezterm.table/has_key.md | 24 ++++++++++++++-------- docs/config/lua/wezterm.table/has_value.md | 2 +- 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/docs/config/lua/wezterm.table/extend.md b/docs/config/lua/wezterm.table/extend.md index 410b83dc89c..9f4e1b9fe56 100644 --- a/docs/config/lua/wezterm.table/extend.md +++ b/docs/config/lua/wezterm.table/extend.md @@ -81,4 +81,4 @@ assert( ) ``` -See also [flatten](flatten.md) and [deep_extend](deep_extend.md). +See also [wezterm.table.flatten](flatten.md) and [wezterm.table.deep_extend](deep_extend.md). diff --git a/docs/config/lua/wezterm.table/get.md b/docs/config/lua/wezterm.table/get.md index eb123145cfb..425b87333be 100644 --- a/docs/config/lua/wezterm.table/get.md +++ b/docs/config/lua/wezterm.table/get.md @@ -1,4 +1,4 @@ -# `wezterm.table.get(table, key [, ...])` +# `wezterm.table.get(table, keys..)` {{since('nightly')}} @@ -13,8 +13,12 @@ nested tables: wezterm.table.get(tbl, 'a', 'b', 'c') == tbl['a']['b']['c'] ``` -*Note:* In the above `tbl['a']['b']['c']` might cause an error, since we might be indexing a nil value, -but `wezterm.table.get(tbl, 'a', 'b', 'c')` won't error in this case; instead it will return nil. +*Note:* + +* In the above `tbl['a']['b']['c']` might cause an error, since we might be indexing a nil value, + but `wezterm.table.get(tbl, 'a', 'b', 'c')` won't error in this case; instead it will return `nil`. +* This function can also be used on `Userdata` objects that implement an `__index` + metamethod. ```lua @@ -42,4 +46,4 @@ assert(get(arr1, 4) == nil) assert(get(arr1, 1, 2) == nil) ``` -See also [has_key](has_key.md) and [has_value](has_value.md). +See also [wezterm.table.has_key](has_key.md) and [wezterm.table.has_value](has_value.md). diff --git a/docs/config/lua/wezterm.table/has_key.md b/docs/config/lua/wezterm.table/has_key.md index 78f44e347bb..c3c36aa4c0b 100644 --- a/docs/config/lua/wezterm.table/has_key.md +++ b/docs/config/lua/wezterm.table/has_key.md @@ -1,15 +1,21 @@ -# `wezterm.table.has_key(table, key [, ...])` +# `wezterm.table.has_key(table, keys..)` {{since('nightly')}} -This function accepts a Lua table `table` and a key `key`. -It returns `true` if `table` contains a key equal to `key` (with non-nil value) -and `false` otherwise. +This function accepts a Lua table `table` and an arbitrary number of keys `keys..`. +It returns `true` if `table` contains a non-nil value at the position specified by +`keys..` and `false` otherwise. -The function accepts an optional arbitrary number of extra arguments, that will -all be intepreted as extra keys to check for recursively in the table. I.e., to -check whether `table` has any non-nil value at `table.a.b.c`, we can use -`wezterm.table.has_key(table, 'a', 'b', 'c')`. +The arbitrary number of extra arguments will all be intepreted as extra keys to check +for recursively in the table. I.e., to check whether `table` has any non-nil value at +`table['a']['b']['c']`, we can use `wezterm.table.has_key(table, 'a', 'b', 'c')`. + +*Note:* + +* In the above `table['a']['b']['c']` might cause an error, since we might be indexing a nil value, + but `wezterm.table.has_key(table, 'a', 'b', 'c')` won't error in this case; instead it will return `false`. +* This function can also be used on `Userdata` objects that implement an `__index` + metamethod. ```lua local wezterm = require 'wezterm' @@ -35,4 +41,4 @@ assert(not has_key(arr1, 4)) assert(not has_key(arr1, 1, 2)) ``` -See also [has_value](has_value.md) and [get](get.md). +See also [wezterm.table.has_value](has_value.md) and [wezterm.table.get](get.md). diff --git a/docs/config/lua/wezterm.table/has_value.md b/docs/config/lua/wezterm.table/has_value.md index c862b956d2c..9be858e098f 100644 --- a/docs/config/lua/wezterm.table/has_value.md +++ b/docs/config/lua/wezterm.table/has_value.md @@ -43,4 +43,4 @@ assert(has_value(arr1, 'a')) assert(not has_value(arr1, '1')) ``` -See also [has_key](has_key.md) and [get](get.md). +See also [wezterm.table.has_key](has_key.md) and [wezterm.table.get](get.md). From 796ea5ffd27f47c1595e395b3a15882251cb6995 Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Mon, 4 Dec 2023 17:47:07 +0100 Subject: [PATCH 39/42] More docs cleanup --- docs/config/lua/wezterm.table/get.md | 12 ++++++------ docs/config/lua/wezterm.table/has_key.md | 25 +++++++++++++----------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/docs/config/lua/wezterm.table/get.md b/docs/config/lua/wezterm.table/get.md index 425b87333be..ba9d6035929 100644 --- a/docs/config/lua/wezterm.table/get.md +++ b/docs/config/lua/wezterm.table/get.md @@ -9,16 +9,16 @@ assert(wezterm.table.get(tbl, key) == tbl[key]) ``` You may pass a sequence of keys that will be used to successively resolve nested tables: -``` -wezterm.table.get(tbl, 'a', 'b', 'c') == tbl['a']['b']['c'] +```lua +local tbl = { a = { b = { c = true } } } +assert(wezterm.table.get(tbl, 'a', 'b', 'c') == tbl['a']['b']['c']) ``` *Note:* -* In the above `tbl['a']['b']['c']` might cause an error, since we might be indexing a nil value, - but `wezterm.table.get(tbl, 'a', 'b', 'c')` won't error in this case; instead it will return `nil`. -* This function can also be used on `Userdata` objects that implement an `__index` - metamethod. +* In the above `tbl['a']['a']['a']` would cause an error, since we are indexing a nil value, + but `wezterm.table.get(tbl, 'a', 'a', 'a')` won't error in this case; instead it will return `nil`. +* This function can also be used on `Userdata` objects that implement an `__index` metamethod. ```lua diff --git a/docs/config/lua/wezterm.table/has_key.md b/docs/config/lua/wezterm.table/has_key.md index c3c36aa4c0b..93d2c1ee65e 100644 --- a/docs/config/lua/wezterm.table/has_key.md +++ b/docs/config/lua/wezterm.table/has_key.md @@ -2,20 +2,23 @@ {{since('nightly')}} -This function accepts a Lua table `table` and an arbitrary number of keys `keys..`. -It returns `true` if `table` contains a non-nil value at the position specified by -`keys..` and `false` otherwise. - -The arbitrary number of extra arguments will all be intepreted as extra keys to check -for recursively in the table. I.e., to check whether `table` has any non-nil value at -`table['a']['b']['c']`, we can use `wezterm.table.has_key(table, 'a', 'b', 'c')`. +This function can be used to check if a key in a table has a non-nil value. In its most +basic form it is equivalent to: +```lua +assert(wezterm.table.has_key(tbl, key) == (tbl[key] ~= nil)) +``` +You may pass a sequence of keys that will be used to successively check nested tables: +```lua +local tbl = { a = { b = { c = true } } } +assert(wezterm.table.has_key(tbl, 'a', 'b', 'c') == (tbl['a']['b']['c'] ~= nil)) +``` *Note:* -* In the above `table['a']['b']['c']` might cause an error, since we might be indexing a nil value, - but `wezterm.table.has_key(table, 'a', 'b', 'c')` won't error in this case; instead it will return `false`. -* This function can also be used on `Userdata` objects that implement an `__index` - metamethod. +* In the above `tbl['a']['a']['a']` would cause an error, since we are indexing a nil value, + but `wezterm.table.has_key(tbl, 'a', 'a', 'a')` won't error in this case; instead it will return + `false`. +* This function can also be used on `Userdata` objects that implement an `__index` metamethod. ```lua local wezterm = require 'wezterm' From 76e1e70b3ac25ced9257af4333e2e07810a1e464 Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Tue, 5 Dec 2023 17:38:06 +0100 Subject: [PATCH 40/42] `deep_extend` fix --- lua-api-crates/table-funcs/src/lib.rs | 68 +++++++++++++++++---------- 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index 0352344c82d..31a3108e336 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -80,34 +80,50 @@ fn deep_extend<'lua>( for table in array_of_tables { for pair in table.pairs::() { let (key, value) = pair?; - let contains_key = tbl.contains_key(key.clone())?; + let tbl_value = tbl.get::<_, LuaValue>(key.clone())?; - if behavior == ConflictMode::Error && contains_key { - return Err(mlua::Error::runtime(format!( - "The key '{}' is in more than one of the tables.", - key.to_string()? - ))); - } - - if !contains_key && !value.is_table() { - tbl.set(key, value)?; - } else if let LuaValue::Table(t) = value { - let cur_val_as_tbl = tbl.get::<_, Table>(key.clone()); - let mut update_bool = true; - let inner_tbl = if cur_val_as_tbl.is_ok() { - deep_extend(lua, (vec![cur_val_as_tbl?, t], Some(behavior)))? - } else { - // Note: we can get here in modes Force and Keep - if behavior == ConflictMode::Keep { - update_bool = false; - } - t - }; - if update_bool { + match (tbl_value, value) { + (LuaValue::Table(tbl_value_table), LuaValue::Table(value_table)) => { + let inner_tbl = deep_extend(lua, (vec![tbl_value_table, value_table], Some(behavior)))?; tbl.set(key, inner_tbl)?; - } - } else if behavior == ConflictMode::Force { - tbl.set(key, value)?; + }, + (tbl_val, LuaValue::Table(value_tbl)) => { + // if tbl[key] is set to a non-table value, but we get a table + if tbl_val.is_nil() { + tbl.set(key, clone(lua, (value_tbl, Some(DepthMode::Deep)))?)?; + } else if behavior == ConflictMode::Force { + tbl.set(key, clone(lua, (value_tbl, Some(DepthMode::Deep)))?)?; + } else if behavior == ConflictMode::Error { + return Err(mlua::Error::runtime(format!( + "The key '{}' is in more than one of the tables.", + key.to_string()? + ))); + } + }, + (LuaValue::Table(_), val) => { + // if tbl[key] is set to a table, but we get a non-table value + if behavior == ConflictMode::Force { + tbl.set(key, val)?; + } else if behavior == ConflictMode::Error { + return Err(mlua::Error::runtime(format!( + "The key '{}' is in more than one of the tables.", + key.to_string()? + ))); + } + }, + (tbl_val, val) => { + // tbl_val and val are not tables + if tbl_val.is_nil() { + tbl.set(key, val)?; + } else if behavior == ConflictMode::Force { + tbl.set(key, val)?; + } else if behavior == ConflictMode::Error { + return Err(mlua::Error::runtime(format!( + "The key '{}' is in more than one of the tables.", + key.to_string()? + ))); + } + }, } } } From 3d766d4b82f748fa2e7b094506e4755bbe5a0e1c Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Tue, 5 Dec 2023 20:59:35 +0100 Subject: [PATCH 41/42] Minor cleanup --- lua-api-crates/table-funcs/src/lib.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index 31a3108e336..82c5ea9b562 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -83,10 +83,12 @@ fn deep_extend<'lua>( let tbl_value = tbl.get::<_, LuaValue>(key.clone())?; match (tbl_value, value) { + // if tbl[key] is set to a table value and we get a table (LuaValue::Table(tbl_value_table), LuaValue::Table(value_table)) => { - let inner_tbl = deep_extend(lua, (vec![tbl_value_table, value_table], Some(behavior)))?; + let inner_tbl = + deep_extend(lua, (vec![tbl_value_table, value_table], Some(behavior)))?; tbl.set(key, inner_tbl)?; - }, + } (tbl_val, LuaValue::Table(value_tbl)) => { // if tbl[key] is set to a non-table value, but we get a table if tbl_val.is_nil() { @@ -99,7 +101,7 @@ fn deep_extend<'lua>( key.to_string()? ))); } - }, + } (LuaValue::Table(_), val) => { // if tbl[key] is set to a table, but we get a non-table value if behavior == ConflictMode::Force { @@ -110,7 +112,7 @@ fn deep_extend<'lua>( key.to_string()? ))); } - }, + } (tbl_val, val) => { // tbl_val and val are not tables if tbl_val.is_nil() { @@ -123,7 +125,7 @@ fn deep_extend<'lua>( key.to_string()? ))); } - }, + } } } } @@ -168,9 +170,8 @@ fn flatten<'lua>( let mut flat = flatten(lua, (tbl_as_vec, Some(behavior)))?; flat_vec.append(&mut flat); } else { - for elem in tbl.sequence_values::() { - flat_vec.push(elem?); - } + let mut tbl_as_vec = tbl.sequence_values().filter_map(|x| x.ok()).collect(); + flat_vec.append(&mut tbl_as_vec); } } LuaValue::Nil => (), From 34653a72ca219d27a9f2a5b5b09fab9b21ef6619 Mon Sep 17 00:00:00 2001 From: Danielkonge Date: Wed, 6 Dec 2023 22:26:24 +0100 Subject: [PATCH 42/42] Add two example tests --- docs/config/lua/wezterm.table/extend.md | 2 +- docs/config/lua/wezterm.table/has_key.md | 4 +- lua-api-crates/table-funcs/src/lib.rs | 74 ++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/docs/config/lua/wezterm.table/extend.md b/docs/config/lua/wezterm.table/extend.md index 9f4e1b9fe56..68b9568ea54 100644 --- a/docs/config/lua/wezterm.table/extend.md +++ b/docs/config/lua/wezterm.table/extend.md @@ -60,7 +60,7 @@ local ok, msg = pcall(function() extend({ tbl1, tbl3 }, 'Error') end) local msg_string = wezterm.to_string(msg) -wezterm.log_info( +assert( not ok and msg_string:find "The key 'e' is in more than one of the tables." ~= nil diff --git a/docs/config/lua/wezterm.table/has_key.md b/docs/config/lua/wezterm.table/has_key.md index 93d2c1ee65e..47bc37f972f 100644 --- a/docs/config/lua/wezterm.table/has_key.md +++ b/docs/config/lua/wezterm.table/has_key.md @@ -10,7 +10,9 @@ assert(wezterm.table.has_key(tbl, key) == (tbl[key] ~= nil)) You may pass a sequence of keys that will be used to successively check nested tables: ```lua local tbl = { a = { b = { c = true } } } -assert(wezterm.table.has_key(tbl, 'a', 'b', 'c') == (tbl['a']['b']['c'] ~= nil)) +assert( + wezterm.table.has_key(tbl, 'a', 'b', 'c') == (tbl['a']['b']['c'] ~= nil) +) ``` *Note:* diff --git a/lua-api-crates/table-funcs/src/lib.rs b/lua-api-crates/table-funcs/src/lib.rs index 82c5ea9b562..e757081166f 100644 --- a/lua-api-crates/table-funcs/src/lib.rs +++ b/lua-api-crates/table-funcs/src/lib.rs @@ -295,3 +295,77 @@ fn lua_table_eq(table1: Table, table2: Table) -> mlua::Result { fn equal<'lua>(_: &'lua Lua, (table1, table2): (Table<'lua>, Table<'lua>)) -> mlua::Result { lua_table_eq(table1, table2) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_table_funcs_equal() -> mlua::Result<()> { + let lua = Lua::new(); + + lua.load( + r#" + tbl1 = { 1, 'a', 2, 'abc' } + tbl2 = { 1, 'a', 2, 'abc' } + tbl3 = { a = 1, b = '2' } + tbl4 = { b = '2', a = 1 } + tbl5 = { a = { 1, 2 }, b = { c = { 1 } } } + tbl6 = { b = { c = { 1 } }, a = { 1, 2 } } + "#, + ) + .exec()?; + let tbl1 = lua.globals().get::<_, Table>("tbl1")?; + let tbl2 = lua.globals().get::<_, Table>("tbl2")?; + let tbl3 = lua.globals().get::<_, Table>("tbl3")?; + let tbl4 = lua.globals().get::<_, Table>("tbl4")?; + let tbl5 = lua.globals().get::<_, Table>("tbl5")?; + let tbl6 = lua.globals().get::<_, Table>("tbl6")?; + + assert!(equal(&lua, (tbl1, tbl2))?); + assert!(equal(&lua, (tbl3, tbl4))?); + assert!(equal(&lua, (tbl5, tbl6))?); + + Ok(()) + } + + #[test] + fn test_table_funcs_extend() -> mlua::Result<()> { + let lua = Lua::new(); + + lua.load( + r#" + tbl1 = { a = 1, b = { d = { 4 } }, e = 3 } + tbl2 = { a = 2, b = { e = 5 }, d = 4 } + tbl3 = { e = 3 } + + ext_tbl1_tbl2 = { a = 2, b = { e = 5 }, e = 3, d = 4 } + ext_tbl1_tbl2_Keep = { a = 1, b = { d = { 4 } }, e = 3, d = 4 } + "#, + ) + .exec()?; + let tbl1 = lua.globals().get::<_, Table>("tbl1")?; + let tbl2 = lua.globals().get::<_, Table>("tbl2")?; + let tbl3 = lua.globals().get::<_, Table>("tbl3")?; + let ext_tbl1_tbl2 = lua.globals().get::<_, Table>("ext_tbl1_tbl2")?; + let ext_tbl1_tbl2_keep = lua.globals().get::<_, Table>("ext_tbl1_tbl2_Keep")?; + + assert!(equal( + &lua, + ( + extend(&lua, (vec![tbl1.clone(), tbl2.clone()], None))?, + ext_tbl1_tbl2 + ) + )?); + assert!(equal( + &lua, + ( + extend(&lua, (vec![tbl1.clone(), tbl2], Some(ConflictMode::Keep)))?, + ext_tbl1_tbl2_keep + ) + )?); + assert!(extend(&lua, (vec![tbl1, tbl3], Some(ConflictMode::Error))).is_err()); + + Ok(()) + } +}