Created
April 29, 2023 21:59
-
-
Save Zren/c933d4ae1b3549b2a6df97ff9cfdcd3b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import argparse | |
import configparser | |
import datetime | |
import glob | |
import json | |
import logging | |
import os | |
import re | |
import subprocess | |
import shutil | |
import sys | |
sourceDir="package" | |
buildTag="-plasma5.18" | |
buildFilenameFormat="{packageName}-v{packageVersion}{buildTag}" | |
buildExt="plasmoid" # Renamed zip | |
#--- | |
__version__ = '1' | |
logger = logging.getLogger('kpac') | |
logger.setLevel(logging.DEBUG) | |
logger.addHandler(logging.StreamHandler()) | |
# KDE rc files differences: | |
# Keys are cAsE sensitive | |
# No spaces around the = | |
# [Sections can have spaces and : colons] | |
# Parses [Sub][Sections] as "Sub][Sections", but cannot have comments on the [Section] line | |
class KdeConfig(configparser.ConfigParser): | |
def __init__(self, filename): | |
super().__init__() | |
# Keep case sensitive keys | |
# http://stackoverflow.com/questions/19359556/configparser-reads-capital-keys-and-make-them-lower-case | |
self.optionxform = str | |
# Parse SubSections as "Sub][Sections" | |
self.SECTCRE = re.compile(r"\[(?P<header>.+?)]\w*$") | |
self.filename = filename | |
self.read(self.filename) | |
#--- | |
def isCommandInstalled(name): | |
return shutil.which(name) is not None | |
class LineReplace: | |
def __init__(self, filepath): | |
self.filepath = filepath | |
self.lines = None | |
self.output = '' | |
def __enter__(self): | |
with open(self.filepath, 'r') as fin: | |
self.lines = self.readlines() | |
return self | |
def __exit__(self, exc_type, exc_val, exc_tb): | |
with open(self.filepath, 'w') as fout: | |
fout.write(self.output) | |
def write(self, text): | |
self.output += text | |
#--- Parse metadata as global variables | |
jsonMetaFilepath = os.path.join(sourceDir, 'metadata.json') | |
desktopMetaFilepath = os.path.join(sourceDir, 'metadata.desktop') | |
if os.path.exists(jsonMetaFilepath): | |
# .json | |
with open(jsonMetaFilepath, 'r') as fin: | |
metadata = json.load(fin) | |
packageNamespace = metadata['KPlugin']['Id'] | |
packageName = packageNamespace.split('.')[-1] | |
packageVersion = metadata['KPlugin']['Version'] | |
packageAuthor = metadata['KPlugin']['Authors']['Name'] | |
packageAuthorEmail = metadata['KPlugin']['Authors']['Email'] | |
packageWebsite = metadata['KPlugin']['Website'] | |
packageComment = metadata['KPlugin']['Description'] | |
elif os.path.exists(desktopMetaFilepath): | |
# .desktop | |
metadata = KdeConfig(desktopMetaFilepath) | |
packageNamespace = metadata.get('Desktop Entry', 'X-KDE-PluginInfo-Name') | |
packageName = packageNamespace.split('.')[-1] | |
packageVersion = metadata.get('Desktop Entry', 'X-KDE-PluginInfo-Version') | |
packageAuthor = metadata.get('Desktop Entry', 'X-KDE-PluginInfo-Author') | |
packageAuthorEmail = metadata.get('Desktop Entry', 'X-KDE-PluginInfo-Email') | |
packageWebsite = metadata.get('Desktop Entry', 'X-KDE-PluginInfo-Website') | |
packageComment = metadata.get('Desktop Entry', 'Comment') | |
else: | |
print("Could not find metadata.json or metadata.desktop in '{}'".format(sourceDir)) | |
sys.exit(1) | |
def printMetadata(): | |
logger.info("Namespace: %s", packageNamespace) | |
logger.info("Name: %s", packageName) | |
logger.info("Version: %s", packageVersion) | |
logger.info("Author: %s", packageAuthor) | |
logger.info("AuthorEmail: %s", packageAuthorEmail) | |
logger.info("Website: %s", packageWebsite) | |
logger.info("Comment: %s", packageComment) | |
logger.info("") | |
#--- | |
def kpac_install(args): | |
# kpackagetool5 -t Plasma/Applet -s package | |
p = subprocess.Popen([ | |
'kpackagetool5', | |
'--type', 'Plasma/Applet', | |
'--show', packageNamespace, | |
], stdout=subprocess.PIPE, stderr=subprocess.PIPE) | |
p.wait() | |
# Not installed: "Error: Can't find plugin metadata: org.kde.plasma.name" is returncode=3 | |
isAlreadyInstalled = p.returncode == 0 | |
# logger.debug("isInstalledReturnCode: %s", p.returncode) | |
if isAlreadyInstalled: | |
# kpackagetool5 -t Plasma/Applet -u package | |
subprocess.call(['kpackagetool5', | |
'--type', 'Plasma/Applet', | |
'--upgrade', sourceDir, | |
]) | |
if args.restart: | |
subprocess.call(['kstart5', | |
'--', | |
'plasmashell', '--replace', | |
]) | |
else: | |
# kpackagetool5 -t Plasma/Applet -i package | |
subprocess.call(['kpackagetool5', | |
'--type', 'Plasma/Applet', | |
'--install', sourceDir, | |
]) | |
def kpac_uninstall(args): | |
# kpackagetool5 -t Plasma/Applet -r package | |
subprocess.call(['kpackagetool5', | |
'--type', 'Plasma/Applet', | |
'--remove', sourceDir, | |
]) | |
def kpac_i18n(args): | |
translateDir = os.path.join(sourceDir, 'translate') | |
translateRoot = '..' # package/tranlate/ => package/tranlate/../ | |
bugAddress = packageWebsite | |
translationDomain = "plasma_applet_{}".format(packageNamespace) | |
if not isCommandInstalled('xgettext'): | |
print("[kpac-i18n] Error: xgettext command not found. Need to install gettext") | |
print("[kpac-i18n] Run 'sudo apt install gettext' on Kubuntu/KDE Neon first") | |
sys.exit(1) | |
potArgs = [ | |
'--from-code=UTF-8', | |
'--width=200', # Don't wrap on short sentences | |
'--add-location=file', # Filename only, no line numbers | |
] | |
# TODO: | |
# Extract messages from metadata.desktop + metadata.json | |
# See Ki18n's extract-messages.sh for a full example: | |
# https://invent.kde.org/sysadmin/l10n-scripty/-/blob/master/extract-messages.sh#L25 | |
# The -kN_ and -kaliasLocale keywords are mentioned in the Outside_KDE_repositories wiki. | |
# We don't need -kN_ since we don't use intltool-extract but might as well keep it. | |
# I have no idea what -kaliasLocale is used for. Googling aliasLocale found only listed kde1 code. | |
# We don't need to parse -ki18nd since that'll extract messages from other domains. | |
infilesPath = os.path.join(translateDir, 'infiles.list') | |
with open(infilesPath, 'w') as fout: | |
findProc = subprocess.Popen([ | |
'find', | |
translateRoot, | |
'-name', '*.cpp', | |
'-o', '-name', '*.h', | |
'-o', '-name', '*.c', | |
'-o', '-name', '*.qml', | |
'-o', '-name', '*.js', | |
], stdout=subprocess.PIPE, cwd=translateDir) | |
sortProc = subprocess.call([ | |
'sort' | |
], stdin=findProc.stdout, stdout=fout) | |
findProc.wait() | |
oldTemplateFilename = os.path.join('template.pot') | |
newTemplateFilename = os.path.join('template.pot.new') | |
oldTemplatePath = os.path.join(translateDir, oldTemplateFilename) | |
newTemplatePath = os.path.join(translateDir, newTemplateFilename) | |
potArgs += [ | |
# '--files-from', infilesPath, | |
'--files-from', 'infiles.list', | |
'-C', '-kde', | |
'-ci18n', | |
'-ki18n:1', | |
'-ki18nc:1c,2', | |
'-ki18np:1,2', | |
'-ki18ncp:1c,2,3', | |
'-kki18n:1', | |
'-kki18nc:1c,2', | |
'-kki18np:1,2', | |
'-kki18ncp:1c,2,3', | |
'-kxi18n:1', | |
'-kxi18nc:1c,2', | |
'-kxi18np:1,2', | |
'-kxi18ncp:1c,2,3', | |
'-kkxi18n:1', | |
'-kkxi18nc:1c,2', | |
'-kkxi18np:1,2', | |
'-kkxi18ncp:1c,2,3', | |
'-kI18N_NOOP:1', | |
'-kI18NC_NOOP:1c,2', | |
'-kI18N_NOOP2:1c,2', | |
'-kI18N_NOOP2_NOSTRIP:1c,2', | |
'-ktr2i18n:1', | |
'-ktr2xi18n:1', | |
'-kN_:1', | |
'-kaliasLocale', | |
'--package-name', packageName, | |
'--msgid-bugs-address', bugAddress, | |
] | |
subprocess.call(['xgettext'] + potArgs + [ | |
'-D', translateRoot, | |
'-D', '.', # cwd should be translateDir | |
'-o', newTemplateFilename, | |
], cwd=translateDir) | |
if not os.path.exists(newTemplatePath): | |
# Error generating template.pot.new | |
print('template.pot.new does not exist') | |
sys.exit(1) | |
# Replace gettext placeholders | |
with LineReplace(newTemplatePath) as rep: | |
for line in rep.lines: | |
line = line.replace( | |
"Content-Type: text\/plain; charset=CHARSET", | |
"Content-Type: text\/plain; charset=UTF-8", | |
) | |
line = line.replace( | |
"# SOME DESCRIPTIVE TITLE.", | |
"# Translation of {} in LANGUAGE".format(packageName), | |
) | |
line = line.replace( | |
"# Copyright (C) YEAR THE PACKAGE\'S COPYRIGHT HOLDER", | |
"# Copyright (C) {}".format(datetime.date.today().year), | |
) | |
rep.write(line) | |
if os.path.exists(oldTemplatePath): | |
with open() | |
newPotDate = '' | |
oldPotDate = '' | |
with LineReplace(newTemplatePath) as rep: | |
for line in rep.lines: | |
line = line.replace( | |
"Content-Type: text\/plain; charset=CHARSET", | |
"Content-Type: text\/plain; charset=UTF-8", | |
) | |
rep.write(line) | |
else: | |
# template.pot didn't already exist | |
os.rename(newTemplatePath, oldTemplatePath) | |
def kpac_localetest(args): | |
# https://stackoverflow.com/questions/3191664/list-of-all-locales-and-their-short-codes/28357857#28357857 | |
langArr = [ | |
["af_ZA", "af", "Afrikaans (South Africa)"], | |
["ak_GH", "ak", "Akan (Ghana)"], | |
["am_ET", "am", "Amharic (Ethiopia)"], | |
["ar_EG", "ar", "Arabic (Egypt)"], | |
["as_IN", "as", "Assamese (India)"], | |
["az_AZ", "az", "Azerbaijani (Azerbaijan)"], | |
["be_BY", "be", "Belarusian (Belarus)"], | |
["bem_ZM", "bem", "Bemba (Zambia)"], | |
["bg_BG", "bg", "Bulgarian (Bulgaria)"], | |
["bo_IN", "bo", "Tibetan (India)"], | |
["bs_BA", "bs", "Bosnian (Bosnia and Herzegovina)"], | |
["ca_ES", "ca", "Catalan (Spain)"], | |
["chr_US", "ch", "Cherokee (United States)"], | |
["cs_CZ", "cs", "Czech (Czech Republic)"], | |
["cy_GB", "cy", "Welsh (United Kingdom)"], | |
["da_DK", "da", "Danish (Denmark)"], | |
["de_DE", "de", "German (Germany)"], | |
["el_GR", "el", "Greek (Greece)"], | |
["es_MX", "es", "Spanish (Mexico)"], | |
["et_EE", "et", "Estonian (Estonia)"], | |
["eu_ES", "eu", "Basque (Spain)"], | |
["fa_IR", "fa", "Persian (Iran)"], | |
["ff_SN", "ff", "Fulah (Senegal)"], | |
["fi_FI", "fi", "Finnish (Finland)"], | |
["fo_FO", "fo", "Faroese (Faroe Islands)"], | |
["fr_CA", "fr", "French (Canada)"], | |
["ga_IE", "ga", "Irish (Ireland)"], | |
["gl_ES", "gl", "Galician (Spain)"], | |
["gu_IN", "gu", "Gujarati (India)"], | |
["gv_GB", "gv", "Manx (United Kingdom)"], | |
["ha_NG", "ha", "Hausa (Nigeria)"], | |
["he_IL", "he", "Hebrew (Israel)"], | |
["hi_IN", "hi", "Hindi (India)"], | |
["hr_HR", "hr", "Croatian (Croatia)"], | |
["hu_HU", "hu", "Hungarian (Hungary)"], | |
["hy_AM", "hy", "Armenian (Armenia)"], | |
["id_ID", "id", "Indonesian (Indonesia)"], | |
["ig_NG", "ig", "Igbo (Nigeria)"], | |
["is_IS", "is", "Icelandic (Iceland)"], | |
["it_IT", "it", "Italian (Italy)"], | |
["ja_JP", "ja", "Japanese (Japan)"], | |
["ka_GE", "ka", "Georgian (Georgia)"], | |
["kk_KZ", "kk", "Kazakh (Kazakhstan)"], | |
["kl_GL", "kl", "Kalaallisut (Greenland)"], | |
["km_KH", "km", "Khmer (Cambodia)"], | |
["kn_IN", "kn", "Kannada (India)"], | |
["ko_KR", "ko", "Korean (South Korea)"], | |
["ko_KR", "ko", "Korean (South Korea)"], | |
["lg_UG", "lg", "Ganda (Uganda)"], | |
["lt_LT", "lt", "Lithuanian (Lithuania)"], | |
["lv_LV", "lv", "Latvian (Latvia)"], | |
["mg_MG", "mg", "Malagasy (Madagascar)"], | |
["mk_MK", "mk", "Macedonian (Macedonia)"], | |
["ml_IN", "ml", "Malayalam (India)"], | |
["mr_IN", "mr", "Marathi (India)"], | |
["ms_MY", "ms", "Malay (Malaysia)"], | |
["mt_MT", "mt", "Maltese (Malta)"], | |
["my_MM", "my", "Burmese (Myanmar [Burma])"], | |
["nb_NO", "nb", "Norwegian Bokmål (Norway)"], | |
["ne_NP", "ne", "Nepali (Nepal)"], | |
["nl_NL", "nl", "Dutch (Netherlands)"], | |
["nn_NO", "nn", "Norwegian Nynorsk (Norway)"], | |
["om_ET", "om", "Oromo (Ethiopia)"], | |
["or_IN", "or", "Oriya (India)"], | |
["pa_PK", "pa", "Punjabi (Pakistan)"], | |
["pl_PL", "pl", "Polish (Poland)"], | |
["ps_AF", "ps", "Pashto (Afghanistan)"], | |
["pt_BR", "pt", "Portuguese (Brazil)"], | |
["ro_RO", "ro", "Romanian (Romania)"], | |
["ru_RU", "ru", "Russian (Russia)"], | |
["rw_RW", "rw", "Kinyarwanda (Rwanda)"], | |
["si_LK", "si", "Sinhala (Sri Lanka)"], | |
["sk_SK", "sk", "Slovak (Slovakia)"], | |
["sl_SI", "sl", "Slovenian (Slovenia)"], | |
["so_SO", "so", "Somali (Somalia)"], | |
["sq_AL", "sq", "Albanian (Albania)"], | |
["sr_RS", "sr", "Serbian (Serbia)"], | |
["sv_SE", "sv", "Swedish (Sweden)"], | |
["sw_KE", "sw", "Swahili (Kenya)"], | |
["ta_IN", "ta", "Tamil (India)"], | |
["te_IN", "te", "Telugu (India)"], | |
["th_TH", "th", "Thai (Thailand)"], | |
["ti_ER", "ti", "Tigrinya (Eritrea)"], | |
["to_TO", "to", "Tonga (Tonga)"], | |
["tr_TR", "tr", "Turkish (Turkey)"], | |
["uk_UA", "uk", "Ukrainian (Ukraine)"], | |
["ur_IN", "ur", "Urdu (India)"], | |
["uz_UZ", "uz", "Uzbek (Uzbekistan)"], | |
["vi_VN", "vi", "Vietnamese (Vietnam)"], | |
["yo_NG", "yo", "Yoruba (Nigeria)"], | |
["yo_NG", "yo", "Yoruba (Nigeria)"], | |
["yue_HK", "yu", "Cantonese (Hong Kong)"], | |
["zh_CN", "zh", "Chinese (China)"], | |
["zu_ZA", "zu", "Zulu (South Africa)"], | |
] | |
def getLang(langcode): | |
for lang in langArr: | |
if lang[1] == langcode: | |
return lang | |
raise Exception("localetest doesn't recognize the language " + langcode + '.') | |
if ":" in args.langcode: | |
l1, l2, _ = args.langcode.split(":") | |
else: | |
lang = getLang(args.langcode) | |
l1, l2, l3 = lang | |
language = l1 + ':' + l2 | |
langUtf8 = l1 + '.UTF-8' | |
widgetEnv = os.environ.copy() | |
widgetEnv['LANGUAGE'] = language | |
widgetEnv['LANG'] = langUtf8 | |
widgetEnv['LC_TIME'] = langUtf8 | |
logger.info("LANGUAGE=%s", language) | |
logger.info("LANG=%s", langUtf8) | |
logger.info("LC_TIME=%s", langUtf8) | |
# Build .mo files | |
# TODO | |
subprocess.call([ | |
'plasmoidviewer', | |
'-a', sourceDir, | |
'-l', 'topedge', | |
'-f', 'horizontal', | |
'-x', '0', '-y', '0', | |
], env=widgetEnv) | |
def kpac_build(args): | |
buildFilename = buildFilenameFormat.format( | |
packageName=packageName, | |
packageVersion=packageVersion, | |
buildTag=buildTag, | |
) | |
logger.info("buildTag: %s", buildTag) | |
logger.info("buildFilenameFormat: %s", buildFilenameFormat) | |
logger.info("buildExt: .%s", buildExt) | |
logger.info("buildFilename: %s", buildFilename + '.' + buildExt) | |
logger.info("") | |
# Cleanup | |
oldPackageList = glob.glob('*.' + buildExt) | |
for oldPackage in oldPackageList: | |
print("DELETED: {}".format(oldPackage)) | |
if not args.dryrun: | |
os.remove(oldPackage) | |
# Zip | |
zipLogger = logging.getLogger('build') | |
zipLogger.setLevel(logging.DEBUG) | |
zipLogger.addHandler(logging.StreamHandler()) | |
shutil.make_archive(buildFilename, 'zip', sourceDir, | |
dry_run=args.dryrun, | |
logger=zipLogger, | |
) | |
if not args.dryrun: | |
os.rename(buildFilename + '.zip', buildFilename + '.' + buildExt) | |
# Checksums | |
# echo "[plasmoid] md5: $(md5sum $filename | awk '{ print $1 }')" | |
# echo "[plasmoid] sha256: $(sha256sum $filename | awk '{ print $1 }')" | |
def main(): | |
parser = argparse.ArgumentParser( | |
prog='kpac', | |
description='v{} - Misc tools for a plasma widget like kpackages.'.format(__version__), | |
) | |
subparsers = parser.add_subparsers() | |
parser_install = subparsers.add_parser('install', help='kpac install') | |
parser_install.set_defaults(func=kpac_install) | |
parser_install.add_argument('--no-restart', dest='restart', action='store_false', default=True, help='Do not restart plasmashell after upgrading') | |
parser_uninstall = subparsers.add_parser('uninstall', help='kpac uninstall') | |
parser_uninstall.set_defaults(func=kpac_uninstall) | |
parser_mergei18n = subparsers.add_parser('i18n', help='kpac i18n (Run xgettext translation tools)') | |
parser_mergei18n.set_defaults(func=kpac_i18n) | |
parser_localetest = subparsers.add_parser('localetest', help='kpac localetest [langcode] (Eg: fr=French)') | |
parser_localetest.set_defaults(func=kpac_localetest) | |
parser_localetest.add_argument('langcode', help='Can use just "ar" for the default Arabic locale, or specify a specific locale with "ar_EG:ar".') | |
parser_build = subparsers.add_parser('build', help='kpac build') | |
parser_build.set_defaults(func=kpac_build) | |
parser_build.add_argument('--dryrun', action='store_true', default=False) | |
printMetadata() | |
args = parser.parse_args() | |
if 'func' in args: | |
try: | |
args.func(args) | |
except KeyboardInterrupt: | |
pass | |
else: | |
parser.print_help() | |
if __name__ == '__main__': | |
main() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment