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
pub 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

            
42
pub 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

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

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

            
147
fn run_lsp_server() -> anyhow::Result<()> {
148
    server::main();
149
    Ok(())
150
}
151

            
152
/// Runs the selected subcommand
153
fn 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)]
166
mod 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
}