cli.zsh 10.1 KB
Newer Older
1
2
3
#!/usr/bin/env zsh

function omz {
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  [[ $# -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 "$@"
20
21
22
}

function _omz {
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
  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
55
56
57
58
}

compdef _omz omz

59
## Utility functions
60

61
function _omz::confirm {
62
63
64
65
66
67
68
69
70
71
72
73
74
  # 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
75
76
}

77
function _omz::log {
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
  # 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
102
103
}

104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
## User-facing commands

function _omz::help {
  cat <<EOF
Usage: omz <command> [options]

Available commands:

  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::changelog {
  local version=${1:-HEAD} format=${3:-"--text"}

  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]

NOTE: <version> must be a valid branch, tag or commit.
EOF
    return 1
  fi

  "$ZSH/tools/changelog.sh" "$version" "${2:-}" "$format"
}

139
function _omz::plugin {
140
141
  (( $# > 0 && $+functions[_omz::plugin::$1] )) || {
    cat <<EOF
142
143
144
145
Usage: omz plugin <command> [options]

Available commands:

146
  list            List all available Oh My Zsh plugins
147
148

EOF
149
150
    return 1
  }
151

152
153
  local command="$1"
  shift
154

155
  _omz::plugin::$command "$@"
156
157
158
}

function _omz::plugin::list {
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
  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
180
181
}

182
function _omz::pr {
183
184
  (( $# > 0 && $+functions[_omz::pr::$1] )) || {
    cat <<EOF
185
186
187
188
Usage: omz pr <command> [options]

Available commands:

189
190
  clean                       Delete all PR branches (ohmyzsh/pull-*)
  test <PR_number_or_URL>     Fetch PR #NUMBER and rebase against master
191
192

EOF
193
194
    return 1
  }
195

196
197
  local command="$1"
  shift
198

199
  _omz::pr::$command "$@"
200
201
202
}

function _omz::pr::clean {
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
  (
    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
  )
230
231
232
}

function _omz::pr::test {
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
  # 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
    }
270

271
272
273
274
275
    # 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
276
277
    }

278
279
280
281
282
283
284
285
286
    # 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
    }
287

288
289
    _omz::log info "fetch of PR #${1} successful."
  )
290

291
292
  # If there was an error, abort running zsh to test the PR
  [[ $? -eq 0 ]] || return 1
293

294
295
296
  # Run zsh to test the changes
  _omz::log info "running \`zsh\` to test the changes. Run \`exit\` to go back."
  command zsh -l
297

298
299
300
301
  # 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
302

303
304
305
  (
    set -e
    builtin cd -q "$ZSH"
306

307
308
309
310
311
    command git checkout "$branch" -- || {
      _omz::log error "could not go back to the previous branch ('$branch')."
      return 1
    }
  )
312
}
313

314
function _omz::theme {
315
316
  (( $# > 0 && $+functions[_omz::theme::$1] )) || {
    cat <<EOF
317
318
319
320
Usage: omz theme <command> [options]

Available commands:

321
322
  list            List all available Oh My Zsh themes
  use <theme>     Load an Oh My Zsh theme
323
324

EOF
325
326
    return 1
  }
327

328
329
  local command="$1"
  shift
330

331
  _omz::theme::$command "$@"
332
333
334
}

function _omz::theme::list {
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
  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
356
357
358
}

function _omz::theme::use {
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
  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
375
376
}

377
function _omz::update {
378
  # Run update script
379
380
381
382
383
  if [[ "$1" != --unattended ]]; then
    ZSH="$ZSH" zsh -f "$ZSH/tools/upgrade.sh" --interactive
  else
    ZSH="$ZSH" zsh -f "$ZSH/tools/upgrade.sh"
  fi
384
  local ret=$?
385

386
387
388
389
390
  # 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"
391

392
  # Restart the zsh session
393
  if [[ $ret -eq 0 && "$1" != --unattended ]]; then
394
395
396
    # Check whether to run a login shell
    [[ "$ZSH_ARGZERO" = -* ]] && exec -l "${ZSH_ARGZERO#-}" || exec "$ZSH_ARGZERO"
  fi
397
}