diff --git a/fixtures/configs/cache.toml b/fixtures/configs/cache.toml index 6d771e2b99..ebf41e3e2d 100644 --- a/fixtures/configs/cache.toml +++ b/fixtures/configs/cache.toml @@ -1,2 +1,2 @@ cache = true -max_age = "1d" +max_cache_age = "1d" diff --git a/fixtures/configs/files_from/example.md b/fixtures/configs/files_from/example.md new file mode 100644 index 0000000000..0c0f933fbf --- /dev/null +++ b/fixtures/configs/files_from/example.md @@ -0,0 +1 @@ +https://wikipedia.org diff --git a/fixtures/configs/files_from/files.txt b/fixtures/configs/files_from/files.txt new file mode 100644 index 0000000000..92daeecdf6 --- /dev/null +++ b/fixtures/configs/files_from/files.txt @@ -0,0 +1 @@ +example.md diff --git a/fixtures/configs/files_from/lychee.toml b/fixtures/configs/files_from/lychee.toml new file mode 100644 index 0000000000..1633f00ccb --- /dev/null +++ b/fixtures/configs/files_from/lychee.toml @@ -0,0 +1 @@ +files_from = "files.txt" diff --git a/fixtures/configs/invalid-key.toml b/fixtures/configs/invalid-key.toml new file mode 100644 index 0000000000..89525df8f5 --- /dev/null +++ b/fixtures/configs/invalid-key.toml @@ -0,0 +1 @@ +this_is_invalid = "this is purely fictional" diff --git a/fixtures/configs/invalid.toml b/fixtures/configs/invalid.toml index b1cd4c8a37..90f1b1ad5c 100644 --- a/fixtures/configs/invalid.toml +++ b/fixtures/configs/invalid.toml @@ -1 +1 @@ -max_age = 42my +max_cache_age = 42my diff --git a/fixtures/configs/smoketest.toml b/fixtures/configs/smoketest.toml index 1e8874754b..97d6a8c727 100644 --- a/fixtures/configs/smoketest.toml +++ b/fixtures/configs/smoketest.toml @@ -123,4 +123,4 @@ exclude_link_local = false exclude_loopback = false # Exclude all mail addresses from checking. -exclude_mail = false +include_mail = false diff --git a/lychee-bin/src/main.rs b/lychee-bin/src/main.rs index 00493408ba..a9564efaf1 100644 --- a/lychee-bin/src/main.rs +++ b/lychee-bin/src/main.rs @@ -136,8 +136,8 @@ fn read_lines(file: &File) -> Result> { .collect()) } -/// Merge all provided config options into one This includes a potential config -/// file, command-line- and environment variables +/// Merge all provided config options into one. +/// This includes a potential config file, command-line- and environment variables fn load_config() -> Result { let mut opts = LycheeOptions::parse(); diff --git a/lychee-bin/src/options.rs b/lychee-bin/src/options.rs index 6d97dc2c5a..f0891b7663 100644 --- a/lychee-bin/src/options.rs +++ b/lychee-bin/src/options.rs @@ -335,27 +335,6 @@ NOTE: Use `--` to separate inputs from options that allow multiple arguments." )] raw_inputs: Vec, - /// Read input filenames from the given file or stdin (if path is '-'). - #[arg( - long = "files-from", - value_name = "PATH", - long_help = "Read input filenames from the given file or stdin (if path is '-'). - -This is useful when you have a large number of inputs that would be -cumbersome to specify on the command line directly. - -Examples: - lychee --files-from list.txt - find . -name '*.md' | lychee --files-from - - echo 'README.md' | lychee --files-from - - -File Format: - Each line should contain one input (file path, URL, or glob pattern). - Lines starting with '#' are treated as comments and ignored. - Empty lines are also ignored." - )] - files_from: Option, - /// Configuration file to use #[arg(short, long = "config")] #[arg(help = HELP_MSG_CONFIG_FILE)] @@ -374,7 +353,7 @@ impl LycheeOptions { let mut all_inputs = self.raw_inputs.clone(); // If --files-from is specified, read inputs from the file - if let Some(files_from_path) = &self.files_from { + if let Some(files_from_path) = &self.config.files_from { let files_from = FilesFrom::try_from(files_from_path.as_path()) .context("Cannot read inputs from --files-from")?; all_inputs.extend(files_from.inputs); @@ -407,7 +386,29 @@ where /// The main configuration for lychee #[allow(clippy::struct_excessive_bools)] #[derive(Parser, Debug, Deserialize, Clone, Default)] +#[serde(deny_unknown_fields)] pub(crate) struct Config { + /// Read input filenames from the given file or stdin (if path is '-'). + #[arg( + long = "files-from", + value_name = "PATH", + long_help = "Read input filenames from the given file or stdin (if path is '-'). + +This is useful when you have a large number of inputs that would be +cumbersome to specify on the command line directly. + +Examples: + lychee --files-from list.txt + find . -name '*.md' | lychee --files-from - + echo 'README.md' | lychee --files-from - + +File Format: + Each line should contain one input (file path, URL, or glob pattern). + Lines starting with '#' are treated as comments and ignored. + Empty lines are also ignored." + )] + files_from: Option, + /// Verbose program output #[clap(flatten)] #[serde(default = "verbosity")] @@ -953,6 +954,7 @@ impl Config { exclude_private: false, extensions: FileType::default_extensions(), fallback_extensions: Vec::::new(), + files_from: None, format: StatsFormat::default(), generate: None, glob_ignore_case: false, diff --git a/lychee-bin/tests/cli.rs b/lychee-bin/tests/cli.rs index 5ea253be14..9908ec6580 100644 --- a/lychee-bin/tests/cli.rs +++ b/lychee-bin/tests/cli.rs @@ -6,7 +6,7 @@ mod cli { use http::{Method, StatusCode}; use lychee_lib::{InputSource, ResponseBody}; use predicates::{ - prelude::{PredicateBooleanExt, predicate}, + prelude::PredicateBooleanExt, str::{contains, is_empty}, }; use pretty_assertions::assert_eq; @@ -321,10 +321,10 @@ mod cli { #[test] fn test_email_html_with_subject() -> Result<()> { - let mut cmd = main_command!(); let input = fixtures_path!().join("TEST_EMAIL_QUERY_PARAMS.html"); - cmd.arg("--dump") + main_command!() + .arg("--dump") .arg(input) .arg("--include-mail") .assert() @@ -336,10 +336,10 @@ mod cli { #[test] fn test_email_markdown_with_subject() -> Result<()> { - let mut cmd = main_command!(); let input = fixtures_path!().join("TEST_EMAIL_QUERY_PARAMS.md"); - cmd.arg("--dump") + main_command!() + .arg("--dump") .arg(input) .arg("--include-mail") .assert() @@ -376,13 +376,13 @@ mod cli { /// Test unsupported URI schemes #[test] fn test_unsupported_uri_schemes_are_ignored() { - let mut cmd = main_command!(); let test_schemes_path = fixtures_path!().join("TEST_SCHEMES.txt"); // Exclude file link because it doesn't exist on the filesystem. // (File URIs are absolute paths, which we don't have.) // Nevertheless, the `file` scheme should be recognized. - cmd.arg(test_schemes_path) + main_command!() + .arg(test_schemes_path) .arg("--exclude") .arg("file://") .env_clear() @@ -395,10 +395,10 @@ mod cli { #[test] fn test_resolve_paths() { - let mut cmd = main_command!(); let dir = fixtures_path!().join("resolve_paths"); - cmd.arg("--offline") + main_command!() + .arg("--offline") .arg("--base-url") .arg(&dir) .arg(dir.join("index.html")) @@ -411,10 +411,10 @@ mod cli { #[test] fn test_resolve_paths_from_root_dir() { - let mut cmd = main_command!(); let dir = fixtures_path!().join("resolve_paths_from_root_dir"); - cmd.arg("--offline") + main_command!() + .arg("--offline") .arg("--include-fragments") .arg("--root-dir") .arg(&dir) @@ -429,10 +429,10 @@ mod cli { #[test] fn test_resolve_paths_from_root_dir_and_base_url() { - let mut cmd = main_command!(); let dir = fixtures_path!(); - cmd.arg("--offline") + main_command!() + .arg("--offline") .arg("--root-dir") .arg("/resolve_paths") .arg("--base-url") @@ -501,8 +501,8 @@ mod cli { let mut file = File::create(&file_path)?; writeln!(file, "{}", mock_server.uri())?; - let mut cmd = main_command!(); - cmd.arg(file_path) + main_command!() + .arg(file_path) .write_stdin(mock_server.uri()) .assert() .failure() @@ -513,10 +513,10 @@ mod cli { #[test] fn test_schemes() { - let mut cmd = main_command!(); let test_schemes_path = fixtures_path!().join("TEST_SCHEMES.md"); - cmd.arg(test_schemes_path) + main_command!() + .arg(test_schemes_path) .arg("--scheme") .arg("https") .arg("--scheme") @@ -531,11 +531,11 @@ mod cli { #[test] fn test_caching_single_file() { - let mut cmd = main_command!(); // Repetitions in one file shall all be checked and counted only once. let test_schemes_path_1 = fixtures_path!().join("TEST_REPETITION_1.txt"); - cmd.arg(&test_schemes_path_1) + main_command!() + .arg(&test_schemes_path_1) .env_clear() .assert() .success() @@ -568,10 +568,10 @@ mod cli { #[test] fn test_failure_github_404_no_token() { - let mut cmd = main_command!(); let test_github_404_path = fixtures_path!().join("TEST_GITHUB_404.md"); - cmd.arg(test_github_404_path) + main_command!() + .arg(test_github_404_path) .arg("--no-progress") .env_clear() .assert() @@ -587,10 +587,10 @@ mod cli { #[tokio::test] async fn test_stdin_input() { - let mut cmd = main_command!(); let mock_server = mock_server!(StatusCode::OK); - cmd.arg("-") + main_command!() + .arg("-") .write_stdin(mock_server.uri()) .assert() .success(); @@ -598,10 +598,10 @@ mod cli { #[tokio::test] async fn test_stdin_input_failure() { - let mut cmd = main_command!(); let mock_server = mock_server!(StatusCode::INTERNAL_SERVER_ERROR); - cmd.arg("-") + main_command!() + .arg("-") .write_stdin(mock_server.uri()) .assert() .failure() @@ -610,13 +610,13 @@ mod cli { #[tokio::test] async fn test_stdin_input_multiple() { - let mut cmd = main_command!(); let mock_server_a = mock_server!(StatusCode::OK); let mock_server_b = mock_server!(StatusCode::OK); // this behavior (treating multiple `-` as separate inputs) is the same as most CLI tools // that accept `-` as stdin, e.g. `cat`, `bat`, `grep` etc. - cmd.arg("-") + main_command!() + .arg("-") .arg("-") .write_stdin(mock_server_a.uri()) .write_stdin(mock_server_b.uri()) @@ -626,10 +626,12 @@ mod cli { #[test] fn test_missing_file_ok_if_skip_missing() { - let mut cmd = main_command!(); let filename = format!("non-existing-file-{}", uuid::Uuid::new_v4()); - - cmd.arg(&filename).arg("--skip-missing").assert().success(); + main_command!() + .arg(&filename) + .arg("--skip-missing") + .assert() + .success(); } #[test] @@ -732,9 +734,6 @@ mod cli { #[tokio::test] async fn test_glob() -> Result<()> { - // using Result to be able to use `?` - let mut cmd = main_command!(); - let dir = tempfile::tempdir()?; let mock_server_a = mock_server!(StatusCode::OK); let mock_server_b = mock_server!(StatusCode::OK); @@ -744,7 +743,8 @@ mod cli { writeln!(file_a, "{}", mock_server_a.uri().as_str())?; writeln!(file_b, "{}", mock_server_b.uri().as_str())?; - cmd.arg(dir.path().join("*.md")) + main_command!() + .arg(dir.path().join("*.md")) .arg("--verbose") .assert() .success() @@ -756,8 +756,6 @@ mod cli { #[cfg(target_os = "linux")] // MacOS and Windows have case-insensitive filesystems #[tokio::test] async fn test_glob_ignore_case() -> Result<()> { - let mut cmd = main_command!(); - let dir = tempfile::tempdir()?; let mock_server_a = mock_server!(StatusCode::OK); let mock_server_b = mock_server!(StatusCode::OK); @@ -767,7 +765,8 @@ mod cli { writeln!(file_a, "{}", mock_server_a.uri().as_str())?; writeln!(file_b, "{}", mock_server_b.uri().as_str())?; - cmd.arg(dir.path().join("[r]eadme.md")) + main_command!() + .arg(dir.path().join("[r]eadme.md")) .arg("--verbose") .arg("--glob-ignore-case") .assert() @@ -779,8 +778,6 @@ mod cli { #[tokio::test] async fn test_glob_recursive() -> Result<()> { - let mut cmd = main_command!(); - let dir = tempfile::tempdir()?; let subdir_level_1 = tempfile::tempdir_in(&dir)?; let subdir_level_2 = tempfile::tempdir_in(&subdir_level_1)?; @@ -790,8 +787,8 @@ mod cli { writeln!(file, "{}", mock_server.uri().as_str())?; - // ** should be a recursive glob - cmd.arg(dir.path().join("**/*.md")) + main_command!() + .arg(dir.path().join("**/*.md")) // ** should be a recursive glob .arg("--verbose") .assert() .success() @@ -817,11 +814,11 @@ mod cli { /// 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") + main_command!() + .arg("--output") .arg(&outfile) .arg("--dump") .arg("--include-mail") @@ -843,10 +840,10 @@ mod cli { /// Test excludes #[test] fn test_exclude_wildcard() -> Result<()> { - let mut cmd = main_command!(); let test_path = fixtures_path!().join("TEST.md"); - cmd.arg(test_path) + main_command!() + .arg(test_path) .arg("--exclude") .arg(".*") .assert() @@ -858,10 +855,10 @@ mod cli { #[test] fn test_exclude_multiple_urls() -> Result<()> { - let mut cmd = main_command!(); let test_path = fixtures_path!().join("TEST.md"); - cmd.arg(test_path) + main_command!() + .arg(test_path) .arg("--exclude") .arg("https://en.wikipedia.org/*") .arg("--exclude") @@ -874,11 +871,11 @@ mod cli { } #[tokio::test] - async fn test_empty_config() -> Result<()> { + async fn test_empty_config() { let mock_server = mock_server!(StatusCode::OK); let config = fixtures_path!().join("configs").join("empty.toml"); - let mut cmd = main_command!(); - cmd.arg("--config") + main_command!() + .arg("--config") .arg(config) .arg("-") .write_stdin(mock_server.uri()) @@ -887,12 +884,10 @@ mod cli { .success() .stdout(contains("1 Total")) .stdout(contains("1 OK")); - - Ok(()) } #[test] - fn test_invalid_default_config() -> Result<()> { + fn test_invalid_default_config() { let test_path = fixtures_path!().join("configs"); let mut cmd = main_command!(); cmd.current_dir(test_path) @@ -900,8 +895,6 @@ mod cli { .assert() .failure() .stderr(contains("Cannot load default configuration file")); - - Ok(()) } #[tokio::test] @@ -911,8 +904,8 @@ mod cli { let mut config = NamedTempFile::new()?; writeln!(config, "include_mail = false")?; - let mut cmd = main_command!(); - cmd.arg("--config") + main_command!() + .arg("--config") .arg(config.path().to_str().unwrap()) .arg("-") .write_stdin(test_mail_address) @@ -925,8 +918,8 @@ mod cli { let mut config = NamedTempFile::new()?; writeln!(config, "include_mail = true")?; - let mut cmd = main_command!(); - cmd.arg("--config") + main_command!() + .arg("--config") .arg(config.path().to_str().unwrap()) .arg("-") .write_stdin(test_mail_address) @@ -943,8 +936,8 @@ mod cli { async fn test_cache_config() -> Result<()> { let mock_server = mock_server!(StatusCode::OK); let config = fixtures_path!().join("configs").join("cache.toml"); - let mut cmd = main_command!(); - cmd.arg("--config") + main_command!() + .arg("--config") .arg(config) .arg("-") .write_stdin(mock_server.uri()) @@ -960,23 +953,39 @@ mod cli { #[tokio::test] async fn test_invalid_config() { let config = fixtures_path!().join("configs").join("invalid.toml"); - let mut cmd = main_command!(); - cmd.arg("--config") + main_command!() + .arg("--config") + .arg(config) + .arg("-") + .env_clear() + .assert() + .failure() + .stderr(contains("Cannot load configuration file")) + .stderr(contains("Failed to parse")) + .stderr(contains("TOML parse error")); + } + + #[tokio::test] + async fn test_config_invalid_keys() { + let mock_server = mock_server!(StatusCode::OK); + let config = fixtures_path!().join("configs").join("invalid-key.toml"); + main_command!() + .arg("--config") .arg(config) .arg("-") + .write_stdin(mock_server.uri()) .env_clear() .assert() .failure() - .stderr(predicate::str::contains("Cannot load configuration file")) - .stderr(predicate::str::contains("Failed to parse")) - .stderr(predicate::str::contains("TOML parse error")); + .code(3) + .stderr(contains("unknown field `this_is_invalid`, expected one of")); } #[tokio::test] async fn test_missing_config_error() { let mock_server = mock_server!(StatusCode::OK); - let mut cmd = main_command!(); - cmd.arg("--config") + main_command!() + .arg("--config") .arg("config.does.not.exist.toml") .arg("-") .write_stdin(mock_server.uri()) @@ -989,8 +998,8 @@ mod cli { async fn test_config_example() { let mock_server = mock_server!(StatusCode::OK); let config = root_path!().join("lychee.example.toml"); - let mut cmd = main_command!(); - cmd.arg("--config") + main_command!() + .arg("--config") .arg(config) .arg("-") .write_stdin(mock_server.uri()) @@ -999,12 +1008,53 @@ mod cli { .success(); } + #[test] + #[cfg(unix)] + fn test_all_arguments_in_config() -> Result<()> { + let help_cmd = main_command!().env_clear().arg("--help").assert().success(); + let help_text = std::str::from_utf8(&help_cmd.get_output().stdout)?; + + let regex = test_utils::arg_regex_help!()?; + let excluded = [ + "base", // deprecated + "exclude_file", // deprecated + "config", // not part of config + "quiet", // not part of config + "help", // special clap argument + "version", // special clap argument + ]; + + let arguments: Vec = help_text + .lines() + .filter_map(|line| { + let captures = regex.captures(line)?; + captures.name("long").map(|m| m.as_str()) + }) + .map(|arg| arg.replace("-", "_")) + .filter(|arg| !excluded.contains(&arg.as_str())) + .collect(); + + let config = root_path!().join("lychee.example.toml"); + let values: toml::Table = toml::from_str(&std::fs::read_to_string(config)?)?; + + for argument in arguments { + if !values.contains_key(&argument) { + panic!( + "Key '{argument}' missing in config. +The config file should contain every possible key for documentation purposes." + ) + } + } + + Ok(()) + } + #[tokio::test] async fn test_config_smoketest() { let mock_server = mock_server!(StatusCode::OK); let config = fixtures_path!().join("configs").join("smoketest.toml"); - let mut cmd = main_command!(); - cmd.arg("--config") + main_command!() + .arg("--config") .arg(config) .arg("-") .write_stdin(mock_server.uri()) @@ -1017,8 +1067,8 @@ mod cli { async fn test_config_accept() { let mock_server = mock_server!(StatusCode::OK); let config = fixtures_path!().join("configs").join("accept.toml"); - let mut cmd = main_command!(); - cmd.arg("--config") + main_command!() + .arg("--config") .arg(config) .arg("-") .write_stdin(mock_server.uri()) @@ -1027,12 +1077,25 @@ mod cli { .success(); } + #[tokio::test] + #[cfg(unix)] + async fn test_config_files_from() { + let dir = fixtures_path!().join("configs").join("files_from"); + let result = main_command!() + .current_dir(dir) + .arg("/dev/null") // at least one input arg is required. this could be changed in the future + .arg("--dump") + .assert() + .success(); + + assert_lines_eq(result, vec!["https://wikipedia.org/"]); + } + #[test] fn test_lycheeignore_file() -> Result<()> { - let mut cmd = main_command!(); let test_path = fixtures_path!().join("lycheeignore"); - let cmd = cmd + let cmd = main_command!() .current_dir(test_path) .arg("--dump") .arg("TEST.md") @@ -1050,11 +1113,11 @@ mod cli { #[test] fn test_lycheeignore_and_exclude_file() -> Result<()> { - let mut cmd = main_command!(); let test_path = fixtures_path!().join("lycheeignore"); let excludes_path = test_path.join("normal-exclude-file"); - cmd.current_dir(test_path) + main_command!() + .current_dir(test_path) .arg("TEST.md") .arg("--exclude-file") .arg(excludes_path) @@ -1288,8 +1351,8 @@ mod cli { async fn test_accept_overrides_defaults_not_additive() -> Result<()> { let mock_server_200 = mock_server!(StatusCode::OK); - let mut cmd = main_command!(); - cmd.arg("--accept") + main_command!() + .arg("--accept") .arg("404") // ONLY accept 404 - should reject 200 as we overwrite the default .arg("-") .write_stdin(mock_server_200.uri()) @@ -1399,25 +1462,23 @@ mod cli { } #[test] - fn test_verbatim_skipped_by_default() -> Result<()> { - let mut cmd = main_command!(); + fn test_verbatim_skipped_by_default() { let input = fixtures_path!().join("TEST_CODE_BLOCKS.md"); - cmd.arg(input) + main_command!() + .arg(input) .arg("--dump") .assert() .success() .stdout(is_empty()); - - Ok(()) } #[test] - fn test_include_verbatim() -> Result<()> { - let mut cmd = main_command!(); + fn test_include_verbatim() { let input = fixtures_path!().join("TEST_CODE_BLOCKS.md"); - cmd.arg("--include-verbatim") + main_command!() + .arg("--include-verbatim") .arg(input) .arg("--dump") .assert() @@ -1425,11 +1486,9 @@ mod cli { .stdout(contains("http://127.0.0.1/block")) .stdout(contains("http://127.0.0.1/inline")) .stdout(contains("http://127.0.0.1/bash")); - - Ok(()) } #[tokio::test] - async fn test_verbatim_skipped_by_default_via_file() -> Result<()> { + async fn test_verbatim_skipped_by_default_via_file() { let file = fixtures_path!().join("TEST_VERBATIM.html"); main_command!() @@ -1438,34 +1497,30 @@ mod cli { .assert() .success() .stdout(is_empty()); - - Ok(()) } #[tokio::test] - async fn test_verbatim_skipped_by_default_via_remote_url() -> Result<()> { - let mut cmd = main_command!(); + async fn test_verbatim_skipped_by_default_via_remote_url() { let file = fixtures_path!().join("TEST_VERBATIM.html"); - let body = fs::read_to_string(file)?; + let body = fs::read_to_string(file).unwrap(); let mock_server = mock_response!(body); - cmd.arg("--dump") + main_command!() + .arg("--dump") .arg(mock_server.uri()) .assert() .success() .stdout(is_empty()); - - Ok(()) } #[tokio::test] - async fn test_include_verbatim_via_remote_url() -> Result<()> { - let mut cmd = main_command!(); + async fn test_include_verbatim_via_remote_url() { let file = fixtures_path!().join("TEST_VERBATIM.html"); - let body = fs::read_to_string(file)?; + let body = fs::read_to_string(file).unwrap(); let mock_server = mock_response!(body); - cmd.arg("--include-verbatim") + main_command!() + .arg("--include-verbatim") .arg("--dump") .arg(mock_server.uri()) .assert() @@ -1476,64 +1531,54 @@ mod cli { .stdout(contains("http://www.example.com/kbd")) .stdout(contains("http://www.example.com/var")) .stdout(contains("http://www.example.com/script")); - Ok(()) } #[test] - fn test_require_https() -> Result<()> { - let mut cmd = main_command!(); + fn test_require_https() { let test_path = fixtures_path!().join("TEST_HTTP.html"); - cmd.arg(&test_path).assert().success(); + main_command!().arg(&test_path).assert().success(); - let mut cmd = main_command!(); - cmd.arg("--require-https") + main_command!() + .arg("--require-https") .arg(test_path) .assert() .failure() .stdout(contains("This URI is available in HTTPS protocol, but HTTP is provided. Use 'https://example.com/' instead")); - - Ok(()) } /// If `base-dir` is not set, don't throw an error in case we encounter /// an absolute local link (e.g. `/about`) within a file. /// Instead, simply ignore the link. #[test] - fn test_ignore_absolute_local_links_without_base() -> Result<()> { - let mut cmd = main_command!(); - + fn test_ignore_absolute_local_links_without_base() { let offline_dir = fixtures_path!().join("offline"); - cmd.arg("--offline") + main_command!() + .arg("--offline") .arg(offline_dir.join("index.html")) .env_clear() .assert() .success() .stdout(contains("0 Total")); - - Ok(()) } #[test] - fn test_inputs_without_scheme() -> Result<()> { + fn test_inputs_without_scheme() { let test_path = fixtures_path!().join("TEST_HTTP.html"); - let mut cmd = main_command!(); - - cmd.arg("--dump") + main_command!() + .arg("--dump") .arg("example.com") .arg(&test_path) .arg("https://example.org") .assert() .success(); - Ok(()) } #[test] - fn test_print_excluded_links_in_verbose_mode() -> Result<()> { + fn test_print_excluded_links_in_verbose_mode() { let test_path = fixtures_path!().join("TEST_DUMP_EXCLUDE.txt"); - let mut cmd = main_command!(); - - cmd.arg("--dump") + main_command!() + .arg("--dump") .arg("--verbose") .arg("--exclude") .arg("example.com") @@ -1553,14 +1598,12 @@ mod cli { "https://example.com/foo/bar ({}) [excluded]", test_path.display() ))); - Ok(()) } #[test] - fn test_remap_uri() -> Result<()> { - let mut cmd = main_command!(); - - cmd.arg("--dump") + fn test_remap_uri() { + main_command!() + .arg("--dump") .arg("--remap") .arg("https://example.com http://127.0.0.1:8080") .arg("--remap") @@ -1574,16 +1617,13 @@ mod cli { .stdout(contains("http://127.0.0.1:8080/")) .stdout(contains("https://staging.example.com/")) .stdout(contains("https://example.net/")); - - Ok(()) } #[test] #[ignore = "Skipping test until https://github.com/robinst/linkify/pull/58 is merged"] - fn test_remap_path() -> Result<()> { - let mut cmd = main_command!(); - - cmd.arg("--dump") + fn test_remap_path() { + main_command!() + .arg("--dump") .arg("--remap") .arg("../../issues https://github.com/usnistgov/OSCAL/issues") .arg("--") @@ -1593,15 +1633,12 @@ mod cli { .assert() .success() .stdout(contains("https://github.com/usnistgov/OSCAL/issues")); - - Ok(()) } #[test] - fn test_remap_capture() -> Result<()> { - let mut cmd = main_command!(); - - cmd.arg("--dump") + fn test_remap_capture() { + main_command!() + .arg("--dump") .arg("--remap") .arg("https://example.com/(.*) http://example.org/$1") .arg("--") @@ -1611,15 +1648,12 @@ mod cli { .assert() .success() .stdout(contains("http://example.org/foo")); - - Ok(()) } #[test] - fn test_remap_named_capture() -> Result<()> { - let mut cmd = main_command!(); - - cmd.arg("--dump") + fn test_remap_named_capture() { + main_command!() + .arg("--dump") .arg("--remap") .arg("https://github.com/(?P.*)/(?P.*) https://gitlab.com/$org/$repo") .arg("--") @@ -1629,18 +1663,14 @@ mod cli { .assert() .success() .stdout(contains("https://gitlab.com/lycheeverse/lychee")); - - Ok(()) } #[test] - fn test_excluded_paths_regex() -> Result<()> { + fn test_excluded_paths_regex() { let test_path = fixtures_path!().join("exclude-path"); let excluded_path_1 = "\\/excluded?\\/"; // exclude paths containing a directory "exclude" and "excluded" let excluded_path_2 = "(\\.mdx|\\.txt)$"; // exclude .mdx and .txt files - let mut cmd = main_command!(); - - let result = cmd + let result = main_command!() .arg("--exclude-path") .arg(excluded_path_1) .arg("--exclude-path") @@ -1658,16 +1688,14 @@ mod cli { "https://test.md/to-be-included-inner", ], ); - - Ok(()) } #[test] - fn test_handle_relative_paths_as_input() -> Result<()> { + fn test_handle_relative_paths_as_input() { let test_path = fixtures_path!(); - let mut cmd = main_command!(); - cmd.current_dir(&test_path) + main_command!() + .current_dir(&test_path) .arg("--verbose") .arg("--exclude") .arg("example.*") @@ -1677,16 +1705,14 @@ mod cli { .success() .stdout(contains("3 Total")) .stdout(contains("3 Excluded")); - - Ok(()) } #[test] - fn test_handle_nonexistent_relative_paths_as_input() -> Result<()> { + fn test_handle_nonexistent_relative_paths_as_input() { let test_path = fixtures_path!(); - let mut cmd = main_command!(); - cmd.current_dir(&test_path) + main_command!() + .current_dir(&test_path) .arg("--verbose") .arg("--exclude") .arg("example.*") @@ -1695,23 +1721,19 @@ mod cli { .assert() .failure() .stderr(contains("Invalid file path: ./NOT-A-REAL-TEST-FIXTURE.md")); - - Ok(()) } #[test] - fn test_prevent_too_many_redirects() -> Result<()> { - let mut cmd = main_command!(); + fn test_prevent_too_many_redirects() { let url = "https://http.codes/308"; - cmd.write_stdin(url) + main_command!() + .write_stdin(url) .arg("--max-redirects") .arg("0") .arg("-") .assert() .failure(); - - Ok(()) } #[test] @@ -1748,7 +1770,7 @@ mod cli { } #[tokio::test] - async fn test_basic_auth() -> Result<()> { + async fn test_basic_auth() { let username = "username"; let password = "password123"; @@ -1785,12 +1807,10 @@ mod cli { .assert() .success() .stdout(contains("0 Total")); // Mock server returns no body, so there are no URLs to check - - Ok(()) } #[tokio::test] - async fn test_multi_basic_auth() -> Result<()> { + async fn test_multi_basic_auth() { let username1 = "username"; let password1 = "password123"; let mock_server1 = wiremock::MockServer::start().await; @@ -1821,16 +1841,14 @@ mod cli { .success() .stdout(contains("2 Total")) .stdout(contains("2 OK")); - - Ok(()) } #[tokio::test] async fn test_cookie_jar() -> Result<()> { // Create a random cookie jar file let cookie_jar = NamedTempFile::new()?; - let mut cmd = main_command!(); - cmd.arg("--cookie-jar") + main_command!() + .arg("--cookie-jar") .arg(cookie_jar.path().to_str().unwrap()) .arg("-") // Using Google as a test target because I couldn't @@ -1850,57 +1868,51 @@ mod cli { } #[test] - fn test_dump_inputs_does_not_include_duplicates() -> Result<()> { + fn test_dump_inputs_does_not_include_duplicates() { let pattern = fixtures_path!().join("dump_inputs/markdown.md"); - let mut cmd = main_command!(); - cmd.arg("--dump-inputs") + main_command!() + .arg("--dump-inputs") .arg(&pattern) .arg(&pattern) .assert() .success() .stdout(contains("fixtures/dump_inputs/markdown.md").count(1)); - - Ok(()) } #[test] - fn test_dump_inputs_glob_does_not_include_duplicates() -> Result<()> { + fn test_dump_inputs_glob_does_not_include_duplicates() { let pattern1 = fixtures_path!().join("**/markdown.*"); let pattern2 = fixtures_path!().join("**/*.md"); - let mut cmd = main_command!(); - cmd.arg("--dump-inputs") + main_command!() + .arg("--dump-inputs") .arg(pattern1) .arg(pattern2) .assert() .success() .stdout(contains("fixtures/dump_inputs/markdown.md").count(1)); - - Ok(()) } #[test] - fn test_dump_inputs_glob_md() -> Result<()> { + fn test_dump_inputs_glob_md() { let pattern = fixtures_path!().join("**/*.md"); - let mut cmd = main_command!(); - cmd.arg("--dump-inputs") + main_command!() + .arg("--dump-inputs") .arg(pattern) .assert() .success() .stdout(contains("fixtures/dump_inputs/subfolder/file2.md")) .stdout(contains("fixtures/dump_inputs/markdown.md")); - - Ok(()) } #[test] - fn test_dump_inputs_glob_all() -> Result<()> { + fn test_dump_inputs_glob_all() { let pattern = fixtures_path!().join("**/*"); - let mut cmd = main_command!(); - cmd.arg("--dump-inputs") + main_command!() + .arg("--dump-inputs") .arg(pattern) .assert() .success() @@ -1909,16 +1921,14 @@ mod cli { .stdout(contains("fixtures/dump_inputs/subfolder")) .stdout(contains("fixtures/dump_inputs/markdown.md")) .stdout(contains("fixtures/dump_inputs/some_file.txt")); - - Ok(()) } #[test] - fn test_dump_inputs_glob_exclude_path() -> Result<()> { + fn test_dump_inputs_glob_exclude_path() { let pattern = fixtures_path!().join("**/*"); - let mut cmd = main_command!(); - cmd.arg("--dump-inputs") + main_command!() + .arg("--dump-inputs") .arg(pattern) .arg("--exclude-path") .arg(fixtures_path!().join("dump_inputs/subfolder")) @@ -1927,27 +1937,22 @@ mod cli { .stdout(contains("fixtures/dump_inputs/subfolder/test.html").not()) .stdout(contains("fixtures/dump_inputs/subfolder/file2.md").not()) .stdout(contains("fixtures/dump_inputs/subfolder").not()); - - Ok(()) } #[test] - fn test_dump_inputs_url() -> Result<()> { - let mut cmd = main_command!(); - let result = cmd + fn test_dump_inputs_url() { + let result = main_command!() .arg("--dump-inputs") .arg("https://example.com") .assert() .success(); assert_lines_eq(result, vec!["https://example.com/"]); - Ok(()) } #[test] - fn test_dump_inputs_path() -> Result<()> { - let mut cmd = main_command!(); - let result = cmd + fn test_dump_inputs_path() { + let result = main_command!() .arg("--dump-inputs") .arg(fixtures_path!().join("dump_inputs")) .assert() @@ -1965,17 +1970,15 @@ mod cli { .collect(); assert_lines_eq(result, expected_lines); - Ok(()) } // Ensures that dumping stdin does not panic and results in an empty output // as `stdin` is not a path #[test] - fn test_dump_inputs_with_extensions() -> Result<()> { - let mut cmd = main_command!(); + fn test_dump_inputs_with_extensions() { let test_dir = fixtures_path!().join("dump_inputs"); - let output = cmd + let output = main_command!() .arg("--dump-inputs") .arg("--extensions") .arg("md,txt") @@ -2009,12 +2012,10 @@ mod cli { "Should not contain example.bin: {line}" ); } - - Ok(()) } #[test] - fn test_dump_inputs_skip_hidden() -> Result<()> { + fn test_dump_inputs_skip_hidden() { let test_dir = fixtures_path!().join("hidden"); // Test default behavior (skip hidden) @@ -2033,43 +2034,36 @@ mod cli { .assert() .success() .stdout(contains(".hidden/file.md")); - - Ok(()) } #[test] - fn test_dump_inputs_individual_file() -> Result<()> { - let mut cmd = main_command!(); + fn test_dump_inputs_individual_file() { let test_file = fixtures_path!().join("TEST.md"); - cmd.arg("--dump-inputs") + main_command!() + .arg("--dump-inputs") .arg(&test_file) .assert() .success() .stdout(contains("fixtures/TEST.md")); - - Ok(()) } #[test] - fn test_dump_inputs_stdin() -> Result<()> { - let mut cmd = main_command!(); - - cmd.arg("--dump-inputs") + fn test_dump_inputs_stdin() { + main_command!() + .arg("--dump-inputs") .arg("-") .assert() .success() .stdout(contains("")); - - Ok(()) } #[test] fn test_fragments_regression() { - let mut cmd = main_command!(); let input = fixtures_path!().join("FRAGMENT_REGRESSION.md"); - cmd.arg("--include-fragments") + main_command!() + .arg("--include-fragments") .arg("--verbose") .arg(input) .assert() @@ -2078,10 +2072,9 @@ mod cli { #[test] fn test_fragments() { - let mut cmd = main_command!(); let input = fixtures_path!().join("fragments"); - let mut result = cmd + let mut result = main_command!() .arg("--include-fragments") .arg("--verbose") .arg(input) @@ -2171,12 +2164,12 @@ mod cli { #[test] fn test_fragments_when_accept_error_status_codes() { - let mut cmd = main_command!(); let input = fixtures_path!().join("TEST_FRAGMENT_ERR_CODE.md"); // it's common for user to accept 429, but let's test with 404 since // triggering 429 may annoy the server - cmd.arg("--verbose") + main_command!() + .arg("--verbose") .arg("--accept=200,404") .arg("--include-fragments") .arg(input) @@ -2192,10 +2185,10 @@ mod cli { #[test] fn test_fallback_extensions() { - let mut cmd = main_command!(); let input = fixtures_path!().join("fallback-extensions"); - cmd.arg("--verbose") + main_command!() + .arg("--verbose") .arg("--fallback-extensions=htm,html") .arg(input) .assert() @@ -2205,10 +2198,10 @@ mod cli { #[test] fn test_fragments_fallback_extensions() { - let mut cmd = main_command!(); let input = fixtures_path!().join("fragments-fallback-extensions"); - cmd.arg("--include-fragments") + main_command!() + .arg("--include-fragments") .arg("--fallback-extensions=html") .arg("--no-progress") .arg("--offline") @@ -2236,7 +2229,7 @@ mod cli { /// Note that the relative path is not resolved to the root of the server /// but relative to the file that contains the link. #[tokio::test] - async fn test_resolve_relative_paths_in_subfolder() -> Result<()> { + async fn test_resolve_relative_paths_in_subfolder() { let mock_server = wiremock::MockServer::start().await; let body = r#"next"#; @@ -2252,23 +2245,20 @@ mod cli { .mount(&mock_server) .await; - let mut cmd = main_command!(); - cmd.arg("--verbose") + main_command!() + .arg("--verbose") .arg(format!("{}/test/index.html", mock_server.uri())) .assert() .success() .stdout(contains("1 Total")) .stdout(contains("0 Errors")); - - Ok(()) } #[tokio::test] async fn test_json_format_in_config() -> Result<()> { let mock_server = mock_server!(StatusCode::OK); let config = fixtures_path!().join("configs").join("format.toml"); - let mut cmd = main_command!(); - let output = cmd + let output = main_command!() .arg("--config") .arg(config) .arg("-") @@ -2277,11 +2267,10 @@ mod cli { .assert() .success() .get_output() - .clone() - .unwrap(); + .clone(); // Check that the output is in JSON format - let output = std::str::from_utf8(&output.stdout).unwrap(); + let output = std::str::from_utf8(&output.stdout)?; let json: serde_json::Value = serde_json::from_str(output)?; assert_eq!(json["total"], 1); @@ -2292,8 +2281,7 @@ mod cli { async fn test_redirect_json() { use serde_json::json; redirecting_mock_server!(async |redirect_url: Url, ok_url| { - let mut cmd = main_command!(); - let output = cmd + let output = main_command!() .arg("-") .arg("--format") .arg("json") @@ -2327,7 +2315,7 @@ mod cli { } #[tokio::test] - async fn test_retry() -> Result<()> { + async fn test_retry() { let mock_server = wiremock::MockServer::start().await; wiremock::Mock::given(wiremock::matchers::method("GET")) @@ -2341,18 +2329,15 @@ mod cli { .mount(&mock_server) .await; - let mut cmd = main_command!(); - cmd.arg("-") + main_command!() + .arg("-") .write_stdin(mock_server.uri()) .assert() .success(); - - Ok(()) } #[tokio::test] - async fn test_no_header_set_on_input() -> Result<()> { - let mut cmd = main_command!(); + async fn test_no_header_set_on_input() { let server = wiremock::MockServer::start().await; server .register( @@ -2362,7 +2347,11 @@ mod cli { ) .await; - cmd.arg("--verbose").arg(server.uri()).assert().success(); + main_command!() + .arg("--verbose") + .arg(server.uri()) + .assert() + .success(); let received_requests = server.received_requests().await.unwrap(); assert_eq!(received_requests.len(), 1); @@ -2373,12 +2362,10 @@ mod cli { // Make sure the request does not contain the custom header assert!(!received_request.headers.contains_key("X-Foo")); - Ok(()) } #[tokio::test] - async fn test_header_set_on_input() -> Result<()> { - let mut cmd = main_command!(); + async fn test_header_set_on_input() { let server = wiremock::MockServer::start().await; server .register( @@ -2391,7 +2378,8 @@ mod cli { ) .await; - cmd.arg("--verbose") + main_command!() + .arg("--verbose") .arg("--header") .arg("X-Foo: Bar") .arg(server.uri()) @@ -2400,12 +2388,10 @@ mod cli { // Check that the server received the request with the header server.verify().await; - Ok(()) } #[tokio::test] - async fn test_multi_header_set_on_input() -> Result<()> { - let mut cmd = main_command!(); + async fn test_multi_header_set_on_input() { let server = wiremock::MockServer::start().await; server .register( @@ -2419,7 +2405,8 @@ mod cli { ) .await; - cmd.arg("--verbose") + main_command!() + .arg("--verbose") .arg("--header") .arg("X-Foo: Bar") .arg("--header") @@ -2430,12 +2417,10 @@ mod cli { // Check that the server received the request with the header server.verify().await; - Ok(()) } #[tokio::test] - async fn test_header_set_in_config() -> Result<()> { - let mut cmd = main_command!(); + async fn test_header_set_in_config() { let server = wiremock::MockServer::start().await; server .register( @@ -2450,7 +2435,8 @@ mod cli { .await; let config = fixtures_path!().join("configs").join("headers.toml"); - cmd.arg("--verbose") + main_command!() + .arg("--verbose") .arg("--config") .arg(config) .arg(server.uri()) @@ -2459,13 +2445,11 @@ mod cli { // Check that the server received the request with the header server.verify().await; - Ok(()) } #[test] - fn test_sorted_error_output() -> Result<()> { + fn test_sorted_error_output() { let test_files = ["TEST_GITHUB_404.md", "TEST_INVALID_URLS.html"]; - let test_urls = [ "https://httpbin.org/status/404", "https://httpbin.org/status/500", @@ -2505,16 +2489,14 @@ mod cli { assert!(next_position > position); position = next_position; } - - Ok(()) } #[test] fn test_extract_url_ending_with_period_file() { let test_path = fixtures_path!().join("LINK_PERIOD.html"); - let mut cmd = main_command!(); - cmd.arg("--dump") + main_command!() + .arg("--dump") .arg(test_path) .assert() .success() @@ -2523,11 +2505,11 @@ mod cli { #[tokio::test] async fn test_extract_url_ending_with_period_webserver() { - let mut cmd = main_command!(); let body = r#"link"#; let mock_server = mock_response!(body); - cmd.arg("--dump") + main_command!() + .arg("--dump") .arg(mock_server.uri()) .assert() .success() @@ -2538,8 +2520,8 @@ mod cli { fn test_wikilink_extract_when_specified() { let test_path = fixtures_path!().join("TEST_WIKI.md"); - let mut cmd = main_command!(); - cmd.arg("--dump") + main_command!() + .arg("--dump") .arg("--include-wikilinks") .arg(test_path) .assert() @@ -2551,8 +2533,8 @@ mod cli { fn test_wikilink_dont_extract_when_not_specified() { let test_path = fixtures_path!().join("TEST_WIKI.md"); - let mut cmd = main_command!(); - cmd.arg("--dump") + main_command!() + .arg("--dump") .arg(test_path) .assert() .success() @@ -2681,8 +2663,7 @@ mod cli { let inputs = fixtures_path!().join("invalid_utf8"); // Run the command with the binary input - let mut cmd = main_command!(); - let result = cmd + let result = main_command!() .arg("--verbose") .arg(&inputs) .assert() @@ -2711,8 +2692,8 @@ mod cli { let inputs = fixtures_path!().join("invalid_utf8"); // Run the command with the binary input - let mut cmd = main_command!(); - cmd.arg("--dump-inputs") + main_command!() + .arg("--dump-inputs") .arg(inputs) .assert() .success() @@ -2847,8 +2828,8 @@ mod cli { fs::write(&test_md, "# Test\n[link](https://example.com)")?; fs::write(&files_list_path, test_md.to_string_lossy().as_ref())?; - let mut cmd = main_command!(); - cmd.arg("--files-from") + main_command!() + .arg("--files-from") .arg(&files_list_path) .arg("--dump-inputs") .assert() @@ -2866,8 +2847,8 @@ mod cli { // Create test file fs::write(&test_md, "# Test\n[link](https://example.com)")?; - let mut cmd = main_command!(); - cmd.arg("--files-from") + main_command!() + .arg("--files-from") .arg("-") .arg("--dump-inputs") .write_stdin(test_md.to_string_lossy().as_ref()) @@ -2894,8 +2875,8 @@ mod cli { ), )?; - let mut cmd = main_command!(); - cmd.arg("--files-from") + main_command!() + .arg("--files-from") .arg(&files_list_path) .arg("--dump-inputs") .assert() @@ -2931,16 +2912,14 @@ mod cli { } #[test] - fn test_files_from_nonexistent_file_error() -> Result<()> { - let mut cmd = main_command!(); - cmd.arg("--files-from") + fn test_files_from_nonexistent_file_error() { + main_command!() + .arg("--files-from") .arg("/nonexistent/file.txt") .arg("--dump-inputs") .assert() .failure() .stderr(contains("Cannot open --files-from file")); - - Ok(()) } /// Test the --default-extension option for files without extensions diff --git a/lychee-bin/tests/data_uris.rs b/lychee-bin/tests/data_uris.rs index 4b370b7a5e..2427fb4815 100644 --- a/lychee-bin/tests/data_uris.rs +++ b/lychee-bin/tests/data_uris.rs @@ -10,6 +10,7 @@ mod cli { use assert_cmd::Command; use predicates::str::contains; + use test_utils::main_command; type Result = std::result::Result>; @@ -20,14 +21,9 @@ mod cli { .join("fixtures") } - fn main_command() -> Command { - // this gets the "main" binary name (e.g. `lychee`) - Command::cargo_bin(env!("CARGO_PKG_NAME")).expect("Couldn't get cargo package name") - } - #[test] fn test_dont_dump_data_uris_by_default() -> Result<()> { - let mut cmd = main_command(); + let mut cmd = main_command!(); let input = fixtures_path().join("TEST_DATA_URIS.html"); let cmd = cmd @@ -46,7 +42,7 @@ mod cli { #[test] fn test_dump_data_uris_in_verbose_mode() -> Result<()> { - let mut cmd = main_command(); + let mut cmd = main_command!(); let input = fixtures_path().join("TEST_DATA_URIS.html"); let cmd = cmd diff --git a/lychee-bin/tests/example_domains.rs b/lychee-bin/tests/example_domains.rs index 51249114b0..5562255b01 100644 --- a/lychee-bin/tests/example_domains.rs +++ b/lychee-bin/tests/example_domains.rs @@ -10,6 +10,7 @@ mod cli { use assert_cmd::Command; use predicates::str::contains; + use test_utils::main_command; type Result = std::result::Result>; @@ -20,15 +21,10 @@ mod cli { .join("fixtures") } - fn main_command() -> Command { - // this gets the "main" binary name (e.g. `lychee`) - Command::cargo_bin(env!("CARGO_PKG_NAME")).expect("Couldn't get cargo package name") - } - #[test] #[cfg(not(feature = "check_example_domains"))] fn test_exclude_example_domains() -> Result<()> { - let mut cmd = main_command(); + let mut cmd = main_command!(); let input = fixtures_path().join("TEST_EXAMPLE_DOMAINS.md"); let cmd = cmd @@ -51,7 +47,7 @@ mod cli { #[test] fn test_do_not_exclude_false_positive_example_domains() -> Result<()> { - let mut cmd = main_command(); + let mut cmd = main_command!(); let input = fixtures_path().join("TEST_EXAMPLE_DOMAINS_FALSE_POSITIVES.md"); let cmd = cmd diff --git a/lychee-bin/tests/usage.rs b/lychee-bin/tests/usage.rs index 469ca60efe..073a203c5d 100644 --- a/lychee-bin/tests/usage.rs +++ b/lychee-bin/tests/usage.rs @@ -4,11 +4,7 @@ mod readme { use pretty_assertions::assert_eq; use regex::Regex; use test_utils::load_readme_text; - - fn main_command() -> Command { - // this gets the "main" binary name (e.g. `lychee`) - Command::cargo_bin(env!("CARGO_PKG_NAME")).expect("Couldn't get cargo package name") - } + use test_utils::main_command; /// Remove line `[default: lychee/x.y.z]` from the string fn remove_lychee_version_line(string: &str) -> String { @@ -35,7 +31,7 @@ mod readme { #[cfg(unix)] fn test_readme_usage_up_to_date() -> Result<(), Box> { const BEGIN: &str = "```help-message\n"; - let mut cmd = main_command(); + let mut cmd = main_command!(); let help_cmd = cmd.env_clear().arg("--help").assert().success(); let usage_in_help = std::str::from_utf8(&help_cmd.get_output().stdout)?; @@ -59,12 +55,11 @@ mod readme { #[test] #[cfg(unix)] fn test_arguments_ordered_alphabetically() -> Result<(), Box> { - let mut cmd = main_command(); + let mut cmd = main_command!(); let help_cmd = cmd.env_clear().arg("--help").assert().success(); let help_text = std::str::from_utf8(&help_cmd.get_output().stdout)?; - let regex = Regex::new(r"^\s{2,6}(?:-(?[a-zA-Z]),)?\s--(?[a-zA-Z-]+)")?; - + let regex = test_utils::arg_regex_help!()?; let arguments: Vec<&str> = help_text .lines() .filter_map(|line| { diff --git a/lychee.example.toml b/lychee.example.toml index 1d495a0a9f..e8989b67b4 100644 --- a/lychee.example.toml +++ b/lychee.example.toml @@ -4,12 +4,24 @@ # Accepts log level: "error", "warn", "info", "debug", "trace" verbose = "info" +# Output format +format = "detailed" + +# Output display mode +mode = "emoji" + # Don't show interactive progress bar while checking links. no_progress = false # Path to summary output file. output = ".config.dummy.report.md" +# Extract links instead of checking them +dump = true + +# Dump inputs instead of doing link extraction / checking +dump_inputs = true + ############################# Cache ############################### # Enable link caching. This can be helpful to avoid checking the same links on @@ -19,8 +31,14 @@ cache = true # Discard all cached requests older than this duration. max_cache_age = "2d" +# A list of status codes that will be ignored from the cache +cache_exclude_status = "500.." + ############################# Runtime ############################# +# File to read and write cookies +cookie_jar = "cookie-jar" + # Number of threads to utilize. # Defaults to number of cores available to the system if omitted. threads = 2 @@ -34,6 +52,18 @@ max_retries = 2 # Maximum number of concurrent link checks. max_concurrency = 14 +# extension applied to files without extension +default_extension = "md" + +# GitHub API token +github_token = "secret" + +# Resolve directories to index files +index_files = ["index.html"] + +# Preprocess input files +preprocess = { command = "preprocess.sh" } + ############################# Requests ############################ # User agent to send with each request. @@ -92,11 +122,20 @@ basic_auth = ["example.com user:pwd"] # Enable the checking of fragments in links. include_fragments = true +# Minimum accepted TLS Version +min_tls = "TLSv1_3" + ############################# Exclusions ########################## # Skip missing input files (default is to error if they don't exist). skip_missing = false +# Do not skip hidden directories and files +hidden = true + +# Do not skip files that are ignored +no_ignore = true + # Check links inside `` and `
` blocks as well as Markdown code
 # blocks.
 include_verbatim = false
@@ -119,6 +158,9 @@ exclude_path = [
     "[aeiouAEIOU]", # exclude paths containing vowels
 ]
 
+# Check the specified file extensions
+extensions = ["md","txt","html"]
+
 # URLs to check (supports regex). Has preference over all excludes.
 include = ['gist\.github\.com.*']
 
@@ -138,3 +180,23 @@ exclude_loopback = false
 
 # Check mail addresses
 include_mail = true
+
+# Check WikiLinks in Markdown files
+include_wikilinks = true
+
+# Only check local files
+offline = true
+
+#############################  Other  #############################
+
+# Read input filenames from the given file or stdin ('-')
+files_from = "files.txt"
+
+# Generate special output instead of performing link checking
+generate = "man"
+
+# Use the Wayback Machine as web archive
+archive = "wayback"
+
+# Search and suggest link replacements for all broken links
+suggest = true
diff --git a/test-utils/src/lib.rs b/test-utils/src/lib.rs
index 4e949b3433..8b0887d065 100644
--- a/test-utils/src/lib.rs
+++ b/test-utils/src/lib.rs
@@ -186,3 +186,11 @@ macro_rules! main_command {
         Command::cargo_bin(env!("CARGO_PKG_NAME")).expect("Couldn't get cargo package name")
     };
 }
+
+/// Capture all CLI flags (e.g. `-a` or `--accept`) from the help message via regex
+#[macro_export]
+macro_rules! arg_regex_help {
+    () => {
+        Regex::new(r"^\s{2,6}(?:-(?[a-zA-Z]),)?\s--(?[a-zA-Z-]+)")
+    };
+}