Skip to main content

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}