1
#![allow(clippy::unwrap_used)]
2
mod cli;
3
mod pretty;
4
mod print_info_schema;
5
mod solve;
6
mod test_solve;
7
use clap::{CommandFactory, Parser};
8
use clap_complete::generate;
9
use cli::{Cli, GlobalArgs};
10
use pretty::run_pretty_command;
11
use print_info_schema::run_print_info_schema_command;
12
use solve::run_solve_command;
13
use std::fs::File;
14
use std::io;
15
use std::process::exit;
16
use std::sync::Arc;
17
use test_solve::run_test_solve_command;
18

            
19
use conjure_cp_rules as _;
20

            
21
use git_version::git_version;
22
use tracing_subscriber::filter::{FilterFn, LevelFilter};
23
use tracing_subscriber::layer::SubscriberExt as _;
24
use tracing_subscriber::util::SubscriberInitExt as _;
25
use tracing_subscriber::{EnvFilter, Layer, fmt};
26

            
27
use conjure_cp_lsp::server;
28

            
29
16
pub fn main() {
30
    // exit with 2 instead of 1 on failure,like grep
31
16
    match run() {
32
        Ok(_) => {
33
12
            exit(0);
34
        }
35
4
        Err(e) => {
36
4
            eprintln!("{e:?}");
37
4
            exit(2);
38
        }
39
    }
40
}
41

            
42
16
pub fn run() -> anyhow::Result<()> {
43
16
    let cli = Cli::parse();
44

            
45
16
    if cli.version {
46
        println!("Version: {}", git_version!());
47
        return Ok(());
48
16
    }
49

            
50
16
    setup_logging(&cli.global_args)?;
51

            
52
16
    run_subcommand(cli)
53
16
}
54

            
55
16
fn 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

            
65
16
    let default_stderr_level = if global_args.verbose {
66
        LevelFilter::DEBUG
67
    } else {
68
16
        LevelFilter::WARN
69
    };
70

            
71
16
    let env_filter = EnvFilter::builder()
72
16
        .with_default_directive(default_stderr_level.into())
73
16
        .from_env_lossy();
74

            
75
16
    let stderr_layer = if global_args.verbose {
76
        Layer::boxed(
77
            tracing_subscriber::fmt::layer()
78
                .pretty()
79
                .with_writer(Arc::new(std::io::stderr()))
80
                .with_ansi(true)
81
                .with_filter(env_filter),
82
        )
83
    } else {
84
16
        Layer::boxed(
85
16
            tracing_subscriber::fmt::layer()
86
16
                .compact()
87
16
                .with_writer(Arc::new(std::io::stderr()))
88
16
                .with_ansi(true)
89
16
                .with_filter(env_filter),
90
16
        )
91
    };
92

            
93
16
    let human_rule_trace_layer = global_args.human_rule_trace.clone().map(|x| {
94
        let file = File::create(x).expect("Unable to create rule trace file");
95
        fmt::layer()
96
            .with_writer(file)
97
            .with_level(false)
98
            .without_time()
99
            .with_target(false)
100
            .with_filter(EnvFilter::new("rule_engine_human=trace"))
101
            .with_filter(FilterFn::new(|meta| meta.target() == "rule_engine_human"))
102
    });
103

            
104
    // only setup logs IF the argument is passed
105
16
    if global_args.log {
106
6
        let mut log_path = global_args
107
6
            .logfile
108
6
            .clone()
109
6
            .unwrap_or_else(|| std::path::PathBuf::from("conjure_oxide.log"));
110
6
        let mut log_json = global_args
111
6
            .logfile_json
112
6
            .clone()
113
6
            .unwrap_or_else(|| std::path::PathBuf::from("conjure_oxide_log.json"));
114

            
115
6
        log_path.set_extension("log");
116
6
        log_json.set_extension("json");
117

            
118
6
        let json_log_file = File::options()
119
6
            .truncate(true)
120
6
            .write(true)
121
6
            .create(true)
122
6
            .append(false)
123
6
            .open(log_json)?;
124

            
125
6
        let log_file = File::options()
126
6
            .truncate(true)
127
6
            .write(true)
128
6
            .create(true)
129
6
            .append(false)
130
6
            .open(log_path)?;
131

            
132
        // get log level from env-var RUST_LOG
133
6
        let json_layer = tracing_subscriber::fmt::layer()
134
6
            .json()
135
6
            .with_writer(Arc::new(json_log_file))
136
6
            .with_filter(LevelFilter::TRACE);
137

            
138
6
        let file_layer = tracing_subscriber::fmt::layer()
139
6
            .compact()
140
6
            .with_ansi(false)
141
6
            .with_writer(Arc::new(log_file))
142
6
            .with_filter(LevelFilter::TRACE);
143

            
144
        // load the loggers
145
6
        tracing_subscriber::registry()
146
6
            .with(json_layer)
147
6
            .with(stderr_layer)
148
6
            .with(file_layer)
149
6
            .with(human_rule_trace_layer)
150
6
            .init();
151
10
    }
152

            
153
16
    Ok(())
154
16
}
155

            
156
fn run_completion_command(completion_args: cli::CompletionArgs) -> anyhow::Result<()> {
157
    let mut cmd = Cli::command();
158
    let shell = completion_args.shell;
159
    let name = cmd.get_name().to_string();
160

            
161
    eprintln!("Generating completion for {shell}...");
162

            
163
    generate(shell, &mut cmd, name, &mut io::stdout());
164
    Ok(())
165
}
166

            
167
fn run_lsp_server() -> anyhow::Result<()> {
168
    server::main();
169
    Ok(())
170
}
171

            
172
/// Runs the selected subcommand
173
16
fn run_subcommand(cli: Cli) -> anyhow::Result<()> {
174
16
    let global_args = cli.global_args;
175
16
    match cli.subcommand {
176
14
        cli::Command::Solve(solve_args) => run_solve_command(global_args, solve_args),
177
        cli::Command::TestSolve(local_args) => run_test_solve_command(global_args, local_args),
178
        cli::Command::PrintJsonSchema => run_print_info_schema_command(),
179
        cli::Command::Completion(completion_args) => run_completion_command(completion_args),
180
2
        cli::Command::Pretty(pretty_args) => run_pretty_command(global_args, pretty_args),
181
        cli::Command::ServerLSP => run_lsp_server(),
182
    }
183
16
}
184

            
185
#[cfg(test)]
186
mod tests {
187
    use conjure_cp::parse::conjure_json::{get_example_model, get_example_model_by_path};
188

            
189
    #[test]
190
1
    fn test_get_example_model_success() {
191
1
        let filename = "input";
192
1
        get_example_model(filename).unwrap();
193
1
    }
194

            
195
    #[test]
196
1
    fn test_get_example_model_by_filepath() {
197
1
        let filepath = "../../tests-integration/tests/integration/xyz/input.essence";
198
1
        get_example_model_by_path(filepath).unwrap();
199
1
    }
200

            
201
    #[test]
202
1
    fn test_get_example_model_fail_empty_filename() {
203
1
        let filename = "";
204
1
        get_example_model(filename).unwrap_err();
205
1
    }
206

            
207
    #[test]
208
1
    fn test_get_example_model_fail_empty_filepath() {
209
1
        let filepath = "";
210
1
        get_example_model_by_path(filepath).unwrap_err();
211
1
    }
212
}