blob: 93ea1a17bfdf60209b73fe8f1312bd857d2eb787 [file] [log] [blame]
#!/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(object):
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(object):
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, "r") 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",
]