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}