use clap::{Args, CommandFactory, Parser, Subcommand, ValueEnum};
use clap_complete::generator::generate;
use clap_complete::{Generator, Shell};
use cubing::alg::{Alg, Move};
use cubing::kpuzzle::KPuzzle;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use std::io::stdout;
use std::path::PathBuf;
use std::process::exit;
use crate::_internal::puzzle_traits::puzzle_traits::GroupActionPuzzle;
use crate::_internal::search::prune_table_trait::Depth;
use crate::scramble::{DerivationSalt, DerivationSeed, Puzzle};
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
#[clap(name = "twsearch-cpp-wrapper")]
pub struct TwsearchCppWrapperArgs {
#[command(subcommand)]
pub command: CliCommand,
}
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
#[clap(name = "twsearch")]
pub struct TwsearchArgs {
#[command(subcommand)]
pub command: CliCommand,
}
#[allow(clippy::large_enum_variant)]
#[derive(Subcommand, Debug)]
pub enum CliCommand {
Search(SearchCommandArgs),
#[allow(rustdoc::bare_urls)]
Serve(ServeCommandArgs),
SolveKnownPuzzle(SolveKnownPuzzleCommandArgs),
SchreierSims(SchreierSimsArgs),
GodsAlgorithm(GodsAlgorithmArgs),
TimingTest(TimingTestArgs),
CanonicalAlgs(CanonicalAlgsArgs),
Scramble(ScrambleArgs),
ScrambleFinder(ScrambleFinderArgs),
Derive(DeriveArgs),
Benchmark(BenchmarkArgs),
Completions(CompletionsArgs),
}
#[derive(Args, Debug, Default)]
pub struct CommonSearchArgs {
#[clap(long/*, visible_alias = "checkbeforesolve" */)]
pub check_before_solve: Option<EnableAutoAlwaysNeverValueEnum>,
#[clap(long/*, visible_alias = "randomstart"`*/)]
pub random_start: bool,
#[clap(long)]
pub all_optimal: bool,
#[clap(long/*, visible_alias = "startprunedepth" */, id = "DEPTH")]
pub start_prune_depth: Option<Depth>,
#[clap(long/* , visible_alias = "mindepth" */)]
pub min_depth: Option<Depth>,
#[clap(long/* , visible_alias = "maxdepth" */)]
pub max_depth: Option<Depth>,
#[clap(long, group = "continue_search")]
pub continue_after: Option<Alg>,
#[clap(long, group = "continue_search")]
pub continue_at: Option<Alg>,
#[command(flatten)]
pub performance_args: PerformanceArgs,
}
#[derive(Args, Debug)]
pub struct SearchCommandArgs {
#[command(flatten)]
pub def_args: RequiredDefArgs,
#[command(flatten)]
pub optional: SearchCommandOptionalArgs,
}
#[derive(Args, Debug, Default)]
pub struct SearchCommandOptionalArgs {
#[clap(long/* , visible_short_alias = 't' */)]
pub min_num_solutions: Option<usize>,
#[command(flatten)]
pub generator_args: GeneratorArgs,
#[command(flatten)]
pub search_args: CommonSearchArgs,
#[command(flatten)]
pub search_persistence_args: SearchPersistenceArgs,
#[command(flatten)]
pub metric_args: MetricArgs,
#[command(flatten)]
pub verbosity_args: VerbosityArgs,
#[command(flatten)]
pub scramble_and_target_pattern_optional_args: ScrambleAndTargetPatternOptionalArgs,
}
#[derive(Args, Debug)]
pub struct SolveKnownPuzzleCommandArgs {
#[clap(value_parser = puzzle_from_id)]
pub puzzle: Puzzle,
pub scramble_setup_alg: Alg,
#[clap(long, default_value = "true")]
pub print_link: Option<bool>,
}
fn puzzle_from_id(s: &str) -> Result<Puzzle, String> {
Puzzle::try_from_id(s).map_err(|e| e.description)
}
#[derive(Debug, Clone, Copy, ValueEnum, Serialize, Deserialize, Default)]
pub enum VerbosityLevel {
Silent,
Error,
#[default]
Warning,
Info,
Extra,
}
#[derive(Args, Debug, Default)]
pub struct VerbosityArgs {
#[clap(long)]
pub verbosity: Option<VerbosityLevel>,
}
#[derive(Args, Debug, Default)]
pub struct GeneratorArgs {
#[clap(long = "generator-moves", value_delimiter = ',')]
pub generator_moves_string: Option<Vec<Move>>,
#[clap(long, value_delimiter = ',')]
pub generator_algs: Option<Vec<Alg>>,
}
#[derive(Clone, Debug)]
pub enum Generators {
Default,
Custom(CustomGenerators),
}
impl Generators {
pub fn enumerate_moves_for_kpuzzle(&self, kpuzzle: &KPuzzle) -> Vec<Move> {
if let Generators::Custom(custom_generators) = self {
if !custom_generators.algs.is_empty() {
eprintln!("WARNING: Alg generators are not implemented yet. Ignoring.");
}
};
match self {
Generators::Default => kpuzzle.puzzle_definition_all_moves(),
Generators::Custom(generators) => generators.moves.clone(),
}
}
}
#[derive(Clone, Debug)]
pub struct CustomGenerators {
pub moves: Vec<Move>,
pub algs: Vec<Alg>,
}
impl GeneratorArgs {
pub fn parse(&self) -> Generators {
match (&self.generator_moves_string, &self.generator_algs) {
(None, None) => Generators::Default,
(moves, algs) => Generators::Custom(CustomGenerators {
moves: moves.clone().unwrap_or_default(),
algs: algs.clone().unwrap_or_default(),
}),
}
}
}
#[derive(Args, Debug, Default)]
pub struct SearchPersistenceArgs {
#[clap(long, help_heading = "Persistence"/* , visible_alias = "writeprunetables" */)]
pub write_prune_tables: Option<EnableAutoAlwaysNeverValueEnum>,
#[clap(long, help_heading = "Persistence"/* , visible_alias = "cachedir" */)]
pub cache_dir: Option<PathBuf>,
}
#[derive(Debug, Clone, ValueEnum, Serialize, Deserialize)]
pub enum EnableAutoAlwaysNeverValueEnum {
Auto,
Never,
Always,
}
impl EnableAutoAlwaysNeverValueEnum {
pub fn enabled(&self, auto_case: fn() -> bool) -> bool {
match self {
EnableAutoAlwaysNeverValueEnum::Auto => auto_case(),
EnableAutoAlwaysNeverValueEnum::Never => false,
EnableAutoAlwaysNeverValueEnum::Always => true,
}
}
}
impl Display for EnableAutoAlwaysNeverValueEnum {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
EnableAutoAlwaysNeverValueEnum::Auto => "auto",
EnableAutoAlwaysNeverValueEnum::Never => "never",
EnableAutoAlwaysNeverValueEnum::Always => "always",
};
write!(f, "{}", s)
}
}
#[derive(Args, Debug, Default)]
pub struct PerformanceArgs {
#[clap(long, help_heading = "Performance"/* , visible_short_alias = 't' */)]
pub num_threads: Option<usize>,
#[command(flatten)]
pub memory_args: MemoryArgs,
}
#[derive(Args, Debug, Default)]
pub struct MemoryArgs {
#[clap(long = "memory-MiB", help_heading = "Performance"/* , visible_short_alias = 'm' */, id = "MEBIBYTES")]
pub memory_mebibytes: Option<usize>,
}
#[derive(Args, Debug)]
pub struct CompletionsArgs {
#[clap(verbatim_doc_comment, id = "SHELL")]
shell: Shell,
}
#[derive(Args, Debug)]
pub struct SchreierSimsArgs {
#[command(flatten)]
pub def_args: DefOnlyArgs,
#[command(flatten)]
pub performance_args: PerformanceArgs,
}
#[derive(Args, Debug)]
pub struct GodsAlgorithmArgs {
#[command(flatten)]
pub def_args: DefOnlyArgs,
#[command(flatten)]
pub optional: GodsAlgorithmOptionalArgs,
}
#[derive(Args, Debug, Default)]
pub struct GodsAlgorithmOptionalArgs {
#[command(flatten)]
pub start_pattern_args: StartPatternArgs,
#[command(flatten)]
pub generator_args: GeneratorArgs,
#[clap(long/* , visible_short_alias = 'a' */, default_value_t = 20)]
pub num_antipodes: u32,
#[clap(long/* , visible_short_alias = 'F' */)]
pub force_arrays: bool,
#[clap(long/* , visible_short_alias = 'H' */)]
pub hash_patterns: bool,
#[command(flatten)]
pub metric_args: MetricArgs,
#[command(flatten)]
pub performance_args: PerformanceArgs,
}
#[derive(Args, Debug)]
pub struct TimingTestArgs {
#[command(flatten)]
pub def_args: DefOnlyArgs,
#[command(flatten)]
pub metric_args: MetricArgs,
#[command(flatten)]
pub performance_args: PerformanceArgs,
}
#[derive(Args, Debug)]
pub struct CanonicalAlgsArgs {
#[command(flatten)]
pub def_args: DefOnlyArgs,
#[command(flatten)]
pub generator_args: GeneratorArgs,
#[command(flatten)]
pub metric_args: MetricArgs,
#[command(flatten)]
pub performance_args: PerformanceArgs,
}
#[derive(Clone, Args, Debug)]
pub struct MetricArgs {
#[clap(long, default_value_t = MetricEnum::Hand)]
pub metric: MetricEnum,
}
impl Default for MetricArgs {
fn default() -> Self {
Self {
metric: MetricEnum::Hand,
}
}
}
#[derive(Debug, Clone, ValueEnum, Serialize, Deserialize)]
pub enum MetricEnum {
Hand,
Quantum,
}
impl Display for MetricEnum {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
MetricEnum::Hand => "hand",
MetricEnum::Quantum => "quantum",
};
write!(f, "{}", s)
}
}
#[derive(Args, Debug)]
pub struct ScrambleArgs {
pub event_id: String,
#[clap(long, default_value_t = 1)]
pub amount: usize,
}
#[derive(Args, Debug)]
pub struct ScrambleFinderArgs {
#[command(subcommand)]
pub command: ScrambleFinderCommand,
}
#[derive(Subcommand, Debug)]
pub enum ScrambleFinderCommand {
Search(ScrambleFinderSearchArgs),
Filter(ScrambleFinderFilterArgs),
}
#[derive(Args, Debug)]
pub struct ScrambleFinderSearchArgs {
#[clap(long, default_value = "true")]
pub print_link: Option<bool>,
#[clap(long, default_value_t = false)]
pub apply_filtering: bool,
#[command(flatten)]
pub filter_args: ScrambleFinderFilterArgs,
}
#[derive(Args, Debug)]
pub struct ScrambleFinderFilterArgs {
pub event_id: String,
pub scramble_setup_alg: Alg,
}
#[derive(Args, Debug)]
pub struct DefOnlyArgs {
#[clap()]
pub def_file: PathBuf,
}
#[derive(Args, Debug)]
pub struct RequiredDefArgs {
#[command(flatten)]
pub def_args: DefOnlyArgs,
}
#[derive(Args, Debug, Default)]
pub struct ScrambleAndTargetPatternOptionalArgs {
#[clap(help_heading = "Scramble input", group = "scramble_input")]
pub scramble_file: Option<PathBuf>,
#[clap(long/*, visible_alias = "scramblealg" */, help_heading = "Scramble input", group = "scramble_input")]
pub scramble_alg: Option<Alg>,
#[clap(long, help_heading = "Scramble input", group = "scramble_input"/* , visible_short_alias = 's' */)]
pub stdin_scrambles: bool,
#[clap(long, help_heading = "Scramble input")]
pub experimental_target_pattern: Option<PathBuf>,
}
#[derive(Args, Debug)]
pub struct DeriveArgs {
pub root_derivation_seed: DerivationSeed,
#[clap(required = true, value_delimiter = '/')]
pub derivation_salts: Vec<DerivationSalt>,
}
#[derive(Args, Debug, Default)]
pub struct StartPatternArgs {
#[clap(long)]
pub start_pattern: Option<PathBuf>,
}
#[derive(Args, Debug)]
pub struct BenchmarkArgs {
#[command(flatten)]
pub def_args: DefOnlyArgs,
#[command(flatten)]
pub memory_args: MemoryArgs,
#[command(flatten)]
pub generator_args: GeneratorArgs,
#[command(flatten)]
pub metric_args: MetricArgs,
}
fn completions_for_shell(cmd: &mut clap::Command, generator: impl Generator) {
generate(generator, cmd, "twsearch", &mut stdout());
}
pub fn get_options() -> TwsearchArgs {
let mut command = TwsearchArgs::command();
let args = TwsearchArgs::parse();
if let CliCommand::Completions(completions_args) = args.command {
completions_for_shell(&mut command, completions_args.shell);
exit(0);
};
args
}
fn completions_for_shell_cpp_wrapper(cmd: &mut clap::Command, generator: impl Generator) {
generate(generator, cmd, "twsearch-cpp-wrapper", &mut stdout());
}
pub fn get_options_cpp_wrapper() -> TwsearchCppWrapperArgs {
let mut command = TwsearchCppWrapperArgs::command();
let args = TwsearchCppWrapperArgs::parse();
if let CliCommand::Completions(completions_args) = args.command {
completions_for_shell_cpp_wrapper(&mut command, completions_args.shell);
exit(0);
};
args
}
pub struct ServeArgsForIndividualSearch<'a> {
pub commandline_args: &'a ServeCommandArgs,
pub client_args: &'a Option<ServeClientArgs>,
}
#[derive(Args, Debug)]
pub struct ServeCommandArgs {
#[command(flatten)]
pub performance_args: PerformanceArgs,
#[command(flatten)]
pub verbosity_args: VerbosityArgs,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ServeClientArgs {
pub check_before_solve: Option<EnableAutoAlwaysNeverValueEnum>,
pub random_start: Option<bool>,
pub min_depth: Option<Depth>,
pub max_depth: Option<Depth>,
pub start_prune_depth: Option<Depth>,
pub quantum_metric: Option<bool>, pub generator_moves: Option<Vec<Move>>,
}
#[cfg(test)]
mod tests {
use crate::_internal::cli::args::{TwsearchArgs, TwsearchCppWrapperArgs};
#[test]
fn test_clap_args() {
use clap::CommandFactory;
TwsearchArgs::command().debug_assert();
TwsearchCppWrapperArgs::command().debug_assert();
}
}