def test_terminal_size(monkeypatch): """Test terminal_size(). In CIs the default height/width is returned since there is no terminal.""" width, height = terminal_size() assert width > 0 assert height > 0 if hasattr(ctypes, 'windll'): monkeypatch.setattr('ctypes.windll.kernel32.GetConsoleScreenBufferInfo', fake_csbi) else: monkeypatch.setattr('fcntl.ioctl', lambda *_: b'\x1d\x00w\x00\xca\x02\x96\x01') width, height = terminal_size() assert width == 119 assert height == 29
def test_terminal_size(monkeypatch): """Test terminal_size(). In CIs the default height/width is returned since there is no terminal.""" width, height = terminal_size() assert width > 0 assert height > 0 if hasattr(ctypes, 'windll'): monkeypatch.setattr( 'ctypes.windll.kernel32.GetConsoleScreenBufferInfo', fake_csbi) else: monkeypatch.setattr('fcntl.ioctl', lambda *_: b'\x1d\x00w\x00\xca\x02\x96\x01') width, height = terminal_size() assert width == 119 assert height == 29
def test_nix(monkeypatch): """Test function with IS_WINDOWS=False. :param monkeypatch: pytest fixture. """ # Test exception (no terminal within pytest). width, height = terminal_size() assert width == DEFAULT_WIDTH assert height == DEFAULT_HEIGHT # Test mocked. monkeypatch.setattr('fcntl.ioctl', lambda *_: b'\x1d\x00w\x00\xca\x02\x96\x01') width, height = terminal_size() assert width == 119 assert height == 29
def column_max_width(table_data, column_number, outer_border, inner_border, padding): """Determine the maximum width of a column based on the current terminal width. :param iter table_data: List of list of strings (unmodified table data). :param int column_number: The column number to query. :param int outer_border: Sum of left and right outer border visible widths. :param int inner_border: Visible width of the inner border character. :param int padding: Total padding per cell (left + right padding). :return: The maximum width the column can be without causing line wrapping. """ column_widths = max_dimensions(table_data)[0] column_count = len(column_widths) terminal_width = terminal_size()[0] # Count how much space padding, outer, and inner borders take up. non_data_space = outer_border if column_count: non_data_space += inner_border * (column_count - 1) non_data_space += column_count * padding # Exclude selected column's width. data_space = sum(column_widths) - column_widths[column_number] return terminal_width - data_space - non_data_space
def wrap_long_strings(self, table_data): while self.table_width > terminal_size()[0]: longest = 0 for rowindex, row in enumerate(table_data): for columnindex, column in enumerate(row): if len(column) > longest: longest = len(column) longestrow = rowindex longestcollumn = columnindex max_width = self.column_max_width(longestcollumn) longstring = self.table_data[longestrow][longestcollumn] wrapped_string = '\n'.join(wrap(longstring, max_width)) self.table_data[longestrow][longestcollumn] = wrapped_string
def render_table(table: AsciiTable, interactive=True): '''Do all wrapping to make the table fit in screen''' MAX_WIDTH = terminal_size()[0] table.inner_row_border = True if not table.table_width < MAX_WIDTH: non_negative = True i = 0 while table.table_width > MAX_WIDTH and non_negative and i < 50: non_negative = fix_iteration(table, MAX_WIDTH) i += 1 if interactive: pager = getpager() pager(table.table) else: print(table.table)
def column_max_width(self, column_number): """Return the maximum width of a column based on the current terminal width. :param int column_number: The column number to query. :return: The max width of the column. :rtype: int """ column_widths = self.column_widths borders_padding = (len(column_widths) * self.padding_left) + (len(column_widths) * self.padding_right) if self.outer_border: borders_padding += 2 if self.inner_column_border and column_widths: borders_padding += len(column_widths) - 1 other_column_widths = sum(column_widths) - column_widths[column_number] return terminal_size()[0] - other_column_widths - borders_padding
def column_max_width(self, column_number): """Return the maximum width of a column based on the current terminal width. :param int column_number: The column number to query. :return: The max width of the column. :rtype: int """ widths = self.column_widths borders_padding = (len(widths) * self.padding_left) + ( len(widths) * self.padding_right) if self.outer_border: borders_padding += 2 if self.inner_column_border and widths: borders_padding += len(widths) - 1 other_column_widths = sum(widths) - widths[column_number] return terminal_size()[0] - other_column_widths - borders_padding
def _calc_wrap(self): """ :return: Calculated wrap value to be used for self.wrap_columns. If wraps are not defined None is returned """ if not self.wrap_columns: return None longest = self._longest_rows() margin = self._columns * 2 + self._columns + 1 static_columns = sum(longest.values()) for wrap in self.wrap_columns: static_columns -= longest[wrap] space_left = terminal_size()[0] - static_columns - margin """ print('margin: %s' % margin) print('static_columns: %s' % static_columns) print('space_left: %s' % space_left) print('self.table.table_width: %s' % self.table.table_width) print(longest) """ # TODO: This is a bit dumb if wrapped columns have huge disparity # for example in flexget plugins the phases and flags return int(space_left / len(self.wrap_columns))
def _calc_wrap(self): """ :return: Calculated wrap value to be used for self.wrap_columns. If wraps are not defined None is returned """ if not self.wrap_columns: return None longest = self._longest_rows() margin = self._columns * 2 + self._columns + 1 static_columns = sum(longest.values()) for wrap in self.wrap_columns: static_columns -= longest[wrap] space_left = terminal_size()[0] - static_columns - margin """ print('margin: %s' % margin) print('static_columns: %s' % static_columns) print('space_left: %s' % space_left) print('self.table.table_width: %s' % self.table.table_width) print(longest) """ # TODO: This is a bit dumb if wrapped columns have huge disparity # for example in flexget plugins the phases and flags return int(space_left/len(self.wrap_columns))
def test_windows(monkeypatch, stderr, stdout): """Test function with IS_WINDOWS=True. :param monkeypatch: pytest fixture. :param int stderr: Mock handle value. :param int stdout: Mock handle value. """ monkeypatch.setattr('terminaltables.terminal_io.IS_WINDOWS', True) kernel32 = MockKernel32(stderr=stderr, stdout=stdout) width, height = terminal_size(kernel32) if stderr == INVALID_HANDLE_VALUE and stdout == INVALID_HANDLE_VALUE: assert width == DEFAULT_WIDTH assert height == DEFAULT_HEIGHT elif stdout == INVALID_HANDLE_VALUE: assert width == 119 assert height == 29 elif stderr == INVALID_HANDLE_VALUE: assert width == 75 assert height == 28 else: assert width == 119 assert height == 29
def exposed_terminal_info(self): return {'size': terminal_size(), 'isatty': sys.stdout.isatty()}
def test_terminal_width_height(): """Test terminal width/height functions.""" assert terminal_size() == (80, 24)
def ok(self): # Too late to change API. # pylint: disable=invalid-name """Return True if the table fits within the terminal width, False if the table breaks.""" return self.table_width <= terminal_size()[0]
def test_terminal_width_height(): """Test terminal width/height functions.""" assert (80, 24) == terminal_size()
def execute_status(args): status = get_status() # First rows, showing daemon status if status['status'] == 'running': status['status'] = Color('{autogreen}' + '{}'.format(status['status']) + '{/autogreen}') elif status['status'] == 'paused': status['status'] = Color('{autoyellow}' + '{}'.format(status['status']) + '{/autoyellow}') if status['process'] == 'running' or status['process'] == 'paused': status['process'] = Color('{autogreen}' + '{}'.format(status['process']) + '{/autogreen}') print('Daemon: {}\nProcess status: {} \n'.format(status['status'], status['process'])) # Handle queue data data = status['data'] if isinstance(data, str): print(data) elif isinstance(data, dict): # Format incomming data to be compatible with Terminaltables formatted_data = [] formatted_data.append(['Index', 'Status', 'Code', 'Command', 'Path', 'Start', 'End']) for key, entry in sorted(data.items(), key=operator.itemgetter(0)): formatted_data.append( [ '#{}'.format(key), entry['status'], '{}'.format(entry['returncode']), entry['command'], entry['path'], entry['start'], entry['end'] ] ) # Create AsciiTable instance and define style table = AsciiTable(formatted_data) table.outer_border = False table.inner_column_border = False terminal_width = terminal_size() customWidth = table.column_widths # If the text is wider than the actual terminal size, we # compute a new size for the Command and Path column. if (reduce(lambda a, b: a+b, table.column_widths) + 10) > terminal_width[0]: # We have to subtract 14 because of table paddings left_space = math.floor((terminal_width[0] - customWidth[0] - customWidth[1] - customWidth[2] - customWidth[5] - customWidth[6] - 14)/2) if customWidth[3] < left_space: customWidth[4] = 2*left_space - customWidth[3] elif customWidth[4] < left_space: customWidth[3] = 2*left_space - customWidth[4] else: customWidth[3] = left_space customWidth[4] = left_space # Format long strings to match the console width for i, entry in enumerate(table.table_data): for j, string in enumerate(entry): max_width = customWidth[j] wrapped_string = '\n'.join(wrap(string, max_width)) if j == 1: if wrapped_string == 'done' or wrapped_string == 'running' or wrapped_string == 'paused': wrapped_string = Color('{autogreen}' + '{}'.format(wrapped_string) + '{/autogreen}') elif wrapped_string == 'queued': wrapped_string = Color('{autoyellow}' + '{}'.format(wrapped_string) + '{/autoyellow}') elif wrapped_string in ['errored', 'stopping', 'killing']: wrapped_string = Color('{autored}' + '{}'.format(wrapped_string) + '{/autored}') elif j == 2: if wrapped_string == '0' and wrapped_string != 'Code': wrapped_string = Color('{autogreen}' + '{}'.format(wrapped_string) + '{/autogreen}') elif wrapped_string != '0' and wrapped_string != 'Code': wrapped_string = Color('{autored}' + '{}'.format(wrapped_string) + '{/autored}') table.table_data[i][j] = wrapped_string print(table.table) print('')
def exposed_terminal_size(self, *args): return terminal_size(*args)
__all__ = ["choice", "colored", "randint", "re", "strip_escape_codes", "term_width", "wrap", "Object", "ObjectTooLarge", "FONTS"] # little patch to available attributes ; may not work for some terminals ATTRIBUTES['italic'] = 3 ATTRIBUTES['strikethrough'] = 9 with open(join(dirname(__file__), "fonts.txt")) as f: FONTS = list(set(f.read().splitlines())) strip_escape_codes = lambda s: re.sub(r"(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]", "", s) term_width = lambda: terminal_size()[0] or 79 class Object(object): def __repr__(self): """ Represent the object with every ASCII escape code removed. """ return strip_escape_codes(str(self)) @staticmethod def check_width(text, width=term_width()): """ Generic method for checking if the ASCII art object does not exceed the configured width. """ if any(len(l) > width for l in text.splitlines()): raise ObjectTooLarge("Object does not fit the given width") @staticmethod def wrap(text, width=term_width()):
def execute_status(args, root_dir=None): """Print the status of the daemon. This function displays the current status of the daemon as well as the whole queue and all available information about every entry in the queue. `terminaltables` is used to format and display the queue contents. `colorclass` is used to color format the various items in the queue. Args: root_dir (string): The path to the root directory the daemon is running in. """ status = command_factory('status')({}, root_dir=root_dir) # First rows, showing daemon status if status['status'] == 'running': status['status'] = Color('{autogreen}' + '{}'.format(status['status']) + '{/autogreen}') elif status['status'] in ['paused']: status['status'] = Color('{autoyellow}' + '{}'.format(status['status']) + '{/autoyellow}') print('Daemon: {}\n'.format(status['status'])) # Handle queue data data = status['data'] if isinstance(data, str): print(data) elif isinstance(data, dict): # Format incomming data to be compatible with Terminaltables formatted_data = [] formatted_data.append( ['Index', 'Status', 'Code', 'Command', 'Path', 'Start', 'End']) for key, entry in sorted(data.items(), key=operator.itemgetter(0)): formatted_data.append([ '#{}'.format(key), entry['status'], '{}'.format(entry['returncode']), entry['command'], entry['path'], entry['start'], entry['end'] ]) # Create AsciiTable instance and define style table = AsciiTable(formatted_data) table.outer_border = False table.inner_column_border = False terminal_width = terminal_size() customWidth = table.column_widths # If the text is wider than the actual terminal size, we # compute a new size for the Command and Path column. if (reduce(lambda a, b: a + b, table.column_widths) + 10) > terminal_width[0]: # We have to subtract 14 because of table paddings left_space = math.floor( (terminal_width[0] - customWidth[0] - customWidth[1] - customWidth[2] - customWidth[5] - customWidth[6] - 14) / 2) if customWidth[3] < left_space: customWidth[4] = 2 * left_space - customWidth[3] elif customWidth[4] < left_space: customWidth[3] = 2 * left_space - customWidth[4] else: customWidth[3] = left_space customWidth[4] = left_space # Format long strings to match the console width for i, entry in enumerate(table.table_data): for j, string in enumerate(entry): max_width = customWidth[j] wrapped_string = '\n'.join(wrap(string, max_width)) if j == 1: if wrapped_string == 'done' or wrapped_string == 'running' or wrapped_string == 'paused': wrapped_string = Color('{autogreen}' + '{}'.format(wrapped_string) + '{/autogreen}') elif wrapped_string in ['queued', 'stashed']: wrapped_string = Color('{autoyellow}' + '{}'.format(wrapped_string) + '{/autoyellow}') elif wrapped_string in ['failed', 'stopping', 'killing']: wrapped_string = Color('{autored}' + '{}'.format(wrapped_string) + '{/autored}') elif j == 2: if wrapped_string == '0' and wrapped_string != 'Code': wrapped_string = Color('{autogreen}' + '{}'.format(wrapped_string) + '{/autogreen}') elif wrapped_string != '0' and wrapped_string != 'Code': wrapped_string = Color('{autored}' + '{}'.format(wrapped_string) + '{/autored}') table.table_data[i][j] = wrapped_string print(table.table) print('')
def terminal_info(): """ Returns info we need about the output terminal. When called over IPC, this function is monkeypatched to return the info about the client terminal. """ return {'size': terminal_size(), 'isatty': sys.stdout.isatty()}
def valid_table(self): # print('self.table.table_width: %s' % self.table.table_width) # print('terminal_size()[0]: %s' % terminal_size()[0]) return self.table.table_width <= terminal_size()[0]
def exposed_terminal_info(self): return {"size": terminal_size(), "isatty": sys.stdout.isatty()}