Skip to content

Commit

Permalink
Enhance --dump-inputs to support excluded paths and add tests for v…
Browse files Browse the repository at this point in the history
…arious scenarios
  • Loading branch information
mre committed Nov 7, 2024
1 parent 6b53695 commit 3e11c88
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 10 deletions.
129 changes: 120 additions & 9 deletions lychee-bin/src/commands/dump.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,17 @@ where
// Apply URI remappings (if any)
params.client.remap(&mut request.uri)?;

// Avoid panic on broken pipe.
// See https://github.com/rust-lang/rust/issues/46016
// This can occur when piping the output of lychee
// to another program like `grep`.

let excluded = params.client.is_excluded(&request.uri);

if excluded && params.cfg.verbose.log_level() < log::Level::Info {
continue;
}

if let Err(e) = write(&mut writer, &request, &params.cfg.verbose, excluded) {
// Avoid panic on broken pipe.
// See https://github.com/rust-lang/rust/issues/46016
// This can occur when piping the output of lychee
// to another program like `grep`.
if e.kind() != io::ErrorKind::BrokenPipe {
error!("{e}");
return Ok(ExitCode::UnexpectedFailure);
Expand All @@ -72,22 +72,31 @@ where

/// Dump all input sources to stdout without extracting any links and checking
/// them.
pub(crate) async fn dump_inputs<S>(sources: S, output: Option<&PathBuf>) -> Result<ExitCode>
pub(crate) async fn dump_inputs<S>(
sources: S,
output: Option<&PathBuf>,
excluded_paths: &[PathBuf],
) -> Result<ExitCode>
where
S: futures::Stream<Item = Result<String>>,
{
let sources = sources;
tokio::pin!(sources);

if let Some(out_file) = output {
fs::File::create(out_file)?;
}

let mut writer = create_writer(output.cloned())?;

tokio::pin!(sources);
while let Some(source) = sources.next().await {
let source = source?;

let excluded = excluded_paths
.iter()
.any(|path| source.starts_with(path.to_string_lossy().as_ref()));
if excluded {
continue;
}

writeln!(writer, "{source}")?;
}

Expand Down Expand Up @@ -127,3 +136,105 @@ fn write(
fn write_out(writer: &mut Box<dyn Write>, out_str: &str) -> io::Result<()> {
writeln!(writer, "{out_str}")
}

#[cfg(test)]
mod tests {
use super::*;
use futures::stream;
use std::io::Read;
use tempfile::NamedTempFile;

/// Helper function to read entire contents of a file
fn read_file(path: &PathBuf) -> Result<String> {
let mut contents = String::new();
fs::File::open(path)?.read_to_string(&mut contents)?;
Ok(contents)
}

#[tokio::test]
async fn test_dump_inputs_basic() -> Result<()> {
// Create temp file for output
let temp_file = NamedTempFile::new()?;
let output_path = temp_file.path().to_path_buf();

// Create test input stream
let inputs = vec![
Ok(String::from("test/path1")),
Ok(String::from("test/path2")),
Ok(String::from("test/path3")),
];
let stream = stream::iter(inputs);

// Run dump_inputs
let result = dump_inputs(stream, Some(&output_path), &[]).await?;
assert_eq!(result, ExitCode::Success);

// Verify output
let contents = read_file(&output_path)?;
assert_eq!(contents, "test/path1\ntest/path2\ntest/path3\n");
Ok(())
}

#[tokio::test]
async fn test_dump_inputs_with_excluded_paths() -> Result<()> {
let temp_file = NamedTempFile::new()?;
let output_path = temp_file.path().to_path_buf();

let inputs = vec![
Ok(String::from("test/path1")),
Ok(String::from("excluded/path")),
Ok(String::from("test/path2")),
];
let stream = stream::iter(inputs);

let excluded = vec![PathBuf::from("excluded")];
let result = dump_inputs(stream, Some(&output_path), &excluded).await?;
assert_eq!(result, ExitCode::Success);

let contents = read_file(&output_path)?;
assert_eq!(contents, "test/path1\ntest/path2\n");
Ok(())
}

#[tokio::test]
async fn test_dump_inputs_empty_stream() -> Result<()> {
let temp_file = NamedTempFile::new()?;
let output_path = temp_file.path().to_path_buf();

let stream = stream::iter::<Vec<Result<String>>>(vec![]);
let result = dump_inputs(stream, Some(&output_path), &[]).await?;
assert_eq!(result, ExitCode::Success);

let contents = read_file(&output_path)?;
assert_eq!(contents, "");
Ok(())
}

#[tokio::test]
async fn test_dump_inputs_error_in_stream() -> Result<()> {
let temp_file = NamedTempFile::new()?;
let output_path = temp_file.path().to_path_buf();

let inputs: Vec<Result<String>> = vec![
Ok(String::from("test/path1")),
Err(io::Error::new(io::ErrorKind::Other, "test error").into()),
Ok(String::from("test/path2")),
];
let stream = stream::iter(inputs);

let result = dump_inputs(stream, Some(&output_path), &[]).await;
assert!(result.is_err());
Ok(())
}

#[tokio::test]
async fn test_dump_inputs_to_stdout() -> Result<()> {
// When output path is None, should write to stdout
let inputs = vec![Ok(String::from("test/path1"))];
let stream = stream::iter(inputs);

let result = dump_inputs(stream, None, &[]).await?;
assert_eq!(result, ExitCode::Success);
Ok(())
}
}
8 changes: 7 additions & 1 deletion lychee-bin/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ use crate::{
};

/// A C-like enum that can be cast to `i32` and used as process exit code.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ExitCode {
Success = 0,
// NOTE: exit code 1 is used for any `Result::Err` bubbled up to `main()`
Expand Down Expand Up @@ -297,7 +298,12 @@ async fn run(opts: &LycheeOptions) -> Result<i32> {

if opts.config.dump_inputs {
let sources = collector.collect_sources(inputs);
let exit_code = commands::dump_inputs(sources, opts.config.output.as_ref()).await?;
let exit_code = commands::dump_inputs(
sources,
opts.config.output.as_ref(),
&opts.config.exclude_path,
)
.await?;

return Ok(exit_code as i32);
}
Expand Down

0 comments on commit 3e11c88

Please sign in to comment.