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