uu_basename/
basename.rs

1// This file is part of the uutils coreutils package.
2//
3// For the full copyright and license information, please view the LICENSE
4// file that was distributed with this source code.
5
6// spell-checker:ignore (ToDO) fullname
7
8use clap::builder::ValueParser;
9use clap::{Arg, ArgAction, Command};
10use std::ffi::OsString;
11use std::io::{Write, stdout};
12use std::path::PathBuf;
13use uucore::display::Quotable;
14use uucore::error::{UResult, UUsageError};
15use uucore::format_usage;
16use uucore::line_ending::LineEnding;
17
18use uucore::translate;
19
20pub mod options {
21    pub static MULTIPLE: &str = "multiple";
22    pub static NAME: &str = "name";
23    pub static SUFFIX: &str = "suffix";
24    pub static ZERO: &str = "zero";
25}
26
27#[uucore::main]
28pub fn uumain(args: impl uucore::Args) -> UResult<()> {
29    //
30    // Argument parsing
31    //
32    let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?;
33
34    let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO));
35
36    let mut name_args = matches
37        .get_many::<OsString>(options::NAME)
38        .unwrap_or_default()
39        .collect::<Vec<_>>();
40    if name_args.is_empty() {
41        return Err(UUsageError::new(
42            1,
43            translate!("basename-error-missing-operand"),
44        ));
45    }
46    let multiple_paths = matches.get_one::<OsString>(options::SUFFIX).is_some()
47        || matches.get_flag(options::MULTIPLE);
48    let suffix = if multiple_paths {
49        matches
50            .get_one::<OsString>(options::SUFFIX)
51            .cloned()
52            .unwrap_or_default()
53    } else {
54        // "simple format"
55        match name_args.len() {
56            0 => panic!("already checked"),
57            1 => OsString::default(),
58            2 => name_args.pop().unwrap().clone(),
59            _ => {
60                return Err(UUsageError::new(
61                    1,
62                    translate!("basename-error-extra-operand",
63                               "operand" => name_args[2].quote()),
64                ));
65            }
66        }
67    };
68
69    //
70    // Main Program Processing
71    //
72
73    for path in name_args {
74        stdout().write_all(&basename(path, &suffix)?)?;
75        print!("{line_ending}");
76    }
77
78    Ok(())
79}
80
81pub fn uu_app() -> Command {
82    Command::new(uucore::util_name())
83        .version(uucore::crate_version!())
84        .help_template(uucore::localized_help_template(uucore::util_name()))
85        .about(translate!("basename-about"))
86        .override_usage(format_usage(&translate!("basename-usage")))
87        .infer_long_args(true)
88        .arg(
89            Arg::new(options::MULTIPLE)
90                .short('a')
91                .long(options::MULTIPLE)
92                .help(translate!("basename-help-multiple"))
93                .action(ArgAction::SetTrue)
94                .overrides_with(options::MULTIPLE),
95        )
96        .arg(
97            Arg::new(options::NAME)
98                .action(ArgAction::Append)
99                .value_parser(ValueParser::os_string())
100                .value_hint(clap::ValueHint::AnyPath)
101                .hide(true)
102                .trailing_var_arg(true),
103        )
104        .arg(
105            Arg::new(options::SUFFIX)
106                .short('s')
107                .long(options::SUFFIX)
108                .value_name("SUFFIX")
109                .value_parser(ValueParser::os_string())
110                .help(translate!("basename-help-suffix"))
111                .overrides_with(options::SUFFIX),
112        )
113        .arg(
114            Arg::new(options::ZERO)
115                .short('z')
116                .long(options::ZERO)
117                .help(translate!("basename-help-zero"))
118                .action(ArgAction::SetTrue)
119                .overrides_with(options::ZERO),
120        )
121}
122
123// We return a Vec<u8>. Returning a seemingly more proper `OsString` would
124// require back and forth conversions as we need a &[u8] for printing anyway.
125fn basename(fullname: &OsString, suffix: &OsString) -> UResult<Vec<u8>> {
126    let fullname_bytes = uucore::os_str_as_bytes(fullname)?;
127
128    // Handle special case where path ends with /.
129    if fullname_bytes.ends_with(b"/.") {
130        return Ok(b".".into());
131    }
132
133    // Convert to path buffer and get last path component
134    let pb = PathBuf::from(fullname);
135
136    pb.components().next_back().map_or(Ok([].into()), |c| {
137        let name = c.as_os_str();
138        let name_bytes = uucore::os_str_as_bytes(name)?;
139        if name == suffix {
140            Ok(name_bytes.into())
141        } else {
142            let suffix_bytes = uucore::os_str_as_bytes(suffix)?;
143            Ok(name_bytes
144                .strip_suffix(suffix_bytes)
145                .unwrap_or(name_bytes)
146                .into())
147        }
148    })
149}