Skip to content

Commit 3662fd5

Browse files
authored
Merge pull request #11 from replit/rmehri01/append-array-at-path
feat: support for appending to array at path
2 parents 040f3d2 + cbe0300 commit 3662fd5

File tree

5 files changed

+150
-34
lines changed

5 files changed

+150
-34
lines changed

src/adder.rs

Lines changed: 85 additions & 4 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();
@@ -188,9 +205,9 @@ test = "yo"
188205

189206
let op = AddOp {
190207
path: field,
191-
table_header_path: table_header_path,
208+
table_header_path,
192209
dotted_path: None,
193-
value: value,
210+
value,
194211
};
195212
let $result = handle_add(&mut doc, op);
196213
$(
@@ -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: 47 additions & 12 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<()> {
23-
match table_header_path.get(0) {
24+
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,17 +111,32 @@ 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<()> {
111-
match dotted_path.get(0) {
116+
match dotted_path.first() {
112117
None => Ok(()),
113118
Some(field) => match table.get_mut(field) {
114119
None | Some(Item::None) => {
115120
if dotted_path.len() > 1 {
116121
let mut inner_table = Table::new();
117122
inner_table.set_dotted(true);
118-
return add_value_with_dotted_path(&mut inner_table, &dotted_path[1..], value)
119-
.map(|_| table.insert(field, Item::Table(inner_table)))
120-
.map(|_| ());
123+
add_value_with_dotted_path(
124+
&mut inner_table,
125+
&dotted_path[1..],
126+
value,
127+
append_array_at_path,
128+
)?;
129+
table.insert(field, Item::Table(inner_table));
130+
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-
return 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")

src/field_finder.rs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pub enum DoInsert {
1919

2020
pub fn get_field<'a>(
2121
path: &[String],
22-
last_field: &String,
22+
last_field: &str,
2323
do_insert: DoInsert,
2424
doc: &'a mut DocumentMut,
2525
) -> Result<TomlValue<'a>> {
@@ -32,9 +32,9 @@ fn descend_table<'a>(
3232
table: &'a mut Table,
3333
path: &[String],
3434
do_insert: DoInsert,
35-
last_field: &String,
35+
last_field: &str,
3636
) -> Result<TomlValue<'a>> {
37-
let segment = match path.get(0) {
37+
let segment = match path.first() {
3838
Some(segment) => segment,
3939
None => return Ok(TomlValue::Table(table)),
4040
};
@@ -67,7 +67,7 @@ fn descend_item<'a>(
6767
item: &'a mut Item,
6868
path: &[String],
6969
do_insert: DoInsert,
70-
last_field: &String,
70+
last_field: &str,
7171
) -> Result<TomlValue<'a>> {
7272
match item {
7373
Item::Table(table) => descend_table(table, path, do_insert, last_field),
@@ -81,7 +81,7 @@ fn descend_value<'a>(
8181
value: &'a mut Value,
8282
path: &[String],
8383
do_insert: DoInsert,
84-
last_field: &String,
84+
last_field: &str,
8585
) -> Result<TomlValue<'a>> {
8686
match value {
8787
Value::Array(array) => descend_array(array, path, do_insert, last_field),
@@ -101,9 +101,9 @@ fn descend_array_of_tables<'a>(
101101
array: &'a mut ArrayOfTables,
102102
path: &[String],
103103
do_insert: DoInsert,
104-
last_field: &String,
104+
last_field: &str,
105105
) -> Result<TomlValue<'a>> {
106-
let segment = match path.get(0) {
106+
let segment = match path.first() {
107107
Some(segment) => segment,
108108
None => return Ok(TomlValue::ArrayOfTables(array)),
109109
};
@@ -141,9 +141,9 @@ fn descend_inline_table<'a>(
141141
inline_table: &'a mut InlineTable,
142142
path: &[String],
143143
do_insert: DoInsert,
144-
last_field: &String,
144+
last_field: &str,
145145
) -> Result<TomlValue<'a>> {
146-
let segment = match path.get(0) {
146+
let segment = match path.first() {
147147
Some(segment) => segment,
148148
None => return Ok(TomlValue::InlineTable(inline_table)),
149149
};
@@ -163,9 +163,9 @@ fn descend_array<'a>(
163163
array: &'a mut Array,
164164
path: &[String],
165165
do_insert: DoInsert,
166-
last_field: &String,
166+
last_field: &str,
167167
) -> Result<TomlValue<'a>> {
168-
let segment = match path.get(0) {
168+
let segment = match path.first() {
169169
Some(segment) => segment,
170170
None => return Ok(TomlValue::Array(array)),
171171
};
@@ -205,8 +205,8 @@ bla = "bla"
205205

206206
let mut doc = doc_string.parse::<DocumentMut>().unwrap();
207207
let val = get_field(
208-
&(vec!["foo".to_string()]),
209-
&"bar".to_string(),
208+
&["foo".to_string()],
209+
"bar",
210210
DoInsert::Yes,
211211
&mut doc,
212212
)
@@ -224,7 +224,7 @@ bla = "bla"
224224
let doc_string = r#"test = [ 1 ]"#;
225225
let mut doc = doc_string.parse::<DocumentMut>().unwrap();
226226
let fields = ["test".to_string()];
227-
let val = get_field(&(fields), &"1".to_string(), DoInsert::Yes, &mut doc).unwrap();
227+
let val = get_field(&(fields), "1", DoInsert::Yes, &mut doc).unwrap();
228228

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

248248
if let TomlValue::ArrayOfTables(array) = val {
249249
assert_eq!(array.len(), 2);

src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ fn do_edits(
106106

107107
// we need to re-read the file each time since the user might manually edit the
108108
// file and so we need to make sure we have the most up to date version.
109-
let dotreplit_contents = match fs::read_to_string(&dotreplit_filepath) {
109+
let dotreplit_contents = match fs::read_to_string(dotreplit_filepath) {
110110
Ok(contents) => contents,
111111
Err(err) if err.kind() == io::ErrorKind::NotFound => "".to_string(), // if .replit doesn't exist start with an empty one
112112
Err(_) => return Err(anyhow!("error: reading file - {:?}", &dotreplit_filepath)),
@@ -146,7 +146,7 @@ fn do_edits(
146146

147147
// write the file back to disk
148148
if changed {
149-
fs::write(&dotreplit_filepath, doc.to_string())
149+
fs::write(dotreplit_filepath, doc.to_string())
150150
.with_context(|| format!("error: writing file: {:?}", &dotreplit_filepath))?;
151151
}
152152
Ok(("".to_string(), outputs))

src/traversal.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ pub fn traverse<'a>(
5353
}
5454

5555
match op {
56-
TraverseOps::Get => result?.to_value().map(|v| Some(v)),
56+
TraverseOps::Get => result?.to_value().map(Some),
5757
}
5858
}
5959

0 commit comments

Comments
 (0)