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}