#[macro_use]
mod format;
mod args;
mod result;
#[cfg(feature = "test")]
mod test_runner;
#[cfg(feature = "test")]
mod testing;
use format::Logger;
pub use result::{
CompilationResult, Diagnostic, Kind, QuickFix, Replacement, Severity, Span, Timings, Version,
};
use std::{path::Path, process::exit, time::Instant};
use pomsky::{
Expr,
options::{CompileOptions as PomskyCompileOptions, RegexFlavor},
};
use args::{CompileOptions, GlobalOptions, Input};
pub fn main() {
let mut logger = Logger::new();
let (subcommand, args) = args::parse_args(&logger).unwrap_or_else(|error| {
logger.error().println(error);
args::print_usage_and_help();
exit(2)
});
if args.json {
logger = logger.color(false).enabled(false);
}
match subcommand {
args::Subcommand::Compile(compile_args) => {
if compile_args.test.is_some() {
handle_disabled_tests(&logger);
show_tests_deprecated_warning(&logger);
}
match &compile_args.input {
Input::Value(input) => {
compile(None, input, &compile_args, &args).output(
&logger,
args.json,
!compile_args.no_new_line,
compile_args.in_test_suite,
input,
);
}
Input::File(path) => match std::fs::read_to_string(path) {
Ok(input) => {
compile(Some(path), &input, &compile_args, &args).output(
&logger,
args.json,
!compile_args.no_new_line,
compile_args.in_test_suite,
&input,
);
}
Err(error) => {
logger.error().println(error);
exit(3);
}
},
}
}
args::Subcommand::Test(_test_args) => {
handle_disabled_tests(&logger);
#[cfg(feature = "test")]
testing::test(&logger, args, _test_args);
}
}
}
fn handle_disabled_tests(_logger: &Logger) {
#[cfg(not(feature = "test"))]
{
_logger.error_plain(
"Testing is not supported, because this pomsky binary \
was compiled with the `test` feature disabled!",
);
exit(4);
}
}
fn show_tests_deprecated_warning(logger: &Logger) {
logger
.warn()
.println("The `--test` argument is deprecated, use the `pomsky test` subcommand instead");
}
fn compile(
path: Option<&Path>,
input: &str,
#[allow(unused)] compile_args: &CompileOptions,
args: &GlobalOptions,
) -> CompilationResult {
let start = Instant::now();
let options = PomskyCompileOptions {
flavor: args.flavor.unwrap_or(RegexFlavor::Pcre),
max_range_size: 12,
allowed_features: args.allowed_features,
};
let (parsed, warnings) = match Expr::parse(input) {
(Some(res), warnings) => (res, warnings),
(None, err) => {
return CompilationResult::error(
path,
start.elapsed().as_micros(),
0,
err,
input,
&args.warnings,
args.json,
);
}
};
if args.debug {
eprintln!("======================== debug ========================");
eprintln!("{parsed:?}\n");
}
let mut diagnostics = warnings.collect::<Vec<_>>();
let (output, compile_diagnostics) = parsed.compile(input, options);
diagnostics.extend(compile_diagnostics);
if let Some(output) = output {
#[allow(unused_mut)] let mut time_test = 0;
#[cfg(feature = "test")]
if let Some(test_engine) = compile_args.test {
let mut test_errors = Vec::new();
let start = Instant::now();
test_runner::run_tests(&parsed, input, options, test_engine, &mut test_errors);
time_test = start.elapsed().as_micros();
if !test_errors.is_empty() {
diagnostics.extend(test_errors);
if let Some(last) = diagnostics.last_mut() {
let mut prev_help = last.help.take().unwrap_or_default();
if !prev_help.is_empty() {
prev_help.push('\n');
}
prev_help += "executed with ";
match options.flavor {
RegexFlavor::Pcre => {
let (major, minor) = pcre2::version();
prev_help += &format!("PCRE2 version {major}.{minor}");
}
flavor => {
prev_help += &format!("{flavor:?}");
}
}
last.help = Some(prev_help);
}
return CompilationResult::error(
path,
start.elapsed().as_micros(),
time_test,
diagnostics,
input,
&args.warnings,
args.json,
);
}
}
CompilationResult::success(
path,
output,
start.elapsed().as_micros(),
time_test,
diagnostics,
input,
&args.warnings,
args.json,
)
} else {
CompilationResult::error(
path,
start.elapsed().as_micros(),
0,
diagnostics,
input,
&args.warnings,
args.json,
)
}
}