diff --git a/lychee-bin/src/commands/dump.rs b/lychee-bin/src/commands/dump.rs index 147d755..c872ad5 100644 --- a/lychee-bin/src/commands/dump.rs +++ b/lychee-bin/src/commands/dump.rs @@ -1,12 +1,32 @@ use lychee_lib::Request; use lychee_lib::Result; +use std::fs; use std::io::{self, Write}; +use std::path::PathBuf; use tokio_stream::StreamExt; use crate::ExitCode; use super::CommandParams; +// Helper function to create an output writer. +// +// If the output file is not specified, it will use `stdout`. +// +// # Errors +// +// If the output file cannot be opened, an error is returned. +fn create_writer(output: Option) -> Result> { + let out = if let Some(output) = output { + let out = fs::OpenOptions::new().append(true).open(output)?; + Box::new(out) as Box + } else { + let out = io::stdout(); + Box::new(out.lock()) as Box + }; + Ok(out) +} + /// Dump all detected links to stdout without checking them pub(crate) async fn dump(params: CommandParams) -> Result where @@ -15,6 +35,12 @@ where let requests = params.requests; tokio::pin!(requests); + if let Some(outfile) = ¶ms.cfg.output { + fs::File::create(outfile)?; + } + + let mut writer = create_writer(params.cfg.output)?; + while let Some(request) = requests.next().await { let mut request = request?; @@ -32,7 +58,7 @@ where if excluded && !verbose { continue; } - if let Err(e) = write(&request, verbose, excluded) { + if let Err(e) = write(&mut writer, &request, verbose, excluded) { if e.kind() != io::ErrorKind::BrokenPipe { eprintln!("{e}"); return Ok(ExitCode::UnexpectedFailure); @@ -44,7 +70,12 @@ where } /// Dump request to stdout -fn write(request: &Request, verbose: bool, excluded: bool) -> io::Result<()> { +fn write( + writer: &mut Box, + request: &Request, + verbose: bool, + excluded: bool, +) -> io::Result<()> { let request = if verbose { // Only print source in verbose mode. This way the normal link output // can be fed into another tool without data mangling. @@ -52,9 +83,17 @@ fn write(request: &Request, verbose: bool, excluded: bool) -> io::Result<()> { } else { request.uri.to_string() }; - if excluded { - writeln!(io::stdout(), "{} [excluded]", request) + + // Mark excluded links + let out_str = if excluded { + format!("{request} [excluded]") } else { - writeln!(io::stdout(), "{}", request) - } + request + }; + + write_out(writer, &out_str) +} + +fn write_out(writer: &mut Box, out_str: &str) -> io::Result<()> { + writeln!(writer, "{}", out_str) } diff --git a/lychee-bin/tests/cli.rs b/lychee-bin/tests/cli.rs index 6978234..a36c773 100644 --- a/lychee-bin/tests/cli.rs +++ b/lychee-bin/tests/cli.rs @@ -449,6 +449,31 @@ mod cli { Ok(()) } + /// Test writing output of `--dump` command to file + #[test] + fn test_dump_to_file() -> Result<()> { + let mut cmd = main_command(); + let test_path = fixtures_path().join("TEST.md"); + let outfile = format!("{}", Uuid::new_v4()); + + cmd.arg("--output") + .arg(&outfile) + .arg("--dump") + .arg(test_path) + .assert() + .success(); + + let output = fs::read_to_string(&outfile)?; + + // We expect 11 links in the test file + // Running the command from the command line will print 9 links, + // because the actual `--dump` command filters out the two + // http(s)://example.com links + assert_eq!(output.lines().count(), 11); + fs::remove_file(outfile)?; + Ok(()) + } + /// Test excludes #[test] fn test_exclude_wildcard() -> Result<()> {