#! /usr/bin/python3 # # The original enum-converter.py may be found at: # https://github.com/PackageKit/PackageKit/blob/b64ee9dfa707d5dd2b93c8eebe9930a55fcde108/lib/python/enum-convertor.py # # Copyright (C) 2008 - 2012 PackageKit Authors # # Licensed under the GNU General Public License Version 2.0 # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # The following modifications have been made to the original # script: # * Print the time of conversion # * Extract and print the original license of the source # * Enumerations are extracted from the header file to preserve # order # * Use Python "Flag" Enumerations # * Extract comments and include them as docstrings # * Introduce a string constant validation mode. This extracts # strings from pk-enum.c, then compares to the calculated # strings from pk-enum.h. # # Copyright (C) 2022 Eric Callahan # # Usage: # pk-enum-converter.py pk_enum.h > enums.py # # Enum String Validation Mode: # pk-enum-converter.py pk_enum.h pk_enum.c # # The pk_enum source files, pk-enum.c and pk-enum.h, can be found in the # PackageKit GitHub repo: # https://github.com/PackageKit/PackageKit/blob/main/lib/packagekit-glib2/pk-enum.c # https://github.com/PackageKit/PackageKit/blob/main/lib/packagekit-glib2/pk-enum.h # from __future__ import print_function from re import compile, DOTALL, MULTILINE import time import sys import pathlib import textwrap HEADER = \ ''' # This file was autogenerated from %s by pk-enum-converter.py # on %s UTC # # License for original source: # %s from __future__ import annotations import sys from enum import Flag, auto class PkFlag(Flag): @classmethod def from_pkstring(cls, pkstring: str): for name, member in cls.__members__.items(): if member.pkstring == pkstring: return cls(member.value) # Return "unknown" flag return cls(1) @classmethod def from_index(cls, index: int): return cls(1 << index) @property def pkstring(self) -> str: if self.name is None: return " | ".join([f.pkstring for f in self]) return self.name.lower().replace("_", "-") @property def desc(self) -> str: if self.name is None: return ", ".join([f.desc for f in self]) description = self.name.lower().replace("_", " ") return description.capitalize() @property def index(self) -> int: return self.value.bit_length() - 1 if sys.version_info < (3, 11): def __iter__(self): for i in range(self._value_.bit_length()): val = 1 << i if val & self._value_ == val: yield self.__class__(val) ''' # noqa: E122 FILTER_PKSTRING = \ ''' @property def pkstring(self) -> str: pks = self.name if pks is None: return " | ".join([f.pkstring for f in self]) if pks in ["DEVELOPMENT", "NOT_DEVELOPMENT"]: pks = pks[:-6] if pks[:4] == "NOT_": pks = "~" + pks[4:] return pks.lower().replace("_", "-") ''' # noqa: E122 ERROR_PROPS = \ ''' @property def pkstring(self) -> str: if self == Error.UPDATE_FAILED_DUE_TO_RUNNING_PROCESS: return "failed-due-to-running-process" return super().pkstring ''' # noqa: E122 ALIASES = { "Error.OOM": "OUT_OF_MEMORY" } header_enum = compile(r"/\*\*\n(.+?)@PK_[A-Z_]+_ENUM_LAST:\s+\*\s+(.+?)" r"\s+\*\*/\s+typedef enum {(.+?)} Pk(.+?)Enum", DOTALL | MULTILINE) header_value = compile(r"(PK_[A-Z_]+_ENUM)_([A-Z0-9_]+)") header_desc = compile(r"@PK_[A-Z_]+_ENUM_([A-Z_]+):(.*)") license = compile(r"(Copyright.+?)\*/", DOTALL | MULTILINE) enum_h_name = sys.argv[1] header = pathlib.Path(enum_h_name).read_text() # Get License lic_match = license.search(header) assert lic_match is not None lic_parts = lic_match.group(1).split("\n") lic = "\n".join([("# " + p.lstrip("* ")).rstrip(" ") for p in lic_parts]) if len(sys.argv) > 2: # Validation Mode, extract strings from the source file, compare to # those calculated from the enums in the header file enum_to_string = {} enum = compile(r"static const PkEnumMatch enum_([^\]]+)\[\] = {(.*?)};", DOTALL | MULTILINE) value = compile(r"(PK_[A-Z_]+_ENUM_[A-Z0-9_]+),\s+\"([^\"]+)\"") enum_c_name = sys.argv[2] inp = pathlib.Path(enum_c_name).read_text() for (name, data) in enum.findall(inp): for (enum_name, string) in value.findall(data): enum_to_string[enum_name] = string for (desc_data, comments, data, name) in header_enum.findall(header): for (prefix, short_name) in header_value.findall(data): if short_name == "LAST": continue # Validation Mode enum_name = f"{prefix}_{short_name}" string = enum_to_string[enum_name] calc_string = short_name.lower().replace("_", "-") if calc_string[:4] == "not-" and name == "Filter": calc_string = "~" + calc_string[4:] if calc_string != string: print( f"Calculated String Mismatch: {name}.{short_name}\n" f"Calculated: {calc_string}\n" f"Extracted: {string}\n") exit(0) print(HEADER % (enum_h_name, time.asctime(time.gmtime()), lic)) # Use the header file for correct enum ordering for (desc_data, comments, data, name) in header_enum.findall(header): print(f"\nclass {name}(PkFlag):") # Print Docstring print(' """') comments = [(" " * 4 + c.lstrip("* ")).rstrip(" ") for c in comments.splitlines()] for comment in comments: comment = comment.expandtabs(4) if len(comment) > 79: comment = "\n".join(textwrap.wrap( comment, 79, subsequent_indent=" ", tabsize=4)) print(comment) print("") for (item, desc) in header_desc.findall(desc_data): line = f" * {name}.{item}: {desc}".rstrip() if len(line) > 79: print(f" * {name}.{item}:") print(f" {desc}") else: print(line) print(' """') if name == "Filter": print(FILTER_PKSTRING) elif name == "Error": print(ERROR_PROPS) aliases = [] for (prefix, short_name) in header_value.findall(data): if short_name == "LAST": continue long_name = f"{name}.{short_name}" if long_name in ALIASES: alias = ALIASES[long_name] aliases.append((short_name, alias)) short_name = alias # Print Enums print(f" {short_name} = auto()") for name, alias in aliases: print(f" {name} = {alias}")