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

vlinkz / nix-editor / 4658825839

pending completion
4658825839

push

github

Unknown Committer
Unknown Commit Message

36 of 36 new or added lines in 1 file covered. (100.0%)

374 of 413 relevant lines covered (90.56%)

1.76 hits per line

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

90.64
/src/write.rs
1
use crate::parse::{findattr, getcfgbase, getkey};
2
use rnix::{self, SyntaxKind, SyntaxNode};
3
use std::collections::HashMap;
4
use thiserror::Error;
5

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

18
pub fn write(f: &str, query: &str, val: &str) -> Result<String, WriteError> {
1✔
19
    let ast = rnix::Root::parse(f);
1✔
20
    let configbase = match getcfgbase(&ast.syntax()) {
3✔
21
        Some(x) => x,
1✔
22
        None => {
23
            return Err(WriteError::ParseError);
×
24
        }
25
    };
26
    if val.trim_start().starts_with('{') && val.trim_end().ends_with('}') {
1✔
27
        if let Some(x) = getcfgbase(&rnix::Root::parse(val).syntax()) {
3✔
28
            if x.kind() == SyntaxKind::NODE_ATTR_SET {
2✔
29
                return addattrval(f, &configbase, query, &x);
1✔
30
            }
31
        }
32
    }
33

34
    let outnode = match findattr(&configbase, query) {
2✔
35
        Some(x) => {
1✔
36
            if let Some(n) = x.children().last() {
2✔
37
                if n.kind() == SyntaxKind::NODE_ATTR_SET {
2✔
38
                    return Err(WriteError::WriteValueToSet);
×
39
                }
40
            }
41
            modvalue(&x, val).unwrap()
2✔
42
        }
43
        None => {
44
            let mut y = query.split('.').collect::<Vec<_>>();
2✔
45
            y.pop();
1✔
46
            let x = findattrset(&configbase, &y.join("."), 0);
1✔
47
            match x {
1✔
48
                Some((base, attr_prefix, spaces)) => {
1✔
49
                    if let Some(stripped) = query.strip_prefix(&format!("{}.", attr_prefix)) {
3✔
50
                        addvalue(&base, &format!("{}{}", " ".repeat(spaces), stripped), val)
3✔
51
                    } else {
52
                        addvalue(&configbase, query, val)
×
53
                    }
54
                }
55
                None => {
56
                    addvalue(&configbase, query, val)
2✔
57
                }
58
            }
59
        }
60
    };
61
    Ok(outnode.to_string())
2✔
62
}
63

