├── .github └── workflows │ └── workflow.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── ci ├── before_deploy.ps1 ├── before_deploy.sh ├── install.sh └── script.sh ├── rustfmt.toml ├── scripts └── bootstrap.sh ├── src ├── app.rs ├── dotfiles.rs ├── entry.rs ├── lib.rs ├── main.rs ├── util.rs └── windows.rs ├── templates └── mappings-example.toml └── tests └── dotfiles └── .mappings /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | name: Workflow 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - 'v*' 9 | pull_request: 10 | branches: 11 | - master 12 | 13 | env: 14 | CARGO_TERM_VERBOSE: true 15 | 16 | jobs: 17 | build: 18 | strategy: 19 | matrix: 20 | conf: 21 | - { target: x86_64-unknown-linux-gnu , os: ubuntu-18.04 } 22 | - { target: x86_64-unknown-linux-musl , os: ubuntu-18.04 , use_cross: true } 23 | - { target: x86_64-pc-windows-msvc , os: windows-latest } 24 | # - { target: x86_64-pc-windows-gnu , os: ubuntu-latest , use_cross: true } 25 | - { target: x86_64-apple-darwin , os: macos-latest } 26 | # - { target: x86_64-unknown-freebsd , os: ubuntu-18.04 , use_cross: true , disable_tests: true } 27 | # - { target: arm-linux-androideabi , os: ubuntu-18.04 , use_cross: true , disable_tests: true } 28 | 29 | runs-on: ${{ matrix.conf.os }} 30 | steps: 31 | - uses: actions/checkout@v1 32 | 33 | - name: Cache cargo registry 34 | uses: actions/cache@v1 35 | with: 36 | path: ~/.cargo/registry 37 | key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} 38 | - name: Cache cargo index 39 | uses: actions/cache@v1 40 | with: 41 | path: ~/.cargo/git 42 | key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} 43 | 44 | - name: Install Rust toolchain 45 | uses: actions-rs/toolchain@v1 46 | with: 47 | profile: minimal 48 | toolchain: stable 49 | target: ${{ matrix.conf.target }} 50 | override: true 51 | 52 | - name: Run tests 53 | uses: actions-rs/cargo@v1 54 | if: matrix.conf.disable_tests != true 55 | with: 56 | use-cross: ${{ matrix.conf.use_cross == true }} 57 | command: test 58 | args: --target ${{ matrix.conf.target }} 59 | 60 | - name: Build artifact 61 | uses: actions-rs/cargo@v1 62 | with: 63 | use-cross: ${{ matrix.conf.use_cross == true }} 64 | command: build 65 | args: --target ${{ matrix.conf.target }} --release 66 | 67 | - name: Zip artifact 68 | run: zip --junk-paths dot-${{ matrix.conf.target }}.zip target/${{ matrix.conf.target }}/release/dot 69 | if: contains(runner.os, 'windows') == false 70 | 71 | - name: Zip artifact 72 | run: Compress-Archive -Destination dot-${{ matrix.conf.target }}.zip -Path target/${{ matrix.conf.target }}/release/dot.exe 73 | if: contains(runner.os, 'windows') 74 | 75 | - uses: actions/upload-artifact@v1 76 | with: 77 | name: dot-${{ matrix.conf.target }}.zip 78 | path: dot-${{ matrix.conf.target }}.zip 79 | 80 | # TODO: deploy to GitHub Release 81 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | *.tar.gz 3 | dot-*/ 4 | completions/ 5 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "advapi32-sys" 5 | version = "0.2.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | dependencies = [ 8 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 9 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 10 | ] 11 | 12 | [[package]] 13 | name = "aho-corasick" 14 | version = "0.6.10" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | dependencies = [ 17 | "memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 18 | ] 19 | 20 | [[package]] 21 | name = "ansi_term" 22 | version = "0.9.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | 25 | [[package]] 26 | name = "ansi_term" 27 | version = "0.11.0" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | dependencies = [ 30 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 31 | ] 32 | 33 | [[package]] 34 | name = "arrayref" 35 | version = "0.3.5" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | 38 | [[package]] 39 | name = "arrayvec" 40 | version = "0.5.1" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | 43 | [[package]] 44 | name = "atty" 45 | version = "0.2.14" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | dependencies = [ 48 | "hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 49 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 50 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 51 | ] 52 | 53 | [[package]] 54 | name = "backtrace" 55 | version = "0.3.41" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | dependencies = [ 58 | "backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 59 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 60 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 61 | "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 62 | ] 63 | 64 | [[package]] 65 | name = "backtrace-sys" 66 | version = "0.1.32" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | dependencies = [ 69 | "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", 70 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 71 | ] 72 | 73 | [[package]] 74 | name = "base64" 75 | version = "0.10.1" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | dependencies = [ 78 | "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 79 | ] 80 | 81 | [[package]] 82 | name = "bitflags" 83 | version = "1.2.1" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | 86 | [[package]] 87 | name = "blake2b_simd" 88 | version = "0.5.10" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | dependencies = [ 91 | "arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 92 | "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 93 | "constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 94 | ] 95 | 96 | [[package]] 97 | name = "byteorder" 98 | version = "1.3.2" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | 101 | [[package]] 102 | name = "cc" 103 | version = "1.0.50" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | 106 | [[package]] 107 | name = "cfg-if" 108 | version = "0.1.10" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | 111 | [[package]] 112 | name = "clap" 113 | version = "2.33.0" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | dependencies = [ 116 | "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 117 | "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", 118 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 119 | "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 120 | "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 121 | "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 122 | "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 123 | ] 124 | 125 | [[package]] 126 | name = "cloudabi" 127 | version = "0.0.3" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | dependencies = [ 130 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 131 | ] 132 | 133 | [[package]] 134 | name = "constant_time_eq" 135 | version = "0.1.5" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | 138 | [[package]] 139 | name = "crossbeam-utils" 140 | version = "0.6.6" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | dependencies = [ 143 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 144 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 145 | ] 146 | 147 | [[package]] 148 | name = "dirs" 149 | version = "2.0.2" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | dependencies = [ 152 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 153 | "dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 154 | ] 155 | 156 | [[package]] 157 | name = "dirs-sys" 158 | version = "0.3.4" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | dependencies = [ 161 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 162 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 163 | "redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 164 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 165 | ] 166 | 167 | [[package]] 168 | name = "dot" 169 | version = "0.1.4" 170 | dependencies = [ 171 | "advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 172 | "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 173 | "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", 174 | "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 175 | "error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", 176 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 177 | "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 178 | "runas 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 179 | "shellexpand 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 180 | "toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", 181 | "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", 182 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 183 | ] 184 | 185 | [[package]] 186 | name = "error-chain" 187 | version = "0.12.1" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | dependencies = [ 190 | "backtrace 0.3.41 (registry+https://github.com/rust-lang/crates.io-index)", 191 | "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 192 | ] 193 | 194 | [[package]] 195 | name = "failure" 196 | version = "0.1.6" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | dependencies = [ 199 | "backtrace 0.3.41 (registry+https://github.com/rust-lang/crates.io-index)", 200 | "failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 201 | ] 202 | 203 | [[package]] 204 | name = "failure_derive" 205 | version = "0.1.6" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | dependencies = [ 208 | "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 209 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 210 | "syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", 211 | "synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", 212 | ] 213 | 214 | [[package]] 215 | name = "fuchsia-cprng" 216 | version = "0.1.1" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | 219 | [[package]] 220 | name = "gcc" 221 | version = "0.3.55" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | 224 | [[package]] 225 | name = "hermit-abi" 226 | version = "0.1.6" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | dependencies = [ 229 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 230 | ] 231 | 232 | [[package]] 233 | name = "idna" 234 | version = "0.1.5" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | dependencies = [ 237 | "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 238 | "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 239 | "unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 240 | ] 241 | 242 | [[package]] 243 | name = "kernel32-sys" 244 | version = "0.2.2" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | dependencies = [ 247 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 248 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 249 | ] 250 | 251 | [[package]] 252 | name = "lazy_static" 253 | version = "1.4.0" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | 256 | [[package]] 257 | name = "libc" 258 | version = "0.2.66" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | 261 | [[package]] 262 | name = "matches" 263 | version = "0.1.8" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | 266 | [[package]] 267 | name = "memchr" 268 | version = "2.3.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | 271 | [[package]] 272 | name = "percent-encoding" 273 | version = "1.0.1" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | 276 | [[package]] 277 | name = "proc-macro2" 278 | version = "1.0.7" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | dependencies = [ 281 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 282 | ] 283 | 284 | [[package]] 285 | name = "quote" 286 | version = "1.0.2" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | dependencies = [ 289 | "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 290 | ] 291 | 292 | [[package]] 293 | name = "rand_core" 294 | version = "0.3.1" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | dependencies = [ 297 | "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 298 | ] 299 | 300 | [[package]] 301 | name = "rand_core" 302 | version = "0.4.2" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | 305 | [[package]] 306 | name = "rand_os" 307 | version = "0.1.3" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | dependencies = [ 310 | "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 311 | "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 312 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 313 | "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 314 | "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 315 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 316 | ] 317 | 318 | [[package]] 319 | name = "rdrand" 320 | version = "0.4.0" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | dependencies = [ 323 | "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 324 | ] 325 | 326 | [[package]] 327 | name = "redox_syscall" 328 | version = "0.1.56" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | 331 | [[package]] 332 | name = "redox_users" 333 | version = "0.3.1" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | dependencies = [ 336 | "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 337 | "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 338 | "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", 339 | "rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 340 | ] 341 | 342 | [[package]] 343 | name = "regex" 344 | version = "0.2.11" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | dependencies = [ 347 | "aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", 348 | "memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 349 | "regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", 350 | "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 351 | "utf8-ranges 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 352 | ] 353 | 354 | [[package]] 355 | name = "regex-syntax" 356 | version = "0.5.6" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | dependencies = [ 359 | "ucd-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 360 | ] 361 | 362 | [[package]] 363 | name = "runas" 364 | version = "0.1.4" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | dependencies = [ 367 | "gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)", 368 | ] 369 | 370 | [[package]] 371 | name = "rust-argon2" 372 | version = "0.5.1" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | dependencies = [ 375 | "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", 376 | "blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)", 377 | "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", 378 | ] 379 | 380 | [[package]] 381 | name = "rustc-demangle" 382 | version = "0.1.16" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | 385 | [[package]] 386 | name = "serde" 387 | version = "1.0.104" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | 390 | [[package]] 391 | name = "shellexpand" 392 | version = "1.1.1" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | dependencies = [ 395 | "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 396 | ] 397 | 398 | [[package]] 399 | name = "smallvec" 400 | version = "1.1.0" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | 403 | [[package]] 404 | name = "strsim" 405 | version = "0.8.0" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | 408 | [[package]] 409 | name = "syn" 410 | version = "1.0.13" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | dependencies = [ 413 | "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 414 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 415 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 416 | ] 417 | 418 | [[package]] 419 | name = "synstructure" 420 | version = "0.12.3" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | dependencies = [ 423 | "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 424 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 425 | "syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", 426 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 427 | ] 428 | 429 | [[package]] 430 | name = "textwrap" 431 | version = "0.11.0" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | dependencies = [ 434 | "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 435 | ] 436 | 437 | [[package]] 438 | name = "thread_local" 439 | version = "0.3.6" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | dependencies = [ 442 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 443 | ] 444 | 445 | [[package]] 446 | name = "toml" 447 | version = "0.4.10" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | dependencies = [ 450 | "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 451 | ] 452 | 453 | [[package]] 454 | name = "ucd-util" 455 | version = "0.1.5" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | 458 | [[package]] 459 | name = "unicode-bidi" 460 | version = "0.3.4" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | dependencies = [ 463 | "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 464 | ] 465 | 466 | [[package]] 467 | name = "unicode-normalization" 468 | version = "0.1.11" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | dependencies = [ 471 | "smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 472 | ] 473 | 474 | [[package]] 475 | name = "unicode-width" 476 | version = "0.1.7" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | 479 | [[package]] 480 | name = "unicode-xid" 481 | version = "0.2.0" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | 484 | [[package]] 485 | name = "url" 486 | version = "1.7.2" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | dependencies = [ 489 | "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 490 | "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 491 | "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 492 | ] 493 | 494 | [[package]] 495 | name = "utf8-ranges" 496 | version = "1.0.4" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | 499 | [[package]] 500 | name = "vec_map" 501 | version = "0.8.1" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | 504 | [[package]] 505 | name = "version_check" 506 | version = "0.1.5" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | 509 | [[package]] 510 | name = "winapi" 511 | version = "0.2.8" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | 514 | [[package]] 515 | name = "winapi" 516 | version = "0.3.8" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | dependencies = [ 519 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 520 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 521 | ] 522 | 523 | [[package]] 524 | name = "winapi-build" 525 | version = "0.1.1" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | 528 | [[package]] 529 | name = "winapi-i686-pc-windows-gnu" 530 | version = "0.4.0" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | 533 | [[package]] 534 | name = "winapi-x86_64-pc-windows-gnu" 535 | version = "0.4.0" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | 538 | [metadata] 539 | "checksum advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e06588080cb19d0acb6739808aafa5f26bfb2ca015b2b6370028b44cf7cb8a9a" 540 | "checksum aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5" 541 | "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 542 | "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" 543 | "checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" 544 | "checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" 545 | "checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 546 | "checksum backtrace 0.3.41 (registry+https://github.com/rust-lang/crates.io-index)" = "a4ed64ae6d9ebfd9893193c4b2532b1292ec97bd8271c9d7d0fa90cd78a34cba" 547 | "checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" 548 | "checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" 549 | "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 550 | "checksum blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" 551 | "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" 552 | "checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" 553 | "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 554 | "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" 555 | "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 556 | "checksum constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" 557 | "checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" 558 | "checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" 559 | "checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" 560 | "checksum error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3ab49e9dcb602294bc42f9a7dfc9bc6e936fca4418ea300dbfb84fe16de0b7d9" 561 | "checksum failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9" 562 | "checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" 563 | "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 564 | "checksum gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" 565 | "checksum hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eff2656d88f158ce120947499e971d743c05dbcbed62e5bd2f38f1698bbc3772" 566 | "checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" 567 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 568 | "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 569 | "checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" 570 | "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 571 | "checksum memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3197e20c7edb283f87c071ddfc7a2cca8f8e0b888c242959846a6fce03c72223" 572 | "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" 573 | "checksum proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0319972dcae462681daf4da1adeeaa066e3ebd29c69be96c6abb1259d2ee2bcc" 574 | "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" 575 | "checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 576 | "checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 577 | "checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" 578 | "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 579 | "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 580 | "checksum redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecedbca3bf205f8d8f5c2b44d83cd0690e39ee84b951ed649e9f1841132b66d" 581 | "checksum regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384" 582 | "checksum regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" 583 | "checksum runas 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6faa02258923b46584eb9738a2274fcd70530db56614189eab0ab424bb143c99" 584 | "checksum rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf" 585 | "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" 586 | "checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" 587 | "checksum shellexpand 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2c7e79eddc7b411f9beeaaf2d421de7e7cb3b1ab9eaf1b79704c0e4130cba6b5" 588 | "checksum smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44e59e0c9fa00817912ae6e4e6e3c4fe04455e75699d06eedc7d85917ed8e8f4" 589 | "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 590 | "checksum syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1e4ff033220a41d1a57d8125eab57bf5263783dfdcc18688b1dacc6ce9651ef8" 591 | "checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" 592 | "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 593 | "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" 594 | "checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" 595 | "checksum ucd-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa9b3b49edd3468c0e6565d85783f51af95212b6fa3986a5500954f00b460874" 596 | "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 597 | "checksum unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b561e267b2326bb4cebfc0ef9e68355c7abe6c6f522aeac2f5bf95d56c59bdcf" 598 | "checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" 599 | "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 600 | "checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" 601 | "checksum utf8-ranges 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba" 602 | "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" 603 | "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" 604 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 605 | "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 606 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 607 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 608 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 609 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dot" 3 | version = "0.1.4" 4 | authors = ["Yusuke Sasaki "] 5 | description = "Alternative of dotfile management frameworks" 6 | edition = "2018" 7 | 8 | [dependencies] 9 | ansi_term = "0.9" 10 | clap = "2.20" 11 | error-chain = "0.12.1" 12 | regex = "0.2.1" 13 | shellexpand = "1" 14 | toml = "0.4" 15 | url = "1.5" 16 | dirs = "2.0.2" 17 | 18 | [target.'cfg(windows)'.dependencies] 19 | winapi = "0.2.8" 20 | advapi32-sys = "0.2.0" 21 | kernel32-sys = "0.2.2" 22 | runas = "0.1.1" 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Yusuke Sasaki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `dot` 2 | 3 | ![GitHub Actions](https://github.com/ubnt-intrepid/dot/workflows/Workflow/badge.svg) 4 | 5 | `dot` is a command-line tool for managing dotfiles, written in Rust. 6 | 7 | ## Overview 8 | `dot` provides a way to organize configuration files in your home directory. 9 | 10 | ## Installation 11 | Precompiled binaries are on our [GitHub releases page](https://github.com/ubnt-intrepid/dot/releases/latest). 12 | If you want to use the development version, try `cargo install` to build from source: 13 | 14 | ```shell-session 15 | $ cargo install --git https://github.com/ubnt-intrepid/dot.git 16 | ``` 17 | 18 | ## Example Usage 19 | Clone your dotfiles repository from github and then create home directory symlinks: 20 | ```sh 21 | $ dot init ubnt-intrepid/dotfiles 22 | ``` 23 | 24 | Check if all of the links exist and are correct: 25 | ```sh 26 | $ dot check 27 | ``` 28 | 29 | `` determines the remote repository's URL of dotfiles. 30 | 31 | Pattern types: 32 | 33 | * `(http|https|ssh|git)://[username@]github.com[:port]/path-to-repo.git` – URL of dotfiles repository 34 | * `git@github.com:path-to-repo.git` – SCP-like path 35 | * `username/dotfiles` – GitHub user and repository 36 | * `username` – GitHub user only (repository `dotfiles`, e.g.: `https://github.com/myuser/dotfiles`) 37 | 38 | By default, the repository will be cloned locally to `$HOME/.dotfiles`. This can be overridden with `$DOT_DIR`. 39 | 40 | For more information, run `dot help`. 41 | 42 | ## Configuration 43 | `$DOT_DIR/.mappings` where the symlinks are defined in [TOML](https://github.com/toml-lang/toml). For example: 44 | 45 | ```toml 46 | [general] 47 | gitconfig = "~/.gitconfig" 48 | "vim/vimrc" = ["~/.vimrc", "~/.config/nvim/init.vim"] 49 | #... 50 | 51 | [windows] 52 | vscode = "$APPDATA/Code/User" 53 | powershell = "$HOME/Documents/WindowsPowerShell" 54 | #... 55 | 56 | [linux] 57 | xinitrc = "~/.xinitrc" 58 | ``` 59 | 60 | Use `[general]` for symlinks on all platforms. `[windows]`, `[linux]`, `[macos]` for symlinks on specific platforms. 61 | 62 | See [my dotfiles](https://github.com/ubnt-intrepid/dotfiles) for a real example. 63 | 64 | ## License 65 | `dot` is distributed under the MIT license. 66 | See [LICENSE](LICENSE) for details. 67 | 68 | ## Similar Projects 69 | - [ssh0/dot](https://github.com/ssh0/dot) 70 | written in shell script 71 | - [rhysd/dotfiles](https://github.com/rhysd/dotfiles) 72 | written in Golang 73 | -------------------------------------------------------------------------------- /ci/before_deploy.ps1: -------------------------------------------------------------------------------- 1 | # This script takes care of packaging the build artifacts that will go in the 2 | # release zipfile 3 | 4 | $SRC_DIR = $PWD.Path 5 | $STAGE = [System.Guid]::NewGuid().ToString() 6 | 7 | Set-Location $ENV:Temp 8 | New-Item -Type Directory -Name $STAGE 9 | Set-Location $STAGE 10 | 11 | $ZIP = "$SRC_DIR\$($Env:CRATE_NAME)-$($Env:APPVEYOR_REPO_TAG_NAME)-$($Env:TARGET).zip" 12 | 13 | # TODO Update this to package the right artifacts 14 | Copy-Item "$SRC_DIR\target\$($Env:TARGET)\release\dot.exe" '.\' 15 | 16 | 7z a "$ZIP" * 17 | 18 | Push-AppveyorArtifact "$ZIP" 19 | 20 | Remove-Item *.* -Force 21 | Set-Location .. 22 | Remove-Item $STAGE 23 | Set-Location $SRC_DIR 24 | -------------------------------------------------------------------------------- /ci/before_deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script takes care of building your crate and packaging it for release 4 | 5 | set -ex 6 | 7 | main() { 8 | local src=$(pwd) \ 9 | stage= 10 | 11 | case $TRAVIS_OS_NAME in 12 | linux) 13 | stage=$(mktemp -d) 14 | ;; 15 | osx) 16 | stage=$(mktemp -d -t tmp) 17 | ;; 18 | esac 19 | 20 | test -f Cargo.lock || cargo generate-lockfile 21 | 22 | # TODO Update this to build the artifacts that matter to you 23 | cross rustc --bin dot --target $TARGET --release -- -C lto 24 | 25 | # TODO Update this to package the right artifacts 26 | cp target/$TARGET/release/dot $stage/ 27 | 28 | cd $stage 29 | tar czf $src/$CRATE_NAME-$TRAVIS_TAG-$TARGET.tar.gz * 30 | cd $src 31 | 32 | rm -rf $stage 33 | } 34 | 35 | main 36 | -------------------------------------------------------------------------------- /ci/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # copied from trust 4 | 5 | set -ex 6 | 7 | main() { 8 | local target= 9 | if [ $TRAVIS_OS_NAME = linux ]; then 10 | target=x86_64-unknown-linux-musl 11 | sort=sort 12 | else 13 | target=x86_64-apple-darwin 14 | sort=gsort # for `sort --sort-version`, from brew's coreutils. 15 | fi 16 | 17 | # This fetches latest stable release 18 | local tag=$(git ls-remote --tags --refs --exit-code https://github.com/japaric/cross \ 19 | | cut -d/ -f3 \ 20 | | grep -E '^v[0.1.0-9.]+$' \ 21 | | $sort --version-sort \ 22 | | tail -n1) 23 | curl -LSfs https://japaric.github.io/trust/install.sh | \ 24 | sh -s -- \ 25 | --force \ 26 | --git japaric/cross \ 27 | --tag $tag \ 28 | --target $target 29 | } 30 | 31 | main 32 | -------------------------------------------------------------------------------- /ci/script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | # TODO This is the "test phase", tweak it as you see fit 6 | main() { 7 | cross build --target $TARGET --release 8 | 9 | if [ ! -z $DISABLE_TESTS ]; then 10 | return 11 | fi 12 | 13 | cross test --target $TARGET --release 14 | } 15 | 16 | # we don't run the "test phase" when doing deploys 17 | if [ -z $TRAVIS_TAG ]; then 18 | main 19 | fi 20 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | write_mode = "Overwrite" 2 | tab_spaces = 4 3 | max_width = 120 4 | array_layout = "Block" 5 | chain_indent = "Visual" 6 | closure_block_indent_threshold = 0 7 | take_source_hints = true 8 | -------------------------------------------------------------------------------- /scripts/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Usage: 4 | # DOTURL=https://github.com/ubnt-intrepid/.dotfiles.git [PREFIX=$HOME/.local] ./bootstrap.sh 5 | 6 | # Repository URL of your dotfiles. 7 | DOT_URL=${DOT_URL:-"https://github.com/ubnt-intrepid/.dotfiles.git"} 8 | 9 | # 10 | DOT_DIR=${DOT_DIR:-"$HOME/.dotfiles"} 11 | 12 | # installation directory of `dot` 13 | PREFIX=${PREFIX:-"$HOME/.local"} 14 | 15 | 16 | # --- export as environment variables 17 | export DOT_DIR 18 | 19 | 20 | # --- download `dot.rs` from GitHub Releases and install 21 | case `uname -s | tr '[A-Z]' '[a-z]'` in 22 | *mingw* | *msys*) 23 | DOTRS_SUFFIX="`uname -m`-windows-msvc" 24 | ;; 25 | *darwin*) 26 | DOTRS_SUFFIX="`uname -m`-apple-darwin" 27 | ;; 28 | *linux*) 29 | DOTRS_SUFFIX="`uname -m`-unknown-linux-musl" 30 | ;; 31 | *android*) 32 | # TODO: support for other architectures 33 | DOTRS_SUFFIX="arm-linux-androideabi" 34 | ;; 35 | *) 36 | echo "[fatal] cannot recognize the platform." 37 | exit 1 38 | esac 39 | 40 | DOTRS_URL="`curl -s https://api.github.com/repos/ubnt-intrepid/dot.rs/releases | grep browser_download_url | cut -d '"' -f 4 | grep "$DOTRS_SUFFIX" | head -n 1`" 41 | echo "$DOTRS_URL" 42 | 43 | mkdir -p "${PREFIX}/bin" 44 | curl -sL "${DOTRS_URL}" | tar xz -C "$PREFIX/bin/" --strip=1 './dot' 45 | 46 | export PATH="$PREFIX/bin:$PATH" 47 | 48 | # --- clone your dotfiles into home directory, and make links. 49 | [[ -d "$DOT_DIR" ]] || git clone "$DOT_URL" "$DOT_DIR" 50 | dot link --verbose 51 | -------------------------------------------------------------------------------- /src/app.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Borrow; 2 | use std::env; 3 | use std::path::Path; 4 | use crate::dotfiles::Dotfiles; 5 | use crate::util; 6 | use crate::errors::Result; 7 | use url::Url; 8 | use regex::Regex; 9 | use dirs; 10 | 11 | #[cfg(windows)] 12 | use crate::windows; 13 | 14 | pub struct App { 15 | dotfiles: Dotfiles, 16 | dry_run: bool, 17 | verbose: bool, 18 | } 19 | 20 | impl App { 21 | pub fn new(dry_run: bool, verbose: bool) -> Result { 22 | let dotdir = init_envs()?; 23 | let dotfiles = Dotfiles::new(Path::new(&dotdir).to_path_buf()); 24 | Ok(App { 25 | dotfiles: dotfiles, 26 | dry_run: dry_run, 27 | verbose: verbose, 28 | }) 29 | } 30 | 31 | pub fn command_clone(&self, query: &str) -> Result { 32 | let url = resolve_url(query)?; 33 | let dotdir = self.dotfiles.root_dir().to_string_lossy(); 34 | util::wait_exec("git", 35 | &["clone", url.as_str(), dotdir.borrow()], 36 | None, 37 | self.dry_run) 38 | .map_err(Into::into) 39 | } 40 | 41 | pub fn command_root(&self) -> Result { 42 | println!("{}", self.dotfiles.root_dir().display()); 43 | Ok(0) 44 | } 45 | 46 | pub fn command_check(&mut self) -> Result { 47 | self.dotfiles.read_entries(); 48 | 49 | let mut num_unhealth = 0; 50 | for entry in self.dotfiles.entries() { 51 | if entry.check(self.verbose).unwrap() == false { 52 | num_unhealth += 1; 53 | } 54 | } 55 | Ok(num_unhealth) 56 | } 57 | 58 | pub fn command_link(&mut self) -> Result { 59 | self.dotfiles.read_entries(); 60 | 61 | if !self.dry_run { 62 | check_symlink_privilege(); 63 | } 64 | 65 | for entry in self.dotfiles.entries() { 66 | entry.mklink(self.dry_run, self.verbose).unwrap(); 67 | } 68 | 69 | Ok(0) 70 | } 71 | 72 | pub fn command_clean(&mut self) -> Result { 73 | self.dotfiles.read_entries(); 74 | 75 | for entry in self.dotfiles.entries() { 76 | entry.unlink(self.dry_run, self.verbose).unwrap(); 77 | } 78 | 79 | Ok(0) 80 | } 81 | } 82 | 83 | 84 | #[cfg(windows)] 85 | fn check_symlink_privilege() { 86 | use windows::ElevationType; 87 | 88 | match windows::get_elevation_type().unwrap() { 89 | ElevationType::Default => { 90 | match windows::enable_privilege("SeCreateSymbolicLinkPrivilege") { 91 | Ok(_) => (), 92 | Err(err) => panic!("failed to enable SeCreateSymbolicLinkPrivilege: {}", err), 93 | } 94 | } 95 | ElevationType::Limited => { 96 | panic!("should be elevate as an Administrator."); 97 | } 98 | ElevationType::Full => (), 99 | } 100 | } 101 | 102 | #[cfg(not(windows))] 103 | #[inline] 104 | pub fn check_symlink_privilege() {} 105 | 106 | 107 | fn init_envs() -> Result { 108 | if env::var("HOME").is_err() { 109 | env::set_var("HOME", dirs::home_dir().unwrap()); 110 | } 111 | 112 | let dotdir = env::var("DOT_DIR") 113 | .or(util::expand_full("$HOME/.dotfiles")) 114 | .map_err(|_| "failed to determine dotdir".to_string())?; 115 | env::set_var("DOT_DIR", dotdir.as_str()); 116 | env::set_var("dotdir", dotdir.as_str()); 117 | 118 | Ok(dotdir) 119 | } 120 | 121 | fn resolve_url(s: &str) -> Result { 122 | let re_scheme = Regex::new(r"^([^:]+)://").unwrap(); 123 | let re_scplike = Regex::new(r"^((?:[^@]+@)?)([^:]+):/?(.+)$").unwrap(); 124 | 125 | if let Some(cap) = re_scheme.captures(s) { 126 | match cap.get(1).unwrap().as_str() { 127 | "http" | "https" | "ssh" | "git" | "file" => Url::parse(s).map_err(Into::into), 128 | scheme => Err(format!("'{}' is invalid scheme", scheme).into()), 129 | } 130 | 131 | } else if let Some(cap) = re_scplike.captures(s) { 132 | let username = cap.get(1) 133 | .and_then(|s| if s.as_str() != "" { 134 | Some(s.as_str()) 135 | } else { 136 | None 137 | }) 138 | .unwrap_or("git@"); 139 | let host = cap.get(2).unwrap().as_str(); 140 | let path = cap.get(3).unwrap().as_str(); 141 | 142 | Url::parse(&format!("ssh://{}{}/{}.git", username, host, path)).map_err(Into::into) 143 | 144 | } else { 145 | let username = s.splitn(2, "/") 146 | .next() 147 | .ok_or("'username' is unknown".to_owned())?; 148 | let reponame = s.splitn(2, "/").skip(1).next().unwrap_or("dotfiles"); 149 | Url::parse(&format!("https://github.com/{}/{}.git", username, reponame)).map_err(Into::into) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/dotfiles.rs: -------------------------------------------------------------------------------- 1 | use crate::entry::Entry; 2 | use crate::util; 3 | use std::path::{Path, PathBuf}; 4 | use toml; 5 | 6 | pub struct Dotfiles { 7 | _root_dir: PathBuf, 8 | _entries: Vec, 9 | } 10 | 11 | impl Dotfiles { 12 | pub fn new(root_dir: PathBuf) -> Dotfiles { 13 | Dotfiles { 14 | _root_dir: root_dir, 15 | _entries: Vec::new(), 16 | } 17 | } 18 | 19 | pub fn read_entries(&mut self) { 20 | self._entries = read_entries(self._root_dir.as_path()); 21 | } 22 | 23 | pub fn root_dir(&self) -> &Path { 24 | self._root_dir.as_path() 25 | } 26 | 27 | pub fn entries(&self) -> &[Entry] { 28 | self._entries.as_slice() 29 | } 30 | } 31 | 32 | fn read_entries(root_dir: &Path) -> Vec { 33 | let ref entries = util::read_toml(root_dir.join(".mappings")).unwrap(); 34 | 35 | let mut buf = Vec::new(); 36 | read_entries_from_key(&mut buf, entries, root_dir, "general"); 37 | read_entries_from_key(&mut buf, entries, root_dir, util::OS_NAME); 38 | 39 | buf 40 | } 41 | 42 | fn new_entry(root_dir: &Path, key: &str, val: &str) -> Entry { 43 | let src = util::expand_full(&format!("{}/{}", root_dir.display(), key)).unwrap(); 44 | 45 | let mut dst = util::expand_full(val).unwrap(); 46 | if Path::new(&dst).is_relative() { 47 | dst = util::expand_full(&format!("$HOME/{}", val)).unwrap(); 48 | } 49 | 50 | Entry::new(&src, &dst) 51 | } 52 | 53 | fn read_entries_from_key(buf: &mut Vec, entries: &toml::value::Table, root_dir: &Path, key: &str) { 54 | if let Some(entries_table) = entries.get(key).and_then(|value| value.as_table()) { 55 | for (ref key, ref val) in entries_table.iter() { 56 | if let Some(val) = val.as_str() { 57 | buf.push(new_entry(root_dir, key, val)); 58 | } 59 | if let Some(val) = val.as_array() { 60 | for v in val { 61 | if let Some(v) = v.as_str() { 62 | buf.push(new_entry(root_dir, key, v)); 63 | } 64 | } 65 | } 66 | } 67 | } 68 | } 69 | 70 | #[cfg(test)] 71 | mod tests { 72 | use super::{read_entries_from_key, Dotfiles}; 73 | use crate::util; 74 | use std::path::Path; 75 | 76 | #[test] 77 | fn smoke_test() { 78 | let root_dir = Path::new("tests/dotfiles").to_path_buf(); 79 | let mut dotfiles = Dotfiles::new(root_dir); 80 | assert_eq!(Path::new("tests/dotfiles"), dotfiles.root_dir()); 81 | dotfiles.read_entries(); 82 | } 83 | 84 | #[test] 85 | fn do_nothing_if_given_key_is_not_exist() { 86 | let root_dir = Path::new("tests/dotfiles").to_path_buf(); 87 | let entries = util::read_toml(root_dir.join(".mappings")).unwrap(); 88 | 89 | let mut buf = Vec::new(); 90 | read_entries_from_key(&mut buf, &entries, &root_dir, "hogehoge"); 91 | assert_eq!(buf.len(), 0); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/entry.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::fs; 3 | use std::path::{Path, PathBuf}; 4 | use ansi_term; 5 | use crate::util; 6 | 7 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 8 | pub enum EntryStatus { 9 | Healthy, 10 | LinkNotCreated, 11 | NotSymLink, 12 | WrongLinkPath, 13 | } 14 | 15 | 16 | #[derive(Debug, Clone)] 17 | pub struct Entry { 18 | src: PathBuf, 19 | dst: PathBuf, 20 | } 21 | 22 | impl Entry { 23 | pub fn new(src: &str, dst: &str) -> Entry { 24 | Entry { 25 | src: util::make_pathbuf(src), 26 | dst: util::make_pathbuf(dst), 27 | } 28 | } 29 | 30 | pub fn status(&self) -> Result { 31 | let status = if !self.dst.exists() { 32 | EntryStatus::LinkNotCreated 33 | } else if !util::is_symlink(&self.dst)? { 34 | EntryStatus::NotSymLink 35 | } else if self.src != self.dst.read_link()? { 36 | EntryStatus::WrongLinkPath 37 | } else { 38 | EntryStatus::Healthy 39 | }; 40 | 41 | Ok(status) 42 | } 43 | 44 | pub fn check(&self, verbose: bool) -> Result { 45 | let status = self.status()?; 46 | if status != EntryStatus::Healthy { 47 | println!("{} {} ({:?})", 48 | ansi_term::Style::new() 49 | .bold() 50 | .fg(ansi_term::Colour::Red) 51 | .paint("✘"), 52 | self.dst.display(), 53 | status); 54 | return Ok(false); 55 | } 56 | if verbose { 57 | println!("{} {}\n => {}", 58 | ansi_term::Style::new() 59 | .bold() 60 | .fg(ansi_term::Colour::Green) 61 | .paint("✓"), 62 | self.dst.display(), 63 | self.src.display()); 64 | } 65 | Ok(true) 66 | } 67 | 68 | pub fn mklink(&self, dry_run: bool, verbose: bool) -> Result<(), io::Error> { 69 | if !self.src.exists() || self.status()? == EntryStatus::Healthy { 70 | return Ok(()); // Do nothing. 71 | } 72 | 73 | if self.dst.exists() && !util::is_symlink(&self.dst)? { 74 | let origpath = orig_path(&self.dst); 75 | println!("file {} has already existed. It will be renamed to {}", 76 | self.dst.display(), 77 | origpath.display()); 78 | fs::rename(&self.dst, origpath)?; 79 | } 80 | 81 | if verbose { 82 | println!("{}\n => {}", self.dst.display(), self.src.display()); 83 | } 84 | util::make_link(&self.src, &self.dst, dry_run) 85 | } 86 | 87 | pub fn unlink(&self, dry_run: bool, verbose: bool) -> Result<(), io::Error> { 88 | if !self.dst.exists() || !util::is_symlink(&self.dst)? { 89 | return Ok(()); // do nothing 90 | } 91 | 92 | if verbose { 93 | println!("unlink {}", self.dst.display()); 94 | } 95 | util::remove_link(&self.dst, dry_run)?; 96 | 97 | let origpath = orig_path(&self.dst); 98 | if origpath.exists() { 99 | fs::rename(origpath, &self.dst)?; 100 | } 101 | 102 | Ok(()) 103 | } 104 | } 105 | 106 | 107 | fn orig_path>(path: P) -> PathBuf { 108 | let origpath = format!("{}.bk", path.as_ref().to_str().unwrap()); 109 | Path::new(&origpath).to_path_buf() 110 | } 111 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate ansi_term; 2 | extern crate shellexpand; 3 | extern crate toml; 4 | #[macro_use] 5 | extern crate error_chain; 6 | extern crate url; 7 | extern crate regex; 8 | 9 | #[cfg(windows)] 10 | extern crate winapi; 11 | #[cfg(windows)] 12 | extern crate advapi32; 13 | #[cfg(windows)] 14 | extern crate kernel32; 15 | 16 | pub mod app; 17 | mod dotfiles; 18 | mod entry; 19 | pub mod util; 20 | #[cfg(windows)] 21 | mod windows; 22 | 23 | mod errors { 24 | error_chain!{ 25 | foreign_links { 26 | Io(::std::io::Error); 27 | UrlParse(::url::ParseError); 28 | } 29 | } 30 | } 31 | pub use crate::errors::*; 32 | 33 | pub use crate::app::App; 34 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate dot; 2 | extern crate clap; 3 | 4 | use clap::{Arg, AppSettings, SubCommand}; 5 | use dot::App; 6 | 7 | pub fn main() { 8 | match run() { 9 | Ok(retcode) => std::process::exit(retcode), 10 | Err(err) => panic!("unknown error: {}", err), 11 | } 12 | } 13 | 14 | pub fn run() -> dot::Result { 15 | let matches = cli().get_matches(); 16 | let dry_run = matches.is_present("dry-run"); 17 | let verbose = matches.is_present("verbose"); 18 | 19 | let mut app = App::new(dry_run, verbose)?; 20 | 21 | match matches.subcommand() { 22 | ("check", _) => app.command_check(), 23 | ("link", _) => app.command_link(), 24 | ("clean", _) => app.command_clean(), 25 | ("root", _) => app.command_root(), 26 | 27 | ("clone", Some(args)) => { 28 | let url = args.value_of("url").unwrap(); 29 | app.command_clone(url) 30 | } 31 | 32 | ("init", Some(args)) => { 33 | let url = args.value_of("url").unwrap(); 34 | let ret = app.command_clone(url)?; 35 | if ret != 0 { 36 | return Ok(ret); 37 | } 38 | app.command_link() 39 | } 40 | 41 | ("completion", Some(args)) => { 42 | let shell = args.value_of("shell").unwrap(); 43 | cli().gen_completions_to(env!("CARGO_PKG_NAME"), 44 | shell.parse::().unwrap(), 45 | &mut std::io::stdout()); 46 | Ok(0) 47 | } 48 | 49 | (_, _) => unreachable!(), 50 | } 51 | } 52 | 53 | fn cli() -> clap::App<'static, 'static> { 54 | clap::App::new(env!("CARGO_PKG_NAME")) 55 | .about(env!("CARGO_PKG_DESCRIPTION")) 56 | .version(env!("CARGO_PKG_VERSION")) 57 | .author(env!("CARGO_PKG_AUTHORS")) 58 | .setting(AppSettings::VersionlessSubcommands) 59 | .setting(AppSettings::SubcommandRequiredElseHelp) 60 | .arg(Arg::with_name("verbose") 61 | .help("Use verbose output") 62 | .long("verbose") 63 | .short("v")) 64 | .arg(Arg::with_name("dry-run") 65 | .help("do not actually perform I/O operations") 66 | .long("dry-run") 67 | .short("n")) 68 | .subcommand(SubCommand::with_name("check").about("Check the files are correctly linked to the right places")) 69 | .subcommand(SubCommand::with_name("link").about("Create all of the symbolic links into home directory")) 70 | .subcommand(SubCommand::with_name("clean").about("Remote all of registered links from home directory")) 71 | .subcommand(SubCommand::with_name("root").about("Show the location of dotfiles repository and exit")) 72 | .subcommand(SubCommand::with_name("clone") 73 | .about("Clone dotfiles repository from remote") 74 | .arg(Arg::with_name("url") 75 | .help("URL of remote repository") 76 | .required(true) 77 | .takes_value(true))) 78 | .subcommand(SubCommand::with_name("init") 79 | .about("Clone dotfiles repository from remote & make links") 80 | .arg(Arg::with_name("url") 81 | .help("URL of remote repository") 82 | .required(true) 83 | .takes_value(true))) 84 | .subcommand(SubCommand::with_name("completion") 85 | .about("Generate completion scripts") 86 | .setting(AppSettings::ArgRequiredElseHelp) 87 | .arg(Arg::with_name("shell") 88 | .help("target shell") 89 | .required(true) 90 | .possible_values(&["bash", "fish", "zsh", "powershell"]))) 91 | } 92 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::io::{self, Read}; 3 | use std::fs::{self, File}; 4 | use std::process::{Command, Stdio}; 5 | use std::path::{Path, PathBuf, MAIN_SEPARATOR}; 6 | use shellexpand::{self, LookupError}; 7 | use toml; 8 | 9 | 10 | #[allow(dead_code)] 11 | pub fn wait_exec(cmd: &str, args: &[&str], curr_dir: Option<&Path>, dry_run: bool) -> Result { 12 | if dry_run { 13 | println!("{} {:?} (@ {:?})", cmd, args, curr_dir); 14 | return Ok(0); 15 | } 16 | 17 | let mut command = Command::new(cmd); 18 | command.args(args) 19 | .stdin(Stdio::inherit()) 20 | .stdout(Stdio::inherit()) 21 | .stderr(Stdio::inherit()); 22 | if let Some(curr_dir) = curr_dir { 23 | command.current_dir(curr_dir); 24 | } 25 | 26 | let mut child = command.spawn()?; 27 | child.wait() 28 | .and_then(|st| st.code().ok_or(io::Error::new(io::ErrorKind::Other, ""))) 29 | } 30 | 31 | 32 | pub fn expand_full(s: &str) -> Result> { 33 | shellexpand::full(s).map(|s| s.into_owned()) 34 | } 35 | 36 | 37 | #[cfg(windows)] 38 | fn symlink, Q: AsRef>(src: P, dst: Q) -> Result<(), io::Error> { 39 | use std::os::windows::fs; 40 | if src.as_ref().is_dir() { 41 | fs::symlink_dir(src, dst) 42 | } else { 43 | fs::symlink_file(src, dst) 44 | } 45 | } 46 | 47 | #[cfg(not(windows))] 48 | fn symlink, Q: AsRef>(src: P, dst: Q) -> Result<(), io::Error> { 49 | use std::os::unix::fs::symlink; 50 | symlink(src, dst) 51 | } 52 | 53 | pub fn make_link(src: P, dst: Q, dry_run: bool) -> Result<(), io::Error> 54 | where P: AsRef, 55 | Q: AsRef 56 | { 57 | if dry_run { 58 | println!("make_link({}, {})", 59 | src.as_ref().display(), 60 | dst.as_ref().display()); 61 | Ok(()) 62 | } else { 63 | fs::create_dir_all(dst.as_ref().parent().unwrap())?; 64 | symlink(src, dst) 65 | } 66 | } 67 | 68 | 69 | #[cfg(windows)] 70 | fn unlink>(dst: P) -> Result<(), io::Error> { 71 | if dst.as_ref().is_dir() { 72 | fs::remove_dir(dst) 73 | } else { 74 | fs::remove_file(dst) 75 | } 76 | } 77 | 78 | #[cfg(not(windows))] 79 | fn unlink>(dst: P) -> Result<(), io::Error> { 80 | fs::remove_file(dst) 81 | } 82 | 83 | pub fn remove_link>(dst: P, dry_run: bool) -> Result<(), io::Error> { 84 | if dry_run { 85 | println!("fs::remove_file {}", dst.as_ref().display()); 86 | Ok(()) 87 | } else { 88 | unlink(dst) 89 | } 90 | } 91 | 92 | 93 | pub fn read_toml>(path: P) -> Result { 94 | let mut file = File::open(path)?; 95 | 96 | let mut buf = Vec::new(); 97 | file.read_to_end(&mut buf)?; 98 | 99 | let content = String::from_utf8_lossy(&buf[..]).into_owned(); 100 | toml::de::from_str(&content).map_err(|_| { 101 | io::Error::new(io::ErrorKind::Other, 102 | "failed to parse configuration file as TOML") 103 | }) 104 | } 105 | 106 | 107 | #[cfg(target_os = "windows")] 108 | pub static OS_NAME: &'static str = "windows"; 109 | 110 | #[cfg(target_os = "macos")] 111 | pub static OS_NAME: &'static str = "darwin"; 112 | 113 | #[cfg(target_os = "linux")] 114 | pub static OS_NAME: &'static str = "linux"; 115 | 116 | #[cfg(target_os = "android")] 117 | pub static OS_NAME: &'static str = "linux"; 118 | 119 | #[cfg(target_os = "freebsd")] 120 | pub static OS_NAME: &'static str = "freebsd"; 121 | 122 | #[cfg(target_os = "openbsd")] 123 | pub static OS_NAME: &'static str = "openbsd"; 124 | 125 | // create an instance of PathBuf from string. 126 | pub fn make_pathbuf(path: &str) -> PathBuf { 127 | let path = path.replace("/", &format!("{}", MAIN_SEPARATOR)); 128 | Path::new(&path).to_path_buf() 129 | } 130 | 131 | pub fn is_symlink>(path: P) -> Result { 132 | let meta = path.as_ref().symlink_metadata()?; 133 | Ok(meta.file_type().is_symlink()) 134 | } 135 | -------------------------------------------------------------------------------- /src/windows.rs: -------------------------------------------------------------------------------- 1 | // 2 | 3 | #![allow(non_camel_case_types)] 4 | #![allow(non_snake_case)] 5 | #![allow(dead_code)] 6 | #![allow(improper_ctypes)] 7 | 8 | use std::ffi::CString; 9 | use std::mem; 10 | use std::os::raw::c_void; 11 | use std::ptr::{null, null_mut}; 12 | use winapi::LUID; 13 | use winapi::winerror::ERROR_SUCCESS; 14 | use winapi::winnt; 15 | use kernel32; 16 | use advapi32; 17 | 18 | 19 | type BYTE = u8; 20 | type BOOL = i32; 21 | type DWORD = u32; 22 | 23 | 24 | #[repr(C)] 25 | struct TOKEN_ELEVATION { 26 | TokenIsElevated: DWORD, 27 | } 28 | 29 | type TOKEN_ELEVATION_TYPE = u32; 30 | 31 | #[repr(C)] 32 | struct TOKEN_GROUPS { 33 | GroupCount: DWORD, 34 | Groups: [SID_AND_ATTRIBUTES; 0], 35 | } 36 | 37 | #[repr(C)] 38 | struct SID_AND_ATTRIBUTES { 39 | Sid: PSID, 40 | Attributes: DWORD, 41 | } 42 | 43 | #[repr(C)] 44 | struct SID_IDENTIFIER_AUTHORITY { 45 | Value: [BYTE; 6], 46 | } 47 | 48 | #[repr(C)] 49 | #[allow(improper_ctypes)] 50 | struct SID; 51 | 52 | type PSID = *mut SID; 53 | type PSID_IDENTIFIER_AUTHORITY = *mut SID_IDENTIFIER_AUTHORITY; 54 | 55 | #[allow(dead_code)] 56 | enum TOKEN_INFORMATION_CLASS { 57 | TokenUser = 1, 58 | TokenGroups, 59 | TokenPrivileges, 60 | TokenOwner, 61 | TokenPrimaryGroup, 62 | TokenDefaultDacl, 63 | TokenSource, 64 | TokenType, 65 | TokenImpersonationLevel, 66 | TokenStatistics, 67 | TokenRestrictedSids, 68 | TokenSessionId, 69 | TokenGroupsAndPrivileges, 70 | TokenSessionReference, 71 | TokenSandBoxInert, 72 | TokenAuditPolicy, 73 | TokenOrigin, 74 | TokenElevationType, 75 | TokenLinkedToken, 76 | TokenElevation, 77 | TokenHasRestrictions, 78 | TokenAccessInformation, 79 | TokenVirtualizationAllowed, 80 | TokenVirtualizationEnabled, 81 | TokenIntegrityLevel, 82 | TokenUIAccess, 83 | TokenMandatoryPolicy, 84 | TokenLogonSid, 85 | TokenIsAppContainer, 86 | TokenCapabilities, 87 | TokenAppContainerSid, 88 | TokenAppContainerNumber, 89 | TokenUserClaimAttributes, 90 | TokenDeviceClaimAttributes, 91 | TokenRestrictedUserClaimAttributes, 92 | TokenRestrictedDeviceClaimAttributes, 93 | TokenDeviceGroups, 94 | TokenRestrictedDeviceGroups, 95 | TokenSecurityAttributes, 96 | TokenIsRestricted, 97 | MaxTokenInfoClass, 98 | } 99 | 100 | extern "system" { 101 | fn GetTokenInformation(TokenHandle: winnt::HANDLE, 102 | TokenInformationClass: DWORD, 103 | TokenInformation: *mut c_void, 104 | TokenInformationLength: DWORD, 105 | ReturnLength: *mut DWORD) 106 | -> BOOL; 107 | 108 | fn IsUserAnAdmin() -> BOOL; 109 | 110 | fn AllocateAndInitializeSid(pIdentifierAuthority: PSID_IDENTIFIER_AUTHORITY, 111 | nSubAuthorityCount: BYTE, 112 | dwSubAuthority0: DWORD, 113 | dwSubAuthority1: DWORD, 114 | dwSubAuthority2: DWORD, 115 | dwSubAuthority3: DWORD, 116 | dwSubAuthority4: DWORD, 117 | dwSubAuthority5: DWORD, 118 | dwSubAuthority6: DWORD, 119 | dwSubAuthority7: DWORD, 120 | pSid: *mut PSID) 121 | -> BOOL; 122 | fn FreeSid(pSid: PSID) -> *mut c_void; 123 | 124 | fn CheckTokenMembership(TokenHandle: winnt::HANDLE, SidToCheck: PSID, IsMember: *mut BOOL) -> BOOL; 125 | } 126 | 127 | 128 | 129 | struct Handle(winnt::HANDLE); 130 | 131 | impl Handle { 132 | fn new(h: winnt::HANDLE) -> Handle { 133 | Handle(h) 134 | } 135 | 136 | fn as_raw(&self) -> winnt::HANDLE { 137 | self.0 138 | } 139 | } 140 | 141 | impl Drop for Handle { 142 | fn drop(&mut self) { 143 | unsafe { kernel32::CloseHandle(self.0) }; 144 | self.0 = null_mut(); 145 | } 146 | } 147 | 148 | 149 | struct Sid(PSID); 150 | 151 | impl Sid { 152 | fn as_raw(&self) -> PSID { 153 | self.0 154 | } 155 | } 156 | 157 | impl Drop for Sid { 158 | fn drop(&mut self) { 159 | unsafe { FreeSid(self.0) }; 160 | self.0 = null_mut(); 161 | } 162 | } 163 | 164 | 165 | pub fn enable_privilege(name: &str) -> Result<(), &'static str> { 166 | // 1. retrieve the process token of current process. 167 | let token = open_process_token(winnt::TOKEN_ADJUST_PRIVILEGES | winnt::TOKEN_QUERY)?; 168 | 169 | // 2. retrieve a LUID for given priviledge 170 | let luid = lookup_privilege_value(name)?; 171 | 172 | let len = mem::size_of::() + 1 * mem::size_of::(); 173 | let token_privileges = vec![0u8; len]; 174 | unsafe { 175 | let mut p = token_privileges.as_ptr() as *mut winnt::TOKEN_PRIVILEGES; 176 | let mut la = (*p).Privileges.as_ptr() as *mut winnt::LUID_AND_ATTRIBUTES; 177 | (*p).PrivilegeCount = 1; 178 | (*la).Luid = luid; 179 | (*la).Attributes = winnt::SE_PRIVILEGE_ENABLED; 180 | } 181 | 182 | unsafe { 183 | advapi32::AdjustTokenPrivileges(token.as_raw(), 184 | 0, 185 | token_privileges.as_ptr() as *mut winnt::TOKEN_PRIVILEGES, 186 | 0, 187 | null_mut(), 188 | null_mut()); 189 | } 190 | 191 | match unsafe { kernel32::GetLastError() } { 192 | ERROR_SUCCESS => Ok(()), 193 | _ => Err("failed to adjust token privilege"), 194 | } 195 | } 196 | 197 | pub fn is_elevated() -> Result { 198 | let token = open_process_token(winnt::TOKEN_QUERY)?; 199 | 200 | let mut elevation = TOKEN_ELEVATION { TokenIsElevated: 0 }; 201 | let mut cb_size: u32 = mem::size_of_val(&elevation) as u32; 202 | let ret = unsafe { 203 | GetTokenInformation(token.as_raw(), 204 | mem::transmute::<_, u8>(TOKEN_INFORMATION_CLASS::TokenElevation) as u32, 205 | mem::transmute(&mut elevation), 206 | mem::size_of_val(&elevation) as u32, 207 | &mut cb_size) 208 | }; 209 | if ret == 0 { 210 | return Err("failed to get token information"); 211 | } 212 | 213 | Ok(elevation.TokenIsElevated != 0) 214 | } 215 | 216 | #[derive(Debug, PartialEq)] 217 | pub enum ElevationType { 218 | Default = 1, 219 | Full, 220 | Limited, 221 | } 222 | 223 | pub fn get_elevation_type() -> Result { 224 | let token = open_process_token(winnt::TOKEN_QUERY)?; 225 | 226 | let mut elev_type = 0; 227 | let mut cb_size = mem::size_of_val(&elev_type) as u32; 228 | let ret = unsafe { 229 | GetTokenInformation(token.as_raw(), 230 | mem::transmute::<_, u8>(TOKEN_INFORMATION_CLASS::TokenElevationType) as u32, 231 | mem::transmute(&mut elev_type), 232 | mem::size_of_val(&elev_type) as u32, 233 | &mut cb_size) 234 | }; 235 | if ret == 0 { 236 | return Err("failed to get token information"); 237 | } 238 | 239 | match elev_type { 240 | 1 => Ok(ElevationType::Default), // default (standard user/ administrator without UAC) 241 | 2 => Ok(ElevationType::Full), // full access (administrator, not elevated) 242 | 3 => Ok(ElevationType::Limited), // limited access (administrator, not elevated) 243 | _ => Err("unknown elevation type"), 244 | } 245 | } 246 | 247 | fn open_process_token(token_type: u32) -> Result { 248 | let mut h_token = null_mut(); 249 | let ret = unsafe { advapi32::OpenProcessToken(kernel32::GetCurrentProcess(), token_type, &mut h_token) }; 250 | match ret { 251 | 0 => Err("failed to get process token"), 252 | _ => Ok(Handle::new(h_token)), 253 | } 254 | } 255 | 256 | fn lookup_privilege_value(name: &str) -> Result { 257 | let mut luid = LUID { 258 | LowPart: 0, 259 | HighPart: 0, 260 | }; 261 | let ret = unsafe { 262 | let name = CString::new(name).unwrap(); 263 | advapi32::LookupPrivilegeValueA(null(), name.as_ptr(), &mut luid) 264 | }; 265 | match ret { 266 | 0 => Err("failed to get the privilege value"), 267 | _ => Ok(luid), 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /templates/mappings-example.toml: -------------------------------------------------------------------------------- 1 | # vim: set ft=toml ts=2 sw=2 et : 2 | 3 | [general] 4 | "tmux.conf" = "~/.tmux.conf" 5 | gitconfig = "~/.gitconfig" 6 | tigrc = "~/.tigrc" 7 | zsh = "~/.config/zsh" 8 | "zsh/zshenv" = "~/.zshenv" 9 | "zsh/zshrc" = "~/.zshrc" 10 | vim = "~/.config/vim" 11 | "vim/vimrc" = "~/.vimrc" 12 | "vim/gvimrc" = "~/.gvimrc" 13 | 14 | [windows] 15 | atom = "~/.config/atom" 16 | mintty = "~/.config/mintty" 17 | "vscode/settings.json" = "$APPDATA/Code/User/settings.json" 18 | "vscode/locale.json" = "$APPDATA/Code/User/locale.json" 19 | powershell = "~/Documents/WindowsPowerShell" 20 | consolez = "$APPDATA/Console" 21 | "ConEmu.xml" = "$APPDATA/ConEmu.xml" 22 | 23 | [linux] 24 | neovim = "~/.config/nvim" 25 | xinitrc = "~/.xinitrc" 26 | termux = "~/.termux" 27 | "virtualenvwrapper/postactivate" = "~/.virtualenvs/postactivate" 28 | "virtualenvwrapper/postdeactivate" = "~/.virtualenvs/postdeactivate" 29 | -------------------------------------------------------------------------------- /tests/dotfiles/.mappings: -------------------------------------------------------------------------------- 1 | # vim: set ft=toml ts=2 sw=2 et : 2 | 3 | [general] 4 | "tmux.conf" = "~/.tmux.conf" 5 | gitconfig = ["~/.gitconfig", "~/.config/git/config"] 6 | tigrc = "~/.tigrc" 7 | zsh = "~/.config/zsh" 8 | "zsh/zshenv" = "~/.zshenv" 9 | "zsh/zshrc" = "~/.zshrc" 10 | vim = ["~/.vim", "~/.config/nvim"] 11 | "vim/vimrc" = "~/.vimrc" 12 | "vim/gvimrc" = "~/.gvimrc" 13 | 14 | [windows] 15 | atom = "~/.config/atom" 16 | mintty = "~/.config/mintty" 17 | "vscode/settings.json" = "$APPDATA/Code/User/settings.json" 18 | "vscode/locale.json" = "$APPDATA/Code/User/locale.json" 19 | powershell = "~/Documents/WindowsPowerShell" 20 | consolez = "$APPDATA/Console" 21 | "ConEmu.xml" = "$APPDATA/ConEmu.xml" 22 | 23 | [linux] 24 | neovim = "~/.config/nvim" 25 | xinitrc = "~/.xinitrc" 26 | termux = "~/.termux" 27 | "virtualenvwrapper/postactivate" = "~/.virtualenvs/postactivate" 28 | "virtualenvwrapper/postdeactivate" = "~/.virtualenvs/postdeactivate" 29 | --------------------------------------------------------------------------------