use std::{path::PathBuf, fs, process::exit}; use pipeline::{Change, ChangeKind}; use rowan::{ast::AstNode, TextSize, NodeOrToken}; use clap::{arg, Parser}; #[allow(dead_code)] mod queries; #[allow(dead_code)] mod status_reporter; mod batchmode; #[allow(dead_code)] mod util; #[allow(dead_code)] mod pipeline; #[derive(clap::Parser)] struct Args { query: String, path: Vec, #[arg(short, long)] debug: bool, #[arg(long)] batchmode: bool } fn parse_nexp(path: &PathBuf) -> anyhow::Result<(String, rnix::Root)> { let content = fs::read_to_string(path)?; let parse = rnix::Root::parse(&content); if !parse.errors().is_empty() { anyhow::bail!("error: {:?}", parse.errors()); } Ok((content, parse.tree())) } fn main() { let args = Args::parse(); let parse = queries::parse(&args.query); if parse.errors.len() != 0 { eprintln!( "syntax {}: \n {}", if parse.errors.len() == 1 { "error" } else { "errors" }, parse.errors.join(" \n") ); exit(1); } // println!("{nexp:#?}"); if args.batchmode { batchmode::batchmode(args.path, parse, args.debug); } else { let (content, nexp) = match parse_nexp(&args.path[0]) { Err(e) => { eprintln!("could not parse file: {e}"); exit(2); }, Ok(exp) => exp }; let (changes, results) = parse.apply(&content, nexp.syntax().clone()).unwrap(); if args.debug { println!("{changes:?}"); } if changes.len() == 0 { for result in results { println!("{result}"); } } else { let changed = apply_changes(&content, changes, args.debug); println!("{changed}"); } } } fn apply_changes(content: &str, mut changes: Vec, debug: bool) -> String { let mut last_pos = 0; let mut ncontent = String::new(); changes.sort_by_key(|change| change.node.text_range().start()); for change in changes { match change.kind { ChangeKind::Remove => { let (before, after) = remove_node(&change.node); if last_pos > before { continue } ncontent += &content[last_pos..before]; if debug { println!("removing: {}", &content[before..after]) } last_pos = after; } ChangeKind::Keep => { // let (before, after) = surrounding_whitespace(&change.node); let (before, after) = (change.node.text_range().start().into(), change.node.text_range().end().into()); if debug { println!("keeping: {}", &content[before..after]) }; ncontent += &content[before..after]; // last_pos = after; } } } ncontent += &content[last_pos..]; ncontent } #[allow(unused)] fn surrounding_whitespace(node: &rnix::SyntaxNode) -> (usize, usize) { let before = node .prev_sibling_or_token() .filter(|n| n.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE) .map(|n| n.text_range().start().into()) .unwrap_or_else(|| node.text_range().start().into()); let after = node .next_sibling_or_token() .filter(|n| n.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE) .map(|n| n.text_range().end().into()) .unwrap_or_else(|| node.text_range().end().into()); (before, after) } fn remove_node(span: &rnix::SyntaxNode) -> (usize, usize) { // TODO: how to remove brackets around the node? fn eat_to_bracket(node: &rnix::SyntaxNode, backwards: bool) -> (TextSize, usize) { let mut span = NodeOrToken::Node(node.clone()); let mut bracket = span.clone(); let mut brackets = 0; loop { match if backwards { span.prev_sibling_or_token() } else { span.next_sibling_or_token() } { Some(NodeOrToken::Token(t)) => match t.kind() { rnix::SyntaxKind::TOKEN_WHITESPACE => span = NodeOrToken::Token(t), rnix::SyntaxKind::TOKEN_L_PAREN if backwards => { span = NodeOrToken::Token(t); bracket = span.clone(); brackets += 1; }, rnix::SyntaxKind::TOKEN_R_PAREN if !backwards => { span = NodeOrToken::Token(t); bracket = span.clone(); brackets += 1; }, _ => break, }, _ => break, } } (if backwards { bracket.text_range().start() } else { bracket.text_range().end() }, brackets) } let (before, after) = match (eat_to_bracket(span, true), eat_to_bracket(span, false)) { ((before, n1), (after, n2)) if n1 == n2 && n1 != 0 => (before.into(), after.into()), _ => match (span.prev_sibling_or_token(), span.next_sibling_or_token()) { (Some(prev), Some(next)) if prev.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE && next.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE => { if prev.to_string().lines().count() < next.to_string().lines().count() { (Into::::into(span.text_range().start() - prev.text_range().len()) + previous_indentation(&span).unwrap_or(0) , Into::::into(span.text_range().end()) + next_indentation(&span).unwrap_or(0)) } else { (Into::::into(span.text_range().start()) - previous_indentation(&span).unwrap_or(0), Into::::into(span.text_range().end() + next.text_range().len()) - next_indentation(&span).unwrap_or(0)) } } _ => (span.text_range().start().into(), span.text_range().end().into()) } }; (before, after) // ( Into::::into(span.text_range().start()) - before // , Into::::into(span.text_range().end()) + after // ) } fn previous_indentation(node: &rnix::SyntaxNode) -> Option { let whitespace_token = node.prev_sibling_or_token()?; if whitespace_token.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE { Some(whitespace_token.to_string().lines().last().unwrap().len()) } else { None } } fn next_indentation(node: &rnix::SyntaxNode) -> Option { let whitespace_token = node.next_sibling_or_token()?; if whitespace_token.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE { Some(whitespace_token.to_string().lines().last().unwrap().len()) } else { None } }