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::{rumtk_format, EscapeExceptions, RUMString};
25 use crate::types::{RUMBuffer, RUMCLIParser};
26 use compact_str::CompactStringExt;
27 use std::io::{stdin, stdout, Read, StdinLock, Write};
28 use std::num::NonZeroU16;
29
30 pub const BUFFER_SIZE: usize = 1024 * 4;
31 pub const BUFFER_CHUNK_SIZE: usize = 512;
32
33 pub static CLI_ESCAPE_EXCEPTIONS: EscapeExceptions =
34 &[("\\n", "\n"), ("\\r", "\r"), ("\\\\", "\\")];
35
36 pub type BufferSlice = Vec<u8>;
37 pub type BufferChunk = [u8; BUFFER_CHUNK_SIZE];
38
39 ///
40 /// Example CLI parser that can be used to paste in your binary and adjust as needed.
41 ///
42 /// Note, this is only an example.
43 ///
44 #[derive(RUMCLIParser, Debug)]
45 #[command(author, version, about, long_about = None)]
46 pub struct RUMTKArgs {
47 ///
48 /// For interface crate only. Specifies the ip address to connect to.
49 ///
50 /// In outbound mode, `--ip` and `--port` are required parameters.
51 ///
52 /// In inbound mode, you can omit either or both parameters.
53 ///
54 #[arg(short, long)]
55 ip: Option<RUMString>,
56 ///
57 /// For interface crate only. Specifies the port to connect to.
58 ///
59 /// In outbound mode, `--ip` and `--port` are required parameters.
60 ///
61 /// In inbound mode, you can omit either or both parameters.
62 ///
63 #[arg(short, long)]
64 port: Option<NonZeroU16>,
65 ///
66 /// For process crate only. Specifies command line script to execute on message.
67 ///
68 #[arg(short, long)]
69 x: Option<RUMString>,
70 ///
71 /// Number of processing threads to allocate for this program.
72 ///
73 #[arg(short, long, default_value_t = 1)]
74 threads: usize,
75 ///
76 /// For interface crate only. Specifies if the interface is in outbound mode.
77 ///
78 /// In outbound mode, `--ip` and `--port` are required parameters.
79 ///
80 /// In inbound mode, you can omit either or both parameters.
81 ///
82 #[arg(short, long)]
83 outbound: bool,
84 ///
85 /// Request program runs in debug mode and log more information.
86 ///
87 #[arg(short, long, default_value_t = false)]
88 debug: bool,
89 ///
90 /// Request program runs in dry run mode and simulate as many steps as possible but not commit
91 /// to a critical non-reversible step.
92 ///
93 /// For example, if it was meant to write contents to a file, stop before doing so.
94 ///
95 #[arg(short, long, default_value_t = false)]
96 dry_run: bool,
97 }
98
99 ///
100 /// Consumes the incoming buffer in chunks of [BUFFER_CHUNK_SIZE](BUFFER_CHUNK_SIZE) bytes size
101 /// until no more bytes are present.
102 ///
103 /// ## Example
104 ///
105 /// ```
106 /// use rumtk_core::cli::cli_utils::{read_stdin};
107 ///
108 /// let stdin_data = read_stdin().unwrap();
109 ///
110 /// assert_eq!(stdin_data.len(), 0, "Returned data with {} size even though we expected 0 bytes!", stdin_data.len())
111 /// ```
112 ///
113 pub fn read_stdin() -> RUMResult<RUMBuffer> {
114 let mut stdin_lock = stdin().lock();
115 let mut stdin_buffer = RUMVec::with_capacity(BUFFER_SIZE);
116 let mut s = read_some_stdin(&mut stdin_lock, &mut stdin_buffer)?;
117
118 while s > 0 {
119 s = read_some_stdin(&mut stdin_lock, &mut stdin_buffer)?;
120 }
121
122 Ok(RUMBuffer::from(stdin_buffer))
123 }
124
125 ///
126 /// Consumes the incoming buffer in chunks of [BUFFER_CHUNK_SIZE](BUFFER_CHUNK_SIZE) bytes size.
127 ///
128 /// ## Example
129 ///
130 /// ```
131 /// use std::io::stdin;
132 /// use std::io::prelude::*;
133 /// use std::process::{Command, Stdio};
134 /// use rumtk_core::cli::cli_utils::{read_some_stdin, BUFFER_SIZE, BUFFER_CHUNK_SIZE};
135 ///
136 /// let mut stdin_lock = stdin().lock();
137 /// let mut stdin_buffer: Vec<u8> = Vec::with_capacity(BUFFER_SIZE);
138 /// let mut s = read_some_stdin(&mut stdin_lock, &mut stdin_buffer).unwrap();
139 /// let mut totas_s = s;
140 /// while s > 0 {
141 /// s = read_some_stdin(&mut stdin_lock, &mut stdin_buffer).unwrap();
142 /// totas_s += s;
143 /// }
144 ///
145 /// assert_eq!(totas_s, 0, "Returned data with {} size even though we expected 0 bytes!", totas_s)
146 /// ```
147 ///
148 pub fn read_some_stdin(input: &mut StdinLock, buf: &mut BufferSlice) -> RUMResult<usize> {
149 let mut chunk: BufferChunk = [0; BUFFER_CHUNK_SIZE];
150 match input.read(&mut chunk) {
151 Ok(s) => {
152 if s > 0 {
153 buf.extend_from_slice(&chunk[0..s]);
154 }
155 Ok(s)
156 }
157 Err(e) => Err(rumtk_format!("Error reading stdin chunk because {}!", e)),
158 }
159 }
160
161 //TODO: Turn into a RUMBuffer for future tools.
162 pub fn write_stdout(data: &RUMString) -> RUMResult<()> {
163 let mut stdout_handle = stdout();
164 match stdout_handle.write_all(data.as_bytes()) {
165 Ok(_) => match stdout_handle.flush() {
166 Ok(_) => Ok(()),
167 Err(e) => Err(rumtk_format!("Error flushing stdout: {}", e)),
168 },
169 Err(e) => Err(rumtk_format!("Error writing to stdout!")),
170 }
171 }
172
173 pub fn print_license_notice(program: &str, year: &str, author_list: &Vec<&str>) {
174 let authors = author_list.join_compact(", ");
175 let notice = rumtk_format!(
176 " {program} Copyright (C) {year} {authors}
177 This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
178 This is free software, and you are welcome to redistribute it
179 under certain conditions; type `show c' for details."
180 );
181 println!("{}", notice);
182 }
183}
184
185pub mod macros {
186 ///
187 /// Reads STDIN and unescapes the incoming message.
188 /// Return this unescaped message.
189 ///
190 /// # Example
191 /// ```
192 /// use rumtk_core::core::RUMResult;
193 /// use rumtk_core::types::RUMBuffer;
194 /// use rumtk_core::rumtk_read_stdin;
195 ///
196 /// fn test_read_stdin() -> RUMResult<RUMBuffer> {
197 /// rumtk_read_stdin!()
198 /// }
199 ///
200 /// match test_read_stdin() {
201 /// Ok(s) => (),
202 /// Err(e) => panic!("Error reading stdin because => {}", e)
203 /// }
204 /// ```
205 ///
206 #[macro_export]
207 macro_rules! rumtk_read_stdin {
208 ( ) => {{
209 use $crate::cli::cli_utils::read_stdin;
210 read_stdin()
211 }};
212 }
213
214 ///
215 /// Escapes a message and writes it to stdout via the print! macro.
216 ///
217 /// # Example
218 /// ```
219 /// use rumtk_core::rumtk_write_stdout;
220 ///
221 /// rumtk_write_stdout!("I ❤ my wife!");
222 /// ```
223 ///
224 #[macro_export]
225 macro_rules! rumtk_write_stdout {
226 ( $message:expr ) => {{
227 use $crate::cli::cli_utils::{write_stdout, CLI_ESCAPE_EXCEPTIONS};
228 use $crate::strings::basic_escape;
229 let escaped_message = basic_escape($message, CLI_ESCAPE_EXCEPTIONS);
230 write_stdout(&escaped_message);
231 }};
232 }
233
234 ///
235 /// Prints the mandatory GPL License Notice to terminal!
236 ///
237 /// # Example
238 /// ## Default
239 /// ```
240 /// use rumtk_core::rumtk_print_license_notice;
241 ///
242 /// rumtk_print_license_notice!();
243 /// ```
244 /// ## Program Only
245 /// ```
246 /// use rumtk_core::rumtk_print_license_notice;
247 ///
248 /// rumtk_print_license_notice!("RUMTK");
249 /// ```
250 /// ## Program + Year
251 /// ```
252 /// use rumtk_core::rumtk_print_license_notice;
253 ///
254 /// rumtk_print_license_notice!("RUMTK", "2025");
255 /// ```
256 /// ## Program + Year + Authors
257 /// ```
258 /// use rumtk_core::rumtk_print_license_notice;
259 ///
260 /// rumtk_print_license_notice!("RUMTK", "2025", &vec!["Luis M. Santos, M.D."]);
261 /// ```
262 ///
263 #[macro_export]
264 macro_rules! rumtk_print_license_notice {
265 ( ) => {{
266 use $crate::cli::cli_utils::print_license_notice;
267
268 print_license_notice("RUMTK", "2025", &vec!["Luis M. Santos, M.D."]);
269 }};
270 ( $program:expr ) => {{
271 use $crate::cli::cli_utils::print_license_notice;
272 print_license_notice(&$program, "2025", &vec!["2025", "Luis M. Santos, M.D."]);
273 }};
274 ( $program:expr, $year:expr ) => {{
275 use $crate::cli::cli_utils::print_license_notice;
276 print_license_notice(&$program, &$year, &vec!["Luis M. Santos, M.D."]);
277 }};
278 ( $program:expr, $year:expr, $authors:expr ) => {{
279 use $crate::cli::cli_utils::print_license_notice;
280 print_license_notice(&$program, &$year, &$authors);
281 }};
282 }
283}