Repository: python-cmd2/cmd2
Pull request #1066 (2021-03-01 22:44:38)

Fixed issue where TableCreator was tossing blank last lines

codecov[bot]: # [Codecov](https://codecov.io/gh/python-cmd2/cmd2/pull/1066?src=pr&el=h1) Report
> Merging [#1066](https://codecov.io/gh/python-cmd2/cmd2/pull/1066?src=pr&el=desc) (6820165) into [master](https://codecov.io/gh/python-cmd2/cmd2/commit/97f14b266e038a5d4acf7841d538cf37f066d328?el=desc) (97f14b2) will **decrease** coverage by `1.10%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/python-cmd2/cmd2/pull/1066/graphs/tree.svg?width=650&height=150&src=pr&token=eks2PUwa6A)](https://codecov.io/gh/python-cmd2/cmd2/pull/1066?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #1066      +/-   ##
==========================================
- Coverage   98.22%   97.11%   -1.11%     
==========================================
  Files          22       22              
  Lines        4623     4617       -6     
==========================================
- Hits         4541     4484      -57     
- Misses         82      133      +51     
```


| [Impacted Files](https://codecov.io/gh/python-cmd2/cmd2/pull/1066?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [cmd2/table\_creator.py](https://codecov.io/gh/python-cmd2/cmd2/pull/1066/diff?src=pr&el=tree#diff-Y21kMi90YWJsZV9jcmVhdG9yLnB5) | `100.00% <100.00%> (ø)` | |
| [cmd2/rl\_utils.py](https://codecov.io/gh/python-cmd2/cmd2/pull/1066/diff?src=pr&el=tree#diff-Y21kMi9ybF91dGlscy5weQ==) | `58.53% <0.00%> (-41.47%)` | :arrow_down: |
| [cmd2/clipboard.py](https://codecov.io/gh/python-cmd2/cmd2/pull/1066/diff?src=pr&el=tree#diff-Y21kMi9jbGlwYm9hcmQucHk=) | `69.23% <0.00%> (-30.77%)` | :arrow_down: |
| [cmd2/cmd2.py](https://codecov.io/gh/python-cmd2/cmd2/pull/1066/diff?src=pr&el=tree#diff-Y21kMi9jbWQyLnB5) | `96.68% <0.00%> (-1.27%)` | :arrow_down: |
| [cmd2/utils.py](https://codecov.io/gh/python-cmd2/cmd2/pull/1066/diff?src=pr&el=tree#diff-Y21kMi91dGlscy5weQ==) | `96.98% <0.00%> (-0.70%)` | :arrow_down: |
| [plugins/template/cmd2\_myplugin/myplugin.py](https://codecov.io/gh/python-cmd2/cmd2/pull/1066/diff?src=pr&el=tree#diff-cGx1Z2lucy90ZW1wbGF0ZS9jbWQyX215cGx1Z2luL215cGx1Z2luLnB5) | `92.30% <0.00%> (-0.29%)` | :arrow_down: |
| [cmd2/plugin.py](https://codecov.io/gh/python-cmd2/cmd2/pull/1066/diff?src=pr&el=tree#diff-Y21kMi9wbHVnaW4ucHk=) | `100.00% <0.00%> (ø)` | |
| [cmd2/history.py](https://codecov.io/gh/python-cmd2/cmd2/pull/1066/diff?src=pr&el=tree#diff-Y21kMi9oaXN0b3J5LnB5) | `100.00% <0.00%> (ø)` | |
| [cmd2/parsing.py](https://codecov.io/gh/python-cmd2/cmd2/pull/1066/diff?src=pr&el=tree#diff-Y21kMi9wYXJzaW5nLnB5) | `100.00% <0.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/python-cmd2/cmd2/pull/1066?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/python-cmd2/cmd2/pull/1066?src=pr&el=footer). Last update [97f14b2...6820165](https://codecov.io/gh/python-cmd2/cmd2/pull/1066?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 911df477..afed0b53 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,7 @@
 ## 2.0.0 (TBD, 2021)
-* Breaking changes
+* Bug Fixes
+  * Fixed issue where TableCreator was tossing blank last lines
+* Breaking Changes
     * `cmd2` 2.0 supports Python 3.6+ (removed support for Python 3.5)
     * Argparse Completion / Settables
         * Replaced `choices_function` / `choices_method` with `choices_provider`.
diff --git a/cmd2/table_creator.py b/cmd2/table_creator.py
index 8dcd80f0..d159ddbf 100644
--- a/cmd2/table_creator.py
+++ b/cmd2/table_creator.py
@@ -351,6 +351,10 @@ class TableCreator:
                 last_word = data_line_index == len(data_str_lines) - 1 and char_index == len(data_line)
                 add_word(cur_word_buf.getvalue(), last_word)
 
+            # If the last line is empty, then add a newline
+            elif data_line_index == len(data_str_lines) - 1:
+                wrapped_buf.write('\n')
+
             # Stop line loop if we've written to max_lines
             if total_lines == max_lines:
                 # If this isn't the last data line and there is space


Test patch:
diff --git a/tests/test_table_creator.py b/tests/test_table_creator.py
index 64db0dd7..396016eb 100644
--- a/tests/test_table_creator.py
+++ b/tests/test_table_creator.py
@@ -18,6 +18,10 @@ from cmd2.table_creator import (
     VerticalAlignment,
 )
 
+# Turn off black formatting for entire file so multiline strings
+# can be visually aligned to match the tables being tested.
+# fmt: off
+
 
 def test_column_creation():
     # Width less than 1
@@ -119,6 +123,17 @@ def test_column_alignment():
     )
 
 
