• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

vlinkz / nix-editor / 4085253443

pending completion
4085253443

push

github

Unknown Committer
Unknown Commit Message

362 of 397 relevant lines covered (91.18%)

1.65 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.63
/src/write.rs
1
use std::collections::HashMap;
2

3
use crate::parse::{findattr, getcfgbase, getkey};
4
use thiserror::Error;
5
use rnix::{self, SyntaxKind, SyntaxNode};
6

7
#[derive(Error, Debug)]
8
pub enum WriteError {
9
    #[error("Error while parsing.")]
10
    ParseError,
11
    #[error("No attributes.")]
12
    NoAttr,
13
    #[error("Error with array.")]
14
    ArrayError,
15
    #[error("Writing value to attribute set.")]
16
    WriteValueToSet,
17
}
18

19
pub fn write(f: &str, query: &str, val: &str) -> Result<String, WriteError> {
1✔
20
    let ast = rnix::Root::parse(f);
1✔
21
    let configbase = match getcfgbase(&ast.syntax()) {
3✔
22
        Some(x) => x,
1✔
23
        None => {
24
            return Err(WriteError::ParseError);
×
25
        }
26
    };
27
    if val.trim_start().starts_with('{') && val.trim_end().ends_with('}'){
1✔
28
        if let Some(x) = getcfgbase(&rnix::Root::parse(val).syntax()) {
3✔
29
            if x.kind() == SyntaxKind::NODE_ATTR_SET {
2✔
30
                return addattrval(f, &configbase, query, &x);
1✔
31
            }
32
        }
33
    }
34
    
35
    let outnode = match findattr(&configbase, query) {
2✔
36
        Some(x) => {
1✔
37
            if let Some(n) = x.children().last() {
2✔
38
                if n.kind() == SyntaxKind::NODE_ATTR_SET {
2✔
39
                    return Err(WriteError::WriteValueToSet);
×
40
                }
41
            }
42
            modvalue(&x, val).unwrap()
2✔
43
        }
44
        None => {
45
            let mut y = query.split('.').collect::<Vec<_>>();
2✔
46
            y.pop();
1✔
47
            let x = findattrset(&configbase, &y.join("."), 0);
1✔
48
            match x {
1✔
49
                Some((base, v, spaces)) => addvalue(
50
                    &base,
51
                    &format!("{}{}", " ".repeat(spaces), &query[v.len() + 1..]),
2✔
52
                    val,
53
                ),
54
                None => addvalue(&configbase, query, val),
2✔
55
            }
56
        }
57
    };
58
    Ok(outnode.to_string())
2✔
59
}
60

61
fn addvalue(configbase: &SyntaxNode, query: &str, val: &str) -> SyntaxNode {
1✔
62
    let mut index = configbase.green().children().len() - 2;
2✔
63
    // To find a better index for insertion, first find a matching node, then find the next newline token, after that, insert
64
    if let Some(x) = matchval(configbase, query, query.split('.').count()) {
2✔
65
        let i = configbase
4✔
66
            .green()
67
            .children()
68
            .position(|y| match y.into_node() {
3✔
69
                Some(y) => y.to_owned() == x.green().into_owned(),
2✔
70
                None => false,
1✔
71
            })
72
            .unwrap();
73
        let configgreen = configbase.green().to_owned();
1✔
74
        let configafter = &configgreen.children().collect::<Vec<_>>()[i..];
2✔
75
        for child in configafter {
2✔
76
            if let Some(x) = child.as_token() {
1✔
77
                if x.text().contains('\n') {
1✔
78
                    let cas = configafter.to_vec();
1✔
79
                    index = i + cas
3✔
80
                        .iter()
81
                        .position(|y| match y.as_token() {
3✔
82
                            Some(t) => t == x,
1✔
83
                            None => false,
1✔
84
                        })
85
                        .unwrap();
86
                    break;
87
                }
88
            }
89
        }
90
    }
91
    let input = rnix::Root::parse(format!("\n  {} = {};", &query, &val).as_str())
3✔
92
        .syntax();
93
    let input = input
2✔
94
        .green()
95
        .to_owned();
96
    if index == 0 {
1✔
97
        index += 1;
×
98
    };
99
    let new = configbase
3✔
100
        .green()
101
        .insert_child(index, rnix::NodeOrToken::Node(input.into_owned()));
1✔
102
    let replace = configbase.replace_with(new);
1✔
103
    rnix::Root::parse(&replace.to_string()).syntax()
3✔
104
}
105

