mod error;
mod ordered_hashmap;
mod parser;
pub use error::{Error, ParseError};
use ordered_hashmap::OrderedHashMap;
use parser::{parse_line, Parsed};
use std::fmt;
use std::fs::File;
use std::io::{self, BufReader, BufWriter, Read, Write};
use std::iter::Iterator;
use std::path::Path;
use std::str::FromStr;
#[derive(Debug)]
pub struct Ini {
#[doc(hidden)]
document: OrderedHashMap<String, Section>,
last_section_name: String,
empty_section: Section,
}
impl Ini {
pub fn new() -> Ini {
Ini { document: OrderedHashMap::new(), last_section_name: String::new(), empty_section: Section::new() }
}
fn parse(string: &str) -> Result<Ini, Error> {
let mut result = Ini::new();
for (index, line) in string.lines().enumerate() {
match parse_line(&line, index + 1)? {
Parsed::Section(name) => result = result.section(name),
Parsed::Value(name, value) => result = result.item(name, value),
_ => (),
};
}
Ok(result)
}
pub fn from_file<S>(path: &S) -> Result<Ini, Error>
where
S: AsRef<Path> + ?Sized,
{
let file = File::open(path)?;
let mut reader = BufReader::new(file);
Ini::from_reader(&mut reader)
}
pub fn from_reader<R>(reader: &mut R) -> Result<Ini, Error>
where
R: Read,
{
let mut buffer = String::new();
reader.read_to_string(&mut buffer)?;
Ini::parse(&buffer)
}
pub fn from_string<S>(buf: S) -> Result<Ini, Error>
where
S: Into<String>,
{
Ini::parse(&buf.into())
}
pub fn to_file<S>(&self, path: &S) -> Result<(), io::Error>
where
S: AsRef<Path> + ?Sized,
{
let file = File::create(path)?;
let mut writer = BufWriter::new(file);
self.to_writer(&mut writer)
}
pub fn to_writer<W>(&self, writer: &mut W) -> Result<(), io::Error>
where
W: Write,
{
writer.write_all(self.to_string().as_bytes())?;
Ok(())
}
pub fn section<S>(mut self, name: S) -> Self
where
S: Into<String>,
{
self.last_section_name = name.into();
self
}
pub fn item<N, V>(mut self, name: N, value: V) -> Self
where
N: Into<String>,
V: fmt::Display,
{
self.document
.entry(self.last_section_name.clone())
.or_insert_with(Section::new)
.inner
.insert(name.into(), value.to_string());
self
}
pub fn item_vec_with_sep<S, V>(mut self, name: S, vector: &[V], sep: &str) -> Self
where
S: Into<String>,
V: fmt::Display,
{
let vector_data = vector.iter().map(|v| v.to_string()).collect::<Vec<_>>().join(sep);
self.document
.entry(self.last_section_name.clone())
.or_insert_with(Section::new)
.insert(name.into(), vector_data);
self
}
pub fn item_vec<S, V>(self, name: S, vector: &[V]) -> Self
where
S: Into<String>,
V: fmt::Display,
{
self.item_vec_with_sep(name, vector, ", ")
}
pub fn items<K, V, I>(mut self, items: I) -> Self
where
K: Into<String>,
V: fmt::Display,
I: IntoIterator<Item = (K, V)>,
{
for (k, v) in items {
self = self.item(k.into(), v.to_string());
}
self
}
pub fn clear(mut self) -> Self {
self.document.remove(&self.last_section_name);
self
}
pub fn erase(mut self, key: &str) -> Self {
self.document.get_mut(&self.last_section_name).and_then(|s| s.remove(key));
self
}
fn get_raw(&self, section: &str, key: &str) -> Option<&String> {
self.document.get(section).and_then(|s| s.get_raw(key))
}
pub fn get<T>(&self, section: &str, key: &str) -> Option<T>
where
T: FromStr,
{
self.get_raw(section, key).and_then(|x| x.parse().ok())
}
pub fn get_vec<T>(&self, section: &str, key: &str) -> Option<Vec<T>>
where
T: FromStr,
{
self.get_vec_with_sep(section, key, ",")
}
pub fn get_vec_with_sep<T>(&self, section: &str, key: &str, sep: &str) -> Option<Vec<T>>
where
T: FromStr,
{
self.get_raw(section, key)
.and_then(|x| x.split(sep).map(|s| s.trim().parse()).collect::<Result<Vec<T>, _>>().ok())
}
pub fn section_iter(&self, section: &str) -> SectionIter {
self.document.get(section).unwrap_or(&self.empty_section).iter()
}
pub fn iter(&self) -> IniIter {
IniIter { iter: self.document.iter() }
}
pub fn iter_mut(&mut self) -> IniIterMut {
IniIterMut { iter: self.document.iter_mut() }
}
}
impl fmt::Display for Ini {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut items = Vec::new();
for (name, section) in self.iter() {
items.push(format!("[{}]", name));
for (key, value) in section.iter() {
items.push(format!("{} = {}", key, value));
}
items.push("".to_string());
}
write!(f, "{}", items.join("\n"))
}
}
impl Default for Ini {
fn default() -> Self {
Self::new()
}
}
pub struct IniIter<'a> {
#[doc(hidden)]
iter: ordered_hashmap::Iter<'a, String, Section>,
}
impl<'a> Iterator for IniIter<'a> {
type Item = (&'a String, &'a Section);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.iter.next()
}
}
pub struct IniIterMut<'a> {
#[doc(hidden)]
iter: ordered_hashmap::IterMut<'a, String, Section>,
}
impl<'a> Iterator for IniIterMut<'a> {
type Item = (&'a String, &'a mut Section);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.iter.next()
}
}
#[derive(Debug)]
pub struct Section {
inner: OrderedHashMap<String, String>,
}
pub struct SectionIter<'a> {
#[doc(hidden)]
iter: ordered_hashmap::Iter<'a, String, String>,
}
impl<'a> Iterator for SectionIter<'a> {
type Item = (&'a String, &'a String);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.iter.next()
}
}
pub struct SectionIterMut<'a> {
#[doc(hidden)]
iter: ordered_hashmap::IterMut<'a, String, String>,
}
impl<'a> Iterator for SectionIterMut<'a> {
type Item = (&'a String, &'a mut String);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.iter.next()
}
}
impl Section {
pub fn new() -> Self {
Section { inner: OrderedHashMap::new() }
}
pub fn get<'a, T>(&'a self, key: &str) -> Option<T>
where
T: FromStr,
{
self.inner.get(key).and_then(|x| x.parse().ok())
}
pub fn get_raw<'a>(&'a self, key: &str) -> Option<&String> {
self.inner.get(key)
}
pub fn remove(&mut self, key: &str) -> Option<String> {
self.inner.remove(key)
}
pub fn insert(&mut self, key: String, value: String) {
self.inner.insert(key, value);
}
pub fn iter(&self) -> SectionIter {
SectionIter { iter: self.inner.iter() }
}
pub fn iter_mut(&mut self) -> SectionIterMut {
SectionIterMut { iter: self.inner.iter_mut() }
}
}
#[cfg(test)]
mod library_test {
use super::*;
#[test]
fn bool() -> Result<(), Error> {
let ini = Ini::from_string("[string]\nabc = true")?;
let abc: Option<bool> = ini.get("string", "abc");
assert_eq!(abc, Some(true));
Ok(())
}
#[test]
fn float() -> Result<(), Error> {
let ini = Ini::from_string("[section]\nname=10.5")?;
let name: Option<f64> = ini.get("section", "name");
assert_eq!(name, Some(10.5));
Ok(())
}
#[test]
fn float_vec() -> Result<(), Error> {
let ini = Ini::from_string("[section]\nname=1.2, 3.4, 5.6")?;
let name: Option<Vec<f64>> = ini.get_vec("section", "name");
assert_eq!(name, Some(vec![1.2, 3.4, 5.6]));
Ok(())
}
#[test]
fn empty_key() {
match Ini::from_string("[a]\nx = 1\n=2") {
Err(Error::Parse(ParseError::EmptyKey(index))) => assert_eq!(index, 3),
_ => assert!(false),
}
}
#[test]
fn invalid_section() {
match Ini::from_string("[a]\nx = 1\ny = 2\n[b") {
Err(Error::Parse(ParseError::IncorrectSection(index))) => assert_eq!(index, 4),
_ => assert!(false),
}
}
#[test]
fn invalid_syntax() {
match Ini::from_string("[a]\n\t- b") {
Err(Error::Parse(ParseError::IncorrectSyntax(index))) => assert_eq!(index, 2),
_ => assert!(false),
}
}
#[test]
fn bad_cast() {
let ini = Ini::new().section("one").item("a", 3.14);
let a: Option<u32> = ini.get("one", "a");
assert_eq!(a, None);
}
#[test]
fn string_vec() -> Result<(), Error> {
let ini = Ini::from_string("[section]\nname=a, b, c")?;
let name: Vec<String> = ini.get_vec("section", "name").unwrap_or(vec![]);
assert_eq!(name, ["a", "b", "c"]);
Ok(())
}
#[test]
fn parse_error() -> Result<(), Error> {
let ini = Ini::from_string("[section]\nlist = 1, 2, --, 4")?;
let name: Option<Vec<u8>> = ini.get_vec("section", "list");
assert_eq!(name, None);
Ok(())
}
#[test]
fn get_or_macro() -> Result<(), Error> {
let ini = Ini::from_string("[section]\nlist = 1, 2, --, 4")?;
let with_value: Vec<u8> = ini.get_vec("section", "list").unwrap_or(vec![1, 2, 3, 4]);
assert_eq!(with_value, [1, 2, 3, 4]);
Ok(())
}
#[test]
fn ordering_iter() -> Result<(), Error> {
let ini = Ini::from_string("[a]\nc = 1\nb = 2\na = 3")?;
let keys: Vec<&String> = ini.document.get("a").unwrap().iter().map(|(k, _)| k).collect();
assert_eq!(["c", "b", "a"], keys[..]);
Ok(())
}
#[test]
fn random_access_section() -> Result<(), Error> {
let ini = Ini::from_string("[a]\nc = 1\nb = 2\na = 3\n\n[b]\na = 1\nb = 2\nc = 3\n\n[c]\na=0\nd=4")?;
for (name, section) in ini.iter() {
match name.as_str() {
"a" => {
assert_eq!(section.get_raw("a"), Some(&"3".to_owned()));
assert_eq!(section.get_raw("d"), None);
}
"b" => {
assert_eq!(section.get_raw("a"), Some(&"1".to_owned()));
assert_eq!(section.get_raw("d"), None);
}
"c" => {
assert_eq!(section.get_raw("a"), Some(&"0".to_owned()));
assert_eq!(section.get_raw("b"), None);
assert_eq!(section.get_raw("d"), Some(&"4".to_owned()));
}
_ => unreachable!(),
}
}
Ok(())
}
}