+def test_blank_last_line():
+    """This tests that an empty line is inserted when the last data line is blank"""
+    column_1 = Column("Col 1", width=10)
+    tc = TableCreator([column_1])
+
+    row_data = ['my line\n\n']
+    row = tc.generate_row(row_data=row_data)
+    assert row == ('my line   \n'
+                   '          ')
+
+
 def test_wrap_text():
     column_1 = Column("Col 1", width=10)
     tc = TableCreator([column_1])
@@ -126,12 +141,19 @@ def test_wrap_text():
     # Test normal wrapping
     row_data = ['Some text to wrap\nA new line that will wrap\nNot wrap\n 1  2   3']
     row = tc.generate_row(row_data=row_data)
-    assert row == ('Some text \n' 'to wrap   \n' 'A new line\n' 'that will \n' 'wrap      \n' 'Not wrap  \n' ' 1  2   3 ')
+    assert row == ('Some text \n'
+                   'to wrap   \n'
+                   'A new line\n'
+                   'that will \n'
+                   'wrap      \n'
+                   'Not wrap  \n'
+                   ' 1  2   3 ')
 
     # Test preserving a multiple space sequence across a line break
     row_data = ['First      last one']
     row = tc.generate_row(row_data=row_data)
-    assert row == ('First     \n' ' last one ')
+    assert row == ('First     \n'
+                   ' last one ')
 
 
 def test_wrap_text_max_lines():
@@ -141,27 +163,32 @@ def test_wrap_text_max_lines():
     # Test not needing to truncate the final line
     row_data = ['First line last line']
     row = tc.generate_row(row_data=row_data)
-    assert row == ('First line\n' 'last line ')
+    assert row == ('First line\n'
+                   'last line ')
 
     # Test having to truncate the last word because it's too long for the final line
     row_data = ['First line last lineextratext']
     row = tc.generate_row(row_data=row_data)
-    assert row == ('First line\n' 'last line…')
+    assert row == ('First line\n'
+                   'last line…')
 
     # Test having to truncate the last word because it fits the final line but there is more text not being included
     row_data = ['First line thistxtfit extra']
     row = tc.generate_row(row_data=row_data)
-    assert row == ('First line\n' 'thistxtfi…')
+    assert row == ('First line\n'
+                   'thistxtfi…')
 
     # Test having to truncate the last word because it fits the final line but there are more lines not being included
     row_data = ['First line thistxtfit\nextra']
     row = tc.generate_row(row_data=row_data)
-    assert row == ('First line\n' 'thistxtfi…')
+    assert row == ('First line\n'
+                   'thistxtfi…')
 
     # Test having space left on the final line and adding an ellipsis because there are more lines not being included
     row_data = ['First line last line\nextra line']
     row = tc.generate_row(row_data=row_data)
-    assert row == ('First line\n' 'last line…')
+    assert row == ('First line\n'
+                   'last line…')
 
 
 def test_wrap_long_word():
@@ -174,7 +201,8 @@ def test_wrap_long_word():
 
     # Test header row
     header = tc.generate_row()
-    assert header == ('LongColumn            \n' 'Name        Col 2     ')
+    assert header == ('LongColumn            \n'
+                      'Name        Col 2     ')
 
     # Test data row
     row_data = list()
@@ -230,7 +258,8 @@ def test_wrap_long_word_max_data_lines():
     row_data.append("A LongerThan10RunsOverLast")
 
     row = tc.generate_row(row_data=row_data)
-    assert row == ('LongerThan  LongerThan  LongerThan  A LongerT…\n' '10FitsLast  10FitsLas…  10RunsOve…            ')
+    assert row == ('LongerThan  LongerThan  LongerThan  A LongerT…\n'
+                   '10FitsLast  10FitsLas…  10RunsOve…            ')
 
 
 def test_wrap_long_char_wider_than_max_width():
@@ -318,7 +347,10 @@ def test_simple_table_creation():
     table = st.generate_table(row_data)
 
     assert table == (
-        'Col 1             Col 2           \n' 'Col 1 Row 1       Col 2 Row 1     \n' '\n' 'Col 1 Row 2       Col 2 Row 2     '
+        'Col 1             Col 2           \n'
+        'Col 1 Row 1       Col 2 Row 1     \n'
+        '\n'
+        'Col 1 Row 2       Col 2 Row 2     '
     )
 
     # No row spacing
@@ -335,7 +367,9 @@ def test_simple_table_creation():
     st = SimpleTable([column_1, column_2])
     table = st.generate_table(row_data, include_header=False)
 
-    assert table == ('Col 1 Row 1       Col 2 Row 1     \n' '\n' 'Col 1 Row 2       Col 2 Row 2     ')
+    assert table == ('Col 1 Row 1       Col 2 Row 1     \n'
+                     '\n'
+                     'Col 1 Row 2       Col 2 Row 2     ')
 
     # Wide custom divider (divider needs no padding)
     st = SimpleTable([column_1, column_2], divider_char='深')