From 744f14d6f931b9f1e0db57fe239d0b3d129eecb4 Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Sun, 16 Jan 2022 11:17:03 -0500 Subject: [PATCH] scripts: add pk-enum-converter.py This is added to distrubute the changes made to "enum-converter.py" taken from the original PackageKit repo. Signed-off-by: Eric Callahan --- scripts/pk-enum-convertor.py | 209 +++++++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100755 scripts/pk-enum-convertor.py diff --git a/scripts/pk-enum-convertor.py b/scripts/pk-enum-convertor.py new file mode 100755 index 0000000..3f8630d --- /dev/null +++ b/scripts/pk-enum-convertor.py @@ -0,0 +1,209 @@ +#! /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}")