1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
//! `badm` is a tool that stores your configuration files, or //! [dotfiles](https://en.wikipedia.org/wiki/Hidden_file_and_hidden_directory), in a directory that replicates the directory hierarchy of the //! dotfiles' original path, and creates symlinks to their original paths. This creates a //! standardized and systematic approach for managing, deploying, and sharing dotfiles //! among different systems and users. //! //! badm is ultimately "But Another Dotfiles Manager". //! //! # Examples //! //! - ferris has created a directory to store their dotfiles at `~/.dots` //! - `badm set-dir ~/.dots` sets the BADM dotfiles dir at `~/.dots` //! - badm will search for a badm config file at one of the two valid locations: `$HOME` //! and `$XDG_CONFIG_HOME`. If the config file not found, badm will create it under //! `$HOME` //! //! <pre> //! /home //! └── ferris //! └── .dots //! ├── .badm.toml //! └── .gitconfig //! </pre> //! //! //! - to store `~/.gitconfig` as a dotfile, ferris runs `badm stow ~/.gitconfig` //! _(relative paths work as well)_ //! - badm replicates the path of the dotfile under the `~/.dots` directory //! - the dotfile is moved to this new path in the set dotfiles directory and symlinked at //! its original path which points to its new path //! //! <pre> //! /home //! └── ferris //! ├── .badm.toml //! ├── .dots //! │ └── home //! │ └── ferris //! │ └── .gitconfig //! └── .gitconfig -> /home/ferris/.dots/home/ferris/.gitconfig //! </pre> //! //! # Commands //! //! - `badm set-dir <DIRECTORY>` - set dotfiles directory location, if the location is not //! created BADM has the ability to create one for you //! - `badm stow <FILE>` - store a file in the dotfiles directory, create a symlink at the //! original source of the stowed file. //! - `badm deploy <FILE>` - for new configurations, create symlinks in directories //! relative to the dotfile's directory hierarchy. Directories to replicate the stored //! dotfile's directory structure will be created if not found. //! - `badm restore <FILE>` - restore the stored file from the dotfiles directory and //! replace the symlink with the original file #![cfg_attr(test, deny(warnings))] #![deny(clippy::all)] #![deny(clippy::pedantic)] #![allow(clippy::must_use_candidate)] #![deny( future_incompatible, missing_debug_implementations, missing_docs, missing_copy_implementations, missing_docs, nonstandard_style, trivial_casts, trivial_numeric_casts, unsafe_code, unused_extern_crates, unused_import_braces, unused_qualifications, unused_results, unused_qualifications )] pub mod commands; pub(crate) mod config; mod errors; pub mod paths; pub use crate::config::Config; pub use crate::errors::InputError; #[macro_use] extern crate failure; use std::fs::{self, File}; use std::io::{self, prelude::*, BufWriter}; use std::path::{Path, PathBuf}; // TODO: create dotfile struct /// Struct used to traverse directories and collect entries located within. #[derive(Debug)] pub struct DirScanner { entries: Vec<PathBuf>, recursive: bool, } impl DirScanner { /// Given a directory, traverse path and get entries located within `dir`. /// If the [`DirScanner::recursive`] method is not called before `get_entries`, it /// will only traverse one level below. /// /// [`DirScanner::recursive`]: struct.DirScanner.html/#method.recursive pub fn get_entries(mut self, dir: &Path) -> io::Result<Vec<PathBuf>> { self.collect_entries(dir)?; self.entries = self .entries .into_iter() .map(|path| { if path.is_relative() { fs::canonicalize(path) } else { Ok(path) } }) .filter_map(Result::ok) .collect(); Ok(self.entries) } /// Builder method to set recursive flag to `true` when scanning directory. pub fn recursive(mut self) -> Self { self.recursive = true; self } fn collect_entries(&mut self, dir: &Path) -> io::Result<()> { if dir.is_dir() { for entry in fs::read_dir(dir)? { let path = entry.map(|e| e.path())?; if path.is_dir() && self.recursive { if !path.ends_with(".git") { self.collect_entries(&path)?; } } else { self.entries.push(path) } } } Ok(()) } } impl Default for DirScanner { fn default() -> Self { Self { entries: vec![], recursive: false, } } } #[derive(Copy, Clone, Debug)] /// Moves, stores, and creates files and symlinks. pub struct FileHandler; impl FileHandler { /// Store a file in the dotfiles directory, create a symlink at the original /// source of the stowed file. pub fn store_file(src: &Path, dst: &Path) -> io::Result<()> { Self::move_file(src, dst)?; Self::create_symlink(dst, src) } /// Read file at path src and write to created/truncated file at path dst. pub fn move_file(src: &Path, dst: &Path) -> io::Result<()> { // read file path to String let contents = crate::paths::read_path(src)?; // write String contents to dst file let dst_file = File::create(dst)?; let mut writer = BufWriter::new(dst_file); writer.write_all(contents.as_bytes())?; // remove file at src location fs::remove_file(&src) } /// Create a symlink at "dst" pointing to "src." /// /// For Unix platforms, [`std::os::unix::fs::symlink`] is used to create /// symlinks. For Windows, [`std::os::windows::fs::symlink_file`] is used. /// /// [`std::os::unix::fs::symlink`]: std/os/unix/fs/fn.symlink.html /// [`std::os::windows::fs::symlink_file`]: std/os/windows/fs/fn.symlink_file.html pub fn create_symlink(src: &Path, dst: &Path) -> io::Result<()> { #[cfg(not(target_os = "windows"))] use std::os::unix::fs::symlink; #[cfg(target_os = "windows")] use std::os::windows::fs::symlink_file as symlink; symlink(src, dst) } }