1use crate::record::{RecordView, RecordViewMut};
4use crate::Error;
5use scheme_edit::{string_lit, sym, Document, Node};
6
7#[derive(Debug)]
9pub struct ModuleFile {
10 doc: Document,
11}
12
13impl ModuleFile {
14 pub fn parse(src: &str) -> Result<Self, Error> {
15 Ok(Self {
16 doc: Document::parse(src)?,
17 })
18 }
19
20 pub fn package_names(&self) -> Vec<String> {
25 self.doc
26 .forms()
27 .filter(|n| n.head_symbol() == Some("define-public"))
28 .filter(|n| find_package(n).is_some())
29 .filter_map(|n| n.data_child(1)?.as_symbol().map(str::to_owned))
30 .collect()
31 }
32
33 pub fn package(&self, sym: &str) -> Option<PackageView<'_>> {
34 let form = self.define_public_form(sym)?;
35 let pkg = find_package(form)?;
36 Some(PackageView {
37 view: RecordView::new(pkg)?,
38 })
39 }
40
41 pub fn package_mut(&mut self, sym: &str) -> Option<PackageViewMut<'_>> {
42 let (form_idx, path) = {
43 let (i, form) = self
44 .doc
45 .forms()
46 .enumerate()
47 .find(|(_, n)| is_define_public_of(n, sym))?;
48 let mut path = Vec::new();
49 if !find_package_path(form, &mut path) {
50 return None;
51 }
52 (i, path)
53 };
54 let mut node = self.doc.forms_mut().nth(form_idx)?;
55 for idx in path {
56 node = node.data_child_mut(idx)?;
57 }
58 Some(PackageViewMut {
59 view: RecordViewMut::new(node)?,
60 })
61 }
62
63 pub fn record_type_names(&self) -> Vec<String> {
66 self.doc
67 .forms()
68 .filter(|n| n.head_symbol() == Some("define-record-type*"))
69 .filter_map(|n| n.data_child(1)?.as_symbol().map(str::to_owned))
70 .collect()
71 }
72
73 pub fn record_type(&self, name: &str) -> Option<RecordTypeView<'_>> {
76 let node = self.doc.forms().find(|n| {
77 n.head_symbol() == Some("define-record-type*")
78 && n.data_child(1).and_then(Node::as_symbol) == Some(name)
79 })?;
80 Some(RecordTypeView { node })
81 }
82
83 fn define_public_form(&self, sym: &str) -> Option<&Node> {
84 self.doc.forms().find(|n| is_define_public_of(n, sym))
85 }
86}
87
88pub struct RecordTypeView<'a> {
92 node: &'a Node,
93}
94
95impl<'a> RecordTypeView<'a> {
96 pub fn name(&self) -> Option<&'a str> {
97 self.node.data_child(1)?.as_symbol()
98 }
99
100 pub fn constructor(&self) -> Option<&'a str> {
102 self.node.data_child(2)?.as_symbol()
103 }
104
105 pub fn predicate(&self) -> Option<&'a str> {
106 self.node.data_child(4)?.as_symbol()
107 }
108
109 pub fn fields(&self) -> Vec<RecordFieldView<'a>> {
110 self.node
111 .list_nodes()
112 .skip(5)
113 .filter(|n| n.head_symbol().is_some())
114 .map(|node| RecordFieldView { node })
115 .collect()
116 }
117}
118
119pub struct RecordFieldView<'a> {
120 node: &'a Node,
121}
122
123impl<'a> RecordFieldView<'a> {
124 pub fn name(&self) -> Option<&'a str> {
125 self.node.data_child(0)?.as_symbol()
126 }
127
128 pub fn getter(&self) -> Option<&'a str> {
129 self.node.data_child(1)?.as_symbol()
130 }
131
132 pub fn default(&self) -> Option<String> {
135 let clause = self
136 .node
137 .list_nodes()
138 .skip(2)
139 .find(|n| n.head_symbol() == Some("default"))?;
140 Some(clause.data_child(1)?.to_source())
141 }
142}
143
144impl std::fmt::Display for ModuleFile {
145 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146 self.doc.fmt(f)
147 }
148}
149
150pub struct PackageView<'a> {
152 view: RecordView<'a>,
153}
154
155impl PackageView<'_> {
156 pub fn name(&self) -> Option<String> {
157 self.view.string_field("name")
158 }
159
160 pub fn version(&self) -> Option<String> {
163 self.view.string_field("version")
164 }
165
166 pub fn synopsis(&self) -> Option<String> {
167 self.view.string_field("synopsis")
168 }
169
170 pub fn home_page(&self) -> Option<String> {
171 self.view.string_field("home-page")
172 }
173
174 pub fn origin_sha256_base32(&self) -> Option<String> {
176 let origin = self
177 .view
178 .field("source")
179 .filter(|n| n.head_symbol() == Some("origin"))?;
180 let sha = RecordView::new(origin)?.field("sha256")?;
181 if sha.head_symbol() != Some("base32") {
182 return None;
183 }
184 sha.data_child(1)?.as_string_lit()
185 }
186
187 pub fn inputs(&self) -> Vec<String> {
191 self.input_list("inputs")
192 }
193
194 pub fn native_inputs(&self) -> Vec<String> {
195 self.input_list("native-inputs")
196 }
197
198 pub fn propagated_inputs(&self) -> Vec<String> {
199 self.input_list("propagated-inputs")
200 }
201
202 fn input_list(&self, field: &str) -> Vec<String> {
203 let Some(v) = self.view.field(field) else {
204 return Vec::new();
205 };
206 if v.head_symbol() != Some("list") {
207 return Vec::new();
208 }
209 v.list_nodes()
210 .skip(1)
211 .filter_map(|n| n.as_symbol().map(str::to_owned))
212 .collect()
213 }
214}
215
216pub struct PackageViewMut<'a> {
218 view: RecordViewMut<'a>,
219}
220
221impl PackageViewMut<'_> {
222 pub fn set_field(&mut self, name: &str, value: Node) {
223 self.view.set_field(name, value);
224 }
225
226 pub fn field_mut(&mut self, name: &str) -> Option<&mut Node> {
227 self.view.field_mut(name)
228 }
229
230 pub fn set_version(&mut self, version: &str) {
231 self.view.set_field("version", string_lit(version));
232 }
233
234 pub fn set_origin_sha256_base32(&mut self, hash: &str) -> bool {
237 let Some(origin) = self.view.field_mut("source") else {
238 return false;
239 };
240 if origin.head_symbol() != Some("origin") {
241 return false;
242 }
243 let Some(mut ov) = RecordViewMut::new(origin) else {
244 return false;
245 };
246 let Some(sha) = ov.field_mut("sha256") else {
247 return false;
248 };
249 if sha.head_symbol() != Some("base32") {
250 return false;
251 }
252 sha.replace_child(1, string_lit(hash))
253 }
254
255 pub fn replace_input(&mut self, old: &str, new: &str) -> bool {
257 for field in ["inputs", "native-inputs", "propagated-inputs"] {
258 if let Some(v) = self.view.field_mut(field) {
259 if v.head_symbol() == Some("list") {
260 if let Some(idx) = v.position_of(|n| n.as_symbol() == Some(old)) {
261 return v.replace_child(idx, sym(new));
262 }
263 }
264 }
265 }
266 false
267 }
268}
269
270fn is_define_public_of(form: &Node, name: &str) -> bool {
271 form.head_symbol() == Some("define-public")
272 && form.data_child(1).and_then(Node::as_symbol) == Some(name)
273}
274
275fn find_package(node: &Node) -> Option<&Node> {
276 for child in node.list_nodes().skip(1) {
277 if matches!(child.head_symbol(), Some("package" | "package/inherit")) {
278 return Some(child);
279 }
280 if child.head_symbol().is_some() {
281 if let Some(p) = find_package(child) {
282 return Some(p);
283 }
284 }
285 }
286 None
287}
288
289fn find_package_path(node: &Node, path: &mut Vec<usize>) -> bool {
292 for (i, child) in node.list_nodes().enumerate().skip(1) {
293 if matches!(child.head_symbol(), Some("package" | "package/inherit")) {
294 path.push(i);
295 return true;
296 }
297 if child.head_symbol().is_some() {
298 path.push(i);
299 if find_package_path(child, path) {
300 return true;
301 }
302 path.pop();
303 }
304 }
305 false
306}