Lines
85.54 %
Functions
50 %
#![allow(clippy::unwrap_used)]
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
mod cli;
mod pretty;
mod print_info_schema;
mod rule_trace_aggregates;
mod solve;
mod test_solve;
use clap::{CommandFactory, Parser};
use clap_complete::generate;
use cli::{Cli, GlobalArgs};
use pretty::run_pretty_command;
use print_info_schema::run_print_info_schema_command;
use rule_trace_aggregates::RuleTraceAggregatesHandle;
use solve::run_solve_command;
use std::fs::File;
use std::io;
use std::process::exit;
use std::sync::Arc;
use test_solve::run_test_solve_command;
use conjure_cp_rules as _;
use git_version::git_version;
use tracing_subscriber::filter::{FilterFn, LevelFilter};
use tracing_subscriber::layer::SubscriberExt as _;
use tracing_subscriber::util::SubscriberInitExt as _;
use tracing_subscriber::{EnvFilter, Layer, fmt};
use conjure_cp_lsp::server;
struct LoggingState {
rule_trace_aggregates: Option<RuleTraceAggregatesHandle>,
}
impl LoggingState {
fn flush(&self) {
if let Some(handle) = &self.rule_trace_aggregates {
handle.flush();
pub fn main() {
// exit with 2 instead of 1 on failure,like grep
match run() {
Ok(_) => {
exit(0);
Err(e) => {
eprintln!("{e:?}");
exit(2);
pub fn run() -> anyhow::Result<()> {
let cli = Cli::parse();
if cli.version {
println!("Version: {}", git_version!());
return Ok(());
let logging_state = setup_logging(&cli.global_args)?;
let result = run_subcommand(cli);
logging_state.flush();
result
fn setup_logging(global_args: &GlobalArgs) -> anyhow::Result<LoggingState> {
// It consists of composable layers, each of which logs to a different place in a different
// format.
let default_stderr_level = if global_args.verbose {
LevelFilter::DEBUG
} else {
LevelFilter::WARN
};
let env_filter = EnvFilter::builder()
.with_default_directive(default_stderr_level.into())
.from_env_lossy();
let stderr_layer = if global_args.verbose {
Layer::boxed(
tracing_subscriber::fmt::layer()
.pretty()
.with_writer(Arc::new(std::io::stderr()))
.with_ansi(true)
.with_filter(env_filter),
)
.compact()
let rule_trace_layer = global_args.rule_trace.clone().map(|x| {
let file = File::create(x).expect("Unable to create rule trace file");
fmt::layer()
.with_writer(file)
.with_level(false)
.without_time()
.with_target(false)
.with_filter(EnvFilter::new("rule_engine_rule_trace=trace"))
.with_filter(FilterFn::new(|meta| {
meta.target() == "rule_engine_rule_trace"
}))
});
let rule_trace_verbose_layer = global_args.rule_trace_verbose.clone().map(|x| {
let file = File::create(x).expect("Unable to create verbose rule trace file");
.with_ansi(false)
.with_filter(EnvFilter::new("rule_engine_rule_trace_verbose=trace"))
meta.target() == "rule_engine_rule_trace_verbose"
let rule_trace_aggregates_handle = global_args
.rule_trace_aggregates
.clone()
.map(RuleTraceAggregatesHandle::new)
.transpose()?;
let rule_trace_aggregates_layer = rule_trace_aggregates_handle.as_ref().map(|handle| {
handle
.layer()
.with_filter(EnvFilter::new("rule_engine_rule_trace_aggregates=trace"))
meta.target() == "rule_engine_rule_trace_aggregates"
let (json_layer, file_layer) = if global_args.log {
let mut log_path = global_args
.logfile
.unwrap_or_else(|| std::path::PathBuf::from("conjure_oxide.log"));
let mut log_json = global_args
.logfile_json
.unwrap_or_else(|| std::path::PathBuf::from("conjure_oxide_log.json"));
log_path.set_extension("log");
log_json.set_extension("json");
let json_log_file = File::options()
.truncate(true)
.write(true)
.create(true)
.append(false)
.open(log_json)?;
let log_file = File::options()
.open(log_path)?;
let json_layer = tracing_subscriber::fmt::layer()
.json()
.with_writer(Arc::new(json_log_file))
.with_filter(LevelFilter::TRACE);
let file_layer = tracing_subscriber::fmt::layer()
.with_writer(Arc::new(log_file))
(Some(json_layer), Some(file_layer))
(None, None)
tracing_subscriber::registry()
.with(stderr_layer)
.with(rule_trace_layer)
.with(rule_trace_verbose_layer)
.with(rule_trace_aggregates_layer)
.with(json_layer)
.with(file_layer)
.init();
Ok(LoggingState {
rule_trace_aggregates: rule_trace_aggregates_handle,
})
fn run_completion_command(completion_args: cli::CompletionArgs) -> anyhow::Result<()> {
let mut cmd = Cli::command();
let shell = completion_args.shell;
let name = cmd.get_name().to_string();
eprintln!("Generating completion for {shell}...");
generate(shell, &mut cmd, name, &mut io::stdout());
Ok(())
fn run_lsp_server() -> anyhow::Result<()> {
server::main();
/// Runs the selected subcommand
fn run_subcommand(cli: Cli) -> anyhow::Result<()> {
let global_args = cli.global_args;
match cli.subcommand {
cli::Command::Solve(solve_args) => run_solve_command(global_args, solve_args),
cli::Command::TestSolve(local_args) => run_test_solve_command(global_args, local_args),
cli::Command::PrintJsonSchema => run_print_info_schema_command(),
cli::Command::Completion(completion_args) => run_completion_command(completion_args),
cli::Command::Pretty(pretty_args) => run_pretty_command(global_args, pretty_args),
cli::Command::ServerLSP => run_lsp_server(),
#[cfg(test)]
mod tests {
use conjure_cp::parse::conjure_json::{get_example_model, get_example_model_by_path};
#[test]
fn test_get_example_model_success() {
let filename = "input";
get_example_model(filename).unwrap();
fn test_get_example_model_by_filepath() {
let filepath = "../../tests-integration/tests/integration/xyz/input.essence";
get_example_model_by_path(filepath).unwrap();
fn test_get_example_model_fail_empty_filename() {
let filename = "";
get_example_model(filename).unwrap_err();
fn test_get_example_model_fail_empty_filepath() {
let filepath = "";
get_example_model_by_path(filepath).unwrap_err();