1#[macro_use]
2mod format;
3mod args;
4mod result;
5#[cfg(feature = "test")]
6mod test_runner;
7#[cfg(feature = "test")]
8mod testing;
9
10use format::Logger;
11pub use result::{
12 CompilationResult, Diagnostic, Kind, QuickFix, Replacement, Severity, Span, Timings, Version,
13};
14
15use std::{path::Path, process::exit, time::Instant};
16
17use pomsky::{
18 Expr,
19 options::{CompileOptions as PomskyCompileOptions, RegexFlavor},
20};
21
22use args::{CompileOptions, GlobalOptions, Input};
23
24pub fn main() {
25 let mut logger = Logger::new();
26
27 let (subcommand, args) = args::parse_args(&logger).unwrap_or_else(|error| {
28 logger.error().println(error);
29 args::print_usage_and_help();
30 exit(2)
31 });
32
33 if args.json {
34 logger = logger.color(false).enabled(false);
35 }
36
37 match subcommand {
38 args::Subcommand::Compile(compile_args) => {
39 if compile_args.test.is_some() {
40 handle_disabled_tests(&logger);
41 show_tests_deprecated_warning(&logger);
42 }
43
44 match &compile_args.input {
45 Input::Value(input) => {
46 compile(None, input, &compile_args, &args).output(
47 &logger,
48 args.json,
49 !compile_args.no_new_line,
50 compile_args.in_test_suite,
51 input,
52 );
53 }
54 Input::File(path) => match std::fs::read_to_string(path) {
55 Ok(input) => {
56 compile(Some(path), &input, &compile_args, &args).output(
57 &logger,
58 args.json,
59 !compile_args.no_new_line,
60 compile_args.in_test_suite,
61 &input,
62 );
63 }
64 Err(error) => {
65 logger.error().println(error);
66 exit(3);
67 }
68 },
69 }
70 }
71 args::Subcommand::Test(_test_args) => {
72 handle_disabled_tests(&logger);
73
74 #[cfg(feature = "test")]
75 testing::test(&logger, args, _test_args);
76 }
77 }
78}
79
80fn handle_disabled_tests(_logger: &Logger) {
81 #[cfg(not(feature = "test"))]
82 {
83 _logger.error_plain(
84 "Testing is not supported, because this pomsky binary \
85 was compiled with the `test` feature disabled!",
86 );
87 exit(4);
88 }
89}
90
91fn show_tests_deprecated_warning(logger: &Logger) {
92 logger
93 .warn()
94 .println("The `--test` argument is deprecated, use the `pomsky test` subcommand instead");
95}
96
97fn compile(
99 path: Option<&Path>,
100 input: &str,
101 #[allow(unused)] compile_args: &CompileOptions,
102 args: &GlobalOptions,
103) -> CompilationResult {
104 let start = Instant::now();
105
106 let options = PomskyCompileOptions {
107 flavor: args.flavor.unwrap_or(RegexFlavor::Pcre),
108 max_range_size: 12,
109 allowed_features: args.allowed_features,
110 };
111
112 let (parsed, warnings) = match Expr::parse(input) {
113 (Some(res), warnings) => (res, warnings),
114 (None, err) => {
115 return CompilationResult::error(
116 path,
117 start.elapsed().as_micros(),
118 0,
119 err,
120 input,
121 &args.warnings,
122 args.json,
123 );
124 }
125 };
126
127 if args.debug {
128 eprintln!("======================== debug ========================");
129 eprintln!("{parsed:?}\n");
130 }
131
132 let mut diagnostics = warnings.collect::<Vec<_>>();
133
134 let (output, compile_diagnostics) = parsed.compile(input, options);
135 diagnostics.extend(compile_diagnostics);
136
137 if let Some(output) = output {
138 #[allow(unused_mut)] let mut time_test = 0;
140
141 #[cfg(feature = "test")]
142 if let Some(test_engine) = compile_args.test {
143 let mut test_errors = Vec::new();
144
145 let start = Instant::now();
146 test_runner::run_tests(&parsed, input, options, test_engine, &mut test_errors);
147 time_test = start.elapsed().as_micros();
148
149 if !test_errors.is_empty() {
150 diagnostics.extend(test_errors);
151 if let Some(last) = diagnostics.last_mut() {
152 let mut prev_help = last.help.take().unwrap_or_default();
153 if !prev_help.is_empty() {
154 prev_help.push('\n');
155 }
156 prev_help += "executed with ";
157 match options.flavor {
158 RegexFlavor::Pcre => {
159 let (major, minor) = pcre2::version();
160 prev_help += &format!("PCRE2 version {major}.{minor}");
161 }
162 flavor => {
163 prev_help += &format!("{flavor:?}");
164 }
165 }
166 last.help = Some(prev_help);
167 }
168
169 return CompilationResult::error(
170 path,
171 start.elapsed().as_micros(),
172 time_test,
173 diagnostics,
174 input,
175 &args.warnings,
176 args.json,
177 );
178 }
179 }
180
181 CompilationResult::success(
182 path,
183 output,
184 start.elapsed().as_micros(),
185 time_test,
186 diagnostics,
187 input,
188 &args.warnings,
189 args.json,
190 )
191 } else {
192 CompilationResult::error(
193 path,
194 start.elapsed().as_micros(),
195 0,
196 diagnostics,
197 input,
198 &args.warnings,
199 args.json,
200 )
201 }
202}