rumtk_core/
lib.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) 2024  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
22//#![feature(unboxed_closures)]
23//#![feature(inherent_associated_types)]
24#![feature(type_alias_impl_trait)]
25#![feature(unboxed_closures)]
26#![feature(buf_read_has_data_left)]
27
28pub mod cache;
29pub mod cli;
30pub mod core;
31pub mod json;
32pub mod log;
33pub mod maths;
34pub mod net;
35pub mod queue;
36pub mod scripting;
37pub mod search;
38pub mod strings;
39pub mod threading;
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44    use crate::cache::RUMCache;
45    use crate::search::rumtk_search::*;
46    use crate::strings::{
47        rumtk_format, RUMArrayConversions, RUMString, RUMStringConversions, StringUtils,
48    };
49    use compact_str::CompactString;
50    use serde::{Deserialize, Deserializer, Serialize, Serializer};
51    use serde_json::to_string;
52    use std::sync::Arc;
53    use tokio::sync::RwLock;
54
55    #[test]
56    fn test_is_escaped_str() {
57        let input = "\r\n\'\\\"";
58        let expected = false;
59        let result = strings::is_escaped_str(input);
60        println!("Input: {} Expected: {} Got: {}", input, expected, result);
61        assert_eq!(
62            expected, result,
63            "Incorrect detection of unescaped string as escaped!!"
64        );
65        println!("Passed!")
66    }
67
68    #[test]
69    fn test_escaping_control() {
70        let input = "\r\n\'\"\\";
71        let expected = "\\r\\n\\'\\\"\\\\";
72        let result = strings::escape(input);
73        println!(
74            "Input: {} Expected: {} Got: {}",
75            input,
76            expected,
77            result.as_str()
78        );
79        assert_eq!(expected, result, "Incorrect string escaping!");
80        println!("Passed!")
81    }
82
83    #[test]
84    fn test_escaping_unicode() {
85        let input = "❤";
86        let expected = "\\u2764";
87        let result = strings::escape(input);
88        println!(
89            "Input: {} Expected: {} Got: {}",
90            input,
91            expected,
92            result.as_str()
93        );
94        assert_eq!(expected, result, "Incorrect string escaping!");
95        println!("Passed!")
96    }
97
98    #[test]
99    fn test_unescaping_unicode() {
100        let input = "❤";
101        let escaped = strings::escape(input);
102        let expected = "❤";
103        let result = RUMString::from_utf8(strings::unescape(escaped.as_str()).unwrap()).unwrap();
104        println!(
105            "Input: {} Expected: {} Got: {}",
106            input,
107            expected,
108            result.as_str()
109        );
110        assert_eq!(expected, result.as_str(), "Incorrect string unescaping!");
111        println!("Passed!")
112    }
113
114    #[test]
115    fn test_unescaping_string() {
116        let input = "I \\u2764 my wife!";
117        let expected = "I ❤ my wife!";
118        let result = strings::unescape_string(input).unwrap();
119        println!(
120            "Input: {} Expected: {} Got: {}",
121            input,
122            expected,
123            result.as_str()
124        );
125        assert_eq!(expected, result.as_str(), "Incorrect string unescaping!");
126        println!("Passed!")
127    }
128
129    #[test]
130    fn test_is_escaped_string() {
131        let input = "I \\u2764 my wife!";
132        let expected = true;
133        let result = strings::is_escaped_str(input);
134        println!("Input: {} Expected: {} Got: {}", input, expected, result);
135        assert_eq!(
136            expected, result,
137            "Escaped string detected as unescaped string!"
138        );
139        println!("Passed!")
140    }
141
142    #[test]
143    fn test_is_unescaped_string() {
144        let input = "I ❤ my wife!";
145        let expected = false;
146        let result = strings::is_escaped_str(input);
147        println!("Input: {} Expected: {} Got: {}", input, expected, result);
148        assert_eq!(
149            expected, result,
150            "Unescaped string detected as escaped string!"
151        );
152        println!("Passed!")
153    }
154
155    #[test]
156    fn test_unique_string() {
157        let input = "I❤mywife!";
158        assert!(input.is_unique(), "String was not detected as unique.");
159    }
160
161    #[test]
162    fn test_non_unique_string() {
163        let input = "I❤❤mywife!";
164        assert!(!input.is_unique(), "String was detected as unique.");
165    }
166
167    #[test]
168    fn test_escaping_string() {
169        let input = "I ❤ my wife!";
170        let expected = "I \\u2764 my wife!";
171        let result = strings::escape(input);
172        println!(
173            "Input: {} Expected: {} Got: {}",
174            input,
175            expected,
176            result.as_str()
177        );
178        assert_eq!(expected, result.as_str(), "Incorrect string escaping!");
179        println!("Passed!")
180    }
181
182    #[test]
183    fn test_autodecode_utf8() {
184        let input = "I ❤ my wife!";
185        let result = strings::try_decode(input.as_bytes());
186        println!(
187            "Input: {} Expected: {} Got: {}",
188            input,
189            input,
190            result.as_str()
191        );
192        assert_eq!(input, result, "Incorrect string decoding!");
193        println!("Passed!")
194    }
195
196    #[test]
197    fn test_autodecode_other() {
198        //TODO: Need an example of other encoding texts.
199        let input = "I ❤ my wife!";
200        let result = input;
201        println!("Input: {} Expected: {} Got: {}", input, input, result);
202        assert_eq!(input, result, "Incorrect string decoding!");
203        println!("Passed!")
204    }
205
206    #[test]
207    fn test_decode() {
208        let input = "I ❤ my wife!";
209        let result = strings::try_decode_with(input.as_bytes(), "utf-8");
210        println!(
211            "Input: {} Expected: {} Got: {}",
212            input,
213            input,
214            result.as_str()
215        );
216        assert_eq!(input, result, "Incorrect string decoding!");
217        println!("Passed!")
218    }
219
220    #[test]
221    fn test_rumcache_insertion() {
222        let mut cache: RUMCache<&str, CompactString> = RUMCache::with_capacity(5);
223        cache.insert("❤", CompactString::from("I ❤ my wife!"));
224        println!("Contents: {:#?}", &cache);
225        assert_eq!(cache.len(), 1, "Incorrect number of items in cache!");
226        println!("Passed!")
227    }
228
229    #[test]
230    fn test_search_string_letters() {
231        let input = "Hello World!";
232        let expr = r"\w";
233        let result = string_search(input, expr, "");
234        let expected: RUMString = RUMString::from("HelloWorld");
235        println!(
236            "Input: {:?} Expected: {:?} Got: {:?}",
237            input, expected, result
238        );
239        assert_eq!(expected, result, "String search results mismatch");
240        println!("Passed!")
241    }
242
243    #[test]
244    fn test_search_string_words() {
245        let input = "Hello World!";
246        let expr = r"\w+";
247        let result = string_search(input, expr, " ");
248        let expected: RUMString = RUMString::from("Hello World");
249        println!(
250            "Input: {:?} Expected: {:?} Got: {:?}",
251            input, expected, result
252        );
253        assert_eq!(expected, result, "String search results mismatch");
254        println!("Passed!")
255    }
256
257    #[test]
258    fn test_search_string_named_groups() {
259        let input = "Hello World!";
260        let expr = r"(?<hello>\w{5}) (?<world>\w{5})";
261        let result = string_search_named_captures(input, expr, "");
262        let expected: RUMString = RUMString::from("World");
263        println!(
264            "Input: {:?} Expected: {:?} Got: {:?}",
265            input, expected, result
266        );
267        assert_eq!(expected, result["world"], "String search results mismatch");
268        println!("Passed!")
269    }
270
271    #[test]
272    fn test_search_string_all_groups() {
273        let input = "Hello World!";
274        let expr = r"(?<hello>\w{5}) (?<world>\w{5})";
275        let result = string_search_all_captures(input, expr, "");
276        let expected: Vec<&str> = vec!["Hello", "World"];
277        println!(
278            "Input: {:?} Expected: {:?} Got: {:?}",
279            input, expected, result
280        );
281        assert_eq!(expected, result, "String search results mismatch");
282        println!("Passed!")
283    }
284
285    ///////////////////////////////////Threading Tests/////////////////////////////////////////////////
286    #[test]
287    fn test_default_num_threads() {
288        use num_cpus;
289        let threads = threading::threading_functions::get_default_system_thread_count();
290        assert_eq!(
291            threads >= num_cpus::get(),
292            true,
293            "Default thread count is incorrect! We got {}, but expected {}!",
294            threads,
295            num_cpus::get()
296        );
297    }
298
299    #[test]
300    fn test_execute_job() {
301        let rt = rumtk_init_threads!();
302        let expected = vec![1, 2, 3];
303        let task_processor = async |args: &SafeTaskArgs<i32>| -> TaskResult<i32> {
304            let owned_args = Arc::clone(args);
305            let lock_future = owned_args.read();
306            let locked_args = lock_future.await;
307            let mut results = TaskItems::<i32>::with_capacity(locked_args.len());
308            print!("Contents: ");
309            for arg in locked_args.iter() {
310                results.push(arg.clone());
311                println!("{} ", &arg);
312            }
313            Ok(results)
314        };
315        let locked_args = RwLock::new(expected.clone());
316        let task_args = SafeTaskArgs::<i32>::new(locked_args);
317        let task_result = rumtk_wait_on_task!(rt, task_processor, &task_args);
318        let result = task_result.unwrap();
319        assert_eq!(&result, &expected, "{}", rumtk_format!("Task processing returned a different result than expected! Expected {:?} \nResults {:?}", &expected, &result));
320    }
321
322    #[test]
323    fn test_execute_job_macros() {
324        let rt = rumtk_init_threads!();
325        let expected = vec![1, 2, 3];
326        let task_processor = async |args: &SafeTaskArgs<i32>| -> TaskResult<i32> {
327            let owned_args = Arc::clone(args);
328            let lock_future = owned_args.read();
329            let locked_args = lock_future.await;
330            let mut results = TaskItems::<i32>::with_capacity(locked_args.len());
331            print!("Contents: ");
332            for arg in locked_args.iter() {
333                results.push(arg.clone());
334                println!("{} ", &arg);
335            }
336            Ok(results)
337        };
338        let task_args = rumtk_create_task_args!(1, 2, 3);
339        let task_result = rumtk_wait_on_task!(rt, task_processor, &task_args);
340        let result = task_result.unwrap();
341        assert_eq!(&result, &expected, "{}", rumtk_format!("Task processing returned a different result than expected! Expected {:?} \nResults {:?}", &expected, &result));
342    }
343
344    #[test]
345    fn test_execute_job_macros_one_line() {
346        let rt = rumtk_init_threads!();
347        let expected = vec![1, 2, 3];
348        let result = rumtk_exec_task!(
349            async |args: &SafeTaskArgs<i32>| -> TaskResult<i32> {
350                let owned_args = Arc::clone(args);
351                let lock_future = owned_args.read();
352                let locked_args = lock_future.await;
353                let mut results = TaskItems::<i32>::with_capacity(locked_args.len());
354                print!("Contents: ");
355                for arg in locked_args.iter() {
356                    results.push(arg.clone());
357                    println!("{} ", &arg);
358                }
359                Ok(results)
360            },
361            vec![1, 2, 3]
362        )
363        .unwrap();
364        assert_eq!(&result, &expected, "{}", rumtk_format!("Task processing returned a different result than expected! Expected {:?} \nResults {:?}", &expected, &result));
365    }
366
367    #[test]
368    fn test_clamp_index_positive_index() {
369        let values = vec![1, 2, 3, 4];
370        let given_index = 3isize;
371        let max_size = values.len() as isize;
372        let index = clamp_index(&given_index, &max_size).unwrap();
373        assert_eq!(
374            index, 3,
375            "Index mismatch! Requested index {} but got {}",
376            &given_index, &index
377        );
378        assert_eq!(
379            values[index], 4,
380            "Value mismatch! Expected {} but got {}",
381            &values[3], &values[index]
382        );
383    }
384
385    #[test]
386    fn test_clamp_index_reverse_index() {
387        let values = vec![1, 2, 3, 4];
388        let given_index = -1isize;
389        let max_size = values.len() as isize;
390        let index = clamp_index(&given_index, &max_size).unwrap();
391        assert_eq!(
392            index, 4,
393            "Index mismatch! Requested index {} but got {}",
394            &given_index, &index
395        );
396        assert_eq!(
397            values[index - 1],
398            4,
399            "Value mismatch! Expected {} but got {}",
400            &values[3],
401            &values[index]
402        );
403    }
404
405    ///////////////////////////////////Queue Tests/////////////////////////////////////////////////
406    use crate::cli::cli_utils::print_license_notice;
407    use crate::core::clamp_index;
408    use crate::net::tcp::LOCALHOST;
409    use crate::threading::thread_primitives::{SafeTaskArgs, TaskItems, TaskResult};
410    use crate::threading::threading_functions::sleep;
411    use queue::queue::*;
412
413    #[test]
414    fn test_queue_data() {
415        let expected = vec![
416            RUMString::from("Hello"),
417            RUMString::from("World!"),
418            RUMString::from("Overcast"),
419            RUMString::from("and"),
420            RUMString::from("Sad"),
421        ];
422        let mut queue = TaskQueue::<RUMString>::new(&5).unwrap();
423        let locked_args = RwLock::new(expected.clone());
424        let task_args = SafeTaskArgs::<RUMString>::new(locked_args);
425        let processor = rumtk_create_task!(
426            async |args: &SafeTaskArgs<RUMString>| -> TaskResult<RUMString> {
427                let owned_args = Arc::clone(args);
428                let lock_future = owned_args.read();
429                let locked_args = lock_future.await;
430                let mut results = TaskItems::<RUMString>::with_capacity(locked_args.len());
431                print!("Contents: ");
432                for arg in locked_args.iter() {
433                    print!("{} ", &arg);
434                    results.push(RUMString::new(arg));
435                }
436                Ok(results)
437            },
438            task_args
439        );
440        queue.add_task::<_>(processor);
441        let results = queue.wait();
442        let mut result_data = Vec::<RUMString>::with_capacity(5);
443        for r in results {
444            for v in r.unwrap().iter() {
445                result_data.push(v.clone());
446            }
447        }
448        assert_eq!(result_data, expected, "Results do not match expected!");
449    }
450
451    ///////////////////////////////////Net Tests/////////////////////////////////////////////////
452    #[test]
453    fn test_server_start() {
454        let mut server = match rumtk_create_server!("localhost", 0) {
455            Ok(server) => server,
456            Err(e) => panic!("Failed to create server because {}", e),
457        };
458        match server.start(false) {
459            Ok(_) => (),
460            Err(e) => panic!("Failed to start server because {}", e),
461        }
462    }
463
464    #[test]
465    fn test_server_send() {
466        let msg = RUMString::from("Hello World!");
467        let mut server = match rumtk_create_server!(LOCALHOST, 0, 1) {
468            Ok(server) => server,
469            Err(e) => panic!("Failed to create server because {}", e),
470        };
471        match server.start(false) {
472            Ok(_) => (),
473            Err(e) => panic!("Failed to start server because {}", e),
474        };
475        let address_info = server.get_address_info().unwrap();
476        let (ip, port) = rumtk_get_ip_port!(address_info);
477        println!("Sleeping");
478        rumtk_sleep!(1);
479        let mut client = match rumtk_connect!(port) {
480            Ok(client) => client,
481            Err(e) => panic!("Failed to create server because {}", e),
482        };
483        let client_id = client.get_address().unwrap();
484        rumtk_sleep!(1);
485        match server.send(&client_id, &msg.to_raw()) {
486            Ok(_) => (),
487            Err(e) => panic!("Server failed to send message because {}", e),
488        };
489        rumtk_sleep!(1);
490        let received_message = client.receive().unwrap();
491        assert_eq!(
492            &msg.to_raw(),
493            &received_message,
494            "{}",
495            rumtk_format!(
496                "Received message does not match sent message by server {:?}",
497                &received_message
498            )
499        );
500    }
501
502    #[test]
503    fn test_server_receive() {
504        let msg = RUMString::from("Hello World!");
505        let mut server = match rumtk_create_server!(LOCALHOST, 0) {
506            Ok(server) => server,
507            Err(e) => panic!("Failed to create server because {}", e),
508        };
509        match server.start(false) {
510            Ok(_) => (),
511            Err(e) => panic!("Failed to start server because {}", e),
512        };
513        let address_info = server.get_address_info().unwrap();
514        let (ip, port) = rumtk_get_ip_port!(address_info);
515        println!("Sleeping");
516        rumtk_sleep!(1);
517        let mut client = match rumtk_connect!(port) {
518            Ok(client) => client,
519            Err(e) => panic!("Failed to create server because {}", e),
520        };
521        match client.send(&msg.to_raw()) {
522            Ok(_) => (),
523            Err(e) => panic!("Failed to send message because {}", e),
524        };
525        rumtk_sleep!(1);
526        let client_id = client.get_address().expect("Failed to get client id");
527        let incoming_message = server.receive(&client_id).unwrap().to_rumstring();
528        println!("Received message => {:?}", &incoming_message);
529        assert_eq!(&incoming_message, msg, "Received message corruption!");
530    }
531
532    #[test]
533    fn test_server_get_clients() {
534        let mut server = match rumtk_create_server!(LOCALHOST, 0) {
535            Ok(server) => server,
536            Err(e) => panic!("Failed to create server because {}", e),
537        };
538        match server.start(false) {
539            Ok(_) => (),
540            Err(e) => panic!("Failed to start server because {}", e),
541        };
542        let address_info = server.get_address_info().unwrap();
543        let (ip, port) = rumtk_get_ip_port!(address_info);
544        println!("Sleeping");
545        rumtk_sleep!(1);
546        let mut client = match rumtk_connect!(port) {
547            Ok(client) => client,
548            Err(e) => panic!("Failed to create server because {}", e),
549        };
550        rumtk_sleep!(1);
551        let expected_client_id = client.get_address().expect("Failed to get client id");
552        let clients = server.get_client_ids();
553        let incoming_client_id = clients.get(0).expect("Expected client to have connected!");
554        println!("Connected client id => {}", &incoming_client_id);
555        assert_eq!(
556            &incoming_client_id, &expected_client_id,
557            "Connected client does not match the connecting client! Client id => {}",
558            &incoming_client_id
559        );
560    }
561
562    #[test]
563    fn test_server_stop() {
564        let msg = RUMString::from("Hello World!");
565        let mut server = match rumtk_create_server!("localhost", 0) {
566            Ok(server) => server,
567            Err(e) => panic!("Failed to create server because {}", e),
568        };
569        match server.start(false) {
570            Ok(_) => (),
571            Err(e) => panic!("Failed to start server because {}", e),
572        };
573        println!("Sleeping");
574        rumtk_sleep!(1);
575        match server.stop() {
576            Ok(_) => (),
577            Err(e) => panic!("Failed to stop server because {}", e),
578        };
579    }
580
581    #[test]
582    fn test_server_get_address_info() {
583        let msg = RUMString::from("Hello World!");
584        let mut server = match rumtk_create_server!("localhost", 0) {
585            Ok(server) => server,
586            Err(e) => panic!("Failed to create server because {}", e),
587        };
588        match server.start(false) {
589            Ok(_) => (),
590            Err(e) => panic!("Failed to start server because {}", e),
591        };
592        println!("Sleeping");
593        rumtk_sleep!(1);
594        match server.get_address_info() {
595            Some(addr) => println!("Server address info => {}", addr),
596            None => panic!("No address. Perhaps the server was never initialized?"),
597        };
598    }
599
600    #[test]
601    fn test_client_send() {
602        let msg = RUMString::from("Hello World!");
603        let mut server = match rumtk_create_server!(LOCALHOST, 0) {
604            Ok(server) => server,
605            Err(e) => panic!("Failed to create server because {}", e),
606        };
607        match server.start(false) {
608            Ok(_) => (),
609            Err(e) => panic!("Failed to start server because {}", e),
610        };
611        let address_info = server.get_address_info().unwrap();
612        let (ip, port) = rumtk_get_ip_port!(address_info);
613        println!("Sleeping");
614        rumtk_sleep!(1);
615        let mut client = match rumtk_connect!(port) {
616            Ok(client) => client,
617            Err(e) => panic!("Failed to create server because {}", e),
618        };
619        rumtk_sleep!(2);
620        match client.send(&msg.to_raw()) {
621            Ok(_) => (),
622            Err(e) => panic!("Failed to send message because {}", e),
623        };
624        rumtk_sleep!(1);
625        let clients = server.get_client_ids();
626        let incoming_client_id = clients.first().expect("Expected client to have connected!");
627        let mut received_message = server.receive(incoming_client_id).unwrap();
628        if received_message.is_empty() {
629            rumtk_sleep!(1);
630            received_message = server.receive(incoming_client_id).unwrap();
631        }
632        assert_eq!(
633            &msg.to_raw(),
634            &received_message,
635            "{}",
636            rumtk_format!(
637                "Received message does not match sent message by client {:?}",
638                &received_message
639            )
640        );
641    }
642
643    ////////////////////////////JSON Tests/////////////////////////////////
644
645    #[test]
646    fn test_serialize_json() {
647        #[derive(Serialize)]
648        struct MyStruct {
649            hello: RUMString,
650        }
651
652        let hw = MyStruct {
653            hello: RUMString::from("World"),
654        };
655        let hw_str = rumtk_serialize!(&hw, true).unwrap();
656
657        assert!(
658            !hw_str.is_empty(),
659            "Empty JSON string generated from the test struct!"
660        );
661    }
662
663    #[test]
664    fn test_deserialize_serde_json() {
665        use serde::{Deserialize, Deserializer, Serialize, Serializer};
666        use serde_json::{from_str, to_string};
667
668        #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
669        struct MyStruct {
670            hello: RUMString,
671        }
672
673        let hw = MyStruct {
674            hello: RUMString::from("World"),
675        };
676        let hw_str = to_string(&hw).unwrap();
677        let new_hw: MyStruct = from_str(&hw_str).unwrap();
678
679        assert_eq!(
680            new_hw, hw,
681            "Deserialized JSON does not match the expected value!"
682        );
683    }
684
685    #[test]
686    fn test_deserialize_json() {
687        #[derive(Serialize, Deserialize, PartialEq)]
688        struct MyStruct {
689            hello: RUMString,
690        }
691
692        let hw = MyStruct {
693            hello: RUMString::from("World"),
694        };
695        let hw_str = rumtk_serialize!(&hw, true).unwrap();
696        let new_hw: MyStruct = rumtk_deserialize!(&hw_str).unwrap();
697
698        assert!(
699            new_hw == hw,
700            "Deserialized JSON does not match the expected value!"
701        );
702    }
703
704    /*
705    #[test]
706    fn test_escape_unescape_json() {
707        #[derive(Serialize, Deserialize, PartialEq)]
708        struct MyStruct {
709            hello: RUMString,
710        }
711
712        let hw = MyStruct {
713            hello: RUMString::from("World"),
714        };
715
716        let hw_str = rumtk_serialize!(&hw, true).unwrap();
717        let hw_escaped_str = strings::basic_escape(&hw_str);
718        println!("Escaped => {}", hw_escaped_str);
719
720        let hw_unescaped_str = strings::unescape_string(&hw_escaped_str).unwrap();
721        println!("Unescaped => {}", hw_unescaped_str);
722        assert_eq!(
723            hw_str.to_rumstring(),
724            hw_unescaped_str.to_rumstring(),
725            "Unescaped serialized JSON mismatch!"
726        );
727
728        let new_hw: MyStruct = rumtk_deserialize!(&hw_unescaped_str).unwrap();
729
730        assert!(
731            new_hw == hw,
732            "Deserialized JSON does not match the expected value!"
733        );
734    }
735    */
736
737    ////////////////////////////CLI Tests/////////////////////////////////
738
739    #[test]
740    fn test_print_license_notice() {
741        print_license_notice("RUMTK", "2025", &vec!["Luis M. Santos, M.D."]);
742    }
743
744    //////////////////////////////////////////////////////////////////////////////////////////////
745}