rumtk_core/cli.rs
1/*
2 * rumtk attempts to implement HL7 and medical protocols for interoperability in medicine.
3 * This toolkit aims to be reliable, simple, performant, and standards compliant.
4 * Copyright (C) 2025 Luis M. Santos, M.D.
5 * Copyright (C) 2025 MedicalMasses L.L.C.
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22pub mod cli_utils {
23 use crate::core::{RUMResult, RUMVec};
24 use crate::strings::{
25 basic_escape, rumtk_format, EscapeExceptions, RUMString, RUMStringConversions,
26 };
27 use crate::types::{RUMBuffer, RUMCLIParser};
28 use compact_str::CompactStringExt;
29 use std::io::{stdin, stdout, Read, StdinLock, Write};
30 use std::num::NonZeroU16;
31
32 pub const BUFFER_SIZE: usize = 1024 * 4;
33 pub const BUFFER_CHUNK_SIZE: usize = 512;
34
35 pub static CLI_ESCAPE_EXCEPTIONS: EscapeExceptions =
36 &[("\\n", "\n"), ("\\r", "\r"), ("\\\\", "\\")];
37
38 pub type BufferSlice = Vec<u8>;
39 pub type BufferChunk = [u8; BUFFER_CHUNK_SIZE];
40
41 ///
42 /// Example CLI parser that can be used to paste in your binary and adjust as needed.
43 ///
44 /// Note, this is only an example.
45 ///
46 #[derive(RUMCLIParser, Debug)]
47 #[command(author, version, about, long_about = None)]
48 pub struct RUMTKArgs {
49 ///
50 /// For interface crate only. Specifies the ip address to connect to.
51 ///
52 /// In outbound mode, `--ip` and `--port` are required parameters.
53 ///
54 /// In inbound mode, you can omit either or both parameters.
55 ///
56 #[arg(short, long)]
57 ip: Option<RUMString>,
58 ///
59 /// For interface crate only. Specifies the port to connect to.
60 ///
61 /// In outbound mode, `--ip` and `--port` are required parameters.
62 ///
63 /// In inbound mode, you can omit either or both parameters.
64 ///
65 #[arg(short, long)]
66 port: Option<NonZeroU16>,
67 ///
68 /// For process crate only. Specifies command line script to execute on message.
69 ///
70 #[arg(short, long)]
71 x: Option<RUMString>,
72 ///
73 /// Number of processing threads to allocate for this program.
74 ///
75 #[arg(short, long, default_value_t = 1)]
76 threads: usize,
77 ///
78 /// For interface crate only. Specifies if the interface is in outbound mode.
79 ///
80 /// In outbound mode, `--ip` and `--port` are required parameters.
81 ///
82 /// In inbound mode, you can omit either or both parameters.
83 ///
84 #[arg(short, long)]
85 outbound: bool,
86 ///
87 /// Request program runs in debug mode and log more information.
88 ///
89 #[arg(short, long, default_value_t = false)]
90 debug: bool,
91 ///
92 /// Request program runs in dry run mode and simulate as many steps as possible but not commit
93 /// to a critical non-reversible step.
94 ///
95 /// For example, if it was meant to write contents to a file, stop before doing so.
96 ///
97 #[arg(short, long, default_value_t = false)]
98 dry_run: bool,
99 }
100
101 ///
102 /// Consumes the incoming buffer in chunks of [BUFFER_CHUNK_SIZE](BUFFER_CHUNK_SIZE) bytes size
103 /// until no more bytes are present.
104 ///
105 /// ## Example
106 ///
107 /// ```
108 /// use rumtk_core::cli::cli_utils::{read_stdin};
109 ///
110 /// let stdin_data = read_stdin().unwrap();
111 ///
112 /// assert_eq!(stdin_data.len(), 0, "Returned data with {} size even though we expected 0 bytes!", stdin_data.len())
113 /// ```
114 ///
115 pub fn read_stdin() -> RUMResult<RUMBuffer> {
116 let mut stdin_lock = stdin().lock();
117 let mut stdin_buffer = RUMVec::with_capacity(BUFFER_SIZE);
118 let mut s = read_some_stdin(&mut stdin_lock, &mut stdin_buffer)?;
119
120 while s > 0 {
121 s = read_some_stdin(&mut stdin_lock, &mut stdin_buffer)?;
122 }
123
124 Ok(RUMBuffer::from(stdin_buffer))
125 }
126
127 ///
128 /// Consumes the incoming buffer in chunks of [BUFFER_CHUNK_SIZE](BUFFER_CHUNK_SIZE) bytes size.
129 ///
130 /// ## Example
131 ///
132 /// ```
133 /// use std::io::stdin;
134 /// use std::io::prelude::*;
135 /// use std::process::{Command, Stdio};
136 /// use rumtk_core::cli::cli_utils::{read_some_stdin, BUFFER_SIZE, BUFFER_CHUNK_SIZE};
137 ///
138 /// let mut stdin_lock = stdin().lock();
139 /// let mut stdin_buffer: Vec<u8> = Vec::with_capacity(BUFFER_SIZE);
140 /// let mut s = read_some_stdin(&mut stdin_lock, &mut stdin_buffer).unwrap();
141 /// let mut totas_s = s;
142 /// while s > 0 {
143 /// s = read_some_stdin(&mut stdin_lock, &mut stdin_buffer).unwrap();
144 /// totas_s += s;
145 /// }
146 ///
147 /// assert_eq!(totas_s, 0, "Returned data with {} size even though we expected 0 bytes!", totas_s)
148 /// ```
149 ///
150 pub fn read_some_stdin(input: &mut StdinLock, buf: &mut BufferSlice) -> RUMResult<usize> {
151 let mut chunk: BufferChunk = [0; BUFFER_CHUNK_SIZE];
152 match input.read(&mut chunk) {
153 Ok(s) => {
154 if s > 0 {
155 buf.extend_from_slice(&chunk[0..s]);
156 }
157 Ok(s)
158 }
159 Err(e) => Err(rumtk_format!("Error reading stdin chunk because {}!", e)),
160 }
161 }
162
163 ///
164 /// Escapes a string (`stringview`) and writes it to `stdout`.
165 ///
166 pub fn write_string_stdout(data: &str) -> RUMResult<()> {
167 let escaped = basic_escape(data, CLI_ESCAPE_EXCEPTIONS);
168 write_stdout(&escaped.to_buffer())
169 }
170
171 ///
172 /// Writes [RUMBuffer] to `stdout`.
173 ///
174 pub fn write_stdout(data: &RUMBuffer) -> RUMResult<()> {
175 let mut stdout_handle = stdout();
176 match stdout_handle.write_all(data.as_slice()) {
177 Ok(_) => match stdout_handle.flush() {
178 Ok(_) => Ok(()),
179 Err(e) => Err(rumtk_format!("Error flushing stdout: {}", e)),
180 },
181 Err(e) => Err(rumtk_format!("Error writing to stdout!")),
182 }
183 }
184
185 pub fn print_license_notice(program: &str, year: &str, author_list: &Vec<&str>) {
186 let authors = author_list.join_compact(", ");
187 let notice = rumtk_format!(
188 " {program} Copyright (C) {year} {authors}
189 This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
190 This is free software, and you are welcome to redistribute it
191 under certain conditions; type `show c' for details."
192 );
193 println!("{}", notice);
194 }
195}
196
197pub mod macros {
198 ///
199 /// Reads STDIN and unescapes the incoming message.
200 /// Return this unescaped message.
201 ///
202 /// # Example
203 /// ```
204 /// use rumtk_core::core::RUMResult;
205 /// use rumtk_core::types::RUMBuffer;
206 /// use rumtk_core::rumtk_read_stdin;
207 ///
208 /// fn test_read_stdin() -> RUMResult<RUMBuffer> {
209 /// rumtk_read_stdin!()
210 /// }
211 ///
212 /// match test_read_stdin() {
213 /// Ok(s) => (),
214 /// Err(e) => panic!("Error reading stdin because => {}", e)
215 /// }
216 /// ```
217 ///
218 #[macro_export]
219 macro_rules! rumtk_read_stdin {
220 ( ) => {{
221 use $crate::cli::cli_utils::read_stdin;
222 read_stdin()
223 }};
224 }
225
226 ///
227 /// Writes [RUMString](crate::strings::RUMString) or [RUMBuffer](crate::types::RUMBuffer) to `stdout`.
228 ///
229 /// If the `binary` parameter is omitted, we take a [RUMString](crate::strings::RUMString), escape it
230 /// while preserving [CLI_ESCAPE_EXCEPTIONS](crate::cli::cli_utils::CLI_ESCAPE_EXCEPTIONS) characters,
231 /// and finally write it out as a [RUMBuffer](crate::types::RUMBuffer) to `stdout`.
232 ///
233 /// If the `binary` parameter is passed, we push the `message` parameter directly to `stdout`. the
234 /// `message` parameter has to be of type [RUMBuffer](crate::types::RUMBuffer).
235 ///
236 /// ## Example
237 ///
238 /// ### Default / Pushing a String
239 /// ```
240 /// use rumtk_core::rumtk_write_stdout;
241 ///
242 /// rumtk_write_stdout!("I ❤ my wife!");
243 /// ```
244 ///
245 /// ## Pushing Binary Buffer
246 /// ```
247 /// use rumtk_core::rumtk_write_stdout;
248 /// use rumtk_core::core::new_random_buffer;
249 ///
250 /// let buffer = new_random_buffer();
251 /// rumtk_write_stdout!(buffer, true);
252 /// ```
253 ///
254 #[macro_export]
255 macro_rules! rumtk_write_stdout {
256 ( $message:expr ) => {{
257 use $crate::cli::cli_utils::write_string_stdout;
258 write_string_stdout(&$message)
259 }};
260 ( $message:expr, $binary:expr ) => {{
261 use $crate::cli::cli_utils::write_stdout;
262 write_stdout(&$message)
263 }};
264 }
265
266 ///
267 /// Prints the mandatory GPL License Notice to terminal!
268 ///
269 /// # Example
270 /// ## Default
271 /// ```
272 /// use rumtk_core::rumtk_print_license_notice;
273 ///
274 /// rumtk_print_license_notice!();
275 /// ```
276 /// ## Program Only
277 /// ```
278 /// use rumtk_core::rumtk_print_license_notice;
279 ///
280 /// rumtk_print_license_notice!("RUMTK");
281 /// ```
282 /// ## Program + Year
283 /// ```
284 /// use rumtk_core::rumtk_print_license_notice;
285 ///
286 /// rumtk_print_license_notice!("RUMTK", "2025");
287 /// ```
288 /// ## Program + Year + Authors
289 /// ```
290 /// use rumtk_core::rumtk_print_license_notice;
291 ///
292 /// rumtk_print_license_notice!("RUMTK", "2025", &vec!["Luis M. Santos, M.D."]);
293 /// ```
294 ///
295 #[macro_export]
296 macro_rules! rumtk_print_license_notice {
297 ( ) => {{
298 use $crate::cli::cli_utils::print_license_notice;
299
300 print_license_notice("RUMTK", "2025", &vec!["Luis M. Santos, M.D."]);
301 }};
302 ( $program:expr ) => {{
303 use $crate::cli::cli_utils::print_license_notice;
304 print_license_notice(&$program, "2025", &vec!["2025", "Luis M. Santos, M.D."]);
305 }};
306 ( $program:expr, $year:expr ) => {{
307 use $crate::cli::cli_utils::print_license_notice;
308 print_license_notice(&$program, &$year, &vec!["Luis M. Santos, M.D."]);
309 }};
310 ( $program:expr, $year:expr, $authors:expr ) => {{
311 use $crate::cli::cli_utils::print_license_notice;
312 print_license_notice(&$program, &$year, &$authors);
313 }};
314 }
315}