diff options
| author | Max Bossing <info@maxbossing.de> | 2025-07-15 14:41:26 +0200 |
|---|---|---|
| committer | Max Bossing <info@maxbossing.de> | 2025-07-15 14:41:26 +0200 |
| commit | 75ed52cb761d90b5e5a96717a4d00ef57003994d (patch) | |
| tree | d4464a446d3324105274101fe7a72d81b658960e | |
init
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | Cargo.lock | 319 | ||||
| -rw-r--r-- | Cargo.toml | 9 | ||||
| -rw-r--r-- | LICENSE | 13 | ||||
| -rw-r--r-- | README.md | 41 | ||||
| -rw-r--r-- | src/config.rs | 44 | ||||
| -rw-r--r-- | src/main.rs | 57 |
7 files changed, 485 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e673575 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea/ +target/
\ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..3093ab3 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,319 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys", +] + +[[package]] +name = "dots" +version = "1.0.0" +dependencies = [ + "dirs", + "serde", + "toml", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libredox" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_users" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_parser" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8f93c25 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "dots" +version = "1.0.0" +edition = "2024" + +[dependencies] +dirs = "6.0.0" +serde = { version = "1.0.219", features = ["derive"] } +toml = "0.9.2" @@ -0,0 +1,13 @@ +Copyright © 2025 Max Bossing + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the “Software”), to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and +to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..8cadb89 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +If you are like me, you probably store your dotfiles in a git repo and symlink them into the right places. However, this +always takes a considerable amount of time to do on a new system. In the past, I resorted to shell scripts to do my bidding, +but that was often machine-specific, wouldn't allow me to configure multiple machines without significant hassle, and was +generally a bit _clunky_. + +# The solution: `dots` + +`dots` is a machine-agnostic, fs-agnostic and structure-agnostic deployer for dotfiles stored in an arbitrary directory. +Written in rust, it's :sparkles: _blazingly fast_ :sparkles: and easy to use. + +## dots.toml + +At the core, `dots` just symlinks arbitrary files and directories as specified by a configuration file. By default, `dots` +will look for a `dots.toml` file in the current directory. + +Example `dots.toml`: +```toml +dots_dir = "/home/anon/dots" # If omitted, defaults to working directory + +[[dot]] +src = "nvim" # Resolved against dots_dir +dest = ".config/nvim" # Resolved against $HOME + +[[dot]] +src = "fish" +dest = ".config/fish" + +[[dot]] +src = "tmux.conf" +dest = ".tmux.conf" + +[[...]] +``` + +If you want to use a different file (for example a different machine but based on the same files), pass a path to the executable +```bash +dots some_file.dots.toml +``` + +## License +This is licensed under the [MIT](LICENSE) license.
\ No newline at end of file diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..6fd790a --- /dev/null +++ b/src/config.rs @@ -0,0 +1,44 @@ +use std::env; +use std::path::PathBuf; +use serde::Deserialize; + +#[derive(Deserialize, Debug)] +pub struct Config { + #[serde(default = "default_dots_dir")] + pub dots_dir: PathBuf, + #[serde(rename = "dot")] + pub dots: Vec<Dot>, +} + +#[derive(Deserialize, Debug)] +pub struct Dot { + #[serde(rename = "src")] + pub source: PathBuf, + #[serde(rename = "dest")] + pub destination: PathBuf, +} + +pub enum ConfigLoadError { + IOError(std::io::Error), + DeserializationError(toml::de::Error), +} + +impl Config { + pub fn load(from: Option<PathBuf>) -> Result<Config, ConfigLoadError> { + let path = from.unwrap_or(PathBuf::from("dots.toml")); + match std::fs::read_to_string(path).map_err(ConfigLoadError::IOError) { + Ok(string) => match toml::from_str(&string) { + Ok(config) => Ok(config), + Err(err) => Err(ConfigLoadError::DeserializationError(err)), + } + Err(e) => { + Err(e) + } + } + } +} + +fn default_dots_dir() -> PathBuf { + env::current_dir().expect("failed to get current dir") +} + diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..151ee8a --- /dev/null +++ b/src/main.rs @@ -0,0 +1,57 @@ +use std::env; +use std::os::unix::fs::symlink; +use std::path::PathBuf; +use std::process::exit; +use dirs::{home_dir}; +use crate::config::{Config, ConfigLoadError, Dot}; + +mod config; + +fn main() { + let args = env::args().skip(1).collect::<Vec<String>>(); + + if args.len() > 1 { + eprintln!("usage: {} [config]", args[0]); + exit(1); + } + + if args.len() == 1 && (args[0] == "-h" || args[0] == "--help") { + println!("Usage: {} [config]", args[0]); + println!("options:"); + println!(" -h, --help Print help information"); + println!("By default, dots will look for a bummsdots.toml file in the current directory"); + println!("This can be changed by passing the filename"); + exit(0); + } + + let config = Config::load(args.get(0).map(|o| PathBuf::from(o))).map_err(|err| + match err { + ConfigLoadError::IOError(err) => { + eprintln!("failed to load config file: {}", err); + exit(1) + } + ConfigLoadError::DeserializationError(err) => { + eprintln!("failed to deserialize config file: {}", err); + exit(1) + } + } + ).unwrap(); + + let prepended_mappings = config.dots.iter().map(|m| + Dot { + source: env::current_dir().expect("failed to get current dir").join(&m.source), + destination: prepend_user_dir(&m.destination) + } + ); + + for dot in prepended_mappings { + println!("linking from {} to {}", dot.source.display(), dot.destination.display()); + let _ = symlink(&dot.source, &dot.destination).map_err(|err| + eprintln!("failed to symlink: {}", err.to_string()) + ); + } +} + +fn prepend_user_dir(path: &PathBuf) -> PathBuf { + home_dir().unwrap().join(path) +}
\ No newline at end of file |
