diff options
29 files changed, 1525 insertions, 647 deletions
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e16ebb39b..b091f5d89 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,5 +1,7 @@ # Plugin owners plugins/aws/ @maksyms +plugins/genpass/ @atoponce plugins/git-lfs/ @vietduc01100001 plugins/gitfast/ @felipec plugins/sdk/ @rgoldberg +plugins/wp-cli/ @joshmedeski diff --git a/lib/cli.zsh b/lib/cli.zsh index b1478a89f..92d447a8e 100644 --- a/lib/cli.zsh +++ b/lib/cli.zsh @@ -1,360 +1,397 @@ #!/usr/bin/env zsh function omz { - [[ $# -gt 0 ]] || { - _omz::help - return 1 - } - - local command="$1" - shift - - # Subcommand functions start with _ so that they don't - # appear as completion entries when looking for `omz` - (( $+functions[_omz::$command] )) || { - _omz::help - return 1 - } - - _omz::$command "$@" + [[ $# -gt 0 ]] || { + _omz::help + return 1 + } + + local command="$1" + shift + + # Subcommand functions start with _ so that they don't + # appear as completion entries when looking for `omz` + (( $+functions[_omz::$command] )) || { + _omz::help + return 1 + } + + _omz::$command "$@" } function _omz { - local -a cmds subcmds - cmds=( - 'help:Usage information' - 'plugin:Commands for Oh My Zsh plugins management' - 'pr:Commands for Oh My Zsh Pull Requests management' - 'theme:Commands for Oh My Zsh themes management' - 'update:Update Oh My Zsh' - ) - - if (( CURRENT == 2 )); then - _describe 'command' cmds - elif (( CURRENT == 3 )); then - case "$words[2]" in - plugin) subcmds=('list:List plugins') - _describe 'command' subcmds ;; - pr) subcmds=('test:Test a Pull Request' 'clean:Delete all Pull Request branches') - _describe 'command' subcmds ;; - theme) subcmds=('use:Load a theme' 'list:List themes') - _describe 'command' subcmds ;; - esac - elif (( CURRENT == 4 )); then - case "$words[2]::$words[3]" in - theme::use) compadd "$ZSH"/themes/*.zsh-theme(.N:t:r) \ - "$ZSH_CUSTOM"/**/*.zsh-theme(.N:r:gs:"$ZSH_CUSTOM"/themes/:::gs:"$ZSH_CUSTOM"/:::) ;; - esac - fi - - return 0 + local -a cmds subcmds + cmds=( + 'changelog:Print the changelog' + 'help:Usage information' + 'plugin:Manage plugins' + 'pr:Manage Oh My Zsh Pull Requests' + 'theme:Manage themes' + 'update:Update Oh My Zsh' + ) + + if (( CURRENT == 2 )); then + _describe 'command' cmds + elif (( CURRENT == 3 )); then + case "$words[2]" in + changelog) local -a refs + refs=("${(@f)$(command git for-each-ref --format="%(refname:short):%(subject)" refs/heads refs/tags)}") + _describe 'command' refs ;; + plugin) subcmds=('list:List plugins') + _describe 'command' subcmds ;; + pr) subcmds=('test:Test a Pull Request' 'clean:Delete all Pull Request branches') + _describe 'command' subcmds ;; + theme) subcmds=('use:Load a theme' 'list:List themes') + _describe 'command' subcmds ;; + esac + elif (( CURRENT == 4 )); then + case "$words[2]::$words[3]" in + theme::use) compadd "$ZSH"/themes/*.zsh-theme(.N:t:r) \ + "$ZSH_CUSTOM"/**/*.zsh-theme(.N:r:gs:"$ZSH_CUSTOM"/themes/:::gs:"$ZSH_CUSTOM"/:::) ;; + esac + fi + + return 0 } compdef _omz omz +## Utility functions + +function _omz::confirm { + # If question supplied, ask it before reading the answer + # NOTE: uses the logname of the caller function + if [[ -n "$1" ]]; then + _omz::log prompt "$1" "${${functrace[1]#_}%:*}" + fi + + # Read one character + read -r -k 1 + + # If no newline entered, add a newline + if [[ "$REPLY" != $'\n' ]]; then + echo + fi +} + +function _omz::log { + # if promptsubst is set, a message with `` or $() + # will be run even if quoted due to `print -P` + setopt localoptions nopromptsubst + + # $1 = info|warn|error|debug + # $2 = text + # $3 = (optional) name of the logger + + local logtype=$1 + local logname=${3:-${${functrace[1]#_}%:*}} + + # Don't print anything if debug is not active + if [[ $logtype = debug && -z $_OMZ_DEBUG ]]; then + return + fi + + # Choose coloring based on log type + case "$logtype" in + prompt) print -Pn "%S%F{blue}$logname%f%s: $2" ;; + debug) print -P "%F{white}$logname%f: $2" ;; + info) print -P "%F{green}$logname%f: $2" ;; + warn) print -P "%S%F{yellow}$logname%f%s: $2" ;; + error) print -P "%S%F{red}$logname%f%s: $2" ;; + esac >&2 +} + +## User-facing commands function _omz::help { - cat <<EOF + cat <<EOF Usage: omz <command> [options] Available commands: - help Print this help message - plugin <command> Manage plugins - pr <command> Manage Oh My Zsh Pull Requests - theme <command> Manage themes - update Update Oh My Zsh + help Print this help message + changelog Print the changelog + plugin <command> Manage plugins + pr <command> Manage Oh My Zsh Pull Requests + theme <command> Manage themes + update Update Oh My Zsh EOF } -function _omz::confirm { - # If question supplied, ask it before reading the answer - # NOTE: uses the logname of the caller function - if [[ -n "$1" ]]; then - _omz::log prompt "$1" "${${functrace[1]#_}%:*}" - fi - - # Read one character - read -r -k 1 - - # If no newline entered, add a newline - if [[ "$REPLY" != $'\n' ]]; then - echo - fi -} - -function _omz::log { - # if promptsubst is set, a message with `` or $() - # will be run even if quoted due to `print -P` - setopt localoptions nopromptsubst - - # $1 = info|warn|error|debug - # $2 = text - # $3 = (optional) name of the logger +function _omz::changelog { + local version=${1:-HEAD} format=${3:-"--text"} - local logtype=$1 - local logname=${3:-${${functrace[1]#_}%:*}} + if ! command git -C "$ZSH" show-ref --verify refs/heads/$version &>/dev/null && \ + ! command git -C "$ZSH" show-ref --verify refs/tags/$version &>/dev/null && \ + ! command git -C "$ZSH" rev-parse --verify "${version}^{commit}" &>/dev/null; then + cat <<EOF +Usage: omz changelog [version] - # Don't print anything if debug is not active - if [[ $logtype = debug && -z $_OMZ_DEBUG ]]; then - return - fi +NOTE: <version> must be a valid branch, tag or commit. +EOF + return 1 + fi - # Choose coloring based on log type - case "$logtype" in - prompt) print -Pn "%S%F{blue}$logname%f%s: $2" ;; - debug) print -P "%F{white}$logname%f: $2" ;; - info) print -P "%F{green}$logname%f: $2" ;; - warn) print -P "%S%F{yellow}$logname%f%s: $2" ;; - error) print -P "%S%F{red}$logname%f%s: $2" ;; - esac >&2 + "$ZSH/tools/changelog.sh" "$version" "${2:-}" "$format" } function _omz::plugin { - (( $# > 0 && $+functions[_omz::plugin::$1] )) || { - cat <<EOF + (( $# > 0 && $+functions[_omz::plugin::$1] )) || { + cat <<EOF Usage: omz plugin <command> [options] Available commands: - list List all available Oh My Zsh plugins + list List all available Oh My Zsh plugins EOF - return 1 - } + return 1 + } - local command="$1" - shift + local command="$1" + shift - _omz::plugin::$command "$@" + _omz::plugin::$command "$@" } function _omz::plugin::list { - local -a custom_plugins builtin_plugins - custom_plugins=("$ZSH_CUSTOM"/plugins/*(-/N:t)) - builtin_plugins=("$ZSH"/plugins/*(-/N:t)) - - # If the command is being piped, print all found line by line - if [[ ! -t 1 ]]; then - print -l ${(q-)custom_plugins} ${(q-)builtin_plugins} - return - fi - - if (( ${#custom_plugins} )); then - print -P "%U%BCustom plugins%b%u:" - print -l ${(q-)custom_plugins} | column - fi - - if (( ${#builtin_plugins} )); then - (( ${#custom_plugins} )) && echo # add a line of separation - - print -P "%U%BBuilt-in plugins%b%u:" - print -l ${(q-)builtin_plugins} | column - fi + local -a custom_plugins builtin_plugins + custom_plugins=("$ZSH_CUSTOM"/plugins/*(-/N:t)) + builtin_plugins=("$ZSH"/plugins/*(-/N:t)) + + # If the command is being piped, print all found line by line + if [[ ! -t 1 ]]; then + print -l ${(q-)custom_plugins} ${(q-)builtin_plugins} + return + fi + + if (( ${#custom_plugins} )); then + print -P "%U%BCustom plugins%b%u:" + print -l ${(q-)custom_plugins} | column + fi + + if (( ${#builtin_plugins} )); then + (( ${#custom_plugins} )) && echo # add a line of separation + + print -P "%U%BBuilt-in plugins%b%u:" + print -l ${(q-)builtin_plugins} | column + fi } function _omz::pr { - (( $# > 0 && $+functions[_omz::pr::$1] )) || { - cat <<EOF + (( $# > 0 && $+functions[_omz::pr::$1] )) || { + cat <<EOF Usage: omz pr <command> [options] Available commands: - clean Delete all PR branches (ohmyzsh/pull-*) - test <PR_number_or_URL> Fetch PR #NUMBER and rebase against master + clean Delete all PR branches (ohmyzsh/pull-*) + test <PR_number_or_URL> Fetch PR #NUMBER and rebase against master EOF - return 1 - } + return 1 + } - local command="$1" - shift + local command="$1" + shift - _omz::pr::$command "$@" + _omz::pr::$command "$@" } function _omz::pr::clean { - ( - set -e - builtin cd -q "$ZSH" - - # Check if there are PR branches - local fmt branches - fmt="%(color:bold blue)%(align:18,right)%(refname:short)%(end)%(color:reset) %(color:dim bold red)%(objectname:short)%(color:reset) %(color:yellow)%(contents:subject)" - branches="$(command git for-each-ref --sort=-committerdate --color --format="$fmt" "refs/heads/ohmyzsh/pull-*")" - - # Exit if there are no PR branches - if [[ -z "$branches" ]]; then - _omz::log info "there are no Pull Request branches to remove." - return - fi - - # Print found PR branches - echo "$branches\n" - # Confirm before removing the branches - _omz::confirm "do you want remove these Pull Request branches? [Y/n] " - # Only proceed if the answer is a valid yes option - [[ "$REPLY" != [yY$'\n'] ]] && return - - _omz::log info "removing all Oh My Zsh Pull Request branches..." - command git branch --list 'ohmyzsh/pull-*' | while read branch; do - command git branch -D "$branch" - done - ) + ( + set -e + builtin cd -q "$ZSH" + + # Check if there are PR branches + local fmt branches + fmt="%(color:bold blue)%(align:18,right)%(refname:short)%(end)%(color:reset) %(color:dim bold red)%(objectname:short)%(color:reset) %(color:yellow)%(contents:subject)" + branches="$(command git for-each-ref --sort=-committerdate --color --format="$fmt" "refs/heads/ohmyzsh/pull-*")" + + # Exit if there are no PR branches + if [[ -z "$branches" ]]; then + _omz::log info "there are no Pull Request branches to remove." + return + fi + + # Print found PR branches + echo "$branches\n" + # Confirm before removing the branches + _omz::confirm "do you want remove these Pull Request branches? [Y/n] " + # Only proceed if the answer is a valid yes option + [[ "$REPLY" != [yY$'\n'] ]] && return + + _omz::log info "removing all Oh My Zsh Pull Request branches..." + command git branch --list 'ohmyzsh/pull-*' | while read branch; do + command git branch -D "$branch" + done + ) } function _omz::pr::test { - # Allow $1 to be a URL to the pull request - if [[ "$1" = https://* ]]; then - 1="${1:t}" - fi + # Allow $1 to be a URL to the pull request + if [[ "$1" = https://* ]]; then + 1="${1:t}" + fi + + # Check the input + if ! [[ -n "$1" && "$1" =~ ^[[:digit:]]+$ ]]; then + echo >&2 "Usage: omz pr test <PR_NUMBER_or_URL>" + return 1 + fi + + # Save current git HEAD + local branch + branch=$(builtin cd -q "$ZSH"; git symbolic-ref --short HEAD) || { + _omz::log error "error when getting the current git branch. Aborting..." + return 1 + } + + + # Fetch PR onto ohmyzsh/pull-<PR_NUMBER> branch and rebase against master + # If any of these operations fail, undo the changes made + ( + set -e + builtin cd -q "$ZSH" + + # Get the ohmyzsh git remote + command git remote -v | while read remote url _; do + case "$url" in + https://github.com/ohmyzsh/ohmyzsh(|.git)) found=1; break ;; + git@github.com:ohmyzsh/ohmyzsh(|.git)) found=1; break ;; + esac + done + + (( $found )) || { + _omz::log error "could not found the ohmyzsh git remote. Aborting..." + return 1 + } - # Check the input - if ! [[ -n "$1" && "$1" =~ ^[[:digit:]]+$ ]]; then - echo >&2 "Usage: omz pr test <PR_NUMBER_or_URL>" - return 1 - fi + # Fetch pull request head + _omz::log info "fetching PR #$1 to ohmyzsh/pull-$1..." + command git fetch -f "$remote" refs/pull/$1/head:ohmyzsh/pull-$1 || { + _omz::log error "error when trying to fetch PR #$1." + return 1 + } - # Save current git HEAD - local branch - branch=$(builtin cd -q "$ZSH"; git symbolic-ref --short HEAD) || { - _omz::log error "error when getting the current git branch. Aborting..." - return 1 + # Rebase pull request branch against the current master + _omz::log info "rebasing PR #$1..." + command git rebase master ohmyzsh/pull-$1 || { + command git rebase --abort &>/dev/null + _omz::log warn "could not rebase PR #$1 on top of master." + _omz::log warn "you might not see the latest stable changes." + _omz::log info "run \`zsh\` to test the changes." + return 1 } + _omz::log info "fetch of PR #${1} successful." + ) - # Fetch PR onto ohmyzsh/pull-<PR_NUMBER> branch and rebase against master - # If any of these operations fail, undo the changes made - ( - set -e - builtin cd -q "$ZSH" - - # Get the ohmyzsh git remote - command git remote -v | while read remote url _; do - case "$url" in - https://github.com/ohmyzsh/ohmyzsh(|.git)) found=1; break ;; - git@github.com:ohmyzsh/ohmyzsh(|.git)) found=1; break ;; - esac - done - - (( $found )) || { - _omz::log error "could not found the ohmyzsh git remote. Aborting..." - return 1 - } - - # Fetch pull request head - _omz::log info "fetching PR #$1 to ohmyzsh/pull-$1..." - command git fetch -f "$remote" refs/pull/$1/head:ohmyzsh/pull-$1 || { - _omz::log error "error when trying to fetch PR #$1." - return 1 - } - - # Rebase pull request branch against the current master - _omz::log info "rebasing PR #$1..." - command git rebase master ohmyzsh/pull-$1 || { - command git rebase --abort &>/dev/null - _omz::log warn "could not rebase PR #$1 on top of master." - _omz::log warn "you might not see the latest stable changes." - _omz::log info "run \`zsh\` to test the changes." - return 1 - } - - _omz::log info "fetch of PR #${1} successful." - ) - - # If there was an error, abort running zsh to test the PR - [[ $? -eq 0 ]] || return 1 - - # Run zsh to test the changes - _omz::log info "running \`zsh\` to test the changes. Run \`exit\` to go back." - command zsh -l - - # After testing, go back to the previous HEAD if the user wants - _omz::confirm "do you want to go back to the previous branch? [Y/n] " - # Only proceed if the answer is a valid yes option - [[ "$REPLY" != [yY$'\n'] ]] && return + # If there was an error, abort running zsh to test the PR + [[ $? -eq 0 ]] || return 1 + + # Run zsh to test the changes + _omz::log info "running \`zsh\` to test the changes. Run \`exit\` to go back." + command zsh -l - ( - set -e - builtin cd -q "$ZSH" + # After testing, go back to the previous HEAD if the user wants + _omz::confirm "do you want to go back to the previous branch? [Y/n] " + # Only proceed if the answer is a valid yes option + [[ "$REPLY" != [yY$'\n'] ]] && return - command git checkout "$branch" -- || { - _omz::log error "could not go back to the previous branch ('$branch')." - return 1 - } - ) + ( + set -e + builtin cd -q "$ZSH" + + command git checkout "$branch" -- || { + _omz::log error "could not go back to the previous branch ('$branch')." + return 1 + } + ) } function _omz::theme { - (( $# > 0 && $+functions[_omz::theme::$1] )) || { - cat <<EOF + (( $# > 0 && $+functions[_omz::theme::$1] )) || { + cat <<EOF Usage: omz theme <command> [options] Available commands: - list List all available Oh My Zsh themes - use <theme> Load an Oh My Zsh theme + list List all available Oh My Zsh themes + use <theme> Load an Oh My Zsh theme EOF - return 1 - } + return 1 + } - local command="$1" - shift + local command="$1" + shift - _omz::theme::$command "$@" + _omz::theme::$command "$@" } function _omz::theme::list { - local -a custom_themes builtin_themes - custom_themes=("$ZSH_CUSTOM"/**/*.zsh-theme(.N:r:gs:"$ZSH_CUSTOM"/themes/:::gs:"$ZSH_CUSTOM"/:::)) - builtin_themes=("$ZSH"/themes/*.zsh-theme(.N:t:r)) - - # If the command is being piped, print all found line by line - if [[ ! -t 1 ]]; then - print -l ${(q-)custom_themes} ${(q-)builtin_themes} - return - fi - - if (( ${#custom_themes} )); then - print -P "%U%BCustom themes%b%u:" - print -l ${(q-)custom_themes} | column - fi - - if (( ${#builtin_themes} )); then - (( ${#custom_themes} )) && echo # add a line of separation - - print -P "%U%BBuilt-in themes%b%u:" - print -l ${(q-)builtin_themes} | column - fi + local -a custom_themes builtin_themes + custom_themes=("$ZSH_CUSTOM"/**/*.zsh-theme(.N:r:gs:"$ZSH_CUSTOM"/themes/:::gs:"$ZSH_CUSTOM"/:::)) + builtin_themes=("$ZSH"/themes/*.zsh-theme(.N:t:r)) + + # If the command is being piped, print all found line by line + if [[ ! -t 1 ]]; then + print -l ${(q-)custom_themes} ${(q-)builtin_themes} + return + fi + + if (( ${#custom_themes} )); then + print -P "%U%BCustom themes%b%u:" + print -l ${(q-)custom_themes} | column + fi + + if (( ${#builtin_themes} )); then + (( ${#custom_themes} )) && echo # add a line of separation + + print -P "%U%BBuilt-in themes%b%u:" + print -l ${(q-)builtin_themes} | column + fi } function _omz::theme::use { - if [[ -z "$1" ]]; then - echo >&2 "Usage: omz theme use <theme>" - return 1 - fi - - # Respect compatibility with old lookup order - if [[ -f "$ZSH_CUSTOM/$1.zsh-theme" ]]; then - source "$ZSH_CUSTOM/$1.zsh-theme" - elif [[ -f "$ZSH_CUSTOM/themes/$1.zsh-theme" ]]; then - source "$ZSH_CUSTOM/themes/$1.zsh-theme" - elif [[ -f "$ZSH/themes/$1.zsh-theme" ]]; then - source "$ZSH/themes/$1.zsh-theme" - else - _omz::log error "theme '$1' not found" - return 1 - fi + if [[ -z "$1" ]]; then + echo >&2 "Usage: omz theme use <theme>" + return 1 + fi + + # Respect compatibility with old lookup order + if [[ -f "$ZSH_CUSTOM/$1.zsh-theme" ]]; then + source "$ZSH_CUSTOM/$1.zsh-theme" + elif [[ -f "$ZSH_CUSTOM/themes/$1.zsh-theme" ]]; then + source "$ZSH_CUSTOM/themes/$1.zsh-theme" + elif [[ -f "$ZSH/themes/$1.zsh-theme" ]]; then + source "$ZSH/themes/$1.zsh-theme" + else + _omz::log error "theme '$1' not found" + return 1 + fi } function _omz::update { - # Run update script - env ZSH="$ZSH" sh "$ZSH/tools/upgrade.sh" - # Update last updated file - zmodload zsh/datetime - echo "LAST_EPOCH=$(( EPOCHSECONDS / 60 / 60 / 24 ))" >! "${ZSH_CACHE_DIR}/.zsh-update" - # Remove update lock if it exists - command rm -rf "$ZSH/log/update.lock" + # Run update script + if [[ "$1" != --unattended ]]; then + ZSH="$ZSH" zsh -f "$ZSH/tools/upgrade.sh" --interactive + else + ZSH="$ZSH" zsh -f "$ZSH/tools/upgrade.sh" + fi + local ret=$? + + # Update last updated file + zmodload zsh/datetime + echo "LAST_EPOCH=$(( EPOCHSECONDS / 60 / 60 / 24 ))" >! "${ZSH_CACHE_DIR}/.zsh-update" + # Remove update lock if it exists + command rm -rf "$ZSH/log/update.lock" + + # Restart the zsh session + if [[ $ret -eq 0 && "$1" != --unattended ]]; then + # Check whether to run a login shell + [[ "$ZSH_ARGZERO" = -* ]] && exec -l "${ZSH_ARGZERO#-}" || exec "$ZSH_ARGZERO" + fi } diff --git a/lib/functions.zsh b/lib/functions.zsh index 15526cd5f..7beb62ad9 100644 --- a/lib/functions.zsh +++ b/lib/functions.zsh @@ -9,17 +9,8 @@ function uninstall_oh_my_zsh() { } function upgrade_oh_my_zsh() { - if (( $+functions[_omz::update] )); then - echo >&2 "${fg[yellow]}Note: \`$0\` is deprecated. Use \`omz update\` instead.$reset_color" - fi - - # Run update script - env ZSH="$ZSH" sh "$ZSH/tools/upgrade.sh" - # Update last updated file - zmodload zsh/datetime - echo "LAST_EPOCH=$(( EPOCHSECONDS / 60 / 60 / 24 ))" >! "${ZSH_CACHE_DIR}/.zsh-update" - # Remove update lock if it exists - command rm -rf "$ZSH/log/update.lock" + echo >&2 "${fg[yellow]}Note: \`$0\` is deprecated. Use \`omz update\` instead.$reset_color" + omz update } function take() { diff --git a/lib/git.zsh b/lib/git.zsh index 53d39609e..157c85062 100644 --- a/lib/git.zsh +++ b/lib/git.zsh @@ -9,14 +9,27 @@ function __git_prompt_git() { GIT_OPTIONAL_LOCKS=0 command git "$@" } -# Outputs current branch info in prompt format function git_prompt_info() { + # If we are on a folder not tracked by git, get out. + # Otherwise, check for hide-info at global and local repository level + if ! __git_prompt_git rev-parse --git-dir &> /dev/null \ + || [[ "$(__git_prompt_git config --get oh-my-zsh.hide-info 2>/dev/null)" == 1 ]]; then + return 0 + fi + local ref - if [[ "$(__git_prompt_git config --get oh-my-zsh.hide-status 2>/dev/null)" != "1" ]]; then - ref=$(__git_prompt_git symbolic-ref HEAD 2> /dev/null) || \ - ref=$(__git_prompt_git rev-parse --short HEAD 2> /dev/null) || return 0 - echo "$ZSH_THEME_GIT_PROMPT_PREFIX${ref#refs/heads/}$(parse_git_dirty)$ZSH_THEME_GIT_PROMPT_SUFFIX" + ref=$(__git_prompt_git symbolic-ref --short HEAD 2> /dev/null) \ + || ref=$(__git_prompt_git rev-parse --short HEAD 2> /dev/null) \ + || return 0 + + # Use global ZSH_THEME_GIT_SHOW_UPSTREAM=1 for including upstream remote info + local upstream + if (( ${+ZSH_THEME_GIT_SHOW_UPSTREAM} )); then + upstream=$(__git_prompt_git rev-parse --abbrev-ref --symbolic-full-name "@{upstream}" 2>/dev/null) \ + && upstream=" -> ${upstream}" fi + + echo "${ZSH_THEME_GIT_PROMPT_PREFIX}${ref}${upstream}$(parse_git_dirty)${ZSH_THEME_GIT_PROMPT_SUFFIX}" } # Checks if working tree is dirty diff --git a/plugins/ansible/ansible.plugin.zsh b/plugins/ansible/ansible.plugin.zsh index f68ff23a5..75393b704 100644 --- a/plugins/ansible/ansible.plugin.zsh +++ b/plugins/ansible/ansible.plugin.zsh @@ -4,7 +4,7 @@ function ansible-version(){ } function ansible-role-init(){ - if ! [ -z $1] ; then + if ! [ -z $1 ] ; then echo "Ansible Role : $1 Creating...." ansible-galaxy init $1 tree $1 @@ -25,4 +25,4 @@ alias ainv='ansible-inventory ' alias adoc='ansible-doc ' alias agal='ansible-galaxy ' alias apull='ansible-pull ' -alias aval='ansible-vault'
\ No newline at end of file +alias aval='ansible-vault' diff --git a/plugins/archlinux/archlinux.plugin.zsh b/plugins/archlinux/archlinux.plugin.zsh index 4d39bd86b..c72e06dc2 100644 --- a/plugins/archlinux/archlinux.plugin.zsh +++ b/plugins/archlinux/archlinux.plugin.zsh @@ -171,14 +171,13 @@ function paclist() { } function pacdisowned() { - emulate -L zsh - + local tmp db fs tmp=${TMPDIR-/tmp}/pacman-disowned-$UID-$$ db=$tmp/db fs=$tmp/fs mkdir "$tmp" - trap 'rm -rf "$tmp"' EXIT + trap 'rm -rf "$tmp"' EXIT pacman -Qlq | sort -u > "$db" @@ -189,15 +188,14 @@ function pacdisowned() { } function pacmanallkeys() { - emulate -L zsh curl -s https://www.archlinux.org/people/{developers,trustedusers}/ | \ awk -F\" '(/pgp.mit.edu/) { sub(/.*search=0x/,""); print $1}' | \ xargs sudo pacman-key --recv-keys } function pacmansignkeys() { - emulate -L zsh - for key in $*; do + local key + for key in $@; do sudo pacman-key --recv-keys $key sudo pacman-key --lsign-key $key printf 'trust\n3\n' | sudo gpg --homedir /etc/pacman.d/gnupg \ @@ -207,13 +205,13 @@ function pacmansignkeys() { if (( $+commands[xdg-open] )); then function pacweb() { - pkg="$1" - infos="$(LANG=C pacman -Si "$pkg")" + local pkg="$1" + local infos="$(LANG=C pacman -Si "$pkg")" if [[ -z "$infos" ]]; then return fi - repo="$(grep -m 1 '^Repo' <<< "$infos" | grep -oP '[^ ]+$')" - arch="$(grep -m 1 '^Arch' <<< "$infos" | grep -oP '[^ ]+$')" + local repo="$(grep -m 1 '^Repo' <<< "$infos" | grep -oP '[^ ]+$')" + local arch="$(grep -m 1 '^Arch' <<< "$infos" | grep -oP '[^ ]+$')" xdg-open "https://www.archlinux.org/packages/$repo/$arch/$pkg/" &>/dev/null } fi diff --git a/plugins/command-not-found/command-not-found.plugin.zsh b/plugins/command-not-found/command-not-found.plugin.zsh index 81d76e638..aea1e1b4c 100644 --- a/plugins/command-not-found/command-not-found.plugin.zsh +++ b/plugins/command-not-found/command-not-found.plugin.zsh @@ -2,7 +2,22 @@ # as seen in https://www.porcheron.info/command-not-found-for-zsh/ # this is installed in Ubuntu -[[ -e /etc/zsh_command_not_found ]] && source /etc/zsh_command_not_found +if [ -x /usr/lib/command-not-found -o -x /usr/share/command-not-found/command-not-found ]; then + function command_not_found_handler { + # check because c-n-f could've been removed in the meantime + if [ -x /usr/lib/command-not-found ]; then + /usr/lib/command-not-found -- "$1" + return $? + elif [ -x /usr/share/command-not-found/command-not-found ]; then + /usr/share/command-not-found/command-not-found -- "$1" + return $? + else + printf "zsh: command not found: %s\n" "$1" >&2 + return 127 + fi + return 0 + } +fi # Arch Linux command-not-found support, you must have package pkgfile installed # https://wiki.archlinux.org/index.php/Pkgfile#.22Command_not_found.22_hook @@ -10,13 +25,12 @@ # Fedora command-not-found support if [ -f /usr/libexec/pk-command-not-found ]; then - command_not_found_handler () { + command_not_found_handler() { runcnf=1 retval=127 [ ! -S /var/run/dbus/system_bus_socket ] && runcnf=0 [ ! -x /usr/libexec/packagekitd ] && runcnf=0 - if [ $runcnf -eq 1 ] - then + if [ $runcnf -eq 1 ]; then /usr/libexec/pk-command-not-found $@ retval=$? fi @@ -32,7 +46,7 @@ fi # NixOS command-not-found support if [ -x /run/current-system/sw/bin/command-not-found ]; then - command_not_found_handler () { + command_not_found_handler() { /run/current-system/sw/bin/command-not-found $@ } fi diff --git a/plugins/common-aliases/README.md b/plugins/common-aliases/README.md index d198a29ac..b6f34cb54 100644 --- a/plugins/common-aliases/README.md +++ b/plugins/common-aliases/README.md @@ -40,11 +40,13 @@ plugins=(... common-aliases) | Alias | Command | Description | |-------|-----------------------------------------------------|-----------------------------------------| -| fd | `find . -type d -name` | Find a directory with the given name | +| fd\* | `find . -type d -name` | Find a directory with the given name | | ff | `find . -type f -name` | Find a file with the given name | | grep | `grep --color` | Searches for a query string | | sgrep | `grep -R -n -H -C 5 --exclude-dir={.git,.svn,CVS}` | Useful for searching within files | +\* Only if the [`fd`](https://github.com/sharkdp/fd) command isn't installed. + ### Other Aliases | Alias | Command | Description | diff --git a/plugins/common-aliases/common-aliases.plugin.zsh b/plugins/common-aliases/common-aliases.plugin.zsh index a9d4f1a3d..4e5ff848f 100644 --- a/plugins/common-aliases/common-aliases.plugin.zsh +++ b/plugins/common-aliases/common-aliases.plugin.zsh @@ -34,7 +34,7 @@ alias -g P="2>&1| pygmentize -l pytb" alias dud='du -d 1 -h' alias duf='du -sh *' -alias fd='find . -type d -name' +(( $+commands[fd] )) || alias fd='find . -type d -name' alias ff='find . -type f -name' alias h='history' diff --git a/plugins/composer/composer.plugin.zsh b/plugins/composer/composer.plugin.zsh index 75b03ffe7..218a13e5f 100644 --- a/plugins/composer/composer.plugin.zsh +++ b/plugins/composer/composer.plugin.zsh @@ -51,7 +51,7 @@ alias cget='curl -s https://getcomposer.org/installer | php' # Add Composer's global binaries to PATH, using Composer if available. if (( $+commands[composer] )); then - autoload -Uz _store_cache _retrieve_cache + autoload -Uz _store_cache _retrieve_cache _cache_invalid _retrieve_cache composer diff --git a/plugins/genpass/README.md b/plugins/genpass/README.md new file mode 100644 index 000000000..a5ff4a876 --- /dev/null +++ b/plugins/genpass/README.md @@ -0,0 +1,66 @@ +# genpass + +This plugin provides three unique password generators for ZSH. Each generator +has at least a 128-bit security margin and generates passwords from the +cryptographically secure `/dev/urandom`. Each generator can also take an +optional numeric argument to generate multiple passwords. + +To use it from an interactive ZSH, add `genpass` to the plugins array in your +zshrc file: + + plugins=(... genpass) + +You can also invoke password generators directly (they are implemented as +standalone executable files), which can be handy when you need to generate +passwords in a script: + + ~/.oh-my-zsh/plugins/genpass/genpass-apple 3 + +## genpass-apple + +Generates a pronounceable pseudoword passphrase of the "cvccvc" consonant/vowel +syntax, inspired by [Apple's iCloud Keychain password generator][1]. Each +password has exactly 1 digit placed at the edge of a "word" and exactly 1 +capital letter to satisfy most password security requirements. + + % genpass-apple + gelcyv-foqtam-fotqoh-viMleb-lexduv-6ixfuk + + % genpass-apple 3 + japvyz-qyjti4-kajrod-nubxaW-hukkan-dijcaf + vydpig-fucnul-3ukpog-voggom-zygNad-jepgad + zocmez-byznis-hegTaj-jecdyq-qiqmiq-5enwom + +[1]: https://developer.apple.com/password-rules/ + +## genpass-monkey + +Generates visually unambiguous random meaningless strings using [Crockford's +base32][2]. + + % genpass-monkey + xt7gn976e7jj3fstgpy27330x3 + + % genpass-monkey 3 + n1qqwtzgejwgqve9yzf2gxvx4m + r2n3f5s6vbqs2yx7xjnmahqewy + 296w9y9rts3p5r9yay0raek8e5 + +[2]: https://www.crockford.com/base32.html + +## genpass-xkcd + +Generates passphrases from `/usr/share/dict/words` inspired by the [famous (and +slightly misleading) XKCD comic][3]. Each passphrase is prepended with a digit +showing the number of words in the passphrase to adhere to password security +requirements that require digits. Each word is 6 characters or less. + + % genpass-xkcd + 9-eaten-Slav-rife-aired-hill-cordon-splits-welsh-napes + + % genpass-xkcd 3 + 9-worker-Vlad-horde-shrubs-smite-thwart-paw-alters-prawns + 9-tutors-stink-rhythm-junk-snappy-hooray-barbs-mewl-clomp + 9-vital-escape-Angkor-Huff-wet-Mayra-abbés-putts-guzzle + +[3]: https://xkcd.com/936/ diff --git a/plugins/genpass/genpass-apple b/plugins/genpass/genpass-apple new file mode 100755 index 000000000..963ab6447 --- /dev/null +++ b/plugins/genpass/genpass-apple @@ -0,0 +1,79 @@ +#!/usr/bin/env zsh +# +# Usage: genpass-apple [NUM] +# +# Generate a password made of 6 pseudowords of 6 characters each +# with the security margin of at least 128 bits. +# +# Example password: xudmec-4ambyj-tavric-mumpub-mydVop-bypjyp +# +# If given a numerical argument, generate that many passwords. + +emulate -L zsh -o no_unset -o warn_create_global -o warn_nested_var + +if [[ ARGC -gt 1 || ${1-1} != ${~:-<1-$((16#7FFFFFFF))>} ]]; then + print -ru2 -- "usage: $0 [NUM]" + return 1 +fi + +zmodload zsh/system zsh/mathfunc || return + +{ + local -r vowels=aeiouy + local -r consonants=bcdfghjklmnpqrstvwxz + local -r digits=0123456789 + + # Sets REPLY to a uniformly distributed random number in [1, $1]. + # Requires: $1 <= 256. + function -$0-rand() { + local c + while true; do + sysread -s1 c || return + # Avoid bias towards smaller numbers. + (( #c < 256 / $1 * $1 )) && break + done + typeset -g REPLY=$((#c % $1 + 1)) + } + + local REPLY chars + + repeat ${1-1}; do + # Generate 6 pseudowords of the form cvccvc where c and v + # denote random consonants and vowels respectively. + local words=() + repeat 6; do + words+=('') + repeat 2; do + for chars in $consonants $vowels $consonants; do + -$0-rand $#chars || return + words[-1]+=$chars[REPLY] + done + done + done + + local pwd=${(j:-:)words} + + # Replace either the first or the last character in one of + # the words with a random digit. + -$0-rand $#digits || return + local digit=$digits[REPLY] + -$0-rand $((2 * $#words)) || return + pwd[REPLY/2*7+2*(REPLY%2)-1]=$digit + + # Convert one lower-case character to upper case. + while true; do + -$0-rand $#pwd || return + [[ $vowels$consonants == *$pwd[REPLY]* ]] && break + done + # NOTE: We aren't using ${(U)c} here because its results are + # locale-dependent. For example, when upper-casing 'i' in Turkish + # locale we would get 'İ', a.k.a. latin capital letter i with dot + # above. We could set LC_CTYPE=C locally but then we would run afoul + # of this zsh bug: https://www.zsh.org/mla/workers/2020/msg00588.html. + local c=$pwd[REPLY] + printf -v c '%o' $((#c - 32)) + printf "%s\\$c%s\\n" "$pwd[1,REPLY-1]" "$pwd[REPLY+1,-1]" || return + done +} always { + unfunction -m -- "-${(b)0}-*" +} </dev/urandom diff --git a/plugins/genpass/genpass-monkey b/plugins/genpass/genpass-monkey new file mode 100755 index 000000000..94ff5e131 --- /dev/null +++ b/plugins/genpass/genpass-monkey @@ -0,0 +1,32 @@ +#!/usr/bin/env zsh +# +# Usage: genpass-monkey [NUM] +# +# Generate a password made of 26 alphanumeric characters +# with the security margin of at least 128 bits. +# +# Example password: nz5ej2kypkvcw0rn5cvhs6qxtm +# +# If given a numerical argument, generate that many passwords. + +emulate -L zsh -o no_unset -o warn_create_global -o warn_nested_var + +if [[ ARGC -gt 1 || ${1-1} != ${~:-<1-$((16#7FFFFFFF))>} ]]; then + print -ru2 -- "usage: $0 [NUM]" + return 1 +fi + +zmodload zsh/system || return + +{ + local -r chars=abcdefghjkmnpqrstvwxyz0123456789 + local c + repeat ${1-1}; do + repeat 26; do + sysread -s1 c || return + # There is uniform because $#chars divides 256. + print -rn -- $chars[#c%$#chars+1] + done + print + done +} </dev/urandom diff --git a/plugins/genpass/genpass-xkcd b/plugins/genpass/genpass-xkcd new file mode 100755 index 000000000..a486ccb40 --- /dev/null +++ b/plugins/genpass/genpass-xkcd @@ -0,0 +1,68 @@ +#!/usr/bin/env zsh +# +# Usage: genpass-xkcd [NUM] +# +# Generate a password made of words from /usr/share/dict/words +# with the security margin of at least 128 bits. +# +# Example password: 9-mien-flood-Patti-buxom-dozes-ickier-pay-ailed-Foster +# +# If given a numerical argument, generate that many passwords. +# +# The name of this utility is a reference to https://xkcd.com/936/. + +emulate -L zsh -o no_unset -o warn_create_global -o warn_nested_var -o extended_glob + +if [[ ARGC -gt 1 || ${1-1} != ${~:-<1-$((16#7FFFFFFF))>} ]]; then + print -ru2 -- "usage: $0 [NUM]" + return 1 +fi + +zmodload zsh/system zsh/mathfunc || return + +local -r dict=/usr/share/dict/words + +if [[ ! -e $dict ]]; then + print -ru2 -- "$0: file not found: $dict" + return 1 +fi + +# Read all dictionary words and leave only those made of 1-6 characters. +local -a words +words=(${(M)${(f)"$(<$dict)"}:#[a-zA-Z](#c1,6)}) || return + +if (( $#words < 2 )); then + print -ru2 -- "$0: not enough suitable words in $dict" + return 1 +fi + +if (( $#words > 16#7FFFFFFF )); then + print -ru2 -- "$0: too many words in $dict" + return 1 +fi + +# Figure out how many words we need for 128 bits of security margin. +# Each word adds log2($#words) bits. +local -i n=$((ceil(128. / log2($#words)))) + +{ + local c + repeat ${1-1}; do + print -rn -- $n + repeat $n; do + while true; do + # Generate a random number in [0, 2**31). + local -i rnd=0 + repeat 4; do + sysread -s1 c || return + (( rnd = (~(1 << 23) & rnd) << 8 | #c )) + done + # Avoid bias towards words in the beginning of the list. + (( rnd < 16#7FFFFFFF / $#words * $#words )) || continue + print -rn -- -$words[rnd%$#words+1] + break + done + done + print + done +} </dev/urandom diff --git a/plugins/genpass/genpass.plugin.zsh b/plugins/genpass/genpass.plugin.zsh new file mode 100644 index 000000000..a0ea841cd --- /dev/null +++ b/plugins/genpass/genpass.plugin.zsh @@ -0,0 +1 @@ +autoload -Uz genpass-apple genpass-monkey genpass-xkcd diff --git a/plugins/git-auto-fetch/README.md b/plugins/git-auto-fetch/README.md index 35f3c2f71..e96ab42a3 100644 --- a/plugins/git-auto-fetch/README.md +++ b/plugins/git-auto-fetch/README.md @@ -1,26 +1,29 @@ # Git auto-fetch -Automatically fetches all changes from all remotes while you are working in git-initialized directory. +Automatically fetches all changes from all remotes while you are working in a git-initialized directory. -#### Usage - -Add `git-auto-fetch` to the plugins array in your zshrc file: +To use it, add `git-auto-fetch` to the plugins array in your zshrc file: ```shell plugins=(... git-auto-fetch) ``` -Every time you launch a command in your shell all remotes will be fetched in background. -By default autofetch will be triggered only if last fetch was done at least 60 seconds ago. -You can change fetch interval in your .zshrc: -``` -GIT_AUTO_FETCH_INTERVAL=1200 #in seconds +## Usage + +Every time the command prompt is shown all remotes will be fetched in the background. By default, +`git-auto-fetch` will be triggered only if the last auto-fetch was done at least 60 seconds ago. +You can change the fetch interval in your .zshrc: + +```sh +GIT_AUTO_FETCH_INTERVAL=1200 # in seconds ``` -Log of `git fetch --all` will be saved into `.git/FETCH_LOG` +A log of `git fetch --all` will be saved in `.git/FETCH_LOG`. + +## Toggle auto-fetch per folder -#### Toggle auto fetch per folder -If you are using mobile connection or for any other reason you can disable git-auto-fetch for any folder: +If you are using a mobile connection or for any other reason you can disable git-auto-fetch +for any folder: ```shell $ cd to/your/project @@ -29,3 +32,19 @@ disabled $ git-auto-fetch enabled ``` + +## Caveats + +Automatically fetching all changes defeats the purpose of `git push --force-with-lease`, +and makes it behave like `git push --force` in some cases. For example: + +Consider that you made some changes and possibly rebased some stuff, which means you'll +need to use `--force-with-lease` to overwrite the remote history of a branch. Between the +time when you make the changes (maybe do a `git log`) and the time when you `git push`, +it's possible that someone else updates the branch you're working on. + +If `git-auto-fetch` triggers then, you'll have fetched the remote changes without knowing +it, and even though you're running the push with `--force-with-lease`, git will overwrite +the recent changes because you already have them in your local repository. The +[`git push --force-with-lease` docs](https://git-scm.com/docs/git-push) talk about possible +solutions to this problem. diff --git a/plugins/git-auto-fetch/git-auto-fetch.plugin.zsh b/plugins/git-auto-fetch/git-auto-fetch.plugin.zsh index 5c42c21a7..0da84f2f5 100644 --- a/plugins/git-auto-fetch/git-auto-fetch.plugin.zsh +++ b/plugins/git-auto-fetch/git-auto-fetch.plugin.zsh @@ -1,36 +1,61 @@ -GIT_AUTO_FETCH_INTERVAL=${GIT_AUTO_FETCH_INTERVAL:=60} +# Default auto-fetch interval: 60 seconds +: ${GIT_AUTO_FETCH_INTERVAL:=60} + +# Necessary for the git-fetch-all function +zmodload zsh/datetime zsh/stat function git-fetch-all { - (`command git rev-parse --is-inside-work-tree 2>/dev/null` && - dir=`command git rev-parse --git-dir` && - [[ ! -f $dir/NO_AUTO_FETCH ]] && - (( `date +%s` - `date -r $dir/FETCH_LOG +%s 2>/dev/null || echo 0` > $GIT_AUTO_FETCH_INTERVAL )) && - GIT_SSH_COMMAND="command ssh -o BatchMode=yes" \ - command git fetch --all 2>/dev/null &>! $dir/FETCH_LOG &) + ( + # Get git root directory + if ! gitdir="$(command git rev-parse --git-dir 2>/dev/null)"; then + return 0 + fi + + # Do nothing if auto-fetch disabled + if [[ -z "$gitdir" || -f "$gitdir/NO_AUTO_FETCH" ]]; then + return 0 + fi + + # Get time (seconds) when auto-fetch was last run + lastrun="$(zstat +mtime "$gitdir/FETCH_LOG" 2>/dev/null || echo 0)" + # Do nothing if not enough time has passed since last auto-fetch + if (( EPOCHSECONDS - lastrun < $GIT_AUTO_FETCH_INTERVAL )); then + return 0 + fi + + # Fetch all remotes (avoid ssh passphrase prompt) + GIT_SSH_COMMAND="command ssh -o BatchMode=yes" \ + command git fetch --all 2>/dev/null &>! "$gitdir/FETCH_LOG" + ) &| } function git-auto-fetch { - `command git rev-parse --is-inside-work-tree 2>/dev/null` || return - guard="`command git rev-parse --git-dir`/NO_AUTO_FETCH" + # Do nothing if not in a git repository + command git rev-parse --is-inside-work-tree &>/dev/null || return 0 - (rm $guard 2>/dev/null && - echo "${fg_bold[green]}enabled${reset_color}") || - (touch $guard && - echo "${fg_bold[red]}disabled${reset_color}") + # Remove or create guard file depending on its existence + local guard="$(command git rev-parse --git-dir)/NO_AUTO_FETCH" + if [[ -f "$guard" ]]; then + command rm "$guard" && echo "${fg_bold[green]}enabled${reset_color}" + else + command touch "$guard" && echo "${fg_bold[red]}disabled${reset_color}" + fi } -# Override zle-line-init if it exists -if (( $+functions[zle-line-init] )); then - eval "override-git-auto-fetch-$(declare -f zle-line-init)" - - function zle-line-init () { - git-fetch-all - override-git-auto-fetch-zle-line-init - } -else - function zle-line-init () { - git-fetch-all - } -fi - -zle -N zle-line-init +# zle-line-init widget (don't redefine if already defined) +(( ! ${+functions[_git-auto-fetch_zle-line-init]} )) || return 0 + +case "$widgets[zle-line-init]" in + # Simply define the function if zle-line-init doesn't yet exist + builtin|"") function _git-auto-fetch_zle-line-init() { + git-fetch-all + } ;; + # Override the current zle-line-init widget, calling the old one + user:*) zle -N _git-auto-fetch_orig_zle-line-init "${widgets[zle-line-init]#user:}" + function _git-auto-fetch_zle-line-init() { + git-fetch-all + zle _git-auto-fetch_orig_zle-line-init -- "$@" + } ;; +esac + +zle -N zle-line-init _git-auto-fetch_zle-line-init diff --git a/plugins/git-prompt/gitstatus.py b/plugins/git-prompt/gitstatus.py index 300365d71..bf3173614 100644 --- a/plugins/git-prompt/gitstatus.py +++ b/plugins/git-prompt/gitstatus.py @@ -11,11 +11,11 @@ def get_tagname_or_hash(): """return tagname if exists else hash""" # get hash hash_cmd = ['git', 'rev-parse', '--short', 'HEAD'] - hash_ = check_output(hash_cmd).strip() + hash_ = check_output(hash_cmd).decode('utf-8').strip() # get tagname tags_cmd = ['git', 'for-each-ref', '--points-at=HEAD', '--count=2', '--sort=-version:refname', '--format=%(refname:short)', 'refs/tags'] - tags = check_output(tags_cmd).split() + tags = check_output(tags_cmd).decode('utf-8').split() if tags: return tags[0] + ('+' if len(tags) > 1 else '') diff --git a/plugins/git/git.plugin.zsh b/plugins/git/git.plugin.zsh index bf2619976..e32136f15 100644 --- a/plugins/git/git.plugin.zsh +++ b/plugins/git/git.plugin.zsh @@ -31,6 +31,7 @@ function work_in_progress() { # Check if main exists and use instead of master function git_main_branch() { + command git rev-parse --git-dir &>/dev/null || return local branch for branch in main trunk; do if command git show-ref -q --verify refs/heads/$branch; then diff --git a/plugins/grc/README.md b/plugins/grc/README.md new file mode 100644 index 000000000..dfda41466 --- /dev/null +++ b/plugins/grc/README.md @@ -0,0 +1,37 @@ +# Generic Colouriser plugin + +This plugin adds wrappers for commands supported by [Generic Colouriser](https://github.com/garabik/grc): + +To use it, add `grc` to the plugins array in your zshrc file: + +```zsh +plugins=(... grc) +``` + +## Commands + +The following commands are wrapped by `grc` so that their output is automatically colored: + +- `cc` +- `configure` +- `cvs` +- `df` +- `diff` +- `dig` +- `gcc` +- `gmake` +- `ifconfig` +- `iwconfig` +- `last` +- `ldap` +- `make` +- `mount` +- `mtr` +- `netstat` +- `ping` +- `ping6` +- `ps` +- `traceroute` +- `traceroute6` +- `wdiff` +- `whois` diff --git a/plugins/grc/grc.plugin.zsh b/plugins/grc/grc.plugin.zsh new file mode 100644 index 000000000..6a52ec568 --- /dev/null +++ b/plugins/grc/grc.plugin.zsh @@ -0,0 +1,44 @@ +# Adapted from: https://github.com/garabik/grc/blob/master/grc.zsh + +if [[ "$TERM" = dumb ]] || (( ! $+commands[grc] )); then + return +fi + +# Supported commands +cmds=( + cc + configure + cvs + df + diff + dig + gcc + gmake + ifconfig + iwconfig + last + ldap + make + mount + mtr + netstat + ping + ping6 + ps + traceroute + traceroute6 + wdiff + whois +) + +# Set alias for supported commands +for cmd in $cmds; do + if (( $+commands[$cmd] )); then + eval "function $cmd { + grc --colour=auto \"${commands[$cmd]}\" \"\$@\" + }" + fi +done + +# Clean up variables +unset cmds cmd diff --git a/plugins/hanami/README.md b/plugins/hanami/README.md index 3ac8defbb..c9e09f4f2 100644 --- a/plugins/hanami/README.md +++ b/plugins/hanami/README.md @@ -1,32 +1,45 @@ -# Hanami Plugin # -This plugin adds convenient ways to work with [Hanami](https://hanamirb.org/) via console. +# Hanami Plugin + +This plugin adds convenient aliases to work with [Hanami](https://hanamirb.org/) via console. It's inspired by Rails plugin, so if you've used it, you'll feel like home. -## Usage ## +To use it, add `hanami` to the plugins array in your zshrc file: + +```zsh +plugins=(... hanami) +``` + +## Usage For example, type `hc` into your console when you're within Hanami project directory to run the application console. Have a look at available shortcuts below. You can read more about these commands [on the official website](https://hanamirb.org/guides/command-line/applications/). -## Aliases ## - -| Alias | Command | Description | -|-------|---------------------------|---------------------------------------------------------| -| HED | HANAMI_ENV=development | Set environment variable HANAMI_ENV to development | -| HEP | HANAMI_ENV=production | Set environment variable HANAMI_ENV to production | -| HET | HANAMI_ENV=test | Set environment variable HANAMI_ENV to test | -| hc | hanami console | Run application console | -| hd | hanami destroy | Remove specified hanami resource | -| hg | hanami generate | Create specified hanami resource | -| hgm | hanami generate migration | Create migration file | -| hs | hanami server | Launch server with hanami application | -| hsp | hanami server -p | Launch server with specified port | -| hr | hanami routes | List application routes | -| hdc | hanami db create | Create application database | -| hdd | hanami db drop | Delete application database | -| hdp | hanami db prepare | Prepare database for the current environment | -| hda | hanami db apply | Recreates a fresh schema after migrations (destructive) | -| hdv | hanami db version | Print current database version | -| hdrs | hdd && hdp | Drop and recreate application database | -| hdtp | HET hdp | Actualize test environment database | -| hrg | hr | grep | Grep hanami routes with specified pattern | +## Aliases + +| Alias | Command | Description | +|-------|-----------------------------|---------------------------------------------------------| +| HED\* | `HANAMI_ENV=development` | Set environment variable HANAMI_ENV to development | +| HEP\* | `HANAMI_ENV=production` | Set environment variable HANAMI_ENV to production | +| HET\* | `HANAMI_ENV=test` | Set environment variable HANAMI_ENV to test | +| hc | `hanami console` | Run application console | +| hd | `hanami destroy` | Remove specified hanami resource | +| hg | `hanami generate` | Create specified hanami resource | +| hgm | `hanami generate migration` | Create migration file | +| hs | `hanami server` | Launch server with hanami application | +| hsp | `hanami server -p` | Launch server with specified port | +| hr | `hanami routes` | List application routes | +| hdc | `hanami db create` | Create application database | +| hdd | `hanami db drop` | Delete application database | +| hdp | `hanami db prepare` | Prepare database for the current environment | +| hda | `hanami db apply` | Recreates a fresh schema after migrations (destructive) | +| hdv | `hanami db version` | Print current database version | +| hdrs | `hdd && hdp` | Drop and recreate application database | +| hdtp | `HET hdp` | Actualize test environment database | +| hrg | `hr | grep` | Grep hanami routes with specified pattern | + +\* You should use these at the beginning of a command, for example: + +```console +$ HED hdd # equivalent to 'HANAMI_ENV=development hanami db drop' +``` diff --git a/plugins/hanami/hanami.plugin.zsh b/plugins/hanami/hanami.plugin.zsh index 349c42cae..42143b428 100644 --- a/plugins/hanami/hanami.plugin.zsh +++ b/plugins/hanami/hanami.plugin.zsh @@ -1,6 +1,6 @@ -alias -g HED='HANAMI_ENV=development' -alias -g HEP='HANAMI_ENV=production' -alias -g HET='HANAMI_ENV=test' +alias HED='HANAMI_ENV=development ' +alias HEP='HANAMI_ENV=production ' +alias HET='HANAMI_ENV=test ' alias hc='hanami console' alias hd='hanami destroy' diff --git a/plugins/magic-enter/magic-enter.plugin.zsh b/plugins/magic-enter/magic-enter.plugin.zsh index 2d4d578b6..55b893535 100644 --- a/plugins/magic-enter/magic-enter.plugin.zsh +++ b/plugins/magic-enter/magic-enter.plugin.zsh @@ -1,27 +1,38 @@ -# Bind quick stuff to enter! -# -# Pressing enter in a git directory runs `git status` -# in other directories `ls` -magic-enter () { - # If commands are not already set, use the defaults - [ -z "$MAGIC_ENTER_GIT_COMMAND" ] && MAGIC_ENTER_GIT_COMMAND="git status -u ." - [ -z "$MAGIC_ENTER_OTHER_COMMAND" ] && MAGIC_ENTER_OTHER_COMMAND="ls -lh ." +# Default commands +: ${MAGIC_ENTER_GIT_COMMAND:="git status -u ."} # run when in a git repository +: ${MAGIC_ENTER_OTHER_COMMAND:="ls -lh ."} # run anywhere else - if [[ -z $BUFFER ]]; then - echo "" - if git rev-parse --is-inside-work-tree &>/dev/null; then - eval "$MAGIC_ENTER_GIT_COMMAND" - else - eval "$MAGIC_ENTER_OTHER_COMMAND" - fi - zle redisplay +magic-enter() { + # Only run MAGIC_ENTER commands when in PS1 and command line is empty + # http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html#User_002dDefined-Widgets + if [[ -n "$BUFFER" || "$CONTEXT" != start ]]; then + return + fi + + if command git rev-parse --is-inside-work-tree &>/dev/null; then + BUFFER="$MAGIC_ENTER_GIT_COMMAND" else - zle accept-line + BUFFER="$MAGIC_ENTER_OTHER_COMMAND" fi } -zle -N magic-enter +# Wrapper for the accept-line zle widget (run when pressing Enter) + +# If the wrapper already exists don't redefine it +(( ! ${+functions[_magic-enter_accept-line]} )) || return 0 + +case "$widgets[accept-line]" in + # Override the current accept-line widget, calling the old one + user:*) zle -N _magic-enter_orig_accept-line "${widgets[accept-line]#user:}" + function _magic-enter_accept-line() { + magic-enter + zle _magic-enter_orig_accept-line -- "$@" + } ;; + # If no user widget defined, call the original accept-line widget + builtin) function _magic-enter_accept-line() { + magic-enter + zle .accept-line + } ;; +esac -bindkey -M emacs "^M" magic-enter -bindkey -M vicmd "^M" magic-enter -bindkey -M viins "^M" magic-enter +zle -N accept-line _magic-enter_accept-line diff --git a/plugins/wp-cli/README.md b/plugins/wp-cli/README.md index 43c41eb53..c4993ab4c 100644 --- a/plugins/wp-cli/README.md +++ b/plugins/wp-cli/README.md @@ -1,107 +1,109 @@ # WP-CLI -**Maintainer:** [joshmedeski](https://github.com/joshmedeski) - -WordPress Command Line Interface (https://wp-cli.org/) - -WP-CLI is a set of command-line tools for managing WordPress installations. You can update plugins, set up multisite installs and much more, without using a web browser. - -This plugin adds [tab completion](https://wp-cli.org/#tab-completions) for `wp-cli` as well as several aliases. - -## List of Aliases - -### Core -- wpcc='wp core config' -- wpcd='wp core download' -- wpci='wp core install' -- wpcii='wp core is-installed' -- wpcmc='wp core multisite-convert' -- wpcmi='wp core multisite-install' -- wpcu='wp core update' -- wpcudb='wp core update-db' -- wpcvc='wp core verify-checksums' - -### Cron -- wpcre='wp cron event' -- wpcrs='wp cron schedule' -- wpcrt='wp cron test' +The [WordPress CLI](https://wp-cli.org/) is a command-line tool for managing WordPress installations. You can update plugins, set up multisite installs and much more, without using a web browser. -### Menu -- wpmc='wp menu create' -- wpmd='wp menu delete' -- wpmi='wp menu item' -- wpml='wp menu list' -- wpmlo='wp menu location' +This plugin adds [tab completion](https://wp-cli.org/#tab-completions) for `wp-cli` as well as several aliases for commonly used commands. -### Plugin -- wppa='activate' -- wppda='deactivate' -- wppd='delete' -- wppg='get' -- wppi='install' -- wppis='is-installed' -- wppl='list' -- wppp='path' -- wpps='search' -- wppst='status' -- wppt='toggle' -- wppun='uninstall' -- wppu='update' +To use it, add `wp-cli` to the plugins array in your zshrc file: -### Post -- wppoc='wp post create' -- wppod='wp post delete' -- wppoe='wp post edit' -- wppogen='wp post generate' -- wppog='wp post get' -- wppol='wp post list' -- wppom='wp post meta' -- wppou='wp post update' -- wppourl='wp post url' +```zsh +plugins=(... wp-cli) +``` -### Sidebar -- wpsbl='wp sidebar list' - -### Theme -- wpta='wp theme activate' -- wptd='wp theme delete' -- wptdis='wp theme disable' -- wpte='wp theme enable' -- wptg='wp theme get' -- wpti='wp theme install' -- wptis='wp theme is-installed' -- wptl='wp theme list' -- wptm='wp theme mod' -- wptp='wp theme path' -- wpts='wp theme search' -- wptst='wp theme status' -- wptu='wp theme update' - -### User -- wpuac='wp user add-cap' -- wpuar='wp user add-role' -- wpuc='wp user create' -- wpud='wp user delete' -- wpugen='wp user generate' -- wpug='wp user get' -- wpui='wp user import-csv' -- wpul='wp user list' -- wpulc='wp user list-caps' -- wpum='wp user meta' -- wpurc='wp user remove-cap' -- wpurr='wp user remove-role' -- wpusr='wp user set-role' -- wpuu='wp user update' - -### Widget -- wpwa='wp widget add' -- wpwda='wp widget deactivate' -- wpwd='wp widget delete' -- wpwl='wp widget list' -- wpwm='wp widget move' -- wpwu='wp widget update' - -The entire list of wp-cli commands can be found here: https://wp-cli.org/commands/ - -I only included the commands that are most used. Please feel free to contribute to this project if you want more commands. +**Maintainer:** [joshmedeski](https://github.com/joshmedeski) +## Aliases + +The entire list of `wp-cli` commands can be found here: https://developer.wordpress.org/cli/commands/ + +| Alias | Command | +|-----------|-----------------------------| +| **Core** | +| `wpcc` | `wp core config` | +| `wpcd` | `wp core download` | +| `wpci` | `wp core install` | +| `wpcii` | `wp core is-installed` | +| `wpcmc` | `wp core multisite-convert` | +| `wpcmi` | `wp core multisite-install` | +| `wpcu` | `wp core update` | +| `wpcudb` | `wp core update-db` | +| `wpcvc` | `wp core verify-checksums` | +| **Cron** | +| `wpcre` | `wp cron event` | +| `wpcrs` | `wp cron schedule` | +| `wpcrt` | `wp cron test` | +| **Database** | +| `wpdbe` | `wp db export` | +| `wpdbi` | `wp db import` | +| `wpdbcr` | `wp db create` | +| `wpdbs` | `wp db search` | +| `wpdbch` | `wp db check` | +| `wpdbr` | `wp db repair` | +| **Menu** | +| `wpmc` | `wp menu create` | +| `wpmd` | `wp menu delete` | +| `wpmi` | `wp menu item` | +| `wpml` | `wp menu list` | +| `wpmlo` | `wp menu location` | +| **Plugin** | +| `wppa` | `wp plugin activate` | +| `wppda` | `wp plugin deactivate` | +| `wppd` | `wp plugin delete` | +| `wppg` | `wp plugin get` | +| `wppi` | `wp plugin install` | +| `wppis` | `wp plugin is-installed` | +| `wppl` | `wp plugin list` | +| `wppp` | `wp plugin path` | +| `wpps` | `wp plugin search` | +| `wppst` | `wp plugin status` | +| `wppt` | `wp plugin toggle` | +| `wppun` | `wp plugin uninstall` | +| `wppu` | `wp plugin update` | +| **Post** | +| `wppoc` | `wp post create` | +| `wppod` | `wp post delete` | +| `wppoe` | `wp post edit` | +| `wppogen` | `wp post generate` | +| `wppog` | `wp post get` | +| `wppol` | `wp post list` | +| `wppom` | `wp post meta` | +| `wppou` | `wp post update` | +| `wppourl` | `wp post url` | +| **Sidebar** | +| `wpsbl` | `wp sidebar list` | +| **Theme** | +| `wpta` | `wp theme activate` | +| `wptd` | `wp theme delete` | +| `wptdis` | `wp theme disable` | +| `wpte` | `wp theme enable` | +| `wptg` | `wp theme get` | +| `wpti` | `wp theme install` | +| `wptis` | `wp theme is-installed` | +| `wptl` | `wp theme list` | +| `wptm` | `wp theme mod` | +| `wptp` | `wp theme path` | +| `wpts` | `wp theme search` | +| `wptst` | `wp theme status` | +| `wptu` | `wp theme update` | +| **User** | +| `wpuac` | `wp user add-cap` | +| `wpuar` | `wp user add-role` | +| `wpuc` | `wp user create` | +| `wpud` | `wp user delete` | +| `wpugen` | `wp user generate` | +| `wpug` | `wp user get` | +| `wpui` | `wp user import-csv` | +| `wpul` | `wp user list` | +| `wpulc` | `wp user list-caps` | +| `wpum` | `wp user meta` | +| `wpurc` | `wp user remove-cap` | +| `wpurr` | `wp user remove-role` | +| `wpusr` | `wp user set-role` | +| `wpuu` | `wp user update` | +| **Widget** | +| `wpwa` | `wp widget add` | +| `wpwda` | `wp widget deactivate` | +| `wpwd` | `wp widget delete` | +| `wpwl` | `wp widget list` | +| `wpwm` | `wp widget move` | +| `wpwu` | `wp widget update` | diff --git a/plugins/wp-cli/wp-cli.plugin.zsh b/plugins/wp-cli/wp-cli.plugin.zsh index 97bed406e..09bdf3260 100644 --- a/plugins/wp-cli/wp-cli.plugin.zsh +++ b/plugins/wp-cli/wp-cli.plugin.zsh @@ -2,14 +2,6 @@ # A command line interface for WordPress # https://wp-cli.org/ -# Cache - -# Cap - -# CLI - -# Comment - # Core alias wpcc='wp core config' alias wpcd='wp core download' @@ -27,18 +19,12 @@ alias wpcrs='wp cron schedule' alias wpcrt='wp cron test' # Db - -# Eval - -# Eval-File - -# Export - -# Help - -# Import - -# Media +alias wpdbe='wp db export' +alias wpdbi='wp db import' +alias wpdbcr='wp db create' +alias wpdbs='wp db search' +alias wpdbch='wp db check' +alias wpdbr='wp db repair' # Menu alias wpmc='wp menu create' @@ -47,10 +33,6 @@ alias wpmi='wp menu item' alias wpml='wp menu list' alias wpmlo='wp menu location' -# Network - -# Option - # Plugin alias wppa='wp plugin activate' alias wppda='wp plugin deactivate' @@ -77,25 +59,9 @@ alias wppom='wp post meta' alias wppou='wp post update' alias wppourl='wp post url' -# Rewrite - -# Role - -# Scaffold - -# Search-Replace - -# Shell - # Sidebar alias wpsbl='wp sidebar list' -# Site - -# Super-Admin - -# Term - # Theme alias wpta='wp theme activate' alias wptd='wp theme delete' @@ -111,8 +77,6 @@ alias wpts='wp theme search' alias wptst='wp theme status' alias wptu='wp theme update' -# Transient - # User alias wpuac='wp user add-cap' alias wpuar='wp user add-role' @@ -138,9 +102,8 @@ alias wpwm='wp widget move' alias wpwu='wp widget update' +# Completion for wp autoload -U +X bashcompinit && bashcompinit -# bash completion for the `wp` command - _wp_complete() { local cur=${COMP_WORDS[COMP_CWORD]} diff --git a/tools/changelog.sh b/tools/changelog.sh new file mode 100755 index 000000000..8753212e9 --- /dev/null +++ b/tools/changelog.sh @@ -0,0 +1,429 @@ +#!/usr/bin/env zsh + +############################## +# CHANGELOG SCRIPT CONSTANTS # +############################## + +#* Holds the list of valid types recognized in a commit subject +#* and the display string of such type +local -A TYPES +TYPES=( + build "Build system" + chore "Chore" + ci "CI" + docs "Documentation" + feat "Features" + fix "Bug fixes" + perf "Performance" + refactor "Refactor" + style "Style" + test "Testing" +) + +#* Types that will be displayed in their own section, +#* in the order specified here. +local -a MAIN_TYPES +MAIN_TYPES=(feat fix perf docs) + +#* Types that will be displayed under the category of other changes +local -a OTHER_TYPES +OTHER_TYPES=(refactor style other) + +#* Commit types that don't appear in $MAIN_TYPES nor $OTHER_TYPES +#* will not be displayed and will simply be ignored. + + +############################ +# COMMIT PARSING UTILITIES # +############################ + +function parse-commit { + + # This function uses the following globals as output: commits (A), + # subjects (A), scopes (A) and breaking (A). All associative arrays (A) + # have $hash as the key. + # - commits holds the commit type + # - subjects holds the commit subject + # - scopes holds the scope of a commit + # - breaking holds the breaking change warning if a commit does + # make a breaking change + + function commit:type { + local type="$(sed -E 's/^([a-zA-Z_\-]+)(\(.+\))?!?: .+$/\1/' <<< "$1")" + + # If $type doesn't appear in $TYPES array mark it as 'other' + if [[ -n "${(k)TYPES[(i)$type]}" ]]; then + echo $type + else + echo other + fi + } + + function commit:scope { + local scope + + # Try to find scope in "type(<scope>):" format + scope=$(sed -nE 's/^[a-zA-Z_\-]+\((.+)\)!?: .+$/\1/p' <<< "$1") + if [[ -n "$scope" ]]; then + echo "$scope" + return + fi + + # If no scope found, try to find it in "<scope>:" format + # Make sure it's not a type before printing it + scope=$(sed -nE 's/^([a-zA-Z_\-]+): .+$/\1/p' <<< "$1") + if [[ -z "${(k)TYPES[(i)$scope]}" ]]; then + echo "$scope" + fi + } + + function commit:subject { + # Only display the relevant part of the commit, i.e. if it has the format + # type[(scope)!]: subject, where the part between [] is optional, only + # displays subject. If it doesn't match the format, returns the whole string. + sed -E 's/^[a-zA-Z_\-]+(\(.+\))?!?: (.+)$/\2/' <<< "$1" + } + + # Return subject if the body or subject match the breaking change format + function commit:is-breaking { + local subject="$1" body="$2" message + + if [[ "$body" =~ "BREAKING CHANGE: (.*)" || \ + "$subject" =~ '^[^ :\)]+\)?!: (.*)$' ]]; then + message="${match[1]}" + # skip next paragraphs (separated by two newlines or more) + message="${message%%$'\n\n'*}" + # ... and replace newlines with spaces + echo "${message//$'\n'/ }" + else + return 1 + fi + } + + # Return truncated hash of the reverted commit + function commit:is-revert { + local subject="$1" body="$2" + + if [[ "$subject" = Revert* && \ + "$body" =~ "This reverts commit ([^.]+)\." ]]; then + echo "${match[1]:0:7}" + else + return 1 + fi + } + + # Parse commit with hash $1 + local hash="$1" subject body warning rhash + subject="$(command git show -s --format=%s $hash)" + body="$(command git show -s --format=%b $hash)" + + # Commits following Conventional Commits (https://www.conventionalcommits.org/) + # have the following format, where parts between [] are optional: + # + # type[(scope)][!]: subject + # + # commit body + # [BREAKING CHANGE: warning] + + # commits holds the commit type + commits[$hash]="$(commit:type "$subject")" + # scopes holds the commit scope + scopes[$hash]="$(commit:scope "$subject")" + # subjects holds the commit subject + subjects[$hash]="$(commit:subject "$subject")" + + # breaking holds whether a commit has breaking changes + # and its warning message if it does + if warning=$(commit:is-breaking "$subject" "$body"); then + breaking[$hash]="$warning" + fi + + # reverts holds commits reverted in the same release + if rhash=$(commit:is-revert "$subject" "$body"); then + reverts[$hash]=$rhash + fi +} + +############################# +# RELEASE CHANGELOG DISPLAY # +############################# + +function display-release { + + # This function uses the following globals: output, version, + # commits (A), subjects (A), scopes (A), breaking (A) and reverts (A). + # + # - output is the output format to use when formatting (raw|text|md) + # - version is the version in which the commits are made + # - commits, subjects, scopes, breaking, and reverts are associative arrays + # with commit hashes as keys + + # Remove commits that were reverted + local hash rhash + for hash rhash in ${(kv)reverts}; do + if (( ${+commits[$rhash]} )); then + # Remove revert commit + unset "commits[$hash]" "subjects[$hash]" "scopes[$hash]" "breaking[$hash]" + # Remove reverted commit + unset "commits[$rhash]" "subjects[$rhash]" "scopes[$rhash]" "breaking[$rhash]" + fi + done + + # If no commits left skip displaying the release + if (( $#commits == 0 )); then + return + fi + + ##* Formatting functions + + # Format the hash according to output format + # If no parameter is passed, assume it comes from `$hash` + function fmt:hash { + #* Uses $hash from outer scope + local hash="${1:-$hash}" + case "$output" in + raw) printf "$hash" ;; + text) printf "\e[33m$hash\e[0m" ;; # red + md) printf "[\`$hash\`](https://github.com/ohmyzsh/ohmyzsh/commit/$hash)" ;; + esac + } + + # Format headers according to output format + # Levels 1 to 2 are considered special, the rest are formatted + # the same, except in md output format. + function fmt:header { + local header="$1" level="$2" + case "$output" in + raw) + case "$level" in + 1) printf "$header\n$(printf '%.0s=' {1..${#header}})\n\n" ;; + 2) printf "$header\n$(printf '%.0s-' {1..${#header}})\n\n" ;; + *) printf "$header:\n\n" ;; + esac ;; + text) + case "$level" in + 1|2) printf "\e[1;4m$header\e[0m\n\n" ;; # bold, underlined + *) printf "\e[1m$header:\e[0m\n\n" ;; # bold + esac ;; + md) printf "$(printf '%.0s#' {1..${level}}) $header\n\n" ;; + esac + } + + function fmt:scope { + #* Uses $scopes (A) and $hash from outer scope + local scope="${1:-${scopes[$hash]}}" + + # Get length of longest scope for padding + local max_scope=0 padding=0 + for hash in ${(k)scopes}; do + max_scope=$(( max_scope < ${#scopes[$hash]} ? ${#scopes[$hash]} : max_scope )) + done + + # If no scopes, exit the function + if [[ $max_scope -eq 0 ]]; then + return + fi + + # Get how much padding is required for this scope + padding=$(( max_scope < ${#scope} ? 0 : max_scope - ${#scope} )) + padding="${(r:$padding:: :):-}" + + # If no scope, print padding and 3 spaces (equivalent to "[] ") + if [[ -z "$scope" ]]; then + printf "${padding} " + return + fi + + # Print [scope] + case "$output" in + raw|md) printf "[$scope]${padding} " ;; + text) printf "[\e[38;5;9m$scope\e[0m]${padding} " ;; # red 9 + esac + } + + # If no parameter is passed, assume it comes from `$subjects[$hash]` + function fmt:subject { + #* Uses $subjects (A) and $hash from outer scope + local subject="${1:-${subjects[$hash]}}" + + # Capitalize first letter of the subject + subject="${(U)subject:0:1}${subject:1}" + + case "$output" in + raw) printf "$subject" ;; + # In text mode, highlight (#<issue>) and dim text between `backticks` + text) sed -E $'s|#([0-9]+)|\e[32m#\\1\e[0m|g;s|`([^`]+)`|`\e[2m\\1\e[0m`|g' <<< "$subject" ;; + # In markdown mode, link to (#<issue>) issues + md) sed -E 's|#([0-9]+)|[#\1](https://github.com/ohmyzsh/ohmyzsh/issues/\1)|g' <<< "$subject" ;; + esac + } + + function fmt:type { + #* Uses $type from outer scope + local type="${1:-${TYPES[$type]:-${(C)type}}}" + [[ -z "$type" ]] && return 0 + case "$output" in + raw|md) printf "$type: " ;; + text) printf "\e[4m$type\e[24m: " ;; # underlined + esac + } + + ##* Section functions + + function display:version { + fmt:header "$version" 2 + } + + function display:breaking { + (( $#breaking != 0 )) || return 0 + + case "$output" in + raw) fmt:header "BREAKING CHANGES" 3 ;; + text|md) fmt:header "⚠ BREAKING CHANGES" 3 ;; + esac + + local hash subject + for hash message in ${(kv)breaking}; do + echo " - $(fmt:hash) $(fmt:subject "${message}")" + done | sort + echo + } + + function display:type { + local hash type="$1" + + local -a hashes + hashes=(${(k)commits[(R)$type]}) + + # If no commits found of type $type, go to next type + (( $#hashes != 0 )) || return 0 + + fmt:header "${TYPES[$type]}" 3 + for hash in $hashes; do + echo " - $(fmt:hash) $(fmt:scope)$(fmt:subject)" + done | sort -k3 # sort by scope + echo + } + + function display:others { + local hash type + + # Commits made under types considered other changes + local -A changes + changes=(${(kv)commits[(R)${(j:|:)OTHER_TYPES}]}) + + # If no commits found under "other" types, don't display anything + (( $#changes != 0 )) || return 0 + + fmt:header "Other changes" 3 + for hash type in ${(kv)changes}; do + case "$type" in + other) echo " - $(fmt:hash) $(fmt:scope)$(fmt:subject)" ;; + *) echo " - $(fmt:hash) $(fmt:scope)$(fmt:type)$(fmt:subject)" ;; + esac + done | sort -k3 # sort by scope + echo + } + + ##* Release sections order + + # Display version header + display:version + + # Display breaking changes first + display:breaking + + # Display changes for commit types in the order specified + for type in $MAIN_TYPES; do + display:type "$type" + done + + # Display other changes + display:others +} + +function main { + # $1 = until commit, $2 = since commit + local until="$1" since="$2" + + # $3 = output format (--text|--raw|--md) + # --md: uses markdown formatting + # --raw: outputs without style + # --text: uses ANSI escape codes to style the output + local output=${${3:-"--text"}#--*} + + if [[ -z "$until" ]]; then + until=HEAD + fi + + if [[ -z "$since" ]]; then + # If $since is not specified: + # 1) try to find the version used before updating + # 2) try to find the first version tag before $until + since=$(command git config --get oh-my-zsh.lastVersion 2>/dev/null) || \ + since=$(command git describe --abbrev=0 --tags "$until^" 2>/dev/null) || \ + unset since + elif [[ "$since" = --all ]]; then + unset since + fi + + # Commit classification arrays + local -A commits subjects scopes breaking reverts + local truncate=0 read_commits=0 + local hash version tag + + # Get the first version name: + # 1) try tag-like version, or + # 2) try name-rev, or + # 3) try branch name, or + # 4) try short hash + version=$(command git describe --tags $until 2>/dev/null) \ + || version=$(command git name-rev --no-undefined --name-only --exclude="remotes/*" $until 2>/dev/null) \ + || version=$(command git symbolic-ref --quiet --short $until 2>/dev/null) \ + || version=$(command git rev-parse --short $until 2>/dev/null) + + # Get commit list from $until commit until $since commit, or until root + # commit if $since is unset, in short hash form. + # --first-parent is used when dealing with merges: it only prints the + # merge commit, not the commits of the merged branch. + command git rev-list --first-parent --abbrev-commit --abbrev=7 ${since:+$since..}$until | while read hash; do + # Truncate list on versions with a lot of commits + if [[ -z "$since" ]] && (( ++read_commits > 35 )); then + truncate=1 + break + fi + + # If we find a new release (exact tag) + if tag=$(command git describe --exact-match --tags $hash 2>/dev/null); then + # Output previous release + display-release + # Reinitialize commit storage + commits=() + subjects=() + scopes=() + breaking=() + reverts=() + # Start work on next release + version="$tag" + read_commits=1 + fi + + parse-commit "$hash" + done + + display-release + + if (( truncate )); then + echo " ...more commits omitted" + echo + fi +} + +cd "$ZSH" + +# Use raw output if stdout is not a tty +if [[ ! -t 1 && -z "$3" ]]; then + main "$1" "$2" --raw +else + main "$@" +fi diff --git a/tools/check_for_upgrade.sh b/tools/check_for_upgrade.sh index cadd5fe49..d0ceba92d 100644 --- a/tools/check_for_upgrade.sh +++ b/tools/check_for_upgrade.sh @@ -1,6 +1,6 @@ # Migrate .zsh-update file to $ZSH_CACHE_DIR if [[ -f ~/.zsh-update && ! -f "${ZSH_CACHE_DIR}/.zsh-update" ]]; then - mv ~/.zsh-update "${ZSH_CACHE_DIR}/.zsh-update" + mv ~/.zsh-update "${ZSH_CACHE_DIR}/.zsh-update" fi # Cancel update if: @@ -10,79 +10,81 @@ fi if [[ "$DISABLE_AUTO_UPDATE" = true ]] \ || [[ ! -w "$ZSH" || ! -O "$ZSH" ]] \ || ! command -v git &>/dev/null; then - return + return fi function current_epoch() { - zmodload zsh/datetime - echo $(( EPOCHSECONDS / 60 / 60 / 24 )) + zmodload zsh/datetime + echo $(( EPOCHSECONDS / 60 / 60 / 24 )) } function update_last_updated_file() { - echo "LAST_EPOCH=$(current_epoch)" >! "${ZSH_CACHE_DIR}/.zsh-update" + echo "LAST_EPOCH=$(current_epoch)" >! "${ZSH_CACHE_DIR}/.zsh-update" } function update_ohmyzsh() { - ZSH="$ZSH" sh "$ZSH/tools/upgrade.sh" - update_last_updated_file + ZSH="$ZSH" zsh -f "$ZSH/tools/upgrade.sh" --interactive + update_last_updated_file } () { - emulate -L zsh + emulate -L zsh - local epoch_target mtime option LAST_EPOCH + local epoch_target mtime option LAST_EPOCH - # Remove lock directory if older than a day - zmodload zsh/datetime - zmodload -F zsh/stat b:zstat - if mtime=$(zstat +mtime "$ZSH/log/update.lock" 2>/dev/null); then - if (( (mtime + 3600 * 24) < EPOCHSECONDS )); then - command rm -rf "$ZSH/log/update.lock" - fi + # Remove lock directory if older than a day + zmodload zsh/datetime + zmodload -F zsh/stat b:zstat + if mtime=$(zstat +mtime "$ZSH/log/update.lock" 2>/dev/null); then + if (( (mtime + 3600 * 24) < EPOCHSECONDS )); then + command rm -rf "$ZSH/log/update.lock" fi + fi - # Check for lock directory - if ! command mkdir "$ZSH/log/update.lock" 2>/dev/null; then - return - fi + # Check for lock directory + if ! command mkdir "$ZSH/log/update.lock" 2>/dev/null; then + return + fi - # Remove lock directory on exit. `return 1` is important for when trapping a SIGINT: - # The return status from the function is handled specially. If it is zero, the signal is - # assumed to have been handled, and execution continues normally. Otherwise, the shell - # will behave as interrupted except that the return status of the trap is retained. - trap "command rm -rf '$ZSH/log/update.lock'; return 1" EXIT INT QUIT + # Remove lock directory on exit. `return 1` is important for when trapping a SIGINT: + # The return status from the function is handled specially. If it is zero, the signal is + # assumed to have been handled, and execution continues normally. Otherwise, the shell + # will behave as interrupted except that the return status of the trap is retained. + trap " + unset -f current_epoch update_last_updated_file update_ohmyzsh + command rm -rf '$ZSH/log/update.lock' + return 1 + " EXIT INT QUIT - # Create or update .zsh-update file if missing or malformed - if ! source "${ZSH_CACHE_DIR}/.zsh-update" 2>/dev/null || [[ -z "$LAST_EPOCH" ]]; then - update_last_updated_file - return - fi + # Create or update .zsh-update file if missing or malformed + if ! source "${ZSH_CACHE_DIR}/.zsh-update" 2>/dev/null || [[ -z "$LAST_EPOCH" ]]; then + update_last_updated_file + return + fi - # Number of days before trying to update again - epoch_target=${UPDATE_ZSH_DAYS:-13} - # Test if enough time has passed until the next update - if (( ( $(current_epoch) - $LAST_EPOCH ) < $epoch_target )); then - return - fi + # Number of days before trying to update again + epoch_target=${UPDATE_ZSH_DAYS:-13} + # Test if enough time has passed until the next update + if (( ( $(current_epoch) - $LAST_EPOCH ) < $epoch_target )); then + return + fi - # Ask for confirmation before updating unless disabled - if [[ "$DISABLE_UPDATE_PROMPT" = true ]]; then - update_ohmyzsh - else - # input sink to swallow all characters typed before the prompt - # and add a newline if there wasn't one after characters typed - while read -t -k 1 option; do true; done - [[ "$option" != ($'\n'|"") ]] && echo + # Ask for confirmation before updating unless disabled + if [[ "$DISABLE_UPDATE_PROMPT" = true ]]; then + update_ohmyzsh + else + # input sink to swallow all characters typed before the prompt + # and add a newline if there wasn't one after characters typed + while read -t -k 1 option; do true; done + [[ "$option" != ($'\n'|"") ]] && echo - echo -n "[oh-my-zsh] Would you like to update? [Y/n] " - read -r -k 1 option - [[ "$option" != $'\n' ]] && echo - case "$option" in - [yY$'\n']) update_ohmyzsh ;; - [nN]) update_last_updated_file ;; - esac - fi + echo -n "[oh-my-zsh] Would you like to update? [Y/n] " + read -r -k 1 option + [[ "$option" != $'\n' ]] && echo + case "$option" in + [yY$'\n']) update_ohmyzsh ;; + [nN]) update_last_updated_file ;; + esac + fi } - -unset -f current_epoch update_last_updated_file update_ohmyzsh diff --git a/tools/upgrade.sh b/tools/upgrade.sh index e005519d6..4df7eb184 100644..100755 --- a/tools/upgrade.sh +++ b/tools/upgrade.sh @@ -1,40 +1,49 @@ -# Use colors, but only if connected to a terminal, and that terminal -# supports them. +#!/usr/bin/env zsh + +if [ -z "$ZSH_VERSION" ]; then + exec zsh "$0" +fi + +cd "$ZSH" + +# Use colors, but only if connected to a terminal +# and that terminal supports them. + +local -a RAINBOW +local RED GREEN YELLOW BLUE BOLD DIM UNDER RESET + if [ -t 1 ]; then - RB_RED=$(printf '\033[38;5;196m') - RB_ORANGE=$(printf '\033[38;5;202m') - RB_YELLOW=$(printf '\033[38;5;226m') - RB_GREEN=$(printf '\033[38;5;082m') - RB_BLUE=$(printf '\033[38;5;021m') - RB_INDIGO=$(printf '\033[38;5;093m') - RB_VIOLET=$(printf '\033[38;5;163m') + RAINBOW=( + "$(printf '\033[38;5;196m')" + "$(printf '\033[38;5;202m')" + "$(printf '\033[38;5;226m')" + "$(printf '\033[38;5;082m')" + "$(printf '\033[38;5;021m')" + "$(printf '\033[38;5;093m')" + "$(printf '\033[38;5;163m')" + ) RED=$(printf '\033[31m') GREEN=$(printf '\033[32m') YELLOW=$(printf '\033[33m') BLUE=$(printf '\033[34m') BOLD=$(printf '\033[1m') + DIM=$(printf '\033[2m') UNDER=$(printf '\033[4m') RESET=$(printf '\033[m') -else - RB_RED="" - RB_ORANGE="" - RB_YELLOW="" - RB_GREEN="" - RB_BLUE="" - RB_INDIGO="" - RB_VIOLET="" - - RED="" - GREEN="" - YELLOW="" - BLUE="" - UNDER="" - BOLD="" - RESET="" fi -cd "$ZSH" +# Update upstream remote to ohmyzsh org +git remote -v | while read remote url extra; do + case "$url" in + https://github.com/robbyrussell/oh-my-zsh(|.git)) + git remote set-url "$remote" "https://github.com/ohmyzsh/ohmyzsh.git" + break ;; + git@github.com:robbyrussell/oh-my-zsh(|.git)) + git remote set-url "$remote" "git@github.com:ohmyzsh/ohmyzsh.git" + break ;; + esac +done # Set git-config values known to fix git errors # Line endings (#4069) @@ -45,30 +54,50 @@ git config fsck.zeroPaddedFilemode ignore git config fetch.fsck.zeroPaddedFilemode ignore git config receive.fsck.zeroPaddedFilemode ignore # autostash on rebase (#7172) -resetAutoStash=$(git config --bool rebase.autoStash 2>&1) +resetAutoStash=$(git config --bool rebase.autoStash 2>/dev/null) git config rebase.autoStash true -# Update upstream remote to ohmyzsh org -remote=$(git remote -v | awk '/https:\/\/github\.com\/robbyrussell\/oh-my-zsh\.git/{ print $1; exit }') -if [ -n "$remote" ]; then - git remote set-url "$remote" "https://github.com/ohmyzsh/ohmyzsh.git" -fi +local ret=0 +# Update Oh My Zsh printf "${BLUE}%s${RESET}\n" "Updating Oh My Zsh" -if git pull --rebase --stat origin master -then - printf '%s %s__ %s %s %s %s %s__ %s\n' $RB_RED $RB_ORANGE $RB_YELLOW $RB_GREEN $RB_BLUE $RB_INDIGO $RB_VIOLET $RB_RESET - printf '%s ____ %s/ /_ %s ____ ___ %s__ __ %s ____ %s_____%s/ /_ %s\n' $RB_RED $RB_ORANGE $RB_YELLOW $RB_GREEN $RB_BLUE $RB_INDIGO $RB_VIOLET $RB_RESET - printf '%s / __ \%s/ __ \ %s / __ `__ \%s/ / / / %s /_ / %s/ ___/%s __ \ %s\n' $RB_RED $RB_ORANGE $RB_YELLOW $RB_GREEN $RB_BLUE $RB_INDIGO $RB_VIOLET $RB_RESET - printf '%s/ /_/ /%s / / / %s / / / / / /%s /_/ / %s / /_%s(__ )%s / / / %s\n' $RB_RED $RB_ORANGE $RB_YELLOW $RB_GREEN $RB_BLUE $RB_INDIGO $RB_VIOLET $RB_RESET - printf '%s\____/%s_/ /_/ %s /_/ /_/ /_/%s\__, / %s /___/%s____/%s_/ /_/ %s\n' $RB_RED $RB_ORANGE $RB_YELLOW $RB_GREEN $RB_BLUE $RB_INDIGO $RB_VIOLET $RB_RESET - printf '%s %s %s %s /____/ %s %s %s %s\n' $RB_RED $RB_ORANGE $RB_YELLOW $RB_GREEN $RB_BLUE $RB_INDIGO $RB_VIOLET $RB_RESET - printf "${BLUE}%s\n" "Hooray! Oh My Zsh has been updated and/or is at the current version." - printf "${BLUE}${BOLD}%s ${UNDER}%s${RESET}\n" "To keep up on the latest news and updates, follow us on Twitter:" "https://twitter.com/ohmyzsh" +last_commit=$(git rev-parse HEAD) +if git pull --rebase --stat origin master; then + # Check if it was really updated or not + if [[ "$(git rev-parse HEAD)" = "$last_commit" ]]; then + message="Oh My Zsh is already at the latest version." + ret=80 # non-zero exit code to indicate no changes pulled + else + message="Hooray! Oh My Zsh has been updated!" + + # Save the commit prior to updating + git config oh-my-zsh.lastVersion "$last_commit" + + # Display changelog with less if available, otherwise just print it to the terminal + if [[ "$1" = --interactive ]]; then + if (( $+commands[less] )); then + "$ZSH/tools/changelog.sh" HEAD "$last_commit" --text | LESS= command less -R + else + "$ZSH/tools/changelog.sh" HEAD "$last_commit" + fi + fi + + printf "${BLUE}%s \`${BOLD}%s${RESET}${BLUE}\`${RESET}\n" "You can see the changelog again with" "omz changelog" + fi + + printf '%s %s__ %s %s %s %s %s__ %s\n' $RAINBOW $RESET + printf '%s ____ %s/ /_ %s ____ ___ %s__ __ %s ____ %s_____%s/ /_ %s\n' $RAINBOW $RESET + printf '%s / __ \%s/ __ \ %s / __ `__ \%s/ / / / %s /_ / %s/ ___/%s __ \ %s\n' $RAINBOW $RESET + printf '%s/ /_/ /%s / / / %s / / / / / /%s /_/ / %s / /_%s(__ )%s / / / %s\n' $RAINBOW $RESET + printf '%s\____/%s_/ /_/ %s /_/ /_/ /_/%s\__, / %s /___/%s____/%s_/ /_/ %s\n' $RAINBOW $RESET + printf '%s %s %s %s /____/ %s %s %s %s\n' $RAINBOW $RESET + printf '\n' + printf "${BLUE}%s${RESET}\n" "$message" + printf "${BLUE}${BOLD}%s ${UNDER}%s${RESET}\n" "To keep up with the latest news and updates, follow us on Twitter:" "https://twitter.com/ohmyzsh" printf "${BLUE}${BOLD}%s ${UNDER}%s${RESET}\n" "Want to get involved in the community? Join our Discord:" "https://discord.gg/ohmyzsh" printf "${BLUE}${BOLD}%s ${UNDER}%s${RESET}\n" "Get your Oh My Zsh swag at:" "https://shop.planetargon.com/collections/oh-my-zsh" else - status=$? + ret=$? printf "${RED}%s${RESET}\n" 'There was an error updating. Try again later?' fi @@ -79,4 +108,4 @@ case "$resetAutoStash" in esac # Exit with `1` if the update failed -exit $status +exit $ret |