use clap::{Parser, Subcommand, ValueEnum};
use optify::builder::BuilderOptions;
use optify::provider::{GetOptionsPreferences, OptionsProvider, OptionsRegistry};
use std::path::PathBuf;
use std::process;
mod format_analysis;
use format_analysis::format_analysis_text;
#[derive(Parser)]
#[command(name = "optify", version, about, long_about = None)]
struct Cli {
#[arg(short = 'd', long = "dir", value_name = "DIR", required = true)]
dirs: Vec<PathBuf>,
#[arg(long, value_name = "PATH")]
schema: Option<PathBuf>,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Analyze {
#[arg(short, long, value_name = "JSON_POINTER")]
path: String,
#[arg(short, long, value_name = "FEATURE", num_args = 0..)]
features: Vec<String>,
#[arg(long = "preferences", visible_alias = "prefs", value_name = "JSON")]
preferences: Option<String>,
#[arg(long, value_enum, default_value_t = OutputFormat::Json)]
format: OutputFormat,
},
ListFeatures,
GetAllOptions {
#[arg(short, long, value_name = "FEATURE", num_args = 0..)]
features: Vec<String>,
#[arg(long = "preferences", visible_alias = "prefs", value_name = "JSON")]
preferences: Option<String>,
},
GetOptions {
#[arg(short, long)]
key: String,
#[arg(short, long, value_name = "FEATURE", num_args = 0..)]
features: Vec<String>,
#[arg(long = "preferences", visible_alias = "prefs", value_name = "JSON")]
preferences: Option<String>,
},
}
#[derive(Clone, Copy, ValueEnum)]
enum OutputFormat {
Json,
Text,
}
fn parse_preferences(json: Option<&str>) -> Result<Option<GetOptionsPreferences>, String> {
json.map(|s| serde_json::from_str(s).map_err(|e| format!("Failed to parse preferences: {e}")))
.transpose()
}
fn build_provider(
dirs: &[PathBuf],
schema: Option<&PathBuf>,
is_analysis_enabled: bool,
) -> Result<OptionsProvider, String> {
let options = BuilderOptions {
is_analysis_enabled,
schema_path: schema.cloned(),
..BuilderOptions::default()
};
OptionsProvider::build_from_directories_with_options(dirs, options)
}
fn run() -> Result<(), String> {
let cli = Cli::parse();
let is_analysis_enabled = matches!(cli.command, Commands::Analyze { .. });
let provider = build_provider(&cli.dirs, cli.schema.as_ref(), is_analysis_enabled)?;
match cli.command {
Commands::Analyze {
path,
features,
preferences,
format,
} => {
let preferences = parse_preferences(preferences.as_deref())?;
let analysis = provider.analyze(&path, &features, preferences.as_ref())?;
match format {
OutputFormat::Json => println!(
"{}",
serde_json::to_string(&analysis)
.map_err(|e| format!("Failed to serialize analysis: {e}"))?
),
OutputFormat::Text => println!("{}", format_analysis_text(&path, &analysis)),
}
}
Commands::ListFeatures => {
let features = provider.get_features_with_metadata();
let list: Vec<_> = features.into_values().collect();
println!(
"{}",
serde_json::to_string(&list)
.map_err(|e| format!("Failed to serialize features: {e}"))?
);
}
Commands::GetAllOptions {
features,
preferences,
} => {
let preferences = parse_preferences(preferences.as_deref())?;
let value = provider.get_all_options(&features, None, preferences.as_ref())?;
println!(
"{}",
serde_json::to_string(&value)
.map_err(|e| format!("Failed to serialize options: {e}"))?
);
}
Commands::GetOptions {
key,
features,
preferences,
} => {
let preferences = parse_preferences(preferences.as_deref())?;
let value = provider.get_options_with_preferences(
&key,
&features,
None,
preferences.as_ref(),
)?;
println!(
"{}",
serde_json::to_string(&value)
.map_err(|e| format!("Failed to serialize options: {e}"))?
);
}
}
Ok(())
}
fn main() {
if let Err(e) = run() {
eprintln!("Error: {e}");
process::exit(1);
}
}