22import keyword
33import logging
44import re
5- import sys
65from dataclasses import dataclass , field
76from pathlib import Path
87from textwrap import indent
98from typing import *
109from typing import Callable
1110
11+ import click
12+
1213logger = logging .getLogger (__name__ )
13- logger .setLevel (logging .INFO )
1414
1515DEBUG = False
1616
17- if __name__ == "__main__" :
18- import maya .standalone
19-
20- try :
21- logger .info ("Initializing Maya Standalone." )
22- maya .standalone .initialize ()
23- except BaseException :
24- logger .info ("Failed to initialize Maya Standalone." )
25- else :
26- logger .info ("Initialized Maya Standalone Successfully." )
27-
2817
2918from maya import cmds
3019
@@ -137,43 +126,86 @@ def cmds_functions() -> List[Callable]:
137126 return inspect .getmembers (cmds , callable )
138127
139128
140- def _args_from_docstring ( docstring : str ) -> List [Argument ]:
141- if "No Flags" in docstring :
129+ def _args_from_help ( synopsis : str ) -> List [Argument ]:
130+ if "No Flags" in synopsis :
142131 arguments = []
143- elif "Quick help is not available" in docstring :
132+ elif "Quick help is not available" in synopsis :
144133 arguments = ["*args" , "**kwargs" ]
145134 else :
146135 arguments = []
147- types_regex = (
148- r"(\s+((?P<types>[\w\|]+( \w+)?)) ?(?P<multi_use>\(multi-use\))?)?$"
136+
137+ # https://regex101.com/r/9595nC/1
138+ header_regex = r"Synopsis: (?P<name>\w+)( \[flags\] ?(?P<implicit_args>.*))?"
139+
140+ # https://regex101.com/r/bBZoCh/3
141+ flag_regex = (
142+ r"-(?P<short_name>\w+)\s+"
143+ r"-(?P<long_name>\w+)"
144+ r"(?P<types>[\w\|\s\[\]]+)?\s?"
145+ r"(?P<multi_use>\(multi-use\))?\s?"
146+ r"(\(Query Arg (?P<query_arg_mandatory>Mandatory|Optional)\))?"
149147 )
150- flag_regex = r"^-(?P<short_name>\w+)\s+-(?P<long_name>\S+)" + types_regex
151- for line in docstring .splitlines ():
148+
149+ for line in synopsis .splitlines ():
152150 line = line .strip ()
153- match = re .match (flag_regex , line )
154- if match :
155- long_name = match ["long_name" ]
156- short_name = match ["short_name" ]
151+
152+ match_header = re .match (header_regex , line )
153+ if match_header :
154+ implicit_args = match_header ["implicit_args" ]
155+
156+ if not implicit_args :
157+ continue
158+
159+ implicit_args = implicit_args .strip ()
160+
161+ if "..." in implicit_args :
162+ # the type is a list. Eg [String...]
163+ implicit_args = implicit_args [1 :- 1 ].replace ("..." , "" )
164+ list_type = mel_to_python_type (implicit_args )
165+ arg_type = f"List[{ list_type } ]"
166+ arg_name = "*args"
167+ elif implicit_args .count (" " ) > 0 :
168+ # the type is a tuple
169+ implicit_args = implicit_args .replace ("[" , "" ).replace ("]" , "" )
170+ tuple_types = map (mel_to_python_type , implicit_args .split ())
171+ arg_type = f"Tuple[{ ', ' .join (tuple_types )} ]"
172+ arg_name = "*args"
173+ else :
174+ # the type is a basic type
175+ arg_type = mel_to_python_type (implicit_args )
176+ arg_name = "arg0"
177+
178+ argument = Argument (arg_name , None , arg_type )
179+ arguments .append (argument )
180+
181+ continue
182+
183+ match_flag = re .match (flag_regex , line )
184+ if match_flag :
185+ long_name = match_flag ["long_name" ]
186+ short_name = match_flag ["short_name" ]
157187
158188 # types can be either
159189 # - One type. eg: Float
160190 # - Multiple types. eg: Float String Int
191+ # - Union of types?. eg: [Float on|off] # TODO: Unsupported
161192 # - None (when no type is specified).
162- types = str (match ["types" ]).split ()
193+ types = str (match_flag ["types" ]).split ()
163194 types = [mel_to_python_type (t ) for t in types ]
164195
165196 if len (types ) == 1 :
166- type_ = types [0 ]
197+ arg_type = types [0 ]
167198 else :
168- type_ = f"Tuple[{ ', ' .join (types )} ]"
199+ arg_type = f"Tuple[{ ', ' .join (types )} ]"
169200
170- multi_use = match ["multi_use" ]
201+ multi_use = match_flag ["multi_use" ]
171202 if multi_use :
172- type_ = f"List[{ type_ } ]"
203+ arg_type = f"List[{ arg_type } ]"
173204
174- argument = Argument (long_name , short_name , type_ )
205+ argument = Argument (long_name , short_name , arg_type )
175206 if argument not in arguments :
176207 arguments .append (argument )
208+ continue
177209
178210 return arguments
179211
@@ -200,6 +232,8 @@ def mel_to_python_type(type: str) -> str:
200232 "UnsignedInt" : "int" ,
201233 "Time" : "int" ,
202234 # bool
235+ "" : "bool" ,
236+ None : "bool" ,
203237 "None" : "bool" ,
204238 "on|off" : "bool" ,
205239 }
@@ -217,52 +251,40 @@ def generate_stubs_content() -> str:
217251 for func , _ in cmds_functions ():
218252 logger .debug ("Generating signature for %s" , func )
219253 try :
220- docstring = cmds .help (func ).strip ("\n " )
254+ help = cmds .help (func ).strip ("\n " )
221255 except RuntimeError :
222- docstring = ""
256+ help = ""
223257
224- args = _args_from_docstring ( docstring )
258+ args = _args_from_help ( help )
225259
226- function = Function (func , arguments = args , docstring = docstring )
260+ function = Function (func , arguments = args , docstring = help )
227261 lines .append (function .stub )
228262
229263 return "\n " .join (lines )
230264
231265
232266def write_stubs (stubs : str ) -> None :
233267 stubs_path = Path (__file__ ).parent .parent / "maya-stubs" / "cmds" / "__init__.pyi"
268+
234269 logger .debug ("Writing stubs in %s" , stubs_path )
270+
235271 stubs_path .parent .mkdir (parents = True , exist_ok = True )
236272
237273 with stubs_path .open ("w" ) as f :
238274 f .write (stubs )
239275
276+ return stubs_path
240277
278+
279+ @click .command ()
241280def generate_stubs () -> None :
242281 logger .info ("Generating Stubs" )
243282
244- stubs = generate_stubs_content ()
245- write_stubs (stubs )
246-
247- logger .info ("Stubs Generated Successfully" )
248-
249-
250- def main ():
251- global DEBUG
252- DEBUG = "--debug" in sys .argv
253-
254- generate_stubs ()
255-
256-
257- if __name__ == "__main__" :
258- main ()
259-
260- import maya .standalone
261-
262283 try :
263- logger .info ("Uninitializing Maya Standalone." )
264- maya .standalone .uninitialize ()
265- except BaseException :
266- logger .info ("Failed to uninitialize Maya Standalone." )
284+ stubs = generate_stubs_content ()
285+ stubs_path = write_stubs (stubs )
286+ except BaseException as e :
287+ logger .error ("Failed to generate stubs" )
288+ raise e
267289 else :
268- logger .info ( "Uninitialized Maya Standalone Successfully." )
290+ logger .success ( "Stubs generated in %s" , stubs_path )
0 commit comments