1use crate::record::{RecordView, RecordViewMut};
9use crate::Error;
10use scheme_edit::{list, sym, Document, Item, Node};
11
12fn is_os(node: &Node) -> bool {
13 node.head_symbol() == Some("operating-system")
14}
15
16fn invalid(field: &str, reason: impl Into<String>) -> Error {
17 Error::Invalid {
18 field: field.into(),
19 reason: reason.into(),
20 }
21}
22
23fn parse_single_datum(src: &str, field: &str) -> Result<Node, Error> {
25 let doc = Document::parse(src).map_err(|e| invalid(field, format!("invalid source: {e}")))?;
26 doc.items
27 .into_iter()
28 .find_map(|i| match i {
29 Item::Node(n) => Some(n),
30 _ => None,
31 })
32 .ok_or_else(|| invalid(field, "no datum in source"))
33}
34
35fn splice_into_container(val: &mut Node, elt: Node, field: &str) -> Result<(), Error> {
42 if let Some(base) = val.as_symbol() {
43 let base = base.to_string();
44 *val = list(vec![sym("cons*"), elt, sym(&base)]);
45 return Ok(());
46 }
47 match val.head_symbol() {
48 Some("list") => {
49 val.push_child(elt);
50 Ok(())
51 }
52 Some("cons*") => {
53 let tail = val.data_len().saturating_sub(1);
54 val.insert_child(tail, elt);
55 Ok(())
56 }
57 Some("cons") => {
58 let tail = val.data_len().saturating_sub(1);
59 val.insert_child(tail, elt);
60 val.replace_child(0, sym("cons*"));
61 Ok(())
62 }
63 Some("append") => {
64 let idx = val.position_of(|n| n.head_symbol() == Some("list"));
65 match idx.and_then(|i| val.data_child_mut(i)) {
66 Some(inner) => {
67 inner.push_child(elt);
68 Ok(())
69 }
70 None => Err(invalid(field, "unsupported `append` shape for editing")),
71 }
72 }
73 _ => Err(invalid(field, "unsupported container shape for editing")),
74 }
75}
76
77pub struct OperatingSystemView<'a> {
80 record: RecordView<'a>,
81}
82
83impl<'a> OperatingSystemView<'a> {
84 pub fn new(node: &'a Node) -> Option<Self> {
86 if !is_os(node) {
87 return None;
88 }
89 Some(Self {
90 record: RecordView::new(node)?,
91 })
92 }
93
94 pub fn host_name(&self) -> Option<String> {
95 self.record.string_field("host-name")
96 }
97
98 pub fn timezone(&self) -> Option<String> {
99 self.record.string_field("timezone")
100 }
101
102 pub fn locale(&self) -> Option<String> {
103 self.record.string_field("locale")
104 }
105
106 pub fn field(&self, name: &str) -> Option<&'a Node> {
108 self.record.field(name)
109 }
110
111 pub fn has_field(&self, name: &str) -> bool {
112 self.record.field(name).is_some()
113 }
114}
115
116pub struct OperatingSystemViewMut<'a> {
118 node: &'a mut Node,
119}
120
121impl<'a> OperatingSystemViewMut<'a> {
122 pub fn new(node: &'a mut Node) -> Option<Self> {
124 if !is_os(node) {
125 return None;
126 }
127 Some(Self { node })
128 }
129
130 fn record_mut(&mut self) -> RecordViewMut<'_> {
132 RecordViewMut::new(&mut *self.node).expect("operating-system form")
133 }
134
135 pub fn set_field(&mut self, name: &str, value_source: &str) -> Result<(), Error> {
137 let value = parse_single_datum(value_source, name)?;
138 self.record_mut().set_field(name, value);
139 Ok(())
140 }
141
142 pub fn add_file_system(&mut self, fs_source: &str) -> Result<(), Error> {
146 let fs = parse_single_datum(fs_source, "file-systems")?;
147 if fs.head_symbol() != Some("file-system") {
148 return Err(invalid(
149 "file-systems",
150 "expected a (file-system ...) datum",
151 ));
152 }
153 {
154 let mut record = self.record_mut();
155 if let Some(val) = record.field_mut("file-systems") {
156 return splice_into_container(val, fs, "file-systems");
157 }
158 }
159 self.record_mut().set_field(
160 "file-systems",
161 list(vec![sym("cons*"), fs, sym("%base-file-systems")]),
162 );
163 Ok(())
164 }
165
166 pub fn add_mapped_device(&mut self, md_source: &str) -> Result<(), Error> {
169 let md = parse_single_datum(md_source, "mapped-devices")?;
170 if md.head_symbol() != Some("mapped-device") {
171 return Err(invalid(
172 "mapped-devices",
173 "expected a (mapped-device ...) datum",
174 ));
175 }
176 {
177 let mut record = self.record_mut();
178 if let Some(val) = record.field_mut("mapped-devices") {
179 return splice_into_container(val, md, "mapped-devices");
180 }
181 }
182 self.record_mut()
183 .set_field("mapped-devices", list(vec![sym("list"), md]));
184 Ok(())
185 }
186}