chore(version): bump to v0.2.0 #10

Merged
cafkafk merged 8 commits from dev into main 2023-07-07 06:13:59 +02:00
12 changed files with 434 additions and 145 deletions

4
.gitignore vendored
View file

@ -1,3 +1,3 @@
/target
/tst/test
/tst/test.yaml
/src/test/test
/src/test/test.yaml

View file

@ -2,12 +2,40 @@
All notable changes to this project will be documented in this file.
## [0.2.0] - 2023-07-07
### Bug Fixes
- Made categories with only links possible
### Features
- [**breaking**] Put links in categories
### Miscellaneous Tasks
- Filled out Cargo.toml
- Added test.yaml to gitignore
- Fixed up code, roadmap for bump
### Refactor
- Simple code quality changes
### Testing
- Refactored testing, added tests dir
## [0.1.2] - 2023-07-03
### Features
- Implemented quiet flag
### Miscellaneous Tasks
- Bump to v0.1.2
## [0.1.1] - 2023-07-03
### Bug Fixes
@ -24,6 +52,7 @@ All notable changes to this project will be documented in this file.
### Miscellaneous Tasks
- Bump v0.1.0, housekeeping, #8 from cafkafk/dev
- Bump to v0.1.1
## [0.1.0] - 2023-07-03
@ -42,6 +71,7 @@ All notable changes to this project will be documented in this file.
### Miscellaneous Tasks
- Bump to 0.0.7 #7 from cafkafk/dev
- Bump v0.1.0, housekeeping
### Refactor
@ -95,6 +125,7 @@ All notable changes to this project will be documented in this file.
- Version bump to v0.0.3
- Moved install scripts to ./bin
- Merge 0.0.6
- Merge 0.0.6 #6 from cafkafk/dev
- Bump to 0.0.7
### Refactor

2
Cargo.lock generated
View file

