diff --git a/README.md b/README.md index d2bcad3..278be16 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,36 @@ -### mfunc -#### a function wrapper plugin for ZSH +# mfunc -Allows you to define persistent functions on-the-fly, without the need to add -them to your config files. Your functions are permanently available until you -delete them. +A function wrapper plugin for ZSH. + +Define, view, edit and delete persistent functions on-the-fly, without extra steps. They are permanently available until you delete them. The plugin defines 3 functions: | `Command` | Action -|-------------------------|---------------------------------------- +|-------------------------|----------------------------------------- | `mfunc name [name] ...` | create new function(s) interactively | `rfunc name [name] ...` | delete existing user-defined function(s) -| `lfunc` | list all user-defined functions +| `lfunc [-h\|v]` | list all user-defined functions -functions are stored as plain text in $ZSH/functions/ and made available via +Functions are stored as plain text in `$MFUNDCIR` and made available via the autoload builtin (i.e. they are only loaded into memory when called for the first time). -#### Installation +## Installation -######a) As an oh-my-zsh plugin +### a) As an oh-my-zsh plugin 1. Run: -`cd $ZSH/custom && git clone https://github.com/hlohm/mfunc.git plugins/mfunc` +`cd $ZSH/custom && git clone https://github.com/amogus07/mfunc.git plugins/mfunc` 2. Add `mfunc` to your plugins in your `.zshrc`. The relevant line should look something like this: `plugins=(git mfunc)` -######b) using antigen +### b) using antigen 1. Add this line where you load your antigen bundles in your `.zshrc`: -`antigen bundle hlohm/mfunc` -######c) with vanilla ZSH +`antigen bundle amogus07/mfunc` +### c) with vanilla ZSH 1. `git clone` this repo to a location of your choice 2. add a line `source /location/of/your/choice/mfunc.plugin.zsh` to your .zshrc @@ -39,8 +38,20 @@ look something like this: Upon its first run the plugin will notify you that it created the directory in which it stores your functions. -#### Disclaimer +## Configuration + +| Environment Variable | Default | Description +|----------------------|------------------------------------------|------------------ +| `$MFUNCDIR` | `${ZDOTDIR/functions:-$HOME/.functions}` | functions storage + +## Optional dependencies + +| Dependency | Description +|-------------------------------------|----------------------------------------------------------- +| [highlight](https://repology.org/project/highlight) | enables syntax highlighting for `lfunc -v` + +## Disclaimer This is an early version covering only the most basic functionality. There are -no safeguards whatsover, so use at you own risk. Things like tab completion, +no safeguards whatsover, so use at you own risk. Things like tab completion, input sanitization and the like are on the TODO list. diff --git a/functions/_lf_help b/functions/_lf_help new file mode 100644 index 0000000..95929ea --- /dev/null +++ b/functions/_lf_help @@ -0,0 +1,9 @@ +echo """\ +Part of the mfunc zsh plugin +Lists all the functions in $MFUNCDIR + +Usage: $0 [-v|h] +Options: + -v Enable verbose mode (print the content of each function) + -h Print this help message\ + """ diff --git a/functions/_lf_verbose b/functions/_lf_verbose new file mode 100755 index 0000000..415ff14 --- /dev/null +++ b/functions/_lf_verbose @@ -0,0 +1,29 @@ +# Ensure MFUNCDIR is set +if ! [[ -v MFUNCDIR ]] +then + if [[ -v ZDOTDIR ]] + then + MFUNCDIR="${ZDOTDIR}/functions" + else + MFUNCDIR="${HOME}/.functions" + fi +fi + +# get a list of functions managed by mfunc +local functions_array=( "$MFUNCDIR"/* ) + +# strip the paths +local functions="${functions_array[@]:t}" + +( + # load functions + autoload +X "${(z)functions[@]}" + + if ((${+commands[highlight]})) + then + highlight -S zsh -i <(where "${(z)functions[@]}") + else + where "${(z)functions[@]}" + echo "Hint: install 'highlight' to enable syntax highlighting" >&2 + fi +) diff --git a/functions/_mf_define b/functions/_mf_define new file mode 100644 index 0000000..42b32e5 --- /dev/null +++ b/functions/_mf_define @@ -0,0 +1,21 @@ +if [[ $EDITOR == *vim ]]; then + local ftopts="-c 'set ft=zsh'" +elif [[ $EDITOR == *micro ]]; then + local ftopts="-filetype=zsh" +fi + +eval "$EDITOR" "$ftopts" "${MFUNCDIR}/$1" + +if ! [[ -f "${MFUNCDIR}/$1" ]]; then + echo "aborting..." + return 1 +else + [[ ${functions[${1}]} ]] && unfunction "$1" # just in case + autoload "$1" +fi + +if "$edit"; then + echo "updated function '${1}'" +else + echo "new function '${1}' created in $MFUNCDIR" +fi diff --git a/functions/lfunc b/functions/lfunc new file mode 100644 index 0000000..c9dbd71 --- /dev/null +++ b/functions/lfunc @@ -0,0 +1,16 @@ +# TODO: specific functions, wildcards +if getopts "hv" opt; then + shift $((OPTIND - 1)) + case ${opt} in + h) + _lf_help + return + ;; + v) + _lf_verbose + return + ;; + esac +fi + +command ls "$MFUNCDIR" diff --git a/functions/mfunc b/functions/mfunc new file mode 100644 index 0000000..b242d53 --- /dev/null +++ b/functions/mfunc @@ -0,0 +1,27 @@ +# berate user if no arguments given +# TODO: interactive mode +(($# == 0)) && { + echo "usage: mfunc [function name]" + return 1 +} + +for i; do + # TODO: input sanitization + local edit=false + if [[ -e $MFUNCDIR/$i ]]; then + if ! read -rq "choice?function $i already exists, press Y/y to edit with $EDITOR or any other key to abort: "; then + echo + return + fi + echo + unfunction "$i" # forget the old version first + edit=true + fi + + _mf_define "$i" || return +done + +echo -n "function" +(($# == 1)) && + echo -n " is" || echo -n "s are" +echo " now available" diff --git a/functions/rfunc b/functions/rfunc new file mode 100644 index 0000000..52f6105 --- /dev/null +++ b/functions/rfunc @@ -0,0 +1,31 @@ +# demand argument +# TODO: interactive mode +if (($# == 0)); then + echo "please name at least one function to delete" + return 1 +fi + +# TODO: autocompletion/wildcards +local count +typeset -i count=0 +for i; do + if ! [[ -f "$MFUNCDIR/$i" ]]; then + echo "function $i not found" + return 2 + fi + + autoload +X "$i" + rm -iv "$MFUNCDIR/$i" + [[ -f $MFUNCDIR/$i ]] || count+=1 +done + +((count == 0)) && + return + +echo -n "function" +if ((count == 1)); then + echo -n " is" +else + echo -n "s are" +fi +echo " still available until next login and can be viewed with the 'where' builtin" diff --git a/mfunc.plugin.zsh b/mfunc.plugin.zsh index b441876..5826672 100755 --- a/mfunc.plugin.zsh +++ b/mfunc.plugin.zsh @@ -6,130 +6,31 @@ # github.com/hlohm ################## - -# -# init -###### - # this is where our functions live -fdir=$HOME/.functions - -# check if functions directory exists, create if it doesn't -if [[ ! -d $fdir/ ]]; then - mkdir $fdir/ - echo "mfunc init: functions directory created in $fdir" -fi - -# check if fpath contains our fdir, add it if it doesn't -if (( ! ${fpath[(I)$fdir]} )); then - fpath=($fdir $fpath) -fi - -# autoload any functions in functions directory -if [[ -e $fdir/* ]]; then - autoload $(ls $fdir/) -fi - -# -#functions -########## - -# helpers -_mf_yesorno() -{ - # variables - local question="${1}" - local prompt="${question} " - local yes_RETVAL="0" - local no_RETVAL="3" - local RETVAL="" - local answer="" - - # read-eval loop - while true ; do - printf $prompt - read -r answer - - case ${answer:=${default}} in - Y|y|YES|yes|Yes ) - RETVAL=${yes_RETVAL} && \ - break - ;; - N|n|NO|no|No ) - RETVAL=${no_RETVAL} && \ - break - ;; - * ) - echo "Please provide a valid answer (y or n)" - ;; - esac - done - - return ${RETVAL} -} - -function _mf_define() { - touch $fdir/$i - chmod +x $fdir/$i - echo "enter function '$i' and finish with CTRL-D" - cat >$HOME/.functions/$i - echo "new function '$i' created in $fdir" - autoload $(ls $fdir/) - echo "function is now available" -} - -# make function(s) -function mfunc() { - - # berate user if no arguments given - # TODO: interactive mode - if (($# == 0)) +if ! [[ -v MFUNCDIR ]] +then + if [[ -v ZDOTDIR ]] then - echo "usage: mfunc [function name]" + MFUNCDIR="${ZDOTDIR}/functions" else - for i do; - # TODO: input sanitization - if [[ -e $fdir/$i ]] - then - if _mf_yesorno "function $i already exists, overwrite? (Y/n)" - then - unfunction $1 # forget the old version first - _mf_define $i - else - echo "aborted" - fi - else - _mf_define $i - fi - done + MFUNCDIR="${HOME}/.functions" fi -} +fi -# remove function(s) -function rfunc() { +# check if functions directory exists, create if it doesn't +if ! [[ -d "${MFUNCDIR}/" ]] +then + mkdir -p "${MFUNCDIR}/" + echo "mfunc init: functions directory created in $MFUNCDIR" +fi - # demand argument - # TODO: interactive mode - if (($# == 0)) ; then - echo "please name at least one function to delete"; - fi +# check if fpath contains MFUNC_FUNCTIONS_D, add it if it doesn't +MFUNC_FUNCTIONS_D="${0:h}/functions" +(( ${fpath[(I)$MFUNC_FUNCTIONS_D]} )) || fpath=($MFUNC_FUNCTIONS_D "${fpath[@]}") - # TODO: autocompletion/wildcards - for i; do - if [ -e $fdir/$i ]; then - rm $fdir/$i - echo "function $i removed" - else - echo "function $i not found" - fi - done - echo "functions might still be available until next login" -} +# check if fpath contains our MFUNCDIR, add it if it doesn't +(( ${fpath[(I)$MFUNCDIR]} )) || fpath=($MFUNCDIR "${fpath[@]}") -# list functions -function lfunc() { - # TODO: specific functions, wildcards, names only OR name + definition - for f in $(ls $fdir); do - echo $f "() {"; cat $fdir/$f; echo "}\n" - done -} +# autoload functions +files=($MFUNC_FUNCTIONS_D/* $MFUNCDIR/*) +autoload ${(z)files[@]:t}