]> arthur.barton.de Git - ax-zsh.git/blob - bin/axzshctl
axzshctl: Show help on stderr, add "help" alias
[ax-zsh.git] / bin / axzshctl
1 #!/usr/bin/env zsh
2 #
3 # AX-ZSH: Alex' Modular ZSH Configuration
4 # Copyright (c) 2015-2020 Alexander Barton <alex@barton.de>
5 #
6
7 # Include "ax-common.sh", if available:
8 for dir ("$HOME/lib" "$HOME/.ax" /usr/local /opt/ax /usr); do
9         [[ -z "$ax_common_sourced" ]] || break
10         ax_common="${dir}/lib/ax/ax-common.sh"
11         [[ -r "$ax_common" ]] && source "$ax_common"
12 done
13 if [[ -z "$ax_common_sourced" ]]; then
14         function ax_msg {
15                 shift
16                 echo "$@"
17         }
18         function ax_error {
19                 ax_msg 2 "$@" >&2
20         }
21 fi
22 unset dir ax_common ax_common_sourced
23
24 function Usage {
25         echo "Usage: $NAME <command> [...]"
26         echo
27         echo "  enable"
28         echo "    Enable AX-ZSH altogether."
29         echo "  disable"
30         echo "    Disable AX-ZSH altogether."
31         echo
32         echo "  enable-plugin <name|directory> [<name|directory> [...]]"
33         echo "    Enable plugin(s)."
34         echo "  disable-plugin <name> [<name> [...]]"
35         echo "    Disable plugin(s)."
36         echo "  list-enabled"
37         echo "    List enabled plugins."
38         echo
39         echo "  reset-plugins"
40         echo "    Reset active plugins to the default set."
41         echo "  enable-default-plugins"
42         echo "    Enable all default plugins."
43         echo "  check-plugins"
44         echo "    Detect plugins which are \"useful\" on this system."
45         echo
46         echo "  set-theme <name>|-"
47         echo "    Set active theme to <name>, or to the default."
48         echo
49         echo "  upgrade"
50         echo "    Upgrade AX-ZSH installation (requires Git)."
51         echo "  update-caches"
52         echo "    Force rebuild of all cache files."
53         echo
54         exit 0
55 }
56
57 function UpdatePluginCache {
58         [[ -r "$AXZSH/cache" ]] || return 0
59
60         [[ "$1" = "-v" ]] && ax_msg - "Updating plugin cache ..."
61         rm -rf \
62                 $AXZSH/cache/zlogin.cache \
63                 $AXZSH/cache/zlogout.cache \
64                 $AXZSH/cache/zprofile.cache \
65                 $AXZSH/cache/zshrc.cache \
66                 || return 1
67         echo "Regenerating cache files ..."
68         zsh -ilc '' >/dev/null
69 }
70
71 function NormalizedPluginName {
72         if [[ "$1" =~ "^[[:alnum:]-]+/[[:alnum:]_-]+$" ]]; then
73                 echo "${1:gs/\//#}"
74         elif [[ "$1" =~ "/" ]]; then
75                 echo "${1:t}"
76         else
77                 echo "$1"
78         fi
79 }
80
81 function EnableAXZSH {
82         for f (~/.zlogin ~/.zlogout ~/.zprofile ~/.zshrc); do
83                 ln -s "$AXZSH/ax.zsh" "$f" \
84                         || ax_error "Failed to create symbolic link for \"$f\"!"
85         done
86 }
87
88 function DisableAXZSH {
89         for f (~/.zlogin ~/.zlogout ~/.zprofile ~/.zshrc); do
90                 if [[ -h "$f" ]]; then
91                         rm "$f" || ax_msg 2 "Failed to remove \"$f\"!"
92                 elif [[ -e "$f" ]]; then
93                         ax_error "Error: Not removing \"$f\", it is not a symbolic link!"
94                 else
95                         ax_msg 1 "Warning: \"$f\" already does not exist. Ok."
96                 fi
97         done
98 }
99
100 function EnablePlugin {
101         local plugin=$(NormalizedPluginName "$1")
102         local dir="$AXZSH/active_plugins"
103
104         if [[ -h "$dir/$plugin" ]]; then
105                 ax_msg 1 "Plugin \"$1\" already active!"
106                 return 1
107         fi
108
109         if [[ "$1" =~ "^[[:alnum:]-]+/[[:alnum:]_-]+$" ]]; then
110                 # GitHub plugin
111                 mkdir -p "$AXZSH/repos"
112                 if [[ ! -e "$AXZSH/repos/$plugin" ]]; then
113                         ax_msg - "Cloning module from GitHub ..."
114                         git clone --depth=1 "https://github.com/$1.git" \
115                          "$AXZSH/repos/$plugin" \
116                                 || ax_error "Failed to clone repository!"
117                 fi
118                 # Try to enable a theme in this "foreign module", but ignore
119                 # errors: we don't know if this module provides a theme or is
120                 # a "regular" plugin ...
121                 if SetTheme "$plugin" 2>/dev/null; then
122                         ax_msg 0 "Module \"$1\" was enabled as theme \"${plugin#*#}\"."
123                         # A theme was enabled: So assume that this is a theme
124                         # and don't enable it as plugin.
125                         return 0
126                 fi
127                 echo "Trying to enable \"$1\" as plugin ..."
128         fi
129
130         for dname (
131                 "$plugin:A"
132                 "$AXZSH_PLUGIN_D/$plugin"
133                 "$ZSH_CUSTOM/$plugin"
134                 "$AXZSH/custom_plugins/$plugin"
135                 "$AXZSH/repos/$plugin"
136                 "$AXZSH/plugins/$plugin"
137                 "$AXZSH/default_plugins/$plugin"
138                 "$AXZSH/core/$plugin"
139         ); do
140                 [[ ! -d "$dname" ]] && continue
141                 mkdir -p "$dir"
142                 if ! (
143                         cd "$dir" || exit 9
144                         ln -s "$dname" "$PWD"
145                 ); then
146                         ax_error "Failed to create link!"
147                         return 1
148                 fi
149                 ax_msg 0 "Plugin \"$plugin\" enabled."
150                 return 0
151         done
152
153         ax_error "Plugin \"$1\" not found!"
154         return 1
155 }
156
157 function DisablePlugin {
158         local plugin=$(NormalizedPluginName "$1")
159         local dir="$AXZSH/active_plugins"
160         local r=-1
161
162         # Active theme?
163         if [[ $(readlink "$AXZSH/active_theme") = "$AXZSH/repos/$plugin/"* ]]; then
164                 rm "$AXZSH/active_theme"; r=$?
165         fi
166
167         # Active plugin?
168         if [[ -h "$dir/$plugin" ]]; then
169                 rm "$dir/$plugin"; r=$?
170         fi
171
172         if [[ $r -eq -1 ]]; then
173                 ax_msg 1 "Plugin \"$1\" not active, nothing to do?"
174                 r=1
175         fi
176
177         if [[ "$plugin" = *"#"* ]]; then
178                 # Name matches a cloned repository, try to clean up!
179                 echo "Cleaning up cloned repository ..."
180                 rm -fr "$AXZSH/repos/$plugin"
181         fi
182
183         return $r
184 }
185
186 function ListEnabledPlugins {
187         for plugin ($AXZSH/active_plugins/*(N)); do
188                 print ${plugin:t:s/#/\//}
189         done
190         return 0
191 }
192
193 function ResetPlugins {
194         local dir="$AXZSH/active_plugins"
195         local r1=0, r2=0
196
197         if [[ -e "$dir" ]]; then
198                 ax_msg - "Removing all symbolic links in $dir ..."
199                 find "$dir" -type l -print -delete; r1=$?
200         fi
201
202         ax_msg - "Removing all external repositories in \"$AXZSH/repos\" ..."
203         rm -fr "$AXZSH/repos"; r2=$?
204
205         [[ $r1 == 0 && $r2 == 0 ]] && return 0 || return 1
206 }
207
208 function EnableDefaultPlugins {
209         local dir="$AXZSH/active_plugins"
210
211         ax_msg - "Activating default plugins ..."
212         mkdir -p "$dir"
213         (
214                 cd "$dir" || exit 9
215                 ln -sf "$AXZSH/default_plugins/"* "$PWD"
216         )
217         return $?
218 }
219
220 function SetTheme {
221         local link_name="$AXZSH/active_theme"
222
223         # --- Powerlevel10k ---
224         # Remove "instant prompt" configuration, if any ...
225         rm -f "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh"
226
227         if [[ "$1" = "-" ]]; then
228                 rm -f "$link_name" || return 1
229                 ax_msg 0 "Theme settings have been reset."
230                 return 0
231         fi
232
233         if [[ -r "$1" ]]; then
234                 theme="$1"
235         elif [[ -r "$AXZSH/custom_themes/$1.axzshtheme" ]]; then
236                 theme="$AXZSH/custom_themes/$1.axzshtheme"
237         elif [[ -r "$AXZSH/themes/$1.axzshtheme" ]]; then
238                 theme="$AXZSH/themes/$1.axzshtheme"
239         else
240                 # Look for theme in specific remote module:
241                 for f (
242                         "$AXZSH/repos/$1/"*.axzshtheme(N[1])
243                         "$AXZSH/repos/$1/"*.zsh-theme(N[1])
244                 ); do
245                         if [[ -r "$f" ]]; then
246                                 theme="$f"
247                                 break
248                         fi
249                 done
250
251                 # Look for theme inside of installed plugins:
252                 for dname (
253                         "$AXZSH/custom_themes"
254                         "$AXZSH/custom_plugins/"*(N)
255                         "$AXZSH/repos/"*(N)
256                 ); do
257                         if [[ -r "$dname/$1.axzshtheme" ]]; then
258                                 theme="$dname/$1.axzshtheme"
259                                 break
260                         elif [[ -r "$dname/$1.zsh-theme" ]]; then
261                                 theme="$dname/$1.zsh-theme"
262                                 break
263                         fi
264                 done
265
266                 if [[ -z "$theme" ]]; then
267                         ax_error "Theme \"$1\" not found!"
268                         return 1
269                 fi
270         fi
271         ln -fs "$theme" "$link_name" || return 1
272         return $?
273 }
274
275 function UpgradeAXZSH {
276         if [[ $+commands[git] -eq 0 ]]; then
277                 ax_error "The git(1) command is not available!"
278                 return 1
279         fi
280         if [[ ! -d "$AXZSH/.git" ]]; then
281                 ax_error "AX-ZSH seems not to be installed using Git. Can't upgrade!"
282                 return 1
283         fi
284
285         ax_msg - "Upgrading AX-ZSH in \"$AXZSH\" using git(1) ..."
286         ( cd "$AXZSH" && git pull --ff-only )
287 }
288
289 function UpgradeForeignPlugins {
290         if [[ $+commands[git] -eq 0 ]]; then
291                 ax_error "The git(1) command is not available!"
292                 return 1
293         fi
294
295         for dir ($AXZSH/repos/*(N)); do
296                 name=${dir:t:s/#/\//}
297                 if [[ -d "$dir/.git" ]]; then
298                         ax_msg - "Upgrading \"$name\" [git] ..."
299                         (
300                                 cd "$dir"
301                                 git pull --ff-only || ax_error "Pull failed!"
302                         )
303                 else
304                         ax_error "Unknown repository type!"
305                 fi
306         done
307 }
308
309 function CheckPlugins {
310         missing_plugins=()
311         invalid_plugins=()
312
313         ax_msg - "Checking plugins ..."
314         for dir ($AXZSH/plugins/*(N)); do
315                 plugin=${dir:t}
316
317                 # Test if plugin is already enabled
318                 [[ -e "$AXZSH/active_plugins/$plugin" ]] \
319                         && enabled=" (enabled)" \
320                         || unset enabled
321
322                 # Test plugin ...
323                 printf " - \"%s\"%s ... " "$plugin" "$enabled"
324                 new_plugin=""
325                 for script ($AXZSH/plugins/$plugin/$plugin.{zshrc,zprofile}); do
326                         [[ -r "$script" ]] || continue
327                         AXZSH_PLUGIN_CHECK=1 zsh -i -c "source $script"; r=$?
328                         if [[ $r -eq 0 ]]; then
329                                 new_plugin=$plugin
330                                 break
331                         fi
332                 done
333                 if [[ -n "$new_plugin" ]]; then
334                         detected_plugins+=($new_plugin)
335                         [[ -n "$enabled" ]] || missing_plugins+=($new_plugin)
336                         ax_msg 0 "OK."
337                 elif [[ $r -eq 91 ]]; then
338                         ax_msg 1 "ignored."
339                 elif [[ $r -eq 92 ]]; then
340                         ax_msg 1 "optional."
341                 else
342                         [[ -n "$enabled" ]] && invalid_plugins+=($plugin)
343                         ax_msg 2 "failed."
344                 fi
345         done
346         echo
347
348         result=0
349         if [[ -n "$missing_plugins" ]]; then
350                 ax_msg 1 "Run the following command to enable all missing plugins:"
351                 echo "$AXZSH/bin/axzshctl enable-plugin" $missing_plugins
352                 echo
353                 result=1
354         else
355                 ax_msg 0 "All detected plugins are already enabled."
356         fi
357
358         if [[ -n "$invalid_plugins" ]]; then
359                 ax_msg 1 "Run the following command to disable all failed plugins:"
360                 echo "$AXZSH/bin/axzshctl disable-plugin" $invalid_plugins
361                 result=1
362         else
363                 ax_msg 0 "No failed plugins are enabled."
364         fi
365
366         echo
367         return $result
368 }
369
370 NAME="$0:t"
371
372 [[ $# -gt 0 ]] || Usage
373
374 if [[ -z "$AXZSH" || ! -r "$AXZSH/ax.zsh" ]]; then
375         [[ -r "$HOME/.axzsh/ax.zsh" ]] && AXZSH="$HOME/.axzsh"
376         if [[ ! -r "$AXZSH/ax.zsh" ]]; then
377                 ax_error "Oops, \"AXZSH\" is not set or invalid and can't be autodetected!"
378                 exit 3
379         fi
380 fi
381
382 cmd="$1"
383 shift
384
385 case "$cmd" in
386         "enable")
387                 [[ $# -eq 0 ]] || Usage
388                 EnableAXZSH
389                 ;;
390         "disable")
391                 [[ $# -eq 0 ]] || Usage
392                 DisableAXZSH
393                 ;;
394         "enable-plugin")
395                 [[ $# -gt 0 ]] || Usage
396                 for plugin in "$@"; do
397                         EnablePlugin "$plugin"
398                 done
399                 UpdatePluginCache
400                 ;;
401         "disable-plugin")
402                 [[ $# -gt 0 ]] || Usage
403                 for plugin in "$@"; do
404                         DisablePlugin "$plugin"
405                 done
406                 UpdatePluginCache
407                 ;;
408         "list-enabled")
409                 [[ $# -eq 0 ]] || Usage
410                 ListEnabledPlugins
411                 ;;
412         "reset-plugins")
413                 [[ $# -eq 0 ]] || Usage
414                 ResetPlugins
415                 EnableDefaultPlugins
416                 UpdatePluginCache
417                 ;;
418         "enable-default-plugins")
419                 [[ $# -eq 0 ]] || Usage
420                 EnableDefaultPlugins && UpdatePluginCache
421                 ;;
422         "check-plugins")
423                 [[ $# -eq 0 ]] || Usage
424                 CheckPlugins
425                 ;;
426         "set-theme")
427                 [[ $# -eq 1 ]] || Usage
428                 SetTheme "$1"
429                 ;;
430         "upgrade")
431                 [[ $# -eq 0 ]] || Usage
432                 UpgradeAXZSH
433                 UpgradeForeignPlugins
434                 UpdatePluginCache
435                 ;;
436         "update-caches")
437                 [[ $# -eq 0 ]] || Usage
438                 UpdatePluginCache -v
439                 ;;
440         "--help"|"help")
441                 Usage >&2
442                 ;;
443         *)
444                 ax_error "Invalid command \"$cmd\"!"
445                 ax_error "Try \"$0 --help\" for more information."
446                 exit 2
447 esac