rust-ini-0.17.0/.cargo_vcs_info.json0000644000000001120000000000000126640ustar { "git": { "sha1": "9ec649ba3552070cd6bf395ad514eb7c67a30268" } } rust-ini-0.17.0/.gitignore000064400000000000000000000000710000000000000134260ustar 00000000000000/target/ Cargo.lock .DS_Store Thumbs.db /.idea /.vscode rust-ini-0.17.0/.travis.yml000064400000000000000000000005370000000000000135560ustar 00000000000000sudo: false language: rust matrix: include: - os: windows rust: stable - os: osx rust: stable - os: linux rust: stable cache: cargo script: - cargo test -v --no-fail-fast - cargo test -v --no-fail-fast --features inline-comment - cargo test -v --no-fail-fast --features case-insensitive - cargo doc --no-deps rust-ini-0.17.0/Cargo.lock0000644000000071640000000000000106550ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "ahash" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0adac150c2dd5a9c864d054e07bda5e6bc010cd10036ea5f17e82a2f5867f735" [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "dlv-list" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b391911b9a786312a10cb9d2b3d0735adfd5a8113eb3648de26a75e91b0826c" dependencies = [ "rand", ] [[package]] name = "getrandom" version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" dependencies = [ "cfg-if 0.1.10", "libc", "wasi", ] [[package]] name = "hashbrown" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" dependencies = [ "ahash", ] [[package]] name = "libc" version = "0.2.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235" [[package]] name = "ordered-multimap" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87a6df8506c2d359d5105344891f283e8e7ef81a7c6a542d516f8707e4a09b6b" dependencies = [ "dlv-list", "hashbrown", ] [[package]] name = "ppv-lite86" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20" [[package]] name = "rand" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "getrandom", "libc", "rand_chacha", "rand_core", "rand_hc", ] [[package]] name = "rand_chacha" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ "getrandom", ] [[package]] name = "rand_hc" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ "rand_core", ] [[package]] name = "rust-ini" version = "0.17.0" dependencies = [ "cfg-if 1.0.0", "ordered-multimap", "unicase", ] [[package]] name = "unicase" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" dependencies = [ "version_check", ] [[package]] name = "version_check" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" rust-ini-0.17.0/Cargo.toml0000644000000021340000000000000106700ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "rust-ini" version = "0.17.0" authors = ["Y. T. Chung "] description = "An Ini configuration file parsing library in Rust" documentation = "https://docs.rs/rust-ini/" keywords = ["ini", "configuration", "conf", "cfg"] license = "MIT" repository = "https://github.com/zonyitoo/rust-ini" [lib] name = "ini" test = true [dependencies.cfg-if] version = "1.0" [dependencies.ordered-multimap] version = "0.3" [dependencies.unicase] version = "2.6" optional = true [features] case-insensitive = ["unicase"] default = [] inline-comment = [] rust-ini-0.17.0/Cargo.toml.orig000064400000000000000000000013100000000000000143220ustar 00000000000000[package] name = "rust-ini" version = "0.17.0" authors = ["Y. T. Chung "] description = "An Ini configuration file parsing library in Rust" repository = "https://github.com/zonyitoo/rust-ini" documentation = "https://docs.rs/rust-ini/" keywords = ["ini", "configuration", "conf", "cfg"] license = "MIT" edition = "2018" [lib] name = "ini" test = true [dependencies] cfg-if = "1.0" unicase = { version = "2.6", optional = true } ordered-multimap = "0.3" [features] default = [] # Allowing inline comments # # For example: # # [A] # Key=value # COMMENTS # # This was a default feature < 0.14.0 inline-comment = [] # Case-insensitive Sections and Property Keys case-insensitive = ["unicase"] rust-ini-0.17.0/LICENSE000064400000000000000000000020660000000000000124510ustar 00000000000000The 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. rust-ini-0.17.0/README.rst000064400000000000000000000062620000000000000131350ustar 00000000000000INI in Rust ----------- .. image:: https://travis-ci.org/zonyitoo/rust-ini.svg?branch=master :target: https://travis-ci.org/zonyitoo/rust-ini .. image:: https://img.shields.io/crates/v/rust-ini.svg :target: https://crates.io/crates/rust-ini .. image:: https://docs.rs/rust-ini/badge.svg :target: https://docs.rs/rust-ini INI_ is an informal standard for configuration files for some platforms or software. INI files are simple text files with a basic structure composed of "sections" and "properties". .. _INI: http://en.wikipedia.org/wiki/INI_file This is an INI file parser in Rust_. .. _Rust: http://www.rust-lang.org/ .. code:: toml [dependencies] rust-ini = "0.17" Usage ===== * Create a Ini configuration file. .. code:: rust extern crate ini; use ini::Ini; fn main() { let mut conf = Ini::new(); conf.with_section(None::) .set("encoding", "utf-8"); conf.with_section(Some("User")) .set("given_name", "Tommy") .set("family_name", "Green") .set("unicode", "Raspberry树莓"); conf.with_section(Some("Book")) .set("name", "Rust cool"); conf.write_to_file("conf.ini").unwrap(); } Then you will get ``conf.ini`` .. code:: ini encoding=utf-8 [User] given_name=Tommy family_name=Green unicode=Raspberry\x6811\x8393 [Book] name=Rust cool * Read from file ``conf.ini`` .. code:: rust extern crate ini; use ini::Ini; fn main() { let conf = Ini::load_from_file("conf.ini").unwrap(); let section = conf.section(Some("User")).unwrap(); let tommy = section.get("given_name").unwrap(); let green = section.get("family_name").unwrap(); println!("{:?} {:?}", tommy, green); // iterating for (sec, prop) in &conf { println!("Section: {:?}", sec); for (key, value) in prop.iter() { println!("{:?}:{:?}", key, value); } } } * More details could be found in `examples`. License ======= `The MIT License (MIT)`_ .. _The MIT License (MIT): https://opensource.org/licenses/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. rust-ini-0.17.0/examples/test.rs000064400000000000000000000026520000000000000146100ustar 00000000000000use std::io::stdout; use ini::Ini; const CONF_FILE_NAME: &str = "test.ini"; fn main() { let mut conf = Ini::new(); conf.with_section(None::).set("encoding", "utf-8"); conf.with_section(Some("User")) .set("name", "Raspberry树莓") .set("value", "Pi"); conf.with_section(Some("Library")) .set("name", "Sun Yat-sen U") .set("location", "Guangzhou=world\x0ahahaha"); conf.section_mut(Some("Library")).unwrap().insert("seats", "42"); println!("---------------------------------------"); println!("Writing to file {:?}\n", CONF_FILE_NAME); conf.write_to(&mut stdout()).unwrap(); conf.write_to_file(CONF_FILE_NAME).unwrap(); println!("----------------------------------------"); println!("Reading from file {:?}", CONF_FILE_NAME); let i = Ini::load_from_file(CONF_FILE_NAME).unwrap(); println!("Iterating"); let general_section_name = "__General__"; for (sec, prop) in i.iter() { let section_name = sec.as_ref().unwrap_or(&general_section_name); println!("-- Section: {:?} begins", section_name); for (k, v) in prop.iter() { println!("{}: {:?}", k, v); } } println!(); let section = i.section(Some("User")).unwrap(); println!("name={}", section.get("name").unwrap()); println!("conf[User][name]={}", &i["User"]["name"]); println!("General Section: {:?}", i.general_section()); } rust-ini-0.17.0/rustfmt.toml000064400000000000000000000004650000000000000140460ustar 00000000000000max_width = 120 indent_style = "Visual" #fn_call_width = 120 reorder_imports = true reorder_imports_in_group = true reorder_imported_names = true condense_wildcard_suffixes = true #fn_args_layout = "Visual" #fn_call_style = "Visual" #chain_indent = "Visual" normalize_comments = true use_try_shorthand = true rust-ini-0.17.0/src/lib.rs000064400000000000000000001575510000000000000133610ustar 00000000000000// 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 parser for Rust //! //! ```no_run //! use ini::Ini; //! //! let mut conf = Ini::new(); //! conf.with_section(Some("User")) //! .set("name", "Raspberry树莓") //! .set("value", "Pi"); //! conf.with_section(Some("Library")) //! .set("name", "Sun Yat-sen U") //! .set("location", "Guangzhou=world"); //! conf.write_to_file("conf.ini").unwrap(); //! //! let i = Ini::load_from_file("conf.ini").unwrap(); //! for (sec, prop) in i.iter() { //! println!("Section: {:?}", sec); //! for (k, v) in prop.iter() { //! println!("{}:{}", k, v); //! } //! } //! ``` use std::char; use std::error; use std::fmt::{self, Display}; use std::fs::{File, OpenOptions}; use std::io::{self, Read, Write}; use std::io::{Seek, SeekFrom}; use std::ops::{Index, IndexMut}; use std::path::Path; use std::str::Chars; use cfg_if::cfg_if; use ordered_multimap::list_ordered_multimap::{Entry, Iter, IterMut, OccupiedEntry, VacantEntry}; use ordered_multimap::ListOrderedMultimap; #[cfg(feature = "case-insensitive")] use unicase::UniCase; /// Policies for escaping logic #[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 { // A single backslash, must be escaped // ASCII control characters, U+0000 NUL..= U+001F UNIT SEPARATOR, or U+007F DELETE. The same as char::is_ascii_control() '\\' | '\x00'..='\x1f' | '\x7f' => 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 } /// Parsing configuration pub struct ParseOption { /// Allow quote (`"` or `'`) in value /// For example /// ```ini /// [Section] /// Key1="Quoted value" /// Key2='Single Quote' with extra value /// ``` /// /// In this example, Value of `Key1` is `Quoted value`, /// and value of `Key2` is `Single Quote with extra value` /// if `enabled_quote` is set to `true`. pub enabled_quote: bool, /// Interpret `\` as an escape character /// For example /// ```ini /// [Section] /// Key1=C:\Windows /// ``` /// /// If `enabled_escape` is true, then the value of `Key` will become `C:Windows` (`\W` equals to `W`). pub enabled_escape: bool, } impl Default for ParseOption { fn default() -> ParseOption { ParseOption { enabled_quote: true, enabled_escape: true } } } /// Newline style #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum LineSeparator { /// System-dependent line separator /// /// On UNIX system, uses "\n" /// On Windows system, uses "\r\n" SystemDefault, /// Uses "\n" as new line separator CR, /// Uses "\r\n" as new line separator CRLF, } #[cfg(not(windows))] static DEFAULT_LINE_SEPARATOR: &str = "\n"; #[cfg(windows)] static DEFAULT_LINE_SEPARATOR: &str = "\r\n"; impl fmt::Display for LineSeparator { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { f.write_str(self.as_str()) } } impl LineSeparator { /// String representation pub fn as_str(self) -> &'static str { match self { LineSeparator::SystemDefault => DEFAULT_LINE_SEPARATOR, LineSeparator::CR => "\n", LineSeparator::CRLF => "\r\n", } } } /// Writing configuration #[derive(Debug, Clone)] pub struct WriteOption { /// Policies about how to escape characters pub escape_policy: EscapePolicy, /// Newline style pub line_separator: LineSeparator, } impl Default for WriteOption { fn default() -> WriteOption { WriteOption { escape_policy: EscapePolicy::Basics, line_separator: LineSeparator::SystemDefault } } } cfg_if! { if #[cfg(feature = "case-insensitive")] { /// Internal storage of section's key pub type SectionKey = Option>; /// Internal storage of property's key pub type PropertyKey = UniCase; macro_rules! property_get_key { ($s:expr) => { &UniCase::from($s) }; } macro_rules! property_insert_key { ($s:expr) => { UniCase::from($s) }; } macro_rules! section_key { ($s:expr) => { $s.map(|s| UniCase::from(s.into())) }; } } else { /// Internal storage of section's key pub type SectionKey = Option; /// Internal storage of property's key pub type PropertyKey = String; macro_rules! property_get_key { ($s:expr) => { $s }; } macro_rules! property_insert_key { ($s:expr) => { $s }; } macro_rules! section_key { ($s:expr) => { $s.map(Into::into) }; } } } /// 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, section_name } } /// Set (replace) key-value pair in this section (all with the same name) pub fn set(&'a mut self, key: K, value: V) -> &'a mut SectionSetter<'a> where K: Into, V: Into { self.ini .entry(self.section_name.clone()) .or_insert_with(Default::default) .insert(key, value); self } /// Delete the first entry in this section with `key` pub fn delete>(&'a mut self, key: &K) -> &'a mut SectionSetter<'a> { for prop in self.ini.section_all_mut(self.section_name.as_ref()) { prop.remove(key); } self } /// Get the entry in this section with `key` pub fn get>(&'a mut self, key: K) -> Option<&'a str> { self.ini .section(self.section_name.as_ref()) .and_then(|prop| prop.get(key)) .map(AsRef::as_ref) } } /// Properties type (key-value pairs) #[derive(Clone, Default, Debug, PartialEq)] pub struct Properties { data: ListOrderedMultimap, } impl Properties { /// Create an instance pub fn new() -> Properties { Default::default() } /// Get the number of the properties pub fn len(&self) -> usize { self.data.keys_len() } /// Check if properties has 0 elements pub fn is_empty(&self) -> bool { self.data.is_empty() } /// Get an iterator of the properties pub fn iter(&self) -> impl Iterator { self.data.iter().map(|(k, v)| (k.as_ref(), v.as_str())) } /// Return true if property exist pub fn contains_key>(&self, s: S) -> bool { self.data.contains_key(property_get_key!(s.as_ref())) } /// Insert (key, value) pair by replace pub fn insert(&mut self, k: K, v: V) where K: Into, V: Into { self.data.insert(property_insert_key!(k.into()), v.into()); } /// Append key with (key, value) pair pub fn append(&mut self, k: K, v: V) where K: Into, V: Into { self.data.append(property_insert_key!(k.into()), v.into()); } /// Get the first value associate with the key pub fn get>(&self, s: S) -> Option<&str> { self.data.get(property_get_key!(s.as_ref())).map(|v| v.as_str()) } /// Get all values associate with the key pub fn get_all>(&self, s: S) -> impl Iterator { self.data.get_all(property_get_key!(s.as_ref())).map(|v| v.as_str()) } /// Remove the property with the first value of the key pub fn remove>(&mut self, s: S) -> Option { self.data.remove(property_get_key!(s.as_ref())) } /// Remove the property with all values with the same key pub fn remove_all<'a, S: AsRef>(&'a mut self, s: S) -> impl Iterator + 'a { self.data.remove_all(property_get_key!(s.as_ref())) } fn get_mut>(&mut self, s: S) -> Option<&mut str> { self.data.get_mut(property_get_key!(s.as_ref())).map(|v| v.as_mut_str()) } } impl> Index for Properties { type Output = str; fn index(&self, index: S) -> &str { let s = index.as_ref(); match self.get(s) { Some(p) => p, None => panic!("Key `{}` does not exist", s), } } } /// A view into a vacant entry in a `Ini` pub struct SectionVacantEntry<'a> { inner: VacantEntry<'a, SectionKey, Properties>, } impl<'a> SectionVacantEntry<'a> { /// Insert one new section pub fn insert(self, value: Properties) -> &'a mut Properties { self.inner.insert(value) } } /// A view into a occupied entry in a `Ini` pub struct SectionOccupiedEntry<'a> { inner: OccupiedEntry<'a, SectionKey, Properties>, } impl<'a> SectionOccupiedEntry<'a> { /// Into the first internal mutable properties pub fn into_mut(self) -> &'a mut Properties { self.inner.into_mut() } /// Append a new section pub fn append(&mut self, prop: Properties) { self.inner.append(prop); } fn last_mut(&'a mut self) -> &'a mut Properties { self.inner .iter_mut() .next_back() .expect("occupied section shouldn't have 0 property") } } /// A view into an `Ini`, which may either be vacant or occupied. pub enum SectionEntry<'a> { Vacant(SectionVacantEntry<'a>), Occupied(SectionOccupiedEntry<'a>), } impl<'a> SectionEntry<'a> { /// Ensures a value is in the entry by inserting the default if empty, and returns a mutable reference to the value in the entry. pub fn or_insert(self, properties: Properties) -> &'a mut Properties { match self { SectionEntry::Occupied(e) => e.into_mut(), SectionEntry::Vacant(e) => e.insert(properties), } } /// Ensures a value is in the entry by inserting the result of the default function if empty, and returns a mutable reference to the value in the entry. pub fn or_insert_with Properties>(self, default: F) -> &'a mut Properties { match self { SectionEntry::Occupied(e) => e.into_mut(), SectionEntry::Vacant(e) => e.insert(default()), } } } impl<'a> From> for SectionEntry<'a> { fn from(e: Entry<'a, SectionKey, Properties>) -> SectionEntry<'a> { match e { Entry::Occupied(inner) => SectionEntry::Occupied(SectionOccupiedEntry { inner }), Entry::Vacant(inner) => SectionEntry::Vacant(SectionVacantEntry { inner }), } } } /// Ini struct #[derive(Clone, Default)] pub struct Ini { sections: ListOrderedMultimap, } impl Ini { /// Create an instance pub fn new() -> Ini { Default::default() } /// Set with a specified section, `None` is for the general section pub fn with_section(&mut self, section: Option) -> SectionSetter where S: Into { SectionSetter::new(self, section.map(Into::into)) } /// Set with general section, a simple wrapper of `with_section(None::)` pub fn with_general_section(&mut self) -> SectionSetter { self.with_section(None::) } /// 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(&self, name: Option) -> Option<&Properties> where S: Into { self.sections.get(§ion_key!(name)) } /// Get a mutable section pub fn section_mut(&mut self, name: Option) -> Option<&mut Properties> where S: Into { self.sections.get_mut(§ion_key!(name)) } /// Get all sections immutable with the same key pub fn section_all(&self, name: Option) -> impl Iterator where S: Into { self.sections.get_all(§ion_key!(name)) } /// Get all sections mutable with the same key pub fn section_all_mut(&mut self, name: Option) -> impl Iterator where S: Into { self.sections.get_all_mut(§ion_key!(name)) } /// Get the entry #[cfg(not(feature = "case-insensitive"))] pub fn entry(&mut self, name: Option) -> SectionEntry<'_> { SectionEntry::from(self.sections.entry(name.map(|s| s))) } /// Get the entry #[cfg(feature = "case-insensitive")] pub fn entry(&mut self, name: Option) -> SectionEntry<'_> { SectionEntry::from(self.sections.entry(name.map(UniCase::from))) } /// Clear all entries pub fn clear(&mut self) { self.sections.clear() } /// Iterate with sections pub fn sections(&self) -> impl Iterator> { self.sections.keys().map(|s| s.as_ref().map(AsRef::as_ref)) } /// 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 first value from the sections 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 { self.sections.get(§ion_key!(section)).and_then(|prop| prop.get(key)) } /// Get the first value from the sections 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 { self.get_from(section, key).unwrap_or(default) } /// Get the first mutable value from the sections with key pub fn get_from_mut<'a, S>(&'a mut self, section: Option, key: &str) -> Option<&'a mut str> where S: Into { self.sections .get_mut(§ion_key!(section)) .and_then(|prop| prop.get_mut(key)) } /// Delete the first section with key, return the properties if it exists pub fn delete(&mut self, section: Option) -> Option where S: Into { let key = section_key!(section); self.sections.remove(&key) } /// Delete the key from the section, return the value if key exists or None pub fn delete_from(&mut self, section: Option, key: &str) -> Option where S: Into { self.section_mut(section).and_then(|prop| prop.remove(key)) } /// Total sections count pub fn len(&self) -> usize { self.sections.keys_len() } /// Check if object coutains no section pub fn is_empty(&self) -> bool { self.sections.is_empty() } } impl> Index> for Ini { type Output = Properties; fn index(&self, index: Option) -> &Properties { match self.section(index) { Some(p) => p, None => panic!("Section does not exist"), } } } impl> IndexMut> for Ini { fn index_mut(&mut self, index: Option) -> &mut Properties { match self.section_mut(index) { Some(p) => p, None => panic!("Section does not exist"), } } } impl<'q> Index<&'q str> for Ini { type Output = Properties; fn index<'a>(&'a self, index: &'q str) -> &'a Properties { match self.section(Some(index)) { 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.section_mut(Some(index)) { 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 = OpenOptions::new().write(true) .truncate(true) .create(true) .open(filename.as_ref())?; self.write_to_policy(&mut file, policy) } /// Write to a file with options pub fn write_to_file_opt>(&self, filename: P, opt: WriteOption) -> io::Result<()> { let mut file = OpenOptions::new().write(true) .truncate(true) .create(true) .open(filename.as_ref())?; self.write_to_opt(&mut file, opt) } /// Write to a writer pub fn write_to(&self, writer: &mut W) -> io::Result<()> { self.write_to_opt(writer, Default::default()) } /// Write to a writer pub fn write_to_policy(&self, writer: &mut W, policy: EscapePolicy) -> io::Result<()> { self.write_to_opt(writer, WriteOption { escape_policy: policy, ..Default::default() }) } /// Write to a writer with options pub fn write_to_opt(&self, writer: &mut W, opt: WriteOption) -> io::Result<()> { let mut firstline = true; if let Some(props) = self.sections.get(&None) { for (k, v) in props.iter() { let k_str = escape_str(&k[..], opt.escape_policy); let v_str = escape_str(&v[..], opt.escape_policy); write!(writer, "{}={}{}", k_str, v_str, opt.line_separator)?; firstline = false; } } for (section, props) in &self.sections { if let Some(ref section) = *section { if firstline { firstline = false; } else { // Write an empty line between sections writer.write_all(opt.line_separator.as_str().as_bytes())?; } write!(writer, "[{}]{}", escape_str(§ion[..], opt.escape_policy), opt.line_separator)?; for (k, v) in props.iter() { let k_str = escape_str(&k[..], opt.escape_policy); let v_str = escape_str(&v[..], opt.escape_policy); write!(writer, "{}={}{}", k_str, v_str, opt.line_separator)?; } } } Ok(()) } } impl Ini { /// Load from a string pub fn load_from_str(buf: &str) -> Result { Ini::load_from_str_opt(buf, ParseOption::default()) } /// Load from a string, but do not interpret '\' as an escape character pub fn load_from_str_noescape(buf: &str) -> Result { Ini::load_from_str_opt(buf, ParseOption { enabled_escape: false, ..ParseOption::default() }) } /// Load from a string with options pub fn load_from_str_opt(buf: &str, opt: ParseOption) -> Result { let mut parser = Parser::new(buf.chars(), opt); parser.parse() } /// Load from a reader pub fn read_from(reader: &mut R) -> Result { Ini::read_from_opt(reader, ParseOption::default()) } /// Load from a reader, but do not interpret '\' as an escape character pub fn read_from_noescape(reader: &mut R) -> Result { Ini::read_from_opt(reader, ParseOption { enabled_escape: false, ..ParseOption::default() }) } /// Load from a reader with options pub fn read_from_opt(reader: &mut R, opt: ParseOption) -> Result { let mut s = String::new(); reader.read_to_string(&mut s).map_err(Error::Io)?; let mut parser = Parser::new(s.chars(), opt); match parser.parse() { Err(e) => Err(Error::Parse(e)), Ok(success) => Ok(success), } } /// Load from a file pub fn load_from_file>(filename: P) -> Result { Ini::load_from_file_opt(filename, ParseOption::default()) } /// Load from a file, but do not interpret '\' as an escape character pub fn load_from_file_noescape>(filename: P) -> Result { Ini::load_from_file_opt(filename, ParseOption { enabled_escape: false, ..ParseOption::default() }) } /// Load from a file with options pub fn load_from_file_opt>(filename: P, opt: ParseOption) -> Result { let mut reader = match File::open(filename.as_ref()) { Err(e) => { return Err(Error::Io(e)); } Ok(r) => r, }; let mut with_bom = false; // Check if file starts with a BOM marker // UTF-8: EF BB BF let mut bom = [0u8; 3]; if let Ok(..) = reader.read_exact(&mut bom) { if &bom == b"\xEF\xBB\xBF" { with_bom = true; } } if !with_bom { // Reset file pointer reader.seek(SeekFrom::Start(0))?; } Ini::read_from_opt(&mut reader, opt) } } /// Iterator for traversing sections pub struct SectionIter<'a> { inner: Iter<'a, SectionKey, Properties>, } impl<'a> Iterator for SectionIter<'a> { type Item = (Option<&'a str>, &'a Properties); fn next(&mut self) -> Option { self.inner.next().map(|(k, v)| (k.as_ref().map(|s| s.as_str()), v)) } } /// Iterator for traversing sections pub struct SectionIterMut<'a> { inner: IterMut<'a, SectionKey, Properties>, } impl<'a> Iterator for SectionIterMut<'a> { type Item = (Option<&'a str>, &'a mut Properties); fn next(&mut self) -> Option { self.inner.next().map(|(k, v)| (k.as_ref().map(|s| s.as_str()), v)) } } impl<'a> Ini { /// Immutable iterate though sections pub fn iter(&'a self) -> SectionIter<'a> { SectionIter { inner: self.sections.iter() } } /// Mutable iterate though sections /// *Deprecated! Use `iter_mut` instead!* pub fn mut_iter(&'a mut self) -> SectionIterMut<'a> { self.iter_mut() } /// Mutable iterate though sections pub fn iter_mut(&'a mut self) -> SectionIterMut<'a> { SectionIterMut { inner: self.sections.iter_mut() } } } impl<'a> IntoIterator for &'a Ini { type Item = (Option<&'a str>, &'a Properties); type IntoIter = SectionIter<'a>; fn into_iter(self) -> Self::IntoIter { self.iter() } } impl<'a> IntoIterator for &'a mut Ini { type Item = (Option<&'a str>, &'a mut Properties); type IntoIter = SectionIterMut<'a>; fn into_iter(self) -> Self::IntoIter { self.iter_mut() } } // Ini parser struct Parser<'a> { ch: Option, rdr: Chars<'a>, line: usize, col: usize, opt: ParseOption, } #[derive(Debug)] /// Parse error pub struct ParseError { pub line: usize, pub col: usize, pub msg: String, } impl Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}:{} {}", self.line, self.col, self.msg) } } impl error::Error for ParseError {} /// Error while parsing an INI document #[derive(Debug)] pub enum Error { Io(io::Error), Parse(ParseError), } impl Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Error::Io(ref err) => err.fmt(f), Error::Parse(ref err) => err.fmt(f), } } } impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match *self { Error::Io(ref err) => err.source(), Error::Parse(ref err) => err.source(), } } } impl From for Error { fn from(err: io::Error) -> Self { Error::Io(err) } } impl<'a> Parser<'a> { // Create a parser pub fn new(rdr: Chars<'a>, opt: ParseOption) -> Parser<'a> { let mut p = Parser { ch: None, line: 0, col: 0, rdr, opt }; 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: M) -> Result { Err(ParseError { line: self.line, col: self.col, msg: msg.into() }) } /// 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 { ';' | '#' => { if cfg!(not(feature = "inline-comment")) { // Inline comments is not supported, so comments must starts from a new line // // https://en.wikipedia.org/wiki/INI_file#Comments if self.col > 1 { return self.error("doesn't support inline comment"); } } self.parse_comment(); } '[' => match self.parse_section() { Ok(sec) => { let msec = sec[..].trim(); cursec = Some((*msec).to_string()); match result.entry(cursec.clone()) { SectionEntry::Vacant(v) => { v.insert(Default::default()); } SectionEntry::Occupied(mut o) => { o.append(Default::default()); } } self.bump(); } Err(e) => return Err(e), }, '=' | ':' => { if (&curkey[..]).is_empty() { return self.error("missing key"); } match self.parse_val() { Ok(val) => { let mval = val[..].trim().to_owned(); match result.entry(cursec.clone()) { SectionEntry::Vacant(v) => { // cursec must be None (the General Section) let mut prop = Properties::new(); prop.insert(curkey, mval); v.insert(prop); } SectionEntry::Occupied(mut o) => { // Insert into the last (current) section o.last_mut().append(curkey, mval); } } curkey = "".into(); } Err(e) => return Err(e), } } _ => match self.parse_key() { Ok(key) => { let mkey: String = key[..].trim().to_owned(); curkey = mkey; } 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.opt.enabled_escape => { 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) => match char::from_u32(c) { Some(c) => result.push(c), None => { return self.error("unknown character in \\xHH form"); } }, Err(_) => return self.error("unknown character in \\xHH form"), } } 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('"') if self.opt.enabled_quote => { self.bump(); self.parse_str_until(&[Some('"')]).and_then(|s| { self.bump(); // Eats the last " // Parse until EOL self.parse_str_until_eol().map(|x| s + &x) }) } Some('\'') if self.opt.enabled_quote => { self.bump(); self.parse_str_until(&[Some('\'')]).and_then(|s| { self.bump(); // Eats the last ' // Parse until EOL self.parse_str_until_eol().map(|x| s + &x) }) } _ => self.parse_str_until_eol(), } } #[cfg(not(feature = "inline-comment"))] fn parse_str_until_eol(&mut self) -> Result { self.parse_str_until(&[Some('\n'), Some('\r'), None]) } #[cfg(feature = "inline-comment")] fn parse_str_until_eol(&mut self) -> Result { self.parse_str_until(&[Some('\n'), Some('\r'), Some(';'), Some('#'), None]) } } // ------------------------------------------------------------------------------ #[cfg(test)] mod test { use std::env::temp_dir; use super::*; #[test] fn property_replace() { let mut props = Properties::new(); props.insert("k1", "v1"); assert_eq!(Some("v1"), props.get("k1")); let res = props.get_all("k1").collect::>(); assert_eq!(res, vec!["v1"]); props.insert("k1", "v2"); assert_eq!(Some("v2"), props.get("k1")); let res = props.get_all("k1").collect::>(); assert_eq!(res, vec!["v2"]); } #[test] fn property_get_vec() { let mut props = Properties::new(); props.append("k1", "v1"); assert_eq!(Some("v1"), props.get("k1")); props.append("k1", "v2"); assert_eq!(Some("v1"), props.get("k1")); let res = props.get_all("k1").collect::>(); assert_eq!(res, vec!["v1", "v2"]); let res = props.get_all("k2").collect::>(); assert!(res.is_empty()); } #[test] fn property_remove() { let mut props = Properties::new(); props.append("k1", "v1"); props.append("k1", "v2"); let res = props.remove_all("k1").collect::>(); assert_eq!(res, vec!["v1", "v2"]); assert!(!props.contains_key("k1")); } #[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.len(), 2); assert!(output.section(Some("sec1")).is_some()); let sec1 = output.section(Some("sec1")).unwrap(); 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 parse_comment() { let input = "; abcdefghijklmn\n"; let opt = Ini::load_from_str(input); assert!(opt.is_ok()); } #[cfg(not(feature = "inline-comment"))] #[test] fn inline_comment_not_supported() { 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 # abcdefg"); assert_eq!(ini.get_from(Some("section name"), "gender").unwrap(), "mail ; abdddd"); } #[test] #[cfg_attr(not(feature = "inline-comment"), should_panic)] fn inline_comment() { let input = " [section name] # comment in section line 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 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 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 colon() { let input = " [section name] name: hello gender : mail "; 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 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 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 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 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 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 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 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.len(), 1); assert!(output.section(None::).is_some()); let sec1 = output.section(None::).unwrap(); 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.len(), 1); assert!(output.section(None::).is_some()); let sec1 = output.section(None::).unwrap(); 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.len(), 1); assert!(output.section(None::).is_some()); let sec1 = output.section(None::).unwrap(); 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_file_with_bom() { let file_name = temp_dir().join("rust_ini_load_from_file_with_bom"); let file_content = b"\xEF\xBB\xBF[Test]Key=Value\n"; { let mut file = File::create(&file_name).expect("create"); file.write_all(file_content).expect("write"); } let ini = Ini::load_from_file(&file_name).unwrap(); assert_eq!(ini.get_from(Some("Test"), "Key"), Some("Value")); } #[test] fn load_from_file_without_bom() { let file_name = temp_dir().join("rust_ini_load_from_file_without_bom"); let file_content = b"[Test]Key=Value\n"; { let mut file = File::create(&file_name).expect("create"); file.write_all(file_content).expect("write"); } let ini = Ini::load_from_file(&file_name).unwrap(); assert_eq!(ini.get_from(Some("Test"), "Key"), Some("Value")); } #[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.section(None::).unwrap(); 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.len(), 1); let sec = output.section(None::).unwrap(); assert_eq!(sec.len(), 1); assert!(sec.contains_key("path")); assert_eq!(&sec["path"], "C:\\Windows\\Some\\Folder\\"); } #[test] fn partial_quoting_double() { let input = " [Section] A=\"quote\" arg0 B=b"; let opt = Ini::load_from_str(input).unwrap(); let sec = opt.section(Some("Section")).unwrap(); assert_eq!(&sec["A"], "quote arg0"); assert_eq!(&sec["B"], "b"); } #[test] fn partial_quoting_single() { let input = " [Section] A='quote' arg0 B=b"; let opt = Ini::load_from_str(input).unwrap(); let sec = opt.section(Some("Section")).unwrap(); assert_eq!(&sec["A"], "quote arg0"); assert_eq!(&sec["B"], "b"); } #[test] fn parse_without_quote() { let input = " [Desktop Entry] Exec = \"/path/to/exe with space\" arg "; let opt = Ini::load_from_str_opt(input, ParseOption { enabled_quote: false, ..ParseOption::default() }).unwrap(); let sec = opt.section(Some("Desktop Entry")).unwrap(); assert_eq!(&sec["Exec"], "\"/path/to/exe with space\" arg"); } #[test] #[cfg(feature = "case-insensitive")] fn case_insensitive() { let input = " [SecTION] KeY=value "; let ini = Ini::load_from_str(input).unwrap(); let section = ini.section(Some("section")).unwrap(); let val = section.get("key").unwrap(); assert_eq!("value", val); } #[test] fn preserve_order_section() { let input = r" none2 = n2 [SB] p2 = 2 [SA] x2 = 2 [SC] cd1 = x [xC] xd = x "; let data = Ini::load_from_str(input).unwrap(); let keys: Vec> = data.iter().map(|(k, _)| k).collect(); assert_eq!(keys.len(), 5); assert_eq!(keys[0], None); assert_eq!(keys[1], Some("SB")); assert_eq!(keys[2], Some("SA")); assert_eq!(keys[3], Some("SC")); assert_eq!(keys[4], Some("xC")); } #[test] fn preserve_order_property() { let input = r" x2 = n2 x1 = n2 x3 = n2 "; let data = Ini::load_from_str(input).unwrap(); let section = data.general_section(); let keys: Vec<&str> = section.iter().map(|(k, _)| k).collect(); assert_eq!(keys, vec!["x2", "x1", "x3"]); } #[test] fn preserve_order_property_in_section() { let input = r" [s] x2 = n2 xb = n2 a3 = n3 "; let data = Ini::load_from_str(input).unwrap(); let section = data.section(Some("s")).unwrap(); let keys: Vec<&str> = section.iter().map(|(k, _)| k).collect(); assert_eq!(keys, vec!["x2", "xb", "a3"]) } #[test] fn preserve_order_write() { let input = r" x2 = n2 x1 = n2 x3 = n2 [s] x2 = n2 xb = n2 a3 = n3 "; let data = Ini::load_from_str(input).unwrap(); let mut buf = vec![]; data.write_to(&mut buf).unwrap(); let new_data = Ini::load_from_str(&String::from_utf8(buf).unwrap()).unwrap(); let sec0 = new_data.general_section(); let keys0: Vec<&str> = sec0.iter().map(|(k, _)| k).collect(); assert_eq!(keys0, vec!["x2", "x1", "x3"]); let sec1 = new_data.section(Some("s")).unwrap(); let keys1: Vec<&str> = sec1.iter().map(|(k, _)| k).collect(); assert_eq!(keys1, vec!["x2", "xb", "a3"]); } #[test] fn write_line_separator() { use std::str; let mut ini = Ini::new(); ini.with_section(Some("Section1")) .set("Key1", "Value") .set("Key2", "Value"); ini.with_section(Some("Section2")) .set("Key1", "Value") .set("Key2", "Value"); { let mut buf = Vec::new(); ini.write_to_opt(&mut buf, WriteOption { line_separator: LineSeparator::CR, ..Default::default() }) .unwrap(); assert_eq!("[Section1]\nKey1=Value\nKey2=Value\n\n[Section2]\nKey1=Value\nKey2=Value\n", str::from_utf8(&buf).unwrap()); } { let mut buf = Vec::new(); ini.write_to_opt(&mut buf, WriteOption { line_separator: LineSeparator::CRLF, ..Default::default() }) .unwrap(); assert_eq!("[Section1]\r\nKey1=Value\r\nKey2=Value\r\n\r\n[Section2]\r\nKey1=Value\r\nKey2=Value\r\n", str::from_utf8(&buf).unwrap()); } { let mut buf = Vec::new(); ini.write_to_opt(&mut buf, WriteOption { line_separator: LineSeparator::SystemDefault, ..Default::default() }) .unwrap(); if cfg!(windows) { assert_eq!("[Section1]\r\nKey1=Value\r\nKey2=Value\r\n\r\n[Section2]\r\nKey1=Value\r\nKey2=Value\r\n", str::from_utf8(&buf).unwrap()); } else { assert_eq!("[Section1]\nKey1=Value\nKey2=Value\n\n[Section2]\nKey1=Value\nKey2=Value\n", str::from_utf8(&buf).unwrap()); } } } #[test] fn duplicate_sections() { // https://github.com/zonyitoo/rust-ini/issues/49 let input = r" [Peer] foo = a bar = b [Peer] foo = c bar = d [Peer] foo = e bar = f "; let ini = Ini::load_from_str(input).unwrap(); assert_eq!(3, ini.section_all(Some("Peer")).count()); let mut iter = ini.iter(); let (k1, p1) = iter.next().unwrap(); assert_eq!(Some("Peer"), k1); assert_eq!(Some("a"), p1.get("foo")); assert_eq!(Some("b"), p1.get("bar")); let (k2, p2) = iter.next().unwrap(); assert_eq!(Some("Peer"), k2); assert_eq!(Some("c"), p2.get("foo")); assert_eq!(Some("d"), p2.get("bar")); let (k3, p3) = iter.next().unwrap(); assert_eq!(Some("Peer"), k3); assert_eq!(Some("e"), p3.get("foo")); assert_eq!(Some("f"), p3.get("bar")); assert_eq!(None, iter.next()); } #[test] fn fix_issue63() { let section = "PHP"; let key = "engine"; let value = "On"; let new_value = "Off"; // create a new configuration let mut conf = Ini::new(); conf.with_section(Some(section)).set(key, value); // assert the value is the one expected let v = conf.get_from(Some(section), key).unwrap(); assert_eq!(v, value); // update the section/key with a new value conf.set_to(Some(section), key.to_string(), new_value.to_string()); // assert the new value was set let v = conf.get_from(Some(section), key).unwrap(); assert_eq!(v, new_value); } #[test] fn fix_issue64() { let input = format!("some-key=åäö{}", super::DEFAULT_LINE_SEPARATOR); let conf = Ini::load_from_str(&input).unwrap(); let mut output = Vec::new(); conf.write_to_policy(&mut output, EscapePolicy::Basics).unwrap(); assert_eq!(input, String::from_utf8(output).unwrap()); } #[test] fn invalid_codepoint() { use std::io::Cursor; let d = vec![10, 8, 68, 8, 61, 10, 126, 126, 61, 49, 10, 62, 8, 8, 61, 10, 91, 93, 93, 36, 91, 61, 10, 75, 91, 10, 10, 10, 61, 92, 120, 68, 70, 70, 70, 70, 70, 126, 61, 10, 0, 0, 61, 10, 38, 46, 49, 61, 0, 39, 0, 0, 46, 92, 120, 46, 36, 91, 91, 1, 0, 0, 16, 0, 0, 0, 0, 0, 0]; let mut file = Cursor::new(d); assert!(Ini::read_from(&mut file).is_err()); } }