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::{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}