diff options
Diffstat (limited to 'src/util.rs')
-rw-r--r-- | src/util.rs | 154 |
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) +} |