1212
1313from ._version import __version__ # NOQA
1414
15- ASYNC_TO_SYNC = {
15+ __all__ = [
16+ "Rule" ,
17+ "cmdclass_build_py" ,
18+ ]
19+
20+
21+ _ASYNC_TO_SYNC = {
1622 "__aenter__" : "__enter__" ,
1723 "__aexit__" : "__exit__" ,
1824 "__aiter__" : "__iter__" ,
2733 "StopAsyncIteration" : "StopIteration" ,
2834}
2935
36+
37+ class Rule :
38+ """A single set of rules for 'unasync'ing file(s)"""
39+
40+ def __init__ (self , fromdir , todir , replacements = None ):
41+ self .fromdir = fromdir .replace ("/" , os .sep )
42+ self .todir = todir .replace ("/" , os .sep )
43+
44+ # Add any additional user-defined token replacements to our list.
45+ self .token_replacements = _ASYNC_TO_SYNC .copy ()
46+ for key , val in (replacements or {}).items ():
47+ self .token_replacements [key ] = val
48+
49+ def match (self , filepath ):
50+ """Determines if a Rule matches a given filepath and if so
51+ returns a higher comparable value if the match is more specific.
52+ """
53+ file_segments = [x for x in filepath .split (os .sep ) if x ]
54+ from_segments = [x for x in self .fromdir .split (os .sep ) if x ]
55+ len_from_segments = len (from_segments )
56+
57+ if len_from_segments > len (file_segments ):
58+ return False
59+
60+ for i in range (len (file_segments ) - len_from_segments + 1 ):
61+ if file_segments [i : i + len_from_segments ] == from_segments :
62+ return len_from_segments , i
63+
64+ return False
65+
66+ def unasync_file (self , filepath ):
67+ with open (filepath , "rb" ) as f :
68+ write_kwargs = {}
69+ if sys .version_info [0 ] >= 3 :
70+ encoding , _ = std_tokenize .detect_encoding (f .readline )
71+ write_kwargs ["encoding" ] = encoding
72+ f .seek (0 )
73+ tokens = tokenize (f )
74+ tokens = self .unasync_tokens (tokens )
75+ result = untokenize (tokens )
76+ outfilepath = filepath .replace (self .fromdir , self .todir )
77+ makedirs_existok (os .path .dirname (outfilepath ))
78+ with open (outfilepath , "w" , ** write_kwargs ) as f :
79+ print (result , file = f , end = "" )
80+
81+ def unasync_tokens (self , tokens ):
82+ # TODO __await__, ...?
83+ used_space = None
84+ for space , toknum , tokval in tokens :
85+ if tokval in ["async" , "await" ]:
86+ # When removing async or await, we want to use the whitespace that
87+ # was before async/await before the next token so that
88+ # `print(await stuff)` becomes `print(stuff)` and not
89+ # `print( stuff)`
90+ used_space = space
91+ else :
92+ if toknum == std_tokenize .NAME :
93+ tokval = self .unasync_name (tokval )
94+ elif toknum == std_tokenize .STRING :
95+ left_quote , name , right_quote = tokval [0 ], tokval [1 :- 1 ], tokval [- 1 ]
96+ tokval = left_quote + self .unasync_name (name ) + right_quote
97+ if used_space is None :
98+ used_space = space
99+ yield (used_space , tokval )
100+ used_space = None
101+
102+ def unasync_name (self , name ):
103+ if name in self .token_replacements :
104+ return self .token_replacements [name ]
105+ # Convert classes prefixed with 'Async' into 'Sync'
106+ elif len (name ) > 5 and name .startswith ("Async" ) and name [5 ].isupper ():
107+ return "Sync" + name [5 :]
108+ return name
109+
110+
30111Token = collections .namedtuple ("Token" , ["type" , "string" , "start" , "end" , "line" ])
31112
32113
@@ -60,37 +141,6 @@ def tokenize(f):
60141 last_end = (tok .end [0 ] + 1 , 0 )
61142
62143
63- def unasync_name (name ):
64- if name in ASYNC_TO_SYNC :
65- return ASYNC_TO_SYNC [name ]
66- # Convert classes prefixed with 'Async' into 'Sync'
67- elif len (name ) > 5 and name .startswith ("Async" ) and name [5 ].isupper ():
68- return "Sync" + name [5 :]
69- return name
70-
71-
72- def unasync_tokens (tokens ):
73- # TODO __await__, ...?
74- used_space = None
75- for space , toknum , tokval in tokens :
76- if tokval in ["async" , "await" ]:
77- # When removing async or await, we want to use the whitespace that
78- # was before async/await before the next token so that
79- # `print(await stuff)` becomes `print(stuff)` and not
80- # `print( stuff)`
81- used_space = space
82- else :
83- if toknum == std_tokenize .NAME :
84- tokval = unasync_name (tokval )
85- elif toknum == std_tokenize .STRING :
86- left_quote , name , right_quote = tokval [0 ], tokval [1 :- 1 ], tokval [- 1 ]
87- tokval = left_quote + unasync_name (name ) + right_quote
88- if used_space is None :
89- used_space = space
90- yield (used_space , tokval )
91- used_space = None
92-
93-
94144def untokenize (tokens ):
95145 return "" .join (space + tokval for space , tokval in tokens )
96146
@@ -103,34 +153,21 @@ def makedirs_existok(dir):
103153 raise
104154
105155
106- def unasync_file (filepath , fromdir , todir ):
107- with open (filepath , "rb" ) as f :
108- write_kwargs = {}
109- if sys .version_info [0 ] >= 3 :
110- encoding , _ = std_tokenize .detect_encoding (f .readline )
111- write_kwargs ["encoding" ] = encoding
112- f .seek (0 )
113- tokens = tokenize (f )
114- tokens = unasync_tokens (tokens )
115- result = untokenize (tokens )
116- outfilepath = filepath .replace (fromdir , todir )
117- makedirs_existok (os .path .dirname (outfilepath ))
118- with open (outfilepath , "w" , ** write_kwargs ) as f :
119- print (result , file = f , end = "" )
156+ _DEFAULT_RULE = Rule (fromdir = "/_async/" , todir = "/_sync/" )
120157
121158
122- class build_py (orig .build_py ):
159+ class _build_py (orig .build_py ):
123160 """
124161 Subclass build_py from setuptools to modify its behavior.
125162
126163 Convert files in _async dir from being asynchronous to synchronous
127164 and saves them in _sync dir.
128165 """
129166
130- RENAME_DIR_FROM_TO = ("_async" , "_sync" ) # Controls what directory will be renamed.
167+ UNASYNC_RULES = (_DEFAULT_RULE ,)
131168
132169 def run (self ):
133- dir_from , dir_to = self .RENAME_DIR_FROM_TO
170+ rules = self .UNASYNC_RULES
134171
135172 self ._updated_files = []
136173
@@ -143,8 +180,17 @@ def run(self):
143180
144181 # Our modification!
145182 for f in self ._updated_files :
146- if os .sep + dir_from + os .sep in f :
147- unasync_file (f , dir_from , dir_to )
183+ found_rule = None
184+ found_weight = None
185+
186+ for rule in rules :
187+ weight = rule .match (f )
188+ if weight and (found_weight is None or weight > found_weight ):
189+ found_rule = rule
190+ found_weight = weight
191+
192+ if found_rule :
193+ found_rule .unasync_file (f )
148194
149195 # Remaining base class code
150196 self .byte_compile (self .get_outputs (include_bytecode = 0 ))
@@ -156,8 +202,10 @@ def build_module(self, module, module_file, package):
156202 return outfile , copied
157203
158204
159- def customize_build_py (rename_dir_from_to = ("_async" , "_sync" )):
160- class _build_py (build_py ):
161- RENAME_DIR_FROM_TO = rename_dir_from_to
205+ def cmdclass_build_py (rules = (_DEFAULT_RULE ,)):
206+ """Creates a 'build_py' class for use within 'cmdclass={"build_py": ...}'"""
207+
208+ class _custom_build_py (_build_py ):
209+ UNASYNC_RULES = rules
162210
163- return _build_py
211+ return _custom_build_py
0 commit comments