use crate::record::{RecordView, RecordViewMut};
use crate::Error;
use scheme_edit::{list, sym, Document, Item, Node};
fn is_os(node: &Node) -> bool {
node.head_symbol() == Some("operating-system")
}
fn invalid(field: &str, reason: impl Into<String>) -> Error {
Error::Invalid {
field: field.into(),
reason: reason.into(),
}
}
fn parse_single_datum(src: &str, field: &str) -> Result<Node, Error> {
let doc = Document::parse(src).map_err(|e| invalid(field, format!("invalid source: {e}")))?;
doc.items
.into_iter()
.find_map(|i| match i {
Item::Node(n) => Some(n),
_ => None,
})
.ok_or_else(|| invalid(field, "no datum in source"))
}
fn splice_into_container(val: &mut Node, elt: Node, field: &str) -> Result<(), Error> {
if let Some(base) = val.as_symbol() {
let base = base.to_string();
*val = list(vec![sym("cons*"), elt, sym(&base)]);
return Ok(());
}
match val.head_symbol() {
Some("list") => {
val.push_child(elt);
Ok(())
}
Some("cons*") => {
let tail = val.data_len().saturating_sub(1);
val.insert_child(tail, elt);
Ok(())
}
Some("cons") => {
let tail = val.data_len().saturating_sub(1);
val.insert_child(tail, elt);
val.replace_child(0, sym("cons*"));
Ok(())
}
Some("append") => {
let idx = val.position_of(|n| n.head_symbol() == Some("list"));
match idx.and_then(|i| val.data_child_mut(i)) {
Some(inner) => {
inner.push_child(elt);
Ok(())
}
None => Err(invalid(field, "unsupported `append` shape for editing")),
}
}
_ => Err(invalid(field, "unsupported container shape for editing")),
}
}
pub struct OperatingSystemView<'a> {
record: RecordView<'a>,
}
impl<'a> OperatingSystemView<'a> {
pub fn new(node: &'a Node) -> Option<Self> {
if !is_os(node) {
return None;
}
Some(Self {
record: RecordView::new(node)?,
})
}
pub fn host_name(&self) -> Option<String> {
self.record.string_field("host-name")
}
pub fn timezone(&self) -> Option<String> {
self.record.string_field("timezone")
}
pub fn locale(&self) -> Option<String> {
self.record.string_field("locale")
}
pub fn field(&self, name: &str) -> Option<&'a Node> {
self.record.field(name)
}
pub fn has_field(&self, name: &str) -> bool {
self.record.field(name).is_some()
}
}
pub struct OperatingSystemViewMut<'a> {
node: &'a mut Node,
}
impl<'a> OperatingSystemViewMut<'a> {
pub fn new(node: &'a mut Node) -> Option<Self> {
if !is_os(node) {
return None;
}
Some(Self { node })
}
fn record_mut(&mut self) -> RecordViewMut<'_> {
RecordViewMut::new(&mut *self.node).expect("operating-system form")
}
pub fn set_field(&mut self, name: &str, value_source: &str) -> Result<(), Error> {
let value = parse_single_datum(value_source, name)?;
self.record_mut().set_field(name, value);
Ok(())
}
pub fn add_file_system(&mut self, fs_source: &str) -> Result<(), Error> {
let fs = parse_single_datum(fs_source, "file-systems")?;
if fs.head_symbol() != Some("file-system") {
return Err(invalid(
"file-systems",
"expected a (file-system ...) datum",
));
}
{
let mut record = self.record_mut();
if let Some(val) = record.field_mut("file-systems") {
return splice_into_container(val, fs, "file-systems");
}
}
self.record_mut().set_field(
"file-systems",
list(vec![sym("cons*"), fs, sym("%base-file-systems")]),
);
Ok(())
}
pub fn add_mapped_device(&mut self, md_source: &str) -> Result<(), Error> {
let md = parse_single_datum(md_source, "mapped-devices")?;
if md.head_symbol() != Some("mapped-device") {
return Err(invalid(
"mapped-devices",
"expected a (mapped-device ...) datum",
));
}
{
let mut record = self.record_mut();
if let Some(val) = record.field_mut("mapped-devices") {
return splice_into_container(val, md, "mapped-devices");
}
}
self.record_mut()
.set_field("mapped-devices", list(vec![sym("list"), md]));
Ok(())
}
}