extern crate serde;
extern crate strum_macros;
extern crate toml;
use serde::{Deserialize, Deserializer, Serialize};
use std::collections::HashMap;
use std::str::FromStr;
use std::{
fmt, fs,
io::{self, prelude::*, BufReader},
path::{Path, PathBuf},
string::String,
};
use strum_macros::EnumString;
use toml::value;
use super::hmerror::{HMError, Result as HMResult};
#[derive(Debug, Clone)]
pub struct Worker {
pub name: String,
pub status: Option<i32>,
pub completed: bool,
}
impl<'a> Worker {
#[allow(dead_code)]
pub fn new() -> Worker {
Worker {
name: String::from(""),
status: Some(1),
completed: false,
}
}
}
impl<'a> Default for Worker {
fn default() -> Self {
Self::new()
}
}
impl PartialEq for Worker {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
impl Eq for Worker {}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd, Ord, Hash, EnumString)]
#[strum(serialize_all = "snake_case")]
pub enum OS {
Windows,
Unknown,
Linux(LinuxDistro),
}
impl Default for OS {
fn default() -> Self {
OS::Unknown
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, PartialOrd, Ord, Hash, EnumString)]
#[strum(serialize_all = "snake_case")]
pub enum LinuxDistro {
Fedora,
Debian,
Ubuntu,
Arch,
Generic,
}
impl Default for LinuxDistro {
fn default() -> Self {
LinuxDistro::Generic
}
}
impl Eq for OS {}
impl Eq for LinuxDistro {}
pub fn determine_os() -> OS {
match sys_info::os_type() {
Ok(s) => match s.to_ascii_lowercase().as_str() {
"linux" => match sys_info::linux_os_release() {
Ok(l) => {
let a: String = l.name.unwrap().to_ascii_lowercase();
if a.contains("fedora") {
OS::Linux(LinuxDistro::Fedora)
} else if a.contains("debian") {
OS::Linux(LinuxDistro::Debian)
} else if a.contains("ubuntu") {
OS::Linux(LinuxDistro::Ubuntu)
} else {
OS::Unknown
}
}
Err(_e) => OS::Unknown,
},
"windows" => OS::Windows,
_ => OS::Unknown,
},
Err(_e) => OS::Unknown,
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ManagedObject {
pub name: String,
pub source: String,
pub file: String,
pub destination: String,
pub method: String,
pub task: String,
pub solution: String,
pub dependencies: Vec<String>,
pub satisfied: bool,
pub os: Option<OS>,
pub force: bool,
pub post: String,
}
impl ManagedObject {
pub fn is_task(&self) -> bool {
!self.solution.is_empty()
}
pub fn set_satisfied(&mut self) {
self.satisfied = true;
}
}
impl PartialEq for ManagedObject {
fn eq(&self, other: &Self) -> bool {
if self.name == other.name
&& self.source == other.source
&& self.destination == other.destination
&& self.task == other.task
&& self.solution == other.solution
{
return true;
}
false
}
}
impl fmt::Display for ManagedObject {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{} {} {} {} {} {} {} {} {:?} {:?} {}",
self.name,
self.file,
self.source,
self.method,
self.destination,
self.task,
self.solution,
self.satisfied,
self.os,
self.force,
self.post
)
}
}
impl Default for ManagedObject {
fn default() -> Self {
ManagedObject {
name: String::from(""),
source: String::from(""),
file: String::from(""),
destination: String::from(""),
method: String::from(""),
task: String::from(""),
solution: String::from(""),
dependencies: Vec::new(),
satisfied: false,
os: None,
force: false,
post: "".to_string(),
}
}
}
#[derive(Deserialize, Clone)]
pub struct Config {
#[serde(rename = "obj", deserialize_with = "deserialize_files")]
pub files: Vec<(String, value::Value)>,
}
impl Default for Config {
fn default() -> Self {
Config { files: Vec::new() }
}
}
impl fmt::Display for Config {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mos = Config::as_managed_objects(self.clone());
write!(f, "{:#?}", mos)
}
}
impl Config {
#[allow(dead_code)]
pub fn get_mo(&mut self, _n: &str) -> Option<ManagedObject> {
Config::as_managed_objects(self.clone())
.get(_n)
.map(|a| a.to_owned())
}
pub fn as_managed_objects(config: Config) -> HashMap<String, ManagedObject> {
config
.files
.iter()
.map(|(name, val)| {
let mut mo = ManagedObject {
name: name.to_owned(),
..Default::default()
};
mo.name = name.to_owned();
if let Some(_x) = val.get("solution") {
mo.solution = String::from(_x.as_str().unwrap());
}
if let Some(_x) = val.get("task") {
mo.task = String::from(_x.as_str().unwrap());
}
if let Some(_x) = val.get("source") {
mo.source = String::from(_x.as_str().unwrap());
}
if let Some(_x) = val.get("method") {
mo.method = String::from(_x.as_str().unwrap());
}
if let Some(_x) = val.get("destination") {
mo.destination = String::from(_x.as_str().unwrap());
}
if let Some(_x) = val.get("dependencies") {
let _f = _x.as_array().unwrap();
mo.dependencies = _f.iter().map(|v| v.as_str().unwrap().to_owned()).collect();
}
if let Some(_x) = val.get("force") {
mo.force = matches!(_x.as_str().unwrap(), "true");
}
if let Some(_x) = val.get("post") {
mo.post = String::from(_x.as_str().unwrap());
}
if let Some(_x) = val.get("os") {
let _b = String::from(_x.as_str().unwrap());
let _a: Vec<&str> = _b.split("::").collect::<Vec<&str>>();
mo.os = if _a.len() > 1 {
Some(OS::Linux(
LinuxDistro::from_str(_a[1].to_lowercase().as_str()).unwrap(),
))
} else {
Some(OS::from_str(_a[0].to_lowercase().as_str()).unwrap())
};
} else {
mo.os = None;
}
(mo.name.clone(), mo)
})
.collect()
}
}
pub fn deserialize_files<'de, D>(deserializer: D) -> Result<Vec<(String, value::Value)>, D::Error>
where
D: Deserializer<'de>,
{
let mut files: Vec<(String, value::Value)> = Vec::new();
let raw_files: Vec<value::Table> = Deserialize::deserialize(deserializer)?;
for mut entry in raw_files {
if let Some(name) = entry.remove("file") {
if let Some(name) = name.as_str() {
files.push((name.to_owned(), value::Value::Table(entry)));
}
} else if let Some(name) = entry.remove("task") {
if let Some(name) = name.as_str() {
files.push((name.to_owned(), value::Value::Table(entry)));
}
}
}
Ok(files)
}
fn open_config(file: &str) -> io::Result<fs::File> {
fs::File::open(file)
}
pub fn deserialize_file(file: &str) -> HMResult<Config> {
let mut contents = String::new();
let g = match open_config(file) {
Ok(_a) => _a,
Err(e) => return Err(HMError::Other(e.to_string())),
};
let mut file_contents = BufReader::new(g);
match file_contents.read_to_string(&mut contents) {
Ok(v) => v,
Err(_e) => 0,
};
if cfg!(debug_assertions) {
println!("file: {}", &file);
}
deserialize_str(&contents)
}
fn deserialize_str(contents: &str) -> HMResult<Config> {
toml::from_str(contents).map_err(|e| HMError::Other(e.to_string()))
}
pub fn ensure_config_dir() -> Result<PathBuf, &'static str> {
match dirs_next::config_dir() {
Some(p) => {
let whole_path = p.join(Path::new("homemaker"));
match fs::create_dir_all(&whole_path) {
Ok(()) => Ok(PathBuf::from(&whole_path.join("config.toml"))),
Err(_e) => Err("Couldn't create config dir!"),
}
}
None => Err("Couldn't get config directory from $XDG"),
}
}
#[cfg(test)]
mod config_test {
use super::*;
#[test]
fn test_mo_deserialization() {
let mut a: Config = deserialize_file("./benches/config.toml").unwrap();
let mo = ManagedObject {
name: "tmux.conf".to_string(),
source: "~/dotfiles/.tmux.conf".to_string(),
destination: "~/.tmux.conf".to_string(),
method: "symlink".to_string(),
..Default::default()
};
assert_eq!(mo, a.get_mo("tmux.conf").unwrap());
}
#[test]
fn dependencies_is_array() {
let mut a: Config = deserialize_str(
r#"
[[obj]]
task = 'zt'
solution = 'cd ~/dotfiles/zt && git pull'
dependencies = ['grim', 'slurp']
"#,
)
.unwrap();
assert_eq!(vec!["grim", "slurp"], a.get_mo("zt").unwrap().dependencies);
}
}