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
70
pub fn main() {
30
    // exit with 2 instead of 1 on failure,like grep
31
70
    match run() {
32
        Ok(_) => {
33
58
            exit(0);
34
        }
35
12
        Err(e) => {
36
12
            eprintln!("{e:?}");
37
12
            exit(2);
38
        }
39
    }
40
}
41

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

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

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

            
52
54
    run_subcommand(cli)
53
54
}
54

            
55
70
fn setup_logging(global_args: &GlobalArgs) -> anyhow::Result<()> {
56
70
    let default_stderr_level = if global_args.verbose {
57
        LevelFilter::DEBUG
58
    } else {
59
70
        LevelFilter::WARN
60
    };
61

            
62
70
    let env_filter = EnvFilter::builder()
63
70
        .with_default_directive(default_stderr_level.into())
64
70
        .from_env_lossy();
65

            
66
70
    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
70
        Layer::boxed(
76
70
            tracing_subscriber::fmt::layer()
77
70
                .compact()
78
70
                .with_writer(Arc::new(std::io::stderr()))
79
70
                .with_ansi(true)
80
70
                .with_filter(env_filter),
81
70
        )
82
    };
83

            
84
70
    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
70
    let rule_trace_verbose_layer = global_args.rule_trace_verbose.clone().map(|x| {
96
8
        let file = File::create(x).expect("Unable to create verbose rule trace file");
97
8
        fmt::layer()
98
8
            .with_writer(file)
99
8
            .with_level(false)
100
8
            .without_time()
101
8
            .with_target(false)
102
8
            .compact()
103
8
            .with_ansi(false)
104
8
            .with_filter(EnvFilter::new("rule_engine_human_verbose=trace"))
105
9154784
            .with_filter(FilterFn::new(|meta| {
106
9154784
                meta.target() == "rule_engine_human_verbose"
107
9154784
            }))
108
8
    });
109

            
110
70
    let (json_layer, file_layer) = if global_args.log {
111
12
        let mut log_path = global_args
112
12
            .logfile
113
12
            .clone()
114
12
            .unwrap_or_else(|| std::path::PathBuf::from("conjure_oxide.log"));
115
12
        let mut log_json = global_args
116
12
            .logfile_json
117
12
            .clone()
118
12
            .unwrap_or_else(|| std::path::PathBuf::from("conjure_oxide_log.json"));
119

            
120
12
        log_path.set_extension("log");
121
12
        log_json.set_extension("json");
122

            
123
12
        let json_log_file = File::options()
124
12
            .truncate(true)
125
12
            .write(true)
126
12
            .create(true)
127
12
            .append(false)
128
12
            .open(log_json)?;
129

            
130
12
        let log_file = File::options()
131
12
            .truncate(true)
132
12
            .write(true)
133
12
            .create(true)
134
12
            .append(false)
135
12
            .open(log_path)?;
136

            
137
12
        let json_layer = tracing_subscriber::fmt::layer()
138
12
            .json()
139
12
            .with_writer(Arc::new(json_log_file))
140
12
            .with_filter(LevelFilter::TRACE);
141

            
142
12
        let file_layer = tracing_subscriber::fmt::layer()
143
12
            .compact()
144
12
            .with_ansi(false)
145
12
            .with_writer(Arc::new(log_file))
146
12
            .with_filter(LevelFilter::TRACE);
147

            
148
12
        (Some(json_layer), Some(file_layer))
149
    } else {
150
58
        (None, None)
151
    };
152

            
153
70
    tracing_subscriber::registry()
154
70
        .with(stderr_layer)
155
70
        .with(rule_trace_layer)
156
70
        .with(rule_trace_verbose_layer)
157
70
        .with(json_layer)
158
70
        .with(file_layer)
159
70
        .init();
160

            
161
70
    Ok(())
162
70
}
163

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

            
175
fn run_lsp_server() -> anyhow::Result<()> {
176
    server::main();
177
    Ok(())
178
}
179

            
180
/// Runs the selected subcommand
181
70
fn run_subcommand(cli: Cli) -> anyhow::Result<()> {
182
70
    let global_args = cli.global_args;
183
70
    match cli.subcommand {
184
40
        cli::Command::Solve(solve_args) => run_solve_command(global_args, solve_args),
185
2
        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
28
        cli::Command::Pretty(pretty_args) => run_pretty_command(global_args, pretty_args),
189
        cli::Command::ServerLSP => run_lsp_server(),
190
    }
191
70
}
192

            
193
#[cfg(test)]
194
mod tests {
195
    use conjure_cp::parse::conjure_json::{get_example_model, get_example_model_by_path};
196

            
197
    #[test]
198
3
    fn test_get_example_model_success() {
199
3
        let filename = "input";
200
3
        get_example_model(filename).unwrap();
201
3
    }
202

            
203
    #[test]
204
3
    fn test_get_example_model_by_filepath() {
205
3
        let filepath = "../../tests-integration/tests/integration/xyz/input.essence";
206
3
        get_example_model_by_path(filepath).unwrap();
207
3
    }
208

            
209
    #[test]
210
3
    fn test_get_example_model_fail_empty_filename() {
211
3
        let filename = "";
212
3
        get_example_model(filename).unwrap_err();
213
3
    }
214

            
215
    #[test]
216
3
    fn test_get_example_model_fail_empty_filepath() {
217
3
        let filepath = "";
218
3
        get_example_model_by_path(filepath).unwrap_err();
219
3
    }
220
}