Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved routine for terminal dimensions. Works on windows too. #127

Merged
merged 3 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 14 additions & 16 deletions tests/terminal_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,11 @@ class TerminalTest(unittest.TestCase):
def setUp(self):
super(TerminalTest, self).setUp()
self.environ_orig = terminal.os.environ
self.open_orig = terminal.os.open
self.terminal_orig = terminal.TerminalSize

def tearDown(self):
super(TerminalTest, self).tearDown()
terminal.os.environ = self.environ_orig
terminal.os.open = self.open_orig
terminal.TerminalSize = self.terminal_orig

def testAnsiCmd(self):
Expand Down Expand Up @@ -68,20 +66,18 @@ def testEncloseAnsi(self):

def testTerminalSize(self):
# pylint: disable=unused-argument
def StubOpen(args, *kwargs):
raise IOError
terminal.open = StubOpen
terminal.os.environ = {}
# Raise exceptions on ioctl and environ and assign a default.
self.assertEqual((24, 80), terminal.TerminalSize())
terminal.os.environ = {'LINES': 'bogus', 'COLUMNS': 'bogus'}
self.assertEqual((24, 80), terminal.TerminalSize())
# Still raise exception on ioctl and use environ.
terminal.os.environ = {'LINES': '10', 'COLUMNS': '20'}
self.assertEqual((10, 20), terminal.TerminalSize())
# Falls back to default values as unittest is without terminal.
self.assertEqual((80, 24), terminal.TerminalSize())
# Ignores invalid environ values.
terminal.os.environ = {'COLUMNS': 'bogus', 'LINES': 'bogus'}
self.assertEqual((80, 24), terminal.TerminalSize())
# Honours valid environ values.
terminal.os.environ = {'COLUMNS': '20', 'LINES': '10'}
self.assertEqual((20, 10), terminal.TerminalSize())

def testLineWrap(self):
terminal.TerminalSize = lambda: (5, 11)
terminal.TerminalSize = lambda: (11, 5)
text = ''
self.assertEqual(text, terminal.LineWrap(text))
text = 'one line'
Expand Down Expand Up @@ -146,7 +142,7 @@ def setUp(self):
self.get_ch_orig = terminal.Pager._GetCh
terminal.Pager._GetCh = lambda self: 'q'
self.ts_orig = terminal.TerminalSize
terminal.TerminalSize = lambda: (24, 80)
terminal.TerminalSize = lambda: (80, 24)

self.p = terminal.Pager()

Expand All @@ -157,7 +153,8 @@ def tearDown(self):
sys.stdout = sys.__stdout__

def testPager(self):
self.assertEqual(terminal.TerminalSize()[0], self.p._cli_lines)
(_, term_length) = terminal.TerminalSize()
self.assertEqual(term_length, self.p._cli_lines)

self.p.Clear()
self.assertEqual('', self.p._text)
Expand All @@ -170,7 +167,8 @@ def testPage(self):
txt += '%d a random line of text here\n' % i
self.p._text = txt
self.p.Page()
self.assertEqual(terminal.TerminalSize()[0]+2, sys.stdout.CountLines())
(_, term_length) = terminal.TerminalSize()
self.assertEqual(term_length+2, sys.stdout.CountLines())

sys.stdout.output = ''
self.p = terminal.Pager()
Expand Down
38 changes: 13 additions & 25 deletions textfsm/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@
"""Simple terminal related routines."""

import getopt
import os
import os # needed for testing
import re
import struct
import shutil
import sys
import time

Expand All @@ -32,8 +32,6 @@
except (ImportError, ModuleNotFoundError):
pass

__version__ = '0.1.1'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed as this was a legacy of terminal.py being its own package.


# ANSI, ISO/IEC 6429 escape sequences, SGR (Select Graphic Rendition) subset.
SGR = {
'reset': 0,
Expand Down Expand Up @@ -169,18 +167,8 @@ def EncloseAnsiText(text):


def TerminalSize():
"""Returns terminal length and width as a tuple."""
try:
with open(os.ctermid()) as tty_instance:
length_width = struct.unpack(
'hh', fcntl.ioctl(tty_instance.fileno(), termios.TIOCGWINSZ, '1234')
)
except (IOError, OSError, NameError):
try:
length_width = (int(os.environ['LINES']), int(os.environ['COLUMNS']))
except (ValueError, KeyError):
length_width = (24, 80)
return length_width
"""Returns terminal width and length as a tuple."""
harro marked this conversation as resolved.
Show resolved Hide resolved
return shutil.get_terminal_size(fallback=(80, 24))


def LineWrap(text, omit_sgr=False):
Expand All @@ -194,7 +182,7 @@ def LineWrap(text, omit_sgr=False):
Text with additional line wraps inserted for lines grater than the width.
"""

def _SplitWithSgr(text_line):
def _SplitWithSgr(text_line, width):
"""Tokenise the line so that the sgr sequences can be omitted."""
token_list = sgr_re.split(text_line)
text_line_list = []
Expand Down Expand Up @@ -226,20 +214,20 @@ def _SplitWithSgr(text_line):

# We don't use textwrap library here as it insists on removing
# trailing/leading whitespace (pre 2.6).
(_, width) = TerminalSize()
(term_width, _) = TerminalSize()
text = str(text)
text_multiline = []
for text_line in text.splitlines():
# Is this a line that needs splitting?
while (omit_sgr and (len(StripAnsiText(text_line)) > width)) or (
len(text_line) > width
while (omit_sgr and (len(StripAnsiText(text_line)) > term_width)) or (
len(text_line) > term_width
):
# If there are no sgr escape characters then do a straight split.
if not omit_sgr:
text_multiline.append(text_line[:width])
text_line = text_line[width:]
text_multiline.append(text_line[:term_width])
text_line = text_line[term_width:]
else:
(multiline_line, text_line) = _SplitWithSgr(text_line)
(multiline_line, text_line) = _SplitWithSgr(text_line, term_width)
text_multiline.append(multiline_line)
if text_line:
text_multiline.append(text_line)
Expand Down Expand Up @@ -318,7 +306,7 @@ def SetLines(self, lines):
ValueError, TypeError: Not a valid integer representation.
"""

(self._cli_lines, self._cli_cols) = TerminalSize()
(self._cli_cols, self._cli_lines) = TerminalSize()

if lines:
self._cli_lines = int(lines)
Expand Down Expand Up @@ -472,7 +460,7 @@ def main(argv=None):
# Prints the size of the terminal and returns.
# Mutually exclusive to the paging of text and overrides that behaviour.
if opt in ('-s', '--size'):
print('Length: %d, Width: %d' % TerminalSize())
print('Width: %d, Length: %d' % TerminalSize())
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only user facing change.

return 0
elif opt in ('-d', '--delay'):
isdelay = True
Expand Down