Skip to content

Commit

Permalink
Merge pull request conjure-cp#286 from gskorokhod/simple-cli
Browse files Browse the repository at this point in the history
(minor) Add CLI
  • Loading branch information
ozgurakgun authored Apr 2, 2024
2 parents fea3f18 + 573aeef commit 4945113
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 37 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

104 changes: 81 additions & 23 deletions conjure_oxide/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use std::process::exit;
use anyhow::Result as AnyhowResult;
use anyhow::{anyhow, bail};
use clap::{arg, command, Parser};
use conjure_oxide::utils::json::sort_json_object;
use schemars::schema_for;
use serde_json::json;
use serde_json::to_string_pretty;
Expand All @@ -27,23 +26,46 @@ use conjure_oxide::SolverFamily;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[arg(long, value_name = "SOLVER")]
solver: Option<String>,

#[arg(
value_name = "INPUT_ESSENCE",
default_value = "./conjure_oxide/tests/integration/xyz/input.essence"
default_value = "./conjure_oxide/tests/integration/xyz/input.essence",
help = "The input Essence file"
)]
input_file: PathBuf,

#[arg(
long,
value_name = "EXTRA_RULE_SETS",
help = "Names of extra rule sets to enable"
)]
extra_rule_sets: Vec<String>,

#[arg(
long,
value_enum,
value_name = "SOLVER",
short = 's',
help = "Solver family use (Minion by default)"
)]
solver: Option<SolverFamily>, // ToDo this should probably set the solver adapter

// TODO: subcommands instead of these being a flag.
#[arg(long, default_value_t = false)]
/// Prints the schema for the info JSON then exits.
#[arg(
long,
default_value_t = false,
help = "Print the schema for the info JSON and exit"
)]
print_info_schema: bool,

#[arg(long)]
/// Saves execution info as JSON to the given file-path.
#[arg(long, help = "Save execution info as JSON to the given file-path.")]
info_json_path: Option<PathBuf>,

#[arg(
long,
short = 'o',
help = "Save solutions to a JSON file (prints to stdin by default)"
)]
output: Option<PathBuf>,
}

#[allow(clippy::unwrap_used)]
Expand All @@ -57,21 +79,34 @@ pub fn main() -> AnyhowResult<()> {
return Ok(());
}

let target_family = SolverFamily::Minion; // ToDo get this from CLI input
let extra_rule_sets: Vec<&str> = vec!["Constant"]; // ToDo get this from CLI input

let target_family = cli.solver.unwrap_or(SolverFamily::Minion);
let extra_rule_sets: Vec<String> = cli.extra_rule_sets;
let out_file: Option<File> = match &cli.output {
None => None,
Some(pth) => Some(
File::options()
.create(true)
.truncate(true)
.write(true)
.open(pth)?,
),
};
#[allow(clippy::unwrap_used)]
let log_file = File::options()
.create(true)
.append(true)
.open("conjure_oxide.log")
.unwrap();
.open("conjure_oxide.log")?;

Builder::new()
.with_target_writer("info", new_writer(stdout()))
.with_target_writer("file", new_writer(log_file))
.init();

if target_family != SolverFamily::Minion {
log::error!("Only the Minion solver is currently supported!");
exit(1);
}

