bytebraise_syntax/editor/
list_var_editor.rs

1use crate::parser::parse_bitbake_from_str;
2use crate::syntax::ast::nodes::Root;
3use crate::syntax::ast::{AstNode, AstToken};
4use crate::syntax::ted::Position;
5use crate::syntax::{make, ted};
6use anyhow::Context;
7use maplit::hashset;
8use std::collections::HashSet;
9use std::fs::{File, OpenOptions};
10use std::io::{Read, Write};
11use std::path::{Path, PathBuf};
12
13enum EditActionKind {
14    Set,
15    Add,
16    Remove,
17}
18
19// TODO: optionally make set
20struct EditAction {
21    values: HashSet<String>,
22    kind: EditActionKind,
23}
24
25pub struct ListVarEditor {
26    root: Root,
27    path: PathBuf,
28    var: String,
29    actions: Vec<EditAction>,
30}
31
32impl ListVarEditor {
33    pub fn from_file<P: AsRef<Path>>(path: P, var: String) -> anyhow::Result<Self> {
34        let path = path.as_ref();
35
36        let mut source = String::new();
37        File::open(path)
38            .with_context(|| format!("failed to read {:?}", &path))?
39            .read_to_string(&mut source)?;
40
41        let root = parse_bitbake_from_str(&source).clone_for_update();
42        let assignments = root.identifier_assignments(&var).collect::<Vec<_>>();
43        assert!(
44            assignments.len() < 2,
45            "editing a file with multiple assignments to same variable not supported yet"
46        );
47
48        Ok(Self {
49            root,
50            path: path.to_path_buf(),
51            var,
52            actions: vec![],
53        })
54    }
55
56    pub fn add_value(&mut self, val: String) {
57        self.actions.push(EditAction {
58            kind: EditActionKind::Add,
59            values: hashset![val],
60        })
61    }
62
63    pub fn remove_value(&mut self, val: String) {
64        self.actions.push(EditAction {
65            kind: EditActionKind::Remove,
66            values: hashset![val],
67        })
68    }
69
70    pub fn commit(&mut self) -> anyhow::Result<()> {
71        let assignments = self
72            .root
73            .identifier_assignments(&self.var)
74            .collect::<Vec<_>>();
75
76        // TODO split value
77        let values: HashSet<String> = match assignments.len() {
78            0 => hashset![],
79            1 => assignments
80                .first()
81                .unwrap()
82                .right()
83                .lines()
84                .map(|v| v.trim().to_owned())
85                .filter(|v| !v.is_empty())
86                .collect(),
87            _ => unreachable!(),
88        };
89
90        let values = self
91            .actions
92            .iter()
93            .fold(values, |mut values, action| match action.kind {
94                EditActionKind::Set => action.values.clone(),
95                EditActionKind::Add => {
96                    values.extend(action.values.clone());
97                    values
98                }
99                EditActionKind::Remove => {
100                    values.retain(|v| !action.values.contains(v));
101                    values
102                }
103            });
104
105        // TODO optional sort
106        let mut values = values.into_iter().collect::<Vec<_>>();
107        values.sort();
108
109        let value_node = make::quoted_value_from_slice(&values);
110
111        if assignments.is_empty() {
112            let last_node = self.root.syntax().last_child_or_token().unwrap();
113
114            // TODO operator
115            let new_assignment = make::assignment(
116                self.var.to_string(),
117                String::from("?="),
118                value_node.syntax().to_string(),
119            )
120            .clone_for_update();
121
122            ted::insert(Position::after(last_node), new_assignment.syntax());
123        } else {
124            let assignment = assignments.first().unwrap();
125            ted::replace(assignment.right().syntax(), value_node.syntax());
126        }
127
128        let mut f = OpenOptions::new()
129            .write(true)
130            .truncate(true)
131            .open(&self.path)?;
132        f.write_all(self.root.syntax().to_string().as_bytes())?;
133
134        Ok(())
135    }
136}