diff --git a/Cargo.lock b/Cargo.lock index 27e1620..24b4651 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -102,12 +102,28 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "dissimilar" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59f8e79d1fbf76bdfbde321e902714bf6c49df88a7dda6fc682fc2979226962d" + [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "expect-test" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63af43ff4431e848fb47472a920f14fa71c24de13255a5692e93d4e90302acb0" +dependencies = [ + "dissimilar", + "once_cell", +] + [[package]] name = "hashbrown" version = "0.14.3" @@ -142,6 +158,12 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + [[package]] name = "proc-macro2" version = "1.0.79" @@ -221,6 +243,7 @@ version = "0.5.3" dependencies = [ "anyhow", "clap", + "expect-test", "serde", "serde_json", "toml_edit", diff --git a/Cargo.toml b/Cargo.toml index 1ffa68e..74f32ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,6 @@ serde_json = { version = "1.0", features = ["preserve_order"] } serde = { version = "1.0", features = ["derive"] } clap = { version = "4.5.4", features = ["derive"] } anyhow = "1.0.58" + +[dev-dependencies] +expect-test = "1.5.1" diff --git a/src/adder.rs b/src/adder.rs index 10eb0f6..5e40339 100644 --- a/src/adder.rs +++ b/src/adder.rs @@ -2,35 +2,62 @@ mod table_header_adder; use anyhow::{bail, Context, Result}; use serde_json::{from_str, Value as JValue}; -use toml_edit::{Array, ArrayOfTables, DocumentMut, InlineTable, Item, Table, Value}; +use toml_edit::{Array, ArrayOfTables, DocumentMut, InlineTable, Item, Value}; use crate::converter::json_to_toml; use crate::field_finder::{get_field, DoInsert, TomlValue}; +use crate::AddOp; -pub fn handle_add( - field: &str, - table_header_path: Option, - dotted_path: Option, - value: &str, - doc: &mut DocumentMut, -) -> Result<()> { - match table_header_path { +pub fn handle_add(doc: &mut DocumentMut, op: AddOp) -> Result<()> { + let path = op.dotted_path.or(op.path); // TODO: dotted_path is just a duplicated + // codepath of "path". Delete this once pid1 has + // been updated. + match op.table_header_path { Some(thpath) => { - let dpath = dotted_path.context("error: expected dotted_path to add")?; - handle_add_with_table_header_path(&thpath, &dpath, value, doc) + let value = op.value.context("error: expected value to add")?; + let mut table_header_path_vec = thpath + .split('/') + .map(|s| s.to_string()) + .collect::>(); + let 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")?; + let field_value_toml: Item = json_to_toml(&field_value_json, true) + .context("converting value in add request from json to toml")?; + + let array_of_tables = if table_header_path_vec + .last() + .is_some_and(|key| key == "[[]]") + { + table_header_path_vec.pop(); + true + } else { + 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, + ) } None => { - let mut path_split = field + let mut path_split = path + .context("Missing 'path' value")? .split('/') .map(|s| s.to_string()) .collect::>(); let last_field = path_split.pop().context("Path is empty")?; - let final_field_value = - get_field(&path_split, &last_field, DoInsert::Yes, doc).context("Could not find field")?; + let final_field_value = get_field(&path_split, &last_field, DoInsert::Yes, doc) + .context("Could not find field")?; - let field_value_json: JValue = from_str(value).context("parsing value field in add request")?; + let value = op.value.context("error: expected value to add")?; + let field_value_json: JValue = + from_str(&value).context("parsing value field in add request")?; let is_inline = matches!( final_field_value, @@ -41,23 +68,25 @@ pub fn handle_add( .context("converting value in add request from json to toml")?; match final_field_value { - TomlValue::Table(table) => add_in_table(table, &last_field, field_value_toml), + TomlValue::Table(table) => { + table.insert(&last_field, field_value_toml); + Ok(()) + } TomlValue::ArrayOfTables(array) => { add_in_array_of_tables(array, &last_field, field_value_toml) } TomlValue::Array(array) => add_in_array(array, &last_field, field_value_toml), - TomlValue::InlineTable(table) => add_in_inline_table(table, &last_field, field_value_toml), - TomlValue::Value(value) => add_in_generic_value(value, &last_field, field_value_toml), + TomlValue::InlineTable(table) => { + add_in_inline_table(table, &last_field, field_value_toml) + } + TomlValue::Value(value) => { + add_in_generic_value(value, &last_field, field_value_toml) + } } } } } -fn add_in_table(table: &mut Table, last_field: &str, toml: Item) -> Result<()> { - table.insert(last_field, toml); - Ok(()) -} - fn add_in_array_of_tables(array: &mut ArrayOfTables, last_field: &str, toml: Item) -> Result<()> { let insert_at_index = last_field.parse::().context("parsing last_field")?; @@ -124,33 +153,13 @@ fn add_in_generic_value(generic_value: &mut Value, last_field: &str, toml: Item) } } -fn handle_add_with_table_header_path( - table_header_path: &str, - dotted_path: &str, - value: &str, - doc: &mut DocumentMut, -) -> Result<()> { - let table_header_path_vec = table_header_path - .split('/') - .map(|s| s.to_string()) - .collect::>(); - let dotted_path_vec = dotted_path - .split('/') - .map(|s| s.to_string()) - .collect::>(); - let field_value_json: JValue = from_str(value).context("parsing value field in add request")?; - let field_value_toml: Item = json_to_toml(&field_value_json, true) - .context("converting value in add request from json to toml")?; - return table_header_adder::add_value_with_table_header_and_dotted_path(doc, &table_header_path_vec, &dotted_path_vec, field_value_toml); -} - #[cfg(test)] +#[macro_use] mod adder_tests { use super::*; - use toml_edit::{DocumentMut, TomlError}; - fn get_dotreplit_content_with_formatting() -> Result { - r#"test = "yo" + const GET_DOTREPLIT_CONTENT_WITH_FORMATTING: &str = r#" +test = "yo" [foo] bar = "baz" # comment inlineTable = {a = "b", c = "d" } @@ -165,32 +174,87 @@ mod adder_tests { [[foo.arr]] glub = "group" [[foo.arr]] - none = "all""# - .to_string() - .parse::() - } + none = "all""#; - macro_rules! add_test { - ($name:ident, $field:expr, $value:expr, $contents:expr, $expected:expr) => { + macro_rules! meta_add_test { + ($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; + let mut doc = $contents.parse::().unwrap(); let expected = $expected; - let field = $field; - let value = $value.to_string(); - - let result = handle_add(field, None, None, &value, &mut doc); - assert!(result.is_ok(), "error: {:?}", result); + let table_header_path = ($table_header_path as Option<&str>).map(|s| s.to_string()); + let field = ($field as Option<&str>).map(|s| s.to_owned()); + let value = Some($value.to_string()); + + let op = AddOp { + path: field, + table_header_path: table_header_path, + dotted_path: None, + value: value, + }; + let $result = handle_add(&mut doc, op); + $( + $assertion + )* assert_eq!(doc.to_string().trim(), expected.trim()); } }; } + macro_rules! add_test { + ($name:ident, $field:expr, $value:expr, $contents:expr, $expected:expr) => { + meta_add_test!( + $name, + None, + Some($field), + $value, + $contents, + $expected, + result, + { assert!(result.is_ok(), "error: {:?}", result) } + ); + }; + } + + #[macro_export] + macro_rules! add_table_header_test { + ($name:ident, $table_header_path:expr, $field:expr, $value:expr, $contents:expr, $expected:expr) => { + meta_add_test!( + $name, + $table_header_path, + $field, + $value, + $contents, + $expected, + result, + { assert!(result.is_ok(), "error: {:?}", result) } + ); + }; + } + + #[macro_export] + macro_rules! add_table_header_error_test { + ($name:ident, $table_header_path:expr, $field:expr, $value:expr, $contents:expr, $expected:expr) => { + meta_add_test!( + $name, + $table_header_path, + $field, + $value, + $contents, + $expected, + result, + { + assert!(result.is_err(), "expected an error, got : {:?}", result); + } + ); + }; + } + add_test!( add_to_toml_basic, "new", "\"yo\"", - get_dotreplit_content_with_formatting().unwrap(), + GET_DOTREPLIT_CONTENT_WITH_FORMATTING, r#" test = "yo" new = "yo" @@ -216,7 +280,7 @@ new = "yo" add_to_toml_deep, "foo/bla/new", "\"yo\"", - get_dotreplit_content_with_formatting().unwrap(), + GET_DOTREPLIT_CONTENT_WITH_FORMATTING, r#" test = "yo" [foo] @@ -242,7 +306,7 @@ new = "yo" add_array, "new", r#"["a", "b", "c"]"#, - get_dotreplit_content_with_formatting().unwrap(), + GET_DOTREPLIT_CONTENT_WITH_FORMATTING, r#" test = "yo" new = ["a", "b", "c"] @@ -268,7 +332,7 @@ new = ["a", "b", "c"] add_array_at_index, "foo/arr/1/glub", r#"{"hi": 123}"#, - get_dotreplit_content_with_formatting().unwrap(), + GET_DOTREPLIT_CONTENT_WITH_FORMATTING, r#" test = "yo" [foo] @@ -295,7 +359,7 @@ hi = 123 replace_large, "foo", r#"[1, 2, 3]"#, - get_dotreplit_content_with_formatting().unwrap(), + GET_DOTREPLIT_CONTENT_WITH_FORMATTING, r#" test = "yo" foo = [1, 2, 3] @@ -306,7 +370,7 @@ foo = [1, 2, 3] simple_push_into_array, "arr/2", "123", - r#"arr = [1, 2]"#.parse::().unwrap(), + r#"arr = [1, 2]"#, r#"arr = [1, 2, 123]"# ); @@ -314,7 +378,7 @@ foo = [1, 2, 3] push_into_table_array, "foo/arr/3", r#"{}"#, - get_dotreplit_content_with_formatting().unwrap(), + GET_DOTREPLIT_CONTENT_WITH_FORMATTING, r#" test = "yo" [foo] @@ -341,7 +405,7 @@ test = "yo" add_as_you_traverse, "foo/arr", r#""yup""#, - r#"meep = "nope""#.parse::().unwrap(), + r#"meep = "nope""#, r#"meep = "nope" [foo] @@ -352,7 +416,7 @@ arr = "yup""# add_array_objects_deep, "foo/0/hi", r#"123"#, - r#"top = "hi""#.parse::().unwrap(), + r#"top = "hi""#, r#"top = "hi" [[foo]] @@ -364,7 +428,7 @@ hi = 123 add_array_objects, "foo/0", r#"{"hi": 123}"#, - r#"top = "hi""#.parse::().unwrap(), + r#"top = "hi""#, r#"top = "hi" [[foo]] @@ -378,9 +442,7 @@ hi = 123 r#"{"hi": 1234}"#, r#"top = "hi" [[foo]] -hi = 123"# - .parse::() - .unwrap(), +hi = 123"#, r#"top = "hi" [[foo]] hi = 123 @@ -404,9 +466,7 @@ PYTHONPATH="${VIRTUAL_ENV}/lib/python3.8/site-packages" REPLIT_POETRY_PYPI_REPOSITORY="https://package-proxy.replit.com/pypi/" MPLBACKEND="TkAgg" POETRY_CACHE_DIR="${HOME}/${REPL_SLUG}/.cache/pypoetry" -"# - .parse::() - .unwrap(), +"#, r#" run = "echo heyo" @@ -432,9 +492,7 @@ PYTHONPATH="${VIRTUAL_ENV}/lib/python3.8/site-packages" REPLIT_POETRY_PYPI_REPOSITORY="https://package-proxy.replit.com/pypi/" MPLBACKEND="TkAgg" POETRY_CACHE_DIR="${HOME}/${REPL_SLUG}/.cache/pypoetry" -"# - .parse::() - .unwrap(), +"#, r#" [env] VIRTUAL_ENV = "/home/runner/${REPL_SLUG}/venv" @@ -459,9 +517,7 @@ PYTHONPATH="${VIRTUAL_ENV}/lib/python3.8/site-packages" REPLIT_POETRY_PYPI_REPOSITORY="https://package-proxy.replit.com/pypi/" MPLBACKEND="TkAgg" POETRY_CACHE_DIR="${HOME}/${REPL_SLUG}/.cache/pypoetry" -"# - .parse::() - .unwrap(), +"#, r#" [env] VIRTUAL_ENV = "/home/runner/${REPL_SLUG}/venv" @@ -480,9 +536,7 @@ POETRY_CACHE_DIR="${HOME}/${REPL_SLUG}/.cache/pypoetry" "#, r#" run = "hi" -"# - .parse::() - .unwrap(), +"#, r#" run = "hi" @@ -496,3 +550,128 @@ POETRY_CACHE_DIR = "${HOME}/${REPL_SLUG}/.cache/pypoetry" "# ); } + +#[cfg(test)] +mod table_header_adder_tests { + use super::*; + + add_table_header_test!( + test_one_element_table_header, + Some("moduleConfig"), + Some("interpreters/ruby/enable"), + "true", + "", + r#" +[moduleConfig] +interpreters.ruby.enable = true + "# + ); + + add_table_header_test!( + test_two_element_table_header, + Some("moduleConfig/interpreters"), + Some("ruby/enable"), + "true", + "", + r#" +[moduleConfig.interpreters] +ruby.enable = true + "# + ); + + add_table_header_test!( + test_preserve_existing, + Some("moduleConfig"), + Some("interpreters/ruby/enable"), + "true", + r#" +[moduleConfig] +bundles.go.enable = true + "#, + r#" +[moduleConfig] +bundles.go.enable = true +interpreters.ruby.enable = true +"# + ); + + add_table_header_test!( + test_preserve_existing_inner_tables, + Some("moduleConfig"), + Some("interpreters/ruby/version"), + "\"3.2.3\"", + r#" +[moduleConfig] +interpreter.ruby.enable = true + "#, + r#" +[moduleConfig] +interpreter.ruby.enable = true +interpreters.ruby.version = "3.2.3" + "# + ); + + add_table_header_error_test!( + test_error_when_adding_key_to_non_table, + Some("moduleConfig"), + Some("interpreters/ruby/version"), + "\"3.2.3\"", + r#" +[moduleConfig] +interpreters.ruby = "my dear" + "#, + r#" +[moduleConfig] +interpreters.ruby = "my dear" + "# + ); + + add_table_header_test!( + test_add_arrays_of_tables, + Some("tool/uv/index/[[]]"), + None, + r#" + {"key": "value"} + "#, + "", + r#" +[[tool.uv.index]] +key = "value" +"# + ); + + add_table_header_test!( + test_append_arrays_of_tables, + Some("tool/uv/index/[[]]"), + None, + r#" + {"key": "second"} + "#, + r#" +[[tool.uv.index]] +key = "first" + "#, + r#" +[[tool.uv.index]] +key = "first" + +[[tool.uv.index]] +key = "second" +"# + ); + + add_table_header_test!( + test_add_table_literal, + Some("tool/uv/sources"), + None, + r#" + {"torchvision": [{ "index": "pytorch-cpu", "marker": "platform_system == 'Linux'" }]} + "#, + r#" + "#, + r#" +[tool.uv.sources] +torchvision = [{ index = "pytorch-cpu", marker = "platform_system == 'Linux'" }] + "# + ); +} diff --git a/src/adder/table_header_adder.rs b/src/adder/table_header_adder.rs index 55c34b5..4abbc13 100644 --- a/src/adder/table_header_adder.rs +++ b/src/adder/table_header_adder.rs @@ -1,5 +1,5 @@ -use toml_edit::{Table, Item}; -use anyhow::{bail, Result}; +use anyhow::{bail, Context, Result}; +use toml_edit::{array, Item, Table, Value}; /* Perform an "add" at a table_header_path followed by a dotted_path. @@ -16,42 +16,80 @@ c.b = true pub fn add_value_with_table_header_and_dotted_path( table: &mut Table, table_header_path: &[String], - dotted_path: &[String], - value: Item) -> Result<()> -{ + dotted_path: Option>, + value: Item, + array_of_tables: bool, +) -> Result<()> { match table_header_path.get(0) { - None => add_value_with_dotted_path(table, dotted_path, value), - Some(field) => { - match table.get_mut(field) { - Some(Item::Table(ref mut inner_table)) => { - inner_table.set_dotted(table_header_path.len() > 1); - add_value_with_table_header_and_dotted_path( - inner_table, - &table_header_path[1..], - dotted_path, - value - ) - } - None | Some(Item::None) => { + None => { + add_value_with_dotted_path( + table, + dotted_path.context("Missing 'path' value")?.as_slice(), + value, + )?; + Ok(()) + } + Some(field) => match table.get_mut(field) { + Some(Item::Table(ref mut inner_table)) => { + inner_table.set_dotted(table_header_path.len() > 1); + add_value_with_table_header_and_dotted_path( + inner_table, + &table_header_path[1..], + dotted_path, + value, + array_of_tables, + )?; + Ok(()) + } + None | Some(Item::None) => { + if table_header_path.len() > 1 || dotted_path.is_some() { let mut inner_table = Table::new(); inner_table.set_dotted(table_header_path.len() > 1); add_value_with_table_header_and_dotted_path( &mut inner_table, &table_header_path[1..], dotted_path, - value - ) - .map(|_| table.insert(field, Item::Table(inner_table))) - .map(|_| ()) - } - Some(Item::Value(_)) => { - bail!("cannot set a key on a non-table") - } - Some(Item::ArrayOfTables(_)) => { - bail!("cannot set a key on an array of tables") + value, + array_of_tables, + )?; + table.insert(field, Item::Table(inner_table)); + } else { + match value { + Item::Value(Value::InlineTable(it)) => { + if array_of_tables { + table.insert(field, array()); + let aot = table[field].as_array_of_tables_mut().unwrap(); + aot.push(it.into_table()); + } else { + table.insert(field, Item::Table(it.into_table())); + } + } + other => { + bail!("unexpected value: {:?}", other); + } + } } + Ok(()) } - } + Some(Item::Value(_)) => { + bail!("cannot set a key on a non-table") + } + Some(Item::ArrayOfTables(aot)) => { + match value { + Item::Value(Value::InlineTable(it)) => { + if array_of_tables { + aot.push(it.into_table()); + } else { + bail!("Expected [[]] syntax for appending to an array of tables"); + } + } + other => { + bail!("unexpected value: {:?}", other); + } + }; + Ok(()) + } + }, } } @@ -65,131 +103,46 @@ yields: a.b = true ``` */ -fn add_value_with_dotted_path(table: &mut Table, dotted_path: &[String], value: Item) -> Result<()> { +fn add_value_with_dotted_path( + table: &mut Table, + dotted_path: &[String], + value: Item, +) -> Result<()> { match dotted_path.get(0) { - None => { - Ok(()) - }, - Some(field) => { - match table.get_mut(field) { - None | Some(Item::None) => { - if dotted_path.len() > 1 { - let mut inner_table = Table::new(); - inner_table.set_dotted(true); - return add_value_with_dotted_path( - &mut inner_table, - &dotted_path[1..], - value - ) + None => Ok(()), + Some(field) => match table.get_mut(field) { + None | Some(Item::None) => { + if dotted_path.len() > 1 { + let mut inner_table = Table::new(); + inner_table.set_dotted(true); + return add_value_with_dotted_path(&mut inner_table, &dotted_path[1..], value) .map(|_| table.insert(field, Item::Table(inner_table))) .map(|_| ()); - } else { - table.insert(field, value); - Ok(()) - } + } else { + table.insert(field, value); + Ok(()) } - Some(Item::Table(ref mut inner_table)) => { - if dotted_path.len() > 1 { - inner_table.set_dotted(true); - return add_value_with_dotted_path( - inner_table, - &dotted_path[1..], - value - ); - } else { - table.insert(field, value); - Ok(()) - } - } - Some(Item::Value(_)) => { - if dotted_path.len() == 1 { - table.insert(field, value); - Ok(()) - } else { - bail!("Cannot overwrite a non-table with a table") - } + } + Some(Item::Table(ref mut inner_table)) => { + if dotted_path.len() > 1 { + inner_table.set_dotted(true); + return add_value_with_dotted_path(inner_table, &dotted_path[1..], value); + } else { + table.insert(field, value); + Ok(()) } - Some(Item::ArrayOfTables(_)) => { - bail!("Cannot add key to a array of tables") + } + Some(Item::Value(_)) => { + if dotted_path.len() == 1 { + table.insert(field, value); + Ok(()) + } else { + bail!("Cannot overwrite a non-table with a table") } } - } + Some(Item::ArrayOfTables(_)) => { + bail!("Cannot add key to a array of tables") + } + }, } } - -#[cfg(test)] -mod table_header_adder_tests { - use super::*; - use toml_edit::{DocumentMut, value}; - - #[test] - fn test_one_element_table_header() { - let mut doc = "".to_string().parse::() - .expect("invalid doc"); - let table_header_path = vec!["moduleConfig"] - .iter().map(|s| s.to_string()).collect::>(); - let dotted_path = vec!["interpreters", "ruby", "enable"] - .iter().map(|s| s.to_string()).collect::>(); - _ = add_value_with_table_header_and_dotted_path(&mut doc, &table_header_path, &dotted_path, value(true)); - assert_eq!(doc.to_string(), "[moduleConfig]\ninterpreters.ruby.enable = true\n"); - } - - #[test] - fn test_two_element_table_header() { - let mut doc = "".to_string().parse::() - .expect("invalid doc"); - let table_header_path = vec!["moduleConfig", "interpreters"] - .iter().map(|s| s.to_string()).collect::>(); - let dotted_path = vec!["ruby", "enable"] - .iter().map(|s| s.to_string()).collect::>(); - _ = add_value_with_table_header_and_dotted_path(&mut doc, &table_header_path, &dotted_path, value(true)); - assert_eq!(doc.to_string(), "[moduleConfig.interpreters]\nruby.enable = true\n"); - } - - #[test] - fn test_preserve_existing() { - let mut doc = r#" -[moduleConfig] -bundles.go.enable = true - "#.to_string().parse::() - .expect("invalid doc"); - let table_header_path = vec!["moduleConfig"] - .iter().map(|s| s.to_string()).collect::>(); - let dotted_path = vec!["interpreters", "ruby", "enable"] - .iter().map(|s| s.to_string()).collect::>(); - _ = add_value_with_table_header_and_dotted_path(&mut doc, &table_header_path, &dotted_path, value(true)); - assert_eq!(doc.to_string(), "\n[moduleConfig]\nbundles.go.enable = true\ninterpreters.ruby.enable = true\n "); - } - - #[test] - fn test_preserve_existing_inner_tables() { - let mut doc = r#" -[moduleConfig] -interpreter.ruby.enable = true - "#.to_string().parse::() - .expect("invalid doc"); - let table_header_path = vec!["moduleConfig"] - .iter().map(|s| s.to_string()).collect::>(); - let dotted_path = vec!["interpreters", "ruby", "version"] - .iter().map(|s| s.to_string()).collect::>(); - _ = add_value_with_table_header_and_dotted_path(&mut doc, &table_header_path, &dotted_path, value("3.2.3")); - assert_eq!(doc.to_string(), "\n[moduleConfig]\ninterpreter.ruby.enable = true\ninterpreters.ruby.version = \"3.2.3\"\n "); - } - - #[test] - fn test_error_when_adding_key_to_non_table() { - let mut doc = r#" -[moduleConfig] -interpreters.ruby = "my dear" - "#.to_string().parse::() - .expect("invalid doc"); - let table_header_path = vec!["moduleConfig"] - .iter().map(|s| s.to_string()).collect::>(); - let dotted_path = vec!["interpreters", "ruby", "version"] - .iter().map(|s| s.to_string()).collect::>(); - let res = &add_value_with_table_header_and_dotted_path(&mut doc, &table_header_path, &dotted_path, value("3.2.3")); - assert!(res.is_err()); - assert_eq!(doc.to_string(), "\n[moduleConfig]\ninterpreters.ruby = \"my dear\"\n "); - } - -} \ No newline at end of file diff --git a/src/field_finder.rs b/src/field_finder.rs index 88ebdf2..140cef5 100644 --- a/src/field_finder.rs +++ b/src/field_finder.rs @@ -192,7 +192,6 @@ fn descend_array<'a>( #[cfg(test)] mod finger_tests { use super::*; - use toml_edit::DocumentMut; #[test] fn get_basic_field() { diff --git a/src/main.rs b/src/main.rs index 28da6d1..53e052d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,24 +31,24 @@ struct Args { } #[derive(Serialize, Deserialize)] +#[serde(tag = "op")] enum OpKind { /// Creates the field if it doesn't already exist and sets it #[serde(rename = "add")] - Add, + Add(AddOp), /// Gets the value at the specified path, returned as JSON #[serde(rename = "get")] - Get, + Get { path: String }, /// Removes the field if it exists #[serde(rename = "remove")] - Remove, + Remove { path: String }, } #[derive(Serialize, Deserialize)] -struct Op { - op: OpKind, - path: String, +struct AddOp { + path: Option, table_header_path: Option, dotted_path: Option, value: Option, @@ -102,7 +102,7 @@ fn do_edits( return_output: bool, ) -> Result<(String, Vec)> { // parse line as json - let json: Vec = from_str(msg)?; + let json: Vec = from_str(msg)?; // we need to re-read the file each time since the user might manually edit the // file and so we need to make sure we have the most up to date version. @@ -119,28 +119,20 @@ fn do_edits( let mut changed: bool = false; let mut outputs: Vec = vec![]; for op in json { - let path = op.path; - match op.op { - OpKind::Add => { - let value = op.value.context("error: expected value to add")?; + match op { + OpKind::Add(op) => { changed = true; - handle_add( - &path, - op.table_header_path, - op.dotted_path, - &value, - &mut doc - )?; + handle_add(&mut doc, op)?; outputs.push(json!("ok")); } - OpKind::Get => match traversal::traverse(TraverseOps::Get, &mut doc, &path) { + OpKind::Get { path } => match traversal::traverse(TraverseOps::Get, &mut doc, &path) { Ok(value) => outputs.push(value.unwrap_or_default()), Err(error) => { eprintln!("Error processing {}: {}", path, error); outputs.push(Value::Null) } }, - OpKind::Remove => { + OpKind::Remove { path } => { changed = true; handle_remove(&path, &mut doc)?; outputs.push(json!("ok")); diff --git a/src/remover.rs b/src/remover.rs index 8762b08..8db35d4 100644 --- a/src/remover.rs +++ b/src/remover.rs @@ -67,7 +67,7 @@ fn remove_in_inline_table(inline_table: &mut InlineTable, last_field: &str) -> R #[cfg(test)] mod remover_tests { use super::*; - use toml_edit::{DocumentMut, TomlError}; + use toml_edit::TomlError; fn get_dotreplit_content() -> Result { r#"test = "yo"