use std::convert::AsRef;
use std::default::Default;
use std::fmt;
use std::fs::{self, File};
use std::io::Write;
use std::path::Path;
use std::str::FromStr;
use structconf::{clap, Error, StructConf};
use strum_macros::{Display, EnumString};
struct TempFile(String);
impl TempFile {
pub fn new(path: &str) -> TempFile {
fs::remove_file(path).ok();
TempFile(path.to_string())
}
}
impl Drop for TempFile {
fn drop(&mut self) {
fs::remove_file(&self.0).ok();
}
}
impl AsRef<str> for TempFile {
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
impl AsRef<Path> for TempFile {
fn as_ref(&self) -> &Path {
Path::new(&self.0)
}
}
impl std::ops::Deref for TempFile {
type Target = str;
fn deref(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Display, EnumString)]
enum MyEnum {
One,
Two,
Three,
}
impl Default for MyEnum {
fn default() -> Self {
MyEnum::One
}
}
#[derive(Debug, Default, Clone, PartialEq)]
struct MyStruct {
data: i32,
moredata: String,
}
impl FromStr for MyStruct {
type Err = std::num::ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut s = s.split(";");
Ok(MyStruct {
data: s.next().unwrap().parse::<i32>()?,
moredata: String::from(s.next().unwrap()),
})
}
}
impl fmt::Display for MyStruct {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{};{}", self.data, self.moredata)
}
}
#[derive(Debug, PartialEq, StructConf)]
struct Config {
#[conf(no_file)]
no_file: i32,
#[conf(no_short)]
no_short: bool,
#[conf(no_long, short = "x")]
no_long: bool,
#[conf(no_short, no_long)]
no_short_no_long: bool,
#[conf(no_short, no_long, no_file)]
empty: i32,
#[conf(file = "new_file")]
file: bool,
#[conf(long = "new_long")]
long: bool,
#[conf(short = "s")]
short: bool,
#[conf(file = "new_combined", long = "new_combined", short = "c")]
combined: bool,
#[conf(default = "123 + 123")]
default: i64,
#[conf(no_short, negated_arg, long = "no-value")]
inverse: bool,
#[conf(no_short)]
someenum: MyEnum,
#[conf(no_short)]
astruct: MyStruct,
#[conf(no_short)]
option_i32: Option<i32>,
#[conf(no_short)]
option_enum: Option<MyEnum>,
#[conf(no_short)]
option_string: Option<String>,
}
#[test]
fn read_file() {
let file = TempFile::new("file_read.ini");
let mut f = File::create(&file).unwrap();
f.write(
b"[Defaults]
no_file = 1234
no_short = \"true\"
no_long = true
new_file = true
new_combined = true
someenum = Two
astruct = 123;strval
empty = 2134",
)
.unwrap();
let app = clap::App::new("test");
let conf = Config::parse(app, &file).unwrap();
assert_eq!(conf.no_file, 0);
assert_eq!(conf.no_short, true);
assert_eq!(conf.no_long, true);
assert_eq!(conf.no_short_no_long, false);
assert_eq!(conf.file, true);
assert_eq!(conf.long, false);
assert_eq!(conf.short, false);
assert_eq!(conf.combined, true);
assert_eq!(conf.empty, 0); assert_eq!(conf.someenum, MyEnum::Two);
assert_eq!(
conf.astruct,
MyStruct {
data: 123,
moredata: String::from("strval"),
}
);
}
#[test]
fn write_file() {
let file = TempFile::new("write_file.ini");
let app = clap::App::new("test");
let args = Config::parse_args(app);
let mut conf = Config::parse_file(&args, &file).unwrap();
let written_enum = MyEnum::Three;
let written_struct = MyStruct {
data: 999,
moredata: String::from("another value"),
};
conf.someenum = written_enum.clone();
conf.astruct = written_struct.clone();
conf.file = true;
conf.long = true;
conf.short = true;
conf.write_file(&file).unwrap();
let conf = Config::parse_file(&args, &file).unwrap();
assert_eq!(conf.someenum, written_enum);
assert_eq!(conf.astruct, written_struct);
assert_eq!(conf.file, true);
assert_eq!(conf.long, true);
assert_eq!(conf.short, true);
assert_eq!(conf.combined, false); }
#[test]
fn args() {
let file = TempFile::new("args.ini");
let app = clap::App::new("test");
let args = vec![
"test", "--no-file",
"123", "--no-short",
"-x", "--file",
"--new-long", "--option-i32",
"321", "--no-value", ];
let args = Config::parse_args_from(app, args);
let conf = Config::parse_file(&args, &file).unwrap();
assert_eq!(conf.no_file, 123);
assert_eq!(conf.no_short, true);
assert_eq!(conf.no_long, true);
assert_eq!(conf.no_short_no_long, false);
assert_eq!(conf.empty, 0); assert_eq!(conf.file, true);
assert_eq!(conf.inverse, false);
assert_eq!(conf.option_i32, Some(321));
}
#[test]
fn priorities() {
}
#[test]
fn errors() {
let file = TempFile::new("errors.ini");
let mut f = File::create(&file).unwrap();
f.write(
b"
[Defaults]
no_short = \"should be a boolean\"",
)
.unwrap();
let app = clap::App::new("test");
match Config::parse(app, &file) {
Err(Error::Parse(_)) => assert!(true),
s => assert!(false, "parse error not returned: {:?}", s),
}
let app = clap::App::new("test");
match Config::parse(app, "/") {
Err(Error::IO(_)) => assert!(true),
_ => assert!(false, "IO error not returned"),
}
}
#[test]
fn optionals() {
let file = TempFile::new("optionals.ini");
let app = clap::App::new("test");
let conf = Config::parse(app, &file).unwrap();
assert_eq!(conf.option_i32, None);
assert_eq!(conf.option_enum, None);
assert_eq!(conf.option_string, None);
let mut f = File::create(&file).unwrap();
f.write(
b"[Defaults]
option_i32 = 1234
option_enum = Three
option_string = some text goes here",
)
.unwrap();
let app = clap::App::new("test");
let conf = Config::parse(app, &file).unwrap();
assert_eq!(conf.option_i32, Some(1234));
assert_eq!(conf.option_enum, Some(MyEnum::Three));
assert_eq!(
conf.option_string,
Some(String::from("some text goes here"))
);
let app = clap::App::new("test");
let mut conf = Config::parse(app, &file).unwrap();
let written_i32 = None;
let written_enum = Some(MyEnum::Two);
let written_string = Some(String::from("value"));
conf.option_i32 = written_i32;
conf.option_enum = written_enum.clone();
conf.option_string = written_string.clone();
fs::remove_file(&file).unwrap();
conf.write_file(&file).unwrap();
let app = clap::App::new("test");
let conf = Config::parse(app, &file).unwrap();
assert_eq!(conf.option_i32, written_i32);
assert_eq!(conf.option_enum, written_enum);
assert_eq!(conf.option_string, written_string);
}
#[test]
fn defaults() {
let file = TempFile::new("custom_types.ini");
let app = clap::App::new("test");
let conf = Config::parse(app, &file).unwrap();
assert_eq!(conf.no_file, 0);
assert_eq!(conf.no_short, false);
assert_eq!(conf.no_long, false);
assert_eq!(conf.no_short_no_long, false);
assert_eq!(conf.empty, 0);
assert_eq!(conf.default, 246);
assert_eq!(conf.inverse, true);
assert_eq!(conf.option_i32, None);
assert_eq!(conf.someenum, Default::default());
assert_eq!(conf.astruct, Default::default());
}