From 92fe81d8b072d0222322b94b8cc436c658c78c55 Mon Sep 17 00:00:00 2001 From: Federico Ceratto Date: Sat, 3 Jun 2017 14:02:13 +0100 Subject: [PATCH] Initial commit --- .gitignore | 36 +++++ LICENSE | 165 +++++++++++++++++++ README.adoc | 132 +++++++++++++++ dashing.py | 453 ++++++++++++++++++++++++++++++++++++++++++++++++++++ demo.py | 74 +++++++++ setup.cfg | 3 + setup.py | 27 ++++ 7 files changed, 890 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.adoc create mode 100644 dashing.py create mode 100755 demo.py create mode 100644 setup.cfg create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ded6067 --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +*.py[cod] + +# C extensions +*.so + +# Packages +*.egg +*.egg-info +dist +build +eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg +lib +lib64 +__pycache__ + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox +nosetests.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..65c5ca8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/README.adoc b/README.adoc new file mode 100644 index 0000000..b7ef2d6 --- /dev/null +++ b/README.adoc @@ -0,0 +1,132 @@ + +image:https://img.shields.io/badge/status-alpha-orange.svg[badge] +image:https://img.shields.io/badge/version-0.1.0-orange.svg[badge] +image:https://img.shields.io/pypi/v/dashing.svg?style=plastic["Latest Version", link="https://pypi.python.org/pypi/bottle-cork"] +image:https://img.shields.io/badge/License-LGPL%20v3-blue.svg[License] + +Dashing is a library to quickly create terminal-based dashboards in Python. + +image:https://raw.githubusercontent.com/FedericoCeratto/dashing/gh-pages/tty.gif[Example] + +Similar libraries for other languages: https://github.com/gizak/termui[termui] https://github.com/chjj/blessed[blessed] https://github.com/yaronn/blessed-contrib[blessed-contrib] + +=== Dependencies + +The link:https://pypi.python.org/pypi/blessed[blessed] library. + +=== Installation + +Use packages from your Linux distribution, or: + +[source,bash] +---- +pip install dashing +---- + +=== Quick mode + +If you want to provide a simple UI to a script, Dashing can generate a layout automatically. + + +[source,python] +---- +import time +from dashing import QuickDash + +def main(): + d = QuickDash() + d.status = "Running..." + d.logs.append("Started") + for progress in range(100): + d.gauges['progess'] = progress + if progress % 10 == 0: + d.logs.append("Started") + time.sleep(0.05) + + d.status = "Done!" + time.sleep(1) +---- + + +=== Normal mode + +In normal mode you have full control over the UI + +.Usage +[source,python] +---- +from time import sleep, time +import math + +from dashing import * + +if __name__ == '__main__': + + ui = HSplit( + VSplit( + HGauge(val=50, title="only title", border_color=5), + HGauge(label="only label", val=20, border_color=5), + HGauge(label="only label", val=30, border_color=5), + HGauge(label="only label", val=50, border_color=5), + HGauge(label="only label", val=80, border_color=5), + HGauge(val=20), + HGauge(label="label, no border", val=55), + HSplit( + VGauge(val=0, border_color=2), + VGauge(val=5, border_color=2), + VGauge(val=30, border_color=2), + VGauge(val=50, border_color=2), + VGauge(val=80, border_color=2, color=4), + VGauge(val=95, border_color=2, color=3), + ColorRangeVGauge( + val=100, + border_color=2, + colormap=( + (33, 2), + (66, 3), + (100, 1), + ) + ), + ) + ), + VSplit( + Text('Hello World,\nthis is dashing.', border_color=2), + Log(title='logs', border_color=5), + VChart(border_color=2, color=2), + HChart(border_color=2, color=2), + HBrailleChart(border_color=2, color=2), + # HBrailleFilledChart(border_color=2, color=2), + ), + title='Dashing', + ) + log = ui.items[1].items[1] + vchart = ui.items[1].items[2] + hchart = ui.items[1].items[3] + bchart = ui.items[1].items[4] + # bfchart = ui.items[1].items[5] + log.append("0 -----") + log.append("1 Hello") + log.append("2 -----") + prev_time = time() + for cycle in range(0, 200): + ui.items[0].items[0].value = int(50 + 49.9 * math.sin(cycle / 80.0)) + ui.items[0].items[1].value = int(50 + 45 * math.sin(cycle / 20.0)) + ui.items[0].items[2].value = int(50 + 45 * math.sin(cycle / 30.0 + 3)) + + vgauges = ui.items[0].items[-1].items + for gaugenum, vg in enumerate(vgauges): + vg.value = 50 + 49.9 * math.sin(cycle / 12.0 + gaugenum) + + t = int(time()) + if t != prev_time: + log.append("%s" % t) + prev_time = t + vchart.append(50 + 50 * math.sin(cycle / 16.0)) + hchart.append(99.9 * abs(math.sin(cycle / 26.0))) + bchart.append(50 + 50 * math.sin(cycle / 6.0)) + # bfchart.append(50 + 50 * math.sin(cycle / 16.0)) + ui.display() + + sleep(1.0/25) +---- + diff --git a/dashing.py b/dashing.py new file mode 100644 index 0000000..906d081 --- /dev/null +++ b/dashing.py @@ -0,0 +1,453 @@ +# -*- coding: utf-8 -*- +# + +from collections import deque, namedtuple + +from blessed import Terminal + +try: + unichr +except NameError: + unichr = chr + +# "graphic" elements + +border_bl = u'└' +border_br = u'┘' +border_tl = u'┌' +border_tr = u'┐' +border_h = u'─' +border_v = u'│' +hbar_elements = (u"▏", u"▎", u"▍", u"▌", u"▋", u"▊", u"▉") +vbar_elements = (u"▁", u"▂", u"▃", u"▄", u"▅", u"▆", u"▇", u"█") +braille_left = (0x01, 0x02, 0x04, 0x40, 0) +braille_right = (0x08, 0x10, 0x20, 0x80, 0) +braille_r_left = (0x04, 0x02, 0x01) +braille_r_right = (0x20, 0x10, 0x08) + +TBox = namedtuple('TBox', 't x y w h') + + +class Tile(object): + def __init__(self, title=None, border_color=None, color=0): + self.title = title + self.color = color + self.border_color = border_color + + def _display(self, tbox, parent): + """Render current tile + """ + raise NotImplementedError + + def _draw_borders(self, tbox): + # top border + print(tbox.t.color(self.border_color) + tbox.t.move(tbox.x, tbox.y) + + border_tl + border_h * (tbox.w - 2) + border_tr) + # left and right + for dx in range(1, tbox.h - 1): + print(tbox.t.move(tbox.x + dx, tbox.y) + border_v) + print(tbox.t.move(tbox.x + dx, tbox.y + tbox.w - 1) + border_v) + # bottom + print(tbox.t.move(tbox.x + tbox.h - 1, tbox.y) + border_bl + + border_h * (tbox.w - 2) + border_br) + + def _draw_borders_and_title(self, tbox): + """Draw borders and title as needed and returns + inset (x, y, width, height) + """ + if self.border_color is not None: + self._draw_borders(tbox) + if self.title: + fill_all_width = (self.border_color is None) + self._draw_title(tbox, fill_all_width) + + if self.border_color is not None: + return TBox(tbox.t, tbox.x + 1, tbox.y + 1, tbox.w - 2, tbox.h - 2) + + elif self.title is not None: + return TBox(tbox.t, tbox.x + 1, tbox.y, tbox.w - 1, tbox.h - 1) + + return TBox(tbox.t, tbox.x, tbox.y, tbox.w, tbox.h) + + def _fill_area(self, tbox, char, *a, **kw): # FIXME + """Fill area with a character + """ + # for dx in range(0, height): + # print(tbox.t.move(x + dx, tbox.y) + char * width) + pass + + def display(self): + """Render current tile and its items. Recurse into nested splits + if any. + """ + try: + t = self._terminal + except AttributeError: + t = self._terminal = Terminal() + tbox = TBox(t, 0, 0, t.width, t.height - 1) + self._fill_area(tbox.t, 0, 0, t.width, t.height - 1, 'f') # FIXME + + tbox = TBox(t, 0, 0, t.width, t.height - 1) + self._display(tbox, None) + # park cursor in a safe place and reset color + print(t.move(t.height - 3, 0) + t.color(0)) + + def _draw_title(self, tbox, fill_all_width): + if not self.title: + return + margin = int((tbox.w - len(self.title)) / 20) + col = '' if self.border_color is None else \ + tbox.t.color(self.border_color) + if fill_all_width: + title = ' ' * margin + self.title + \ + ' ' * (tbox.w - margin - len(self.title)) + print(tbox.t.move(tbox.x, tbox.y) + col + title) + else: + title = ' ' * margin + self.title + ' ' * margin + print(tbox.t.move(tbox.x, tbox.y + margin) + col + title) + + +class Split(Tile): + def __init__(self, *items, **kw): + super(Split, self).__init__(**kw) + self.items = items + + def _display(self, tbox, parent): + """Render current tile and its items. Recurse into nested splits + """ + tbox = self._draw_borders_and_title(tbox) + + if not self.items: + # empty split + self._fill_area(tbox, ' ') + return + + if isinstance(self, VSplit): + item_height = tbox.h // len(self.items) + item_width = tbox.w + else: + item_height = tbox.h + item_width = tbox.w // len(self.items) + + x = tbox.x + y = tbox.y + for i in self.items: + i._display(TBox(tbox.t, x, y, item_width, item_height), + self) + if isinstance(self, VSplit): + x += item_height + else: + y += item_width + + # Fill leftover area + if isinstance(self, VSplit): + leftover_x = tbox.h - x + 1 + if leftover_x > 0: + self._fill_area(TBox(tbox.t, x, y, tbox.w, leftover_x), ' ') + else: + leftover_y = tbox.w - y + 1 + if leftover_y > 0: + self._fill_area(TBox(tbox.t, x, y, leftover_y, tbox.h), ' ') + + +class VSplit(Split): + pass + + +class HSplit(Split): + pass + + +class Text(Tile): + def __init__(self, text, color=0, *args, **kw): + super(Text, self).__init__(**kw) + self.text = text + self.color = color + + def _display(self, tbox, parent): + tbox = self._draw_borders_and_title(tbox) + for dx, line in enumerate(self.text.splitlines()): + print(tbox.t.color(self.color) + tbox.t.move(tbox.x + dx, tbox.y) + + line + ' ' * (tbox.w - len(line))) + dx += 1 + while dx < tbox.h: + print(tbox.t.move(tbox.x + dx, tbox.y) + ' ' * tbox.w) + dx += 1 + + +class Log(Tile): + def __init__(self, *args, **kw): + self.logs = deque(maxlen=50) + super(Log, self).__init__(**kw) + + def _display(self, tbox, parent): + tbox = self._draw_borders_and_title(tbox) + n_logs = len(self.logs) + log_range = min(n_logs, tbox.h) + start = n_logs - log_range + print(tbox.t.color(self.color)) + for i in range(0, log_range): + line = self.logs[start + i] + print(tbox.t.move(tbox.x + i, tbox.y) + line + + ' ' * (tbox.w - len(line))) + + if i < tbox.h: + for i2 in range(i + 1, tbox.h): + print(tbox.t.move(tbox.x + i2, tbox.y) + ' ' * tbox.w) + + def append(self, msg): + self.logs.append(msg) + + +class HGauge(Tile): + def __init__(self, label=None, val=100, color=2, **kw): + kw['color'] = color + super(HGauge, self).__init__(**kw) + self.value = val + self.label = label + + def _display(self, tbox, parent): + tbox = self._draw_borders_and_title(tbox) + if self.label: + wi = (tbox.w - len(self.label) - 3) * self.value / 100 + v_center = int((tbox.h) * 0.5) + else: + wi = tbox.w * self.value / 100.0 + index = int((wi - int(wi)) * 7) + bar = hbar_elements[-1] * int(wi) + hbar_elements[index] + print(tbox.t.color(self.color) + tbox.t.move(tbox.x, tbox.y + 1)) + if self.label: + pad = tbox.w - 1 - len(self.label) - len(bar) + else: + pad = tbox.w - len(bar) + bar += hbar_elements[0] * pad + # draw bar + for dx in range(0, tbox.h): + m = tbox.t.move(tbox.x + dx, tbox.y) + if self.label: + if dx == v_center: + # draw label + print(m + self.label + ' ' + bar) + else: + print(m + ' ' * len(self.label) + ' ' + bar) + else: + print(m + bar) + + +class VGauge(Tile): + def __init__(self, val=100, color=2, **kw): + kw['color'] = color + super(VGauge, self).__init__(**kw) + self.value = val + + def _display(self, tbox, parent): + """Render current tile + """ + tbox = self._draw_borders_and_title(tbox) + nh = tbox.h * (self.value / 100.5) + print(tbox.t.move(tbox.x, tbox.y) + tbox.t.color(self.color)) + for dx in range(tbox.h): + m = tbox.t.move(tbox.x + tbox.h - dx - 1, tbox.y) + if dx < int(nh): + bar = vbar_elements[-1] * tbox.w + elif dx == int(nh): + index = int((nh - int(nh)) * 8) + bar = vbar_elements[index] * tbox.w + else: + bar = ' ' * tbox.w + + print(m + bar) + + +class ColorRangeVGauge(Tile): + """Vertical gauge with color map. + E.g.: green gauge for values below 50, red otherwise: + colormap=((50, 2), (100, 1)) + """ + def __init__(self, val=100, colormap=(), **kw): + self.colormap = colormap + super(ColorRangeVGauge, self).__init__(**kw) + self.value = val + + def _display(self, tbox, parent): + tbox = self._draw_borders_and_title(tbox) + nh = tbox.h * (self.value / 100.5) + filled_element = vbar_elements[-1] + for thresh, col in self.colormap: + if thresh > self.value: + break + print(tbox.t.move(tbox.x, tbox.y) + tbox.t.color(col)) + for dx in range(tbox.h): + m = tbox.t.move(tbox.x + tbox.h - dx - 1, tbox.y) + if dx < int(nh): + bar = filled_element * tbox.w + elif dx == int(nh): + index = int((nh - int(nh)) * 8) + bar = vbar_elements[index] * tbox.w + else: + bar = ' ' * tbox.w + + print(m + bar) + + +class VChart(Tile): + """Vertical chart. Values must be between 0 and 100 and can be float. + """ + def __init__(self, val=100, *args, **kw): + super(VChart, self).__init__(**kw) + self.value = val + self.datapoints = deque(maxlen=50) + + def append(self, dp): + self.datapoints.append(dp) + + def _display(self, tbox, parent): + tbox = self._draw_borders_and_title(tbox) + filled_element = hbar_elements[-1] + scale = tbox.w / 100.0 + print(tbox.t.color(self.color)) + for dx in range(tbox.h): + index = 50 - (tbox.h) + dx + try: + dp = self.datapoints[index] * scale + index = int((dp - int(dp)) * 8) + bar = filled_element * int(dp) + hbar_elements[index] + assert len(bar) <= tbox.w, dp + bar += ' ' * (tbox.w - len(bar)) + except IndexError: + bar = ' ' * tbox.w + print(tbox.t.move(tbox.x + dx, tbox.y) + bar) + + +class HChart(Tile): + """Horizontal chart, filled + """ + def __init__(self, val=100, *args, **kw): + super(HChart, self).__init__(**kw) + self.value = val + self.datapoints = deque(maxlen=500) + + def append(self, dp): + self.datapoints.append(dp) + + def _display(self, tbox, parent): + tbox = self._draw_borders_and_title(tbox) + print(tbox.t.color(self.color)) + for dx in range(tbox.h): + bar = '' + for dy in range(tbox.w): + dp_index = - tbox.w + dy + try: + dp = self.datapoints[dp_index] + q = (1 - dp / 100) * tbox.h + if dx == int(q): + index = int((int(q) - q) * 8 - 1) + bar += vbar_elements[index] + elif dx < int(q): + bar += ' ' + else: + bar += vbar_elements[-1] + + except IndexError: + bar += ' ' + + # assert len(bar) == tbox.w + print(tbox.t.move(tbox.x + dx, tbox.y) + bar) + + +class HBrailleChart(Tile): + def __init__(self, val=100, *args, **kw): + super(HBrailleChart, self).__init__(**kw) + self.value = val + self.datapoints = deque(maxlen=500) + + def append(self, dp): + self.datapoints.append(dp) + + def _generate_braille(self, l, r): + v = 0x28 * 256 + (braille_left[l] + braille_right[r]) + return unichr(v) + + def _display(self, tbox, parent): + tbox = self._draw_borders_and_title(tbox) + print(tbox.t.color(self.color)) + for dx in range(tbox.h): + bar = '' + for dy in range(tbox.w): + dp_index = (dy - tbox.w) * 2 + try: + dp1 = self.datapoints[dp_index] + dp2 = self.datapoints[dp_index + 1] + except IndexError: + # no data (yet) + bar += ' ' + continue + + q1 = (1 - dp1 / 100) * tbox.h + q2 = (1 - dp2 / 100) * tbox.h + if dx == int(q1): + index1 = int((q1 - int(q1)) * 4) + if dx == int(q2): # both datapoints in the same rune + index2 = int((q2 - int(q2)) * 4) + else: + index2 = -1 # no dot + bar += self._generate_braille(index1, index2) + elif dx == int(q2): + # the right dot only is in the current rune + index2 = int((q2 - int(q2)) * 4) + bar += self._generate_braille(-1, index2) + else: + bar += ' ' + + print(tbox.t.move(tbox.x + dx, tbox.y) + bar) + + +class HBrailleFilledChart(Tile): + def __init__(self, val=100, *args, **kw): + super(HBrailleFilledChart, self).__init__(**kw) + self.value = val + self.datapoints = deque(maxlen=500) + + def append(self, dp): + self.datapoints.append(dp) + + def _generate_braille(self, lmax, rmax): + v = 0x28 * 256 + for l in range(lmax): + v += braille_r_left[l] + for r in range(rmax): + v += braille_r_right[r] + return unichr(v) + + def _display(self, tbox, parent): + tbox = self._draw_borders_and_title(tbox) + print(tbox.t.color(self.color)) + for dx in range(tbox.h): + bar = '' + for dy in range(tbox.w): + dp_index = (dy - tbox.w) * 2 + try: + dp1 = self.datapoints[dp_index] + dp2 = self.datapoints[dp_index + 1] + except IndexError: + # no data (yet) + bar += ' ' + continue + + q1 = (1 - dp1 / 100.0) * tbox.h + q2 = (1 - dp2 / 100.0) * tbox.h + if dx == int(q1): + index1 = 3 - int((q1 - int(q1)) * 4) + elif dx > q1: + index1 = 3 + else: + index1 = 0 + if dx == int(q2): + index2 = 3 - int((q2 - int(q2)) * 4) + elif dx > q2: + index2 = 3 + else: + index2 = 0 + bar += self._generate_braille(index1, index2) + + print(tbox.t.move(tbox.x + dx, tbox.y) + bar) diff --git a/demo.py b/demo.py new file mode 100755 index 0000000..c80b507 --- /dev/null +++ b/demo.py @@ -0,0 +1,74 @@ + +from time import sleep, time +import math + +from dashing import * + +if __name__ == '__main__': + + ui = HSplit( + VSplit( + HGauge(val=50, title="only title", border_color=5), + HGauge(label="only label", val=20, border_color=5), + HGauge(label="only label", val=30, border_color=5), + HGauge(label="only label", val=50, border_color=5), + HGauge(label="only label", val=80, border_color=5), + HGauge(val=20), + HGauge(label="label, no border", val=55), + HSplit( + VGauge(val=0, border_color=2), + VGauge(val=5, border_color=2), + VGauge(val=30, border_color=2), + VGauge(val=50, border_color=2), + VGauge(val=80, border_color=2, color=4), + VGauge(val=95, border_color=2, color=3), + ColorRangeVGauge( + val=100, + border_color=2, + colormap=( + (33, 2), + (66, 4), + (100, 1), + ) + ), + ) + ), + VSplit( + Text('Hello World,\nthis is dashing.', border_color=2), + Log(title='logs', border_color=5), + VChart(border_color=2, color=2), + HChart(border_color=2, color=2), + HBrailleChart(border_color=2, color=2), + # HBrailleFilledChart(border_color=2, color=2), + ), + title='Dashing', + ) + log = ui.items[1].items[1] + vchart = ui.items[1].items[2] + hchart = ui.items[1].items[3] + bchart = ui.items[1].items[4] + # bfchart = ui.items[1].items[5] + log.append("0 -----") + log.append("1 Hello") + log.append("2 -----") + prev_time = time() + for cycle in range(0, 100): + ui.items[0].items[0].value = int(50 + 49.9 * math.sin(cycle / 80.0)) + ui.items[0].items[1].value = int(50 + 45 * math.sin(cycle / 20.0)) + ui.items[0].items[2].value = int(50 + 45 * math.sin(cycle / 30.0 + 3)) + + vgauges = ui.items[0].items[-1].items + for gaugenum, vg in enumerate(vgauges): + vg.value = 50 + 49.9 * math.sin(cycle / 12.0 + gaugenum) + + t = int(time()) + if t != prev_time: + log.append("%s" % t) + prev_time = t + vchart.append(50 + 50 * math.sin(cycle / 16.0)) + hchart.append(99.9 * abs(math.sin(cycle / 26.0))) + bchart.append(50 + 50 * math.sin(cycle / 6.0)) + # bfchart.append(50 + 50 * math.sin(cycle / 16.0)) + ui.display() + + sleep(1.0/25) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..01bb954 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,3 @@ +[egg_info] +tag_build = dev +tag_svn_revision = true diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..7dd903f --- /dev/null +++ b/setup.py @@ -0,0 +1,27 @@ +from setuptools import setup, find_packages + +version = '0.1' + +setup(name='dashing', + version=version, + description="High-level terminal-based dashboard", + long_description="""\ +Easily create dashboards""", + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)', + ], + keywords='dashboard terminal', + author='Federico Ceratto', + author_email='federico@debian.org', + url='https://github.com/FedericoCeratto/dashing', + license='LGPL', + packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), + include_package_data=True, + zip_safe=False, + install_requires=[ + 'blessed' + ], + )