summaryrefslogtreecommitdiffstats
path: root/third_party/rust/rust-ini/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/rust-ini/src
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/rust-ini/src')
-rw-r--r--third_party/rust/rust-ini/src/ini.rs1150
-rw-r--r--third_party/rust/rust-ini/src/lib.rs47
2 files changed, 1197 insertions, 0 deletions
diff --git a/third_party/rust/rust-ini/src/ini.rs b/third_party/rust/rust-ini/src/ini.rs
new file mode 100644
index 0000000000..9e1e3b2d14
--- /dev/null
+++ b/third_party/rust/rust-ini/src/ini.rs
@@ -0,0 +1,1150 @@
+// 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<String>,
+}
+
+impl<'a> SectionSetter<'a> {
+ fn new(ini: &'a mut Ini, section_name: Option<String>) -> SectionSetter<'a> {
+ SectionSetter {
+ ini: ini,
+ section_name: section_name,
+ }
+ }
+
+ /// Set key-value pair in this section
+ pub fn set<K, V>(&'a mut self, key: K, value: V) -> &'a mut SectionSetter<'a>
+ where
+ K: Into<String>,
+ V: Into<String>,
+ {
+ {
+ 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<K>(&'a mut self, key: &K) -> &'a mut SectionSetter<'a>
+ where
+ String: Borrow<K>,
+ 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<K>(&'a mut self, key: &K) -> Option<&'a str>
+ where
+ String: Borrow<K>,
+ 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<String, String>; // Key-value pairs
+
+/// Ini struct
+#[derive(Clone)]
+pub struct Ini {
+ sections: HashMap<Option<String>, 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<S>) -> SectionSetter<'b>
+ where
+ S: Into<String>,
+ {
+ SectionSetter::new(self, section.map(|s| s.into()))
+ }
+
+ /// Get the immmutable general section
+ pub fn general_section(&self) -> &Properties {
+ self.section(None::<String>)
+ .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::<String>)
+ .expect("There is no general section in this Ini")
+ }
+
+ /// Get a immutable section
+ pub fn section<'a, S>(&'a self, name: Option<S>) -> Option<&'a Properties>
+ where
+ S: Into<String>,
+ {
+ self.sections.get(&name.map(|s| s.into()))
+ }
+
+ /// Get a mutable section
+ pub fn section_mut<'a, S>(&'a mut self, name: Option<S>) -> Option<&'a mut Properties>
+ where
+ S: Into<String>,
+ {
+ self.sections.get_mut(&name.map(|s| s.into()))
+ }
+
+ /// Get the entry
+ pub fn entry<'a>(&'a mut self, name: Option<String>) -> Entry<Option<String>, 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<String>, Properties> {
+ self.sections.keys()
+ }
+
+ /// Set key-value to a section
+ pub fn set_to<S>(&mut self, section: Option<S>, key: String, value: String)
+ where
+ S: Into<String>,
+ {
+ 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<S>, key: &str) -> Option<&'a str>
+ where
+ S: Into<String>,
+ {
+ match self.sections.get(&section.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<S>, key: &str, default: &'a str) -> &'a str
+ where
+ S: Into<String>,
+ {
+ match self.sections.get(&section.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<S>, key: &str) -> Option<&'a str>
+ where
+ S: Into<String>,
+ {
+ match self.sections.get_mut(&section.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<S>(&mut self, section: Option<S>) -> Option<Properties>
+ where
+ S: Into<String>,
+ {
+ self.sections.remove(&section.map(|s| s.into()))
+ }
+
+ pub fn delete_from<S>(&mut self, section: Option<S>, key: &str) -> Option<String>
+ where
+ S: Into<String>,
+ {
+ match self.section_mut(section) {
+ None => return None,
+ Some(prop) => prop.remove(key),
+ }
+ }
+}
+
+impl<'q> Index<&'q Option<String>> for Ini {
+ type Output = Properties;
+
+ fn index<'a>(&'a self, index: &'q Option<String>) -> &'a Properties {
+ match self.sections.get(index) {
+ Some(p) => p,
+ None => panic!("Section `{:?}` does not exist", index),
+ }
+ }
+}
+
+impl<'i> IndexMut<&'i Option<String>> for Ini {
+ fn index_mut<'a>(&'a mut self, index: &Option<String>) -> &'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<P: AsRef<Path>>(&self, filename: P) -> io::Result<()> {
+ self.write_to_file_policy(filename, EscapePolicy::Basics)
+ }
+
+ /// Write to a file
+ pub fn write_to_file_policy<P: AsRef<Path>>(
+ &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<W: Write>(&self, writer: &mut W) -> io::Result<()> {
+ self.write_to_policy(writer, EscapePolicy::Basics)
+ }
+
+ /// Write to a writer
+ pub fn write_to_policy<W: Write>(
+ &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(&section[..], 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<Ini, Error> {
+ 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<Ini, Error> {
+ let mut parser = Parser::new(buf.chars(), true);
+ parser.parse()
+ }
+
+ /// Load from a reader
+ pub fn read_from<R: Read>(reader: &mut R) -> Result<Ini, Error> {
+ 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<R: Read>(reader: &mut R) -> Result<Ini, Error> {
+ 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<P: AsRef<Path>>(filename: P) -> Result<Ini, Error> {
+ 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<P: AsRef<Path>>(filename: P) -> Result<Ini, Error> {
+ 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<String>, Properties>,
+}
+
+/// Iterator for mutable sections
+pub struct SectionMutIterator<'a> {
+ mapiter: IterMut<'a, Option<String>, 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<String>, &'a Properties);
+
+ #[inline]
+ fn next(&mut self) -> Option<(&'a Option<String>, &'a Properties)> {
+ self.mapiter.next()
+ }
+}
+
+impl<'a> Iterator for SectionMutIterator<'a> {
+ type Item = (&'a Option<String>, &'a mut Properties);
+
+ #[inline]
+ fn next(&mut self) -> Option<(&'a Option<String>, &'a mut Properties)> {
+ self.mapiter.next()
+ }
+}
+
+impl<'a> IntoIterator for &'a Ini {
+ type Item = (&'a Option<String>, &'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<String>, &'a mut Properties);
+ type IntoIter = SectionMutIterator<'a>;
+
+ fn into_iter(self) -> SectionMutIterator<'a> {
+ self.iter_mut()
+ }
+}
+
+pub struct SectionIntoIter {
+ iter: IntoIter<Option<String>, Properties>,
+}
+
+impl Iterator for SectionIntoIter {
+ type Item = (Option<String>, Properties);
+ fn next(&mut self) -> Option<Self::Item> {
+ self.iter.next()
+ }
+}
+
+impl IntoIterator for Ini {
+ type Item = (Option<String>, Properties);
+ type IntoIter = SectionIntoIter;
+
+ fn into_iter(self) -> SectionIntoIter {
+ SectionIntoIter {
+ iter: self.sections.into_iter(),
+ }
+ }
+}
+
+// Ini parser
+struct Parser<'a> {
+ ch: Option<char>,
+ 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<U>(&self, msg: String) -> Result<U, Error> {
+ 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<Ini, Error> {
+ let mut result = Ini::new();
+ let mut curkey: String = "".into();
+ let mut cursec: Option<String> = 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<char>]) -> Result<String, Error> {
+ 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<String, Error> {
+ // Skip [
+ self.bump();
+ self.parse_str_until(&[Some(']')])
+ }
+
+ fn parse_key(&mut self) -> Result<String, Error> {
+ self.parse_str_until(&[Some('='), Some(':')])
+ }
+
+ fn parse_val(&mut self) -> Result<String, Error> {
+ 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::<String>));
+
+ let sec1 = &output.sections[&None::<String>];
+ 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::<String>));
+ let sec1 = &output.sections[&None::<String>];
+ 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::<String>));
+ let sec1 = &output.sections[&None::<String>];
+ 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::<String>];
+
+ 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::<String>];
+ assert_eq!(sec.len(), 1);
+ assert!(sec.contains_key("path"));
+ assert_eq!(sec["path"], "C:\\Windows\\Some\\Folder\\");
+ }
+}
diff --git a/third_party/rust/rust-ini/src/lib.rs b/third_party/rust/rust-ini/src/lib.rs
new file mode 100644
index 0000000000..7eab7e6536
--- /dev/null
+++ b/third_party/rust/rust-ini/src/lib.rs
@@ -0,0 +1,47 @@
+// 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".to_owned()))
+//! .set("name", "Raspberry树莓")
+//! .set("value", "Pi");
+//! conf.with_section(Some("Library".to_owned()))
+//! .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);
+//! }
+//! }
+//! ```
+
+pub use ini::Ini;
+pub mod ini;