# Text formatting utilities bold=$(tput bold) underline=$(tput sgr 0 1) reset=$(tput sgr0) purple=$(tput setaf 171) red=$(tput setaf 1) green=$(tput setaf 76) tan=$(tput setaf 3) blue=$(tput setaf 38) header() { printf "\n${bold}${purple}========== %s ==========${reset}\n" "$@" } arrow() { printf " ➜ %s\n" "$@" } success() { printf "${green} ✔ %s${reset}\n" "$@" } error() { printf "${red} ✖ %s${reset}\n" "$@" } warning() { printf "${tan} ➜ %s${reset}\n" "$@" } underline() { printf "${underline}${bold}%s${reset}\n" "$@" } bold() { printf "${bold}%s${reset}\n" "$@" } note() { printf "${underline}${bold}${blue}Note:${reset} ${blue}%s${reset}\n" "$@" } function substring() { local _length=${#1} local _start=${2} local _end=${3} echo ${1:_start:_length-_end} } # Browser interaction utilities function browse() { open -na "$DEFAULT_BROWSER" --args "$1" } function google() { browse "https://www.google.com/search?q=$*" } function stackoverflow() { browse "https://www.google.com/search?q=site:stackoverflow.com $*" } function github() { browse "https://github.com/search?q=$*" } function hacker() { browse "https://hn.algolia.com/?sort=byDate&query=$*" } function gmail() { browse "https://mail.google.com/mail/u/0" } function gmail2() { browse "https://mail.google.com/mail/u/1" } function cicdboard() { browse "$JIRA_URL/secure/RapidBoard.jspa?rapidView=457&view=planning.nodetail" } function cicddashboard() { browse "$JIRA_URL/secure/Dashboard.jspa?selectPageId=13131" } function calendar() { browse "https://calendar.google.com/calendar/r?tab=mc" } function asana() { browse "https://app.asana.com" } function confluencetasks() { browse "$CONFLUENCE_URL/plugins/inlinetasks/mytasks.action" } function trello_web() { browse "$TRELLO_BOARD_URL" } function bookmarks() { browse "https://github.com/MorganGeek/bookmarks/blob/master/README.md" } function spotify() { browse "https://open.spotify.com/search/$*" } function lob() { browse "https://lobste.rs" } function archive() { browse "https://web.archive.org/web/*/$*" } function iknowwhatyoudownload() { browse "https://iknowwhatyoudownload.com/en/peer/?ip=$*" } # Jira utilities function issues() { jira issue jql "status = Open AND text ~ \"$*\" ORDER BY Created DESC" } function istherenewissues() { LASTISSUE=$(newissues | sed $'s,\x1b\\[[0-9;]*[a-zA-Z],,g' | awk 'FNR==2{print $2}') if [[ -f "$HOME/.newjiraissue" ]]; then previous_jira_issue=$(\cat "$HOME/.newjiraissue" | sed $'s,\x1b\\[[0-9;]*[a-zA-Z],,g') # the sed part is for removing output colors if [ "$LASTISSUE" != "$previous_jira_issue" ]; then newissues else success "no new issue" fi fi echo "$LASTISSUE" >"$HOME/.newjiraissue" } # Prolog / Logtalk function logtalk() { path_swilgt=$(find /usr/local/Cellar/logtalk -name "*swilgt.sh" 2>/dev/null) sh "$path_swilgt" } # History / Aliases helpers function top_commands() { local filter="$1" local max_results=${2:-'50'} history | \cat | awk '{CMD[$2]++;count++;}END { for (a in CMD)print CMD[a] " " CMD[a]/count*100 "% " a; }' G --invert-match "./" | column -c3 -s " " -t G "$filter" | sort --numeric-sort --reverse | nl H "-n$max_results" } function top_commands_full() { local filter="$1" local max_results=${2:-'50'} history | \cat | awk '{$1=$1};1' | sed 's/^[0-9 TAB]*//g' | awk '{CMD[$0]++;count++;}END { for (a in CMD)print CMD[a] " " CMD[a]/count*100 "%\t" a; }' G "$filter" | sort --numeric-sort --reverse | nl H "-n$max_results" } # Where is a function defined? function whichfunc() { whence -v $1 type -a $1 } source $HOME/Code/dotfiles/dot_scripts/suggest_readable_parameters.sh # TODO : suggest spelling fixes function suggest_code_refactoring() { #inspired by : \grep 'awk '\''{$1=$1};1'\' $HOME/Code/dotfiles/dot_zsh* header "code refactoring suggestions" while read -r line; do local short_name=$(echo "$line" | sed -E "s/='.*//g") local alias_value=$(echo "$line" | sed -E 's/[a-zA-Z_-]+=//g') local alias_value_truncated=${alias_value:1:$((${#alias_value} - 2))} \fgrep -d skip $alias_value_truncated "$@" 2>/tmp/error.log | \grep --invert-match "alias $short_name=" 1>/dev/null if [ $? -eq 0 ]; then success "found $short_name --> $alias_value_truncated" \fgrep -d skip "$alias_value_truncated" "$@" --color -n -H --line-buffered | \grep --invert-match "alias $short_name=" else local error_message=$(\cat /tmp/error.log) if [ -n "$error_message" ]; then error "issue occured when looking for $alias_value_truncated : $error_message" fi fi done < <(alias | awk -v COUNT=1 'NF>COUNT') # list of all aliases, excluding the less interesting ones (where we use one word for another) } function suggest_aliases() { local search_input_size=${1:-'50'} header "alias recommendations" while read -r line; do local matching_aliases=$(ag "$line") if [ ! -z "$matching_aliases" ]; then success "there is an alias for $line :" while read -r alias_line; do arrow "$alias_line" done < <(echo "$matching_aliases") echo else warning "no alias for $line" fi done < <(top_commands_full "" "$search_input_size" | awk '{ $1=""; $2=""; $3=""; print}' | awk 'NF' | awk '{$1=$1};1' | awk -v COUNT=1 'NF>COUNT' H "-$search_input_size") } # Sound management # Inspired by https://apple.stackexchange.com/a/213048/231885 for switching devices # and https://coderwall.com/p/22p0ja/set-get-osx-volume-mute-from-the-command-line for volume management function mute_device() { local current_device=$(SwitchAudioSource -c) local target_device="$1" if SwitchAudioSource -a | grep "$target_device" 1>/dev/null; then SwitchAudioSource -s "$1" 1>/dev/null osascript -e 'set volume output muted true' success "device $target_device muted" SwitchAudioSource -s "$current_device" 1>/dev/null if [ "$current_device" != "$target_device" ]; then arrow "switching back to $current_device" fi arrow "currently using $target_device" else error "sound device not found : $target_device (maybe it's disconnected ?)" fi } function switch_device() { local target_device="$1" if SwitchAudioSource -a | grep "$target_device" 1>/dev/null; then if SwitchAudioSource -s "$1" 1>/dev/null; then success "switched to $target_device" unmute else error "failed to switch to $target_device" fi else error "sound device not found : $target_device (maybe it's disconnected ?)" fi } # Web Crawling function aboutpage() { year=$(echo "$*" | egrep --extended-regexp --only-matching '\b[[:digit:]]{4}\b' H -n1) if [ -z "$year" ]; then year=$(curl --silent --show-error --location "$*" | tr '<' '\r' | \egrep --ignore-case "date|datetime" -A 1 | \grep --extended-regexp --only-matching '\b[[:digit:]]{4}\b' H -n1) fi author=$(curl --silent --show-error --location "$*" | tr '<' '\r' | \egrep --ignore-case "author" -A 1 | \grep --extended-regexp --only-matching '([A-Z][A-Za-z]+\s([A-Za-z ]+)*)' H -n1) title=$(curl --silent -SL "$*" | tr '<' '<\n' | \grep title -A 1 H -n1 | sed -E 's/.*(.*)<\/title>.*/\1/' | sed "s/ [^[:alnum:]]*$author//") yearint=$(($year + 0)) currentyear=$(echo $(date +"%Y")) if [ ! -z "$author" ]; then echo "by $author" fi if [ ! -z "$title" ]; then echo "-> $title" fi if [[ $yearint -ge 1970 && $yearint -le $currentyear ]]; then echo "$yearint" fi if [ ! -z "$title" ] && [ ! -z "$author" ] && [[ $yearint -ge 1970 && $yearint -le $currentyear ]]; then echo "[$author]($*) - ($yearint) $title" fi } # Extract a column from a tabular output # via https://blog.developer.atlassian.com/ten-tips-for-wonderful-bash-productivity/ function col() { awk -v col=$1 '{print $col}' } # Skip first x words in line # via https://blog.developer.atlassian.com/ten-tips-for-wonderful-bash-productivity/ function skip() { n=$(($1 + 1)) cut -d' ' -f$n- } # Code search / stats helpers cmd_loc="find . -type f \( \ -name '*.py' \ -o -name '*.rb' \ -o -name '*.go' \ -o -name '*.java' \ -o -name '*.kt' \ -o -name '*.c' -o -name '*.h' \ -o -name '*.js' \ -o -name '*.tsx' \ -o -name '*.sh' \ -o -name '*.md' \ -o -name '*.xml' \ -o -name '*.yaml' -o -name '*.yml' \ -o -name '*.groovy' \ -o -name '*.gradle' \ -o -name '*.properties' \ \) -exec \cat \{\} \; | LANG=C LC_CTYPE=C sed -e 's/^[ \t]*//;s/[ \t]*$//'" # Unique lines of code # Via https://text.causal.agency/004-uloc.txt function uloc() { eval "$cmd_loc | LANG=C LC_CTYPE=C sort --unique | wc -l" } # Top lines of code function toploc() { eval "$cmd_loc | LANG=C LC_CTYPE=C cut -c 1-100 | LANG=C LC_CTYPE=C sort | uniq -c | LANG=C LC_CTYPE=C sort --numeric-sort --reverse H -50" } function historyloc() { top_commands_full "" 5000 topshellloc } function how_in() { where="$1" shift IFS=+ curl "cht.sh/${where}/$*" } # File name references in file function filerefs() { FILENAME=${1:-README*)} \grep --extended-regexp --only-matching "\b([a-zA-Z.-]+)\.([a-z]{2,4}\b)" $FILENAME \ | sort --unique \ | egrep --invert-match --ignore-case "\.com|\.git|\.io|\net|\.org|\.se|\.me|\.fr|contributing\.md" } function invalid_file_refs() { FILENAME=${1:-"README.*"} while read -r file_ref; do arrow "processing $file_ref ..." find . -name "$file_ref" 1>/dev/null local exit_status=$? if [ $exit_status -eq 1 ]; then warning "$file_ref does not exist in the project" else success "$file_ref was found in the project" fi arrow "checking if $file_ref is present in the source code..." source_refs=$(\grep "$file_ref" * -r -l G --invert-match "$FILENAME" | wc -l 2>/dev/null | trim) if [ "$source_refs" -eq 0 ]; then error "$file_ref was not found in source code" else arrow "searching for $file_ref references in soure code..." while read -r source_ref; do success "$file_ref was found in $source_ref" done < <(\grep "$file_ref" * -r -l) fi done < <(filerefs "$FILENAME") } # Text search # run write-good, proselint, and aspell in non interactive mode, to list all mispelled words found function checkenlist() { aspell -d en list < "$1" | sort --unique --ignore-case write-good --no-passive "$1" proselint "$1" alex -q --stdin < "$1" } function checkenremote() { local target_url=$(echo "$1" | sed 's/github.com/raw.githubusercontent.com/g' | sed 's;blob/;;') curl --location --insecure --silent "$target_url" > /tmp/file && checkenlist /tmp/file && write-good --no-passive /tmp/file && proselint /tmp/file && \cat /tmp/file > alex -q --stdin } # Time management function zoomtimeboxed() { declare -i total_minutes=$1 declare -i total_seconds=$total_minutes*60 echo $total_seconds; arrow "will start zoom and leave it after $total_minutes minutes ($total_seconds seconds)" zoom; (sleep "$total_seconds" && nozoom) & } # File stats helpers # Find files bigger than X size and sort them by size function biggerthan() { find . -size "+$*" -type f -print0 | xargs -0 ls -Ssh | sort --zero-terminated } # To automatically ls when changing directory function cd() { builtin cd "$@" && ll -atr } function mouse() { case "$(uname -s)" in Darwin) sh ~/.scripts/mouse_bluetooth.sh ;; esac } # Conversion function epub2pdf() { local output_file_name=$(echo $1 | sed 's/.epub$/.pdf/') ebook-convert "$1" "$output_file_name" } function epub2mobi() { local output_file_name=$(echo $1 | sed 's/.epub$/.mobi/') ebook-convert "$1" "$output_file_name" } # Information gathering function meteo() { curl "fr.wttr.in/$*" } function rate() { curl "http://rate.sx/$*" } # Uploaders function transfer() { # check arguments if [ $# -eq 0 ]; then warning "No arguments specified. Usage:\necho transfer /tmp/test.md\ncat /tmp/test.md | transfer test.md" return 1 fi # get temporarily filename, output is written to this file show progress can be showed tmpfile=$(mktemp -t transferXXX) # upload stdin or file file=$1 if tty -s; then basefile=$(basename "$file" | sed -e 's/[^a-zA-Z0-9._-]/-/g') if [ ! -e $file ]; then error "File $file doesn't exists." return 1 fi if [ -d $file ]; then # zip directory and transfer zipfile=$(mktemp -t transferXXX.zip) cd $(dirname $file) && zip -r -q - $(basename $file) >>$zipfile curl --progress-bar --upload-file "$zipfile" "https://transfer.sh/$basefile.zip" >>$tmpfile rm -f $zipfile else # transfer file curl --progress-bar --upload-file "$file" "https://transfer.sh/$basefile" >>$tmpfile fi else # transfer pipe curl --progress-bar --upload-file "-" "https://transfer.sh/$file" >>$tmpfile fi # cat output link cat $tmpfile # cleanup rm -f $tmpfile } function checkov() { docker run -i --rm -v "$(pwd):/tf" bridgecrew/checkov -d /tf "$@"; } function vimat() { vim +/$1 $2 } function httperr() { curl --silent "https://http.cat/$1" | imgcat } # Config / Infra as code function terraform-compliance() { docker run --rm -v "$(pwd):/target" -i -t eerkunt/terraform-compliance "$@"; } function terragrunt_color() { BOLD=$(tput bold) BLACK=$(tput setaf 0) RED=$(tput setaf 1) GREEN=$(tput setaf 2) YELLOW=$(tput setaf 3) BLUE=$(tput setaf 4) CYAN=$(tput setaf 6) RESET=$(tput sgr0) REDBOLD=${RED}${BOLD} REDRESET=${RESET}${RED} BLUEBOLD=${BLUE}${BOLD} BLUERESET=${RESET}${BLUE} terragrunt ${*} 2>&1 | sed \ -e "s/\(\\[terragrunt\\] \\[.*\\]\)\( [0-9\\/]* [0-9:]*\) /${BLUEBOLD}\1${BLUERESET}\2${RESET} /" \ -e "s/\(\\[terragrunt\\]\)\( [0-9\\/]* [0-9:]*\) /${BLUEBOLD}\1${BLUERESET}\2${RESET} /" \ -e "s/\(Error: \)\(.*\)/${REDBOLD}\1${REDRESET}\2${RESET}/" \ -e "s/\(Hit multiple errors:\)/${REDBOLD}\1${RESET}/" \ -e "s/\(exit status 1\)/${RED}\1${RESET}/" \ -e "s/\( WARNING: \)\(.*\)/${YELLOW}${BOLD}\1${RESET}${YELLOW}\2${RESET}/" \ -e "s/\( Running command: \)\(.*\)/\1${CYAN}\2${RESET}/" \ -e "s/\( *.*: *\)\(\".*\"\)\( => \)\(\".*\"\)/${YELLOW}\1${RED}\2${BLACK}\3${GREEN}\4${RESET}/" \ -e "s/\( *.*: *\".*\"\)/${GREEN}\1${RESET}/" } function jenkins-cli() { local script_location=$(find $HOME/code/jenkins-cloudbees-core -name "jenkins-cli.sh") eval "$script_location $*" } # Security / Secrets helpers function passwords() { bw list items --search "$1" | jq -c '.[] | .name + " " + .login.username + ":" + .login.password + " " + .login.uris[0].uri' } function password() { bw get password "$1" } function vaultgetsecret() { local secret=$(grep -A 500 "ANSIBLE_VAULT" "$1" | awk '{$1=$1;print}' | \grep --extended-regexp --only-matching "^[0-9a-z^ ]+$") local secret_string=$(echo "\$ANSIBLE_VAULT;1.1;AES256\n$secret") echo "$secret_string" | awk '{$1=$1;print}' | ansible-vault decrypt --vault-password-file=$VAULT_PASSWORD_FILE } ### Git helpers function backupgithub() { cd "$REPO_PATH" curl --silent --location "https://api.github.com/users/$1/repos" | jq -r '.[] | .ssh_url' | xargs -n1 git clone --mirror --no-hardlinks } function gitydiff() { local path_to_file="$1" gsh "HEAD:$path_to_file" | colordiff -y - "$path_to_file" } function gcrb() { branch=$1 gcb $branch origin/$branch } function installhooks() { pre-commit install --install-hooks --overwrite --allow-missing-config } function copyhooks() { FILENAME=${1:-"$HOME/.git-template/.pre-commit-config.yaml"} cp -f "$FILENAME" ./.pre-commit-config.yaml installhooks runhooks } function git_listobjectsbysize() { tempFile=$(mktemp) IFS=$'\n' for commitSHA1 in $(git rev-list --all); do git ls-tree -r --long "$commitSHA1" >>"$tempFile" done # sort files by SHA1, de-dupe list and finally re-sort by filesize sort --key 3 "$tempFile" | uniq | sort --key 4 --numeric-sort --reverse # remove temp file rm -f "$tempFile" } function setorigin() { gra origin "$1" 2>/dev/null grset origin "$1" success "updated origin to $1" arrow "copying pre-commit hooks ..." if [[ "$1" =~ "$COMPANY_NAME" ]]; then copyhooks gitpro else copyhooks "$HOME/.git-template/.pre-commit-minimal-config.yaml" gitperso fi } function gitpushcurrentremote() { gitpushallremote "$(git_current_branch)" } function gitpushallremote() { local param_branch="$1" grv grv G push | awk '{print $1}' | while read -r remote; do if [ -z "$param_branch" ]; then arrow "pushing all branches to $remote" gp --all "$remote" else arrow "pushing $param_branch to $remote" gp "$remote" "$param_branch" fi done } function clone() { local folder=$(basename $1 | sed 's/\.git.*//g') arrow "git project identified as $folder" arrow "cloning repository ..." if gcls "$1"; then success "repository cloned" if [[ -n "$folder" ]]; then cd "$folder" || exit arrow "copying pre-commit hooks ..." if [[ "$1:u" =~ "$COMPANY_NAME:u" ]]; then copyhooks gitpro else copyhooks "$HOME/.git-template/.pre-commit-minimal-config.yaml" gitperso fi else error "unable to change current directory to : $folder" fi else error "unable to clone repository from url : $1" fi } function fork() { # FIXME : go to local copy of the repo after the fork code local folder=$(basename $1 | sed 's/\.git.*//g') gh repo fork "$1" } function git-project() { if [ -d "$REPO_PATH" ]; then REPO_PATH="$(pwd)" fi local preview='lsd --color always --icon always --group-dirs first {}' local dir=$(find "$REPO_PATH" -maxdepth 3 -type d -name ".git" | sed 's#.git$##' | fzf --select-1 --query="$*" --preview "$preview") if [[ -n "$dir" ]]; then cd "$dir" || exit fi } function gitignorefor() { local language=${1:-''} if [ ! -d "$HOME/Code/gitignore" ]; then arrow "cloning https://github.com/github/gitignore into $REPO_PATH/gitignore" git clone "https://github.com/github/gitignore $_" fi local gitignore_file=$(fd "$language" "$REPO_PATH/gitignore" H -n1) if [ ! -z "$gitignore_file" ]; then success "matched gitignore file : $gitignore_file" if [ ! -f "$(pwd)/.gitignore" ]; then arrow "you don't have a $(pwd)/.gitignore file, but that's not an issue :-) ..." fi arrow "copying the file content to your $(pwd)/.gitignore file" adhoc blockinfile -a "block='{{ lookup('file', '$gitignore_file') }}' dest='$(pwd)/.gitignore' create=yes" else error "no gitignore file found for $language" fi } # Miscellaneous helpers function colorpic() { local picture_url="$1" arrow "Colorizing $picture_url" local result_url=$(\curl -F "image=@$picture_url" -H "api-key:$COLORPIC_APIKEY" https://api.deepai.org/api/colorizer -s | jq '.output_url' | strings) success "Generated $result_url" arrow "Display in progress..." eval "\curl --silent $result_url | imgcat" } function bookmarkadd() { adhoc lineinfile -a "path=$HOME/Code/bookmarks/README.md insertafter='"$1"' line='* "$2"'" } function rssadd() { adhoc lineinfile -a "path=~/.newsboat/urls line='"$1"'" newsboat } # Via https://stackoverflow.com/a/58598185/2309958 # capture the output of a command so it can be retrieved with ret function cap () { tee /tmp/capture.out} # return the output of the most recent command that was captured by cap function ret () { \cat /tmp/capture.out } # Package / Dependencies management helpers function adhocbis() { local ansible_output=$(adhoc "$*") echo $ansible_output | sed 's/127.0.0.1.*SUCCESS/WOKE/g' } function brewadd() { brew install "$1" adhoc lineinfile -a "path=~/Brewfile line='brew \"$1\"'" } function pipadd() { pip install "$1" pip freeze >"$HOME/requirements.txt" } function goadd() { adhoc lineinfile -a "path=~/.scripts/godeps.sh line='go get -u -v $1'" go get -u -v "$1" } # Make a directory and cd to it function take() { md -p "$@" && cd "${@:$#}" } # Date / Time management helpers function endofday() { local planned_end=$(moro status 2>&1 | \grep --extended-regexp --only-matching "Working until ([0-9:]+) will make.*" | uniq | \grep --extended-regexp --only-matching "([0-9]+:[0-9]+)") local max_hour="$planned_end" local min_hour=$(current_time) if [ -z "$planned_end" ]; then local clockout=$(moro report 2>&1 | \grep --extended-regexp --only-matching "Clock out.*([0-9:]+)" | \grep --extended-regexp --only-matching "([0-9]+:[0-9]+)") max_hour="$clockout" fi if is_earlier "$min_hour" "$max_hour"; then arrow "you should keep working, it's only $min_hour while you should work until $max_hour" moro status else warning "you should stop working now because it's later than $max_hour" moro report fi } function convtimetodate() { date -j -f '%H:%M' "$1" +'%Y/%m/%d %H:%M' } function convtimetotimestamp() { date -j -f '%H:%M' "$1" +'%s' } function is_earlier() { local first=$(convtimetotimestamp "$1") local second=$(convtimetotimestamp "$2") if [ "$second" -gt "$first" ]; then true else false fi } function dl_stopwords() { curl --location --insecure --silent https://raw.githubusercontent.com/MorganGeek/bookmarks/master/stopwords.txt -o "$HOME/stopwords.txt" } function file_getwords() { dl_stopwords \cat "$1" | tr '[:upper:]' '[:lower:]' | \grep --extended-regexp --only-matching '\w{3,}' | \grep --invert-match --word-regexp --fixed-strings --file="$HOME/stopwords.txt" | \sed 's/s$//g' | \sed 's/ing$//g' | sort_count } function file_getpairs() { dl_stopwords \cat "$1" | filter_pairs } function file_dups() { \cat "$1" | sort_count } function foreach_run() { find . -name "$1" -exec "$2" {} \; } # input should be something like : 1-10 to generate one number between 1 and 10 function chance() { [[ $(shuf -i "$1" -n 1) == 1 ]] } function runiflucky() { if chance "1-10"; then if alias "$1" 2>/dev/null || (compgen -A function G "$1" 1>/dev/null && compgen -A function "$1" 1>/dev/null); then eval "$1" fi fi }