Skip to content

Commit aea7818

Browse files
committed
feat: Added a map api for pathCompleter and its test
1 parent 4dd47a6 commit aea7818

File tree

2 files changed

+130
-0
lines changed

2 files changed

+130
-0
lines changed

clap_complete/src/engine/custom.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,8 @@ pub struct PathCompleter {
216216
#[allow(clippy::type_complexity)]
217217
filter: Option<Box<dyn Fn(&std::path::Path) -> bool + Send + Sync>>,
218218
stdio: bool,
219+
#[allow(clippy::type_complexity)]
220+
mapper: Option<Box<dyn Fn(CompletionCandidate) -> CompletionCandidate + Send + Sync>>,
219221
}
220222

221223
impl PathCompleter {
@@ -225,6 +227,7 @@ impl PathCompleter {
225227
filter: None,
226228
current_dir: None,
227229
stdio: false,
230+
mapper: None,
228231
}
229232
}
230233

@@ -253,6 +256,15 @@ impl PathCompleter {
253256
self
254257
}
255258

259+
/// Transform completion candidates after filtering
260+
pub fn map(
261+
mut self,
262+
mapper: impl Fn(CompletionCandidate) -> CompletionCandidate + Send + Sync + 'static,
263+
) -> Self {
264+
self.mapper = Some(Box::new(mapper));
265+
self
266+
}
267+
256268
/// Override [`std::env::current_dir`]
257269
pub fn current_dir(mut self, path: impl Into<std::path::PathBuf>) -> Self {
258270
self.current_dir = Some(path.into());
@@ -275,6 +287,14 @@ impl ValueCompleter for PathCompleter {
275287
current_dir_actual.as_deref()
276288
});
277289
let mut candidates = complete_path(current, current_dir, filter);
290+
291+
if let Some(mapper) = &self.mapper {
292+
candidates = candidates
293+
.into_iter()
294+
.map(|candidate| mapper(candidate))
295+
.collect();
296+
}
297+
278298
if self.stdio && current.is_empty() {
279299
candidates.push(CompletionCandidate::new("-").help(Some("stdio".into())));
280300
}

clap_complete/tests/testsuite/engine.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1228,3 +1228,113 @@ d_dir/
12281228
"#]],
12291229
);
12301230
}
1231+
1232+
#[test]
1233+
fn suggest_value_path_with_filter_and_mapper() {
1234+
let testdir = snapbox::dir::DirRoot::mutable_temp().unwrap();
1235+
let testdir_path = testdir.path().unwrap();
1236+
fs::write(testdir_path.join("a_file"), "").unwrap();
1237+
fs::write(testdir_path.join("b_file"), "").unwrap();
1238+
fs::create_dir_all(testdir_path.join("c_dir")).unwrap();
1239+
fs::create_dir_all(testdir_path.join("d_dir")).unwrap();
1240+
1241+
fs::write(testdir_path.join("c_dir").join("Cargo.toml"), "").unwrap();
1242+
1243+
let mut cmd = Command::new("dynamic")
1244+
.arg(
1245+
clap::Arg::new("input")
1246+
.long("input")
1247+
.short('i')
1248+
.add(ArgValueCompleter::new(
1249+
PathCompleter::dir()
1250+
.filter(|path| {
1251+
path.file_name()
1252+
.and_then(|n| n.to_str())
1253+
.map_or(false, |name| name.starts_with('c'))
1254+
})
1255+
.map(|candidate| {
1256+
let path = Path::new(candidate.get_value());
1257+
let cargo_toml_path = path.join("Cargo.toml");
1258+
let has_cargo_toml = cargo_toml_path.exists();
1259+
1260+
if has_cargo_toml {
1261+
candidate
1262+
.help(Some("Addable cargo package".into()))
1263+
.display_order(Some(0))
1264+
} else {
1265+
candidate.display_order(Some(1))
1266+
}
1267+
}),
1268+
)),
1269+
)
1270+
.args_conflicts_with_subcommands(true);
1271+
1272+
let original_dir = std::env::current_dir().unwrap();
1273+
std::env::set_current_dir(testdir_path).unwrap();
1274+
1275+
let result = complete!(cmd, "--input [TAB]", current_dir = None);
1276+
1277+
std::env::set_current_dir(original_dir).unwrap();
1278+
1279+
assert_data_eq!(
1280+
result,
1281+
snapbox::str![[r#"
1282+
c_dir/ Addable cargo package
1283+
d_dir/
1284+
"#]],
1285+
);
1286+
}
1287+
1288+
#[test]
1289+
fn suggest_value_file_path_with_mapper() {
1290+
let testdir = snapbox::dir::DirRoot::mutable_temp().unwrap();
1291+
let testdir_path = testdir.path().unwrap();
1292+
fs::write(testdir_path.join("a_file.txt"), "").unwrap();
1293+
fs::write(testdir_path.join("b_file.md"), "").unwrap();
1294+
fs::write(testdir_path.join("c_file.rs"), "").unwrap();
1295+
fs::create_dir_all(testdir_path.join("d_dir")).unwrap();
1296+
1297+
let mut cmd = Command::new("dynamic")
1298+
.arg(
1299+
clap::Arg::new("input")
1300+
.long("input")
1301+
.short('i')
1302+
.add(ArgValueCompleter::new(
1303+
PathCompleter::file()
1304+
.map(|candidate| {
1305+
let path = Path::new(candidate.get_value());
1306+
if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
1307+
match ext {
1308+
"rs" => candidate
1309+
.help(Some("Rust source file".into()))
1310+
.display_order(Some(0)),
1311+
"txt" => candidate
1312+
.help(Some("Text file".into()))
1313+
.display_order(Some(1)),
1314+
_ => candidate.display_order(Some(2))
1315+
}
1316+
} else {
1317+
candidate.display_order(Some(3))
1318+
}
1319+
}),
1320+
)),
1321+
)
1322+
.args_conflicts_with_subcommands(true);
1323+
1324+
let original_dir = std::env::current_dir().unwrap();
1325+
std::env::set_current_dir(testdir_path).unwrap();
1326+
1327+
let result = complete!(cmd, "--input [TAB]", current_dir = None);
1328+
1329+
std::env::set_current_dir(original_dir).unwrap();
1330+
1331+
assert_data_eq!(
1332+
result,
1333+
snapbox::str![[r#"
1334+
c_file.rs Rust source file
1335+
a_file.txt Text file
1336+
b_file.md
1337+
d_dir/
1338+
"#]],
1339+
);
1340+
}

0 commit comments

Comments
 (0)