nix-archive-1(type directoryentry(namebinnode(type directoryentry(name .jtbl-realnode(typeregular executablecontents#!/gnu/store/vp43y5f3w5vqaash0f5j4rawz3bqglm5-python-wrapper-3.10.7/bin/python # EASY-INSTALL-ENTRY-SCRIPT: 'jtbl==1.1.7','console_scripts','jtbl' import re import sys # for compatibility with easy_install; see #2198 __requires__ = 'jtbl==1.1.7' try: from importlib.metadata import distribution except ImportError: try: from importlib_metadata import distribution except ImportError: from pkg_resources import load_entry_point def importlib_load_entry_point(spec, group, name): dist_name, _, _ = spec.partition('==') matches = ( entry_point for entry_point in distribution(dist_name).entry_points if entry_point.group == group and entry_point.name == name ) return next(matches).load() globals().setdefault('load_entry_point', importlib_load_entry_point) if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) sys.exit(load_entry_point('jtbl==1.1.7', 'console_scripts', 'jtbl')()) ))entry(namejtblnode(typeregular executablecontents#!/gnu/store/h1ap2ldl46a57fa3978w6pdbrq3zhzks-bash-minimal-5.1.16/bin/bash export GUIX_PYTHONPATH="/gnu/store/9mcmrnvqh9q4r9zs8v46sx5frjnmlxy6-jtbl-1.1.7/lib/python3.10/site-packages:/gnu/store/f45zjbsxcv4d65sk6aqcg13bsw4xbjwc-python-tabulate-0.9.0/lib/python3.10/site-packages:/gnu/store/4fx1islsjlayh19y509m17bhsrg7bfwd-python-3.10.7/lib/python3.10/site-packages${GUIX_PYTHONPATH:+:}$GUIX_PYTHONPATH" exec -a "$0" "/gnu/store/9mcmrnvqh9q4r9zs8v46sx5frjnmlxy6-jtbl-1.1.7/bin/.jtbl-real" "$@" ))))entry(namelibnode(type directoryentry(name python3.10node(type directoryentry(name site-packagesnode(type directoryentry(namejtblnode(type directoryentry(name __init__.pynode(typeregularcontents))entry(name __pycache__node(type directoryentry(name__init__.cpython-310.pycnode(typeregularcontentso %%'@sdS)Nrrrd/gnu/store/9mcmrnvqh9q4r9zs8v46sx5frjnmlxy6-jtbl-1.1.7/lib/python3.10/site-packages/jtbl/__init__.pys))entry(namecli.cpython-310.pycnode(typeregularcontentso Tp @sddlZddlZddlZddlZddlZddlZdZddZddZddZ d d Z d d Z   dddZ ddZ edkrEe dSdS)Nz1.1.7cCstddS)zexit with error on SIGINTN)sysexit)signumframer_/gnu/store/9mcmrnvqh9q4r9zs8v46sx5frjnmlxy6-jtbl-1.1.7/lib/python3.10/site-packages/jtbl/cli.pyctrlc sr cCstjrdStjS)zreturn STDIN dataN)rstdinisattyreadrrrr get_stdins  r cCsttddS)Na jtbl: Converts JSON and JSON Lines to a table Usage: | jtbl [OPTIONS] --cols=n manually configure the terminal width -n do not try to wrap if too long for the terminal width -t truncate data instead of wrapping if too long for the terminal width -v version info -h help ) print_errortextwrapdedentrrrrhelptextsrcCst|tjdtddS)z7print error messages to STDERR and quit with error code)filerN)printrstderrr)messagerrrr&src sfi}|D]*}|D]#\|vr%tt|kr$tt|<q tt|<q qt|}g}|D]\ttdd}||q;t|} | |kr/t|dd} | d|rgdnd} dkr| ||| krt| dd} | dd8<t| } | ddkr| ||| ksu|D]}g} g} |D]s\d urd |rtd}td}|ksLj|kr| | ||fqd }d fd dt dttD}d fddt dttD}|ks |kr| | ||fq| D]}||=q| D] }|d||d<q"q||fS)a Wrap or truncate the data to fit the terminal width. Returns a tuple of (data, table_format) data (dictionary) a modified dictionary with wrapped or truncated string values. wrapping is achieved by inserting characters into the value strings. table_format (string) 'simple' (for truncation) or 'grid' (for wrapping) T)reverserg@g@rNgrid c g|] }t||qSrstr.0i)k wrap_widthrr g zwrap..crrrr)vr#rrr$hr%) itemslenrkeysmaxappendsumsortedjoinrange)datacolumns table_formattruncate data_widthentrynum_of_headerscombined_total_list highest_value total_width sorted_listscale delete_keysadd_keysnew_key new_valuer!r)r"r&r#rwrap,sf        **  r@Fsimplec Csd\}}|dur |dfS|durtj}|r|szt|}t|tur0g}|||}WnWt y| }g}t |D]A\} } z t| } || WqBt y} z%|t d| d| ddt| d|d d fWYd} ~ YSd} ~ ww|}Ynwz"t|dtst|}|t d t|d|d d fWSWnt y|t d t|d|d dfYSw|st||||d\}}|tj|d|dfS|dfS)a Generates the table from the JSON input. Returns a tuple of ([SUCCESS | ERROR], result) SUCCESS | ERROR (boolean) SUCCESS (True) if no error, ERROR (False) if error encountered result (string) text string of the table result or error message )TFNzjtbl: Missing piped data z+ jtbl: Exception - z2 Cannot parse line rz? (Not JSON or JSON Lines data): rz z jtbl: Cannot represent this part of the JSON Object as a table. (Could be an Element, an Array, or Null data instead of an Object): z zc jtbl: Cannot parse the data (Not JSON or JSON Lines data): z )r0r1r2r3r))headerstablefmtr)shutilget_terminal_sizer1isspacejsonloadstypelistr+ Exception splitlines enumeraterrr isinstancedictdumpsr@tabulate) input_datar3nowrapr1r2SUCCESSERRORr0 data_listr!jsonliner5errr make_tablevs`           rZc CsBttjtz ttjtjWn tyYnwt}g}i}tjD]:}| dr:| ds:| |dd| dr_z|dd d\}}t |||<Wq%t y^tYq%wq%d|v}d|v}d|v}d |v} d} d |vrz|d } |rtd td | rtt|||| d \} } | rt| dSt| dS)N-z--rr=ntr&hcolszjtbl: version r)rSr3rTr1)signalSIGINTr SIGPIPESIG_DFLAttributeErrorr rargv startswithextendsplitintrLrr __version__rZr) r options long_optionsargr"r&rTr3 version_infohelpmer1 succeeededresultrrrmainsF       rs__main__)NFFNrA)rrarrHrRrErkr r rrr@rZrs__name__rrrrs,J I0 ))))entry(namecli.pynode(typeregularcontentsimport sys import signal import textwrap import json import tabulate import shutil __version__ = '1.1.7' def ctrlc(signum, frame): """exit with error on SIGINT""" sys.exit(1) def get_stdin(): """return STDIN data""" if sys.stdin.isatty(): return None else: return sys.stdin.read() def helptext(): print_error(textwrap.dedent('''\ jtbl: Converts JSON and JSON Lines to a table Usage: | jtbl [OPTIONS] --cols=n manually configure the terminal width -n do not try to wrap if too long for the terminal width -t truncate data instead of wrapping if too long for the terminal width -v version info -h help ''')) def print_error(message): """print error messages to STDERR and quit with error code""" print(message, file=sys.stderr) sys.exit(1) def wrap(data, columns, table_format, truncate): """ Wrap or truncate the data to fit the terminal width. Returns a tuple of (data, table_format) data (dictionary) a modified dictionary with wrapped or truncated string values. wrapping is achieved by inserting \n characters into the value strings. table_format (string) 'simple' (for truncation) or 'grid' (for wrapping) """ # find the length of the keys (headers) and longest values data_width = {} for entry in data: for k, v in entry.items(): if k in data_width: if len(str(v)) > data_width[k]: data_width[k] = len(str(v)) else: data_width[k] = len(str(v)) # highest_value calculations are only approximate since there can be left and right justification num_of_headers = len(data_width.keys()) combined_total_list = [] for k, v in data_width.items(): highest_value = max(len(k) + 4, v + 2) combined_total_list.append(highest_value) total_width = sum(combined_total_list) if total_width > columns: # Find the best wrap_width based on the terminal size sorted_list = sorted(combined_total_list, reverse=True) wrap_width = sorted_list[0] scale = 2.5 if truncate else 4.5 while wrap_width > 4 and total_width >= (columns - (num_of_headers * scale)): sorted_list = sorted(sorted_list, reverse=True) sorted_list[0] -= 1 total_width = sum(sorted_list) wrap_width = sorted_list[0] # truncate or wrap every wrap_width chars for all field values for entry in data: delete_keys = [] add_keys = [] for k, v in entry.items(): if v is None: v = '' if truncate: new_key = str(k)[0:wrap_width] new_value = str(v)[0:wrap_width] if k != new_key or v != new_value: delete_keys.append(k) add_keys.append((new_key, new_value)) else: table_format = 'grid' new_key = '\n'.join([str(k)[i:i + wrap_width] for i in range(0, len(str(k)), wrap_width)]) new_value = '\n'.join([str(v)[i:i + wrap_width] for i in range(0, len(str(v)), wrap_width)]) if k != new_key or v != new_value: delete_keys.append(k) add_keys.append((new_key, new_value)) for i in delete_keys: del entry[i] for i in add_keys: entry[i[0]] = i[1] return (data, table_format) def make_table(input_data=None, truncate=False, nowrap=False, columns=None, table_format='simple'): """ Generates the table from the JSON input. Returns a tuple of ([SUCCESS | ERROR], result) SUCCESS | ERROR (boolean) SUCCESS (True) if no error, ERROR (False) if error encountered result (string) text string of the table result or error message """ SUCCESS, ERROR = True, False if input_data is None: return (ERROR, 'jtbl: Missing piped data\n') if columns is None: columns = shutil.get_terminal_size().columns # only process if there is data if input_data and not input_data.isspace(): try: data = json.loads(input_data) if type(data) is not list: data_list = [] data_list.append(data) data = data_list except Exception: # if json.loads fails, assume the data is formatted as json lines and parse data = input_data.splitlines() data_list = [] for i, jsonline in enumerate(data): try: entry = json.loads(jsonline) data_list.append(entry) except Exception as e: # can't parse the data. Throw a nice message and quit return (ERROR, textwrap.dedent(f'''\ jtbl: Exception - {e} Cannot parse line {i + 1} (Not JSON or JSON Lines data): {str(jsonline)[0:columns - 8]} ''')) data = data_list try: if not isinstance(data[0], dict): data = json.dumps(data) return (ERROR, textwrap.dedent(f'''\ jtbl: Cannot represent this part of the JSON Object as a table. (Could be an Element, an Array, or Null data instead of an Object): {str(data)[0:columns - 8]} ''')) except Exception: # can't parse the data. Throw a nice message and quit return (ERROR, textwrap.dedent(f'''\ jtbl: Cannot parse the data (Not JSON or JSON Lines data): {str(data)[0:columns - 8]} ''')) if not nowrap: data, table_format = wrap(data=data, columns=columns, table_format=table_format, truncate=truncate) return (SUCCESS, tabulate.tabulate(data, headers='keys', tablefmt=table_format)) else: return (ERROR, '') def main(): # break on ctrl-c keyboard interrupt signal.signal(signal.SIGINT, ctrlc) # break on pipe error. need try/except for windows compatibility try: signal.signal(signal.SIGPIPE, signal.SIG_DFL) except AttributeError: pass stdin = get_stdin() options = [] long_options = {} for arg in sys.argv: if arg.startswith('-') and not arg.startswith('--'): options.extend(arg[1:]) if arg.startswith('--'): try: k, v = arg[2:].split('=') long_options[k] = int(v) except Exception: helptext() nowrap = 'n' in options truncate = 't' in options version_info = 'v' in options helpme = 'h' in options columns = None if 'cols' in long_options: columns = long_options['cols'] if version_info: print_error(f'jtbl: version {__version__}\n') if helpme: helptext() succeeeded, result = make_table(input_data=stdin, truncate=truncate, nowrap=nowrap, columns=columns) if succeeeded: print(result) else: print_error(result) if __name__ == '__main__': main() ))))entry(namejtbl-1.1.7-py3.10.egg-infonode(type directoryentry(namePKG-INFOnode(typeregularcontents+;Metadata-Version: 2.1 Name: jtbl Version: 1.1.7 Summary: A simple cli tool to print JSON and JSON Lines data as a table in the terminal. Home-page: https://github.com/kellyjonbrazil/jtbl Author: Kelly Brazil Author-email: kellyjonbrazil@gmail.com License: MIT Classifier: Programming Language :: Python :: 3 Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Topic :: Utilities Requires-Python: >=3.6 Description-Content-Type: text/markdown License-File: LICENSE ![Tests](https://github.com/kellyjonbrazil/jtbl/workflows/Tests/badge.svg?branch=master) ![Pypi](https://img.shields.io/pypi/v/jtbl.svg) # jtbl A simple cli tool to print JSON data as a table in the terminal. `jtbl` accepts piped JSON data from `stdin` and outputs a text table representation to `stdout`. e.g: ``` $ cat cities.json | jtbl LatD LatM LatS NS LonD LonM LonS EW City State ------ ------ ------ ---- ------ ------ ------ ---- ----------------- ------- 41 5 59 N 80 39 0 W Youngstown OH 42 52 48 N 97 23 23 W Yankton SD 46 35 59 N 120 30 36 W Yakima WA 42 16 12 N 71 48 0 W Worcester MA 43 37 48 N 89 46 11 W Wisconsin Dells WI 36 5 59 N 80 15 0 W Winston-Salem NC 49 52 48 N 97 9 0 W Winnipeg MB ``` `jtbl` expects a JSON array of JSON objects or [JSON Lines](http://jsonlines.org/). It can be useful to JSONify command line output with `jc`, filter through a tool like `jq`, and present in `jtbl`: ``` $ jc ifconfig | jq -c '.[] | {name, type, ipv4_addr, ipv4_mask}'| jtbl name type ipv4_addr ipv4_mask ------- -------------- -------------- ------------- docker0 Ethernet 172.17.0.1 255.255.0.0 ens33 Ethernet 192.168.71.146 255.255.255.0 lo Local Loopback 127.0.0.1 255.0.0.0 ``` ## Installation You can install `jtbl` via `pip`, via OS Package Repositories, MSI installer for Windows, or by downloading the correct binary for your architecture and running it anywhere on your filesystem. ### Pip (macOS, linux, unix, Windows) For the most up-to-date version and the most cross-platform option, use `pip` or `pip3` to download and install `jtbl` directly from [PyPi](https://pypi.org/project/jtbl/): ![Pypi](https://img.shields.io/pypi/v/jtbl.svg) ```bash pip3 install jtbl ``` ### OS Packages [![Packaging status](https://repology.org/badge/vertical-allrepos/jtbl.svg)](https://repology.org/project/jtbl/versions) ### MSI Installer (Windows 2016+) The MSI Installer packages for Windows are built from PyPi and can be installed on modern versions of Windows. These installers may not always be on the very latest `jtbl` version, but are regularly updated. | Version | File | SHA256 Hash | |-----------|-----------------------------------------------------------------------------------|-------------------------------------------------------------------| | 1.1.6 | [jtbl-1.1.6.msi](https://jtbl-packages.s3-us-west-1.amazonaws.com/jtbl-1.1.6.msi) | c0008e6ab020dd8ed663629f8eea3c7dbe0d4797937bec46fa38c263b6270a10 | ### Binaries (x86_64) Linux and macOS x86_64 binaries are built from PyPi and can be copied to any location in your path and run. These binaries may not always be on the very latest `jtbl` version, but are regularly updated. #### Linux (Fedora, RHEL, CentOS, Debian, Ubuntu) | Version | File | SHA256 Hash (binary file) | |-----------|-------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------| | 1.1.6 | [jtbl-1.1.6-linux.tar.gz](https://jtbl-packages.s3-us-west-1.amazonaws.com/bin/jtbl-1.1.6-linux.tar.gz) | 7fb8e7a2d3858ac1c0975fe2646de8963dd75e7290b9bc32215bc65933e33cbf | #### macOS (Mojave and higher) | Version | File | SHA256 Hash (binary file) | |-----------|------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------| | 1.1.6 | [jtbl-1.1.6-darwin.tar.gz](https://jtbl-packages.s3-us-west-1.amazonaws.com/bin/jtbl-1.1.6-darwin.tar.gz) | 34a5a0b6155a1080ea86f6c3a6e68f451fcf3e3eab8ea023735d21cae44cba25 | ## Usage Just pipe JSON data to `jtbl`. (e.g. `cat` a JSON file, `jc`, `jq`, `aws` cli, `kubectl`, etc.) ``` $ | jtbl [OPTIONS] ``` ### Options - `--cols=n` manually configure the terminal width - `-n` no data wrapping if too long for the terminal width (overrides `--cols` and `-t`) - `-t` truncate data instead of wrapping if too long for the terminal width - `-v` prints version information - `-h` prints help information ## Compatible JSON Formats `jtbl` works best with a shallow array of JSON objects. Each object should have a few elements that will be turned into table columns. Fortunately, this is how many APIs present their data. **JSON Array Example** ``` [ { "unit": "proc-sys-fs-binfmt_misc.automount", "load": "loaded", "active": "active", "sub": "waiting", "description": "Arbitrary Executable File Formats File System Automount Point" }, { "unit": "sys-devices-pci0000:00-0000:00:07.1-ata2-host2-target2:0:0-2:0:0:0-block-sr0.device", "load": "loaded", "active": "active", "sub": "plugged", "description": "VMware_Virtual_IDE_CDROM_Drive" }, ... ] ``` `jtbl` can also work with [JSON Lines](http://jsonlines.org/) format with similar features. **JSON Lines Example** ``` {"name": "docker0", type": "Ethernet", "ipv4_addr": "172.17.0.1", "ipv4_mask": "255.255.0.0"} {"name": "ens33", "type": "Ethernet", "ipv4_addr": "192.168.71.146", "ipv4_mask": "255.255.255.0"} {"name": "lo", "type": "Local Loopback", "ipv4_addr": "127.0.0.1", "ipv4_mask": "255.0.0.0"} ... ``` ## Filtering the JSON Input If there are too many elements, or the data in the elements are too large, the table may not fit in the terminal screen. In this case you can use a JSON filter like `jq` or `jello` to send `jtbl` only the elements you are interested in: ### `jq` Array Method The following example uses `jq` to filter and format the filtered elements into a proper JSON array. ``` $ cat /etc/passwd | jc --passwd | jq '[.[] | {username, shell}]' [ { "username": "root", "shell": "/bin/bash" }, { "username": "bin", "shell": "/sbin/nologin" }, { "username": "daemon", "shell": "/sbin/nologin" }, ... ] ``` *(Notice the square brackets around the filter)* ### `jq` Slurp Method The following example uses `jq` to filter and 'slurp' the filtered elements into a proper JSON array. ``` $ cat /etc/passwd | jc --passwd | jq '.[] | {username, shell}' | jq -s [ { "username": "root", "shell": "/bin/bash" }, { "username": "bin", "shell": "/sbin/nologin" }, { "username": "daemon", "shell": "/sbin/nologin" }, ... ] ``` *(Notice the `jq -s` at the end)* ### `jq` JSON Lines Method The following example will send the data in JSON Lines format, which `jtbl` can understand: ``` $ cat /etc/passwd | jc --passwd | jq -c '.[] | {username, shell}' {"username":"root","shell":"/bin/bash"} {"username":"bin","shell":"/sbin/nologin"} {"username":"daemon","shell":"/sbin/nologin"} ... ``` *(Notice the `-c` option being used)* ### `jello` List Comprehension Method If you prefer python list and dictionary syntax to filter JSON data, you can use `jello`: ``` $ cat /etc/passwd | jc --passwd | jello '[{"username": x["username"], "shell": x["shell"]} for x in _]' [ { "username": "root", "shell": "/bin/bash" }, { "username": "bin", "shell": "/sbin/nologin" }, { "username": "daemon", "shell": "/sbin/nologin" }, ... ] ``` When piping any of these to `jtbl` you get the following result: ``` $ cat /etc/passwd | jc --passwd | jello '[{"username": x["username"], "shell": x["shell"]} for x in _]' | jtbl username shell --------------- -------------- root /bin/bash bin /sbin/nologin daemon /sbin/nologin ... ``` ## Working with Deeper JSON Structures `jtbl` will happily dump deeply nested JSON structures into a table, but usually this is not what you are looking for. ``` $ jc dig www.cnn.com | jtbl +-------+----------+----------+--------------+-------------+--------------+-----------------+------------------+--------------+--------------+--------------+----------+--------------+--------+ | id | opcode | status | flags | query_num | answer_num | authority_num | additional_num | question | answer | query_time | server | when | rcvd | +=======+==========+==========+==============+=============+==============+=================+==================+==============+==============+==============+==========+==============+========+ | 28791 | QUERY | NOERROR | ['qr', 'rd', | 1 | 5 | 0 | 1 | {'name': 'ww | [{'name': 'w | 32 | 2600 | Fri Mar 06 1 | 143 | | | | | 'ra'] | | | | | w.cnn.com.', | ww.cnn.com.' | | | 7:15:25 PST | | | | | | | | | | | 'class': 'I | , 'class': ' | | | 2020 | | | | | | | | | | | N', 'type': | IN', 'type': | | | | | | | | | | | | | | 'A'} | 'CNAME', 't | | | | | | | | | | | | | | | tl': 251, 'd | | | | | | | | | | | | | | | ata': 'turne | | | | | | | | | | | | | | | r-tls.map.fa | | | | | | | | | | | | | | | stly.net.'}, | | | | | | | | | | | | | | | {'name': 't | | | | | | | | | | | | | | | urner-tls.ma | | | | | | | | | | | | | | | p.fastly.net | | | | | | | | | | | | | | | ... | | | | | +-------+----------+----------+--------------+-------------+--------------+-----------------+------------------+--------------+--------------+--------------+----------+--------------+--------+ ``` ## Diving Deeper into the JSON with `jq` or `jello`: To get to the data you are interested in you can use a JSON filter like `jq` or `jello` to dive deeper. Using `jq`: ``` $ jc dig www.cnn.com | jq '.[0].answer' ``` or with `jello`: ``` $ jc dig www.cnn.com | jello '_[0]["answer"]' ``` Both will produce the following output: ``` [ { "name": "www.cnn.com.", "class": "IN", "type": "CNAME", "ttl": 90, "data": "turner-tls.map.fastly.net." }, { "name": "turner-tls.map.fastly.net.", "class": "IN", "type": "A", "ttl": 20, "data": "151.101.1.67" } ... ] ``` This will produce the following table in `jtbl` ``` $ jc dig www.cnn.com | jello '_[0]["answer"]' | jtbl name class type ttl data -------------------------- ------- ------ ----- -------------------------- www.cnn.com. IN CNAME 11 turner-tls.map.fastly.net. turner-tls.map.fastly.net. IN A 23 151.101.129.67 turner-tls.map.fastly.net. IN A 23 151.101.1.67 turner-tls.map.fastly.net. IN A 23 151.101.65.67 turner-tls.map.fastly.net. IN A 23 151.101.193.67 ``` ## Column Width `jtbl` will attempt to shrink columns to a sane size if it detects the output is wider than the terminal width. The `--cols` option will override the automatic terminal width detection. You can use the `-t` option to truncate the rows instead of wrapping when the terminal width is too small for all of the data. The `-n` option disables wrapping and overrides the `--cols` and `-t` options. This can be useful to present a nicely non-wrapped table of infinite width in combination with `less -S`: ``` $ jc ps aux | jtbl -n | less -S user pid vsz rss tt stat started time command ------------------ ----- --------- ------ ---- ------ --------- --------- --------------------------------------------------- joeuser 34029 4277364 24800 s000 S+ 9:28AM 0:00.27 /usr/local/Cellar/python/3.7.6_1/Frameworks/Python.... joeuser 34030 4283136 17104 s000 S+ 9:28AM 0:00.20 /usr/local/Cellar/python/3.7.6_1/Frameworks/Python.... joeuser 481 5728568 189328 S 17Apr20 21:46.52 /Applications/Utilities/Terminal.app/Contents/MacOS... joeuser 45827 6089084 693768 S Wed01PM 84:54.87 /Applications/Microsoft Teams.app/Contents/Framewor... joeuser 1493 9338824 911600 S 17Apr20 143:27.08 /Applications/Microsoft Outlook.app/Contents/MacOS/... joeuser 45822 5851524 163840 S Wed01PM 38:48.83 /Applications/Microsoft Teams.app/Contents/MacOS/Te... ``` ))entry(name SOURCES.txtnode(typeregularcontentsCHANGELOG LICENSE MANIFEST.in README.md setup.cfg setup.py jtbl/__init__.py jtbl/cli.py jtbl.egg-info/PKG-INFO jtbl.egg-info/SOURCES.txt jtbl.egg-info/dependency_links.txt jtbl.egg-info/entry_points.txt jtbl.egg-info/requires.txt jtbl.egg-info/top_level.txt))entry(namedependency_links.txtnode(typeregularcontents ))entry(nameentry_points.txtnode(typeregularcontents'[console_scripts] jtbl = jtbl.cli:main ))entry(name requires.txtnode(typeregularcontentstabulate>=0.8.6 ))entry(name top_level.txtnode(typeregularcontentsjtbl ))))))))))entry(namesharenode(type directoryentry(namedocnode(type directoryentry(name jtbl-1.1.7node(type directoryentry(nameLICENSEnode(typeregularcontents-MIT License Copyright (c) 2020 Kelly Brazil Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. )))))))))