├── .gitignore ├── .zshrc ├── LICENSE ├── README.md ├── completions └── _znap ├── functions ├── ..znap.auto-compile ├── ..znap.compinit-hook ├── ..znap.dirname ├── ..znap.error ├── ..znap.fetch ├── ..znap.repos ├── ..znap.repos-dir ├── ..znap.tput ├── .znap.clean ├── .znap.clone ├── .znap.clone.task ├── .znap.compile ├── .znap.eval ├── .znap.fpath ├── .znap.function ├── .znap.help ├── .znap.ignore ├── .znap.install ├── .znap.multi ├── .znap.prompt ├── .znap.prompt.load ├── .znap.pull ├── .znap.restart ├── .znap.source ├── .znap.source.link ├── .znap.status ├── .znap.uninstall └── znap ├── scripts ├── init.zsh └── opts.zsh └── znap.zsh /.gitignore: -------------------------------------------------------------------------------- 1 | *.zwc 2 | -------------------------------------------------------------------------------- /.zshrc: -------------------------------------------------------------------------------- 1 | ## 2 | # ⚠️ WARNING: Don't manually `source` your .zshrc file! This can have unexpected 3 | # side effects. 4 | # Instead, to apply changes, open a new terminal or restart your shell. 5 | # 6 | 7 | 8 | ## 9 | # Source Znap at the start of your .zshrc file. 10 | # 11 | source ~/git/zsh-snap/znap.zsh 12 | 13 | 14 | ## 15 | # Does your shell feels slow to start? `znap prompt` reduces the time between 16 | # opening your terminal and seeing your prompt to just 15 - 40 ms! 17 | # 18 | znap prompt agnoster/agnoster-zsh-theme 19 | 20 | # `znap prompt` also supports Oh-My-Zsh themes. Just make sure you load the 21 | # required libs first: 22 | znap source ohmyzsh/ohmyzsh lib/{git,theme-and-appearance} 23 | znap prompt ohmyzsh/ohmyzsh robbyrussell 24 | 25 | # Using your own custom prompt? After initializing the prompt, just call 26 | # `znap prompt` without arguments to get it to show: 27 | PS1=$'%(?,%F{g},%F{r})%#%f ' 28 | znap prompt 29 | 30 | # The same goes for any other kind of custom prompt: 31 | znap eval starship 'starship init zsh --print-full-init' 32 | znap prompt 33 | 34 | # NOTE that `znap prompt` does not work with Powerlevel10k. 35 | # With that theme, you should use its "instant prompt" feature instead. 36 | 37 | 38 | ## 39 | # Load your plugins with `znap source`. 40 | # 41 | znap source marlonrichert/zsh-autocomplete 42 | znap source marlonrichert/zsh-edit 43 | 44 | # You can also choose to load one or more files specifically: 45 | znap source sorin-ionescu/prezto modules/{environment,history} 46 | znap source ohmyzsh/ohmyzsh \ 47 | 'lib/(*~(git|theme-and-appearance).zsh)' plugins/git 48 | 49 | 50 | # No special syntax is needed to configure plugins. Just use normal Zsh 51 | # statements: 52 | 53 | znap source marlonrichert/zsh-hist 54 | bindkey '^[q' push-line-or-edit 55 | bindkey -r '^Q' '^[Q' 56 | 57 | ZSH_AUTOSUGGEST_STRATEGY=( history ) 58 | znap source zsh-users/zsh-autosuggestions 59 | 60 | ZSH_HIGHLIGHT_HIGHLIGHTERS=( main brackets ) 61 | znap source zsh-users/zsh-syntax-highlighting 62 | 63 | 64 | ## 65 | # Cache the output of slow commands with `znap eval`. 66 | # 67 | 68 | # If the first arg is a repo, then the command will run inside it. Plus, 69 | # whenever you update a repo with `znap pull`, its eval cache gets regenerated 70 | # automatically. 71 | znap eval trapd00r/LS_COLORS "$( whence -a dircolors gdircolors ) -b LS_COLORS" 72 | 73 | # The cache gets regenerated, too, when the eval command has changed. For 74 | # example, here we include a variable. So, the cache gets invalidated whenever 75 | # this variable has changed. 76 | znap source marlonrichert/zcolors 77 | znap eval marlonrichert/zcolors "zcolors ${(q)LS_COLORS}" 78 | 79 | # Combine `znap eval` with `curl` or `wget` to download, cache and source 80 | # individual files: 81 | znap eval omz-git 'curl -fsSL \ 82 | https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/plugins/git/git.plugin.zsh' 83 | 84 | 85 | ## 86 | # Defer initilization code with lazily loaded functions created by 87 | # `znap function`. 88 | # 89 | 90 | # For each of the examples below, the `eval` statement on the right is not 91 | # executed until you try to execute the associated command or try to use 92 | # completion on it. 93 | 94 | znap function _pyenv pyenv 'eval "$( pyenv init - --no-rehash )"' 95 | compctl -K _pyenv pyenv 96 | 97 | znap function _pip_completion pip 'eval "$( pip completion --zsh )"' 98 | compctl -K _pip_completion pip 99 | 100 | znap function _python_argcomplete pipx 'eval "$( register-python-argcomplete pipx )"' 101 | complete -o nospace -o default -o bashdefault \ 102 | -F _python_argcomplete pipx 103 | 104 | znap function _pipenv pipenv 'eval "$( pipenv --completion )"' 105 | compdef _pipenv pipenv 106 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Marlon Richert 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ⚡️Znap! 2 | **Znap** is a fast, light-weight set of tools to ease the use of Zsh plugins & 3 | Git repos and reduce your shell's startup time. 4 | 5 | > Enjoy using this software? [Become a sponsor!](https://github.com/sponsors/marlonrichert) 6 | 7 | ## Requirements 8 | Tested with: 9 | * Zsh 5.8.1 10 | * Git 2.39.1 11 | 12 | ## Installation 13 | Put this in your `.zshrc` file (replacing `~/Repos` with wherever you want to 14 | keep your Zsh plugins and/or Git repos): 15 | ```sh 16 | # Download Znap, if it's not there yet. 17 | [[ -r ~/Repos/znap/znap.zsh ]] || 18 | git clone --depth 1 -- \ 19 | https://github.com/marlonrichert/zsh-snap.git ~/Repos/znap 20 | source ~/Repos/znap/znap.zsh # Start Znap 21 | ``` 22 | Then restart your shell. 23 | 24 | To uninstall, simply remove the above from your `.zshrc` file and remove Znap's repo. 25 | 26 | Znap will automatically manage the repos found in its parent directory. To change the directory it should manage, add 27 | the following to your `.zshrc` file: 28 | ```sh 29 | zstyle ':znap:*' repos-dir 30 | ``` 31 | 32 | ### Updating 33 | To update Znap and all of your plugins/repos simultaneously, run 34 | ```sh 35 | % znap pull 36 | ``` 37 | 38 | Note, that if you told Znap not to manage its parent directory (see the previous section), then it will not update 39 | itself with this. You will have to manually `cd` to its directory and run `git pull`. 40 | 41 | If there are repos that you do not want to be included by `znap pull`, add the following to your `.zshrc` file: 42 | ```sh 43 | zstyle ':znap:pull:*' exclude ... 44 | ``` 45 | 46 | To run `znap pull` on specific repos only, including ones you have set to be excluded, pass them as an arguments: 47 | ```sh 48 | % znap pull ... 49 | ``` 50 | 51 | ## `.zshrc` optimization 52 | Using Znap to optimize your Zsh config can be as simple as this: 53 | ```sh 54 | [[ -r ~/Repos/znap/znap.zsh ]] || 55 | git clone --depth 1 -- https://github.com/marlonrichert/zsh-snap.git ~/Repos/znap 56 | source ~/Repos/znap/znap.zsh 57 | 58 | # `znap prompt` makes your prompt visible in just 15-40ms! 59 | znap prompt sindresorhus/pure 60 | 61 | # `znap source` starts plugins. 62 | znap source marlonrichert/zsh-autocomplete 63 | 64 | # `znap eval` makes evaluating generated command output up to 10 times faster. 65 | znap eval iterm2 'curl -fsSL https://iterm2.com/shell_integration/zsh' 66 | 67 | # `znap function` lets you lazy-load features you don't always need. 68 | znap function _pyenv pyenv "znap eval pyenv 'pyenv init - --no-rehash'" 69 | compctl -K _pyenv pyenv 70 | 71 | # `znap install` adds new commands and completions. 72 | znap install aureliojargas/clitest zsh-users/zsh-completions 73 | ``` 74 | 75 | For more examples of what Znap can do for your dotfiles, please see [the included `.zshrc` 76 | file](.zshrc). 77 | 78 | Additionaly, Znap makes it so that you actually need to have _less_ in your `.zshrc` file, by 79 | automating several tasks for you. 80 | 81 | ### Faster `eval` 82 | Use `znap eval ... ` to cache the output of ``, compile it, and then `source` it (instead of `eval` it): 83 | ```sh 84 | znap eval '' 85 | ``` 86 | This can be up 10 times faster than a regular `eval "$( )"` statement! If you pass a repo as the first 87 | argument, then Znap will `eval` the command output inside the given repo and will invalidate the cache whenever the repo 88 | is update. Otherwise, the cache will be invalidated whenever `` changes. Caches are stored in 89 | `${XDG_CACHE_HOME:-$HOME/.cache}/zsh-snap/eval`. 90 | 91 | ### Automatic `compinit` and `bashcompinit` 92 | Note that the above example does not include any call to 93 | [`complist`](http://zsh.sourceforge.net/Doc/Release/Zsh-Modules.html#The-zsh_002fcomplist-Module), 94 | [`compinit`, or 95 | `bashcompinit`](http://zsh.sourceforge.net/Doc/Release/Completion-System.html#Initialization) in 96 | the `.zshrc` file. That is because Znap will run these for you as needed. 97 | 98 | Znap also regenerates your [comp dump 99 | file](http://zsh.sourceforge.net/Doc/Release/Completion-System.html#Use-of-compinit) automatically whenever you update a 100 | repo, install a repo, or change your `.zshrc` file. 101 | 102 | If necessary, you can let Znap pass arguments to `compinit` as follows: 103 | ```sh 104 | zstyle '*:compinit' arguments -D -i -u -C -w 105 | ``` 106 | 107 | ### Asynchronous compilation 108 | Znap compiles your scripts and functions in the background. This way, your shell will start up even 109 | faster next time! 110 | 111 | Should you not want this feature, you can disable it with 112 | ```sh 113 | zstyle ':znap:*' auto-compile no 114 | ``` 115 | 116 | In any case, you can compile sources manually at any time with 117 | `znap compile [ | ] ...`. 118 | 119 | ## Automatic `git maintenance` 120 | When using `git` 2.31.0 or newer, Znap automatically enables `git maintenance` in each repo that it 121 | manages. This automatically optimizes your repos in the background, so that your `git` and `znap` 122 | commands will run faster. 123 | 124 | To selectively disable this feature, add 125 | ```sh 126 | zstyle ':znap:*:' git-maintenance off 127 | ``` 128 | to your `.zshrc` file. Next time you run `znap pull`, `git maintenance` will then be disabled for 129 | each repo whose name matches ``. 130 | 131 | Use `*` as your [glob 132 | pattern](https://zsh.sourceforge.io/Doc/Release/Expansion.html#Filename-Generation) to opt out of 133 | this feature completely. 134 | 135 | ## Command-Line Usage 136 | Znap also makes life on the command line easier. For a full list of available commands, run 137 | ```sh 138 | % znap 139 | ``` 140 | For more help on a particular command, run 141 | ```sh 142 | % znap help 143 | ``` 144 | Exhaustive tab completion is available, too. For examples of the most important command-line features, see below. 145 | 146 | > Note: 147 | > * The examples below you should run on the command line, not add to your `.zshrc` file! 148 | > * `%` represents the prompt. You shouldn't type that part. 🙂 149 | 150 | ### Check Git status of all repos 151 | To check the Git status of all repos managed by Znap, run 152 | ```sh 153 | % znap status 154 | ``` 155 | 156 | If there are repos that you do not want to be included by `znap status`, add the following to your `.zshrc` file: 157 | ```sh 158 | zstyle ':znap:status:*' exclude ... 159 | ``` 160 | 161 | To run `znap status` on specific repos only, including ones you have set to be excluded, pass them as an arguments: 162 | ```sh 163 | % znap status ... 164 | ``` 165 | 166 | ### Removing repos 167 | To remove one or more repos, use `znap uninstall`: 168 | ```sh 169 | % znap uninstall asdf-vm/asdf ohmyzsh/ohmyzsh 170 | ``` 171 | 172 | ### Install generated functions 173 | Some commands generate output that should be loaded as a function. You can install these generated functions with 174 | `znap fpath ''`. For example: 175 | ```sh 176 | % znap fpath _kubectl 'kubectl completion zsh' 177 | % znap fpath _rustup 'rustup completions zsh' 178 | % znap fpath _cargo 'rustup completions zsh cargo' 179 | ``` 180 | 181 | This will save them to `${XDG_DATA_HOME:-$HOME/.local/share}/zsh/site-functions`. 182 | 183 | ### Named dirs 184 | Znap makes all of the repos it manages available as [named 185 | directories](http://zsh.sourceforge.net/Doc/Release/Expansion.html#Filename-Expansion): 186 | ```sh 187 | % cd ~[zsh-snap] # `cd` to a repo 188 | % ls ~[asdf]/completions # `ls` a subdir in a repo 189 | ``` 190 | 191 | ## Author 192 | © 2020-2021 [Marlon Richert](https://github.com/marlonrichert) 193 | 194 | ## License 195 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. 196 | -------------------------------------------------------------------------------- /completions/_znap: -------------------------------------------------------------------------------- 1 | #compdef znap 2 | 3 | [[ -v _ZNAP_ARGS ]] || 4 | .znap.help &> /dev/null 5 | 6 | local -a subcommands=() 7 | local k; for k in ${(ko)_ZNAP_ARGS}; do 8 | subcommands+=( "$k\:'$_ZNAP_ARGS[$k]'" ) 9 | done 10 | 11 | local -A opt_args 12 | local -a opts=( -qS ' ' ) context line state state_descr 13 | _arguments -A '' -O opts : ":command:(( $subcommands ))" '*:: :->next' 14 | local ret=$? 15 | 16 | [[ $state == next ]] || 17 | return ret 18 | 19 | case $words[1] in 20 | ( clean ) 21 | _arguments : '*:: :->dirs' 22 | ;; 23 | ( clone ) 24 | _arguments : '*:remote repository:_urls' 25 | ;; 26 | ( compdef ) 27 | _arguments : ':function name:' ': :_default' 28 | ;; 29 | ( compile ) 30 | _arguments : ': :_default' 31 | ;; 32 | ( eval ) 33 | _arguments : ': :->repos-caches' ': :_default' 34 | ;; 35 | ( function ) 36 | _arguments : '*:: :_normal' 37 | ;; 38 | ( help ) 39 | _arguments : ":command:(( $subcommands ))" 40 | ;; 41 | ( ignore ) 42 | _arguments : ': :->repos' ':pattern:' 43 | ;; 44 | ( install ) 45 | _arguments : '*:remote repository:_urls' 46 | ;; 47 | ( multi ) 48 | _arguments : ': :_default' 49 | ;; 50 | ( prompt ) 51 | _arguments : ': :->repos' '*:: :->themes' 52 | ;; 53 | ( pull ) 54 | _arguments : "*:: :->repos" 55 | ;; 56 | ( restart ) 57 | false 58 | ;; 59 | ( source ) 60 | _arguments : ": :->repos" '*:: :->repo-files' 61 | ;; 62 | ( status ) 63 | _arguments : "*:: :->repos" 64 | ;; 65 | ( uninstall ) 66 | _arguments : "*:: :->repos" 67 | ;; 68 | esac 69 | ret=$(( ret && ? )) 70 | 71 | case $state in 72 | ( dirs ) 73 | _alternative 'directories:directory:_files -/' 74 | ;; 75 | ( repo-files ) 76 | local repo=~[$words[1]] 77 | [[ -d $repo ]] && 78 | _arguments : "*::file:_files -W $repo -F '*.zwc'" 79 | ;; 80 | ( repos-caches ) 81 | local -aU caches=( ${XDG_CACHE_HOME:-$HOME/.cache}/zsh-snap/eval/*.zsh(D-.) ) 82 | local -a repos=() opts=( -qS ' ' ) 83 | local gitdir= 84 | ..znap.repos 85 | _alternative -O opts "repositories:repo:($repos)" "cache-files:cache:(${caches[@]:t:r})" 86 | ;; 87 | ( repos ) 88 | local -a repos=() opts=( -qS ' ' ) 89 | local gitdir= 90 | ..znap.repos 91 | _alternative -O opts "repositories:repo:($repos)" 92 | ;; 93 | ( themes ) 94 | local repo=~[$words[1]] 95 | if [[ -d $repo ]]; then 96 | local -aU themes=( $repo/**.zsh-theme(-.) ) 97 | themes=( ${themes[@]:t:r} ) 98 | _arguments : "::prompt theme:($themes)" 99 | fi 100 | ;; 101 | esac 102 | 103 | return $(( ret && ? )) 104 | -------------------------------------------------------------------------------- /functions/..znap.auto-compile: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | add-zsh-hook -d preexec ..znap.auto-compile 3 | 4 | local -a include=() match=() mbegin=() mend=() 5 | local exclude= 6 | zstyle -s :znap: auto-compile-ignore exclude \| && 7 | exclude="($exclude)" 8 | include=( "${${(@vu)functions_source}[@]:#$~exclude}" ) 9 | 10 | (( $#include )) && 11 | ( .znap.compile &> /dev/null ) &| 12 | 13 | true 14 | -------------------------------------------------------------------------------- /functions/..znap.compinit-hook: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | autoload -Uz add-zsh-hook 3 | 4 | add-zsh-hook -d precmd ..znap.compinit-hook 5 | unfunction ..znap.compinit-hook 6 | 7 | zmodload zsh/complist 8 | if ! [[ -v _comp_setup && -f $_comp_dumpfile ]]; then 9 | unfunction compdef compinit 2>/dev/null 10 | autoload -Uz compinit 11 | bindkey() {:} 12 | { 13 | local -a compargs=() 14 | zstyle -a ':autocomplete::compinit' arguments compargs 15 | compinit -d "$_comp_dumpfile" "$compargs[@]" 16 | } always { 17 | unfunction bindkey 18 | } 19 | [[ -r $_comp_dumpfile ]] && 20 | ( 21 | emulate -L zsh 22 | .znap.compile $_comp_dumpfile 23 | ) &| 24 | fi 25 | 26 | private _P__args= 27 | for _P__args in "$_znap_compdef[@]"; do 28 | eval "compdef $_P__args" 29 | done 30 | 31 | unset _znap_compdef 32 | 33 | compinit() {:} 34 | 35 | true 36 | -------------------------------------------------------------------------------- /functions/..znap.dirname: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # Resolve ~[$1] 4 | ..znap.dirname.n() { 5 | emulate -L zsh; setopt extendedglob 6 | private -a dirs=() 7 | 8 | local gitdir='' 9 | ..znap.repos-dir 10 | 11 | case $1 in 12 | ( ${1:t} ) 13 | dirs=( $gitdir/{*/,}$1/.git(Non-/:h) ) 14 | ;; 15 | ( ${1:t2} ) 16 | dirs=( $gitdir/$1/.git(Non-/:h) ) 17 | ;; 18 | esac 19 | 20 | (( $#dirs[@] == 1 )) || 21 | return 22 | 23 | reply=( $dirs ) 24 | } 25 | 26 | # ~Abbreviate $1 27 | ..znap.dirname.d() { 28 | emulate -L zsh; setopt extendedglob 29 | 30 | local gitdir='' 31 | ..znap.repos-dir 32 | 33 | [[ $1 != $gitdir/* ]] && 34 | return 1 35 | 36 | private subdirs=${1#$gitdir} 37 | private repo=$gitdir 38 | private next= 39 | while [[ ! -d $repo/.git && -n $subdirs ]]; do 40 | next=${subdirs:h2} 41 | repo+=$next 42 | subdirs=${subdirs#$next} 43 | done 44 | private name=${repo#$gitdir} 45 | 46 | [[ -d $gitdir/$name/.git ]] || 47 | return 1 48 | 49 | private -a samenamed=( $gitdir/{,*/}${name:t}/.git(N-/) ) 50 | (( $#samenamed[@] == 1 )) && 51 | name=${name:t} 52 | reply=( "$name" $#repo ) 53 | } 54 | 55 | # Complete ~[...] 56 | ..znap.dirname.c() { 57 | if [[ -n $ISUFFIX ]]; then 58 | # Workaround: In Zsh <= 5.9.0, _setup doesn't get called when completing inside a subscript. 59 | _setup default 60 | else 61 | local suf=-S] 62 | fi 63 | 64 | local -a expl=() 65 | _description named-directories expl 'named directory' 66 | 67 | local -a repos=() 68 | local gitdir= 69 | ..znap.repos 70 | 71 | builtin compadd "$expl[@]" $suf -a -- repos 72 | } 73 | 74 | ..znap.dirname() { 75 | ..znap.dirname.$1 "$2" 76 | } 77 | 78 | ..znap.dirname "$@" 79 | -------------------------------------------------------------------------------- /functions/..znap.error: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | emulate -L zsh; setopt $_znap_opts 3 | 4 | print -Pn '%F{red}' 5 | print -rn -- "${@//(error|fatal): /}" 6 | print -P '%f' 7 | false 8 | -------------------------------------------------------------------------------- /functions/..znap.fetch: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | emulate -L zsh; setopt $_znap_opts 3 | 4 | git fetch --prune &> /dev/null 5 | 6 | if ! upstream=( ${(s:/:)$( git rev-parse -q --abbrev-ref @{u} 2> /dev/null )} ); then 7 | git branch -q --unset-upstream 2> /dev/null 8 | return 1 9 | fi 10 | 11 | if ! git ls-remote -q --exit-code $upstream[@] > /dev/null && [[ $upstream[2] == master ]]; then 12 | upstream=( --set-upstream $upstream[1] main ) 13 | fi 14 | 15 | private msg= 16 | if ! msg="$( git fetch -t $upstream[@] 2>&1 > /dev/null )"; then 17 | ..znap.error "$msg" 18 | fi 19 | -------------------------------------------------------------------------------- /functions/..znap.repos: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | ..znap.repos-dir 4 | 5 | if (( $# > 1 )); then 6 | shift 7 | repos=( $gitdir/${^@}(ND-/) ) 8 | 9 | else 10 | repos=( $gitdir/*{,/*}/.git(ND-/:h) ) 11 | 12 | local -a exclude=() 13 | if zstyle -a ":znap:$1:" exclude exclude; then 14 | repos=( ${repos[@]:#$gitdir/(${(~j:|:)~exclude})} ) 15 | fi 16 | fi 17 | repos=( ${repos[@]#$gitdir/} ) 18 | -------------------------------------------------------------------------------- /functions/..znap.repos-dir: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | zstyle -s :znap: repos-dir gitdir || 4 | gitdir=${${(%):-%x}:P:h:h:a:h} 5 | -------------------------------------------------------------------------------- /functions/..znap.tput: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | zmodload -F zsh/terminfo b:echoti p:terminfo 3 | 4 | [[ -v terminfo[$1] ]] && 5 | echoti $@ 6 | -------------------------------------------------------------------------------- /functions/.znap.clean: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | # remove outdated .zwc binaries 3 | # args: ... 4 | emulate -L zsh; setopt $_znap_opts 5 | zmodload -Fa zsh/files b:zf_rm 6 | 7 | private -a files=( $^@/**.zwc(-.) ) 8 | (( $# > 0 )) || 9 | files=( $PWD/**.zwc(-.) $^fpath/*.zwc(-.) ) 10 | private -i ret=0 11 | private bin= src= 12 | for bin in $files; do 13 | src=${bin%.zwc} 14 | if [[ -w $bin:h && ( ! -e $src || $bin -ot $src ) ]]; then 15 | zf_rm -f $bin 2>/dev/null || 16 | ret=1 17 | fi 18 | done 19 | return ret 20 | -------------------------------------------------------------------------------- /functions/.znap.clone: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | # download repos in parallel 3 | # args: ( / | ) ... 4 | emulate -L zsh; setopt $_znap_opts 5 | 6 | if (( $# < 1 )); then 7 | print -u2 "znap clone: not enough arguments" 8 | .znap.help clone 9 | return $(( sysexits[(i)USAGE] + 63 )) 10 | fi 11 | 12 | local -a match=() mbegin=() mend=() 13 | private repo='' 14 | for repo in $@; do 15 | if [[ $repo != ([^/]##/[^/]##|*(@|://)*/*.git) ]] && ! [[ -d ${repo#file://} && $repo == */.git ]]; then 16 | print -u2 "znap clone: invalid argument '$repo'" 17 | .znap.help clone 18 | return $(( sysexits[(i)USAGE] + 63 )) 19 | fi 20 | done 21 | 22 | local server='' 23 | zstyle -s :znap:clone: default-server server || 24 | server='https://github.com/' 25 | .znap.multi ".znap.clone.task $server "$^@ || 26 | return 'sysexits[(i)IOERR] + 63' 27 | -------------------------------------------------------------------------------- /functions/.znap.clone.task: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | emulate -L zsh 3 | 4 | setopt $_znap_opts 5 | zmodload -Fa zsh/files b:zf_mkdir b:zf_mv b:zf_rm 6 | autoload -Uz is-at-least 7 | 8 | private server=$1 url=$2 9 | [[ $url != (*/*/*|*:*|*.git(|/)) ]] && 10 | url=$server$url.git 11 | 12 | local gitdir='' 13 | ..znap.repos-dir 14 | 15 | local -P name=${${${url##git@*.*:}%%(|/).git}:t2} 16 | private new= old=$gitdir/${name:t} 17 | 18 | if [[ -d ${old}/.git ]]; then 19 | if new=$gitdir/${${(M)$( 20 | git -C $old remote get-url origin 2> /dev/null 21 | )%%[^/:]##/[^/:]##}%.git}; then 22 | if [[ -d $new ]]; then 23 | zf_rm -rf $old 24 | else 25 | zf_mkdir -p $new:h 26 | zf_mv $old $new 27 | fi 28 | fi 29 | fi 30 | 31 | private repo=$gitdir/$name 32 | [[ -d $repo ]] && 33 | return 34 | 35 | zf_mkdir -p $repo 36 | if git -C $gitdir clone --depth 1 --recurse-submodules --shallow-submodules $url $repo; then 37 | [[ -v _comp_dumpfile && -f $_comp_dumpfile ]] && 38 | zf_rm -f $_comp_dumpfile 39 | .znap.ignore "$name" '*.zwc' # Add `*.zwc` to repo's local ignore list. 40 | .znap.compile $repo &> /dev/null &| 41 | true 42 | fi 43 | -------------------------------------------------------------------------------- /functions/.znap.compile: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | # compile zsh scripts and functions 3 | # args: [ ( | ) ... ] 4 | emulate -L zsh; setopt $_znap_opts 5 | zmodload -F zsh/files b:zf_rm 6 | zmodload -F zsh/parameter p:functions_source 7 | 8 | private -aU dirs=() files=() bugs=() 9 | if (( $# )); then 10 | bugs=( $^dirs/**?.zwc.zwc ) 11 | (( $#bugs[@] )) && 12 | zf_rm -- $bugs[@] 13 | dirs=( $^@(N-/:a) $^@/**(N-/:a) ) 14 | files=( $^@(N-.:a) $^dirs/*~*.zwc~*(#i)test*(ND-.) ) 15 | else 16 | files=( ${(@vu)functions_source} ) 17 | fi 18 | files=( ${(@n)files} ) 19 | 20 | private -i ret=0 21 | private f= func= opt= 22 | while (( $#files[@] )); do 23 | { 24 | f=$files[1] 25 | shift files 26 | if ! [[ -w $f:h ]]; then 27 | print -Pru2 -- "${(D)f:h} %F{red}not writable%f" 28 | files=( ${files:#${f:h}/[^/]##} ) 29 | continue 30 | fi 31 | 32 | if [[ $f.zwc -ot $f ]]; then 33 | if ! zf_rm -f -- $f.zwc; then 34 | print -Pru2 -- "${(D)f}.zwc %F{red}not writable%f" 35 | continue 36 | fi 37 | fi 38 | 39 | if ! [[ -r $f ]]; then 40 | print -Pru2 -- "${(D)f} %F{red}not readable%f" 41 | continue 42 | fi 43 | 44 | if ! [[ -e $f.zwc ]]; then 45 | func=${(k)functions_source[(r)$f:a]} 46 | if [[ -n $func && $func == $f:t ]] || (( fpath[(i)$f:a:h] )); then 47 | opt=zM 48 | else 49 | opt=R 50 | fi 51 | 52 | if emulate zsh -c "zcompile -U$opt -- ${(q)f}"; then 53 | if [[ $funcstack[2] == znap ]] ; then 54 | print -Pr -- "${(D)f:h}/%F{green}$f:t.zwc%f" 55 | fi 56 | else 57 | ret=1 58 | zf_rm -f -- $f.zwc 59 | fi 60 | fi 61 | } always { 62 | if [[ -f $f.zwc ]]; then 63 | if zmodload -F zsh/files b:zf_chmod &> /dev/null; then 64 | builtin zf_chmod 640 $f.zwc 65 | else 66 | command chmod 640 $f.zwc 67 | fi 68 | fi 69 | } 70 | done 71 | return ret 72 | -------------------------------------------------------------------------------- /functions/.znap.eval: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | # cache & eval output of command 3 | # args: ( | ) 4 | zmodload -F zsh/files b:zf_mkdir 5 | 6 | # TODO: deprecate this and provide `znap cache ` instead. 7 | 8 | if (( $# < 2 )); then 9 | print -u2 'znap eval: not enough arguments' 10 | .znap.help eval 11 | return $(( sysexits[(i)USAGE] + 63 )) 12 | fi 13 | 14 | local gitdir='' 15 | ..znap.repos-dir 16 | 17 | if [[ $1 == */* ]]; then 18 | .znap.clone $1 || 19 | return 20 | private _P__repo=~[$1] 21 | private _P__name=${_P__repo#$gitdir/} 22 | else 23 | local _P__name=$1 _P__repo= 24 | fi 25 | private _P__cmd=$2 26 | 27 | private _P__cache_dir= 28 | private _P__cache_file=$XDG_CACHE_HOME/zsh-snap/eval/${_P__name}.zsh 29 | zf_mkdir -pm 0700 $_P__cache_file:h 30 | 31 | [[ -r $_P__cache_file ]] || 32 | print -r "znap eval: generating cache for ${(q+)_P__cmd}" 33 | 34 | private _P__line= _P__header="#${(q+)_P__cmd}" _P__index=$_P__repo/.git/index 35 | ( 36 | [[ -r $_P__cache_file ]] && 37 | IFS='' read -r _P__line < $_P__cache_file 38 | 39 | if [[ $_P__line != $_P__header ]] || 40 | [[ -n $_P__repo && -f $_P__index && $_P__index -nt $_P__cache_file ]]; then 41 | [[ -d $_P__repo ]] && 42 | builtin cd -q -- $_P__repo 43 | print -r -- "$_P__header" >! $_P__cache_file 44 | eval "$_P__cmd" >>! $_P__cache_file 45 | .znap.compile $_P__cache_file 46 | fi 47 | ) 48 | 49 | # Wrap in a named function for profiling purposes. 50 | .znap.eval:${_P__cache_file:t}() { 51 | . "$1" 52 | } 53 | { 54 | .znap.eval:${_P__cache_file:t} "$_P__cache_file" 55 | } always { 56 | TRY_BLOCK_ERROR= 57 | unfunction .znap.eval:${_P__cache_file:t} 58 | } 59 | -------------------------------------------------------------------------------- /functions/.znap.fpath: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | # install command output as a function 3 | # args: 4 | emulate -L zsh; setopt $_znap_opts 5 | zmodload -F zsh/files b:zf_mkdir 6 | 7 | private _P__name=$1 _P__generator=$2 dir=${XDG_DATA_HOME:-$HOME/.local/share}/zsh/site-functions 8 | shift 2 9 | 10 | zf_mkdir -pm 0700 $dir 11 | 12 | private _P__file=$dir/$_P__name 13 | eval "$_P__generator" >| $_P__file || 14 | return 15 | 16 | print -r -- "#$_P__generator" >>| $_P__file 17 | .znap.compile $_P__file 18 | 19 | [[ -f $_comp_dumpfile ]] && 20 | zf_rm -f $_comp_dumpfile 21 | 22 | true 23 | -------------------------------------------------------------------------------- /functions/.znap.function: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | # create a set of lazy-loading functions 3 | # args: ... 4 | emulate -L zsh; setopt $_znap_opts 5 | 6 | private body=$@[-1] 7 | shift -p 1 8 | 9 | eval "$* () { 10 | unfunction $* 11 | $body 12 | \${(%):-%N} "\$@" 13 | }" 14 | 15 | true 16 | -------------------------------------------------------------------------------- /functions/.znap.help: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | # print help text for command 3 | # args: [ ] 4 | zmodload -F zsh/mapfile p:mapfile 5 | zmodload -F zsh/parameter p:functions 6 | 7 | .znap.help() { 8 | emulate -LR zsh; setopt $_znap_opts 9 | 10 | typeset -gHA _ZNAP_ARGS=() 11 | private f= 12 | for f in ${${(%):-%x}:a:h}/.znap.[^.]##; do 13 | _ZNAP_ARGS[${${f:t}#.znap.}]=${${(f)mapfile[$f]}[2]#'# '} 14 | done 15 | 16 | private -i fd=1 17 | if (( $# > 0 )); then 18 | if [[ -v functions[.znap.$1] ]]; then 19 | private -a funcfile=( ${(f)"$( < ${${(%):-%x}:a:h}/.znap.$1 )"} ) 20 | print -lP -- "Usage: %F{10}znap%f $1 ${funcfile[3]#'# args: '}" 21 | 22 | private help="${funcfile[2]#'# '}" 23 | private first=${${=help}[1]} 24 | print -l "${(C)first} ${help#$first }." 25 | return 26 | fi 27 | fd=2 28 | print -u$fd $@ 29 | fi 30 | print -Pu$fd -- \ 31 | 'Usage: %F{10}znap%f [ ... ] 32 | 33 | Commands:' 34 | private -i MBEGIN= MEND= 35 | private MATCH= 36 | print -l -u$fd "${(ko@)_ZNAP_ARGS/(#m)*/ ${(r:9:)MATCH} $_ZNAP_ARGS[$MATCH]}" 37 | print -Pu$fd -- ' 38 | For more info on a command, type `%F{10}znap%f help `. 39 | ' 40 | true 41 | } 42 | 43 | .znap.help "$@" 44 | -------------------------------------------------------------------------------- /functions/.znap.ignore: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | # add local exclude patterns to repo 3 | # args: ... 4 | emulate -L zsh; setopt $_znap_opts 5 | 6 | if ! [[ -v 1 ]]; then 7 | .znap.help ignore 8 | return 1 9 | fi 10 | 11 | local gitdir='' 12 | ..znap.repos-dir 13 | 14 | private repo=~[$1] 15 | shift 16 | 17 | private -aU lines=() files=( $repo/${GIT_DIR:-.git}/**/info/exclude(.) ) 18 | private file= 19 | for file in $files[@]; do 20 | [[ -r $file ]] && 21 | lines=( ${(f)"$(< $file)"} ) 22 | lines+=( $@ ) 23 | print -r "${(F)lines}" >! $file 2>/dev/null 24 | done 25 | (( $#files )) 26 | -------------------------------------------------------------------------------- /functions/.znap.install: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | # install executables & completion functions 3 | # args: ... 4 | emulate -L zsh; setopt $_znap_opts 5 | zmodload -Fa zsh/files b:zf_ln b:zf_mkdir 6 | 7 | if (( $# < 1 )); then 8 | print -u2 'znap install: not enough arguments' 9 | .znap.help install 10 | return $(( sysexits[(i)USAGE] + 63 )) 11 | fi 12 | 13 | .znap.clone $@ || 14 | return 15 | 16 | local REPLY= 17 | private -aU funcfiles=() 18 | private bindir=~/.local/bin 19 | private d='' link='' pat='[[:alnum:]][[:alnum:]_-]#[[:alnum:]]' 20 | 21 | local -a repos=() 22 | local gitdir= 23 | ..znap.repos install $@ 24 | 25 | private repo= 26 | for repo in $repos; do 27 | repo=~[$repo] 28 | 29 | path=( 30 | $repo/{,bin/}$~pat(N*:h) 31 | $path[@] 32 | ) 33 | fpath=( 34 | $repo/{,{[Cc]ompletions,[Ss]rc,zsh}/($~pat/)#}_$~pat(N^/:h) 35 | $fpath 36 | ) 37 | 38 | funcfiles=( $repo/{,[Ff]unctions}/($~pat/)#}$~pat(N^/) ) 39 | (( $#funcfiles )) && 40 | autoload -Uz $funcfiles 41 | done 42 | -------------------------------------------------------------------------------- /functions/.znap.multi: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | # run tasks in parallel 3 | # args: 4 | 5 | local fd= 6 | private -a _P__fds=() 7 | private _P__cmd= 8 | { 9 | for _P__cmd in $@; do 10 | exec {fd}< <( eval "$_P__cmd" ) 11 | _P__fds+=( $fd ) 12 | done 13 | } always { 14 | for fd in $_P__fds[@]; do 15 | <&$fd 16 | exec {fd}<&- 17 | done 18 | } 19 | -------------------------------------------------------------------------------- /functions/.znap.prompt: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | # instant prompt from repo 3 | # args: [ ] 4 | zmodload -F zsh/parameter p:functions 5 | zmodload -F zsh/zleparameter p:widgets 6 | autoload -Uz add-zsh-hook add-zle-hook-widget promptinit 7 | 8 | .znap.prompt() { 9 | local theme= 10 | 11 | if (( $# )); then 12 | if [[ $1 == */* ]]; then 13 | .znap.clone $1 || 14 | return 15 | fi 16 | 17 | local repo=$1 theme=${2:-${${${1:t}/(.zsh|zsh-)/}//-(prompt|theme)/}} 18 | .znap.prompt.load 19 | fi 20 | 21 | ..znap.tput civis # Make cursor invisible. 22 | ..znap.tput sc # Save cursor position. 23 | add-zsh-hook precmd .znap.prompt.precmd 24 | 25 | # Temporarily redirect standard error to file. 26 | mkdir -pm 0700 ${XDG_DATA_HOME:-$HOME/.local/share}/zsh-snap/err 27 | typeset -gHi _znap_prompt_fd=-1 28 | typeset -gH _znap_prompt_tmp==( <<< '' ) 29 | exec {_znap_prompt_fd}>&2 # Save a copy of std err. 30 | exec 2> $_znap_prompt_tmp # Redirect std err to tmp file. 31 | 32 | promptinit 33 | if [[ -n $theme ]]; then 34 | prompt_themes+=( $theme ) 35 | prompt $theme 36 | ..znap.tput sc # Save cursor position (overwriting the previous save). 37 | 38 | private _P__precmd=prompt_${theme}_precmd 39 | if [[ -n $functions[$_P__precmd] ]]; then 40 | $_P__precmd 41 | 42 | # Ensure $_P__precmd doesn't get called twice before the first prompt. 43 | functions[:znap:$_P__precmd]=$functions[$_P__precmd] 44 | eval "$_P__precmd() { 45 | functions[$_P__precmd]=\$functions[:znap:$_P__precmd] 46 | unfunction :znap:$_P__precmd 47 | }" 48 | fi 49 | fi 50 | 51 | setopt NO_promptsp 52 | print -nPr -- "%$COLUMNS<<${(l:$COLUMNS:: :)}$RPS1" 53 | print -nPr -- $'\r'"$PS1" 54 | ..znap.tput cnorm # Make cursor normal (visible). 55 | true 56 | } 57 | 58 | .znap.prompt.precmd() { 59 | add-zsh-hook -d precmd ${(%):-%N} 60 | 61 | ..znap.tput civis # Make cursor invisible. 62 | ..znap.tput rc # Restore saved cursor position. 63 | 64 | # If there are error messages, print them above the new prompt. 65 | exec 2>&$_znap_prompt_fd # Restore std error. 66 | private err="$( < $_znap_prompt_tmp )" 67 | [[ -n $err ]] && 68 | print -u2 -- "$err" 69 | unset _znap_prompt_fd _znap_prompt_tmp 70 | 71 | # Workaround for https://www.zsh.org/mla/workers/2021/msg01310.html 72 | local -a match=() mbegin=() mend=() 73 | if zstyle -L zle-hook types > /dev/null; then 74 | private hook= 75 | for hook in isearch-{exit,update} line-{pre-redraw,init,finish} history-line-set keymap-select 76 | do 77 | [[ $widgets[zle-$hook] == user:_zsh_highlight_widget_orig-s<->.<->-r<->-zle-$hook ]] && 78 | zle -N zle-$hook azhw:zle-$hook 79 | done 80 | fi 81 | 82 | add-zle-hook-widget line-init .znap.prompt.line-init 83 | 84 | return 0 85 | } 86 | 87 | .znap.prompt.line-init() { 88 | add-zle-hook-widget -d line-init ${(%):-%N} 89 | 90 | ..znap.tput cnorm # Make cursor normal (visible). 91 | [[ -v prompt_opts ]] || 92 | setopt promptsp 93 | 94 | return 0 95 | } 96 | 97 | .znap.prompt "$@" 98 | -------------------------------------------------------------------------------- /functions/.znap.prompt.load: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | zmodload -F zsh/parameter p:functions 3 | 4 | .znap.prompt.load() { 5 | emulate -L zsh; setopt $_znap_opts 6 | 7 | private setup=prompt_${theme}_setup 8 | 9 | [[ -v functions[$setup] ]] && 10 | return 11 | 12 | autoload +X -Uz $setup &>/dev/null && 13 | return 14 | 15 | unfunction $setup 16 | 17 | local repos=() 18 | local gitdir= 19 | ..znap.repos prompt $repo 20 | private dir=$gitdir/$repos[1] 21 | 22 | private -a file=( $dir/{,[Ff]unctions/}$setup(-.Y1) ) 23 | if [[ -n $file ]]; then 24 | 25 | if autoload +X -Uz $file &>/dev/null; then 26 | fpath=( $file:h $fpath[@] ) 27 | return 28 | fi 29 | 30 | unfunction $file 31 | fi 32 | 33 | file=( $dir/*$theme*.zsh*~*/*test*(-.Y1) ) 34 | 35 | if [[ -n $file ]]; then 36 | eval "$setup() { 37 | unsetopt localoptions prompt{bang,cr,percent,sp,subst} 38 | PS1='%# ' 39 | . $file[1] 40 | private opt= 41 | for opt in bang cr percent sp subst; do 42 | [[ -o prompt\$opt ]] && 43 | prompt_opts+=( \$opt ) 44 | done 45 | setopt localoptions 46 | }" 47 | return 48 | fi 49 | 50 | $setup() { 51 | prompt_opts=( bang cr percent sp subst ) 52 | } 53 | } 54 | 55 | .znap.prompt.load "$@" 56 | -------------------------------------------------------------------------------- /functions/.znap.pull: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | # update repos in parallel 3 | # args: [ ... ] 4 | zmodload -Fa zsh/files b:zf_rm 5 | autoload +X -Uz is-at-least 6 | 7 | .znap.pull() { 8 | emulate -L zsh; setopt $_znap_opts 9 | 10 | local gitdir='' 11 | ..znap.repos-dir 12 | 13 | private link='' bindir=~/.local/bin datadir=${XDG_DATA_HOME:-$HOME/.local/share}/zsh/site-functions 14 | for link in {$bindir,$datadir}/*(N@); do 15 | [[ $( readlink -f $link ) == $gitdir/* ]] && 16 | zf_rm $link 17 | done 18 | 19 | ( 20 | builtin cd -q $gitdir 21 | 22 | local -a repos=() 23 | ..znap.repos pull "$@" 24 | 25 | private r= 26 | for r in $repos; do 27 | git -C $r config --local submodule.fetchJobs 0 28 | done 29 | 30 | if is-at-least 2.31 ${${=$( git --version )}[3]}; then 31 | local -a match=() mbegin=() mend=() 32 | for r in $repos; do 33 | if zstyle -T :znap:pull:${r:t} git-maintenance; then 34 | git -C $r maintenance start &> /dev/null 35 | else 36 | git -C $r maintenance unregister &> /dev/null 37 | fi 38 | done 39 | fi 40 | 41 | .znap.multi '.znap.pull.task '${(i)^repos} 42 | ) 43 | } 44 | 45 | .znap.pull.task() { 46 | emulate -L zsh; setopt $_znap_opts 47 | 48 | print -nr -- "${$( eval "ls -d $1" )%[/@]} " 49 | builtin cd -q $1 50 | 51 | local -a upstream=() 52 | if ! ..znap.fetch; then 53 | ..znap.error no upstream 54 | return 1 55 | fi 56 | 57 | .znap.ignore $1:t '*.zwc' # Add `*.zwc` to repo's local ignore list. 58 | 59 | private -a count=() 60 | if count=( ${="$( git rev-list --count --left-right @{u}...@ 2> /dev/null )"} ); then 61 | if (( count[2] )); then 62 | print -P "%F{yellow}ahead $count[2]%f" 63 | return 64 | elif (( count[1] )); then 65 | print -n 'updating... ' 66 | 67 | local msg= 68 | if ! msg=$( git pull -q -r --recurse-submodules $upstream[@] 2>&1 > /dev/null ); then 69 | ..znap.error $=msg 70 | return 71 | else 72 | private cache_dir=$XDG_CACHE_HOME/zsh-snap 73 | zf_rm -f -- $_comp_dumpfile $cache_dir/fpath/_$1:t $cache_dir/eval/${1:t}.zsh 74 | .znap.compile $1 &> /dev/null &| 75 | print -P '%F{green}done%f' 76 | fi 77 | else 78 | print -n '\r' 79 | ..znap.tput el # Clear to end of line. 80 | fi 81 | fi 82 | } 83 | 84 | .znap.pull "$@" 85 | -------------------------------------------------------------------------------- /functions/.znap.restart: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | # (DEPRECATED) validate dotfiles & safely restart Zsh 3 | # args: -- 4 | 5 | print -Pr -- '%F{9}Note: This command will be removed in a future version of Znap.%f' 6 | print 7 | print 'Validating dotfiles...' 8 | 9 | # These need to be two separate statements or we won't get the right `$?`. 10 | private msg= 11 | msg="$( exec zsh -ilc exit 2>&1 > /dev/null )" 12 | 13 | private err=$? 14 | 15 | if [[ err -ne 0 || -n $msg ]]; then 16 | print -nu2 'Validation failed with ' 17 | if [[ -z $msg ]]; then 18 | print -Pu2 "exit status %F{red}$err%f." 19 | else 20 | print -nPu2 'the following errors:\n%F{red}' 21 | print -nru2 -- "$msg" 22 | print -P '%f' 23 | fi 24 | print -u2 'Restart aborted.' 25 | return err 26 | else 27 | print 'Restarting Zsh...' 28 | exec zsh -l 29 | fi 30 | -------------------------------------------------------------------------------- /functions/.znap.source: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | # load plugins 3 | # args: [ | ] ... 4 | zmodload -Fa zsh/files b:zf_rm 5 | 6 | if ! (( $# )); then 7 | print -u2 'znap source: not enough arguments' 8 | .znap.help source 9 | return $(( sysexits[(i)USAGE] + 63 )) 10 | fi 11 | 12 | if [[ $1 == */* ]]; then 13 | .znap.clone $1 || 14 | return 15 | fi 16 | private _P__repo=~[$1] 17 | 18 | private _P__state=$XDG_STATE_HOME/znap 19 | private _P__file='' _P__src='' 20 | shift 21 | 22 | for _P__src in ${@:-''}; do 23 | [[ -h $XDG_STATE_HOME/${_P__repo:t2}${_P__src:+.}${_P__src//\//.} ]] && 24 | zf_rm -fr $XDG_STATE_HOME/${_P__repo:t2:h} 25 | 26 | _P__file=$_P__state/${_P__repo:t2}${_P__src:+.}${_P__src//\//.} 27 | if ! [[ -r $_P__file:P ]]; then 28 | .znap.source.link "$_P__repo" "$_P__src" "$_P__file" || 29 | return 30 | fi 31 | 32 | # Wrap in a named function for profiling purposes. 33 | .znap.source:${_P__file:t}() { 34 | . "$1" 35 | } 36 | { 37 | .znap.source:${_P__file:t} "$_P__file:P" 38 | } always { 39 | TRY_BLOCK_ERROR= 40 | unfunction .znap.source:${_P__file:t} 41 | } 42 | done 43 | -------------------------------------------------------------------------------- /functions/.znap.source.link: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | emulate -L zsh 3 | setopt localoptions extendedglob NO_nomatch NO_nullglob 4 | zmodload -F zsh/files b:zf_ln b:zf_mkdir 5 | 6 | private -aU tmp=( $1${2:+/$2} ) ext=( '.plugin.zsh' '(|.*).zsh[^.]#' '.sh' ) 7 | 8 | if [[ -d $tmp[1] ]]; then 9 | tmp=( $tmp[1]/{*${(~j:?:)${=${tmp[1]:t}//[-_]/ }:#zsh}*,init,*}${^~ext}(NY1-.) ) 10 | elif ! [[ -r $tmp[1] ]]; then 11 | tmp=( ${tmp[1]}${^~ext}(NY1-.) ) 12 | fi 13 | 14 | if ! (( $#tmp )); then 15 | print -ru2 -- "znap source: file not found: $2" 16 | return $(( sysexits[(i)NOINPUT] + 63 )) 17 | fi 18 | 19 | zf_mkdir -pm 0700 "$3:h" 20 | zf_ln -fhs "$tmp[1]" "$3" || 21 | return 22 | 23 | .znap.compile $tmp[1] 24 | return 0 # It doesn't matter if the compilation fails. 25 | -------------------------------------------------------------------------------- /functions/.znap.status: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | # fetch updates & show git status 3 | # args: [ ... ] 4 | autoload +X -Uz is-at-least 5 | 6 | .znap.status() { 7 | emulate -L zsh; setopt $_znap_opts 8 | 9 | { 10 | local -a repos=() 11 | local gitdir='' 12 | ..znap.repos status "$@" 13 | 14 | pushd -q $gitdir 15 | 16 | .znap.multi '.znap.status.task '${(i)^repos} 17 | } always { 18 | popd -q 19 | } 20 | } 21 | 22 | .znap.status.task() { 23 | emulate -L zsh; setopt $_znap_opts 24 | 25 | print -nr -- "${$( eval "ls -d $1" )%[/@]} " 26 | builtin cd -q $1 27 | 28 | local -a upstream=() 29 | private err="$( ..znap.fetch )" 30 | 31 | private rev= log="$( git log -n1 --decorate --oneline )" 32 | if [[ $log == *'tag: '* ]]; then 33 | private -a chunks=( ${(s.tag: .)log} ) 34 | rev="${chunks[1]##*[ (]}${chunks[2]%%[,)]*}"$'\e[m' 35 | fi 36 | 37 | .znap.ignore $1:t '*.zwc' # Add `*.zwc` to repo's local ignore list. 38 | 39 | private -aU lines=( "${(f)$( git status -sb )}" ) 40 | private branch=${${${lines[1]#'## '}%...*}:#*(main|master)*} 41 | 42 | private -i MBEGIN MEND 43 | private MATCH 44 | private -aU changes=( ${(0)${${(F)lines[2,-1]}// [[:print:]]#($'\n'|)}//$'m\C-['/$'m\0\C-['} ) 45 | changes=( ${(@)changes[@]//(#m)('??'|'!!')/$MATCH[1]} ) 46 | 47 | private ahead_behind= 48 | if [[ -z $err ]]; then 49 | ahead_behind=${${(M)lines[1]% \[*\]}# } 50 | [[ -z $ahead_behind && -z $changes ]] && 51 | ahead_behind=$'\e[32m✓\e[39m' 52 | fi 53 | 54 | print -r -- $rev ${err:-$branch} $ahead_behind $changes 55 | } 56 | 57 | .znap.status "$@" 58 | -------------------------------------------------------------------------------- /functions/.znap.uninstall: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | # remove executables & completion functions 3 | # args: ... 4 | emulate -L zsh; setopt $_znap_opts 5 | zmodload -F zsh/parameter p:commands 6 | autoload -Uz is-at-least 7 | 8 | # Try if we have a safer and faster alternative to `rm`. 9 | if [[ $VENDOR == apple ]]; then 10 | private rm=trash 11 | trash() { 12 | local -aU items=( $^@(N) ) 13 | items=( '(POSIX file "'${^items[@]:a}'")' ) 14 | osascript -e 'tell application "Finder" to delete every item of {'${(j:, :)items}'}' \ 15 | > /dev/null 16 | } 17 | elif command -v gio > /dev/null; then 18 | # gio is available for macOS, but gio trash does NOT work correctly there. 19 | private rm='gio trash' 20 | else 21 | zmodload -F zsh/files b:zf_rm 22 | private rm='zf_rm -frs' 23 | fi 24 | 25 | 26 | if (( $# < 1 )); then 27 | print -u2 'znap uninstall: not enough arguments' 28 | .znap.help uninstall 29 | return $(( sysexits[(i)USAGE] + 63 )) 30 | fi 31 | 32 | private -a files=() 33 | private -i ret=0 34 | private dir= file repo= 35 | for repo in $@; do 36 | dir=~[$repo] 37 | is-at-least 2.31 ${${=$( git --version )}[3]} && 38 | git -C $repo maintenance unregister &> /dev/null 39 | print -Pr "Removing %F{4}${dir}%f" 40 | $=rm $dir 41 | print -P "%F{12}${repo}%f uninstalled." 42 | done 43 | hash -f 44 | [[ -f $_comp_dumpfile ]] && 45 | $=rm $_comp_dumpfile 46 | return ret 47 | -------------------------------------------------------------------------------- /functions/znap: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | zmodload zsh/param/private 3 | zmodload -F zsh/zutil b:zparseopts 4 | 5 | local -a reply=() 6 | local REPLY= 7 | 8 | local -A opts_pre=() 9 | zparseopts -A opts_pre -D -- h -help 10 | 11 | if ! (( $# )); then 12 | .znap.help 13 | return 14 | fi 15 | 16 | private _P__cmd=$1 17 | shift 18 | 19 | local -A opts_pre=() opts_post=() 20 | zparseopts -A opts_post -D -- h -help 21 | 22 | private _P__func=.znap.$_P__cmd 23 | if [[ -v functions[$_P__func] ]]; then 24 | if (( ${#opts_pre} || ${#opts_post} )); then 25 | .znap.help $_P__cmd 26 | else 27 | $_P__func $@ 28 | fi 29 | else 30 | print -u2 "znap: unknown command: $_P__cmd" 31 | .znap.help 32 | return $(( sysexits[(i)USAGE] + 63 )) 33 | fi 34 | -------------------------------------------------------------------------------- /scripts/init.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | emulate -L zsh 3 | zmodload -Fa zsh/files b:zf_ln b:zf_mkdir b:zf_rm 4 | autoload -Uz add-zsh-hook 5 | local -a match=() mbegin=() mend=() # These are otherwise leaked by zstyle. 6 | 7 | [[ ${(t)sysexits} != *readonly ]] && 8 | readonly -ga sysexits=( 9 | USAGE # 64 10 | DATAERR 11 | NOINPUT 12 | NOUSER 13 | NOHOST 14 | UNAVAILABLE 15 | SOFTWARE 16 | OSERR 17 | OSFILE 18 | CANTCREAT 19 | IOERR 20 | TEMPFAIL 21 | PROTOCOL 22 | NOPERM 23 | CONFIG # 78 24 | ) 25 | 26 | export XDG_CACHE_HOME=${XDG_CACHE_HOME:-$HOME/.cache} \ 27 | XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config} \ 28 | XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share} \ 29 | XDG_STATE_HOME=${XDG_STATE_HOME:-$HOME/.local/state} 30 | typeset -gU PATH path FPATH fpath MANPATH manpath 31 | 32 | private basedir=${${(%):-%x}:P:h:h} 33 | if [[ -z $basedir ]]; then 34 | print -u2 "znap: Could not find Znap's repo. Aborting." 35 | print -u2 "znap: file name = ${(%):-%x}" 36 | print -u2 "znap: absolute path = ${${(%):-%x}:P}" 37 | print -u2 "znap: parent dir = ${${(%):-%x}:P:h}" 38 | return $(( sysexits[(i)NOINPUT] + 63 )) 39 | fi 40 | . $basedir/scripts/opts.zsh 41 | setopt $_znap_opts 42 | 43 | private sitefuncdir=$XDG_DATA_HOME/zsh/site-functions 44 | fpath=( $basedir/completions $sitefuncdir $fpath[@] ) 45 | builtin autoload -Uz $basedir/functions/{znap,(|.).znap.*~*.zwc} 46 | 47 | local gitdir= 48 | ..znap.repos-dir 49 | if [[ -z $gitdir || ! -d $gitdir ]]; then 50 | print -u2 "znap: Could not find repos dir. Aborting." 51 | return $(( sysexits[(i)NOINPUT] + 63 )) 52 | fi 53 | 54 | zf_mkdir -pm 0700 $sitefuncdir $gitdir \ 55 | $XDG_CACHE_HOME/zsh{,-snap} $XDG_CONFIG_HOME/zsh $XDG_DATA_HOME 56 | 57 | zstyle -T :znap: auto-compile && 58 | add-zsh-hook preexec ..znap.auto-compile 59 | add-zsh-hook zsh_directory_name ..znap.dirname 60 | 61 | typeset -gH _comp_dumpfile=${_comp_dumpfile:-$XDG_CACHE_HOME/zsh/compdump} 62 | [[ -f $_comp_dumpfile && ${${:-${ZDOTDIR:-$HOME}/.zshrc}:P} -nt $_comp_dumpfile ]] && 63 | zf_rm -f $_comp_dumpfile 64 | zstyle -s :completion: cache-path _ || 65 | zstyle ':completion:*' cache-path $XDG_CACHE_HOME/zsh/compcache 66 | zstyle -s ':completion:*' completer _ || 67 | zstyle ':completion:*' completer _expand _complete _ignored 68 | .znap.function bindkey 'zmodload zsh/complist' 69 | typeset -gHa _znap_compdef=() 70 | compdef() { 71 | _znap_compdef+=( "${(j: :)${(@q+)@}}" ) 72 | } 73 | compinit() {:} 74 | add-zsh-hook precmd ..znap.compinit-hook 75 | [[ -v functions[_bash_complete] ]] || 76 | .znap.function _bash_complete compgen complete ' 77 | autoload -Uz bashcompinit 78 | bashcompinit 79 | bashcompinit() {:} 80 | ' 81 | -------------------------------------------------------------------------------- /scripts/opts.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | typeset -gHa _znap_opts=( 3 | extendedglob globstarshort nullglob rcexpandparam 4 | localloops pipefail NO_shortloops NO_unset warncreateglobal 5 | ) 6 | -------------------------------------------------------------------------------- /znap.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | zmodload zsh/param/private 3 | 4 | autoload -Uz ${${(%):-%x}:P:h}/scripts/init.zsh 5 | { 6 | init.zsh 7 | } always { 8 | unfunction init.zsh 9 | } 10 | --------------------------------------------------------------------------------