| # Copyright 2024 The Fuchsia Authors. All rights reserved. | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| from collections import OrderedDict | |
| from dataclasses import dataclass, field | |
| from enum import StrEnum | |
| class Category(StrEnum): | |
| ENVIRONMENT = "environmentInfo" | |
| SOURCE = "sourceInfo" | |
| BUILD = "buildInfo" | |
| def pretty_str(self) -> str: | |
| if self == Category.ENVIRONMENT: | |
| return "Environment Info" | |
| elif self == Category.SOURCE: | |
| return "Source Info" | |
| elif self == Category.BUILD: | |
| return "Build Info" | |
| @dataclass(frozen=True) | |
| class Item: | |
| """An individual item that will be displayed in the output.""" | |
| # The category of the item. Items with matching categories are | |
| # grouped together. | |
| category: Category | |
| # The key of the item, used in the JSON output. | |
| key: str | |
| # The title of the item, shown in the text output. | |
| title: str | |
| # The value of the item. | |
| value: str | int | float | None | bool | list[str] | |
| # Additional notes on the source of this value, optionally. | |
| notes: str | None = None | |
| def to_dict( | |
| self, | |
| ) -> OrderedDict[str, str | None | bool | int | float | list[str]]: | |
| """Convert this item to a dictionary that preserves key ordering.""" | |
| return OrderedDict(title=self.title, value=self.value, notes=self.notes) | |
| def normalized_value(self) -> str: | |
| """Format the value slightly different than the default. | |
| - Bools are formatted lowercase. | |
| - Lists do not have quotes surrounding string members. | |
| All other values are represented normally using their __str__ | |
| implementation. | |
| Returns: | |
| str: Formatted string value | |
| """ | |
| if isinstance(self.value, list): | |
| list_val = ", ".join(self.value) | |
| return f"[{list_val}]" | |
| elif isinstance(self.value, bool): | |
| return "true" if self.value else "false" | |
| else: | |
| return str(self.value) | |
| @dataclass(frozen=True) | |
| class Result: | |
| """A wrapper for a result of a collector, which can either be | |
| an item to display or an error. | |
| """ | |
| item: Item | None = None | |
| error: str | None = None | |
| @dataclass | |
| class Data: | |
| """Data accumulated from multiple collectors.""" | |
| items: OrderedDict[Category, list[Item]] = field( | |
| default_factory=OrderedDict | |
| ) | |
| errors: list[str] = field(default_factory=list) | |
| def add_results(self, results: list[Result]) -> None: | |
| """Aggregate results into this container. | |
| Args: | |
| results (list[Result]): Data to add. | |
| """ | |
| for value in results: | |
| assert value.error is None or value.item is None | |
| if value.error is not None: | |
| self.errors.append(value.error) | |
| elif value.item is not None: | |
| item = value.item | |
| if item.category not in self.items: | |
| self.items[item.category] = [] | |
| self.items[item.category].append(item) |