Skip to content

Commit

Permalink
Optionally follow symlinks when traversing inputs
Browse files Browse the repository at this point in the history
Resolves #834
  • Loading branch information
Xophmeister committed Jan 24, 2025
1 parent 6aee2db commit 51d2f54
Showing 1 changed file with 38 additions and 10 deletions.
48 changes: 38 additions & 10 deletions topiary-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ pub struct GlobalArgs {
// NOTE This abstraction is largely to workaround clap-rs/clap#4707
#[derive(Args, Debug)]
pub struct FromStdin {
/// Topiary language identifier (for formatting stdin)
/// Topiary language identifier (when formatting stdin)
#[arg(short, long)]
pub language: String,

Expand Down Expand Up @@ -109,6 +109,10 @@ pub struct AtLeastOneInput {
/// Language detection and query selection is automatic, mapped from file extensions defined in
/// the Topiary configuration.
pub files: Vec<PathBuf>,

/// Follow symlinks (when formatting files)
#[arg(short = 'L', long)]
pub follow_symlinks: bool,
}

// NOTE When changing the subcommands, please update verify-documented-usage.sh respectively.
Expand Down Expand Up @@ -172,16 +176,34 @@ pub enum Commands {
}

/// Given a vector of paths, recursively expand those that identify as directories, in place
fn traverse_fs(files: &mut Vec<PathBuf>) -> CLIResult<()> {
fn traverse_fs(files: &mut Vec<PathBuf>, follow_symlinks: bool) -> CLIResult<()> {
let mut expanded = vec![];

for file in &mut *files {
if file.is_dir() {
let is_dir = if follow_symlinks {
file.is_dir()
} else {
file.is_dir() && !file.is_symlink()
};

if is_dir {
// Descend into directory, symlink-aware as required
let mut subfiles = file.read_dir()?.flatten().map(|f| f.path()).collect();
traverse_fs(&mut subfiles)?;
traverse_fs(&mut subfiles, follow_symlinks)?;
expanded.append(&mut subfiles);
} else {
expanded.push(file.to_path_buf());
if file.is_symlink() && !follow_symlinks {
log::debug!(
"{} is a symlink, which we're not following",
file.to_string_lossy()
);
continue;
}

// Only push the file if the canonicalisation succeeds (i.e., skip broken symlinks)
if let Ok(candidate) = file.canonicalize() {
expanded.push(candidate.to_path_buf());
}
}
}

Expand Down Expand Up @@ -217,17 +239,23 @@ pub fn get_args() -> CLIResult<Cli> {

match &mut args.command {
Commands::Format {
inputs: AtLeastOneInput { files, .. },
inputs:
AtLeastOneInput {
files,
follow_symlinks,
..
},
..
} => {
// If we're given a list of FILES... then we assume them to all be on disk, even if "-"
// is passed as an argument (i.e., interpret this as a valid filename, rather than as
// stdin). We deduplicate this list to avoid formatting the same file multiple times
// and recursively expand directories until we're left with a list of unique
// (potential) files as input sources.
// stdin). We recursively expand directories until we're left with a list of
// (potential) files, as input sources. This is finally deduplicated to avoid
// formatting the same file multiple times (e.g., in the case that a symlink points to
// a file within the set, or if the same file is specified twice at the command line).
traverse_fs(files, *follow_symlinks)?;
files.sort_unstable();
files.dedup();
traverse_fs(files)?;
}

Commands::Visualise {
Expand Down

0 comments on commit 51d2f54

Please sign in to comment.