rustutils_dirname/
lib.rs

1use clap::Parser;
2use rustutils_runnable::Runnable;
3use std::error::Error;
4use std::io::Write;
5use std::os::unix::ffi::OsStrExt;
6use std::path::{Path, PathBuf};
7
8/// Output the parent directory of each path.
9#[derive(Parser, Clone, Debug)]
10#[clap(author, version, about, long_about = None)]
11pub struct Dirname {
12    #[clap(required = true)]
13    path: Vec<PathBuf>,
14    /// End each output line with a NUL character instead of a newline.
15    #[clap(short, long)]
16    zero: bool,
17}
18
19/// Given a path, compue the parent directory path.
20///
21/// If the path is relative, it assumes that it is relative to the current directory
22/// (`.`). When the root path (`/`) is given, it returns the root path (`/`).
23pub fn dirname(path: &Path) -> &Path {
24    match path.parent() {
25        Some(path) if path.as_os_str().len() == 0 => Path::new("."),
26        Some(path) => path,
27        None if path.as_os_str().len() == 0 => Path::new("."),
28        None => Path::new("/"),
29    }
30}
31
32impl Runnable for Dirname {
33    fn run(&self) -> Result<(), Box<dyn Error>> {
34        // Since Linux paths do not have to be valid UTF-8, but Rust strings do, we need to
35        // be careful here not to use println or similar macros, but rather write directly
36        // to standard output.
37        let mut stdout = std::io::stdout();
38        for path in &self.path {
39            stdout.write_all(dirname(&path).as_os_str().as_bytes())?;
40            if self.zero {
41                stdout.write_all(&[0])?;
42            } else {
43                stdout.write_all(&[b'\n'])?;
44            }
45        }
46
47        Ok(())
48    }
49}
50
51#[test]
52fn dirname_absolute_works() {
53    assert_eq!(dirname(&Path::new("/")), Path::new("/"));
54    assert_eq!(dirname(&Path::new("/abc")), Path::new("/"));
55    assert_eq!(dirname(&Path::new("/abc/")), Path::new("/"));
56    assert_eq!(dirname(&Path::new("/abc/def")), Path::new("/abc"));
57}
58
59#[test]
60fn dirname_relative_works() {
61    assert_eq!(dirname(&Path::new("")), Path::new("."));
62    assert_eq!(dirname(&Path::new(".")), Path::new("."));
63    assert_eq!(dirname(&Path::new("abc")), Path::new("."));
64    assert_eq!(dirname(&Path::new("./abc")), Path::new("."));
65    assert_eq!(dirname(&Path::new("./abc/")), Path::new("."));
66    assert_eq!(dirname(&Path::new("./abc/def")), Path::new("./abc"));
67    assert_eq!(dirname(&Path::new("../abc")), Path::new(".."));
68    assert_eq!(dirname(&Path::new("../abc/")), Path::new(".."));
69    assert_eq!(dirname(&Path::new("../../abc")), Path::new("../.."));
70}