1use clap::{
9 Arg, ArgAction, ArgMatches, Command,
10 builder::{TypedValueParser, ValueParserFactory},
11};
12use std::{
13 ffi::{OsStr, OsString},
14 io::{Write, stdout},
15 path::{Path, PathBuf},
16};
17use uucore::fs::make_path_relative_to;
18use uucore::translate;
19use uucore::{
20 display::{Quotable, print_verbatim},
21 error::{FromIo, UResult},
22 format_usage,
23 fs::{MissingHandling, ResolveMode, canonicalize},
24 line_ending::LineEnding,
25 show_if_err,
26};
27
28const OPT_QUIET: &str = "quiet";
29const OPT_STRIP: &str = "strip";
30const OPT_ZERO: &str = "zero";
31const OPT_PHYSICAL: &str = "physical";
32const OPT_LOGICAL: &str = "logical";
33const OPT_CANONICALIZE_MISSING: &str = "canonicalize-missing";
34const OPT_CANONICALIZE: &str = "canonicalize";
35const OPT_CANONICALIZE_EXISTING: &str = "canonicalize-existing";
36const OPT_RELATIVE_TO: &str = "relative-to";
37const OPT_RELATIVE_BASE: &str = "relative-base";
38
39const ARG_FILES: &str = "files";
40
41#[derive(Clone, Debug)]
43struct NonEmptyOsStringParser;
44
45impl TypedValueParser for NonEmptyOsStringParser {
46 type Value = OsString;
47
48 fn parse_ref(
49 &self,
50 _cmd: &Command,
51 _arg: Option<&Arg>,
52 value: &OsStr,
53 ) -> Result<Self::Value, clap::Error> {
54 if value.is_empty() {
55 let mut err = clap::Error::new(clap::error::ErrorKind::ValueValidation);
56 err.insert(
57 clap::error::ContextKind::Custom,
58 clap::error::ContextValue::String(translate!("realpath-invalid-empty-operand")),
59 );
60 return Err(err);
61 }
62 Ok(value.to_os_string())
63 }
64}
65
66impl ValueParserFactory for NonEmptyOsStringParser {
67 type Parser = Self;
68
69 fn value_parser() -> Self::Parser {
70 Self
71 }
72}
73
74#[uucore::main(no_signals)]
75pub fn uumain(args: impl uucore::Args) -> UResult<()> {
76 let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?;
77
78 let paths: Vec<PathBuf> = matches
81 .get_many::<OsString>(ARG_FILES)
82 .unwrap()
83 .map(PathBuf::from)
84 .collect();
85
86 let strip = matches.get_flag(OPT_STRIP);
87 let line_ending = LineEnding::from_zero_flag(matches.get_flag(OPT_ZERO));
88 let quiet = matches.get_flag(OPT_QUIET);
89 let logical = matches.get_flag(OPT_LOGICAL);
90 let can_mode = if matches.get_flag(OPT_CANONICALIZE_MISSING) {
91 MissingHandling::Missing
92 } else if matches.get_flag(OPT_CANONICALIZE_EXISTING) {
93 MissingHandling::Existing
96 } else {
97 MissingHandling::Normal
100 };
101 let resolve_mode = if strip {
102 ResolveMode::None
103 } else if logical {
104 ResolveMode::Logical
105 } else {
106 ResolveMode::Physical
107 };
108 let (relative_to, relative_base) = prepare_relative_options(&matches, can_mode, resolve_mode)?;
109 for path in &paths {
110 let result = resolve_path(
111 path,
112 line_ending,
113 resolve_mode,
114 can_mode,
115 relative_to.as_deref(),
116 relative_base.as_deref(),
117 );
118 if !quiet {
119 show_if_err!(result.map_err_context(|| path.maybe_quote().to_string()));
120 }
121 }
122 Ok(())
126}
127
128pub fn uu_app() -> Command {
129 Command::new("realpath")
130 .version(uucore::crate_version!())
131 .help_template(uucore::localized_help_template("realpath"))
132 .about(translate!("realpath-about"))
133 .override_usage(format_usage(&translate!("realpath-usage")))
134 .infer_long_args(true)
135 .arg(
136 Arg::new(OPT_QUIET)
137 .short('q')
138 .long(OPT_QUIET)
139 .help(translate!("realpath-help-quiet"))
140 .action(ArgAction::SetTrue),
141 )
142 .arg(
143 Arg::new(OPT_STRIP)
144 .short('s')
145 .long(OPT_STRIP)
146 .visible_alias("no-symlinks")
147 .help(translate!("realpath-help-strip"))
148 .action(ArgAction::SetTrue),
149 )
150 .arg(
151 Arg::new(OPT_ZERO)
152 .short('z')
153 .long(OPT_ZERO)
154 .help(translate!("realpath-help-zero"))
155 .action(ArgAction::SetTrue),
156 )
157 .arg(
158 Arg::new(OPT_LOGICAL)
159 .short('L')
160 .long(OPT_LOGICAL)
161 .help(translate!("realpath-help-logical"))
162 .action(ArgAction::SetTrue),
163 )
164 .arg(
165 Arg::new(OPT_PHYSICAL)
166 .short('P')
167 .long(OPT_PHYSICAL)
168 .overrides_with_all([OPT_STRIP, OPT_LOGICAL])
169 .help(translate!("realpath-help-physical"))
170 .action(ArgAction::SetTrue),
171 )
172 .arg(
173 Arg::new(OPT_CANONICALIZE)
174 .short('E')
175 .long(OPT_CANONICALIZE)
176 .overrides_with_all([OPT_CANONICALIZE_EXISTING, OPT_CANONICALIZE_MISSING])
177 .help(translate!("realpath-help-canonicalize"))
178 .action(ArgAction::SetTrue),
179 )
180 .arg(
181 Arg::new(OPT_CANONICALIZE_EXISTING)
182 .short('e')
183 .long(OPT_CANONICALIZE_EXISTING)
184 .overrides_with_all([OPT_CANONICALIZE, OPT_CANONICALIZE_MISSING])
185 .help(translate!("realpath-help-canonicalize-existing"))
186 .action(ArgAction::SetTrue),
187 )
188 .arg(
189 Arg::new(OPT_CANONICALIZE_MISSING)
190 .short('m')
191 .long(OPT_CANONICALIZE_MISSING)
192 .overrides_with_all([OPT_CANONICALIZE, OPT_CANONICALIZE_EXISTING])
193 .help(translate!("realpath-help-canonicalize-missing"))
194 .action(ArgAction::SetTrue),
195 )
196 .arg(
197 Arg::new(OPT_RELATIVE_TO)
198 .long(OPT_RELATIVE_TO)
199 .value_name("DIR")
200 .value_parser(NonEmptyOsStringParser)
201 .help(translate!("realpath-help-relative-to")),
202 )
203 .arg(
204 Arg::new(OPT_RELATIVE_BASE)
205 .long(OPT_RELATIVE_BASE)
206 .value_name("DIR")
207 .value_parser(NonEmptyOsStringParser)
208 .help(translate!("realpath-help-relative-base")),
209 )
210 .arg(
211 Arg::new(ARG_FILES)
212 .action(ArgAction::Append)
213 .required(true)
214 .value_parser(NonEmptyOsStringParser)
215 .value_hint(clap::ValueHint::AnyPath),
216 )
217}
218
219fn prepare_relative_options(
224 matches: &ArgMatches,
225 can_mode: MissingHandling,
226 resolve_mode: ResolveMode,
227) -> UResult<(Option<PathBuf>, Option<PathBuf>)> {
228 let relative_to = matches
229 .get_one::<OsString>(OPT_RELATIVE_TO)
230 .map(PathBuf::from);
231 let relative_base = matches
232 .get_one::<OsString>(OPT_RELATIVE_BASE)
233 .map(PathBuf::from);
234 let relative_to = canonicalize_relative_option(relative_to, can_mode, resolve_mode)?;
235 let relative_base = canonicalize_relative_option(relative_base, can_mode, resolve_mode)?;
236 match (relative_base.as_deref(), relative_to.as_deref()) {
237 (Some(b), Some(t)) if !t.starts_with(b) => Ok((None, None)),
238 _ => Ok((relative_to, relative_base)),
239 }
240}
241
242fn canonicalize_relative_option(
244 relative: Option<PathBuf>,
245 can_mode: MissingHandling,
246 resolve_mode: ResolveMode,
247) -> UResult<Option<PathBuf>> {
248 Ok(match relative {
249 None => None,
250 Some(p) => Some(
251 canonicalize_relative(&p, can_mode, resolve_mode)
252 .map_err_context(|| p.maybe_quote().to_string())?,
253 ),
254 })
255}
256
257fn canonicalize_relative(
265 r: &Path,
266 can_mode: MissingHandling,
267 resolve: ResolveMode,
268) -> std::io::Result<PathBuf> {
269 let abs = canonicalize(r, can_mode, resolve)?;
270 if can_mode == MissingHandling::Existing && !abs.is_dir() {
271 abs.read_dir()?; }
273 Ok(abs)
274}
275
276fn resolve_path(
290 p: &Path,
291 line_ending: LineEnding,
292 resolve: ResolveMode,
293 can_mode: MissingHandling,
294 relative_to: Option<&Path>,
295 relative_base: Option<&Path>,
296) -> std::io::Result<()> {
297 let abs = canonicalize(p, can_mode, resolve)?;
298
299 let abs = process_relative(abs, relative_base, relative_to);
300
301 print_verbatim(abs)?;
302 stdout().write_all(&[line_ending.into()])?;
303 Ok(())
304}
305
306fn process_relative(
318 path: PathBuf,
319 relative_base: Option<&Path>,
320 relative_to: Option<&Path>,
321) -> PathBuf {
322 if let Some(base) = relative_base {
323 if path.starts_with(base) {
324 make_path_relative_to(path, relative_to.unwrap_or(base))
325 } else {
326 path
327 }
328 } else if let Some(to) = relative_to {
329 make_path_relative_to(path, to)
330 } else {
331 path
332 }
333}