1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
use clap::Parser;
use rustutils_runnable::Runnable;
use std::error::Error;
use std::ffi::{OsStr, OsString};
use std::io::Write;
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};

/// Print path with any leading directory components removed.
///
/// If specified, also remove a trailing suffix.
#[derive(Parser, Clone, Debug)]
#[clap(author, version, about, long_about = None)]
pub struct Basename {
    /// Path to remove leading directory components from.
    path: PathBuf,
    /// Suffix to optionally remove.
    suffix: Option<OsString>,
}

pub fn remove_suffix<'a, 'b>(string: &'a OsStr, suffix: Option<&'b OsStr>) -> &'a OsStr {
    if let Some(suffix) = suffix {
        if suffix.len() <= string.len() {
            if &string.as_bytes()[string.len() - suffix.len()..] == suffix.as_bytes() {
                return OsStr::from_bytes(&string.as_bytes()[0..string.len() - suffix.len()]);
            }
        }
    }

    string
}

pub fn basename<'a, 'b>(path: &'a Path, suffix: Option<&'b OsStr>) -> &'a OsStr {
    match path.file_name() {
        Some(string) => remove_suffix(string, suffix),
        None if path.as_os_str() == "" => OsStr::new(""),
        None if path.ends_with("..") => OsStr::new(".."),
        None => OsStr::new(""),
    }
}

impl Runnable for Basename {
    fn run(&self) -> Result<(), Box<dyn Error>> {
        let mut stdout = std::io::stdout();
        let path = basename(&self.path, self.suffix.as_deref());
        stdout.write_all(&path.as_bytes())?;
        stdout.write_all(&[b'\n'])?;
        Ok(())
    }
}