Skip to content

Commit cbe0300

Browse files
committed
feat: add /[] syntax to append to array at path
1 parent e68e7db commit cbe0300

File tree

2 files changed

+126
-10
lines changed

2 files changed

+126
-10
lines changed

src/adder.rs

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pub fn handle_add(doc: &mut DocumentMut, op: AddOp) -> Result<()> {
1919
.split('/')
2020
.map(|s| s.to_string())
2121
.collect::<Vec<String>>();
22-
let dotted_path_vec =
22+
let mut dotted_path_vec =
2323
path.map(|p| p.split('/').map(|s| s.to_string()).collect::<Vec<String>>());
2424
let field_value_json: JValue =
2525
from_str(&value).context("parsing value field in add request")?;
@@ -35,12 +35,20 @@ pub fn handle_add(doc: &mut DocumentMut, op: AddOp) -> Result<()> {
3535
} else {
3636
false
3737
};
38+
let append_array_at_path = match &mut dotted_path_vec {
39+
Some(path_vec) if path_vec.last().is_some_and(|key| key == "[]") => {
40+
path_vec.pop();
41+
true
42+
}
43+
_ => false,
44+
};
3845
table_header_adder::add_value_with_table_header_and_dotted_path(
3946
doc,
4047
&table_header_path_vec,
4148
dotted_path_vec,
4249
field_value_toml,
4350
array_of_tables,
51+
append_array_at_path,
4452
)
4553
}
4654
None => {
@@ -177,7 +185,16 @@ test = "yo"
177185
none = "all""#;
178186

179187
macro_rules! meta_add_test {
180-
($name:ident, $table_header_path:expr, $field:expr, $value:expr, $contents:expr, $expected:expr, $result:ident, $($assertion:stmt)*) => {
188+
(
189+
$name:ident,
190+
$table_header_path:expr,
191+
$field:expr,
192+
$value:expr,
193+
$contents:expr,
194+
$expected:expr,
195+
$result:ident,
196+
$($assertion:stmt)*
197+
) => {
181198
#[test]
182199
fn $name() {
183200
let mut doc = $contents.parse::<DocumentMut>().unwrap();
@@ -674,4 +691,68 @@ key = "second"
674691
torchvision = [{ index = "pytorch-cpu", marker = "platform_system == 'Linux'" }]
675692
"#
676693
);
694+
695+
add_table_header_test!(
696+
test_append_array_at_path_empty,
697+
Some("tool/uv/sources"),
698+
Some("torch/[]"),
699+
r#"
700+
{"index": "pytorch-cpu", "marker": "platform_system == 'Linux'"}
701+
"#,
702+
r#"
703+
"#,
704+
r#"
705+
[tool.uv.sources]
706+
torch = [{ index = "pytorch-cpu", marker = "platform_system == 'Linux'" }]
707+
"#
708+
);
709+
710+
add_table_header_test!(
711+
test_append_array_at_path_empty_dotted_path,
712+
Some("tool/uv/sources"),
713+
Some("torch/test/[]"),
714+
r#"
715+
{"index": "pytorch-cpu", "marker": "platform_system == 'Linux'"}
716+
"#,
717+
r#"
718+
"#,
719+
r#"
720+
[tool.uv.sources]
721+
torch.test = [{ index = "pytorch-cpu", marker = "platform_system == 'Linux'" }]
722+
"#
723+
);
724+
725+
add_table_header_test!(
726+
test_append_array_at_path_existing,
727+
Some("tool/uv/sources"),
728+
Some("torch/[]"),
729+
r#"
730+
{"index": "pytorch-cpu", "marker": "platform_system == 'Linux'"}
731+
"#,
732+
r#"
733+
[tool.uv.sources]
734+
torch = [{ index = "foo", marker = "platform_system == 'Windows'" }]
735+
"#,
736+
r#"
737+
[tool.uv.sources]
738+
torch = [{ index = "foo", marker = "platform_system == 'Windows'" }, { index = "pytorch-cpu", marker = "platform_system == 'Linux'" }]
739+
"#
740+
);
741+
742+
add_table_header_error_test!(
743+
test_append_array_at_path_existing_non_array_at_path,
744+
Some("tool/uv/sources"),
745+
Some("torch/[]"),
746+
r#"
747+
{"index": "pytorch-cpu", "marker": "platform_system == 'Linux'"}
748+
"#,
749+
r#"
750+
[tool.uv.sources]
751+
torch = 1
752+
"#,
753+
r#"
754+
[tool.uv.sources]
755+
torch = 1
756+
"#
757+
);
677758
}

src/adder/table_header_adder.rs

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use anyhow::{bail, Context, Result};
1+
use anyhow::{anyhow, bail, Context, Result};
22
use toml_edit::{array, Item, Table, Value};
33

44
/*
@@ -19,13 +19,15 @@ pub fn add_value_with_table_header_and_dotted_path(
1919
dotted_path: Option<Vec<String>>,
2020
value: Item,
2121
array_of_tables: bool,
22+
append_array_at_path: bool,
2223
) -> Result<()> {
2324
match table_header_path.first() {
2425
None => {
2526
add_value_with_dotted_path(
2627
table,
2728
dotted_path.context("Missing 'path' value")?.as_slice(),
2829
value,
30+
append_array_at_path,
2931
)?;
3032
Ok(())
3133
}
@@ -38,6 +40,7 @@ pub fn add_value_with_table_header_and_dotted_path(
3840
dotted_path,
3941
value,
4042
array_of_tables,
43+
append_array_at_path,
4144
)?;
4245
Ok(())
4346
}
@@ -51,6 +54,7 @@ pub fn add_value_with_table_header_and_dotted_path(
5154
dotted_path,
5255
value,
5356
array_of_tables,
57+
append_array_at_path,
5458
)?;
5559
table.insert(field, Item::Table(inner_table));
5660
} else {
@@ -107,6 +111,7 @@ fn add_value_with_dotted_path(
107111
table: &mut Table,
108112
dotted_path: &[String],
109113
value: Item,
114+
append_array_at_path: bool,
110115
) -> Result<()> {
111116
match dotted_path.first() {
112117
None => Ok(()),
@@ -115,9 +120,23 @@ fn add_value_with_dotted_path(
115120
if dotted_path.len() > 1 {
116121
let mut inner_table = Table::new();
117122
inner_table.set_dotted(true);
118-
add_value_with_dotted_path(&mut inner_table, &dotted_path[1..], value)?;
123+
add_value_with_dotted_path(
124+
&mut inner_table,
125+
&dotted_path[1..],
126+
value,
127+
append_array_at_path,
128+
)?;
119129
table.insert(field, Item::Table(inner_table));
120130
Ok(())
131+
} else if append_array_at_path {
132+
let mut arr = toml_edit::Array::new();
133+
arr.push(
134+
value
135+
.into_value()
136+
.map_err(|_| anyhow!("Cannot append non-value item to array"))?,
137+
);
138+
table.insert(field, Item::Value(Value::Array(arr)));
139+
Ok(())
121140
} else {
122141
table.insert(field, value);
123142
Ok(())
@@ -126,19 +145,35 @@ fn add_value_with_dotted_path(
126145
Some(Item::Table(ref mut inner_table)) => {
127146
if dotted_path.len() > 1 {
128147
inner_table.set_dotted(true);
129-
add_value_with_dotted_path(inner_table, &dotted_path[1..], value)
148+
add_value_with_dotted_path(
149+
inner_table,
150+
&dotted_path[1..],
151+
value,
152+
append_array_at_path,
153+
)
130154
} else {
131155
table.insert(field, value);
132156
Ok(())
133157
}
134158
}
135-
Some(Item::Value(_)) => {
136-
if dotted_path.len() == 1 {
137-
table.insert(field, value);
138-
Ok(())
139-
} else {
159+
Some(item @ Item::Value(_)) => {
160+
if dotted_path.len() != 1 {
140161
bail!("Cannot overwrite a non-table with a table")
141162
}
163+
164+
if append_array_at_path {
165+
let arr = item
166+
.as_array_mut()
167+
.context(format!("Cannot append non-array field '{field}'"))?;
168+
arr.push(
169+
value
170+
.into_value()
171+
.map_err(|_| anyhow!("Cannot append non-value item to array"))?,
172+
);
173+
} else {
174+
table.insert(field, value);
175+
}
176+
Ok(())
142177
}
143178
Some(Item::ArrayOfTables(_)) => {
144179
bail!("Cannot add key to a array of tables")

0 commit comments

Comments
 (0)