pomsky_bin/
lib.rs

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
97// TODO: refactor this
98fn 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)] // the `mut` is only needed when cfg(feature = "test")
139        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}