Skip to content

Latest commit

 

History

History
1504 lines (1375 loc) · 46 KB

zsh-utilities-functions.org

File metadata and controls

1504 lines (1375 loc) · 46 KB

Zsh Utilities Functions

This is part of the Zsh Utilities.

Zsh Utilities - Functions

This file provides several functions for very different use. See the description to understand the aim of each of them.

Package manager

This part defines some function in relation with package management. It defines generic function name given the linux system installed : “debian-like” system or archlinux.

Check linux system

function __is_debian ()
{
    if pkgtools::has_binary apt-fast; then
        __pkg_mgr="apt-fast"
        return 0
    elif pkgtools::has_binary apt; then
        __pkg_mgr="apt"
        return 0
    elif pkgtools::has_binary apt-get; then
        __pkg_mgr="apt-get"
        return 0
    elif pkgtools::has_binary yaourt; then
        __pkg_mgr="yaourt"
        return 1
    elif pkgtools::has_binary pacman; then
        __pkg_mgr="pacman"
        return 1
    fi
}

Implement generic function

alias pkg-notify='--notify -t 2000 -i gtk-info  "${__pkg_mgr}"'
if __is_debian; then

    function install ()
    {
        pkgtools::at_function_enter install
        sudo ${__pkg_mgr} install -y $* && pkg-notify "Installed $@"
        pkgtools::at_function_exit
        return 0
    }
    compdef "_deb_packages uninstalled" install

    function remove ()
    {
        pkgtools::at_function_enter remove
        sudo ${__pkg_mgr} remove -y $* && pkg-notify "Removed $@"
        pkgtools::at_function_exit
        return 0
    }
    compdef "_deb_packages installed" remove

    function upgrade ()
    {
        pkgtools::at_function_enter upgrade
        sudo ${__pkg_mgr} update && sudo ${__pkg_mgr} -y upgrade && pkg-notify "Upgrade done"
        pkgtools::at_function_exit
        return 0
    }

    function search ()
    {
        pkgtools::at_function_enter search
        apt-cache search $@
        pkgtools::at_function_exit
        return 0
    }

    function purge ()
    {
        pkgtools::at_function_enter purge
        sudo ${__pkg_mgr} purge && pkg-notify "Purge done"
        pkgtools::at_function_exit
        return 0
    }

    function distupgrade ()
    {
        pkgtools::at_function_enter distupgrade
        sudo ${__pkg_mgr} update && sudo ${__pkg_mgr} dist-upgrade && pkg-notify "Distribution upgrade done"
        pkgtools::at_function_exit
        return 0
    }

    function autoremove ()
    {
        pkgtools::at_function_enter autoremove
        sudo ${__pkg_mgr} autoremove && pkg-notify "Autoremove done"
        pkgtools::at_function_exit
        return 0
    }
else
    function upgrade ()
    {
        pkgtools::at_function_enter upgrade
        ${__pkg_mgr} -Suy --noconfirm && pkg-notify "Upgrade done"
        pkgtools::at_function_exit
        return 0
    }

    function upgrade-aur ()
    {
        pkgtools::at_function_enter upgrade-aur
        ${__pkg_mgr} -Suya --devel --noconfirm && pkg-notify "Upgrade done"
        pkgtools::at_function_exit
        return 0
    }

    function search ()
    {
        pkgtools::at_function_enter search
        ${__pkg_mgr} -Ss $@
        pkgtools::at_function_exit
        return 0
    }

    function purge ()
    {
        pkgtools::at_function_enter purge
        ${__pkg_mgr} -Qdt && pkg-notify "Purge done"
        pkgtools::at_function_exit
        return 0
    }
    function autoremove ()
    {
        pkgtools::at_function_enter autoremove
        ${__pkg_mgr} -Sc && pkg-notify "Autoremove done"
        pkgtools::at_function_exit
        return 0
    }

fi

Compress/extract archive

function extract ()
{
    pkgtools::at_function_enter extract
    local remove_archive
    local success
    local file_name
    local extract_dir

    if [[ "$1" == "" ]]; then
        echo "Usage: extract [-option] [file ...]"
        echo
        echo "Options:"
        echo "    -r, --remove : Remove archive."
        echo
    fi

    remove_archive=1
    if [[ "$1" == "-r" ]] || [[ "$1" == "--remove" ]]; then
        remove_archive=0
        shift
    fi

    while [ -n "$1" ]; do
        if [[ ! -f "$1" ]]; then
            pkgtools::msg_warning "'$1' is not a valid file"
            shift
            continue
        fi

        success=0
        file_name="$( basename "$1" )"
        extract_dir="$( echo "$file_name" | sed "s/\.${1##*.}//g" )"
        case "$1" in
            (*.tar.gz|*.tgz) tar xvzf "$1" ;;
            (*.tar.bz2|*.tbz|*.tbz2) tar xvjf "$1" ;;
            (*.tar.xz|*.txz) tar --xz --help &> /dev/null \
                && tar --xz -xvf "$1" \
                || xzcat "$1" | tar xvf - ;;
            (*.tar.zma|*.tlz) tar --lzma --help &> /dev/null \
                && tar --lzma -xvf "$1" \
                || lzcat "$1" | tar xvf - ;;
            (*.tar.zstd) tar -I zstd -xvf "$1";;
            (*.tar) tar xvf "$1" ;;
            (*.gz) gunzip "$1" ;;
            (*.bz2) bunzip2 "$1" ;;
            (*.xz) unxz "$1" ;;
            (*.lzma) unlzma "$1" ;;
            (*.Z) uncompress "$1" ;;
            (*.zip) unzip "$1" -d $extract_dir ;;
            (*.rar) unrar e -ad "$1" ;;
            (*.7z) 7za x "$1" ;;
            (*.deb)
                mkdir -p "$extract_dir/control"
                mkdir -p "$extract_dir/data"
                cd "$extract_dir"; ar vx "../${1}" > /dev/null
                cd control; tar xzvf ../control.tar.gz
                cd ../data; tar xzvf ../data.tar.gz
                cd ..; rm *.tar.gz debian-binary
                cd ..
                ;;
            (*)
                pkgtools::msg_error "'$1' cannot be extracted" 1>&2
                success=1
                ;;
        esac

        (( success = $success > 0 ? $success : $? ))
        (( $success == 0 )) && (( $remove_archive == 0 )) && rm "$1"
        shift
    done
    pkgtools::at_function_exit
    return 0
}
function compress ()
{
    pkgtools::at_function_enter compress

    if [[ "$1" == "" ]]; then
        echo "Usage: compress [-option] dir[.tar.gz]"
        echo
    fi

    while [ -n "$1" ]; do
        local base="$(basename \"$1\")"
        local filename="${base%%.*}"
        local file="${base%.*}"
        local dir="$(dirname \"$1\")/$filename"
        local ext="${base#*.}"
        if [[ ! -d "$dir" || ! -f "$file" ]]; then
            pkgtools::msg_warning "'$dir' is not a valid directory/file"
            shift
            continue
        fi

        success=0
        if [[ -d "$dir" ]]; then
          (
            cd $(dirname $1)
            case "$ext" in
                (tar.gz|tgz) tar czf "$1" "$filename" ;;
                (tar.bz2|tbz|*.tbz2) tar cjf "$1" "$filename" ;;
                (tar.xz|txz) tar --xz -cvf "$1" "$filename" ;;
                (tar.zma|tlz) tar --lzma -cvf "$1" "$filename" ;;
                (tar) tar cvf "$1" "$filename" ;;
                (zip) zip "$dir" $dir/*;;
                (*)
                    pkgtools::msg_error "'$dir' cannot be compressed!" 1>&2
                    success=1
                    ;;
            esac
          )
        elif [[ -f "$file" ]]; then
            ext="${base##*.}"
            case "$ext" in
                (gz) gzip "$file" ;;
                (bz2) bzip2 "$file" ;;
                (xz) xz "$file"  ;;
                (lzma) lzma "$1"  ;;
                (7z) 7za x "$1" ;;
                (*)
                    pkgtools::msg_error "'$file' $ext cannot be compressed!" 1>&2
                    success=1
                    ;;
            esac
        fi

        (( success = $success > 0 ? $success : $? ))
        shift
    done
    pkgtools::at_function_exit
    return success
}

Manage dotfiles

Previous method was using https://pypi.python.org/pypi/dotfiles, since there are still some uncontrolled things (like managing ~/.config directory), I change it by using a classical Makefile

# function dotfiles ()
# {
#     pkgtools::at_function_enter dotfiles
#     cd ~/Development/github.com/xgarrido/dotfiles
#     make $@
#     pkgtools::at_function_exit
#     return 0
# }
# # Connect completion system
# compdef _dotfiles dotfiles

# function _dotfiles ()
# {
#     local -a _actions
#     _actions=(
#         help:'show help'
#         list:'show dot files'
#         deploy:'create symlink to home directory'
#         update:'fetch changes'
#         install:'run update and deploy command'
#         clean:'remove the dot files'
#     )
#     _describe -t _actions 'Actions' _actions && ret=0
# }

Notification

Base function for notification

function --notify ()
{
    if pkgtools::has_binary notify-send; then
        case $HOSTNAME in
            garrido-laptop|nb-garrido)
                notify-send $@ > /dev/null 2>&1
                ;;
        esac
    fi
    return 0
}

Success

function notify_success ()
{
    --notify -t 5000 -u low -i gtk-info "notice" "${PREEXEC_CMD} $@"
    return 0
}
alias notify=notify_success

Error

function notify_error ()
{
    --notify -t 5000 -u critical -i gtk-stop "error" "${PREEXEC_CMD:-Shell Command} $@"
    return 0
}

Zsh precmd and preexec

These two functions are only available for zsh shell. There are run at every shell command and trigger notification events in case of long time command or failling ones. This is pretty useful when long command such as compilation command are running : user can go to another desktop do whatever he wants but get warned when the command has finished or has failed.

function precmd ()
{
    # must be first
    if [ $? -ne 0 ]; then
        notify_error
    fi

    # BEGIN notify long running cmds
    stop=$(date +'%s')
    start=${PREEXEC_TIME:-$stop}
    let elapsed=$stop-$start
    max=${PREEXEC_MAX:-10}

    for i in ${PREEXEC_EXCLUDE_LIST:-}; do
        if [ "x$i" = "x$PREEXEC_CMD" ]; then
            max=999999;
            break;
        fi
    done

    if [ $elapsed -gt $max ]; then
        notify_success "finished ($elapsed secs)"
    fi
    # END notify long running cmds

    # Update scheme color
    if (( $+functions[__load_scheme] )); then
        __load_scheme
    fi

    return 0
}

function preexec ()
{
    if [[ "$TERM" == "screen" ]]; then
        local CMD=${1}
        echo -ne "\ek$CMD\e\\"
    fi
    # for notifying of long running commands
    export PREEXEC_CMD=`echo $1 | awk '{ print $1; }'`
    export PREEXEC_TIME=$(date +'%s')
    return 0
}

SSH connection

This should be improved by doing something as wakeonlan did with a small machine db.

function connect ()
{
    pkgtools::at_function_enter connect
    local use_screen=0
    local server_name=
    local ssh_option=
    local append_command=

    if [[ "$1" == "" ]]; then
        pkgtools::msg_error "Missing the name of machine to connect !"
        pkgtools::at_function_exit
        return 1
    fi

    while [ -n "$1" ]; do
        if [[ "$1" == "-s" ]]; then
            use_screen=1
        elif [[ "$1" == "ligo" ]]; then
          [email protected]
        elif [[ "$1" == "livingston" ]]; then
          [email protected]
        elif [[ "$1" == "livingston-dev6" ]]; then
          [email protected]
        elif [[ "$1" == "hanford" ]]; then
          [email protected]
        elif [[ "$1" == "caltech" ]]; then
          [email protected]
        elif [[ "$1" == "caltech-dev4" ]]; then
          [email protected]
        elif [[ "$1" == "caltech-dev6" ]]; then
          [email protected]
        elif [[ "$1" == "lyon" ]]; then
            server_name="[email protected]"
        elif [[ "$1" == "nersc" ]]; then
            server_name="[email protected]"
        elif [[ "$1" == "ruche" ]]; then
            server_name="[email protected]"
        elif [[ "$1" == "dell-xps" ]]; then
            server_name="[email protected]"
        elif [[ "$1" == "rpi" ]]; then
            server_name="[email protected]"
        elif [[ "$1" == "lal" ]]; then
            server_name="[email protected]"
        elif [[ $1 == pc-mag ]]; then
            server_name="[email protected]"
        fi
        shift 1
    done

    if [ ${use_screen} -eq 0 ]; then
        pkgtools::msg_notice "Connecting to ${server_name}..."
        ssh ${ssh_option} ${server_name} "${append_command}"
    else
        pkgtools::msg_notice "Connecting to ${server_name} with screen support..."
        screen ssh ${ssh_option} ${server_name}
    fi

    pkgtools::at_function_exit
    return 0
}
# Connect completion system
compdef _connect connect

function _connect ()
{
    local -a _machines
    _machines=(
        lyon:'CC Lyon job machines'
        nersc:'NERSC machines'
        dell-xps:'Laptop Dell XPS'
        rpi:'Ubuntu - Raspberry Pi'
        lal:'lxplus machine @ LAL'
        pc-mag:'Magistère machine'
        ligo:'LIGO machines'
        livingston:'Livingston job machines'
        livingston-dev6:'Livingston dev machines'
        caltech:'Caltech job machines'
        caltech-dev4:'Caltech dev4 machines'
        caltech-dev6:'Caltech dev6 machines'
        hanford:'Hanford job machines'
        ruche:'Ruche mesocentre'
    )
    _describe -t _machines 'SSH machines' _machines && ret=0
}

Grepping information

Find a running job

function psgrep ()
{
    pkgtools::at_function_enter psgrep
    if [[ ! -z $1 ]] ; then
        pkgtools::msg_notice "Grepping for processes matching $1..."
        ps aux | grep $1 | grep -v grep
    else
        pkgtools::msg_error "Need name to grep for !"
        pkgtools::at_function_exit
        return 1
    fi
    pkgtools::at_function_exit
    return 0
}

Find a running job & kill it

function pskill ()
{
    pkgtools::at_function_enter pskill
    psgrep $1 | awk '{print "kill -9",$2}' | sh
    pkgtools::at_function_exit
    return 0
}

Find a command within history

function hgrep ()
{
    pkgtools::at_function_enter hgrep
    if [[ ! -z $1 ]] ; then
        pkgtools::msg_notice "Grepping for command matching $1..."
        history | grep $1
    else
        pkgtools::msg_error "Need name to grep for !"
        pkgtools::at_function_exit
        return 1
    fi
    pkgtools::at_function_exit
    return 0
}

Text edition

Remove all trailing whitespace in a given file

function remove_trailing_whitespace ()
{
    pkgtools::at_function_enter remove_trailing_whitespace
    if [[ ! -z $1 ]] ; then
        pkgtools::msg_notice "Removing trailing whitespace in file $1..."
        find $1 -type f -exec sed -i 's/ *$//' '{}' ';'
    else
        pkgtools::msg_error "Missing filename !"
        pkgtools::at_function_exit
        return 1
    fi
    pkgtools::at_function_exit
    return 0
}

Remove duplicate lines

function remove_duplicate_lines ()
{
    pkgtools::at_function_enter remove_duplicate_lines
    if [[ ! -z $1 ]] ; then
        pkgtools::msg_notice "Removing duplicate lines in file $1..."
        awk '!seen[$0]++' $1 > /tmp/$(basename $1).tmp
        mv /tmp/$(basename $1).tmp $1
    else
        pkgtools::msg_error "Missing filename !"
        pkgtools::at_function_exit
        return 1
    fi
    pkgtools::at_function_exit
    return 0
}

Convert Le Monde article

function convert_lemonde()
{
    cat $1 | awk '
BEGIN{
    found   = 0
    process = 0
}
{
    if ($0 ~ /LE MONDE/) {
        if (found == 2) {print "*",prev,"\n"; process=1; $0=$0"."}
        else found++
    }

    if ($0 ~ /Abonnez vous/) process=0

    if ($0 ~ /Lire aussi/) next

    if (process) {
        last=($0 ~ /\./)
        if (last) print $0
        else if ($0 != "") print "**",$0
        else print $0
    }
    if ($0 != "") prev=$0
}
' > $1.tmp
    mv $1.tmp $1
}

Image edition

Convert an EPS figure into tikz

function eps2tikz ()
{
    pkgtools::at_function_enter eps2tikz
    local use_helvetica=0
    local keep_xfig=0
    local remove_duplicate_lines=1
    local eps_file=
    local parse_switch=1

    if [[ "$1" == "" ]]; then
        echo "Usage: eps2tikz [-option] [eps files ...]"
        echo
        echo "Options:"
        echo "    -k, --keep-xfig : Keep the intermediate xfig file."
        echo
    fi

    while [ -n "$1" ]; do
        token="$1"
        if [[ "${token[1]}" = "-" ]]; then
            opt=${token}
            if [[ ${parse_switch} -eq 0 ]]; then
                break
            fi
            if [ "${opt}" = "--keep-xfig" ]; then
                keep_xfig=1
            elif [ "${opt}" = "--do-not-remove-duplicate-lines" ]; then
                remove_duplicate_lines=0
            else
                pkgtools::msg_warning "Ignoring option '${opt}' !"
            fi
        else
            arg=${token}
            parse_switch=0
            if [ "${arg##*.}" = "eps" ]; then
                eps_file="${eps_file} ${arg}"
            else
                pkgtools::msg_warning "'${eps_file}' is not an Encapsulated PostScript"
            fi
        fi
        shift
    done

    if [[ -z "${eps_file}" ]]; then
        pkgtools::msg_error "Missing EPS file !"
        pkgtools::at_function_exit
        return 1
    fi

    for i in $(echo ${eps_file}); do
        if [ ! -f "${i}" ]; then
            pkgtools::msg_warning "File ${i} does not exist! Skip it"
            continue
        fi

        local fig_file=${i/.eps/.fig}
        local tikz_file=${i/.eps/.tikz}

        pkgtools::msg_notice "Converting ${i} file to ${tikz_file}..."

        if [[ ! -x $(which pstoedit) ]]; then
            pkgtools::msg_error "Missing 'pstoedit' binary !"
            pkgtools::at_function_exit
            return 1
        fi
        pstoedit -f xfig "${i}" > ${fig_file} 2> /dev/null

        if [[ ! -x $(which fig2tikz) ]]; then
            pkgtools::msg_error "Missing fig2tikz' binary !"
            pkgtools::at_function_exit
            return 1
        fi
        fig2tikz ${fig_file} > ${tikz_file}.tmp
        if [[ ${remove_duplicate_lines} -eq 1 ]]; then
            pkgtools::msg_notice "Remove duplicate lines..."
            awk '{if (match($0,"definecolor") || !seen[$0]++) {print $0}}' ${tikz_file}.tmp > ${tikz_file}
        else
            cp ${tikz_file}.tmp ${tikz_file}
        fi

        rm -f ${tikz_file}.tmp

        if [[ ${keep_xfig} -eq 0 ]]; then
            rm -f ${fig_file}
        fi

    done
    pkgtools::at_function_exit
    return 0
}

# completion system
compdef _eps2tikz eps2tikz

function _eps2tikz ()
{
    _arguments -C                                                                             \
        '(-v --verbose)'{-v,--verbose}'[verbose output]'                                      \
        '(-h --help)'{-h,--help}'[print help message]'                                        \
        --keep-xfig'[Keep Xfig files]'                                                        \
        --do-not-remove-duplicate-lines'[do not edit tikz file by removing duplicated lines]' \
        "*:filename: _alternative 'files:file:_files -g  \"*.eps\"'" && ret=0
}

Grab point with dexter

Dexter is a little java program to interactively or semi-automatically extract data from scanned graphs. In its applet incarnation it is used by the Astrophysics Data System.

function dexter ()
{
    pkgtools::at_function_enter dexter
    if [[ "$1" == "" ]]; then
        echo "Usage: dexter [image files ...]"
        echo
        pkgtools::at_function_exit
        return 1
    else
        java -jar /home/garrido/Workdir/Development/java/dexter/Debuxter.jar $1
    fi
    pkgtools::at_function_exit
    return 0
}

Wrapper arround tikz2pdf

function plot2pdf()
{
    local dirs
    local files
    local options
    for i in $@
    do
        if [ -f $i ]; then
            files+="$i "
        elif [ -d $i ]; then
            dirs+="$i "
        else
            options+="$i "
        fi
    done
    if [[ -z ${dirs} && -z ${files} ]]; then
      pkgtools::msg_notice "Adding current directory"
      dirs="."
    fi
    for d in ${=dirs}
    do
        for f in $(find $d -name "*.pgf" -o -name "*.tikz")
        do
          files+="$f "
        done
    done
    for f in ${=files}
    do
        file=$(echo $f)
        base=${f%.*}
        pdf=$base.pdf

        if [[ ! -a $pdf || $file -nt $pdf ]]; then
            # Remove 'Dimension too large'
            sed -i -e 's/\(.*pgfqpoint{.*}{\)\(-[2-9][0-9][0-9]\)\(.*in}.*\)/\1-200\3/g' $file
            eval $(echo tikz2pdf ${options} ${file})
        else
          pkgtools::msg_notice "File '$file' already processed"
        fi
    done
}
compdef _tikz2pdf plot2pdf

Music edition

Convert flac to mp3

function flac2mp3() {
    if [[ "$1" == "" ]]; then
        echo "Usage: flac2mp3 [flac files ...]"
        echo
        return 1
    fi

    if (( $+commands[parallel] )); then
        parallel ffmpeg -i {} -qscale:a 0 {.}.mp3 ::: $1/**/*.flac
    else
      pkgtools::msg_error "Missing parallel package!"
      return 1
    fi
    return 0
}

Google music uploader

function google-music-uploader()
{
  if [ ! -d /tmp/gmusic ]; then
    pkgtools::msg_notice "Creating virtual env for gmusicapi"
    python3 -m venv /tmp/gmusic
  fi
  source /tmp/gmusic/bin/activate
  pip install google-music-manager-uploader
  if [ ! -f ~/.auth.key ]; then
      google-music-auth ~/.auth.key
  fi
  sed -i -e 's/eth0/wlp2s0/' /tmp/gmusic/lib/python3.8/site-packages/google_music_manager_uploader/uploader_daemon.py
  google-music-upload -d /home/garrido/Musique -a ~/.auth.key -r
}

Mounting/unmounting USB drives

function usb_umount()
{
    for d in /run/media/*; do
        sudo umount $d
    done
    sudo modprobe usb_storage
}

Subversion functions

Better SVN status

function svnstatus ()
{
    pkgtools::at_function_enter svnstatus
    templist=$(svn status $*)
    echo "$(echo $templist | grep '^?' | wc -l) unversioned files/directories"
    echo $templist | grep -v '^?'
    pkgtools::at_function_exit
    return 0
}

Better SVN diff (needs code2color)

function svndiff ()
{
    pkgtools::at_function_enter svndiff
    svn diff $* | code2color -l patch -
    pkgtools::at_function_exit
    return 0
}

Better ls with git support

Original work done by supercrabtree

zmodload zsh/datetime
zmodload -F zsh/stat b:zstat

k () {
    # ----------------------------------------------------------------------------
    # Setup
    # ----------------------------------------------------------------------------

    # Stop stat failing when a directory contains either no files or no hidden files
    # Track if we _accidentally_ create a new global variable
    setopt local_options null_glob warn_create_global

    # Turn on 256 colour terminal, not sure this works at all.
    typeset OLD_TERM="$TERM"
    TERM='xterm-256color'

    # ----------------------------------------------------------------------------
    # Vars
    # ----------------------------------------------------------------------------

    typeset -a MAX_LEN A RESULTS STAT_RESULTS
    typeset TOTAL_BLOCKS

    # Get now
    typeset K_EPOCH="${EPOCHSECONDS:?}"

    typeset -i TOTAL_BLOCKS=0

    MAX_LEN=(0 0 0 0 0 0)

    # Array to hold results from `stat` call
    RESULTS=()

    # only set once so must be out of the main loop
    typeset -i IS_GIT_REPO=0

    typeset -i LARGE_FILE_COLOR=196
    typeset -a SIZELIMITS_TO_COLOR
    # SIZELIMITS_TO_COLOR=(
    #     1024  46    # <= 1kb
    #     2048  82    # <= 2kb
    #     3072  118   # <= 3kb
    #     5120  154   # <= 5kb
    #     10240  190   # <= 10kb
    #     20480  226   # <= 20kb
    #     40960  220   # <= 40kb
    #     102400  214   # <= 100kb
    #     262144  208   # <= 0.25mb || 256kb
    #     524288  202   # <= 0.5mb || 512kb
    # )
    SIZELIMITS_TO_COLOR=(
        1024   226    # <= 1kb
        2048   220    # <= 2kb
        5120   214   # <= 5kb
        524288  208   # <= 512kb
        1024000  202   # <= 1Mb
    )
    typeset -i ANCIENT_TIME_COLOR=252  # > more than 2 years old
    typeset -a FILEAGES_TO_COLOR
    FILEAGES_TO_COLOR=(
        0 196  # < in the future, #spooky
        60 236  # < less than a min old
        3600 238  # < less than an hour old
        86400 240  # < less than 1 day old
        604800 242  # < less than 1 week old
        2419200 244  # < less than 28 days (4 weeks) old
        15724800 244  # < less than 26 weeks (6 months) old
        31449600 250  # < less than 1 year old
        62899200 252  # < less than 2 years old
    )

    # ----------------------------------------------------------------------------
    # Stat call to get directory listing
    # ----------------------------------------------------------------------------

    # Break total blocks of the front of the stat call, then push the rest to results
    typeset -i i=1 j=1 k=1
    typeset -a STATS_PARAMS_LIST
    typeset fn statvar
    typeset -A sv

    # for fn in . .. *(D)
    for fn in $(command \ls -rt $@)
    do
        if [[ $fn == "." || $fn == ".." ]]; then continue; fi
        statvar="stats_$i"
        typeset -A $statvar
        zstat -H $statvar -Lsn -F "%s^%d^%b^%H:%M^%Y" -- "$fn"  # use lstat, render mode/uid/gid to strings
        STATS_PARAMS_LIST+=($statvar)
        i+=1
    done


    # On each result calculate padding by getting max length on each array member
    for statvar in "${STATS_PARAMS_LIST[@]}"
    do
        sv=("${(@Pkv)statvar}")
        if [[ ${#sv[mode]}  -gt $MAX_LEN[1] ]]; then MAX_LEN[1]=${#sv[mode]}  ; fi
        if [[ ${#sv[nlink]} -gt $MAX_LEN[2] ]]; then MAX_LEN[2]=${#sv[nlink]} ; fi
        if [[ ${#sv[uid]}   -gt $MAX_LEN[3] ]]; then MAX_LEN[3]=${#sv[uid]}   ; fi
        if [[ ${#sv[gid]}   -gt $MAX_LEN[4] ]]; then MAX_LEN[4]=${#sv[gid]}   ; fi
        if [[ ${#sv[size]}  -gt $MAX_LEN[5] ]]; then MAX_LEN[5]=${#sv[size]}  ; fi
        TOTAL_BLOCKS+=$sv[blocks]
    done

    # Print total block before listing
    echo "total $TOTAL_BLOCKS"

    # ----------------------------------------------------------------------------
    # Loop through each line of stat, pad where appropriate and do git dirty checking
    # ----------------------------------------------------------------------------

    typeset REPOMARKER
    typeset PERMISSIONS HARDLINKCOUNT OWNER GROUP FILESIZE DATE NAME SYMLINK_TARGET
    typeset FILETYPE PER1 PER2 PER3 PERMISSIONS_OUTPUT STATUS
    typeset TIME_DIFF TIME_COLOR DATE_OUTPUT
    typeset -i IS_DIRECTORY IS_SYMLINK IS_EXECUTABLE
    typeset -i COLOR

    k=1
    for statvar in "${STATS_PARAMS_LIST[@]}"
    do
        sv=("${(@Pkv)statvar}")

        # We check if the result is a git repo later, so set a blank marker indication the result is not a git repo
        REPOMARKER=""
        IS_DIRECTORY=0
        IS_SYMLINK=0
        IS_EXECUTABLE=0

        PERMISSIONS="${sv[mode]}"
        HARDLINKCOUNT="${sv[nlink]}"
        OWNER="${sv[uid]}"
        GROUP="${sv[gid]}"
        FILESIZE="${sv[size]}"
        DATE=(${(s:^:)sv[mtime]}) # Split date on ^
        NAME="${sv[name]}"
        SYMLINK_TARGET="${sv[link]}"

        # Check for file types
        if [[ -d "$NAME" ]]; then IS_DIRECTORY=1; fi
        if [[ -L "$NAME" ]]; then   IS_SYMLINK=1; fi

        # is this a git repo
        if [[ $k == 1 && $(command git rev-parse --is-inside-work-tree 2>/dev/null) == true ]]
        then
            IS_GIT_REPO=1
        fi;

        # Pad so all the lines align - firstline gets padded the other way
        PERMISSIONS="${(r:MAX_LEN[1]:)PERMISSIONS}"
        HARDLINKCOUNT="${(l:MAX_LEN[2]:)HARDLINKCOUNT}"
        OWNER="${(l:MAX_LEN[3]:)OWNER}"
        GROUP="${(l:MAX_LEN[4]:)GROUP}"
        FILESIZE="${(l:MAX_LEN[5]:)FILESIZE}"

        # --------------------------------------------------------------------------
        # Colour the permissions - TODO
        # --------------------------------------------------------------------------
        # Colour the first character based on filetype
        FILETYPE="${PERMISSIONS[1]}"
        # if (( IS_DIRECTORY ))
        # then
        #     FILETYPE=${FILETYPE//d/$'\e[01;38;5;004m'd$'\e[0m'};
        # elif (( IS_SYMLINK ))
        # then
        #     FILETYPE=${FILETYPE//l/$'\e[0;35m'l$'\e[0m'};
        # elif [[ $FILETYPE == "-" ]];
        # then
        #     FILETYPE=${FILETYPE//-/$'\e[0;37m'-$'\e[0m'};
        # fi

        # Permissions Owner
        PER1="${PERMISSIONS[2,4]}"

        # Permissions Group
        PER2="${PERMISSIONS[5,7]}"

        # Permissions User
        PER3="${PERMISSIONS[8,10]}"

        PERMISSIONS_OUTPUT="$FILETYPE$PER1$PER2$PER3"

        # --x --x --x warning
        if [[ $PER1[3] == "x" || $PER2[3] == "x" || $PER3[3] == "x" ]]; then IS_EXECUTABLE=1; fi

        # --- --- rwx warning
        if [[ $PER3 == "rwx" && IS_SYMLINK == 0 ]]; then PERMISSIONS_OUTPUT=$'\e[30;41m'"$PERMISSIONS"$'\e[0m'; fi

        # --------------------------------------------------------------------------
        # Colour the symlinks - TODO
        # --------------------------------------------------------------------------

        # --------------------------------------------------------------------------
        # Colour Owner and Group
        # --------------------------------------------------------------------------
        # OWNER=$'\e[38;5;011m'"$OWNER"$'\e[0m'
        # GROUP=$'\e[38;5;009m'"$GROUP"$'\e[0m'

        # --------------------------------------------------------------------------
        # Colour file weights
        # --------------------------------------------------------------------------
        COLOR=LARGE_FILE_COLOR
        for i j in ${SIZELIMITS_TO_COLOR[@]}
        do
            (( FILESIZE <= i )) || continue
            COLOR=$j
            break
        done

        FILESIZE="$(command numfmt --to=si --format="%4f" $FILESIZE)"
        FILESIZE=$'\e[38;5;'"${COLOR}m$FILESIZE"$'\e[0m'

        # --------------------------------------------------------------------------
        # Colour the date and time based on age, then format for output
        # --------------------------------------------------------------------------
        # Setup colours based on time difference
        TIME_DIFF=$(( K_EPOCH - DATE[1] ))
        TIME_COLOR=$ANCIENT_TIME_COLOR
        for i j in ${FILEAGES_TO_COLOR[@]}
        do
            (( TIME_DIFF < i )) || continue
            TIME_COLOR=$j
            break
        done

        # Format date to show year if more than 6 months since last modified
        if (( TIME_DIFF < 15724800 )); then
            DATE_OUTPUT="${DATE[2]} ${(r:5:: :)${DATE[3][0,5]}} ${DATE[4]}"
        else
            DATE_OUTPUT="${DATE[2]} ${(r:6:: :)${DATE[3][0,5]}} ${DATE[5]}"  # extra space; 4 digit year instead of 5 digit HH:MM
        fi;
        DATE_OUTPUT[1]="${DATE_OUTPUT[1]//0/ }" # If day of month begins with zero, replace zero with space

        # # Apply colour to formated date
        # DATE_OUTPUT=$'\e[38;5;'"${TIME_COLOR}m${DATE_OUTPUT}"$'\e[0m'

        # --------------------------------------------------------------------------
        # Colour the repomarker
        # --------------------------------------------------------------------------
        # # Check for git repo, first checking if the result is a directory
        # if (( IS_GIT_REPO == 0)) || (( k <= 2 ))
        # then
            # if (( IS_DIRECTORY )) && [[ -d "$NAME/.git" ]]
            # then
            #     if command git --git-dir="$PWD/$NAME/.git" --work-tree="$PWD/$NAME" diff --quiet --ignore-submodules HEAD &>/dev/null # if dirty
            #     then REPOMARKER=$'\e[38;5;46m|\e[0m' # Show a green vertical bar for dirty
            #     else REPOMARKER=$'\e[0;31m|\e[0m' # Show a red vertical bar if clean
            #     fi
            # fi
        # fi

        if (( IS_GIT_REPO )) && (( k > 0 )) && [[ "$NAME" != '.git' ]]
        then
            STATUS="$(command git status --porcelain --ignored --untracked-files=normal "$NAME")"
            STATUS="${STATUS[1,2]}"
            if [[ $STATUS == ' M' ]]; then REPOMARKER=$'\e[01;38;5;001m✘\e[0m'; # Modified
            elif [[ $STATUS == '??' ]]; then REPOMARKER=$'\e[01;38;5;009m⚑\e[0m'; # Untracked
            elif [[ $STATUS == '!!' ]]; then REPOMARKER=$'\e[01;38;5;004m⚐\e[0m'; # Ignored
            elif [[ $STATUS == 'A ' ]]; then REPOMARKER=$'\e[01;38;5;093m|\e[0m'; # Added
            else                             REPOMARKER=$'\e[01;38;5;002m✔\e[0m'; # Good
            fi
        fi

        # --------------------------------------------------------------------------
        # Colour the filename
        # --------------------------------------------------------------------------
        # Unfortunately, the choices for quoting which escape ANSI color sequences are q & qqqq; none of q- qq qqq work.
        # But we don't want to quote '.'; so instead we escape the escape manually and use q-
        NAME="${(q-)NAME//$'\e'/\\e}"    # also propagate changes to SYMLINK_TARGET below

        if (( IS_SYMLINK ))
        then
            NAME=$'\e[07;38;5;4m'"$NAME"$'\e[0m'
        elif (( IS_DIRECTORY ))
        then
            NAME=$'\e[01;38;5;4m'"$NAME"$'\e[0m'
        fi

        # --------------------------------------------------------------------------
        # Format symlink target
        # --------------------------------------------------------------------------
        if [[ $SYMLINK_TARGET != "" ]]; then SYMLINK_TARGET="-> ${(q-)SYMLINK_TARGET//$'\e'/\\e}"; fi
        #SYMLINK_TARGET=$'\e[38;5;27m'"$SYMLINK_TARGET"$'\e[0m'

        # --------------------------------------------------------------------------
        # Display final result
        # --------------------------------------------------------------------------
        print -r -- "$PERMISSIONS_OUTPUT $HARDLINKCOUNT $OWNER $GROUP $FILESIZE $DATE_OUTPUT $REPOMARKER $NAME $SYMLINK_TARGET"
        # print -r -- "$FILESIZE $DATE_OUTPUT $REPOMARKER $NAME $SYMLINK_TARGET"

        k=$((k+1)) # Bump loop index
    done

    # cleanup / recovery
    TERM="$OLD_TERM"
}

# http://upload.wikimedia.org/wikipedia/en/1/15/Xterm_256color_chart.svg

Better tree with git support

function t()
{
    arguments="$@"
    skip_ignore_files=0
    if [[ $arguments == *--skip-ignore-files* ]]; then
        skip_ignore_files=1
        arguments=${@/--skip-ignore-files/}
    fi
    tree -ft $(echo $arguments) | awk -v skip_ignore_files=$skip_ignore_files '{
  status=""
  marker=" "
  color="\033[0m"
  # if (system("test -f " $NF) == 0) print "file="$NF
  if (system("test -d " $NF) == 0) {
      is_git_repo=system("git rev-parse --is-inside-work-tree > /dev/null 2>&1")
      color="\033[01;38;5;004m"
      marker=""
  }
  if (is_git_repo == 0) {
      cmd = "git status --porcelain --ignored --untracked-files=normal \"" $NF "\""
      cmd | getline status
      status=substr(status,1,2)
      if (status == " M") {
          color="\033[38;5;001m"
          marker="✘ "
      } else if (status == "??") {
          color="\033[38;5;009m"
          marker="⚑ "
      } else if (status == "!!") {
          color="\033[38;5;004m"
          marker=color "⚐ "
          if (skip_ignore_files) next
      } else if (status == "A ") {
          color="\033[38;5;093m"
          marker="| "
      } else {
          color="\033[38;5;002m"
          marker="✔ "
      }
  }
  basefile=$NF
  sub(".*/",color marker,basefile)
  sub($NF,basefile,$0)

  print $0 "\033[0m"
    }'
}
alias tt='t -L 1'
alias ta='t -a -L 1'
alias ti='t --skip-ignore-files'

Countdown

function countdown()
{
    date1=$((`date +%s` + $1));
    while [ "$date1" -ne `date +%s` ]; do
        echo -ne "$(date -u --date @$(($date1 - `date +%s`)) +%H:%M:%S)\r";
        sleep 0.1
    done
}
function stopwatch()
{
    date1=`date +%s`;
    while true; do
        echo -ne "$(date -u --date @$((`date +%s` - $date1)) +%H:%M:%S)\r";
        sleep 0.1
    done
}

Colored man pages

From colored-man-pages plugin

# termcap
# ks       make the keypad send commands
# ke       make the keypad send digits
# vb       emit visual bell
# mb       start blink
# md       start bold
# me       turn off bold, blink and underline
# so       start standout (reverse video)
# se       stop standout
# us       start underline
# ue       stop underline

function man()
{
	  env \
		  LESS_TERMCAP_mb=$(printf "\e[1;34m") \
		  LESS_TERMCAP_md=$(printf "\e[1;34m") \
		  LESS_TERMCAP_me=$(printf "\e[0m") \
		  LESS_TERMCAP_so=$(printf "\e[1;47;33m") \
		  LESS_TERMCAP_se=$(printf "\e[0m") \
		  LESS_TERMCAP_us=$(printf "\e[1;32m") \
		  LESS_TERMCAP_ue=$(printf "\e[0m") \
		  PAGER="${commands[less]:-$PAGER}" \
		  man "$@"
}

youtube-dl

function youtube-dl()
{
  if [ ! -d /tmp/youtube-dl ]; then
    pkgtools::msg_notice "Creating virtual env for youtube-dl"
    python3 -m venv /tmp/youtube-dl
    source /tmp/youtube-dl/bin/activate
    # pip install --upgrade --force-reinstall "git+https://github.com/ytdl-org/youtube-dl.git"
    pip install --upgrade --force-reinstall "git+https://github.com/yt-dlp/yt-dlp.git"
  fi
  #pip install youtube-dl
  /tmp/youtube-dl/bin/yt-dlp $@
}

webm to mp3

function webm2mp3()
{
    for f in *.webm; do
        if [ ! -f ${f/webm/mp3} ]; then
            pkgtools::msg_notice "Converting $f..."
            ffmpeg -i "$f" -ab 160k -ar 44100 "${f/webm/mp3}"
            new_name=$(echo $f | sed 's/\(.*\)\( \[.*\)/\1.mp3/')
            mv "${f/webm/mp3}" "${new_name}"
        fi
    done 
}

Misc.

Grabbing video from mms link

function grab_video ()
{
    pkgtools::at_function_enter grab_video
    if [[ ! -z $1 ]] ; then
        pkgtools::msg_notice "Grabing video from $1 link and saving it to /tmp/dump_video.avi..."
        mplayer -dumpstream "$1" -dumpfile /tmp/dump_video.avi
    else
        pkgtools::msg_error "Missing mms link !"
        pkgtools::at_function_exit
        return 1
    fi
    pkgtools::at_function_exit
    return 0
}

Create backup file or directory

function bak ()
{
    pkgtools::at_function_enter bak
    if [ -z $1 ]; then
        pkgtools::msg_error "Missing file or directory name !"
        pkgtools::at_function_exit
        return 1
    fi
    [ -f $1 ] && cp $1 $1.bak
    [ -d $1 ] && cp -r $1 $1.bak
    pkgtools::at_function_exit
    return 0
}
function unbak ()
{
    pkgtools::at_function_enter unbak
    if [ -z $1 ]; then
        pkgtools::msg_error "Missing file or directory name !"
        pkgtools::at_function_exit
        return 1
    fi
    if  [[ ${1##*.} != "bak" ]]; then
        pkgtools::msg_error "$1 is not a backup copy !"
        pkgtools::at_function_exit
        return 1
    fi
    unbak_name=${1/.bak/}
    if [[ -f ${unbak_name} || -d ${unbak_name} ]]; then
      pkgtools::msg_warning "File or directory ${unbak_name} already exists !"
      return 1
    fi
    [ -f $1 ] && mv $1 ${1/.bak/}
    [ -d $1 ] && mv $1 ${1/.bak/}
    pkgtools::at_function_exit
    return 0
}

Grab picture of University Paris XI student

function search_student ()
{
    pkgtools::at_function_enter search_student
    if [ -z $1 ]; then
        pkgtools::msg_error "Missing student name !"
        pkgtools::at_function_exit
        return 1
    elif [[ $1 == "--help" || $1 == "-h" ]]; then
      pkgtools::msg_notice "Usage : search_student \"firstname lastname\""
      pkgtools::at_function_exit
      return 0
    fi

    adonis_http="http://adonis.u-psud.fr/users"
    adonis_pics_path="/tmp/adonis_pics"
    mkdir -p ${adonis_pics_path}

    names=$(echo $1 | sed 's/ /./g')
    for name in ${names}
    do
        nname=$(echo $name | iconv -f utf8 -t ascii//TRANSLIT)
        prefix="Getting picture for $nname"
        if [ -f ${adonis_pics_path}/${nname}.jpg ]; then
            pkgtools::msg_notice "${prefix} -> already downloaded"
            xdg-open ${adonis_pics_path}/${nname}.jpg &
            continue
        fi
        for i in 0 1 2 3
        do
            http_path=${adonis_http}/sl$i/${nname:0:1}/${nname}/
            wget -r --no-parent -A "*.jpg" ${http_path} -P /tmp > /dev/null 2>&1
            if [ $? -eq 0 ]; then
                pkgtools::msg_notice "${prefix} -> picture found"
                ls -1 /tmp/${http_path/http:\/\//}/*.jpg | tail -n1 | xargs -i cp {} ${adonis_pics_path}/${nname}.jpg
                xdg-open ${adonis_pics_path}/${nname}.jpg &
                break
            elif [ $i -eq 3 ]; then
                pkgtools::msg_error "${prefix} -> picture not found"
                pkgtools::at_function_exit
                return 1
            fi
        done
    done
    pkgtools::at_function_exit
    return 0
}

Kill offlineimap

function kill_offlineimap()
{
    pids=$(psgrep offlineimap | awk '{print $2}')
    for pid in ${=pids}; do
        kill -9 $pid
    done
}

Get current IP

function get_ip()
{
    dig +short myip.opendns.com @resolver1.opendns.com
}

Activate virtual env

function activate-venv()
{
    dir=pyenv
    while [ -n "$1" ]; do
        token="$1"
        if [[ "${token[1]}" = "-" ]]; then
            opt=${token}
            if [ "${opt}" = "--dir" ]; then
                shift 1
                dir="$1"
            else
                pkgtools::msg_warning "Ignoring option '${opt}' !"
            fi
        else
            arg=${token}
        fi
        shift
    done
    source ${dir}/bin/activate
}

Create python virtual env

function create-venv()
{
    dir=pyenv
    install_scientific=false
    while [ -n "$1" ]; do
        token="$1"
        if [[ "${token[1]}" = "-" ]]; then
            opt=${token}
            if [ "${opt}" = "--dir" ]; then
                shift 1
                dir="$1"
            elif [ "${opt}" = "--install-scientific" ]; then
                install_scientific=true
            else
                pkgtools::msg_warning "Ignoring option '${opt}' !"
            fi
        else
            arg=${token}
        fi
        shift 1
    done

    if [ -d ${dir} ]; then
        pkgtools::msg_error "Directory '${dir}' already exists"
        return 1
    fi

    python -m venv ${dir}
    source ${dir}/bin/activate
    python -m pip install -U pip wheel
    pkgtools::msg_info "Virtual env. created and sourced!"
}

Update python virtual env

function upgrade-venv()
{
  pkgtools::msg_info "Upgrading pip first"
  python -m pip install --upgrade pip
  while read -r line; do
    if [[ $line == *"@"* ]]; then
      continue
    fi
    name=$(echo $line | cut -d= -f1)
    pkgtools::msg_info "Upgrading ${name}..."
    python -m pip install --upgrade ${name} | grep --color=never --line-buffered -E -v "Requirement already satisfied:|Defaulting to user"
  done <<< $(pip freeze | grep -E -v "#|^-e")
  pkgtools::msg_info "Upgrade python env done"
}
function upgrade-science()
{
  python -m pip install --upgrade numpy pandas scipy matplotlib seaborn $@
  pkgtools::msg_info "Upgrade python env done"
}

Fix zsh corrupt file

function fix_zsh_history()
{
  pkgtools::msg_info "Fix zsh corrupt file..."
  mv ~/.zsh_history ~/.zsh_history.bad
  strings ~/.zsh_history.bad > ~/.zsh_history
  fc -R ~/.zsh_history
  rm ~/.zsh_history.bad
}