spiegel-keyman/linux/keyman-config/keyman_config/kmpmetadata.py
glasseyes 9dc84b83e5 get un/install from ibus working (for me at least!)
move get_metadata to kmpmetadata.py
some ibus utility functions for un/install, restart
and change keyboard
2018-12-19 12:55:26 +07:00

540 lines
17 KiB
Python
Executable file

#!/usr/bin/python3
import json
import configparser
import logging
import sys
import os.path
import magic
from enum import Enum
class KMFileTypes(Enum):
KM_ICON = 1
KM_SOURCE = 2
KM_OSK_SOURCE = 3
KM_KMX = 4
KM_OSK = 5
KM_TOUCH = 6
KM_FONT = 7
KM_DOC = 8
KM_META = 9
KM_IMAGE = 10
KM_TECKIT = 11
KM_CC = 12
KM_XML = 13
KM_UNKNOWN = 99
def print_info(info):
try:
print("---- Info ----")
print("Name: ", info['name']['description'])
print("Copyright: ", info['copyright']['description'])
if 'version' in info:
print("Version: ", info['version']['description'])
if 'author' in info:
print("Author: ", info['author']['description'])
if 'url' in info['author']:
print("Author URL: ", info['author']['url'])
if 'website' in info:
print("Website description: ", info['website']['description'])
if 'url' in info['website']:
print("Website URL: ", info['website']['url'])
except Exception as e:
print(type(e)) # the exception instance
print(e.args) # arguments stored in .args
print(e) # __str__ allows args to be printed directly, pass
pass
def print_system(system):
try:
print("---- System ----")
if 'fileVersion' in system:
print("File Version: ", system['fileVersion'])
if 'keymanDeveloperVersion' in system:
print("Keyman Developer Version: ", system['keymanDeveloperVersion'])
except Exception as e:
print(type(e)) # the exception instance
print(e.args) # arguments stored in .args
print(e) # __str__ allows args to be printed directly, pass
pass
def print_options(options):
try:
print("---- Options ----")
if 'readmeFile' in options:
print("Readme File: ", options['readmeFile'])
if 'graphicFile' in options:
print("Graphic File: ", options['graphicFile'])
except Exception as e:
print(type(e)) # the exception instance
print(e.args) # arguments stored in .args
print(e) # __str__ allows args to be printed directly, pass
pass
def print_keyboards(keyboards):
try:
print("---- Keyboards ----")
for kb in keyboards:
print("Keyboard Name: ", kb['name'])
print("Keyboard Id: ", kb['id'])
if 'version' in kb:
print("Keyboard Version: ", kb['version'])
if 'oskFont' in kb:
print("Keyboard On screen keyboard Font: ", kb['oskFont'])
if 'oskFont' in kb:
print("Keyboard Display Font: ", kb['displayFont'])
print("Languages")
for lang in kb['languages']:
print(" Name: ", lang['name'], "Id: ", lang['id'])
except Exception as e:
print(type(e)) # the exception instance
print(e.args) # arguments stored in .args
print(e) # __str__ allows args to be printed directly, pass
pass
def determine_filetype(kblist, filename):
"""
Determine file type of a filename in a kmp from the extension
Args:
kblist (list): list of keyboard ids
filename (str): File name
Returns:
KMFileTypes: Enum of file type
KM_ICON: Keyboard icon
KM_SOURCE: Keyboard source
KM_OSK_SOURCE: Keyboard on-screen keyboard source
KM_KMX: Compiled keyboard
KM_OSK: Compiled on screen keyboard
KM_TOUCH: JS touch keyboard
KM_FONT: Font
KM_DOC: Documentation
KM_META: Metadata
KM_IMAGE: Image
KM_TECKIT: Files to use with teckit
KM_CC: Consistent changes tables
KM_XML: unspecified xml files
KM_UNKNOWN: Unknown
"""
name, ext = os.path.splitext(filename)
if ext.lower() == ".ico":
return KMFileTypes.KM_ICON
elif ext.lower() == ".kmn":
return KMFileTypes.KM_SOURCE
elif ext.lower() == ".kvks":
return KMFileTypes.KM_OSK_SOURCE
elif ext.lower() == ".kmx":
return KMFileTypes.KM_KMX
elif ext.lower() == ".kvk":
return KMFileTypes.KM_OSK
elif ext.lower() == ".ttf" or ext.lower() == ".otf":
return KMFileTypes.KM_FONT
elif ext.lower() == ".js":
if kblist is None:
return KMFileTypes.KM_UNKNOWN
if name in kblist:
return KMFileTypes.KM_TOUCH
else:
if name == "keyrenderer": # currently 2018-09-21 this is the own known non touch js file
return KMFileTypes.KM_DOC
else:
return KMFileTypes.KM_UNKNOWN
elif ext.lower() == ".txt" or ext.lower() == ".pdf" or ext.lower() == ".htm" \
or ext.lower() == ".html" or ext.lower() == ".doc" or ext.lower() == ".docx" \
or ext.lower() == ".css" or ext.lower() == ".chm" or ext.lower() == "" \
or ext.lower() == ".md" or ext.lower() == ".odt" or ext.lower() == ".rtf" \
or ext.lower() == ".dot" or ext.lower() == ".mht" or ext.lower() == ".woff" \
or ext.lower() == ".php":
return KMFileTypes.KM_DOC
elif ext.lower() == ".inf" or ext.lower() == ".json":
return KMFileTypes.KM_META
elif ext.lower() == ".png" or ext.lower() == ".jpeg" \
or ext.lower() == ".jpg" or ext.lower() == ".gif" \
or ext.lower() == ".bmp":
return KMFileTypes.KM_IMAGE
elif ext.lower() == ".tec" or ext.lower() == ".map":
return KMFileTypes.KM_TECKIT
elif ext.lower() == ".cct":
return KMFileTypes.KM_CC
elif ext.lower() == ".xml":
return KMFileTypes.KM_XML
else:
return KMFileTypes.KM_UNKNOWN
def print_files(files, extracted_dir):
try:
print("---- Files ----")
for kbfile in files:
print("* File name: ", kbfile['name'])
print(" Description: ", kbfile['description'])
print(" Type: ", kbfile['type'])
file = os.path.join(extracted_dir, kbfile['name'])
if os.path.isfile(file):
print(" File", file, "exists")
ms = magic.open(magic.MAGIC_NONE)
ms.load()
ftype = ms.file(file)
print (" Type: ", ftype)
ms.close()
else:
print(" File", file, "does not exist")
except Exception as e:
print(type(e)) # the exception instance
print(e.args) # arguments stored in .args
print(e) # __str__ allows args to be printed directly, pass
pass
def get_fonts(files):
fonts = []
for kbfile in files:
if kbfile['type'] == KMFileTypes.KM_FONT:
fonts.append(kbfile)
return fonts
def parseinfdata(inffile, verbose=False):
"""
Parse the metadata in a kmp.inf file.
Args:
jsonfile (str): Path to kmp.inf
verbose (bool, default False): verbose output
Returns:
list[5]: info, system, options, keyboards, files
info (dict):
name (dict):
description (str): KMP name
copyright (dict):
description (str): KMP copyright
version (dict):
description (str): KMP version
author (dict):
description (str): KMP author
url (str): contact url for the author
system (dict): System info
fileVersion (str): Keyman file format version
keymanDeveloperVersion (str): Keyman Developer version that compiled keyboard
options (dict): Keyboard options
readmeFile (str) : README for the keyboard
keyboards (list): Keyboards in the kmp
name (str): Keyboard name
id (str): Keyboard ID
version (str): Keyboard version
oskFont (str, optional): Recommended on screen keyboard font
displayFont (str, optional): Recommended display font
languages (list): Languages the keyboard is used for
name (str): Language name
id (str): Language ID
files (list): Files in the kmp
name (str): File name
description (str): File description
type (KMFileTypes): Keyman file type
"""
info = system = keyboards = files = options = nonexistent = None
extracted_dir = os.path.dirname(inffile)
if os.path.isfile(inffile):
config = configparser.ConfigParser()
config.optionxform = str
logging.debug("parseinfdata: reading file:%s dir:%s", inffile, extracted_dir)
with open(inffile, 'r', encoding='latin_1') as f:
config.read_file(f)
info = None
for section in config.sections():
if section == 'Info':
if not info:
info = {}
for item in config.items('Info'):
if item[0] == 'Name' or item[0] == 'name':
info['name'] = { 'description' : item[1].split("\"")[1] }
elif item[0] == 'Copyright' or item[0] == 'copyright':
info['copyright'] = { 'description' : item[1].split("\"")[1] }
elif item[0] == 'Version':
info['version'] = { 'description' : item[1].split("\"")[1] }
elif item[0] == 'Author':
info['author'] = { 'description' : item[1].split("\"")[1], 'url' : item[1].split("\"")[3] }
elif item[0] == "WebSite":
info['website'] = { 'description' : item[1].split("\"")[1], 'url' : item[1].split("\"")[3] }
else:
logging.warning("Unknown item in Info: %s", item[0])
if section == 'PackageInfo':
if not info:
info = {}
info['version'] = { 'description' : "1.0" }
for item in config.items('PackageInfo'):
if item[0] == 'Name' or item[0] == 'name':
if item[1].split("\"")[1]:
info['name'] = { 'description' : item[1].split("\"")[1] }
else:
info['name'] = { 'description' : item[1].split("\"")[2] }
elif item[0] == 'Copyright' or item[0] == 'copyright':
if item[1].split("\"")[1]:
info['copyright'] = { 'description' : item[1].split("\"")[1] }
else:
info['copyright'] = { 'description' : item[1].split("\"")[2] }
elif item[0] == 'Version':
info['version'] = { 'description' : item[1].split("\"")[1] }
elif item[0] == 'Author':
info['author'] = { 'description' : item[1].split("\"")[1], 'url' : item[1].split("\"")[3] }
elif item[0] == "WebSite":
if item[1].split("\"")[1]:
info['website'] = { 'description' : item[1].split("\"")[1] }
else:
info['website'] = { 'description' : item[1].split("\"")[2] }
else:
logging.warning("Unknown item in Info: %s", item[0])
elif section == 'Package':
system = {}
if not options:
options = {}
for item in config.items('Package'):
if item[0] == 'Version':
system['fileVersion'] = item[1]
elif item[0] == 'ReadMeFile':
options['readmeFile'] = item[1]
elif item[0] == 'GraphicFile':
options['graphicFile'] = item[1]
elif item[0] == 'DisableKeepFonts':
options['disableKeepFonts'] = item[1]
elif item[0] == 'BothVersionsIncluded':
options['bothVersionsIncluded'] = item[1]
elif item[0] == 'ExecuteProgram':
pass
else:
print("Unknown item in Package:", item[0])
system['keymanDeveloperVersion'] = ""
elif "Keyboard" in section:
keyboards = []
keyboard = {}
languages = []
for item in config.items(section):
if item[0] == 'Name':
keyboard['name'] = item[1]
elif item[0] == 'ID':
keyboard['id'] = item[1]
elif item[0] == 'Version':
keyboard['version'] = item[1]
elif item[0] == 'OSKFont':
keyboard['oskFont'] = item[1]
elif item[0] == 'DisplayFont':
keyboard['displayFont'] = item[1]
elif item[0] == 'RTL':
keyboard['RTL'] = item[1]
elif "Language" in item[0]:
# only split on first ','
langid, langname = item[1].split(",", 1)
languages.append({ 'name' : langname, 'id' : langid })
else:
logging.warning("Unknown item in keyboard: %s", item[0])
keyboard['languages'] = languages
keyboards.append(keyboard)
elif section == "Files":
files = []
for item in config.items(section):
splititem = item[1].split("\"")
kbfile = { 'name' : splititem[3], 'description' : splititem[1], 'type' : determine_filetype(None, splititem[3]) }
files.append(kbfile)
elif section == "InstallFiles":
files = []
for item in config.items(section):
kbfile = { 'name' : item[0], 'description' : item[1], 'type' : determine_filetype(None, item[0]) }
files.append(kbfile)
elif section == 'Install':
if not options:
options = {}
for item in config.items('Install'):
if item[0] == 'ReadmeFile':
options['readmeFile'] = item[1]
kblist = []
if not info:
info = {}
if not 'version' in info:
info['version'] = { 'description' : "1.0" }
# inf file may not have keyboards in legacy kmps so generate it if needed
if files and not keyboards:
id = "unknown"
keyboards = []
for kbfile in files:
if kbfile['type'] == KMFileTypes.KM_KMX:
id = os.path.basename(os.path.splitext(kbfile['name'])[0])
keyboards = [ { 'name' : id,
'id' : id,
'version' : info['version']['description'] } ]
kblist = []
if files:
for k in keyboards:
kblist.append(k['id'])
for kbfile in files:
if kbfile['type'] == KMFileTypes.KM_UNKNOWN:
kbfile['type'] = determine_filetype(kblist, kbfile['name'])
logging.debug("finished parsing %s", inffile)
if verbose:
print_info(info)
print_system(system)
print_options(options)
print_keyboards(keyboards)
print_files(files, extracted_dir)
return info, system, options, keyboards, files
def parsemetadata(jsonfile, verbose=False):
"""
Parse the metadata in a kmp.json file.
Args:
jsonfile (str): Path to kmp.json
verbose (bool, default False): verbose output
Returns:
list[5]: info, system, options, keyboards, files
info (dict):
name (dict):
description (str): KMP name
copyright (dict):
description (str): KMP copyright
version (dict):
description (str): KMP version
author (dict):
description (str): KMP author
url (str): contact url for the author
system (dict): System info
fileVersion (str): Keyman file format version
keymanDeveloperVersion (str): Keyman Developer version that compiled keyboard
options (dict): Keyboard options
readmeFile (str) : README for the keyboard
keyboards (list): Keyboards in the kmp
name (str): Keyboard name
id (str): Keyboard ID
version (str): Keyboard version
oskFont (str, optional): Recommended on screen keyboard font
displayFont (str, optional): Recommended display font
languages (list): Languages the keyboard is used for
name (str): Language name
id (str): Language ID
files (list): Files in the kmp
name (str): File name
description (str): File description
"""
info = system = keyboards = files = options = nonexistent = None
extracted_dir = os.path.dirname(jsonfile)
logging.debug("parsemetadata: reading file:%s dir:%s", jsonfile, extracted_dir)
if os.path.isfile(jsonfile):
with open(jsonfile, "r") as read_file:
data = json.load(read_file)
for x in data:
if x == 'info':
info = data[x]
elif x == 'system':
system = data[x]
elif x == 'keyboards':
keyboards = data[x]
elif x == 'files':
files = data[x]
elif x == 'options':
options = data[x]
elif x == 'nonexistent':
nonexistent = data[x]
kblist = []
for k in keyboards:
kblist.append(k['id'])
for kbfile in files:
kbfile['type'] = determine_filetype(kblist, kbfile['name'])
if nonexistent != None:
logging.warning("This should not happen")
if verbose:
print_info(info)
print_system(system)
if options:
print_options(options)
print_keyboards(keyboards)
print_files(files, extracted_dir)
return info, system, options, keyboards, files
def get_metadata(tmpdirname):
"""
Get metadata from kmp.json if it exists.
If it does not exist then will return get_and_convert_infdata
Args:
inputfile (str): path to kmp file
tmpdirname(str): temp directory to extract kmp
Returns:
list[5]: info, system, options, keyboards, files
see kmpmetadata.parsemetadata for details
"""
kmpjson = os.path.join(tmpdirname, "kmp.json")
if os.path.isfile(kmpjson):
return parsemetadata(kmpjson, False)
else:
return get_and_convert_infdata(tmpdirname)
def get_and_convert_infdata(tmpdirname):
"""
Get metadata from kmp.inf if it exists.
Convert it to kmp.json if possible
Args:
inputfile (str): path to kmp file
tmpdirname(str): temp directory to extract kmp
Returns:
list[5]: info, system, options, keyboards, files
see kmpmetadata.parseinfdata for details
"""
kmpinf = os.path.join(tmpdirname, "kmp.inf")
if os.path.isfile(kmpinf):
info, system, options, keyboards, files = parseinfdata(kmpinf, False)
j = infmetadata_to_json(info, system, options, keyboards, files)
kmpjson = os.path.join(tmpdirname, "kmp.json")
with open(kmpjson, "w") as write_file:
print(j, file=write_file)
return info, system, options, keyboards, files
else:
return None, None, None, None, None
def infmetadata_to_json(info, system, options, keyboards, files):
jsonfiles = []
for entry in files:
jsonfiles.append({"name" : entry["name"], "description" : entry["description"]})
d = { "system" : system, "options" : options,
"info" : info , "keyboards" : keyboards, "files" : jsonfiles }
return json.dumps(d, indent=2)
def main(argv):
if len(sys.argv) != 2:
logging.error("kmpmetadata.py <kmp.json> or <kmp.inf>")
sys.exit(2)
inputfile = sys.argv[1]
if not os.path.isfile(inputfile):
logging.error("kmpmetadata.py Input file ", inputfile, " not found.")
logging.error("kmpmetadata.py <kmp.json> or <kmp.inf>")
sys.exit(2)
name, ext = os.path.splitext(inputfile)
if ext == ".json":
parsemetadata(inputfile, True)
elif ext == ".inf":
info, system, options, keyboards, files = parseinfdata(inputfile, True)
jsontest = metadata_to_json(info, system, options, keyboards, files)
print(jsontest)
else:
logging.error("kmpmetadata.py Input file must be json or inf.")
logging.error("kmpmetadata.py <kmp.json> or <kmp.inf>")
sys.exit(2)
if __name__ == "__main__":
main(sys.argv[1:])