diff options
Diffstat (limited to 'oh-my-zsh/tools')
-rwxr-xr-x | oh-my-zsh/tools/changelog.sh | 476 | ||||
-rw-r--r-- | oh-my-zsh/tools/check_for_upgrade.sh | 209 | ||||
-rwxr-xr-x | oh-my-zsh/tools/install.sh | 523 | ||||
-rwxr-xr-x | oh-my-zsh/tools/require_tool.sh | 161 | ||||
-rwxr-xr-x | oh-my-zsh/tools/theme_chooser.sh | 98 | ||||
-rw-r--r-- | oh-my-zsh/tools/uninstall.sh | 40 | ||||
-rwxr-xr-x | oh-my-zsh/tools/upgrade.sh | 251 |
7 files changed, 1758 insertions, 0 deletions
diff --git a/oh-my-zsh/tools/changelog.sh b/oh-my-zsh/tools/changelog.sh new file mode 100755 index 0000000..a5cc468 --- /dev/null +++ b/oh-my-zsh/tools/changelog.sh @@ -0,0 +1,476 @@ +#!/usr/bin/env zsh + +cd "$ZSH" +setopt extendedglob + +############################## +# 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. +local -a IGNORED_TYPES +IGNORED_TYPES=(${${${(@k)TYPES}:|MAIN_TYPES}:|OTHER_TYPES}) + +############################ +# 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 + + # Parse commit type from the subject + if [[ "$1" =~ '^([a-zA-Z_\-]+)(\(.+\))?!?: .+$' ]]; then + type="${match[1]}" + fi + + # If $type doesn't appear in $TYPES array mark it as 'other' + if [[ -n "$type" && -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 + if [[ "$1" =~ '^[a-zA-Z_\-]+\((.+)\)!?: .+$' ]]; then + echo "${match[1]}" + return + fi + + # If no scope found, try to find it in "<scope>:" format + if [[ "$1" =~ '^([a-zA-Z_\-]+): .+$' ]]; then + scope="${match[1]}" + # Make sure it's not a type before printing it + if [[ -z "${(k)TYPES[(i)$scope]}" ]]; then + echo "$scope" + fi + 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. + if [[ "$1" =~ '^[a-zA-Z_\-]+(\(.+\))?!?: (.+)$' ]]; then + echo "${match[2]}" + else + echo "$1" + fi + } + + # 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]}" + # remove CR characters (might be inserted in GitHub UI commit description form) + message="${message//$'\r'/}" + # 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="$2" body="$3" warning rhash + + # 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 + types[$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, + # types (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 + # - types, 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 (( ${+types[$rhash]} )); then + # Remove revert commit + unset "types[$hash]" "subjects[$hash]" "scopes[$hash]" "breaking[$hash]" + # Remove reverted commit + unset "types[$rhash]" "subjects[$rhash]" "scopes[$rhash]" "breaking[$rhash]" + fi + done + + # Remove commits from ignored types unless it has breaking change information + for hash in ${(k)types[(R)${(j:|:)IGNORED_TYPES}]}; do + (( ! ${+breaking[$hash]} )) || continue + unset "types[$hash]" "subjects[$hash]" "scopes[$hash]" + done + + # If no commits left skip displaying the release + if (( $#types == 0 )); then + return + fi + + # Get length of longest scope for padding + local max_scope=0 + for hash in ${(k)scopes}; do + max_scope=$(( max_scope < ${#scopes[$hash]} ? ${#scopes[$hash]} : max_scope )) + done + + ##* 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 '%s' "$hash" ;; + text) printf '\e[33m%s\e[0m' "$hash" ;; # red + md) printf '[`%s`](https://github.com/ohmyzsh/ohmyzsh/commit/%s)' "$hash" "$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 '%s\n%s\n\n' "$header" "$(printf '%.0s=' {1..${#header}})" ;; + 2) printf '%s\n%s\n\n' "$header" "$(printf '%.0s-' {1..${#header}})" ;; + *) printf '%s:\n\n' "$header" ;; + esac ;; + text) + case "$level" in + 1|2) printf '\e[1;4m%s\e[0m\n\n' "$header" ;; # bold, underlined + *) printf '\e[1m%s:\e[0m\n\n' "$header" ;; # bold + esac ;; + md) printf '%s %s\n\n' "$(printf '%.0s#' {1..${level}})" "$header" ;; + esac + } + + function fmt:scope { + #* Uses $scopes (A) and $hash from outer scope + local scope="${1:-${scopes[$hash]}}" + + # If no scopes, exit the function + if [[ $max_scope -eq 0 ]]; then + return + fi + + # Get how much padding is required for this scope + local padding=0 + 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 '[%s]%s ' "$scope" "$padding";; + text) printf '[\e[38;5;9m%s\e[0m]%s ' "$scope" "$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 '%s' "$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 '%s: ' "$type" ;; + text) printf '\e[4m%s\e[24m: ' "$type" ;; # underlined + esac + } + + ##* Section functions + + function display:version { + fmt:header "$version" 2 + } + + function display:breaking { + (( $#breaking != 0 )) || return 0 + + case "$output" in + text) printf '\e[31m'; fmt:header "BREAKING CHANGES" 3 ;; + raw) fmt:header "BREAKING CHANGES" 3 ;; + md) fmt:header "BREAKING CHANGES ⚠" 3 ;; + esac + + local hash message + local wrap_width=$(( (COLUMNS < 100 ? COLUMNS : 100) - 3 )) + for hash message in ${(kv)breaking}; do + # Format the BREAKING CHANGE message by word-wrapping it at maximum 100 + # characters (use $COLUMNS if smaller than 100) + message="$(fmt -w $wrap_width <<< "$message")" + # Display hash and scope in their own line, and then the full message with + # blank lines as separators and a 3-space left padding + echo " - $(fmt:hash) $(fmt:scope)\n\n$(fmt:subject "$message" | sed 's/^/ /')\n" + done + } + + function display:type { + local hash type="$1" + + local -a hashes + hashes=(${(k)types[(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)types[(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 types subjects scopes breaking reverts + local truncate=0 read_commits=0 + local version tag + local hash refs subject body + + # Get the first version name: + # 1) try tag-like version, or + # 2) try branch name, or + # 3) try name-rev, or + # 4) try short hash + version=$(command git describe --tags $until 2>/dev/null) \ + || version=$(command git symbolic-ref --quiet --short $until 2>/dev/null) \ + || version=$(command git name-rev --no-undefined --name-only --exclude="remotes/*" $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 + local range=${since:+$since..}$until + + # Git log options + # -z: commits are delimited by null bytes + # --format: [7-char hash]<field sep>[ref names]<field sep>[subject]<field sep>[body] + # --abbrev=7: force commit hashes to be 7 characters long + # --no-merges: merge commits are omitted + # --first-parent: commits from merged branches are omitted + local SEP="0mZmAgIcSeP" + local -a raw_commits + raw_commits=(${(0)"$(command git -c log.showSignature=false log -z \ + --format="%h${SEP}%D${SEP}%s${SEP}%b" --abbrev=7 \ + --no-merges --first-parent $range)"}) + + local raw_commit + local -a raw_fields + for raw_commit in $raw_commits; do + # Truncate list on versions with a lot of commits + if [[ -z "$since" ]] && (( ++read_commits > 35 )); then + truncate=1 + break + fi + + # Read the commit fields (@ is needed to keep empty values) + eval "raw_fields=(\"\${(@ps:$SEP:)raw_commit}\")" + hash="${raw_fields[1]}" + refs="${raw_fields[2]}" + subject="${raw_fields[3]}" + body="${raw_fields[4]}" + + # If we find a new release (exact tag) + if [[ "$refs" = *tag:\ * ]]; then + # Parse tag name (needs: setopt extendedglob) + tag="${${refs##*tag: }%%,# *}" + # Output previous release + display-release + # Reinitialize commit storage + types=() + subjects=() + scopes=() + breaking=() + reverts=() + # Start work on next release + version="$tag" + read_commits=1 + fi + + parse-commit "$hash" "$subject" "$body" + done + + display-release + + if (( truncate )); then + echo " ...more commits omitted" + echo + fi +} + +# 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/oh-my-zsh/tools/check_for_upgrade.sh b/oh-my-zsh/tools/check_for_upgrade.sh new file mode 100644 index 0000000..cc527e7 --- /dev/null +++ b/oh-my-zsh/tools/check_for_upgrade.sh @@ -0,0 +1,209 @@ +# 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" +fi + +# Get user's update preferences +# +# Supported update modes: +# - prompt (default): the user is asked before updating when it's time to update +# - auto: the update is performed automatically when it's time +# - reminder: a reminder is shown to the user when it's time to update +# - disabled: automatic update is turned off +zstyle -s ':omz:update' mode update_mode || { + update_mode=prompt + + # If the mode zstyle setting is not set, support old-style settings + [[ "$DISABLE_UPDATE_PROMPT" != true ]] || update_mode=auto + [[ "$DISABLE_AUTO_UPDATE" != true ]] || update_mode=disabled +} + +# Cancel update if: +# - the automatic update is disabled. +# - the current user doesn't have write permissions nor owns the $ZSH directory. +# - git is unavailable on the system. +if [[ "$update_mode" = disabled ]] \ + || [[ ! -w "$ZSH" || ! -O "$ZSH" ]] \ + || ! command -v git &>/dev/null; then + unset update_mode + return +fi + +function current_epoch() { + zmodload zsh/datetime + echo $(( EPOCHSECONDS / 60 / 60 / 24 )) +} + +function is_update_available() { + local branch + branch=${"$(builtin cd -q "$ZSH"; git config --local oh-my-zsh.branch)":-master} + + local remote remote_url remote_repo + remote=${"$(builtin cd -q "$ZSH"; git config --local oh-my-zsh.remote)":-origin} + remote_url=$(builtin cd -q "$ZSH"; git config remote.$remote.url) + + local repo + case "$remote_url" in + https://github.com/*) repo=${${remote_url#https://github.com/}%.git} ;; + git@github.com:*) repo=${${remote_url#git@github.com:}%.git} ;; + *) + # If the remote is not using GitHub we can't check for updates + # Let's assume there are updates + return 0 ;; + esac + + # If the remote repo is not the official one, let's assume there are updates available + [[ "$repo" = ohmyzsh/ohmyzsh ]] || return 0 + local api_url="https://api.github.com/repos/${repo}/commits/${branch}" + + # Get local HEAD. If this fails assume there are updates + local local_head + local_head=$(builtin cd -q "$ZSH"; git rev-parse $branch 2>/dev/null) || return 0 + + # Get remote HEAD. If no suitable command is found assume there are updates + # On any other error, skip the update (connection may be down) + local remote_head + remote_head=$( + if (( ${+commands[curl]} )); then + curl --connect-timeout 2 -fsSL -H 'Accept: application/vnd.github.v3.sha' $api_url 2>/dev/null + elif (( ${+commands[wget]} )); then + wget -T 2 -O- --header='Accept: application/vnd.github.v3.sha' $api_url 2>/dev/null + elif (( ${+commands[fetch]} )); then + HTTP_ACCEPT='Accept: application/vnd.github.v3.sha' fetch -T 2 -o - $api_url 2>/dev/null + else + exit 0 + fi + ) || return 1 + + # Compare local and remote HEADs (if they're equal there are no updates) + [[ "$local_head" != "$remote_head" ]] || return 1 + + # If local and remote HEADs don't match, check if there's a common ancestor + # If the merge-base call fails, $remote_head might not be downloaded so assume there are updates + local base + base=$(builtin cd -q "$ZSH"; git merge-base $local_head $remote_head 2>/dev/null) || return 0 + + # If the common ancestor ($base) is not $remote_head, + # the local HEAD is older than the remote HEAD + [[ $base != $remote_head ]] +} + +function update_last_updated_file() { + echo "LAST_EPOCH=$(current_epoch)" >! "${ZSH_CACHE_DIR}/.zsh-update" +} + +function update_ohmyzsh() { + if ZSH="$ZSH" zsh -f "$ZSH/tools/upgrade.sh" --interactive; then + update_last_updated_file + fi +} + +function has_typed_input() { + # Created by Philippe Troin <phil@fifi.org> + # https://zsh.org/mla/users/2022/msg00062.html + emulate -L zsh + zmodload zsh/zselect + + # Back up stty settings prior to disabling canonical mode + # Consider that no input can be typed if stty fails + # (this might happen if stdin is not a terminal) + local termios + termios=$(stty --save 2>/dev/null) || return 1 + { + # Disable canonical mode so that typed input counts + # regardless of whether Enter was pressed + stty -icanon + + # Poll stdin (fd 0) for data ready to be read + zselect -t 0 -r 0 + return $? + } always { + # Restore stty settings + stty $termios + } +} + +() { + emulate -L zsh + + 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 + fi + + # Check for lock directory + if ! command mkdir "$ZSH/log/update.lock" 2>/dev/null; then + return + fi + + # Remove lock directory on exit. `return $ret` 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. + # This means that for a CTRL+C, the trap needs to return the same exit status so that + # the shell actually exits what it's running. + trap " + ret=\$? + unset update_mode + unset -f current_epoch is_update_available update_last_updated_file update_ohmyzsh 2>/dev/null + command rm -rf '$ZSH/log/update.lock' + return \$ret + " 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 + + # Number of days before trying to update again + zstyle -s ':omz:update' frequency epoch_target || 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 + + # Test if Oh My Zsh directory is a git repository + if ! (builtin cd -q "$ZSH" && LANG= git rev-parse &>/dev/null); then + echo >&2 "[oh-my-zsh] Can't update: not a git repository." + return + fi + + # Check if there are updates available before proceeding + if ! is_update_available; then + return + fi + + # If in reminder mode or user has typed input, show reminder and exit + if [[ "$update_mode" = reminder ]] || has_typed_input; then + printf '\r\e[0K' # move cursor to first column and clear whole line + echo "[oh-my-zsh] It's time to update! You can do that by running \`omz update\`" + return 0 + fi + + # Don't ask for confirmation before updating if in auto mode + if [[ "$update_mode" = auto ]]; then + update_ohmyzsh + return $? + fi + + # Ask for confirmation and only update on 'y', 'Y' or Enter + # Otherwise just show a reminder for how to update + 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 ;& + *) echo "[oh-my-zsh] You can update manually by running \`omz update\`" ;; + esac +} + +unset update_mode +unset -f current_epoch is_update_available update_last_updated_file update_ohmyzsh diff --git a/oh-my-zsh/tools/install.sh b/oh-my-zsh/tools/install.sh new file mode 100755 index 0000000..a6538f9 --- /dev/null +++ b/oh-my-zsh/tools/install.sh @@ -0,0 +1,523 @@ +#!/bin/sh +# +# This script should be run via curl: +# sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" +# or via wget: +# sh -c "$(wget -qO- https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" +# or via fetch: +# sh -c "$(fetch -o - https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" +# +# As an alternative, you can first download the install script and run it afterwards: +# wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh +# sh install.sh +# +# You can tweak the install behavior by setting variables when running the script. For +# example, to change the path to the Oh My Zsh repository: +# ZSH=~/.zsh sh install.sh +# +# Respects the following environment variables: +# ZSH - path to the Oh My Zsh repository folder (default: $HOME/.oh-my-zsh) +# REPO - name of the GitHub repo to install from (default: ohmyzsh/ohmyzsh) +# REMOTE - full remote URL of the git repo to install (default: GitHub via HTTPS) +# BRANCH - branch to check out immediately after install (default: master) +# +# Other options: +# CHSH - 'no' means the installer will not change the default shell (default: yes) +# RUNZSH - 'no' means the installer will not run zsh after the install (default: yes) +# KEEP_ZSHRC - 'yes' means the installer will not replace an existing .zshrc (default: no) +# +# You can also pass some arguments to the install script to set some these options: +# --skip-chsh: has the same behavior as setting CHSH to 'no' +# --unattended: sets both CHSH and RUNZSH to 'no' +# --keep-zshrc: sets KEEP_ZSHRC to 'yes' +# For example: +# sh install.sh --unattended +# or: +# sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended +# +set -e + +# Make sure important variables exist if not already defined +# +# $USER is defined by login(1) which is not always executed (e.g. containers) +# POSIX: https://pubs.opengroup.org/onlinepubs/009695299/utilities/id.html +USER=${USER:-$(id -u -n)} +# $HOME is defined at the time of login, but it could be unset. If it is unset, +# a tilde by itself (~) will not be expanded to the current user's home directory. +# POSIX: https://pubs.opengroup.org/onlinepubs/009696899/basedefs/xbd_chap08.html#tag_08_03 +HOME="${HOME:-$(getent passwd $USER 2>/dev/null | cut -d: -f6)}" +# macOS does not have getent, but this works even if $HOME is unset +HOME="${HOME:-$(eval echo ~$USER)}" + + +# Track if $ZSH was provided +custom_zsh=${ZSH:+yes} + +# Default settings +ZSH="${ZSH:-$HOME/.oh-my-zsh}" +REPO=${REPO:-ohmyzsh/ohmyzsh} +REMOTE=${REMOTE:-https://github.com/${REPO}.git} +BRANCH=${BRANCH:-master} + +# Other options +CHSH=${CHSH:-yes} +RUNZSH=${RUNZSH:-yes} +KEEP_ZSHRC=${KEEP_ZSHRC:-no} + + +command_exists() { + command -v "$@" >/dev/null 2>&1 +} + +user_can_sudo() { + # Check if sudo is installed + command_exists sudo || return 1 + # The following command has 3 parts: + # + # 1. Run `sudo` with `-v`. Does the following: + # • with privilege: asks for a password immediately. + # • without privilege: exits with error code 1 and prints the message: + # Sorry, user <username> may not run sudo on <hostname> + # + # 2. Pass `-n` to `sudo` to tell it to not ask for a password. If the + # password is not required, the command will finish with exit code 0. + # If one is required, sudo will exit with error code 1 and print the + # message: + # sudo: a password is required + # + # 3. Check for the words "may not run sudo" in the output to really tell + # whether the user has privileges or not. For that we have to make sure + # to run `sudo` in the default locale (with `LANG=`) so that the message + # stays consistent regardless of the user's locale. + # + ! LANG= sudo -n -v 2>&1 | grep -q "may not run sudo" +} + +# The [ -t 1 ] check only works when the function is not called from +# a subshell (like in `$(...)` or `(...)`, so this hack redefines the +# function at the top level to always return false when stdout is not +# a tty. +if [ -t 1 ]; then + is_tty() { + true + } +else + is_tty() { + false + } +fi + +# This function uses the logic from supports-hyperlinks[1][2], which is +# made by Kat Marchán (@zkat) and licensed under the Apache License 2.0. +# [1] https://github.com/zkat/supports-hyperlinks +# [2] https://crates.io/crates/supports-hyperlinks +# +# Copyright (c) 2021 Kat Marchán +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +supports_hyperlinks() { + # $FORCE_HYPERLINK must be set and be non-zero (this acts as a logic bypass) + if [ -n "$FORCE_HYPERLINK" ]; then + [ "$FORCE_HYPERLINK" != 0 ] + return $? + fi + + # If stdout is not a tty, it doesn't support hyperlinks + is_tty || return 1 + + # DomTerm terminal emulator (domterm.org) + if [ -n "$DOMTERM" ]; then + return 0 + fi + + # VTE-based terminals above v0.50 (Gnome Terminal, Guake, ROXTerm, etc) + if [ -n "$VTE_VERSION" ]; then + [ $VTE_VERSION -ge 5000 ] + return $? + fi + + # If $TERM_PROGRAM is set, these terminals support hyperlinks + case "$TERM_PROGRAM" in + Hyper|iTerm.app|terminology|WezTerm) return 0 ;; + esac + + # kitty supports hyperlinks + if [ "$TERM" = xterm-kitty ]; then + return 0 + fi + + # Windows Terminal also supports hyperlinks + if [ -n "$WT_SESSION" ]; then + return 0 + fi + + # Konsole supports hyperlinks, but it's an opt-in setting that can't be detected + # https://github.com/ohmyzsh/ohmyzsh/issues/10964 + # if [ -n "$KONSOLE_VERSION" ]; then + # return 0 + # fi + + return 1 +} + +# Adapted from code and information by Anton Kochkov (@XVilka) +# Source: https://gist.github.com/XVilka/8346728 +supports_truecolor() { + case "$COLORTERM" in + truecolor|24bit) return 0 ;; + esac + + case "$TERM" in + iterm |\ + tmux-truecolor |\ + linux-truecolor |\ + xterm-truecolor |\ + screen-truecolor) return 0 ;; + esac + + return 1 +} + +fmt_link() { + # $1: text, $2: url, $3: fallback mode + if supports_hyperlinks; then + printf '\033]8;;%s\033\\%s\033]8;;\033\\\n' "$2" "$1" + return + fi + + case "$3" in + --text) printf '%s\n' "$1" ;; + --url|*) fmt_underline "$2" ;; + esac +} + +fmt_underline() { + is_tty && printf '\033[4m%s\033[24m\n' "$*" || printf '%s\n' "$*" +} + +# shellcheck disable=SC2016 # backtick in single-quote +fmt_code() { + is_tty && printf '`\033[2m%s\033[22m`\n' "$*" || printf '`%s`\n' "$*" +} + +fmt_error() { + printf '%sError: %s%s\n' "${FMT_BOLD}${FMT_RED}" "$*" "$FMT_RESET" >&2 +} + +setup_color() { + # Only use colors if connected to a terminal + if ! is_tty; then + FMT_RAINBOW="" + FMT_RED="" + FMT_GREEN="" + FMT_YELLOW="" + FMT_BLUE="" + FMT_BOLD="" + FMT_RESET="" + return + fi + + if supports_truecolor; then + FMT_RAINBOW=" + $(printf '\033[38;2;255;0;0m') + $(printf '\033[38;2;255;97;0m') + $(printf '\033[38;2;247;255;0m') + $(printf '\033[38;2;0;255;30m') + $(printf '\033[38;2;77;0;255m') + $(printf '\033[38;2;168;0;255m') + $(printf '\033[38;2;245;0;172m') + " + else + FMT_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') + " + fi + + FMT_RED=$(printf '\033[31m') + FMT_GREEN=$(printf '\033[32m') + FMT_YELLOW=$(printf '\033[33m') + FMT_BLUE=$(printf '\033[34m') + FMT_BOLD=$(printf '\033[1m') + FMT_RESET=$(printf '\033[0m') +} + +setup_ohmyzsh() { + # Prevent the cloned repository from having insecure permissions. Failing to do + # so causes compinit() calls to fail with "command not found: compdef" errors + # for users with insecure umasks (e.g., "002", allowing group writability). Note + # that this will be ignored under Cygwin by default, as Windows ACLs take + # precedence over umasks except for filesystems mounted with option "noacl". + umask g-w,o-w + + echo "${FMT_BLUE}Cloning Oh My Zsh...${FMT_RESET}" + + command_exists git || { + fmt_error "git is not installed" + exit 1 + } + + ostype=$(uname) + if [ -z "${ostype%CYGWIN*}" ] && git --version | grep -Eq 'msysgit|windows'; then + fmt_error "Windows/MSYS Git is not supported on Cygwin" + fmt_error "Make sure the Cygwin git package is installed and is first on the \$PATH" + exit 1 + fi + + # Manual clone with git config options to support git < v1.7.2 + git init --quiet "$ZSH" && cd "$ZSH" \ + && git config core.eol lf \ + && git config core.autocrlf false \ + && git config fsck.zeroPaddedFilemode ignore \ + && git config fetch.fsck.zeroPaddedFilemode ignore \ + && git config receive.fsck.zeroPaddedFilemode ignore \ + && git config oh-my-zsh.remote origin \ + && git config oh-my-zsh.branch "$BRANCH" \ + && git remote add origin "$REMOTE" \ + && git fetch --depth=1 origin \ + && git checkout -b "$BRANCH" "origin/$BRANCH" || { + [ ! -d "$ZSH" ] || { + cd - + rm -rf "$ZSH" 2>/dev/null + } + fmt_error "git clone of oh-my-zsh repo failed" + exit 1 + } + # Exit installation directory + cd - + + echo +} + +setup_zshrc() { + # Keep most recent old .zshrc at .zshrc.pre-oh-my-zsh, and older ones + # with datestamp of installation that moved them aside, so we never actually + # destroy a user's original zshrc + echo "${FMT_BLUE}Looking for an existing zsh config...${FMT_RESET}" + + # Must use this exact name so uninstall.sh can find it + OLD_ZSHRC=~/.zshrc.pre-oh-my-zsh + if [ -f ~/.zshrc ] || [ -h ~/.zshrc ]; then + # Skip this if the user doesn't want to replace an existing .zshrc + if [ "$KEEP_ZSHRC" = yes ]; then + echo "${FMT_YELLOW}Found ~/.zshrc.${FMT_RESET} ${FMT_GREEN}Keeping...${FMT_RESET}" + return + fi + if [ -e "$OLD_ZSHRC" ]; then + OLD_OLD_ZSHRC="${OLD_ZSHRC}-$(date +%Y-%m-%d_%H-%M-%S)" + if [ -e "$OLD_OLD_ZSHRC" ]; then + fmt_error "$OLD_OLD_ZSHRC exists. Can't back up ${OLD_ZSHRC}" + fmt_error "re-run the installer again in a couple of seconds" + exit 1 + fi + mv "$OLD_ZSHRC" "${OLD_OLD_ZSHRC}" + + echo "${FMT_YELLOW}Found old ~/.zshrc.pre-oh-my-zsh." \ + "${FMT_GREEN}Backing up to ${OLD_OLD_ZSHRC}${FMT_RESET}" + fi + echo "${FMT_YELLOW}Found ~/.zshrc.${FMT_RESET} ${FMT_GREEN}Backing up to ${OLD_ZSHRC}${FMT_RESET}" + mv ~/.zshrc "$OLD_ZSHRC" + fi + + echo "${FMT_GREEN}Using the Oh My Zsh template file and adding it to ~/.zshrc.${FMT_RESET}" + + # Replace $HOME path with '$HOME' in $ZSH variable in .zshrc file + omz=$(echo "$ZSH" | sed "s|^$HOME/|\$HOME/|") + sed "s|^export ZSH=.*$|export ZSH=\"${omz}\"|" "$ZSH/templates/zshrc.zsh-template" > ~/.zshrc-omztemp + mv -f ~/.zshrc-omztemp ~/.zshrc + + echo +} + +setup_shell() { + # Skip setup if the user wants or stdin is closed (not running interactively). + if [ "$CHSH" = no ]; then + return + fi + + # If this user's login shell is already "zsh", do not attempt to switch. + if [ "$(basename -- "$SHELL")" = "zsh" ]; then + return + fi + + # If this platform doesn't provide a "chsh" command, bail out. + if ! command_exists chsh; then + cat <<EOF +I can't change your shell automatically because this system does not have chsh. +${FMT_BLUE}Please manually change your default shell to zsh${FMT_RESET} +EOF + return + fi + + echo "${FMT_BLUE}Time to change your default shell to zsh:${FMT_RESET}" + + # Prompt for user choice on changing the default login shell + printf '%sDo you want to change your default shell to zsh? [Y/n]%s ' \ + "$FMT_YELLOW" "$FMT_RESET" + read -r opt + case $opt in + y*|Y*|"") ;; + n*|N*) echo "Shell change skipped."; return ;; + *) echo "Invalid choice. Shell change skipped."; return ;; + esac + + # Check if we're running on Termux + case "$PREFIX" in + *com.termux*) termux=true; zsh=zsh ;; + *) termux=false ;; + esac + + if [ "$termux" != true ]; then + # Test for the right location of the "shells" file + if [ -f /etc/shells ]; then + shells_file=/etc/shells + elif [ -f /usr/share/defaults/etc/shells ]; then # Solus OS + shells_file=/usr/share/defaults/etc/shells + else + fmt_error "could not find /etc/shells file. Change your default shell manually." + return + fi + + # Get the path to the right zsh binary + # 1. Use the most preceding one based on $PATH, then check that it's in the shells file + # 2. If that fails, get a zsh path from the shells file, then check it actually exists + if ! zsh=$(command -v zsh) || ! grep -qx "$zsh" "$shells_file"; then + if ! zsh=$(grep '^/.*/zsh$' "$shells_file" | tail -n 1) || [ ! -f "$zsh" ]; then + fmt_error "no zsh binary found or not present in '$shells_file'" + fmt_error "change your default shell manually." + return + fi + fi + fi + + # We're going to change the default shell, so back up the current one + if [ -n "$SHELL" ]; then + echo "$SHELL" > ~/.shell.pre-oh-my-zsh + else + grep "^$USER:" /etc/passwd | awk -F: '{print $7}' > ~/.shell.pre-oh-my-zsh + fi + + echo "Changing your shell to $zsh..." + + # Check if user has sudo privileges to run `chsh` with or without `sudo` + # + # This allows the call to succeed without password on systems where the + # user does not have a password but does have sudo privileges, like in + # Google Cloud Shell. + # + # On systems that don't have a user with passwordless sudo, the user will + # be prompted for the password either way, so this shouldn't cause any issues. + # + if user_can_sudo; then + sudo -k chsh -s "$zsh" "$USER" # -k forces the password prompt + else + chsh -s "$zsh" "$USER" # run chsh normally + fi + + # Check if the shell change was successful + if [ $? -ne 0 ]; then + fmt_error "chsh command unsuccessful. Change your default shell manually." + else + export SHELL="$zsh" + echo "${FMT_GREEN}Shell successfully changed to '$zsh'.${FMT_RESET}" + fi + + echo +} + +# shellcheck disable=SC2183 # printf string has more %s than arguments ($FMT_RAINBOW expands to multiple arguments) +print_success() { + printf '%s %s__ %s %s %s %s %s__ %s\n' $FMT_RAINBOW $FMT_RESET + printf '%s ____ %s/ /_ %s ____ ___ %s__ __ %s ____ %s_____%s/ /_ %s\n' $FMT_RAINBOW $FMT_RESET + printf '%s / __ \\%s/ __ \\ %s / __ `__ \\%s/ / / / %s /_ / %s/ ___/%s __ \\ %s\n' $FMT_RAINBOW $FMT_RESET + printf '%s/ /_/ /%s / / / %s / / / / / /%s /_/ / %s / /_%s(__ )%s / / / %s\n' $FMT_RAINBOW $FMT_RESET + printf '%s\\____/%s_/ /_/ %s /_/ /_/ /_/%s\\__, / %s /___/%s____/%s_/ /_/ %s\n' $FMT_RAINBOW $FMT_RESET + printf '%s %s %s %s /____/ %s %s %s %s....is now installed!%s\n' $FMT_RAINBOW $FMT_GREEN $FMT_RESET + printf '\n' + printf '\n' + printf "%s %s %s\n" "Before you scream ${FMT_BOLD}${FMT_YELLOW}Oh My Zsh!${FMT_RESET} look over the" \ + "$(fmt_code "$(fmt_link ".zshrc" "file://$HOME/.zshrc" --text)")" \ + "file to select plugins, themes, and options." + printf '\n' + printf '%s\n' "• Follow us on Twitter: $(fmt_link @ohmyzsh https://twitter.com/ohmyzsh)" + printf '%s\n' "• Join our Discord community: $(fmt_link "Discord server" https://discord.gg/ohmyzsh)" + printf '%s\n' "• Get stickers, t-shirts, coffee mugs and more: $(fmt_link "Planet Argon Shop" https://shop.planetargon.com/collections/oh-my-zsh)" + printf '%s\n' $FMT_RESET +} + +main() { + # Run as unattended if stdin is not a tty + if [ ! -t 0 ]; then + RUNZSH=no + CHSH=no + fi + + # Parse arguments + while [ $# -gt 0 ]; do + case $1 in + --unattended) RUNZSH=no; CHSH=no ;; + --skip-chsh) CHSH=no ;; + --keep-zshrc) KEEP_ZSHRC=yes ;; + esac + shift + done + + setup_color + + if ! command_exists zsh; then + echo "${FMT_YELLOW}Zsh is not installed.${FMT_RESET} Please install zsh first." + exit 1 + fi + + if [ -d "$ZSH" ]; then + echo "${FMT_YELLOW}The \$ZSH folder already exists ($ZSH).${FMT_RESET}" + if [ "$custom_zsh" = yes ]; then + cat <<EOF + +You ran the installer with the \$ZSH setting or the \$ZSH variable is +exported. You have 3 options: + +1. Unset the ZSH variable when calling the installer: + $(fmt_code "ZSH= sh install.sh") +2. Install Oh My Zsh to a directory that doesn't exist yet: + $(fmt_code "ZSH=path/to/new/ohmyzsh/folder sh install.sh") +3. (Caution) If the folder doesn't contain important information, + you can just remove it with $(fmt_code "rm -r $ZSH") + +EOF + else + echo "You'll need to remove it if you want to reinstall." + fi + exit 1 + fi + + setup_ohmyzsh + setup_zshrc + setup_shell + + print_success + + if [ $RUNZSH = no ]; then + echo "${FMT_YELLOW}Run zsh to try it out.${FMT_RESET}" + exit + fi + + exec zsh -l +} + +main "$@" diff --git a/oh-my-zsh/tools/require_tool.sh b/oh-my-zsh/tools/require_tool.sh new file mode 100755 index 0000000..19c5f6f --- /dev/null +++ b/oh-my-zsh/tools/require_tool.sh @@ -0,0 +1,161 @@ +__require_tool_version_compare () +{ + ( + # Locally ignore failures, otherwise we'll exit whenever $1 and $2 + # are not equal! + set +e + +awk_strverscmp=' + # Use only awk features that work with 7th edition Unix awk (1978). + # My, what an old awk you have, Mr. Solaris! + END { + while (length(v1) || length(v2)) { + # Set d1 to be the next thing to compare from v1, and likewise for d2. + # Normally this is a single character, but if v1 and v2 contain digits, + # compare them as integers and fractions as strverscmp does. + if (v1 ~ /^[0-9]/ && v2 ~ /^[0-9]/) { + # Split v1 and v2 into their leading digit string components d1 and d2, + # and advance v1 and v2 past the leading digit strings. + for (len1 = 1; substr(v1, len1 + 1) ~ /^[0-9]/; len1++) continue + for (len2 = 1; substr(v2, len2 + 1) ~ /^[0-9]/; len2++) continue + d1 = substr(v1, 1, len1); v1 = substr(v1, len1 + 1) + d2 = substr(v2, 1, len2); v2 = substr(v2, len2 + 1) + if (d1 ~ /^0/) { + if (d2 ~ /^0/) { + # Compare two fractions. + while (d1 ~ /^0/ && d2 ~ /^0/) { + d1 = substr(d1, 2); len1-- + d2 = substr(d2, 2); len2-- + } + if (len1 != len2 && ! (len1 && len2 && substr(d1, 1, 1) == substr(d2, 1, 1))) { + # The two components differ in length, and the common prefix + # contains only leading zeros. Consider the longer to be less. + d1 = -len1 + d2 = -len2 + } else { + # Otherwise, compare as strings. + d1 = "x" d1 + d2 = "x" d2 + } + } else { + # A fraction is less than an integer. + exit 1 + } + } else { + if (d2 ~ /^0/) { + # An integer is greater than a fraction. + exit 2 + } else { + # Compare two integers. + d1 += 0 + d2 += 0 + } + } + } else { + # The normal case, without worrying about digits. + if (v1 == "") d1 = v1; else { d1 = substr(v1, 1, 1); v1 = substr(v1,2) } + if (v2 == "") d2 = v2; else { d2 = substr(v2, 1, 1); v2 = substr(v2,2) } + } + if (d1 < d2) exit 1 + if (d1 > d2) exit 2 + } + } +' + awk "$awk_strverscmp" v1="$1" v2="$2" /dev/null + case $? in + 1) echo '<';; + 0) echo '=';; + 2) echo '>';; + esac + ) +} + + +__require_tool_fatal () +{ + echo $@ >/dev/stderr + return 1 +} + +# Usage: require_tool program version +# Returns: 0 if $1 version if greater equals than $2, 1 otherwise. +# In case of error, message is written on error output. +# +# Example: require_tool gcc 4.6 +# Use GCC environment variable if defined instead of lookup for the tool +# in the environment. +require_tool () +{ + envvar_name=$(echo $1 | tr '[:lower:]' '[:upper:]') + tool=$(printenv $envvar_name || echo $1) + local version=$($tool --version 2>/dev/null| \ + sed -n 's/.*[^0-9.]\([0-9]*\.[0-9.]*\).*/\1/p;q') + if test x"$version" = x ; then + echo "$tool is required" >/dev/stderr + return 1 + fi + case $(__require_tool_version_compare "$2" "$version") in + '>') + echo "$1 $2 or better is required: this is $tool $version" >/dev/stderr + return 1 + ;; + esac +} + +usage() { + cat <<EOF +NAME + require_tool.sh - Ensure version of a tool is greater than the one expected + +SYNOPSIS + require_tool.sh [ -h ] + [ --help ] + [ TOOL MIN_VERSION ] + +DESCRIPTION + TOOL is the name or path of the program to check. If the name is specified, its + path is deduced from PATH environment variable. If environment variable TOOL + (in upper-case characters) is defined, considers its value as path to the tool. + + MIN_VERSION is a string representing the minimum required version. + +BEHAVIOR + * locate path to the program. + * execute $ TOOL_PATH --version + * extract version from standard output. + * compare this version to the expected one. + +OPTIONS + -h --help + Display this message and exit 0 + +ERRORS + if program is not found or its version is prior to expected version, + a message is written to error output. + +EXIT VALUE + returns 0 if program version if greater equals than expected version, + returns 1 otherwise. + +EXAMPLE + $ require_tool.sh emacs 23 + $ CC=g++ require_tool.sh cc 4.6 + $ require_tool.sh zsh 4.5 + +EOF +} + +for arg in $@; do + case $arg in + -h|--help) + usage + exit 0 + ;; + esac +done +if [ $# -gt 2 ] ; then + echo "ERROR: expecting 2 parameters. Please see option --help" + exit 1 +fi + +require_tool $@ diff --git a/oh-my-zsh/tools/theme_chooser.sh b/oh-my-zsh/tools/theme_chooser.sh new file mode 100755 index 0000000..3883f1d --- /dev/null +++ b/oh-my-zsh/tools/theme_chooser.sh @@ -0,0 +1,98 @@ +#!/bin/zsh + +# Zsh Theme Chooser by fox (fox91 at anche dot no) +# This program is free software. It comes without any warranty, to +# the extent permitted by applicable law. You can redistribute it +# and/or modify it under the terms of the Do What The Fuck You Want +# To Public License, Version 2, as published by Sam Hocevar. See +# http://www.wtfpl.net/txt/copying/ for more details. + +THEMES_DIR="$ZSH/themes" +FAVLIST="${HOME}/.zsh_favlist" +source $ZSH/oh-my-zsh.sh + +function noyes() { + read "a?$1 [y/N] " + if [[ $a == "N" || $a == "n" || $a = "" ]]; then + return 0 + fi + return 1 +} + +function theme_preview() { + THEME=$1 + THEME_NAME=`echo $THEME | sed s/\.zsh-theme$//` + print "$fg[blue]${(l.((${COLUMNS}-${#THEME_NAME}-5))..─.)}$reset_color $THEME_NAME $fg[blue]───$reset_color" + source "$THEMES_DIR/$THEME" + cols=$(tput cols) + (exit 1) + print -P "$PROMPT $RPROMPT" +} + +function banner() { + echo + echo "[0;1;35;95m╺━[0;1;31;91m┓┏[0;1;33;93m━┓[0;1;32;92m╻[0m [0;1;36;96m╻[0m [0;1;35;95m╺┳[0;1;31;91m╸╻[0m [0;1;33;93m╻[0;1;32;92m┏━[0;1;36;96m╸┏[0;1;34;94m┳┓[0;1;35;95m┏━[0;1;31;91m╸[0m [0;1;32;92m┏━[0;1;36;96m╸╻[0m [0;1;34;94m╻[0;1;35;95m┏━[0;1;31;91m┓┏[0;1;33;93m━┓[0;1;32;92m┏━[0;1;36;96m┓┏[0;1;34;94m━╸[0;1;35;95m┏━[0;1;31;91m┓[0m" + echo "[0;1;31;91m┏━[0;1;33;93m┛┗[0;1;32;92m━┓[0;1;36;96m┣━[0;1;34;94m┫[0m [0;1;31;91m┃[0m [0;1;33;93m┣[0;1;32;92m━┫[0;1;36;96m┣╸[0m [0;1;34;94m┃[0;1;35;95m┃┃[0;1;31;91m┣╸[0m [0;1;36;96m┃[0m [0;1;34;94m┣[0;1;35;95m━┫[0;1;31;91m┃[0m [0;1;33;93m┃┃[0m [0;1;32;92m┃[0;1;36;96m┗━[0;1;34;94m┓┣[0;1;35;95m╸[0m [0;1;31;91m┣┳[0;1;33;93m┛[0m" + echo "[0;1;33;93m┗━[0;1;32;92m╸┗[0;1;36;96m━┛[0;1;34;94m╹[0m [0;1;35;95m╹[0m [0;1;33;93m╹[0m [0;1;32;92m╹[0m [0;1;36;96m╹[0;1;34;94m┗━[0;1;35;95m╸╹[0m [0;1;31;91m╹[0;1;33;93m┗━[0;1;32;92m╸[0m [0;1;34;94m┗━[0;1;35;95m╸╹[0m [0;1;31;91m╹[0;1;33;93m┗━[0;1;32;92m┛┗[0;1;36;96m━┛[0;1;34;94m┗━[0;1;35;95m┛┗[0;1;31;91m━╸[0;1;33;93m╹┗[0;1;32;92m╸[0m" + echo +} + +function usage() { + echo "Usage: $0 [options] [theme]" + echo + echo "Options" + echo " -l List available themes" + echo " -s Show all themes" + echo " -h Get this help message" + exit 1 +} + +function list_themes() { + for THEME in $(ls $THEMES_DIR); do + THEME_NAME=`echo $THEME | sed s/\.zsh-theme$//` + echo $THEME_NAME + done +} + +function insert_favlist() { + if grep -q "$THEME_NAME" $FAVLIST 2> /dev/null ; then + echo "Already in favlist" + else + echo $THEME_NAME >> $FAVLIST + echo "Saved to favlist" + fi + +} + +function theme_chooser() { + for THEME in $(ls $THEMES_DIR); do + echo + theme_preview $THEME + echo + if [[ -z $1 ]]; then + noyes "Do you want to add it to your favourite list ($FAVLIST)?" || \ + insert_favlist $THEME_NAME + echo + fi + done +} + +while getopts ":lhs" Option +do + case $Option in + l ) list_themes ;; + s ) theme_chooser 0 ;; + h ) usage ;; + * ) usage ;; # Default. + esac +done + +if [[ -z $Option ]]; then + if [[ -z $1 ]]; then + banner + echo + theme_chooser + else + theme_preview $1".zsh-theme" + fi +fi diff --git a/oh-my-zsh/tools/uninstall.sh b/oh-my-zsh/tools/uninstall.sh new file mode 100644 index 0000000..6a0e7b4 --- /dev/null +++ b/oh-my-zsh/tools/uninstall.sh @@ -0,0 +1,40 @@ +read -r -p "Are you sure you want to remove Oh My Zsh? [y/N] " confirmation +if [ "$confirmation" != y ] && [ "$confirmation" != Y ]; then + echo "Uninstall cancelled" + exit +fi + +echo "Removing ~/.oh-my-zsh" +if [ -d ~/.oh-my-zsh ]; then + rm -rf ~/.oh-my-zsh +fi + +if [ -e ~/.zshrc ]; then + ZSHRC_SAVE=~/.zshrc.omz-uninstalled-$(date +%Y-%m-%d_%H-%M-%S) + echo "Found ~/.zshrc -- Renaming to ${ZSHRC_SAVE}" + mv ~/.zshrc "${ZSHRC_SAVE}" +fi + +echo "Looking for original zsh config..." +ZSHRC_ORIG=~/.zshrc.pre-oh-my-zsh +if [ -e "$ZSHRC_ORIG" ]; then + echo "Found $ZSHRC_ORIG -- Restoring to ~/.zshrc" + mv "$ZSHRC_ORIG" ~/.zshrc + echo "Your original zsh config was restored." +else + echo "No original zsh config found" +fi + +if hash chsh >/dev/null 2>&1 && [ -f ~/.shell.pre-oh-my-zsh ]; then + old_shell=$(cat ~/.shell.pre-oh-my-zsh) + echo "Switching your shell back to '$old_shell':" + if chsh -s "$old_shell"; then + rm -f ~/.shell.pre-oh-my-zsh + else + echo "Could not change default shell. Change it manually by running chsh" + echo "or editing the /etc/passwd file." + fi +fi + +echo "Thanks for trying out Oh My Zsh. It's been uninstalled." +echo "Don't forget to restart your terminal!" diff --git a/oh-my-zsh/tools/upgrade.sh b/oh-my-zsh/tools/upgrade.sh new file mode 100755 index 0000000..596a593 --- /dev/null +++ b/oh-my-zsh/tools/upgrade.sh @@ -0,0 +1,251 @@ +#!/usr/bin/env zsh + +# Protect against running with shells other than zsh +if [ -z "$ZSH_VERSION" ]; then + exec zsh "$0" "$@" +fi + +# Protect against unwanted sourcing +case "$ZSH_EVAL_CONTEXT" in + *:file) echo "error: this file should not be sourced" && return ;; +esac + +cd "$ZSH" + +# Use colors, but only if connected to a terminal +# and that terminal supports them. + +# The [ -t 1 ] check only works when the function is not called from +# a subshell (like in `$(...)` or `(...)`, so this hack redefines the +# function at the top level to always return false when stdout is not +# a tty. +if [ -t 1 ]; then + is_tty() { + true + } +else + is_tty() { + false + } +fi + +# This function uses the logic from supports-hyperlinks[1][2], which is +# made by Kat Marchán (@zkat) and licensed under the Apache License 2.0. +# [1] https://github.com/zkat/supports-hyperlinks +# [2] https://crates.io/crates/supports-hyperlinks +# +# Copyright (c) 2021 Kat Marchán +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +supports_hyperlinks() { + # $FORCE_HYPERLINK must be set and be non-zero (this acts as a logic bypass) + if [ -n "$FORCE_HYPERLINK" ]; then + [ "$FORCE_HYPERLINK" != 0 ] + return $? + fi + + # If stdout is not a tty, it doesn't support hyperlinks + is_tty || return 1 + + # DomTerm terminal emulator (domterm.org) + if [ -n "$DOMTERM" ]; then + return 0 + fi + + # VTE-based terminals above v0.50 (Gnome Terminal, Guake, ROXTerm, etc) + if [ -n "$VTE_VERSION" ]; then + [ $VTE_VERSION -ge 5000 ] + return $? + fi + + # If $TERM_PROGRAM is set, these terminals support hyperlinks + case "$TERM_PROGRAM" in + Hyper|iTerm.app|terminology|WezTerm) return 0 ;; + esac + + # kitty supports hyperlinks + if [ "$TERM" = xterm-kitty ]; then + return 0 + fi + + # Windows Terminal also supports hyperlinks + if [ -n "$WT_SESSION" ]; then + return 0 + fi + + # Konsole supports hyperlinks, but it's an opt-in setting that can't be detected + # https://github.com/ohmyzsh/ohmyzsh/issues/10964 + # if [ -n "$KONSOLE_VERSION" ]; then + # return 0 + # fi + + return 1 +} + +# Adapted from code and information by Anton Kochkov (@XVilka) +# Source: https://gist.github.com/XVilka/8346728 +supports_truecolor() { + case "$COLORTERM" in + truecolor|24bit) return 0 ;; + esac + + case "$TERM" in + iterm |\ + tmux-truecolor |\ + linux-truecolor |\ + xterm-truecolor |\ + screen-truecolor) return 0 ;; + esac + + return 1 +} + +fmt_link() { + # $1: text, $2: url, $3: fallback mode + if supports_hyperlinks; then + printf '\033]8;;%s\033\\%s\033]8;;\033\\\n' "$2" "$1" + return + fi + + case "$3" in + --text) printf '%s\n' "$1" ;; + --url|*) fmt_underline "$2" ;; + esac +} + +fmt_underline() { + is_tty && printf '\033[4m%s\033[24m\n' "$*" || printf '%s\n' "$*" +} + +setopt typeset_silent +typeset -a RAINBOW + +if is_tty; then + if supports_truecolor; then + RAINBOW=( + "$(printf '\033[38;2;255;0;0m')" + "$(printf '\033[38;2;255;97;0m')" + "$(printf '\033[38;2;247;255;0m')" + "$(printf '\033[38;2;0;255;30m')" + "$(printf '\033[38;2;77;0;255m')" + "$(printf '\033[38;2;168;0;255m')" + "$(printf '\033[38;2;245;0;172m')" + ) + else + 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')" + ) + fi + + RED=$(printf '\033[31m') + GREEN=$(printf '\033[32m') + YELLOW=$(printf '\033[33m') + BLUE=$(printf '\033[34m') + BOLD=$(printf '\033[1m') + RESET=$(printf '\033[0m') +fi + +# 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 ;; + # Update out-of-date "unauthenticated git protocol on port 9418" to https + git://github.com/robbyrussell/oh-my-zsh(|.git)) + git remote set-url "$remote" "https://github.com/ohmyzsh/ohmyzsh.git" + break ;; + esac +done + +# Set git-config values known to fix git errors +# Line endings (#4069) +git config core.eol lf +git config core.autocrlf false +# zeroPaddedFilemode fsck errors (#4963) +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>/dev/null) +git config rebase.autoStash true + +local ret=0 + +# repository settings +remote=${"$(git config --local oh-my-zsh.remote)":-origin} +branch=${"$(git config --local oh-my-zsh.branch)":-master} + +# repository state +last_head=$(git symbolic-ref --quiet --short HEAD || git rev-parse HEAD) +# checkout update branch +git checkout -q "$branch" -- || exit 1 +# branch commit before update (used in changelog) +last_commit=$(git rev-parse "$branch") + +# Update Oh My Zsh +printf "${BLUE}%s${RESET}\n" "Updating Oh My Zsh" +if LANG= git pull --quiet --rebase $remote $branch; 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." + else + message="Hooray! Oh My Zsh has been updated!" + + # Save the commit prior to updating + git config oh-my-zsh.lastVersion "$last_commit" + + # Print changelog to the terminal + if [[ "$1" = --interactive ]]; then + "$ZSH/tools/changelog.sh" HEAD "$last_commit" + fi + + printf "${BLUE}%s \`${BOLD}%s${RESET}${BLUE}\`${RESET}\n" "You can see the changelog 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\n" "$message" + printf "${BLUE}${BOLD}%s %s${RESET}\n" "To keep up with the latest news and updates, follow us on Twitter:" "$(fmt_link @ohmyzsh https://twitter.com/ohmyzsh)" + printf "${BLUE}${BOLD}%s %s${RESET}\n" "Want to get involved in the community? Join our Discord:" "$(fmt_link "Discord server" https://discord.gg/ohmyzsh)" + printf "${BLUE}${BOLD}%s %s${RESET}\n" "Get your Oh My Zsh swag at:" "$(fmt_link "Planet Argon Shop" https://shop.planetargon.com/collections/oh-my-zsh)" +else + ret=$? + printf "${RED}%s${RESET}\n" 'There was an error updating. Try again later?' +fi + +# go back to HEAD previous to update +git checkout -q "$last_head" -- + +# Unset git-config values set just for the upgrade +case "$resetAutoStash" in + "") git config --unset rebase.autoStash ;; + *) git config rebase.autoStash "$resetAutoStash" ;; +esac + +# Exit with `1` if the update failed +exit $ret |