feat(cli): rework query to use new input handler
Co-authored-by: Will Lillis <will.lillis24@gmail.com>
This commit is contained in:
parent
6bad1bc6c5
commit
88d2f010f5
3 changed files with 189 additions and 108 deletions
141
cli/src/main.rs
141
cli/src/main.rs
|
|
@ -327,7 +327,7 @@ struct Fuzz {
|
|||
#[command(about = "Search files using a syntax tree query", alias = "q")]
|
||||
struct Query {
|
||||
#[arg(help = "Path to a file with queries", index = 1, required = true)]
|
||||
query_path: String,
|
||||
query_path: PathBuf,
|
||||
#[arg(long, short, help = "Measure execution time")]
|
||||
pub time: bool,
|
||||
#[arg(long, short, help = "Suppress main output")]
|
||||
|
|
@ -336,9 +336,9 @@ struct Query {
|
|||
long = "paths",
|
||||
help = "The path to a file with paths to source file(s)"
|
||||
)]
|
||||
pub paths_file: Option<String>,
|
||||
pub paths_file: Option<PathBuf>,
|
||||
#[arg(index = 2, num_args=1.., help = "The source file(s) to use")]
|
||||
pub paths: Option<Vec<String>>,
|
||||
pub paths: Option<Vec<PathBuf>>,
|
||||
#[arg(
|
||||
long,
|
||||
help = "The range of byte offsets in which the query will be executed"
|
||||
|
|
@ -357,6 +357,9 @@ struct Query {
|
|||
pub test: bool,
|
||||
#[arg(long, help = "The path to an alternative config.json file")]
|
||||
pub config_path: Option<PathBuf>,
|
||||
#[arg(long, short = 'n', help = "Query the contents of a specific test")]
|
||||
#[clap(conflicts_with = "paths", conflicts_with = "paths_file")]
|
||||
pub test_number: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
|
|
@ -1084,31 +1087,37 @@ impl Test {
|
|||
.map(|s| s.to_str().unwrap_or_default())
|
||||
.unwrap_or_default();
|
||||
if stem != "highlights" && stem != "tags" {
|
||||
let paths = walkdir::WalkDir::new(test_dir.join(stem))
|
||||
let entries = walkdir::WalkDir::new(test_dir.join(stem))
|
||||
.into_iter()
|
||||
.filter_map(|e| {
|
||||
let entry = e.ok()?;
|
||||
if entry.file_type().is_file() {
|
||||
Some(String::from(entry.path().to_string_lossy()))
|
||||
Some(entry)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
if !paths.is_empty() {
|
||||
.collect::<Vec<_>>();
|
||||
if !entries.is_empty() {
|
||||
println!("{stem}:");
|
||||
}
|
||||
query::query_files_at_paths(
|
||||
language,
|
||||
paths,
|
||||
entry.path(),
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
)?;
|
||||
|
||||
for entry in entries {
|
||||
let path = entry.path();
|
||||
query::query_file_at_path(
|
||||
language,
|
||||
path,
|
||||
&path.display().to_string(),
|
||||
path,
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -1156,11 +1165,8 @@ impl Fuzz {
|
|||
impl Query {
|
||||
fn run(self, mut loader: loader::Loader, current_dir: &Path) -> Result<()> {
|
||||
let config = Config::load(self.config_path)?;
|
||||
let paths = collect_paths(self.paths_file.as_deref(), self.paths)?;
|
||||
let loader_config = config.get()?;
|
||||
loader.find_all_languages(&loader_config)?;
|
||||
let language =
|
||||
loader.select_language(Path::new(&paths[0]), current_dir, self.scope.as_deref())?;
|
||||
let query_path = Path::new(&self.query_path);
|
||||
|
||||
let byte_range = self.byte_range.as_ref().and_then(|range| {
|
||||
|
|
@ -1176,17 +1182,90 @@ impl Query {
|
|||
Some(Point::new(start, 0)..Point::new(end, 0))
|
||||
});
|
||||
|
||||
query::query_files_at_paths(
|
||||
&language,
|
||||
paths,
|
||||
query_path,
|
||||
self.captures,
|
||||
byte_range,
|
||||
point_range,
|
||||
self.test,
|
||||
self.quiet,
|
||||
self.time,
|
||||
let cancellation_flag = util::cancel_on_signal();
|
||||
|
||||
let input = get_input(
|
||||
self.paths_file.as_deref(),
|
||||
self.paths,
|
||||
self.test_number,
|
||||
&cancellation_flag,
|
||||
)?;
|
||||
|
||||
match input {
|
||||
CliInput::Paths(paths) => {
|
||||
let language = loader.select_language(
|
||||
Path::new(&paths[0]),
|
||||
current_dir,
|
||||
self.scope.as_deref(),
|
||||
)?;
|
||||
|
||||
for path in paths {
|
||||
query::query_file_at_path(
|
||||
&language,
|
||||
&path,
|
||||
&path.display().to_string(),
|
||||
query_path,
|
||||
self.captures,
|
||||
byte_range.clone(),
|
||||
point_range.clone(),
|
||||
self.test,
|
||||
self.quiet,
|
||||
self.time,
|
||||
false,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
CliInput::Test {
|
||||
name,
|
||||
contents,
|
||||
languages: language_names,
|
||||
} => {
|
||||
let path = get_tmp_source_file(&contents)?;
|
||||
let languages = loader.languages_at_path(current_dir)?;
|
||||
let language = languages
|
||||
.iter()
|
||||
.find(|(_, n)| language_names.contains(&Box::from(n.as_str())))
|
||||
.or_else(|| languages.first())
|
||||
.map(|(l, _)| l.clone())
|
||||
.ok_or_else(|| anyhow!("No language found"))?;
|
||||
query::query_file_at_path(
|
||||
&language,
|
||||
&path,
|
||||
&name,
|
||||
query_path,
|
||||
self.captures,
|
||||
byte_range,
|
||||
point_range,
|
||||
self.test,
|
||||
self.quiet,
|
||||
self.time,
|
||||
true,
|
||||
)?;
|
||||
fs::remove_file(path)?;
|
||||
}
|
||||
CliInput::Stdin(contents) => {
|
||||
// Place user input and query output on separate lines
|
||||
println!();
|
||||
|
||||
let path = get_tmp_source_file(&contents)?;
|
||||
let language = loader.select_language(&path, current_dir, None)?;
|
||||
query::query_file_at_path(
|
||||
&language,
|
||||
&path,
|
||||
"stdin",
|
||||
query_path,
|
||||
self.captures,
|
||||
byte_range,
|
||||
point_range,
|
||||
self.test,
|
||||
self.quiet,
|
||||
self.time,
|
||||
true,
|
||||
)?;
|
||||
fs::remove_file(path)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
152
cli/src/query.rs
152
cli/src/query.rs
|
|
@ -17,9 +17,10 @@ use crate::{
|
|||
};
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn query_files_at_paths(
|
||||
pub fn query_file_at_path(
|
||||
language: &Language,
|
||||
paths: Vec<String>,
|
||||
path: &Path,
|
||||
name: &str,
|
||||
query_path: &Path,
|
||||
ordered_captures: bool,
|
||||
byte_range: Option<Range<usize>>,
|
||||
|
|
@ -27,6 +28,7 @@ pub fn query_files_at_paths(
|
|||
should_test: bool,
|
||||
quiet: bool,
|
||||
print_time: bool,
|
||||
stdin: bool,
|
||||
) -> Result<()> {
|
||||
let stdout = io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
|
|
@ -46,26 +48,24 @@ pub fn query_files_at_paths(
|
|||
let mut parser = Parser::new();
|
||||
parser.set_language(language)?;
|
||||
|
||||
for path in paths {
|
||||
let mut results = Vec::new();
|
||||
let mut results = Vec::new();
|
||||
|
||||
if !should_test {
|
||||
writeln!(&mut stdout, "{path}")?;
|
||||
}
|
||||
if !should_test && !stdin {
|
||||
writeln!(&mut stdout, "{name}")?;
|
||||
}
|
||||
|
||||
let source_code =
|
||||
fs::read(&path).with_context(|| format!("Error reading source file {path:?}"))?;
|
||||
let tree = parser.parse(&source_code, None).unwrap();
|
||||
let source_code =
|
||||
fs::read(path).with_context(|| format!("Error reading source file {path:?}"))?;
|
||||
let tree = parser.parse(&source_code, None).unwrap();
|
||||
|
||||
let start = Instant::now();
|
||||
if ordered_captures {
|
||||
let mut captures =
|
||||
query_cursor.captures(&query, tree.root_node(), source_code.as_slice());
|
||||
while let Some((mat, capture_index)) = captures.next() {
|
||||
let capture = mat.captures[*capture_index];
|
||||
let capture_name = &query.capture_names()[capture.index as usize];
|
||||
if !quiet && !should_test {
|
||||
writeln!(
|
||||
let start = Instant::now();
|
||||
if ordered_captures {
|
||||
let mut captures = query_cursor.captures(&query, tree.root_node(), source_code.as_slice());
|
||||
while let Some((mat, capture_index)) = captures.next() {
|
||||
let capture = mat.captures[*capture_index];
|
||||
let capture_name = &query.capture_names()[capture.index as usize];
|
||||
if !quiet && !should_test {
|
||||
writeln!(
|
||||
&mut stdout,
|
||||
" pattern: {:>2}, capture: {} - {capture_name}, start: {}, end: {}, text: `{}`",
|
||||
mat.pattern_index,
|
||||
|
|
@ -74,6 +74,37 @@ pub fn query_files_at_paths(
|
|||
capture.node.end_position(),
|
||||
capture.node.utf8_text(&source_code).unwrap_or("")
|
||||
)?;
|
||||
}
|
||||
results.push(query_testing::CaptureInfo {
|
||||
name: (*capture_name).to_string(),
|
||||
start: to_utf8_point(capture.node.start_position(), source_code.as_slice()),
|
||||
end: to_utf8_point(capture.node.end_position(), source_code.as_slice()),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let mut matches = query_cursor.matches(&query, tree.root_node(), source_code.as_slice());
|
||||
while let Some(m) = matches.next() {
|
||||
if !quiet && !should_test {
|
||||
writeln!(&mut stdout, " pattern: {}", m.pattern_index)?;
|
||||
}
|
||||
for capture in m.captures {
|
||||
let start = capture.node.start_position();
|
||||
let end = capture.node.end_position();
|
||||
let capture_name = &query.capture_names()[capture.index as usize];
|
||||
if !quiet && !should_test {
|
||||
if end.row == start.row {
|
||||
writeln!(
|
||||
&mut stdout,
|
||||
" capture: {} - {capture_name}, start: {start}, end: {end}, text: `{}`",
|
||||
capture.index,
|
||||
capture.node.utf8_text(&source_code).unwrap_or("")
|
||||
)?;
|
||||
} else {
|
||||
writeln!(
|
||||
&mut stdout,
|
||||
" capture: {capture_name}, start: {start}, end: {end}",
|
||||
)?;
|
||||
}
|
||||
}
|
||||
results.push(query_testing::CaptureInfo {
|
||||
name: (*capture_name).to_string(),
|
||||
|
|
@ -81,65 +112,36 @@ pub fn query_files_at_paths(
|
|||
end: to_utf8_point(capture.node.end_position(), source_code.as_slice()),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if query_cursor.did_exceed_match_limit() {
|
||||
writeln!(
|
||||
&mut stdout,
|
||||
" WARNING: Query exceeded maximum number of in-progress captures!"
|
||||
)?;
|
||||
}
|
||||
if should_test {
|
||||
let path_name = if stdin {
|
||||
"stdin"
|
||||
} else {
|
||||
let mut matches =
|
||||
query_cursor.matches(&query, tree.root_node(), source_code.as_slice());
|
||||
while let Some(m) = matches.next() {
|
||||
if !quiet && !should_test {
|
||||
writeln!(&mut stdout, " pattern: {}", m.pattern_index)?;
|
||||
}
|
||||
for capture in m.captures {
|
||||
let start = capture.node.start_position();
|
||||
let end = capture.node.end_position();
|
||||
let capture_name = &query.capture_names()[capture.index as usize];
|
||||
if !quiet && !should_test {
|
||||
if end.row == start.row {
|
||||
writeln!(
|
||||
&mut stdout,
|
||||
" capture: {} - {capture_name}, start: {start}, end: {end}, text: `{}`",
|
||||
capture.index,
|
||||
capture.node.utf8_text(&source_code).unwrap_or("")
|
||||
)?;
|
||||
} else {
|
||||
writeln!(
|
||||
&mut stdout,
|
||||
" capture: {capture_name}, start: {start}, end: {end}",
|
||||
)?;
|
||||
}
|
||||
}
|
||||
results.push(query_testing::CaptureInfo {
|
||||
name: (*capture_name).to_string(),
|
||||
start: to_utf8_point(capture.node.start_position(), source_code.as_slice()),
|
||||
end: to_utf8_point(capture.node.end_position(), source_code.as_slice()),
|
||||
});
|
||||
}
|
||||
Path::new(&path).file_name().unwrap().to_str().unwrap()
|
||||
};
|
||||
match query_testing::assert_expected_captures(&results, path, &mut parser, language) {
|
||||
Ok(assertion_count) => {
|
||||
println!(
|
||||
" ✓ {} ({} assertions)",
|
||||
paint(Some(AnsiColor::Green), path_name),
|
||||
assertion_count
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
println!(" ✗ {}", paint(Some(AnsiColor::Red), path_name));
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
if query_cursor.did_exceed_match_limit() {
|
||||
writeln!(
|
||||
&mut stdout,
|
||||
" WARNING: Query exceeded maximum number of in-progress captures!"
|
||||
)?;
|
||||
}
|
||||
if should_test {
|
||||
let path_name = Path::new(&path).file_name().unwrap().to_str().unwrap();
|
||||
match query_testing::assert_expected_captures(&results, &path, &mut parser, language) {
|
||||
Ok(assertion_count) => {
|
||||
println!(
|
||||
" ✓ {} ({} assertions)",
|
||||
paint(Some(AnsiColor::Green), path_name),
|
||||
assertion_count
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
println!(" ✗ {}", paint(Some(AnsiColor::Red), path_name));
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if print_time {
|
||||
writeln!(&mut stdout, "{:?}", start.elapsed())?;
|
||||
}
|
||||
}
|
||||
if print_time {
|
||||
writeln!(&mut stdout, "{:?}", start.elapsed())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::fs;
|
||||
use std::{fs, path::Path};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use bstr::{BStr, ByteSlice};
|
||||
|
|
@ -224,7 +224,7 @@ pub fn parse_position_comments(
|
|||
|
||||
pub fn assert_expected_captures(
|
||||
infos: &[CaptureInfo],
|
||||
path: &str,
|
||||
path: &Path,
|
||||
parser: &mut Parser,
|
||||
language: &Language,
|
||||
) -> Result<usize> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue