Skip to main content

conjure_oxide/
main.rs

1#![allow(clippy::unwrap_used)]
2
3#[global_allocator]
4static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
5
6mod cli;
7mod pretty;
8mod print_info_schema;
9mod rule_trace_aggregates;
10mod solve;
11mod test_solve;
12use clap::{CommandFactory, Parser};
13use clap_complete::generate;
14use cli::{Cli, GlobalArgs};
15use pretty::run_pretty_command;
16use print_info_schema::run_print_info_schema_command;
17use rule_trace_aggregates::RuleTraceAggregatesHandle;
18use solve::run_solve_command;
19use std::fs::File;
20use std::io;
21use std::process::exit;
22use std::sync::Arc;
23use test_solve::run_test_solve_command;
24
25use conjure_cp_rules as _;
26
27use git_version::git_version;
28use tracing_subscriber::filter::{FilterFn, LevelFilter};
29use tracing_subscriber::layer::SubscriberExt as _;
30use tracing_subscriber::util::SubscriberInitExt as _;
31use tracing_subscriber::{EnvFilter, Layer, fmt};
32
33use conjure_cp_lsp::server;
34
35struct LoggingState {
36    rule_trace_aggregates: Option<RuleTraceAggregatesHandle>,
37}
38
39impl LoggingState {
40    fn flush(&self) {
41        if let Some(handle) = &self.rule_trace_aggregates {
42            handle.flush();
43        }
44    }
45}
46
47pub fn main() {
48    // exit with 2 instead of 1 on failure,like grep
49    match run() {
50        Ok(_) => {
51            exit(0);
52        }
53        Err(e) => {
54            eprintln!("{e:?}");
55            exit(2);
56        }
57    }
58}
59
60pub fn run() -> anyhow::Result<()> {
61    let cli = Cli::parse();
62
63    if cli.version {
64        println!("Version: {}", git_version!());
65        return Ok(());
66    }
67
68    let logging_state = setup_logging(&cli.global_args)?;
69    let result = run_subcommand(cli);
70    logging_state.flush();
71    result
72}
73
74fn setup_logging(global_args: &GlobalArgs) -> anyhow::Result<LoggingState> {
75    // It consists of composable layers, each of which logs to a different place in a different
76    // format.
77    let default_stderr_level = if global_args.verbose {
78        LevelFilter::DEBUG
79    } else {
80        LevelFilter::WARN
81    };
82
83    let env_filter = EnvFilter::builder()
84        .with_default_directive(default_stderr_level.into())
85        .from_env_lossy();
86
87    let stderr_layer = if global_args.verbose {
88        Layer::boxed(
89            tracing_subscriber::fmt::layer()
90                .pretty()
91                .with_writer(Arc::new(std::io::stderr()))
92                .with_ansi(true)
93                .with_filter(env_filter),
94        )
95    } else {
96        Layer::boxed(
97            tracing_subscriber::fmt::layer()
98                .compact()
99                .with_writer(Arc::new(std::io::stderr()))
100                .with_ansi(true)
101                .with_filter(env_filter),
102        )
103    };
104
105    let rule_trace_layer = global_args.rule_trace.clone().map(|x| {
106        let file = File::create(x).expect("Unable to create rule trace file");
107        fmt::layer()
108            .with_writer(file)
109            .with_level(false)
110            .without_time()
111            .with_target(false)
112            .with_filter(EnvFilter::new("rule_engine_rule_trace=trace"))
113            .with_filter(FilterFn::new(|meta| {
114                meta.target() == "rule_engine_rule_trace"
115            }))
116    });
117
118    let rule_trace_verbose_layer = global_args.rule_trace_verbose.clone().map(|x| {
119        let file = File::create(x).expect("Unable to create verbose rule trace file");
120        fmt::layer()
121            .with_writer(file)
122            .with_level(false)
123            .without_time()
124            .with_target(false)
125            .compact()
126            .with_ansi(false)
127            .with_filter(EnvFilter::new("rule_engine_rule_trace_verbose=trace"))
128            .with_filter(FilterFn::new(|meta| {
129                meta.target() == "rule_engine_rule_trace_verbose"
130            }))
131    });
132
133    let rule_trace_aggregates_handle = global_args
134        .rule_trace_aggregates
135        .clone()
136        .map(RuleTraceAggregatesHandle::new)
137        .transpose()?;
138
139    let rule_trace_aggregates_layer = rule_trace_aggregates_handle.as_ref().map(|handle| {
140        handle
141            .layer()
142            .with_filter(EnvFilter::new("rule_engine_rule_trace_aggregates=trace"))
143            .with_filter(FilterFn::new(|meta| {
144                meta.target() == "rule_engine_rule_trace_aggregates"
145            }))
146    });
147
148    let (json_layer, file_layer) = if global_args.log {
149        let mut log_path = global_args
150            .logfile
151            .clone()
152            .unwrap_or_else(|| std::path::PathBuf::from("conjure_oxide.log"));
153        let mut log_json = global_args
154            .logfile_json
155            .clone()
156            .unwrap_or_else(|| std::path::PathBuf::from("conjure_oxide_log.json"));
157
158        log_path.set_extension("log");
159        log_json.set_extension("json");
160
161        let json_log_file = File::options()
162            .truncate(true)
163            .write(true)
164            .create(true)
165            .append(false)
166            .open(log_json)?;
167
168        let log_file = File::options()
169            .truncate(true)
170            .write(true)
171            .create(true)
172            .append(false)
173            .open(log_path)?;
174
175        let json_layer = tracing_subscriber::fmt::layer()
176            .json()
177            .with_writer(Arc::new(json_log_file))
178            .with_filter(LevelFilter::TRACE);
179
180        let file_layer = tracing_subscriber::fmt::layer()
181            .compact()
182            .with_ansi(false)
183            .with_writer(Arc::new(log_file))
184            .with_filter(LevelFilter::TRACE);
185
186        (Some(json_layer), Some(file_layer))
187    } else {
188        (None, None)
189    };
190
191    tracing_subscriber::registry()
192        .with(stderr_layer)
193        .with(rule_trace_layer)
194        .with(rule_trace_verbose_layer)
195        .with(rule_trace_aggregates_layer)
196        .with(json_layer)
197        .with(file_layer)
198        .init();
199
200    Ok(LoggingState {
201        rule_trace_aggregates: rule_trace_aggregates_handle,
202    })
203}
204
205fn run_completion_command(completion_args: cli::CompletionArgs) -> anyhow::Result<()> {
206    let mut cmd = Cli::command();
207    let shell = completion_args.shell;
208    let name = cmd.get_name().to_string();
209
210    eprintln!("Generating completion for {shell}...");
211
212    generate(shell, &mut cmd, name, &mut io::stdout());
213    Ok(())
214}
215
216fn run_lsp_server() -> anyhow::Result<()> {
217    server::main();
218    Ok(())
219}
220
221/// Runs the selected subcommand
222fn run_subcommand(cli: Cli) -> anyhow::Result<()> {
223    let global_args = cli.global_args;
224    match cli.subcommand {
225        cli::Command::Solve(solve_args) => run_solve_command(global_args, solve_args),
226        cli::Command::TestSolve(local_args) => run_test_solve_command(global_args, local_args),
227        cli::Command::PrintJsonSchema => run_print_info_schema_command(),
228        cli::Command::Completion(completion_args) => run_completion_command(completion_args),
229        cli::Command::Pretty(pretty_args) => run_pretty_command(global_args, pretty_args),
230        cli::Command::ServerLSP => run_lsp_server(),
231    }
232}
233
234#[cfg(test)]
235mod tests {
236    use conjure_cp::parse::conjure_json::{get_example_model, get_example_model_by_path};
237
238    #[test]
239    fn test_get_example_model_success() {
240        let filename = "input";
241        get_example_model(filename).unwrap();
242    }
243
244    #[test]
245    fn test_get_example_model_by_filepath() {
246        let filepath = "../../tests-integration/tests/integration/xyz/input.essence";
247        get_example_model_by_path(filepath).unwrap();
248    }
249
250    #[test]
251    fn test_get_example_model_fail_empty_filename() {
252        let filename = "";
253        get_example_model(filename).unwrap_err();
254    }
255
256    #[test]
257    fn test_get_example_model_fail_empty_filepath() {
258        let filepath = "";
259        get_example_model_by_path(filepath).unwrap_err();
260    }
261}