// The MIT License (MIT) // Copyright (c) 2014 Y. T. CHUNG // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. //! Ini use std::collections::HashMap; use std::collections::hash_map::{IntoIter, Iter, IterMut, Keys}; use std::collections::hash_map::Entry; use std::fs::{File, OpenOptions}; use std::ops::{Index, IndexMut}; use std::char; use std::io::{self, Read, Write}; use std::fmt::{self, Display}; use std::path::Path; use std::str::Chars; use std::borrow::Borrow; use std::hash::Hash; use std::cmp::Eq; use std::error; #[derive(Debug, PartialEq, Copy, Clone)] pub enum EscapePolicy { /// escape absolutely nothing (dangerous) Nothing, /// only escape the most necessary things Basics, /// escape basics and non-ascii characters BasicsUnicode, /// Escape reserved symbols. Reserved, /// Escape reserved symbols and non-ascii characters ReservedUnicode, /// Escape everything that some INI implementations assume Everything, } impl EscapePolicy { fn escape_basics(&self) -> bool { match *self { EscapePolicy::Nothing => false, _ => true, } } fn escape_reserved(&self) -> bool { match *self { EscapePolicy::Reserved => true, EscapePolicy::ReservedUnicode => true, EscapePolicy::Everything => true, _ => false, } } fn escape_unicode(&self) -> bool { match *self { EscapePolicy::BasicsUnicode => true, EscapePolicy::ReservedUnicode => true, EscapePolicy::Everything => true, _ => false, } } /// Given a character this returns true if it should be escaped as /// per this policy or false if not. pub fn should_escape(&self, c: char) -> bool { match c { '\\' | '\x00'...'\x1f' | '\x7f'...'\u{00ff}' => self.escape_basics(), ';' | '#' | '=' | ':' => self.escape_reserved(), '\u{0080}'...'\u{FFFF}' => self.escape_unicode(), _ => false, } } } // Escape non-INI characters // // Common escape sequences: https://en.wikipedia.org/wiki/INI_file#Escape_characters // // * `\\` \ (a single backslash, escaping the escape character) // * `\0` Null character // * `\a` Bell/Alert/Audible // * `\b` Backspace, Bell character for some applications // * `\t` Tab character // * `\r` Carriage return // * `\n` Line feed // * `\;` Semicolon // * `\#` Number sign // * `\=` Equals sign // * `\:` Colon // * `\x????` Unicode character with hexadecimal code point corresponding to ???? fn escape_str(s: &str, policy: EscapePolicy) -> String { let mut escaped: String = String::with_capacity(s.len()); for c in s.chars() { // if we know this is not something to escape as per policy, we just // write it and continue. if !policy.should_escape(c) { escaped.push(c); continue; } match c { '\\' => escaped.push_str("\\\\"), '\0' => escaped.push_str("\\0"), '\x01'...'\x06' | '\x0e'...'\x1f' | '\x7f'...'\u{00ff}' => { escaped.push_str(&format!("\\x{:04x}", c as isize)[..]) } '\x07' => escaped.push_str("\\a"), '\x08' => escaped.push_str("\\b"), '\x0c' => escaped.push_str("\\f"), '\x0b' => escaped.push_str("\\v"), '\n' => escaped.push_str("\\n"), '\t' => escaped.push_str("\\t"), '\r' => escaped.push_str("\\r"), '\u{0080}'...'\u{FFFF}' => escaped.push_str(&format!("\\x{:04x}", c as isize)[..]), _ => { escaped.push('\\'); escaped.push(c); } } } escaped } /// A setter which could be used to set key-value pair in a specified section pub struct SectionSetter<'a> { ini: &'a mut Ini, section_name: Option, } impl<'a> SectionSetter<'a> { fn new(ini: &'a mut Ini, section_name: Option) -> SectionSetter<'a> { SectionSetter { ini: ini, section_name: section_name, } } /// Set key-value pair in this section pub fn set(&'a mut self, key: K, value: V) -> &'a mut SectionSetter<'a> where K: Into, V: Into, { { let prop = match self.ini.sections.entry(self.section_name.clone()) { Entry::Vacant(entry) => entry.insert(HashMap::new()), Entry::Occupied(entry) => entry.into_mut(), }; prop.insert(key.into(), value.into()); } self } /// Delete the entry in this section with `key` pub fn delete(&'a mut self, key: &K) -> &'a mut SectionSetter<'a> where String: Borrow, K: Hash + Eq + ?Sized, { if let Some(prop) = self.ini.sections.get_mut(&self.section_name) { prop.remove(key); } self } /// Get the entry in this section with `key` pub fn get(&'a mut self, key: &K) -> Option<&'a str> where String: Borrow, K: Hash + Eq + ?Sized, { self.ini .sections .get(&self.section_name) .and_then(|prop| prop.get(key).map(|s| &s[..])) } } /// Properties type (key-value pairs) pub type Properties = HashMap; // Key-value pairs /// Ini struct #[derive(Clone)] pub struct Ini { sections: HashMap, Properties>, } impl Ini { /// Create an instance pub fn new() -> Ini { Ini { sections: HashMap::new(), } } /// Set with a specified section, `None` is for the general section pub fn with_section<'b, S>(&'b mut self, section: Option) -> SectionSetter<'b> where S: Into, { SectionSetter::new(self, section.map(|s| s.into())) } /// Get the immmutable general section pub fn general_section(&self) -> &Properties { self.section(None::) .expect("There is no general section in this Ini") } /// Get the mutable general section pub fn general_section_mut(&mut self) -> &mut Properties { self.section_mut(None::) .expect("There is no general section in this Ini") } /// Get a immutable section pub fn section<'a, S>(&'a self, name: Option) -> Option<&'a Properties> where S: Into, { self.sections.get(&name.map(|s| s.into())) } /// Get a mutable section pub fn section_mut<'a, S>(&'a mut self, name: Option) -> Option<&'a mut Properties> where S: Into, { self.sections.get_mut(&name.map(|s| s.into())) } /// Get the entry pub fn entry<'a>(&'a mut self, name: Option) -> Entry, Properties> { self.sections.entry(name.map(|s| s.into())) } /// Clear all entries pub fn clear<'a>(&mut self) { self.sections.clear() } /// Iterate with sections pub fn sections<'a>(&'a self) -> Keys<'a, Option, Properties> { self.sections.keys() } /// Set key-value to a section pub fn set_to(&mut self, section: Option, key: String, value: String) where S: Into, { self.with_section(section).set(key, value); } /// Get the value from a section with key /// /// Example: /// /// ``` /// use ini::Ini; /// let input = "[sec]\nabc = def\n"; /// let ini = Ini::load_from_str(input).unwrap(); /// assert_eq!(ini.get_from(Some("sec"), "abc"), Some("def")); /// ``` pub fn get_from<'a, S>(&'a self, section: Option, key: &str) -> Option<&'a str> where S: Into, { match self.sections.get(§ion.map(|s| s.into())) { None => None, Some(ref prop) => match prop.get(key) { Some(p) => Some(&p[..]), None => None, }, } } /// Get the value from a section with key, return the default value if it does not exist /// /// Example: /// /// ``` /// use ini::Ini; /// let input = "[sec]\n"; /// let ini = Ini::load_from_str(input).unwrap(); /// assert_eq!(ini.get_from_or(Some("sec"), "key", "default"), "default"); /// ``` pub fn get_from_or<'a, S>(&'a self, section: Option, key: &str, default: &'a str) -> &'a str where S: Into, { match self.sections.get(§ion.map(|s| s.into())) { None => default, Some(ref prop) => match prop.get(key) { Some(p) => &p[..], None => default, }, } } /// Get the mutable from a section with key pub fn get_from_mut<'a, S>(&'a mut self, section: Option, key: &str) -> Option<&'a str> where S: Into, { match self.sections.get_mut(§ion.map(|s| s.into())) { None => None, Some(prop) => prop.get_mut(key).map(|s| &s[..]), } } /// Delete a section, return the properties if it exists pub fn delete(&mut self, section: Option) -> Option where S: Into, { self.sections.remove(§ion.map(|s| s.into())) } pub fn delete_from(&mut self, section: Option, key: &str) -> Option where S: Into, { match self.section_mut(section) { None => return None, Some(prop) => prop.remove(key), } } } impl<'q> Index<&'q Option> for Ini { type Output = Properties; fn index<'a>(&'a self, index: &'q Option) -> &'a Properties { match self.sections.get(index) { Some(p) => p, None => panic!("Section `{:?}` does not exist", index), } } } impl<'i> IndexMut<&'i Option> for Ini { fn index_mut<'a>(&'a mut self, index: &Option) -> &'a mut Properties { match self.sections.get_mut(index) { Some(p) => p, None => panic!("Section `{:?}` does not exist", index), } } } impl<'q> Index<&'q str> for Ini { type Output = Properties; fn index<'a>(&'a self, index: &'q str) -> &'a Properties { match self.sections.get(&Some(index.into())) { Some(p) => p, None => panic!("Section `{}` does not exist", index), } } } impl<'q> IndexMut<&'q str> for Ini { fn index_mut<'a>(&'a mut self, index: &'q str) -> &'a mut Properties { match self.sections.get_mut(&Some(index.into())) { Some(p) => p, None => panic!("Section `{}` does not exist", index), } } } impl Ini { /// Write to a file pub fn write_to_file>(&self, filename: P) -> io::Result<()> { self.write_to_file_policy(filename, EscapePolicy::Basics) } /// Write to a file pub fn write_to_file_policy>( &self, filename: P, policy: EscapePolicy, ) -> io::Result<()> { let mut file = try!( OpenOptions::new() .write(true) .truncate(true) .create(true) .open(filename.as_ref()) ); self.write_to_policy(&mut file, policy) } /// Write to a writer pub fn write_to(&self, writer: &mut W) -> io::Result<()> { self.write_to_policy(writer, EscapePolicy::Basics) } /// Write to a writer pub fn write_to_policy( &self, writer: &mut W, policy: EscapePolicy, ) -> io::Result<()> { let mut firstline = true; match self.sections.get(&None) { Some(props) => { for (k, v) in props.iter() { let k_str = escape_str(&k[..], policy); let v_str = escape_str(&v[..], policy); try!(write!(writer, "{}={}\n", k_str, v_str)); } firstline = false; } None => {} } for (section, props) in self.sections.iter().filter(|&(ref s, _)| s.is_some()) { if firstline { firstline = false; } else { try!(writer.write_all(b"\n")); } if let &Some(ref section) = section { try!(write!(writer, "[{}]\n", escape_str(§ion[..], policy))); for (k, v) in props.iter() { let k_str = escape_str(&k[..], policy); let v_str = escape_str(&v[..], policy); try!(write!(writer, "{}={}\n", k_str, v_str)); } } } Ok(()) } } impl Ini { /// Load from a string pub fn load_from_str(buf: &str) -> Result { let mut parser = Parser::new(buf.chars(), false); parser.parse() } /// Load from a string, but do not interpret '\' as an escape character pub fn load_from_str_noescape(buf: &str) -> Result { let mut parser = Parser::new(buf.chars(), true); parser.parse() } /// Load from a reader pub fn read_from(reader: &mut R) -> Result { let mut s = String::new(); try!(reader.read_to_string(&mut s).map_err(|err| Error { line: 0, col: 0, msg: format!("{}", err), })); let mut parser = Parser::new(s.chars(), false); parser.parse() } /// Load from a reader, but do not interpret '\' as an escape character pub fn read_from_noescape(reader: &mut R) -> Result { let mut s = String::new(); try!(reader.read_to_string(&mut s).map_err(|err| Error { line: 0, col: 0, msg: format!("{}", err), })); let mut parser = Parser::new(s.chars(), true); parser.parse() } /// Load from a file pub fn load_from_file>(filename: P) -> Result { let mut reader = match File::open(filename.as_ref()) { Err(e) => { return Err(Error { line: 0, col: 0, msg: format!("Unable to open `{:?}`: {}", filename.as_ref(), e), }) } Ok(r) => r, }; Ini::read_from(&mut reader) } /// Load from a file, but do not interpret '\' as an escape character pub fn load_from_file_noescape>(filename: P) -> Result { let mut reader = match File::open(filename.as_ref()) { Err(e) => { return Err(Error { line: 0, col: 0, msg: format!("Unable to open `{:?}`: {}", filename.as_ref(), e), }) } Ok(r) => r, }; Ini::read_from_noescape(&mut reader) } } /// Iterator for sections pub struct SectionIterator<'a> { mapiter: Iter<'a, Option, Properties>, } /// Iterator for mutable sections pub struct SectionMutIterator<'a> { mapiter: IterMut<'a, Option, Properties>, } impl<'a> Ini { /// Immutable iterate though sections pub fn iter(&'a self) -> SectionIterator<'a> { SectionIterator { mapiter: self.sections.iter(), } } /// Mutable iterate though sections /// *Deprecated! Use `iter_mut` instead!* pub fn mut_iter(&'a mut self) -> SectionMutIterator<'a> { SectionMutIterator { mapiter: self.sections.iter_mut(), } } /// Mutable iterate though sections pub fn iter_mut(&'a mut self) -> SectionMutIterator<'a> { SectionMutIterator { mapiter: self.sections.iter_mut(), } } } impl<'a> Iterator for SectionIterator<'a> { type Item = (&'a Option, &'a Properties); #[inline] fn next(&mut self) -> Option<(&'a Option, &'a Properties)> { self.mapiter.next() } } impl<'a> Iterator for SectionMutIterator<'a> { type Item = (&'a Option, &'a mut Properties); #[inline] fn next(&mut self) -> Option<(&'a Option, &'a mut Properties)> { self.mapiter.next() } } impl<'a> IntoIterator for &'a Ini { type Item = (&'a Option, &'a Properties); type IntoIter = SectionIterator<'a>; fn into_iter(self) -> SectionIterator<'a> { self.iter() } } impl<'a> IntoIterator for &'a mut Ini { type Item = (&'a Option, &'a mut Properties); type IntoIter = SectionMutIterator<'a>; fn into_iter(self) -> SectionMutIterator<'a> { self.iter_mut() } } pub struct SectionIntoIter { iter: IntoIter, Properties>, } impl Iterator for SectionIntoIter { type Item = (Option, Properties); fn next(&mut self) -> Option { self.iter.next() } } impl IntoIterator for Ini { type Item = (Option, Properties); type IntoIter = SectionIntoIter; fn into_iter(self) -> SectionIntoIter { SectionIntoIter { iter: self.sections.into_iter(), } } } // Ini parser struct Parser<'a> { ch: Option, rdr: Chars<'a>, line: usize, col: usize, literal: bool, } #[derive(Debug)] /// Parse error pub struct Error { pub line: usize, pub col: usize, pub msg: String, } impl Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}:{} {}", self.line, self.col, self.msg) } } impl error::Error for Error { fn description(&self) -> &str { self.msg.as_str() } fn cause(&self) -> Option<&error::Error> { None } } impl<'a> Parser<'a> { // Create a parser pub fn new(rdr: Chars<'a>, literal: bool) -> Parser<'a> { let mut p = Parser { ch: None, line: 0, col: 0, rdr: rdr, literal: literal, }; p.bump(); p } fn eof(&self) -> bool { self.ch.is_none() } fn bump(&mut self) { self.ch = self.rdr.next(); match self.ch { Some('\n') => { self.line += 1; self.col = 0; } Some(..) => { self.col += 1; } None => {} } } fn error(&self, msg: String) -> Result { Err(Error { line: self.line, col: self.col, msg: msg.clone(), }) } /// Consume all the white space until the end of the line or a tab fn parse_whitespace(&mut self) { while let Some(c) = self.ch { if !c.is_whitespace() && c != '\n' && c != '\t' && c != '\r' { break; } self.bump(); } } /// Consume all the white space except line break fn parse_whitespace_except_line_break(&mut self) { while let Some(c) = self.ch { if (c == '\n' || c == '\r' || !c.is_whitespace()) && c != '\t' { break; } self.bump(); } } /// Parse the whole INI input pub fn parse(&mut self) -> Result { let mut result = Ini::new(); let mut curkey: String = "".into(); let mut cursec: Option = None; self.parse_whitespace(); while let Some(cur_ch) = self.ch { match cur_ch { ';' | '#' => { self.parse_comment(); } '[' => match self.parse_section() { Ok(sec) => { let msec = &sec[..].trim(); cursec = Some(msec.to_string()); result .sections .entry(cursec.clone()) .or_insert(HashMap::new()); self.bump(); } Err(e) => return Err(e), }, '=' | ':' => { if (&curkey[..]).is_empty() { return self.error("Missing key".to_string()); } match self.parse_val() { Ok(val) => { let mval = val[..].trim().to_owned(); let sec = result .sections .entry(cursec.clone()) .or_insert(HashMap::new()); sec.insert(curkey, mval); curkey = "".into(); } Err(e) => return Err(e), } } _ => match self.parse_key() { Ok(key) => { let mkey: String = key[..].trim().to_owned(); curkey = mkey.into(); } Err(e) => return Err(e), }, } self.parse_whitespace(); } Ok(result) } fn parse_comment(&mut self) { while let Some(c) = self.ch { self.bump(); if c == '\n' { break; } } } fn parse_str_until(&mut self, endpoint: &[Option]) -> Result { let mut result: String = String::new(); while !endpoint.contains(&self.ch) { match self.ch { None => { return self.error(format!("Expecting \"{:?}\" but found EOF.", endpoint)); } Some('\\') if !self.literal => { self.bump(); if self.eof() { return self.error(format!("Expecting \"{:?}\" but found EOF.", endpoint)); } match self.ch.unwrap() { '0' => result.push('\0'), 'a' => result.push('\x07'), 'b' => result.push('\x08'), 't' => result.push('\t'), 'r' => result.push('\r'), 'n' => result.push('\n'), '\n' => (), 'x' => { // Unicode 4 character let mut code: String = String::with_capacity(4); for _ in 0..4 { self.bump(); if self.eof() { return self.error(format!( "Expecting \"{:?}\" but found EOF.", endpoint )); } else if let Some('\\') = self.ch { self.bump(); if self.ch != Some('\n') { return self.error(format!( "Expecting \"\\\\n\" but \ found \"{:?}\".", self.ch )); } } code.push(self.ch.unwrap()); } let r = u32::from_str_radix(&code[..], 16); match r { Ok(c) => result.push(char::from_u32(c).unwrap()), Err(_) => return self.error("Unknown character.".to_string()), } } c => result.push(c), } } Some(c) => { result.push(c); } } self.bump(); } Ok(result) } fn parse_section(&mut self) -> Result { // Skip [ self.bump(); self.parse_str_until(&[Some(']')]) } fn parse_key(&mut self) -> Result { self.parse_str_until(&[Some('='), Some(':')]) } fn parse_val(&mut self) -> Result { self.bump(); // Issue #35: Allow empty value self.parse_whitespace_except_line_break(); match self.ch { None => Ok(String::new()), Some('"') => { self.bump(); self.parse_str_until(&[Some('"')]).and_then(|s| { self.bump(); // Eats the last " Ok(s) }) } Some('\'') => { self.bump(); self.parse_str_until(&[Some('\'')]).and_then(|s| { self.bump(); // Eats the last ' Ok(s) }) } _ => self.parse_str_until(&[Some('\n'), Some('\r'), Some(';'), Some('#'), None]), } } } // ------------------------------------------------------------------------------ #[cfg(test)] mod test { use ini::*; #[test] fn load_from_str_with_valid_input() { let input = "[sec1]\nkey1=val1\nkey2=377\n[sec2]foo=bar\n"; let opt = Ini::load_from_str(input); assert!(opt.is_ok()); let output = opt.unwrap(); assert_eq!(output.sections.len(), 2); assert!(output.sections.contains_key(&Some("sec1".into()))); let sec1 = &output.sections[&Some("sec1".into())]; assert_eq!(sec1.len(), 2); let key1: String = "key1".into(); assert!(sec1.contains_key(&key1)); let key2: String = "key2".into(); assert!(sec1.contains_key(&key2)); let val1: String = "val1".into(); assert_eq!(sec1[&key1], val1); let val2: String = "377".into(); assert_eq!(sec1[&key2], val2); } #[test] fn load_from_str_without_ending_newline() { let input = "[sec1]\nkey1=val1\nkey2=377\n[sec2]foo=bar"; let opt = Ini::load_from_str(input); assert!(opt.is_ok()); } #[test] fn test_parse_comment() { let input = "; abcdefghijklmn\n"; let opt = Ini::load_from_str(input); assert!(opt.is_ok()); } #[test] fn test_inline_comment() { let input = " [section name] name = hello # abcdefg gender = mail ; abdddd "; let ini = Ini::load_from_str(input).unwrap(); assert_eq!(ini.get_from(Some("section name"), "name").unwrap(), "hello"); } #[test] fn test_sharp_comment() { let input = " [section name] name = hello # abcdefg "; let ini = Ini::load_from_str(input).unwrap(); assert_eq!(ini.get_from(Some("section name"), "name").unwrap(), "hello"); } #[test] fn test_iter() { let input = " [section name] name = hello # abcdefg gender = mail ; abdddd "; let mut ini = Ini::load_from_str(input).unwrap(); for (_, _) in &mut ini {} for (_, _) in &ini {} for (_, _) in ini {} } #[test] fn test_colon() { let input = " [section name] name: hello # abcdefg gender : mail ; abdddd "; let ini = Ini::load_from_str(input).unwrap(); assert_eq!(ini.get_from(Some("section name"), "name").unwrap(), "hello"); assert_eq!( ini.get_from(Some("section name"), "gender").unwrap(), "mail" ); } #[test] fn test_string() { let input = " [section name] # This is a comment Key = \"Value\" "; let ini = Ini::load_from_str(input).unwrap(); assert_eq!(ini.get_from(Some("section name"), "Key").unwrap(), "Value"); } #[test] fn test_string_multiline() { let input = " [section name] # This is a comment Key = \"Value Otherline\" "; let ini = Ini::load_from_str(input).unwrap(); assert_eq!( ini.get_from(Some("section name"), "Key").unwrap(), "Value\nOtherline" ); } #[test] fn test_string_comment() { let input = " [section name] # This is a comment Key = \"Value # This is not a comment ; at all\" Stuff = Other "; let ini = Ini::load_from_str(input).unwrap(); assert_eq!( ini.get_from(Some("section name"), "Key").unwrap(), "Value # This is not a comment ; at all" ); } #[test] fn test_string_single() { let input = " [section name] # This is a comment Key = 'Value' Stuff = Other "; let ini = Ini::load_from_str(input).unwrap(); assert_eq!(ini.get_from(Some("section name"), "Key").unwrap(), "Value"); } #[test] fn test_string_includes_quote() { let input = " [Test] Comment[tr]=İnternet'e erişin Comment[uk]=Доступ до Інтернету "; let ini = Ini::load_from_str(input).unwrap(); assert_eq!( ini.get_from(Some("Test"), "Comment[tr]").unwrap(), "İnternet'e erişin" ); } #[test] fn test_string_single_multiline() { let input = " [section name] # This is a comment Key = 'Value Otherline' Stuff = Other "; let ini = Ini::load_from_str(input).unwrap(); assert_eq!( ini.get_from(Some("section name"), "Key").unwrap(), "Value\nOtherline" ); } #[test] fn test_string_single_comment() { let input = " [section name] # This is a comment Key = 'Value # This is not a comment ; at all' "; let ini = Ini::load_from_str(input).unwrap(); assert_eq!( ini.get_from(Some("section name"), "Key").unwrap(), "Value # This is not a comment ; at all" ); } #[test] fn load_from_str_with_valid_empty_input() { let input = "key1=\nkey2=val2\n"; let opt = Ini::load_from_str(input); assert!(opt.is_ok()); let output = opt.unwrap(); assert_eq!(output.sections.len(), 1); assert!(output.sections.contains_key(&None::)); let sec1 = &output.sections[&None::]; assert_eq!(sec1.len(), 2); let key1: String = "key1".into(); assert!(sec1.contains_key(&key1)); let key2: String = "key2".into(); assert!(sec1.contains_key(&key2)); let val1: String = "".into(); assert_eq!(sec1[&key1], val1); let val2: String = "val2".into(); assert_eq!(sec1[&key2], val2); } #[test] fn load_from_str_with_crlf() { let input = "key1=val1\r\nkey2=val2\r\n"; let opt = Ini::load_from_str(input); assert!(opt.is_ok()); let output = opt.unwrap(); assert_eq!(output.sections.len(), 1); assert!(output.sections.contains_key(&None::)); let sec1 = &output.sections[&None::]; assert_eq!(sec1.len(), 2); let key1: String = "key1".into(); assert!(sec1.contains_key(&key1)); let key2: String = "key2".into(); assert!(sec1.contains_key(&key2)); let val1: String = "val1".into(); assert_eq!(sec1[&key1], val1); let val2: String = "val2".into(); assert_eq!(sec1[&key2], val2); } #[test] fn load_from_str_with_cr() { let input = "key1=val1\rkey2=val2\r"; let opt = Ini::load_from_str(input); assert!(opt.is_ok()); let output = opt.unwrap(); assert_eq!(output.sections.len(), 1); assert!(output.sections.contains_key(&None::)); let sec1 = &output.sections[&None::]; assert_eq!(sec1.len(), 2); let key1: String = "key1".into(); assert!(sec1.contains_key(&key1)); let key2: String = "key2".into(); assert!(sec1.contains_key(&key2)); let val1: String = "val1".into(); assert_eq!(sec1[&key1], val1); let val2: String = "val2".into(); assert_eq!(sec1[&key2], val2); } #[test] fn get_with_non_static_key() { let input = "key1=val1\nkey2=val2\n"; let opt = Ini::load_from_str(input).unwrap(); let sec1 = &opt.sections[&None::]; let key = "key1".to_owned(); sec1.get(&key).unwrap(); } #[test] fn load_from_str_noescape() { let input = "path=C:\\Windows\\Some\\Folder\\"; let opt = Ini::load_from_str_noescape(input); assert!(opt.is_ok()); let output = opt.unwrap(); assert_eq!(output.sections.len(), 1); let sec = &output.sections[&None::]; assert_eq!(sec.len(), 1); assert!(sec.contains_key("path")); assert_eq!(sec["path"], "C:\\Windows\\Some\\Folder\\"); } }