summaryrefslogtreecommitdiff
path: root/src/util.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/util.rs')
-rw-r--r--src/util.rs154
1 files changed, 154 insertions, 0 deletions
diff --git a/src/util.rs b/src/util.rs
new file mode 100644
index 0000000..d49d043
--- /dev/null
+++ b/src/util.rs
@@ -0,0 +1,154 @@
+use std::{path::Path, fs};
+use anyhow::{Result, Context};
+use itertools::Itertools;
+use rnix::{SyntaxKind, ast::{AttrpathValue, AttrSet, HasEntry, Entry::*}, SyntaxNode};
+use rowan::{ast::AstNode, TextSize};
+
+use crate::status_reporter::StatusReport;
+
+fn textsize_at_line(s: &str, line: usize) -> TextSize {
+ s
+ .split('\n')
+ .map(|l|
+ TextSize::of(l) + TextSize::new(1)
+ )
+ .take(line-1)
+ .sum()
+}
+
+
+fn dig_to_kind(kind: SyntaxKind, node: &SyntaxNode) -> Option<SyntaxNode> {
+ if node.kind() == kind {
+ return Some(node.clone());
+ }
+
+ node.descendants()
+ .filter(|node| node.kind() == kind)
+ .next()
+}
+
+fn add_to_meta_block(rough_pos: SyntaxNode, content: &str, main_program: &str, file: &Path, line: usize) -> Result<(String, usize)> {
+ let meta_node = dig_to_kind(SyntaxKind::NODE_ATTR_SET, &rough_pos).unwrap();
+ let meta_set = AttrSet::cast(meta_node.clone()).unwrap();
+
+ let description_entry = meta_set.entries()
+ .filter(|entry| {
+ match &entry {
+ Inherit(it) => it.attrs().any(|c| c.to_string() == "description"),
+ AttrpathValue(it) => it.attrpath().unwrap().to_string() == "description",
+ }
+ })
+ .exactly_one().ok()
+ .with_context(|| format!("meta node has no description attribute in {:?} at {}", file, line))?;
+ let description = description_entry.syntax();
+
+ let pos = description.text_range();
+ let indent = content[..pos.start().into()].chars().rev().position(|c| c == '\n').unwrap();
+
+ let patch = String::new()
+ + "\n"
+ + &" ".repeat(indent)
+ + "mainProgram = \"" + main_program + "\";";
+
+ Ok((patch, pos.end().into()))
+}
+
+fn edit_one(file: &Path, line: usize, main_program: &str, p: &StatusReport) -> Result<String> {
+ let mut content = fs::read_to_string(file)?;
+ let searchpos = textsize_at_line(&content, line);
+
+ p.update_item(format!("doing {:?}", file));
+ let parse = rnix::Root::parse(&content);
+ if !parse.errors().is_empty() {
+ anyhow::bail!("error: {:?}", parse.errors());
+ }
+ let tree = parse.tree();
+
+ let pos_node = tree.syntax().descendants()
+ .filter(|node| {
+ if node.kind() == SyntaxKind::NODE_ATTRPATH_VALUE {
+ let value = AttrpathValue::cast(node.clone()).unwrap();
+ node.text_range().contains(searchpos) && value.attrpath().unwrap().to_string() == "meta"
+ } else { false }
+ })
+ .exactly_one().ok();
+
+ // do we have a meta attrset already?
+ let (patch, insert_offset) = match pos_node {
+ None => {
+ let version_node = tree
+ .syntax()
+ .descendants()
+ .filter(|node| {
+ if node.kind() == SyntaxKind::NODE_ATTRPATH_VALUE {
+ let value = AttrpathValue::cast(node.clone()).unwrap();
+ let name = value.attrpath().unwrap().to_string();
+ node.text_range().contains(searchpos + TextSize::new(5))
+ && (name == "version" || name == "pname" || name == "name")
+ } else { false }
+ })
+ .exactly_one().ok()
+ .with_context(|| format!("neither meta nor version node found for {:?} at {}", file, line))?;
+
+ let attrset = version_node.parent().unwrap();
+ if attrset.kind() != SyntaxKind::NODE_ATTR_SET {
+ anyhow::bail!("name not in an attrset in {:?} at {}", file, line)
+ }
+
+ // does a meta block already exist?
+ let maybe_meta_block = attrset
+ .descendants()
+ .filter(|node| {
+ if node.kind() == SyntaxKind::NODE_ATTRPATH_VALUE {
+ let value = AttrpathValue::cast(node.clone()).unwrap();
+ let name = value.attrpath().unwrap().to_string();
+ name == "meta"
+ } else { false }
+ })
+ .exactly_one();
+
+ if let Ok(meta) = maybe_meta_block {
+ add_to_meta_block(meta.clone(), &content, main_program, file, line)?
+ } else {
+ let before_attrset_end = Into::<usize>::into(attrset.text_range().end())
+ - 1
+ - content[..attrset.text_range().end().into()]
+ .chars().rev().position(|c| c == '\n').unwrap();
+
+ let indent = content[..version_node.text_range().start().into()]
+ .chars().rev().position(|c| c == '\n').unwrap();
+
+ // some language specific build systems don't use meta as its own attrset
+ // there's no good way to recognise these, but this seems to work fine
+ let weird_nonstandard_meta = attrset
+ .descendants()
+ .any(|node| {
+ if node.kind() == SyntaxKind::NODE_ATTRPATH_VALUE {
+ let value = AttrpathValue::cast(node.clone()).unwrap();
+ let name = value.attrpath().unwrap().to_string();
+ name == "description" || name == "homepage" || name == "license"
+ } else { false }
+ });
+ let patch = String::new()
+ + "\n"
+ + &" ".repeat(indent)
+ + if weird_nonstandard_meta { "mainProgram = \"" } else { "meta.mainProgram = \"" }
+ + main_program + "\";";
+
+ (patch, before_attrset_end)
+ }
+ },
+ Some(pos) => {
+ add_to_meta_block(pos.clone(), &content, main_program, file, line)?
+ }
+ };
+
+
+ content = String::new()
+ + &content[..insert_offset]
+ + &patch
+ + &content[insert_offset..];
+
+ p.changed_item();
+ Ok(content)
+}