├── .gitignore ├── LICENSE ├── README.md └── evalcache.plugin.zsh /.gitignore: -------------------------------------------------------------------------------- 1 | *.zwc 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Matthew Rothenberg 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 | # evalcache 2 | 3 | > zsh plugin to cache the output of a binary initialization command, intended 4 | > to help lower shell startup time. 5 | 6 | ## What it does 7 | 8 | There are lots of shell wrapper tools that follow the pattern of asking you to 9 | eval a specific init command in your shell startup, for example, rbenv asks: 10 | 11 | eval "$(hub alias -s)" 12 | 13 | While this is very convenient, the reality is there is a small amount of 14 | overhead associated with shelling out to this, and the output is almost always 15 | actually static in all of the tools I know. So why bear this cost every time 16 | you open a new tab in your shell? 17 | 18 | Instead, after you load this plugin, you can replace that same command with: 19 | 20 | _evalcache hub alias -s 21 | 22 | The first time this runs, it will cache the output of the command to a file, 23 | which will be sourced in the future instead when it exists. 24 | 25 | If you update a tool and expect for some reason that it's initialization might 26 | have changed, you can simply clear the cache and it will be regenerated. 27 | 28 | It also gracefully degrades to a no-op if the tool is no longer installed. 29 | 30 | ## Benchmarks 31 | 32 | Some informal benchmarks from my MacBook on my .zshrc: 33 | 34 | | command | without | first run | subsequent runs | savings | 35 | | ------------ | ------: | --------: | --------------: | ------: | 36 | | rbenv init | ~65ms | ~65ms | ~8ms | 88% | 37 | | hub alias | ~30ms | ~30ms | ~6ms | 80% | 38 | | scmpuff init | ~24ms | ~25ms | ~10ms | 58% | 39 | 40 | The difference isn't huge, but can be handy in shaving down shell startup time, 41 | especially if you use a bunch of these tools. Every millisecond counts! 42 | 43 | ## Options 44 | 45 | - `$ZSH_EVALCACHE_DIR`: cache files storage, default `$HOME/.zsh-evalcache`. 46 | - `$ZSH_EVALCACHE_DISABLE`: set to `true` if you wish to bypass evalcache. 47 | 48 | There is a convenience function to clear the cache called `_evalcache_clear`. 49 | 50 | ## Installation 51 | 52 | ### [Antigen](https://github.com/zsh-users/antigen) 53 | 54 | Add `antigen bundle mroth/evalcache` to your `.zshrc` with your other bundle commands. 55 | 56 | Antigen will handle cloning the plugin for you automatically the next time you start zsh. You can also add the plugin to a running zsh with `antigen bundle mroth/evalcache` for testing before adding it to your `.zshrc`. 57 | 58 | ### [Fig](https://fig.io) 59 | 60 | Fig adds apps, shortcuts, and autocomplete to your existing terminal. 61 | 62 | Install `evalcache` in just one click. 63 | 64 | 65 | 66 | ### [Oh-My-Zsh](http://ohmyz.sh/) 67 | 68 | 1. Clone this repository into `$ZSH_CUSTOM/plugins` (by default `~/.oh-my-zsh/custom/plugins`) 69 | 70 | ```sh 71 | git clone https://github.com/mroth/evalcache ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/evalcache 72 | ``` 73 | 74 | 2. Edit `~/.zshrc` to add _evalcache_ to your plugin list, 75 | ```diff 76 | - plugins=(...) 77 | + plugins=(... evalcache) 78 | ``` 79 | 80 | ### [Zgen](https://github.com/tarjoilija/zgen) 81 | 82 | Add `zgen load mroth/evalcache` to your `.zshrc` file in the same function you're doing your other `zgen load` calls in. Zgen will handle automatically cloning the plugin for you the next time you do a `zgen save`. 83 | -------------------------------------------------------------------------------- /evalcache.plugin.zsh: -------------------------------------------------------------------------------- 1 | # Caches the output of a binary initialization command, to avoid the time to 2 | # execute it in the future. 3 | # 4 | # Usage: _evalcache [NAME=VALUE]... COMMAND [ARG]... 5 | 6 | # default cache directory 7 | export ZSH_EVALCACHE_DIR=${ZSH_EVALCACHE_DIR:-"$HOME/.zsh-evalcache"} 8 | 9 | function _evalcache () { 10 | local cmdHash="nohash" data="$*" name 11 | 12 | # use the first non-variable argument as the name 13 | for name in $@; do 14 | if [ "${name}" = "${name#[A-Za-z_][A-Za-z0-9_]*=}" ]; then 15 | break 16 | fi 17 | done 18 | 19 | # if command is a function, include its definition in data 20 | if typeset -f "${name}" > /dev/null; then 21 | data=${data}$(typeset -f "${name}") 22 | fi 23 | 24 | if builtin command -v md5 > /dev/null; then 25 | cmdHash=$(echo -n "${data}" | md5) 26 | elif builtin command -v md5sum > /dev/null; then 27 | cmdHash=$(echo -n "${data}" | md5sum | cut -d' ' -f1) 28 | fi 29 | 30 | local cacheFile="$ZSH_EVALCACHE_DIR/init-${name##*/}-${cmdHash}.sh" 31 | 32 | if [ "$ZSH_EVALCACHE_DISABLE" = "true" ]; then 33 | eval ${(q)@} 34 | elif [ -s "$cacheFile" ]; then 35 | source "$cacheFile" 36 | else 37 | if type "${name}" > /dev/null; then 38 | echo "evalcache: ${name} initialization not cached, caching output of: $*" >&2 39 | mkdir -p "$ZSH_EVALCACHE_DIR" 40 | eval ${(q)@} > "$cacheFile" 41 | source "$cacheFile" 42 | else 43 | echo "evalcache: ERROR: ${name} is not installed or in PATH" >&2 44 | fi 45 | fi 46 | } 47 | 48 | function _evalcache_clear () { 49 | rm -i "$ZSH_EVALCACHE_DIR"/init-*.sh 50 | } 51 | --------------------------------------------------------------------------------