Skip to content

Commit 0508e84

Browse files
committed
Add --output-dir option to moss index
1 parent a53e4c8 commit 0508e84

File tree

1 file changed

+90
-8
lines changed

1 file changed

+90
-8
lines changed

moss/src/cli/index.rs

Lines changed: 90 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::{
88
time::Duration,
99
};
1010

11-
use camino::Utf8Path;
11+
use camino::{Utf8Path, Utf8PathBuf};
1212
use clap::{ArgMatches, Command, arg, value_parser};
1313
use fs_err as fs;
1414
use moss::{
@@ -25,10 +25,18 @@ pub fn command() -> Command {
2525
.visible_alias("ix")
2626
.about("Index a collection of packages")
2727
.arg(arg!(<INDEX_DIR> "directory of index files").value_parser(value_parser!(PathBuf)))
28+
.arg(
29+
arg!(-o --"output-dir" [output_dir] "directory to write the stone.index to (defaults to INDEX_DIR)")
30+
.value_parser(value_parser!(PathBuf)),
31+
)
2832
}
2933

3034
pub fn handle(args: &ArgMatches) -> Result<(), Error> {
3135
let index_dir = args.get_one::<PathBuf>("INDEX_DIR").unwrap().canonicalize()?;
36+
let output_dir = match args.get_one::<PathBuf>("output-dir") {
37+
Some(dir) => &dir.canonicalize()?,
38+
None => &index_dir,
39+
};
3240

3341
let stone_files = enumerate_stone_files(&index_dir)?;
3442

@@ -46,7 +54,7 @@ pub fn handle(args: &ArgMatches) -> Result<(), Error> {
4654
total_progress.tick();
4755

4856
let ctx = GetMetaCtx {
49-
index_dir: &index_dir,
57+
output_dir,
5058
multi_progress: &multi_progress,
5159
total_progress: &total_progress,
5260
};
@@ -81,11 +89,11 @@ pub fn handle(args: &ArgMatches) -> Result<(), Error> {
8189
}
8290
}
8391

84-
write_index(&index_dir, map, &total_progress)?;
92+
write_index(output_dir, map, &total_progress)?;
8593

8694
multi_progress.clear()?;
8795

88-
println!("\nIndex file written to {:?}", index_dir.join("stone.index").display());
96+
println!("\nIndex file written to {:?}", output_dir.join("stone.index").display());
8997

9098
Ok(())
9199
}
@@ -118,14 +126,13 @@ fn write_index(dir: &Path, map: BTreeMap<package::Name, Meta>, total_progress: &
118126

119127
#[derive(Clone, Copy)]
120128
struct GetMetaCtx<'a> {
121-
index_dir: &'a Path,
129+
output_dir: &'a Path,
122130
multi_progress: &'a MultiProgress,
123131
total_progress: &'a ProgressBar,
124132
}
125133

126134
fn get_meta(path: &Path, ctx: GetMetaCtx<'_>) -> Result<Meta, Error> {
127-
let relative_path: &Utf8Path = path
128-
.strip_prefix(ctx.index_dir)?
135+
let relative_path: Utf8PathBuf = rel_path_from_to(ctx.output_dir, path)
129136
.try_into()
130137
.map_err(|_| Error::NonUtf8Path { path: path.to_owned() })?;
131138

@@ -134,7 +141,7 @@ fn get_meta(path: &Path, ctx: GetMetaCtx<'_>) -> Result<Meta, Error> {
134141
.insert_before(ctx.total_progress, ProgressBar::new_spinner());
135142
progress.enable_steady_tick(Duration::from_millis(150));
136143

137-
let (size, hash) = stat_file(path, relative_path, &progress)?;
144+
let (size, hash) = stat_file(path, &relative_path, &progress)?;
138145

139146
progress.set_message(format!("{} {}", "Indexing".yellow(), relative_path.as_str().bold()));
140147
progress.set_style(
@@ -239,3 +246,78 @@ pub enum Error {
239246
#[error("non-utf8 path: {path}")]
240247
NonUtf8Path { path: PathBuf },
241248
}
249+
250+
/// Make a relative path that points to `to` if the current working directory is `from_dir`.
251+
///
252+
/// Inputs must start with `/` (be absolute) and not contain `.` or `..` segments.
253+
fn rel_path_from_to(from_dir: &Path, to: &Path) -> PathBuf {
254+
assert!(from_dir.is_absolute());
255+
assert!(to.is_absolute());
256+
257+
if from_dir == to {
258+
return ".".into();
259+
}
260+
261+
let mut from_dir = from_dir.to_owned();
262+
let mut result = PathBuf::new();
263+
loop {
264+
if let Ok(suffix) = to.strip_prefix(&from_dir) {
265+
result.push(suffix);
266+
return result;
267+
}
268+
269+
let popped = from_dir.pop();
270+
assert!(popped, "strip_prefix must succeed when reaching the root");
271+
272+
result.push("..");
273+
}
274+
}
275+
276+
#[cfg(test)]
277+
mod tests {
278+
use std::path::Path;
279+
280+
use super::rel_path_from_to;
281+
282+
#[test]
283+
fn test_rel_path_from_to_strips_prefix() {
284+
assert_eq!(rel_path_from_to(Path::new("/"), Path::new("/root")), Path::new("root"));
285+
assert_eq!(rel_path_from_to(Path::new("/x"), Path::new("/x/y/z")), Path::new("y/z"));
286+
}
287+
288+
#[test]
289+
fn test_rel_path_from_to_works_for_identical_inputs() {
290+
assert_eq!(rel_path_from_to(Path::new("/"), Path::new("/")), Path::new("."));
291+
assert_eq!(rel_path_from_to(Path::new("/a"), Path::new("/a")), Path::new("."));
292+
assert_eq!(
293+
rel_path_from_to(Path::new("/hello/world"), Path::new("/hello/world")),
294+
Path::new(".")
295+
);
296+
}
297+
298+
#[test]
299+
fn test_rel_path_from_to_works_for_almost_identical_inputs() {
300+
assert_eq!(rel_path_from_to(Path::new("/a/"), Path::new("/a")), Path::new("."));
301+
}
302+
303+
#[test]
304+
fn test_rel_path_from_to_goes_up_one_level() {
305+
assert_eq!(
306+
rel_path_from_to(Path::new("/a/b"), Path::new("/a/x")),
307+
Path::new("../x")
308+
);
309+
}
310+
311+
#[test]
312+
fn test_rel_path_from_to_goes_up_two_levels() {
313+
assert_eq!(
314+
rel_path_from_to(Path::new("/a/b/c"), Path::new("/a/x")),
315+
Path::new("../../x")
316+
);
317+
}
318+
319+
#[test]
320+
fn test_rel_path_from_to_goes_up_to_root() {
321+
assert_eq!(rel_path_from_to(Path::new("/a"), Path::new("/b")), Path::new("../b"));
322+
}
323+
}

0 commit comments

Comments
 (0)