Fix for x86_64 build fail
[platform/upstream/connectedhomeip.git] / third_party / pigweed / repo / pw_bloat / py / pw_bloat / bloat_output.py
1 # Copyright 2019 The Pigweed Authors
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 # use this file except in compliance with the License. You may obtain a copy of
5 # the License at
6 #
7 #     https://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 # License for the specific language governing permissions and limitations under
13 # the License.
14 """Module containing different output formatters for the bloat script."""
15
16 import abc
17 import enum
18 from typing import (Callable, Collection, Dict, List, Optional, Tuple, Type,
19                     Union)
20
21 from pw_bloat.binary_diff import BinaryDiff, FormattedDiff
22
23
24 class Output(abc.ABC):
25     """An Output produces a size report card in a specific format."""
26     def __init__(self,
27                  title: Optional[str],
28                  diffs: Collection[BinaryDiff] = ()):
29         self._title = title
30         self._diffs = diffs
31
32     @abc.abstractmethod
33     def diff(self) -> str:
34         """Creates a report card for a size diff between binaries and a base."""
35
36     @abc.abstractmethod
37     def absolute(self) -> str:
38         """Creates a report card for the absolute size breakdown of binaries."""
39
40
41 class AsciiCharset(enum.Enum):
42     """Set of ASCII characters for drawing tables."""
43     TL = '+'
44     TM = '+'
45     TR = '+'
46     ML = '+'
47     MM = '+'
48     MR = '+'
49     BL = '+'
50     BM = '+'
51     BR = '+'
52     V = '|'
53     H = '-'
54     HH = '='
55
56
57 class LineCharset(enum.Enum):
58     """Set of line-drawing characters for tables."""
59     TL = '┌'
60     TM = '┬'
61     TR = '┐'
62     ML = '├'
63     MM = '┼'
64     MR = '┤'
65     BL = '└'
66     BM = '┴'
67     BR = '┘'
68     V = '│'
69     H = '─'
70     HH = '═'
71
72
73 def identity(val: str) -> str:
74     """Returns a string unmodified."""
75     return val
76
77
78 class TableOutput(Output):
79     """Tabular output."""
80
81     LABEL_COLUMN = 'Label'
82
83     def __init__(
84             self,
85             title: Optional[str],
86             diffs: Collection[BinaryDiff] = (),
87             charset: Union[Type[AsciiCharset],
88                            Type[LineCharset]] = AsciiCharset,
89             preprocess: Callable[[str], str] = identity,
90             # TODO(frolv): Make this a Literal type.
91             justify: str = 'rjust'):
92         self._cs = charset
93         self._preprocess = preprocess
94         self._justify = justify
95
96         super().__init__(title, diffs)
97
98     def diff(self) -> str:
99         """Build a tabular diff output showing binary size deltas."""
100
101         # Calculate the width of each column in the table.
102         max_label = len(self.LABEL_COLUMN)
103         column_widths = [len(field) for field in FormattedDiff._fields]
104
105         for diff in self._diffs:
106             max_label = max(max_label, len(diff.label))
107             for segment in diff.formatted_segments():
108                 for i, val in enumerate(segment):
109                     val = self._preprocess(val)
110                     column_widths[i] = max(column_widths[i], len(val))
111
112         separators = self._row_separators([max_label] + column_widths)
113
114         def title_pad(string: str) -> str:
115             padding = (len(separators['top']) - len(string)) // 2
116             return ' ' * padding + string
117
118         titles = [
119             self._center_align(val.capitalize(), column_widths[i])
120             for i, val in enumerate(FormattedDiff._fields)
121         ]
122         column_names = [self._center_align(self.LABEL_COLUMN, max_label)
123                         ] + titles
124
125         rows: List[str] = []
126
127         if self._title is not None:
128             rows.extend([
129                 title_pad(self._title),
130                 title_pad(self._cs.H.value * len(self._title)),
131             ])
132
133         rows.extend([
134             separators['top'],
135             self._table_row(column_names),
136             separators['hdg'],
137         ])
138
139         for row, diff in enumerate(self._diffs):
140             subrows: List[str] = []
141
142             for segment in diff.formatted_segments():
143                 subrow: List[str] = []
144                 label = diff.label if not subrows else ''
145                 subrow.append(getattr(label, self._justify)(max_label, ' '))
146                 subrow.extend([
147                     getattr(self._preprocess(val),
148                             self._justify)(column_widths[i], ' ')
149                     for i, val in enumerate(segment)
150                 ])
151                 subrows.append(self._table_row(subrow))
152
153             rows.append('\n'.join(subrows))
154             rows.append(separators['bot' if row == len(self._diffs) -
155                                    1 else 'mid'])
156
157         return '\n'.join(rows)
158
159     def absolute(self) -> str:
160         return ''
161
162     def _row_separators(self, column_widths: List[int]) -> Dict[str, str]:
163         """Returns row separators for a table based on the character set."""
164
165         # Left, middle, and right characters for each of the separator rows.
166         top = (self._cs.TL.value, self._cs.TM.value, self._cs.TR.value)
167         mid = (self._cs.ML.value, self._cs.MM.value, self._cs.MR.value)
168         bot = (self._cs.BL.value, self._cs.BM.value, self._cs.BR.value)
169
170         def sep(chars: Tuple[str, str, str], heading: bool = False) -> str:
171             line = self._cs.HH.value if heading else self._cs.H.value
172             lines = [line * width for width in column_widths]
173             left = f'{chars[0]}{line}'
174             mid = f'{line}{chars[1]}{line}'.join(lines)
175             right = f'{line}{chars[2]}'
176             return f'{left}{mid}{right}'
177
178         return {
179             'top': sep(top),
180             'hdg': sep(mid, True),
181             'mid': sep(mid),
182             'bot': sep(bot),
183         }
184
185     def _table_row(self, vals: Collection[str]) -> str:
186         """Formats a row of the table with the selected character set."""
187         vert = self._cs.V.value
188         main = f' {vert} '.join(vals)
189         return f'{vert} {main} {vert}'
190
191     @staticmethod
192     def _center_align(val: str, width: int) -> str:
193         """Left and right pads a value with spaces to center within a width."""
194         space = width - len(val)
195         padding = ' ' * (space // 2)
196         extra = ' ' if space % 2 == 1 else ''
197         return f'{extra}{padding}{val}{padding}'
198
199
200 class RstOutput(TableOutput):
201     """Tabular output in ASCII format, which is also valid RST."""
202     def __init__(self, diffs: Collection[BinaryDiff] = ()):
203         # Use RST line blocks within table cells to force each value to appear
204         # on a new line in the HTML output.
205         def add_rst_block(val: str) -> str:
206             return f'| {val}'
207
208         super().__init__(None,
209                          diffs,
210                          AsciiCharset,
211                          preprocess=add_rst_block,
212                          justify='ljust')