Source

editfile / editfile

Full commit
#!/bin/bash

# Ben Bass 2012 @codedstructure

# Note various things are wrapped in double-quotes to ensure
# that things with spaces are handled OK.


# TODO: maybe 'base secondlevel [option]' is better than 'base [option] secondlevel'
# except then -s can't work well to determine whether it should make secondlevel
# or whether secondlevel is the searchterm in something...

# maybe only create secondlevel if not search?


EDITFILE_DIR=~/Dropbox/editfile
mkdir -p $EDITFILE_DIR  # ensure this exists
MAJOR_NAME=$(basename "$0")

direct_usage () {
    echo
    echo "this script is normally run from a symlink to it"
    echo
    echo "options when run directly:"
    echo " -l - list editfile command names"
    echo " -n <name> - make new editfile command name"
    echo " -d <name> - delete existing editfile command name"
    echo " -s <pattern> - search for given pattern in editfile files"
    echo
}

if [[ $MAJOR_NAME = "editfile" ]] ; then
    EDITFILE_PATH=$(which editfile)
    case $1 in
        '-l')
            for p in $(echo $PATH | tr : \\n | sort | uniq); do
                find -L $p -maxdepth 1 -perm -100 -samefile $EDITFILE_PATH 2> /dev/null |\
                    while read COMMAND; do
                        CMD_NAME=$(basename $COMMAND)
                        [[ $CMD_NAME != "editfile" ]] && echo $CMD_NAME
                    done
            done
            exit 0
            ;;
        '-n')
            if [[ -z $2 ]] ; then
                echo "editfile -n requires command name argument." >&2
                direct_usage
                exit 1
            fi
            # This could override an alias. Perhaps use compgen -c,
            # but that seems dodgy too (and is likely slow).
            NEW_TARGET="$(dirname $EDITFILE_PATH)/$2"
            if which $2 > /dev/null ; then
                echo "Not overriding existing command $(which $2)" >&2
                direct_usage
                exit 1
            fi
            if test -e $NEW_TARGET ; then
                echo "Not replacing existing file $NEW_TARGET" >&2
                direct_usage
                exit 1
            fi
            ln -s $EDITFILE_PATH $NEW_TARGET
            echo "Created new editfile command '$2'"
            exit 0
            ;;
        '-d')
            if [[ -z $2 ]] ; then
                echo "editfile -d requires command name argument." >&2
                direct_usage
                exit 1
            fi
            # ensure this is a symlink to editfile
            TARGET=$(which $2)
            if ! [[ -L $TARGET && $(readlink $TARGET) == $EDITFILE_PATH ]]; then
                echo "$2 is not an editfile command" >&2
                direct_usage
                exit 1
            fi
            rm -i $TARGET
            exit 0
            ;;
        '-s')
            if [[ -z $2 ]] ; then
                echo "editfile -s requires a search pattern"
                direct_usage
                exit 1
            fi
            # the pattern can be multiple things long...
            shift
            PATTERN="$@"
            # enumerate editfile notes using compgen. Note this is quite slow...
            pushd $EDITFILE_DIR > /dev/null
            find . -type f \( -name \*.txt -o -name \*.rst -o -name \*.md \) -print0 | xargs -0 grep -Hn --color "$PATTERN"
            popd > /dev/null
            exit 0
            ;;
    esac

    direct_usage
    exit 2
fi

edit () {
    # Determine editor to use
    if [[ -n "${EDITOR}" ]] ; then
        EDIT=$EDITOR
    elif $(which gedit) ; then
        EDIT="gedit -b"  # default fallback if present
    else
        EDIT="vim"       # fallback if no gedit there
    fi

    # Edit it...
    $EDIT "$1"
}

usage () {
    echo "Usage: $MAJOR_NAME [CATEGORY] [OPTIONS]"
    echo "  default operation is to edit the file"
    echo
    echo "Options:"
    echo "  -h    this help"
    echo "  -a    append stdin to the file"
    echo "  -l    output the file to stdout"
    echo "  -f    output file path name to stdout"
    echo "  -s <pattern>"
    echo "        search for given pattern"
    echo "  -t    time track mode"
    echo " (Note these options are mutually exclusive)"
    echo
    exit 1
}

CATEGORY=""
if [[ -n $1 ]] && ! [[ $1 =~ ^- ]] ; then
    # starts with something other than '-' - assume a category
    CATEGORY=$1
    shift
