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}