2025-03-07 08:03:18 +01:00

325 lines
16 KiB
Python

import urllib
import urllib.request
import zipfile
import json
import http.client
import socket
import ssl
import time
import re
import io
import os
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class GetKritaAPI(QObject):
MAP_TYPES = {
'QStringList': ['List[str]'],
'QString': ['str'],
'qreal': ['float'],
'double': ['float'],
'QByteArray': ['Union[QByteArray, bytes, bytearray]','QByteArray'],
'QList':['List'],
'QVector':['List'],
'QMap':['Dict'],
'true':['True'],
'false':['False'],
'void': ['None']
}
DECLARE_CLASS = {
'Document': 'Krita.instance().activeDocument()',
'Node': 'Krita.instance().activeDocument().activeNode()'
}
def updateData(self, kritaVersion, defaultTimeout = 5):
headers = {
'Accept': '*/*',
"User-Agent": "Krita-DevTools Plugin"
}
# Get List of tags and confirm if IPv6 is working properly
tagList = "https://invent.kde.org/api/v4/projects/graphics%2Fkrita/repository/tags"
s1 = time.time()
req = urllib.request.Request(tagList, headers=headers)
with urllib.request.urlopen(req, timeout=defaultTimeout) as source:
if time.time() - s1 > defaultTimeout:
ForceIPv4Connection()
defaultTimeout += 25
try:
jres = json.loads(source.read())
tag = 'master'
for tagDict in jres:
if (tagDict['name'].split('-'))[0] == 'v'+ kritaVersion:
tag = tagDict['name']
# Figure out when API was last updated
lastCommit = "https://invent.kde.org/api/graphql"
urldata={"query":"query pathLastCommit($projectPath: ID!, $path: String, $ref: String!) {\n project(fullPath: $projectPath) {\n id\n __typename\n repository {\n __typename\n tree(path: $path, ref: $ref) {\n __typename\n lastCommit {\n __typename\n sha\n title\n titleHtml\n descriptionHtml\n message\n webPath\n authoredDate\n authorName\n authorGravatar\n author {\n __typename\n name\n avatarUrl\n webPath\n }\n signatureHtml\n pipelines(ref: $ref, first: 1) {\n __typename\n edges {\n __typename\n node {\n __typename\n detailedStatus {\n __typename\n detailsPath\n icon\n tooltip\n text\n group\n }\n }\n }\n }\n }\n }\n }\n }\n}\n","variables":{"projectPath":"graphics/krita","ref":tag,"path":"libs/libkis"}}
req = urllib.request.Request(lastCommit, headers=headers)
req.add_header('Content-Type', 'application/json; charset=utf-8')
jsondata = json.dumps(urldata)
jsondataasbytes = jsondata.encode('utf-8') # needs to be bytes
req.add_header('Content-Length', len(jsondataasbytes))
with urllib.request.urlopen(req, jsondataasbytes, timeout=defaultTimeout) as source:
jres = json.loads(source.read())
date = jres['data']['project']['repository']['tree']['lastCommit']['authoredDate']
# Download the API details
zipUrl = 'https://invent.kde.org/graphics/krita/-/archive/'+tag+'/krita-'+tag+'.zip?path=libs/libkis'
req = urllib.request.Request(zipUrl, headers=headers)
with urllib.request.urlopen(req, timeout=defaultTimeout) as source:
data = source.read()
with zipfile.ZipFile(io.BytesIO(data)) as myzip:
if 'krita-'+tag+'-libs-libkis/libs/libkis/Window.h' in myzip.namelist():
with open( os.path.dirname(os.path.realpath(__file__)) + '.KritaAPI.'+kritaVersion+'.zip', 'wb') as f:
f.write(data)
f.close()
return { 'status': 1, 'data': { 'updated': date } }
else:
raise Exception("Archive is invalid.")
except Exception as e:
return { 'status':0, 'error': str(e) }
def parseData(self, kritaVersion):
mapDict = {}
with zipfile.ZipFile( os.path.dirname(os.path.realpath(__file__)) + '.KritaAPI.'+kritaVersion+'.zip' ) as myzip:
for fn in myzip.namelist():
if fn.endswith('.h') and '/Test/' not in fn:
content = str(myzip.read(fn),'utf-8')
className = re.search(r'^.*[/\\](.+?)\.h$', fn).group(1)
#print ("CLASSNAME", className)
#print ( content )
regexFile = re.compile(r"^(?:.+?\/\*\*\s*(.+?)\s*\*\/.*?|.+?)\n\s*?class KRITALIBKIS_EXPORT (\w+)\s*?[\:\n]\s*(?:public\s+|)(\w+|)", re.DOTALL)
match = regexFile.search(content)
content = regexFile.sub('', content)
#print ( content )
regexClass = re.compile(r"\n\s*?(public|private)\s*(Q_SLOTS|)\s*:\s*?\n(.+?)(\n\s*?(?:public|private)\s*(?:Q_SLOTS|)\s*:\s*?\n|\#endif \/\/ LIBKIS_)", re.DOTALL)
regexMethod = re.compile(r"^(?:.+?(?P<doc>\/\*\*\s*.+?\s*\*\/|\/\/\/[^\r\n]+?)\s*?|\s+?)\n\s*?(?:(?P<type>(?:virtual|static|)\s*?[\w\*]+(?:\<[\w \*,]+\>|))\s+(?P<name>[\w\*]+)|(?P<cname>"+className+r"))\((?P<params>[^\r\n]*)\)(?P<extra>[^\r\n])*?;", re.DOTALL)
regexParam = re.compile(r'(?:^|\s)(\S+?(?:\<.+?\>|))\s*[\&\*]*(\w+?)(?:\s*=\s*(.+?)|)\s*$', re.DOTALL)
if match:
#print( "match=",match.group(2), match.group(1) )
modName = match.group(2)
doc = match.group(1) if match.group(1) else ''
doc = re.sub(r'^\s*(?:/\*\*|///)\s*\**', r'', doc)
doc = re.sub(r'/\s*$', r'', doc)
doc = re.sub(r'\n\s*?\* \@', r'\n@', doc )
start = doc.find('@code')
if start > -1:
end = doc.find('@endcode',start)
doc = doc.replace( doc[start:end],re.sub(r'\n\s*?\* ',r'\n', doc[start:end]))
doc = re.sub(r'\n\s*?\*', r'', doc )
mapDict[modName] = { 'doc':doc, 'methods':{}, 'declare':[], 'parent':match.group(3) }
if modName in self.DECLARE_CLASS:
mapDict[modName]['declare'].append({
'doc': self.DECLARE_CLASS[modName],
'return': className,
'params': [],
'access': ''
})
while True:
match = regexClass.search(content)
if match is not None:
#print ( "matchMethod", match.group(1), match.group(2), match.group(3) )
content = regexClass.sub(match.group(4), content, 1)
methodAccess = match.group(1) + " " + match.group(2)
subcontent = match.group(3)
while True:
matchMethod = regexMethod.search(subcontent)
subcontent = regexMethod.sub('', subcontent, 1)
if matchMethod:
#if matchMethod.group('cname'): print ("CNAME!", matchMethod.group('cname') )
docMethod = matchMethod.group('doc') if matchMethod.group('doc') else ''
docMethod = re.sub(r'^\s*(?:/\*\*|///)\s*\**', r'', docMethod)
docMethod = re.sub(r'/\s*$', r'', docMethod)
docMethod = re.sub(r'\n\s*?\* \@', r'\n@', docMethod)
start2 = docMethod.find('@code')
if start2 > -1:
#print ("SELECTION FOUND!")
end2 = docMethod.find('@endcode',start2)
docMethod = docMethod.replace( docMethod[start2:end2],re.sub(r'\n\s*?\* ',r'\n', docMethod[start2:end2]))
docMethod = re.sub(r'\n\s*?\*', r'', docMethod )
methName = (matchMethod.group('name') if matchMethod.group('name') else matchMethod.group('cname')).replace('*','')
params = []
#print ('mp', matchMethod.group('params'))
paramText = matchMethod.group('params')
if paramText:
paramText = re.sub(r'\<(.*?),(.*?)\>',r'<\1&&\2>', paramText)
for param in paramText.split(','):
pmatch = regexParam.search(param)
#print ( "p["+param+']', pmatch )
params.append({
'type': re.sub(r'\<(.*?)&&(.*?)\>',r'<\1,\2>', pmatch.group(1)),
'name': pmatch.group(2),
'optional': re.sub(r'\<(.*?)&&(.*?)\>',r'<\1,\2>', pmatch.group(3)) if pmatch.group(3) else None
})
#print ("met", className, methName, params)
#if 'internal use only' in docMethod: continue
if methName == modName:
mapDict[modName]['declare'].append({
'doc': docMethod,
'return': className if matchMethod.group('cname') else matchMethod.group('type'),
'params': params,
'access': methodAccess
})
else:
mapDict[modName]['methods'][methName]={
'name': methName,
'doc':docMethod,
'return': matchMethod.group('type').strip(),
'params': params,
'access': methodAccess
}
else:
break
#print ("cont=",content)
else:
#print ("NO MATCH!")
break
#print ("cont=",content)
#methods = decl.group(3)
return mapDict
def convertType(self, vtype, classKeys, param = True):
for k in reversed(sorted(self.MAP_TYPES.keys())):
v = self.MAP_TYPES[k]
rv = v[0] if param or len(v) == 1 else v[1]
if k == vtype:
return rv
elif k in vtype:
vtype = vtype.replace(k, rv)
vtype = vtype.replace('<','[').replace('>',']').replace(' *','').replace('*','').replace('&','').replace('virtual ','')
#if param:
for k in reversed(sorted(classKeys)):
vtype = re.sub( r"(^|[\[\( ,])"+k, r"\1'"+k+"'", vtype)
vtype = re.sub( r"((?:Kis|Ko)\w+)", r"'\1'", vtype)
return vtype
def genAutoComplete(self, kritaVersion):
output = "from PyQt5.QtCore import *\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtWidgets import *\nfrom typing import List, Dict, Union\n"
parseData = self.parseData(kritaVersion)
sortedLists = { 'QObject':[], 'Other':[] }
for k in reversed(sorted(parseData.keys())):
v = parseData[k]
if v['parent'] == 'QObject':
sortedLists['QObject'].append( k )
else:
sortedLists['Other'].append( k )
if v['parent'].startswith('Ko') or v['parent'].startswith('Kis'):
output += v['parent']+" = QObject\n"
for k in sortedLists['QObject'] + sortedLists['Other']:
v = parseData[k]
output += "class " + k + "(" + v['parent'] +"):\n"
output += '\t"""' + v['doc'] + ' """\n\n'
if v['declare']:
paramList = ['self']
for param in m['params']:
paramList.append( param['name'] + ": " + self.convertType(param['type'], parseData.keys() ) + (" = " + self.convertType(param['optional'], parseData.keys() ) if param['optional'] else "") )
output += "\tdef __init__(" + ', '.join(paramList) + ") -> " + self.convertType(m['return'], parseData.keys() , False) +":\n"
output += '\t\t"""' + m['doc'] + ' """\n\n'
for km, m in v['methods'].items():
paramList = []
paramNames = []
#print ( "METHOD=", m )
if "static" not in m['return']:
paramList.append('self')
else:
m['return'] = m['return'].replace('static','')
output += "\t@staticmethod\n"
for param in m['params']:
paramNames.append( param['name'] )
paramList.append( param['name'] + ": " + self.convertType(param['type'], parseData.keys() ) + (" = " + self.convertType(param['optional'], parseData.keys() ) if param['optional'] else "") )
output += "\tdef " + m['name'] + "(" + ', '.join(paramList) + ") -> " + self.convertType(m['return'], parseData.keys() , False) +":\n"
output += "\t\t# type: (" + ', '.join(paramNames) + ") -> " + self.convertType(m['return'], {}.keys() , False) +":\n"
output += '\t\t"""@access ' + m['access'] + "\n" + m['doc'] + ' """\n\n'
return output
class ForceIPv4Connection():
def __init__(self, caller = None):
super().__init__()
urllib.request.HTTPSHandler = self.OverloadHTTPSHandler
print ("overloaded!")
class OverloadHTTPSConnection(http.client.HTTPSConnection):
def connect(self):
self.sock = socket.socket(socket.AF_INET)
self.sock.connect((self.host, self.port))
if self._tunnel_host:
self._tunnel()
self.sock = ssl.wrap_socket(self.sock, self.key_file, self.cert_file)
class OverloadHTTPSHandler(urllib.request.AbstractHTTPHandler):
def __init__(self, debuglevel=0, context=None, check_hostname=None):
urllib.request.AbstractHTTPHandler.__init__(self, debuglevel)
self._context = context
self._check_hostname = check_hostname
def https_open(self, req):
return self.do_open(ForceIPv4Connection.OverloadHTTPSConnection, req,
context=self._context, check_hostname=self._check_hostname)
https_request = urllib.request.AbstractHTTPHandler.do_request_