#!/usr/bin/env python3 # # Copyright (c) 2020 Arm Limited # All rights reserved # # The license below extends only to copyright in the software and shall # not be construed as granting a license to any other intellectual # property including but not limited to intellectual property relating # to a hardware implementation of the functionality of the software # licensed hereunder. You may use the software subject to the license # terms below provided that you ensure that this notice is replicated # unmodified and in its entirety in all distributions of the software, # modified or unmodified, in source code or in binary form. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer; # redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution; # neither the name of the copyright holders nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import email.utils import enum import os from typing import ( Any, Dict, Iterator, List, Mapping, Optional, Sequence, TextIO, Tuple, Union, ) import yaml PathOrFile = Union[TextIO, str] class FileFormatException(Exception): pass class MissingFieldException(FileFormatException): pass class IllegalValueException(FileFormatException): pass class Status(enum.Enum): MAINTAINED = enum.auto() ORPHANED = enum.auto() @classmethod def from_str(cls, key: str) -> "Status": _status_dict = {"maintained": cls.MAINTAINED, "orphaned": cls.ORPHANED} return _status_dict[key] def __str__(self) -> str: return {Status.MAINTAINED: "maintained", Status.ORPHANED: "orphaned"}[ self ] class Subsystem: tag: str status: Status maintainers: List[Tuple[str, str]] # Name, email description: str def __init__( self, tag: str, maintainers: Optional[Sequence[Tuple[str, str]]], description: str = "", status: Status = Status.ORPHANED, ): self.tag = tag self.status = status self.maintainers = list(maintainers) if maintainers is not None else [] self.description = description if description is not None else "" class Maintainers: DEFAULT_MAINTAINERS = os.path.join( os.path.dirname(__file__), "../../../MAINTAINERS.yaml" ) _subsystems: Dict[str, Subsystem] # tag -> Subsystem def __init__(self, ydict: Mapping[str, Any]): self._subsystems = {} for tag, maint in list(ydict.items()): self._subsystems[tag] = Maintainers._parse_subsystem(tag, maint) @classmethod def from_file( cls, path_or_file: Optional[PathOrFile] = None ) -> "Maintainers": return cls(Maintainers._load_maintainers_file(path_or_file)) @classmethod def from_yaml(cls, yaml_str: str) -> "Maintainers": return cls(yaml.load(yaml_str, Loader=yaml.SafeLoader)) @classmethod def _load_maintainers_file( cls, path_or_file: Optional[PathOrFile] = None ) -> Mapping[str, Any]: if path_or_file is None: path_or_file = cls.DEFAULT_MAINTAINERS if isinstance(path_or_file, str): with open(path_or_file) as fin: return yaml.load(fin, Loader=yaml.SafeLoader) else: return yaml.load(path_or_file, Loader=yaml.SafeLoader) @classmethod def _parse_subsystem(cls, tag: str, ydict: Mapping[str, Any]) -> Subsystem: def required_field(name): try: return ydict[name] except KeyError: raise MissingFieldException( f"{tag}: Required field '{name}' is missing" ) maintainers: List[Tuple[str, str]] = [] raw_maintainers = ydict.get("maintainers", []) if not isinstance(raw_maintainers, Sequence): raise IllegalValueException( f"{tag}: Illegal field 'maintainers' isn't a list." ) for maintainer in raw_maintainers: name, address = email.utils.parseaddr(maintainer) if name == "" and address == "": raise IllegalValueException( f"{tag}: Illegal maintainer field: '{maintainer}'" ) maintainers.append((name, address)) try: status = Status.from_str(required_field("status")) except KeyError: raise IllegalValueException( f"{tag}: Invalid status '{ydict['status']}'" ) return Subsystem( tag, maintainers=maintainers, status=status, description=ydict.get("desc", ""), ) def __iter__(self) -> Iterator[Tuple[str, Subsystem]]: return iter(list(self._subsystems.items())) def __getitem__(self, key: str) -> Subsystem: return self._subsystems[key] def _main(): maintainers = Maintainers.from_file() for tag, subsys in maintainers: print(f"{tag}: {subsys.description}") print(f" Status: {subsys.status}") print(f" Maintainers:") for maint in subsys.maintainers: print(f" - {maint[0]} <{maint[1]}>") print() if __name__ == "__main__": _main() __all__ = [ "FileFormatException", "MissingFieldException", "IllegalValueException", "Status", "Subsystem", "Maintainers", ]