diff options
-rw-r--r-- | src/batchmode.rs | 4 | ||||
-rw-r--r-- | src/changes.rs | 176 | ||||
-rw-r--r-- | src/main.rs | 261 |
3 files changed, 252 insertions, 189 deletions
diff --git a/src/batchmode.rs b/src/batchmode.rs index 3e83694..aae74b5 100644 --- a/src/batchmode.rs +++ b/src/batchmode.rs @@ -2,7 +2,7 @@ use std::{path::PathBuf, fs, sync::{Arc, Mutex}}; use rowan::ast::AstNode; use threadpool::ThreadPool; -use crate::{status_reporter::*, queries::Query, parse_nexp, apply_changes}; +use crate::{status_reporter::*, queries::Query, parse_nixfile, changes::apply_changes}; #[allow(unreachable_code, unused)] @@ -10,7 +10,7 @@ pub fn batchmode(tasks: Vec<PathBuf>, query: Query, debug: bool) { fn do_task(path: PathBuf, query: Query, debug: bool) -> anyhow::Result<(PathBuf, Option<String>)> { - let (content, nexp) = match parse_nexp(&path) { + let (content, nexp) = match parse_nixfile(&path) { Err(e) => { anyhow::bail!("could not parse file {path:?}") }, diff --git a/src/changes.rs b/src/changes.rs new file mode 100644 index 0000000..5099364 --- /dev/null +++ b/src/changes.rs @@ -0,0 +1,176 @@ +use rowan::NodeOrToken; +use crate::pipeline::Change; + +pub fn apply_changes(content: &str, mut changes: Vec<Change>, 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 { + use crate::queries::Operator::*; + match change.kind { + 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; + } + Keep => { + let (before, after) = surrounding_noise(&change.node); + let (before, after) = eat_whitespace(&before, &after); + // let (before, after) = (change.node.text_range().start().into(), change.node.text_range().end().into()); + let (before, after) = (before.text_range().start().into(), after.text_range().end().into()); + if debug { + println!("keeping: {}", &content[before..after]) + }; + ncontent += &content[before..after]; + // last_pos = after; + } + } + } + + ncontent += &content[last_pos..]; + + ncontent +} + + + +// TODO: does rnix really not have an alias for this? +type NixNodeOrToken = NodeOrToken<rowan::SyntaxNode<rnix::NixLanguage>, rowan::SyntaxToken<rnix::NixLanguage>>; + +/// eat all tokens of the given kind (backwards or forwards) +fn eat_kind(node: &NixNodeOrToken, kind: rnix::SyntaxKind, backwards: bool) -> NixNodeOrToken { + let mut span = node.clone(); + + loop { + span = match if backwards { span.prev_sibling_or_token() } else { span.next_sibling_or_token() } { + Some(NodeOrToken::Token(t)) + if t.kind() == kind + => NodeOrToken::Token(t), + _ => break + }; + } + span +} + + +/// eat whitespace outwards both ways +fn eat_whitespace(begin: &NixNodeOrToken, end: &NixNodeOrToken) + -> (NixNodeOrToken, NixNodeOrToken) { + let begin = eat_kind(begin, rnix::SyntaxKind::TOKEN_WHITESPACE, true); + let end = eat_kind(end, rnix::SyntaxKind::TOKEN_WHITESPACE, false); + (begin, end) +} + +/// for a node, get all the "syntactic noise" which surrounds it +/// (i.e. brackets, whitespace, etc.) which should be removed if +/// the node itself is removed, except the final bit of whitespace +/// potentially separating it from neighbouring nodes, which should +/// be handled separately to keep formatting nice +fn surrounding_noise(node: &rnix::SyntaxNode) -> (NixNodeOrToken, NixNodeOrToken) { + + fn eat_matching_parens(begin: &NixNodeOrToken, end: &NixNodeOrToken) + -> Option<(NixNodeOrToken, NixNodeOrToken)> { + match (begin.prev_sibling_or_token(), end.next_sibling_or_token()) { + (Some(begin), Some(end)) + if begin.kind() == rnix::SyntaxKind::TOKEN_L_PAREN + && end.kind() == rnix::SyntaxKind::TOKEN_R_PAREN + => Some((begin, end)), + _ => None + } + } + + let (mut begin, mut end): (NixNodeOrToken, NixNodeOrToken) = + (NodeOrToken::Node(node.clone()), NodeOrToken::Node(node.clone())); + + loop { + let (a, b) = eat_whitespace(&begin, &end); + // println!("loop: {begin}, {end}"); + // println!("loop: {a}, {b}"); + // println!("{:?}", (begin.prev_sibling_or_token(), end.next_sibling_or_token())); + if let Some(res) = eat_matching_parens(&a, &b) { + // println!("ate parens!"); + (begin, end) = res; + } else { + break + } + } + + (begin, end) +} + + +fn remove_node(node: &rnix::SyntaxNode) -> (usize, usize) { + let (before, after) = surrounding_noise(node); + + let (mut prev, /*mut*/ next) = + eat_whitespace(&before, &after); + + // println!("{prev:?}, {next:?}"); + if let Some(t) = prev.as_token().map(|t| t.prev_token()).flatten() { + if t.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE { + prev = NodeOrToken::Token(t); + } + }; + // if let Some(t) = next.as_token().map(|t| t.next_token()).flatten() { + // if t.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE { + // next = NodeOrToken::Token(t); + // } + // }; + // println!("{prev:?}, {next:?}"); + + + if prev.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE + && next.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE { + // println!("inside if!"); + if prev.to_string().lines().count() < next.to_string().lines().count() { + let start: usize = (node.text_range().start() - prev.text_range().len()).into(); + let end: usize = node.text_range().end().into(); + (start + previous_indentation(&node).unwrap_or(0) + , end + next_indentation(&node).unwrap_or(0)) + } else { + let start: usize = node.text_range().start().into(); + let end: usize = (node.text_range().end() + next.text_range().len()).into(); + (start, end) + // (start - previous_indentation(&node).unwrap_or(0), + // end - next_indentation(&node).unwrap_or(0)) + } + } + else { + (prev.text_range().start().into(), next.text_range().end().into()) + } +} + +fn indentation_of_string(string: &str) -> Option<usize> { + if string.contains("\n") { + None + } else { + Some(string.lines().last().unwrap().len()) + } +} + +fn previous_indentation(node: &rnix::SyntaxNode) -> Option<usize> { + let whitespace_token = node.prev_sibling_or_token()?; + + if whitespace_token.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE { + indentation_of_string(&whitespace_token.to_string()) + } else { + None + } +} + +fn next_indentation(node: &rnix::SyntaxNode) -> Option<usize> { + let whitespace_token = node.next_sibling_or_token()?; + + if whitespace_token.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE { + indentation_of_string(&whitespace_token.to_string()) + // Some(whitespace_token.to_string().lines().last().unwrap().len()) + } else { + None + } +} diff --git a/src/main.rs b/src/main.rs index 2a4898d..6e85257 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,10 @@ use std::{path::PathBuf, fs, process::exit}; -use pipeline::Change; -use rowan::{ast::AstNode, NodeOrToken}; +use rowan::ast::AstNode; use clap::{arg, Parser}; +use queries::Query; +use changes::apply_changes; + #[allow(dead_code)] mod queries; #[allow(dead_code)] @@ -12,6 +14,7 @@ mod batchmode; mod util; #[allow(dead_code)] mod pipeline; +mod changes; #[derive(clap::Parser)] @@ -24,39 +27,11 @@ struct Args { 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); - } - - let query = match parse.to_query() { - Err(es) => { - queries::print_query_errors(&args.query, es); - exit(1); - }, - Ok(query) => query - }; + let query = parse_query(&args.query); if args.debug { println!("{query:?}"); @@ -65,7 +40,7 @@ fn main() { if args.batchmode { batchmode::batchmode(args.path, query, args.debug); } else { - let (content, nexp) = match parse_nexp(&args.path[0]) { + let (content, nexp) = match parse_nixfile(&args.path[0]) { Err(e) => { eprintln!("could not parse file: {e}"); exit(2); @@ -92,176 +67,88 @@ fn main() { } -fn apply_changes(content: &str, mut changes: Vec<Change>, 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 { - use queries::Operator::*; - match change.kind { - 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; - } - Keep => { - let (before, after) = surrounding_noise(&change.node); - let (before, after) = eat_whitespace(&before, &after); - // let (before, after) = (change.node.text_range().start().into(), change.node.text_range().end().into()); - let (before, after) = (before.text_range().start().into(), after.text_range().end().into()); - if debug { - println!("keeping: {}", &content[before..after]) - }; - ncontent += &content[before..after]; - // last_pos = after; - } - } - } - - ncontent += &content[last_pos..]; +fn parse_nixfile(path: &PathBuf) -> anyhow::Result<(String, rnix::Root)> { + let content = fs::read_to_string(path)?; - ncontent + let tree = parse_nexpr(&content)?; + Ok((content, tree)) } - - -// TODO: does rnix really not have an alias for this? -type NixNodeOrToken = NodeOrToken<rowan::SyntaxNode<rnix::NixLanguage>, rowan::SyntaxToken<rnix::NixLanguage>>; - -/// eat all tokens of the given kind (backwards or forwards) -fn eat_kind(node: &NixNodeOrToken, kind: rnix::SyntaxKind, backwards: bool) -> NixNodeOrToken { - let mut span = node.clone(); - - loop { - span = match if backwards { span.prev_sibling_or_token() } else { span.next_sibling_or_token() } { - Some(NodeOrToken::Token(t)) - if t.kind() == kind - => NodeOrToken::Token(t), - _ => break - }; +fn parse_nexpr(code: &str) -> anyhow::Result<rnix::Root> { + let parse = rnix::Root::parse(&code); + if !parse.errors().is_empty() { + anyhow::bail!("error: {:?}", parse.errors()); } - span + Ok(parse.tree()) } - -/// eat whitespace outwards both ways -fn eat_whitespace(begin: &NixNodeOrToken, end: &NixNodeOrToken) - -> (NixNodeOrToken, NixNodeOrToken) { - let begin = eat_kind(begin, rnix::SyntaxKind::TOKEN_WHITESPACE, true); - let end = eat_kind(end, rnix::SyntaxKind::TOKEN_WHITESPACE, false); - (begin, end) -} - -/// for a node, get all the "syntactic noise" which surrounds it -/// (i.e. brackets, whitespace, etc.) which should be removed if -/// the node itself is removed, except the final bit of whitespace -/// potentially separating it from neighbouring nodes, which should -/// be handled separately to keep formatting nice -fn surrounding_noise(node: &rnix::SyntaxNode) -> (NixNodeOrToken, NixNodeOrToken) { - - fn eat_matching_parens(begin: &NixNodeOrToken, end: &NixNodeOrToken) - -> Option<(NixNodeOrToken, NixNodeOrToken)> { - match (begin.prev_sibling_or_token(), end.next_sibling_or_token()) { - (Some(begin), Some(end)) - if begin.kind() == rnix::SyntaxKind::TOKEN_L_PAREN - && end.kind() == rnix::SyntaxKind::TOKEN_R_PAREN - => Some((begin, end)), - _ => None - } +fn parse_query(querystring: &str) -> Query { + let parse = queries::parse(querystring); + if parse.errors.len() != 0 { + eprintln!( + "syntax {}: \n {}", + if parse.errors.len() == 1 { "error" } else { "errors" }, + parse.errors.join(" \n") + ); + exit(1); } - let (mut begin, mut end): (NixNodeOrToken, NixNodeOrToken) = - (NodeOrToken::Node(node.clone()), NodeOrToken::Node(node.clone())); - - loop { - let (a, b) = eat_whitespace(&begin, &end); - // println!("loop: {begin}, {end}"); - // println!("loop: {a}, {b}"); - // println!("{:?}", (begin.prev_sibling_or_token(), end.next_sibling_or_token())); - if let Some(res) = eat_matching_parens(&a, &b) { - // println!("ate parens!"); - (begin, end) = res; - } else { - break - } - } + let query = match parse.to_query() { + Err(es) => { + queries::print_query_errors(querystring, es); + exit(1); + }, + Ok(query) => query + }; - (begin, end) + query } -fn remove_node(node: &rnix::SyntaxNode) -> (usize, usize) { - let (before, after) = surrounding_noise(node); - - let (mut prev, /*mut*/ next) = - eat_whitespace(&before, &after); - - // println!("{prev:?}, {next:?}"); - if let Some(t) = prev.as_token().map(|t| t.prev_token()).flatten() { - if t.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE { - prev = NodeOrToken::Token(t); - } - }; - // if let Some(t) = next.as_token().map(|t| t.next_token()).flatten() { - // if t.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE { - // next = NodeOrToken::Token(t); - // } - // }; - // println!("{prev:?}, {next:?}"); - - - if prev.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE - && next.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE { - // println!("inside if!"); - if prev.to_string().lines().count() < next.to_string().lines().count() { - let start: usize = (node.text_range().start() - prev.text_range().len()).into(); - let end: usize = node.text_range().end().into(); - (start + previous_indentation(&node).unwrap_or(0) - , end + next_indentation(&node).unwrap_or(0)) - } else { - let start: usize = node.text_range().start().into(); - let end: usize = (node.text_range().end() + next.text_range().len()).into(); - (start, end) - // (start - previous_indentation(&node).unwrap_or(0), - // end - next_indentation(&node).unwrap_or(0)) - } +macro_rules! test_change { + ($name:ident, $query:literal, $before:literal, $after:literal) => { + #[test] + fn $name() { + let nix = $before; + let nexpr = parse_nexpr(&nix).unwrap(); + let query = parse_query($query); + let (changes, _) = query.apply(&nix, nexpr.syntax().clone()).unwrap(); + let changed = apply_changes(&nix, changes, false); + assert_eq!(changed, $after); } - else { - (prev.text_range().start().into(), next.text_range().end().into()) - } -} - -fn indentation_of_string(string: &str) -> Option<usize> { - if string.contains("\n") { - None - } else { - Some(string.lines().last().unwrap().len()) } } -fn previous_indentation(node: &rnix::SyntaxNode) -> Option<usize> { - let whitespace_token = node.prev_sibling_or_token()?; - - if whitespace_token.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE { - indentation_of_string(&whitespace_token.to_string()) - } else { - None - } -} +test_change!( + test_remove_inherit, + ">> Inherit >> mdDoc[remove]", r#" +{lib, ...}: +mkDerivation { + pname = "dings"; + inherit (lib) mdDoc a b c mdDoc; +}"#, + r#" +{lib, ...}: +mkDerivation { + pname = "dings"; + inherit (lib) a b c; +}"# +); + +test_change!( + test_remove_attrpath, + ">> mkDerivation >> pname[remove]", + r#" +{lib, ...}: +mkDerivation { + pname = "dings"; + meta.mainProgram = "blub"; +}"#, + r#" +{lib, ...}: +mkDerivation { + meta.mainProgram = "blub"; +}"# +); -fn next_indentation(node: &rnix::SyntaxNode) -> Option<usize> { - let whitespace_token = node.next_sibling_or_token()?; - if whitespace_token.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE { - indentation_of_string(&whitespace_token.to_string()) - // Some(whitespace_token.to_string().lines().last().unwrap().len()) - } else { - None - } -} |