Skip to content

Arguments imply other arguments #15

@tertsdiepraam

Description

@tertsdiepraam

The many-to-many relationship of arguments and settings is currently handled entirely in the struct, but we could take some of the heavy lifting into the arguments. This will prevent that we forget to handle arguments in the struct.

My proposal is that arguments can imply other arguments, which will be added to the iterator after the initial argument. This allows us to rewrite some arguments with multiple effects as expanding into multiple smaller arguments. Take this set of arguments from cat for example:

#[derive(Clone, Arguments)]
enum Arg {
    #[arg("-A", "--show-all")]
    ShowAll,

    #[arg("-e")]
    ShowNonPrintingEnds,

    #[arg("-E")]
    ShowEnds,

    #[arg("-t")]
    ShowNonPrintingTabs,

    #[arg("-T", "--show-tabs")]
    ShowTabs,

    #[arg("-v", "--show-nonprinting")]
    ShowNonPrinting,
}

#[derive(Initial)]
struct Settings {
    show_tabs: bool,
    show_ends: bool,
    show_nonprinting: bool,
}

impl Options for Settings {
    type Arg =  Arg;
    fn apply(&mut self, arg: Arg) {
        if let Arg::ShowAll | Arg::ShowNonPrintingTabs | Arg::ShowTabs = arg {
            self.show_tabs = true;
        }
        if let Arg::ShowAll | Arg::ShowNonPrintingEnds | Arg::ShowEnds = arg {
            self.show_ends = true;
        }
        if let Arg::ShowAll | Arg::ShowNonPrintingTabs | Arg::ShowNonPrintingEnds | Arg::ShowNonPrinting = arg {
            self.show_non_printing = true;
        } 
    }
}

This could be rewritten as:

#[derive(Clone, Arguments)]
enum Arg {
    #[option("-A", "--show-all", implies = [Arg::ShowEnds, Arg::ShowTabs, Arg::ShowNonPrinting])]
    ShowAll,

    #[option("-e", implies = [Arg::ShowEnds, Arg::ShowNonPrinting])]
    ShowNonPrintingEnds,

    #[option("-E")]
    ShowEnds,

    #[option("-t", implies = [Arg::ShowEnds, Arg::ShowNonPrinting])]
    ShowNonPrintingTabs,

    #[option("-T", "--show-tabs")]
    ShowTabs,

    #[option("-v", "--show-nonprinting")]
    ShowNonPrinting,
}

#[derive(Initial)]
struct Settings {
    show_tabs: bool,
    show_ends: bool,
    show_nonprinting: bool,
}


impl Options for Settings {
    type Arg =  Arg;
    fn apply(&mut self, arg: Arg) {
        match arg {
            Arg::ShowTabs => self.show_tabs = true,
            Arg::ShowEnds => self.show_ends = true,
            Arg::ShowNonPrinting => self.show_nonprinting = true,
        }
    }
}

Open questions:

  • Should this be applied recursively? E.g. could ShowAll have implies = [Arg::ShowNonPrintingEnds, Arg::ShowTabs]. I think it shouldn't to prevent loops and to keep the implementation simpler.
  • Should the original argument be preserved?

Use cases:

  • -A, -e and -t in cat.
  • --zero, -o, -g, -n in ls
  • -s in basename
  • -d and -a in cp
  • -F in tail
  • -a in uname
  • -a in who

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions