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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
//  * This file is part of the uutils coreutils package.
//  *
//  * (c) 2014 Vsevolod Velichko <torkvemada@sorokdva.net>
//  *
//  * For the full copyright and license information, please view the LICENSE
//  * file that was distributed with this source code.

// spell-checker:ignore (ToDO) subpath absto absfrom absbase

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(); // required
    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))
}