├── .gitignore ├── .travis.yml ├── AUTHORS ├── CHANGES.txt ├── LICENSE ├── MANIFEST.in ├── README.md ├── generate_authors.sh ├── pylsy ├── __init__.py ├── pylsy.py └── tests │ ├── __init__.py │ ├── correct.out │ └── pylsy_test.py ├── pzi └── span.png ├── runtests.sh ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *.pyc 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .cache 41 | nosetests.xml 42 | coverage.xml 43 | 44 | # Translations 45 | *.mo 46 | *.pot 47 | 48 | # Django stuff: 49 | *.log 50 | 51 | # Sphinx documentation 52 | docs/_build/ 53 | 54 | # PyBuilder 55 | target/ 56 | 57 | # IDE 58 | *.idea 59 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - 3.6 4 | - pypy3 5 | 6 | install: 7 | - pip3 install pep8 8 | - pip3 install . 9 | 10 | script: 11 | - "pep8 ." 12 | - "bash runtests.sh" 13 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | chao 2 | Cholerae Hu 3 | DrNightmare 4 | Leviathan 5 | OldPanda 6 | Sinux 7 | Umair Hussain 8 | Mingye Wang 9 | Jeff Quast (wcswidth) 10 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | This file documents changes from version to version. 2 | 3 | v2.5, 2015-12-22 -- Update CHANGES.txt #37 4 | 5 | v2.4, 2015-12-22 -- Run CI unit tests on Python 3 too, #34. 6 | - No significant change in final package. Perhaps it's time to use semver.org. 7 | 8 | v2.3, 2015-12-22 -- README update concerning Py2 and Unicode, #32, #33. 9 | 10 | v2.2, 2015-12-22 -- Unicode and docs from Arthur2e5, #30, #31. 11 | 12 | v2.1, 2015-09-05 -- Add `append_data` method from gnithin. #25. 13 | 14 | v0.1, 2015-08-09 -- Initial release. 15 | 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Pylsy Authors 4 | See the AUTHORS file for a full list of authors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE.txt 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Pylsy 2 | ===== 3 | 4 | [![Build Status](https://travis-ci.org/Leviathan1995/Pylsy.svg?branch=master)](https://travis-ci.org/Leviathan1995/Pylsy) 5 | [![PyPI version](https://badge.fury.io/py/Pylsy.svg)](https://badge.fury.io/py/Pylsy) 6 | 7 | Pylsy is a simple Python library for drawing tables in the terminal/console. Just two lines of code! 8 | 9 | ![Screenshot](https://raw.githubusercontent.com/Leviathan1995/Pylsy/master/pzi/span.png) 10 | 11 | Install 12 | ------- 13 | 14 | pip3 install pylsy 15 | 16 | Sample Usage 17 | ------------ 18 | 19 | ```python 20 | # In the very first, pylsy needs to be imported 21 | from pylsy import pylsytable 22 | 23 | # First, you need to create a list, which will contain the table attributes: 24 | attributes=["name","age","sex","id","time"] 25 | 26 | # Then feed it to PylsyTable to create the table object: 27 | table=pylsytable(attributes) 28 | 29 | # Now populate the attributes with values. Prepare a list for the names: 30 | name=["sun","lsy","luna"] 31 | 32 | # Add the data into it: 33 | table.add_data("name",name) 34 | 35 | # If you want to insert some extra values to the same column, 36 | # you can pass a list as a parameter: 37 | table.append_data("name",["leviathan"]) 38 | 39 | # Just a single value is OK too: 40 | table.append_data("name",u"小明") # Note: everything will be coerced to unicode strings. 41 | 42 | # Now with all your attributes and values, we can create our table: 43 | print(table) 44 | 45 | # With Python 2 things are a bit trickier, since str() is ascii-only and our dear 小明 requires unicode: 46 | print(table.__str__()) # The raw unicode-enabled string. Think as `table.__unicode__()`. 47 | ``` 48 | 49 | License 50 | ------- 51 | MIT 52 | -------------------------------------------------------------------------------- /generate_authors.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | git log --format='%aN <%aE>' | LC_ALL=C.UTF-8 sort -uf > AUTHORS 3 | -------------------------------------------------------------------------------- /pylsy/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from pylsy.pylsy import pylsytable 3 | __version__ = "3.6" 4 | -------------------------------------------------------------------------------- /pylsy/pylsy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Copyright (c) 2015 Pylsy Authors 4 | # For a full list of authors, see the AUTHORS file at 5 | # https://github.com/Leviathan1995/Pylsy/blob/master/AUTHORS. 6 | # @license MIT 7 | from __future__ import print_function 8 | from wcwidth import wcwidth 9 | 10 | 11 | class pylsytable(object): 12 | 13 | def __init__(self, attributes): 14 | """Creates a new PylsyTable object with the given attrs (cols).""" 15 | self.StrTable = "" 16 | self.Table = [] 17 | self.AttributesLength = [] 18 | self.Lines_num = 0 19 | if type(attributes) != list: 20 | attributes = [attributes] 21 | self.Attributes = [u"{0}".format(attr) for attr in attributes] 22 | self.Cols_num = len(self.Attributes) 23 | for attribute in self.Attributes: 24 | col = dict() 25 | col[attribute] = [] 26 | self.Table.append(col) 27 | 28 | def _print_divide(self): 29 | """Prints all those table line dividers.""" 30 | for space in self.AttributesLength: 31 | self.StrTable += "+ " + "- " * space 32 | self.StrTable += "+"+"\n" 33 | 34 | def append_data(self, attribute, values): 35 | """Appends the given value(s) to the attribute (column).""" 36 | found = False 37 | if type(values) != list: 38 | values = [values] 39 | for col in self.Table: 40 | if attribute in col: 41 | dict_values = [u"{0}".format(value) for value in values] 42 | col[attribute] += dict_values 43 | found = True 44 | if not found: 45 | raise KeyError(attribute) 46 | 47 | def add_data(self, attribute, values): 48 | """Sets the given values for the attribute (column).""" 49 | found = False 50 | if type(values) != list: 51 | values = [values] 52 | for col in self.Table: 53 | if attribute in col: 54 | dict_values = [u"{0}".format(value) for value in values] 55 | col[attribute] = dict_values 56 | found = True 57 | if not found: 58 | raise KeyError(attribute) 59 | 60 | def _create_table(self): 61 | """ 62 | Creates a pretty-printed string representation of the table as 63 | ``self.StrTable``. 64 | """ 65 | self.StrTable = "" 66 | self.AttributesLength = [] 67 | self.Lines_num = 0 68 | # Prepare some values.. 69 | for col in self.Table: 70 | # Updates the table line count if necessary 71 | values = list(col.values())[0] 72 | self.Lines_num = max(self.Lines_num, len(values)) 73 | # find the length of longest value in current column 74 | key_length = max([self._disp_width(v) for v in values] or [0]) 75 | # and also the table header 76 | key_length = max(key_length, self._disp_width(list(col.keys())[0])) 77 | self.AttributesLength.append(key_length) 78 | # Do the real thing. 79 | self._print_head() 80 | self._print_value() 81 | 82 | def _print_head(self): 83 | """Generates the table header.""" 84 | self._print_divide() 85 | self.StrTable += "| " 86 | for colwidth, attr in zip(self.AttributesLength, self.Attributes): 87 | self.StrTable += self._pad_string(attr, colwidth * 2) 88 | self.StrTable += "| " 89 | self.StrTable += '\n' 90 | self._print_divide() 91 | 92 | def _print_value(self): 93 | """Generates the table values.""" 94 | for line in range(self.Lines_num): 95 | for col, length in zip(self.Table, self.AttributesLength): 96 | vals = list(col.values())[0] 97 | val = vals[line] if len(vals) != 0 and line < len(vals) else '' 98 | self.StrTable += "| " 99 | self.StrTable += self._pad_string(val, length * 2) 100 | self.StrTable += "|"+'\n' 101 | self._print_divide() 102 | 103 | def _disp_width(self, pwcs, n=None): 104 | """ 105 | A wcswidth that never gives -1. Copying existing code is evil, but.. 106 | 107 | github.com/jquast/wcwidth/blob/07cea7f/wcwidth/wcwidth.py#L182-L204 108 | """ 109 | # pylint: disable=C0103 110 | # Invalid argument name "n" 111 | # TODO: Shall we consider things like ANSI escape seqs here? 112 | # We can implement some ignore-me segment like those wrapped by 113 | # \1 and \2 in readline too. 114 | end = len(pwcs) if n is None else n 115 | idx = slice(0, end) 116 | width = 0 117 | for char in pwcs[idx]: 118 | width += max(0, wcwidth(char)) 119 | return width 120 | 121 | def _pad_string(self, str, colwidth): 122 | """Center-pads a string to the given column width using spaces.""" 123 | width = self._disp_width(str) 124 | prefix = (colwidth - 1 - width) // 2 125 | suffix = colwidth - prefix - width 126 | return ' ' * prefix + str + ' ' * suffix 127 | 128 | def __str__(self): 129 | """Returns a pretty-printed string representation of the table.""" 130 | self._create_table() 131 | return self.StrTable 132 | -------------------------------------------------------------------------------- /pylsy/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mesaukee/Pylsy/d77a180d4b9ab7d28d5b5e4185adbd372b5ff5aa/pylsy/tests/__init__.py -------------------------------------------------------------------------------- /pylsy/tests/correct.out: -------------------------------------------------------------------------------- 1 | + - - - - + - - - + 2 | | name | age | 3 | + - - - - + - - - + 4 | | a | 1 | 5 | + - - - - + - - - + 6 | | b | 2 | 7 | + - - - - + - - - + 8 | -------------------------------------------------------------------------------- /pylsy/tests/pylsy_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import 4 | 5 | import os 6 | import unittest 7 | from pylsy import pylsytable 8 | 9 | 10 | TEST_DIR = os.path.dirname(__file__) 11 | 12 | 13 | class PylsyTableTests(unittest.TestCase): 14 | 15 | def setUp(self): 16 | attributes = ["name", "age"] 17 | self.table = pylsytable(attributes) 18 | 19 | def tearDown(self): 20 | self.table = None 21 | 22 | def testCreateTable(self): 23 | name = ["a"] 24 | self.table.add_data("name", name) 25 | self.table.append_data("name", "b") 26 | age = [1, 2] 27 | self.table.add_data("age", age) 28 | 29 | with open(os.path.join(TEST_DIR, "correct.out"), "r") as correct_file: 30 | self.assertEqual(self.table.__str__(), correct_file.read()) 31 | 32 | def testAddData(self): 33 | # Creating a table with data 34 | old_names = [ 35 | "mercury", 36 | "venus", 37 | "earth" 38 | ] 39 | self.table.add_data("name", old_names) 40 | 41 | # This is necessary as it calls `create_table()`, 42 | # which calculates Lines_num property. 43 | # This is equivalent to calling `print table` 44 | self.table.__str__() 45 | 46 | # Overwriting existing table with new data 47 | new_names = [ 48 | "mars", 49 | "jupiter" 50 | ] 51 | self.table.add_data("name", new_names) 52 | 53 | expected_output = "\n".join([ 54 | "+ - - - - - - - + - - - +", 55 | "| name | age | ", 56 | "+ - - - - - - - + - - - +", 57 | "| mars | |", 58 | "+ - - - - - - - + - - - +", 59 | "| jupiter | |", 60 | "+ - - - - - - - + - - - +", 61 | "" 62 | ]) 63 | 64 | output = self.table.__str__() 65 | 66 | self.assertEqual(output, expected_output) 67 | 68 | 69 | if __name__ == '__main__': 70 | unittest.main() 71 | -------------------------------------------------------------------------------- /pzi/span.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mesaukee/Pylsy/d77a180d4b9ab7d28d5b5e4185adbd372b5ff5aa/pzi/span.png -------------------------------------------------------------------------------- /runtests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | python setup.py test 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file=README.md 3 | 4 | [bdist_wheel] 5 | universal=1 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | try: 4 | # Use setuptools if available, for install_requires (among other things). 5 | from setuptools import setup 6 | except ImportError: 7 | from distutils.core import setup 8 | 9 | setup( 10 | name='Pylsy', 11 | packages=['pylsy'], 12 | install_requires=['wcwidth'], 13 | version='3.6', 14 | description='Pylsy is a simple library that draws tables in the Terminal.', 15 | long_description=open('README.md').read(), 16 | author='leviathan1995', 17 | author_email='leviathan0992@gmail.com', 18 | url='https://github.com/Leviathan1995/Pylsy', 19 | license='MIT', 20 | test_suite='pylsy.tests', 21 | ) 22 | --------------------------------------------------------------------------------