├── .editorconfig ├── LICENSE ├── README.md ├── functions ├── _funchelp ├── funced ├── funchelp └── funcsave ├── zfunctions.plugin.zsh └── zfunctions.zsh /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | indent_style = tab 9 | indent_size = 4 10 | 11 | [*.zsh] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [**/functions/*] 16 | indent_style = space 17 | indent_size = 2 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 zshzoo 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 | # zfunctions plugin 2 | 3 | Use a `$ZDOTDIR/functions` directory to store lazy-loaded zsh function files. 4 | 5 | This plugin is similar in concept to the [fish] functions directory. 6 | 7 | ## Description 8 | 9 | This plugin will enable a directory for you to store function files, and adds that directory to your Zsh `fpath` variable. 10 | Any file placed in this directory should contain the innards of a single function definition. 11 | These files will then be "autoloaded" (aka: lazy loaded) by Zsh into a function of the same name upon their first call. 12 | The lazy-loading functionality is a built-in feature of Zsh called [function autoloading][zsh-autoload]. 13 | 14 | Your functions path by default is: `${ZDOTDIR:-$HOME/.config/zsh}/functions`. 15 | However, you can optionally override the path by setting the `$ZFUNCDIR` value: 16 | 17 | ```zsh 18 | ZFUNCDIR=/path/to/my/lazy/zfunctions 19 | ``` 20 | 21 | ## Features 22 | 23 | The following functions are defined by this plugin: 24 | 25 | | Functions | Arguments | Description | 26 | |:----------|:--------------|:-------------------------------------------------------| 27 | | funced | \ | edit the function specified | 28 | | funcsave | \ | save a function to your configured functions directory | 29 | | funchelp | \ | print function help, designated via '##?' comments | 30 | 31 | **Note:** 32 | Additionally, the built-in zsh `functions` command will list all the zsh functions that are defined. 33 | The built-in `function` keyword will allow you to define a new function. 34 | 35 | ## Example 36 | 37 | First, make sure you have loaded the zfunctions plugin and started a new zsh session. 38 | You can verify that zfunctions is enabled by running the following: 39 | 40 | ```zsh 41 | $ (( $+functions[funcsave] )) && echo "zfunctions loaded" || echo "zfunctions not loaded" 42 | zfuncions loaded 43 | ``` 44 | 45 | Next, let's set a $ZFUNCDIR variable for our examples 46 | ```zsh 47 | ZFUNCDIR=${ZDOTDIR:-$HOME/.config/zsh}/functions 48 | ``` 49 | 50 | Now, let's make a quick function to test with called 'foo'. 51 | 52 | The 'foo' function should always print "bar" and sometimes also print "baz". 53 | 54 | From a zsh prompt, define this function: 55 | 56 | ```zsh 57 | # 'foo' with comments and custom formatting 58 | function foo() { 59 | # print bar 60 | echo "bar" 61 | # and sometimes baz 62 | if [[ $[${RANDOM}%2] -eq 0 ]]; then 63 | echo "baz" 64 | fi 65 | } 66 | ``` 67 | 68 | Next, we can save the function. 69 | 70 | ```zsh 71 | funcsave foo 72 | ``` 73 | 74 | Now you should have a function file called "foo" in `$ZFUNCDIR`. Let's verify: 75 | 76 | ```zsh 77 | cat $ZFUNCDIR/foo 78 | ``` 79 | 80 | Notice that the function was reformatted and also that only the function *internals* are saved to the "foo" file, not the function name definition 81 | (ie: the "`function foo() {`" part is purposely missing). 82 | 83 | ```zsh 84 | # contents of $ZFUNCDIR/foo 85 | echo "bar" 86 | if [[ $[${RANDOM}%2] -eq 0 ]] 87 | then 88 | echo "baz" 89 | fi 90 | ``` 91 | 92 | Run `zsh` to start a new zsh session to show how lazy loading works. 93 | 94 | ```zsh 95 | zsh 96 | ``` 97 | 98 | Now, check out the function definition for `foo` by using the `functions` 99 | built-in (notice the trailing "s" on the word function**s**): 100 | 101 | ```zsh 102 | functions foo 103 | ``` 104 | 105 | You should see this: 106 | 107 | ```zsh 108 | foo () { 109 | # undefined 110 | builtin autoload -XUz ~/.config/zsh/functions 111 | } 112 | ``` 113 | 114 | Now execute the `foo` function once (or do it a few times for fun): 115 | 116 | ```zsh 117 | # outputs bar, and sometimes baz 118 | $ foo 119 | bar 120 | $ foo 121 | bar 122 | baz 123 | $ foo 124 | bar 125 | ``` 126 | 127 | Now go back and run `functions foo` again and check out the results... 128 | The function definition is now filled in from the `foo` file in your `$ZFUNCDIR`. 129 | 130 | ```zsh 131 | foo() { 132 | echo "bar" 133 | if [[ $[${RANDOM}%2] -eq 0 ]] 134 | then 135 | echo "baz" 136 | fi 137 | } 138 | ``` 139 | 140 | You can edit the 'foo' function by using `funced`: 141 | 142 | ```zsh 143 | # edit the foo function 144 | funced foo 145 | 146 | # or, make a new one entirely 147 | funced bar 148 | ``` 149 | 150 | That's it! Note that you do not need to use `funcsave` or `funced` if you don't prefer to. 151 | Adding files to `$ZFUNCDIR` yourself is also an option. Just remember that your function 152 | files should be named without a file extension (ie: foo, not foo.zsh), and should not 153 | contain the function declaration part (ie: `function foo() {`). 154 | 155 | Here's a great first function to create called "up". 156 | Start by typing `funced up` and add this to the file: 157 | 158 | ```zsh 159 | ### $ZFUNCDIR/up 160 | # goes up any number of directories 161 | if [[ "$#" < 1 ]] ; then 162 | cd .. 163 | else 164 | local rpt=$(printf "%${1}s") 165 | local cdstr=${rpt// /..\/} 166 | cd $cdstr 167 | fi 168 | ``` 169 | 170 | Have fun building your zsh function library! 171 | 172 | [omz]: https://github.com/ohmyzsh/ohmyzsh 173 | [fish]: https://fishshell.com 174 | [zsh-autoload]: http://zsh.sourceforge.net/Doc/Release/Functions.html#Autoloading-Functions 175 | -------------------------------------------------------------------------------- /functions/_funchelp: -------------------------------------------------------------------------------- 1 | #compdef funchelp 2 | 3 | # show files in the functions dir (exclude dirs) 4 | _path_files -W "${ZFUNCDIR:-${ZDOTDIR:-${XDG_CONFIG_HOME:-$HOME/.config}/zsh}/functions}" -g "**/*(.)" 5 | 6 | # vim: ft=zsh sw=2 ts=2 et 7 | -------------------------------------------------------------------------------- /functions/funced: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | ##? funced - edit the function specified 3 | 4 | local ZFUNCDIR="${ZFUNCDIR:-${ZDOTDIR:-${XDG_CONFIG_HOME:-$HOME/.config}/zsh}/functions}" 5 | 6 | # check args 7 | if [[ -z "$1" ]]; then 8 | echo >&2 "funced: Expected function name argument" 9 | return 1 10 | elif [[ ! -d "$ZFUNCDIR" ]]; then 11 | echo >&2 "funced: Zsh function directory not found '$ZFUNCDIR'." 12 | return 1 13 | fi 14 | 15 | # new function definition: make a file template 16 | if [[ ! -f "$ZFUNCDIR/$1" ]]; then 17 | cat < "$ZFUNCDIR/$1" 18 | #!/bin/zsh 19 | ##? TODO: Add description. Use ##? comments for funchelp docs. 20 | 21 | ### 22 | ### TODO: write your autoload function here. 23 | ### WARNING: Do not add a 'function $1() {}' wrapper for autoloads. 24 | ### 25 | 26 | # vim: ft=zsh sw=2 ts=2 et 27 | 28 | EOS 29 | autoload -Uz "$ZFUNCDIR/$1" 30 | fi 31 | 32 | # open the function file 33 | if [[ -n "$VISUAL" ]]; then 34 | $VISUAL "$ZFUNCDIR/$1" 35 | else 36 | ${EDITOR:-vim} "$ZFUNCDIR/$1" 37 | fi 38 | -------------------------------------------------------------------------------- /functions/funchelp: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | ##? funchelp - use '##?' comments in function files as help docs 4 | ##? 5 | ##? usage: funchelp 6 | 7 | emulate -L zsh 8 | setopt local_options extended_glob rc_expand_param 9 | 10 | local funcs=(${fpath}/$1(N)) 11 | if (( $#funcs )); then 12 | print "# Defined in $funcs[1]" 13 | command grep "^##?" "$funcs[1]" | cut -c 5- 14 | else 15 | echo >&2 "funchelp: function not found '$1'." 16 | return 1 17 | fi 18 | -------------------------------------------------------------------------------- /functions/funcsave: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | ##? funcsave - save a function to $ZFUNCDIR for lazy loading 3 | 4 | local ZFUNCDIR="${ZFUNCDIR:-${ZDOTDIR:-${XDG_CONFIG_HOME:-$HOME/.config}/zsh}/functions}" 5 | 6 | # check args 7 | if [[ -z "$1" ]]; then 8 | echo >&2 "funcsave: Expected function name argument" 9 | return 1 10 | elif ! typeset -f "$1" > /dev/null; then 11 | echo >&2 "funcsave: Unknown function '$1'" 12 | return 1 13 | elif [[ ! -d "$ZFUNCDIR" ]]; then 14 | echo >&2 "funcsave: Zsh function directory not found '$ZFUNCDIR'." 15 | return 1 16 | fi 17 | 18 | # make sure the function is loaded in case it's already lazy 19 | autoload +X "$1" > /dev/null 20 | 21 | # remove first/last lines (ie: 'function foo {' and '}') and de-indent one level 22 | type -f "$1" | awk 'NR>2 {print prev} {gsub(/^\t/, "", $0); prev=$0}' >| "$ZFUNCDIR/$1" 23 | -------------------------------------------------------------------------------- /zfunctions.plugin.zsh: -------------------------------------------------------------------------------- 1 | ##? zfunctions - Use autoload functions directory for Zsh functions. 2 | 3 | # Set $0. 4 | 0=${(%):-%N} 5 | 6 | setopt extended_glob 7 | 8 | # Use $ZFUNCDIR as functions directory. 9 | : ${ZFUNCDIR:=${ZDOTDIR:-${XDG_CONFIG_HOME:-$HOME/.config}/zsh}/functions} 10 | [[ -d $ZFUNCDIR ]] || mkdir -p $ZFUNCDIR 11 | 12 | # Autoload Zsh function dir and its subdirs. 13 | for _zfuncdir in ${0:a:h}/functions $ZFUNCDIR(N) $ZFUNCDIR/*(N/); do 14 | fpath=($_zfuncdir $fpath) 15 | _zautoloads=($_zfuncdir/*(N.:t)) 16 | (( $#_zautoloads > 0 )) && autoload -Uz $_zautoloads 17 | done 18 | unset _zfuncdir _zautoloads 19 | -------------------------------------------------------------------------------- /zfunctions.zsh: -------------------------------------------------------------------------------- 1 | 0=${(%):-%N} 2 | source ${0:A:h}/zfunctions.plugin.zsh 3 | --------------------------------------------------------------------------------