This repository has been archived by the owner on Jul 31, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 65
/
Copy pathcode_manager.py
442 lines (346 loc) · 13.5 KB
/
code_manager.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
# GUI object/properties browser.
# Copyright (C) 2015 Matiychuk D.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public License
# as published by the Free Software Foundation; either version 2.1
# of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc.,
# 59 Temple Place,
# Suite 330,
# Boston, MA 02111-1307 USA
import re
def check_valid_identifier(identifier):
"""
Check the identifier is a valid Python identifier.
Since the identifier will be used as an attribute,
don't check for reserved names.
"""
return bool(re.match("[_A-Za-z][_a-zA-Z0-9]*$", identifier))
class CodeSnippet(object):
"""
A piece of the code.
`init_code` a code used one time in the beginning.
`action_code` use already inited object.
`close_code` a part passed to the end of the code.
`indent` means `action_code` and `close_code` should be under the indent.
"""
INIT_SNIPPET = 1
ACTION_SNIPPET = 2
def __init__(self, owner, init_code='', action_code='', close_code='',
indent=False):
if not init_code and not action_code and not close_code:
raise SyntaxError("At least one of init_code, "
"action_code, close_code should be passed.")
self.init_code = init_code
self.action_code = action_code
self.close_code = close_code
self.indent = indent
self.owner = owner
def update(self, init_code=None, action_code=None, close_code=None,
indent=None):
"""
Update code.
Updates only passed the args.
To clear a code use empty line. For instance
.update(init_code='new init code',
action_code='') # Erase old action_code
"""
if init_code is not None:
self.init_code = init_code
if action_code is not None:
self.action_code = action_code
if close_code is not None:
self.close_code = close_code
if indent is not None:
self.indent = indent
@property
def types(self):
"""
Return mask with INIT_SNIPPET and\or ACTION_SNIPPET flags
"""
mask = 0
if self.init_code or self.close_code:
mask |= self.INIT_SNIPPET
if self.action_code:
mask |= self.ACTION_SNIPPET
return mask
def __repr__(self):
lines = []
for code in ("init_code: %s" % self.init_code,
"action_code: %s" % self.action_code,
"close_code: %s" % self.close_code):
lines.append(code)
lines.append('-'*40)
return '\n'.join(lines)
class CodeManager(object):
"""
Manages code snippets. Handles intent if needed and keeps `close_code`
always at the end.
Single instance.
"""
single_object = None
inited = False
def __new__(cls, *args, **kwargs):
if cls.single_object is None:
new = super(CodeManager, cls).__new__(cls, *args, **kwargs)
cls.single_object = new
return new
else:
return cls.single_object
def __init__(self, indent_symbols=' '*4):
if not self.inited:
self.snippets = []
self.indent_symbols = indent_symbols
self.inited = True
def __len__(self):
return len(self.snippets)
def _line(self, code, indent_count=0):
return "{indents}{code}".format(
indents=self.indent_symbols * indent_count,
code=code)
def add(self, snippet):
self.snippets.append(snippet)
def clear(self):
"""
Safely clear all the snippents. Reset all the code counters.
"""
while self.snippets:
self.clear_last()
def clear_last(self):
"""
Remove the latest snippet, decrease appropriate code counter
for snippets of INIT type.
"""
if self.snippets:
last_snippet = self.snippets.pop()
if last_snippet.types & CodeSnippet.INIT_SNIPPET:
last_snippet.owner.release_variable()
def get_full_code(self):
"""
Compose complete code from the snippets.
"""
lines = []
endings = []
indent_count = 0
for snippet in self.snippets:
if snippet.init_code:
lines.append(self._line(snippet.init_code, indent_count))
if snippet.indent:
# Add indent if needed. Notice the indent does not affect the
# init_code in this iteration.
indent_count += 1
if snippet.action_code:
lines.append(self._line(snippet.action_code, indent_count))
if snippet.close_code:
endings.append(self._line(snippet.close_code, indent_count))
# Add the close_code codes.
# Reverse the list for a close_code from the first snippet was passed
# at the end of the code.
if lines:
full_code = "\n".join(lines)
full_code += 2*"\n"
full_code += "\n".join(endings[::-1])
else:
full_code = ""
return full_code
def get_init_snippet(self, owner):
"""
Return the owner's the first INIT snippet.
"""
for snippet in self.snippets:
if snippet.owner == owner and \
snippet.types & CodeSnippet.INIT_SNIPPET:
return snippet
else:
return None
def __repr__(self):
return self.get_full_code()
class CodeGenerator(object):
"""
Code generation behavior. Expect be used as one of base classes of the
SWAPYObject's wrapper.
"""
code_manager = CodeManager()
code_var_name = None # Default value, will be rewrote with composed
# variable name as an instance attribute.
code_var_counters = {} # Default value, will be rewrote as instance's
# class attribute by get_code_id(cls)
@classmethod
def get_code_id(cls, var_prefix='default'):
"""
Increment code id. For example, the script already has
`button1=...` line, so for a new button make `button2=... code`.
The idea is the CodeGenerator's default value
code_var_counters['var_prefix'] will be overwrote by this funk
as a control's wrapper class(e.g Pwa_window) attribute.
Its non default value will be shared for all the control's wrapper
class(e.g Pwa_window) instances.
"""
if var_prefix not in cls.code_var_counters or \
cls.code_var_counters[var_prefix] == 0:
cls.code_var_counters[var_prefix] = 1
return '' # "app=..." instead of "app1=..."
else:
cls.code_var_counters[var_prefix] += 1
return cls.code_var_counters[var_prefix]
@classmethod
def decrement_code_id(cls, var_prefix='default'):
"""
Decrement code id.
"""
cls.code_var_counters[var_prefix] -= 1
def get_code_self(self):
"""
Composes code to access the control.
E. g.: `button1 = calcframe1['Button12']`
Pattern may use the next argument:
* {var}
* {parent_var}
* {main_parent_var}
E. g.: `"{var} = {parent_var}['access_name']\n"`.
"""
pattern = self._code_self
if pattern:
if self.code_var_name is None:
self.code_var_name = self.code_var_pattern.format(
id=self.get_code_id(self.code_var_pattern))
format_kwargs = {'var': self.code_var_name}
try:
main_parent = self.code_parents[0]
except IndexError:
main_parent = None
if self.parent or main_parent:
if self.parent:
format_kwargs['parent_var'] = self.parent.code_var_name
if main_parent:
format_kwargs['main_parent_var'] = main_parent.code_var_name
return pattern.format(**format_kwargs)
return ""
def get_code_action(self, action):
"""
Composes code to run an action. E. g.: `button1.Click()`
Pattern may use the next argument:
* {var}
* {action}
* {parent_var}
* {main_parent_var}
E. g.: `"{var}.{action}()\n"`.
"""
format_kwargs = {'var': self.code_var_name,
'action': action}
if self.parent:
format_kwargs['parent_var'] = self.parent.code_var_name
try:
main_parent = self.code_parents[0]
except IndexError:
main_parent = None
if main_parent:
format_kwargs['main_parent_var'] = main_parent.code_var_name
return self._code_action.format(**format_kwargs)
def get_code_close(self):
"""
Composes code to close the access to the control. E.g.: `app.Kill_()`
Pattern may use the next argument:
* {var}
* {parent_var}
* {main_parent_var}
E. g.: `"{var}.Kill_()\n"`.
"""
pattern = self._code_close
if pattern:
format_kwargs = {'var': self.code_var_name}
if self.parent:
format_kwargs['parent_var'] = self.parent.code_var_name
try:
main_parent = self.code_parents[0]
except IndexError:
main_parent = None
if main_parent:
format_kwargs['main_parent_var'] = main_parent.code_var_name
return pattern.format(**format_kwargs)
return ""
def Get_code(self, action=None):
"""
Return all the code needed to make the action on the control.
Walk parents if needed.
"""
if not self._check_existence(): # target does not exist
raise Exception("Target object does not exist")
if self.code_var_name is None:
# parent/s code is not inited
code_parents = self.code_parents[:]
code_parents.reverse() # start from the top level parent
for p in code_parents:
if not p.code_var_name:
p_code_self = p.get_code_self()
p_close_code = p.get_code_close()
if p_code_self or p_close_code:
parent_snippet = CodeSnippet(p,
init_code=p_code_self,
close_code=p_close_code)
self.code_manager.add(parent_snippet)
own_code_self = self.get_code_self()
own_close_code = self.get_code_close()
# get_code_action call should be after the get_code_self call
own_code_action = self.get_code_action(action) if action else ''
if own_code_self or own_close_code or own_code_action:
own_snippet = CodeSnippet(self,
init_code=own_code_self,
action_code=own_code_action,
close_code=own_close_code)
self.code_manager.add(own_snippet)
else:
# Already inited (all parents too), may use get_code_action
own_code_action = self.get_code_action(action) if action else ''
if own_code_action:
new_action_snippet = CodeSnippet(self,
action_code=own_code_action)
self.code_manager.add(new_action_snippet)
return self.code_manager.get_full_code()
def update_code_style(self):
"""
Seeks for the first INIT snippet and update
`init_code` and `close_code`.
"""
init_code_snippet = self.code_manager.get_init_snippet(self)
if init_code_snippet:
own_code_self = self.get_code_self()
own_close_code = self.get_code_close()
if own_code_self or own_close_code:
init_code_snippet.update(init_code=own_code_self,
close_code=own_close_code)
def release_variable(self):
"""
Clear the access variable to mark the object is not inited and
make possible other use the variable name.
"""
if self.code_var_name:
self.code_var_name = None
self.decrement_code_id(self.code_var_pattern)
if __name__ == '__main__':
c1 = CodeSnippet(None, 'with Start_ as app:', 'frame1 = app.Frame', '',
True)
c2 = CodeSnippet(None, 'button1 = frame1.button', 'button1.Click()',
'del button1')
c3 = CodeSnippet(None, 'button2 = frame1.button', 'button2.Click()')
c4 = CodeSnippet(None, 'with Start_ as app:', 'frame2 = app.Frame', '',
True)
c5 = CodeSnippet(None, '', 'button1.Click()')
cm = CodeManager()
print c1
[cm.add(i) for i in [c1, c2, c3, c4, c5]]
print id(c2)
print cm
c2.update('button1 = the_change', 'the_change.Click()',
'del the_change')
print id(c2)
print cm