bytebraise_syntax/editor/
list_var_editor.rs1use 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
19struct 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 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 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 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}