mirror of
https://github.com/eza-community/eza
synced 2024-09-30 05:06:30 +00:00
feat(input): adding piping of args
fix: lifetime issue fix: try to fix lifetime issues for pipe from stdin
This commit is contained in:
parent
2a63c93a63
commit
0889f23919
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -36,3 +36,6 @@ tests/tmp
|
||||||
|
|
||||||
## Dynamically generated
|
## Dynamically generated
|
||||||
tests/test_dir
|
tests/test_dir
|
||||||
|
|
||||||
|
# Miscenallous
|
||||||
|
.idea
|
||||||
|
|
|
@ -142,6 +142,7 @@ These options are available when running with `--long` (`-l`):
|
||||||
- **--no-filesize**: suppress the filesize field
|
- **--no-filesize**: suppress the filesize field
|
||||||
- **--no-user**: suppress the user 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
|
||||||
|
|
||||||
Some of the options accept parameters:
|
Some of the options accept parameters:
|
||||||
|
|
||||||
|
|
|
@ -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-user -d "Suppress the user field"
|
||||||
complete -c eza -l no-time -d "Suppress the time 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 -s M -l mounts -d "Show mount details"
|
||||||
|
complete -c eza -l stdin -d "Read file names from standard input"
|
||||||
|
|
||||||
# Optional extras
|
# Optional extras
|
||||||
complete -c eza -l git -d "List each file's Git status, if tracked"
|
complete -c eza -l git -d "List each file's Git status, if tracked"
|
||||||
|
|
|
@ -58,4 +58,5 @@ export extern "eza" [
|
||||||
--extended(-@) # List each file's extended attributes and sizes
|
--extended(-@) # List each file's extended attributes and sizes
|
||||||
--context(-Z) # List each file's security context
|
--context(-Z) # List each file's security context
|
||||||
--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 paths from stdin
|
||||||
]
|
]
|
||||||
|
|
|
@ -67,7 +67,8 @@ __eza() {
|
||||||
{-Z,--context}"[List each file's security context]" \
|
{-Z,--context}"[List each file's security context]" \
|
||||||
{-M,--mounts}"[Show mount details (long mode only)]" \
|
{-M,--mounts}"[Show mount details (long mode only)]" \
|
||||||
'*:filename:_files' \
|
'*: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
|
__eza
|
||||||
|
|
|
@ -237,6 +237,9 @@ These options are available when running with `--long` (`-l`):
|
||||||
`--no-time`
|
`--no-time`
|
||||||
: Suppress the time field.
|
: Suppress the time field.
|
||||||
|
|
||||||
|
`--stdin`
|
||||||
|
: read file names from stdin, one per line or other separator specified in environment
|
||||||
|
|
||||||
`-@`, `--extended`
|
`-@`, `--extended`
|
||||||
: List each file’s extended attributes and sizes.
|
: 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.
|
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
|
EXIT STATUSES
|
||||||
=============
|
=============
|
||||||
|
|
24
src/main.rs
24
src/main.rs
|
@ -23,20 +23,20 @@
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::ffi::{OsStr, OsString};
|
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::path::{Component, PathBuf};
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
|
||||||
use ansiterm::{ANSIStrings, Style};
|
use ansiterm::{ANSIStrings, Style};
|
||||||
|
|
||||||
use log::*;
|
|
||||||
|
|
||||||
use crate::fs::feature::git::GitCache;
|
use crate::fs::feature::git::GitCache;
|
||||||
use crate::fs::filter::GitIgnore;
|
use crate::fs::filter::GitIgnore;
|
||||||
use crate::fs::{Dir, File};
|
use crate::fs::{Dir, File};
|
||||||
|
use crate::options::stdin::FilesInput;
|
||||||
use crate::options::{vars, Options, OptionsResult, Vars};
|
use crate::options::{vars, Options, OptionsResult, Vars};
|
||||||
use crate::output::{details, escape, file_name, grid, grid_details, lines, Mode, View};
|
use crate::output::{details, escape, file_name, grid, grid_details, lines, Mode, View};
|
||||||
use crate::theme::Theme;
|
use crate::theme::Theme;
|
||||||
|
use log::*;
|
||||||
|
|
||||||
mod fs;
|
mod fs;
|
||||||
mod info;
|
mod info;
|
||||||
|
@ -60,13 +60,29 @@ fn main() {
|
||||||
|
|
||||||
let stdout_istty = io::stdout().is_terminal();
|
let stdout_istty = io::stdout().is_terminal();
|
||||||
|
|
||||||
|
let mut input = String::new();
|
||||||
let args: Vec<_> = env::args_os().skip(1).collect();
|
let args: Vec<_> = env::args_os().skip(1).collect();
|
||||||
match Options::parse(args.iter().map(std::convert::AsRef::as_ref), &LiveVars) {
|
match Options::parse(args.iter().map(std::convert::AsRef::as_ref), &LiveVars) {
|
||||||
OptionsResult::Ok(options, mut input_paths) => {
|
OptionsResult::Ok(options, mut input_paths) => {
|
||||||
// List the current directory by default.
|
// List the current directory by default.
|
||||||
// (This has to be done here, otherwise git_options won’t see it.)
|
// (This has to be done here, otherwise git_options won’t see it.)
|
||||||
if input_paths.is_empty() {
|
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);
|
let git = git_options(&options, &input_paths);
|
||||||
|
|
|
@ -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 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 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 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(&[
|
pub static ALL_ARGS: Args = Args(&[
|
||||||
&VERSION, &HELP,
|
&VERSION, &HELP,
|
||||||
|
@ -96,5 +97,5 @@ pub static ALL_ARGS: Args = Args(&[
|
||||||
&NO_PERMISSIONS, &NO_FILESIZE, &NO_USER, &NO_TIME, &SMART_GROUP,
|
&NO_PERMISSIONS, &NO_FILESIZE, &NO_USER, &NO_TIME, &SMART_GROUP,
|
||||||
|
|
||||||
&GIT, &NO_GIT, &GIT_REPOS, &GIT_REPOS_NO_STAT,
|
&GIT, &NO_GIT, &GIT_REPOS, &GIT_REPOS_NO_STAT,
|
||||||
&EXTENDED, &OCTAL, &SECURITY_CONTEXT
|
&EXTENDED, &OCTAL, &SECURITY_CONTEXT, &STDIN,
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -75,7 +75,8 @@ LONG VIEW OPTIONS
|
||||||
-o, --octal-permissions list each file's permission in octal format
|
-o, --octal-permissions list each file's permission in octal format
|
||||||
--no-filesize suppress the filesize field
|
--no-filesize suppress the filesize field
|
||||||
--no-user suppress the user 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 = " \
|
static GIT_VIEW_HELP: &str = " \
|
||||||
--git list each file's Git status, if tracked or ignored
|
--git list each file's Git status, if tracked or ignored
|
||||||
|
|
|
@ -72,6 +72,7 @@ use std::ffi::OsStr;
|
||||||
|
|
||||||
use crate::fs::dir_action::DirAction;
|
use crate::fs::dir_action::DirAction;
|
||||||
use crate::fs::filter::{FileFilter, GitIgnore};
|
use crate::fs::filter::{FileFilter, GitIgnore};
|
||||||
|
use crate::options::stdin::FilesInput;
|
||||||
use crate::output::{details, grid_details, Mode, View};
|
use crate::output::{details, grid_details, Mode, View};
|
||||||
use crate::theme::Options as ThemeOptions;
|
use crate::theme::Options as ThemeOptions;
|
||||||
|
|
||||||
|
@ -95,7 +96,9 @@ use self::parser::MatchedFlags;
|
||||||
pub mod vars;
|
pub mod vars;
|
||||||
pub use self::vars::Vars;
|
pub use self::vars::Vars;
|
||||||
|
|
||||||
|
pub mod stdin;
|
||||||
mod version;
|
mod version;
|
||||||
|
|
||||||
use self::version::VersionString;
|
use self::version::VersionString;
|
||||||
|
|
||||||
/// These **options** represent a parsed, error-checked versions of the
|
/// 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.
|
/// The options to make up the styles of the UI and file names.
|
||||||
pub theme: ThemeOptions,
|
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
|
/// Parse the given iterator of command-line strings into an Options
|
||||||
/// struct and a list of free filenames, using the environment variables
|
/// struct and a list of free filenames, using the environment variables
|
||||||
/// for extra options.
|
/// for extra options.
|
||||||
#[allow(unused_results)]
|
#[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
|
where
|
||||||
I: IntoIterator<Item = &'args OsStr>,
|
I: IntoIterator<Item = &'args OsStr>,
|
||||||
V: Vars,
|
V: Vars,
|
||||||
|
@ -199,12 +205,14 @@ impl Options {
|
||||||
let dir_action = DirAction::deduce(matches, matches!(view.mode, Mode::Details(_)))?;
|
let dir_action = DirAction::deduce(matches, matches!(view.mode, Mode::Details(_)))?;
|
||||||
let filter = FileFilter::deduce(matches)?;
|
let filter = FileFilter::deduce(matches)?;
|
||||||
let theme = ThemeOptions::deduce(matches, vars)?;
|
let theme = ThemeOptions::deduce(matches, vars)?;
|
||||||
|
let stdin = FilesInput::deduce(matches, vars)?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
dir_action,
|
dir_action,
|
||||||
filter,
|
filter,
|
||||||
view,
|
view,
|
||||||
theme,
|
theme,
|
||||||
|
stdin,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
29
src/options/stdin.rs
Normal file
29
src/options/stdin.rs
Normal file
|
@ -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
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -64,6 +64,8 @@ pub static EZA_MIN_LUMINANCE: &str = "EZA_MIN_LUMINANCE";
|
||||||
/// Any explicit use of `--icons=WHEN` overrides this behavior.
|
/// Any explicit use of `--icons=WHEN` overrides this behavior.
|
||||||
pub static EZA_ICONS_AUTO: &str = "EZA_ICONS_AUTO";
|
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`.
|
/// Mockable wrapper for `std::env::var_os`.
|
||||||
pub trait Vars {
|
pub trait Vars {
|
||||||
fn get(&self, name: &'static str) -> Option<OsString>;
|
fn get(&self, name: &'static str) -> Option<OsString>;
|
||||||
|
|
Loading…
Reference in a new issue