scan_fmt/
lib.rs

1// Copyright 2015-2019 Will Lentz.
2// Licensed under the MIT license.
3
4//! This crate provides a simple sscanf()-like interface to extract
5//! data from strings and stdin.
6//!
7//! In version 0.2 scan_fmt! changed to return a Result.
8//! Use scan_fmt_some! for the 0.1.x behavior.
9//!
10//! To use this crate, do:
11//!
12//! ```ignore
13//! #[macro_use] extern crate scan_fmt;
14//! ```
15//!
16//! Example to read from a string:
17//!
18//! ```rust
19//! # #[macro_use] extern crate scan_fmt;
20//! # fn main() {
21//!   if let Ok((a,b)) = scan_fmt!( "-11 0x22", // input string
22//!                                 "{d} {x}",  // format
23//!                                 i8, [hex u8]) { // types
24//!     assert_eq!( a, -11 ) ;
25//!     assert_eq!( b, 0x22 ) ;
26//!   }
27//!
28//!   let (a,b,c) = scan_fmt_some!( "hello 12 345 bye", // input string
29//!                                 "hello {} {d} {}",  // format
30//!                                 u8, i32, String);   // type of a-c Options
31//!   assert_eq!( a, Some(12) ) ;
32//!   assert_eq!( b, Some(345) ) ;
33//!   assert_eq!( c, Some("bye".into()) ) ;
34//! # }
35//! ```
36//!
37//! Special format_string tokens:
38//! <pre class="rust">
39//!   {{ = escape for '{'
40//!   }} = escape for '}'
41//!   {} = return any value (until next whitespace)
42//!   {d} = return base-10 decimal
43//!   {x} = return hex (0xab or ab)
44//!       = you must wrap the type in [hex type], e.g. "[hex u32]"
45//!   {f} = return float
46//!   {*d} = "*" as the first character means "match but don't return"
47//!   {2d} or {2x} or {2f} = limit the maximum width to 2.  Any positive integer works.
48//!   {[...]} = return pattern.
49//!     ^ inverts if it is the first character
50//!     - is for ranges.  For a literal - put it at the start or end.
51//!     To add a literal ] do "[]abc]"
52//!   {e} = doesn't return a value, but matches end of line.  Use this if you
53//!         don't want to ignore potential extra characters at end of input.
54//!   Examples:
55//!     {[0-9ab]} = match 0-9 or a or b
56//!     {[^,.]} = match anything but , or .
57//!     {/.../} = return regex inside of `//`. (if regex feature is installed)
58//!      If there is a single capture group inside of the slashes then
59//!      that group will make up the pattern.
60//!   Examples:
61//!     {/[0-9ab]/} = same as {[0-9ab]}, above
62//!     {/a+/} = matches at least one `a`, greedily
63//!     {/jj(a*)jj/} = matches any number of `a`s, but only if
64//!       they're surrounded by two `j`s
65//! </pre>
66//!
67//! Example to read from stdin:
68//!
69//! ```ignore
70//! # #[macro_use] extern crate scan_fmt;
71//! # use std::error::Error ;
72//! # fn main() -> Result<(),Box<dyn Error>> {
73//!     let (a,b) = scanln_fmt!( "{}-{}", u16, u8) ? ;
74//!     println!("Got {} and {}",a,b);
75//!
76//!     let (a,b) = scanln_fmt_some!( "{}-{}",   // format
77//!                                  u16, u8);    // type of a&b Options
78//!     match (a,b) {
79//!       (Some(aa),Some(bb)) => println!("Got {} and {}",aa,bb),
80//!       _ => println!("input error")
81//!     }
82//!     Ok(())
83//! # }
84//! ```
85//!
86//! ## LIMITATIONS:
87//! There are no compile-time checks to make sure the format
88//! strings matches the number of return arguments.  Extra
89//! return values will be None or cause a Result error.
90//!
91//! Like sscanf(), whitespace (including \n) is largely ignored.
92//!
93//! Conversion to output values is done using parse::<T>().
94
95#![no_std]
96
97#[cfg(feature = "regex")]
98extern crate regex;
99
100#[cfg(any(test, doctest, feature = "std"))]
101extern crate std;
102
103#[macro_use]
104extern crate alloc;
105
106pub mod parse;
107
108#[macro_export]
109macro_rules! scan_fmt_help {
110    ( wrap $res:expr, [hex $arg:tt] ) => {
111        match $res.next() {
112            Some(item) => $arg::from_str_radix(&item, 16).ok(),
113            _ => None,
114        }
115    };
116    ( wrap $res:expr , $($arg1:tt)::* ) => {
117        match $res.next() {
118            Some(item) => item.parse::<$($arg1)::*>().ok(),
119            _ => None,
120        }
121    };
122    ( no_wrap $err:ident, $res:expr, [hex $arg:tt] ) => {
123        match $res.next() {
124            Some(item) => {
125                let ret = $arg::from_str_radix(&item, 16);
126                if ret.is_err() {
127                    $err = "from_str_radix hex";
128                }
129                ret.unwrap_or(0)
130            }
131            _ => {
132                $err = "internal hex";
133                0
134            }
135        }
136    };
137    ( no_wrap $err:ident, $res:expr , $($arg1:tt)::* ) => {{
138        // We need to return a value of type $($arg1)::* if parsing fails.
139        // Is there a better way?
140        let mut err = "0".parse::<$($arg1)::*>(); // most types
141        if err.is_err() {
142           err = "0.0.0.0".parse::<$($arg1)::*>(); // IpAddr
143        }
144        let err = err.unwrap();
145        match $res.next() {
146            Some(item) => {
147                let ret = item.parse::<$($arg1)::*>();
148                if(item == "") {
149                    $err = "match::none";
150                } else if ret.is_err() {
151                    $err = concat!("parse::", stringify!($($arg1)::*));
152                }
153                ret.unwrap_or(err)
154            }
155            _ => {
156                $err = concat!("internal ", stringify!($($arg1)::*));
157                err
158            }
159        }
160    }};
161}
162
163#[macro_export]
164macro_rules! scan_fmt_some {
165    ( $instr:expr, $fmt:expr, $($($args:tt)::*),* ) => {
166        {
167            let mut res = $crate::parse::scan( $instr, $fmt ) ;
168            ($($crate::scan_fmt_help!(wrap res,$($args)::*)),*)
169        }
170    };
171}
172
173#[macro_export]
174macro_rules! scan_fmt {
175    ( $instr:expr, $fmt:expr, $($($args:tt)::*),* ) => {
176        {
177            let mut err = "" ;
178            let mut res = $crate::parse::scan( $instr, $fmt ) ;
179            let result = ($($crate::scan_fmt_help!(no_wrap err,res,$($args)::*)),*) ;
180            if err == "" {
181                Ok(result)
182            } else {
183                Err($crate::parse::ScanError(err.into()))
184            }
185        }
186    };
187}
188
189#[cfg(feature = "std")]
190pub use std_features::*;
191
192#[cfg(feature = "std")]
193mod std_features {
194    use std::string::String;
195
196    pub fn get_input_unwrap() -> String {
197        let mut input = String::new();
198        std::io::stdin().read_line(&mut input).unwrap();
199        input
200    }
201
202    /// (a,+) = scanln_fmt!( format_string, types,+ )
203    /// <p>Same as scan_fmt!(), but reads input string from stdin.</p>
204    #[macro_export]
205    macro_rules! scanln_fmt {
206        ($($arg:tt)*) => {{ scan_fmt!(&$crate::get_input_unwrap(), $($arg)*) }}
207    }
208
209    /// (a,+) = scanln_fmt_some!( format_string, types,+ )
210    /// <p>Same as scan_fmt_some!(), but reads input string from stdin.</p>
211    #[macro_export]
212    macro_rules! scanln_fmt_some {
213        ($($arg:tt)*) => {{ scan_fmt_some!(&$crate::get_input_unwrap(), $($arg)*) }}
214    }
215}
216
217#[cfg(test)]
218use alloc::string::{String, ToString};
219#[cfg(test)]
220use parse::ScanError;
221
222#[cfg(test)]
223macro_rules! assert_flt_eq {
224    ($t:ident, $v1:expr, $v2:expr) => {{
225        assert!(($v1 - $v2).abs() <= 2.0 * std::$t::EPSILON);
226    }};
227}
228
229#[cfg(test)]
230fn ret_scan_all() -> Result<(), ScanError> {
231    let (a, b) = scan_fmt!("1.2 e","{f} {x}",f32,[hex u32])?;
232    assert_flt_eq!(f32, a, 1.2);
233    assert_eq!(b, 14);
234    Ok(())
235}
236
237#[test]
238fn test_scan_all() {
239    if let Ok(a) = scan_fmt!("hi1 3", "{} {d}", std::string::String, u32) {
240        assert_eq!(a, ("hi1".to_string(), 3));
241    } else {
242        assert!(false, "error 0");
243    }
244    if let Ok((a, b, c)) = scan_fmt!("hi1 0xf -3","{} {x} {d}",String,[hex u32],i8) {
245        assert_eq!(a, "hi1");
246        assert_eq!(b, 0xf);
247        assert_eq!(c, -3);
248    } else {
249        assert!(false, "error 1");
250    }
251    let a = scan_fmt!("hi1 f", "{} {d}", String, i32);
252    assert!(a.is_err());
253    let a = ret_scan_all();
254    std::println!("{:?}", a);
255    assert!(a.is_ok());
256}
257
258#[test]
259fn test_plus_sign() {
260    let a = scan_fmt_some!("+42", "{d}", i32);
261    assert_eq!(a, Some(42));
262    let a = scan_fmt_some!("+42.0", "{f}", f64);
263    assert_flt_eq!(f64, a.unwrap(), 42.0);
264}
265
266#[test]
267fn test_hex() {
268    let (a, b, c) =
269        scan_fmt_some!("DEV 0xab 0x1234", "{} {x} {x}", std::string::String, [hex u32], [hex u64]);
270    assert_eq!(a, Some("DEV".into()));
271    assert_eq!(b, Some(0xab));
272    assert_eq!(c, Some(0x1234));
273}
274
275#[test]
276fn test_limited_data_range() {
277    let (a, b, c) = scan_fmt_some!(
278        "test{\t 1e9 \n bye 257} hi  22.7e-1",
279        "test{{ {} bye {d}}} hi {f}",
280        f64,
281        u8,
282        f32
283    );
284    assert_flt_eq!(f64, a.unwrap(), 1e9);
285    assert_eq!(b, None); // 257 doesn't fit into a u8
286    assert_flt_eq!(f32, c.unwrap(), 2.27);
287}
288
289#[test]
290fn test_too_many_outputs() {
291    let (a, b, c, d) = scan_fmt_some!("a_aa bb_b c", "{} {s} {}", String, String, String, String);
292    assert_eq!(a.unwrap(), "a_aa");
293    assert_eq!(b.unwrap(), "bb_b");
294    assert_eq!(c.unwrap(), "c");
295    assert_eq!(d, None);
296}
297
298#[test]
299fn test_skip_assign() {
300    let (a, b) = scan_fmt_some!("1 2 3, 4 5, 6 7", "{[^,]},{*[^,]},{[^,]}", String, String);
301    assert_eq!(a.unwrap(), "1 2 3");
302    assert_eq!(b.unwrap(), "6 7");
303    let a = scan_fmt!("1 2 3, 4 5, 6 7", "{[^,]},{*[^,]},{[^,]}", String, String).unwrap();
304    assert_eq!(a.0, "1 2 3");
305    assert_eq!(a.1, "6 7");
306}
307
308#[test]
309fn test_width_specifier() {
310    let a = scan_fmt!("123ab71 2.1234",
311                      "{1d}{2d}{3x}{4d}{3f}",
312                      u8, u8, [hex u16], u16, f32)
313    .unwrap();
314    assert_eq!(a.0, 1);
315    assert_eq!(a.1, 23);
316    assert_eq!(a.2, 0xab7);
317    assert_eq!(a.3, 1);
318    assert_flt_eq!(f32, a.4, 2.1);
319}
320
321#[test]
322fn test_err_equals() {
323    let a = scan_fmt!("hi 123", "hi {d", u8);
324    assert_eq!(a, Err(parse::ScanError("internal u8".to_string())));
325}
326
327#[test]
328fn test_no_post_match_regex() {
329    let a = scan_fmt!("74in", "{d}{/in/}", u8, String);
330    assert_eq!(a, Ok((74, String::from("in"))));
331    let a = scan_fmt!("74in", "{d}{/cm/}", u8, String);
332    assert_eq!(a, Err(parse::ScanError("match::none".to_string())));
333}
334
335#[test]
336fn test_no_post_match() {
337    let a = scan_fmt!("17in", "{d}in", u8);
338    assert_eq!(a, Ok(17u8));
339
340    let a = scan_fmt!("17in", "{d}cm", u8);
341    assert_eq!(a, Err(parse::ScanError("match::none".to_string())));
342}
343
344#[test]
345fn test_match_end() {
346    let a = scan_fmt!("17in", "{d}in{e}", u8);
347    assert_eq!(a, Ok(17u8));
348    let a = scan_fmt!("17ink", "{d}in{e}", u8);
349    assert_eq!(a, Err(parse::ScanError("match::none".to_string())));
350}
351
352#[test]
353fn test_ip_addr() {
354    let a = scan_fmt!("x 185.187.165.163 y", "x {} y", std::net::IpAddr);
355    assert_eq!(
356        a.unwrap(),
357        std::net::IpAddr::V4(std::net::Ipv4Addr::new(185, 187, 165, 163))
358    );
359}