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