Skip to content

Commit d74d2e5

Browse files
authored
Merge pull request #8447 from yuankunzhang/allow-empty-string-arguments-in-mktemp
mktemp: allow empty string arguments for -p/--tmpdir options
2 parents 51600a2 + de77fc9 commit d74d2e5

File tree

2 files changed

+79
-6
lines changed

2 files changed

+79
-6
lines changed

src/uu/mktemp/src/mktemp.rs

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55

66
// spell-checker:ignore (paths) GPGHome findxs
77

8-
use clap::{Arg, ArgAction, ArgMatches, Command, builder::ValueParser};
8+
use clap::builder::{TypedValueParser, ValueParserFactory};
9+
use clap::{Arg, ArgAction, ArgMatches, Command};
910
use uucore::display::{Quotable, println_verbatim};
1011
use uucore::error::{FromIo, UError, UResult, UUsageError};
1112
use uucore::format_usage;
@@ -110,9 +111,18 @@ pub struct Options {
110111
impl Options {
111112
fn from(matches: &ArgMatches) -> Self {
112113
let tmpdir = matches
113-
.get_one::<PathBuf>(OPT_TMPDIR)
114-
.or_else(|| matches.get_one::<PathBuf>(OPT_P))
115-
.cloned();
114+
.get_one::<Option<PathBuf>>(OPT_TMPDIR)
115+
.or_else(|| matches.get_one::<Option<PathBuf>>(OPT_P))
116+
.map(|dir| match dir {
117+
// If the argument of -p/--tmpdir is non-empty, use it as the
118+
// tmpdir.
119+
Some(d) => d.clone(),
120+
// Otherwise use $TMPDIR if set, else use the system's default
121+
// temporary directory.
122+
None => env::var(TMPDIR_ENV_VAR)
123+
.ok()
124+
.map_or_else(env::temp_dir, PathBuf::from),
125+
});
116126
let (tmpdir, template) = match matches.get_one::<String>(ARG_TEMPLATE) {
117127
// If no template argument is given, `--tmpdir` is implied.
118128
None => {
@@ -274,6 +284,49 @@ impl Params {
274284
}
275285
}
276286

287+
/// Custom parser that converts empty string to `None`, and non-empty string to
288+
/// `Some(PathBuf)`.
289+
///
290+
/// This parser is used for the `-p` and `--tmpdir` options where an empty string
291+
/// argument should be treated as "not provided", causing mktemp to fall back to
292+
/// using the `$TMPDIR` environment variable or the system's default temporary
293+
/// directory.
294+
///
295+
/// # Examples
296+
///
297+
/// - Empty string `""` -> `None`
298+
/// - Non-empty string `"/tmp"` -> `Some(PathBuf::from("/tmp"))`
299+
///
300+
/// This handles the special case where users can pass an empty directory name
301+
/// to explicitly request fallback behavior.
302+
#[derive(Clone, Debug)]
303+
struct OptionalPathBufParser;
304+
305+
impl TypedValueParser for OptionalPathBufParser {
306+
type Value = Option<PathBuf>;
307+
308+
fn parse_ref(
309+
&self,
310+
_cmd: &Command,
311+
_arg: Option<&Arg>,
312+
value: &OsStr,
313+
) -> Result<Self::Value, clap::Error> {
314+
if value.is_empty() {
315+
Ok(None)
316+
} else {
317+
Ok(Some(PathBuf::from(value)))
318+
}
319+
}
320+
}
321+
322+
impl ValueParserFactory for OptionalPathBufParser {
323+
type Parser = Self;
324+
325+
fn value_parser() -> Self::Parser {
326+
Self
327+
}
328+
}
329+
277330
#[uucore::main]
278331
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
279332
let args: Vec<_> = args.collect();
@@ -376,7 +429,7 @@ pub fn uu_app() -> Command {
376429
.help(translate!("mktemp-help-p"))
377430
.value_name("DIR")
378431
.num_args(1)
379-
.value_parser(ValueParser::path_buf())
432+
.value_parser(OptionalPathBufParser)
380433
.value_hint(clap::ValueHint::DirPath),
381434
)
382435
.arg(
@@ -390,7 +443,7 @@ pub fn uu_app() -> Command {
390443
// Require an equals to avoid ambiguity if no tmpdir is supplied
391444
.require_equals(true)
392445
.overrides_with(OPT_P)
393-
.value_parser(ValueParser::path_buf())
446+
.value_parser(OptionalPathBufParser)
394447
.value_hint(clap::ValueHint::DirPath),
395448
)
396449
.arg(

tests/by-util/test_mktemp.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,26 @@ fn test_mktemp_tmpdir() {
426426
.fails();
427427
}
428428

429+
#[test]
430+
fn test_mktemp_empty_tmpdir() {
431+
let scene = TestScenario::new(util_name!());
432+
let pathname = scene.fixtures.as_string();
433+
434+
let result = scene
435+
.ucmd()
436+
.env(TMPDIR, &pathname)
437+
.args(&["-p", ""])
438+
.succeeds();
439+
assert!(result.stdout_str().trim().starts_with(&pathname));
440+
441+
let result = scene
442+
.ucmd()
443+
.env(TMPDIR, &pathname)
444+
.arg("--tmpdir=")
445+
.succeeds();
446+
assert!(result.stdout_str().trim().starts_with(&pathname));
447+
}
448+
429449
#[test]
430450
fn test_mktemp_tmpdir_one_arg() {
431451
let scene = TestScenario::new(util_name!());

0 commit comments

Comments
 (0)