diff --git a/.gitignore b/.gitignore index 5342d3e66..de30f33ad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ /target **/*.rs.bk /stage -*.snap /parts /prime .gitignore.swp diff --git a/Cargo.lock b/Cargo.lock index 04441ae39..02294f2ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,6 +62,12 @@ dependencies = [ "nodrop", ] +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "arrayvec" version = "0.7.2" @@ -362,6 +368,19 @@ dependencies = [ "ryu", ] +[[package]] +name = "console" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c050367d967ced717c04b65d8c619d863ef9292ce0c5760028655a2fb298718c" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "terminal_size 0.1.17", + "winapi", +] + [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -486,6 +505,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctor" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "dashmap" version = "4.0.2" @@ -523,6 +552,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690" +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.10.5" @@ -577,6 +612,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "encoding_rs" version = "0.8.31" @@ -1635,6 +1676,22 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "insta" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581d4e3314cae4536e5d22ffd23189d4a374696c5ef733eadafae0ed273fd303" +dependencies = [ + "console", + "lazy_static", + "linked-hash-map", + "pest", + "pest_derive", + "serde", + "similar", + "yaml-rust", +] + [[package]] name = "instant" version = "0.1.12" @@ -1760,9 +1817,9 @@ dependencies = [ [[package]] name = "linked-hash-map" -version = "0.5.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" @@ -1971,16 +2028,19 @@ dependencies = [ "git-testtools", "git2", "image", + "insta", "lazy_static", "libc", "owo-colors", + "pretty_assertions", "regex", "serde", "serde_json", "serde_yaml", + "strip-ansi-escapes", "strum", "tera", - "terminal_size", + "terminal_size 0.2.1", "time", "time-humanize", "tokei", @@ -1994,6 +2054,15 @@ version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "029d8d0b2f198229de29dca79676f2738ff952edf3fde542eb8bf94d8c21b435" +[[package]] +name = "output_vt100" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" +dependencies = [ + "winapi", +] + [[package]] name = "owo-colors" version = "3.5.0" @@ -2196,6 +2265,18 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "pretty_assertions" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755" +dependencies = [ + "ctor", + "diff", + "output_vt100", + "yansi", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -2509,6 +2590,12 @@ dependencies = [ "libc", ] +[[package]] +name = "similar" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ac7f900db32bf3fd12e0117dd3dc4da74bc52ebaac97f39668446d89694803" + [[package]] name = "siphasher" version = "0.3.10" @@ -2539,6 +2626,15 @@ dependencies = [ "lock_api", ] +[[package]] +name = "strip-ansi-escapes" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "011cbb39cf7c1f62871aea3cc46e5817b0937b49e9447370c93cacbe93a766d8" +dependencies = [ + "vte", +] + [[package]] name = "strsim" version = "0.8.0" @@ -2649,6 +2745,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "terminal_size" version = "0.2.1" @@ -2933,6 +3039,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1" +[[package]] +name = "utf8parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" + [[package]] name = "vcpkg" version = "0.2.15" @@ -2951,6 +3063,27 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vte" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983" +dependencies = [ + "arrayvec 0.5.2", + "utf8parse", + "vte_generate_state_changes", +] + +[[package]] +name = "vte_generate_state_changes" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "walkdir" version = "2.3.2" @@ -3163,6 +3296,12 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + [[package]] name = "zstd" version = "0.11.2+zstd.1.5.2" diff --git a/Cargo.toml b/Cargo.toml index 62eb28431..f6bc44297 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,9 @@ section = "utility" [dev-dependencies] git-testtools = "0.9.0" +strip-ansi-escapes = "0.1.1" +pretty_assertions = "1.3.0" +insta = { version = "1.21.0", features = ["json", "redactions"] } [dependencies] anyhow = "1.0.65" diff --git a/src/cli.rs b/src/cli.rs index e3f627edb..858084465 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -151,6 +151,38 @@ pub struct Config { pub completion: Option, } +impl Default for Config { + fn default() -> Config { + Config { + input: PathBuf::from("."), + ascii_input: Default::default(), + ascii_language: Default::default(), + ascii_colors: Default::default(), + disabled_fields: Default::default(), + image: Default::default(), + image_protocol: Default::default(), + color_resolution: 16, + no_bold: Default::default(), + no_merges: Default::default(), + no_color_palette: Default::default(), + number_of_authors: 3, + exclude: Default::default(), + no_bots: Default::default(), + languages: Default::default(), + package_managers: Default::default(), + output: Default::default(), + true_color: When::Auto, + show_logo: When::Always, + text_colors: Default::default(), + iso_time: Default::default(), + email: Default::default(), + include_hidden: Default::default(), + r#type: vec![LanguageType::Programming, LanguageType::Markup], + completion: Default::default(), + } + } +} + pub fn print_supported_languages() -> Result<()> { for l in Language::iter() { println!("{}", l); @@ -199,13 +231,13 @@ mod test { #[test] fn test_default_config() { - let config = get_default_config(); + let config: Config = Default::default(); assert_eq!(config, Config::parse_from(&["onefetch"])) } #[test] fn test_custom_config() { - let mut config = get_default_config(); + let mut config: Config = Default::default(); config.number_of_authors = 4; config.input = PathBuf::from("/tmp/folder"); config.no_merges = true; @@ -255,36 +287,6 @@ mod test { fn test_config_with_text_colors_but_out_of_bounds() { assert!(Config::try_parse_from(&["onefetch", "--text-colors", "17"]).is_err()) } - - fn get_default_config() -> Config { - Config { - input: PathBuf::from("."), - ascii_input: Default::default(), - ascii_language: Default::default(), - ascii_colors: Default::default(), - disabled_fields: Default::default(), - image: Default::default(), - image_protocol: Default::default(), - color_resolution: 16, - no_bold: Default::default(), - no_merges: Default::default(), - no_color_palette: Default::default(), - number_of_authors: 3, - exclude: Default::default(), - no_bots: Default::default(), - languages: Default::default(), - package_managers: Default::default(), - output: Default::default(), - true_color: When::Auto, - show_logo: When::Always, - text_colors: Default::default(), - iso_time: Default::default(), - email: Default::default(), - include_hidden: Default::default(), - r#type: vec![LanguageType::Programming, LanguageType::Markup], - completion: Default::default(), - } - } } #[derive(Clone, Debug)] diff --git a/src/info/mod.rs b/src/info/mod.rs index 529d57216..a692e97ef 100644 --- a/src/info/mod.rs +++ b/src/info/mod.rs @@ -148,12 +148,16 @@ impl std::fmt::Display for Info { } impl Info { - pub fn new(config: &Config) -> Result { - let git_repo = git_repository::discover(&config.input)?; - let repo_path = git_repo + pub fn init_repo_path(repo: &git_repository::Repository) -> Result { + Ok(repo .work_dir() .context("please run onefetch inside of a non-bare git repository")? - .to_owned(); + .to_owned()) + } + + pub fn new(config: &Config) -> Result { + let git_repo = git_repository::discover(&config.input)?; + let repo_path = Info::init_repo_path(&git_repo)?; let languages_handle = std::thread::spawn({ let ignored_directories = config.exclude.clone(); @@ -323,7 +327,11 @@ mod tests { use super::*; use crate::ui::num_to_color; use clap::Parser; + use git_repository::{open, ThreadSafeRepository}; + use git_testtools; + use insta::assert_json_snapshot; use owo_colors::AnsiColors; + use pretty_assertions::assert_eq; #[test] fn test_get_style() -> Result<()> { @@ -364,4 +372,85 @@ mod tests { ); Ok(()) } + + type Result = std::result::Result>; + + #[test] + fn test_bare_repo() -> Result { + let repo_path = git_testtools::scripted_fixture_repo_read_only("bare_repo.sh").unwrap(); + let safe_repo = + ThreadSafeRepository::open_opts(repo_path, open::Options::isolated()).unwrap(); + let repo = safe_repo.to_thread_local(); + let res = Info::init_repo_path(&repo); + assert!(res.is_err(), "oops, info was returned on a bare git repo"); + assert_eq!( + res.unwrap_err().to_string(), + "please run onefetch inside of a non-bare git repository" + ); + Ok(()) + } + + fn assert_info_str_matches(config: &Config, re: &Regex) { + let info = Info::new(config).unwrap(); + let info_str = format!("{}", info); + let info_u8 = strip_ansi_escapes::strip(&info_str).unwrap(); + let simple_info_str = std::str::from_utf8(&info_u8).unwrap(); + assert!( + re.is_match(&simple_info_str), + "OOPS, REGEX\n{}\nDOESNT MATCH\n{}", + re.to_string(), + simple_info_str + ); + } + + #[test] + fn test_language_repo() -> Result { + let repo_path = git_testtools::scripted_fixture_repo_read_only_with_args( + "language_repo.sh", + ["verilog"], + ) + .unwrap(); + let safe_repo = + ThreadSafeRepository::open_opts(repo_path.join("verilog"), open::Options::isolated())?; + let repo = safe_repo.to_thread_local(); + + // TEST JSON SERILIZER FIRST + let mut config: Config = Config { + input: repo.path().to_path_buf(), + ..Default::default() + }; + let info = Info::new(&config).unwrap(); + assert_json_snapshot!( + info, + { + ".gitVersion" => "git version", + ".head.short_commit_id" => "short commit" + } + ); + + // TEST FORMAT FUNCTION DEFAULT Config + let expected_regex = include_str!("../../tests/regex/test_verilog_repo.stdout.regex"); + let re = Regex::new(&expected_regex).unwrap(); + assert_info_str_matches(&config, &re); + + // TEST FORMAT FUNCTION Config true_color Always + config.true_color = When::Always; + assert_info_str_matches(&config, &re); + + // TEST FORMAT FUNCTION Config true_color Never + config.true_color = When::Never; + assert_info_str_matches(&config, &re); + + // TEST FORMAT FUNCTION Config no_bots default regex + config.no_bots.replace(None); + assert_info_str_matches(&config, &re); + + // TEST FORMAT FUNCTION Config no_bots user provided regex + config + .no_bots + .replace(Some(MyRegex(Regex::from_str(r"(b|B)ot")?))); + assert_info_str_matches(&config, &re); + + Ok(()) + } } diff --git a/src/info/snapshots/onefetch__info__tests__language_repo.snap b/src/info/snapshots/onefetch__info__tests__language_repo.snap new file mode 100644 index 000000000..092def9fe --- /dev/null +++ b/src/info/snapshots/onefetch__info__tests__language_repo.snap @@ -0,0 +1,55 @@ +--- +source: src/info/mod.rs +expression: info +--- +{ + "gitUsername": "onefetch-committer-name", + "gitVersion": "git version", + "project": { + "repo_name": "repo", + "number_of_branches": 0, + "number_of_tags": 1 + }, + "head": { + "short_commit_id": "short commit", + "refs": [ + "main" + ] + }, + "version": "tag1", + "created": "22 years ago", + "languages": [ + { + "language": "Verilog", + "percentage": 100.0 + } + ], + "dependencies": "1 (cargo)", + "authors": [ + { + "name": "Author Four", + "email": "author4@example.org", + "nbr_of_commits": 1, + "contribution": 25 + }, + { + "name": "Author One", + "email": "author1@example.org", + "nbr_of_commits": 1, + "contribution": 25 + }, + { + "name": "Author Three", + "email": "author3@example.org", + "nbr_of_commits": 1, + "contribution": 25 + } + ], + "lastChange": "22 years ago", + "contributors": 4, + "repoUrl": "https://github.com/user/repo.git", + "numberOfCommits": "4", + "linesOfCode": 4, + "repoSize": "22 B", + "license": "MIT" +} diff --git a/tests/fixtures/bare_repo.sh b/tests/fixtures/bare_repo.sh new file mode 100644 index 000000000..d42c782a8 --- /dev/null +++ b/tests/fixtures/bare_repo.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -eu -o pipefail + +git init -q --bare diff --git a/tests/fixtures/language_repo.sh b/tests/fixtures/language_repo.sh new file mode 100644 index 000000000..f51f40464 --- /dev/null +++ b/tests/fixtures/language_repo.sh @@ -0,0 +1,74 @@ +#!/bin/bash +set -eu -o pipefail + +case "${1}" in + verilog) + LANG_DIR="verilog" + LANG_EXT="vg" + ;; + *) + echo "OOPS, ARGUMENT EXPECTED TO BE ONE OF THESE VALUES:" + echo " verilog for language type verilog" + exit + ;; +esac + +mkdir ${LANG_DIR} +cd ${LANG_DIR} + +git init -q + +# BOTH NAME AND EMAIL ARE NEEDED FOR RECOGNITION +git config --local --add "committer.name" "onefetch-committer-name" +git config --local --add "committer.email" "onefetch-committer-email@onefetch.com" + +git config --local --add "user.name" "onefetch-user-name" +git config --local --add "user.email" "onefetch-user-email@onefetch.com" + +git config --local --add "author.name" "onefetch-author-name" +git config --local --add "author.email" "onefetch-author-email@onefetch.com" + +git remote add origin https://github.com/user/repo.git + +git checkout -b main +touch code.${LANG_EXT} +git add code.${LANG_EXT} +git commit -q -m c1 --author="Author One " +git tag tag1 +echo hello >> code.${LANG_EXT} +git add code.${LANG_EXT} +git commit -q -m c2 --author="Author Two " +echo world >> code.${LANG_EXT} +git add code.${LANG_EXT} +git commit -q -m c3 --author="Author Three " +echo something >> code.${LANG_EXT} +git add code.${LANG_EXT} +git commit -q -m c4 --author="Author Four " +echo more >> code.${LANG_EXT} + +echo "[dependencies]" > Cargo.toml +echo 'anyhow = "1.0.65"' >> Cargo.toml + +cat > LICENSE << '__LICENSE__' +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +__LICENSE__ + + diff --git a/tests/regex/test_verilog_repo.stdout.regex b/tests/regex/test_verilog_repo.stdout.regex new file mode 100644 index 000000000..05c5e60c9 --- /dev/null +++ b/tests/regex/test_verilog_repo.stdout.regex @@ -0,0 +1,21 @@ +(?x)# ESCAPED SPACES ARE USED WHEN A SPACE MATCH IS EXPLICITLY NEEDED +onefetch-committer-name\ ~\ git\ version\ ([^\r\n]+)\r?\n # MATCH LINE 1 CAPTURE GIT VERSION +-{37,}\r?\n # MATCH LINE 2 +Project:\ repo\ \(1\ tag\)\r?\n # MATCH LINE 3 +HEAD:\ ([^\ ]+)\ \(main\)\r?\n # MATCH LINE 4 CAPTURE HEAD SHORT HASH +Pending:\ 1\+-\ 2\+\r?\n # MATCH LINE 5 +Version:\ tag1\r?\n # MATCH LINE 6 +Created:\ 22\ years\ ago\r?\n # MATCH LINE 7 +Language:[^\r\n]+\r?\n # MATCH LINE 8 SKIP JUNK AFTER COLON +[^V]+Verilog\ \(100.0\ %\)[^\r\n]+\r?\n # MATCH LINE 9 SKIP JUNK BEFORE VERILOG +Dependencies:\ 1\ \(cargo\)\r?\n # MATCH LINE 10 +Authors:\ 25%\ Author\ ([^\ ]+)\ 1\r?\n # MATCH LINE 11 CAPTURE AUTHOR NUMBER +([^2]+)(25%\ Author\ ([^\ ]+)\ 1)\r?\n # MATCH LINE 12 CAPTURE AUTHOR NUMBER +([^2]+)(25%\ Author\ ([^\ ]+)\ 1)\r?\n # MATCH LINE 13 CAPTURE AUTHOR NUMBER +Last\ change:\ 22\ years\ ago\r?\n # MATCH LINE 14 +Contributors:\ 4\r?\n # MATCH LINE 15 +Repo:\ https://github.com/user/repo.git\r?\n # MATCH LINE 16 +Commits:\ 4\r?\n # MATCH LINE 17 +Lines\ of\ code:\ 4\r?\n # MATCH LINE 18 +Size:\ 22\ B\ \(1\ file\)\r?\n # MATCH LINE 19 +License:\ MIT # MATCH LINE 20