• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MilesCranmer / rip2 / #797

02 Sep 2025 11:17PM UTC coverage: 58.14% (-5.0%) from 63.158%
#797

push

325 of 559 relevant lines covered (58.14%)

180557783.75 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.61
/src/args.rs
1
use anstyle::{AnsiColor, Color::Ansi, Style};
2
use clap::builder::styling::Styles;
3
use clap::{Parser, Subcommand};
4

5
use std::io::{Error, ErrorKind};
6
use std::path::PathBuf;
7

8
const CMD_STYLE: Style = Style::new()
9
    .bold()
10
    .fg_color(Some(Ansi(AnsiColor::BrightCyan)));
11
const HEADER_STYLE: Style = Style::new().bold().fg_color(Some(Ansi(AnsiColor::Green)));
12
const PLACEHOLDER_STYLE: Style = Style::new().fg_color(Some(Ansi(AnsiColor::BrightCyan)));
13
const STYLES: Styles = Styles::styled()
14
    .literal(AnsiColor::BrightCyan.on_default().bold())
15
    .placeholder(AnsiColor::BrightCyan.on_default());
16

17
const OPTIONS_PLACEHOLDER: &str = "{options}";
18
const SUBCOMMANDS_PLACEHOLDER: &str = "{subcommands}";
19

20
fn help_template(template: &str) -> String {
36✔
21
    let header = HEADER_STYLE.render();
72✔
22
    let rheader = HEADER_STYLE.render_reset();
72✔
23
    let rip_s = CMD_STYLE.render();
72✔
24
    let rrip_s = CMD_STYLE.render_reset();
72✔
25
    let place = PLACEHOLDER_STYLE.render();
72✔
26
    let rplace = PLACEHOLDER_STYLE.render_reset();
72✔
27

28
    match template {
36✔
29
        "rip" => format!(
504✔
30
            "\
31
rip: a safe and ergonomic alternative to rm
32

33
{header}Usage{rheader}: {rip_s}rip{rrip_s} [{place}OPTIONS{rplace}] [{place}FILES{rplace}]...
34
       {rip_s}rip{rrip_s} [{place}SUBCOMMAND{rplace}]
35

36
{header}Arguments{rheader}:
37
    [{place}FILES{rplace}]...  Files or directories to remove
38

39
{header}Options{rheader}:
40
{OPTIONS_PLACEHOLDER}
41

42
{header}Subcommands{rheader}:
43
{SUBCOMMANDS_PLACEHOLDER}
44
"
45
        ),
46
        "completions" => format!(
466✔
47
            "\
48
Generate the shell completions file
49

50
{header}Usage{rheader}: {rip_s}rip completions{rrip_s} <{place}SHELL{rplace}>
51

52
{header}Arguments{rheader}:
53
    <{place}SHELL{rplace}>  The shell to generate completions for (bash, elvish, fish, powershell, zsh, nushell)
54

55
{header}Options{rheader}:
56
{OPTIONS_PLACEHOLDER}
57
"
58
        ),
59
        "graveyard" => format!(
466✔
60
            "\
61
Print the graveyard path
62

63
{header}Usage{rheader}: {rip_s}rip graveyard{rrip_s} [{place}OPTIONS{rplace}]
64

65
{header}Options{rheader}:
66
{OPTIONS_PLACEHOLDER}
67
"
68
        ),
69
        _ => unreachable!(),
70
    }
71
}
72

73
#[derive(Parser, Debug, Default)]
74
#[command(
75
    name = "rip",
76
    version,
77
    about,
78
    long_about = None,
79
    styles=STYLES,
80
    help_template = help_template("rip"),
81
)]
82
pub struct Args {
83
    /// Files and directories to remove
84
    pub targets: Vec<PathBuf>,
85

86
    /// Directory where deleted files rest
87
    #[arg(long)]
88
    pub graveyard: Option<PathBuf>,
89

90
    /// Permanently deletes the graveyard
91
    #[arg(short, long)]
92
    pub decompose: bool,
93

94
    /// Prints files that were deleted
95
    /// in the current directory
96
    #[arg(short, long)]
97
    pub seance: bool,
98

99
    /// Restore the specified
100
    /// files or the last file
101
    /// if none are specified
102
    #[arg(short, long, num_args = 0..)]
103
    pub unbury: Option<Vec<PathBuf>>,
104

105
    /// Print some info about FILES before
106
    /// burying
107
    #[arg(short, long)]
108
    pub inspect: bool,
109

110
    /// Non-interactive mode
111
    #[arg(short, long)]
112
    pub force: bool,
113

114
    #[command(subcommand)]
115
    pub command: Option<Commands>,
116
}
117

118
#[derive(Subcommand, Debug)]
119
pub enum Commands {
120
    /// Generate shell completions file
121
    #[command(styles=STYLES, help_template=help_template("completions"))]
122
    Completions {
123
        /// The shell to generate completions for
124
        #[arg(value_name = "SHELL")]
125
        shell: String,
126
    },
127

128
    /// Print the graveyard path
129
    #[command(styles=STYLES, help_template=help_template("graveyard"))]
130
    Graveyard {
131
        /// Get the graveyard subdirectory
132
        /// of the current directory
133
        #[arg(short, long)]
134
        seance: bool,
135
    },
136
}
137

138
struct IsDefault {
139
    graveyard: bool,
140
    decompose: bool,
141
    seance: bool,
142
    unbury: bool,
143
    inspect: bool,
144
    force: bool,
145
    completions: bool,
146
}
147

148
impl IsDefault {
149
    fn new(cli: &Args) -> Self {
20✔
150
        let defaults = Args::default();
40✔
151
        Self {
152
            graveyard: cli.graveyard == defaults.graveyard,
40✔
153
            decompose: cli.decompose == defaults.decompose,
40✔
154
            seance: cli.seance == defaults.seance,
40✔
155
            unbury: cli.unbury == defaults.unbury,
40✔
156
            inspect: cli.inspect == defaults.inspect,
40✔
157
            force: cli.force == defaults.force,
40✔
158
            completions: cli.command.is_none(),
20✔
159
        }
160
    }
161
}
162

163
#[allow(clippy::nonminimal_bool)]
164
pub fn validate_args(cli: &Args) -> Result<(), Error> {
36✔
165
    let defaults = IsDefault::new(cli);
108✔
166

167
    // [completions] can only be used by itself
168
    if !defaults.completions
36✔
169
        && !(defaults.graveyard
×
170
            && defaults.decompose
×
171
            && defaults.seance
×
172
            && defaults.unbury
×
173
            && defaults.inspect
×
174
            && defaults.force)
×
175
    {
176
        return Err(Error::new(
2,147,483,647✔
177
            ErrorKind::InvalidInput,
2,147,483,647✔
178
            "--completions can only be used by itself",
2,147,483,647✔
179
        ));
180
    }
181
    if !defaults.decompose && !(defaults.seance && defaults.unbury && defaults.inspect) {
152✔
182
        return Err(Error::new(
148✔
183
            ErrorKind::InvalidInput,
148✔
184
            "-d,--decompose can only be used with --graveyard",
148✔
185
        ));
186
    }
187

188
    // Force and inspect are incompatible
189
    if !defaults.force && !defaults.inspect {
6✔
190
        return Err(Error::new(
×
191
            ErrorKind::InvalidInput,
×
192
            "-f,--force and -i,--inspect cannot be used together",
×
193
        ));
194
    }
195

196
    Ok(())
×
197
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc