From b4369739ace31cefe65b5c6b5a5729c65c4e6329 Mon Sep 17 00:00:00 2001 From: Joshua Potter Date: Wed, 29 Dec 2021 22:38:04 -0500 Subject: [PATCH] Add first pass on ideas for generating the config. --- Cargo.toml | 1 + examples/config.yaml | 5 ++-- src/config.rs | 71 +++++++++++++++++++++++++++++++++++++------- src/lib.rs | 69 +++++++++++++++++++++++++++++------------- src/main.rs | 39 +++++++++++++++++------- 5 files changed, 141 insertions(+), 44 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e1bc662..bb4251d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ version = "0.1.0" edition = "2021" [dependencies] +ansi_term = "0.12.1" clap = { version = "3.0.0-rc.9", features = ["derive"] } notify = "4.0.16" serde = "1.0" diff --git a/examples/config.yaml b/examples/config.yaml index 706b461..3395ff9 100644 --- a/examples/config.yaml +++ b/examples/config.yaml @@ -1,6 +1,7 @@ +--- remote: - owner: jrpotter - name: home-config + owner: test + name: woah packages: homesync: configs: diff --git a/src/config.rs b/src/config.rs index c22a762..1e19a7c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,11 +1,9 @@ +use ansi_term::Colour::Green; use serde_derive::{Deserialize, Serialize}; use std::collections::HashMap; -use std::env; -use std::error; -use std::fmt; -use std::fs; -use std::io; -use std::path::PathBuf; +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::{env, error, fmt, fs, io}; // ======================================== // Error @@ -69,10 +67,28 @@ impl Config { pub fn new(contents: &str) -> Result { 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(()) + } } // ======================================== -// Public +// Loading // ======================================== /// Returns the default configuration files `homesync` looks for. @@ -83,7 +99,7 @@ impl Config { /// - `$XDG_CONFIG_HOME/homesync/homesync.yml` /// /// Returned `PathBuf`s are looked for in the above order. -pub fn default_configs() -> Vec { +pub fn default_paths() -> Vec { let mut paths: Vec = Vec::new(); if let Ok(home) = env::var("HOME") { paths.extend_from_slice(&[ @@ -104,15 +120,50 @@ pub fn default_configs() -> Vec { paths } -pub fn read_config(paths: &Vec) -> Result { +pub fn load(paths: &Vec) -> 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(Config::new(&contents)?), + 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) +} diff --git a/src/lib.rs b/src/lib.rs index 878a8fc..5450a9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,36 +1,63 @@ pub mod config; +use ansi_term::Colour::Green; +use config::Config; use std::error::Error; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; -pub fn run_configure( - paths: Vec, - _matches: &clap::ArgMatches, -) -> Result<(), Box> { +pub fn run_add(paths: Vec) -> 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. + Ok(()) +} + +pub fn run_init(paths: Vec) -> Result<(), config::Error> { + // TODO(jrpotter): Use a nonempty implementation instead of this. + 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 // YAML file. - match config::read_config(&paths) { - Ok(_) => { - print!("successfully read\n"); - Ok(()) - } - Err(config::Error::MissingConfig) => { - print!("missing config\n"); - Ok(()) - } - Err(e) => Err(Box::new(e)), + match config::load(&paths) { + Ok((path, config)) => config::init(path, config), + // TODO(jrpotter): Verify I have permission to write at specified path. + // Make directories if necessary. + Err(config::Error::MissingConfig) => config::init(&paths[0], Config::default()), + Err(e) => Err(e), } } -pub fn run_push(_matches: &clap::ArgMatches) -> Result<(), Box> { +pub fn run_list(paths: Vec) -> 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), + } +} + +pub fn run_pull(_: &clap::ArgMatches) -> Result<(), Box> { Ok(()) } -pub fn run_pull(_matches: &clap::ArgMatches) -> Result<(), Box> { - Ok(()) -} - -pub fn run_add(_matches: &clap::ArgMatches) -> Result<(), Box> { +pub fn run_push(_: &clap::ArgMatches) -> Result<(), Box> { Ok(()) } diff --git a/src/main.rs b/src/main.rs index 4ec5dd9..3dc82a1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,7 @@ use clap::{App, AppSettings, Arg}; -use std::error::Error; use std::path::PathBuf; -fn main() -> Result<(), Box> { +fn main() { let matches = App::new("homesync") .about("Cross desktop configuration sync tool.") .version("0.1.0") @@ -16,22 +15,40 @@ fn main() -> Result<(), Box> { .help("Specify a configuration file to use in place of defaults") .takes_value(true), ) - .subcommand(App::new("configure").about("Initialize the homesync local repository")) - .subcommand(App::new("push").about("Push local repository to remote repository")) - .subcommand(App::new("pull").about("Pull remote repository into local repository")) .subcommand(App::new("add").about("Add new configuration to 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("pull").about("Pull remote repository into local repository")) + .subcommand(App::new("push").about("Push local repository to remote repository")) .get_matches(); - let configs = match matches.value_of("config") { + let paths = match matches.value_of("config") { Some(path) => vec![PathBuf::from(path)], - None => homesync::config::default_configs(), + None => homesync::config::default_paths(), }; match matches.subcommand() { - Some(("configure", ms)) => homesync::run_configure(configs, ms), - Some(("push", ms)) => homesync::run_push(ms), - Some(("pull", ms)) => homesync::run_pull(ms), - Some(("add", ms)) => homesync::run_add(ms), + Some(("add", _)) => { + if let Err(e) = homesync::run_add(paths) { + 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!(), } }