Added support for toml and yaml configurations.

This commit is contained in:
Aaron Weiss 2017-06-29 00:31:27 -07:00
parent a63dbb5422
commit c749146d5c
No known key found for this signature in database
GPG key ID: 0237035D9BF03AE2
6 changed files with 274 additions and 77 deletions

View file

@ -7,10 +7,10 @@ sudo: false
script: script:
- chmod +x mktestconfig.sh - chmod +x mktestconfig.sh
- ./mktestconfig.sh - ./mktestconfig.sh
- cargo build --verbose --features "toml yaml"
- cargo test --verbose --features "toml yaml"
- cargo build --verbose --no-default-features - cargo build --verbose --no-default-features
- cargo build --verbose
- cargo test --verbose --no-default-features - cargo test --verbose --no-default-features
- cargo test --verbose
notifications: notifications:
email: false email: false
irc: irc:

View file

@ -11,9 +11,11 @@ repository = "https://github.com/aatxe/irc"
readme = "README.md" readme = "README.md"
[features] [features]
default = ["ctcp"] default = ["ctcp", "json"]
ctcp = [] ctcp = []
nochanlists = [] nochanlists = []
json = ["serde_json"]
yaml = ["serde_yaml"]
[dependencies] [dependencies]
bufstream = "0.1" bufstream = "0.1"
@ -25,9 +27,15 @@ futures = "0.1"
native-tls = "0.1" native-tls = "0.1"
serde = "1.0" serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"
serde_json = "1.0" serde_json = { version = "1.0", optional = true }
serde_yaml = { version = "0.7", optional = true }
tokio-core = "0.1" tokio-core = "0.1"
tokio-io = "0.1" tokio-io = "0.1"
tokio-mockstream = "1.1" tokio-mockstream = "1.1"
tokio-timer = "0.1" tokio-timer = "0.1"
tokio-tls = "0.1" tokio-tls = "0.1"
toml = { version = "0.4", optional = true }
[dev-dependencies]
args = "2.0"
getopts = "0.2"

62
examples/convertconf.rs Normal file
View file

@ -0,0 +1,62 @@
extern crate args;
extern crate getopts;
extern crate irc;
use std::env;
use std::process::exit;
use args::{Args, ArgsError};
use getopts::Occur;
use irc::client::data::Config;
const PROGRAM_DESC: &'static str = "Use this program to convert configs between {JSON, TOML, YAML}.";
const PROGRAM_NAME: &'static str = "convertconf";
fn main() {
let args: Vec<_> = env::args().collect();
match parse(&args) {
Ok(Some((ref input, ref output))) => {
let cfg = Config::load(input).unwrap();
cfg.save(output).unwrap();
println!("Converted {} to {}.", input, output);
}
Ok(None) => {
println!("Failed to provide required arguments.");
exit(1);
}
Err(err) => {
println!("{}", err);
exit(1);
}
}
}
fn parse(input: &Vec<String>) -> Result<Option<(String, String)>, ArgsError> {
let mut args = Args::new(PROGRAM_NAME, PROGRAM_DESC);
args.flag("h", "help", "Print the usage menu");
args.option("i",
"input",
"The path to the input config",
"FILE",
Occur::Req,
None);
args.option("o",
"output",
"The path to output the new config to",
"FILE",
Occur::Req,
None);
args.parse(input)?;
let help = args.value_of("help")?;
if help {
args.full_usage();
return Ok(None);
}
Ok(Some((
args.value_of("input")?,
args.value_of("output")?,
)))
}

View file

@ -1 +1,3 @@
echo "{\"owners\": [\"test\"],\"nickname\": \"test\",\"username\": \"test\",\"realname\": \"test\",\"password\": \"\",\"server\": \"irc.test.net\",\"port\": 6667,\"use_ssl\": false,\"encoding\": \"UTF-8\",\"channels\": [\"#test\", \"#test2\"],\"umodes\": \"+BR\",\"options\": {}}" > client_config.json echo "{\"owners\": [\"test\"],\"nickname\": \"test\",\"username\": \"test\",\"realname\": \"test\",\"password\": \"\",\"server\": \"irc.test.net\",\"port\": 6667,\"use_ssl\": false,\"encoding\": \"UTF-8\",\"channels\": [\"#test\", \"#test2\"],\"umodes\": \"+BR\",\"options\": {}}" > client_config.json
cargo run --example convertconf --features "toml yaml" -- -i client_config.json -o client_config.toml
cargo run --example convertconf --features "toml yaml" -- -i client_config.json -o client_config.yaml

View file

@ -7,7 +7,12 @@ use std::io::{Error, ErrorKind};
use std::net::{SocketAddr, ToSocketAddrs}; use std::net::{SocketAddr, ToSocketAddrs};
use std::path::Path; use std::path::Path;
#[cfg(feature = "json")]
use serde_json; use serde_json;
#[cfg(feature = "yaml")]
use serde_yaml;
#[cfg(feature = "toml")]
use toml;
use error::Result; use error::Result;
@ -80,30 +85,157 @@ pub struct Config {
} }
impl Config { impl Config {
/// Loads a JSON configuration from the desired path. /// Loads a configuration from the desired path. This will use the file extension to detect
/// which format to parse the file as (json, toml, or yaml). Using each format requires having
/// its respective crate feature enabled. Only json is available by default.
pub fn load<P: AsRef<Path>>(path: P) -> Result<Config> { pub fn load<P: AsRef<Path>>(path: P) -> Result<Config> {
let mut file = File::open(path)?; let mut file = File::open(&path)?;
let mut data = String::new(); let mut data = String::new();
file.read_to_string(&mut data)?; file.read_to_string(&mut data)?;
match path.as_ref().extension().and_then(|s| s.to_str()) {
Some("json") => Config::load_json(&data),
Some("toml") => Config::load_toml(&data),
Some("yaml") | Some("yml") => Config::load_yaml(&data),
Some(ext) => return Err(Error::new(
ErrorKind::InvalidInput,
format!("Failed to decode configuration of unknown format {}", ext),
).into()),
None => return Err(Error::new(
ErrorKind::InvalidInput,
"Failed to decode configuration of missing or non-unicode format.",
).into()),
}
}
#[cfg(feature = "json")]
fn load_json(data: &str) -> Result<Config> {
serde_json::from_str(&data[..]).map_err(|_| { serde_json::from_str(&data[..]).map_err(|_| {
Error::new( Error::new(
ErrorKind::InvalidInput, ErrorKind::InvalidInput,
"Failed to decode configuration file.", "Failed to decode JSON configuration file.",
).into() ).into()
}) })
} }
/// Saves a JSON configuration to the desired path. #[cfg(not(feature = "json"))]
fn load_json(_: &str) -> Result<Config> {
Err(Error::new(
ErrorKind::InvalidInput,
"JSON file decoding is disabled.",
).into())
}
#[cfg(feature = "toml")]
fn load_toml(data: &str) -> Result<Config> {
toml::from_str(&data[..]).map_err(|_| {
Error::new(
ErrorKind::InvalidInput,
"Failed to decode TOML configuration file.",
).into()
})
}
#[cfg(not(feature = "toml"))]
fn load_toml(_: &str) -> Result<Config> {
Err(Error::new(
ErrorKind::InvalidInput,
"TOML file decoding is disabled.",
).into())
}
#[cfg(feature = "yaml")]
fn load_yaml(data: &str) -> Result<Config> {
serde_yaml::from_str(&data[..]).map_err(|_| {
Error::new(
ErrorKind::InvalidInput,
"Failed to decode YAML configuration file.",
).into()
})
}
#[cfg(not(feature = "yaml"))]
fn load_yaml(_: &str) -> Result<Config> {
Err(Error::new(
ErrorKind::InvalidInput,
"YAML file decoding is disabled.",
).into())
}
/// Saves a configuration to the desired path. This will use the file extension to detect
/// which format to parse the file as (json, toml, or yaml). Using each format requires having
/// its respective crate feature enabled. Only json is available by default.
pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> { pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let mut file = File::create(path)?; let mut file = File::create(&path)?;
file.write_all( let data = match path.as_ref().extension().and_then(|s| s.to_str()) {
serde_json::to_string(self).map_err(|_| { Some("json") => self.save_json()?,
Error::new( Some("toml") => self.save_toml()?,
ErrorKind::InvalidInput, Some("yaml") | Some("yml") => self.save_yaml()?,
"Failed to encode configuration file.", Some(ext) => return Err(Error::new(
) ErrorKind::InvalidInput,
})?.as_bytes(), format!("Failed to encode configuration of unknown format {}", ext),
).map_err(|e| e.into()) ).into()),
None => return Err(Error::new(
ErrorKind::InvalidInput,
"Failed to encode configuration of missing or non-unicode format.",
).into()),
};
file.write_all(data.as_bytes())?;
Ok(())
}
#[cfg(feature = "json")]
fn save_json(&self) -> Result<String> {
serde_json::to_string(self).map_err(|_| {
Error::new(
ErrorKind::InvalidInput,
"Failed to encode JSON configuration file.",
).into()
})
}
#[cfg(not(feature = "json"))]
fn save_json(&self) -> Result<String> {
Err(Error::new(
ErrorKind::InvalidInput,
"JSON file encoding is disabled.",
).into())
}
#[cfg(feature = "toml")]
fn save_toml(&self) -> Result<String> {
toml::to_string(self).map_err(|_| {
Error::new(
ErrorKind::InvalidInput,
"Failed to encode TOML configuration file.",
).into()
})
}
#[cfg(not(feature = "toml"))]
fn save_toml(&self) -> Result<String> {
Err(Error::new(
ErrorKind::InvalidInput,
"TOML file encoding is disabled.",
).into())
}
#[cfg(feature = "yaml")]
fn save_yaml(&self) -> Result<String> {
serde_yaml::to_string(self).map_err(|_| {
Error::new(
ErrorKind::InvalidInput,
"Failed to encode YAML configuration file.",
).into()
})
}
#[cfg(not(feature = "yaml"))]
fn save_yaml(&self) -> Result<String> {
Err(Error::new(
ErrorKind::InvalidInput,
"YAML file encoding is disabled.",
).into())
} }
/// Determines whether or not the nickname provided is the owner of the bot. /// Determines whether or not the nickname provided is the owner of the bot.
@ -308,78 +440,66 @@ impl Config {
mod test { mod test {
use std::collections::HashMap; use std::collections::HashMap;
use std::default::Default; use std::default::Default;
#[cfg(feature = "json")]
use std::path::Path; use std::path::Path;
use super::Config; use super::Config;
fn test_config() -> Config {
Config {
owners: Some(vec![format!("test")]),
nickname: Some(format!("test")),
nick_password: None,
alt_nicks: None,
username: Some(format!("test")),
realname: Some(format!("test")),
password: Some(String::new()),
umodes: Some(format!("+BR")),
server: Some(format!("irc.test.net")),
port: Some(6667),
use_ssl: Some(false),
cert_path: None,
encoding: Some(format!("UTF-8")),
channels: Some(vec![format!("#test"), format!("#test2")]),
channel_keys: None,
user_info: None,
version: None,
source: None,
ping_time: None,
ping_timeout: None,
burst_window_length: None,
max_messages_in_burst: None,
should_ghost: None,
ghost_sequence: None,
options: Some(HashMap::new()),
use_mock_connection: None,
mock_initial_value: None,
}
}
#[test] #[test]
#[cfg(feature = "json")]
fn load() { fn load() {
let cfg = Config { assert_eq!(Config::load(Path::new("client_config.json")).unwrap(), test_config());
owners: Some(vec![format!("test")]),
nickname: Some(format!("test")),
nick_password: None,
alt_nicks: None,
username: Some(format!("test")),
realname: Some(format!("test")),
password: Some(String::new()),
umodes: Some(format!("+BR")),
server: Some(format!("irc.test.net")),
port: Some(6667),
use_ssl: Some(false),
cert_path: None,
encoding: Some(format!("UTF-8")),
channels: Some(vec![format!("#test"), format!("#test2")]),
channel_keys: None,
user_info: None,
version: None,
source: None,
ping_time: None,
ping_timeout: None,
burst_window_length: None,
max_messages_in_burst: None,
should_ghost: None,
ghost_sequence: None,
options: Some(HashMap::new()),
use_mock_connection: None,
mock_initial_value: None,
};
assert_eq!(Config::load(Path::new("client_config.json")).unwrap(), cfg);
} }
#[test] #[test]
#[cfg(feature = "json")]
fn load_from_str() { fn load_from_str() {
let cfg = Config { assert_eq!(Config::load("client_config.json").unwrap(), test_config());
owners: Some(vec![format!("test")]),
nickname: Some(format!("test")),
nick_password: None,
alt_nicks: None,
username: Some(format!("test")),
realname: Some(format!("test")),
umodes: Some(format!("+BR")),
password: Some(String::new()),
server: Some(format!("irc.test.net")),
port: Some(6667),
use_ssl: Some(false),
cert_path: None,
encoding: Some(format!("UTF-8")),
channels: Some(vec![format!("#test"), format!("#test2")]),
channel_keys: None,
user_info: None,
version: None,
source: None,
ping_time: None,
ping_timeout: None,
burst_window_length: None,
max_messages_in_burst: None,
should_ghost: None,
ghost_sequence: None,
options: Some(HashMap::new()),
use_mock_connection: None,
mock_initial_value: None,
};
assert_eq!(Config::load("client_config.json").unwrap(), cfg);
} }
#[test]
#[cfg(feature = "toml")]
fn load_from_toml() {
assert_eq!(Config::load("client_config.toml").unwrap(), test_config());
}
#[test]
#[cfg(feature = "yaml")]
fn load_from_yaml() {
assert_eq!(Config::load("client_config.yaml").unwrap(), test_config());
}
#[test] #[test]
fn is_owner() { fn is_owner() {

View file

@ -15,12 +15,17 @@ extern crate native_tls;
extern crate serde; extern crate serde;
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
#[cfg(feature = "json")]
extern crate serde_json; extern crate serde_json;
#[cfg(feature = "yaml")]
extern crate serde_yaml;
extern crate tokio_core; extern crate tokio_core;
extern crate tokio_io; extern crate tokio_io;
extern crate tokio_mockstream; extern crate tokio_mockstream;
extern crate tokio_timer; extern crate tokio_timer;
extern crate tokio_tls; extern crate tokio_tls;
#[cfg(feature = "toml")]
extern crate toml;
pub mod client; pub mod client;
pub mod error; pub mod error;