64
fn addvalue(configbase: &SyntaxNode, query: &str, val: &str) -> SyntaxNode {
1✔
65
    let mut index = configbase.green().children().len() - 2;
2✔
66
    // To find a better index for insertion, first find a matching node, then find the next newline token, after that, insert
67
    if let Some(x) = matchval(configbase, query, query.split('.').count()) {
2✔
68
        let i = configbase
4✔
69
            .green()
70
            .children()
71
            .position(|y| match y.into_node() {
3✔
72
                Some(y) => y.to_owned() == x.green().into_owned(),
2✔
73
                None => false,
1✔
74
            })
75
            .unwrap();
76
        let configgreen = configbase.green().clone();
1✔
77
        let configafter = &configgreen.children().collect::<Vec<_>>()[i..];
2✔
78
        for child in configafter {
2✔
79
            if let Some(x) = child.as_token() {
1✔
80
                if x.text().contains('\n') {
1✔
81
                    let cas = configafter.to_vec();
1✔
82
                    index = i + cas
3✔
83
                        .iter()
84
                        .position(|y| match y.as_token() {
3✔
85
                            Some(t) => t == x,
1✔
86
                            None => false,
1✔
87
                        })
88
                        .unwrap();
89
                    break;
90
                }
91
            }
92
        }
93
    }
94
    let input = rnix::Root::parse(format!("\n  {} = {};", &query, &val).as_str()).syntax();
1✔
95
    let input = input.green().clone();
1✔
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
            for subchild in child.children() {
2✔
115
                if subchild.kind() == SyntaxKind::NODE_ATTRPATH {
2✔
116
                    let key = getkey(&subchild);
1✔
117
                    let qkey = name
2✔
118
                        .split('.')
119
                        .map(|s| s.to_string())
2✔
120
                        .collect::<Vec<String>>();
121
                    if qkey == key {
2✔
122
                        match findnestedattrset(&child) {
1✔
123
                            Some(x) => {
1✔
124
                                return Some((x, name.to_string(), spaces + 2));
1✔
125
                            }
126
                            None => {
127
                                return None;
×
128
                            }
129
                        }
130
                    } else if qkey.len() > key.len() && qkey[0..key.len()] == key {
3✔
131
                        let subkey = qkey[key.len()..].join(".");
1✔
132
                        let newbase = getcfgbase(&child).unwrap();
2✔
133
                        let subattr = findattrset(&newbase, &subkey, spaces + 2);
2✔
134
                        match subattr {
1✔
135
                            Some((node, _, spaces)) => {
1✔
136
                                return Some((node, name.to_string(), spaces));
1✔
137
                            }
138
                            None => match findnestedattrset(&child) {
2✔
139
                                Some(x) => {
1✔
140
                                    return Some((x, key.join("."), spaces + 2));
1✔
141
                                }
142
                                None => {
143
                                    return None;
×
144
                                }
145
                            }
146
                        }
147
                    }
148
                }
149
            }
150
        }
151
    }
152
    None
1✔
153
}
154

155
// Recursively check children of NODE_APPLY and NODE_LAMBDA for NODE_ATTR_SET
156
fn findnestedattrset(configbase: &SyntaxNode) -> Option<SyntaxNode> {
1✔
157
    for child in configbase.children() {
4✔
158
        if child.kind() == SyntaxKind::NODE_APPLY
4✔
159
            || child.kind() == SyntaxKind::NODE_LAMBDA
2✔
160
            || child.kind() == SyntaxKind::NODE_WITH
2✔
161
        {
162
            return findnestedattrset(&child);
×
163
        } else if child.kind() == SyntaxKind::NODE_ATTR_SET {
2✔
164
            return Some(child);
1✔
165
        }
166
    }
167
    None
×
168
}
169

170
fn matchval(configbase: &SyntaxNode, query: &str, acc: usize) -> Option<SyntaxNode> {
1✔
171
    let qvec = &query
1✔
172
        .split('.')
173
        .map(|s| s.to_string())
2✔
174
        .collect::<Vec<String>>();
175
    let q = &qvec[..acc];
2✔
176
    for child in configbase.children() {
3✔
177
        if child.kind() == SyntaxKind::NODE_ATTRPATH_VALUE {
2✔
178
            for subchild in child.children() {
2✔
179
                if subchild.kind() == SyntaxKind::NODE_ATTRPATH {
2✔
180
                    let key = getkey(&subchild);
1✔
181
                    if key.len() >= q.len() && &key[..q.len()] == q {
3✔
182
                        return Some(child);
1✔
183
                    }
184
                }
185
            }
186
        }
187
    }
188
    if acc == 1 {
2✔
189
        None
1✔
190
    } else {
191
        matchval(configbase, query, acc - 1)
1✔
192
    }
193
}
194

