cli.zsh
27390 bytes
1#!/usr/bin/env zsh
2
3function omz {
4 setopt localoptions noksharrays
5 [[ $# -gt 0 ]] || {
6 _omz::help
7 return 1
8 }
9
10 local command="$1"
11 shift
12
13 # Subcommand functions start with _ so that they don't
14 # appear as completion entries when looking for `omz`
15 (( ${+functions[_omz::$command]} )) || {
16 _omz::help
17 return 1
18 }
19
20 _omz::$command "$@"
21}
22
23function _omz {
24 local -a cmds subcmds
25 cmds=(
26 'changelog:Print the changelog'
27 'help:Usage information'
28 'plugin:Manage plugins'
29 'pr:Manage Oh My Zsh Pull Requests'
30 'reload:Reload the current zsh session'
31 'shop:Open the Oh My Zsh shop'
32 'theme:Manage themes'
33 'update:Update Oh My Zsh'
34 'version:Show the version'
35 )
36
37 if (( CURRENT == 2 )); then
38 _describe 'command' cmds
39 elif (( CURRENT == 3 )); then
40 case "$words[2]" in
41 changelog) local -a refs
42 refs=("${(@f)$(builtin cd -q "$ZSH"; command git for-each-ref --format="%(refname:short):%(subject)" refs/heads refs/tags)}")
43 _describe 'command' refs ;;
44 plugin) subcmds=(
45 'disable:Disable plugin(s)'
46 'enable:Enable plugin(s)'
47 'info:Get plugin information'
48 'list:List plugins'
49 'load:Load plugin(s)'
50 )
51 _describe 'command' subcmds ;;
52 pr) subcmds=('clean:Delete all Pull Request branches' 'test:Test a Pull Request')
53 _describe 'command' subcmds ;;
54 theme) subcmds=('list:List themes' 'set:Set a theme in your .zshrc file' 'use:Load a theme')
55 _describe 'command' subcmds ;;
56 esac
57 elif (( CURRENT == 4 )); then
58 case "${words[2]}::${words[3]}" in
59 plugin::(disable|enable|load))
60 local -aU valid_plugins
61
62 if [[ "${words[3]}" = disable ]]; then
63 # if command is "disable", only offer already enabled plugins
64 valid_plugins=($plugins)
65 else
66 valid_plugins=("$ZSH"/plugins/*/{_*,*.plugin.zsh}(-.N:h:t) "$ZSH_CUSTOM"/plugins/*/{_*,*.plugin.zsh}(-.N:h:t))
67 # if command is "enable", remove already enabled plugins
68 [[ "${words[3]}" = enable ]] && valid_plugins=(${valid_plugins:|plugins})
69 fi
70
71 _describe 'plugin' valid_plugins ;;
72 plugin::info)
73 local -aU plugins
74 plugins=("$ZSH"/plugins/*/{_*,*.plugin.zsh}(-.N:h:t) "$ZSH_CUSTOM"/plugins/*/{_*,*.plugin.zsh}(-.N:h:t))
75 _describe 'plugin' plugins ;;
76 plugin::list)
77 local -a opts
78 opts=('--enabled:List enabled plugins only')
79 _describe -o 'options' opts ;;
80 theme::(set|use))
81 local -aU themes
82 themes=("$ZSH"/themes/*.zsh-theme(-.N:t:r) "$ZSH_CUSTOM"/**/*.zsh-theme(-.N:r:gs:"$ZSH_CUSTOM"/themes/:::gs:"$ZSH_CUSTOM"/:::))
83 _describe 'theme' themes ;;
84 esac
85 elif (( CURRENT > 4 )); then
86 case "${words[2]}::${words[3]}" in
87 plugin::(enable|disable|load))
88 local -aU valid_plugins
89
90 if [[ "${words[3]}" = disable ]]; then
91 # if command is "disable", only offer already enabled plugins
92 valid_plugins=($plugins)
93 else
94 valid_plugins=("$ZSH"/plugins/*/{_*,*.plugin.zsh}(-.N:h:t) "$ZSH_CUSTOM"/plugins/*/{_*,*.plugin.zsh}(-.N:h:t))
95 # if command is "enable", remove already enabled plugins
96 [[ "${words[3]}" = enable ]] && valid_plugins=(${valid_plugins:|plugins})
97 fi
98
99 # Remove plugins already passed as arguments
100 # NOTE: $(( CURRENT - 1 )) is the last plugin argument completely passed, i.e. that which
101 # has a space after them. This is to avoid removing plugins partially passed, which makes
102 # the completion not add a space after the completed plugin.
103 local -a args
104 args=(${words[4,$(( CURRENT - 1))]})
105 valid_plugins=(${valid_plugins:|args})
106
107 _describe 'plugin' valid_plugins ;;
108 esac
109 fi
110
111 return 0
112}
113
114# If run from a script, do not set the completion function
115if (( ${+functions[compdef]} )); then
116 compdef _omz omz
117fi
118
119## Utility functions
120
121function _omz::confirm {
122 # If question supplied, ask it before reading the answer
123 # NOTE: uses the logname of the caller function
124 if [[ -n "$1" ]]; then
125 _omz::log prompt "$1" "${${functrace[1]#_}%:*}"
126 fi
127
128 # Read one character
129 read -r -k 1
130
131 # If no newline entered, add a newline
132 if [[ "$REPLY" != $'\n' ]]; then
133 echo
134 fi
135}
136
137function _omz::log {
138 # if promptsubst is set, a message with `` or $()
139 # will be run even if quoted due to `print -P`
140 setopt localoptions nopromptsubst
141
142 # $1 = info|warn|error|debug
143 # $2 = text
144 # $3 = (optional) name of the logger
145
146 local logtype=$1
147 local logname=${3:-${${functrace[1]#_}%:*}}
148
149 # Don't print anything if debug is not active
150 if [[ $logtype = debug && -z $_OMZ_DEBUG ]]; then
151 return
152 fi
153
154 # Choose coloring based on log type
155 case "$logtype" in
156 prompt) print -Pn "%S%F{blue}$logname%f%s: $2" ;;
157 debug) print -P "%F{white}$logname%f: $2" ;;
158 info) print -P "%F{green}$logname%f: $2" ;;
159 warn) print -P "%S%F{yellow}$logname%f%s: $2" ;;
160 error) print -P "%S%F{red}$logname%f%s: $2" ;;
161 esac >&2
162}
163
164## User-facing commands
165
166function _omz::help {
167 cat >&2 <<EOF
168Usage: omz <command> [options]
169
170Available commands:
171
172 help Print this help message
173 changelog Print the changelog
174 plugin <command> Manage plugins
175 pr <command> Manage Oh My Zsh Pull Requests
176 reload Reload the current zsh session
177 shop Open the Oh My Zsh shop
178 theme <command> Manage themes
179 update Update Oh My Zsh
180 version Show the version
181
182EOF
183}
184
185function _omz::changelog {
186 local version=${1:-HEAD} format=${3:-"--text"}
187
188 if (
189 builtin cd -q "$ZSH"
190 ! command git show-ref --verify refs/heads/$version && \
191 ! command git show-ref --verify refs/tags/$version && \
192 ! command git rev-parse --verify "${version}^{commit}"
193 ) &>/dev/null; then
194 cat >&2 <<EOF
195Usage: ${(j: :)${(s.::.)0#_}} [version]
196
197NOTE: <version> must be a valid branch, tag or commit.
198EOF
199 return 1
200 fi
201
202 ZSH="$ZSH" command zsh -f "$ZSH/tools/changelog.sh" "$version" "${2:-}" "$format"
203}
204
205function _omz::plugin {
206 (( $# > 0 && $+functions[$0::$1] )) || {
207 cat >&2 <<EOF
208Usage: ${(j: :)${(s.::.)0#_}} <command> [options]
209
210Available commands:
211
212 disable <plugin> Disable plugin(s)
213 enable <plugin> Enable plugin(s)
214 info <plugin> Get information of a plugin
215 list [--enabled] List Oh My Zsh plugins
216 load <plugin> Load plugin(s)
217
218EOF
219 return 1
220 }
221
222 local command="$1"
223 shift
224
225 $0::$command "$@"
226}
227
228function _omz::plugin::disable {
229 if [[ -z "$1" ]]; then
230 echo >&2 "Usage: ${(j: :)${(s.::.)0#_}} <plugin> [...]"
231 return 1
232 fi
233
234 # Check that plugin is in $plugins
235 local -a dis_plugins
236 for plugin in "$@"; do
237 if [[ ${plugins[(Ie)$plugin]} -eq 0 ]]; then
238 _omz::log warn "plugin '$plugin' is not enabled."
239 continue
240 fi
241 dis_plugins+=("$plugin")
242 done
243
244 # Exit if there are no enabled plugins to disable
245 if [[ ${#dis_plugins} -eq 0 ]]; then
246 return 1
247 fi
248
249 # Remove plugins substitution awk script
250 local awk_subst_plugins="\
251 gsub(/[ \t]+(${(j:|:)dis_plugins})[ \t]+/, \" \") # with spaces before or after
252 gsub(/[ \t]+(${(j:|:)dis_plugins})$/, \"\") # with spaces before and EOL
253 gsub(/^(${(j:|:)dis_plugins})[ \t]+/, \"\") # with BOL and spaces after
254
255 gsub(/\((${(j:|:)dis_plugins})[ \t]+/, \"(\") # with parenthesis before and spaces after
256 gsub(/[ \t]+(${(j:|:)dis_plugins})\)/, \")\") # with spaces before or parenthesis after
257 gsub(/\((${(j:|:)dis_plugins})\)/, \"()\") # with only parentheses
258
259 gsub(/^(${(j:|:)dis_plugins})\)/, \")\") # with BOL and closing parenthesis
260 gsub(/\((${(j:|:)dis_plugins})$/, \"(\") # with opening parenthesis and EOL
261"
262
263 # Disable plugins awk script
264 local awk_script="
265# if plugins=() is in oneline form, substitute disabled plugins and go to next line
266/^[ \t]*plugins=\([^#]+\).*\$/ {
267 $awk_subst_plugins
268 print \$0
269 next
270}
271
272# if plugins=() is in multiline form, enable multi flag and disable plugins if they're there
273/^[ \t]*plugins=\(/ {
274 multi=1
275 $awk_subst_plugins
276 print \$0
277 next
278}
279
280# if multi flag is enabled and we find a valid closing parenthesis, remove plugins and disable multi flag
281multi == 1 && /^[^#]*\)/ {
282 multi=0
283 $awk_subst_plugins
284 print \$0
285 next
286}
287
288multi == 1 && length(\$0) > 0 {
289 $awk_subst_plugins
290 if (length(\$0) > 0) print \$0
291 next
292}
293
294{ print \$0 }
295"
296
297 local zdot="${ZDOTDIR:-$HOME}"
298 local zshrc="${${:-"${zdot}/.zshrc"}:A}"
299 awk "$awk_script" "$zshrc" > "$zdot/.zshrc.new" \
300 && command cp -f "$zshrc" "$zdot/.zshrc.bck" \
301 && command mv -f "$zdot/.zshrc.new" "$zshrc"
302
303 # Exit if the new .zshrc file wasn't created correctly
304 [[ $? -eq 0 ]] || {
305 local ret=$?
306 _omz::log error "error disabling plugins."
307 return $ret
308 }
309
310 # Exit if the new .zshrc file has syntax errors
311 if ! command zsh -n "$zdot/.zshrc"; then
312 _omz::log error "broken syntax in '"${zdot/#$HOME/\~}/.zshrc"'. Rolling back changes..."
313 command mv -f "$zdot/.zshrc.bck" "$zshrc"
314 return 1
315 fi
316
317 # Restart the zsh session if there were no errors
318 _omz::log info "plugins disabled: ${(j:, :)dis_plugins}."
319
320 # Only reload zsh if run in an interactive session
321 [[ ! -o interactive ]] || _omz::reload
322}
323
324function _omz::plugin::enable {
325 if [[ -z "$1" ]]; then
326 echo >&2 "Usage: ${(j: :)${(s.::.)0#_}} <plugin> [...]"
327 return 1
328 fi
329
330 # Check that plugin is not in $plugins
331 local -a add_plugins
332 for plugin in "$@"; do
333 if [[ ${plugins[(Ie)$plugin]} -ne 0 ]]; then
334 _omz::log warn "plugin '$plugin' is already enabled."
335 continue
336 fi
337 add_plugins+=("$plugin")
338 done
339
340 # Exit if there are no plugins to enable
341 if [[ ${#add_plugins} -eq 0 ]]; then
342 return 1
343 fi
344
345 # Enable plugins awk script
346 local awk_script="
347# if plugins=() is in oneline form, substitute ) with new plugins and go to the next line
348/^[ \t]*plugins=\([^#]+\).*\$/ {
349 sub(/\)/, \" $add_plugins&\")
350 print \$0
351 next
352}
353
354# if plugins=() is in multiline form, enable multi flag and indent by default with 2 spaces
355/^[ \t]*plugins=\(/ {
356 multi=1
357 indent=\" \"
358 print \$0
359 next
360}
361
362# if multi flag is enabled and we find a valid closing parenthesis,
363# add new plugins with proper indent and disable multi flag
364multi == 1 && /^[^#]*\)/ {
365 multi=0
366 split(\"$add_plugins\",p,\" \")
367 for (i in p) {
368 print indent p[i]
369 }
370 print \$0
371 next
372}
373
374# if multi flag is enabled and we didn't find a closing parenthesis,
375# get the indentation level to match when adding plugins
376multi == 1 && /^[^#]*/ {
377 indent=\"\"
378 for (i = 1; i <= length(\$0); i++) {
379 char=substr(\$0, i, 1)
380 if (char == \" \" || char == \"\t\") {
381 indent = indent char
382 } else {
383 break
384 }
385 }
386}
387
388{ print \$0 }
389"
390
391 local zdot="${ZDOTDIR:-$HOME}"
392 local zshrc="${${:-"${zdot}/.zshrc"}:A}"
393 awk "$awk_script" "$zshrc" > "$zdot/.zshrc.new" \
394 && command cp -f "$zshrc" "$zdot/.zshrc.bck" \
395 && command mv -f "$zdot/.zshrc.new" "$zshrc"
396
397 # Exit if the new .zshrc file wasn't created correctly
398 [[ $? -eq 0 ]] || {
399 local ret=$?
400 _omz::log error "error enabling plugins."
401 return $ret
402 }
403
404 # Exit if the new .zshrc file has syntax errors
405 if ! command zsh -n "$zdot/.zshrc"; then
406 _omz::log error "broken syntax in '"${zdot/#$HOME/\~}/.zshrc"'. Rolling back changes..."
407 command mv -f "$zdot/.zshrc.bck" "$zshrc"
408 return 1
409 fi
410
411 # Restart the zsh session if there were no errors
412 _omz::log info "plugins enabled: ${(j:, :)add_plugins}."
413
414 # Only reload zsh if run in an interactive session
415 [[ ! -o interactive ]] || _omz::reload
416}
417
418function _omz::plugin::info {
419 if [[ -z "$1" ]]; then
420 echo >&2 "Usage: ${(j: :)${(s.::.)0#_}} <plugin>"
421 return 1
422 fi
423
424 local readme
425 for readme in "$ZSH_CUSTOM/plugins/$1/README.md" "$ZSH/plugins/$1/README.md"; do
426 if [[ -f "$readme" ]]; then
427 # If being piped, just cat the README
428 if [[ ! -t 1 ]]; then
429 cat "$readme"
430 return $?
431 fi
432
433 # Enrich the README display depending on the tools we have
434 # - glow: https://github.com/charmbracelet/glow
435 # - bat: https://github.com/sharkdp/bat
436 # - less: typical pager command
437 case 1 in
438 ${+commands[glow]}) glow -p "$readme" ;;
439 ${+commands[bat]}) bat -l md --style plain "$readme" ;;
440 ${+commands[less]}) less "$readme" ;;
441 *) cat "$readme" ;;
442 esac
443 return $?
444 fi
445 done
446
447 if [[ -d "$ZSH_CUSTOM/plugins/$1" || -d "$ZSH/plugins/$1" ]]; then
448 _omz::log error "the '$1' plugin doesn't have a README file"
449 else
450 _omz::log error "'$1' plugin not found"
451 fi
452
453 return 1
454}
455
456function _omz::plugin::list {
457 local -a custom_plugins builtin_plugins
458
459 # If --enabled is provided, only list what's enabled
460 if [[ "$1" == "--enabled" ]]; then
461 local plugin
462 for plugin in "${plugins[@]}"; do
463 if [[ -d "${ZSH_CUSTOM}/plugins/${plugin}" ]]; then
464 custom_plugins+=("${plugin}")
465 elif [[ -d "${ZSH}/plugins/${plugin}" ]]; then
466 builtin_plugins+=("${plugin}")
467 fi
468 done
469 else
470 custom_plugins=("$ZSH_CUSTOM"/plugins/*(-/N:t))
471 builtin_plugins=("$ZSH"/plugins/*(-/N:t))
472 fi
473
474 # If the command is being piped, print all found line by line
475 if [[ ! -t 1 ]]; then
476 print -l ${(q-)custom_plugins} ${(q-)builtin_plugins}
477 return
478 fi
479
480 if (( ${#custom_plugins} )); then
481 print -P "%U%BCustom plugins%b%u:"
482 print -lac ${(q-)custom_plugins}
483 fi
484
485 if (( ${#builtin_plugins} )); then
486 (( ${#custom_plugins} )) && echo # add a line of separation
487
488 print -P "%U%BBuilt-in plugins%b%u:"
489 print -lac ${(q-)builtin_plugins}
490 fi
491}
492
493function _omz::plugin::load {
494 if [[ -z "$1" ]]; then
495 echo >&2 "Usage: ${(j: :)${(s.::.)0#_}} <plugin> [...]"
496 return 1
497 fi
498
499 local plugin base has_completion=0
500 for plugin in "$@"; do
501 if [[ -d "$ZSH_CUSTOM/plugins/$plugin" ]]; then
502 base="$ZSH_CUSTOM/plugins/$plugin"
503 elif [[ -d "$ZSH/plugins/$plugin" ]]; then
504 base="$ZSH/plugins/$plugin"
505 else
506 _omz::log warn "plugin '$plugin' not found"
507 continue
508 fi
509
510 # Check if its a valid plugin
511 if [[ ! -f "$base/_$plugin" && ! -f "$base/$plugin.plugin.zsh" ]]; then
512 _omz::log warn "'$plugin' is not a valid plugin"
513 continue
514 # It is a valid plugin, add its directory to $fpath unless it is already there
515 elif (( ! ${fpath[(Ie)$base]} )); then
516 fpath=("$base" $fpath)
517 fi
518
519 # Check if it has completion to reload compinit
520 local -a comp_files
521 comp_files=($base/_*(N))
522 has_completion=$(( $#comp_files > 0 ))
523
524 # Load the plugin
525 if [[ -f "$base/$plugin.plugin.zsh" ]]; then
526 source "$base/$plugin.plugin.zsh"
527 fi
528 done
529
530 # If we have completion, we need to reload the completion
531 # We pass -D to avoid generating a new dump file, which would overwrite our
532 # current one for the next session (and we don't want that because we're not
533 # actually enabling the plugins for the next session).
534 # Note that we still have to pass -d "$_comp_dumpfile", so that compinit
535 # doesn't use the default zcompdump location (${ZDOTDIR:-$HOME}/.zcompdump).
536 if (( has_completion )); then
537 compinit -D -d "$_comp_dumpfile"
538 fi
539}
540
541function _omz::pr {
542 (( $# > 0 && $+functions[$0::$1] )) || {
543 cat >&2 <<EOF
544Usage: ${(j: :)${(s.::.)0#_}} <command> [options]
545
546Available commands:
547
548 clean Delete all PR branches (ohmyzsh/pull-*)
549 test <PR_number_or_URL> Fetch PR #NUMBER and rebase against master
550
551EOF
552 return 1
553 }
554
555 local command="$1"
556 shift
557
558 $0::$command "$@"
559}
560
561function _omz::pr::clean {
562 (
563 set -e
564 builtin cd -q "$ZSH"
565
566 # Check if there are PR branches
567 local fmt branches
568 fmt="%(color:bold blue)%(align:18,right)%(refname:short)%(end)%(color:reset) %(color:dim bold red)%(objectname:short)%(color:reset) %(color:yellow)%(contents:subject)"
569 branches="$(command git for-each-ref --sort=-committerdate --color --format="$fmt" "refs/heads/ohmyzsh/pull-*")"
570
571 # Exit if there are no PR branches
572 if [[ -z "$branches" ]]; then
573 _omz::log info "there are no Pull Request branches to remove."
574 return
575 fi
576
577 # Print found PR branches
578 echo "$branches\n"
579 # Confirm before removing the branches
580 _omz::confirm "do you want remove these Pull Request branches? [Y/n] "
581 # Only proceed if the answer is a valid yes option
582 [[ "$REPLY" != [yY$'\n'] ]] && return
583
584 _omz::log info "removing all Oh My Zsh Pull Request branches..."
585 command git branch --list 'ohmyzsh/pull-*' | while read branch; do
586 command git branch -D "$branch"
587 done
588 )
589}
590
591function _omz::pr::test {
592 # Allow $1 to be a URL to the pull request
593 if [[ "$1" = https://* ]]; then
594 1="${1:t}"
595 fi
596
597 # Check the input
598 if ! [[ -n "$1" && "$1" =~ ^[[:digit:]]+$ ]]; then
599 echo >&2 "Usage: ${(j: :)${(s.::.)0#_}} <PR_NUMBER_or_URL>"
600 return 1
601 fi
602
603 # Save current git HEAD
604 local branch
605 branch=$(builtin cd -q "$ZSH"; git symbolic-ref --short HEAD) || {
606 _omz::log error "error when getting the current git branch. Aborting..."
607 return 1
608 }
609
610
611 # Fetch PR onto ohmyzsh/pull-<PR_NUMBER> branch and rebase against master
612 # If any of these operations fail, undo the changes made
613 (
614 set -e
615 builtin cd -q "$ZSH"
616
617 # Get the ohmyzsh git remote
618 command git remote -v | while read remote url _; do
619 case "$url" in
620 https://github.com/ohmyzsh/ohmyzsh(|.git)) found=1; break ;;
621 git@github.com:ohmyzsh/ohmyzsh(|.git)) found=1; break ;;
622 esac
623 done
624
625 (( $found )) || {
626 _omz::log error "could not find the ohmyzsh git remote. Aborting..."
627 return 1
628 }
629
630 # Check if Pull Request has the "testers needed" label
631 _omz::log info "checking if PR #$1 has the 'testers needed' label..."
632 local pr_json label label_id="MDU6TGFiZWw4NzY1NTkwNA=="
633 pr_json=$(
634 curl -fsSL \
635 -H "Accept: application/vnd.github+json" \
636 -H "X-GitHub-Api-Version: 2022-11-28" \
637 "https://api.github.com/repos/ohmyzsh/ohmyzsh/pulls/$1"
638 )
639
640 if [[ $? -gt 0 || -z "$pr_json" ]]; then
641 _omz::log error "error when trying to fetch PR #$1 from GitHub."
642 return 1
643 fi
644
645 # Check if the label is present with jq or grep
646 if (( $+commands[jq] )); then
647 label="$(command jq ".labels.[] | select(.node_id == \"$label_id\")" <<< "$pr_json")"
648 else
649 label="$(command grep "\"$label_id\"" <<< "$pr_json" 2>/dev/null)"
650 fi
651
652 # If a maintainer hasn't labeled the PR to test, explain the security risk
653 if [[ -z "$label" ]]; then
654 _omz::log warn "PR #$1 does not have the 'testers needed' label. This means that the PR"
655 _omz::log warn "has not been reviewed by a maintainer and may contain malicious code."
656
657 # Ask for explicit confirmation: user needs to type "yes" to continue
658 _omz::log prompt "Do you want to continue testing it? [yes/N] "
659 builtin read -r
660 if [[ "${REPLY:l}" != yes ]]; then
661 _omz::log error "PR test canceled. Please ask a maintainer to review and label the PR."
662 return 1
663 else
664 _omz::log warn "Continuing to check out and test PR #$1. Be careful!"
665 fi
666 fi
667
668 # Fetch pull request head
669 _omz::log info "fetching PR #$1 to ohmyzsh/pull-$1..."
670 command git fetch -f "$remote" refs/pull/$1/head:ohmyzsh/pull-$1 || {
671 _omz::log error "error when trying to fetch PR #$1."
672 return 1
673 }
674
675 # Rebase pull request branch against the current master
676 _omz::log info "rebasing PR #$1..."
677 local ret gpgsign
678 {
679 # Back up commit.gpgsign setting: use --local to get the current repository
680 # setting, not the global one. If --local is not a known option, it will
681 # exit with a 129 status code.
682 gpgsign=$(command git config --local commit.gpgsign 2>/dev/null) || ret=$?
683 [[ $ret -ne 129 ]] || gpgsign=$(command git config commit.gpgsign 2>/dev/null)
684 command git config commit.gpgsign false
685
686 command git rebase master ohmyzsh/pull-$1 || {
687 command git rebase --abort &>/dev/null
688 _omz::log warn "could not rebase PR #$1 on top of master."
689 _omz::log warn "you might not see the latest stable changes."
690 _omz::log info "run \`zsh\` to test the changes."
691 return 1
692 }
693 } always {
694 case "$gpgsign" in
695 "") command git config --unset commit.gpgsign ;;
696 *) command git config commit.gpgsign "$gpgsign" ;;
697 esac
698 }
699
700 _omz::log info "fetch of PR #${1} successful."
701 )
702
703 # If there was an error, abort running zsh to test the PR
704 [[ $? -eq 0 ]] || return 1
705
706 # Run zsh to test the changes
707 _omz::log info "running \`zsh\` to test the changes. Run \`exit\` to go back."
708 command zsh -l
709
710 # After testing, go back to the previous HEAD if the user wants
711 _omz::confirm "do you want to go back to the previous branch? [Y/n] "
712 # Only proceed if the answer is a valid yes option
713 [[ "$REPLY" != [yY$'\n'] ]] && return
714
715 (
716 set -e
717 builtin cd -q "$ZSH"
718
719 command git checkout "$branch" -- || {
720 _omz::log error "could not go back to the previous branch ('$branch')."
721 return 1
722 }
723 )
724}
725
726function _omz::shop {
727 local shop_url="https://commitgoods.com/collections/oh-my-zsh"
728
729 _omz::log info "Opening Oh My Zsh shop in your browser..."
730 _omz::log info "$shop_url"
731
732 open_command "$shop_url"
733}
734
735function _omz::reload {
736 # Delete current completion cache
737 command rm -f $_comp_dumpfile $ZSH_COMPDUMP
738
739 # Old zsh versions don't have ZSH_ARGZERO
740 local zsh="${ZSH_ARGZERO:-${functrace[-1]%:*}}"
741 # Check whether to run a login shell
742 [[ "$zsh" = -* || -o login ]] && exec -l "${zsh#-}" || exec "$zsh"
743}
744
745function _omz::theme {
746 (( $# > 0 && $+functions[$0::$1] )) || {
747 cat >&2 <<EOF
748Usage: ${(j: :)${(s.::.)0#_}} <command> [options]
749
750Available commands:
751
752 list List all available Oh My Zsh themes
753 set <theme> Set a theme in your .zshrc file
754 use <theme> Load a theme
755
756EOF
757 return 1
758 }
759
760 local command="$1"
761 shift
762
763 $0::$command "$@"
764}
765
766function _omz::theme::list {
767 local -a custom_themes builtin_themes
768 custom_themes=("$ZSH_CUSTOM"/**/*.zsh-theme(-.N:r:gs:"$ZSH_CUSTOM"/themes/:::gs:"$ZSH_CUSTOM"/:::))
769 builtin_themes=("$ZSH"/themes/*.zsh-theme(-.N:t:r))
770
771 # If the command is being piped, print all found line by line
772 if [[ ! -t 1 ]]; then
773 print -l ${(q-)custom_themes} ${(q-)builtin_themes}
774 return
775 fi
776
777 # Print theme in use
778 if [[ -n "$ZSH_THEME" ]]; then
779 print -Pn "%U%BCurrent theme%b%u: "
780 [[ $ZSH_THEME = random ]] && echo "$RANDOM_THEME (via random)" || echo "$ZSH_THEME"
781 echo
782 fi
783
784 # Print custom themes if there are any
785 if (( ${#custom_themes} )); then
786 print -P "%U%BCustom themes%b%u:"
787 print -lac ${(q-)custom_themes}
788 echo
789 fi
790
791 # Print built-in themes
792 print -P "%U%BBuilt-in themes%b%u:"
793 print -lac ${(q-)builtin_themes}
794}
795
796function _omz::theme::set {
797 if [[ -z "$1" ]]; then
798 echo >&2 "Usage: ${(j: :)${(s.::.)0#_}} <theme>"
799 return 1
800 fi
801
802 # Check that theme exists
803 if [[ ! -f "$ZSH_CUSTOM/$1.zsh-theme" ]] \
804 && [[ ! -f "$ZSH_CUSTOM/themes/$1.zsh-theme" ]] \
805 && [[ ! -f "$ZSH/themes/$1.zsh-theme" ]]; then
806 _omz::log error "%B$1%b theme not found"
807 return 1
808 fi
809
810 # Enable theme in .zshrc
811 local awk_script='
812!set && /^[ \t]*ZSH_THEME=[^#]+.*$/ {
813 set=1
814 sub(/^[ \t]*ZSH_THEME=[^#]+.*$/, "ZSH_THEME=\"'$1'\" # set by `omz`")
815 print $0
816 next
817}
818
819{ print $0 }
820
821END {
822 # If no ZSH_THEME= line was found, return an error
823 if (!set) exit 1
824}
825'
826
827 local zdot="${ZDOTDIR:-$HOME}"
828 local zshrc="${${:-"${zdot}/.zshrc"}:A}"
829 awk "$awk_script" "$zshrc" > "$zdot/.zshrc.new" \
830 || {
831 # Prepend ZSH_THEME= line to .zshrc if it doesn't exist
832 cat <<EOF
833ZSH_THEME="$1" # set by \`omz\`
834
835EOF
836 cat "$zdot/.zshrc"
837 } > "$zdot/.zshrc.new" \
838 && command cp -f "$zshrc" "$zdot/.zshrc.bck" \
839 && command mv -f "$zdot/.zshrc.new" "$zshrc"
840
841 # Exit if the new .zshrc file wasn't created correctly
842 [[ $? -eq 0 ]] || {
843 local ret=$?
844 _omz::log error "error setting theme."
845 return $ret
846 }
847
848 # Exit if the new .zshrc file has syntax errors
849 if ! command zsh -n "$zdot/.zshrc"; then
850 _omz::log error "broken syntax in '"${zdot/#$HOME/\~}/.zshrc"'. Rolling back changes..."
851 command mv -f "$zdot/.zshrc.bck" "$zshrc"
852 return 1
853 fi
854
855 # Restart the zsh session if there were no errors
856 _omz::log info "'$1' theme set correctly."
857
858 # Only reload zsh if run in an interactive session
859 [[ ! -o interactive ]] || _omz::reload
860}
861
862function _omz::theme::use {
863 if [[ -z "$1" ]]; then
864 echo >&2 "Usage: ${(j: :)${(s.::.)0#_}} <theme>"
865 return 1
866 fi
867
868 # Respect compatibility with old lookup order
869 if [[ -f "$ZSH_CUSTOM/$1.zsh-theme" ]]; then
870 source "$ZSH_CUSTOM/$1.zsh-theme"
871 elif [[ -f "$ZSH_CUSTOM/themes/$1.zsh-theme" ]]; then
872 source "$ZSH_CUSTOM/themes/$1.zsh-theme"
873 elif [[ -f "$ZSH/themes/$1.zsh-theme" ]]; then
874 source "$ZSH/themes/$1.zsh-theme"
875 else
876 _omz::log error "%B$1%b theme not found"
877 return 1
878 fi
879
880 # Update theme settings
881 ZSH_THEME="$1"
882 [[ $1 = random ]] || unset RANDOM_THEME
883}
884
885function _omz::update {
886 # Check if git command is available
887 (( $+commands[git] )) || {
888 _omz::log error "git is not installed. Aborting..."
889 return 1
890 }
891
892 # Check if --unattended was passed
893 [[ "$1" != --unattended ]] || {
894 _omz::log error "the \`\e[2m--unattended\e[0m\` flag is no longer supported, use the \`\e[2mupgrade.sh\e[0m\` script instead."
895 _omz::log error "for more information see https://github.com/ohmyzsh/ohmyzsh/wiki/FAQ#how-do-i-update-oh-my-zsh"
896 return 1
897 }
898
899 local last_commit=$(builtin cd -q "$ZSH"; git rev-parse HEAD 2>/dev/null)
900 [[ $? -eq 0 ]] || {
901 _omz::log error "\`$ZSH\` is not a git directory. Aborting..."
902 return 1
903 }
904
905 # Run update script
906 zstyle -s ':omz:update' verbose verbose_mode || verbose_mode=default
907 ZSH="$ZSH" command zsh -f "$ZSH/tools/upgrade.sh" -i -v $verbose_mode || return $?
908
909 # Update last updated file
910 zmodload zsh/datetime
911 echo "LAST_EPOCH=$(( EPOCHSECONDS / 60 / 60 / 24 ))" >! "${ZSH_CACHE_DIR}/.zsh-update"
912 # Remove update lock if it exists
913 command rm -rf "$ZSH/log/update.lock"
914
915 # Restart the zsh session if there were changes
916 if [[ "$(builtin cd -q "$ZSH"; git rev-parse HEAD)" != "$last_commit" ]]; then
917 # Old zsh versions don't have ZSH_ARGZERO
918 local zsh="${ZSH_ARGZERO:-${functrace[-1]%:*}}"
919 # Check whether to run a login shell
920 [[ "$zsh" = -* || -o login ]] && exec -l "${zsh#-}" || exec "$zsh"
921 fi
922}
923
924function _omz::version {
925 (
926 builtin cd -q "$ZSH"
927
928 # Get the version name:
929 # 1) try tag-like version
930 # 2) try branch name
931 # 3) try name-rev (tag~<rev> or branch~<rev>)
932 local version
933 version=$(command git describe --tags HEAD 2>/dev/null) \
934 || version=$(command git symbolic-ref --quiet --short HEAD 2>/dev/null) \
935 || version=$(command git name-rev --no-undefined --name-only --exclude="remotes/*" HEAD 2>/dev/null) \
936 || version="<detached>"
937
938 # Get short hash for the current HEAD
939 local commit=$(command git rev-parse --short HEAD 2>/dev/null)
940
941 # Show version and commit hash
942 printf "%s (%s)\n" "$version" "$commit"
943 )
944}