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 {
66 LevelFilter::DEBUG
67 } else {
68 LevelFilter::WARN
69 };
70
71 let env_filter = EnvFilter::builder()
72 .with_default_directive(default_stderr_level.into())
73 .from_env_lossy();
74
75 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 Layer::boxed(
85 tracing_subscriber::fmt::layer()
86 .compact()
87 .with_writer(Arc::new(std::io::stderr()))
88 .with_ansi(true)
89 .with_filter(env_filter),
90 )
91 };
92
93 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 if global_args.log {
106 let mut log_path = global_args
107 .logfile
108 .clone()
109 .unwrap_or_else(|| std::path::PathBuf::from("conjure_oxide.log"));
110 let mut log_json = global_args
111 .logfile_json
112 .clone()
113 .unwrap_or_else(|| std::path::PathBuf::from("conjure_oxide_log.json"));
114
115 log_path.set_extension("log");
116 log_json.set_extension("json");
117
118 let json_log_file = File::options()
119 .truncate(true)
120 .write(true)
121 .create(true)
122 .append(false)
123 .open(log_json)?;
124
125 let log_file = File::options()
126 .truncate(true)
127 .write(true)
128 .create(true)
129 .append(false)
130 .open(log_path)?;
131
132 let json_layer = tracing_subscriber::fmt::layer()
134 .json()
135 .with_writer(Arc::new(json_log_file))
136 .with_filter(LevelFilter::TRACE);
137
138 let file_layer = tracing_subscriber::fmt::layer()
139 .compact()
140 .with_ansi(false)
141 .with_writer(Arc::new(log_file))
142 .with_filter(LevelFilter::TRACE);
143
144 tracing_subscriber::registry()
146 .with(json_layer)
147 .with(stderr_layer)
148 .with(file_layer)
149 .with(human_rule_trace_layer)
150 .init();
151 }
152
153 Ok(())
154}
155
156fn 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
167fn run_lsp_server() -> anyhow::Result<()> {
168 server::main();
169 Ok(())
170}
171
172fn run_subcommand(cli: Cli) -> anyhow::Result<()> {
174 let global_args = cli.global_args;
175 match cli.subcommand {
176 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 cli::Command::Pretty(pretty_args) => run_pretty_command(global_args, pretty_args),
181 cli::Command::ServerLSP => run_lsp_server(),
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use conjure_cp::parse::conjure_json::{get_example_model, get_example_model_by_path};
188
189 #[test]
190 fn test_get_example_model_success() {
191 let filename = "input";
192 get_example_model(filename).unwrap();
193 }
194
195 #[test]
196 fn test_get_example_model_by_filepath() {
197 let filepath = "../../tests-integration/tests/integration/xyz/input.essence";
198 get_example_model_by_path(filepath).unwrap();
199 }
200
201 #[test]
202 fn test_get_example_model_fail_empty_filename() {
203 let filename = "";
204 get_example_model(filename).unwrap_err();
205 }
206
207 #[test]
208 fn test_get_example_model_fail_empty_filepath() {
209 let filepath = "";
210 get_example_model_by_path(filepath).unwrap_err();
211 }
212}