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 default_stderr_level = if global_args.verbose {
57 LevelFilter::DEBUG
58 } else {
59 LevelFilter::WARN
60 };
61
62 let env_filter = EnvFilter::builder()
63 .with_default_directive(default_stderr_level.into())
64 .from_env_lossy();
65
66 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 Layer::boxed(
76 tracing_subscriber::fmt::layer()
77 .compact()
78 .with_writer(Arc::new(std::io::stderr()))
79 .with_ansi(true)
80 .with_filter(env_filter),
81 )
82 };
83
84 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 let rule_trace_verbose_layer = global_args.rule_trace_verbose.clone().map(|x| {
96 let file = File::create(x).expect("Unable to create verbose rule trace file");
97 fmt::layer()
98 .with_writer(file)
99 .with_level(false)
100 .without_time()
101 .with_target(false)
102 .compact()
103 .with_ansi(false)
104 .with_filter(EnvFilter::new("rule_engine_human_verbose=trace"))
105 .with_filter(FilterFn::new(|meta| {
106 meta.target() == "rule_engine_human_verbose"
107 }))
108 });
109
110 let (json_layer, file_layer) = if global_args.log {
111 let mut log_path = global_args
112 .logfile
113 .clone()
114 .unwrap_or_else(|| std::path::PathBuf::from("conjure_oxide.log"));
115 let mut log_json = global_args
116 .logfile_json
117 .clone()
118 .unwrap_or_else(|| std::path::PathBuf::from("conjure_oxide_log.json"));
119
120 log_path.set_extension("log");
121 log_json.set_extension("json");
122
123 let json_log_file = File::options()
124 .truncate(true)
125 .write(true)
126 .create(true)
127 .append(false)
128 .open(log_json)?;
129
130 let log_file = File::options()
131 .truncate(true)
132 .write(true)
133 .create(true)
134 .append(false)
135 .open(log_path)?;
136
137 let json_layer = tracing_subscriber::fmt::layer()
138 .json()
139 .with_writer(Arc::new(json_log_file))
140 .with_filter(LevelFilter::TRACE);
141
142 let file_layer = tracing_subscriber::fmt::layer()
143 .compact()
144 .with_ansi(false)
145 .with_writer(Arc::new(log_file))
146 .with_filter(LevelFilter::TRACE);
147
148 (Some(json_layer), Some(file_layer))
149 } else {
150 (None, None)
151 };
152
153 tracing_subscriber::registry()
154 .with(stderr_layer)
155 .with(rule_trace_layer)
156 .with(rule_trace_verbose_layer)
157 .with(json_layer)
158 .with(file_layer)
159 .init();
160
161 Ok(())
162}
163
164fn 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
175fn run_lsp_server() -> anyhow::Result<()> {
176 server::main();
177 Ok(())
178}
179
180fn run_subcommand(cli: Cli) -> anyhow::Result<()> {
182 let global_args = cli.global_args;
183 match cli.subcommand {
184 cli::Command::Solve(solve_args) => run_solve_command(global_args, solve_args),
185 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 cli::Command::Pretty(pretty_args) => run_pretty_command(global_args, pretty_args),
189 cli::Command::ServerLSP => run_lsp_server(),
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use conjure_cp::parse::conjure_json::{get_example_model, get_example_model_by_path};
196
197 #[test]
198 fn test_get_example_model_success() {
199 let filename = "input";
200 get_example_model(filename).unwrap();
201 }
202
203 #[test]
204 fn test_get_example_model_by_filepath() {
205 let filepath = "../../tests-integration/tests/integration/xyz/input.essence";
206 get_example_model_by_path(filepath).unwrap();
207 }
208
209 #[test]
210 fn test_get_example_model_fail_empty_filename() {
211 let filename = "";
212 get_example_model(filename).unwrap_err();
213 }
214
215 #[test]
216 fn test_get_example_model_fail_empty_filepath() {
217 let filepath = "";
218 get_example_model_by_path(filepath).unwrap_err();
219 }
220}