106
// Currently indentation is badly done by inserting spaces, it should check the spaces of the previous attr instead
107
fn findattrset(
1✔
108
    configbase: &SyntaxNode,
109
    name: &str,
110
    spaces: usize,
111
) -> Option<(SyntaxNode, String, usize)> {
112
    for child in configbase.children() {
3✔
113
        if child.kind() == SyntaxKind::NODE_ATTRPATH_VALUE {
2✔
114
            // Now we have to read all the indent values from the key
115
            for subchild in child.children() {
2✔
116
                if subchild.kind() == SyntaxKind::NODE_ATTRPATH {
2✔
117
                    // We have a key, now we need to check if it's the one we're looking for
118
                    let key = getkey(&subchild);
1✔
119
                    let qkey = name
2✔
120
                        .split('.')
121
                        .map(|s| s.to_string())
2✔
122
                        .collect::<Vec<String>>();
123
                    if qkey == key {
2✔
124
                        // We have key, now lets find the attrset
125
                        for possibleset in child.children() {
3✔
126
                            if possibleset.kind() == SyntaxKind::NODE_ATTR_SET {
2✔
127
                                return Some((possibleset, name.to_string(), spaces + 2));
1✔
128
                            }
129
                        }
130
                        return None;
×
131
                    } else if qkey.len() > key.len() {
2✔
132
                        // We have a subkey, so we need to recurse
133
                        if key == qkey[0..key.len()] {
1✔
134
                            // We have a subkey, so we need to recurse
135
                            let subkey = &qkey[key.len()..].join(".").to_string();
1✔
136
                            let newbase = getcfgbase(&child).unwrap();
1✔
137
                            let subattr = findattrset(&newbase, subkey, spaces + 2);
2✔
138
                            if let Some((node, _, spaces)) = subattr {
1✔
139
                                return Some((node, name.to_string(), spaces));
1✔
140
                            }
141
                        }
142
                    }
143
                }
144
            }
145
        }
146
    }
147
    None
1✔
148
}
149

150
fn matchval(configbase: &SyntaxNode, query: &str, acc: usize) -> Option<SyntaxNode> {
1✔
151
    let qvec = &query
1✔
152
        .split('.')
153
        .map(|s| s.to_string())
2✔
154
        .collect::<Vec<String>>();
155
    let q = &qvec[..acc];
2✔
156
    for child in configbase.children() {
3✔
157
        if child.kind() == SyntaxKind::NODE_ATTRPATH_VALUE {
2✔
158
            for subchild in child.children() {
2✔
159
                if subchild.kind() == SyntaxKind::NODE_ATTRPATH {
2✔
160
                    let key = getkey(&subchild);
1✔
161
                    if key.len() >= q.len() && &key[..q.len()] == q {
3✔
162
                        return Some(child);
1✔
163
                    }
164
                }
165
            }
166
        }
167
    }
168
    if acc == 1 {
2✔
169
        None
1✔
170
    } else {
171
        matchval(configbase, query, acc - 1)
1✔
172
    }
173
}
174

