#!/bin/zsh export YSU_VERSION='1.9.0' if ! type "tput" > /dev/null; then printf "WARNING: tput command not found on your PATH.\n" printf "zsh-you-should-use will fallback to uncoloured messages\n" else NONE="$(tput sgr0)" BOLD="$(tput bold)" RED="$(tput setaf 1)" YELLOW="$(tput setaf 3)" PURPLE="$(tput setaf 5)" fi function check_alias_usage() { # Optional parameter that limits how far back history is checked # I've chosen a large default value instead of bypassing tail because it's simpler local limit="${1:-${HISTSIZE:-9000000000000000}}" local key declare -A usage for key in "${(@k)aliases}"; do usage[$key]=0 done # TODO: # Handle and (&&) + (&) # others? watch, time etc... local -a histfile_lines histfile_lines=("${(@f)$(<$HISTFILE)}") histfile_lines=("${histfile_lines[@]#*;}") local current=0 local total=${#histfile_lines} if [[ $total -gt $limit ]]; then total=$limit fi local entry for line in ${histfile_lines[@]} ; do for entry in ${(@s/|/)line}; do # Remove leading whitespace entry=${entry##*[[:space:]]} # We only care about the first word because that's all aliases work with # (this does not count global and git aliases) local word=${entry[(w)1]} if [[ -n ${usage[$word]} ]]; then (( usage[$word]++ )) fi done # print current progress (( current++ )) printf "Analysing: [$current/$total]\r" done # Clear all previous line output printf "\r\033[K" # Print ordered usage for key in ${(k)usage}; do echo "${usage[$key]}: ${(q+)key}=${(q+)aliases[$key]}" done | sort -rn -k1 } # Writing to a buffer rather than directly to stdout/stderr allows us to decide # if we want to write the reminder message before or after a command has been executed function _write_ysu_buffer() { _YSU_BUFFER+="$@" # Maintain historical behaviour by default local position="${YSU_MESSAGE_POSITION:-before}" if [[ "$position" = "before" ]]; then _flush_ysu_buffer elif [[ "$position" != "after" ]]; then (>&2 printf "${RED}${BOLD}Unknown value for YSU_MESSAGE_POSITION '$position'. ") (>&2 printf "Expected value 'before' or 'after'${NONE}\n") _flush_ysu_buffer fi } function _flush_ysu_buffer() { # It's important to pass $_YSU_BUFFER to printfs first argument # because otherwise all escape codes will not printed correctly (>&2 printf "$_YSU_BUFFER") _YSU_BUFFER="" } function ysu_message() { local DEFAULT_MESSAGE_FORMAT="${BOLD}${YELLOW}\ Found existing %alias_type for ${PURPLE}\"%command\"${YELLOW}. \ You should use: ${PURPLE}\"%alias\"${NONE}" local alias_type_arg="${1}" local command_arg="${2}" local alias_arg="${3}" # Escape arguments which will be interpreted by printf incorrectly # unfortunately there does not seem to be a nice way to put this into # a function because returning the values requires to be done by printf/echo!! command_arg="${command_arg//\%/%%}" command_arg="${command_arg//\\/\\\\}" local MESSAGE="${YSU_MESSAGE_FORMAT:-"$DEFAULT_MESSAGE_FORMAT"}" MESSAGE="${MESSAGE//\%alias_type/$alias_type_arg}" MESSAGE="${MESSAGE//\%command/$command_arg}" MESSAGE="${MESSAGE//\%alias/$alias_arg}" _write_ysu_buffer "$MESSAGE\n" } # Prevent command from running if hardcore mode enabled function _check_ysu_hardcore() { if (( ${+YSU_HARDCORE} )); then _write_ysu_buffer "${BOLD}${RED}You Should Use hardcore mode enabled. Use your aliases!${NONE}\n" kill -s INT $$ fi } function _check_git_aliases() { local typed="$1" local expanded="$2" # sudo will use another user's profile and so aliases would not apply if [[ "$typed" = "sudo "* ]]; then return fi if [[ "$typed" = "git "* ]]; then local found=false git config --get-regexp "^alias\..+$" | sort | while read key value; do key="${key#alias.}" # if for some reason, read does not split correctly, we # detect that and manually split the key and value if [[ -z "$value" ]]; then value="${key#* }" key="${key%% *}" fi if [[ "$expanded" = "git $value" || "$expanded" = "git $value "* ]]; then ysu_message "git alias" "$value" "git $key" found=true fi done if $found; then _check_ysu_hardcore fi fi } function _check_global_aliases() { local typed="$1" local expanded="$2" local found=false local tokens local key local value local entry # sudo will use another user's profile and so aliases would not apply if [[ "$typed" = "sudo "* ]]; then return fi alias -g | sort | while IFS="=" read -r key value; do key="${key## }" key="${key%% }" value="${(Q)value}" # Skip ignored global aliases if [[ ${YSU_IGNORED_GLOBAL_ALIASES[(r)$key]} == "$key" ]]; then continue fi if [[ "$typed" = *" $value "* || \ "$typed" = *" $value" || \ "$typed" = "$value "* || \ "$typed" = "$value" ]]; then ysu_message "global alias" "$value" "$key" found=true fi done if $found; then _check_ysu_hardcore fi } function _check_aliases() { local typed="$1" local expanded="$2" local found_aliases found_aliases=() local best_match="" local best_match_value="" local key local value # sudo will use another user's profile and so aliases would not apply if [[ "$typed" = "sudo "* ]]; then return fi # Find alias matches for key in "${(@k)aliases}"; do value="${aliases[$key]}" # Skip ignored aliases if [[ ${YSU_IGNORED_ALIASES[(r)$key]} == "$key" ]]; then continue fi if [[ "$typed" = "$value" || "$typed" = "$value "* ]]; then # if the alias longer or the same length as its command # we assume that it is there to cater for typos. # If not, then the alias would not save any time # for the user and so doesn't hold much value anyway if [[ "${#value}" -gt "${#key}" ]]; then found_aliases+="$key" # Match aliases to longest portion of command if [[ "${#value}" -gt "${#best_match_value}" ]]; then best_match="$key" best_match_value="$value" # on equal length, choose the shortest alias elif [[ "${#value}" -eq "${#best_match}" && ${#key} -lt "${#best_match}" ]]; then best_match="$key" best_match_value="$value" fi fi fi done # Print result matches based on current mode if [[ "$YSU_MODE" = "ALL" ]]; then for key in ${(@ok)found_aliases}; do value="${aliases[$key]}" ysu_message "alias" "$value" "$key" done elif [[ (-z "$YSU_MODE" || "$YSU_MODE" = "BESTMATCH") && -n "$best_match" ]]; then # make sure that the best matched alias has not already # been typed by the user value="${aliases[$best_match]}" if [[ "$typed" = "$best_match" || "$typed" = "$best_match "* ]]; then return fi ysu_message "alias" "$value" "$best_match" fi if [[ -n "$found_aliases" ]]; then _check_ysu_hardcore fi } function disable_you_should_use() { add-zsh-hook -D preexec _check_aliases add-zsh-hook -D preexec _check_global_aliases add-zsh-hook -D preexec _check_git_aliases add-zsh-hook -D precmd _flush_ysu_buffer } function enable_you_should_use() { disable_you_should_use # Delete any possible pre-existing hooks add-zsh-hook preexec _check_aliases add-zsh-hook preexec _check_global_aliases add-zsh-hook preexec _check_git_aliases add-zsh-hook precmd _flush_ysu_buffer } autoload -Uz add-zsh-hook enable_you_should_use