Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support for appending to array at path #11

Merged
merged 4 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 85 additions & 4 deletions src/adder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub fn handle_add(doc: &mut DocumentMut, op: AddOp) -> Result<()> {
.split('/')
.map(|s| s.to_string())
.collect::<Vec<String>>();
let dotted_path_vec =
let mut dotted_path_vec =
path.map(|p| p.split('/').map(|s| s.to_string()).collect::<Vec<String>>());
let field_value_json: JValue =
from_str(&value).context("parsing value field in add request")?;
Expand All @@ -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 => {
Expand Down Expand Up @@ -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::<DocumentMut>().unwrap();
Expand All @@ -188,9 +205,9 @@ test = "yo"

let op = AddOp {
path: field,
table_header_path: table_header_path,
table_header_path,
dotted_path: None,
value: value,
value,
};
let $result = handle_add(&mut doc, op);
$(
Expand Down Expand Up @@ -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
"#
);
}
59 changes: 47 additions & 12 deletions src/adder/table_header_adder.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use anyhow::{bail, Context, Result};
use anyhow::{anyhow, bail, Context, Result};
use toml_edit::{array, Item, Table, Value};

/*
Expand All @@ -19,13 +19,15 @@ pub fn add_value_with_table_header_and_dotted_path(
dotted_path: Option<Vec<String>>,
value: Item,
array_of_tables: bool,
append_array_at_path: bool,
) -> Result<()> {
match table_header_path.get(0) {
match table_header_path.first() {
None => {
add_value_with_dotted_path(
table,
dotted_path.context("Missing 'path' value")?.as_slice(),
value,
append_array_at_path,
)?;
Ok(())
}
Expand All @@ -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(())
}
Expand All @@ -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 {
Expand Down Expand Up @@ -107,17 +111,32 @@ fn add_value_with_dotted_path(
table: &mut Table,
dotted_path: &[String],
value: Item,
append_array_at_path: bool,
) -> Result<()> {
match dotted_path.get(0) {
match dotted_path.first() {
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(|_| ());
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(())
Expand All @@ -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);
return 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")
Expand Down
30 changes: 15 additions & 15 deletions src/field_finder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub enum DoInsert {

pub fn get_field<'a>(
path: &[String],
last_field: &String,
last_field: &str,
do_insert: DoInsert,
doc: &'a mut DocumentMut,
) -> Result<TomlValue<'a>> {
Expand All @@ -32,9 +32,9 @@ fn descend_table<'a>(
table: &'a mut Table,
path: &[String],
do_insert: DoInsert,
last_field: &String,
last_field: &str,
) -> Result<TomlValue<'a>> {
let segment = match path.get(0) {
let segment = match path.first() {
Some(segment) => segment,
None => return Ok(TomlValue::Table(table)),
};
Expand Down Expand Up @@ -67,7 +67,7 @@ fn descend_item<'a>(
item: &'a mut Item,
path: &[String],
do_insert: DoInsert,
last_field: &String,
last_field: &str,
) -> Result<TomlValue<'a>> {
match item {
Item::Table(table) => descend_table(table, path, do_insert, last_field),
Expand All @@ -81,7 +81,7 @@ fn descend_value<'a>(
value: &'a mut Value,
path: &[String],
do_insert: DoInsert,
last_field: &String,
last_field: &str,
) -> Result<TomlValue<'a>> {
match value {
Value::Array(array) => descend_array(array, path, do_insert, last_field),
Expand All @@ -101,9 +101,9 @@ fn descend_array_of_tables<'a>(
array: &'a mut ArrayOfTables,
path: &[String],
do_insert: DoInsert,
last_field: &String,
last_field: &str,
) -> Result<TomlValue<'a>> {
let segment = match path.get(0) {
let segment = match path.first() {
Some(segment) => segment,
None => return Ok(TomlValue::ArrayOfTables(array)),
};
Expand Down Expand Up @@ -141,9 +141,9 @@ fn descend_inline_table<'a>(
inline_table: &'a mut InlineTable,
path: &[String],
do_insert: DoInsert,
last_field: &String,
last_field: &str,
) -> Result<TomlValue<'a>> {
let segment = match path.get(0) {
let segment = match path.first() {
Some(segment) => segment,
None => return Ok(TomlValue::InlineTable(inline_table)),
};
Expand All @@ -163,9 +163,9 @@ fn descend_array<'a>(
array: &'a mut Array,
path: &[String],
do_insert: DoInsert,
last_field: &String,
last_field: &str,
) -> Result<TomlValue<'a>> {
let segment = match path.get(0) {
let segment = match path.first() {
Some(segment) => segment,
None => return Ok(TomlValue::Array(array)),
};
Expand Down Expand Up @@ -205,8 +205,8 @@ bla = "bla"

let mut doc = doc_string.parse::<DocumentMut>().unwrap();
let val = get_field(
&(vec!["foo".to_string()]),
&"bar".to_string(),
&["foo".to_string()],
"bar",
DoInsert::Yes,
&mut doc,
)
Expand All @@ -224,7 +224,7 @@ bla = "bla"
let doc_string = r#"test = [ 1 ]"#;
let mut doc = doc_string.parse::<DocumentMut>().unwrap();
let fields = ["test".to_string()];
let val = get_field(&(fields), &"1".to_string(), DoInsert::Yes, &mut doc).unwrap();
let val = get_field(&(fields), "1", DoInsert::Yes, &mut doc).unwrap();

if let TomlValue::Array(array) = val {
assert_eq!(array.len(), 1);
Expand All @@ -243,7 +243,7 @@ foo = "baz"
"#;
let mut doc = doc_string.parse::<DocumentMut>().unwrap();
let fields = ["test".to_string()];
let val = get_field(&(fields), &"2".to_string(), DoInsert::Yes, &mut doc).unwrap();
let val = get_field(&(fields), "2", DoInsert::Yes, &mut doc).unwrap();

if let TomlValue::ArrayOfTables(array) = val {
assert_eq!(array.len(), 2);
Expand Down
4 changes: 2 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ fn do_edits(

// 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.
let dotreplit_contents = match fs::read_to_string(&dotreplit_filepath) {
let dotreplit_contents = match fs::read_to_string(dotreplit_filepath) {
Ok(contents) => contents,
Err(err) if err.kind() == io::ErrorKind::NotFound => "".to_string(), // if .replit doesn't exist start with an empty one
Err(_) => return Err(anyhow!("error: reading file - {:?}", &dotreplit_filepath)),
Expand Down Expand Up @@ -146,7 +146,7 @@ fn do_edits(

// write the file back to disk
if changed {
fs::write(&dotreplit_filepath, doc.to_string())
fs::write(dotreplit_filepath, doc.to_string())
.with_context(|| format!("error: writing file: {:?}", &dotreplit_filepath))?;
}
Ok(("".to_string(), outputs))
Expand Down
2 changes: 1 addition & 1 deletion src/traversal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ pub fn traverse<'a>(
}

match op {
TraverseOps::Get => result?.to_value().map(|v| Some(v)),
TraverseOps::Get => result?.to_value().map(Some),
}
}

Expand Down
Loading