175
fn modvalue(node: &SyntaxNode, val: &str) -> Option<SyntaxNode> {
1✔
176
    // First find the IDENT node
177
    for child in node.children() {
3✔
178
        if child.kind() != SyntaxKind::NODE_ATTRPATH {
2✔
179
            let c = &child;
1✔
180
            let input = val.to_string();
1✔
181
            let rep = &rnix::Root::parse(&input)
5✔
182
                .syntax()
183
                .children()
184
                .collect::<Vec<SyntaxNode>>()[0];
1✔
185
            let index = node
3✔
186
                .green()
187
                .children()
188
                .position(|y| match y.into_node() {
3✔
189
                    Some(y) => y.to_owned() == c.green().into_owned(),
2✔
190
                    None => false,
1✔
191
                })
192
                .unwrap();
193
            let replaced = node
2✔
194
                .green()
195
                .replace_child(index, rnix::NodeOrToken::Node(rep.green().into_owned()));
1✔
196
            let out = node.replace_with(replaced);
1✔
197
            let rnode = rnix::Root::parse(&out.to_string()).syntax();
2✔
198
            return Some(rnode);
1✔
199
        }
200
    }
201
    None
×
202
}
203

204
// Add an attribute to the config
205
fn addattrval(
1✔
206
    f: &str,
207
    configbase: &SyntaxNode,
208
    query: &str,
209
    val: &SyntaxNode,
210
) -> Result<String, WriteError> {
211
    let mut attrmap = HashMap::new();
1✔
212
    buildattrvec(val, vec![], &mut attrmap);
2✔
213
    let mut file = f.to_string();
1✔
214

215
    if attrmap.iter().any(|(key, _)| findattr(configbase, &format!("{}.{}", query, key)).is_some()) {
5✔
216
        for (key, val) in attrmap {
1✔
217
            match write(&file, &format!("{}.{}", query, key), &val) {
3✔
218
                Ok(x) => {
1✔
219
                    file = x
2✔
220
                },
221
                Err(e) => return Err(e),
×
222
            }
223
        }
224
    } else if let Some(c) = getcfgbase(&rnix::Root::parse(&file).syntax()) {
4✔
225
        file = addvalue(&c, query, &val.to_string()).to_string();
2✔
226
    }    
227
    Ok(file)
1✔
228
}
229

230
fn buildattrvec(val: &SyntaxNode, prefix: Vec<String>, map: &mut HashMap<String, String>) {
1✔
231
    for child in val.children() {
2✔
232
        if child.kind() == SyntaxKind::NODE_ATTRPATH_VALUE {
3✔
233
            if let Some(subchild) = child.children().last() {
1✔
234
                if subchild.kind() == SyntaxKind::NODE_ATTR_SET {
2✔
235
                    for c in child.children() {
1✔
236
                        if c.kind() == SyntaxKind::NODE_ATTRPATH {
2✔
237
                            let key = getkey(&c);
1✔
238
                            let mut newprefix = prefix.clone();
2✔
239
                            newprefix.append(&mut key.clone());
2✔
240
                            buildattrvec(&subchild, newprefix, map);
1✔
241
                            break;
242
                        }
243
                    }
244
                } else {
245
                    for c in child.children() {
2✔
246
                        if c.kind() == SyntaxKind::NODE_ATTRPATH {
2✔
247
                            let key = getkey(&c);
1✔
248
                            let mut newprefix = prefix.clone();
1✔
249
                            newprefix.append(&mut key.clone());
2✔
250
                            map.insert(newprefix.join("."), subchild.to_string());
1✔
251
                        }
252
                    }
253
                }
254
            }
255
        }
256
    }
257
}
258

259
pub fn addtoarr(f: &str, query: &str, items: Vec<String>) -> Result<String, WriteError> {
1✔
260
    let ast = rnix::Root::parse(f);
1✔
261
    let configbase = match getcfgbase(&ast.syntax()) {
3✔
262
        Some(x) => x,
1✔
263
        None => return Err(WriteError::ParseError),
×
264
    };
265
    let outnode = match findattr(&configbase, query) {
1✔
266
        Some(x) => match addtoarr_aux(&x, items) {
2✔
267
            Some(x) => x,
1✔
268
            None => return Err(WriteError::ArrayError),
×
269
        },
270
        // If no arrtibute is found, create a new one
271
        None => {
272
            let newval = addvalue(&configbase, query, "[\n  ]");
2✔
273
            return addtoarr(&newval.to_string(), query, items);
2✔
274
        }
275
    };
276
    Ok(outnode.to_string())
2✔
277
}
278

