Browse Source

feat(input): adding piping of args
fix: lifetime issue
fix: try to fix lifetime issues for pipe from stdin

MartinFillon 2 years ago
parent
commit
0889f23919

+ 3 - 0
.gitignore

@@ -36,3 +36,6 @@ tests/tmp
 
 ## Dynamically generated
 tests/test_dir
+
+# Miscenallous
+.idea

+ 1 - 0
README.md

@@ -142,6 +142,7 @@ These options are available when running with `--long` (`-l`):
 - **--no-filesize**: suppress the filesize field
 - **--no-user**: suppress the user field
 - **--no-time**: suppress the time field
+- **--stdin**: read file names from stdin
 
 Some of the options accept parameters:
 

+ 1 - 0
completions/fish/eza.fish

@@ -110,6 +110,7 @@ complete -c eza -l no-filesize -d "Suppress the filesize field"
 complete -c eza -l no-user -d "Suppress the user field"
 complete -c eza -l no-time -d "Suppress the time field"
 complete -c eza -s M -l mounts -d "Show mount details"
+complete -c eza -l stdin -d "Read file names from standard input"
 
 # Optional extras
 complete -c eza -l git -d "List each file's Git status, if tracked"

+ 1 - 0
completions/nush/eza.nu

@@ -58,4 +58,5 @@ export extern "eza" [
     --extended(-@)             # List each file's extended attributes and sizes
     --context(-Z)              # List each file's security context
     --smart-group              # Only show group if it has a different name from owner
+    --stdin                    # Read file paths from stdin
 ]

+ 2 - 1
completions/zsh/_eza

@@ -67,7 +67,8 @@ __eza() {
         {-Z,--context}"[List each file's security context]" \
         {-M,--mounts}"[Show mount details (long mode only)]" \
         '*:filename:_files' \
-        --smart-group"[Only show group if it has a different name from owner]"
+        --smart-group"[Only show group if it has a different name from owner]" \
+        --stdin"[Read file names from stdin]"
 }
 
 __eza

+ 6 - 0
man/eza.1.md

@@ -237,6 +237,9 @@ These options are available when running with `--long` (`-l`):
 `--no-time`
 : Suppress the time field.
 
+`--stdin`
+: read file names from stdin, one per line or other separator specified in environment
+
 `-@`, `--extended`
 : List each file’s extended attributes and sizes.
 
@@ -323,6 +326,9 @@ If set, automates the same behavior as using `--icons` or `--icons=auto`. Useful
 
 Any explicit use of the `--icons=WHEN` flag overrides this behavior. 
 
+## `EZA_STDIN_SEPARATOR`
+
+Specifies the separator to use when reading file names from stdin. Defaults to newline.
 
 EXIT STATUSES
 =============

+ 20 - 4
src/main.rs

@@ -23,20 +23,20 @@
 
 use std::env;
 use std::ffi::{OsStr, OsString};
-use std::io::{self, ErrorKind, IsTerminal, Write};
+use std::io::{self, stdin, ErrorKind, IsTerminal, Read, Write};
 use std::path::{Component, PathBuf};
 use std::process::exit;
 
 use ansiterm::{ANSIStrings, Style};
 
-use log::*;
-
 use crate::fs::feature::git::GitCache;
 use crate::fs::filter::GitIgnore;
 use crate::fs::{Dir, File};
+use crate::options::stdin::FilesInput;
 use crate::options::{vars, Options, OptionsResult, Vars};
 use crate::output::{details, escape, file_name, grid, grid_details, lines, Mode, View};
 use crate::theme::Theme;
+use log::*;
 
 mod fs;
 mod info;
