From e2b97438cbcf562ac4c10fbaac3005cc87ae329e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dawid=20Ci=C4=99=C5=BCarkiewicz?= <dpc@dpc.pw>
Date: Thu, 6 Apr 2023 16:39:21 -0700
Subject: [PATCH] chore: add `--file-list` support

---
 crates/typos-cli/src/bin/typos-cli/args.rs    |  8 ++++-
 crates/typos-cli/src/bin/typos-cli/main.rs    | 31 +++++++++++++++++--
 crates/typos-cli/tests/cli_tests.rs           |  4 ++-
 crates/typos-cli/tests/cmd/help.toml          |  9 +++---
 .../tests/cmd/stdin-paths.in/_typos.toml      |  8 +++++
 .../typos-cli/tests/cmd/stdin-paths.in/a.fail |  1 +
 .../typos-cli/tests/cmd/stdin-paths.in/b.fail |  1 +
 .../typos-cli/tests/cmd/stdin-paths.in/c.fail |  1 +
 .../typos-cli/tests/cmd/stdin-paths.in/d.fail |  1 +
 crates/typos-cli/tests/cmd/stdin-paths.toml   | 22 +++++++++++++
 .../tests/cmd/stdin-paths0.in/_typos.toml     |  8 +++++
 .../tests/cmd/stdin-paths0.in/a.fail          |  1 +
 .../tests/cmd/stdin-paths0.in/b.fail          |  1 +
 .../tests/cmd/stdin-paths0.in/c.fail          |  1 +
 .../tests/cmd/stdin-paths0.in/d.fail          |  1 +
 15 files changed, 90 insertions(+), 8 deletions(-)
 create mode 100644 crates/typos-cli/tests/cmd/stdin-paths.in/_typos.toml
 create mode 100644 crates/typos-cli/tests/cmd/stdin-paths.in/a.fail
 create mode 100644 crates/typos-cli/tests/cmd/stdin-paths.in/b.fail
 create mode 100644 crates/typos-cli/tests/cmd/stdin-paths.in/c.fail
 create mode 100644 crates/typos-cli/tests/cmd/stdin-paths.in/d.fail
 create mode 100644 crates/typos-cli/tests/cmd/stdin-paths.toml
 create mode 100644 crates/typos-cli/tests/cmd/stdin-paths0.in/_typos.toml
 create mode 100644 crates/typos-cli/tests/cmd/stdin-paths0.in/a.fail
 create mode 100644 crates/typos-cli/tests/cmd/stdin-paths0.in/b.fail
 create mode 100644 crates/typos-cli/tests/cmd/stdin-paths0.in/c.fail
 create mode 100644 crates/typos-cli/tests/cmd/stdin-paths0.in/d.fail

diff --git a/crates/typos-cli/src/bin/typos-cli/args.rs b/crates/typos-cli/src/bin/typos-cli/args.rs
index 6ccbc69c6..103ad36a0 100644
--- a/crates/typos-cli/src/bin/typos-cli/args.rs
+++ b/crates/typos-cli/src/bin/typos-cli/args.rs
@@ -1,3 +1,5 @@
+use std::path::PathBuf;
+
 use clap::builder::TypedValueParser;
 use clap::Parser;
 
