Skip to content

Commit 717e751

Browse files
committed
feat: add infer_drive_from_path utility to uffs-mft
Add a reusable utility function to extract drive letters from paths: - Absolute paths: C:\foo\bar.parquet → 'C' - Relative paths: falls back to current directory's drive This is now used by the index command and can be used by other commands that need drive inference from paths. Exported from uffs_mft::infer_drive_from_path (Windows only).
1 parent 1f8d3ae commit 717e751

File tree

4 files changed

+75
-47
lines changed

4 files changed

+75
-47
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/uffs-cli/src/commands.rs

Lines changed: 15 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -629,66 +629,36 @@ 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-
}
671632
672633
/// Build an index from a drive's MFT.
673634
///
674-
/// The drive is inferred from the output path if not explicitly specified.
675-
// CLI command handler - separate function for testability and maintainability.
676-
#[allow(clippy::shadow_unrelated, clippy::single_call_fn)]
635+
/// The drive is inferred from the output path if not explicitly specified:
636+
/// - Absolute path with drive: `C:\foo\bar.parquet` → indexes C:
637+
/// - Relative path: `bar.parquet` → indexes drive of current directory
677638
pub async fn index(
678-
output: PathBuf,
639+
output_path: PathBuf,
679640
single_drive: Option<char>,
680641
multi_drives: Option<Vec<char>>,
681642
) -> Result<()> {
682643
// Ensure output has an extension (default to .parquet)
683-
let output = ensure_extension(output);
644+
let output = if output_path.extension().is_some() {
645+
output_path
646+
} else {
647+
output_path.with_extension("parquet")
648+
};
684649

685650
// Determine which drives to index
686651
let drive_list: Vec<char> = match (single_drive, multi_drives) {
687652
(Some(drv), None) => vec![drv],
688653
(None, Some(drvs)) => drvs,
689654
(None, None) => {
690655
// Infer drive from output path
691-
if let Some(drive) = infer_drive_from_path(&output) {
656+
#[cfg(windows)]
657+
let inferred = uffs_mft::infer_drive_from_path(&output);
658+
#[cfg(not(windows))]
659+
let inferred: Option<char> = None;
660+
661+
if let Some(drive) = inferred {
692662
info!(drive = %drive, "Inferred drive from output path");
693663
vec![drive]
694664
} else {

crates/uffs-mft/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ pub use ntfs::{
122122
#[cfg(windows)]
123123
pub use platform::{
124124
DriveType, MftBitmap, MftExtent, NtfsVolumeData, VolumeHandle, detect_drive_type,
125-
detect_ntfs_drives, is_elevated,
125+
detect_ntfs_drives, infer_drive_from_path, is_elevated,
126126
};
127127
pub use raw::{
128128
LoadRawOptions, RawMftData, RawMftHeader, SaveRawOptions, load_raw_mft, load_raw_mft_header,

crates/uffs-mft/src/platform.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,64 @@ pub fn volume_root_path(volume: char) -> PathBuf {
10861086
PathBuf::from(format!("{}:\\", volume.to_ascii_uppercase()))
10871087
}
10881088

1089+
/// Infer drive letter from a file path.
1090+
///
1091+
/// Extracts the drive letter from absolute paths (e.g., `C:\foo\bar.parquet` →
1092+
/// `'C'`). For relative paths, falls back to the current working directory's
1093+
/// drive.
1094+
///
1095+
/// # Arguments
1096+
///
1097+
/// * `path` - Any file path (absolute or relative)
1098+
///
1099+
/// # Returns
1100+
///
1101+
/// - `Some(char)` - Uppercase drive letter if found
1102+
/// - `None` - If path has no drive prefix and current directory cannot be
1103+
/// determined
1104+
///
1105+
/// # Example
1106+
///
1107+
/// ```rust,ignore
1108+
/// use std::path::Path;
1109+
/// use uffs_mft::infer_drive_from_path;
1110+
///
1111+
/// // Absolute path
1112+
/// assert_eq!(infer_drive_from_path(Path::new("C:\\data\\index.parquet")), Some('C'));
1113+
///
1114+
/// // Relative path (uses current directory's drive)
1115+
/// // If cwd is D:\work, returns Some('D')
1116+
/// let drive = infer_drive_from_path(Path::new("index.parquet"));
1117+
/// ```
1118+
#[must_use]
1119+
pub fn infer_drive_from_path(path: &Path) -> Option<char> {
1120+
use std::path::{Component, Prefix};
1121+
1122+
// Try to get drive from the path itself (absolute paths like C:\foo)
1123+
if let Some(Component::Prefix(prefix)) = path.components().next() {
1124+
match prefix.kind() {
1125+
Prefix::Disk(drive_byte) | Prefix::VerbatimDisk(drive_byte) => {
1126+
return Some((drive_byte as char).to_ascii_uppercase());
1127+
}
1128+
_ => {} // UNC paths, device paths, etc. - no drive letter
1129+
}
1130+
}
1131+
1132+
// For relative paths, get drive from current working directory
1133+
std::env::current_dir().ok().and_then(|cwd| {
1134+
if let Some(Component::Prefix(prefix)) = cwd.components().next() {
1135+
match prefix.kind() {
1136+
Prefix::Disk(drive_byte) | Prefix::VerbatimDisk(drive_byte) => {
1137+
Some((drive_byte as char).to_ascii_uppercase())
1138+
}
1139+
_ => None,
1140+
}
1141+
} else {
1142+
None
1143+
}
1144+
})
1145+
}
1146+
10891147
/// Detects all available NTFS drives on the system.
10901148
///
10911149
/// This function iterates through all possible drive letters (A-Z) and

0 commit comments

Comments
 (0)