multiio/cli/
mod.rs

1//! CLI integration helpers for multiio.
2//!
3//! This module keeps CLI-related types intentionally lightweight so callers can
4//! integrate with any argument parser (clap/sarge/argh/…).
5//!
6//! # Token conventions
7//!
8//! multiio intentionally supports a small set of conventional "special" tokens
9//! for CLI ergonomics:
10//!
11//! - Inputs:
12//!   - `-` or `stdin` => stdin
13//!   - `=<content>` => inline content (in-memory input)
14//!   - `@<path>` => force treating the value as a file path (useful for
15//!     disambiguating reserved tokens)
16//! - Outputs:
17//!   - `-` or `stdout` => stdout
18//!   - `stderr` => stderr
19//!   - `@<path>` => force treating the value as a file path (e.g. `@stderr`)
20//!
21//! # Example
22//!
23//! ```rust,ignore
24//! use multiio::{default_registry, MultiioBuilder};
25//! use multiio::cli::{InputArgs, OutputArgs};
26//!
27//! fn run(inputs: Vec<String>, outputs: Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
28//!     let input = InputArgs::from(inputs);
29//!     let output = OutputArgs::from(outputs);
30//!
31//!     let engine = MultiioBuilder::new(default_registry())
32//!         .with_input_args(&input)
33//!         .with_output_args(&output)
34//!         .build()?;
35//!
36//!     let values: Vec<serde_json::Value> = engine.read_all()?;
37//!     engine.write_all(&values)?;
38//!     Ok(())
39//! }
40//! ```
41
42#[cfg(feature = "sarge")]
43mod sarge;
44
45macro_rules! impls_for {
46    (
47        $name:ident => $type:path
48    ) => {
49        impl Deref for $name {
50            type Target = $type;
51
52            fn deref(&self) -> &Self::Target {
53                &self.0
54            }
55        }
56
57        impl DerefMut for $name {
58            fn deref_mut(&mut self) -> &mut Self::Target {
59                &mut self.0
60            }
61        }
62
63        impl From<$type> for $name {
64            fn from(inputs: $type) -> Self {
65                Self(inputs)
66            }
67        }
68
69        impl From<$name> for $type {
70            fn from(args: $name) -> Self {
71                args.0
72            }
73        }
74    };
75}
76
77/// Common input arguments for CLI applications.
78#[derive(Debug, Clone, Default)]
79pub struct InputArgs(Vec<String>);
80
81impl InputArgs {
82    pub fn new() -> Self {
83        Self::default()
84    }
85
86    pub fn with_input(mut self, path: impl Into<String>) -> Self {
87        self.push(path.into());
88        self
89    }
90
91    pub fn is_stdin(&self) -> bool {
92        self.iter()
93            .any(|s| s == "-" || s.eq_ignore_ascii_case("stdin"))
94    }
95}
96
97#[derive(Debug, Clone, Default)]
98pub struct OutputArgs(Vec<String>);
99
100impl OutputArgs {
101    pub fn new() -> Self {
102        Self::default()
103    }
104
105    pub fn with_output(mut self, path: impl Into<String>) -> Self {
106        self.push(path.into());
107        self
108    }
109
110    /// Check if writing to stdout.
111    pub fn is_stdout(&self) -> bool {
112        self.iter()
113            .any(|s| s == "-" || s.eq_ignore_ascii_case("stdout"))
114    }
115
116    /// Check if writing to stderr.
117    pub fn is_stderr(&self) -> bool {
118        self.iter().any(|s| s.eq_ignore_ascii_case("stderr"))
119    }
120}
121
122impls_for!(InputArgs => Vec<String>);
123impls_for!(OutputArgs => Vec<String>);
124
125use std::ops::{Deref, DerefMut};
126
127use crate::format::FormatKind;
128
129/// Parse a format string into a FormatKind.
130///
131/// Supports common format names and aliases.
132pub fn parse_format(s: &str) -> Option<FormatKind> {
133    s.parse::<FormatKind>().ok()
134}
135
136/// Infer format from file extension.
137pub fn infer_format_from_path(path: &str) -> Option<FormatKind> {
138    let ext = std::path::Path::new(path)
139        .extension()
140        .and_then(|e| e.to_str())?;
141    ext.parse::<FormatKind>().ok()
142}