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