shellexpand/funcs.rs
1//! Implementation - **instantiated twice**
2//!
3//! **IMPORTANT NOTE TO IMPLEMENTORS**
4//!
5//! This module is included twice - there are two `mod` statements.
6//! The `use super::wtraits::*` line imports *different types* each type.
7//!
8//! This allows the same code to do double duty: it works with `str`, and also with `Path`.
9//! Working with `Path` is quite awkward and complicated - see `path.rs` for the type definitions.
10//!
11//! The `wtraits` module has all the type names and traits we use,
12//! along with documentation of their semantics.
13//!
14//! But we also allow the use of inherent methods
15//! (if they do the right things with both the string and path types).
16
17use std::env::VarError;
18use std::error::Error;
19
20use super::wtraits::*;
21
22#[cfg(test)]
23mod test;
24
25/// Performs both tilde and environment expansion using the provided contexts.
26///
27/// `home_dir` and `context` are contexts for tilde expansion and environment expansion,
28/// respectively. See [`env_with_context()`] and [`tilde_with_context()`] for more details on
29/// them.
30///
31/// Unfortunately, expanding both `~` and `$VAR`s at the same time is not that simple. First,
32/// this function has to track ownership of the data. Since all functions in this crate
33/// return [`Cow<str>`], this function takes some precautions in order not to allocate more than
34/// necessary. In particular, if the input string contains neither tilde nor `$`-vars, this
35/// function will perform no allocations.
36///
37/// Second, if the input string starts with a variable, and the value of this variable starts
38/// with tilde, the naive approach may result into expansion of this tilde. This function
39/// avoids this.
40///
41/// # Examples
42///
43/// ```
44/// use std::path::{PathBuf, Path};
45/// use std::borrow::Cow;
46///
47/// fn home_dir() -> Option<String> { Some("/home/user".into()) }
48///
49/// fn get_env(name: &str) -> Result<Option<&'static str>, &'static str> {
50/// match name {
51/// "A" => Ok(Some("a value")),
52/// "B" => Ok(Some("b value")),
53/// "T" => Ok(Some("~")),
54/// "E" => Err("some error"),
55/// _ => Ok(None)
56/// }
57/// }
58///
59/// // Performs both tilde and environment expansions
60/// assert_eq!(
61/// shellexpand::full_with_context("~/$A/$B", home_dir, get_env).unwrap(),
62/// "/home/user/a value/b value"
63/// );
64///
65/// // Errors from environment expansion are propagated to the result
66/// assert_eq!(
67/// shellexpand::full_with_context("~/$E/something", home_dir, get_env),
68/// Err(shellexpand::LookupError {
69/// var_name: "E".into(),
70/// cause: "some error"
71/// })
72/// );
73///
74/// // Input without starting tilde and without variables does not cause allocations
75/// let s = shellexpand::full_with_context("some/path", home_dir, get_env);
76/// match s {
77/// Ok(Cow::Borrowed(s)) => assert_eq!(s, "some/path"),
78/// _ => unreachable!("the above variant is always valid")
79/// }
80///
81/// // Input with a tilde inside a variable in the beginning of the string does not cause tilde
82/// // expansion
83/// assert_eq!(
84/// shellexpand::full_with_context("$T/$A/$B", home_dir, get_env).unwrap(),
85/// "~/a value/b value"
86/// );
87/// ```
88pub fn full_with_context<SI: ?Sized, CO, C, E, P, HD>(
89 input: &SI,
90 home_dir: HD,
91 context: C,
92) -> Result<Cow<Xstr>, LookupError<E>>
93where
94 SI: AsRef<Xstr>,
95 CO: AsRef<Xstr>,
96 C: FnMut(&str) -> Result<Option<CO>, E>,
97 P: AsRef<Xstr>,
98 HD: FnOnce() -> Option<P>,
99{
100 env_with_context(input, context).map(|r| match r {
101 // variable expansion did not modify the original string, so we can apply tilde expansion
102 // directly
103 Cow::Borrowed(s) => tilde_with_context(s, home_dir),
104 Cow::Owned(s) => {
105 // if the original string does not start with a tilde but the processed one does,
106 // then the tilde is contained in one of variables and should not be expanded
107 // (We must convert the input to WInput here because it might be `AsRef<Path>`.
108 // and `Path`'s `starts_with` checks only whole components;
109 // and `OsStr` doesn't let us test prefixes at all.)
110 if !input.into_winput().starts_with('~') && s.starts_with("~") {
111 // return as is
112 s.into()
113 } else if let Cow::Owned(s) = tilde_with_context(&s, home_dir) {
114 s.into()
115 } else {
116 s.into()
117 }
118 }
119 })
120}
121
122/// Same as [`full_with_context()`], but forbids the variable lookup function to return errors.
123///
124/// This function also performs full shell-like expansion, but it uses
125/// [`env_with_context_no_errors()`] for environment expansion whose context lookup function returns
126/// just [`Option<CO>`] instead of [`Result<Option<CO>, E>`]. Therefore, the function itself also
127/// returns just [`Cow<str>`] instead of [`Result<Cow<str>, LookupError<E>>`]. Otherwise it is
128/// identical to [`full_with_context()`].
129///
130/// # Examples
131///
132/// ```
133/// use std::path::{PathBuf, Path};
134/// use std::borrow::Cow;
135///
136/// fn home_dir() -> Option<String> { Some("/home/user".into()) }
137///
138/// fn get_env(name: &str) -> Option<&'static str> {
139/// match name {
140/// "A" => Some("a value"),
141/// "B" => Some("b value"),
142/// "T" => Some("~"),
143/// _ => None
144/// }
145/// }
146///
147/// // Performs both tilde and environment expansions
148/// assert_eq!(
149/// shellexpand::full_with_context_no_errors("~/$A/$B", home_dir, get_env),
150/// "/home/user/a value/b value"
151/// );
152///
153/// // Input without starting tilde and without variables does not cause allocations
154/// let s = shellexpand::full_with_context_no_errors("some/path", home_dir, get_env);
155/// match s {
156/// Cow::Borrowed(s) => assert_eq!(s, "some/path"),
157/// _ => unreachable!("the above variant is always valid")
158/// }
159///
160/// // Input with a tilde inside a variable in the beginning of the string does not cause tilde
161/// // expansion
162/// assert_eq!(
163/// shellexpand::full_with_context_no_errors("$T/$A/$B", home_dir, get_env),
164/// "~/a value/b value"
165/// );
166/// ```
167#[inline]
168pub fn full_with_context_no_errors<SI: ?Sized, CO, C, P, HD>(
169 input: &SI,
170 home_dir: HD,
171 mut context: C,
172) -> Cow<Xstr>
173where
174 SI: AsRef<Xstr>,
175 CO: AsRef<Xstr>,
176 C: FnMut(&str) -> Option<CO>,
177 P: AsRef<Xstr>,
178 HD: FnOnce() -> Option<P>,
179{
180 match full_with_context(input, home_dir, move |s| Ok::<Option<CO>, ()>(context(s))) {
181 Ok(result) => result,
182 Err(_) => unreachable!(),
183 }
184}
185
186/// Performs both tilde and environment expansions in the default system context.
187///
188/// This function delegates to [`full_with_context()`], using the default system sources for both
189/// home directory and environment, namely [`dirs::home_dir()`] and [`std::env::var()`].
190///
191/// Note that variable lookup of unknown variables will fail with an error instead of, for example,
192/// replacing the unknown variable with an empty string. The author thinks that this behavior is
193/// more useful than the other ones. If you need to change it, use [`full_with_context()`] or
194/// [`full_with_context_no_errors()`] with an appropriate context function instead.
195///
196/// This function behaves exactly like [`full_with_context()`] in regard to tilde-containing
197/// variables in the beginning of the input string.
198///
199/// # Examples
200///
201/// ```
202/// use std::env;
203///
204/// env::set_var("A", "a value");
205/// env::set_var("B", "b value");
206///
207/// let home_dir = dirs::home_dir()
208/// .map(|p| p.display().to_string())
209/// .unwrap_or_else(|| "~".to_owned());
210///
211/// // Performs both tilde and environment expansions using the system contexts
212/// assert_eq!(
213/// shellexpand::full("~/$A/${B}s").unwrap(),
214/// format!("{}/a value/b values", home_dir)
215/// );
216///
217/// // Unknown variables cause expansion errors
218/// assert_eq!(
219/// shellexpand::full("~/$UNKNOWN/$B"),
220/// Err(shellexpand::LookupError {
221/// var_name: "UNKNOWN".into(),
222/// cause: env::VarError::NotPresent
223/// })
224/// );
225/// ```
226#[cfg(feature = "tilde")]
227#[inline]
228pub fn full<SI: ?Sized>(input: &SI) -> Result<Cow<Xstr>, LookupError<VarError>>
229where
230 SI: AsRef<Xstr>,
231{
232 full_with_context(input, home_dir, |s| std::env::var(s).map(Some))
233}
234
235#[cfg(feature = "tilde")]
236fn home_dir() -> Option<XString> {
237 let hd = dirs::home_dir()?;
238
239 // If the home directory is not valid Unicode, we might not be able to substitute it.
240 // We don't have an error reporting channel suitable for this (very unusual) situation.
241 // If it happens, we just return `None`, causing `~`-substitution to not occur.
242 //
243 // In `shellexpand` 2.x, we use `Path::display()`, instead, which is lossy - so we would
244 // use a wrong pathname. That is definitely worse.
245 hd.try_into_xstring()
246}
247
248/// Represents a variable lookup error.
249///
250/// This error is returned by [`env_with_context()`] function (and, therefore, also by [`env()`],
251/// [`full_with_context()`] and [`full()`]) when the provided context function returns an error. The
252/// original error is provided in the `cause` field, while `name` contains the name of a variable
253/// whose expansion caused the error.
254#[derive(Debug, Clone, PartialEq, Eq)]
255pub struct LookupError<E> {
256 /// The name of the problematic variable inside the input string.
257 pub var_name: OString,
258 /// The original error returned by the context function.
259 pub cause: E,
260}
261
262impl<E: fmt::Display> fmt::Display for LookupError<E> {
263 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
264 write!(f, "error looking key '")?;
265 self.var_name.display_possibly_lossy(f)?;
266 write!(f, "' up: {}", self.cause)?;
267 Ok(())
268 }
269}
270
271impl<E: Error + 'static> Error for LookupError<E> {
272 fn source(&self) -> Option<&(dyn Error + 'static)> {
273 Some(&self.cause)
274 }
275}
276
277macro_rules! try_lookup {
278 ($name:expr, $e:expr) => {
279 match $e {
280 Ok(s) => s,
281 Err(e) => {
282 return Err(LookupError {
283 var_name: $name.to_ostring(),
284 cause: e,
285 })
286 }
287 }
288 };
289}
290
291fn is_valid_var_name_char(c: char) -> bool {
292 c.is_alphanumeric() || c == '_'
293}
294
295/// Performs the environment expansion using the provided context.
296///
297/// This function walks through the input string `input` and attempts to construct a new string by
298/// replacing all shell-like variable sequences with the corresponding values obtained via the
299/// `context` function. The latter may return an error; in this case the error will be returned
300/// immediately, along with the name of the offending variable. Also the context function may
301/// return `Ok(None)`, indicating that the given variable is not available; in this case the
302/// variable sequence is left as it is in the output string.
303///
304/// The syntax of variables resembles the one of bash-like shells: all of `$VAR`, `${VAR}`,
305/// `$NAME_WITH_UNDERSCORES` are valid variable references, and the form with braces may be used to
306/// separate the reference from the surrounding alphanumeric text: `before${VAR}after`. Note,
307/// however, that for simplicity names like `$123` or `$1AB` are also valid, as opposed to shells
308/// where `$<number>` has special meaning of positional arguments. Also note that "alphanumericity"
309/// of variable names is checked with [`std::primitive::char::is_alphanumeric()`], therefore lots of characters which
310/// are considered alphanumeric by the Unicode standard are also valid names for variables. When
311/// unsure, use braces to separate variables from the surrounding text.
312///
313/// This function has four generic type parameters: `SI` represents the input string, `CO` is the
314/// output of context lookups, `C` is the context closure and `E` is the type of errors returned by
315/// the context function. `SI` and `CO` must be types, a references to which can be converted to
316/// a string slice. For example, it is fine for the context function to return [`&str`]'s, [`String`]'s or
317/// [`Cow<str>`]'s, which gives the user a lot of flexibility.
318///
319/// If the context function returns an error, it will be wrapped into [`LookupError`] and returned
320/// immediately. [`LookupError`], besides the original error, also contains a string with the name of
321/// the variable whose expansion caused the error. [`LookupError`] implements [`Error`], [`Clone`] and
322/// [`Eq`] traits for further convenience and interoperability.
323///
324/// If you need to expand system environment variables, you can use [`env()`] or [`full()`] functions.
325/// If your context does not have errors, you may use [`env_with_context_no_errors()`] instead of
326/// this function because it provides a simpler API.
327///
328/// # Examples
329///
330/// ```
331/// fn context(s: &str) -> Result<Option<&'static str>, &'static str> {
332/// match s {
333/// "A" => Ok(Some("a value")),
334/// "B" => Ok(Some("b value")),
335/// "E" => Err("something went wrong"),
336/// _ => Ok(None)
337/// }
338/// }
339///
340/// // Regular variables are expanded
341/// assert_eq!(
342/// shellexpand::env_with_context("begin/$A/${B}s/end", context).unwrap(),
343/// "begin/a value/b values/end"
344/// );
345///
346/// // Expand to a default value if the variable is not defined
347/// assert_eq!(
348/// shellexpand::env_with_context("begin/${UNSET_ENV:-42}/end", context).unwrap(),
349/// "begin/42/end"
350/// );
351///
352/// // Unknown variables are left as is
353/// assert_eq!(
354/// shellexpand::env_with_context("begin/$UNKNOWN/end", context).unwrap(),
355/// "begin/$UNKNOWN/end"
356/// );
357///
358/// // Errors are propagated
359/// assert_eq!(
360/// shellexpand::env_with_context("begin${E}end", context),
361/// Err(shellexpand::LookupError {
362/// var_name: "E".into(),
363/// cause: "something went wrong"
364/// })
365/// );
366/// ```
367pub fn env_with_context<SI: ?Sized, CO, C, E>(
368 input: &SI,
369 mut context: C,
370) -> Result<Cow<Xstr>, LookupError<E>>
371where
372 SI: AsRef<Xstr>,
373 CO: AsRef<Xstr>,
374 C: FnMut(&str) -> Result<Option<CO>, E>,
375{
376 let input_str = input.into_winput();
377 if let Some(idx) = input_str.find('$') {
378 let mut result = OString::with_capacity(input_str.len());
379
380 let mut input_str = input_str.as_wstr();
381 let mut next_dollar_idx = idx;
382 loop {
383 result.push_wstr(&input_str[..next_dollar_idx]);
384
385 input_str = &input_str[next_dollar_idx..];
386 if input_str.is_empty() {
387 break;
388 }
389
390 fn find_dollar(s: &Wstr) -> usize {
391 s.find('$').unwrap_or(s.len())
392 }
393 let mut lookup = |var_name: &Wstr| {
394 let var_name = match var_name.as_str() {
395 Some(var_name) => var_name,
396 // No non-UTF-8 variables can exist
397 None => return Ok(None),
398 };
399 context(var_name)
400 };
401
402 let mut next_chars = input_str[1..].chars_approx();
403 let next_char = next_chars.next();
404 if next_char == Some('{') {
405 match input_str.find('}') {
406 Some(closing_brace_idx) => {
407 let mut default_value = None;
408
409 // Search for the default split
410 let var_name_end_idx = match input_str[..closing_brace_idx].find(":-") {
411 // Only match if there's a variable name, ie. this is not valid ${:-value}
412 Some(default_split_idx) if default_split_idx != 2 => {
413 default_value =
414 Some(&input_str[default_split_idx + 2..closing_brace_idx]);
415 default_split_idx
416 }
417 _ => closing_brace_idx,
418 };
419
420 let var_name = &input_str[2..var_name_end_idx];
421 match lookup(var_name) {
422 // if we have the variable set to some value
423 Ok(Some(var_value)) => {
424 result.push_xstr(var_value.as_ref());
425 input_str = &input_str[closing_brace_idx + 1..];
426 next_dollar_idx = find_dollar(input_str);
427 }
428
429 // if the variable is set and empty or unset
430 not_found_or_empty => {
431 let value = match (not_found_or_empty, default_value) {
432 // return an error if we don't have a default and the variable is unset
433 (Err(err), None) => {
434 return Err(LookupError {
435 var_name: var_name.to_ostring(),
436 cause: err,
437 });
438 }
439 // use the default value if set
440 (_, Some(default)) => default,
441 // leave the variable as it is if the environment is empty
442 (_, None) => &input_str[..closing_brace_idx + 1],
443 };
444
445 result.push_wstr(value);
446 input_str = &input_str[closing_brace_idx + 1..];
447 next_dollar_idx = find_dollar(input_str);
448 }
449 }
450 }
451 // unbalanced braces
452 None => {
453 result.push_wstr(&input_str[..2]);
454 input_str = &input_str[2..];
455 next_dollar_idx = find_dollar(input_str);
456 }
457 }
458 } else if next_char.map(is_valid_var_name_char) == Some(true) {
459 let mut end_idx;
460 loop {
461 // Subtract the bytes length of the remainder from the length, and that's where we are
462 end_idx = input_str.len() - next_chars.wstr_len();
463 match next_chars.next() {
464 Some(c) if is_valid_var_name_char(c) => {},
465 _ => break,
466 }
467 }
468
469 let var_name = &input_str[1..end_idx];
470 match try_lookup!(var_name, lookup(var_name)) {
471 Some(var_value) => {
472 result.push_xstr(var_value.as_ref());
473 input_str = &input_str[end_idx..];
474 next_dollar_idx = find_dollar(input_str);
475 }
476 None => {
477 result.push_wstr(&input_str[..end_idx]);
478 input_str = &input_str[end_idx..];
479 next_dollar_idx = find_dollar(input_str);
480 }
481 }
482 } else {
483 result.push_str("$");
484 input_str = if next_char == Some('$') {
485 &input_str[2..] // skip the next dollar for escaping
486 } else {
487 &input_str[1..]
488 };
489 next_dollar_idx = find_dollar(input_str);
490 };
491 }
492 Ok(result.into_ocow())
493 } else {
494 Ok(input.into_ocow())
495 }
496}
497
498/// Same as [`env_with_context()`], but forbids the variable lookup function to return errors.
499///
500/// This function also performs environment expansion, but it requires context function of type
501/// `FnMut(&str) -> Option<CO>` instead of `FnMut(&str) -> Result<Option<CO>, E>`. This simplifies
502/// the API when you know in advance that the context lookups may not fail.
503///
504/// Because of the above, instead of [`Result<Cow<str>, LookupError<E>>`] this function returns just
505/// [`Cow<str>`].
506///
507/// Note that if the context function returns [`None`], the behavior remains the same as that of
508/// [`env_with_context()`]: the variable reference will remain in the output string unexpanded.
509///
510/// # Examples
511///
512/// ```
513/// fn context(s: &str) -> Option<&'static str> {
514/// match s {
515/// "A" => Some("a value"),
516/// "B" => Some("b value"),
517/// _ => None
518/// }
519/// }
520///
521/// // Known variables are expanded
522/// assert_eq!(
523/// shellexpand::env_with_context_no_errors("begin/$A/${B}s/end", context),
524/// "begin/a value/b values/end"
525/// );
526///
527/// // Unknown variables are left as is
528/// assert_eq!(
529/// shellexpand::env_with_context_no_errors("begin/$U/end", context),
530/// "begin/$U/end"
531/// );
532/// ```
533#[inline]
534pub fn env_with_context_no_errors<SI: ?Sized, CO, C>(input: &SI, mut context: C) -> Cow<Xstr>
535where
536 SI: AsRef<Xstr>,
537 CO: AsRef<Xstr>,
538 C: FnMut(&str) -> Option<CO>,
539{
540 match env_with_context(input, move |s| Ok::<Option<CO>, ()>(context(s))) {
541 Ok(value) => value,
542 Err(_) => unreachable!(),
543 }
544}
545
546/// Performs the environment expansion using the default system context.
547///
548/// This function delegates to [`env_with_context()`], using the default system source for
549/// environment variables, namely the [`std::env::var()`] function.
550///
551/// Note that variable lookup of unknown variables will fail with an error instead of, for example,
552/// replacing the offending variables with an empty string. The author thinks that such behavior is
553/// more useful than the other ones. If you need something else, use [`env_with_context()`] or
554/// [`env_with_context_no_errors()`] with an appropriate context function.
555///
556/// # Examples
557///
558/// ```
559/// use std::env;
560///
561/// // make sure that some environment variables are set
562/// env::set_var("X", "x value");
563/// env::set_var("Y", "y value");
564///
565/// // Known variables are expanded
566/// assert_eq!(
567/// shellexpand::env("begin/$X/${Y}s/end").unwrap(),
568/// "begin/x value/y values/end"
569/// );
570///
571/// // Unknown variables result in an error
572/// assert_eq!(
573/// shellexpand::env("begin/$Z/end"),
574/// Err(shellexpand::LookupError {
575/// var_name: "Z".into(),
576/// cause: env::VarError::NotPresent
577/// })
578/// );
579/// ```
580#[inline]
581pub fn env<SI: ?Sized>(input: &SI) -> Result<Cow<Xstr>, LookupError<VarError>>
582where
583 SI: AsRef<Xstr>,
584{
585 env_with_context(input, |s| std::env::var(s).map(Some))
586}
587
588/// Performs the tilde expansion using the provided context.
589///
590/// This function expands tilde (`~`) character in the beginning of the input string into contents
591/// of the path returned by `home_dir` function. If the input string does not contain a tilde, or
592/// if it is not followed either by a slash (`/`) or by the end of string, then it is also left as
593/// is. This means, in particular, that expansions like `~anotheruser/directory` are not supported.
594/// The context function may also return a `None`, in that case even if the tilde is present in the
595/// input in the correct place, it won't be replaced (there is nothing to replace it with, after
596/// all).
597///
598/// This function has three generic type parameters: `SI` represents the input string, `P` is the
599/// output of a context lookup, and `HD` is the context closure. `SI` must be a type, a reference
600/// to which can be converted to a string slice via [`AsRef<str>`], and `P` must be a type, a
601/// reference to which can be converted to a `str` via [`AsRef<str>`].
602/// Home directories which are available only as a `Path` are not supported here,
603/// because they cannot be represented in the output string.
604/// If you wish to support home directories which are not valid Unicode,
605/// use the [`path`](crate::path) module.
606///
607/// If you need to expand the tilde into the actual user home directory, you can use [`tilde()`] or
608/// [`full()`] functions.
609///
610/// # Examples
611///
612/// ```
613/// use std::path::{PathBuf, Path};
614///
615/// fn home_dir() -> Option<String> { Some("/home/user".into()) }
616///
617/// assert_eq!(
618/// shellexpand::tilde_with_context("~/some/dir", home_dir),
619/// "/home/user/some/dir"
620/// );
621/// ```
622pub fn tilde_with_context<SI: ?Sized, P, HD>(input: &SI, home_dir: HD) -> Cow<Xstr>
623where
624 SI: AsRef<Xstr>,
625 P: AsRef<Xstr>,
626 HD: FnOnce() -> Option<P>,
627{
628 let input_str = input.into_winput();
629 if let Some(input_after_tilde) = input_str.strip_prefix('~') {
630 if input_after_tilde.is_empty()
631 || input_after_tilde.starts_with('/')
632 || (cfg!(windows) && input_after_tilde.starts_with('\\'))
633 {
634 if let Some(hd) = home_dir() {
635 let hd = hd.into_winput();
636 let mut result = OString::with_capacity(hd.len() + input_after_tilde.len());
637 result.push_wstr(hd.as_wstr());
638 result.push_wstr(input_after_tilde);
639 result.into_ocow()
640 } else {
641 // home dir is not available
642 input.into_ocow()
643 }
644 } else {
645 // we cannot handle `~otheruser/` paths yet
646 input.into_ocow()
647 }
648 } else {
649 // input doesn't start with tilde
650 input.into_ocow()
651 }
652}
653
654/// Performs the tilde expansion using the default system context.
655///
656/// This function delegates to [`tilde_with_context()`], using the default system source of home
657/// directory path, namely [`dirs::home_dir()`] function.
658///
659/// # Examples
660///
661/// ```
662/// let hds = dirs::home_dir()
663/// .map(|p| p.display().to_string())
664/// .unwrap_or_else(|| "~".to_owned());
665///
666/// assert_eq!(
667/// shellexpand::tilde("~/some/dir"),
668/// format!("{}/some/dir", hds)
669/// );
670/// ```
671#[cfg(feature = "tilde")]
672#[inline]
673pub fn tilde<SI: ?Sized>(input: &SI) -> Cow<Xstr>
674where
675 SI: AsRef<Xstr>,
676{
677 tilde_with_context(input, home_dir)
678}