use clap::{crate_version, Arg, Command};
use std::env;
use std::path::{Path, PathBuf};
use uucore::display::println_verbatim;
use uucore::error::{FromIo, UResult};
use uucore::fs::{canonicalize, MissingHandling, ResolveMode};
use uucore::{format_usage, help_about, help_usage};
const USAGE: &str = help_usage!("relpath.md");
const ABOUT: &str = help_about!("relpath.md");
mod options {
pub const DIR: &str = "DIR";
pub const TO: &str = "TO";
pub const FROM: &str = "FROM";
}
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let args = args.collect_lossy();
let matches = uu_app().get_matches_from(args);
let to = Path::new(matches.get_one::<String>(options::TO).unwrap()).to_path_buf(); let from = match matches.get_one::<String>(options::FROM) {
Some(p) => Path::new(p).to_path_buf(),
None => env::current_dir().unwrap(),
};
let absto = canonicalize(to, MissingHandling::Normal, ResolveMode::Logical)
.map_err_context(String::new)?;
let absfrom = canonicalize(from, MissingHandling::Normal, ResolveMode::Logical)
.map_err_context(String::new)?;
if matches.contains_id(options::DIR) {
let base = Path::new(&matches.get_one::<String>(options::DIR).unwrap()).to_path_buf();
let absbase = canonicalize(base, MissingHandling::Normal, ResolveMode::Logical)
.map_err_context(String::new)?;
if !absto.as_path().starts_with(absbase.as_path())
|| !absfrom.as_path().starts_with(absbase.as_path())
{
return println_verbatim(absto).map_err_context(String::new);
}
}
let mut suffix_pos = 0;
for (f, t) in absfrom.components().zip(absto.components()) {
if f == t {
suffix_pos += 1;
} else {
break;
}
}
let mut result = PathBuf::new();
absfrom
.components()
.skip(suffix_pos)
.map(|_| result.push(".."))
.last();
absto
.components()
.skip(suffix_pos)
.map(|x| result.push(x.as_os_str()))
.last();
println_verbatim(result).map_err_context(String::new)
}
pub fn uu_app() -> Command {
Command::new(uucore::util_name())
.version(crate_version!())
.about(ABOUT)
.override_usage(format_usage(USAGE))
.infer_long_args(true)
.arg(Arg::new(options::DIR).short('d').help(
"If any of FROM and TO is not subpath of DIR, output absolute path instead of relative",
))
.arg(Arg::new(options::TO).value_hint(clap::ValueHint::AnyPath))
.arg(Arg::new(options::FROM).value_hint(clap::ValueHint::AnyPath))
}