From 85a9d525ebee0ce65e1597ec06ab24f71eb94725 Mon Sep 17 00:00:00 2001 From: Sebastian Gniazdowski Date: Thu, 1 Nov 2018 17:22:42 +0100 Subject: [PATCH] ZPA: Initial commit of a working evaluation-script, before emoji support --- zsh-plugin-assesor/git-process-output.zsh | 128 ++++++++++++ zsh-plugin-assesor/zpa.config | 2 + zsh-plugin-assesor/zsh-plugin-assessor | 238 ++++++++++++++++++++++ 3 files changed, 368 insertions(+) create mode 100755 zsh-plugin-assesor/git-process-output.zsh create mode 100644 zsh-plugin-assesor/zpa.config create mode 100755 zsh-plugin-assesor/zsh-plugin-assessor diff --git a/zsh-plugin-assesor/git-process-output.zsh b/zsh-plugin-assesor/git-process-output.zsh new file mode 100755 index 0000000..223c8ad --- /dev/null +++ b/zsh-plugin-assesor/git-process-output.zsh @@ -0,0 +1,128 @@ +#!/usr/bin/env zsh + +# Copyright (c) 2018 Sebastian Gniazdowski +# +# This Zsh script is to be fed with Git progress-bar output +# (normally occuring on stderr, so the piping "|&" is needed, +# it redirects both stdout and stderr), induced by --progress +# Git option (for non-terminal stderr, Git skips the progress +# bar and it has to be asked to still generate it). +# +# The script shows animated progress bar instead of the text +# progress data that Git normally shows. +# +# Example: +# git clone --progress https://github.com/... |& git-process-output.zsh +# +# The script can be also used to mute Git's progress bar (keeping +# the error and authentication messages) by passing an option "-q" +# (to the script). + +emulate -LR zsh -o typesetsilent -o extendedglob -o warncreateglobal + +trap "tput cnorm" EXIT +trap "tput cnorm" INT +trap "tput cnorm" TERM + +local first=1 + +# Code by leoj3n +timeline() { + local sp='ā–šā–ž'; sp="${sp:$2%2:1}" + local bar="$(print -f "%-$2sā–“%$(($3-$2))s" "${sp}" "${sp}")" + print -f "%s %s" "${bar// /ā–‘}" "" +} + +# $1 - n. of objects +# $2 - packed objects +# $3 - total objects +# $4 - receiving percentage +# $5 - resolving percentage +print_my_line() { + print -nr -- "OBJ: $1, PACKED: $2/$3${${4:#...}:+, RECEIVING: $4%}${${5:#...}:+, RESOLVING: $5%} " + print -n $'\015' +} + +print_my_line_compress() { + print -nr -- "OBJ: $1, PACKED: $2/$3, COMPRESS: $4%${${5:#...}:+, RECEIVING: $5%}${${6:#...}:+, RESOLVING: $6%} " + print -n $'\015' +} + +integer have_1_counting=0 have_2_total=0 have_3_receiving=0 have_4_deltas=0 have_5_compress=0 +integer counting_1=0 total_2=0 total_packed_2=0 receiving_3=0 deltas_4=0 compress_5=0 +integer loop_count=0 + +IFS='' + +tput civis + +{ command perl -pe 'BEGIN { $|++; $/ = \1 }; tr/\r/\n/' || \ + gstdbuf -o0 gtr '\r' '\n' || \ + cat } |& \ +while read -r line; do + (( ++ loop_count )) + if [[ "$line" = "Cloning into"* ]]; then + #print; print $line + continue + elif [[ "$line" = (#i)*user*name* || "$line" = (#i)*password* ]]; then + print; print $line + continue + elif [[ "$line" = remote:*~*(Counting|Total|Compressing|Enumerating)* || "$line" = fatal:* ]]; then + print $line + continue + fi + if [[ "$line" = (#b)"remote: Counting objects:"[\ ]#([0-9]##)(*) ]]; then + have_1_counting=1 + counting_1="${match[1]}" + fi + if [[ "$line" = (#b)"remote: Enumerating objects:"[\ ]#([0-9]##)(*) ]]; then + have_1_counting=1 + counting_1="${match[1]}" + fi + if [[ "$line" = (#b)*"remote: Total"[\ ]#([0-9]##)*"pack-reused"[\ ]#([0-9]##)* ]]; then + have_2_total=1 + total_2="${match[1]}" total_packed_2="${match[2]}" + fi + if [[ "$line" = (#b)"Receiving objects:"[\ ]#([0-9]##)%* ]]; then + have_3_receiving=1 + receiving_3="${match[1]}" + fi + if [[ "$line" = (#b)"Resolving deltas:"[\ ]#([0-9]##)%* ]]; then + have_4_deltas=1 + deltas_4="${match[1]}" + fi + if [[ "$line" = (#b)"remote: Compressing objects:"[\ ]#([0-9]##)"%"(*) ]]; then + have_5_compress=1 + compress_5="${match[1]}" + fi + + if (( loop_count >= 2 )); then + integer pr + (( pr = have_4_deltas ? deltas_4 / 10 : ( + have_3_receiving ? receiving_3 / 10 : ( + have_5_compress ? compress_5 / 10 : ( ( ( loop_count - 1 ) / 14 ) % 10 ) + 1 ) ) )) + + [[ "$1" != "-q" ]] &&timeline "" $pr 11 + + if (( have_5_compress )); then + [[ "$1" != "-q" ]] && print_my_line_compress "${${${(M)have_1_counting:#1}:+$counting_1}:-...}" \ + "${${${(M)have_2_total:#1}:+$total_packed_2}:-0}" \ + "${${${(M)have_2_total:#1}:+$total_2}:-0}" \ + "${${${(M)have_5_compress:#1}:+$compress_5}:-...}" \ + "${${${(M)have_3_receiving:#1}:+$receiving_3}:-...}" \ + "${${${(M)have_4_deltas:#1}:+$deltas_4}:-...}" + else + [[ "$1" != "-q" ]] && print_my_line "${${${(M)have_1_counting:#1}:+$counting_1}:-...}" \ + "${${${(M)have_2_total:#1}:+$total_packed_2}:-0}" \ + "${${${(M)have_2_total:#1}:+$total_2}:-0}" \ + "${${${(M)have_3_receiving:#1}:+$receiving_3}:-...}" \ + "${${${(M)have_4_deltas:#1}:+$deltas_4}:-...}" + fi + fi +done + +[[ "$1" != "-q" ]] && print + +tput cnorm + +# vim:ft=zsh:et:sw=4:sts=4 diff --git a/zsh-plugin-assesor/zpa.config b/zsh-plugin-assesor/zpa.config new file mode 100644 index 0000000..e8567a4 --- /dev/null +++ b/zsh-plugin-assesor/zpa.config @@ -0,0 +1,2 @@ +zsh_control_bin="zsh" # Select the zsh binary that is to + # run the file `zsh-plugin-assessor' diff --git a/zsh-plugin-assesor/zsh-plugin-assessor b/zsh-plugin-assesor/zsh-plugin-assessor new file mode 100755 index 0000000..9d0b005 --- /dev/null +++ b/zsh-plugin-assesor/zsh-plugin-assessor @@ -0,0 +1,238 @@ +#!/bin/sh + +# Copyright (c) 2018 Sebastian Gniazdowski +# +# This script parses and processes the data in the unixorn/awesone-zsh-plugins +# README.md page. It extracts plugins from the "Plugins" section of the +# document, clones each of them, runs a few git commands to establish facts +# about the plugin and outputs the plugin's overall score. +# +# It then creates a README.md_new file with the scoress visible near each +# plugin's name. + +## +## CONFIGURATION, RESTART TO LEAVE /bin/sh +## +## Via /bin/sh running, to allow selection via configuration +## file, of the `zsh' binary that is to run this script +## + +ZERO="$0" +ZPA_DIR="${ZERO%/*}" +[ "$ZPA_DIR" = "${ZPA_DIR#/}" ] && ZPA_DIR="$PWD/$ZPA_DIR" + +[ "x$ZPA_CONFIG" = "x" ] && { + if [ -f ${XDG_CONFIG_HOME:-$HOME/.config}/zsh-plugin-assessor/zsd.config ]; then + ZPA_CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/zsh-plugin-assessor/zsd.config" + elif [ -f "${ZPA_DIR}/zpa.config" ]; then + ZPA_CONFIG="${ZPA_DIR}/zpa.config" + elif [ -f /usr/local/share/zsh-plugin-assessor/zpa.config ]; then + ZPA_CONFIG="/usr/local/share/zsh-plugin-assessor/zpa.config" + elif [ -f /usr/share/zsh-plugin-assessor/zpa.config ]; then + ZPA_CONFIG="/usr/share/zsh-plugin-assessor/zpa.config" + elif [ -f /opt/share/zsh-plugin-assessor/zpa.config ]; then + ZPA_CONFIG="/opt/share/zsh-plugin-assessor/zpa.config" + fi + + [ "x$ZPA_CONFIG" != "x" ] && { + echo "Reading configuration from: ${ZPA_CONFIG/$HOME/~}" + export ZPA_CONFIG + . "$ZPA_CONFIG" + } +} || { + . "$ZPA_CONFIG" +} + +[ -z "$zsh_control_bin" ] && zsh_control_bin="zsh" + +if [ -z "$ZSH_VERSION" ]; then + args="\"$0\"" + for arg; do + args="$args \"$arg\"" + done + exec /usr/bin/env "$zsh_control_bin" -f -c "source $args" +fi + +## +## START +## +## Finally in a Zsh, the Zsh binary possibly selected by zpa.conf +## + +emulate -R zsh -o extendedglob -o typesetsilent -o warncreateglobal + +zmodload zsh/datetime || { print -r -- "Module zsh/datetime is needed, aborting"; exit 1; } +zmodload zsh/zutil || { print -r -- "Module zsh/zutil is needed, aborting"; exit 1; } + +print -r -- "Running under Zsh-binary:$zsh_control_bin, version:$ZSH_VERSION" +print + +[[ -z "$1" ]] && { + print -r -- "Usage: zsh-plugin-assessor {file.md}" + print -r -- " e.g.: zsh-plugin-assessor ./README.md" + exit 0 +} + +[[ ! -e "$1" ]] && { + print -r -- "The input file ($1) doesn't exist, aborting" + exit 1 +} + +[[ ! -f "$1" ]] && { + print -r -- "The input file ($1) isn't a regular file, aborting" + exit 1 +} + +[[ ! -r "$1" ]] && { + print -r -- "The input file ($1) is unreadable, aborting" + exit 1 +} + +typeset -g INPUT_FILE_PATH="${${(M)1:#/*}:-$PWD/$1}" INPUT_FILE_CONTENTS="$(<$1)" +typeset -gi NUMBER_OF_PLUGINS=0 CURRENT_LINE=0 DEBUG_PLUGIN_COUNT_LIMIT=0 +typeset -ga input_file_lines gathered_plugins plugin_scores +typeset -gA plugin_to_url plugin_to_line_num plugin_to_score +input_file_lines=( "${(@f)INPUT_FILE_CONTENTS}" ) + +print -r -- "The input file has ${#input_file_lines} lines" + +# +# Extract plugins +# + +typeset -g LINE IN_PLUGINS_SECTION=0 +for LINE in "${input_file_lines[@]}"; do + (( ++ CURRENT_LINE )) + if (( ! IN_PLUGINS_SECTION )); then + [[ "$LINE" = "## Plugins" ]] && IN_PLUGINS_SECTION=1 + else + if [[ "$LINE" = "##"[[:blank:]]##[a-zA-Z0-9]##* ]]; then + IN_PLUGINS_SECTION=0 + print -r -- "Found #$NUMBER_OF_PLUGINS plugins" + elif (( DEBUG_PLUGIN_COUNT_LIMIT > 0 && NUMBER_OF_PLUGINS >= DEBUG_PLUGIN_COUNT_LIMIT )); then + IN_PLUGINS_SECTION=0 + # \[[^\]]##\] - plugin name (## works like + in regex) + # \([^\)]##\) - plugin URL + elif [[ "$LINE" = (#b)\*[[:blank:]]##\[([^\]]##)\]\(([^\)]##)\)[[:blank:]]##* ]]; then + (( ++ NUMBER_OF_PLUGINS )) + gathered_plugins+=( "${match[1]}" ) + plugin_to_url+=( "${match[1]}" "${match[2]}" ) + plugin_to_line_num+=( "${match[1]}" "$CURRENT_LINE" ) + plugin_scores+=( "0000" ) + fi + fi +done + +# +# Prepare working directory (in XDG_CACHE_HOME or ~/.config) +# + +typeset -g WORK_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/zsh-plugin-assessor/clones" + +print -r -- "== Removing clones from previous ZPA run... ==" + +builtin cd -q -- "${WORK_DIR:h}" +command rm -rf -- "$WORK_DIR" +command mkdir -p -- "$WORK_DIR" +builtin cd -q -- "$WORK_DIR" + +print -r -- "== Working in $WORK_DIR ==" +print + +# +# Clone each plugin, establish its score +# + +typeset -g PLUGIN +for PLUGIN in "${gathered_plugins[@]}"; do + local URL="${plugin_to_url[$PLUGIN]}" + + if [[ -n "$OPT_VERBOSE" ]]; then + print + command git clone --progress "$URL" "$PLUGIN" |& "${ZPA_DIR}/git-process-output.zsh" + else + command git clone --progress "$URL" "$PLUGIN" |& "${ZPA_DIR}/git-process-output.zsh" -q + fi + + # + # The basic score [ABCD] + # + + # Number of commits in master + typeset -g DATA_COMMIT_COUNT=$(command git -C "$PLUGIN" rev-list --count master 2>/dev/null) + # Time of last commit in master + typeset -g DATA_LAST_COMMIT_DATE=$(command git -C "$PLUGIN" log --max-count=1 --pretty=format:%ct master 2>/dev/null) + # As above, for ^master + typeset -g DATA_COMMIT_COUNT_ALL=$(command git -C "$PLUGIN" rev-list --all --count '^master' 2>/dev/null) + # As above, for ^master + typeset -g DATA_LAST_COMMIT_DATE_BRANCHES=$(command git -C "$PLUGIN" log --max-count=1 --pretty=format:%ct --all '^master' 2>/dev/null) + + # + + # The score is e.g. 1112 for 1-active, 1-50-commits, 1-active, 2-100 commits + integer score_activity=0 score_commit_count=0 score_activity_branches=0 score_commit_count_branches=0 + + # Master + (( DATA_COMMIT_COUNT >= 50 )) && score_commit_count=1 + (( DATA_COMMIT_COUNT >= 100 )) && score_commit_count=2 + if (( EPOCHSECONDS - ${DATA_LAST_COMMIT_DATE:-0} <= 6*30.5*24*60*60 )); then + score_activity=1 + fi + if (( EPOCHSECONDS - ${DATA_LAST_COMMIT_DATE:-0} <= 3*30.5*24*60*60 )); then + score_activity=2 + fi + + # Branches-only + (( DATA_COMMIT_COUNT_ALL >= 50 )) && score_commit_count_branches=1 + (( DATA_COMMIT_COUNT_ALL >= 100 )) && score_commit_count_branches=2 + if (( EPOCHSECONDS - ${DATA_LAST_COMMIT_DATE_BRANCHES:-0} <= 6*30.5*24*60*60 )); then + score_activity_branches=1 + fi + if (( EPOCHSECONDS - ${DATA_LAST_COMMIT_DATE_BRANCHES:-0} <= 3*30.5*24*60*60 )); then + score_activity_branches=2 + fi + + integer score=score_commit_count*1000+score_activity*100+score_commit_count_branches*10+score_activity_branches + + print "$PLUGIN: ${(l:4::0:)score}" + + plugin_to_score[$PLUGIN]="${(l:4::0:)score}" +done + +# +# Create new README.md with the score +# occuring near each plugin's name +# + +typeset -g OUTPUT_FILE_PATH="${INPUT_FILE_PATH}"_new + +# Output all lines preceding "Plugins"-section body +integer top_block_last_line_num=$(( ${plugin_to_line_num[${gathered_plugins[1]}]} - 1 )) +print -r -- "${(F)input_file_lines[1,top_block_last_line_num]}" >! "$OUTPUT_FILE_PATH" + +integer line_num + +for PLUGIN in "${gathered_plugins[@]}"; do + line_num="${plugin_to_line_num[$PLUGIN]}" start_pos=0 + LINE="${input_file_lines[line_num]}" + + # \[[^\]]##\] - plugin name + # \([^\)]##\) - plugin URL + # \*\\\[[0-9]##\\\]\* - the evaluation, in markdown, i.e. *\[0000\]* + if [[ "$LINE" = (#b)\*[[:blank:]]##\[([^\]]##)\]\(([^\)]##)\)[[:blank:]]##(\*\\\[[0-9]##\\\]\*[[:blank:]]##)* ]]; then + # Remove previous e.g. *[0001]* + LINE[${mbegin[3]},${mend[3]}]="" + start_pos=$(( mbegin[3] -1 )) + elif [[ "$LINE" = (#b)\*[[:blank:]]##\[([^\]]##)\]\(([^\)]##)\)[[:blank:]]##* ]]; then + start_pos=$(( mend[2] + 2 )) + fi + + LINE[start_pos]=' *\['"${plugin_to_score[$PLUGIN]}"'\]* ' + print -r -- "$LINE" >>! "$OUTPUT_FILE_PATH" + +done + +# Output unchanged trailing document part +print -r -- "${(F)input_file_lines[line_num+1,-1]}" >>! "$OUTPUT_FILE_PATH" + +# vim:ft=zsh:et:sw=4:sts=4