Skip to main content

conjure_oxide/
main.rs

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