use crate::record::{RecordView, RecordViewMut};
use crate::Error;
use scheme_edit::{string_lit, sym, Document, Node};
#[derive(Debug)]
pub struct ModuleFile {
doc: Document,
}
impl ModuleFile {
pub fn parse(src: &str) -> Result<Self, Error> {
Ok(Self {
doc: Document::parse(src)?,
})
}
pub fn package_names(&self) -> Vec<String> {
self.doc
.forms()
.filter(|n| n.head_symbol() == Some("define-public"))
.filter(|n| find_package(n).is_some())
.filter_map(|n| n.data_child(1)?.as_symbol().map(str::to_owned))
.collect()
}
pub fn package(&self, sym: &str) -> Option<PackageView<'_>> {
let form = self.define_public_form(sym)?;
let pkg = find_package(form)?;
Some(PackageView {
view: RecordView::new(pkg)?,
})
}
pub fn package_mut(&mut self, sym: &str) -> Option<PackageViewMut<'_>> {
let (form_idx, path) = {
let (i, form) = self
.doc
.forms()
.enumerate()
.find(|(_, n)| is_define_public_of(n, sym))?;
let mut path = Vec::new();
if !find_package_path(form, &mut path) {
return None;
}
(i, path)
};
let mut node = self.doc.forms_mut().nth(form_idx)?;
for idx in path {
node = node.data_child_mut(idx)?;
}
Some(PackageViewMut {
view: RecordViewMut::new(node)?,
})
}
pub fn record_type_names(&self) -> Vec<String> {
self.doc
.forms()
.filter(|n| n.head_symbol() == Some("define-record-type*"))
.filter_map(|n| n.data_child(1)?.as_symbol().map(str::to_owned))
.collect()
}
pub fn record_type(&self, name: &str) -> Option<RecordTypeView<'_>> {
let node = self.doc.forms().find(|n| {
n.head_symbol() == Some("define-record-type*")
&& n.data_child(1).and_then(Node::as_symbol) == Some(name)
})?;
Some(RecordTypeView { node })
}
fn define_public_form(&self, sym: &str) -> Option<&Node> {
self.doc.forms().find(|n| is_define_public_of(n, sym))
}
}
pub struct RecordTypeView<'a> {
node: &'a Node,
}
impl<'a> RecordTypeView<'a> {
pub fn name(&self) -> Option<&'a str> {
self.node.data_child(1)?.as_symbol()
}
pub fn constructor(&self) -> Option<&'a str> {
self.node.data_child(2)?.as_symbol()
}
pub fn predicate(&self) -> Option<&'a str> {
self.node.data_child(4)?.as_symbol()
}
pub fn fields(&self) -> Vec<RecordFieldView<'a>> {
self.node
.list_nodes()
.skip(5)
.filter(|n| n.head_symbol().is_some())
.map(|node| RecordFieldView { node })
.collect()
}
}
pub struct RecordFieldView<'a> {
node: &'a Node,
}
impl<'a> RecordFieldView<'a> {
pub fn name(&self) -> Option<&'a str> {
self.node.data_child(0)?.as_symbol()
}
pub fn getter(&self) -> Option<&'a str> {
self.node.data_child(1)?.as_symbol()
}
pub fn default(&self) -> Option<String> {
let clause = self
.node
.list_nodes()
.skip(2)
.find(|n| n.head_symbol() == Some("default"))?;
Some(clause.data_child(1)?.to_source())
}
}
impl std::fmt::Display for ModuleFile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.doc.fmt(f)
}
}
pub struct PackageView<'a> {
view: RecordView<'a>,
}
impl PackageView<'_> {
pub fn name(&self) -> Option<String> {
self.view.string_field("name")
}
pub fn version(&self) -> Option<String> {
self.view.string_field("version")
}
pub fn synopsis(&self) -> Option<String> {
self.view.string_field("synopsis")
}
pub fn home_page(&self) -> Option<String> {
self.view.string_field("home-page")
}
pub fn origin_sha256_base32(&self) -> Option<String> {
let origin = self
.view
.field("source")
.filter(|n| n.head_symbol() == Some("origin"))?;
let sha = RecordView::new(origin)?.field("sha256")?;
if sha.head_symbol() != Some("base32") {
return None;
}
sha.data_child(1)?.as_string_lit()
}
pub fn inputs(&self) -> Vec<String> {
self.input_list("inputs")
}
pub fn native_inputs(&self) -> Vec<String> {
self.input_list("native-inputs")
}
pub fn propagated_inputs(&self) -> Vec<String> {
self.input_list("propagated-inputs")
}
fn input_list(&self, field: &str) -> Vec<String> {
let Some(v) = self.view.field(field) else {
return Vec::new();
};
if v.head_symbol() != Some("list") {
return Vec::new();
}
v.list_nodes()
.skip(1)
.filter_map(|n| n.as_symbol().map(str::to_owned))
.collect()
}
}
pub struct PackageViewMut<'a> {
view: RecordViewMut<'a>,
}
impl PackageViewMut<'_> {
pub fn set_field(&mut self, name: &str, value: Node) {
self.view.set_field(name, value);
}
pub fn field_mut(&mut self, name: &str) -> Option<&mut Node> {
self.view.field_mut(name)
}
pub fn set_version(&mut self, version: &str) {
self.view.set_field("version", string_lit(version));
}
pub fn set_origin_sha256_base32(&mut self, hash: &str) -> bool {
let Some(origin) = self.view.field_mut("source") else {
return false;
};
if origin.head_symbol() != Some("origin") {
return false;
}
let Some(mut ov) = RecordViewMut::new(origin) else {
return false;
};
let Some(sha) = ov.field_mut("sha256") else {
return false;
};
if sha.head_symbol() != Some("base32") {
return false;
}
sha.replace_child(1, string_lit(hash))
}
pub fn replace_input(&mut self, old: &str, new: &str) -> bool {
for field in ["inputs", "native-inputs", "propagated-inputs"] {
if let Some(v) = self.view.field_mut(field) {
if v.head_symbol() == Some("list") {
if let Some(idx) = v.position_of(|n| n.as_symbol() == Some(old)) {
return v.replace_child(idx, sym(new));
}
}
}
}
false
}
}
fn is_define_public_of(form: &Node, name: &str) -> bool {
form.head_symbol() == Some("define-public")
&& form.data_child(1).and_then(Node::as_symbol) == Some(name)
}
fn find_package(node: &Node) -> Option<&Node> {
for child in node.list_nodes().skip(1) {
if matches!(child.head_symbol(), Some("package" | "package/inherit")) {
return Some(child);
}
if child.head_symbol().is_some() {
if let Some(p) = find_package(child) {
return Some(p);
}
}
}
None
}
fn find_package_path(node: &Node, path: &mut Vec<usize>) -> bool {
for (i, child) in node.list_nodes().enumerate().skip(1) {
if matches!(child.head_symbol(), Some("package" | "package/inherit")) {
path.push(i);
return true;
}
if child.head_symbol().is_some() {
path.push(i);
if find_package_path(child, path) {
return true;
}
path.pop();
}
}
false
}