195
fn modvalue(node: &SyntaxNode, val: &str) -> Option<SyntaxNode> {
1✔
196
    // First find the IDENT node
197
    for child in node.children() {
3✔
198
        if child.kind() != SyntaxKind::NODE_ATTRPATH {
2✔
199
            let c = &child;
1✔
200
            let input = val.to_string();
1✔
201
            let rep = &rnix::Root::parse(&input)
5✔
202
                .syntax()
203
                .children()
204
                .collect::<Vec<SyntaxNode>>()[0];
1✔
205
            let index = node
3✔
206
                .green()
207
                .children()
208
                .position(|y| match y.into_node() {
3✔
209
                    Some(y) => y.to_owned() == c.green().into_owned(),
2✔
210
                    None => false,
1✔
211
                })
212
                .unwrap();
213
            let replaced = node
2✔
214
                .green()
215
                .replace_child(index, rnix::NodeOrToken::Node(rep.green().into_owned()));
1✔
216
            let out = node.replace_with(replaced);
1✔
217
            let rnode = rnix::Root::parse(&out.to_string()).syntax();
2✔
218
            return Some(rnode);
1✔
219
        }
220
    }
221
    None
×
222
}
223

224
// Add an attribute to the config
225
fn addattrval(
1✔
226
    f: &str,
227
    configbase: &SyntaxNode,
228
    query: &str,
229
    val: &SyntaxNode,
230
) -> Result<String, WriteError> {
231
    let mut attrmap = HashMap::new();
1✔
232
    buildattrvec(val, vec![], &mut attrmap);
2✔
233
    let mut file = f.to_string();
1✔
234

235
    if attrmap
3✔
236
        .iter()
237
        .any(|(key, _)| findattr(configbase, &format!("{}.{}", query, key)).is_some())
3✔
238
    {
239
        for (key, val) in attrmap {
1✔
240
            match write(&file, &format!("{}.{}", query, key), &val) {
3✔
241
                Ok(x) => file = x,
2✔
242
                Err(e) => return Err(e),
×
243
            }
244
        }
245
    } else if let Some(c) = getcfgbase(&rnix::Root::parse(&file).syntax()) {
4✔
246
        file = addvalue(&c, query, &val.to_string()).to_string();
2✔
247
    }
248
    Ok(file)
1✔
249
}
250

251
fn buildattrvec(val: &SyntaxNode, prefix: Vec<String>, map: &mut HashMap<String, String>) {
1✔
252
    for child in val.children() {
2✔
253
        if child.kind() == SyntaxKind::NODE_ATTRPATH_VALUE {
3✔
254
            if let Some(subchild) = child.children().last() {
1✔
255
                if subchild.kind() == SyntaxKind::NODE_ATTR_SET {
2✔
256
                    for c in child.children() {
1✔
257
                        if c.kind() == SyntaxKind::NODE_ATTRPATH {
2✔
258
                            let key = getkey(&c);
1✔
259
                            let mut newprefix = prefix.clone();
2✔
260
                            newprefix.append(&mut key.clone());
2✔
261
                            buildattrvec(&subchild, newprefix, map);
1✔
262
                            break;
263
                        }
264
                    }
265
                } else {
266
                    for c in child.children() {
2✔
267
                        if c.kind() == SyntaxKind::NODE_ATTRPATH {
2✔
268
                            let key = getkey(&c);
1✔
269
                            let mut newprefix = prefix.clone();
1✔
270
                            newprefix.append(&mut key.clone());
2✔
271
                            map.insert(newprefix.join("."), subchild.to_string());
1✔
272
                        }
273
                    }
274
                }
275
            }
276
        }
277
    }
278
}
279

280
pub fn addtoarr(f: &str, query: &str, items: Vec<String>) -> Result<String, WriteError> {
2✔
281
    let ast = rnix::Root::parse(f);
2✔
282
    let configbase = match getcfgbase(&ast.syntax()) {
6✔
283
        Some(x) => x,
2✔
284
        None => return Err(WriteError::ParseError),
×
285
    };
286
    let outnode = match findattr(&configbase, query) {
2✔
287
        Some(x) => match addtoarr_aux(&x, items) {
3✔
288
            Some(x) => x,
2✔
289
            None => return Err(WriteError::ArrayError),
×
290
        },
291
        // If no arrtibute is found, create a new one
292
        None => {
293
            let newval = addvalue(&configbase, query, "[\n  ]");
2✔
294
            return addtoarr(&newval.to_string(), query, items);
2✔
295
        }
296
    };
297
    Ok(outnode.to_string())
4✔
298
}
299

