1717 from typing import Collection
1818except ImportError :
1919 from typing import Container , Generic , Sized , TypeVar
20+
2021 # Python 3.5
2122 # noinspection PyAbstractClass
2223 class Collection (Generic [TypeVar ('T_co' , covariant = True )], Container , Sized , Iterable ):
@@ -124,6 +125,45 @@ def _split(self, text):
124125 chunks = [c for c in chunks if c ]
125126 return chunks
126127
128+ def _handle_long_word (self , reversed_chunks , cur_line , cur_len , width ):
129+ """_handle_long_word(chunks : [string],
130+ cur_line : [string],
131+ cur_len : int, width : int)
132+
133+ Handle a chunk of text (most likely a word, not whitespace) that
134+ is too long to fit in any line.
135+ """
136+ # Figure out when indent is larger than the specified width, and make
137+ # sure at least one character is stripped off on every pass
138+ if width < 1 :
139+ space_left = 1
140+ else :
141+ space_left = width - cur_len
142+
143+ # If we're allowed to break long words, then do so: put as much
144+ # of the next chunk onto the current line as will fit.
145+ if self .break_long_words :
146+ shard_length = space_left
147+ shard = reversed_chunks [- 1 ][:shard_length ]
148+ while _wcswidth (shard ) > space_left and shard_length > 0 :
149+ shard_length -= 1
150+ shard = reversed_chunks [- 1 ][:shard_length ]
151+ if shard_length > 0 :
152+ cur_line .append (shard )
153+ reversed_chunks [- 1 ] = reversed_chunks [- 1 ][shard_length :]
154+
155+ # Otherwise, we have to preserve the long word intact. Only add
156+ # it to the current line if there's nothing already there --
157+ # that minimizes how much we violate the width constraint.
158+ elif not cur_line :
159+ cur_line .append (reversed_chunks .pop ())
160+
161+ # If we're not allowed to break long words, and there's already
162+ # text on the current line, do nothing. Next time through the
163+ # main loop of _wrap_chunks(), we'll wind up here again, but
164+ # cur_len will be zero, so the next line will be entirely
165+ # devoted to the long word that we can't handle right now.
166+
127167 def _wrap_chunks (self , chunks ):
128168 """_wrap_chunks(chunks : [string]) -> [string]
129169
@@ -174,12 +214,12 @@ def _wrap_chunks(self, chunks):
174214 del chunks [- 1 ]
175215
176216 while chunks :
177- l = _wcswidth (chunks [- 1 ])
217+ length = _wcswidth (chunks [- 1 ])
178218
179219 # Can at least squeeze this chunk onto the current line.
180- if cur_len + l <= width :
220+ if cur_len + length <= width :
181221 cur_line .append (chunks .pop ())
182- cur_len += l
222+ cur_len += length
183223
184224 # Nope, this line is full.
185225 else :
@@ -197,19 +237,15 @@ def _wrap_chunks(self, chunks):
197237 del cur_line [- 1 ]
198238
199239 if cur_line :
200- if (self .max_lines is None or
201- len (lines ) + 1 < self .max_lines or
202- (not chunks or
203- self .drop_whitespace and
204- len (chunks ) == 1 and
205- not chunks [0 ].strip ()) and cur_len <= width ):
240+ if (self .max_lines is None or len (lines ) + 1 < self .max_lines
241+ or (not chunks or self .drop_whitespace and len (chunks ) == 1 and not chunks [0 ].strip ())
242+ and cur_len <= width ):
206243 # Convert current line back to a string and store it in
207244 # list of all lines (return value).
208245 lines .append (indent + '' .join (cur_line ))
209246 else :
210247 while cur_line :
211- if (cur_line [- 1 ].strip () and
212- cur_len + _wcswidth (self .placeholder ) <= width ):
248+ if cur_line [- 1 ].strip () and cur_len + _wcswidth (self .placeholder ) <= width :
213249 cur_line .append (self .placeholder )
214250 lines .append (indent + '' .join (cur_line ))
215251 break
@@ -218,8 +254,7 @@ def _wrap_chunks(self, chunks):
218254 else :
219255 if lines :
220256 prev_line = lines [- 1 ].rstrip ()
221- if (_wcswidth (prev_line ) + _wcswidth (self .placeholder ) <=
222- self .width ):
257+ if _wcswidth (prev_line ) + _wcswidth (self .placeholder ) <= self .width :
223258 lines [- 1 ] = prev_line + self .placeholder
224259 break
225260 lines .append (indent + self .placeholder .lstrip ())
@@ -233,7 +268,7 @@ def _translate_tabs(text: str) -> str:
233268 tabpos = text .find ('\t ' )
234269 while tabpos >= 0 :
235270 before_text = text [:tabpos ]
236- after_text = text [tabpos + 1 :]
271+ after_text = text [tabpos + 1 :]
237272 before_width = _wcswidth (before_text )
238273 tab_pad = TAB_WIDTH - (before_width % TAB_WIDTH )
239274 text = before_text + '{: <{width}}' .format ('' , width = tab_pad ) + after_text
@@ -259,7 +294,7 @@ class TableColors(object):
259294 TEXT_COLOR_GREEN = fg (119 )
260295 TEXT_COLOR_BLUE = fg (27 )
261296 BG_COLOR_ROW = bg (234 )
262- BG_RESET = bg (0 )
297+ BG_RESET = attr ( 'reset' ) # docs say bg(0) should do this but it doesn't work right
263298 BOLD = attr ('bold' )
264299 RESET = attr ('reset' )
265300 except ImportError :
@@ -298,7 +333,7 @@ def set_color_library(cls, library_name: str) -> None:
298333 cls .TEXT_COLOR_GREEN = fg (119 )
299334 cls .TEXT_COLOR_BLUE = fg (27 )
300335 cls .BG_COLOR_ROW = bg (234 )
301- cls .BG_RESET = bg (0 )
336+ cls .BG_RESET = attr ( 'reset' ) # docs say bg(0) should do this but it doesn't work right
302337 cls .BOLD = attr ('bold' )
303338 cls .RESET = attr ('reset' )
304339 elif library_name == 'colorama' :
@@ -351,25 +386,26 @@ def _pad_columns(text: str, pad_char: str, align: Union[ColumnAlignment, str], w
351386 """Returns a string padded out to the specified width"""
352387 text = _translate_tabs (text )
353388 display_width = _printable_width (text )
389+ diff = width - display_width
354390 if display_width >= width :
355391 return text
356392
357393 if align in (ColumnAlignment .AlignLeft , ColumnAlignment .AlignLeft .format_string ()):
358394 out_text = text
359- out_text += '{:{pad}<{width}}' .format ('' , pad = pad_char , width = width - display_width )
395+ out_text += '{:{pad}<{width}}' .format ('' , pad = pad_char , width = diff )
360396 elif align in (ColumnAlignment .AlignRight , ColumnAlignment .AlignRight .format_string ()):
361- out_text = '{:{pad}<{width}}' .format ('' , pad = pad_char , width = width - display_width )
397+ out_text = '{:{pad}<{width}}' .format ('' , pad = pad_char , width = diff )
362398 out_text += text
363399 elif align in (ColumnAlignment .AlignCenter , ColumnAlignment .AlignCenter .format_string ()):
364- lead_pad = int (( width - display_width ) / 2 )
365- tail_pad = width - display_width - lead_pad
400+ lead_pad = diff // 2
401+ tail_pad = diff - lead_pad
366402
367403 out_text = '{:{pad}<{width}}' .format ('' , pad = pad_char , width = lead_pad )
368404 out_text += text
369405 out_text += '{:{pad}<{width}}' .format ('' , pad = pad_char , width = tail_pad )
370406 else :
371407 out_text = text
372- out_text += '{:{pad}<{width}}' .format ('' , pad = pad_char , width = width - display_width )
408+ out_text += '{:{pad}<{width}}' .format ('' , pad = pad_char , width = diff )
373409
374410 return out_text
375411
@@ -565,7 +601,7 @@ def border_right_span(self, row_index: Union[int, None]) -> str:
565601 bg_reset = self .bg_reset if self .bg_reset is not None else TableColors .BG_RESET
566602 return bg_reset + '║'
567603
568- def col_divider_span (self , row_index : Union [int , None ]) -> str :
604+ def col_divider_span (self , row_index : Union [int , None ]) -> str :
569605 bg_reset = self .bg_reset if self .bg_reset is not None else TableColors .BG_RESET
570606 bg_primary = self .bg_primary if self .bg_primary is not None else TableColors .BG_RESET
571607 bg_alt = self .bg_alt if self .bg_alt is not None else TableColors .BG_COLOR_ROW
@@ -948,24 +984,30 @@ def generate_table(self, entries: Iterable[Union[Iterable, object]], force_trans
948984 entry_opts = dict ()
949985 if use_attribs :
950986 # if use_attribs is set, the entries can optionally be a tuple with (object, options)
951- try :
952- iter (entry )
953- except TypeError :
954- # not iterable, so we just use the object directly
987+ if isinstance (entry , dict ):
955988 entry_obj = entry
956- if self ._row_tagger is not None :
957- entry_opts = self ._row_tagger (entry_obj )
958989 else :
959- entry_obj = entry [0 ]
960- if self ._row_tagger is not None :
961- entry_opts = self ._row_tagger (entry_obj )
962- if len (entry ) == 2 and isinstance (entry [1 ], dict ):
963- entry_opts .update (entry [1 ])
990+ try :
991+ iter (entry )
992+ except TypeError :
993+ # not iterable, so we just use the object directly
994+ entry_obj = entry
995+ if self ._row_tagger is not None :
996+ entry_opts = self ._row_tagger (entry_obj )
997+ else :
998+ entry_obj = entry [0 ]
999+ if self ._row_tagger is not None :
1000+ entry_opts = self ._row_tagger (entry_obj )
1001+ if len (entry ) == 2 and isinstance (entry [1 ], dict ):
1002+ entry_opts .update (entry [1 ])
9641003
9651004 for column_index , attrib_name in enumerate (self ._column_attribs ):
9661005 field_obj = None
967- if isinstance (attrib_name , str ) and hasattr (entry_obj , attrib_name ):
968- field_obj = getattr (entry_obj , attrib_name , '' )
1006+ if isinstance (attrib_name , str ):
1007+ if hasattr (entry_obj , attrib_name ):
1008+ field_obj = getattr (entry_obj , attrib_name , '' )
1009+ elif isinstance (entry_obj , dict ) and attrib_name in entry_obj :
1010+ field_obj = entry_obj [attrib_name ]
9691011 # if the object attribute is callable, go ahead and call it and get the result
9701012 if callable (field_obj ):
9711013 field_obj = field_obj ()
0 commit comments