Skip to content

Commit

Permalink
feat: add init subcommand (#168)
Browse files Browse the repository at this point in the history
* feat: add init subcommand

* chore: update sample config

* doc: add doc page for init subcommand

* fix: also create uuid file during init

* style: run cargo fmt

* style: make clippy happy

* test: fix failing test

* doc: add hoard init to list of commands

* chore: resolve clippy error
  • Loading branch information
Shadow53 authored Mar 22, 2023
1 parent 82eeebd commit 9a7c951
Show file tree
Hide file tree
Showing 12 changed files with 186 additions and 29 deletions.
3 changes: 2 additions & 1 deletion book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- [Getting Started](./getting-started/README.md)
- [Installation](./getting-started/installation.md)
- [Initial Setup](./getting-started/initial-setup.md)
- [Creating the Config File](getting-started/create-config/README.md)
- [Hoard](getting-started/create-config/hoard.md)
- [Vim and Neovim](getting-started/create-config/vim.md)
Expand All @@ -20,4 +21,4 @@
- [Environments](./config/envs.md)
- [Hoards and Piles](./config/hoards-piles.md)

- [File Permissions](./permissions.md)
- [File Permissions](./permissions.md)
9 changes: 9 additions & 0 deletions book/src/cli/flags-subcommands.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ the system default handler otherwise.

- On Linux and BSD, this delegates to `xdg-open`, which must be installed if `$EDITOR` is not set.

## `hoard init`

```
hoard [flags...] init
```

Ensures the Hoard configuration and data directories exist. If there is no configuration file,
one is created.

## `hoard list`

```
Expand Down
17 changes: 17 additions & 0 deletions book/src/getting-started/initial-setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Initial Setup

Hoard v0.5.2 added the `init` subcommand to create the folders and files necessary for Hoard to run.

> This command only needs to be run the first time you set Hoard up. After that, including on new
> machines, it is enough to synchronize the [hoard data directory][hoard-data-dir] to the new
> machine and setup or restore the [configuration file][hoard-config-file].
## Initializing Hoard

Run `hoard init`. Everything necessary will be created, including a sample configuration file.

Then, run `hoard edit` to [edit the new configuration file][edit-config-file].

[hoard-data-dir]: ../file-locations.md#hoard-data-directroy
[hoard-config-file]: ../file-locations.md#config-file
[edit-config-file]: ./create-config/
46 changes: 24 additions & 22 deletions config.toml.sample
Original file line number Diff line number Diff line change
Expand Up @@ -90,37 +90,39 @@ exclusivity = [
# This hoard uses "linux" instead of "unix" because Steam/Itch/etc. are not on the BSDs.
[hoards.game_saves]
[hoards.game_saves.apotheon]
"unix|steam" = "${XDG_DATA_HOME}/Apotheon/SavedGames"
"unix|steam_flatpak" = "${HOME}/.var/app/com.valvesoftware.Steam/.local/share/Apotheon/SavedGames"
"linux|steam" = "${XDG_DATA_HOME}/Apotheon/SavedGames"
"linux|steam_flatpak" = "${HOME}/.var/app/com.valvesoftware.Steam/.local/share/Apotheon/SavedGames"
[hoards.game_saves.death_and_taxes]
"unix|itch" = "${XDG_CONFIG_HOME}/unity3d/Placeholder Gameworks/Death and Taxes/Saves"
"linux|itch" = "${XDG_CONFIG_HOME}/unity3d/Placeholder Gameworks/Death and Taxes/Saves"
[hoards.game_saves.hat_in_time]
"unix|steam" = "${XDG_DATA_HOME}/Steam/steamapps/common/HatInTime/HatInTimeGame/SaveData"
"unix|steam_flatpak" = "${HOME}/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/common/HatInTime/HatInTimeGame/SaveData"
"linux|steam" = "${XDG_DATA_HOME}/Steam/steamapps/common/HatInTime/HatInTimeGame/SaveData"
"linux|steam_flatpak" = "${HOME}/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/common/HatInTime/HatInTimeGame/SaveData"
[hoards.game_saves.mindustry]
"unix|steam" = "${XDG_DATA_HOME}/Steam/steamapps/common/Mindustry/saves/saves"
"unix|steam_flatpak" = "${HOME}/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/common/Mindustry/saves/saves"
"linux|steam" = "${XDG_DATA_HOME}/Steam/steamapps/common/Mindustry/saves/saves"
"linux|steam_flatpak" = "${HOME}/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/common/Mindustry/saves/saves"

[hoards.git]
"linux" = "/home/shadow53/.gitconfig"
"unix" = "${HOME}/.gitconfig"

[hoards.hoard]
"unix" = "${XDG_CONFIG_HOME}/hoard/config.toml"
"macos" = "${HOME}/Library/Application Support/com.shadow53.hoard/config.toml"
"windows" = "${APPDATA}\\shadow53\\hoard\\config.toml"

[hoards.vim]
[hoards.vim.init]
# Match linux AND neovim environments
"linux|neovim" = "${XDG_CONFIG_HOME}/nvim/init.vim"
"linux|vim" = "${HOME}/.vimrc"
"windows|neovim" = "C:\\Users\\Shadow53\\AppData\\Local\\nvim\\init.vim"
# Match unix AND neovim environments
"unix|neovim" = "${XDG_CONFIG_HOME}/nvim/init.vim"
"unix|vim" = "${HOME}/.vimrc"
"windows|neovim" = "${CSIDL_LOCAL_APPDATA}\\nvim\\init.vim"
"windows|vim" = "${USERPROFILE}/.vim/_vimrc"
[hoards.vim.configdir]
"windows|neovim" = "C:\\Users\\Shadow53\\AppData\\Local\\nvim\\config"
"windows|neovim" = "${CSIDL_LOCAL_APPDATA}\\nvim\\config"
"windows|vim" = "${USERPROFILE}/.vim/config"
"linux|neovim" = "${XDG_CONFIG_HOME}/nvim/config"
"linux|vim" = "${HOME}/.vim/config"
"unix|neovim" = "${XDG_CONFIG_HOME}/nvim/config"
"unix|vim" = "${HOME}/.vim/config"
[hoards.vim.templates]
"windows|neovim" = "C:\\Users\\Shadow53\\AppData\\Local\\nvim\\templates"
"windows|vim" = "C:\\Users\\Shadow53\\.vim\\templates"
"linux|neovim" = "/home/shadow53/.config/nvim/templates"
"linux|vim" = "/home/shadow53/.vim/templates"

[hoards.vorta]
"linux" = "/home/shadow53/.local/share/Vorta/settings.db"
"windows|neovim" = "${CSIDL_LOCAL_APPDATA}\\nvim\\templates"
"windows|vim" = "${USER_PROFILE}\\.vim\\templates"
"unix|neovim" = "${XGD_CONFIG_HOME}/nvim/templates"
"unix|vim" = "${HOME}/.vim/templates"
2 changes: 1 addition & 1 deletion src/checkers/history/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const UUID_FILE_NAME: &str = "uuid";
const HISTORY_DIR_NAME: &str = "history";

#[tracing::instrument(level = "debug")]
fn get_uuid_file() -> PathBuf {
pub(crate) fn get_uuid_file() -> PathBuf {
crate::dirs::config_dir().join(UUID_FILE_NAME)
}

Expand Down
4 changes: 2 additions & 2 deletions src/command/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use tap::TapFallible;
use thiserror::Error;
use tokio::{fs, io};

use super::DEFAULT_CONFIG;

/// Errors that may occur while running the edit command.
#[derive(Debug, Error)]
#[allow(variant_size_differences)]
Expand All @@ -26,8 +28,6 @@ pub enum Error {
IsDirectory(PathBuf),
}

const DEFAULT_CONFIG: &str = include_str!("../../config.toml.sample");

/// Edit the configuration file at `path`.
///
/// This function:
Expand Down
61 changes: 61 additions & 0 deletions src/command/init.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use crate::checkers::history::{get_or_generate_uuid, get_uuid_file};
use crate::Config;
use tokio::fs;

use super::DEFAULT_CONFIG;

#[tracing::instrument(skip_all)]
pub(crate) async fn run_init(config: &Config) -> Result<(), super::Error> {
let data_dir = crate::paths::hoards_dir();
let config_file = config.config_file.as_path();

tracing::info!("creating data directory: {}", data_dir.display());
fs::create_dir_all(&data_dir)
.await
.map_err(|error| super::Error::Init {
path: data_dir.to_path_buf(),
error,
})?;

if let Some(parent) = config_file.parent() {
tracing::info!("creating config directory: {}", parent.display());
fs::create_dir_all(parent)
.await
.map_err(|error| super::Error::Init {
path: parent.to_path_buf(),
error,
})?;
}

let uuid_file = get_uuid_file();
if !uuid_file.exists() {
tracing::info!("device id not found, creating a new one");
get_or_generate_uuid()
.await
.map_err(|error| super::Error::Init {
path: uuid_file,
error,
})?;
}

if !config_file.exists() {
tracing::info!(
"no configuration file found, creating default at {}",
config_file.display()
);
fs::write(config_file, DEFAULT_CONFIG)
.await
.map_err(|error| super::Error::Init {
path: config_file.to_path_buf(),
error,
})?;
}

tracing::info!(
"If you want to synchronize hoards between multiple machines, synchronize {}",
data_dir.display()
);
tracing::info!("To synchronize your Hoard configuration as well, add an entry that backs up {}, not the whole directory", config_file.display());

Ok(())
}
17 changes: 17 additions & 0 deletions src/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,21 @@ mod backup_restore;
mod cleanup;
mod diff;
mod edit;
mod init;
mod list;
mod status;
mod upgrade;

use std::path::PathBuf;

use clap::Parser;
use thiserror::Error;

pub(crate) use backup_restore::{run_backup, run_restore};
pub(crate) use cleanup::run_cleanup;
pub(crate) use diff::run_diff;
pub(crate) use edit::run_edit;
pub(crate) use init::run_init;
pub(crate) use list::run_list;
pub(crate) use status::run_status;
pub(crate) use upgrade::run_upgrade;
Expand All @@ -23,6 +27,8 @@ use crate::newtypes::HoardName;
pub use backup_restore::Error as BackupRestoreError;
pub use edit::Error as EditError;

const DEFAULT_CONFIG: &str = include_str!("../../config.toml.sample");

/// Errors that can occur while running commands.
#[derive(Debug, Error)]
pub enum Error {
Expand Down Expand Up @@ -50,6 +56,15 @@ pub enum Error {
/// Error occurred while running the edit command.
#[error("error while running hoard edit: {0}")]
Edit(#[from] edit::Error),
/// Error occurred while initializing Hoard.
#[error("failed to create {path}: {error}")]
Init {
/// The path that could not be created.
path: PathBuf,
/// Why that path could not be created.
#[source]
error: std::io::Error,
},
/// Error occurred while restoring a hoard.
#[error("failed to restore: {0}")]
Restore(#[source] backup_restore::Error),
Expand Down Expand Up @@ -83,6 +98,8 @@ pub enum Command {
List,
/// Open the configuration file in the system default editor.
Edit,
/// Initialize a new Hoard setup.
Init,
/// Show which files differ for a given hoard. Optionally show unified diffs for text files
/// too.
Diff {
Expand Down
2 changes: 1 addition & 1 deletion src/config/builder/envtrie.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ fn merge_maps(
for (key, set) in map2 {
let new_set = match map1.remove(&key) {
None => set,
Some(other_set) => set.union(&other_set).into_iter().cloned().collect(),
Some(other_set) => set.union(&other_set).cloned().collect(),
};

map1.insert(key, new_set);
Expand Down
3 changes: 3 additions & 0 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ impl Config {
Command::Edit => {
command::run_edit(&self.config_file).await?;
}
Command::Init => {
command::run_init(self).await?;
}
Command::Validate => {
tracing::info!("configuration is valid");
}
Expand Down
4 changes: 2 additions & 2 deletions src/newtypes/environment_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@ mod tests {
panic!("expected success {expected:?} but got error {err:?}")
}
(Ok(result), Ok(expected)) => {
assert_eq!(result, expected, "expected {expected:?} but got {result:?}")
assert_eq!(result, expected, "expected {expected:?} but got {result:?}");
}
(Err(err), Err(expected)) => {
assert_eq!(err, expected, "expected error {expected:?} but got {err:?}")
assert_eq!(err, expected, "expected error {expected:?} but got {err:?}");
}
}
}
Expand Down
47 changes: 47 additions & 0 deletions tests/hoard_init.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
mod common;

use common::tester::Tester;
use hoard::command::Command;
use tokio::fs;

#[tokio::test]
async fn test_hoard_init() {
let tester = Tester::new("").await;

fs::remove_dir(tester.config_dir())
.await
.expect("should have deleted hoard config dir");
if tester.data_dir().exists() {
// config dir and data dir are the same on macos
fs::remove_dir(tester.data_dir())
.await
.expect("should have deleted hoard data dir");
}

assert!(
!tester.config_dir().exists(),
"hoard config directory should not exist"
);
assert!(
!tester.data_dir().exists(),
"hoard data directory should not exist"
);

tester
.run_command(Command::Init)
.await
.expect("initialization should succeed");

assert!(
tester.config_dir().exists(),
"hoard config directory should exist"
);
assert!(
tester.data_dir().exists(),
"hoard data directory should exist"
);

let config_file = tester.config_dir().join("config.toml");

assert!(config_file.exists(), "config file should have been created");
}

0 comments on commit 9a7c951

Please sign in to comment.