fi

OPTION_CHARS="alfsth"
MODE='';
while getopts "$OPTION_CHARS" option ; do
    case $option in
        'a')
            if [[ -z $MODE ]] ; then MODE="APPEND"; else usage; fi
            ;;
        'l')
            if [[ -z $MODE ]] ; then MODE="LIST"; else usage; fi
            ;;
        'f')
            if [[ -z $MODE ]] ; then MODE="PATH"; else usage; fi
            ;;
        't')
            if [[ -z $MODE ]] ; then MODE="TRACK"; else usage; fi
            ;;
        's')
            if [[ -z $MODE ]] ; then MODE="SEARCH"; else usage; fi
            ;;
        'h')
            usage
            ;;
        *)
            echo "Invalid option"
            echo
            usage
            ;;
    esac
done

shift $(($OPTIND - 1))

# only 'search' can (and must) have additional arguments
if [[ $MODE != "SEARCH" ]] && [[ -n $1 ]]; then
    echo "Too many arguments: $@"
    echo
    usage
elif [[ $MODE == "SEARCH" ]] && [[ -z $1 ]]; then
    echo "Not enough arguments - search option requires pattern"
    echo
    usage
fi

# get path to the file. Also get file path to use for
# history in the TRACK mode.
if [[ -n "$CATEGORY" ]]; then
    # edit a sub-file
    TARGET_DIR=$EDITFILE_DIR/$(basename $0)
    mkdir -p $TARGET_DIR
    TARGET_PATH="$TARGET_DIR/$CATEGORY"
    HIST_FILE="$TARGET_DIR/.hist.$CATEGORY"
else
    # edit the 'main' file
    TARGET_PATH="$EDITFILE_DIR/${MAJOR_NAME}"
    HIST_FILE="$EDITFILE_DIR/.hist.${MAJOR_NAME}"
fi

# support for various extensions.
# Will always default to .txt if nothing already there.
# Up to the user to change / create ext of different type.
# Hopefully once an editor opens the user can see which
# actual file got selected :-)
if ! [[ $TARGET_PATH =~ \.(rst|txt|md)$ ]] ; then
    for EXT in .rst .md .txt ; do
        [[ -f "${TARGET_PATH}${EXT}" ]] && break
    done
    TARGET_PATH="${TARGET_PATH}${EXT}"
fi

case $MODE in
    'LIST')
        # list file and exit
        cat "$TARGET_PATH"
        ;;
    'APPEND')
        cat /dev/stdin >> "$TARGET_PATH"
        ;;
    'PATH')
        echo "$TARGET_PATH"
        ;;
    'SEARCH')
        grep -n --color "$@" "$TARGET_PATH"
        ;;
    'TRACK')
        now=$(date '+%Y/%m/%d %H:%M')
        # read history from previous
        history -r $HIST_FILE
        while read -ep "$now >> " track_input ; do
            STORE_RESULT="no"
            COMMAND=""
            now=$(date '+%Y/%m/%d %H:%M')
            if [[ -z $track_input ]] ; then
                continue
            fi
            if [[ $track_input =~ ^\![^\!] ]] ; then
                # execute and display, don't store command or result
                COMMAND=$(echo $track_input | cut -c2-)
            elif [[ $track_input =~ ^\! ]] ; then
                # execute and display, store command and result
                STORE_RESULT="yes"
                COMMAND=$(echo $track_input | cut -c3-)
            fi
            # use -- to indicate end to options e.g. if track_input
            # starts with '->' which previously caused errors
            history -s -- "$track_input"
            if [[ -n ${COMMAND} ]] ; then
                RESULT=$(eval ${COMMAND})
                echo "${RESULT}"
                if [[ ${STORE_RESULT} = "no" ]] ; then
                    continue
                fi
            fi
            echo "$now $track_input" >> ${TARGET_PATH}
            if [[ $STORE_RESULT = "yes" ]] && [[ -n ${RESULT} ]] ; then
                echo "${RESULT}" >> ${TARGET_PATH}
            fi
        done
        # append current session to history
        history -a $HIST_FILE
        # ensure bash prompt starts on a new line
        echo
        ;;
    '')
        # Edit it...
        edit "$TARGET_PATH"
        ;;
esac