@@ -60,13 +60,29 @@ fn main() {
 
     let stdout_istty = io::stdout().is_terminal();
 
+    let mut input = String::new();
     let args: Vec<_> = env::args_os().skip(1).collect();
     match Options::parse(args.iter().map(std::convert::AsRef::as_ref), &LiveVars) {
         OptionsResult::Ok(options, mut input_paths) => {
             // List the current directory by default.
             // (This has to be done here, otherwise git_options won’t see it.)
             if input_paths.is_empty() {
-                input_paths = vec![OsStr::new(".")];
+                match &options.stdin {
+                    FilesInput::Args => {
+                        input_paths = vec![OsStr::new(".")];
+                    }
+                    FilesInput::Stdin(separator) => {
+                        stdin()
+                            .read_to_string(&mut input)
+                            .expect("Failed to read from stdin");
+                        input_paths.extend(
+                            input
+                                .split(&separator.clone().into_string().unwrap_or("\n".to_string()))
+                                .map(std::ffi::OsStr::new).filter(|s| !s.is_empty())
+                                .collect::<Vec<_>>(),
+                        );
+                    }
+                }
             }
 
             let git = git_options(&options, &input_paths);

+ 2 - 1
src/options/flags.rs

@@ -80,6 +80,7 @@ pub static GIT_REPOS_NO_STAT: Arg = Arg { short: None,       long: "git-repos-no
 pub static EXTENDED:          Arg = Arg { short: Some(b'@'), long: "extended",             takes_value: TakesValue::Forbidden };
 pub static OCTAL:             Arg = Arg { short: Some(b'o'), long: "octal-permissions",    takes_value: TakesValue::Forbidden };
 pub static SECURITY_CONTEXT:  Arg = Arg { short: Some(b'Z'), long: "context",              takes_value: TakesValue::Forbidden };
+pub static STDIN:             Arg = Arg { short: None,       long: "stdin",                takes_value: TakesValue::Forbidden };
 
 pub static ALL_ARGS: Args = Args(&[
     &VERSION, &HELP,
@@ -96,5 +97,5 @@ pub static ALL_ARGS: Args = Args(&[
     &NO_PERMISSIONS, &NO_FILESIZE, &NO_USER, &NO_TIME, &SMART_GROUP,
 
     &GIT, &NO_GIT, &GIT_REPOS, &GIT_REPOS_NO_STAT,
-    &EXTENDED, &OCTAL, &SECURITY_CONTEXT
+    &EXTENDED, &OCTAL, &SECURITY_CONTEXT, &STDIN,
 ]);

+ 2 - 1
src/options/help.rs

@@ -75,7 +75,8 @@ LONG VIEW OPTIONS
   -o, --octal-permissions    list each file's permission in octal format
   --no-filesize              suppress the filesize field
   --no-user                  suppress the user field
-  --no-time                  suppress the time field";
+  --no-time                  suppress the time field
+  --stdin                    read file names from stdin, one per line or other separator specified in environment";
 
 static GIT_VIEW_HELP: &str = "  \
   --git                      list each file's Git status, if tracked or ignored

+ 10 - 2
src/options/mod.rs

@@ -72,6 +72,7 @@ use std::ffi::OsStr;
 
 use crate::fs::dir_action::DirAction;
 use crate::fs::filter::{FileFilter, GitIgnore};
+use crate::options::stdin::FilesInput;
 use crate::output::{details, grid_details, Mode, View};
 use crate::theme::Options as ThemeOptions;
 
@@ -95,7 +96,9 @@ use self::parser::MatchedFlags;
 pub mod vars;
 pub use self::vars::Vars;
 
+pub mod stdin;
 mod version;
+
 use self::version::VersionString;
 
 /// These **options** represent a parsed, error-checked versions of the
@@ -117,14 +120,17 @@ pub struct Options {
 
     /// The options to make up the styles of the UI and file names.
     pub theme: ThemeOptions,
+
+    /// Whether to read file names from stdin instead of the command-line
+    pub stdin: FilesInput,
 }
 
-impl Options {
+impl<'args> Options {
     /// Parse the given iterator of command-line strings into an Options
     /// struct and a list of free filenames, using the environment variables
     /// for extra options.
     #[allow(unused_results)]
-    pub fn parse<'args, I, V>(args: I, vars: &V) -> OptionsResult<'args>
+    pub fn parse<I, V>(args: I, vars: &V) -> OptionsResult<'args>
     where
         I: IntoIterator<Item = &'args OsStr>,
         V: Vars,
@@ -199,12 +205,14 @@ impl Options {
         let dir_action = DirAction::deduce(matches, matches!(view.mode, Mode::Details(_)))?;
         let filter = FileFilter::deduce(matches)?;
         let theme = ThemeOptions::deduce(matches, vars)?;
+        let stdin = FilesInput::deduce(matches, vars)?;
 
         Ok(Self {
             dir_action,
             filter,
             view,
             theme,
+            stdin,
         })
     }
 }

+ 29 - 0
src/options/stdin.rs

@@ -0,0 +1,29 @@
+use crate::options::parser::MatchedFlags;
+use crate::options::vars::EZA_STDIN_SEPARATOR;
+use crate::options::{flags, OptionsError, Vars};
+use std::ffi::OsString;
+use std::io;
+use std::io::IsTerminal;
+
+#[derive(Debug, PartialEq)]
+pub enum FilesInput {
+    Stdin(OsString),
+    Args,
+}
+
+impl FilesInput {
+    pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
+        Ok(
+            if io::stdin().is_terminal() || !matches.has(&flags::STDIN)? {
+                FilesInput::Args
+            } else if matches.has(&flags::STDIN)? && !io::stdin().is_terminal() {
+                let separator = vars
+                    .get(EZA_STDIN_SEPARATOR)
+                    .unwrap_or(OsString::from("\n"));
+                FilesInput::Stdin(separator)
+            } else {
+                FilesInput::Args
+            },
+        )
+    }
+}

+ 2 - 0
src/options/vars.rs

@@ -64,6 +64,8 @@ pub static EZA_MIN_LUMINANCE: &str = "EZA_MIN_LUMINANCE";
 /// Any explicit use of `--icons=WHEN` overrides this behavior.
 pub static EZA_ICONS_AUTO: &str = "EZA_ICONS_AUTO";
 
+pub static EZA_STDIN_SEPARATOR: &str = "EZA_STDIN_SEPARATOR";
+
 /// Mockable wrapper for `std::env::var_os`.
 pub trait Vars {
     fn get(&self, name: &'static str) -> Option<OsString>;