summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Gerace <nickagerace@gmail.com>2021-12-24 16:38:03 -0500
committerGitHub <noreply@github.com>2021-12-24 16:38:03 -0500
commit975fe5b902798e1b357a9acf3245c05b83b2ffab (patch)
treecac8368482556d664c5b1317d6fcdff39837f1f8
parentf7c11ab13880c5ff6b5e8b53e524b037ec7af465 (diff)
downloadgfold-975fe5b902798e1b357a9acf3245c05b83b2ffab.zip
Refactor config to be the only public config type (#153)
- Refactor config to ensure entry config is wrapped by private functions and methods - Ensure tests skip config file with new functionality, which relies on entry config using defaults - Update issue and PR templates to be more clear in what they ask from users (cleaner checklists) - Switch RELEASE to use a list instead an emoji-based markdown table - Push RELEASE commands into their own code blocks - Add new emoji-based "logging" headers to uninstall/install scripts - Refactor uninstall/install scripts to use functions for readability - Add ignore config file option - Give every major type, function, and method a comment-based description - Fix issue template by adding back name and about - Use get flag for remote origin url Signed-off-by: Nick Gerace <nickagerace@gmail.com>
-rw-r--r--.github/ISSUE_TEMPLATE/issue.md17
-rw-r--r--.github/pull_request_template.md5
-rw-r--r--.github/workflows/push.yml8
-rw-r--r--.github/workflows/tag.yml1
-rw-r--r--CHANGELOG.md11
-rw-r--r--Cargo.toml9
-rw-r--r--Makefile2
-rw-r--r--RELEASE.md65
-rwxr-xr-xscripts/install.sh59
-rwxr-xr-xscripts/uninstall.sh19
-rw-r--r--src/cli.rs13
-rw-r--r--src/config.rs42
-rw-r--r--src/main.rs8
-rw-r--r--src/report.rs21
-rw-r--r--src/run.rs3
15 files changed, 197 insertions, 86 deletions
diff --git a/.github/ISSUE_TEMPLATE/issue.md b/.github/ISSUE_TEMPLATE/issue.md
index 0bbea7a..af7aaf8 100644
--- a/.github/ISSUE_TEMPLATE/issue.md
+++ b/.github/ISSUE_TEMPLATE/issue.md
@@ -1,5 +1,12 @@
-<!-- Thanks for filing an issue! Here are some good things to include in order to help get the issue solved quickly: -->
-<!-- 1. Providing a clear description of the issue (alongside reproduction steps, as needed) -->
-<!-- 2. Providing what OS you are using -->
-<!-- 3. Providing the method of installing/obtaining the application -->
-<!-- 4. Pasting the output of `gfold -V` to confirm the version -->
+---
+name: Issue
+about: Create an issue.
+---
+
+<!-- Thanks for filing an issue! Since this is a volunteer-ran repository, filing out your issue with the following can help get your issue solved quickly: -->
+
+<!-- 1) A clear description of the issue (alongside reproduction steps, as needed) -->
+<!-- 2) What OS you are using -->
+<!-- 3) Your method of installing/obtaining the application, if relevant -->
+<!-- 4) The output of `gfold -V` (confirm the version based on the `gfold` installation in your `PATH`) -->
+<!-- 5) The output of `gfold --debug`, if relevant -->
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index b04b6e1..7440c61 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1 +1,4 @@
-<!-- Please describe what your PR does and the reasonining behind it. In addition, please link relevant issue(s), as needed. -->
+<!-- Thanks for filing a PR! Since this is a volunteer-ran repository, filing out your PR with the following can help get your PR reviewed quickly: -->
+
+<!-- 1) Please describe what your PR does and the reasonining behind it. -->
+<!-- 2) Please link relevant issue(s), as needed. -->
diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml
index 24cf20d..3c98fbd 100644
--- a/.github/workflows/push.yml
+++ b/.github/workflows/push.yml
@@ -4,11 +4,12 @@ on:
branches-exclude:
- "main"
paths:
- - **/*.rs
- - **/Cargo.*
- - **/rustfmt.toml
+ - "**.rs"
+ - "Cargo.*"
+ - "rustfmt.toml"
jobs:
+
prepare:
runs-on: ubuntu-latest
steps:
@@ -30,6 +31,7 @@ jobs:
- uses: actions-rs/cargo@v1
with:
command: test
+
build:
runs-on: ${{ matrix.os }}
strategy:
diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml
index a538bec..6e840e7 100644
--- a/.github/workflows/tag.yml
+++ b/.github/workflows/tag.yml
@@ -4,6 +4,7 @@ on:
tags:
- "*"
jobs:
+
publish:
runs-on: ${{ matrix.os }}
strategy:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a9c23f2..f740f07 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,19 +11,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
### Added
+- Ability to ignore config file options
+- Ability to print merged config options
+- Ability to store default path target in config file (defaults to current working directory)
- Ability to use config file in `$HOME/.config/gfold/gfold.json` and `{FOLDERID_Profile}\.config\gfold\gfold.json`
-- Experimental new display mode (`--new` flag)
+- Ability to use old display mode with `--classic` flag and store preference in config file
- Formal CLI parsing library, `argh`
- Install and uninstall scripts
+- New display mode that avoids grouping repositories (API-breaking since this is the new default display mode)
### Changed
-- Codebase to a domain-driven architecture
+- Codebase to a domain-driven architecture (major refactor)
### Notes
-- This crate has used other CLI parsing libraries in the past, and recently did not use any, but with manual testing and [publicly available benchmarks](https://github.com/rust-cli/argparse-benchmarks-rs/blob/c37e78aabdaa4384a9c49be3735a686803d0e37a/README.md#results), `argh` is now in use.
- Evaluated using `tracing` and `tracing-subscriber` over `log` and `env_logger`, but due to their combined larger size, the logging crates remain the same as before.
+- The config file can be non-existent, empty, partially filled out or completely filled out. There's also an option to ignore the config file completely and only use CLI options.
+- This crate has used other CLI parsing libraries in the past, and recently did not use any, but with manual testing and [publicly available benchmarks](https://github.com/rust-cli/argparse-benchmarks-rs/blob/c37e78aabdaa4384a9c49be3735a686803d0e37a/README.md#results), `argh` is now in use.
## [2.0.2] - 2021-12-02
diff --git a/Cargo.toml b/Cargo.toml
index 103a0c3..e7d2763 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,13 +2,14 @@
authors = ["Nick Gerace <nickagerace@gmail.com>"]
categories = ["command-line-utilities", "command-line-interface"]
description = "CLI tool to help keep track of your Git repositories."
-edition = "2021"
homepage = "https://nickgerace.dev"
keywords = ["git", "cli"]
license = "Apache-2.0"
name = "gfold"
readme = "README.md"
repository = "https://github.com/nickgerace/gfold/"
+
+edition = "2021"
version = "2.0.2"
[dependencies]
@@ -32,9 +33,9 @@ codegen-units = 1
# Instruct linker to optimize at the link stage.
lto = true
-# This application should not panic often and only read from the filesystem.
-panic = "abort"
-
# There is a noticeable speed difference from level 3 to 'z' or 's'.
# We need this speed for the user experience.
opt-level = 3
+
+# This application should not panic often and only read from the filesystem.
+panic = "abort"
diff --git a/Makefile b/Makefile
index b1093b4..fdb31d7 100644
--- a/Makefile
+++ b/Makefile
@@ -38,7 +38,7 @@ bench-loosely:
@echo "============================================================="
@time $(INSTALLED) ~/
@echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"
- @time $(NEW) ~/
+ @time $(NEW) -i ~/
@echo "============================================================="
@du -h $(INSTALLED)
@du -h $(NEW)
diff --git a/RELEASE.md b/RELEASE.md
index a9c2f5c..1cd8bda 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -5,24 +5,53 @@ This document contains all information related to release.
## Checklist
This checklist details the `gfold` release process.
-Steps should (and often must) be executed in sequential order.
-
-| RC | Release | Step |
-|-----|---------|---------------------------------------------------------------------------------------------------------------------------------|
-| ✔️ | ✔️ | Checkout and rebase `main` to its latest commit. |
-| ✔️ | ✔️ | Change the `version` field in `Cargo.toml` to the new tag. |
-| ⛔ | ✔️ | Change the version in `CHANGELOG.md` and uncomment the line, `<!--The latest version contains all changes.-->`. |
-| ✔️ | ✔️ | Run `make ci build` and verify that everything looks/works as expected. |
-| ✔️ | ✔️ | Create a commit with the following message: `Update to <tag>`. **Do not push (or merge) the commit yet.** |
-| ✔️ | ✔️ | Test and verify the publishing workflow: `cargo publish --dry-run`. |
-| ✔️ | ✔️ | Push (or merge) the preparation commit into `main`. |
-| ✔️ | ✔️ | Checkout and rebase `main` to its latest commit, which should be the aforementioned commit. |
-| ✔️ | ✔️ | Tag with `git tag <tag>` and push the tag: `git push --tags origin main`. |
-| ✔️ | ✔️ | Publish the crate: `cargo publish`. |
-| ✔️ | ✔️ | Verify that the [crate](https://crates.io/crates/gfold) on `crates.io` looks correct. |
-| ✔️ | ✔️ | Download the crate via `cargo install --locked gfold` or `cargo install --locked --version <tag> gfold`. |
-| ✔️ | ✔️ | Verify that the [GitHub release](https://github.com/nickgerace/gfold/releases) on the repository's releases page looks correct. |
-| ⛔ | ✔️ | Update the formula for the [Hombrew tap](https://github.com/nickgerace/homebrew-nickgerace). |
+Steps should (and frequently must) be executed in sequential order.
+
+- [ ] Checkout and rebase `main` to its latest commit
+- [ ] Change the `version` field in `Cargo.toml` to the new tag
+- [ ] **Full Releases Only**: change the version in `CHANGELOG.md` and uncomment the following line: `<!--The latest version contains all changes.-->`
+- [ ] Run final `make` targets and verify that everything looks/works as expected:
+
+```bash
+make ci build
+```
+
+- [ ] Create and _do not push/merge_ a commit with the following message: `Update to <tag>`
+
+- [ ] Test and verify the publishing workflow:
+
+```bash
+cargo publish --dry-run
+```
+
+- [ ] Push/merge the preparation commit into `main`
+- [ ] Checkout and rebase `main` to its latest commit, which should be the aforementioned commit
+- [ ] Tag and push the tag:
+
+```bash
+git tag <tag>
+git push --tags origin main
+```
+
+- [ ] Publish the crate:
+
+```bash
+cargo publish
+```
+
+- [ ] Verify that the [crate](https://crates.io/crates/gfold) on `crates.io` looks correct
+- [ ] Download and install the crate:
+
+```bash
+# Full releases
+cargo install --locked gfold
+
+# Release candidates (RCs)
+cargo install --locked --version <tag> gfold
+```
+
+- [ ] Verify that the [GitHub release](https://github.com/nickgerace/gfold/releases) on the repository's releases page looks correct
+- [ ] **Full Releases Only**: Update the formula for the [Hombrew tap](https://github.com/nickgerace/homebrew-nickgerace)
## Versioning Scheme
diff --git a/scripts/install.sh b/scripts/install.sh
index cd13b2f..be0bc7c 100755
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -1,27 +1,46 @@
#!/usr/bin/env bash
set -e
-for BINARY in "jq" "wget" "curl"; do
- if ! [ "$(command -v ${BINARY})" ]; then
- echo "error: \"$BINARY\" must be installed and in PATH"
+function check-dependencies {
+ for BINARY in "jq" "wget" "curl"; do
+ if ! [ "$(command -v ${BINARY})" ]; then
+ echo "[install-gfold] 🚫 \"$BINARY\" must be installed and in PATH"
+ exit 1
+ fi
+ done
+}
+
+function perform-install {
+ local INSTALL_OS
+ if [ "$(uname -s)" = "Linux" ] && [ "$(uname -m)" = "x86_64" ]; then
+ INSTALL_OS="linux-gnu"
+ elif [ "$(uname -s)" = "Darwin" ] && [ "$(uname -m)" = "x86_64" ]; then
+ INSTALL_OS="darwin"
+ else
+ echo "[install-gfold] 🚫 must execute on Linux or Darwin x86_64 host"
+ echo "[install-gfold] 🚫 for more installation methods: https://github.com/nickgerace/gfold"
exit 1
fi
-done
-INSTALL_OS="unknown"
-if [ "$(uname -s)" = "Linux" ] && [ "$(uname -m)" = "x86_64" ]; then
- echo "assuming glibc (GNU) and not another libc (e.g. musl-libc)"
- INSTALL_OS="linux-gnu"
-elif [ "$(uname -s)" = "Darwin" ] && [ "$(uname -m)" = "x86_64" ]; then
- INSTALL_OS="darwin"
-else
- echo "error: must execute on Linux or Darwin x86_64 host"
- exit 1
-fi
+ LATEST=$(curl -s https://api.github.com/repos/nickgerace/gfold/releases/latest | jq -r ".tag_name")
+ if [ -f /tmp/gfold ]; then
+ rm /tmp/gfold
+ fi
+ wget -O /tmp/gfold https://github.com/nickgerace/gfold/releases/download/$LATEST/gfold-$INSTALL_OS-amd64
+ chmod +x /tmp/gfold
+
+ if [ -f /usr/local/bin/gfold ]; then
+ rm /usr/local/bin/gfold
+ fi
+ mv /tmp/gfold /usr/local/bin/gfold
+
+ echo "[install-gfold] ✅ gfold has been installed to /usr/local/bin/gfold"
+ if [ $INSTALL_OS = "linux-gnu" ]; then
+ echo "[install-gfold] ⚠️ assuming glibc (GNU) and not another libc (e.g. musl-libc)"
+ echo "[install-gfold] ⚠️ if using another libc, you may need to choose another installation method"
+ echo "[install-gfold] ⚠️ for more information: https://github.com/nickgerace/gfold"
+ fi
+}
-LATEST=$(curl -s https://api.github.com/repos/nickgerace/gfold/releases/latest | jq -r ".tag_name")
-if [ -f /tmp/gfold ]; then rm /tmp/gfold; fi
-wget -O /tmp/gfold https://github.com/nickgerace/gfold/releases/download/$LATEST/gfold-$INSTALL_OS-amd64
-chmod +x /tmp/gfold
-mv /tmp/gfold /usr/local/bin/gfold
-echo "gfold has been installed to /usr/local/bin/gfold"
+check-dependencies
+perform-install \ No newline at end of file
diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh
index d6e683a..3064282 100755
--- a/scripts/uninstall.sh
+++ b/scripts/uninstall.sh
@@ -1,12 +1,19 @@
#!/usr/bin/env bash
set -e
-function delete-binary {
- if [ -f "$1" ]; then
- rm "$1"
+function perform-uninstall {
+ for FILE in "/tmp/gfold" "/usr/local/bin/gfold"; do
+ if [ -f "$FILE" ]; then
+ rm "$FILE"
+ echo "[uninstall-gfold] ✅ deleted $FILE"
+ fi
+ done
+ echo "[uninstall-gfold] ✅ gfold has been uninstalled from your system"
+
+ if [ -f $HOME/.config/gfold/gfold.json ]; then
+ echo "[uninstall-gfold] ⚠️ you may want to delete or backup the config file"
+ echo "[uninstall-gfold] ⚠️ config file path: $HOME/.config/gfold/gfold.json"
fi
}
-delete-binary /tmp/gfold
-delete-binary /usr/local/bin/gfold
-echo "gfold has been removed from your system" \ No newline at end of file
+perform-uninstall \ No newline at end of file
diff --git a/src/cli.rs b/src/cli.rs
index 5ae0ccb..1d0463b 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -23,6 +23,10 @@ struct Args {
description = "path to target directory (defaults to current working directory)"
)]
path: Option<String>,
+
+ #[argh(switch, short = 'i', description = "ignore config file settings")]
+ ignore_config_file: bool,
+
#[argh(switch, description = "display results with classic formatting")]
classic: bool,
#[argh(
@@ -37,9 +41,11 @@ struct Args {
}
pub fn parse() -> Result<()> {
- // First and foremost, get logging up and running.
+ // First and foremost, get logging up and running. We want logs as quickly as possible for
+ // debugging by setting "RUST_LOG".
let args: Args = argh::from_env();
logging::init(args.debug);
+
match args.version {
true => {
println!("gfold {}", env!("CARGO_PKG_VERSION"));
@@ -50,7 +56,10 @@ pub fn parse() -> Result<()> {
}
fn merge_config_and_run(args: &Args) -> Result<()> {
- let mut config = Config::try_config()?;
+ let mut config = match args.ignore_config_file {
+ true => Config::new()?,
+ false => Config::try_config()?,
+ };
if let Some(s) = &args.path {
config.path = env::current_dir()?.join(s).canonicalize()?;
diff --git a/src/config.rs b/src/config.rs
index 084d7a3..c15cdcf 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -7,18 +7,35 @@ use std::fs::File;
use std::io::BufReader;
use std::path::PathBuf;
-#[derive(Deserialize, Default)]
-pub struct EntryConfig {
- pub path: Option<PathBuf>,
- pub display_mode: Option<DisplayMode>,
-}
-
+// "Config" is the actual type consumed through the codebase. It is boostrapped via its public
+// methods and uses "EntryConfig", a private struct, under the hood in order to deserialize empty,
+// non-existent, partial, and complete config files.
#[derive(Serialize)]
pub struct Config {
pub path: PathBuf,
pub display_mode: DisplayMode,
}
+// "EntryConfig" is a reflection of "Config" with its fields wrapped as "Option" types. This is to
+// ensure that we can deserialize from partial config file contents and populate empty fields with
+// defaults. Moreover, enumerations cannot set defaults values currently, so we need to set
+// desired defaults for the user. In this case, the public methods for "Config" use "EntryConfig"
+// privately.
+#[derive(Deserialize, Default)]
+struct EntryConfig {
+ pub path: Option<PathBuf>,
+ pub display_mode: Option<DisplayMode>,
+}
+
+// "DisplayMode" dictates which way the results gathered should be displayed to the user via
+// STDOUT. Setting this enumeration is _mostly_ cosmetic, but it is possible that collected data
+// may differ in order to reduce compute load. For example: if one display mode dislays more
+// information than another display mode, more subcommands and functions might get executed.
+// Conversely, if another display mode requires less information to be displayed, then some
+// commands and functions migth get skipped.
+//
+// TLDR: while this setting is primarily for cosmetics, it may also affect runtime performance
+// based on what needs to be displayed.
#[derive(Serialize, Deserialize, Clone)]
pub enum DisplayMode {
Standard,
@@ -26,6 +43,9 @@ pub enum DisplayMode {
}
impl Config {
+ // This method tries to deserialize the config file (empty, non-existent, partial or complete)
+ // and uses "EntryConfig" as an intermediary struct. This is the primary method used when
+ // creating a config.
pub fn try_config() -> Result<Config> {
let home = dirs::home_dir().ok_or(Error::HomeDirNotFound)?;
let entry_config = match File::open(home.join(".config").join("gfold").join("gfold.json")) {
@@ -41,13 +61,21 @@ impl Config {
entry_config_to_config(&entry_config)
}
+ // This method does not look for the config file and uses "EntryConfig"'s defaults instead.
+ // This method is best for testing use and when the user wishes to skip config file lookup.
+ pub fn new() -> Result<Config> {
+ entry_config_to_config(&EntryConfig::default())
+ }
+
+ // This method prints the full config (merged with config file, as needed) as valid JSON.
pub fn print(self) -> Result<()> {
println!("{}", serde_json::to_string_pretty(&self)?);
Ok(())
}
}
-pub fn entry_config_to_config(entry_config: &EntryConfig) -> Result<Config> {
+// Internal conversion function for private "EntryConfig" objects to "Config" objects.
+fn entry_config_to_config(entry_config: &EntryConfig) -> Result<Config> {
Ok(Config {
path: match &entry_config.path {
Some(s) => s.clone(),
diff --git a/src/main.rs b/src/main.rs
index 5b73d43..56f42db 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -18,20 +18,20 @@ fn main() -> Result<()> {
#[cfg(test)]
mod tests {
use super::*;
- use crate::config::EntryConfig;
+ use crate::config::Config;
use crate::error::Error;
use std::env;
#[test]
fn current_directory() {
- let config = config::entry_config_to_config(&EntryConfig::default()).unwrap();
+ let config = Config::new().unwrap();
assert!(run::run(&config).is_ok());
}
#[test]
fn parent_directory() {
- let mut config = config::entry_config_to_config(&EntryConfig::default()).unwrap();
+ let mut config = Config::new().unwrap();
let mut parent = env::current_dir().expect("failed to get current working directory");
parent.pop();
@@ -42,7 +42,7 @@ mod tests {
#[test]
fn home_directory() {
- let mut config = config::entry_config_to_config(&EntryConfig::default()).unwrap();
+ let mut config = Config::new().unwrap();
config.path = dirs::home_dir().ok_or(Error::HomeDirNotFound).unwrap();
diff --git a/src/report.rs b/src/report.rs
index 79220c8..1a5151c 100644
--- a/src/report.rs
+++ b/src/report.rs
@@ -100,7 +100,7 @@ fn generate_report(path: &Path, include_email: bool) -> Result<Report> {
status,
status_as_string,
branch: branch.to_string(),
- url: match git(&["config", "remote.origin.url"], path)?.strip_suffix(NEWLINE) {
+ url: match git(&["config", "--get", "remote.origin.url"], path)?.strip_suffix(NEWLINE) {
Some(s) => s.to_string(),
None => NONE.to_string(),
},
@@ -128,18 +128,15 @@ fn is_unpushed(path: &Path, branch: &str) -> Result<bool> {
}
fn get_email(path: &Path) -> Result<String> {
- match git(&["config", "--get", "user.email"], path)?.strip_suffix(NEWLINE) {
- Some(s) => Ok(s.to_string()),
- None => Ok(NONE.to_string()),
- }
+ Ok(
+ match git(&["config", "--get", "user.email"], path)?.strip_suffix(NEWLINE) {
+ Some(s) => s.to_string(),
+ None => NONE.to_string(),
+ },
+ )
}
fn git(args: &[&str], wd: &Path) -> Result<String> {
- match Command::new("git").args(args).current_dir(wd).output() {
- Ok(o) => match String::from_utf8(o.stdout) {
- Ok(s) => Ok(s),
- Err(e) => Err(e.into()),
- },
- Err(e) => Err(e.into()),
- }
+ let output = Command::new("git").args(args).current_dir(wd).output()?;
+ Ok(String::from_utf8(output.stdout)?)
}
diff --git a/src/run.rs b/src/run.rs
index b6be64b..db56e89 100644
--- a/src/run.rs
+++ b/src/run.rs
@@ -5,6 +5,9 @@ use crate::report::Reports;
use crate::target_gen::Targets;
use anyhow::Result;
+// This function is the primary entrypoint for the crate. It takes a given config and performs
+// the end-to-end workflow using it. At this point, all CLI and config file options should be
+// set, merged, ignored, etc.
pub fn run(config: &Config) -> Result<()> {
let reports = Reports::new(Targets::new(&config.path)?, &config.display_mode)?;