Skip to content

Commit

Permalink
Change name dot-rs -> up-rs, migrate to anyhow/thiserror
Browse files Browse the repository at this point in the history
Also bumps dependencies.
  • Loading branch information
gibfahn committed Feb 21, 2020
1 parent 4cbc6a1 commit 759eac8
Show file tree
Hide file tree
Showing 23 changed files with 618 additions and 912 deletions.
924 changes: 281 additions & 643 deletions Cargo.lock

Large diffs are not rendered by default.

34 changes: 19 additions & 15 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
[package]
name = "dot-rs"
name = "up-rs"
version = "0.1.0"
authors = ["Gibson Fahnestock <gibfahn@gmail.com>"]
edition = '2018'

[[bin]]
name = "up"
path = "src/main.rs"

[dependencies]
walkdir = "2.2.7"
quicli = "0.4.0"
shellexpand = "1.0.0"
failure = "0.1.5"
# Not a direct dependency, needed by Rust 2018 edition for some reason.
structopt = "0.2.14"
serde = "1.0.84"
serde_derive = "1.0.84"
envy = "0.3.3"
toml = "0.4.10"
serde_json = "1.0.35"
serde_yaml = "0.8.8"
walkdir = "2.3.1"
shellexpand = "2.0.0"
structopt = "0.3.9"
serde = "1.0.104"
serde_derive = "1.0.104"
envy = "0.4.1"
toml = "0.5.6"
serde_json = "1.0.48"
serde_yaml = "0.8.11"
log = "0.4.8"
anyhow = "1.0.26"
thiserror = "1.0.11"
env_logger = "0.7.1"

[dev-dependencies]
whoami = "0.4.1"

whoami = "0.7.0"
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# dot: a tool for managing dotfiles
# up-rs: a tool for keeping your system up to date

I use this to keep my machine up to date. It does a couple of different things.

See `dot --help` for more details.
See `up --help` for more details.

## Subcommands

### Link

```console
$ dot link ~/code/dotfiles ~
$ up link ~/code/dotfiles ~
```

