1 # Licensed under the Apache License, Version 2.0 (the "License"); you may
2 # not use this file except in compliance with the License. You may obtain
3 # a copy of the License at
5 # http://www.apache.org/licenses/LICENSE-2.0
7 # Unless required by applicable law or agreed to in writing, software
8 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10 # License for the specific language governing permissions and limitations
13 """Output formatters using prettytable.
20 from cliff import utils
22 from cliff import columns
28 if isinstance(r, columns.FormattableColumn):
29 r = r.human_readable()
30 if isinstance(r, six.string_types):
31 r = r.replace('\r\n', '\n').replace('\r', ' ')
36 class TableFormatter(base.ListFormatter, base.SingleFormatter):
44 ALIGNMENTS[unicode] = 'l'
48 def add_argument_group(self, parser):
49 group = parser.add_argument_group('table formatter')
53 default=int(os.environ.get('CLIFF_MAX_TERM_WIDTH', 0)),
55 help=('Maximum display width, <1 to disable. You can also '
56 'use the CLIFF_MAX_TERM_WIDTH environment variable, '
57 'but the parameter takes precedence.'),
62 help='Print empty table if there is no data to show.',
65 def add_rows(self, table, column_names, data):
66 # Figure out the types of the columns in the
67 # first row and set the alignment of the
69 data_iter = iter(data)
71 first_row = next(data_iter)
75 for value, name in zip(first_row, column_names):
76 alignment = self.ALIGNMENTS.get(type(value), 'l')
77 table.align[name] = alignment
78 # Now iterate over the data and add the rows.
79 table.add_row(_format_row(first_row))
81 table.add_row(_format_row(row))
83 def emit_list(self, column_names, data, stdout, parsed_args):
84 x = prettytable.PrettyTable(
86 print_empty=parsed_args.print_empty,
90 # Add rows if data is provided
92 self.add_rows(x, column_names, data)
94 # Choose a reasonable min_width to better handle many columns on a
95 # narrow console. The table will overflow the console width in
96 # preference to wrapping columns smaller than 8 characters.
98 self._assign_max_widths(
99 stdout, x, int(parsed_args.max_width), min_width)
101 formatted = x.get_string()
102 stdout.write(formatted)
106 def emit_one(self, column_names, data, stdout, parsed_args):
107 x = prettytable.PrettyTable(field_names=('Field', 'Value'),
110 # Align all columns left because the values are
111 # not all the same type.
112 x.align['Field'] = 'l'
113 x.align['Value'] = 'l'
114 for name, value in zip(column_names, data):
115 x.add_row(_format_row((name, value)))
117 # Choose a reasonable min_width to better handle a narrow
118 # console. The table will overflow the console width in preference
119 # to wrapping columns smaller than 16 characters in an attempt to keep
120 # the Field column readable.
122 self._assign_max_widths(
123 stdout, x, int(parsed_args.max_width), min_width)
125 formatted = x.get_string()
126 stdout.write(formatted)
131 def _field_widths(field_names, first_line):
133 # use the first line +----+-------+ to infer column widths
134 # accounting for padding and dividers
135 widths = [max(0, len(i) - 2) for i in first_line.split('+')[1:-1]]
136 return dict(zip(field_names, widths))
139 def _width_info(term_width, field_count):
140 # remove padding and dividers for width available to actual content
141 usable_total_width = max(0, term_width - 1 - 3 * field_count)
143 # calculate width per column if all columns were equal
147 optimal_width = max(0, usable_total_width // field_count)
149 return usable_total_width, optimal_width
152 def _build_shrink_fields(usable_total_width, optimal_width,
153 field_widths, field_names):
155 shrink_remaining = usable_total_width
156 for field in field_names:
157 w = field_widths[field]
158 if w <= optimal_width:
159 # leave alone columns which are smaller than the optimal width
160 shrink_remaining -= w
162 shrink_fields.append(field)
164 return shrink_fields, shrink_remaining
167 def _assign_max_widths(stdout, x, max_width, min_width=0):
169 x.min_width = min_width
172 term_width = max_width
174 term_width = utils.terminal_width(stdout)
176 # not a tty, so do not set any max widths
178 field_count = len(x.field_names)
181 first_line = x.get_string().splitlines()[0]
182 if len(first_line) <= term_width:
187 usable_total_width, optimal_width = TableFormatter._width_info(
188 term_width, field_count)
190 field_widths = TableFormatter._field_widths(x.field_names, first_line)
192 shrink_fields, shrink_remaining = TableFormatter._build_shrink_fields(
193 usable_total_width, optimal_width, field_widths, x.field_names)
195 shrink_to = shrink_remaining // len(shrink_fields)
196 # make all shrinkable fields size shrink_to apart from the last one
197 for field in shrink_fields[:-1]:
198 x.max_width[field] = max(min_width, shrink_to)
199 shrink_remaining -= shrink_to
201 # give the last shrinkable column shrink_to plus any remaining
202 field = shrink_fields[-1]
203 x.max_width[field] = max(min_width, shrink_remaining)