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"""The binary_diff module defines a class which stores size diff information.""" 15 16import collections 17import csv 18 19from typing import List, Generator, Type 20 21DiffSegment = collections.namedtuple( 22 'DiffSegment', ['name', 'before', 'after', 'delta', 'capacity']) 23FormattedDiff = collections.namedtuple('FormattedDiff', 24 ['segment', 'before', 'delta', 'after']) 25 26 27def format_integer(num: int, force_sign: bool = False) -> str: 28 """Formats a integer with commas.""" 29 prefix = '+' if force_sign and num > 0 else '' 30 return '{}{:,}'.format(prefix, num) 31 32 33def format_percent(num: float, force_sign: bool = False) -> str: 34 """Formats a decimal ratio as a percentage.""" 35 prefix = '+' if force_sign and num > 0 else '' 36 return '{}{:,.1f}%'.format(prefix, num * 100) 37 38 39class BinaryDiff: 40 """A size diff between two binary files.""" 41 def __init__(self, label: str): 42 self.label = label 43 self._segments: collections.OrderedDict = collections.OrderedDict() 44 45 def add_segment(self, segment: DiffSegment): 46 """Adds a segment to the diff.""" 47 self._segments[segment.name] = segment 48 49 def formatted_segments(self) -> Generator[FormattedDiff, None, None]: 50 """Yields each of the segments in this diff with formatted data.""" 51 52 if not self._segments: 53 yield FormattedDiff('(all)', '(same)', '0', '(same)') 54 return 55 56 has_diff_segment = False 57 58 for segment in self._segments.values(): 59 if segment.delta == 0: 60 continue 61 62 has_diff_segment = True 63 yield FormattedDiff( 64 segment.name, 65 format_integer(segment.before), 66 format_integer(segment.delta, force_sign=True), 67 format_integer(segment.after), 68 ) 69 70 if not has_diff_segment: 71 yield FormattedDiff('(all)', '(same)', '0', '(same)') 72 73 @classmethod 74 def from_csv(cls: Type['BinaryDiff'], label: str, 75 raw_csv: List[str]) -> 'BinaryDiff': 76 """Parses a BinaryDiff from bloaty's CSV output.""" 77 78 diff = cls(label) 79 reader = csv.reader(raw_csv) 80 for row in reader: 81 diff.add_segment( 82 DiffSegment(row[0], int(row[5]), int(row[7]), int(row[1]), 83 int(row[3]))) 84 85 return diff 86