@ -179,7 +179,7 @@ dependencies = [
[[package]]
name = "gg"
version = "0.1.2"
version = "0.2.0"
dependencies = [
"clap",
"clap_mangen",

View file

@ -1,10 +1,30 @@
[package]
name = "gg"
version = "0.1.2"
edition = "2021"
version = "0.2.0"
authors = ["Christina Sørensen"]
edition = "2021"
rust-version = "1.72.0"
description = "A Rust GitOps/symlinkfarm orchestrator inspired by GNU Stow."
documentation = "https://github.com/cafkafk/gg"
readme = "./README.org"
homepage = "https://github.com/cafkafk/gg"
repository = "https://github.com/cafkafk/gg"
license = "GPL-3.0-only"
keywords = ["git", "declarative", "cli", "devops", "terminal"]
categories = ["command-line-interface", "command-line-utilities"]
# workspace = "idk, I have no idea how to use this"
# build = "build.rs"
# links = "git2"
# exclude = "./vacation_photos"
# include = "./gg_memes"
publish = false
# metadata
# deafult-run
# autobins
# autoexamples
# autotests
# autobenches
# resolver
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View file

@ -1,7 +1,9 @@
#+title: Roadmap
* 0.2.0 (maybe)
- [ ] Links in categories?
- [X] Links in categories?
- [X] Fix category with no links
- [-] Refactor
* 0.1.2
- [X] Implement Quiet flag
* 0.1.1

View file

@ -54,16 +54,12 @@ pub enum RepoFlags {
/// Represents the config.toml file.
///
/// For diagrams of the underlying architecture, consult ARCHITECHTURE.md
///
///
#[derive(Eq, PartialEq, Debug, Serialize, Deserialize)]
pub struct Config {
/// 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>,
}
/// Represents a category of repositories
@ -73,16 +69,22 @@ pub struct Config {
pub struct Category {
#[serde(skip_serializing_if = "Option::is_none")]
pub flags: Option<Vec<RepoFlags>>, // FIXME: not implemented
/// map of all categories
/// map of all repos in category
///
/// Key should conceptually be seen as the name of the category.
#[serde(skip_serializing_if = "Option::is_none")]
pub repos: Option<HashMap<String, GitRepo>>,
/// map of all links in category
///
/// Key should conceptually be seen as the name of the category.
#[serde(skip_serializing_if = "Option::is_none")]
pub links: Option<HashMap<String, Link>>,
}
/// Contain fields for a single link.
#[derive(Eq, PartialEq, Debug, Serialize, Deserialize)]
pub struct Links {
pub struct Link {
/// The name of the link
pub name: String,
pub rx: String,
@ -115,7 +117,7 @@ pub struct SeriesItem<'series> {
////////////////////////////////////
////////////////////////////////////
fn handle_file_exists(selff: &Links, tx_path: &Path, rx_path: &Path) {
fn handle_file_exists(selff: &Link, tx_path: &Path, rx_path: &Path) -> bool {
match rx_path.read_link() {
Ok(file)
if file.canonicalize().expect("failed to canonicalize file")
@ -125,22 +127,25 @@ fn handle_file_exists(selff: &Links, tx_path: &Path, rx_path: &Path) {
"Linking {} -> {} failed: file already linked",
&selff.tx, &selff.rx
);
false
}
Ok(file) => {
error!(
"Linking {} -> {} failed: link to different file exists",
&selff.tx, &selff.rx
);
false
}
Err(error) => {
error!("Linking {} -> {} failed: file exists", &selff.tx, &selff.rx);
false
}
}
}
impl Links {
impl Link {
/// Creates the link from the link struct
pub fn link(&self) {
pub fn link(&self) -> bool {
let tx_path: &Path = std::path::Path::new(&self.tx);
let rx_path: &Path = std::path::Path::new(&self.rx);
match rx_path.try_exists() {
@ -150,14 +155,17 @@ impl Links {
"Linking {} -> {} failed: broken symlink",
&self.tx, &self.rx
);
false
}
Ok(false) => {
symlink(&self.tx, &self.rx).expect("failed to create link");
true
}
Err(error) => {
error!("Linking {} -> {} failed: {}", &self.tx, &self.rx, error);
false
}
};
}
}
}
@ -305,7 +313,6 @@ impl GitRepo {
}
impl Config {
/* GIT RELATED */
/// Loads the configuration toml from a path in to the Config struct.
pub fn new(path: &String) -> Self {
debug!("initializing new Config struct");
@ -320,15 +327,9 @@ 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),
@ -339,28 +340,88 @@ impl Config {
}
}
}
// /// Runs associated function on all repos in config
// fn on_all_spinner<F>(&self, op: &str, f: F)
// where
// F: Fn(&GitRepo) -> bool,
// {
// for category in self.categories.values() {
// for (_, repo) in category.repos.as_ref().expect("failed to get repos").iter() {
// if !settings::QUIET.load(std::sync::atomic::Ordering::Relaxed) {
// let mut sp = Spinner::new(Spinners::Dots10, format!("{}: {}", repo.name, op));
// if f(repo) {
// sp.stop_and_persist(success_str(), format!("{}: {}", repo.name, op));
// } else {
// sp.stop_and_persist(failure_str(), format!("{}: {}", repo.name, op));
// }
// } else {
// 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)
fn on_all_repos_spinner<F>(&self, op: &str, f: F)
where
F: Fn(&GitRepo) -> bool,
{
for category in self.categories.values() {
for (_, repo) in category.repos.as_ref().expect("failed to get repos").iter() {
if !settings::QUIET.load(std::sync::atomic::Ordering::Relaxed) {
let mut sp = Spinner::new(Spinners::Dots10, format!("{}: {}", repo.name, op));
if f(repo) {
sp.stop_and_persist(success_str(), format!("{}: {}", repo.name, op));
} else {
sp.stop_and_persist(failure_str(), format!("{}: {}", repo.name, op));
match category.repos.as_ref() {
Some(repos) => {
for repo in repos.values() {
if !settings::QUIET.load(std::sync::atomic::Ordering::Relaxed) {
let mut sp =
Spinner::new(Spinners::Dots10, format!("{}: {}", repo.name, op));
if f(repo) {
sp.stop_and_persist(
success_str(),
format!("{}: {}", repo.name, op),
);
} else {
sp.stop_and_persist(
failure_str(),
format!("{}: {}", repo.name, op),
);
}
} else {
f(repo);
}
}
} else {
f(repo);
}
}
None => continue,
};
}
}
/// Runs associated function on all links in config
fn on_all_links_spinner<F>(&self, op: &str, f: F)
where
F: Fn(&Link) -> bool,
{
for category in self.categories.values() {
match category.links.as_ref() {
Some(links) => {
for link in links.values() {
if !settings::QUIET.load(std::sync::atomic::Ordering::Relaxed) {
let mut sp =
Spinner::new(Spinners::Dots10, format!("{}: {}", link.name, op));
if f(link) {
sp.stop_and_persist(
success_str(),
format!("{}: {}", link.name, op),
);
} else {
sp.stop_and_persist(
failure_str(),
format!("{}: {}", link.name, op),
);
}
} else {
f(link);
}
}
}
None => continue,
};
}
}
/// Runs associated function on all repos in config
@ -476,33 +537,61 @@ impl Config {
}
}
}
pub fn get_repo<F>(&self, cat_name: &str, repo_name: &str, f: F)
where
F: FnOnce(&GitRepo),
{
f(&self
.categories
.get(cat_name)
.expect("failed to get category")
.repos
.as_ref()
.expect("failed to get repo")
.get(repo_name)
.expect("failed to get category"))
}
pub fn get_link<F>(&self, cat_name: &str, link_name: &str, f: F)
where
F: FnOnce(&Link),
{
f(&self
.categories
.get(cat_name)
.expect("failed to get category")
.links
.as_ref()
.expect("failed to get repo")
.get(link_name)
.expect("failed to get category"))
}
//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////
/// Tries to pull all repositories, skips if fail.
pub fn pull_all(&self) {
debug!("exectuting pull_all");
self.on_all_spinner("pull", GitRepo::pull);
self.on_all_repos_spinner("pull", GitRepo::pull);
}
/// Tries to clone all repositories, skips if fail.
/// Tries to clone all repossitories, skips if fail.
pub fn clone_all(&self) {
debug!("exectuting clone_all");
self.on_all_spinner("clone", GitRepo::clone);
self.on_all_repos_spinner("clone", GitRepo::clone);
}
/// Tries to add all work in all repositories, skips if fail.
/// Tries to add all work in all repossitories, skips if fail.
pub fn add_all(&self) {
debug!("exectuting clone_all");
self.on_all_spinner("add", GitRepo::add_all);
self.on_all_repos_spinner("add", GitRepo::add_all);
}
/// Tries to commit all repositories one at a time, skips if fail.
/// Tries to commit all repossitories one at a time, skips if fail.
pub fn commit_all(&self) {
debug!("exectuting clone_all");
self.on_all_spinner("commit", GitRepo::commit);
self.on_all_repos_spinner("commit", GitRepo::commit);
}
/// Tries to commit all repositories with msg, skips if fail.
/// Tries to commit all repossitories with msg, skips if fail.
pub fn commit_all_msg(&self, msg: &str) {
debug!("exectuting clone_all");
self.on_all_spinner("commit", |repo| repo.commit_with_msg(msg));
self.on_all_repos_spinner("commit", |repo| repo.commit_with_msg(msg));
}
/// Tries to pull, add all, commit with msg "quick commit", and push all
/// repositories, skips if fail.
@ -558,8 +647,6 @@ impl Config {
/// Tries to link all repositories, skips if fail.
pub fn link_all(&self) {
debug!("exectuting link_all");
for link in &self.links {
link.link();
}
self.on_all_links_spinner("link", Link::link);
}
}

6
src/lib.rs Normal file
View file

@ -0,0 +1,6 @@
// #[allow(unused)]
// pub mod git;
// #[allow(unused)]
// mod settings;
// #[allow(unused)]
// mod utils;

View file

@ -126,7 +126,6 @@ mod config {
fn init_config() {
let _config = Config {
categories: HashMap::new(),
links: vec![],
};
}
#[test]
@ -134,10 +133,10 @@ mod config {
let default_category = Category {
flags: Some(vec![]),
repos: Some(HashMap::new()),
links: Some(HashMap::new()),
};
let mut config = Config {
categories: HashMap::new(),
links: vec![],
};
config
.categories
@ -192,23 +191,10 @@ mod config {
let test_config = Config::new(&RelativePath::new("./src/test/test.yaml").to_string());
assert_eq!(config, test_config);
}
#[allow(dead_code)]
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
.as_ref()
.expect("failed to get repo")
.get(repo_name)
.expect("failed to get category"))
}
#[test]
fn is_config_readable() {
let root = current_dir().expect("failed to get current dir");
@ -220,24 +206,20 @@ mod config {
.expect("failed to turnn config into string"),
);
let flags = vec![Clone, Push];
// FIXME not very extensive
let _flags = vec![Clone, Push];
// NOTE not very extensive
#[allow(clippy::bool_assert_comparison)]
{
get_repo(&config, "config", "qmk_firmware", |repo| {
(&config).get_repo("config", "qmk_firmware", |repo| {
assert_eq!(repo.name, "qmk_firmware");
assert_eq!(repo.path, "/home/ces/org/src/git/");
assert_eq!(repo.url, "git@github.com:cafkafk/qmk_firmware.git");
})
}
{
assert_eq!(config.links[0].name, "gg");
assert_eq!(config.links[0].rx, "/home/ces/.config/gg");
assert_eq!(config.links[0].tx, "/home/ces/.dots/gg");
assert_eq!(config.links[1].name, "starship");
assert_eq!(config.links[1].rx, "/home/ces/.config/starship.toml");
assert_eq!(config.links[1].tx, "/home/ces/.dots/starship.toml");
// FIXME doesn't check repoflags
});
(&config).get_link("stuff", "gg", |link| {
assert_eq!(link.name, "gg");
assert_eq!(link.tx, "/home/ces/.dots/gg");
assert_eq!(link.rx, "/home/ces/.config/gg");
});
}
}
}

View file

@ -36,10 +36,23 @@ categories:
name: li
path: /home/ces/org/src/git/
url: git@github.com:cafkafk/li.git
links:
- name: gg
rx: /home/ces/.config/gg
tx: /home/ces/.dots/gg
- name: starship
rx: /home/ces/.config/starship.toml
tx: /home/ces/.dots/starship.toml
links:
gg:
name: gg
rx: /home/ces/.config/gg
tx: /home/ces/.dots/gg
starship:
name: starship
rx: /home/ces/.config/starship.toml
tx: /home/ces/.dots/starship.toml
fluff:
flags: []
links:
gg:
name: gg
rx: /home/ces/.config/gg
tx: /home/ces/.dots/gg
starship:
name: starship
rx: /home/ces/.config/starship.toml
tx: /home/ces/.dots/starship.toml

View file

@ -1,53 +0,0 @@
categories:
config:
flags: []
repos:
starship:
name: starship
path: /home/ces/org/src/git/
url: https://github.com/starship/starship.git
flags:
- Clone
- Push
qmk_firmware:
name: qmk_firmware
path: /home/ces/org/src/git/
url: git@github.com:cafkafk/qmk_firmware.git
flags:
- Clone
- Push
stuff:
flags: []
repos:
li:
name: li
path: /home/ces/org/src/git/
url: git@github.com:cafkafk/li.git
gg:
name: gg
path: /home/ces/.dots/
url: git@github.com:cafkafk/gg.git
utils:
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
empty: {}
links:
- name: gg
rx: /home/ces/.config/gg
tx: /home/ces/.dots/gg
- name: starship
rx: /home/ces/.config/starship.toml
tx: /home/ces/.dots/starship.toml

View file

@ -77,17 +77,17 @@ pub const SUCCESS_STRING: &str = "SUCC";
pub const FAILURE_STRING: &str = "FAIL";
pub fn success_str() -> &'static str {
if !settings::EMOJIS.load(Ordering::Relaxed) {
SUCCESS_EMOJI
} else {
if settings::EMOJIS.load(Ordering::Relaxed) {
SUCCESS_STRING
} else {
SUCCESS_EMOJI
}
}
pub fn failure_str() -> &'static str {
if !settings::EMOJIS.load(Ordering::Relaxed) {
FAILURE_EMOJI
} else {
if settings::EMOJIS.load(Ordering::Relaxed) {
FAILURE_STRING
} else {
FAILURE_EMOJI
}
}

201
tests/main.rs Normal file
View file

@ -0,0 +1,201 @@
#[test]
fn main() {
assert!(true);
}
/*
#[cfg(test)]
mod config {
use gg::git::RepoFlags::{Clone, Push};
use gg::git::{Category, Config, GitRepo, Link};
use relative_path::RelativePath;
use std::collections::HashMap;
use std::env::current_dir;
use std::fs::File;
use std::io::prelude::*;
#[test]
fn init_config() {
let _config = Config {
categories: HashMap::new(),
};
}
#[test]
fn init_config_populate() {
let default_category = Category {
flags: Some(vec![]),
repos: Some(HashMap::new()),
links: Some(HashMap::new()),
};
let mut config = Config {
categories: HashMap::new(),
};
config
.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
.as_mut()
.expect("failed to get repo")
.insert(
format!("{}", i).to_string(),
GitRepo {
name: "test repo".to_string(),
path: "/tmp".to_string(),
url: "https://github.com/cafkafk/gg".to_string(),
flags: Some(vec![Clone, Push]),
},
);
}
}
#[test]
fn read_config_populate() {
let _config = Config::new(&RelativePath::new("./src/test/config.yaml").to_string());
}
#[test]
fn write_config() {
let root = current_dir().expect("failed to get current dir");
let config = Config::new(
&RelativePath::new("./src/test/config.yaml")
.to_logical_path(&root)
.into_os_string()
.into_string()
.expect("failed to turn config into string"),
);
let mut test_file = File::create(
RelativePath::new("./src/test/test.yaml")
.to_logical_path(&root)
.into_os_string()
.into_string()
.expect("failed to turn config into string"),
)
.expect("failed to create test file");
let contents = serde_yaml::to_string(&config).expect("failed to turn config into string");
test_file
.write_all(contents.as_bytes())
.expect("failed to write contents of config into file");
let test_config = Config::new(&RelativePath::new("./src/test/test.yaml").to_string());
assert_eq!(config, test_config);
}
#[allow(dead_code)]
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
.as_ref()
.expect("failed to get repo")
.get(repo_name)
.expect("failed to get category"))
}
fn get_link<F>(config: &Config, cat_name: &str, link_name: &str, f: F)
where
F: FnOnce(&Link),
{
f(config
.categories
.get(cat_name)
.expect("failed to get category")
.links
.as_ref()
.expect("failed to get repo")
.get(link_name)
.expect("failed to get category"))
}
#[test]
fn is_config_readable() {
let root = current_dir().expect("failed to get current dir");
let config = Config::new(
&RelativePath::new("./src/test/config.yaml")
.to_logical_path(root)
.into_os_string()
.into_string()
.expect("failed to turnn config into string"),
);
let _flags = vec![Clone, Push];
// FIXME not very extensive
#[allow(clippy::bool_assert_comparison)]
{
get_repo(&config, "config", "qmk_firmware", |repo| {
assert_eq!(repo.name, "qmk_firmware");
assert_eq!(repo.path, "/home/ces/org/src/git/");
assert_eq!(repo.url, "git@github.com:cafkafk/qmk_firmware.git");
});
get_link(&config, "stuff", "gg", |link| {
assert_eq!(link.name, "gg");
assert_eq!(link.tx, "/home/ces/.dots/gg");
assert_eq!(link.rx, "/home/ces/.config/gg");
});
}
/*
{
assert_eq!(config.links[0].name, "gg");
assert_eq!(config.links[0].rx, "/home/ces/.config/gg");
assert_eq!(config.links[0].tx, "/home/ces/.dots/gg");
assert_eq!(config.links[1].name, "starship");
assert_eq!(config.links[1].rx, "/home/ces/.config/starship.toml");
assert_eq!(config.links[1].tx, "/home/ces/.dots/starship.toml");
// FIXME doesn't check repoflags
}*/
}
}*/
/*
#[cfg(test)]
mod repo_actions {
use gg::git::GitRepo;
use gg::relative_path::RelativePath;
use gg::std::env::current_dir;
use gg::std::process::Command;
#[test]
#[allow(clippy::redundant_clone)]
fn test_repo_actions() {
let test_repo_name: String = "test".to_string();
let root = current_dir().unwrap();
let test_repo_dir: String = RelativePath::new("./src/test")
.to_logical_path(&root)
.into_os_string()
.into_string()
.unwrap();
let test_repo_url: String = "git@github.com:cafkafk/test.git".to_string();
println!("{}", test_repo_dir);
let mut config = Config {
repos: vec![],
links: vec![],
};
let repo = GitRepo {
name: test_repo_name.to_owned(),
path: test_repo_dir.to_owned(),
url: test_repo_url.to_owned(),
clone: true,
};
config.repos.push(repo);
// BUG FIXME can't do this in flake
// should have a good alternative
// config.clone_all();
// config.pull_all();
for r in config.repos.iter() {
Command::new("touch")
.current_dir(&(r.path.to_owned() + &r.name))
.arg("test")
.status()
.expect("failed to create test file");
}
config.add_all();
config.commit_all_msg(&"test".to_string());
}
}
*/