37 root=ast.parse(source,filename=
'<unknown>',mode=
'exec')
41 root=ast.parse(source,filename=
'<unknown>',mode=
'exec',feature_version=(3,6))
43 except SyntaxError
as error:
44 error.filename=filename
45 lines=io.StringIO(source).readlines()
47 tokens=list(tokenize.generate_tokens(io.StringIO(source).readline))
48 except (tokenize.TokenError,IndentationError):
51 if error.lineno
and error.offset
and error.lineno<=len(lines):
52 line=lines[error.lineno-1]
54 token=next((it
for it
in tokens
if it.type==tokenize.NAME
and
55 it.start==(error.lineno,offset)),
None)
56 if token
and token.string
in (
'True',
'False'):
57 placeholder=f
'__pcart_keyword_{len(replacements)}__'
58 while placeholder
in source:
60 replacements[placeholder]=token.string
61 lines[error.lineno-1]=line[:offset]+placeholder+line[token.end[1]:]
65 if isinstance(error,TabError):
68 stringRows={row
for token
in tokens
if token.type==tokenize.STRING
69 for row
in range(token.start[0]+1,token.end[0]+1)}
70 for row,line
in enumerate(lines,1):
71 if row
not in stringRows:
72 prefix=re.match(
r'[ \t]*',line).group()
73 lines[row-1]=prefix.expandtabs(8)+line[len(prefix):]
74 converted=
''.join(lines)
79 if 'unicodeescape' in error.msg:
84 if token.type
in (tokenize.NL,tokenize.COMMENT,tokenize.INDENT,tokenize.DEDENT):
86 if token.type==tokenize.NEWLINE
or token.string==
';':
87 if statement[:3]==[
'from',
'__future__',
'import']
and 'unicode_literals' in statement:
91 statement.append(token.string)
92 for i,token
in enumerate(tokens):
93 if (token.type!=tokenize.STRING
or not token.string.startswith((
'"',
"'"))
or
94 not token.start[0]<=error.lineno<=token.end[0]):
97 while previous>=0
and tokens[previous].type
in (tokenize.NL,tokenize.COMMENT):
100 while following<len(tokens)
and tokens[following].type
in (tokenize.NL,tokenize.COMMENT):
102 if ((previous>=0
and tokens[previous].type
not in (tokenize.NEWLINE,tokenize.INDENT)
and
103 tokens[previous].string!=
':')
or following>=len(tokens)
or
104 tokens[following].type
not in (tokenize.NEWLINE,tokenize.ENDMARKER)):
106 converted=re.sub(
r'(\\+)([uUN])',
107 lambda it: it[1]+(
'\\' if len(it[1])%2
else '')+it[2],token.string)
108 if converted==token.string:
110 startRow,startColumn=token.start
111 endRow,endColumn=token.end
112 source=(
''.join(lines[:startRow-1])+lines[startRow-1][:startColumn]+
113 converted+lines[endRow-1][endColumn:]+
''.join(lines[endRow:]))
114 docstrings.append((startRow,converted,error))
120 if error.msg==
'Generator expression must be parenthesized' and error.lineno:
122 for i,token
in enumerate(tokens):
123 if token.type==tokenize.NAME
and token.string==
'for' and brackets:
125 if token.type!=tokenize.OP:
127 if token.string
in '([{':
128 brackets.append([token.string,
False,token.start[0]])
129 elif token.string
in ')]}':
132 elif (token.string==
',' and brackets
and brackets[-1][:2]==[
'(',
True]
and
133 brackets[-1][2]<=error.lineno<=token.start[0]):
135 while nextToken<len(tokens)
and tokens[nextToken].type
in (tokenize.NL,tokenize.COMMENT):
137 if nextToken<len(tokens)
and tokens[nextToken].string==
')':
138 row,column=token.start
139 lines[row-1]=lines[row-1][:column]+lines[row-1][column+1:]
140 source=
''.join(lines)
148 python2Pattern=
r'(?m)^\s*(?:print\s+(?!\()|exec\s+(?!\()|except\s+[^:\n]+,\s*\w+\s*:)'
149 if not python2Tried
and re.search(python2Pattern,source):
152 from lib2to3.refactor
import RefactoringTool
153 fixers=[
'lib2to3.fixes.fix_print',
'lib2to3.fixes.fix_except',
'lib2to3.fixes.fix_exec']
154 text=source
if source.endswith(
'\n')
else source+
'\n'
155 converted=str(RefactoringTool(fixers).refactor_string(text,filename))
158 if converted!=source:
166 for node
in ast.walk(root)
if docstrings
else ():
167 if isinstance(node,(ast.Module,ast.ClassDef,ast.FunctionDef,ast.AsyncFunctionDef))
and node.body:
169 if isinstance(first,ast.Expr)
and isinstance(first.value,ast.Constant)
and isinstance(first.value.value,str):
170 positions.add((first.value.lineno,ast.get_source_segment(source,first.value)))
171 for row,text,error
in docstrings:
172 if (row,text)
not in positions:
179 for node
in ast.walk(root):
180 for field,value
in ast.iter_fields(node):
181 if isinstance(value,str):
182 for placeholder,original
in replacements.items():
183 value=value.replace(placeholder,original)
184 setattr(node,field,value)
185 elif isinstance(value,list):
186 for i,item
in enumerate(value):
187 if isinstance(item,str):
188 for placeholder,original
in replacements.items():
189 item=item.replace(placeholder,original)
220 obj=re.compile(self.
_pattern_pattern,re.DOTALL)
224 for index
in range(0,len(lst)):
225 if lst[index].find(
'\n')!=-1:
226 lst[index]=lst[index].replace(
'\n',
'')
227 lst[index]=lst[index].replace(
' ',
'')
244 import_visitor=Import()
250 if isinstance(node,(ast.Import,ast.ImportFrom)):
251 import_visitor.visit(node)
252 nodes.extend(reversed(list(ast.iter_child_nodes(node))))
253 except Exception
as e:
254 print(f
"{filePath or '<unknown>'} import visit failed: {e}")
255 md_names=import_visitor.get_md_name()
258 assign_visitor=AssignVisitor()
262 if isinstance(node,ast.Assign):
263 assign_visitor.visit(node)
264 nodes.extend(reversed(list(ast.iter_child_nodes(node))))
265 target_call=assign_visitor.get_target_call()
267 for key,val
in target_call.items():
268 name_parts=val.split(
'.')
269 if name_parts[0]
in target_call:
270 target_call[key]=target_call[name_parts[0]]+
'.'+
'.'.join(name_parts[1:])
272 for key,val
in target_call.items():
273 name_parts=val.split(
'.')
274 if name_parts[0]
in md_names:
275 target_call[key]=(md_names[name_parts[0]]+
'.'+
'.'.join(name_parts[1:])).rstrip(
'.')
290 absolutePath=[k
for k
in fileDict.keys()][0]
291 relativePath=[v
for v
in fileDict.values()][0]
292 norm_relative=relativePath.replace(
'\\',
'/')
293 norm_absolute=absolutePath.replace(
'\\',
'/')
294 pos1=norm_relative.rfind(
'/')
297 relativePath=relativePath[0:pos1]
298 pos2=norm_absolute.rfind(
'/')
299 absolutePath=absolutePath[0:pos2]
300 initPath=f
"{absolutePath}/__init__.py"
302 if os.path.exists(initPath):
303 currentLevel=relativePath.replace(
'\\',
'/').split(
'/')[-1]
304 cacheKey=(initPath,currentLevel)
305 if importCache
is not None and cacheKey
in importCache:
306 importDict=importCache[cacheKey]
309 with tokenize.open(initPath)
as f:
311 except Exception
as e:
312 print(f
"shortenPath --> ast.parse failed: {e}")
314 obj=FromImport(currentLevel)
316 importDict=obj.importDict
317 if importCache
is not None:
318 importCache[cacheKey]=importDict
323 packagePrefix=relativePath.replace(
'\\',
'/').replace(
'/',
'.')+
'.'
324 for key,value
in importDict.items():
326 key=packagePrefix+key[:-1]
327 if api.startswith(key):
329 replaceVal1=packagePrefix
331 key=packagePrefix+key
333 if api==key
or api.startswith(key+
'.'):
335 replaceVal2=packagePrefix+value
337 api=replaceVal2+api[len(replaceKey2):]
339 api=replaceVal1+api[len(replaceKey1):]
341 shortenPath(lst,{absolutePath:relativePath},importCache)
356 for file
in filePath:
358 if os.path.basename(file)!=
'__init__.py':
362 def2format=Def2format()
363 def2format.toFormat(file)
364 package=def2format.prefix.rsplit(
'.__init__',1)[0]
365 packageParts=package.split(
'.')
367 with tokenize.open(file)
as f:
369 except Exception
as e:
370 print(f
"getExportMap --> ast.parse failed: {e}")
374 for node
in root.body:
375 if not isinstance(node,ast.ImportFrom):
380 module=node.module
or ''
382 parentCount=node.level-1
383 if parentCount>len(packageParts):
385 moduleParts=packageParts[:len(packageParts)-parentCount]
387 moduleParts.extend(node.module.split(
'.'))
388 module=
'.'.join(moduleParts)
390 for name
in node.names:
394 sourcePath=
'.'.join(it
for it
in (module,name.name)
if it)
395 publicPath=f
"{package}.{name.asname or name.name}"
396 if sourcePath
and sourcePath!=publicPath:
397 exportMap.setdefault(sourcePath,set()).add(publicPath)
414 def getApiPaths(apiPath,fileDict,importCache=None,exportMap=None):
417 shortenedPath=[apiPath]
419 if shortenedPath[0]
not in paths:
420 paths.append(shortenedPath[0])
424 while index<len(paths):
425 currentPath=paths[index]
426 for publicPath
in sorted((exportMap
or {}).get(currentPath,set())):
427 if publicPath
not in paths:
428 paths.append(publicPath)
444 def getClass(lst,root,prefix,fileDict, pyiFlag=0, importCache=None, exportMap=None):
446 classPath=f
"{prefix}.{className}"
448 classPaths=
getApiPaths(classPath,fileDict,importCache,exportMap)
453 for n
in ast.iter_child_nodes(root):
456 if isinstance(n,(ast.FunctionDef, ast.AsyncFunctionDef)):
457 if 'overload' in ast.unparse(n.decorator_list)
and not pyiFlag:
460 arg=ast.unparse(n.args)
461 arg=arg.replace(
' ',
'')
463 ret=
'->'+ast.unparse(n.returns)
466 if funcName==
'__init__':
469 elif funcName==
'__new__':
472 elif funcName==
'__call__':
476 methodPath=f
"{classPath}.{funcName}"
477 methodPaths=
getApiPaths(methodPath,fileDict,importCache,exportMap)
478 for currentClassPath
in classPaths:
479 publicMethodPath=f
"{currentClassPath}.{funcName}"
480 if publicMethodPath
not in methodPaths:
481 methodPaths.append(publicMethodPath)
482 for currentMethodPath
in methodPaths:
483 lst.append(f
"{currentMethodPath}({arg}){ret}")
487 specialMethod=
'__init__'
490 specialMethod=
'__new__'
493 specialMethod=
'__call__'
496 pattern=fr
"class {re.escape(className)}(\(.*?):"
497 codeText=ast.unparse(root)
505 for currentClassPath
in classPaths:
507 lst.append(f
"{currentClassPath}.{specialMethod}{para}")
508 lst.append(f
"{currentClassPath}{para}")
511 prefix+=f
".{className}"
512 for n
in ast.iter_child_nodes(root):
513 if isinstance(n,ast.ClassDef):
514 getClass(lst,n,prefix,fileDict,pyiFlag,importCache,exportMap)
528 def task(codeText,libApi,prefix,fileDict, pyiFlag=0, importCache=None, exportMap=None):
531 except Exception
as e:
532 file = list(fileDict.keys())[0]
533 print(f
"{file} ast.parse falied: {e}")
535 for node
in ast.iter_child_nodes(rootNode):
536 if isinstance(node, ast.ClassDef):
537 getClass(libApi,node,prefix,fileDict,pyiFlag,importCache,exportMap)
541 if isinstance(node,(ast.FunctionDef, ast.AsyncFunctionDef)):
542 if 'overload' in ast.unparse(node.decorator_list)
and not pyiFlag:
545 arg=ast.unparse(node.args)
546 arg=arg.replace(
' ',
'')
548 ret=
'->'+ast.unparse(node.returns)
551 apiPath=f
"{prefix}.{funcName}"
552 for currentPath
in getApiPaths(apiPath,fileDict,importCache,exportMap):
553 libApi.append(f
"{currentPath}({arg}){ret}")
564 if not sourceRoot
or not publicRoot:
566 sourcePrefix=sourceRoot+
'.'
567 publicPrefix=publicRoot+
'.'
568 assignSourcePrefix=
'A:'+sourcePrefix
569 assignPublicPrefix=
'A:'+publicPrefix
570 if line.startswith(sourcePrefix):
571 return publicPrefix+line[len(sourcePrefix):]
572 if line.startswith(assignSourcePrefix):
573 return assignPublicPrefix+line[len(assignSourcePrefix):]
586 fw.write(f
"{line}\n")
588 if aliasLine
and aliasLine!=line:
589 fw.write(f
"{aliasLine}\n")
598 libName, version, libPath=args
600 fileObj.getPath(libPath)
602 filePath=fileObj.path
603 if not os.path.exists(f
"LibAPIExtraction/{libName}"):
605 os.mkdir(f
"LibAPIExtraction/{libName}")
608 f=open(f
'LibAPIExtraction/{libName}/{libName}{version}',
'w',encoding=
'UTF-8')
617 if libName==
"tensorflow" and os.path.basename(os.path.normpath(libPath))==
"tensorflow_core":
618 publicAliasSource=
"tensorflow_core"
619 publicAliasTarget=
"tensorflow"
620 for file
in filePath:
623 def2format=Def2format()
624 def2format.toFormat(file)
625 prefix=def2format.prefix
626 relativePath=def2format.relativePath
627 fileDict={file:relativePath}
629 if file[-1]==
'y' and file
not in fileVisitLst:
630 fileVisitLst.append(file)
634 if file+
'i' not in fileVisitLst:
636 with tokenize.open(file+
'i')
as fr:
638 task(code_text,pyiLst,prefix,fileDict, 1, importCache,exportMap)
640 fileVisitLst.append(file+
'i')
641 except FileNotFoundError:
644 with tokenize.open(file)
as fr:
647 except Exception
as e:
648 print(f
"{file} read failed: {e}")
652 except Exception
as e:
653 print(f
'{file} ast.parse failed: {e}')
656 f.write(
'\n'+
'-' * 40 + f
"{file}" +
'-' * 40+
'\n')
657 for key,val
in assignDict.items():
658 writeApiLine(f,f
'A:{prefix}.{key}->{val}',publicAliasSource,publicAliasTarget)
660 task(code_text,pyLst,prefix,fileDict,0,importCache,exportMap)
661 pyLst=sorted(set(pyLst))
670 if it2.split(
'(')[0]==it1.split(
'(')[0]:
671 removeLst.append(it1)
677 pyiLst=sorted(set(pyiLst))
678 f.write(
'\n'+
'-' * 40 + f
"{file}"+
'i' +
'-' * 40+
'\n')
683 elif file[-1]==
'i' and file
not in fileVisitLst:
684 fileVisitLst.append(file)
685 if file.rstrip(
'i')
not in fileVisitLst:
687 with tokenize.open(file.rstrip(
'i'))
as fr:
689 task(code_text,pyLst,prefix,fileDict,0,importCache,exportMap)
690 fileVisitLst.append(file.rstrip(
'i'))
692 assignDict=
getAssign(root_node,file.rstrip(
'i'))
693 f.write(
'\n'+
'-' * 40 + f
"{file.rstrip('i')}" +
'-' * 40+
'\n')
694 for key,value
in assignDict.items():
695 writeApiLine(f,f
'A:{prefix}.{key}->{value}',publicAliasSource,publicAliasTarget)
696 pyLst=sorted(set(pyLst))
700 except FileNotFoundError:
703 with tokenize.open(file)
as fr:
705 task(code_text,pyiLst,prefix,fileDict,1,importCache,exportMap)
707 pyiLst=sorted(set(pyiLst))
710 if it2.split(
'(')[0]==it1.split(
'(')[0]:
711 removeLst.append(it1)
716 f.write(
'\n'+
'-' * 40 + f
"{file}" +
'-' * 40+
'\n')
Regular expression match class 正则表达式匹配类
def regex_match(self)
Perform the regular expression match 执行正则表达式匹配
def __init__(self, code_text, pattern)
The constructor 构造函数
def get_result(self)
Return the match result 返回匹配结果
def writeApiLine(fw, line, sourceRoot='', publicRoot='')
Write one lib API line and its public alias if needed 写出一行库API,并按需写出公开路径别名
def parseLibrarySource(source, filename='< unknown >')
Parse library source with supported legacy syntax in the Python 3.9 runtime 在Python 3....
def getExportMap(filePath)
Build the re-export mapping of library API definitions 构建库API定义的重导出映射
def task(codeText, libApi, prefix, fileDict, pyiFlag=0, importCache=None, exportMap=None)
Extract all lib API definitions from a source file 抽取库源码API定义任务
def getClass(lst, root, prefix, fileDict, pyiFlag=0, importCache=None, exportMap=None)
Extract class method definitions from a give class.
def shortenPath(lst, fileDict, importCache=None)
Shorten the API path based on init.py and import alias 通过解析__init__.py和import别名,把源码中的部分API路径缩短
def getAssign(root_node, filePath=None)
Extract all assign node from a .py file's AST 通过AST获取.py文件的Assign语句
def getApiPaths(apiPath, fileDict, importCache=None, exportMap=None)
Get all available paths of a library API definition 获取库API定义的所有可用路径
def getDefFunction(args)
Extract all lib API definitions from a specified version 抽取给定版本的库API定义
def getPublicAliasLine(line, sourceRoot, publicRoot)
Return a public alias line for source-root API lines 为源码根路径API行生成公开路径别名