@@ -8,7 +8,7 @@ use std::{
88 time:: Duration ,
99} ;
1010
11- use camino:: Utf8Path ;
11+ use camino:: { Utf8Path , Utf8PathBuf } ;
1212use clap:: { ArgMatches , Command , arg, value_parser} ;
1313use fs_err as fs;
1414use 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
3034pub 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 ! ( "\n Index file written to {:?}" , index_dir . join( "stone.index" ) . display( ) ) ;
96+ println ! ( "\n Index 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 ) ]
120128struct GetMetaCtx < ' a > {
121- index_dir : & ' a Path ,
129+ output_dir : & ' a Path ,
122130 multi_progress : & ' a MultiProgress ,
123131 total_progress : & ' a ProgressBar ,
124132}
125133
126134fn 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