PKL+Mh*{{h5glance/__init__.py"""Explore HDF5 files in an HTML view""" from .generate import H5Glance, install_ipython_h5py_display __version__ = "0.2" PKy+ML""h5glance/__main__.pyfrom .terminal import main main() PKZLȢg g 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 })(); PKZLvGuuh5glance/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{x+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+M[~@@@h5glance/terminal.py"""Terminal h5glance interface for inspecting HDF5 files """ import argparse import h5py 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_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 print_dataset_info(ds: h5py.Dataset, file=None): """Print detailed information for an HDF5 dataset.""" print(' dtype:', ds.dtype.name, 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]) 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) def detail_for(obj, link): """Detail for an HDF5 object, to display by its name in the tree view.""" if isinstance(link, h5py.SoftLink): return '\t-> {}'.format(link.path) elif isinstance(link, h5py.ExternalLink): return '\t-> {}/{}'.format(link.filename, link.path) if isinstance(obj, h5py.Dataset): detail = '\t[{dt}: {shape}]'.format( dt=obj.dtype.name, shape=fmt_shape(obj.shape), ) if obj.id.get_create_plist().get_layout() == h5py.h5d.VIRTUAL: detail += ' virtual' return detail elif isinstance(obj, h5py.Group): return '' else: return ' (unknown h5py type)' def print_paths(group, prefix='', file=None): """Visit and print name of all element in HDF5 file (from S Hauf)""" nkeys = len(group.keys()) for i, (k, obj) in enumerate(group.items()): islast = (nkeys == i + 1) link = group.get(k, getlink=True) print(prefix, ('└' if islast else '├'), k, detail_for(obj, link), sep='', file=file) if isinstance(obj, h5py.Group): print_paths(obj, prefix + (' ' if islast else '│ '), 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): """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: print(file.filename + '/' + path.lstrip('/'), file=sio) obj = file[path] else: print(file.filename, file=sio) obj = file if isinstance(obj, h5py.Group): print_paths(obj, file=sio) elif isinstance(obj, h5py.Dataset): print_dataset_info(obj, file=sio) else: print("What is this?", repr(obj), file=sio) # 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('--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) PKZL77h5glance/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.2.dist-info/entry_points.txtN+I/N.,()0MIKN1JRr3sr3%9E ^|rN&DPKZL}  h5glance-0.2.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!HNOh5glance-0.2.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,zd&Y)r$[)T&UrPK!HD&ch5glance-0.2.dist-info/METADATATR@=/\ZAE8RƱt$R`M|REf̏6{eᄑStb$>RlMF\ yULu B{؆т,AD -R9m0L˪'Ϋz&\T: _glr2 2U dzB|3 5+ij‰NYmbvOJ .: kX"US(#ڨ |{/uu^;]<Œ^],Ԫ|PO!13,E,D݌E1A^ΈA"2ixPQÃNױl| 艜0)*R. bc Pc }QĚN+ # e{fg}r9Εp#؀?WjL<ԨJ{--%cx8B3r6X 1' ҖW,Gi+j./N˭_ln[e6yـJ~98r~\DҵWc9vP\꺌՝ےk+*sq vߘԍP$, PK!Hv@Ch5glance-0.2.dist-info/RECORDu˒0}? rYЀW&!\BTg6S'W2RCh \ǢYFͥm*.\ZA]ţR!'W.1".u#/xkjeXy$n&f=)?w_1:'r<̯⣎U+1o]dmL^Ԉ @{>}f:z0S!'Orv)QZ7,W+j' jzd&ƏKmO;NPe1L/7L|֏?cm>̦4ԅbӟ`79N w\ Fш4:0c'v5-dN8::߸bt@MM*֍X~" ;(JKL9˦ހ~6E*.8>°D$b)}r?xH{5p#x#{[Db)Lv79]NFX`xF'ZnT]`$P.UZRcN)|zXzcr_PKL+Mh*{{h5glance/__init__.pyPKy+ML""h5glance/__main__.pyPKZLȢg g h5glance/copypath.jsPKZLvGuu h5glance/generate.pyPK{x+M mmAh5glance/html_cli.pyPK+M[~@@@%h5glance/terminal.pyPKZL77R>h5glance/treeview.cssPK!Hnދ>X'Eh5glance-0.2.dist-info/entry_points.txtPKZL}  ?Fh5glance-0.2.dist-info/LICENSEPK!HNOLh5glance-0.2.dist-info/WHEELPK!HD&cMh5glance-0.2.dist-info/METADATAPK!Hv@CPh5glance-0.2.dist-info/RECORDPK RR