1
#![allow(clippy::unwrap_used)]
2

            
3
#[global_allocator]
4
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
5

            
6
mod cli;
7
mod pretty;
8
mod print_info_schema;
9
mod rule_trace_aggregates;
10
mod solve;
11
mod test_solve;
12
use clap::{CommandFactory, Parser};
13
use clap_complete::generate;
14
use cli::{Cli, GlobalArgs};
15
use pretty::run_pretty_command;
16
use print_info_schema::run_print_info_schema_command;
17
use rule_trace_aggregates::RuleTraceAggregatesHandle;
18
use solve::run_solve_command;
19
use std::fs::File;
20
use std::io;
21
use std::process::exit;
22
use std::sync::Arc;
23
use test_solve::run_test_solve_command;
24

            
25
use conjure_cp_rules as _;
26

            
27
use git_version::git_version;
28
use tracing_subscriber::filter::{FilterFn, LevelFilter};
29
use tracing_subscriber::layer::SubscriberExt as _;
30
use tracing_subscriber::util::SubscriberInitExt as _;
31
use tracing_subscriber::{EnvFilter, Layer, fmt};
32

            
33
use conjure_cp_lsp::server;
34

            
35
struct LoggingState {
36
    rule_trace_aggregates: Option<RuleTraceAggregatesHandle>,
37
}
38

            
39
impl LoggingState {
40
88
    fn flush(&self) {
41
88
        if let Some(handle) = &self.rule_trace_aggregates {
42
12
            handle.flush();
43
76
        }
44
88
    }
45
}
46

            
47
90
pub fn main() {
48
    // exit with 2 instead of 1 on failure,like grep
49
90
    match run() {
50
        Ok(_) => {
51
78
            exit(0);
52
        }
53
12
        Err(e) => {
54
12
            eprintln!("{e:?}");
55
12
            exit(2);
56
        }
57
    }
58
}
59

            
60
90
pub fn run() -> anyhow::Result<()> {
61
90
    let cli = Cli::parse();
62

            
63
90
    if cli.version {
64
        println!("Version: {}", git_version!());
65
        return Ok(());
66
90
    }
67

            
68
90
    let logging_state = setup_logging(&cli.global_args)?;
69
90
    let result = run_subcommand(cli);
70
90
    logging_state.flush();
71
90
    result
72
90
}
73

            
74
90
fn 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
90
    let default_stderr_level = if global_args.verbose {
78
        LevelFilter::DEBUG
79
    } else {
80
90
        LevelFilter::WARN
81
    };
82

            
83
90
    let env_filter = EnvFilter::builder()
84
90
        .with_default_directive(default_stderr_level.into())
85
90
        .from_env_lossy();
86

            
87
90
    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
90
        Layer::boxed(
97
90
            tracing_subscriber::fmt::layer()
98
90
                .compact()
99
90
                .with_writer(Arc::new(std::io::stderr()))
100
90
                .with_ansi(true)
101
90
                .with_filter(env_filter),
102
90
        )
103
    };
104

            
105
90
    let rule_trace_layer = global_args.rule_trace.clone().map(|x| {
106
14
        let file = File::create(x).expect("Unable to create rule trace file");
107
14
        fmt::layer()
108
14
            .with_writer(file)
109
14
            .with_level(false)
110
14
            .without_time()
111
14
            .with_target(false)
112
14
            .with_filter(EnvFilter::new("rule_engine_rule_trace=trace"))
113
2209218
            .with_filter(FilterFn::new(|meta| {
114
2209218
                meta.target() == "rule_engine_rule_trace"
115
2209218
            }))
116
14
    });
117

            
118
90
    let rule_trace_verbose_layer = global_args.rule_trace_verbose.clone().map(|x| {
119
4
        let file = File::create(x).expect("Unable to create verbose rule trace file");
120
4
        fmt::layer()
121
4
            .with_writer(file)
122
4
            .with_level(false)
123
4
            .without_time()
124
4
            .with_target(false)
125
4
            .compact()
126
4
            .with_ansi(false)
127
4
            .with_filter(EnvFilter::new("rule_engine_rule_trace_verbose=trace"))
128
3669876
            .with_filter(FilterFn::new(|meta| {
129
3669876
                meta.target() == "rule_engine_rule_trace_verbose"
130
3669876
            }))
131
4
    });
132

            
133
90
    let rule_trace_aggregates_handle = global_args