@@ -39,9 +41,13 @@ impl Format {
 #[command(group = clap::ArgGroup::new("mode").multiple(false))]
 pub(crate) struct Args {
     /// Paths to check with `-` for stdin
-    #[arg(default_value = ".")]
+    #[arg(default_value = ".", group = "source")]
     pub(crate) path: Vec<std::path::PathBuf>,
 
+    /// Read the list of newline separated paths from file or stdin (if `-`)
+    #[arg(long, group = "source")]
+    pub(crate) file_list: Option<PathBuf>,
+
     /// The approximate number of threads to use.
     #[arg(short = 'j', long = "threads", default_value = "0")]
     pub(crate) threads: usize,
diff --git a/crates/typos-cli/src/bin/typos-cli/main.rs b/crates/typos-cli/src/bin/typos-cli/main.rs
index 017da7df5..6407597c9 100644
--- a/crates/typos-cli/src/bin/typos-cli/main.rs
+++ b/crates/typos-cli/src/bin/typos-cli/main.rs
@@ -1,4 +1,5 @@
-use std::io::Write;
+use std::io::{BufRead as _, BufReader, Write as _};
+use std::path::PathBuf;
 
 use clap::Parser;
 
@@ -167,8 +168,34 @@ fn run_checks(args: &args::Args) -> proc_exit::ExitResult {
 
     let mut typos_found = false;
     let mut errors_found = false;
-    for path in args.path.iter() {
+
+    let file_list = match args.file_list.as_deref() {
+        Some(dash) if dash == PathBuf::from("-") => Some(
+            std::io::stdin()
+                .lines()
+                .map(|res| res.map(PathBuf::from))
+                .collect::<Result<_, _>>()
+                .with_code(proc_exit::sysexits::IO_ERR)?,
+        ),
+        Some(path) => Some(
+            BufReader::new(std::fs::File::open(path).with_code(proc_exit::sysexits::IO_ERR)?)
+                .lines()
+                .map(|res| res.map(PathBuf::from))
+                .collect::<Result<_, _>>()
+                .with_code(proc_exit::sysexits::IO_ERR)?,
+        ),
+        None => None,
+    };
+
+    // Note: file_list and args.path are mutually exclusive, enforced by clap
+    for path in file_list.as_ref().unwrap_or(&args.path) {
+        // Note paths are passed through stdin, `-` is treated like a normal path
         let cwd = if path == std::path::Path::new("-") {
+            if args.file_list.is_some() {
+                return Err(proc_exit::sysexits::USAGE_ERR.with_message(
+                    "Can't use `-` (stdin) while using `--file_list` provided paths",
+                ));
+            };
             global_cwd.clone()
         } else if path.is_file() {
             let mut cwd = path
diff --git a/crates/typos-cli/tests/cli_tests.rs b/crates/typos-cli/tests/cli_tests.rs
index b81c6d1ff..06a850e1f 100644
--- a/crates/typos-cli/tests/cli_tests.rs
+++ b/crates/typos-cli/tests/cli_tests.rs
@@ -1,5 +1,7 @@
 #[test]
 #[cfg(feature = "dict")]
 fn cli_tests() {
-    trycmd::TestCases::new().case("tests/cmd/*.toml");
+    trycmd::TestCases::new()
+        .case("tests/cmd/*.toml")
+        .case("tests/cmd/*.trycmd");
 }
diff --git a/crates/typos-cli/tests/cmd/help.toml b/crates/typos-cli/tests/cmd/help.toml
index 2396e1f3c..25d4cee42 100644
--- a/crates/typos-cli/tests/cmd/help.toml
+++ b/crates/typos-cli/tests/cmd/help.toml
@@ -9,10 +9,11 @@ Arguments:
   [PATH]...  Paths to check with `-` for stdin [default: .]
 
 Options:
-  -j, --threads <THREADS>  The approximate number of threads to use [default: 0]
-      --force-exclude      Respect excluded files even for paths passed explicitly
-  -h, --help               Print help
-  -V, --version            Print version
+      --file-list <FILE_LIST>  Read the list of newline separated paths from file or stdin (if `-`)
+  -j, --threads <THREADS>      The approximate number of threads to use [default: 0]
+      --force-exclude          Respect excluded files even for paths passed explicitly
+  -h, --help                   Print help
+  -V, --version                Print version
 
 Config:
   -c, --config <CUSTOM_CONFIG>  Custom config file
diff --git a/crates/typos-cli/tests/cmd/stdin-paths.in/_typos.toml b/crates/typos-cli/tests/cmd/stdin-paths.in/_typos.toml
new file mode 100644
index 000000000..82c777b9e
--- /dev/null
+++ b/crates/typos-cli/tests/cmd/stdin-paths.in/_typos.toml
@@ -0,0 +1,8 @@
+[files]
+extend-exclude = ["_typos.toml"]
+
+[default.extend-identifiers]
+hello = "goodbye"
+
+[type.fail]
+extend-glob = ["*.fail"]
\ No newline at end of file
diff --git a/crates/typos-cli/tests/cmd/stdin-paths.in/a.fail b/crates/typos-cli/tests/cmd/stdin-paths.in/a.fail
new file mode 100644
index 000000000..ce0136250
--- /dev/null
+++ b/crates/typos-cli/tests/cmd/stdin-paths.in/a.fail
@@ -0,0 +1 @@
+hello
diff --git a/crates/typos-cli/tests/cmd/stdin-paths.in/b.fail b/crates/typos-cli/tests/cmd/stdin-paths.in/b.fail
new file mode 100644
index 000000000..ce0136250
--- /dev/null
+++ b/crates/typos-cli/tests/cmd/stdin-paths.in/b.fail
@@ -0,0 +1 @@
+hello
diff --git a/crates/typos-cli/tests/cmd/stdin-paths.in/c.fail b/crates/typos-cli/tests/cmd/stdin-paths.in/c.fail
new file mode 100644
index 000000000..ce0136250
--- /dev/null
+++ b/crates/typos-cli/tests/cmd/stdin-paths.in/c.fail
@@ -0,0 +1 @@
+hello
diff --git a/crates/typos-cli/tests/cmd/stdin-paths.in/d.fail b/crates/typos-cli/tests/cmd/stdin-paths.in/d.fail
new file mode 100644
index 000000000..ce0136250
--- /dev/null
+++ b/crates/typos-cli/tests/cmd/stdin-paths.in/d.fail
@@ -0,0 +1 @@
+hello
diff --git a/crates/typos-cli/tests/cmd/stdin-paths.toml b/crates/typos-cli/tests/cmd/stdin-paths.toml
new file mode 100644
index 000000000..108965f96
--- /dev/null
+++ b/crates/typos-cli/tests/cmd/stdin-paths.toml
@@ -0,0 +1,22 @@
+bin.name = "typos"
+args = "--file-list -"
+stdin = """
+b.fail
+d.fail
+"""
+stdout = """
+error: `hello` should be `goodbye`
+  --> b.fail:1:1
+  |
+1 | hello
+  | ^^^^^
+  |
+error: `hello` should be `goodbye`
+  --> d.fail:1:1
+  |
+1 | hello
+  | ^^^^^
+  |
+"""
+stderr = ""
+status.code = 2
diff --git a/crates/typos-cli/tests/cmd/stdin-paths0.in/_typos.toml b/crates/typos-cli/tests/cmd/stdin-paths0.in/_typos.toml
new file mode 100644
index 000000000..82c777b9e
--- /dev/null
+++ b/crates/typos-cli/tests/cmd/stdin-paths0.in/_typos.toml
@@ -0,0 +1,8 @@
+[files]
+extend-exclude = ["_typos.toml"]
+
+[default.extend-identifiers]
+hello = "goodbye"
+
+[type.fail]
+extend-glob = ["*.fail"]
\ No newline at end of file
diff --git a/crates/typos-cli/tests/cmd/stdin-paths0.in/a.fail b/crates/typos-cli/tests/cmd/stdin-paths0.in/a.fail
new file mode 100644
index 000000000..ce0136250
--- /dev/null
+++ b/crates/typos-cli/tests/cmd/stdin-paths0.in/a.fail
@@ -0,0 +1 @@
+hello
diff --git a/crates/typos-cli/tests/cmd/stdin-paths0.in/b.fail b/crates/typos-cli/tests/cmd/stdin-paths0.in/b.fail
new file mode 100644
index 000000000..ce0136250
--- /dev/null
+++ b/crates/typos-cli/tests/cmd/stdin-paths0.in/b.fail
@@ -0,0 +1 @@
+hello
diff --git a/crates/typos-cli/tests/cmd/stdin-paths0.in/c.fail b/crates/typos-cli/tests/cmd/stdin-paths0.in/c.fail
new file mode 100644
index 000000000..ce0136250
--- /dev/null
+++ b/crates/typos-cli/tests/cmd/stdin-paths0.in/c.fail
@@ -0,0 +1 @@
+hello
diff --git a/crates/typos-cli/tests/cmd/stdin-paths0.in/d.fail b/crates/typos-cli/tests/cmd/stdin-paths0.in/d.fail
new file mode 100644
index 000000000..ce0136250
--- /dev/null
+++ b/crates/typos-cli/tests/cmd/stdin-paths0.in/d.fail
@@ -0,0 +1 @@
+hello