Reputation: 8315
I'm trying to make a simple Python table class that accepts a list of lists as content and then builds a table string representation that can be printed to the terminal. A feature I want is wrapping of text in cells of the table.
I am happy to use the module textwrap
in order to determine the appropriate text wrapping.
Basically, for the following content
[
["heading 1", "heading 2"],
["some text", "some more text"],
["lots and lots and lots and lots and lots of text", "some more text"]
]
I want a generated representation something like the following:
-------------------------------
|heading 1 |heading 2 |
-------------------------------
|some text |some more text|
-------------------------------
|lots and lots |some more text|
|and lots and | |
|lots and lots | |
|of text | |
-------------------------------
My question is: How can I implement the multiline cells, given the list representation of the text wrapping determined by textwrap
?
The code I have is as follows:
import textwrap
import subprocess
def terminalWidth():
return(
int(
subprocess.Popen(
["tput", "cols"],
stdout = subprocess.PIPE
).communicate()[0].decode("utf-8").strip("\n")
)
)
class Table(object):
def __init__(
self,
content = None,
widthTable = None,
columnDelimiter = "|",
rowDelimiter = "-"
):
self.content = content
if widthTable is None:
self.widthTable = terminalWidth()
self.columnDelimiter = columnDelimiter
self.rowDelimiter = rowDelimiter
def value(self):
self.numberOfColumns = len(self.content[0])
self.widthOfColumns =\
self.widthTable / self.numberOfColumns -\
self.numberOfColumns * len(self.columnDelimiter)
self.tableString = ""
for row in self.content:
for column in row:
self.tableString =\
self.tableString +\
self.columnDelimiter +\
textwrap.wrap(column, self.widthOfColumns)
self.tableString =\
self.tableString +\
self.columnDelimiter +\
"\n" +\
self.widthTable * self.rowDelimiter +\
"\n" +\
return(self.tableString)
def __str__(self):
return(self.value())
def main():
table1Content = [
["heading 1", "heading 2"],
["some text", "some more text"],
["lots and lots and lots and lots and lots of text", "some more text"]
]
table1 = Table(
content = table1Content,
widthTable = 15
)
print(table1)
if __name__ == '__main__':
main()
Upvotes: 1
Views: 3758
Reputation: 1388
Here's a class that does what you want:
import textwrap
class Table:
def __init__(self,
contents,
wrap,
wrapAtWordEnd = True,
colDelim = "|",
rowDelim = "-"):
self.contents = contents
self.wrap = wrap
self.colDelim = colDelim
self.wrapAtWordEnd = wrapAtWordEnd
# Extra rowDelim characters where colDelim characters are
p = len(self.colDelim) * (len(self.contents[0]) - 1)
# Line gets too long for one concatenation
self.rowDelim = self.colDelim
self.rowDelim += rowDelim * (self.wrap * max([len(i) for i in self.contents]) + p)
self.rowDelim += self.colDelim + "\n"
def withoutTextWrap(self):
string = self.rowDelim
for row in self.contents:
maxWrap = (max([len(i) for i in row]) // self.wrap) + 1
for r in range(maxWrap):
string += self.colDelim
for column in row:
start = r * self.wrap
end = (r + 1) * self.wrap
string += column[start : end].ljust(self.wrap)
string += self.colDelim
string += "\n"
string += self.rowDelim
return string
def withTextWrap(self):
print(self.wrap)
string = self.rowDelim
# Restructure to get textwrap.wrap output for each cell
l = [[textwrap.wrap(col, self.wrap) for col in row] for row in self.contents]
for row in l:
for n in range(max([len(i) for i in row])):
string += self.colDelim
for col in row:
if n < len(col):
string += col[n].ljust(self.wrap)
else:
string += " " * self.wrap
string += self.colDelim
string += "\n"
string += self.rowDelim
return string
def __str__(self):
if self.wrapAtWordEnd:
return self.withTextWrap()
else:
return self.withoutTextWrap()
if __name__ == "__main__":
l = [["heading 1", "heading 2", "asdf"],
["some text", "some more text", "Lorem ipsum dolor sit amet."],
["lots and lots and lots and lots and lots of text", "some more text", "foo"]]
table = Table(l, 20, True)
print(table)
withTextWrap()
uses the textwrap
module you mention, and makes use of its output to build a table representation. While playing around with this, I also came up with a way of doing what you want to do (almost), without the textwrap
module, which you can see in the withoutTextWrap()
method. I say "almost" because the textwrap
module breaks lines properly at the end of a word, while my method breaks the strings directly at the wrap point.
So if you create the table with the third constructor argument set to True
, the textwrap
module is used, which produces this output:
|--------------------------------------------------------------|
|heading 1 |heading 2 |asdf |
|--------------------------------------------------------------|
|some text |some more text |Lorem ipsum dolor |
| | |sit amet. |
|--------------------------------------------------------------|
|lots and lots and |some more text |foo |
|lots and lots and | | |
|lots of text | | |
|--------------------------------------------------------------|
And if that argument is False
, the non-textwrap
version is called:
|--------------------------------------------------------------|
|heading 1 |heading 2 |asdf |
|--------------------------------------------------------------|
|some text |some more text |Lorem ipsum dolor si|
| | |t amet. |
|--------------------------------------------------------------|
|lots and lots and lo|some more text |foo |
|ts and lots and lots| | |
| of text | | |
|--------------------------------------------------------------|
Hope this helps.
Upvotes: 5