1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
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 <operation> "what to do")
.required(false))
.get_matches();
let query_string = matches.get_one::<String>("query").unwrap();
let files = matches.get_one::<PathBuf>("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::<String>("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<rnix::SyntaxNode>) -> 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::<usize>::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<usize> {
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
}
}
|