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    // Logging:
57    //
58    // Using `tracing` framework, but this automatically reads stuff from `log`.
59    //
60    // A Subscriber is responsible for logging.
61    //
62    // It consists of composable layers, each of which logs to a different place in a different
63    // format.
64    let json_log_file = File::options()
65        .create(true)
66        .append(true)
67        .open("conjure_oxide_log.json")?;
68
69    let log_file = File::options()
70        .create(true)
71        .append(true)
72        .open("conjure_oxide.log")?;
73
74    // get log level from env-var RUST_LOG
75
76    let json_layer = tracing_subscriber::fmt::layer()
77        .json()
78        .with_writer(Arc::new(json_log_file))
79        .with_filter(LevelFilter::TRACE);
80
81    let file_layer = tracing_subscriber::fmt::layer()
82        .compact()
83        .with_ansi(false)
84        .with_writer(Arc::new(log_file))
85        .with_filter(LevelFilter::TRACE);
86
87    let default_stderr_level = if global_args.verbose {
88        LevelFilter::DEBUG
89    } else {
90        LevelFilter::WARN
91    };
92
93    let env_filter = EnvFilter::builder()
94        .with_default_directive(default_stderr_level.into())
95        .from_env_lossy();
96
97    let stderr_layer = if global_args.verbose {
98        Layer::boxed(
99            tracing_subscriber::fmt::layer()
100                .pretty()
101                .with_writer(Arc::new(std::io::stderr()))
102                .with_ansi(true)
103                .with_filter(env_filter),
104        )
105    } else {
106        Layer::boxed(
107            tracing_subscriber::fmt::layer()
108                .compact()
109                .with_writer(Arc::new(std::io::stderr()))
110                .with_ansi(true)
111                .with_filter(env_filter),
112        )
113    };
114
115    let human_rule_trace_layer = global_args.human_rule_trace.clone().map(|x| {
116        let file = File::create(x).expect("Unable to create rule trace file");
117        fmt::layer()
118            .with_writer(file)
119            .with_level(false)
120            .without_time()
121            .with_target(false)
122            .with_filter(EnvFilter::new("rule_engine_human=trace"))
123            .with_filter(FilterFn::new(|meta| meta.target() == "rule_engine_human"))
124    });
125    // load the loggers
126    tracing_subscriber::registry()
127        .with(json_layer)
128        .with(stderr_layer)
129        .with(file_layer)
130        .with(human_rule_trace_layer)
131        .init();
132
133    Ok(())
134}
135
136fn run_completion_command(completion_args: cli::CompletionArgs) -> anyhow::Result<()> {
137    let mut cmd = Cli::command();
138    let shell = completion_args.shell;
139    let name = cmd.get_name().to_string();
140
141    eprintln!("Generating completion for {shell}...");
142
143    generate(shell, &mut cmd, name, &mut io::stdout());
144    Ok(())
145}
146
147fn run_lsp_server() -> anyhow::Result<()> {
148    server::main();
149    Ok(())
150}
151
152/// Runs the selected subcommand
153fn run_subcommand(cli: Cli) -> anyhow::Result<()> {
154    let global_args = cli.global_args;
155    match cli.subcommand {
156        cli::Command::Solve(solve_args) => run_solve_command(global_args, solve_args),
157        cli::Command::TestSolve(local_args) => run_test_solve_command(global_args, local_args),
158        cli::Command::PrintJsonSchema => run_print_info_schema_command(),
159        cli::Command::Completion(completion_args) => run_completion_command(completion_args),
160        cli::Command::Pretty(pretty_args) => run_pretty_command(global_args, pretty_args),
161        cli::Command::ServerLSP => run_lsp_server(),
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use conjure_cp::parse::conjure_json::{get_example_model, get_example_model_by_path};
168
169    #[test]
170    fn test_get_example_model_success() {
171        let filename = "input";
172        get_example_model(filename).unwrap();
173    }
174
175    #[test]
176    fn test_get_example_model_by_filepath() {
177        let filepath = "../../tests-integration/tests/integration/xyz/input.essence";
178        get_example_model_by_path(filepath).unwrap();
179    }
180
181    #[test]
182    fn test_get_example_model_fail_empty_filename() {
183        let filename = "";
184        get_example_model(filename).unwrap_err();
185    }
186
187    #[test]
188    fn test_get_example_model_fail_empty_filepath() {
189        let filepath = "";
190        get_example_model_by_path(filepath).unwrap_err();
191    }
192}