symlinks the files in `dotfiles` into the matching directory in `~` (so `~/.config/git/config` becomes a link to
Expand Down
15 changes: 0 additions & 15 deletions dot-rs.iml

This file was deleted.

53 changes: 53 additions & 0 deletions src/args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use structopt::{clap::AppSettings, StructOpt};

/// Builds the Args struct from CLI input and from environment variable input.
pub fn parse() -> Args {
Args::from_args()
}

/// Up is a tool to help you manage your developer machine. When run by itself (`up`) it
/// does two things. It links configuration files into the right locations, and it runs scripts to
/// make sure the tools you need are installed and up to date.
///
/// The `up link` command symlinks your dotfiles into your home directory.
///
/// The `up date` command provides an easy way to specify what you want on your system, and how
/// to keep it up to date. It is designed to work with and complement existing package
/// managers rather than replace them.
#[derive(Debug, StructOpt)]
#[structopt(rename_all = "kebab-case")]
#[structopt(global_settings = &[AppSettings::ColoredHelp])]
pub struct Args {
// TODO(gib): Improve help text to cover env_logger setup.
/// Set the logging level explicitly (options: Off, Error, Warn, Info, Debug, Trace).
#[structopt(long, default_value = "up=info,warn", env = "RUST_LOG")]
pub log_level: String,
/// Path to the up.toml file for up.
#[structopt(short = "c", default_value = "$XDG_CONFIG_HOME/up/up.toml")]
pub(crate) config: String,
#[structopt(subcommand)]
pub(crate) cmd: Option<SubCommand>,
}

// Optional subcommand (e.g. the "update" in "up update").
#[derive(Debug, StructOpt)]
pub(crate) enum SubCommand {
// TODO(gib): Work out how to do clap's help and long_help in structopt.
/// Install and update things on your computer.
#[structopt(name = "date")]
Update {},

/// Symlink your dotfiles from a git repo to your home directory.
#[structopt(name = "link")]
Link {
/// Path where your dotfiles are kept (hopefully in source control).
#[structopt(short = "f", default_value = "~/code/dotfiles")]
from_dir: String,
/// Path to link them to.
#[structopt(short = "t", default_value = "~")]
to_dir: String,
/// Path at which to store backups of overwritten files.
#[structopt(short = "b", default_value = "~/backup")]
backup_dir: String,
},
}
108 changes: 54 additions & 54 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
//! Manages the config files (default location ~/.config/dot/).
use std::env;
use std::fs;
//! Manages the config files (default location ~/.config/up/).

use failure::{ensure, Error};
#[allow(unused_imports)]
use quicli::prelude::*;
use std::{
env, fs,
path::{Path, PathBuf},
};

use anyhow::{ensure, Result};
use log::{debug, trace};
use serde_derive::{Deserialize, Serialize};
use std::path::{Path, PathBuf};

use crate::Cli;
use crate::args::Args;

#[derive(Default, Debug)]
pub struct Config {
pub dot_toml_path: Option<PathBuf>,
pub up_toml_path: Option<PathBuf>,
pub config_toml: ConfigToml,
}

Expand All @@ -25,7 +26,7 @@ pub struct Config {
pub struct ConfigToml {
/// Link options.
link: Option<LinkConfigToml>,
/// Path to tasks directory (default dot_dir/tasks).
/// Path to tasks directory (default up_dir/tasks).
tasks_path: Option<String>,
}

Expand All @@ -39,49 +40,48 @@ pub struct LinkConfigToml {

impl Config {
/// Build the `Config` struct by parsing the config toml files.
pub fn from(args: &Cli) -> Result<Self, Error> {
pub fn from(args: &Args) -> Result<Self> {
let mut config_toml = ConfigToml::default();

let maybe_dot_toml_path = Self::get_dot_toml_path(&args.config)?;
let dot_toml_path = if maybe_dot_toml_path.exists() {
let read_result = fs::read(&maybe_dot_toml_path);
if read_result.is_ok() {
let file_contents = read_result.unwrap();
let maybe_up_toml_path = Self::get_up_toml_path(&args.config)?;
let up_toml_path = if maybe_up_toml_path.exists() {
let read_result = fs::read(&maybe_up_toml_path);
if let Ok(file_contents) = read_result {
let config_str = String::from_utf8_lossy(&file_contents);
debug!("config_str: {:?}", config_str);
config_toml = toml::from_str::<ConfigToml>(&config_str)?;
debug!("Config_toml: {:?}", config_toml);
}
Some(maybe_dot_toml_path)
Some(maybe_up_toml_path)
} else {
None
};

Ok(Self {
dot_toml_path,
up_toml_path,
config_toml,
})
}

/// Get the path to the dot.toml file, given the args passed to the cli.
/// If the `args_config_path` is `$XDG_CONFIG_HOME/dot/dot.toml` (the default) then
/// Get the path to the up.toml file, given the args passed to the cli.
/// If the `args_config_path` is `$XDG_CONFIG_HOME/up/up.toml` (the default) then
/// we assume it is unset and check the other options. Order is:
/// 1. `--config`
/// 2. `$DOT_CONFIG`
/// 3. `$XDG_CONFIG_HOME/dot/dot.toml`
/// 4. `~/.config/dot/toml`
/// 4. `~/.dot/dot.toml`
fn get_dot_toml_path(args_config_path: &str) -> Result<PathBuf, Error> {
/// 2. `$UP_CONFIG`
/// 3. `$XDG_CONFIG_HOME/up/up.toml`
/// 4. `~/.config/up/toml`
/// 4. `~/.up/up.toml`
fn get_up_toml_path(args_config_path: &str) -> Result<PathBuf> {
debug!("args_config_file: {}", args_config_path);
let mut config_path: PathBuf;
if args_config_path == "$XDG_CONFIG_HOME/dot/dot.toml" {
let dot_config_env = env::var("DOT_CONFIG");
if args_config_path == "$XDG_CONFIG_HOME/up/up.toml" {
let up_config_env = env::var("UP_CONFIG");

if dot_config_env.is_ok() {
config_path = PathBuf::from(dot_config_env.unwrap());
if let Ok(config_path) = up_config_env {
let config_path = PathBuf::from(config_path);
ensure!(
config_path.exists(),
"Config path specified in DOT_CONFIG env var doesn't exist.\n config_path: {:?}",
"Config path specified in UP_CONFIG env var doesn't exist.\n config_path: {:?}",
&config_path,
);
return Ok(config_path);
Expand All @@ -94,14 +94,14 @@ impl Config {
config_path = env::var("XDG_CONFIG_HOME")
.map_or_else(|_err| Path::new(&home_dir).join(".config"), PathBuf::from);

config_path.push("dot");
config_path.push("up");

if !config_path.exists() {
config_path = PathBuf::from(home_dir);
config_path.push(".dot");
config_path.push(".up");
}

config_path.push("dot.toml");
config_path.push("up.toml");
} else {
config_path = PathBuf::from(args_config_path);
ensure!(
Expand All @@ -124,60 +124,60 @@ mod toml_paths_tests {

use std::env;

/// Test possible options for the dot.toml. All run in one file as they modify the
/// Test possible options for the up.toml. All run in one file as they modify the
/// shared test environment.
#[test]
fn get_toml_paths() {
// Set up paths.
let default_path = "$XDG_CONFIG_HOME/dot/dot.toml";
let fake_home_1 = common::fixtures_dir().join("fake_home_dir_with_dotconfig");
let config_toml_1 = fake_home_1.join(".config/dot/dot.toml");
let fake_home_2 = common::fixtures_dir().join("fake_home_dir_without_dotconfig");
let default_path = "$XDG_CONFIG_HOME/up/up.toml";
let fake_home_1 = common::fixtures_dir().join("fake_home_dir_with_upconfig");
let config_toml_1 = fake_home_1.join(".config/up/up.toml");
let fake_home_2 = common::fixtures_dir().join("fake_home_dir_without_upconfig");

// With all options set, we should pick the one passed as command-line arg.
let args_config_path = env::current_exe().unwrap();
env::set_var("HOME", fake_home_1.clone());
env::set_var("XDG_CONFIG_HOME", fake_home_1.join(".config"));
let config_path = Config::get_dot_toml_path(args_config_path.to_str().unwrap());
let config_path = Config::get_up_toml_path(args_config_path.to_str().unwrap());
assert_eq!(config_path.unwrap(), args_config_path);

// If nothing is passed as an arg but DOT_CONFIG exists, we should use the it.
env::set_var("DOT_CONFIG", args_config_path.clone());
// If nothing is passed as an arg but UP_CONFIG exists, we should use it.
env::set_var("UP_CONFIG", args_config_path.clone());
env::set_var("HOME", fake_home_1.clone());
env::set_var("XDG_CONFIG_HOME", fake_home_1.join(".config"));
let config_path = Config::get_dot_toml_path(default_path);
let config_path = Config::get_up_toml_path(default_path);
assert_eq!(config_path.unwrap(), args_config_path);
env::remove_var("DOT_CONFIG");
env::remove_var("UP_CONFIG");

// If nothing is passed as an arg, we should use the XDG_CONFIG_HOME/dot/dot.toml.
// If nothing is passed as an arg, we should use the XDG_CONFIG_HOME/up/up.toml.
env::set_var("HOME", fake_home_1.clone());
env::set_var("XDG_CONFIG_HOME", fake_home_1.join(".config"));
let config_path = Config::get_dot_toml_path(default_path);
let config_path = Config::get_up_toml_path(default_path);
assert_eq!(config_path.unwrap(), config_toml_1);

// If XDG_CONFIG_HOME is invalid we should use ~/.dot/dot.toml.
// If XDG_CONFIG_HOME is invalid we should use ~/.up/up.toml.
env::set_var("HOME", fake_home_1.clone());
// Set XDG_CONFIG_HOME to a non-existent path.
env::set_var("XDG_CONFIG_HOME", fake_home_1.join(".badconfig"));
let config_path = Config::get_dot_toml_path(default_path);
let config_path = Config::get_up_toml_path(default_path);

// If XDG_CONFIG_HOME is missing we should use ~/.config/dot/dot.toml.
assert_eq!(config_path.unwrap(), fake_home_1.join(".dot/dot.toml"));
// If XDG_CONFIG_HOME is missing we should use ~/.config/up/up.toml.
assert_eq!(config_path.unwrap(), fake_home_1.join(".up/up.toml"));
env::remove_var("XDG_CONFIG_HOME");
let config_path = Config::get_dot_toml_path(default_path);
let config_path = Config::get_up_toml_path(default_path);
assert_eq!(config_path.unwrap(), config_toml_1);

// If XDG_CONFIG_HOME is missing and ~/.config doesn't exist we should use ~/.dot/dot.toml.
// If XDG_CONFIG_HOME is missing and ~/.config doesn't exist we should use ~/.up/up.toml.
env::set_var("HOME", fake_home_2.clone());
env::remove_var("XDG_CONFIG_HOME");
let config_path = Config::get_dot_toml_path(default_path);
assert_eq!(config_path.unwrap(), fake_home_2.join(".dot/dot.toml"),);
let config_path = Config::get_up_toml_path(default_path);
assert_eq!(config_path.unwrap(), fake_home_2.join(".up/up.toml"),);

// If none of the options are present we should error.
env::remove_var("HOME");
env::remove_var("XDG_CONFIG_HOME");
// Default arg, i.e. not passed.
let config_path = Config::get_dot_toml_path(default_path);
let config_path = Config::get_up_toml_path(default_path);
assert!(config_path.is_err(), "Config path: {:?}", config_path);
}
}
37 changes: 37 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use anyhow::{bail, Result};

use crate::{
args::{Args, SubCommand},
config::Config,
};

pub mod args;
mod config;
mod link;
mod update;

pub fn run(args: Args) -> Result<()> {
// TODO(gib): Store and fetch config in config module.
let config = Config::from(&args)?;

match args.cmd {
Some(SubCommand::Update {}) => {
// TODO(gib): Handle updates.
update::update(config)?;
}
// TODO(gib): Handle multiple link directories both as args and in config.
// TODO(gib): Add option to warn instead of failing if there are conflicts.
// TODO(gib): Check for conflicts before doing any linking.
Some(SubCommand::Link {
from_dir,
to_dir,
backup_dir,
}) => {
link::link(&from_dir, &to_dir, &backup_dir)?;
}
None => {
bail!("up requires a subcommand, use -h or --help for the usage args.");
}
}
Ok(())
}
Loading

0 comments on commit 759eac8

Please sign in to comment.