Add simple CLI command for running tree queries
This commit is contained in:
parent
d674bc139a
commit
ad3f21b0e5
3 changed files with 130 additions and 40 deletions
|
|
@ -5,6 +5,7 @@ pub mod highlight;
|
|||
pub mod loader;
|
||||
pub mod logger;
|
||||
pub mod parse;
|
||||
pub mod query;
|
||||
pub mod test;
|
||||
pub mod util;
|
||||
pub mod wasm;
|
||||
|
|
|
|||
120
cli/src/main.rs
120
cli/src/main.rs
|
|
@ -1,10 +1,12 @@
|
|||
use clap::{App, AppSettings, Arg, SubCommand};
|
||||
use error::Error;
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
use std::process::exit;
|
||||
use std::{env, fs, u64};
|
||||
use std::{env, fs, io, u64};
|
||||
use tree_sitter::Language;
|
||||
use tree_sitter_cli::{
|
||||
config, error, generate, highlight, loader, logger, parse, test, wasm, web_ui,
|
||||
config, error, generate, highlight, loader, logger, parse, query, test, wasm, web_ui,
|
||||
};
|
||||
|
||||
const BUILD_VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
|
@ -50,7 +52,7 @@ fn run() -> error::Result<()> {
|
|||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("parse")
|
||||
.about("Parse a file")
|
||||
.about("Parse files")
|
||||
.arg(
|
||||
Arg::with_name("path")
|
||||
.index(1)
|
||||
|
|
@ -73,6 +75,18 @@ fn run() -> error::Result<()> {
|
|||
.number_of_values(1),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("query")
|
||||
.about("Search files using a syntax tree query")
|
||||
.arg(Arg::with_name("query-path").index(1).required(true))
|
||||
.arg(
|
||||
Arg::with_name("path")
|
||||
.index(2)
|
||||
.multiple(true)
|
||||
.required(true),
|
||||
)
|
||||
.arg(Arg::with_name("scope").long("scope").takes_value(true)),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("test")
|
||||
.about("Run a parser's tests")
|
||||
|
|
@ -168,7 +182,6 @@ fn run() -> error::Result<()> {
|
|||
let timeout = matches
|
||||
.value_of("timeout")
|
||||
.map_or(0, |t| u64::from_str_radix(t, 10).unwrap());
|
||||
loader.find_all_languages(&config.parser_directories)?;
|
||||
let paths = matches
|
||||
.values_of("path")
|
||||
.unwrap()
|
||||
|
|
@ -176,43 +189,11 @@ fn run() -> error::Result<()> {
|
|||
.collect::<Vec<_>>();
|
||||
let max_path_length = paths.iter().map(|p| p.chars().count()).max().unwrap();
|
||||
let mut has_error = false;
|
||||
loader.find_all_languages(&config.parser_directories)?;
|
||||
for path in paths {
|
||||
let path = Path::new(path);
|
||||
let language = if let Some(scope) = matches.value_of("scope") {
|
||||
if let Some(config) =
|
||||
loader
|
||||
.language_configuration_for_scope(scope)
|
||||
.map_err(Error::wrap(|| {
|
||||
format!("Failed to load language for scope '{}'", scope)
|
||||
}))?
|
||||
{
|
||||
config.0
|
||||
} else {
|
||||
return Error::err(format!("Unknown scope '{}'", scope));
|
||||
}
|
||||
} else if let Some((lang, _)) = loader
|
||||
.language_configuration_for_file_name(path)
|
||||
.map_err(Error::wrap(|| {
|
||||
format!(
|
||||
"Failed to load language for file name {:?}",
|
||||
path.file_name().unwrap()
|
||||
)
|
||||
}))?
|
||||
{
|
||||
lang
|
||||
} else if let Some(lang) = loader
|
||||
.languages_at_path(¤t_dir)
|
||||
.map_err(Error::wrap(|| {
|
||||
"Failed to load language in current directory"
|
||||
}))?
|
||||
.first()
|
||||
.cloned()
|
||||
{
|
||||
lang
|
||||
} else {
|
||||
eprintln!("No language found");
|
||||
return Ok(());
|
||||
};
|
||||
let language =
|
||||
select_language(&mut loader, path, ¤t_dir, matches.value_of("scope"))?;
|
||||
has_error |= parse::parse_file_at_path(
|
||||
language,
|
||||
path,
|
||||
|
|
@ -226,10 +207,25 @@ fn run() -> error::Result<()> {
|
|||
allow_cancellation,
|
||||
)?;
|
||||
}
|
||||
|
||||
if has_error {
|
||||
return Error::err(String::new());
|
||||
}
|
||||
} else if let Some(matches) = matches.subcommand_matches("query") {
|
||||
let paths = matches
|
||||
.values_of("path")
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(Path::new)
|
||||
.collect::<Vec<&Path>>();
|
||||
loader.find_all_languages(&config.parser_directories)?;
|
||||
let language = select_language(
|
||||
&mut loader,
|
||||
paths[0],
|
||||
¤t_dir,
|
||||
matches.value_of("scope"),
|
||||
)?;
|
||||
let query_path = Path::new(matches.value_of("query-path").unwrap());
|
||||
query::query_files_at_paths(language, paths, query_path)?;
|
||||
} else if let Some(matches) = matches.subcommand_matches("highlight") {
|
||||
let paths = matches.values_of("path").unwrap().into_iter();
|
||||
let html_mode = matches.is_present("html");
|
||||
|
|
@ -296,3 +292,47 @@ fn run() -> error::Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn select_language(
|
||||
loader: &mut loader::Loader,
|
||||
path: &Path,
|
||||
current_dir: &Path,
|
||||
scope: Option<&str>,
|
||||
) -> Result<Language, Error> {
|
||||
if let Some(scope) = scope {
|
||||
if let Some(config) =
|
||||
loader
|
||||
.language_configuration_for_scope(scope)
|
||||
.map_err(Error::wrap(|| {
|
||||
format!("Failed to load language for scope '{}'", scope)
|
||||
}))?
|
||||
{
|
||||
Ok(config.0)
|
||||
} else {
|
||||
return Error::err(format!("Unknown scope '{}'", scope));
|
||||
}
|
||||
} else if let Some((lang, _)) =
|
||||
loader
|
||||
.language_configuration_for_file_name(path)
|
||||
.map_err(Error::wrap(|| {
|
||||
format!(
|
||||
"Failed to load language for file name {:?}",
|
||||
path.file_name().unwrap()
|
||||
)
|
||||
}))?
|
||||
{
|
||||
Ok(lang)
|
||||
} else if let Some(lang) = loader
|
||||
.languages_at_path(¤t_dir)
|
||||
.map_err(Error::wrap(|| {
|
||||
"Failed to load language in current directory"
|
||||
}))?
|
||||
.first()
|
||||
.cloned()
|
||||
{
|
||||
Ok(lang)
|
||||
} else {
|
||||
eprintln!("No language found");
|
||||
Error::err("No language found".to_string())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
49
cli/src/query.rs
Normal file
49
cli/src/query.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
use super::error::{Error, Result};
|
||||
use std::fs;
|
||||
use std::io::{self, Write};
|
||||
use std::path::Path;
|
||||
use tree_sitter::{Language, Parser, Query};
|
||||
|
||||
pub fn query_files_at_paths(
|
||||
language: Language,
|
||||
paths: Vec<&Path>,
|
||||
query_path: &Path,
|
||||
) -> Result<()> {
|
||||
let stdout = io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
|
||||
let query_source = fs::read_to_string(query_path).map_err(Error::wrap(|| {
|
||||
format!("Error reading query file {:?}", query_path)
|
||||
}))?;
|
||||
let query = Query::new(language, &query_source)
|
||||
.map_err(|e| Error::new(format!("Query compilation failed: {:?}", e)))?;
|
||||
|
||||
let query_context = query.context();
|
||||
|
||||
let mut parser = Parser::new();
|
||||
parser.set_language(language).map_err(|e| e.to_string())?;
|
||||
|
||||
for path in paths {
|
||||
writeln!(&mut stdout, "{}", path.to_str().unwrap())?;
|
||||
|
||||
let source_code = fs::read(path).map_err(Error::wrap(|| {
|
||||
format!("Error reading source file {:?}", path)
|
||||
}))?;
|
||||
|
||||
let tree = parser.parse(&source_code, None).unwrap();
|
||||
|
||||
for mat in query_context.exec(tree.root_node()) {
|
||||
writeln!(&mut stdout, " pattern: {}", mat.pattern_index())?;
|
||||
for (capture_id, node) in mat.captures() {
|
||||
writeln!(
|
||||
&mut stdout,
|
||||
" {}: {:?}",
|
||||
&query.capture_names()[capture_id],
|
||||
node.utf8_text(&source_code).unwrap_or("")
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue