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