let rule_sets = match resolve_rule_sets(target_family, &extra_rule_sets) {
Ok(rs) => rs,
Err(e) => {
Expand All @@ -80,23 +115,30 @@ pub fn main() -> AnyhowResult<()> {
}
};

let pretty_rule_sets = rule_sets
.iter()
.map(|rule_set| rule_set.name)
.collect::<Vec<_>>()
.join(", ");

println!("Enabled rule sets: [{}]", pretty_rule_sets);
log::info!(
target: "info",
target: "file",
"Rule sets: {}",
rule_sets.iter().map(|rule_set| rule_set.name).collect::<Vec<_>>().join(", ")
pretty_rule_sets
);

let rule_priorities = get_rule_priorities(&rule_sets)?;
let rules_vec = get_rules_vec(&rule_priorities);

log::info!(target: "info",
log::info!(target: "file",
"Rules and priorities: {}",
rules_vec.iter()
.map(|rule| format!("{}: {}", rule.name, rule_priorities.get(rule).unwrap_or(&0)))
.collect::<Vec<_>>()
.join(", "));

log::info!("Input file: {}", cli.input_file.display());
log::info!(target: "file", "Input file: {}", cli.input_file.display());
let input_file: &str = cli.input_file.to_str().ok_or(anyhow!(
"Given input_file could not be converted to a string"
))?;
Expand Down Expand Up @@ -133,15 +175,31 @@ pub fn main() -> AnyhowResult<()> {

let mut model = model_from_json(&astjson, context.clone())?;

log::info!("Initial model: {}", to_string_pretty(&json!(model))?);
log::info!(target: "file", "Initial model: {}", json!(model));

log::info!("Rewriting model...");
log::info!(target: "file", "Rewriting model...");
model = rewrite_model(&model, &rule_sets)?;

log::info!("Rewritten model: {}", to_string_pretty(&json!(model))?);
log::info!(target: "file", "Rewritten model: {}", json!(model));

let solutions = get_minion_solutions(model)?; // ToDo we need to properly set the solver adaptor here, not hard code minion
log::info!(target: "file", "Solutions: {}", minion_solutions_to_json(&solutions));

let solutions = get_minion_solutions(model)?;
log::info!("Solutions: {}", minion_solutions_to_json(&solutions));
let solutions_json = minion_solutions_to_json(&solutions);
let solutions_str = to_string_pretty(&solutions_json)?;
match out_file {
None => {
println!("Solutions:");
println!("{}", solutions_str);
}
Some(mut outf) => {
outf.write_all(solutions_str.as_bytes())?;
println!(
"Solutions saved to {:?}",
&cli.output.unwrap().canonicalize()?
)
}
}

if let Some(path) = cli.info_json_path {
#[allow(clippy::unwrap_used)]
Expand Down
2 changes: 1 addition & 1 deletion conjure_oxide/tests/generated_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ fn integration_test(path: &str, essence_base: &str) -> Result<(), Box<dyn Error>
assert_eq!(model, expected_model);

// Stage 2: Rewrite the model using the rule engine and check that the result is as expected
let rule_sets = resolve_rule_sets(SolverFamily::Minion, &vec!["Constant"])?;
let rule_sets = resolve_rule_sets(SolverFamily::Minion, &vec!["Constant".to_string()])?;
let model = rewrite_model(&model, &rule_sets)?;
if verbose {
println!("Rewritten model: {:#?}", model)
Expand Down
5 changes: 2 additions & 3 deletions conjure_oxide/tests/rewrite_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use std::collections::HashMap;
use std::env;
use std::process::exit;

use conjure_core::context::Context;
use conjure_core::rules::eval_constant;
use conjure_core::solver::SolverFamily;
use conjure_oxide::{
Expand Down Expand Up @@ -829,7 +828,7 @@ fn rule_distribute_or_over_and() {
fn rewrite_solve_xyz() {
println!("Rules: {:?}", get_rules());

let rule_sets = match resolve_rule_sets(SolverFamily::Minion, &vec!["Constant"]) {
let rule_sets = match resolve_rule_sets(SolverFamily::Minion, &vec!["Constant".to_string()]) {
Ok(rs) => rs,
Err(e) => {
eprintln!("Error resolving rule sets: {}", e);
Expand Down Expand Up @@ -868,7 +867,7 @@ fn rewrite_solve_xyz() {
],
);

let rule_sets = match resolve_rule_sets(SolverFamily::Minion, &vec!["Constant"]) {
let rule_sets = match resolve_rule_sets(SolverFamily::Minion, &vec!["Constant".to_string()]) {
Ok(rs) => rs,
Err(e) => {
eprintln!("Error resolving rule sets: {}", e);
Expand Down
1 change: 1 addition & 0 deletions crates/conjure_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ regex = "1.10.4"
walkdir = "2.5.0"
derivative = "2.2.0"
schemars = "0.8.16"
clap = { version = "4.5.4", features = ["derive"] }

[lints]
workspace = true
10 changes: 5 additions & 5 deletions crates/conjure_core/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::fmt::{Debug, Formatter};
use std::sync::{Arc, RwLock};

use derivative::Derivative;
use schemars::{schema_for, JsonSchema};
use schemars::JsonSchema;
use serde::Serialize;
use serde_with::skip_serializing_none;

Expand All @@ -20,7 +20,7 @@ pub struct Context<'a> {

pub file_name: Option<String>,

pub extra_rule_set_names: Vec<&'a str>,
pub extra_rule_set_names: Vec<String>,

#[serde(skip)]
pub rules: Vec<&'a Rule<'a>>,
Expand All @@ -35,7 +35,7 @@ pub struct Context<'a> {
impl<'a> Context<'a> {
pub fn new(
target_solver_family: SolverFamily,
extra_rule_set_names: Vec<&'a str>,
extra_rule_set_names: Vec<String>,
rules: Vec<&'a Rule<'a>>,
rule_sets: Vec<&'a RuleSet<'a>>,
) -> Self {
Expand All @@ -53,7 +53,7 @@ impl<'a> Context<'a> {
impl Context<'static> {
pub fn new_ptr(
target_solver_family: SolverFamily,
extra_rule_set_names: Vec<&'static str>,
extra_rule_set_names: Vec<String>,
rules: Vec<&'static Rule<'static>>,
rule_sets: Vec<&'static RuleSet<'static>>,
) -> Arc<RwLock<Context<'static>>> {
Expand All @@ -69,7 +69,7 @@ impl Context<'static> {
impl<'a> Debug for Context<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let target_solver_family: Option<SolverFamily> = self.target_solver_family;
let extra_rule_set_names: Vec<&str> = self.extra_rule_set_names.clone();
let extra_rule_set_names: Vec<String> = self.extra_rule_set_names.clone();
let rules: Vec<&str> = self.rules.iter().map(|r| r.name).collect();
let rule_sets: Vec<&str> = self.rule_sets.iter().map(|r| r.name).collect();

Expand Down
1 change: 0 additions & 1 deletion crates/conjure_core/src/parse/example_models.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// example_models with get_example_model function

use std::default;
use std::path::PathBuf;

use project_root::get_project_root;
Expand Down
4 changes: 2 additions & 2 deletions crates/conjure_core/src/rule_engine/resolve_rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ fn get_rule_set(rule_set_name: &str) -> Result<&'static RuleSet<'static>, Resolv
///
#[allow(clippy::mutable_key_type)] // RuleSet is 'static so it's fine
pub fn rule_sets_by_names<'a>(
rule_set_names: &Vec<&str>,
rule_set_names: &Vec<String>,
) -> Result<HashSet<&'a RuleSet<'static>>, ResolveRulesError> {
let mut rs_set: HashSet<&'static RuleSet<'static>> = HashSet::new();

Expand All @@ -69,7 +69,7 @@ pub fn rule_sets_by_names<'a>(
#[allow(clippy::mutable_key_type)] // RuleSet is 'static so it's fine
pub fn resolve_rule_sets<'a>(
target_solver: SolverFamily,
extra_rs_names: &Vec<&str>,
extra_rs_names: &Vec<String>,
) -> Result<Vec<&'a RuleSet<'static>>, ResolveRulesError> {
let mut ans = HashSet::new();

Expand Down
3 changes: 1 addition & 2 deletions crates/conjure_core/src/solver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
//!
//! // Define and rewrite a model for minion.
//! let model = get_example_model("bool-03").unwrap();
//! let rule_sets = resolve_rule_sets(SolverFamily::Minion, &vec!["Constant"]).unwrap();
//! let rule_sets = resolve_rule_sets(SolverFamily::Minion, &vec!["Constant".to_string()]).unwrap();
//! let model = rewrite_model(&model,&rule_sets).unwrap();
//!
//!
Expand Down Expand Up @@ -128,7 +128,6 @@ pub mod states;
Deserialize,
JsonSchema,
)]

pub enum SolverFamily {
SAT,
Minion,
Expand Down

0 comments on commit 4945113

Please sign in to comment.