summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/changes.rs45
-rw-r--r--src/main.rs77
2 files changed, 103 insertions, 19 deletions
diff --git a/src/changes.rs b/src/changes.rs
index 5099364..986e9c4 100644
--- a/src/changes.rs
+++ b/src/changes.rs
@@ -1,3 +1,5 @@
+use std::path::PathBuf;
+
use rowan::NodeOrToken;
use crate::pipeline::Change;
@@ -174,3 +176,46 @@ fn next_indentation(node: &rnix::SyntaxNode) -> Option<usize> {
None
}
}
+
+/// give line & column of a position marker in the given string
+fn line_at_textpos(content: &str, pos: rowan::TextSize) -> Option<(usize, usize)> {
+ let pos: usize = pos.into();
+ content
+ .split("\n")
+ .scan(0usize, |acc,line| {
+ let start = *acc;
+ let end = line.len() + 1 + start;
+ *acc = end;
+ Some((start, end))
+ })
+ .enumerate()
+ .find(|(_, (_, end))| *end > pos)
+ .map(|(line, (start, _))| (line + 1, pos + 1 - start))
+}
+
+pub fn format_pos(path: &PathBuf, content: &str, pos1: rowan::TextSize) -> String {
+ let (line1, col1) = line_at_textpos(content, pos1).unwrap();
+ format!("{path:?}:{line1}:{col1}")
+}
+
+pub fn format_range(path: &PathBuf, content: &str, range: rowan::TextRange) -> String {
+ let (line1, col1) = line_at_textpos(content, range.start()).unwrap();
+ let (line2, col2) = line_at_textpos(content, range.end()).unwrap();
+ format!("{}:{line1}:{col1}-{line2}:{col2}", path.to_string_lossy())
+}
+
+pub fn format_range_json(path: &PathBuf, content: &str, range: rowan::TextRange) -> serde_json::Value {
+ let (line1, col1) = line_at_textpos(content, range.start()).unwrap();
+ let (line2, col2) = line_at_textpos(content, range.end()).unwrap();
+ serde_json::json!({
+ "file": path,
+ "start": {
+ "line": line1,
+ "column": col1
+ },
+ "end": {
+ "line": line2,
+ "column": col2
+ }
+ })
+}
diff --git a/src/main.rs b/src/main.rs
index 6e85257..bd448ce 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,7 +3,9 @@ use rowan::ast::AstNode;
use clap::{arg, Parser};
use queries::Query;
-use changes::apply_changes;
+use changes::{apply_changes, format_range};
+
+use crate::changes::format_range_json;
#[allow(dead_code)]
mod queries;
@@ -24,7 +26,11 @@ struct Args {
#[arg(short, long)]
debug: bool,
#[arg(long)]
- batchmode: bool
+ batchmode: bool,
+ #[arg(long)]
+ print_positions: bool,
+ #[arg(long)]
+ json: bool
}
fn main() {
@@ -38,31 +44,64 @@ fn main() {
}
if args.batchmode {
+ macro_rules! warn_arg {
+ ($arg: ident, $text:literal) => { if args.$arg {
+ eprintln!("Warning: option {} is ignored in batch mode.", $text);
+ }}
+ }
+
+ warn_arg!(json, "--json");
+ warn_arg!(print_positions, "--print-positions");
+
batchmode::batchmode(args.path, query, args.debug);
} else {
- let (content, nexp) = match parse_nixfile(&args.path[0]) {
- Err(e) => {
- eprintln!("could not parse file: {e}");
- exit(2);
- },
- Ok(exp) => exp
- };
-
- let (changes, results) = query.apply(&content, nexp.syntax().clone()).unwrap();
-
- if args.debug {
- println!("{changes:?}");
+ for path in &args.path {
+ handle_file(path, &args, &query)
}
+ }
+}
+
+fn handle_file(path: &PathBuf, args: &Args, query: &Query) {
+ let (content, nexp) = match parse_nixfile(path) {
+ Err(e) => {
+ eprintln!("could not parse file: {e}");
+ exit(2);
+ },
+ Ok(exp) => exp
+ };
- if changes.len() == 0 {
+ let (changes, results) = query.apply(&content, nexp.syntax().clone()).unwrap();
+
+ if args.debug {
+ println!("{changes:?}");
+ }
+
+ if changes.len() == 0 {
+ if args.print_positions {
+ if args.json {
+ let json = results
+ .iter()
+ .map(|result|
+ format_range_json(path, &content, result.text_range()))
+ .collect::<Vec<_>>();
+ println!("{}", serde_json::to_string(&json).unwrap());
+ } else {
+ for result in results {
+ println!(
+ "{}",
+ format_range(path, &content, result.text_range())
+ );
+ }
+ }
+ } else {
for result in results {
println!("{result}");
}
- } else {
- let changed = apply_changes(&content, changes, args.debug);
-
- println!("{changed}");
}
+ } else {
+ let changed = apply_changes(&content, changes, args.debug);
+
+ println!("{changed}");
}
}