Various cleanup and daemon template.
parent
b4369739ac
commit
8db3c4b4ab
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
remote:
|
remote:
|
||||||
owner: test
|
owner: jrpotter
|
||||||
name: woah
|
name: home-config
|
||||||
packages:
|
packages:
|
||||||
homesync:
|
homesync:
|
||||||
configs:
|
configs:
|
||||||
|
|
|
@ -15,9 +15,11 @@
|
||||||
buildInputs = [
|
buildInputs = [
|
||||||
cargo
|
cargo
|
||||||
rls
|
rls
|
||||||
|
libiconv
|
||||||
rustc
|
rustc
|
||||||
rustfmt
|
rustfmt
|
||||||
] ++ lib.optionals stdenv.isDarwin [ libiconv ];
|
] ++ lib.optionals stdenv.isDarwin (
|
||||||
|
with darwin.apple_sdk.frameworks; [ CoreServices ]);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
use super::config;
|
||||||
|
use super::config::PathConfig;
|
||||||
|
use ansi_term::Colour::Green as Success;
|
||||||
|
use ansi_term::Colour::Yellow as Warning;
|
||||||
|
use std::io;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
// TODO(jrpotter): Use curses to make this module behave nicer.
|
||||||
|
|
||||||
|
pub fn write_config(mut pending: PathConfig) -> config::Result<()> {
|
||||||
|
println!(
|
||||||
|
"Generating config at {}...\n",
|
||||||
|
Success.paint(pending.0.display().to_string())
|
||||||
|
);
|
||||||
|
|
||||||
|
print!(
|
||||||
|
"Git repository owner <{}> (enter to continue): ",
|
||||||
|
Warning.paint(pending.1.remote.owner.trim())
|
||||||
|
);
|
||||||
|
io::stdout().flush()?;
|
||||||
|
let mut owner = String::new();
|
||||||
|
io::stdin().read_line(&mut owner)?;
|
||||||
|
let owner = owner.trim().to_owned();
|
||||||
|
if !owner.is_empty() {
|
||||||
|
pending.1.remote.owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
print!(
|
||||||
|
"Git repository name <{}> (enter to continue): ",
|
||||||
|
Warning.paint(pending.1.remote.name.trim())
|
||||||
|
);
|
||||||
|
io::stdout().flush()?;
|
||||||
|
let mut name = String::new();
|
||||||
|
io::stdin().read_line(&mut name)?;
|
||||||
|
let name = name.trim().to_owned();
|
||||||
|
if !name.is_empty() {
|
||||||
|
pending.1.remote.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
pending.write()?;
|
||||||
|
println!("\nFinished writing configuration file.");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_packages(config: PathConfig) {
|
||||||
|
println!(
|
||||||
|
"Listing packages in {}...\n",
|
||||||
|
Success.paint(config.0.display().to_string())
|
||||||
|
);
|
||||||
|
// TODO(jrpotter): Alphabetize the output list.
|
||||||
|
for (k, _) in config.1.packages {
|
||||||
|
println!("• {}", k);
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ use std::{env, error, fmt, fs, io};
|
||||||
// Error
|
// Error
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
@ -67,21 +67,29 @@ impl Config {
|
||||||
pub fn new(contents: &str) -> Result<Self> {
|
pub fn new(contents: &str) -> Result<Self> {
|
||||||
Ok(serde_yaml::from_str(&contents)?)
|
Ok(serde_yaml::from_str(&contents)?)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn default() -> Self {
|
#[derive(Debug)]
|
||||||
Config {
|
pub struct PathConfig(pub PathBuf, pub Config);
|
||||||
|
|
||||||
|
impl PathConfig {
|
||||||
|
pub fn new(path: &Path, config: Option<Config>) -> Self {
|
||||||
|
PathConfig(
|
||||||
|
path.to_path_buf(),
|
||||||
|
config.unwrap_or(Config {
|
||||||
remote: Remote {
|
remote: Remote {
|
||||||
owner: "example-user".to_owned(),
|
owner: "example-user".to_owned(),
|
||||||
name: "home-config".to_owned(),
|
name: "home-config".to_owned(),
|
||||||
},
|
},
|
||||||
packages: HashMap::new(),
|
packages: HashMap::new(),
|
||||||
}
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save(&self, path: &Path) -> Result<()> {
|
pub fn write(&self) -> Result<()> {
|
||||||
// TODO(jrpotter): Create backup file before overwriting.
|
// TODO(jrpotter): Create backup file before overwriting.
|
||||||
let mut file = fs::File::create(path)?;
|
let mut file = fs::File::create(&self.0)?;
|
||||||
let serialized = serde_yaml::to_string(&self)?;
|
let serialized = serde_yaml::to_string(&self.1)?;
|
||||||
file.write_all(serialized.as_bytes())?;
|
file.write_all(serialized.as_bytes())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -120,50 +128,18 @@ pub fn default_paths() -> Vec<PathBuf> {
|
||||||
paths
|
paths
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load(paths: &Vec<PathBuf>) -> Result<(&Path, Config)> {
|
pub fn load(candidates: &Vec<PathBuf>) -> Result<PathConfig> {
|
||||||
// When trying our paths, the only acceptable error is a `NotFound` file.
|
// When trying our paths, the only acceptable error is a `NotFound` file.
|
||||||
// Anything else should be surfaced to the end user.
|
// Anything else should be surfaced to the end user.
|
||||||
for path in paths {
|
for path in candidates {
|
||||||
match fs::read_to_string(path) {
|
match fs::read_to_string(path) {
|
||||||
Err(err) if err.kind() == io::ErrorKind::NotFound => continue,
|
Err(err) if err.kind() == io::ErrorKind::NotFound => continue,
|
||||||
Err(err) => return Err(Error::FileError(err)),
|
Err(err) => return Err(Error::FileError(err)),
|
||||||
Ok(contents) => return Ok((&path, Config::new(&contents)?)),
|
Ok(contents) => {
|
||||||
|
let config = Config::new(&contents)?;
|
||||||
|
return Ok(PathConfig::new(&path, Some(config)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(Error::MissingConfig)
|
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)
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
|
||||||
|
use std::path::Path;
|
||||||
|
use std::sync::mpsc::channel;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
fn watch(path: &Path) -> notify::Result<()> {
|
||||||
|
// Create a channel to receive the events.
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
|
||||||
|
// Automatically select the best implementation for your platform.
|
||||||
|
// You can also access each implementation directly e.g. INotifyWatcher.
|
||||||
|
let mut watcher: RecommendedWatcher = Watcher::new(tx, Duration::from_secs(2))?;
|
||||||
|
|
||||||
|
// Add a path to be watched. All files and directories at that path and
|
||||||
|
// below will be monitored for changes.
|
||||||
|
watcher.watch(path, RecursiveMode::NonRecursive)?;
|
||||||
|
|
||||||
|
// This is a simple loop, but you may want to use more complex logic here,
|
||||||
|
// for example to handle I/O.
|
||||||
|
loop {
|
||||||
|
match rx.recv() {
|
||||||
|
Ok(event) => println!("{:?}", event),
|
||||||
|
Err(e) => println!("watch error: {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
71
src/lib.rs
71
src/lib.rs
|
@ -1,63 +1,50 @@
|
||||||
|
pub mod cli;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
pub mod daemon;
|
||||||
|
|
||||||
use ansi_term::Colour::Green;
|
use config::PathConfig;
|
||||||
use config::Config;
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub fn run_add(paths: Vec<PathBuf>) -> Result<(), config::Error> {
|
pub fn run_add(_candidates: Vec<PathBuf>) -> Result<(), config::Error> {
|
||||||
debug_assert!(!paths.is_empty(), "`run_init` paths empty");
|
|
||||||
if paths.is_empty() {
|
|
||||||
return Err(config::Error::MissingConfig);
|
|
||||||
}
|
|
||||||
// TODO(jrpotter): Show $EDITOR that allows writing specific package.
|
// TODO(jrpotter): Show $EDITOR that allows writing specific package.
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_init(paths: Vec<PathBuf>) -> Result<(), config::Error> {
|
pub fn run_daemon(_candidates: Vec<PathBuf>) -> Result<(), Box<dyn Error>> {
|
||||||
// TODO(jrpotter): Use a nonempty implementation instead of this.
|
Ok(())
|
||||||
debug_assert!(!paths.is_empty(), "`run_init` paths empty");
|
|
||||||
if paths.is_empty() {
|
|
||||||
return Err(config::Error::MissingConfig);
|
|
||||||
}
|
}
|
||||||
// Check if we already have a local config somewhere. If so, reprompt the
|
|
||||||
// same configuration options and override the values present in the current
|
pub fn run_init(candidates: Vec<PathBuf>) -> Result<(), config::Error> {
|
||||||
// YAML file.
|
match config::load(&candidates) {
|
||||||
match config::load(&paths) {
|
// Check if we already have a local config somewhere. If so, reprompt
|
||||||
Ok((path, config)) => config::init(path, config),
|
// the same configuration options and override the values present in the
|
||||||
|
// current YAML file.
|
||||||
|
Ok(pending) => cli::write_config(pending),
|
||||||
|
// Otherwise create a new config file at the given location. We always
|
||||||
|
// assume we want to write to the first file in our priority list. If
|
||||||
|
// not, the user should specify which config they want to write using
|
||||||
|
// the `-c` flag.
|
||||||
// TODO(jrpotter): Verify I have permission to write at specified path.
|
// TODO(jrpotter): Verify I have permission to write at specified path.
|
||||||
// Make directories if necessary.
|
// Make directories if necessary.
|
||||||
Err(config::Error::MissingConfig) => config::init(&paths[0], Config::default()),
|
Err(config::Error::MissingConfig) if !candidates.is_empty() => {
|
||||||
Err(e) => Err(e),
|
let pending = PathConfig::new(&candidates[0], None);
|
||||||
}
|
cli::write_config(pending)
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_list(paths: Vec<PathBuf>) -> Result<(), config::Error> {
|
|
||||||
debug_assert!(!paths.is_empty(), "`run_init` paths empty");
|
|
||||||
if paths.is_empty() {
|
|
||||||
return Err(config::Error::MissingConfig);
|
|
||||||
}
|
|
||||||
match config::load(&paths) {
|
|
||||||
Ok((path, config)) => {
|
|
||||||
// TODO(jrpotter): Should sort these entries.
|
|
||||||
// Also clean up where I use the console writing or not.
|
|
||||||
println!(
|
|
||||||
"Listing packages at {}...\n",
|
|
||||||
Green.paint(path.display().to_string())
|
|
||||||
);
|
|
||||||
for (k, _) in config.packages {
|
|
||||||
println!("• {}", k);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_pull(_: &clap::ArgMatches) -> Result<(), Box<dyn Error>> {
|
pub fn run_list(candidates: Vec<PathBuf>) -> Result<(), config::Error> {
|
||||||
|
let loaded = config::load(&candidates)?;
|
||||||
|
cli::list_packages(loaded);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_push(_: &clap::ArgMatches) -> Result<(), Box<dyn Error>> {
|
pub fn run_pull() -> Result<(), Box<dyn Error>> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_push() -> Result<(), Box<dyn Error>> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
40
src/main.rs
40
src/main.rs
|
@ -1,6 +1,20 @@
|
||||||
use clap::{App, AppSettings, Arg};
|
use clap::{App, AppSettings, Arg};
|
||||||
|
use std::error::Error;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn dispatch(paths: Vec<PathBuf>, matches: clap::ArgMatches) -> Result<(), Box<dyn Error>> {
|
||||||
|
match matches.subcommand() {
|
||||||
|
Some(("add", _)) => homesync::run_add(paths)?,
|
||||||
|
Some(("daemon", _)) => homesync::run_daemon(paths)?,
|
||||||
|
Some(("init", _)) => homesync::run_init(paths)?,
|
||||||
|
Some(("list", _)) => homesync::run_list(paths)?,
|
||||||
|
Some(("pull", _)) => homesync::run_pull()?,
|
||||||
|
Some(("push", _)) => homesync::run_push()?,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let matches = App::new("homesync")
|
let matches = App::new("homesync")
|
||||||
.about("Cross desktop configuration sync tool.")
|
.about("Cross desktop configuration sync tool.")
|
||||||
|
@ -16,39 +30,19 @@ fn main() {
|
||||||
.takes_value(true),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
.subcommand(App::new("add").about("Add new configuration to local repository"))
|
.subcommand(App::new("add").about("Add new configuration to local repository"))
|
||||||
|
.subcommand(App::new("daemon").about("Start up a new homesync daemon"))
|
||||||
.subcommand(App::new("init").about("Initialize the homesync local repository"))
|
.subcommand(App::new("init").about("Initialize the homesync local repository"))
|
||||||
.subcommand(App::new("list").about("See which packages homesync manages"))
|
.subcommand(App::new("list").about("See which packages homesync manages"))
|
||||||
.subcommand(App::new("pull").about("Pull remote repository into local repository"))
|
.subcommand(App::new("pull").about("Pull remote repository into local repository"))
|
||||||
.subcommand(App::new("push").about("Push local repository to remote repository"))
|
.subcommand(App::new("push").about("Push local repository to remote repository"))
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
let paths = match matches.value_of("config") {
|
let candidates = match matches.value_of("config") {
|
||||||
Some(path) => vec![PathBuf::from(path)],
|
Some(path) => vec![PathBuf::from(path)],
|
||||||
None => homesync::config::default_paths(),
|
None => homesync::config::default_paths(),
|
||||||
};
|
};
|
||||||
|
|
||||||
match matches.subcommand() {
|
if let Err(e) = dispatch(candidates, matches) {
|
||||||
Some(("add", _)) => {
|
|
||||||
if let Err(e) = homesync::run_add(paths) {
|
|
||||||
eprintln!("{}", e);
|
eprintln!("{}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(("init", _)) => {
|
|
||||||
if let Err(e) = homesync::run_init(paths) {
|
|
||||||
eprintln!("{}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(("list", _)) => {
|
|
||||||
if let Err(e) = homesync::run_list(paths) {
|
|
||||||
eprintln!("{}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(("pull", ms)) => {
|
|
||||||
homesync::run_pull(ms);
|
|
||||||
}
|
|
||||||
Some(("push", ms)) => {
|
|
||||||
homesync::run_push(ms);
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue