diff --git a/README.rst b/README.rst index 764727d..f798044 100644 --- a/README.rst +++ b/README.rst @@ -32,6 +32,11 @@ completion:: $ h5glance sample.h5 - Object path: sample.h5/ # try tapping tab +Or run ``python -m h5glance.completer`` to install tab completion hooks for bash +and zsh. + +### HTML interface + The HTML interface lets you inspect HDF5 files in a Jupyter Notebook. `Demo.ipynb `_ shows how to use it. diff --git a/h5glance/completer.py b/h5glance/completer.py new file mode 100644 index 0000000..a1af101 --- /dev/null +++ b/h5glance/completer.py @@ -0,0 +1,40 @@ +"""Shell integration to tab complete paths inside an HDF5 file""" +import os +import shutil +import subprocess + +pjoin = os.path.join +pkgdir = os.path.dirname(os.path.abspath(__file__)) + +def install_hooks(): + """Install hooks for bash & zsh to complete paths in files""" + data_home = os.environ.get('XDG_DATA_HOME', '') \ + or os.path.expanduser('~/.local/share') + + # Bash + if shutil.which('bash'): + src = pjoin(pkgdir, 'completion.bash') + dst_dir = pjoin(data_home, 'bash-completion/completions') + os.makedirs(dst_dir, exist_ok=True) + dst = pjoin(dst_dir, 'h5glance') + shutil.copy(src, dst) + print("Copied", dst) + + # Zsh + if shutil.which('zsh'): + src = pjoin(pkgdir, 'completion.zsh') + dst_dir = pjoin(data_home, 'zsh-completions') + os.makedirs(dst_dir, exist_ok=True) + dst = pjoin(dst_dir, '_h5glance') + shutil.copy(src, dst) + print("Copied", dst) + + stdout = subprocess.check_output(['zsh', '-i', '-c', 'echo $FPATH']) + if dst_dir not in stdout.decode('utf-8').split(':'): + with open(os.path.expanduser('~/.zshrc'), 'a') as f: + f.write('\nfpath=("{}" $fpath)\ncompinit\n'.format(dst_dir)) + print("Added {} to fpath in ~/.zshrc".format(dst_dir)) + + +if __name__ == '__main__': + install_hooks() diff --git a/h5glance/completion.bash b/h5glance/completion.bash new file mode 100755 index 0000000..cc05903 --- /dev/null +++ b/h5glance/completion.bash @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +_h5glance() +{ + local cur prev opts base + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + # Complete options + if [[ ${cur} = -* ]]; then + opts="-h --help --version --attrs" + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + + # Complete paths inside file + if [[ -f ${prev} ]]; then + local grouppath="" + if [[ ${cur} =~ / ]]; then + # Hack: 'dirname a/b/' returns 'a'. The trailing x makes it 'a/b'. + grouppath=$(dirname "${cur}x")/ + fi + + # List entries in the group, add the group path and a / suffix for + # subgroups, and case-insensitively filter them against the text entered. + COMPREPLY=($(h5ls --simple "${prev}/${grouppath}" \ + | awk -v g="${grouppath}" \ + '{sfx=" "; if ($2 == "Group") sfx="/"; print g $1 sfx}' \ + | awk -v IGNORECASE=1 -v p="${cur}" \ + 'p==substr($0,0,length(p))' \ + ) ) + return 0 + fi +} + +complete -o default -o nospace -F _h5glance h5glance diff --git a/h5glance/completion.zsh b/h5glance/completion.zsh new file mode 100644 index 0000000..394afbf --- /dev/null +++ b/h5glance/completion.zsh @@ -0,0 +1,49 @@ +#compdef _h5glance h5glance + +function _h5glance { + local curcontext="$curcontext" + local context state state_descr line + typeset -A opt_args + + _arguments -C \ + "-h[Show help information]" \ + "--help[Show help information]" \ + "--version[Show version number]" \ + "--attrs[Show attributes of groups]" \ + ":HDF5 file:_files" \ + ":path in file:->infile" + + case "$state" in + infile) + declare -a matches + local grouppath="" + if [[ ${line[2]} =~ / ]]; then + # Hack: 'dirname a/b/' returns 'a'. The trailing x makes it 'a/b'. + grouppath=$(dirname "${line[2]}x")/ + fi + + # List entries in the group, add the group path and a / suffix for + # subgroups, and case-insensitively filter them against the text entered. + matches=($(h5ls --simple "${line[1]}/${grouppath}" \ + | awk -v g="${grouppath}" \ + '{s=""; if ($2 == "Group") s="/"; print g $1 s}' \ + | awk -v IGNORECASE=1 -v p="${line[2]}" \ + 'p==substr($0,0,length(p))' )) + + # Code below by Xavier Delaruelle, on StackOverflow. + # https://stackoverflow.com/a/53907053/434217 + # Used under SO's default CC-BY-SA-3.0 license. + local suffix=' '; + # do not append space to word completed if it is a directory (ends with /) + for val in $matches; do + if [ "${val: -1:1}" = '/' ]; then + suffix='' + break + fi + done + + # The -M match-spec argument allows case-insensitive matches + compadd -S "$suffix" -M 'm:{a-zA-Z}={A-Za-z}' -a matches + ;; + esac +} diff --git a/h5glance/copypath.js b/h5glance/copypath.js index 8f58160..38e1938 100644 --- a/h5glance/copypath.js +++ b/h5glance/copypath.js @@ -64,7 +64,7 @@ } function enable_copylinks(parent) { - let links = parent.querySelectorAll(".h5glance-dataset-copylink") + let links = parent.querySelectorAll(".h5glance-dataset-copylink"); links.forEach(function (link) { link.addEventListener("click", copy_event_handler); });