use std::{path::{Path, PathBuf}};
use clap::{App, Arg};
use simplelog::*;
pub struct Args {
pub path: PathBuf,
pub pattern: Vec<String>,
pub before: usize,
pub after: usize,
pub extensions: Vec<String>,
pub regexes: Vec<String>,
pub limit: bool,
pub cpp: bool,
pub unique: bool,
pub force_color: bool,
pub force_query: bool,
pub include: Vec<String>,
pub exclude: Vec<String>,
}
pub fn parse_arguments() -> Args {
let matches = App::new("weggli")
.version("0.2.4")
.author("Felix Wilhelm <fwilhelm@google.com>")
.about(help::ABOUT)
.setting(clap::AppSettings::ArgRequiredElseHelp)
.setting(clap::AppSettings::UnifiedHelpMessage)
.template(help::TEMPLATE)
.help_message("Prints help information.")
.version_message("Prints version information.")
.arg(
Arg::with_name("PATTERN")
.help("Search pattern.")
.long_help(help::PATTERN)
.required(true)
.index(1),
)
.arg(
Arg::with_name("p")
.long("pattern")
.short("p")
.help("Specify additional search patterns.")
.takes_value(true)
.multiple(true)
.number_of_values(1),
)
.arg(
Arg::with_name("PATH")
.help("A file or directory to search.")
.long_help(help::PATH)
.required(true)
.index(2),
)
.arg(
Arg::with_name("v")
.long("verbose")
.short("v")
.multiple(true)
.help("Sets the level of verbosity."),
)
.arg(
Arg::with_name("extensions")
.long("extensions")
.short("e")
.takes_value(true)
.multiple(true)
.help("File extensions to include in the search."),
)
.arg(
Arg::with_name("before")
.long("before")
.short("B")
.takes_value(true)
.help("Lines to print before a match. Default = 5."),
)
.arg(
Arg::with_name("after")
.long("after")
.short("A")
.takes_value(true)
.help("Lines to print after a match. Default = 5."),
)
.arg(
Arg::with_name("limit")
.long("limit")
.short("l")
.takes_value(false)
.help("Only show the first match in each function."),
)
.arg(
Arg::with_name("regex")
.long("regex")
.short("R")
.takes_value(true)
.multiple(true)
.number_of_values(1)
.help("Enforce that a variable has to (not) match a regex.")
.long_help(help::REGEX),
)
.arg(
Arg::with_name("cpp")
.short("X")
.long("cpp")
.takes_value(false)
.help("Enable C++ mode."),
)
.arg(
Arg::with_name("color")
.short("C)")
.long("color")
.takes_value(false)
.help("Force enable color output."),
)
.arg(
Arg::with_name("force")
.long("force")
.short("f")
.takes_value(false)
.help("Force a search even if the queries contains syntax errors."),
)
.arg(
Arg::with_name("unique")
.long("unique")
.short("u")
.takes_value(false)
.help("Enforce uniqueness of variable matches.")
.long_help(help::UNIQUE),
)
.arg(
Arg::with_name("exclude")
.long("exclude")
.takes_value(true)
.multiple(true)
.help("Exclude files that match the given regex."),
)
.arg(
Arg::with_name("include")
.long("include")
.takes_value(true)
.multiple(true)
.help("Only search files that match the given regex."),
)
.get_matches();
let helper = |option_name| -> Vec<String> {
if let Some(v) = matches.values_of(option_name) {
v.map(|v| v.to_string()).collect()
} else {
vec![]
}
};
let level = match matches.occurrences_of("v") {
0 => LevelFilter::Warn,
1 => log::LevelFilter::Info,
_ => log::LevelFilter::Debug,
};
let _ = SimpleLogger::init(level, Config::default());
let directory = Path::new(matches.value_of("PATH").unwrap_or("."));
let mut pattern = vec![matches.value_of("PATTERN").unwrap().to_string()];
if let Some(p) = matches.values_of("p") {
pattern.extend(p.map(|v| v.to_string()))
}
let regexes = helper("regex");
let path = if directory.is_absolute() || directory.to_string_lossy() == "-" {
directory.to_path_buf()
} else {
std::env::current_dir().unwrap().join(directory)
};
let before = match matches.value_of("before") {
Some(v) => v.parse().unwrap_or(5),
None => 5,
};
let after = match matches.value_of("after") {
Some(v) => v.parse().unwrap_or(5),
None => 5,
};
let limit = matches.occurrences_of("limit") > 0;
let unique = matches.occurrences_of("unique") > 0;
let cpp = matches.occurrences_of("cpp") > 0;
let force_color = matches.occurrences_of("color") > 0;
let extensions = {
let e = helper("extensions");
if e.is_empty() {
if !cpp {
vec!["c".to_string(), "h".into()]
} else {
vec![
"cc".to_string(),
"cpp".into(),
"h".into(),
"cxx".into(),
"hpp".into(),
]
}
} else {
e
}
};
let exclude = helper("exclude");
let include = helper("include");
let force_query = matches.occurrences_of("force") > 0;
Args {
path,
pattern,
before,
after,
extensions,
regexes,
limit,
cpp,
unique,
force_color,
force_query,
include,
exclude,
}
}
mod help {
pub const ABOUT: &str = "\
weggli is a semantic search tool for C and C++ codebases.
It is designed to quickly find interesting code pattern in large codebases.
Use -h for short descriptions and --help for more details.
Homepage: https://github.com/googleprojectzero/weggli";
pub const TEMPLATE: &str = "\
{bin} {version}
{author}
{about}
USAGE: {usage}
ARGS:
{positionals}
OPTIONS:
{unified}";
pub const PATTERN: &str = "\
A weggli search pattern. weggli's query language closely resembles
C and C++ with a small number of extra features.
For example, the pattern '{_ $buf[_]; memcpy($buf,_,_);}' will
find all calls to memcpy that directly write into a stack buffer.
Besides normal C and C++ constructs, weggli's query language
supports the following features:
_ Wildcard. Will match on any AST node.
$var Variables. Can be used to write queries that are independent
of identifiers. Variables match on identifiers, types,
field names or namespaces. The --unique option
optionally enforces that $x != $y != $z. The --regex option can
enforce that the variable has to match (or not match) a
regular expression.
_(..) Subexpressions. The _(..) wildcard matches on arbitrary
sub expressions. This can be helpful if you are looking for some
operation involving a variable, but don't know more about it.
For example, _(test) will match on expressions like test+10,
buf[test->size] or f(g(&test));
not: Negative sub queries. Only show results that do not match the
following sub query. For example, '{not: $fv==NULL; not: $fv!=NULL *$v;}'
would find pointer dereferences that are not preceded by a NULL check.
strict: Enable stricter matching. This turns off statement unwrapping and greedy
function name matching. For example 'strict: func();' will not match
on 'if (func() == 1)..' or 'a->func()' anymore.
weggli automatically unwraps expression statements in the query source
to search for the inner expression instead. This means that the query `{func($x);}`
will match on `func(a);`, but also on `if (func(a)) {..}` or `return func(a)`.
Matching on `func(a)` will also match on `func(a,b,c)` or `func(z,a)`.
Similarly, `void func($t $param)` will also match function definitions
with multiple parameters.
Additional patterns can be specified using the --pattern (-p) option. This makes
it possible to search across functions or type definitions.
";
pub const PATH: &str = "\
Input directory or file to search. By default, weggli will search inside
.c and .h files for the default C mode or .cc, .cpp, .cxx, .h and .hpp files when
executing in C++ mode (using the --cpp option).
Alternative file endings can be specified using the --extensions (-e) option.
When combining weggli with other tools or preprocessing steps,
files can also be specified via STDIN by setting the directory to '-'
and piping a list of filenames.
";
pub const REGEX: &str = "\
Filter variable matches based on a regular expression.
This feature uses the Rust regex crate, so most Perl-style
regular expression features are supported.
(see https://docs.rs/regex/1.5.4/regex/#syntax)
Examples:
Find calls to functions starting with the string 'mem':
weggli -R 'func=^mem' '$func(_);'
Find memcpy calls where the last argument is NOT named 'size':
weggli -R 's!=^size$' 'memcpy(_,_,$s);'
";
pub const UNIQUE: &str = "\
Enforce uniqueness of variable matches.
By default, two variables such as $a and $b can match on identical values.
For example, the query '$x=malloc($a); memcpy($x, _, $b);' would
match on both
void *buf = malloc(size);
memcpy(buf, src, size);
and
void *buf = malloc(some_constant);
memcpy(buf, src, size);
Using the unique flag would filter out the first match as $a==$b.
";
}