170 lines
4.5 KiB
Rust
170 lines
4.5 KiB
Rust
use ansi_term::Colour::Green;
|
|
use serde_derive::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
use std::io::Write;
|
|
use std::path::{Path, PathBuf};
|
|
use std::{env, error, fmt, fs, io};
|
|
|
|
// ========================================
|
|
// Error
|
|
// ========================================
|
|
|
|
type Result<T> = std::result::Result<T, Error>;
|
|
|
|
#[derive(Debug)]
|
|
pub enum Error {
|
|
FileError(io::Error),
|
|
MissingConfig,
|
|
SerdeError(serde_yaml::Error),
|
|
}
|
|
|
|
impl From<io::Error> for Error {
|
|
fn from(err: io::Error) -> Error {
|
|
Error::FileError(err)
|
|
}
|
|
}
|
|
|
|
impl From<serde_yaml::Error> for Error {
|
|
fn from(err: serde_yaml::Error) -> Error {
|
|
Error::SerdeError(err)
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Error {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match self {
|
|
Error::FileError(e) => write!(f, "{}", e),
|
|
Error::MissingConfig => write!(f, "Could not find configuration file"),
|
|
Error::SerdeError(e) => write!(f, "{}", e),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl error::Error for Error {}
|
|
|
|
// ========================================
|
|
// Config
|
|
// ========================================
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
pub struct Remote {
|
|
pub owner: String,
|
|
pub name: String,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
pub struct Package {
|
|
pub configs: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
pub struct Config {
|
|
pub remote: Remote,
|
|
pub packages: HashMap<String, Package>,
|
|
}
|
|
|
|
impl Config {
|
|
pub fn new(contents: &str) -> Result<Self> {
|
|
Ok(serde_yaml::from_str(&contents)?)
|
|
}
|
|
|
|
pub fn default() -> Self {
|
|
Config {
|
|
remote: Remote {
|
|
owner: "example-user".to_owned(),
|
|
name: "home-config".to_owned(),
|
|
},
|
|
packages: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
pub fn save(&self, path: &Path) -> Result<()> {
|
|
// TODO(jrpotter): Create backup file before overwriting.
|
|
let mut file = fs::File::create(path)?;
|
|
let serialized = serde_yaml::to_string(&self)?;
|
|
file.write_all(serialized.as_bytes())?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
// ========================================
|
|
// Loading
|
|
// ========================================
|
|
|
|
/// Returns the default configuration files `homesync` looks for.
|
|
///
|
|
/// - `$HOME/.homesync.yml`
|
|
/// - `$HOME/.config/homesync/homesync.yml`
|
|
/// - `$XDG_CONFIG_HOME/homesync.yml`
|
|
/// - `$XDG_CONFIG_HOME/homesync/homesync.yml`
|
|
///
|
|
/// Returned `PathBuf`s are looked for in the above order.
|
|
pub fn default_paths() -> Vec<PathBuf> {
|
|
let mut paths: Vec<PathBuf> = Vec::new();
|
|
if let Ok(home) = env::var("HOME") {
|
|
paths.extend_from_slice(&[
|
|
[&home, ".homesync.yml"].iter().collect(),
|
|
[&home, ".config", "homesync", "homesync.yml"]
|
|
.iter()
|
|
.collect(),
|
|
]);
|
|
}
|
|
if let Ok(xdg_config_home) = env::var("XDG_CONFIG_HOME") {
|
|
paths.extend_from_slice(&[
|
|
[&xdg_config_home, "homesync.yml"].iter().collect(),
|
|
[&xdg_config_home, "homesync", "homesync.yml"]
|
|
.iter()
|
|
.collect(),
|
|
]);
|
|
}
|
|
paths
|
|
}
|
|
|
|
pub fn load(paths: &Vec<PathBuf>) -> Result<(&Path, Config)> {
|
|
// When trying our paths, the only acceptable error is a `NotFound` file.
|
|
// Anything else should be surfaced to the end user.
|
|
for path in paths {
|
|
match fs::read_to_string(path) {
|
|
Err(err) if err.kind() == io::ErrorKind::NotFound => continue,
|
|
Err(err) => return Err(Error::FileError(err)),
|
|
Ok(contents) => return Ok((&path, Config::new(&contents)?)),
|
|
}
|
|
}
|
|
Err(Error::MissingConfig)
|
|
}
|
|
|
|
// ========================================
|
|
// Initialization
|
|
// ========================================
|
|
|
|
pub fn init(path: &Path, default: Config) -> Result<()> {
|
|
// TODO(jrpotter): Use curses to make this nicer.
|
|
println!(
|
|
"Generating config at {}...\n\n",
|
|
Green.paint(path.display().to_string())
|
|
);
|
|
print!(
|
|
"Git repository owner <{}> (enter to continue): ",
|
|
default.remote.owner
|
|
);
|
|
io::stdout().flush()?;
|
|
let mut owner = String::new();
|
|
io::stdin().read_line(&mut owner)?;
|
|
let owner = owner.trim().to_owned();
|
|
|
|
print!(
|
|
"Git repository name <{}> (enter to continue): ",
|
|
default.remote.name
|
|
);
|
|
io::stdout().flush()?;
|
|
let mut name = String::new();
|
|
io::stdin().read_line(&mut name)?;
|
|
let name = name.trim().to_owned();
|
|
|
|
Config {
|
|
remote: Remote { owner, name },
|
|
packages: default.packages,
|
|
}
|
|
.save(path)
|
|
}
|