├── .github └── workflows │ └── rust.yml ├── .gitignore ├── CHANGELOG ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md └── src └── main.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Build 17 | run: cargo build --verbose 18 | - name: Run tests 19 | run: cargo test --verbose 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | .git_profiles -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | Version 0.x.x - 2020-??-?? 2 | ========================== 3 | 4 | * Enable continuous integration in GitHub repo 5 | 6 | 7 | Version 0.1.0 - 2020-03-16 8 | ========================== 9 | 10 | * Initial release 11 | 12 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "ansi_term" 5 | version = "0.11.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | dependencies = [ 8 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 9 | ] 10 | 11 | [[package]] 12 | name = "arrayref" 13 | version = "0.3.5" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | 16 | [[package]] 17 | name = "arrayvec" 18 | version = "0.4.12" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | dependencies = [ 21 | "nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", 22 | ] 23 | 24 | [[package]] 25 | name = "atty" 26 | version = "0.2.13" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | dependencies = [ 29 | "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", 30 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 31 | ] 32 | 33 | [[package]] 34 | name = "backtrace" 35 | version = "0.3.40" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | dependencies = [ 38 | "backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 39 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 40 | "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", 41 | "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 42 | ] 43 | 44 | [[package]] 45 | name = "backtrace-sys" 46 | version = "0.1.32" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | dependencies = [ 49 | "cc 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)", 50 | "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", 51 | ] 52 | 53 | [[package]] 54 | name = "base64" 55 | version = "0.10.1" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | dependencies = [ 58 | "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 59 | ] 60 | 61 | [[package]] 62 | name = "bitflags" 63 | version = "1.2.1" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | 66 | [[package]] 67 | name = "blake2b_simd" 68 | version = "0.5.8" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | dependencies = [ 71 | "arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 72 | "arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 73 | "constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 74 | ] 75 | 76 | [[package]] 77 | name = "byteorder" 78 | version = "1.3.2" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | 81 | [[package]] 82 | name = "cc" 83 | version = "1.0.46" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | 86 | [[package]] 87 | name = "cfg-if" 88 | version = "0.1.10" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | 91 | [[package]] 92 | name = "clap" 93 | version = "2.33.3" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | dependencies = [ 96 | "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 97 | "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 98 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 99 | "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 100 | "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 101 | "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 102 | "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 103 | ] 104 | 105 | [[package]] 106 | name = "cloudabi" 107 | version = "0.0.3" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | dependencies = [ 110 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 111 | ] 112 | 113 | [[package]] 114 | name = "constant_time_eq" 115 | version = "0.1.4" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | 118 | [[package]] 119 | name = "crossbeam-utils" 120 | version = "0.6.6" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | dependencies = [ 123 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 124 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 125 | ] 126 | 127 | [[package]] 128 | name = "dirs" 129 | version = "2.0.2" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | dependencies = [ 132 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 133 | "dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 134 | ] 135 | 136 | [[package]] 137 | name = "dirs-sys" 138 | version = "0.3.4" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | dependencies = [ 141 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 142 | "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", 143 | "redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 144 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 145 | ] 146 | 147 | [[package]] 148 | name = "failure" 149 | version = "0.1.6" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | dependencies = [ 152 | "backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)", 153 | "failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 154 | ] 155 | 156 | [[package]] 157 | name = "failure_derive" 158 | version = "0.1.6" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | dependencies = [ 161 | "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 162 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 163 | "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 164 | "synstructure 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", 165 | ] 166 | 167 | [[package]] 168 | name = "fnv" 169 | version = "1.0.6" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | 172 | [[package]] 173 | name = "fuchsia-cprng" 174 | version = "0.1.1" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | 177 | [[package]] 178 | name = "getopts" 179 | version = "0.2.21" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | dependencies = [ 182 | "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 183 | ] 184 | 185 | [[package]] 186 | name = "git-profile" 187 | version = "0.1.0" 188 | dependencies = [ 189 | "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", 190 | "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 191 | "ramhorns 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 192 | "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", 193 | "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", 194 | "toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", 195 | ] 196 | 197 | [[package]] 198 | name = "lazy_static" 199 | version = "1.4.0" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | 202 | [[package]] 203 | name = "libc" 204 | version = "0.2.65" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | 207 | [[package]] 208 | name = "memchr" 209 | version = "2.2.1" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | 212 | [[package]] 213 | name = "nodrop" 214 | version = "0.1.14" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | 217 | [[package]] 218 | name = "proc-macro2" 219 | version = "0.4.30" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | dependencies = [ 222 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 223 | ] 224 | 225 | [[package]] 226 | name = "proc-macro2" 227 | version = "1.0.6" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | dependencies = [ 230 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 231 | ] 232 | 233 | [[package]] 234 | name = "pulldown-cmark" 235 | version = "0.4.1" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | dependencies = [ 238 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 239 | "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", 240 | "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 241 | "unicase 2.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 242 | ] 243 | 244 | [[package]] 245 | name = "quote" 246 | version = "0.6.13" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | dependencies = [ 249 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 250 | ] 251 | 252 | [[package]] 253 | name = "quote" 254 | version = "1.0.2" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | dependencies = [ 257 | "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 258 | ] 259 | 260 | [[package]] 261 | name = "ramhorns" 262 | version = "0.5.0" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | dependencies = [ 265 | "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 266 | "pulldown-cmark 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 267 | "ramhorns-derive 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 268 | ] 269 | 270 | [[package]] 271 | name = "ramhorns-derive" 272 | version = "0.5.0" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | dependencies = [ 275 | "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 276 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 277 | "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", 278 | "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", 279 | ] 280 | 281 | [[package]] 282 | name = "rand_core" 283 | version = "0.3.1" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | dependencies = [ 286 | "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 287 | ] 288 | 289 | [[package]] 290 | name = "rand_core" 291 | version = "0.4.2" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | 294 | [[package]] 295 | name = "rand_os" 296 | version = "0.1.3" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | dependencies = [ 299 | "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 300 | "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 301 | "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", 302 | "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 303 | "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 304 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 305 | ] 306 | 307 | [[package]] 308 | name = "rdrand" 309 | version = "0.4.0" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | dependencies = [ 312 | "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 313 | ] 314 | 315 | [[package]] 316 | name = "redox_syscall" 317 | version = "0.1.56" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | 320 | [[package]] 321 | name = "redox_users" 322 | version = "0.3.1" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | dependencies = [ 325 | "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 326 | "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 327 | "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", 328 | "rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 329 | ] 330 | 331 | [[package]] 332 | name = "rust-argon2" 333 | version = "0.5.1" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | dependencies = [ 336 | "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", 337 | "blake2b_simd 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", 338 | "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", 339 | ] 340 | 341 | [[package]] 342 | name = "rustc-demangle" 343 | version = "0.1.16" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | 346 | [[package]] 347 | name = "serde" 348 | version = "1.0.101" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | 351 | [[package]] 352 | name = "serde_derive" 353 | version = "1.0.101" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | dependencies = [ 356 | "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 357 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 358 | "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 359 | ] 360 | 361 | [[package]] 362 | name = "strsim" 363 | version = "0.8.0" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | 366 | [[package]] 367 | name = "syn" 368 | version = "0.15.44" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | dependencies = [ 371 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 372 | "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", 373 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 374 | ] 375 | 376 | [[package]] 377 | name = "syn" 378 | version = "1.0.5" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | dependencies = [ 381 | "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 382 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 383 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 384 | ] 385 | 386 | [[package]] 387 | name = "synstructure" 388 | version = "0.12.1" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | dependencies = [ 391 | "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 392 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 393 | "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 394 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 395 | ] 396 | 397 | [[package]] 398 | name = "textwrap" 399 | version = "0.11.0" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | dependencies = [ 402 | "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 403 | ] 404 | 405 | [[package]] 406 | name = "toml" 407 | version = "0.5.3" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | dependencies = [ 410 | "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", 411 | ] 412 | 413 | [[package]] 414 | name = "unicase" 415 | version = "2.5.1" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | dependencies = [ 418 | "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 419 | ] 420 | 421 | [[package]] 422 | name = "unicode-width" 423 | version = "0.1.6" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | 426 | [[package]] 427 | name = "unicode-xid" 428 | version = "0.1.0" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | 431 | [[package]] 432 | name = "unicode-xid" 433 | version = "0.2.0" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | 436 | [[package]] 437 | name = "vec_map" 438 | version = "0.8.1" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | 441 | [[package]] 442 | name = "version_check" 443 | version = "0.1.5" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | 446 | [[package]] 447 | name = "winapi" 448 | version = "0.3.8" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | dependencies = [ 451 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 452 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 453 | ] 454 | 455 | [[package]] 456 | name = "winapi-i686-pc-windows-gnu" 457 | version = "0.4.0" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | 460 | [[package]] 461 | name = "winapi-x86_64-pc-windows-gnu" 462 | version = "0.4.0" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | 465 | [metadata] 466 | "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 467 | "checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" 468 | "checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" 469 | "checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" 470 | "checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" 471 | "checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" 472 | "checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" 473 | "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 474 | "checksum blake2b_simd 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)" = "5850aeee1552f495dd0250014cf64b82b7c8879a89d83b33bbdace2cc4f63182" 475 | "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" 476 | "checksum cc 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)" = "0213d356d3c4ea2c18c40b037c3be23cd639825c18f25ee670ac7813beeef99c" 477 | "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 478 | "checksum clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)" = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 479 | "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 480 | "checksum constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120" 481 | "checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" 482 | "checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" 483 | "checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" 484 | "checksum failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9" 485 | "checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" 486 | "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" 487 | "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 488 | "checksum getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)" = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" 489 | "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 490 | "checksum libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8" 491 | "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" 492 | "checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" 493 | "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" 494 | "checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" 495 | "checksum pulldown-cmark 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d1b74cc784b038a9921fd1a48310cc2e238101aa8ae0b94201e2d85121dd68b5" 496 | "checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" 497 | "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" 498 | "checksum ramhorns 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7525e62204b1d821d987e7cf07ef1a51b237495a1caad61001740008387f261c" 499 | "checksum ramhorns-derive 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "64b3a6039d79d457d97d977da3f72ea190527daac2fdd28b822df0346d666563" 500 | "checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 501 | "checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 502 | "checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" 503 | "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 504 | "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 505 | "checksum redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecedbca3bf205f8d8f5c2b44d83cd0690e39ee84b951ed649e9f1841132b66d" 506 | "checksum rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf" 507 | "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" 508 | "checksum serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "9796c9b7ba2ffe7a9ce53c2287dfc48080f4b2b362fcc245a259b3a7201119dd" 509 | "checksum serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e" 510 | "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 511 | "checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" 512 | "checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" 513 | "checksum synstructure 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f085a5855930c0441ca1288cf044ea4aecf4f43a91668abdb870b4ba546a203" 514 | "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 515 | "checksum toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7aabe75941d914b72bf3e5d3932ed92ce0664d49d8432305a8b547c37227724" 516 | "checksum unicase 2.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2e2e6bd1e59e56598518beb94fd6db628ded570326f0a98c679a304bd9f00150" 517 | "checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20" 518 | "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 519 | "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 520 | "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" 521 | "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" 522 | "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 523 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 524 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 525 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "git-profile" 3 | description = "Easy user profiles for git" 4 | version = "0.1.0" 5 | authors = ["David Futcher "] 6 | edition = "2018" 7 | license-file = "LICENSE" 8 | repository = "https://github.com/bobbo/git-profile" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | clap = "2.33.3" 14 | dirs = "2" 15 | ramhorns = "0.5" 16 | serde = "1.0" 17 | serde_derive = "1.0" 18 | toml = "0.5" 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 - 2020 David Futcher 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # git-profile 2 | 3 | *git-profile* is a simple user profile manager for *git*. It lets you set-up multiple user profiles for git & switch 4 | between them, generate remote URLs and more. If you ever have to manage multiple identities with git, *git-profile* can make 5 | your life easier. 6 | 7 | ## Install 8 | 9 | If you have Cargo installed, run `cargo install git-profile`. You can also grab a pre-built (Mac) binaries from the releases page. 10 | It should be installed on your path as `git-profile`. The `git-` prefix allows it to be called like any other git command. 11 | 12 | ## Usage 13 | 14 | ### Create a profile 15 | 16 | At a minimum you need a profile name (best to keep this quite short), your author name and author email. 17 | 18 | `git profile new github 'Full Name' 'open-source@personal.com'` 19 | 20 | Create a profile with a custom URL scheme: 21 | 22 | `git profile new github-work 'Full Name' 'name@work.com' --username CompanyName --remote 'git@github.com-work:{{username}}/{{project}}.git'` 23 | 24 | ### Switch profiles 25 | 26 | The `use` command switches you between profiles. This sets the profile for the repository in your current working directory. 27 | 28 | `git profile use github-work` 29 | 30 | `git profile use open-source` 31 | 32 | ### List profiles 33 | 34 | List all the profiles. An asterisk will appear next to the currently enabled profile. 35 | 36 | `git profile ls` 37 | 38 | ### Use with ssh config 39 | 40 | If your different profiles each have different ssh key-pairs set-up, 41 | you can configure git-profile to use them via shared ssh-hosts and usernames: 42 | 43 | `~/.ssh/config`: 44 | 45 | ```ini 46 | # WORK GITHUB SSH CONFIG 47 | Host work.github.com 48 | HostName github.com 49 | IdentityFile ~/.ssh/work_github_rsa 50 | PreferredAuthentications publickey 51 | IdentitiesOnly yes 52 | 53 | # OPEN SOURCE GITHUB SSH CONFIG 54 | Host oss.github.com 55 | Hostname github.com 56 | IdentityFile ~/.ssh/oss_github_rsa 57 | PreferredAuthentications publickey 58 | IdentitiesOnly yes 59 | ``` 60 | 61 | `~/.git_profiles`: 62 | 63 | ```toml 64 | [gh-work] 65 | author = 'Full Name (at work)' 66 | email = 'me@work.com' 67 | username = 'MyWorkGithubOrganization' 68 | url = 'git@work.github.com:{{username}}/{{project}}.git' 69 | 70 | [open-source] 71 | author = 'Full Name' 72 | email = 'my.open.source.contribs@example.com' 73 | username = 'my-oss-github-account' 74 | url = 'git@oss.github.com:{{username}}/{{project}}.git' 75 | ``` 76 | 77 | You can then `git clone ...` from using the correc key like ... 78 | 79 | ### Generate remote URL 80 | 81 | git-profile can be used to generate remote URLs for your repos. This can be helpful if you have a complicated SSH 82 | set-up that uses custom domains to use the right keys. Or just to save you having to navigate around GitHub Web 83 | and copy and paste remote URLs. 84 | 85 | Generate a URL for a given project name: 86 | 87 | `git profile url project-name` 88 | 89 | Use `-p ` to generate using a different profile. 90 | 91 | `git profile url -p github-work your-project` 92 | 93 | This is particularly handy when used in a sub-shell and combined with `git-remote`: 94 | 95 | `git remote add origin $(git profile url -p github-work my-work-project)` 96 | 97 | ### Generate author string 98 | 99 | `git profile author` => 'Full Name \' 100 | 101 | Can be used to easily fix commits when you've committed under the wrong profile: 102 | 103 | ```sh 104 | git commit -m "Committing with the wrong user" 105 | git profile use github-work 106 | git commit --amend --author $(git profile author) 107 | ``` 108 | 109 | ### Edit profiles 110 | 111 | `git profile edit` opens your `.git_profiles` in `$EDITOR`. Defaults to `vim` if you don't have `$EDITOR` set. 112 | 113 | ## Status 114 | 115 | *git-profile is in early development*. It's solves most of my major issues with using multiple identities with git, but it's by no means perfect. 116 | If you run into a bug or have a feature request, please open an issue. It should work fine on Mac & Linux, it probably won't work as-is on Windows. 117 | 118 | ## License 119 | 120 | MIT license, see `./LICENSE`. 121 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate clap; 3 | extern crate dirs; 4 | extern crate serde_derive; 5 | extern crate ramhorns; 6 | 7 | use std::collections::HashMap; 8 | use std::env; 9 | use std::error::Error; 10 | use std::fs; 11 | use std::io::prelude::*; 12 | use std::path::{Path, PathBuf}; 13 | use std::process::Command; 14 | use std::str::from_utf8; 15 | use std::vec::Vec; 16 | 17 | use clap::{App, Arg, SubCommand}; 18 | use ramhorns::{Template, Content}; 19 | use serde_derive::{Deserialize, Serialize}; 20 | use toml::Value; 21 | 22 | #[derive(Deserialize, Serialize, Debug, PartialEq)] 23 | struct Profile { 24 | #[serde(skip)] 25 | name: String, 26 | author: String, 27 | email: String, 28 | username: Option, 29 | url: Option, 30 | } 31 | 32 | #[derive(Content)] 33 | struct UrlRenderData { 34 | username: String, 35 | project: String, 36 | } 37 | 38 | impl Profile { 39 | 40 | fn new(profile_name: &str, author_name: &str, author_email: &str) -> Profile { 41 | Profile{ 42 | name: profile_name.to_owned(), 43 | author: author_name.to_owned(), 44 | email: author_email.to_owned(), 45 | username: None, 46 | url: None, 47 | } 48 | } 49 | 50 | fn with_remote_url(&mut self, remote: &str) -> &mut Profile { 51 | self.url = Some(remote.to_owned()); 52 | self 53 | } 54 | 55 | fn with_username(&mut self, user: &str) -> &mut Profile { 56 | self.username = Some(user.to_owned()); 57 | self 58 | } 59 | 60 | fn as_map(&self) -> HashMap { 61 | let mut map = HashMap::new(); 62 | 63 | map.insert("author".to_owned(), self.author.clone()); 64 | map.insert("email".to_owned(), self.email.clone()); 65 | 66 | if let Some(username) = &self.username { 67 | map.insert("username".to_owned(), username.clone()); 68 | } 69 | 70 | if let Some(url) = &self.url { 71 | map.insert("url".to_owned(), url.clone()); 72 | } 73 | 74 | map 75 | } 76 | 77 | fn render_data(&self, project: String) -> UrlRenderData { 78 | let username = match &self.username { 79 | Some(user) => user.to_owned(), 80 | _ => String::new() 81 | }; 82 | 83 | UrlRenderData{ 84 | project: project, 85 | username: username 86 | } 87 | } 88 | 89 | } 90 | 91 | struct GitProfilesApp<'a> { 92 | profiles: Option>, 93 | args: Option>, 94 | } 95 | 96 | impl GitProfilesApp<'_> { 97 | 98 | fn new<'a>() -> Result, std::io::Error> { 99 | let mut app = GitProfilesApp{ profiles: None, args: None }; 100 | app.parse_args(); 101 | 102 | let profiles = app.load_profiles()?; 103 | app.profiles = Some(profiles); 104 | Ok(app) 105 | } 106 | 107 | fn parse_args(&mut self) { 108 | let author = crate_authors!("\n").lines().next().unwrap(); 109 | 110 | self.args = Some( 111 | app_from_crate!() 112 | .author(author) 113 | .subcommand( 114 | SubCommand::with_name("new") 115 | .about("Create new profile") 116 | .arg(Arg::with_name("PROFILE") 117 | .help("Name of profile to create") 118 | .required(true)) 119 | .arg(Arg::with_name("AUTHOR") 120 | .required(true)) 121 | .arg(Arg::with_name("EMAIL") 122 | .required(true)) 123 | .arg(Arg::with_name("USERNAME") 124 | .short("u") 125 | .long("username") 126 | .takes_value(true)) 127 | .arg(Arg::with_name("URL") 128 | .short("r") 129 | .long("remote") 130 | .takes_value(true)) 131 | // TODO: Add --edit arg, opens file in editor _after_ writing new profile data 132 | ) 133 | .subcommand( 134 | App::new("list") 135 | .alias("ls") 136 | .about("List profiles")) 137 | .subcommand( 138 | SubCommand::with_name("use") 139 | .about("Switch profile") 140 | .arg(Arg::with_name("PROFILE") 141 | .help("Profile to operate on") 142 | .required(true) 143 | .takes_value(true)) 144 | // TODO: Add --global flag, operating on git config --global 145 | ) 146 | .subcommand( 147 | SubCommand::with_name("url") 148 | .about("Generate remote url") 149 | .arg(Arg::with_name("PROJECT") 150 | .help("Project name") 151 | .required(true)) 152 | .arg(Arg::with_name("PROFILE") 153 | .short("p") 154 | .long("profile") 155 | .takes_value(true) 156 | .help("Profile to use")) 157 | ) 158 | .subcommand( 159 | SubCommand::with_name("author") 160 | .about("Get profile's author string in git format") 161 | .arg(Arg::with_name("PROFILE") 162 | .short("p") 163 | .long("profile") 164 | .help("Profile to use") 165 | .takes_value(true)) 166 | ) 167 | .subcommand( 168 | SubCommand::with_name("edit") 169 | .about("Edit profiles") 170 | .arg(Arg::with_name("EDITOR") 171 | .long("editor") 172 | .takes_value(true)) 173 | ) 174 | .get_matches()); 175 | } 176 | 177 | fn profiles_file_path(&self) -> Option { 178 | match dirs::home_dir() { 179 | Some(mut path) => { 180 | path.push(".git_profiles"); 181 | Some(path) 182 | }, 183 | _ => None 184 | } 185 | } 186 | 187 | fn parse_profiles(&self, contents: String) -> Result, std::io::Error> { 188 | let data_tables = match toml::from_str(&contents)? { 189 | Value::Table(table) => table.into_iter().collect(), 190 | _ => HashMap::new(), 191 | }; 192 | 193 | let mut profiles = Vec::new(); 194 | 195 | for (key, value) in data_tables { 196 | let mut profile: Profile = value.try_into()?; 197 | profile.name = key; 198 | profiles.push(profile); 199 | } 200 | 201 | Ok(profiles) 202 | } 203 | 204 | fn load_profiles(&mut self) -> Result, std::io::Error> { 205 | let path_buf = self.profiles_file_path().expect("expected valid profile file-path"); 206 | let path = path_buf.as_path(); 207 | 208 | if Path::exists(path) { 209 | let mut file = fs::File::open(path)?; 210 | let mut contents = String::new(); 211 | 212 | file.read_to_string(&mut contents)?; 213 | return self.parse_profiles(contents); 214 | } 215 | 216 | Ok(vec![]) 217 | } 218 | 219 | fn save_profiles(&self, profiles: Vec<&Profile>) -> Result<(), Box> { 220 | let path_buf = self.profiles_file_path().expect("expected valid profile file-path"); 221 | let path = path_buf.as_path(); 222 | 223 | if !Path::exists(path) { 224 | fs::File::create(path)?; 225 | } 226 | 227 | let mut tables: HashMap = HashMap::new(); 228 | for profile in profiles { 229 | let key = profile.name.clone(); 230 | let table = toml::Value::try_from(profile.as_map())?; 231 | tables.insert(key, table); 232 | } 233 | 234 | let data = toml::to_string_pretty(&tables)?; 235 | fs::write(path, data.as_bytes())?; 236 | 237 | Ok(()) 238 | } 239 | 240 | fn get_profile(&self, profile_name: String) -> Option<&Profile> { 241 | if let Some(profiles) = &self.profiles { 242 | return profiles.iter().find(|p| p.name == profile_name) 243 | } 244 | 245 | None 246 | } 247 | 248 | fn get_profile_by_email(&self, email: String) -> Option<&Profile> { 249 | if let Some(profiles) = &self.profiles { 250 | return profiles.iter().find(|p| p.email == email); 251 | } 252 | 253 | None 254 | } 255 | 256 | fn get_profile_in_local_use(&self) -> Option<&Profile> { 257 | // TODO: Need to handle the case this is run in a non-git dir. Manually detect ./.git dir? 258 | let email = git_command(vec!["config", "user.email"]); 259 | if let Some(profile) = self.get_profile_by_email(email) { 260 | return Some(profile); 261 | } 262 | 263 | None 264 | } 265 | 266 | fn get_default_profile(&self) -> Option<&Profile> { 267 | if let Some(profile) = self.get_profile_in_local_use() { 268 | return Some(profile) 269 | } 270 | 271 | if let Some(profiles) = &self.profiles { 272 | if profiles.len() > 0 { 273 | return Some(&self.profiles.as_ref().unwrap()[0]); 274 | } 275 | } 276 | 277 | None 278 | } 279 | 280 | /// Unwraps the profile name, finds a matching profile (or falls back to a reasonable default) then executes the 281 | /// closure with the profile as it's argument. 282 | fn with_profile(&self, name: Option<&str>, f: F) 283 | where F: Fn(&Profile) -> () 284 | { 285 | let profile_opt = match name { 286 | Some(name) => self.get_profile(name.to_owned()), 287 | None => self.get_default_profile() 288 | }; 289 | 290 | match profile_opt { 291 | None => { 292 | println!("Couldn't find specified profile, or work out a default"); 293 | }, 294 | Some(profile) => { 295 | f(profile); 296 | } 297 | } 298 | } 299 | 300 | fn handle_list(&self) { 301 | let no_profiles = || println!("No profiles defined"); 302 | 303 | if let Some(profiles) = &self.profiles { 304 | if profiles.len() == 0 { 305 | no_profiles(); 306 | return 307 | } 308 | 309 | let local_profile = self.get_profile_in_local_use(); 310 | 311 | for profile in profiles { 312 | 313 | print!("{}", profile.name); 314 | 315 | if let Some(local) = local_profile { 316 | if local == profile { 317 | print!(" *"); 318 | } 319 | } 320 | 321 | print!("\n"); // TODO: Does this work cross-platform? 322 | } 323 | } else { 324 | no_profiles(); 325 | } 326 | } 327 | 328 | fn handle_use(&self, target: String) { 329 | // We never want to fallback to a default when dealing with 'use' cmd, so we don't use `with_profile`, instead 330 | // handle profile lookup manually 331 | let profile = self.get_profile(target).expect("Could not find target profile"); 332 | 333 | // TODO: These have results we should probably pay attention to 334 | git_command(vec!["config", "user.name", profile.author.as_ref()]); 335 | git_command(vec!["config", "user.email", profile.email.as_ref()]); 336 | } 337 | 338 | fn handle_url(&self, profile_name: Option<&str>, project_name: String) { 339 | self.with_profile(profile_name, |p| { 340 | let urlspec = match &p.url { 341 | Some(url) => url.as_ref(), 342 | None => "git@github.com:{{username}}/{{project}}" 343 | }; 344 | 345 | let template = Template::new(urlspec).expect("Failed to create template from urlspec"); 346 | println!("{}", template.render(&p.render_data(project_name.to_owned()))); 347 | }); 348 | } 349 | 350 | fn handle_author(&self, profile_name: Option<&str>) { 351 | self.with_profile(profile_name, |p| println!("{} <{}>", p.author, p.email)); 352 | } 353 | 354 | fn handle_new(&self, profile_name: &str, author_name: &str, author_email: &str, username: Option<&str>, 355 | remote: Option<&str>) 356 | { 357 | let mut profile = Profile::new(profile_name, author_name, author_email); 358 | 359 | if let Some(user) = username { 360 | profile.with_username(user); 361 | } 362 | 363 | if let Some(url) = remote { 364 | profile.with_remote_url(url); 365 | } 366 | 367 | let mut new_profiles = Vec::new(); 368 | new_profiles.extend(self.profiles.as_ref().unwrap()); 369 | new_profiles.push(&profile); 370 | 371 | let result = self.save_profiles(new_profiles); 372 | match result { 373 | Ok(_) => println!("Profile {} created", profile_name), 374 | Err(e) => println!("Profile create failed: {}", e) 375 | }; 376 | } 377 | 378 | fn handle_edit(&self, editor_opt: Option<&str>) { 379 | let editor: String; 380 | if let Some(val) = editor_opt { 381 | editor = val.to_owned(); 382 | } else if let Ok(val) = env::var("EDITOR") { 383 | editor = val; 384 | } else { 385 | // TODO: Better fallback value needed, won't work too nicely with Windows 386 | editor = "vim".to_owned(); 387 | } 388 | 389 | let path = self.profiles_file_path().expect("Failed to get profiles file path"); 390 | 391 | let result = Command::new(editor) 392 | .arg(path) 393 | .status() 394 | .expect("edit command failed"); 395 | 396 | if result.success() { 397 | println!("Edit success!"); 398 | } else { 399 | println!("Edit failed"); 400 | } 401 | } 402 | } 403 | 404 | fn git_command(args: Vec<&str>) -> String { 405 | let mut command = Command::new("git"); 406 | 407 | for arg in args { 408 | command.arg(arg); 409 | } 410 | 411 | let output_streams = command.output().expect("failed to execute process"); 412 | let output = from_utf8(&output_streams.stdout).unwrap().trim_end(); 413 | 414 | return output.to_owned(); 415 | } 416 | 417 | fn main() { 418 | let app = GitProfilesApp::new().expect("profile loading failed, check your profile config"); 419 | 420 | if let Some(args) = &app.args { 421 | match args.subcommand() { 422 | ("list", _) => app.handle_list(), 423 | ("new", Some(sub_matches)) => { 424 | let profile_name = sub_matches.value_of("PROFILE").expect("failed to parse profile name"); 425 | let author_name = sub_matches.value_of("AUTHOR").expect("failed to parse author name"); 426 | let author_email = sub_matches.value_of("EMAIL").expect("failed to parse author email"); 427 | let url = sub_matches.value_of("URL"); 428 | let username = sub_matches.value_of("USER"); 429 | 430 | app.handle_new(profile_name, author_name, author_email, username, url); 431 | }, 432 | ("use", Some(sub_matches)) => { 433 | let profile_name = sub_matches.value_of("PROFILE").expect("failed to parse profile name"); 434 | app.handle_use(profile_name.to_owned()); 435 | }, 436 | ("url", Some(sub_matches)) => { 437 | let project_name = sub_matches.value_of("PROJECT").expect("failed to parse project name"); 438 | let profile_name = sub_matches.value_of("PROFILE"); 439 | app.handle_url(profile_name, project_name.to_owned()); 440 | }, 441 | ("author", Some(sub_matches)) => { 442 | let profile_name = sub_matches.value_of("PROFILE"); 443 | app.handle_author(profile_name); 444 | }, 445 | ("edit", Some(sub_matches)) => { 446 | let editor = sub_matches.value_of("EDITOR"); 447 | app.handle_edit(editor); 448 | }, 449 | _ => println!("{}", args.usage()), // TODO: Should list sub-commands 450 | }; 451 | } 452 | } 453 | --------------------------------------------------------------------------------