1
use std::path::PathBuf;
2

            
3
use clap::{Args, Parser, Subcommand};
4

            
5
use clap_complete::Shell;
6
use conjure_cp::settings::{
7
    DEFAULT_MINION_DISCRETE_THRESHOLD, Parser as InputParser, QuantifiedExpander, Rewriter,
8
    SolverFamily,
9
};
10

            
11
use crate::{pretty, solve, test_solve};
12

            
13
pub(crate) const DEBUG_HELP_HEADING: Option<&str> = Some("Debug");
14
pub(crate) const LOGGING_HELP_HEADING: Option<&str> = Some("Logging & Output");
15
pub(crate) const CONFIGURATION_HELP_HEADING: Option<&str> = Some("Configuration");
16
pub(crate) const OPTIMISATIONS_HELP_HEADING: Option<&str> = Some("Optimisations");
17

            
18
/// All subcommands of conjure-oxide
19
#[derive(Clone, Debug, Subcommand)]
20
pub enum Command {
21
    /// Solve a model
22
    Solve(solve::Args),
23
    /// Print the JSON info file schema
24
    PrintJsonSchema,
25
    /// Tests whether the Essence model is solvable with Conjure Oxide, and whether it gets the
26
    /// same solutions as Conjure.
27
    ///
28
    /// Return-code will be 0 if the solutions match, 1 if they don't, and >1 on crash.
29
    TestSolve(test_solve::Args),
30
    /// Generate a completion script for the shell provided
31
    Completion(CompletionArgs),
32
    Pretty(pretty::Args),
33
    // Run the language server
34
    ServerLSP,
35
}
36

            
37
/// Global command line arguments.
38
#[derive(Clone, Debug, Parser)]
39
#[command(
40
    author,
41
    about = "Conjure Oxide: Automated Constraints Modelling Toolkit",
42
    before_help = "Full documentation can be found online at: https://conjure-cp.github.io/conjure-oxide"
43
)]
44
pub struct Cli {
45
    #[command(subcommand)]
46
    pub subcommand: Command,
47

            
48
    #[command(flatten)]
49
    pub global_args: GlobalArgs,
50

            
51
    /// Print version
52
    #[arg(long = "version", short = 'V')]
53
    pub version: bool,
54
}
55

            
56
#[derive(Debug, Clone, Args)]
57
pub struct GlobalArgs {
58
    /// Extra rule sets to enable
59
    #[arg(long, value_name = "EXTRA_RULE_SETS", global = true)]
60
    pub extra_rule_sets: Vec<String>,
61

            
62
    /// Log verbosely
63
    #[arg(long, short = 'v', help = "Log verbosely to stderr", global = true, help_heading = LOGGING_HELP_HEADING)]
64
    pub verbose: bool,
65

            
66
    // --no-x flag disables --x flag : https://jwodder.github.io/kbits/posts/clap-bool-negate/
67
    /// Check for multiple equally applicable rules, exiting if any are found.
68
    ///
69
    /// Only compatible with the default rewriter.
70
    #[arg(
71
        long,
72
        overrides_with = "_no_check_equally_applicable_rules",
73
        default_value_t = false,
74
        global = true,
75
        help_heading= DEBUG_HELP_HEADING
76
    )]
77
    pub check_equally_applicable_rules: bool,
78

            
79
    /// Output file for the human readable rule trace.
80
    #[arg(long, global = true, help_heading=LOGGING_HELP_HEADING)]
81
    pub rule_trace: Option<PathBuf>,
82

            
83
    /// Do not check for multiple equally applicable rules [default].
84
    ///
85
    /// Only compatible with the default rewriter.
86
    #[arg(long, global = true, help_heading = DEBUG_HELP_HEADING)]
87
    pub _no_check_equally_applicable_rules: bool,
88

            
89
    /// Which parser to use.
90
    ///
91
    /// Possible values: `tree-sitter`, `via-conjure`.
92
    #[arg(
93
        long,
94
        default_value_t = InputParser::ViaConjure,
95
        value_parser = parse_parser,
96
        global = true,
97
        help_heading = CONFIGURATION_HELP_HEADING
98
    )]
99
    pub parser: InputParser,
100

            
101
    /// Which rewriter to use.
102
    ///
103
    /// Possible values: `naive`, `morph`.
104
    #[arg(long, default_value_t = Rewriter::Naive, value_parser = parse_rewriter, global = true, help_heading = CONFIGURATION_HELP_HEADING)]
105
    pub rewriter: Rewriter,
106

            
107
    /// Which strategy to use for expanding quantified variables in comprehensions.
108
    ///
109
    /// Possible values: `native`, `via-solver`, `via-solver-ac`.
110
    #[arg(
111
        long,
112
        default_value_t = QuantifiedExpander::Native,
113
        value_parser = parse_comprehension_expander,
114
        global = true,
115
        help_heading = CONFIGURATION_HELP_HEADING
116
    )]
117
    pub comprehension_expander: QuantifiedExpander,
118

            
119
    /// Solver family to use.
120
    ///
121
    /// Possible values: `minion`, `sat`, `sat-log`, `sat-direct`, `sat-order`;
122
    /// with `smt` feature: `smt` and `smt-<ints>-<matrices>[-nodiscrete]`
123
    /// where `<ints>` is `lia` or `bv`, and `<matrices>` is `arrays` or `atomic`.
124
    #[arg(
125
        long,
126
        value_name = "SOLVER",
127
        value_parser = parse_solver_family,
128
        default_value = "minion",
129
        short = 's',
130
        global = true,
131
        help_heading = CONFIGURATION_HELP_HEADING
132
    )]
133
    pub solver: SolverFamily,
134

            
135
    /// Int-domain size threshold for using Minion `DISCRETE` variables.
136
    ///
137
    /// If an int domain has size <= this value, Conjure Oxide emits `DISCRETE`; otherwise `BOUND`.
138
    #[arg(
139
        long,
140
        default_value_t = DEFAULT_MINION_DISCRETE_THRESHOLD,
141
        global = true,
142
        help_heading = CONFIGURATION_HELP_HEADING
143
    )]
144
    pub minion_discrete_threshold: usize,
145

            
146
    /// Save a solver input file to <filename>.
147
    ///
148
    /// This input file will be in a format compatible by the command-line
149
    /// interface of the selected solver. For example, when the solver is Minion,
150
    /// a valid .minion file will be output.
151
    ///
152
    /// This file is for informational purposes only; the results of running
153
    /// this file cannot be used by Conjure Oxide in any way.
154
    #[arg(long,global=true, value_names=["filename"], next_line_help=true, help_heading=LOGGING_HELP_HEADING)]
155
    pub save_solver_input_file: Option<PathBuf>,
156

            
157
    /// Stop the solver after the given timeout.
158
    ///
159
    /// Currently only SMT supports this feature.
160
    #[arg(long, global = true, help_heading = OPTIMISATIONS_HELP_HEADING)]
161
    pub solver_timeout: Option<humantime::Duration>,
162

            
163
    /// Generate log files
164
    #[arg(long, default_value_t = false, global = true, help_heading = LOGGING_HELP_HEADING)]
165
    pub log: bool,
166

            
167
    /// Output file for conjure-oxide's text logs
168
    #[arg(long, value_name = "LOGFILE", global = true, help_heading = LOGGING_HELP_HEADING)]
169
    pub logfile: Option<PathBuf>,
170

            
171
    /// Output file for conjure-oxide's json logs
172
    #[arg(long, value_name = "JSON LOGFILE", global = true, help_heading = LOGGING_HELP_HEADING)]
173
    pub logfile_json: Option<PathBuf>,
174
}
175

            
176
#[derive(Debug, Clone, Args)]
177
pub struct CompletionArgs {
178
    /// Shell type for which to generate the completion script
179
    #[arg(value_enum)]
180
    pub shell: Shell,
181
}
182

            
183
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
184
pub enum ShellTypes {
185
    Bash,
186
    Zsh,
187
    Fish,
188
    PowerShell,
189
    Elvish,
190
}
191

            
192
96
fn parse_comprehension_expander(input: &str) -> Result<QuantifiedExpander, String> {
193
96
    input.parse()
194
96
}
195

            
196
96
fn parse_rewriter(input: &str) -> Result<Rewriter, String> {
197
96
    input.parse()
198
96
}
199

            
200
96
fn parse_solver_family(input: &str) -> Result<SolverFamily, String> {
201
96
    input.parse()
202
96
}
203

            
204
96
fn parse_parser(input: &str) -> Result<InputParser, String> {
205
96
    input.parse()
206
96
}