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 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 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 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 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
152fn 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}