Skip to content

Commit e036b54

Browse files
committed
feat: simplify index command with drive inference
- Make output a positional arg: uffs index output.parquet - Infer drive from output path (c:\tmp\x.parquet → C:) - Infer drive from current directory for relative paths - Default extension to .parquet if not provided - Keep --drive and --drives as optional overrides Examples: uffs index index.parquet # Index current drive uffs index c:\data\files.parquet # Index C: drive uffs index myindex # Creates myindex.parquet uffs index -d D index.parquet # Override: index D: drive
1 parent ec5ce22 commit e036b54

File tree

2 files changed

+81
-15
lines changed

2 files changed

+81
-15
lines changed

crates/uffs-cli/src/commands.rs

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
77
use std::fs::File;
88
use std::io::{BufWriter, Write};
9-
use std::path::Path;
9+
use std::path::{Path, PathBuf};
1010

1111
use anyhow::{Context, Result, bail};
1212
#[cfg(windows)]
@@ -629,19 +629,74 @@ async fn search_multi_drive_filtered(
629629
/// Supports both single drive (`--drive C`) and multiple drives (`--drives
630630
/// C,D,E`). When multiple drives are specified, they are read concurrently and
631631
/// merged into a single `DataFrame` with a `drive` column.
632+
/// Infer drive letter from a path.
633+
///
634+
/// - Absolute path with drive: `C:\foo\bar.parquet` → Some('C')
635+
/// - Relative path: `bar.parquet` → infer from current directory
636+
/// - UNC path or no drive: → None
637+
#[cfg(windows)]
638+
fn infer_drive_from_path(path: &Path) -> Option<char> {
639+
use std::path::Component;
640+
641+
// Check if path has a drive prefix (e.g., C:)
642+
if let Some(Component::Prefix(prefix)) = path.components().next() {
643+
use std::path::Prefix;
644+
if let Prefix::Disk(drive_byte) | Prefix::VerbatimDisk(drive_byte) = prefix.kind() {
645+
return Some((drive_byte as char).to_ascii_uppercase());
646+
}
647+
}
648+
649+
// For relative paths, get drive from current directory
650+
if let Ok(cwd) = std::env::current_dir() {
651+
return infer_drive_from_path(&cwd);
652+
}
653+
654+
None
655+
}
656+
657+
/// Stub for non-Windows platforms.
658+
#[cfg(not(windows))]
659+
const fn infer_drive_from_path(_path: &Path) -> Option<char> {
660+
None
661+
}
662+
663+
/// Ensure path has an extension, defaulting to `.parquet`.
664+
fn ensure_extension(path: PathBuf) -> PathBuf {
665+
if path.extension().is_some() {
666+
path
667+
} else {
668+
path.with_extension("parquet")
669+
}
670+
}
671+
672+
/// Build an index from a drive's MFT.
673+
///
674+
/// The drive is inferred from the output path if not explicitly specified.
632675
// CLI command handler - separate function for testability and maintainability.
633676
#[allow(clippy::shadow_unrelated, clippy::single_call_fn)]
634677
pub async fn index(
678+
output: PathBuf,
635679
single_drive: Option<char>,
636680
multi_drives: Option<Vec<char>>,
637-
output: &Path,
638681
) -> Result<()> {
682+
// Ensure output has an extension (default to .parquet)
683+
let output = ensure_extension(output);
684+
639685
// Determine which drives to index
640686
let drive_list: Vec<char> = match (single_drive, multi_drives) {
641687
(Some(drv), None) => vec![drv],
642688
(None, Some(drvs)) => drvs,
643689
(None, None) => {
644-
anyhow::bail!("Either --drive or --drives must be specified");
690+
// Infer drive from output path
691+
if let Some(drive) = infer_drive_from_path(&output) {
692+
info!(drive = %drive, "Inferred drive from output path");
693+
vec![drive]
694+
} else {
695+
anyhow::bail!(
696+
"Could not infer drive from path '{}'. Use --drive or --drives to specify.",
697+
output.display()
698+
);
699+
}
645700
}
646701
(Some(_), Some(_)) => {
647702
// This shouldn't happen due to clap's conflicts_with, but handle it anyway
@@ -697,7 +752,7 @@ pub async fn index(
697752

698753
info!(records = df.height(), "Read records");
699754

700-
MftReader::save_parquet(&mut df, output)
755+
MftReader::save_parquet(&mut df, &output)
701756
.with_context(|| format!("Failed to save index to {}", output.display()))?;
702757

703758
info!(path = %output.display(), "Index saved");
@@ -706,7 +761,7 @@ pub async fn index(
706761
}
707762

708763
// Multiple drives: use MultiDriveMftReader
709-
index_multi_drive(&drive_list, output).await
764+
index_multi_drive(&drive_list, &output).await
710765
}
711766

712767
/// Index multiple drives concurrently.

crates/uffs-cli/src/main.rs

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -284,20 +284,31 @@ enum Commands {
284284
},
285285

286286
/// Build an index from a drive's MFT
287+
///
288+
/// The drive to index is inferred from the output path:
289+
/// - `c:\tmp\index.parquet` → indexes drive C:
290+
/// - `index.parquet` → indexes the drive of the current directory
291+
///
292+
/// Use --drive or --drives to override the inferred drive.
293+
///
294+
/// If no extension is provided, defaults to `.parquet`.
295+
///
296+
/// Examples:
297+
/// uffs index index.parquet # Index current drive
298+
/// uffs index c:\data\files.parquet # Index C: drive
299+
/// uffs index myindex # Creates myindex.parquet
300+
/// uffs index -d D index.parquet # Override: index D: drive
287301
Index {
288-
/// Drive letter to index (e.g., C or C:). Use --drives for multiple
289-
/// drives.
302+
/// Output file path (extension defaults to .parquet)
303+
output: PathBuf,
304+
305+
/// Drive letter to index (overrides path inference)
290306
#[arg(short, long, conflicts_with = "drives", value_parser = parse_drive_letter)]
291307
drive: Option<char>,
292308

293-
/// Multiple drive letters to index concurrently (e.g., C,D,E or
294-
/// C:,D:,E:)
309+
/// Multiple drive letters to index concurrently (e.g., C,D,E)
295310
#[arg(long, value_delimiter = ',', conflicts_with = "drive", value_parser = parse_drive_letter)]
296311
drives: Option<Vec<char>>,
297-
298-
/// Output file path
299-
#[arg(short, long)]
300-
output: PathBuf,
301312
},
302313

303314
/// Show information about an index file
@@ -469,11 +480,11 @@ async fn run() -> Result<()> {
469480
.await?;
470481
}
471482
Some(Commands::Index {
483+
output,
472484
drive,
473485
drives,
474-
output,
475486
}) => {
476-
commands::index(drive, drives, &output).await?;
487+
commands::index(output, drive, drives).await?;
477488
}
478489
Some(Commands::Info { path }) => {
479490
commands::info(&path)?;

0 commit comments

Comments
 (0)