300
fn addtoarr_aux(node: &SyntaxNode, items: Vec<String>) -> Option<SyntaxNode> {
1✔
301
    for child in node.children() {
3✔
302
        if child.kind() == rnix::SyntaxKind::NODE_WITH {
3✔
303
            return addtoarr_aux(&child, items);
1✔
304
        }
305
        if child.kind() == SyntaxKind::NODE_LIST {
2✔
306
            let mut green = child.green().into_owned();
1✔
307

308
            for elem in items {
2✔
309
                let mut i = 0;
1✔
310
                for c in green.children() {
4✔
311
                    if c.to_string() == "]" {
2✔
312
                        if green.children().collect::<Vec<_>>()[i - 1]
5✔
313
                            .as_token()
314
                            .unwrap()
315
                            .to_string()
316
                            .contains('\n')
317
                        {
318
                            i -= 1;
1✔
319
                        }
320
                        green = green.insert_child(
3✔
321
                            i,
1✔
322
                            rnix::NodeOrToken::Node(
1✔
323
                                rnix::Root::parse(&format!("\n{}{}", " ".repeat(4), elem))
4✔
324
                                    .syntax()
325
                                    .green()
326
                                    .into_owned(),
327
                            ),
328
                        );
329
                        break;
330
                    }
331
                    i += 1;
2✔
332
                }
333
            }
334

335
            let index = match node.green().children().position(|x| match x.into_node() {
4✔
336
                Some(x) => x.to_owned() == child.green().into_owned(),
2✔
337
                None => false,
1✔
338
            }) {
339
                Some(x) => x,
1✔
340
                None => return None,
×
341
            };
342

343
            let replace = node
2✔
344
                .green()
345
                .replace_child(index, rnix::NodeOrToken::Node(green));
1✔
346
            let out = node.replace_with(replace);
1✔
347
            let output = rnix::Root::parse(&out.to_string()).syntax();
2✔
348
            return Some(output);
1✔
349
        }
350
    }
351
    None
×
352
}
353

354
pub fn rmarr(f: &str, query: &str, items: Vec<String>) -> Result<String, WriteError> {
1✔
355
    let ast = rnix::Root::parse(f);
1✔
356
    let configbase = match getcfgbase(&ast.syntax()) {
3✔
357
        Some(x) => x,
1✔
358
        None => return Err(WriteError::ParseError),
×
359
    };
360
    let outnode = match findattr(&configbase, query) {
1✔
361
        Some(x) => match rmarr_aux(&x, items) {
2✔
362
            Some(x) => x,
1✔
363
            None => return Err(WriteError::ArrayError),
×
364
        },
365
        None => return Err(WriteError::NoAttr),
×
366
    };
367
    Ok(outnode.to_string())
2✔
368
}
369

