chore: merge 0.0.6 #6 from cafkafk/dev
Of major notice is: - Categories - Repo flags - Architectural overhaul - Increased documentation - Improved UX - Changed config.yaml location - Increased scope of push field - Remove potentially destructive operation - Fixed mini-license typos - Fixed testing with hashmap arch - Spinner on all repoactions - Fixed commit in quick - [**breaking**] Fixed quick, fast messages - Fixed commit with editor regression - Architectural Overview - Moved charts to doc/img - Update image locations - Moved ARCHITECTURE.md to doc/ - Added some documentation - Added roadmap - Started flakification - Added nix flake #5 - [**breaking**] Add push field - [**breaking**] Add repo flags - [**breaking**] Implemented naive categories - Started work on using spinners - Added pull flag - React to exit code of git - Started adding multi instruction logic - Added fast subcommand - Version bump to v0.0.3 - Moved install scripts to ./bin - Fixed various clippy errors - Removed unused code from flake - Improved GitRepo assoc. function debug - Removed redundant line in Cargo.toml - Created on_all for config struct - Naive nested hashmap - Generic refactor - Removed atty dependency - Removed unused ./test dir - Mvp flake working <!-- generated by git-cliff --> Signed-off-by: Christina Sørensen <christina@cafkafk.com>
This commit is contained in:
commit
8476b343ec
18 changed files with 713 additions and 159 deletions
69
Cargo.lock
generated
69
Cargo.lock
generated
|
@ -118,7 +118,7 @@ dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -179,7 +179,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gg"
|
name = "gg"
|
||||||
version = "0.0.3"
|
version = "0.0.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"clap_mangen",
|
"clap_mangen",
|
||||||
|
@ -188,6 +188,7 @@ dependencies = [
|
||||||
"relative-path",
|
"relative-path",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
|
"spinners",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -253,6 +254,12 @@ version = "1.0.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
|
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.144"
|
version = "0.2.144"
|
||||||
|
@ -274,6 +281,12 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "maplit"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
|
@ -357,6 +370,12 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.13"
|
version = "1.0.13"
|
||||||
|
@ -380,7 +399,7 @@ checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -396,12 +415,56 @@ dependencies = [
|
||||||
"unsafe-libyaml",
|
"unsafe-libyaml",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spinners"
|
||||||
|
version = "4.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08615eea740067d9899969bc2891c68a19c315cb1f66640af9a9ecb91b13bcab"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"maplit",
|
||||||
|
"strum",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum"
|
||||||
|
version = "0.24.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
|
||||||
|
dependencies = [
|
||||||
|
"strum_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum_macros"
|
||||||
|
version = "0.24.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rustversion",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.109"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.18"
|
version = "2.0.18"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gg"
|
name = "gg"
|
||||||
version = "0.0.3"
|
version = "0.0.6"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["Christina Sørensen <christina@cafkafk.com>"]
|
authors = ["Christina Sørensen <christina@cafkafk.com>"]
|
||||||
repository = "https://github.com/cafkafk/gg"
|
repository = "https://github.com/cafkafk/gg"
|
||||||
|
@ -15,6 +15,7 @@ clap = { version = "4.0.22", features = ["derive"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
pretty_env_logger = "0.5.0"
|
pretty_env_logger = "0.5.0"
|
||||||
relative-path = "1.8.0"
|
relative-path = "1.8.0"
|
||||||
|
spinners = "4.1.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
clap = { version = "4.3.2", features = ["derive", "cargo", "env", "help"] }
|
clap = { version = "4.3.2", features = ["derive", "cargo", "env", "help"] }
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
cargo rustc --release -- -C target-cpu=native
|
cargo rustc --release -- -C target-cpu=native
|
||||||
cp ./target/release/gg ~/.local/bin
|
cargo install --path .
|
3
bin/install_debug
Executable file
3
bin/install_debug
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# cargo rustc
|
||||||
|
cargo install --debug --path .
|
103
doc/ARCHITECTURE.org
Normal file
103
doc/ARCHITECTURE.org
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
#+title: Architecture
|
||||||
|
|
||||||
|
** Architecture
|
||||||
|
*** Config datastructure
|
||||||
|
|
||||||
|
There were 3 major designs considered so far (here in chronological order).
|
||||||
|
|
||||||
|
**** Vec Based
|
||||||
|
Code sketch in https://github.com/cafkafk/gg/commit/3d3b6d6646bda84333018cd621cd8bd6348b9cef
|
||||||
|
|
||||||
|
#+begin_src mermaid :file ./doc/img/config-struct-vec.png :width 4000px
|
||||||
|
flowchart LR
|
||||||
|
Co[config]
|
||||||
|
Ca["categories (vec<category>)"]
|
||||||
|
L[links]
|
||||||
|
Co ==> Ca & L
|
||||||
|
Ca ----> c1(category 1) & c2(category 2) & c3(category 3)
|
||||||
|
subgraph Categories Vec
|
||||||
|
c1(category 1) ==> flags1 & repos1("repos (vec<GitRepo>)")
|
||||||
|
c2(category 2) ==> flags2 & repos2("repos (vec<GitRepo>)")
|
||||||
|
c3(category 3) ==> flags3 & repos3("repos (vec<GitRepo>)")
|
||||||
|
direction TB
|
||||||
|
subgraph GitRepos
|
||||||
|
repos1 --> gr1 & gr2 & gr3
|
||||||
|
repos2 --> gr4 & gr5 & gr6
|
||||||
|
repos3 --> gr7 & gr8 & gr9
|
||||||
|
end
|
||||||
|
direction TB
|
||||||
|
subgraph Flags Enum
|
||||||
|
flags1 & flags2 & flags3 -. any of .- Push & Clone
|
||||||
|
end
|
||||||
|
end
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
#+RESULTS:
|
||||||
|
[[file:./doc/img/config-struct-vec.png]]
|
||||||
|
|
||||||
|
**** BTreeMap Based (nested)
|
||||||
|
|
||||||
|
#+begin_src mermaid :file ./doc/img/config-struct-nested.png :width 4000px
|
||||||
|
flowchart LR
|
||||||
|
Co[config]
|
||||||
|
Ca["categories (BTreeMap)"]
|
||||||
|
L[links]
|
||||||
|
Co ==> Ca & L
|
||||||
|
Ca -- "unique_name/key" --> c1(category 1) & c2(category 2) & c3(category 3)
|
||||||
|
subgraph Categories BTreeMap
|
||||||
|
c1(category 1) ==> flags1 & repos1("repos (BTreeMap)")
|
||||||
|
c2(category 2) ==> flags2 & repos2("repos (BTreeMap)")
|
||||||
|
c3(category 3) ==> flags3 & repos3("repos (BTreeMap)")
|
||||||
|
direction TB
|
||||||
|
subgraph GitRepos
|
||||||
|
repos1 -- "unique_name/key" --> gr1 & gr2 & gr3
|
||||||
|
repos2 -- "unique_name/key" --> gr4 & gr5 & gr6
|
||||||
|
repos3 -- "unique_name/key" --> gr7 & gr8 & gr9
|
||||||
|
end
|
||||||
|
direction TB
|
||||||
|
subgraph Flags Enum
|
||||||
|
flags1 & flags2 & flags3 -. any of .- Push & Clone
|
||||||
|
end
|
||||||
|
end
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
|
||||||
|
#+RESULTS:
|
||||||
|
[[file:./doc/img/config-struct-nested.png]]
|
||||||
|
|
||||||
|
**** BTreeMap Based (Store)
|
||||||
|
|
||||||
|
#+begin_src mermaid :file ./doc/img/config-struct-store.png :width 4000px
|
||||||
|
flowchart LR
|
||||||
|
S[(Store)]
|
||||||
|
subgraph Repo Store BMapTree
|
||||||
|
S -- "unique_name/key" ----> gr1 & gr2 & gr3
|
||||||
|
S -- "unique_name/key" ----> gr4 & gr5 & gr6
|
||||||
|
S -- "unique_name/key" ----> gr7 & gr8 & gr9
|
||||||
|
end
|
||||||
|
Co[config]
|
||||||
|
Ca["categories (BTreeMap)"]
|
||||||
|
L[links]
|
||||||
|
Co ==> Ca & L
|
||||||
|
Ca -- "unique_name/key" --> c1(category 1) & c2(category 2) & c3(category 3)
|
||||||
|
subgraph Categories BTreeMap
|
||||||
|
c1(category 1) ==> flags1 & repos1("repos (vec<keys>)")
|
||||||
|
c2(category 2) ==> flags2 & repos2("repos (vec<keys>)")
|
||||||
|
c3(category 3) ==> flags3 & repos3("repos (vec<keys>)")
|
||||||
|
direction TB
|
||||||
|
subgraph GitRepos
|
||||||
|
repos1 <-. "unique_name/key" .-> gr1 & gr2 & gr3 & gr4 & gr5 & gr6 & gr7 & gr8 & gr9
|
||||||
|
repos2 <-. "unique_name/key" .-> gr1 & gr2 & gr3 & gr4 & gr5 & gr6 & gr7 & gr8 & gr9
|
||||||
|
repos3 <-. "unique_name/key" .-> gr1 & gr2 & gr3 & gr4 & gr5 & gr6 & gr7 & gr8 & gr9
|
||||||
|
end
|
||||||
|
direction TB
|
||||||
|
subgraph Flags Enum
|
||||||
|
flags1 & flags2 & flags3 -. any of .- Push & Clone
|
||||||
|
end
|
||||||
|
end
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
#+RESULTS:
|
||||||
|
[[file:./doc/img/config-struct-store.png]]
|
||||||
|
|
||||||
|
**** Discussion
|
BIN
doc/img/config-struct-nested.png
Normal file
BIN
doc/img/config-struct-nested.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 145 KiB |
BIN
doc/img/config-struct-store.png
Normal file
BIN
doc/img/config-struct-store.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 436 KiB |
BIN
doc/img/config-struct-vec.png
Normal file
BIN
doc/img/config-struct-vec.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 102 KiB |
15
doc/roadmap.org
Normal file
15
doc/roadmap.org
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#+title: Roadmap
|
||||||
|
|
||||||
|
* Roadmap [0%] [0/4]
|
||||||
|
- [ ] Custom operation sequences
|
||||||
|
- [ ] Generic repositories
|
||||||
|
- [ ] Version pinning
|
||||||
|
- [ ] libgit2 (maybe)
|
||||||
|
* 0.1.0 [20%] [1/5]
|
||||||
|
- [X] No functionality regressions
|
||||||
|
- [X] commit works in quick, fast
|
||||||
|
- [X] commit with edit works
|
||||||
|
- [ ] Repo Flags Finished
|
||||||
|
- [ ] Category Flags Finished
|
||||||
|
- [ ] Optional Fields
|
||||||
|
- [ ] Subcommands
|
|
@ -1,3 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
cargo rustc
|
|
||||||
cp ./target/debug/gg ~/.local/bin
|
|
|
@ -13,6 +13,8 @@
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see https://www.gnu.org/gpl-3.0.html.
|
// along with this program. If not, see https://www.gnu.org/gpl-3.0.html.
|
||||||
|
//
|
||||||
|
//! Handles command line input
|
||||||
|
|
||||||
use crate::utils::dir::home_dir;
|
use crate::utils::dir::home_dir;
|
||||||
use crate::utils::strings::INTERACTIVE_NOTICE;
|
use crate::utils::strings::INTERACTIVE_NOTICE;
|
||||||
|
@ -75,6 +77,10 @@ pub enum Commands {
|
||||||
#[command(visible_alias = "q")]
|
#[command(visible_alias = "q")]
|
||||||
Quick { msg: Option<String> },
|
Quick { msg: Option<String> },
|
||||||
|
|
||||||
|
/// Do fast pull-commit-push with msg for commit, skipping repo on failure
|
||||||
|
#[command(visible_alias = "f")]
|
||||||
|
Fast { msg: Option<String> },
|
||||||
|
|
||||||
/// Clone all repositories
|
/// Clone all repositories
|
||||||
#[command(visible_alias = "c")]
|
#[command(visible_alias = "c")]
|
||||||
Clone { msg: Option<String> },
|
Clone { msg: Option<String> },
|
||||||
|
|
372
src/git.rs
372
src/git.rs
|
@ -13,24 +13,60 @@
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see https://www.gnu.org/gpl-3.0.html.
|
// along with this program. If not, see https://www.gnu.org/gpl-3.0.html.
|
||||||
|
//
|
||||||
|
//! Git repositories
|
||||||
|
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use spinners::{Spinner, Spinners};
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::fs::canonicalize;
|
use std::fs::canonicalize;
|
||||||
use std::os::unix::fs::symlink;
|
use std::os::unix::fs::symlink;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::{fs, process::Command};
|
use std::{fs, process::Command};
|
||||||
|
|
||||||
|
/// An enum containing flags that change behaviour of repos and categories
|
||||||
|
#[derive(PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize, Debug)]
|
||||||
|
pub enum RepoFlags {
|
||||||
|
/// If push is set, the repository should respond to the push subcommand
|
||||||
|
Push,
|
||||||
|
/// If clone is set, the repository should respond to the clone subcommand
|
||||||
|
Clone,
|
||||||
|
/// If pull is set, the repository should respond to the pull subcommand
|
||||||
|
Pull,
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents the config.toml file.
|
/// Represents the config.toml file.
|
||||||
|
///
|
||||||
|
/// For diagrams of the underlying architecture, consult ARCHITECHTURE.md
|
||||||
|
///
|
||||||
|
///
|
||||||
#[derive(PartialEq, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Debug, Serialize, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub repos: Vec<GitRepo>,
|
/// map of all categories
|
||||||
|
///
|
||||||
|
/// Key should conceptually be seen as the name of the category.
|
||||||
|
pub categories: HashMap<String, Category>,
|
||||||
|
/// A vector containing links
|
||||||
pub links: Vec<Links>,
|
pub links: Vec<Links>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a category of repositories
|
||||||
|
///
|
||||||
|
/// This allows you to organize your repositories into categories
|
||||||
|
#[derive(PartialEq, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Category {
|
||||||
|
pub flags: Vec<RepoFlags>, // FIXME: not implemented
|
||||||
|
/// map of all categories
|
||||||
|
///
|
||||||
|
/// Key should conceptually be seen as the name of the category.
|
||||||
|
pub repos: HashMap<String, GitRepo>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Contain fields for a single link.
|
/// Contain fields for a single link.
|
||||||
#[derive(PartialEq, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Debug, Serialize, Deserialize)]
|
||||||
pub struct Links {
|
pub struct Links {
|
||||||
|
/// The name of the link
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub rx: String,
|
pub rx: String,
|
||||||
pub tx: String,
|
pub tx: String,
|
||||||
|
@ -42,9 +78,25 @@ pub struct GitRepo {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub path: String,
|
pub path: String,
|
||||||
pub url: String,
|
pub url: String,
|
||||||
pub clone: bool,
|
pub flags: Vec<RepoFlags>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////
|
||||||
|
////////////////////////////////////
|
||||||
|
////////////////////////////////////
|
||||||
|
|
||||||
|
/// Represents a single operation on a repository
|
||||||
|
struct SeriesItem<'series> {
|
||||||
|
/// The string to be displayed to the user
|
||||||
|
operation: &'series str,
|
||||||
|
/// The closure representing the actual operation
|
||||||
|
closure: Box<dyn Fn(&GitRepo) -> (bool)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////
|
||||||
|
////////////////////////////////////
|
||||||
|
////////////////////////////////////
|
||||||
|
|
||||||
fn handle_file_exists(selff: &Links, tx_path: &Path, rx_path: &Path) {
|
fn handle_file_exists(selff: &Links, tx_path: &Path, rx_path: &Path) {
|
||||||
match rx_path.read_link() {
|
match rx_path.read_link() {
|
||||||
Ok(file) if file.canonicalize().unwrap() == tx_path.canonicalize().unwrap() => {
|
Ok(file) if file.canonicalize().unwrap() == tx_path.canonicalize().unwrap() => {
|
||||||
|
@ -66,8 +118,8 @@ fn handle_file_exists(selff: &Links, tx_path: &Path, rx_path: &Path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Links {
|
impl Links {
|
||||||
/// Creates a link from a file
|
/// Creates the link from the link struct
|
||||||
fn link(&self) {
|
pub fn link(&self) {
|
||||||
let tx_path: &Path = std::path::Path::new(&self.tx);
|
let tx_path: &Path = std::path::Path::new(&self.tx);
|
||||||
let rx_path: &Path = std::path::Path::new(&self.rx);
|
let rx_path: &Path = std::path::Path::new(&self.rx);
|
||||||
match rx_path.try_exists() {
|
match rx_path.try_exists() {
|
||||||
|
@ -90,81 +142,115 @@ impl Links {
|
||||||
|
|
||||||
impl GitRepo {
|
impl GitRepo {
|
||||||
/// Clones the repository to its specified folder.
|
/// Clones the repository to its specified folder.
|
||||||
fn clone(&self) {
|
fn clone(&self) -> bool {
|
||||||
if self.clone {
|
if self.flags.contains(&RepoFlags::Clone) {
|
||||||
// TODO: check if &self.name already exists in dir
|
// TODO: check if &self.name already exists in dir
|
||||||
let out = Command::new("git")
|
let output = Command::new("git")
|
||||||
.current_dir(&self.path)
|
.current_dir(&self.path)
|
||||||
.arg("clone")
|
.arg("clone")
|
||||||
.arg(&self.url)
|
.arg(&self.url)
|
||||||
.arg(&self.name)
|
.arg(&self.name)
|
||||||
.status()
|
.output()
|
||||||
.unwrap_or_else(|_| panic!("git repo failed to clone: {:?}", &self,));
|
.unwrap_or_else(|_| panic!("git repo failed to clone: {:?}", &self,));
|
||||||
info!("{out}");
|
output.status.success()
|
||||||
} else {
|
} else {
|
||||||
info!("{} has clone set to false, not cloned", &self.name);
|
info!("{} has clone set to false, not cloned", &self.name);
|
||||||
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Pulls the repository if able.
|
/// Pulls the repository if able.
|
||||||
fn pull(&self) {
|
fn pull(&self) -> bool {
|
||||||
let out = Command::new("git")
|
if self.flags.contains(&RepoFlags::Pull) {
|
||||||
|
let output = Command::new("git")
|
||||||
.current_dir(format!("{}{}", &self.path, &self.name))
|
.current_dir(format!("{}{}", &self.path, &self.name))
|
||||||
.arg("pull")
|
.arg("pull")
|
||||||
.status()
|
.output()
|
||||||
.unwrap_or_else(|_| panic!("git repo failed to pull: {:?}", &self,));
|
.unwrap_or_else(|_| panic!("git repo failed to pull: {:?}", &self,));
|
||||||
info!("{out}");
|
output.status.success()
|
||||||
|
} else {
|
||||||
|
info!("{} has clone set to false, not pulled", &self.name);
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/// Adds all files in the repository.
|
/// Adds all files in the repository.
|
||||||
fn add_all(&self) {
|
fn add_all(&self) -> bool {
|
||||||
let out = Command::new("git")
|
if self.flags.contains(&RepoFlags::Push) {
|
||||||
|
let output = Command::new("git")
|
||||||
.current_dir(format!("{}{}", &self.path, &self.name))
|
.current_dir(format!("{}{}", &self.path, &self.name))
|
||||||
.arg("add")
|
.arg("add")
|
||||||
.arg(".")
|
.arg(".")
|
||||||
.status()
|
.output()
|
||||||
.unwrap_or_else(|_| panic!("git repo failed to add: {:?}", &self,));
|
.unwrap_or_else(|_| panic!("git repo failed to add: {:?}", &self,));
|
||||||
info!("{out}");
|
output.status.success()
|
||||||
|
} else {
|
||||||
|
info!("{} has clone set to false, not cloned", &self.name);
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/// Tries to commit changes in the repository.
|
/// Tries to commit changes in the repository.
|
||||||
|
///
|
||||||
|
/// # Development
|
||||||
|
///
|
||||||
|
/// - FIXME: this prints extra information to terminal this is because we
|
||||||
|
/// use status() instead of output(), as that makes using the native editor
|
||||||
|
/// easy
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn commit(&self) {
|
fn commit(&self) -> bool {
|
||||||
let out = Command::new("git")
|
if self.flags.contains(&RepoFlags::Push) {
|
||||||
|
let status = Command::new("git")
|
||||||
.current_dir(format!("{}{}", &self.path, &self.name))
|
.current_dir(format!("{}{}", &self.path, &self.name))
|
||||||
.arg("commit")
|
.arg("commit")
|
||||||
.status()
|
.status()
|
||||||
.unwrap_or_else(|_| panic!("git repo failed to commit: {:?}", &self,));
|
.unwrap_or_else(|_| panic!("git repo failed to commit: {:?}", &self,));
|
||||||
info!("{out}");
|
status.success()
|
||||||
|
} else {
|
||||||
|
info!("{} has push set to false, not cloned", &self.name);
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/// Tries to commit changes with a message argument.
|
/// Tries to commit changes with a message argument.
|
||||||
fn commit_with_msg(&self, msg: &String) {
|
fn commit_with_msg(&self, msg: &str) -> bool {
|
||||||
let out = Command::new("git")
|
if self.flags.contains(&RepoFlags::Push) {
|
||||||
|
let output = Command::new("git")
|
||||||
.current_dir(format!("{}{}", &self.path, &self.name))
|
.current_dir(format!("{}{}", &self.path, &self.name))
|
||||||
.arg("commit")
|
.arg("commit")
|
||||||
.arg("-m")
|
.arg("-m")
|
||||||
.arg(msg)
|
.arg(msg)
|
||||||
.status()
|
.output()
|
||||||
.unwrap_or_else(|_| panic!("git repo failed to commit: {:?}", &self,));
|
.unwrap_or_else(|_| panic!("git repo failed to commit: {:?}", &self,));
|
||||||
info!("{out}");
|
output.status.success()
|
||||||
|
} else {
|
||||||
|
info!("{} has clone set to false, not cloned", &self.name);
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/// Attempts to push the repository.
|
/// Attempts to push the repository.
|
||||||
fn push(&self) {
|
fn push(&self) -> bool {
|
||||||
let out = Command::new("git")
|
if self.flags.contains(&RepoFlags::Push) {
|
||||||
|
let output = Command::new("git")
|
||||||
.current_dir(format!("{}{}", &self.path, &self.name))
|
.current_dir(format!("{}{}", &self.path, &self.name))
|
||||||
.arg("push")
|
.arg("push")
|
||||||
.status()
|
.output()
|
||||||
.unwrap_or_else(|_| panic!("git repo failed to push: {:?}", &self,));
|
.unwrap_or_else(|_| panic!("git repo failed to push: {:?}", &self,));
|
||||||
info!("{out}");
|
output.status.success()
|
||||||
|
} else {
|
||||||
|
info!("{} has clone set to false, not cloned", &self.name);
|
||||||
|
false
|
||||||
}
|
}
|
||||||
/// Removes repository
|
}
|
||||||
|
/// Removes a repository (not implemented)
|
||||||
|
///
|
||||||
|
/// Kept here as a reminder that we probably shouldn't do this
|
||||||
fn remove(&self) -> Result<(), std::io::Error> {
|
fn remove(&self) -> Result<(), std::io::Error> {
|
||||||
// https://doc.rust-lang.org/std/fs/fn.remove_dir_all.html
|
// https://doc.rust-lang.org/std/fs/fn.remove_dir_all.html
|
||||||
unimplemented!("This seems to easy to missuse/exploit");
|
unimplemented!("This seems to easy to missuse/exploit");
|
||||||
fs::remove_dir_all(format!("{}{}", &self.path, &self.name))
|
// fs::remove_dir_all(format!("{}{}", &self.path, &self.name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
/* GIT RELATED */
|
/* GIT RELATED */
|
||||||
/// Reads the configuration toml from a path.
|
/// Loads the configuration toml from a path in to the Config struct.
|
||||||
pub fn new(path: &String) -> Self {
|
pub fn new(path: &String) -> Self {
|
||||||
debug!("initializing new Config struct");
|
debug!("initializing new Config struct");
|
||||||
let yaml = fs::read_to_string(path).unwrap_or_else(|_| {
|
let yaml = fs::read_to_string(path).unwrap_or_else(|_| {
|
||||||
|
@ -178,54 +264,230 @@ impl Config {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
|
/// Runs associated function on all repos in config
|
||||||
|
///
|
||||||
|
/// TODO: need to be made over a generic repo type
|
||||||
|
///
|
||||||
|
/// NOTE: currently unused
|
||||||
|
///
|
||||||
|
fn on_all<F>(&self, f: F)
|
||||||
|
where
|
||||||
|
F: Fn(&GitRepo),
|
||||||
|
{
|
||||||
|
for (_, category) in self.categories.iter() {
|
||||||
|
for (_, repo) in category.repos.iter() {
|
||||||
|
f(repo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Runs associated function on all repos in config
|
||||||
|
///
|
||||||
|
/// TODO: need to be made over a generic repo type
|
||||||
|
///
|
||||||
|
///
|
||||||
|
fn on_all_spinner<F>(&self, op: &str, f: F)
|
||||||
|
where
|
||||||
|
F: Fn(&GitRepo) -> bool,
|
||||||
|
{
|
||||||
|
for (_, category) in self.categories.iter() {
|
||||||
|
for (_, repo) in category.repos.iter() {
|
||||||
|
let mut sp =
|
||||||
|
Spinner::new(Spinners::Dots10, format!("{}: {}", repo.name, op).into());
|
||||||
|
if f(repo) {
|
||||||
|
sp.stop_and_persist("✔", format!("{}: {}", repo.name, op).into());
|
||||||
|
} else {
|
||||||
|
sp.stop_and_persist("❎", format!("{}: {}", repo.name, op).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Runs associated function on all repos in config
|
||||||
|
///
|
||||||
|
/// TODO: need to be made over a generic repo type
|
||||||
|
///
|
||||||
|
/// # Current Problem
|
||||||
|
///
|
||||||
|
/// The goal of this function is that it should run some function on all
|
||||||
|
/// repos but stop executing further functions on any repo that fails,
|
||||||
|
/// without blocking the repos that don't have an issue.
|
||||||
|
///
|
||||||
|
/// This is actually somewhat hairy to do, at least at 6:16 am :S
|
||||||
|
///
|
||||||
|
/// However, at 6:24, we're so ready! Let's go!
|
||||||
|
///
|
||||||
|
/// Fun fact: only the last element of a tuple must have a dynamically typed size
|
||||||
|
///
|
||||||
|
/// # Usage
|
||||||
|
///
|
||||||
|
/// Here is an example of how an associated method could use this function.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let series: Vec<SeriesItem> = vec![
|
||||||
|
/// SeriesItem {
|
||||||
|
/// operation: "pull",
|
||||||
|
/// closure: Box::new(move |repo: &GitRepo| repo.pull()),
|
||||||
|
/// },
|
||||||
|
/// SeriesItem {
|
||||||
|
/// operation: "add",
|
||||||
|
/// closure: Box::new(move |repo: &GitRepo| repo.add_all()),
|
||||||
|
/// },
|
||||||
|
/// SeriesItem {
|
||||||
|
/// operation: "commit",
|
||||||
|
/// closure: Box::new(move |repo: &GitRepo| repo.commit()),
|
||||||
|
/// },
|
||||||
|
/// SeriesItem {
|
||||||
|
/// operation: "push",
|
||||||
|
/// closure: Box::new(move |repo: &GitRepo| repo.push()),
|
||||||
|
/// },
|
||||||
|
/// ];
|
||||||
|
/// self.series_on_all(series);
|
||||||
|
/// ```
|
||||||
|
pub fn series_on_all(&self, closures: Vec<SeriesItem>) {
|
||||||
|
for (_, category) in self.categories.iter() {
|
||||||
|
for (_, repo) in category.repos.iter() {
|
||||||
|
for instruction in closures.iter() {
|
||||||
|
let f = &instruction.closure;
|
||||||
|
let op = instruction.operation;
|
||||||
|
let mut sp =
|
||||||
|
Spinner::new(Spinners::Dots10, format!("{}: {}", repo.name, op).into());
|
||||||
|
if f(repo) {
|
||||||
|
sp.stop_and_persist("✔", format!("{}: {}", repo.name, op).into());
|
||||||
|
} else {
|
||||||
|
sp.stop_and_persist("❎", format!("{}: {}", repo.name, op).into());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Runs associated function on all repos in config
|
||||||
|
///
|
||||||
|
/// Unlike `series_on_all`, this does not stop if it encounters an error
|
||||||
|
///
|
||||||
|
/// # Usage
|
||||||
|
///
|
||||||
|
/// Here is an example of how an associated method could use this function.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let series: Vec<SeriesItem> = vec![
|
||||||
|
/// SeriesItem {
|
||||||
|
/// operation: "pull",
|
||||||
|
/// closure: Box::new(move |repo: &GitRepo| repo.pull()),
|
||||||
|
/// },
|
||||||
|
/// SeriesItem {
|
||||||
|
/// operation: "add",
|
||||||
|
/// closure: Box::new(move |repo: &GitRepo| repo.add_all()),
|
||||||
|
/// },
|
||||||
|
/// SeriesItem {
|
||||||
|
/// operation: "commit",
|
||||||
|
/// closure: Box::new(move |repo: &GitRepo| repo.commit()),
|
||||||
|
/// },
|
||||||
|
/// SeriesItem {
|
||||||
|
/// operation: "push",
|
||||||
|
/// closure: Box::new(move |repo: &GitRepo| repo.push()),
|
||||||
|
/// },
|
||||||
|
/// ];
|
||||||
|
/// self.all_on_all(series);
|
||||||
|
/// ```
|
||||||
|
pub fn all_on_all(&self, closures: Vec<SeriesItem>) {
|
||||||
|
for (_, category) in self.categories.iter() {
|
||||||
|
for (_, repo) in category.repos.iter() {
|
||||||
|
for instruction in closures.iter() {
|
||||||
|
let f = &instruction.closure;
|
||||||
|
let op = instruction.operation;
|
||||||
|
let mut sp =
|
||||||
|
Spinner::new(Spinners::Dots10, format!("{}: {}", repo.name, op).into());
|
||||||
|
if f(repo) {
|
||||||
|
sp.stop_and_persist("✔", format!("{}: {}", repo.name, op).into());
|
||||||
|
} else {
|
||||||
|
sp.stop_and_persist("❎", format!("{}: {}", repo.name, op).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
/// Tries to pull all repositories, skips if fail.
|
/// Tries to pull all repositories, skips if fail.
|
||||||
pub fn pull_all(&self) {
|
pub fn pull_all(&self) {
|
||||||
debug!("exectuting pull_all");
|
debug!("exectuting pull_all");
|
||||||
for r in self.repos.iter() {
|
self.on_all_spinner("pull", |repo| repo.pull());
|
||||||
r.pull();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/// Tries to clone all repositories, skips if fail.
|
/// Tries to clone all repositories, skips if fail.
|
||||||
pub fn clone_all(&self) {
|
pub fn clone_all(&self) {
|
||||||
debug!("exectuting clone_all");
|
debug!("exectuting clone_all");
|
||||||
for r in self.repos.iter() {
|
self.on_all_spinner("clone", |repo| repo.clone());
|
||||||
r.clone();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/// Tries to add all work in all repositories, skips if fail.
|
/// Tries to add all work in all repositories, skips if fail.
|
||||||
pub fn add_all(&self) {
|
pub fn add_all(&self) {
|
||||||
debug!("exectuting clone_all");
|
debug!("exectuting clone_all");
|
||||||
for r in self.repos.iter() {
|
self.on_all_spinner("add", |repo| repo.add_all());
|
||||||
r.add_all();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/// Tries to commit all repositories one at a time, skips if fail.
|
/// Tries to commit all repositories one at a time, skips if fail.
|
||||||
pub fn commit_all(&self) {
|
pub fn commit_all(&self) {
|
||||||
debug!("exectuting clone_all");
|
debug!("exectuting clone_all");
|
||||||
for r in self.repos.iter() {
|
self.on_all_spinner("commit", |repo| repo.commit());
|
||||||
r.commit();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/// Tries to commit all repositories with msg, skips if fail.
|
/// Tries to commit all repositories with msg, skips if fail.
|
||||||
pub fn commit_all_msg(&self, msg: &String) {
|
pub fn commit_all_msg(&self, msg: &str) {
|
||||||
debug!("exectuting clone_all");
|
debug!("exectuting clone_all");
|
||||||
for r in self.repos.iter() {
|
self.on_all_spinner("commit", |repo| repo.commit_with_msg(msg));
|
||||||
r.commit_with_msg(msg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/// Tries to pull, add all, commit with msg "quick commit", and push all
|
/// Tries to pull, add all, commit with msg "quick commit", and push all
|
||||||
/// repositories, skips if fail.
|
/// repositories, skips if fail.
|
||||||
pub fn quick(&self, msg: &String) {
|
pub fn quick(&self, msg: &'static str) {
|
||||||
debug!("exectuting quick");
|
debug!("exectuting quick");
|
||||||
for r in self.repos.iter() {
|
let series: Vec<SeriesItem> = vec![
|
||||||
r.pull();
|
SeriesItem {
|
||||||
r.add_all();
|
operation: "pull",
|
||||||
r.commit_with_msg(msg);
|
closure: Box::new(move |repo: &GitRepo| repo.pull()),
|
||||||
r.push();
|
},
|
||||||
|
SeriesItem {
|
||||||
|
operation: "add",
|
||||||
|
closure: Box::new(move |repo: &GitRepo| repo.add_all()),
|
||||||
|
},
|
||||||
|
SeriesItem {
|
||||||
|
operation: "commit",
|
||||||
|
closure: Box::new(move |repo: &GitRepo| repo.commit_with_msg(msg)),
|
||||||
|
},
|
||||||
|
SeriesItem {
|
||||||
|
operation: "push",
|
||||||
|
closure: Box::new(move |repo: &GitRepo| repo.push()),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
self.all_on_all(series);
|
||||||
}
|
}
|
||||||
|
/// Tries to pull, add all, commit with msg "quick commit", and push all
|
||||||
|
/// repositories, skips if fail.
|
||||||
|
pub fn fast(&self, msg: &'static str) {
|
||||||
|
debug!("exectuting fast");
|
||||||
|
let series: Vec<SeriesItem> = vec![
|
||||||
|
SeriesItem {
|
||||||
|
operation: "pull",
|
||||||
|
closure: Box::new(move |repo: &GitRepo| repo.pull()),
|
||||||
|
},
|
||||||
|
SeriesItem {
|
||||||
|
operation: "add",
|
||||||
|
closure: Box::new(move |repo: &GitRepo| repo.add_all()),
|
||||||
|
},
|
||||||
|
SeriesItem {
|
||||||
|
operation: "commit",
|
||||||
|
closure: Box::new(move |repo: &GitRepo| repo.commit()),
|
||||||
|
},
|
||||||
|
SeriesItem {
|
||||||
|
operation: "push",
|
||||||
|
closure: Box::new(move |repo: &GitRepo| repo.push()),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
self.series_on_all(series);
|
||||||
}
|
}
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
/* LINK RELATED */
|
//////////////////////////////////////////////////////////////
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
/// Tries to link all repositories, skips if fail.
|
/// Tries to link all repositories, skips if fail.
|
||||||
pub fn link_all(&self) {
|
pub fn link_all(&self) {
|
||||||
debug!("exectuting link_all");
|
debug!("exectuting link_all");
|
||||||
|
|
129
src/main.rs
129
src/main.rs
|
@ -13,6 +13,23 @@
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see https://www.gnu.org/gpl-3.0.html.
|
// along with this program. If not, see https://www.gnu.org/gpl-3.0.html.
|
||||||
|
//
|
||||||
|
//! A Rust GitOps/symlinkfarm orchestrator inspired by GNU Stow.
|
||||||
|
//!
|
||||||
|
//! # What is?
|
||||||
|
//!
|
||||||
|
//! A Rust GitOps/symlinkfarm orchestrator inspired by GNU Stow. Useful for dealing
|
||||||
|
//! with "dotfiles", and with git support as a first class feature. Configuration is
|
||||||
|
//! done throug a single yaml file, giving it a paradigm that should bring joy to
|
||||||
|
//! those that use declarative operating systems and package managers.
|
||||||
|
//!
|
||||||
|
//! Although this isn't really a case where it matters *that* much for performance,
|
||||||
|
//! being written in rust instead of e.g. /janky/ scripting languages does also mean
|
||||||
|
//! it is snappy and reliable, and the /extensive/ testing helps ensure regressions
|
||||||
|
//! aren't introduced.
|
||||||
|
//!
|
||||||
|
//! That said, we're in 0.0.Z, *here be dragons* for now.
|
||||||
|
#![feature(unsized_tuple_coercion)]
|
||||||
|
|
||||||
extern crate log;
|
extern crate log;
|
||||||
extern crate pretty_env_logger;
|
extern crate pretty_env_logger;
|
||||||
|
@ -26,14 +43,20 @@ mod utils;
|
||||||
|
|
||||||
use cli::{Args, Commands};
|
use cli::{Args, Commands};
|
||||||
use git::Config;
|
use git::Config;
|
||||||
|
use utils::strings::{FAST_COMMIT, QUICK_COMMIT};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
|
|
||||||
|
/// The main loop of the binary
|
||||||
|
///
|
||||||
|
/// Here, we handle parsing the configuration file, as well as matching commands
|
||||||
|
/// to the relavant operations.
|
||||||
fn main() {
|
fn main() {
|
||||||
pretty_env_logger::init();
|
pretty_env_logger::init();
|
||||||
let args = Args::parse();
|
let mut args = Args::parse();
|
||||||
let config = Config::new(&args.config);
|
let config = Config::new(&args.config);
|
||||||
match &args {
|
match &args {
|
||||||
args if args.license => println!("{}", utils::strings::INTERACTIVE_LICENSE),
|
args if args.license => println!("{}", utils::strings::INTERACTIVE_LICENSE),
|
||||||
|
@ -41,12 +64,27 @@ fn main() {
|
||||||
args if args.code_of_conduct => unimplemented!(),
|
args if args.code_of_conduct => unimplemented!(),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
match &args.command {
|
match &mut args.command {
|
||||||
Some(Commands::Link { msg: _ }) => {
|
Some(Commands::Link { msg: _ }) => {
|
||||||
config.link_all();
|
config.link_all();
|
||||||
}
|
}
|
||||||
Some(Commands::Quick { msg }) => {
|
Some(Commands::Quick { msg }) => {
|
||||||
config.quick(msg.as_ref().get_or_insert(&"gg: quick commit".to_string()));
|
let s = Box::leak(
|
||||||
|
msg.as_mut()
|
||||||
|
.get_or_insert(&mut QUICK_COMMIT.to_string())
|
||||||
|
.clone()
|
||||||
|
.into_boxed_str(),
|
||||||
|
);
|
||||||
|
config.quick(s);
|
||||||
|
}
|
||||||
|
Some(Commands::Fast { msg }) => {
|
||||||
|
let s = Box::leak(
|
||||||
|
msg.as_mut()
|
||||||
|
.get_or_insert(&mut FAST_COMMIT.to_string())
|
||||||
|
.clone()
|
||||||
|
.into_boxed_str(),
|
||||||
|
);
|
||||||
|
config.fast(s);
|
||||||
}
|
}
|
||||||
Some(Commands::Clone { msg: _ }) => {
|
Some(Commands::Clone { msg: _ }) => {
|
||||||
config.clone_all();
|
config.clone_all();
|
||||||
|
@ -71,35 +109,51 @@ fn main() {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod config {
|
mod config {
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use git::GitRepo;
|
use git::RepoFlags::{Clone, Push};
|
||||||
|
use git::{Category, GitRepo};
|
||||||
use relative_path::RelativePath;
|
use relative_path::RelativePath;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::env::current_dir;
|
use std::env::current_dir;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
#[test]
|
#[test]
|
||||||
fn init_config() {
|
fn init_config() {
|
||||||
let _config = Config {
|
let _config = Config {
|
||||||
repos: vec![],
|
categories: HashMap::new(),
|
||||||
links: vec![],
|
links: vec![],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn init_config_populate() {
|
fn init_config_populate() {
|
||||||
|
let default_category = Category {
|
||||||
|
flags: vec![],
|
||||||
|
repos: HashMap::new(),
|
||||||
|
};
|
||||||
let mut config = Config {
|
let mut config = Config {
|
||||||
repos: vec![],
|
categories: HashMap::new(),
|
||||||
links: vec![],
|
links: vec![],
|
||||||
};
|
};
|
||||||
for _ in 0..=5 {
|
config
|
||||||
let repo = GitRepo {
|
.categories
|
||||||
|
.insert(format!("{}", 0).to_string(), default_category);
|
||||||
|
for i in 0..=5 {
|
||||||
|
config
|
||||||
|
.categories
|
||||||
|
.get_mut(&format!("{}", 0).to_string())
|
||||||
|
.expect("category not found")
|
||||||
|
.repos
|
||||||
|
.insert(
|
||||||
|
format!("{}", i).to_string(),
|
||||||
|
GitRepo {
|
||||||
name: "test repo".to_string(),
|
name: "test repo".to_string(),
|
||||||
path: "/tmp".to_string(),
|
path: "/tmp".to_string(),
|
||||||
url: "https://github.com/cafkafk/gg".to_string(),
|
url: "https://github.com/cafkafk/gg".to_string(),
|
||||||
clone: false,
|
flags: vec![Clone, Push],
|
||||||
};
|
},
|
||||||
config.repos.push(repo);
|
);
|
||||||
}
|
}
|
||||||
let yaml = serde_yaml::to_string(&config).unwrap();
|
// let yaml = serde_yaml::to_string(&config).unwrap();
|
||||||
println!("{}", yaml);
|
// println!("{}", yaml);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn read_config_populate() {
|
fn read_config_populate() {
|
||||||
|
@ -130,8 +184,23 @@ mod config {
|
||||||
let test_config = Config::new(&RelativePath::new("./src/test/test.yaml").to_string());
|
let test_config = Config::new(&RelativePath::new("./src/test/test.yaml").to_string());
|
||||||
assert_eq!(config, test_config);
|
assert_eq!(config, test_config);
|
||||||
}
|
}
|
||||||
|
fn get_category<'cat>(config: &'cat Config, name: &'cat str) -> &'cat Category {
|
||||||
|
config.categories.get(name).expect("failed to get category")
|
||||||
|
}
|
||||||
|
fn get_repo<F>(config: &Config, cat_name: &str, repo_name: &str, f: F)
|
||||||
|
where
|
||||||
|
F: FnOnce(&GitRepo),
|
||||||
|
{
|
||||||
|
f(config
|
||||||
|
.categories
|
||||||
|
.get(cat_name)
|
||||||
|
.expect("failed to get category")
|
||||||
|
.repos
|
||||||
|
.get(repo_name)
|
||||||
|
.expect("failed to get category"))
|
||||||
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn read_and_verify_config() {
|
fn is_config_readable() {
|
||||||
let root = current_dir().unwrap();
|
let root = current_dir().unwrap();
|
||||||
let config = Config::new(
|
let config = Config::new(
|
||||||
&RelativePath::new("./src/test/config.yaml")
|
&RelativePath::new("./src/test/config.yaml")
|
||||||
|
@ -140,31 +209,16 @@ mod config {
|
||||||
.into_string()
|
.into_string()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
// FIXME This is unnecessarily terse
|
|
||||||
|
let flags = vec![Clone, Push];
|
||||||
|
// FIXME not very extensive
|
||||||
#[allow(clippy::bool_assert_comparison)]
|
#[allow(clippy::bool_assert_comparison)]
|
||||||
{
|
{
|
||||||
assert_eq!(config.repos[0].name, "gg");
|
get_repo(&config, "config", "qmk_firmware", |repo| {
|
||||||
assert_eq!(config.repos[0].path, "/home/ces/.dots/");
|
assert_eq!(repo.name, "qmk_firmware");
|
||||||
assert_eq!(config.repos[0].url, "git@github.com:cafkafk/gg.git");
|
assert_eq!(repo.path, "/home/ces/org/src/git/");
|
||||||
assert_eq!(config.repos[0].clone, true);
|
assert_eq!(repo.url, "git@github.com:cafkafk/qmk_firmware.git");
|
||||||
assert_eq!(config.repos[1].name, "li");
|
})
|
||||||
assert_eq!(config.repos[1].path, "/home/ces/org/src/git/");
|
|
||||||
assert_eq!(config.repos[1].url, "git@github.com:cafkafk/li.git");
|
|
||||||
assert_eq!(config.repos[1].clone, true);
|
|
||||||
assert_eq!(config.repos[2].name, "qmk_firmware");
|
|
||||||
assert_eq!(config.repos[2].path, "/home/ces/org/src/git/");
|
|
||||||
assert_eq!(
|
|
||||||
config.repos[2].url,
|
|
||||||
"git@github.com:cafkafk/qmk_firmware.git"
|
|
||||||
);
|
|
||||||
assert_eq!(config.repos[2].clone, true);
|
|
||||||
assert_eq!(config.repos[3].name, "starship");
|
|
||||||
assert_eq!(config.repos[3].path, "/home/ces/org/src/git/");
|
|
||||||
assert_eq!(
|
|
||||||
config.repos[3].url,
|
|
||||||
"https://github.com/starship/starship.git"
|
|
||||||
);
|
|
||||||
assert_eq!(config.repos[3].clone, true);
|
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
assert_eq!(config.links[0].name, "gg");
|
assert_eq!(config.links[0].name, "gg");
|
||||||
|
@ -173,6 +227,7 @@ mod config {
|
||||||
assert_eq!(config.links[1].name, "starship");
|
assert_eq!(config.links[1].name, "starship");
|
||||||
assert_eq!(config.links[1].rx, "/home/ces/.config/starship.toml");
|
assert_eq!(config.links[1].rx, "/home/ces/.config/starship.toml");
|
||||||
assert_eq!(config.links[1].tx, "/home/ces/.dots/starship.toml");
|
assert_eq!(config.links[1].tx, "/home/ces/.dots/starship.toml");
|
||||||
|
// FIXME doesn't check repoflags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,30 @@
|
||||||
repos:
|
categories:
|
||||||
- name: gg
|
config:
|
||||||
path: /home/ces/.dots/
|
flags: []
|
||||||
url: git@github.com:cafkafk/gg.git
|
repos:
|
||||||
clone: true
|
qmk_firmware:
|
||||||
- name: li
|
name: qmk_firmware
|
||||||
path: /home/ces/org/src/git/
|
|
||||||
url: git@github.com:cafkafk/li.git
|
|
||||||
clone: true
|
|
||||||
- name: qmk_firmware
|
|
||||||
path: /home/ces/org/src/git/
|
path: /home/ces/org/src/git/
|
||||||
url: git@github.com:cafkafk/qmk_firmware.git
|
url: git@github.com:cafkafk/qmk_firmware.git
|
||||||
clone: true
|
flags: [Clone, Push]
|
||||||
- name: starship
|
starship:
|
||||||
|
name: starship
|
||||||
path: /home/ces/org/src/git/
|
path: /home/ces/org/src/git/
|
||||||
url: https://github.com/starship/starship.git
|
url: https://github.com/starship/starship.git
|
||||||
clone: true
|
flags: [Clone, Push]
|
||||||
|
utils:
|
||||||
|
flags: []
|
||||||
|
repos:
|
||||||
|
gg:
|
||||||
|
name: gg
|
||||||
|
path: /home/ces/.dots/
|
||||||
|
url: git@github.com:cafkafk/gg.git
|
||||||
|
flags: [Clone, Push]
|
||||||
|
li:
|
||||||
|
name: li
|
||||||
|
path: /home/ces/org/src/git/
|
||||||
|
url: git@github.com:cafkafk/li.git
|
||||||
|
flags: [Clone, Push]
|
||||||
links:
|
links:
|
||||||
- name: gg
|
- name: gg
|
||||||
rx: /home/ces/.config/gg
|
rx: /home/ces/.config/gg
|
||||||
|
|
|
@ -1,20 +1,38 @@
|
||||||
repos:
|
categories:
|
||||||
- name: gg
|
utils:
|
||||||
path: /home/ces/.dots/
|
flags: []
|
||||||
url: git@github.com:cafkafk/gg.git
|
repos:
|
||||||
clone: true
|
li:
|
||||||
- name: li
|
name: li
|
||||||
path: /home/ces/org/src/git/
|
path: /home/ces/org/src/git/
|
||||||
url: git@github.com:cafkafk/li.git
|
url: git@github.com:cafkafk/li.git
|
||||||
clone: true
|
flags:
|
||||||
- name: qmk_firmware
|
- Clone
|
||||||
|
- Push
|
||||||
|
gg:
|
||||||
|
name: gg
|
||||||
|
path: /home/ces/.dots/
|
||||||
|
url: git@github.com:cafkafk/gg.git
|
||||||
|
flags:
|
||||||
|
- Clone
|
||||||
|
- Push
|
||||||
|
config:
|
||||||
|
flags: []
|
||||||
|
repos:
|
||||||
|
qmk_firmware:
|
||||||
|
name: qmk_firmware
|
||||||
path: /home/ces/org/src/git/
|
path: /home/ces/org/src/git/
|
||||||
url: git@github.com:cafkafk/qmk_firmware.git
|
url: git@github.com:cafkafk/qmk_firmware.git
|
||||||
clone: true
|
flags:
|
||||||
- name: starship
|
- Clone
|
||||||
|
- Push
|
||||||
|
starship:
|
||||||
|
name: starship
|
||||||
path: /home/ces/org/src/git/
|
path: /home/ces/org/src/git/
|
||||||
url: https://github.com/starship/starship.git
|
url: https://github.com/starship/starship.git
|
||||||
clone: true
|
flags:
|
||||||
|
- Clone
|
||||||
|
- Push
|
||||||
links:
|
links:
|
||||||
- name: gg
|
- name: gg
|
||||||
rx: /home/ces/.config/gg
|
rx: /home/ces/.config/gg
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see https://www.gnu.org/gpl-3.0.html.
|
// along with this program. If not, see https://www.gnu.org/gpl-3.0.html.
|
||||||
|
//
|
||||||
|
//! Sublibrary for useful functions
|
||||||
|
|
||||||
pub mod dir;
|
pub mod dir;
|
||||||
pub mod strings;
|
pub mod strings;
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see https://www.gnu.org/gpl-3.0.html.
|
// along with this program. If not, see https://www.gnu.org/gpl-3.0.html.
|
||||||
|
//
|
||||||
|
//! Nice helpers for dealing with filesystem environment.
|
||||||
|
|
||||||
#![feature(stmt_expr_attributes)]
|
#![feature(stmt_expr_attributes)]
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
|
@ -20,6 +22,9 @@ use log::{debug, error, info, trace, warn};
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
/// Returns the users current dir
|
||||||
|
///
|
||||||
|
/// Does not work on Windows
|
||||||
pub fn current_dir() -> String {
|
pub fn current_dir() -> String {
|
||||||
#[allow(deprecated)] // NOTE we don't care about windows , we don't support it
|
#[allow(deprecated)] // NOTE we don't care about windows , we don't support it
|
||||||
env::current_dir()
|
env::current_dir()
|
||||||
|
@ -29,6 +34,9 @@ pub fn current_dir() -> String {
|
||||||
.expect("Failed to turn home_dir into a valid string")
|
.expect("Failed to turn home_dir into a valid string")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the users home dir
|
||||||
|
///
|
||||||
|
/// Does not work on Windows
|
||||||
pub fn home_dir() -> String {
|
pub fn home_dir() -> String {
|
||||||
#[allow(deprecated)] // NOTE we don't care about windows , we don't support it
|
#[allow(deprecated)] // NOTE we don't care about windows , we don't support it
|
||||||
env::home_dir()
|
env::home_dir()
|
||||||
|
|
|
@ -13,14 +13,19 @@
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see https://www.gnu.org/gpl-3.0.html.
|
// along with this program. If not, see https://www.gnu.org/gpl-3.0.html.
|
||||||
|
//
|
||||||
|
//! Module for chunk of text
|
||||||
|
//!
|
||||||
|
//! Ideally, at a VERY long term scale, this should be a nice pattern for
|
||||||
|
//! possible translations.
|
||||||
|
|
||||||
/// Contains the notice for interactive programs from the GPLv3's "How to Apply
|
/// Contains the notice for interactive programs from the GPLv3's "How to Apply
|
||||||
/// These Terms to Your New Programs"
|
/// These Terms to Your New Programs"
|
||||||
pub const INTERACTIVE_NOTICE: &str = "\
|
pub const INTERACTIVE_NOTICE: &str = "\
|
||||||
gg Copyright (C) 2023 Christina Sørensen <christina@cafkafk.com>
|
gg Copyright (C) 2023 Christina Sørensen <christina@cafkafk.com>
|
||||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `gg --warranty'.
|
||||||
This is free software, and you are welcome to redistribute it
|
This is free software, and you are welcome to redistribute it
|
||||||
under certain conditions; type `show c' for details.
|
under certain conditions; type `gg --license' for details.
|
||||||
";
|
";
|
||||||
|
|
||||||
/// Contains the license part of the long notice for interactive programs from
|
/// Contains the license part of the long notice for interactive programs from
|
||||||
|
@ -40,3 +45,9 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
GNU General Public License for more details.
|
GNU General Public License for more details.
|
||||||
";
|
";
|
||||||
|
|
||||||
|
/// Contains the message for quick commit subcommand
|
||||||
|
pub const QUICK_COMMIT: &str = "git: quick commit";
|
||||||
|
|
||||||
|
/// Contains the message for fast commit subcommand
|
||||||
|
pub const FAST_COMMIT: &str = "git: fast commit";
|
||||||
|
|
Loading…
Reference in a new issue