PK5H%|"termdraw-0.1.data/scripts/termdraw#!python # vim:syntax=python:filetype=python:ts=4:sw=4:noet: import sys import os sys.path.append(os.path.join(os.path.dirname(__file__), '..')) import termdraw.termdraw if __name__=='__main__': termdraw.termdraw._main(sys.argv) PK5H$01termdraw/graph.py#!/usr/bin/env python3 # vim:syntax=python:filetype=python:ts=4:sw=4:noet: import os import sys import io import math solid_graph_ticks_unicode = [' ', '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'] solid_graph_ticks_ascii = [' ', '.', '|'] point_graph_tick_unicode = '•' point_graph_tick_ascii = 'o' if __name__ == '__main__': prefix = os.path.basename(__file__) sys.stderr.write(prefix+': this module is not meant to be run directly! ' 'exiting\n') exit(1) sys.path.append(os.path.join(os.path.dirname(__file__), '..')) from . import interpolate def _unique_everseen(iterable, key=None): "List unique elements, preserving order. Remember all elements ever seen." # unique_everseen('AAAABBBCCDAABBB') --> A B C D # unique_everseen('ABBCcAD', str.lower) --> A B C D seen = set() seen_add = seen.add if key is None: for element in filterfalse(seen.__contains__, iterable): seen_add(element) yield element else: for element in iterable: k = key(element) if k not in seen: seen_add(k) yield element def _limit(val, min, max): if val > max: return max else: if val < min: return min else: return val def _scale(val, a, b, c, d): return _limit(1.0*(c+(d-c)*(val-a)/(b-a)), c, d) def _deduplicate_points(pts): sorted_list = pts sorted_list.sort(key=lambda p: p[1], reverse=True) uniques = _unique_everseen(sorted_list, key=lambda p: p[0]) result = list(uniques) result.sort(key=lambda p: p[0]) return result def _interpolate_points(pts): sorted_pts = sorted(pts, key=lambda p: p[0]) result = sorted_pts[:] for i, current in enumerate(sorted_pts): if i == (len(result)-1): break next = result[i+1] interval = int(next[0]-current[0]) a = current[1] b = next[1] if interval <= 1: continue for n in range(interval-1): newx = current[0] + n + 1 newy = interpolate.linear_interpolate(a, b, 1.0*(n+1)/interval) result.append((newx, newy)) return sorted(result, key=lambda p: p[0]) def print_point_graph(stream, width, height, data, interpolate, tick): '''Print an ASCII graph to stream Args: stream (writable): any object that supports write(), for instance, files or sys.stdout width (int): width of the resulting graph in characters height (int): height of the resulting graph in lines data (list): a list of (double,double) tuples representing (x,y) points on a graph interpolate (Boolean): if interpolate is true, empty columns between data points will contain interpolated values tick (String): a single-character string which represents the point on a graph Returns None ''' # Get min and max X and Y values left = min(data, key=lambda p: p[0])[0] right = max(data, key=lambda p: p[0])[0] bottom = min(data, key=lambda p: p[1])[1] top = max(data, key=lambda p: p[1])[1] # Initialize graph table graph = [[' ' for x in range(width)] for y in range(height)] unscaled_pts = [] pts = [] for i in data: rawx = int(i[0]) rawy = i[1] unscaled_pts.append((rawx, rawy)) for i in unscaled_pts: rawx = int(_scale(i[0], left, right, 0, width-1)) rawy = _scale(i[1], bottom, top, 0, height-1) pts.append((rawx, rawy)) if interpolate: pts = _interpolate_points(_deduplicate_points(pts)) for i in pts: graphx = i[0] graphy = int(i[1]) graph[height-graphy-1][graphx] = tick for y in graph: for x in y: stream.write(x) stream.write('\n') def print_solid_graph(stream, width, height, data, interpolate, ticks): '''Print a solid graph to stream This function prints a solid graph to stream. Solid graphs fill the volume below data points with ticks[len(ticks)-1] symbol. Args: stream (writable): any object that supports write), for instance, files or sys.stdout width (int) : width of the resulting graph in characters height (int) : height of the resulting graph in lines data (list) : a list of (double,double) tuples representing (x,y) points on a graph interpolate (Boolean) : if interpolate is true, empty columns between data points will contain interpolated values ticks (list) : a list of single character strings representing data points; space below data points will be filled with the last element, while data points will be drawn with a tick depending on their value after graph scaling; generally, this list should contain symbols that look like blocks with increasing fill, like graph.solid_graph_ticks_ascii = [' ', '.', '|']. Returns None ''' ticks_n = len(ticks) # Get min and max X and Y values left = min(data, key=lambda p: p[0])[0] right = max(data, key=lambda p: p[0])[0] bottom = min(data, key=lambda p: p[1])[1] top = max(data, key=lambda p: p[1])[1] # Initialize graph table graph = [[' ' for x in range(width)] for y in range(height)] # Initialize points list pts = [] for i in data: rawx = int(_scale(i[0], left, right, 0, width-1)) rawy = _scale(i[1], bottom, top, 0, height-1) pts.append((rawx, rawy)) pts = _deduplicate_points(pts) if interpolate: pts = _interpolate_points(pts) for i in pts: graphx = int(_limit(i[0], 0, width-1)) graphy = int(_limit(i[1], 0, height-1)) tickval = int(_scale(i[1]-math.floor(i[1]), 0, 1, 0, ticks_n-1)) if graphy != 0: for n in range(graphy): graph[height-n-1][graphx] = ticks[ticks_n-1] graph[height-graphy-1][graphx] = ticks[tickval] for y in graph: for x in y: stream.write(x) stream.write('\n') PK5HS- termdraw/terminal.py#!/usr/bin/env python3 # vim:syntax=python:filetype=python:ts=4:sw=4:noet: # The following code which detects terminal geometry is courtesy of Github user # @jtriley. This code was taken from https://gist.github.com/jtriley/1108174 '''terminal.py implements get_terminal_size(), which returns a tuple (int,int) representing parent terminal's width and height, respectively. ''' import os import sys import shlex import struct import platform import subprocess def get_terminal_size(): '''getTerminalSize() - get width and height of console - works on linux,os x,windows,cygwin(windows) originally retrieved from: http://stackoverflow.com/questions/566746/how-to-get-console-window-width-in-python ''' current_os = platform.system() tuple_xy = None if current_os == 'Windows': tuple_xy = _get_terminal_size_windows() if tuple_xy is None: tuple_xy = _get_terminal_size_tput() # needed for window's python in cygwin's xterm! if current_os in ['Linux', 'Darwin'] or current_os.startswith('CYGWIN'): tuple_xy = _get_terminal_size_linux() if tuple_xy is None: print('default') tuple_xy = (80, 25) # default value return tuple_xy def _get_terminal_size_windows(): try: from ctypes import windll, create_string_buffer # stdin handle is -10 # stdout handle is -11 # stderr handle is -12 h = windll.kernel32.GetStdHandle(-12) csbi = create_string_buffer(22) res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi) if res: (bufx, bufy, curx, cury, wattr, left, top, right, bottom, maxx, maxy) = struct.unpack('hhhhHhhhhhh', csbi.raw) sizex = right - left + 1 sizey = bottom - top + 1 return sizex, sizey except: pass def _get_terminal_size_tput(): # get terminal width # src: http://stackoverflow.com/questions/263890/how-do-i-find-the-width-height-of-a-terminal-window try: cols = int(subprocess.check_call(shlex.split('tput cols'))) rows = int(subprocess.check_call(shlex.split('tput lines'))) return (cols, rows) except: pass def _get_terminal_size_linux(): def ioctl_GWINSZ(fd): try: import fcntl import termios cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234')) return cr except: pass cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2) if not cr: try: fd = os.open(os.ctermid(), os.O_RDONLY) cr = ioctl_GWINSZ(fd) os.close(fd) except: pass if not cr: try: cr = (os.environ['LINES'], os.environ['COLUMNS']) except: return None return int(cr[1]), int(cr[0]) if __name__ == '__main__': prefix = os.path.basename(__file__) sys.stderr.write(prefix+': this module is not meant to be run' ' directly! exiting\n') exit(1) PK0'Htermdraw/__init__.pyPK5H{Ntermdraw/cli.py#!/usr/bin/env python # vim:fenc=utf8:ts=4:sw=4:sts=4:noet: def _any_pref(haystack, preflist): return any([haystack.startswith(pref) for pref in preflist]) class CLIParser(object): def __init__(self): self.shortoptlist = [] # List of all one letter options self.longoptlist = [] # List of all --long-options self.shortopts_with_arg = [] # List of short opts with arguments self.longopts_with_arg = [] # List of long opts with arguments self.shortopts_required = [] # List of required short opts self.longopts_required = [] # List of required long opts self.short_long_mapping = {} # Long options equivalent to short ones self.rawargs_opt = '' # with_arg option alternatively # supplied with rawargs self.rawargs_opt_long = None self.help_override_req = True # --help overrides arg requirements self.shortoptions = {} # Parsed option->value dictionary self.longoptions = {} self.rawargs = [] # Non-option arguments self.accept_bare_dash = False # Double bare dash -- works self.argv0 = None # Name of the program def parse(self, argv): bare_dash_on = False tmp_shortopt_required_list = self.shortopts_required tmp_longopt_required_list = self.longopts_required if len(argv) <= 0: raise ValueError('argv is empty, something has gone very wrong') self.argv0 = argv[0] optiter = enumerate(argv[1:]) for idx, option in optiter: if option is '--': if self.accept_bare_dash: if bare_dash_on: self.rawargs.append(option) continue else: bare_dash_on = True else: self.rawargs.append(option) elif option.startswith('--'): if self.accept_bare_dash and bare_dash_on: rawargs.append(option) else: optstring = option[2:] if optstring in self.longoptlist: if optstring not in self.longopts_with_arg: self.longoptions[optstring] = True continue else: # Fail if there is no argument to the right if (idx+2 == len(argv)) or (argv[idx+2] is '--') or (argv[idx+2].startswith('-')): raise ValueError('option '+optstring+' requires an argument') else: self.longoptions[optstring] = argv[idx+2] next(optiter, None) continue else: if not _any_pref(optstring, self.longoptlist): raise ValueError('unknown option '+optstring) elif not _any_pref(optstring, [p+'=' for p in self.longopts_with_arg]): raise ValueError('unknown option '+optstring) else: eq_idx = optstring.index('=') optname = optstring[:eq_idx] optval = optstring[eq_idx+1:] self.longoptions[optname] = optval continue elif option.startswith('-'): if self.accept_bare_dash and bare_dash_on: rawargs.append(option) else: optstring = option[1:] chariter = enumerate(optstring) if optstring == '': self.rawargs.append(option) for i, char in chariter: if char not in self.shortoptlist: raise ValueError('unknown option '+char) else: if char not in self.shortopts_with_arg: if char not in self.short_long_mapping: self.shortoptions[char] = True else: self.longoptions[self.short_long_mapping[char]] = True continue else: if (i+1 != len(optstring)) or (idx+2 == len(argv)): raise ValueError('option '+char+' requires an argument') else: if char not in self.short_long_mapping: self.shortoptions[char] = argv[idx+2] else: self.longoptions[self.short_long_mapping[char]] = argv[idx+2] next(optiter, None) else: self.rawargs.append(option) if len(argv) is 1: self.longoptions = {} self.shortoptions = {} self.rawargs = [] if self.rawargs_opt is not '' and len(self.rawargs) == 1: if self.rawargs_opt_long: if not self.longoptions.has_key(self.rawargs_opt): tmp_longopt_required_list.remove(self.rawargs_opt) self.longoptions[self.rawargs_opt] = self.rawargs[0] else: if not self.shortoptions.has_key(self.rawargs_opt): tmp_shortopt_required_list.remove(self.rawargs_opt) self.shortoptions[self.rawargs_opt] = self.rawargs[0] if not ('help' in self.longoptions and self.help_override_req): for o in tmp_shortopt_required_list: if o not in self.shortoptions: raise ValueError('option '+o+' required') for o in tmp_longopt_required_list: if o not in self.longoptions: raise ValueError('option '+o+' required') PK5H-termdraw/interpolate.py#!/usr/bin/env python3 # vim:syntax=python:filetype=python:ts=4:sw=4:noet: '''Define functions used in graph interpolation ''' import math def linear_interpolate(a, b, pos): '''Interpolate between two values based using a simple linear function Args: a (double): First value b (double): Second value pos (double): Position of the interpolated point between two values, must be in the interval [0,1]. Returns: double: interpolated value ''' return a + (b-a)*pos PK5H ܤtermdraw/termdraw.py#!/usr/bin/env python3 # vim:syntax=python:filetype=python:ts=4:sw=4:noet: from __future__ import absolute_import import os import sys import math import io import itertools _max_term_graph_width = 80 _max_term_graph_height = 30 _shortopts = ['i', 'n', 's', 'p', 'a', 'o', 'w', 'h'] _longopts = ['help', 'debug', 'interpolate', 'no-interpolate', 'solid', 'point', 'ascii', 'output', 'width', 'height'] _shortopts_with_arg = ['o', 'w', 'h'] _longopts_with_arg = ['output', 'width', 'height'] _shortlong_map = { 'i': 'interpolate', 'n': 'no-interpolate', 's': 'solid', 'p': 'point', 'a': 'ascii', 'o': 'output', 'w': 'width', 'h': 'height' } if __name__ == '__main__': prefix = os.path.basename(__file__) sys.stderr.write(prefix+': this module is not meant to be run' ' directly! exiting\n') exit(1) sys.path.append(os.path.join(os.path.dirname(__file__), '..')) from . import terminal from . import csv from . import cli from . import graph print_debug_info = False def _termdraw_print_help(progname): _termdraw_help_string_1 = 'Usage: ' _termdraw_help_string_2 = ( ' [options] file.csv\n' 'Draw a human-friendly CLI graph with Unicode symbols.\n\n' ' --help Print this help message and exit\n' ' -w X, --width X Limit graph width to X characters\n' ' -h Y, --height Y Limit graph height to Y lines\n' ' -i, --interpolate Enable interpolation\n' ' -n, --no-interpolate Disable interpolation\n' ' -s, --solid Draw solid graph (with columns)\n' ' -p, --point Draw point graph (with points)\n' ' -a, --ascii Only use ASCII symbols\n' ' -o file, --output file Write to file instead of stdout' ) print(_termdraw_help_string_1 + progname + _termdraw_help_string_2) def _debug_write(str): if print_debug_info: sys.stderr.write(os.path.basename(__file__) + ': debug: ' + str + '\n') def _err(str): sys.stderr.write(os.path.basename(__file__) + ': ' + str + '\n') def _draw_graph(stream, width, height, data, interpolate=None, solid_graph=True, ascii_only=False): intp = None if interpolate is None: if solid_graph: intp = True else: intp = False else: intp = interpolate if ascii_only: solid_graph_ticks = graph.solid_graph_ticks_ascii point_graph_tick = graph.point_graph_tick_ascii else: solid_graph_ticks = graph.solid_graph_ticks_unicode point_graph_tick = graph.point_graph_tick_unicode if solid_graph: graph.print_solid_graph(stream, width, height, data, intp, solid_graph_ticks) else: graph.print_point_graph(stream, width, height, data, intp, point_graph_tick) return 0 def _get_soft_view_width(termwidth): if termwidth >= _max_term_graph_width: return _max_term_graph_width else: return termwidth def _get_soft_view_height(termheight): if termheight >= _max_term_graph_height: return _max_term_graph_height else: return termheight def _main(args): exit_status = 0 global print_debug_info args0 = args[0] prefix = os.path.basename(__file__) output_stream = sys.stdout interpolate = True term_width, term_height = terminal.get_terminal_size() cliparse = cli.CLIParser() cliparse.shortoptlist = _shortopts cliparse.longoptlist = _longopts cliparse.shortopts_with_arg = _shortopts_with_arg cliparse.longopts_with_arg = _longopts_with_arg cliparse.short_long_mapping = _shortlong_map cliparse.accept_bare_dash = True if (len(args) <= 1): _termdraw_print_help(prefix) exit(1) try: cliparse.parse(args) except ValueError as e: _err(str(e)) exit(1) print_debug_info = cliparse.longoptions.get('debug', False) _debug_write('short CLI options: ' + repr(cliparse.shortoptions)) _debug_write('long CLI options: ' + repr(cliparse.longoptions)) _debug_write('raw CLI arguments: ' + repr(cliparse.rawargs)) input_files = cliparse.rawargs opt_interpolate = cliparse.longoptions.get('interpolate', None) opt_no_interpolate = cliparse.longoptions.get('no-interpolate', None) ascii_only = cliparse.longoptions.get('ascii', False) output = cliparse.longoptions.get('output') graph_width = int(cliparse.longoptions.get('width', _get_soft_view_width(term_width))) graph_height = int(cliparse.longoptions.get('height', _get_soft_view_height(term_height))) opt_solid = cliparse.longoptions.get('solid', None) opt_point = cliparse.longoptions.get('point', None) if cliparse.longoptions.get('help', False): _termdraw_print_help(prefix) exit(0) if opt_solid is None and opt_point is None: solid = True if opt_solid is True and opt_point is True: _err('both types of graphs have been specified') exit(1) solid = False if opt_solid is None else True if opt_interpolate is None and opt_no_interpolate is None: _debug_write('no interpolation option set, selecting ' + repr(solid)) interpolate = solid interpolate = (True if opt_interpolate is not None else False) if output is not None: if os.path.exists(output): _err('file system entry already exists: ' + output) exit(1) else: output_stream = open(output, 'w+') if graph_width < 1: _err('graph width too small') exit(1) if graph_height < 1: _err('graph height too small') exit(1) for f in input_files: data = [] is_stdin = False if f is '-': is_stdin = True _debug_write('processing '+(('file '+f) if not is_stdin else 'stdin')) if is_stdin: stdin_string = sys.stdin.read() stdin_string = stdin_string.replace(';', '\n') stdin_string = stdin_string.replace(' ', '\n') stdin_string = stdin_string.strip() rawdata = csv.get_csv_data_string(stdin_string) data = [(float(t[0]), float(t[1])) for t in (tuple(x) for x in rawdata)] else: rawdata = csv.get_csv_data(f) data = [(float(t[0]), float(t[1])) for t in (tuple(x) for x in rawdata)] for n in data: if len(n) != 2: _err('CSV data does not conform to x,y format: ' + repr(n)) exit(1) output_stream.write(f + '\n') _draw_graph(output_stream, graph_width, graph_height, data, interpolate, solid, ascii_only) PK5Hdtermdraw/csv.py#!/usr/bin/env python3 # vim:syntax=python:filetype=python:ts=4:sw=4:noet: '''Define functions for parsing CSV files ''' from __future__ import absolute_import import os import sys import io import csv if __name__ == '__main__': prefix = os.path.basename(__file__) sys.stderr.write(prefix+': this module is not meant to be run' ' directly! exiting\n') exit(1) def get_csv_data(filename): '''Open CSV file, read its contents, return them as a list Args: filename (string): name of the CSV file to be read Returns: list of all entries in filename ''' with open(filename, 'r') as csvfile: dialect = csv.Sniffer().sniff(csvfile.read(1024)) csvfile.seek(0) reader = csv.reader(csvfile, dialect) return list(reader) def get_csv_data_string(s): '''Read CSV data from a string, return as a list Args: s (string): CSV data Returns list of all entries in s ''' tmp = s.split('\n') dialect = csv.Sniffer().sniff(s) reader = csv.reader(tmp, dialect) return list(reader) PK5H^- &termdraw-0.1.dist-info/DESCRIPTION.rstUNKNOWN PK5H[5$termdraw-0.1.dist-info/metadata.json{"classifiers": ["Environment :: Console", "Intended Audience :: Developers", "Intended Audience :: Science/Research", "License :: Freely Distributable", "Programming Language :: Python :: 3 :: Only", "Topic :: Scientific/Engineering :: Visualization", "Topic :: Utilities"], "extensions": {"python.details": {"contacts": [{"email": "bacondropped@gmail.com", "name": "Ilya Terentyev", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/bacondropped/termdraw"}}}, "generator": "bdist_wheel (0.26.0)", "keywords": ["data", "graph", "ascii", "visualization"], "license": "MIT", "metadata_version": "2.0", "name": "termdraw", "summary": "Utility library for textual data visualization", "version": "0.1"}PK5Hx $termdraw-0.1.dist-info/top_level.txttermdraw PK5H''\\termdraw-0.1.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.26.0) Root-Is-Purelib: true Tag: py2-none-any PK5HKIEnntermdraw-0.1.dist-info/METADATAMetadata-Version: 2.0 Name: termdraw Version: 0.1 Summary: Utility library for textual data visualization Home-page: https://github.com/bacondropped/termdraw Author: Ilya Terentyev Author-email: bacondropped@gmail.com License: MIT Keywords: data graph ascii visualization Platform: UNKNOWN Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Science/Research Classifier: License :: Freely Distributable Classifier: Programming Language :: Python :: 3 :: Only Classifier: Topic :: Scientific/Engineering :: Visualization Classifier: Topic :: Utilities UNKNOWN PK5H> RRtermdraw-0.1.dist-info/RECORDtermdraw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 termdraw/cli.py,sha256=8fpPCFkRBmrCfAyWfV7HC7fNLpNpC6jVyPSvN8AivfM,4639 termdraw/csv.py,sha256=TiA2M56-DvmeLqryjomEoMs0W8-sR86Cxbtp-rffyDU,1003 termdraw/graph.py,sha256=3fmOd9TcwLmmT8nWxbqmdQ-SO-t7rnSZOqge4_Ziuvg,5615 termdraw/interpolate.py,sha256=pTkDiOxgpRQZeYZxxU_vw9r_5j6ue1c8fik-mDk4WKU,487 termdraw/termdraw.py,sha256=iTeF3_TdS7LVELhQdayPTzXigRu4XgmBBEzKwC53RDA,6061 termdraw/terminal.py,sha256=BWX-17ZO0EESzL7TNrU5YYJOnDPOoXiM-18Vwwt5Hn4,2696 termdraw-0.1.data/scripts/termdraw,sha256=ERN-ex5mPqd2tYsbxGVyVPrZt05Ca9hT9IgiZTghgDM,234 termdraw-0.1.dist-info/DESCRIPTION.rst,sha256=OCTuuN6LcWulhHS3d5rfjdsQtW22n7HENFRh6jC6ego,10 termdraw-0.1.dist-info/METADATA,sha256=y7kM01mHwRwFwigU2lFzq5_Kax5Aah-Fqtfdza9Lse4,622 termdraw-0.1.dist-info/RECORD,, termdraw-0.1.dist-info/WHEEL,sha256=JTb7YztR8fkPg6aSjc571Q4eiVHCwmUDlX8PhuuqIIE,92 termdraw-0.1.dist-info/metadata.json,sha256=H6PnM7EffEJMV5hYV2tct6yyhIgUzV08KwZSYgn4j_w,768 termdraw-0.1.dist-info/top_level.txt,sha256=zATVFDmg7Ynr24xejeUQD0cRVpjVXtgtFPQ-FbU27bU,9 PK5H%|"termdraw-0.1.data/scripts/termdrawPK5H$01*termdraw/graph.pyPK5HS- Htermdraw/terminal.pyPK0'H"termdraw/__init__.pyPK5H{N4"termdraw/cli.pyPK5H-4termdraw/interpolate.pyPK5H ܤ6termdraw/termdraw.pyPK5Hd{Ntermdraw/csv.pyPK5H^- &Rtermdraw-0.1.dist-info/DESCRIPTION.rstPK5H[5$Rtermdraw-0.1.dist-info/metadata.jsonPK5Hx $#Vtermdraw-0.1.dist-info/top_level.txtPK5H''\\nVtermdraw-0.1.dist-info/WHEELPK5HKIEnnWtermdraw-0.1.dist-info/METADATAPK5H> RRYtermdraw-0.1.dist-info/RECORDPK<^