279
fn addtoarr_aux(node: &SyntaxNode, items: Vec<String>) -> Option<SyntaxNode> {
1✔
280
    for child in node.children() {
3✔
281
        if child.kind() == rnix::SyntaxKind::NODE_WITH {
2✔
282
            return addtoarr_aux(&child, items);
1✔
283
        }
284
        if child.kind() == SyntaxKind::NODE_LIST {
2✔
285
            let mut green = child.green().into_owned();
1✔
286

287
            for elem in items {
2✔
288
                let mut i = 0;
1✔
289
                for c in green.children() {
4✔
290
                    if c.to_string() == "]" {
2✔
291
                        if green.children().collect::<Vec<_>>()[i - 1]
5✔
292
                            .as_token()
293
                            .unwrap()
294
                            .to_string()
295
                            .contains('\n')
296
                        {
297
                            i -= 1;
1✔
298
                        }
299
                        green = green.insert_child(
2✔
300
                            i,
1✔
301
                            rnix::NodeOrToken::Node(
1✔
302
                                rnix::Root::parse(&format!("\n{}{}", " ".repeat(4), elem))
4✔
303
                                    .syntax()
304
                                    .green()
305
                                    .into_owned(),
306
                            ),
307
                        );
308
                        break;
309
                    }
310
                    i += 1;
2✔
311
                }
312
            }
313

314
            let index = match node.green().children().position(|x| match x.into_node() {
4✔
315
                Some(x) => x.to_owned() == child.green().into_owned(),
2✔
316
                None => false,
1✔
317
            }) {
318
                Some(x) => x,
1✔
319
                None => return None,
×
320
            };
321

322
            let replace = node
2✔
323
                .green()
324
                .replace_child(index, rnix::NodeOrToken::Node(green));
1✔
325
            let out = node.replace_with(replace);
1✔
326
            let output = rnix::Root::parse(&out.to_string()).syntax();
2✔
327
            return Some(output);
1✔
328
        }
329
    }
330
    None
×
331
}
332

333
pub fn rmarr(f: &str, query: &str, items: Vec<String>) -> Result<String, WriteError> {
1✔
334
    let ast = rnix::Root::parse(f);
1✔
335
    let configbase = match getcfgbase(&ast.syntax()) {
3✔
336
        Some(x) => x,
1✔
337
        None => return Err(WriteError::ParseError),
×
338
    };
339
    let outnode = match findattr(&configbase, query) {
1✔
340
        Some(x) => match rmarr_aux(&x, items) {
2✔
341
            Some(x) => x,
1✔
342
            None => return Err(WriteError::ArrayError),
×
343
        },
344
        None => return Err(WriteError::NoAttr),
×
345
    };
346
    Ok(outnode.to_string())
2✔
347
}
348

