initial commit
This commit is contained in:
commit
ca1b36218f
9 changed files with 762 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
dartgun-test/
|
2
.prettierrc.toml
Normal file
2
.prettierrc.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
lineWidth = 80
|
||||
proseWrap = "always"
|
338
Cargo.lock
generated
Normal file
338
Cargo.lock
generated
Normal file
|
@ -0,0 +1,338 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
|
||||
|
||||
[[package]]
|
||||
name = "dartgun-rs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.203"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.203"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
|
||||
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.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.6.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
10
Cargo.toml
Normal file
10
Cargo.toml
Normal file
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "dartgun-rs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.7", features = ["derive"] }
|
||||
toml = "0.8.14"
|
148
README.md
Normal file
148
README.md
Normal file
|
@ -0,0 +1,148 @@
|
|||
# dartgun
|
||||
|
||||
the stupid dotfile manager.
|
||||
|
||||
## Impetus
|
||||
|
||||
Managing dot files are annoying. They're some of the most essential parts of a
|
||||
developer's system, yet most operating systems (distros) don't provide a way to
|
||||
easily manage them. There's the `~/.config` directory, which in theory holds all
|
||||
of your configuration files. If this were true, you could simply `git init`
|
||||
inside `~/.config` and have a versioned dotfile repo that you could backup and
|
||||
deploy to new machines. However, configuration files often end up all over a
|
||||
system. There's [NixOS](https://nixos.org/), but not everyone can dedicate 40
|
||||
hours a week to configuring their OS.
|
||||
|
||||
Advanced solutions dotfile helpers, but most users don't really need much more
|
||||
than a set of bash scripts which copies their dotfiles around their system.
|
||||
|
||||
Dartgun essentially does this in a more systematic manner, and seeks to be just
|
||||
as still simple to set up and manage. Everything lives in one folder which can
|
||||
be versioned by git. Dartgun will put your dotfiles in the correct places by
|
||||
symlinking from the central dotfile directory to the specified locations.
|
||||
|
||||
The primary goal is to provide an easy way to manage your dotfiles in a
|
||||
centralized area and sync them between different systems. A secondary goal is to
|
||||
help automatically set up a new system with the configuration files in the
|
||||
correct places. However, automatically installing additional software and
|
||||
dependencies is outside of the scope of the project.
|
||||
|
||||
## Non-goals
|
||||
|
||||
Dartgun is specifically designed to have a minimum amount of features to make it
|
||||
as easy to adopt as possible. If you're reading this, I'll assume that you are
|
||||
already looking for a dotfile manager. Therefore, it might be easier to list
|
||||
things that Dartgun is _not_ designed to do. If you do not need any of these
|
||||
features, then Dartgun might the right dotfile manager for you.
|
||||
|
||||
Dartgun is not for:
|
||||
|
||||
- Managing a fleet of computers
|
||||
- Automatically deploying servers
|
||||
- Deterministically setting up an entire OS from configuration files (see NixOS
|
||||
for that)
|
||||
- Automatically installing packages or software alongside dotfiles; it only
|
||||
manages the files, the user is still responsible for ensuring software is
|
||||
available
|
||||
- Power users who want deep customizability and feature sets to help fully
|
||||
automate system configuration
|
||||
|
||||
## Goals
|
||||
|
||||
- Easily version dotfiles with git by keeping everything in one central
|
||||
directory
|
||||
- Copy dotfiles to a new machine quickly and transparently
|
||||
- Sync dotfiles between different machines
|
||||
- Keep configuration effort to a minimum
|
||||
|
||||
## How it works
|
||||
|
||||
Dartgun allows you to centralize all of your dotfiles in a single directory.
|
||||
|
||||
Dartgun is configured through the `dartgun.toml` file. You should set your
|
||||
machine name in this file like so:
|
||||
|
||||
```toml
|
||||
machine = "youwens-mac"
|
||||
```
|
||||
|
||||
Also, it's common to have programs that are not installed on every computer.
|
||||
Therefore, each dotfile will specify which application it is for, and whether or
|
||||
not it should be applied by default. See the dotfile configuration below for how
|
||||
to configure this.
|
||||
|
||||
You can specify which applications are in which machines with the `apps.toml`
|
||||
file.
|
||||
|
||||
```toml
|
||||
[youwens-mac]
|
||||
available = ["neovim", "hyprland", "zsh"]
|
||||
```
|
||||
|
||||
### Why symlinking?
|
||||
|
||||
Any configuration updates made will always sync back to the dartgun directory so
|
||||
they can be tracked by git. Likewise, any remote updates pulled in will also be
|
||||
automatically reflected in the system for free.
|
||||
|
||||
### Why no Windows support?
|
||||
|
||||
Windows symlinks work a little differently from Unix symlinks. The difference is
|
||||
not massive and I plan to look into supporting Windows at a later date.
|
||||
|
||||
## Usage guide
|
||||
|
||||
Begin by creating a folder to place your dotfiles in. I'll refer to it as the
|
||||
"dartgun folder" for the rest of these instructions. Mine is located at
|
||||
`~/.dartgun`, but it can be called whatever you want and located wherever you
|
||||
want it.
|
||||
|
||||
Place your dotfiles in the dartgun folder. You can organize them however you
|
||||
want. The primary configuration is done in the `dartgun.json` file, located in
|
||||
root of the dartgun folder. In here, you specify where each file or folder in
|
||||
the directory goes.
|
||||
|
||||
For example, I can tell the `.dartgun/nvim` folder to go to `~/.config/nvim`
|
||||
with
|
||||
|
||||
```json
|
||||
{
|
||||
darts = [
|
||||
{
|
||||
location: "./nvim",
|
||||
destination: "~/.config/nvim",
|
||||
strategy: "hardlink",
|
||||
machines: [youwens-mac, youwens-archlinux],
|
||||
for: "neovim"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
location: the relative path, from the dartgun folder, where either the directory or file to be copied is located
|
||||
destination: the destination path to which the file should be copied to. must be an absolute path
|
||||
strategy: which strategy to use to copy the file. accepts 'hardlink', 'symlink', or 'copy'
|
||||
machines: which machine names this file will be copied on
|
||||
for: the application which the dotfile is for
|
||||
```
|
||||
|
||||
Note that you can choose how you want to approach the machine configuration. You
|
||||
can either have a specific machine key for each computer you own, or specify
|
||||
machines by platform (eg. "arch", "mac", "fedora"). You can get as fine-grained
|
||||
or generic as you'd like.
|
||||
|
||||
Then, run `dartgun blast` to apply your dotfiles to the locations. For nested
|
||||
destination directories, it will create all of the directories in the chain if
|
||||
they do not exist already. You may have to run `sudo dartgun blast` if you are
|
||||
trying to copy files to restricted locations.
|
||||
|
||||
For symlinked and hardlinked directories, you can simply sync your dotfiles by
|
||||
updating them in the dartgun folder, for example by running `git pull` after
|
||||
you've set up git versioning.
|
||||
|
||||
If you use the `copy` strategy, then you need to re-run `dartgun blast` each
|
||||
time you update the files in your dartgun folder. generally, we do not recommend
|
||||
the copy method unless you have to for some reason, since it may lead to desyncs
|
||||
between the dartgun folder and your actual system, while this is impossible with
|
||||
hardlinking or symlinking.
|
30
src/cli.rs
Normal file
30
src/cli.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
use std::path::Path;
|
||||
|
||||
use crate::dartfile;
|
||||
use clap::Parser;
|
||||
|
||||
/// dartgun's command line interface
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct Cli {
|
||||
action: Option<String>,
|
||||
}
|
||||
|
||||
fn fire() {
|
||||
println!("Attempting to find and read `dartfile.toml`.");
|
||||
let gun = dartfile::parse(Path::new("./dartgun.toml"));
|
||||
println!("Writing symlinks...");
|
||||
match gun.create_symlinks() {
|
||||
Ok(_) => println!("Symlinks created successfully!"),
|
||||
Err(err) => println!("Something went wrong while creating symlinks: {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_cli() {
|
||||
let cli = Cli::parse();
|
||||
match cli.action.as_deref() {
|
||||
Some("fire") => fire(),
|
||||
_ => println!("No action specified. Run `dartgun -h` for options."),
|
||||
};
|
||||
}
|
196
src/dartfile.rs
Normal file
196
src/dartfile.rs
Normal file
|
@ -0,0 +1,196 @@
|
|||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use toml::Table;
|
||||
|
||||
/// The dartfile module handles parsing the `dotfile.toml` into
|
||||
/// a well-typed and self-validating data structure.
|
||||
|
||||
/// The raw form of a dotfile entry. Paths are stored as strings.
|
||||
///
|
||||
/// Should not be used outside of module internals. Its primary purpose
|
||||
/// is to provide an intermediary between the raw parsed TOML and
|
||||
/// the strongly typed Dotfile struct
|
||||
#[derive(Debug)]
|
||||
struct DotfileRaw {
|
||||
location: String,
|
||||
destination: String,
|
||||
strategy: String,
|
||||
machines: Vec<String>,
|
||||
applies_to: String,
|
||||
}
|
||||
|
||||
impl DotfileRaw {
|
||||
fn determine_strategy(&self) -> Result<Strategy, String> {
|
||||
match self.strategy.as_str() {
|
||||
"hardlink" => Ok(Strategy::Hardlink),
|
||||
"symlink" => Ok(Strategy::Symlink),
|
||||
_ => Err(String::from(
|
||||
"Strategy must be either 'symlink' or 'hardlink'",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_dotfile(&self) -> Result<Dotfile, String> {
|
||||
let location_path = PathBuf::from(&self.location);
|
||||
let destination_path = PathBuf::from(&self.destination);
|
||||
|
||||
Ok(Dotfile {
|
||||
location: location_path,
|
||||
destination: destination_path,
|
||||
strategy: self.determine_strategy()?,
|
||||
machines: self.machines.clone(),
|
||||
applies_to: self.applies_to.clone(),
|
||||
})
|
||||
}
|
||||
// TODO: improve error handling for parsing from raw TOML
|
||||
|
||||
/// Create a new `DotfileRaw` from a `toml::Table`, which is a `Vec<Value>`.
|
||||
/// Will panic if the table does not contain the fields specified.
|
||||
fn from_table(table: Table) -> DotfileRaw {
|
||||
let location = table
|
||||
.get("location")
|
||||
.ok_or("Missing 'location' field.")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let destination = table
|
||||
.get("destination")
|
||||
.ok_or("Missing 'destination' field.")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let strategy = table
|
||||
.get("strategy")
|
||||
.ok_or("Missing 'strategy' field.")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let machines = table
|
||||
.get("machines")
|
||||
.ok_or("Missing 'machines' field.")
|
||||
.unwrap()
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|x| x.as_str().unwrap().to_string())
|
||||
.collect();
|
||||
let applies_to = table
|
||||
.get("applies_to")
|
||||
.ok_or("Missing 'applies_to' field.")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
DotfileRaw {
|
||||
location,
|
||||
destination,
|
||||
strategy,
|
||||
machines,
|
||||
applies_to,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The configuration object parsed from the `config` field in `dartgun.toml`
|
||||
#[derive(Debug)]
|
||||
pub struct Config {
|
||||
machine: String,
|
||||
available: Vec<String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
// TODO: improve error handling for parsing config
|
||||
|
||||
/// Generate a `Config` from a raw `dartfile.toml`.
|
||||
/// Will panic! if the format is invalid.
|
||||
fn from_table(table: Table) -> Config {
|
||||
let machine = table.get("machine").unwrap().as_str().unwrap().to_string();
|
||||
let available = table
|
||||
.get("available")
|
||||
.unwrap()
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|x| x.as_str().unwrap().to_string())
|
||||
.collect();
|
||||
Config { available, machine }
|
||||
}
|
||||
}
|
||||
|
||||
/// A strongly typed Dartfile (aka `dartgun.toml`). Users of this
|
||||
/// struct can assume that the dartfile is at least semantically valid
|
||||
#[derive(Debug)]
|
||||
pub struct Dartfile {
|
||||
pub config: Config,
|
||||
pub dots: Vec<Dotfile>,
|
||||
}
|
||||
|
||||
impl Dartfile {
|
||||
/// Validates the Dartfile by checking each Dotfile entry to ensure
|
||||
/// the paths specified by `location` are accessible.
|
||||
pub fn validate(&self) -> Result<(), String> {
|
||||
for dotfile in self.dots.iter() {
|
||||
if dotfile.validate().is_err() {
|
||||
return Err("Invalid dotfile.".to_string());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents which strategy to use when deploying dotfiles across system.
|
||||
#[derive(Debug)]
|
||||
pub enum Strategy {
|
||||
Hardlink,
|
||||
Symlink,
|
||||
}
|
||||
|
||||
/// A strongly-typed Dotfile entry
|
||||
#[derive(Debug)]
|
||||
pub struct Dotfile {
|
||||
pub location: PathBuf,
|
||||
pub destination: PathBuf,
|
||||
pub strategy: Strategy,
|
||||
pub machines: Vec<String>,
|
||||
pub applies_to: String,
|
||||
}
|
||||
|
||||
impl Dotfile {
|
||||
/// Validates the entry by checking whether the `location` paths
|
||||
/// specified are accessible.
|
||||
pub fn validate(&self) -> Result<(), String> {
|
||||
let path_exists = self.location.try_exists();
|
||||
match path_exists {
|
||||
Ok(true) => Ok(()),
|
||||
Ok(false) => Err("Could not follow broken symlink.".to_string()),
|
||||
Err(_) => Err("An error occurred. Does the path exist?".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes a path to a `dartgun.toml` and produces a well-typed Dartfile object.
|
||||
/// Currently crashes on any parse errors, but this behavior will likely change in the future.
|
||||
pub fn parse(path: &Path) -> Dartfile {
|
||||
let raw_data = fs::read_to_string(path).expect("Couldn't read the file.");
|
||||
let value: Table = raw_data.parse::<Table>().expect("Couldn't parse the TOML.");
|
||||
|
||||
let config_raw = value.get("config").unwrap().as_table().unwrap();
|
||||
let dots_raw = value.get("dots").unwrap().as_array().unwrap();
|
||||
|
||||
let config = Config::from_table(config_raw.clone());
|
||||
let dots = dots_raw
|
||||
.iter()
|
||||
.map(|x| {
|
||||
match DotfileRaw::from_table(x.as_table().unwrap().clone()).to_dotfile() {
|
||||
Ok(dotfile) => dotfile,
|
||||
Err(_) => panic!("An error has occurred parsing the `dartgun.toml` file. Please make sure it is in the correct format.")
|
||||
}
|
||||
})
|
||||
.collect::<Vec<Dotfile>>();
|
||||
|
||||
Dartfile { config, dots }
|
||||
}
|
18
src/dartgun.rs
Normal file
18
src/dartgun.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
/// Utilities for translating Dartfiles into actual actions on the system.
|
||||
use crate::dartfile::{Dartfile, Dotfile};
|
||||
use std::os::unix::fs::symlink;
|
||||
|
||||
impl Dotfile {
|
||||
pub fn create_symlink(&self) -> Result<(), std::io::Error> {
|
||||
symlink(self.location.canonicalize()?, &self.destination)
|
||||
}
|
||||
}
|
||||
|
||||
impl Dartfile {
|
||||
pub fn create_symlinks(&self) -> Result<(), std::io::Error> {
|
||||
for dot in self.dots.iter() {
|
||||
dot.create_symlink()?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
18
src/main.rs
Normal file
18
src/main.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
use std::path::Path;
|
||||
|
||||
use dartfile::parse;
|
||||
|
||||
mod cli;
|
||||
mod dartfile;
|
||||
mod dartgun;
|
||||
|
||||
fn main() {
|
||||
let test_path = Path::new("./dartgun.toml");
|
||||
let test_dotfile = parse(test_path);
|
||||
println!("{:?}", parse(test_path));
|
||||
match test_dotfile.validate() {
|
||||
Ok(_) => println!("Dotfile seems valid!"),
|
||||
Err(_) => println!("Dotfile is invalid!"),
|
||||
}
|
||||
cli::run_cli();
|
||||
}
|
Loading…
Reference in a new issue