370
fn rmarr_aux(node: &SyntaxNode, items: Vec<String>) -> Option<SyntaxNode> {
1✔
371
    for child in node.children() {
3✔
372
        if child.kind() == rnix::SyntaxKind::NODE_WITH {
2✔
373
            return rmarr_aux(&child, items);
×
374
        }
375
        if child.kind() == SyntaxKind::NODE_LIST {
2✔
376
            let green = child.green().into_owned();
1✔
377
            let mut idx = vec![];
1✔
378
            for elem in green.children() {
3✔
379
                if elem.as_node().is_some() && items.contains(&elem.to_string()) {
2✔
380
                    let index = match green.children().position(|x| match x.into_node() {
3✔
381
                        Some(x) => {
1✔
382
                            if let Some(y) = elem.as_node() {
1✔
383
                                x.eq(y)
1✔
384
                            } else {
385
                                false
×
386
                            }
387
                        }
388
                        None => false,
1✔
389
                    }) {
390
                        Some(x) => x,
1✔
391
                        None => return None,
×
392
                    };
393
                    idx.push(index)
1✔
394
                }
395
            }
396
            let mut acc = 0;
1✔
397
            let mut replace = green;
1✔
398

399
            for i in idx {
2✔
400
                replace = replace.remove_child(i - acc);
1✔
401
                let mut v = vec![];
1✔
402
                for c in replace.children() {
3✔
403
                    v.push(c);
1✔
404
                }
405
                if let Some(x) = v.get(i - acc - 1).unwrap().as_token() {
2✔
406
                    if x.to_string().contains('\n') {
3✔
407
                        replace = replace.remove_child(i - acc - 1);
1✔
408
                        acc += 1;
1✔
409
                    }
410
                }
411
                acc += 1;
2✔
412
            }
413
            let out = child.replace_with(replace);
1✔
414

415
            let output = rnix::Root::parse(&out.to_string()).syntax();
2✔
416
            return Some(output);
1✔
417
        }
418
    }
419
    None
×
420
}
421

422
pub fn deref(f: &str, query: &str) -> Result<String, WriteError> {
1✔
423
    let ast = rnix::Root::parse(f);
1✔
424
    let configbase = match getcfgbase(&ast.syntax()) {
3✔
425
        Some(x) => x,
1✔
426
        None => return Err(WriteError::ParseError),
×
427
    };
428
    let outnode = match deref_aux(&configbase, query) {
1✔
429
        Some(x) => x,
1✔
430
        None => return Err(WriteError::NoAttr),
×
431
    };
432
    Ok(outnode.to_string())
2✔
433
}
434

435
fn deref_aux(configbase: &SyntaxNode, name: &str) -> Option<SyntaxNode> {
1✔
436
    for child in configbase.children() {
3✔
437
        if child.kind() == SyntaxKind::NODE_ATTRPATH_VALUE {
2✔
438
            // Now we have to read all the indent values from the key
439
            for subchild in child.children() {
2✔
440
                if subchild.kind() == SyntaxKind::NODE_ATTRPATH {
2✔
441
                    // We have a key, now we need to check if it's the one we're looking for
442
                    let key = getkey(&subchild);
1✔
443
                    let qkey = name
2✔
444
                        .split('.')
445
                        .map(|s| s.to_string())
2✔
446
                        .collect::<Vec<String>>();
447
                    if qkey == key {
2✔
448
                        let index =
3✔
449
                            match configbase
450
                                .green()
451
                                .children()
452
                                .position(|x| match x.into_node() {
3✔
453
                                    Some(x) => x.to_owned() == child.green().into_owned(),
2✔
454
                                    None => false,
1✔
455
                                }) {
456
                                Some(x) => x,
1✔
457
                                None => return None,
×
458
                            };
459
                        let mut del = configbase.green().remove_child(index);
1✔
460

461
                        // Remove leading newline if it still exists
462
                        if del.children().collect::<Vec<_>>()[index]
5✔
463
                            .to_string()
464
                            .contains('\n')
465
                        {
466
                            del = del.remove_child(index);
1✔
467
                        }
468
                        let out = configbase.replace_with(del);
2✔
469
                        return Some(rnix::Root::parse(&out.to_string()).syntax());
2✔
470
                    } else if qkey.len() > key.len() {
3✔
471
                        // We have a subkey, so we need to recurse
472
                        if key == qkey[0..key.len()] {
1✔
473
                            // We have a subkey, so we need to recurse
474
                            let subkey = &qkey[key.len()..].join(".").to_string();
1✔
475
                            let newbase = getcfgbase(&child).unwrap();
1✔
476
                            let subattr = deref_aux(&newbase, subkey);
2✔
477
                            if let Some(s) = subattr {
1✔
478
                                return Some(s);
1✔
479
                            }
480
                        }
481
                    }
482
                }
483
            }
484
        }
485
    }
486
    None
×
487
}
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