moonraker/scripts/pk-enum-convertor.py

210 lines
6.9 KiB
Python
Raw Normal View History

#! /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 <arksine.code@gmail.com>
#
# 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}")