Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Arguments imply other arguments #15

Open
tertsdiepraam opened this issue Jan 3, 2023 · 3 comments
Open

Arguments imply other arguments #15

tertsdiepraam opened this issue Jan 3, 2023 · 3 comments

Comments

@tertsdiepraam
Copy link
Member

tertsdiepraam commented Jan 3, 2023

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
@tertsdiepraam
Copy link
Member Author

tertsdiepraam commented Jan 3, 2023

In fact, this could go even further. For example:

enum Arg {
    #[arg("--sort=SORT")]
    Sort(Sort),

    #[arg("-t", implies = [Arg::Sort(Sort::Time)])]
    SortTime,
}

@tertsdiepraam
Copy link
Member Author

tertsdiepraam commented Jan 23, 2023

On second thought, I wonder how we could do this without additional enum variants, i.e. have multiple arguments mapped to a single variant, which would be really nice for the sort option for instance:

enum Arg {
    /// Set the sorting method
    #[arg("--sort=SORT")]
    #[arg("-t", default = Sort::Time, help = "Sort by time")]
    Sort(Sort),
}

For the Show* arguments, we could define shortcuts of some kind:

enum Arg {
    #[combined("-e", [Arg::ShowEnds, Arg::ShowNonPrinting], help = "...")
    #[arg("-E")]
    ShowEnds,
    #[arg("-v")]
    ShowNonPrinting,
}

@tertsdiepraam
Copy link
Member Author

tertsdiepraam commented Jan 23, 2023

Here's another approach that's currently possible, but requires some boilerplate. There might be some crates that could take the boilerplate away.

#[derive(Default)]
struct Show {
    tabs: bool,
    ends: bool,
    nonprinting: bool,
}

impl Show {
    const TABS: Self = Self {
        tabs: true,
        ends: false,
        nonprinting: false,
    };
    const ENDS: Self = Self {
        tabs: false,
        ends: true,
        nonprinting: false,
    };
    const NONPRINTING: Self = Self {
        tabs: false,
        ends: false,
        nonprinting: true,
    };
}

impl BitOr for Show {
    type Output = Self;
    fn bitor(self, other: Self) -> Self {
        Self {
            tabs: self.tabs | other.tabs,
            ends: self.ends | other.ends,
            nonprinting: self.nonprinting | other.nonprinting,
        }
    }
}

#[derive(Arguments)]
enum Arg {
    #[arg("-A", "--show-all", default = Show::TABS | Show::ENDS | Show::NONPRINTING)]
    #[arg("-E", "--show-ends", default = Show::ENDS)]
    #[arg("-T", "--show-tabs", default = Show::TABS)]
    #[arg("-v", "--show-nonprinting", default = Show::NONPRINTING)]
    #[arg("-e", default = Show::ENDS | Show::NONPRINTING)]
    #[arg("-t", default = Show::TABS | Show::NONPRINTING)]
    Show(Show),
}

impl Options for Settings {
    type Arg = Arg;
    fn apply(&mut self, arg: Arg) {
        match arg {
            Arg::Show(s) => self.show = self.show | s,
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant