|
use clap::{ArgAction, Parser, ValueEnum}; |
|
|
|
fn main() { |
|
let options = Opt::parse(); |
|
println!("Hello, world, we got options..."); |
|
println!("{:#?}", options); |
|
} |
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Default)] |
|
enum KeywordCase { |
|
/// Leave reserved keywords as is (default) |
|
#[default] |
|
None, |
|
/// Convert reserved keywords to uppercase |
|
Uppercase, |
|
/// Convert reserved keywords to lowercase |
|
Lowercase, |
|
} |
|
|
|
/// Picture this: some cli utility is evolving over time. |
|
/// |
|
/// 1. The first release has a boolean `--uppercase` flag which if set will |
|
/// cause keywords to be uppercased during formatting, otherwise they are |
|
/// left as is. |
|
/// |
|
/// We include the negation `--no-uppercase` because we are nice. |
|
/// |
|
/// ``` |
|
/// #[derive(Parser, Debug)] |
|
/// #[command] |
|
/// struct Options { |
|
/// |
|
/// #[arg(long)] |
|
/// uppercase: bool, |
|
/// |
|
/// #[arg(long, overrides_with = "uppercase")] |
|
/// no_uppercase: bool, |
|
/// } |
|
/// ``` |
|
/// |
|
/// 2. Time passes, *lowercase* should be an option too. |
|
/// |
|
/// Duh! We overly simplified the problem, nothing wrong with an |
|
/// `--uppercase` affordance, but the use case indicated multiplicity |
|
/// not than duality, and a simple `--lowercase` flag does not cut it. |
|
/// What we need is expandable `--keyword-case <mode>` and relegate |
|
/// `--uppercase` and `--lowercase` to ergonomics. |
|
/// |
|
/// Clap makes this possible without a breaking change: |
|
/// |
|
/// - Derive`ValueEnum` for the choices, |
|
/// - Add `--keyword-case <mode>` option to the options struct, |
|
/// - Keep `--uppercase` and `--no-uppercase` for compatibility. |
|
/// - Add `--lowercase` and `--no-lowercase` to pretend we meant it this way. |
|
/// |
|
/// See the implementation below. |
|
/// |
|
/// Notes |
|
/// * `--keyword-case` has truly taken over, no need to refer to other struct members. |
|
/// * `uppercase` / `lowercase` flags indicate the outcome as well: continuity / consistency. |
|
/// * Negation flags are never are vestigial, only used for effects, never become true. |
|
/// They could though, if there is some purpose. |
|
/// * Apparently circular dependencies are not a problem because overrides eliminate priors. |
|
/// * It would be better without the negation flags, they complicate interactions with |
|
/// `--keyword-case` and there is a gotcha that, `--no-uppercase` resets it to `none` regardless |
|
/// of the current value. More on this at the keyword_case negation test. |
|
/// ``` |
|
/// |
|
#[derive(Parser, PartialEq, Default, Debug)] |
|
#[command(version)] |
|
struct Opt { |
|
/// Case setting for reserved keywords |
|
/// |
|
/// Overrides earlier instances and `--lowercase`, `--uppercase` flags. |
|
#[arg(long, default_value = "none", |
|
default_value_ifs = [ |
|
("uppercase", "true", "uppercase"), |
|
("lowercase", "true", "lowercase"), |
|
], |
|
overrides_with = "keyword_case", |
|
)] |
|
keyword_case: KeywordCase, |
|
|
|
/// Equivalent to `--keyword-case uppercase` |
|
#[arg(long, |
|
overrides_with_all = ["lowercase", "uppercase", "keyword_case"], |
|
default_value_if("keyword_case", "uppercase", "true"), |
|
)] |
|
uppercase: bool, |
|
|
|
/// Equivalent to `--keyword-case lowercase`. |
|
#[arg(long, |
|
overrides_with_all = ["lowercase", "uppercase", "keyword_case"], |
|
default_value_if("keyword_case", "lowercase", "true"), |
|
)] |
|
lowercase: bool, |
|
|
|
/// Equivalent to `--keyword-case none` (deprecated) |
|
#[arg(long, default_value = "false", action = ArgAction::SetFalse, |
|
overrides_with_all = ["lowercase", "uppercase", "keyword_case"], |
|
)] |
|
no_uppercase: bool, |
|
|
|
/// Equivalent to `--keyword-case none` (deprecated) |
|
#[arg(long, default_value = "false", action = ArgAction::SetFalse, |
|
overrides_with_all = ["lowercase", "uppercase", "keyword_case"], |
|
)] |
|
no_lowercase: bool, |
|
} |
|
|
|
#[cfg(test)] |
|
mod tests { |
|
mod keyword_case { |
|
use super::super::*; |
|
#[test] |
|
fn default_is_none() { |
|
assert_eq!( |
|
Opt { keyword_case: KeywordCase::None, ..Default::default() }, |
|
Opt::parse_from(["test"]) |
|
); |
|
} |
|
|
|
#[test] |
|
fn propagates() { |
|
assert_eq!( |
|
Opt { keyword_case: KeywordCase::Lowercase, lowercase: true, ..Default::default() }, |
|
Opt::parse_from(["test", "--keyword-case", "lowercase"]) |
|
); |
|
assert_eq!( |
|
Opt { keyword_case: KeywordCase::Uppercase, uppercase: true, ..Default::default() }, |
|
Opt::parse_from(["test", "--keyword-case", "uppercase"]) |
|
); |
|
} |
|
|
|
#[test] |
|
fn overrides() { |
|
assert_eq!( |
|
Opt { keyword_case: KeywordCase::None, ..Default::default() }, |
|
Opt::parse_from(["test", "--lowercase", "--uppercase", "--keyword-case", "none"]) |
|
); |
|
} |
|
|
|
#[test] |
|
fn last_wins() { |
|
assert_eq!( |
|
Opt { keyword_case: KeywordCase::Lowercase, lowercase: true, ..Default::default() }, |
|
Opt::parse_from([ |
|
"test", |
|
"--keyword-case", "uppercase", |
|
"--keyword-case", "lowercase" |
|
]) |
|
); |
|
} |
|
|
|
#[test] |
|
fn redundant_okay() { |
|
Opt::parse_from(["test", "--keyword-case", "none", "--keyword-case", "none"]); |
|
} |
|
|
|
#[test] |
|
fn negated() { |
|
assert_eq!( |
|
Opt { keyword_case: KeywordCase::None, ..Default::default() }, |
|
Opt::parse_from(["test", "--keyword-case", "uppercase", "--no-uppercase"]) |
|
); |
|
assert_eq!( |
|
Opt { keyword_case: KeywordCase::None, ..Default::default() }, |
|
Opt::parse_from(["test", "--keyword-case", "lowercase", "--no-lowercase"]) |
|
); |
|
// But negation resets keyword_case regardless of whether its value no the |
|
// thing being negated. I did not find a way to prevent this. Note that |
|
// these only exist because of the progress of events in our scenario. |
|
assert_eq!( |
|
Opt { keyword_case: KeywordCase::None, ..Default::default() }, |
|
Opt::parse_from(["test", "--keyword-case", "uppercase", "--no-lowercase"]) |
|
); |
|
assert_eq!( |
|
Opt { keyword_case: KeywordCase::None, ..Default::default() }, |
|
Opt::parse_from(["test", "--keyword-case", "lowercase", "--no-uppercase"]) |
|
); |
|
} |
|
} |
|
|
|
mod uppercase { |
|
use super::super::*; |
|
|
|
#[test] |
|
fn default_is_false() { |
|
let options = Opt::parse_from(["test"]); |
|
assert!(!options.uppercase); |
|
} |
|
|
|
#[test] |
|
fn propagates() { |
|
assert_eq!( |
|
Opt { keyword_case: KeywordCase::Uppercase, uppercase: true, ..Default::default() }, |
|
Opt::parse_from(["test", "--uppercase"]) |
|
); |
|
} |
|
|
|
#[test] |
|
fn redundant_okay() { |
|
Opt::parse_from(["test", "--uppercase", "--uppercase"]); |
|
} |
|
|
|
#[test] |
|
fn negated() { |
|
assert_eq!( |
|
Opt { keyword_case: KeywordCase::None, ..Default::default() }, |
|
Opt::parse_from(["test", "--uppercase", "--no-uppercase"]) |
|
); |
|
} |
|
} |
|
|
|
mod lowercase { |
|
use super::super::*; |
|
|
|
#[test] |
|
fn default_is_false() { |
|
assert!(!Opt::parse_from(["test"]).lowercase); |
|
} |
|
|
|
#[test] |
|
fn propagates() { |
|
assert_eq!( |
|
Opt { keyword_case: KeywordCase::Lowercase, lowercase: true, ..Default::default() }, |
|
Opt::parse_from(["test", "--lowercase"]) |
|
); |
|
} |
|
|
|
|
|
#[test] |
|
fn overrides_uppercase() { |
|
assert!(!Opt::parse_from(["test", "--lowercase"]).uppercase); |
|
} |
|
|
|
#[test] |
|
fn sets_keyword_case() { |
|
assert_eq!(KeywordCase::Lowercase, |
|
Opt::parse_from(["test", "--lowercase"]).keyword_case); |
|
} |
|
#[test] |
|
fn redundant_okay() { |
|
Opt::parse_from(["test", "--lowercase", "--lowercase"]); |
|
} |
|
|
|
#[test] |
|
fn negated() { |
|
assert_eq!( |
|
Opt { keyword_case: KeywordCase::None, ..Default::default() }, |
|
Opt::parse_from(["test", "--lowercase", "--no-lowercase"]) |
|
); |
|
} |
|
} |
|
} |