summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/batchmode.rs4
-rw-r--r--src/changes.rs176
-rw-r--r--src/main.rs261
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
- }
-}