PKWN<{{h5glance/__init__.py"""Explore HDF5 files in an HTML view""" from .generate import H5Glance, install_ipython_h5py_display __version__ = "0.5" PK>ML""h5glance/__main__.pyfrom .terminal import main main() PKWNmܨOOh5glance/completer.py"""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() PKWN&TC__h5glance/completion.bash#!/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 PKWN۵@~h5glance/completion.zsh#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 } PKGNmh h h5glance/copypath.js// Based on code by Stackoverflow user Dean Taylor // https://stackoverflow.com/questions/400212/how-do-i-copy-to-the-clipboard-in-javascript/30810322 // Used under Stackoverflow's CC-BY-SA 3.0 license (function() { function copyTextToClipboard(text) { let textArea = document.createElement("textarea"); // // *** This styling is an extra step which is likely not required. *** // // Why is it here? To ensure: // 1. the element is able to have focus and selection. // 2. if element was to flash render it has minimal visual impact. // 3. less flakyness with selection and copying which **might** occur if // the textarea element is not visible. // // The likelihood is the element won't even render, not even a flash, // so some of these are just precautions. However in IE the element // is visible whilst the popup box asking the user for permission for // the web page to copy to the clipboard. // // Place in top-left corner of screen regardless of scroll position. textArea.style.position = 'fixed'; textArea.style.top = 0; textArea.style.left = 0; // Ensure it has a small width and height. Setting to 1px / 1em // doesn't work as this gives a negative w/h on some browsers. textArea.style.width = '2em'; textArea.style.height = '2em'; // We don't need padding, reducing the size if it does flash render. textArea.style.padding = 0; // Clean up any borders. textArea.style.border = 'none'; textArea.style.outline = 'none'; textArea.style.boxShadow = 'none'; // Avoid flash of white box if rendered for any reason. textArea.style.background = 'transparent'; textArea.value = text; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { if (!document.execCommand('copy')) { console.log("Unable to copy text with document.execCommand()"); } } finally { document.body.removeChild(textArea); } } function copy_event_handler(event) { copyTextToClipboard(event.target.dataset.hdf5Path); event.preventDefault(); } function enable_copylinks(parent) { let links = parent.querySelectorAll(".h5glance-dataset-copylink"); links.forEach(function (link) { link.addEventListener("click", copy_event_handler); }); } // The code to actually trigger this is substituted below. //ACTIVATE })(); PKJLvGuuh5glance/generate.pyimport h5py from htmlgen import (Document, Element, Division, UnorderedList, Checkbox, Label, ListItem, html_attribute, Span, Link, Script, ) from pathlib import Path _PKGDIR = Path(__file__).parent class Style(Element): def __init__(self, styles): super().__init__('style') self.children.append_raw(styles) class Abbreviation(Element): def __init__(self, *content, title=""): super().__init__("abbr") self.extend(content) self.title = title title = html_attribute('title') class Code(Element): """An HTML in-line element. """ def __init__(self, *content): super().__init__("code") self.extend(content) def id_generator(template): n = 0 while True: yield template % n n += 1 dtype_kind_to_descr = { "u": "unsigned integer", "i": "signed integer", "f": "floating point", "c": "complex floating point", "b": "boolean", "O": "object (e.g. string)", } def make_dtype_abbr(dtobj): desc = "" if dtobj.kind in "uifc": desc += "{}-bit ".format(dtobj.itemsize * 8) try: desc += dtype_kind_to_descr[dtobj.kind] except KeyError: pass code = Code(dtobj.str) if desc: return Abbreviation(code, title=desc) return code def make_list(*items): ul = UnorderedList() ul.extend(items) return ul checkbox_ids = id_generator("h5glance-expand-switch-%d") def checkbox_w_label(label_content): c = Checkbox() c.id = next(checkbox_ids) l = Label(label_content) l.for_ = c.id return [c, l] def item_for_dataset(name, ds): shape = " × ".join(str(n) for n in ds.shape) namespan = Span(name) namespan.add_css_classes("h5glance-dataset-name") copylink = Link("#", "[📋]") copylink.set_attribute("data-hdf5-path", ds.name) copylink.add_css_classes("h5glance-dataset-copylink") li = ListItem( namespan, " ", copylink, ": ", shape, " entries, dtype: ", make_dtype_abbr(ds.dtype) ) li.add_css_classes("h5glance-dataset") return li def item_for_group(gname, grp): subgroups, datasets = [], [] for name, obj in sorted(grp.items()): if isinstance(obj, h5py.Group): subgroups.append((name, obj)) else: datasets.append((name, obj)) return ListItem(*checkbox_w_label(gname), make_list( *[item_for_group(n, g) for n, g in subgroups], *[item_for_dataset(n, d) for n, d in datasets], )) def file_or_grp_name(obj): if isinstance(obj, h5py.File): return obj.filename elif isinstance(obj, h5py.Group): return obj.name return obj treeview_ids = id_generator("h5glance-container-%d") def make_fragment(obj): if isinstance(obj, h5py.Group): name = file_or_grp_name(obj) ct = make_list(item_for_group(name, obj)) elif isinstance(obj, (str, Path)) and h5py.is_hdf5(obj): with h5py.File(obj, 'r') as f: return make_fragment(f) else: raise TypeError("Unknown object type: {!r}".format(obj)) # Expand first level first_chkbx = ct.children.children[0].children.children[0] # Yuck first_chkbx.checked = True tv = Division(ct) tv.add_css_classes("h5glance-css-treeview") tv.id = next(treeview_ids) return tv def get_treeview_css(): with (_PKGDIR / 'treeview.css').open() as f: return Style(f.read()) JS_ACTIVATE_COPYLINKS_DOC = """ window.addEventListener("load", function(event) { enable_copylinks(document); }); """ JS_ACTIVATE_COPYLINKS_FRAG = """ enable_copylinks(document.getElementById("TREEVIEW-ID")); """ def get_copylinks_js(activation): with (_PKGDIR / "copypath.js").open() as f: return f.read().replace("//ACTIVATE", activation) def make_document(obj): d = Document() d.append_head(get_treeview_css()) d.append_head(Script(script=get_copylinks_js(JS_ACTIVATE_COPYLINKS_DOC))) d.title = "{} - H5Glance".format(file_or_grp_name(obj)) d.append_body(make_fragment(obj)) return d def h5obj_to_html(obj): treeview = make_fragment(obj) js_activate = JS_ACTIVATE_COPYLINKS_FRAG.replace("TREEVIEW-ID", treeview.id) div = Division( get_treeview_css(), treeview, Script(script=get_copylinks_js(js_activate)), ) return str(div) class H5Glance: """View an HDF5 object in a Jupyter notebook""" def __init__(self, obj): self.obj = obj def _repr_html_(self): return h5obj_to_html(self.obj) def install_ipython_h5py_display(): """Call inside IPython to install HTML views for h5py groups and files""" from IPython import get_ipython ip = get_ipython() if ip is None: raise EnvironmentError("This function is to be called in IPython") html_formatter = ip.display_formatter.formatters['text/html'] html_formatter.for_type(h5py.Group, h5obj_to_html) PK>M mmh5glance/html_cli.py"""Command line h5glance-html interface for writing and serving HTML views of HDF5 """ import argparse import h5py from http.server import HTTPServer, BaseHTTPRequestHandler from pathlib import Path import sys import threading import webbrowser from .generate import make_document def main(argv=None): ap = argparse.ArgumentParser(prog="h5glance", description="View HDF5 file structure in HTML") ap.add_argument("input", help="HDF5 file to view", type=Path) ap.add_argument("-w", "--write", metavar="HTML_FILE", help="Write output to HTML file.") args = ap.parse_args(argv) if not args.input.is_file(): print("Not a file:", args.input) sys.exit(2) elif not h5py.is_hdf5(args.input): print("Not an HDF5 file:", args.input) sys.exit(2) if args.write: with open("test_output.html", 'w') as f: f.write(str(make_document(args.input))) return serve(args.input) def serve(h5path): class H5ViewHandler(BaseHTTPRequestHandler): def do_GET(self): if self.path != "/": return self.send_error(404) self.send_response(200) self.end_headers() self.wfile.write(str(make_document(h5path)).encode('utf-8')) server = HTTPServer(('localhost', 0), H5ViewHandler) url = "http://{}:{}/".format(server.server_name, server.server_port) print("Serving on", url) t = threading.Timer(0.5, webbrowser.open_new_tab, args=(url,)) t.start() try: server.serve_forever() except KeyboardInterrupt: pass PK%UNk&&h5glance/terminal.py"""Terminal h5glance interface for inspecting HDF5 files """ import argparse import h5py import h5py.h5o import io import os import numpy from pathlib import Path import shlex from shutil import get_terminal_size from subprocess import run import sys def fmt_dtype(dtype): if dtype.metadata and 'vlen' in dtype.metadata: base_dtype = dtype.metadata['vlen'] if base_dtype is str: return 'UTF-8 str' elif base_dtype is bytes: return 'ASCII str' else: return 'vlen {}'.format(fmt_dtype(base_dtype)) elif dtype.fields: fields = ['{}: {}'.format(name, fmt_dtype(dtype.fields[name][0])) for name in dtype.names] return '({})'.format(', '.join(fields)) else: return dtype.name def fmt_shape(shape): return " × ".join(('Unlimited' if n is None else str(n)) for n in shape) layout_names = { h5py.h5d.COMPACT: 'Compact', h5py.h5d.CONTIGUOUS: 'Contiguous', h5py.h5d.CHUNKED: 'Chunked', h5py.h5d.VIRTUAL: 'Virtual', } def fmt_attr(v): if isinstance(v, numpy.ndarray): sv = 'array [{}: {}]'.format(fmt_dtype(v.dtype), fmt_shape(v.shape)) else: sv = repr(v) if len(sv) > 50: sv = sv[:20] + '...' + sv[-20:] return sv def print_dataset_info(ds: h5py.Dataset, file=None): """Print detailed information for an HDF5 dataset.""" print(' dtype:', fmt_dtype(ds.dtype), file=file) print(' shape:', fmt_shape(ds.shape), file=file) print(' maxshape:', fmt_shape(ds.maxshape), file=file) layout = ds.id.get_create_plist().get_layout() print(' layout:', layout_names.get(layout, 'Unknown'), file=file) if layout == h5py.h5d.CHUNKED: print(' chunk:', fmt_shape(ds.chunks), file=file) print('compression: {} (options: {})' .format(ds.compression, ds.compression_opts), file=file) if sys.stdout.isatty(): numpy.set_printoptions(linewidth=get_terminal_size()[0]) if ds.size > 0: print('\nsample data:', file=file) if ds.ndim == 0: print(ds[()], file=file) elif ds.ndim == 1: print(ds[:10], file=file) else: select = (0,) * (ds.ndim - 2) + (slice(0, 10),) * 2 print(ds[select], file=file) print('\n{} attributes:'.format(len(ds.attrs)), file=file) for k, v in ds.attrs.items(): print('* ', k, ': ', fmt_attr(v), sep='', file=file) class ColorsNone: dataset = group = link = reset = '' class ColorsDefault: dataset = '\x1b[1m' # Bold group = '\x1b[94m' # Bright blue link = '\x1b[95m' # Bright magenta reset = '\x1b[0m' def use_colors(): if os.name != 'posix': return False env = os.environ.get('H5GLANCE_COLORS', '') if env: return env != '0' return sys.stdout.isatty() class TreeViewBuilder: """Build a tree view of an HDF5 group or file The tree nodes are tuples (line, children). """ def __init__(self, expand_attrs=False): self.expand_attrs = expand_attrs if use_colors(): self.colors = ColorsDefault else: self.colors = ColorsNone self.visited = dict() def object_node(self, obj, name): """Build a tree node for an HDF5 group/dataset """ color_stop = self.colors.reset if isinstance(obj, h5py.Dataset): color_start = self.colors.dataset elif isinstance(obj, h5py.Group): color_start = self.colors.group else: color_start = '' obj_id = h5py.h5o.get_info(obj.id).addr if obj_id in self.visited: # Hardlink to an object we've seen before first_link = self.visited[obj_id] return (color_start + name + color_stop + '\t= ' + first_link), [] # An object we haven't seen before self.visited[obj_id] = obj.name children = [] detail = attr_detail = '' if self.expand_attrs: children += attrs_tree_nodes(obj) else: n = len(obj.attrs) attr_detail = ' ({} attributes)'.format(n) if n else '' if isinstance(obj, h5py.Dataset): detail = '\t[{dt}: {shape}]'.format( dt=fmt_dtype(obj.dtype), shape=fmt_shape(obj.shape), ) if obj.id.get_create_plist().get_layout() == h5py.h5d.VIRTUAL: detail += ' virtual' elif isinstance(obj, h5py.Group): children += [self.group_item_node(obj, key) for key in obj] else: detail = ' (unknown h5py type)' return (color_start + name + color_stop + detail + attr_detail), children def group_item_node(self, group, key): """Build a tree node for one key in a group""" link = group.get(key, getlink=True) if isinstance(link, h5py.SoftLink): target = link.path elif isinstance(link, h5py.ExternalLink): target = '{}/{}'.format(link.filename, link.path) else: return self.object_node(group[key], key) line = '{}{}{}\t-> {}'.format( self.colors.link, key, self.colors.reset, target) return line, [] def attrs_tree_nodes(obj): """Build tree nodes for attributes""" nattr = len(obj.attrs) if not nattr: return [] children = [('{}: {}'.format(k, fmt_attr(v)), []) for (k, v) in obj.attrs.items()] return [('{} attributes:'.format(nattr), children)] def print_tree(node, prefix1='', prefix2='', file=None): """Render a tree to show in the terminal. Each tree node consists of a line of text to be displayed and a list of child nodes. """ root, children = node print(prefix1 + root, file=file) nchild = len(children) for i, node in enumerate(children): islast = (nchild == i + 1) c_prefix1 = prefix2 + ('└' if islast else '├') c_prefix2 = prefix2 + (' ' if islast else '│ ') print_tree(node, prefix1=c_prefix1, prefix2=c_prefix2, file=file) def page(text): """Display text in a terminal pager Respects the PAGER environment variable if set. """ pager_cmd = shlex.split(os.environ.get('PAGER') or 'less -r') run(pager_cmd, input=text.encode('utf-8')) def display_h5_obj(file: h5py.File, path=None, expand_attrs=False): """Display information on an HDF5 file, group or dataset This is the central function for the h5glance command line tool. """ sio = io.StringIO() if path: root = file.filename + '/' + path.lstrip('/') obj = file[path] else: root = file.filename obj = file if isinstance(obj, h5py.Group): tvb = TreeViewBuilder(expand_attrs=expand_attrs) print_tree(tvb.object_node(obj, root), file=sio) elif isinstance(obj, h5py.Dataset): print(root, file=sio) print_dataset_info(obj, file=sio) else: sys.exit("What is this? " + repr(obj)) # If the output has more lines than the terminal, display it in a pager output = sio.getvalue() if sys.stdout.isatty(): nlines = len(output.splitlines()) _, term_lines = get_terminal_size() if nlines > term_lines: return page(output) print(output) class H5Completer: """Readline tab completion for paths inside an HDF5 file""" def __init__(self, file: h5py.File): self.file = file self.cache = (None, []) def completions(self, text: str): if text == self.cache[0]: return self.cache[1] prev_path, _, prefix = text.rpartition('/') group = self.file if prev_path: group = self.file[prev_path] prev_path += '/' res = [prev_path + k + ('/' if isinstance(v, h5py.Group) else '') for (k, v) in group.items() if k.lower().startswith(prefix.lower())] self.cache = (text, res) return res def rlcomplete(self, text: str, state: int): # print(repr(text), state) try: res = self.completions(text) except Exception as e: #print(e) return None # print(repr(text), len(res)) if state >= len(res): return None return res[state] def prompt_for_path(filename): """Prompt the user for a path inside the HDF5 file""" import readline with h5py.File(filename, 'r') as f: compl = H5Completer(f) readline.set_completer(compl.rlcomplete) readline.set_completer_delims('') readline.parse_and_bind("tab: complete") while True: res = input("Object path: {}/".format(filename)) if f.get(res) is not None: print() return res print("No object at", repr(res)) def main(argv=None): from . import __version__ ap = argparse.ArgumentParser(prog="h5glance", description="View HDF5 file structure in the terminal") ap.add_argument("file", help="HDF5 file to view", type=Path) ap.add_argument("path", nargs='?', help="Object to show within the file, or '-' to prompt for a name" ) ap.add_argument('--attrs', action='store_true', help="Show attributes of groups", ) ap.add_argument('--version', action='version', version='H5glance {}'.format(__version__)) args = ap.parse_args(argv) if not args.file.is_file(): print("Not a file:", args.file) sys.exit(2) elif not h5py.is_hdf5(args.file): print("Not an HDF5 file:", args.file) sys.exit(2) path = args.path if path == '-': path = prompt_for_path(args.file) with h5py.File(args.file, 'r') as f: display_h5_obj(f, path, expand_attrs=args.attrs) PKJL77h5glance/treeview.css/* Based on https://css-tricks.com/snippets/css/nested-expandable-folders/ Reused with permission from https://css-tricks.com/license/ */ .h5glance-css-treeview ul, .h5glance-css-treeview li { padding: 0; margin: 0; margin-left: 12px; list-style: none; } /* Override padding from Jupyter CSS */ .rendered_html .h5glance-css-treeview ul { padding: 0; } /* Make the checkbox itself invisible */ .h5glance-css-treeview input { position: absolute; opacity: 0; } .h5glance-css-treeview { font-family: sans-serif; -moz-user-select: none; -webkit-user-select: none; user-select: none; } .h5glance-css-treeview code { font-size: 12pt; font-family: "courier", monospace; } .h5glance-css-treeview a.h5glance-dataset-copylink { text-decoration: none; } /* These next two bits drive the expanding/collapsing behaviour */ .h5glance-css-treeview input + label + ul { display: none; } .h5glance-css-treeview input:checked:not(:disabled) + label + ul { display: block; } .h5glance-css-treeview label, .h5glance-css-treeview label::before { display: inline-block; height: 16px; line-height: 16px;, vertical-align: middle; } .h5glance-css-treeview label { margin-left: -1.5em; } .h5glance-css-treeview label::before { content: "+"; width: 1.5em; vertical-align: middle; } .h5glance-css-treeview input:checked + label::before { content: "–"; } /* webkit adjacent element selector bugfix */ @media screen and (-webkit-min-device-pixel-ratio:0) { .h5glance-css-treeview { -webkit-animation: webkit-adjacent-element-selector-bugfix infinite 1s; } @-webkit-keyframes webkit-adjacent-element-selector-bugfix { from { padding: 0; } to { padding: 0; } } } .h5glance-css-treeview li.h5glance-dataset { margin-bottom: 3px; } .h5glance-dataset-name { font-weight: bold; } PK!Hnދ>X'h5glance-0.5.dist-info/entry_points.txtN+I/N.,()0MIKN1JRr3sr3%9E ^|rN&DPKJL}  h5glance-0.5.dist-info/LICENSEBSD 3-Clause License Copyright (c) 2017, European X-Ray Free-Electron Laser Facility GmbH All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. PK!HPOh5glance-0.5.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,szd&Y)r$[)T&UrPK!HapI\xh5glance-0.5.dist-info/METADATATmr6bg_n9uZrb'ԉ3LR+10(=Gfzޤ'% ޾LxI38l,j̠J&vֵ]w2b|sЁ 4\\B]F!Ҵjs^:Vmi$ ԎOWfp]Z8xa`-lF޲7][GqO +O|3=j\w ]Χwu)᜜Kא `ru Mcgajx^7=_5c{{(:~QO_!!B}%AsQ gsuPy+b!]RHnl4(tFm/| ɼ%.ȩ xMfN/XiD( vW~_︒q'd}t3WF'zk.w$>ִ#Yg/*AaimR:`s*{KώH9(RcYn^0Ix$ߞ}Ӕ#жt&O2 eo Rd~ml/{if5& akHvv#q*5>|{qXTIqQ])eo]1vn vL6H9@0@ *$,Eմ7ٴ)k 툎Ar ƾ X;pP{E LRdqjڬQuI2乌E5y1Z6,csCuEy,_J+.+0X-ez@խTFx/ = Ԉ(XS\EjqgTGm AM7jT hhY-x:dz~kmXWKjU`3$I_;@sivnh/FՕɕ^j?#+y a Sc+S<%OږKg\GBPKWN<{{h5glance/__init__.pyPK>ML""h5glance/__main__.pyPKWNmܨOOh5glance/completer.pyPKWN&TC__큃h5glance/completion.bashPKWN۵@~ h5glance/completion.zshPKGNmh h h5glance/copypath.jsPKJLvGuuh5glance/generate.pyPK>M mm&1h5glance/html_cli.pyPK%UNk&&7h5glance/terminal.pyPKJL77^h5glance/treeview.cssPK!Hnދ>X':fh5glance-0.5.dist-info/entry_points.txtPKJL}  fh5glance-0.5.dist-info/LICENSEPK!HPOmh5glance-0.5.dist-info/WHEELPK!HapI\xmh5glance-0.5.dist-info/METADATAPK!H{(qh5glance-0.5.dist-info/RECORDPK .t