134
90
        .rule_trace_aggregates
135
90
        .clone()
136
90
        .map(RuleTraceAggregatesHandle::new)
137
90
        .transpose()?;
138

            
139
90
    let rule_trace_aggregates_layer = rule_trace_aggregates_handle.as_ref().map(|handle| {
140
12
        handle
141
12
            .layer()
142
12
            .with_filter(EnvFilter::new("rule_engine_rule_trace_aggregates=trace"))
143
13009408
            .with_filter(FilterFn::new(|meta| {
144
13009408
                meta.target() == "rule_engine_rule_trace_aggregates"
145
13009408
            }))
146
12
    });
147

            
148
90
    let (json_layer, file_layer) = if global_args.log {
149
6
        let mut log_path = global_args
150
6
            .logfile
151
6
            .clone()
152
6
            .unwrap_or_else(|| std::path::PathBuf::from("conjure_oxide.log"));
153
6
        let mut log_json = global_args
154
6
            .logfile_json
155
6
            .clone()
156
6
            .unwrap_or_else(|| std::path::PathBuf::from("conjure_oxide_log.json"));
157

            
158
6
        log_path.set_extension("log");
159
6
        log_json.set_extension("json");
160

            
161
6
        let json_log_file = File::options()
162
6
            .truncate(true)
163
6
            .write(true)
164
6
            .create(true)
165
6
            .append(false)
166
6
            .open(log_json)?;
167

            
168
6
        let log_file = File::options()
169
6
            .truncate(true)
170
6
            .write(true)
171
6
            .create(true)
172
6
            .append(false)
173
6
            .open(log_path)?;
174

            
175
6
        let json_layer = tracing_subscriber::fmt::layer()
176
6
            .json()
177
6
            .with_writer(Arc::new(json_log_file))
178
6
            .with_filter(LevelFilter::TRACE);
179

            
180
6
        let file_layer = tracing_subscriber::fmt::layer()
181
6
            .compact()
182
6
            .with_ansi(false)
183
6
            .with_writer(Arc::new(log_file))
184
6
            .with_filter(LevelFilter::TRACE);
185

            
186
6
        (Some(json_layer), Some(file_layer))
187
    } else {
188
84
        (None, None)
189
    };
190

            
191
90
    tracing_subscriber::registry()
192
90
        .with(stderr_layer)
193
90
        .with(rule_trace_layer)
194
90
        .with(rule_trace_verbose_layer)
195
90
        .with(rule_trace_aggregates_layer)
196
90
        .with(json_layer)
197
90
        .with(file_layer)
198
90
        .init();
199

            
200
90
    Ok(LoggingState {
201
90
        rule_trace_aggregates: rule_trace_aggregates_handle,
202
90
    })
203
90
}
204

            
205
fn 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

            
216
fn run_lsp_server() -> anyhow::Result<()> {
217
    server::main();
218
    Ok(())
219
}
220

            
221
/// Runs the selected subcommand
222
90
fn run_subcommand(cli: Cli) -> anyhow::Result<()> {
223
90
    let global_args = cli.global_args;
224
90
    match cli.subcommand {
225
66
        cli::Command::Solve(solve_args) => run_solve_command(global_args, solve_args),
226
4
        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
20
        cli::Command::Pretty(pretty_args) => run_pretty_command(global_args, pretty_args),
230
        cli::Command::ServerLSP => run_lsp_server(),
231
    }
232
90
}
233

            
234
#[cfg(test)]
235
mod tests {
236
    use conjure_cp::parse::conjure_json::{get_example_model, get_example_model_by_path};
237

            
238
    #[test]
239
1
    fn test_get_example_model_success() {
240
1
        let filename = "input";
241
1
        get_example_model(filename).unwrap();
242
1
    }
243

            
244
    #[test]
245
1
    fn test_get_example_model_by_filepath() {
246
1
        let filepath = "../../tests-integration/tests/integration/xyz/input.essence";
247
1
        get_example_model_by_path(filepath).unwrap();
248
1
    }
249

            
250
    #[test]
251
1
    fn test_get_example_model_fail_empty_filename() {
252
1
        let filename = "";
253
1
        get_example_model(filename).unwrap_err();
254
1
    }
255

            
256
    #[test]
257
1
    fn test_get_example_model_fail_empty_filepath() {
258
1
        let filepath = "";
259
1
        get_example_model_by_path(filepath).unwrap_err();
260
1
    }
261
}