349
fn rmarr_aux(node: &SyntaxNode, items: Vec<String>) -> Option<SyntaxNode> {
1✔
350
    for child in node.children() {
3✔
351
        if child.kind() == rnix::SyntaxKind::NODE_WITH {
2✔
352
            return rmarr_aux(&child, items);
×
353
        }
354
        if child.kind() == SyntaxKind::NODE_LIST {
2✔
355
            let green = child.green().into_owned();
1✔
356
            let mut idx = vec![];
1✔
357
            for elem in green.children() {
3✔
358
                if elem.as_node() != None && items.contains(&elem.to_string()) {
2✔
359
                    let index = match green.children().position(|x| match x.into_node() {
3✔
360
                        Some(x) => {
1✔
361
                            if let Some(y) = elem.as_node() {
1✔
362
                                x.eq(y)
1✔
363
                            } else {
364
                                false
×
365
                            }
366
                        }
367
                        None => false,
1✔
368
                    }) {
369
                        Some(x) => x,
1✔
370
                        None => return None,
×
371
                    };
372
                    idx.push(index)
1✔
373
                }
374
            }
375
            let mut acc = 0;
1✔
376
            let mut replace = green;
1✔
377

378
            for i in idx {
2✔
379
                replace = replace.remove_child(i - acc);
1✔
380
                let mut v = vec![];
1✔
381
                for c in replace.children() {
3✔
382
                    v.push(c);
1✔
383
                }
384
                if let Some(x) = v.get(i - acc - 1).unwrap().as_token() {
2✔
385
                    if x.to_string().contains('\n') {
3✔
386
                        replace = replace.remove_child(i - acc - 1);
1✔
387
                        acc += 1;
1✔
388
                    }
389
                }
390
                acc += 1;
2✔
391
            }
392
            let out = child.replace_with(replace);
1✔
393

394
            let output = rnix::Root::parse(&out.to_string()).syntax();
2✔
395
            return Some(output);
1✔
396
        }
397
    }
398
    None
×
399
}
400

401
pub fn deref(f: &str, query: &str) -> Result<String, WriteError> {
1✔
402
    let ast = rnix::Root::parse(f);
1✔
403
    let configbase = match getcfgbase(&ast.syntax()) {
3✔
404
        Some(x) => x,
1✔
405
        None => return Err(WriteError::ParseError),
×
406
    };
407
    let outnode = match deref_aux(&configbase, query) {
1✔
408
        Some(x) => x,
1✔
409
        None => return Err(WriteError::NoAttr),
×
410
    };
411
    Ok(outnode.to_string())
2✔
412
}
413

414
fn deref_aux(configbase: &SyntaxNode, name: &str) -> Option<SyntaxNode> {
1✔
415
    for child in configbase.children() {
3✔
416
        if child.kind() == SyntaxKind::NODE_ATTRPATH_VALUE {
2✔
417
            // Now we have to read all the indent values from the key
418
            for subchild in child.children() {
2✔
419
                if subchild.kind() == SyntaxKind::NODE_ATTRPATH {
2✔
420
                    // We have a key, now we need to check if it's the one we're looking for
421
                    let key = getkey(&subchild);
1✔
422
                    let qkey = name
2✔
423
                        .split('.')
424
                        .map(|s| s.to_string())
2✔
425
                        .collect::<Vec<String>>();
426
                    if qkey == key {
2✔
427
                        let index =
3✔
428
                            match configbase
429
                                .green()
430
                                .children()
431
                                .position(|x| match x.into_node() {
3✔
432
                                    Some(x) => x.to_owned() == child.green().into_owned(),
2✔
433
                                    None => false,
1✔
434
                                }) {
435
                                Some(x) => x,
1✔
436
                                None => return None,
×
437
                            };
438
                        let mut del = configbase.green().remove_child(index);
1✔
439

440
                        // Remove leading newline if it still exists
441
                        if del.children().collect::<Vec<_>>()[index]
5✔
442
                            .to_string()
443
                            .contains('\n')
444
                        {
445
                            del = del.remove_child(index);
1✔
446
                        }
447
                        let out = configbase.replace_with(del);
2✔
448
                        return Some(rnix::Root::parse(&out.to_string()).syntax());
2✔
449
                    } else if qkey.len() > key.len() {
3✔
450
                        // We have a subkey, so we need to recurse
451
                        if key == qkey[0..key.len()] {
1✔
452
                            // We have a subkey, so we need to recurse
453
                            let subkey = &qkey[key.len()..].join(".").to_string();
1✔
454
                            let newbase = getcfgbase(&child).unwrap();
1✔
455
                            let subattr = deref_aux(&newbase, subkey);
2✔
456
                            if let Some(s) = subattr {
1✔
457
                                return Some(s);
1✔
458
                            }
459
                        }
460
                    }
461
                }
462
            }
463
        }
464
    }
465
    None
×
466
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc