aboutsummaryrefslogtreecommitdiff
path: root/mingling/src/setups/stdin_args.rs
blob: ca55ca7d2128192c1e6f66b2f1be2744f1a6f6c2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
use std::io::{IsTerminal, Read};

use mingling_core::{
    Program, ProgramCollect, hook::ProgramHook, setup::ProgramSetup, utils::ArgumentSplitter,
};

/// Uses the standard input as arguments for the program
///
/// This Setup can take standard input supplied via a pipe or redirect,
/// split it according to whitespace and quoting rules, and append
/// the resulting arguments to the end of the command argument list.
///
/// # Usage
///
/// This Setup can be registered using the
/// [`Program`](https://docs.rs/mingling/latest/mingling/struct.Program.html)
/// `with_setup` method, for example:
///
/// ```rust
/// # use mingling::MockProgramCollect as ThisProgram;
/// use mingling::Program;
/// use mingling::setup::StandardInputArgsSetup;
///
/// let mut program = Program::<ThisProgram>::new();
/// program.with_setup(StandardInputArgsSetup);
/// ```
///
/// # Behavior
///
/// - Standard input is only read when it is not a terminal (i.e., when
///   there is piped or redirected input).
/// - The read content is split into multiple arguments according to
///   whitespace and quoting rules.
/// - If the standard input content is empty, no arguments are produced.
/// - All input is converted to UTF-8 encoding (lossy conversion is used
///   when strict parsing is not possible).
///
/// # Notes
///
/// - This Setup applies uniformly to all subcommands of the entire program
///   and does not provide fine-grained control. If you need different
///   standard input behavior across different subcommands (e.g., some
///   subcommands read stdin while others ignore it), **do not use this Setup**.
/// - This Setup does **not** provide any validation rules. Content provided
///   via standard input is treated as trusted arguments and appended directly.
///   As a result, the input source can also inject arbitrary arguments into
///   the command, so you should be careful when processing untrusted input.
pub struct StandardInputArgsSetup;

impl<C> ProgramSetup<C> for StandardInputArgsSetup
where
    C: ProgramCollect<Enum = C>,
{
    fn setup(self, program: &mut Program<C>) {
        program.with_hook(ProgramHook::empty().on_pre_dispatch(|ctx| {
            let pipe_input = read_stdin();
            if let Some(pipe_input) = pipe_input {
                ctx.arguments.append(&mut pipe_input.trim().split_args());
            }
        }));
    }
}

fn read_stdin() -> Option<String> {
    // Check if stdin is a terminal (no piped input) or has data available
    if std::io::stdin().is_terminal() {
        return None;
    }

    let mut bytes = Vec::new();
    match std::io::stdin().read_to_end(&mut bytes) {
        Ok(_) => {
            if bytes.is_empty() {
                return None;
            }
            // Handle encoding differences, ensure output is always UTF-8.
            // First try strict UTF-8 parsing; fall back to lossy conversion
            Some(String::from_utf8_lossy(&bytes).into_owned())
        }
        Err(_) => None,
    }
}