use std::{path::PathBuf, fs, process::exit}; use rowan::{ast::AstNode, TextSize}; use clap::{arg, command, value_parser}; #[allow(dead_code)] mod queries; #[allow(dead_code)] mod status_reporter; mod batchmode; #[allow(dead_code)] mod util; 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 matches = command!() .arg(arg!(--batchmode "run in batch mode") .required(false) ) .arg(arg!([query] "query to run") .required(true)) .arg(arg!([file] "file to operate on") .required(true) .value_parser(value_parser!(PathBuf)) ) .arg(arg!(--edit "what to do") .required(false)) .get_matches(); let query_string = matches.get_one::("query").unwrap(); let files = matches.get_one::("file").unwrap(); let parse = queries::parse(query_string); if parse.errors.len() != 0 { eprintln!( "syntax {}: \n {}", if parse.errors.len() == 1 { "error" } else { "errors" }, parse.errors.join(" \n") ); exit(1); } let (content, nexp) = match parse_nexp(files) { Err(e) => { eprintln!("could not parse file: {e}"); exit(2); }, Ok(exp) => exp }; // println!("{nexp:#?}"); let results = parse.apply(&content, nexp.syntax().clone()).unwrap(); if let Some(op) = matches.get_one::("edit") { match &op[..] { "remove" => { let new = remove_nodes(content, &results); println!("{new}"); } _ => () } } else { for result in &results { println!("{result}"); } } } fn remove_nodes(content: String, results: &Vec) -> String { assert!(results.len() == 1); let span = &results[0]; let (before, after) = 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() { (prev.text_range().len(), TextSize::new(0)) } else { (TextSize::new(0), next.text_range().len()) } } _ => (TextSize::default(),TextSize::default()) }; String::new() + &content[..Into::::into(span.text_range().start() - before) - previous_indentation(span).unwrap_or(0)] + &content[(span.text_range().end() + after).into()..] } 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 } }