diff --git a/src/adder.rs b/src/adder.rs index 634ab03..2c8bc73 100644 --- a/src/adder.rs +++ b/src/adder.rs @@ -19,7 +19,7 @@ pub fn handle_add(doc: &mut DocumentMut, op: AddOp) -> Result<()> { .split('/') .map(|s| s.to_string()) .collect::>(); - let dotted_path_vec = + let mut dotted_path_vec = path.map(|p| p.split('/').map(|s| s.to_string()).collect::>()); let field_value_json: JValue = from_str(&value).context("parsing value field in add request")?; @@ -35,12 +35,20 @@ pub fn handle_add(doc: &mut DocumentMut, op: AddOp) -> Result<()> { } else { false }; + let append_array_at_path = match &mut dotted_path_vec { + Some(path_vec) if path_vec.last().is_some_and(|key| key == "[]") => { + path_vec.pop(); + true + } + _ => false, + }; table_header_adder::add_value_with_table_header_and_dotted_path( doc, &table_header_path_vec, dotted_path_vec, field_value_toml, array_of_tables, + append_array_at_path, ) } None => { @@ -177,7 +185,16 @@ test = "yo" none = "all""#; macro_rules! meta_add_test { - ($name:ident, $table_header_path:expr, $field:expr, $value:expr, $contents:expr, $expected:expr, $result:ident, $($assertion:stmt)*) => { + ( + $name:ident, + $table_header_path:expr, + $field:expr, + $value:expr, + $contents:expr, + $expected:expr, + $result:ident, + $($assertion:stmt)* + ) => { #[test] fn $name() { let mut doc = $contents.parse::().unwrap(); @@ -674,4 +691,68 @@ key = "second" torchvision = [{ index = "pytorch-cpu", marker = "platform_system == 'Linux'" }] "# ); + + add_table_header_test!( + test_append_array_at_path_empty, + Some("tool/uv/sources"), + Some("torch/[]"), + r#" + {"index": "pytorch-cpu", "marker": "platform_system == 'Linux'"} + "#, + r#" + "#, + r#" +[tool.uv.sources] +torch = [{ index = "pytorch-cpu", marker = "platform_system == 'Linux'" }] + "# + ); + + add_table_header_test!( + test_append_array_at_path_empty_dotted_path, + Some("tool/uv/sources"), + Some("torch/test/[]"), + r#" + {"index": "pytorch-cpu", "marker": "platform_system == 'Linux'"} + "#, + r#" + "#, + r#" +[tool.uv.sources] +torch.test = [{ index = "pytorch-cpu", marker = "platform_system == 'Linux'" }] + "# + ); + + add_table_header_test!( + test_append_array_at_path_existing, + Some("tool/uv/sources"), + Some("torch/[]"), + r#" + {"index": "pytorch-cpu", "marker": "platform_system == 'Linux'"} + "#, + r#" +[tool.uv.sources] +torch = [{ index = "foo", marker = "platform_system == 'Windows'" }] + "#, + r#" +[tool.uv.sources] +torch = [{ index = "foo", marker = "platform_system == 'Windows'" }, { index = "pytorch-cpu", marker = "platform_system == 'Linux'" }] + "# + ); + + add_table_header_error_test!( + test_append_array_at_path_existing_non_array_at_path, + Some("tool/uv/sources"), + Some("torch/[]"), + r#" + {"index": "pytorch-cpu", "marker": "platform_system == 'Linux'"} + "#, + r#" +[tool.uv.sources] +torch = 1 + "#, + r#" +[tool.uv.sources] +torch = 1 + "# + ); } diff --git a/src/adder/table_header_adder.rs b/src/adder/table_header_adder.rs index d88f7e0..a1c4dc1 100644 --- a/src/adder/table_header_adder.rs +++ b/src/adder/table_header_adder.rs @@ -1,4 +1,4 @@ -use anyhow::{bail, Context, Result}; +use anyhow::{anyhow, bail, Context, Result}; use toml_edit::{array, Item, Table, Value}; /* @@ -19,6 +19,7 @@ pub fn add_value_with_table_header_and_dotted_path( dotted_path: Option>, value: Item, array_of_tables: bool, + append_array_at_path: bool, ) -> Result<()> { match table_header_path.first() { None => { @@ -26,6 +27,7 @@ pub fn add_value_with_table_header_and_dotted_path( table, dotted_path.context("Missing 'path' value")?.as_slice(), value, + append_array_at_path, )?; Ok(()) } @@ -38,6 +40,7 @@ pub fn add_value_with_table_header_and_dotted_path( dotted_path, value, array_of_tables, + append_array_at_path, )?; Ok(()) } @@ -51,6 +54,7 @@ pub fn add_value_with_table_header_and_dotted_path( dotted_path, value, array_of_tables, + append_array_at_path, )?; table.insert(field, Item::Table(inner_table)); } else { @@ -107,6 +111,7 @@ fn add_value_with_dotted_path( table: &mut Table, dotted_path: &[String], value: Item, + append_array_at_path: bool, ) -> Result<()> { match dotted_path.first() { None => Ok(()), @@ -115,9 +120,23 @@ fn add_value_with_dotted_path( if dotted_path.len() > 1 { let mut inner_table = Table::new(); inner_table.set_dotted(true); - add_value_with_dotted_path(&mut inner_table, &dotted_path[1..], value)?; + add_value_with_dotted_path( + &mut inner_table, + &dotted_path[1..], + value, + append_array_at_path, + )?; table.insert(field, Item::Table(inner_table)); Ok(()) + } else if append_array_at_path { + let mut arr = toml_edit::Array::new(); + arr.push( + value + .into_value() + .map_err(|_| anyhow!("Cannot append non-value item to array"))?, + ); + table.insert(field, Item::Value(Value::Array(arr))); + Ok(()) } else { table.insert(field, value); Ok(()) @@ -126,19 +145,35 @@ fn add_value_with_dotted_path( Some(Item::Table(ref mut inner_table)) => { if dotted_path.len() > 1 { inner_table.set_dotted(true); - add_value_with_dotted_path(inner_table, &dotted_path[1..], value) + add_value_with_dotted_path( + inner_table, + &dotted_path[1..], + value, + append_array_at_path, + ) } else { table.insert(field, value); Ok(()) } } - Some(Item::Value(_)) => { - if dotted_path.len() == 1 { - table.insert(field, value); - Ok(()) - } else { + Some(item @ Item::Value(_)) => { + if dotted_path.len() != 1 { bail!("Cannot overwrite a non-table with a table") } + + if append_array_at_path { + let arr = item + .as_array_mut() + .context(format!("Cannot append non-array field '{field}'"))?; + arr.push( + value + .into_value() + .map_err(|_| anyhow!("Cannot append non-value item to array"))?, + ); + } else { + table.insert(field, value); + } + Ok(()) } Some(Item::ArrayOfTables(_)) => { bail!("Cannot add key to a array of tables")