~ubuntu-branches/ubuntu/utopic/python-apptools/utopic

« back to all changes in this revision

Viewing changes to enthought/appscripting/script_manager.py

  • Committer: Bazaar Package Importer
  • Author(s): Varun Hiremath
  • Date: 2011-07-08 23:55:50 UTC
  • mfrom: (2.1.9 sid)
  • Revision ID: james.westby@ubuntu.com-20110708235550-yz5u79ubeo4dhyfx
Tags: 4.0.0-1
* New upstream release
* Update debian/watch file

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#------------------------------------------------------------------------------
2
 
# Copyright (c) 2008, Riverbank Computing Limited
3
 
# All rights reserved.
4
 
#
5
 
# This software is provided without warranty under the terms of the BSD
6
 
# license included in enthought/LICENSE.txt and may be redistributed only
7
 
# under the conditions described in the aforementioned license.  The license
8
 
# is also available online at http://www.enthought.com/licenses/BSD.txt
9
 
# Thanks for using Enthought open source!
10
 
#
11
 
# Author: Riverbank Computing Limited
12
 
# Description: <Enthought application scripting package component>
13
 
#------------------------------------------------------------------------------
14
 
 
15
 
 
16
 
# Standard library imports.
17
 
import datetime
18
 
import types
19
 
import weakref
20
 
 
21
 
# Enthought library imports.
22
 
from enthought.traits.api import Any, Bool, Callable, Dict, Event, HasTraits, \
23
 
        implements, Instance, Int, List, Property, Str, Unicode
24
 
 
25
 
# Local imports.
26
 
from bind_event import BindEvent
27
 
from i_bind_event import IBindEvent
28
 
from i_script_manager import IScriptManager
29
 
from lazy_namespace import add_to_namespace, FactoryWrapper, LazyNamespace
30
 
from scriptable_type import make_object_scriptable
31
 
 
32
 
 
33
 
class _BoundObject(HasTraits):
34
 
    """The base class for any object that can be bound to a name."""
35
 
 
36
 
    #### '_BoundObject' interface #############################################
37
 
 
38
 
    # Set if the object was explicitly bound.
39
 
    explicitly_bound = Bool(True)
40
 
 
41
 
    # The name the object is bound to.
42
 
    name = Str
43
 
 
44
 
    # The object being bound.
45
 
    obj = Any
46
 
 
47
 
 
48
 
class _ScriptObject(_BoundObject):
49
 
    """The _ScriptObject class encapsulates a scriptable object."""
50
 
 
51
 
    #### '_BoundObject' interface #############################################
52
 
 
53
 
    # The object being bound.
54
 
    obj = Property
55
 
 
56
 
    #### '_ScriptObject' interface ############################################
57
 
 
58
 
    # The positional arguments passed to __init__ after being converted to
59
 
    # strings.  A particular argument may be an exception if it couldn't be
60
 
    # converted.
61
 
    args = List
62
 
 
63
 
    # The keyword arguments passed to __init__ after being converted to
64
 
    # strings.  A particular argument may be an exception if it couldn't be
65
 
    # converted.
66
 
    kwargs = Dict
67
 
 
68
 
    # The id of the object.
69
 
    obj_id = Int
70
 
 
71
 
    # A weak reference to the object.
72
 
    obj_ref = Any
73
 
 
74
 
    # The type of the scriptable object.
75
 
    scripted_type = Any
76
 
 
77
 
    ###########################################################################
78
 
    # Private interface.
79
 
    ###########################################################################
80
 
 
81
 
    def _get_obj(self):
82
 
        """The property getter."""
83
 
 
84
 
        return self.obj_ref()
85
 
 
86
 
 
87
 
class _ScriptCall(HasTraits):
88
 
    """ The _ScriptCall class is the base class for all script calls. """
89
 
 
90
 
    #### '_ScriptCall' interface ##############################################
91
 
 
92
 
    # The name of the call.
93
 
    name = Str
94
 
 
95
 
    # The scriptable object.
96
 
    so = Any
97
 
 
98
 
    ###########################################################################
99
 
    # '_ScriptCall' interface.
100
 
    ###########################################################################
101
 
 
102
 
    def as_str(self, sm, so_needed):
103
 
        """ Return the string equivalent of the call, updated the list of
104
 
        needed scriptable objects if required.
105
 
        """
106
 
 
107
 
        raise NotImplementedError
108
 
 
109
 
 
110
 
class _ScriptTraitGet(_ScriptCall):
111
 
    """ The _ScriptTraitGet class encapsulates a single call to the get of a
112
 
    scriptable trait.
113
 
    """
114
 
 
115
 
    #### '_ScriptTraitGet' interface ##########################################
116
 
 
117
 
    # Set if the getter has side effects.
118
 
    has_side_effects = Bool(False)
119
 
 
120
 
    # The result of the get.  Keeping a reference to it means that the memory
121
 
    # can't get reused.
122
 
    result = Any
123
 
 
124
 
    ###########################################################################
125
 
    # '_ScriptCall' interface.
126
 
    ###########################################################################
127
 
 
128
 
    def as_str(self, sm, so_needed):
129
 
        """ Return the string equivalent of the call, updated the list of
130
 
        needed scriptable objects if required.
131
 
        """
132
 
 
133
 
        # Ignore if it is no longer bound.
134
 
        if not self.so.name:
135
 
            return None
136
 
 
137
 
        if self.result is None:
138
 
            rstr = ""
139
 
        else:
140
 
            nr, _ = sm._results[id(self.result)]
141
 
 
142
 
            if nr >= 0:
143
 
                rstr = "r%d = " % nr
144
 
            else:
145
 
                rstr = ""
146
 
 
147
 
        # Unless getter has side effects, if the result is not needed then
148
 
        # don't bother including it in the script.
149
 
        if not self.has_side_effects and rstr == "":
150
 
            return None
151
 
 
152
 
        so = sm.arg_as_string(self.so, so_needed)
153
 
 
154
 
        return "%s%s.%s" % (rstr, so, self.name)
155
 
 
156
 
 
157
 
class _ScriptTraitSet(_ScriptCall):
158
 
    """ The _ScriptTraitSet class encapsulates a single call to the set of a
159
 
    scriptable trait.
160
 
    """
161
 
 
162
 
    #### '_ScriptTraitSet' interface ##########################################
163
 
 
164
 
    # The value the trait is set to.
165
 
    value = Any
166
 
 
167
 
    ###########################################################################
168
 
    # '_ScriptCall' interface.
169
 
    ###########################################################################
170
 
 
171
 
    def as_str(self, sm, so_needed):
172
 
        """ Return the string equivalent of the call, updated the list of
173
 
        needed scriptable objects if required.
174
 
        """
175
 
 
176
 
        # Ignore if it is no longer bound.
177
 
        if not self.so.name:
178
 
            return None
179
 
 
180
 
        so = sm.arg_as_string(self.so, so_needed)
181
 
        value = sm.arg_as_string(self.value, so_needed)
182
 
 
183
 
        return "%s.%s = %s" % (so, self.name, value)
184
 
 
185
 
 
186
 
class _ScriptMethod(_ScriptCall):
187
 
    """ The _ScriptMethod class encapsulates a single call to a scriptable
188
 
    method.
189
 
    """
190
 
 
191
 
    #### '_ScriptMethod' interface ############################################
192
 
 
193
 
    # The positional arguments passed to the method after being converted to
194
 
    # strings.  A particular argument may be an exception if it couldn't be
195
 
    # converted.
196
 
    args = List
197
 
 
198
 
    # The keyword arguments passed to the method after being converted to
199
 
    # strings.  A particular argument may be an exception if it couldn't be
200
 
    # converted.
201
 
    kwargs = Dict
202
 
 
203
 
    # The result of the method call.  Keeping a reference to it means that the
204
 
    # memory can't get reused.
205
 
    result = Any
206
 
 
207
 
    ###########################################################################
208
 
    # '_ScriptCall' interface.
209
 
    ###########################################################################
210
 
 
211
 
    def as_str(self, sm, so_needed):
212
 
        """ Return the string equivalent of the call, updated the list of
213
 
        needed scriptable objects if required.
214
 
        """
215
 
 
216
 
        # Ignore if it is no longer bound.
217
 
        if self.so and not self.so.name:
218
 
            return None
219
 
 
220
 
        if self.result is None:
221
 
            rstr = ""
222
 
        elif type(self.result) is type(()):
223
 
            rlist = []
224
 
            needed = False
225
 
 
226
 
            for r in self.result:
227
 
                nr, _ = sm._results[id(r)]
228
 
 
229
 
                if nr >= 0:
230
 
                    rlist.append("r%d" % nr)
231
 
                    needed = True
232
 
                else:
233
 
                    rlist.append("_")
234
 
 
235
 
            if needed:
236
 
                rstr = ", ".join(rlist) + " = "
237
 
            else:
238
 
                rstr = ""
239
 
        else:
240
 
            nr, _ = sm._results[id(self.result)]
241
 
 
242
 
            if nr >= 0:
243
 
                rstr = "r%d = " % nr
244
 
            else:
245
 
                rstr = ""
246
 
 
247
 
        if self.so:
248
 
            so = sm.arg_as_string(self.so, so_needed) + '.'
249
 
        else:
250
 
            so = ''
251
 
 
252
 
        args = sm.args_as_string_list(self.args, self.kwargs, so_needed)
253
 
 
254
 
        return "%s%s%s(%s)" % (rstr, so, self.name, ", ".join(args))
255
 
 
256
 
 
257
 
class _FactoryObject(_BoundObject):
258
 
    """ The _FactoryObject class wraps a factory that lazily creates
259
 
    scriptable objects.
260
 
    """
261
 
 
262
 
    #### '_BoundObject' interface #############################################
263
 
 
264
 
    # The object being bound.
265
 
    obj = Property
266
 
 
267
 
    #### '_FactoryObject' interface ###########################################
268
 
 
269
 
    # The optional object that defines the scripting API.
270
 
    api = Any
271
 
 
272
 
    # The scriptable object factory.
273
 
    factory = Callable
274
 
 
275
 
    # The optional attribute include list.
276
 
    includes = Any
277
 
 
278
 
    # The optional attribute exclude list.
279
 
    excludes = Any
280
 
 
281
 
    ###########################################################################
282
 
    # Private interface.
283
 
    ###########################################################################
284
 
 
285
 
    def _get_obj(self):
286
 
        """The property getter."""
287
 
 
288
 
        return FactoryWrapper(factory=self.factory, api=self.api,
289
 
                includes=self.includes, excludes=self.excludes)
290
 
 
291
 
 
292
 
class ScriptManager(HasTraits):
293
 
    """ The ScriptManager class is the default implementation of
294
 
    IScriptManager.
295
 
    """
296
 
 
297
 
    implements(IScriptManager)
298
 
 
299
 
    #### 'IScriptManager' interface ###########################################
300
 
 
301
 
    # This event is fired whenever a scriptable object is bound or unbound.  It
302
 
    # is intended to be used by an interactive Python shell to give the
303
 
    # advanced user access to the scriptable objects.  If an object is created
304
 
    # via a factory then the event is fired when the factory is called, and not
305
 
    # when the factory is bound.
306
 
    bind_event = Event(IBindEvent)
307
 
 
308
 
    # This is set if user actions are being recorded as a script.  It is
309
 
    # maintained by the script manager.
310
 
    recording = Bool(False)
311
 
 
312
 
    # This is the text of the script currently being recorded (or the last
313
 
    # recorded script if none is currently being recorded).  It is updated
314
 
    # automatically as the user performs actions.
315
 
    script = Property(Unicode)
316
 
 
317
 
    # This event is fired when the recorded script changes.  The value of the
318
 
    # event will be the ScriptManager instance.
319
 
    script_updated = Event(IScriptManager)
320
 
 
321
 
    #### Private interface ####################################################
322
 
 
323
 
    # The list of calls to scriptable calls.
324
 
    _calls = List(Instance(_ScriptCall))
325
 
 
326
 
    # The dictionary of bound names.  The value is the next numerical suffix
327
 
    # to use when the binding policy is 'auto'.
328
 
    _names = Dict
329
 
 
330
 
    # The dictionary of _BoundObject instances keyed by the name the object is
331
 
    # bound to.
332
 
    _namespace = Dict
333
 
 
334
 
    # The next sequential result number.
335
 
    _next_result_nr = Int
336
 
 
337
 
    # The results returned by previous scriptable calls.  The key is the id()
338
 
    # of the result object.  The value is a two element tuple of the sequential
339
 
    # result number (easier for the user to use than the id()) and the result
340
 
    # object itself.
341
 
    _results = Dict
342
 
 
343
 
    # The dictionary of _ScriptObject instances keyed by the object's id().
344
 
    _so_by_id = Dict
345
 
 
346
 
    # The dictionary of _ScriptObject instances keyed by the a weak reference
347
 
    # to the object.
348
 
    _so_by_ref = Dict
349
 
 
350
 
    # The date and time when the script was recorded.
351
 
    _when_started = Any
352
 
 
353
 
    ###########################################################################
354
 
    # 'IScriptManager' interface.
355
 
    ###########################################################################
356
 
 
357
 
    def bind(self, obj, name=None, bind_policy='unique', api=None,
358
 
            includes=None, excludes=None):
359
 
        """ Bind obj to name and make (by default) its public methods and
360
 
        traits (ie. those not beginning with an underscore) scriptable.  The
361
 
        default value of name is the type of obj with the first character
362
 
        forced to lower case.  name may be a dotted name (eg. 'abc.def.xyz').
363
 
 
364
 
        bind_policy determines what happens if the name is already bound.  If
365
 
        the policy is 'auto' then a numerical suffix will be added to the name,
366
 
        if necessary, to make it unique.  If the policy is 'unique' then an
367
 
        exception is raised.  If the policy is 'rebind' then the previous
368
 
        binding is discarded.  The default is 'unique'
369
 
 
370
 
        If api is given then it is a class, or a list of classes, that define
371
 
        the attributes that will be made scriptable.
372
 
 
373
 
        Otherwise if includes is given it is a list of names of attributes that
374
 
        will be made scriptable.
375
 
 
376
 
        Otherwise all the public attributes of scripted_type will be made
377
 
        scriptable except those in the excludes list.
378
 
        """
379
 
 
380
 
        # Register the object.
381
 
        self.new_object(obj, obj.__class__, name=name, bind_policy=bind_policy)
382
 
 
383
 
        # Make it scriptable.
384
 
        make_object_scriptable(obj, api=api, includes=includes,
385
 
                excludes=excludes)
386
 
 
387
 
    def bind_factory(self, factory, name, bind_policy='unique', api=None,
388
 
            includes=None, excludes=None):
389
 
        """ Bind factory to name.  This does the same as the bind() method
390
 
        except that it uses a factory that will be called later on to create
391
 
        the object only if the object is needed.
392
 
 
393
 
        See the documentation for bind() for a description of the remaining
394
 
        arguments.
395
 
        """
396
 
 
397
 
        name = self._unique_name(name, bind_policy)
398
 
        self._namespace[name] = _FactoryObject(name=name, factory=factory,
399
 
                api=api, includes=includes, excludes=excludes)
400
 
 
401
 
    def run(self, script):
402
 
        """ Run the given script, either a string or a file-like object.
403
 
        """
404
 
 
405
 
        # Initialise the namespace with all explicitly bound objects.
406
 
        nspace = LazyNamespace()
407
 
        for name, bo in self._namespace.iteritems():
408
 
            if bo.explicitly_bound:
409
 
                add_to_namespace(bo.obj, name, nspace)
410
 
 
411
 
        exec script in nspace
412
 
 
413
 
    def run_file(self, file_name):
414
 
        """ Run the given script file.
415
 
        """
416
 
 
417
 
        f = open(file_name)
418
 
        self.run(f)
419
 
        f.close()
420
 
 
421
 
    def start_recording(self):
422
 
        """ Start the recording of user actions.  The 'script' trait is cleared
423
 
        and all subsequent actions are added to 'script'.  The 'recording'
424
 
        trait is updated appropriately.
425
 
        """
426
 
 
427
 
        self._calls = []
428
 
        self._next_result_nr = 0
429
 
        self._results = {}
430
 
 
431
 
        self.recording = True
432
 
        self.script_updated = self
433
 
 
434
 
    def stop_recording(self):
435
 
        """ Stop the recording of user actions.  The 'recording' trait is
436
 
        updated appropriately.
437
 
        """
438
 
 
439
 
        self.recording = False
440
 
 
441
 
    ###########################################################################
442
 
    # 'ScriptManager' interface.
443
 
    ###########################################################################
444
 
 
445
 
    def record_method(self, func, args, kwargs):
446
 
        """ Record the call of a method of a ScriptableObject instance and
447
 
        return the result.  This is intended to be used only by the scriptable
448
 
        decorator.
449
 
        """
450
 
        if self.recording:
451
 
            # Record the arguments before the function has a chance to modify
452
 
            # them.
453
 
            srec = self._new_method(func, args, kwargs)
454
 
            result = func(*args, **kwargs)
455
 
            self._add_method(srec, result)
456
 
 
457
 
            self.script_updated = self
458
 
        else:
459
 
            result = func(*args, **kwargs)
460
 
 
461
 
        return result
462
 
 
463
 
    def record_trait_get(self, obj, name, result):
464
 
        """ Record the get of a trait of a scriptable object.  This is intended
465
 
        to be used only by the Scriptable trait getter.
466
 
        """
467
 
 
468
 
        if self.recording:
469
 
            side_effects = self._add_trait_get(obj, name, result)
470
 
 
471
 
            # Don't needlessly fire the event if there are no side effects.
472
 
            if side_effects:
473
 
                self.script_updated = self
474
 
 
475
 
    def record_trait_set(self, obj, name, value):
476
 
        """ Record the set of a trait of a scriptable object.  This is intended
477
 
        to be used only by the Scriptable trait getter.
478
 
        """
479
 
 
480
 
        if self.recording:
481
 
            self._add_trait_set(obj, name, value)
482
 
 
483
 
            self.script_updated = self
484
 
 
485
 
    def new_object(self, obj, scripted_type, args=None, kwargs=None, name=None,
486
 
            bind_policy='auto'):
487
 
        """ Register a scriptable object and the arguments used to create it.
488
 
        If no arguments were provided then assume the object is being
489
 
        explicitly bound.
490
 
        """
491
 
 
492
 
        # The name defaults to the type name.
493
 
        if not name:
494
 
            name = scripted_type.__name__
495
 
            name = name[0].lower() + name[1:]
496
 
 
497
 
        name = self._unique_name(name, bind_policy)
498
 
 
499
 
        obj_id = id(obj)
500
 
        obj_ref = weakref.ref(obj, self._gc_script_obj)
501
 
 
502
 
        so = _ScriptObject(name=name, obj_id=obj_id, obj_ref=obj_ref,
503
 
                scripted_type=scripted_type)
504
 
 
505
 
        # If we are told how to create the object then it must be implicitly
506
 
        # bound.
507
 
        if args is not None:
508
 
            # Convert each argument to its string representation if possible.
509
 
            # Doing this now avoids problems with mutable arguments.
510
 
            so.args = [self._scriptable_object_as_string(a) for a in args]
511
 
 
512
 
            for n, value in kwargs.iteritems():
513
 
                so.kwargs[n] = self._scriptable_object_as_string(value)
514
 
 
515
 
            so.explicitly_bound = False
516
 
 
517
 
        # Remember the scriptable object via the different access methods.
518
 
        self._so_by_id[obj_id] = so
519
 
        self._so_by_ref[obj_ref] = so
520
 
        self._namespace[name] = so
521
 
 
522
 
        # Note that if anything listening to this event doesn't use weak
523
 
        # references then the object will be kept alive.
524
 
        self.bind_event = BindEvent(name=name, obj=obj)
525
 
 
526
 
    @staticmethod
527
 
    def args_as_string_list(args, kwargs, so_needed=None):
528
 
        """ Return a complete argument list from sets of positional and keyword
529
 
        arguments.  Update the optional so_needed list for those arguments that
530
 
        refer to a scriptable object.
531
 
        """
532
 
 
533
 
        if so_needed is None:
534
 
            so_needed = []
535
 
 
536
 
        all_args = []
537
 
 
538
 
        for arg in args:
539
 
            s = ScriptManager.arg_as_string(arg, so_needed)
540
 
            all_args.append(s)
541
 
 
542
 
        for name, value in kwargs.iteritems():
543
 
            s = ScriptManager.arg_as_string(value, so_needed)
544
 
            all_args.append('%s=%s' % (name, s))
545
 
 
546
 
        return all_args
547
 
 
548
 
    @staticmethod
549
 
    def arg_as_string(arg, so_needed):
550
 
        """ Return the string representation of an argument.  Update the
551
 
        so_needed list if the argument refers to a scriptable object.  Any
552
 
        delayed conversion exception is handled here.
553
 
        """
554
 
 
555
 
        if isinstance(arg, Exception):
556
 
            raise arg
557
 
 
558
 
        if isinstance(arg, _ScriptObject):
559
 
            # Check it hasn't been unbound.
560
 
            if not arg.name:
561
 
                raise NameError("%s has been unbound but is needed by the script" % arg.obj_ref())
562
 
 
563
 
            # Add it to the needed list if it isn't already there.
564
 
            if arg not in so_needed:
565
 
                so_needed.append(arg)
566
 
 
567
 
            arg = arg.name
568
 
 
569
 
        return arg
570
 
 
571
 
    ###########################################################################
572
 
    # Private interface.
573
 
    ###########################################################################
574
 
 
575
 
    def _new_method(self, func, args, kwargs):
576
 
        """ Return an object that encapsulates a call to a scriptable method.
577
 
        _add_method() must be called to add it to the current script.
578
 
        """
579
 
 
580
 
        # Convert each argument to its string representation if possible.
581
 
        # Doing this now avoids problems with mutable arguments.
582
 
        nargs = [self._object_as_string(arg) for arg in args]
583
 
 
584
 
        if type(func) is types.FunctionType:
585
 
            so = None
586
 
        else:
587
 
            so = nargs[0]
588
 
            nargs = nargs[1:]
589
 
 
590
 
        nkwargs = {}
591
 
        for name, value in kwargs.iteritems():
592
 
            nkwargs[name] = self._object_as_string(value)
593
 
 
594
 
        return _ScriptMethod(name=func.func_name, so=so, args=nargs,
595
 
                kwargs=nkwargs)
596
 
 
597
 
    def _add_method(self, entry, result):
598
 
        """ Add a method call (returned by _new_method()), with it's associated
599
 
        result and ID, to the current script.
600
 
        """
601
 
 
602
 
        self._start_script()
603
 
 
604
 
        if result is not None:
605
 
            # Assume that a tuple represents multiple returned values - not
606
 
            # necessarily a valid assumption unless we make it a rule for
607
 
            # scriptable functions.
608
 
            if type(result) is type(()):
609
 
                for r in result:
610
 
                    self._save_result(r)
611
 
            else:
612
 
                self._save_result(result)
613
 
 
614
 
            entry.result = result
615
 
 
616
 
        self._calls.append(entry)
617
 
 
618
 
    def _add_trait_get(self, obj, name, result):
619
 
        """ Add a call to a trait getter, with it's associated result and ID,
620
 
        to the current script.  Return True if the get had side effects.
621
 
        """
622
 
 
623
 
        self._start_script()
624
 
 
625
 
        side_effects = obj.trait(name).has_side_effects
626
 
 
627
 
        if side_effects is None:
628
 
            side_effects = False
629
 
 
630
 
        so = self._object_as_string(obj)
631
 
 
632
 
        if result is not None:
633
 
            self._save_result(result)
634
 
 
635
 
        self._calls.append(_ScriptTraitGet(so=so, name=name, result=result,
636
 
                has_side_effects=side_effects))
637
 
 
638
 
        return side_effects
639
 
 
640
 
    def _add_trait_set(self, obj, name, value):
641
 
        """ Add a call to a trait setter, with it's associated value and ID,
642
 
        to the current script.
643
 
        """
644
 
 
645
 
        self._start_script()
646
 
 
647
 
        so = self._object_as_string(obj)
648
 
        value = self._object_as_string(value)
649
 
 
650
 
        self._calls.append(_ScriptTraitSet(so=so, name=name, value=value))
651
 
 
652
 
    def _unique_name(self, name, bind_policy):
653
 
        """ Return a name that is guaranteed to be unique according to the bind
654
 
        policy.
655
 
        """
656
 
 
657
 
        # See if the name is already is use.
658
 
        bo = self._namespace.get(name)
659
 
 
660
 
        if bo is None:
661
 
            self._names[name] = 1
662
 
        elif bind_policy == 'auto':
663
 
            suff = self._names[name]
664
 
            self._names[name] = suff + 1
665
 
 
666
 
            name = '%s%d' % (name, suff)
667
 
        elif bind_policy == 'rebind':
668
 
            self._unbind(bo)
669
 
        else:
670
 
            raise NameError("\"%s\" is already bound to a scriptable object" % name)
671
 
 
672
 
        return name
673
 
 
674
 
    def _unbind(self, bo):
675
 
        """Unbind the given bound object."""
676
 
 
677
 
        # Tell everybody it is no longer bound.  Don't bother if it is a
678
 
        # factory because the corresponding bound event wouldn't have been
679
 
        # fired.
680
 
        if not isinstance(bo, _FactoryObject):
681
 
            self.bind_event = BindEvent(name=bo.name, obj=None)
682
 
 
683
 
        # Forget about it.
684
 
        del self._namespace[bo.name]
685
 
        bo.name = ''
686
 
 
687
 
    @staticmethod
688
 
    def _gc_script_obj(obj_ref):
689
 
        """ The callback invoked when a scriptable object is garbage collected.
690
 
        """
691
 
 
692
 
        # Avoid recursive imports.
693
 
        from package_globals import get_script_manager
694
 
 
695
 
        sm = get_script_manager()
696
 
        so = sm._so_by_ref[obj_ref]
697
 
 
698
 
        if so.name:
699
 
            sm._unbind(so)
700
 
 
701
 
        del sm._so_by_id[so.obj_id]
702
 
        del sm._so_by_ref[so.obj_ref]
703
 
 
704
 
    def _start_script(self):
705
 
        """ Save when a script recording is started. """
706
 
 
707
 
        if len(self._calls) == 0:
708
 
            self._when_started = datetime.datetime.now().strftime('%c')
709
 
 
710
 
    def _object_as_string(self, obj):
711
 
        """ Convert an object to a string as it will appear in a script.  An
712
 
        exception may be returned (not raised) if there was an error in the
713
 
        conversion.
714
 
        """
715
 
 
716
 
        obj_id = id(obj)
717
 
 
718
 
        # See if the argument is the result of a previous call.
719
 
        nr, _ = self._results.get(obj_id, (None, None))
720
 
 
721
 
        if nr is not None:
722
 
            if nr < 0:
723
 
                nr = self._next_result_nr
724
 
                self._next_result_nr += 1
725
 
 
726
 
                # Key on the ID of the argument (which is hashable) rather than
727
 
                # the argument itself (which might not be).
728
 
                self._results[obj_id] = (nr, obj)
729
 
 
730
 
            return "r%d" % nr
731
 
 
732
 
        return self._scriptable_object_as_string(obj)
733
 
 
734
 
    def _scriptable_object_as_string(self, obj):
735
 
        """ Convert an object to a string as it will appear in a script.  An
736
 
        exception may be returned (not raised) if there was an error in the
737
 
        conversion.
738
 
        """
739
 
 
740
 
        obj_id = id(obj)
741
 
 
742
 
        # If it is a scriptable object we return the object and convert it to a
743
 
        # string later when we know it is really needed.
744
 
        so = self._so_by_id.get(obj_id)
745
 
 
746
 
        if so is not None:
747
 
            return so
748
 
 
749
 
        # Use the repr result if it doesn't appear to be the generic response,
750
 
        # ie. it doesn't contain its own address as a hex string.
751
 
        s = repr(obj)
752
 
 
753
 
        if hex(obj_id) not in s:
754
 
            return s
755
 
 
756
 
        # We don't know how to represent the argument as a string.  This is
757
 
        # most likely because an appropriate __init__ hasn't been made
758
 
        # scriptable.  We don't raise an exception until the user decides to
759
 
        # convert the calls to a script.
760
 
        return ValueError("unable to create a script representation of %s" % obj)
761
 
 
762
 
    def _save_result(self, result):
763
 
        """ Save the result of a call to a scriptable method so that it can be
764
 
        recognised later.
765
 
        """
766
 
 
767
 
        if id(result) not in self._results:
768
 
            self._results[id(result)] = (-1, result)
769
 
 
770
 
    def _get_script(self):
771
 
        """ Convert the current list of calls to a script. """
772
 
 
773
 
        # Handle the trivial case.
774
 
        if len(self._calls) == 0:
775
 
            return ""
776
 
 
777
 
        # Generate the header.
778
 
        header = "# Script generated %s" % self._when_started
779
 
 
780
 
        # Generate the calls.
781
 
        so_needed = []
782
 
        calls = []
783
 
 
784
 
        for call in self._calls:
785
 
            s = call.as_str(self, so_needed)
786
 
 
787
 
            if s:
788
 
                calls.append(s)
789
 
 
790
 
        calls = "\n".join(calls)
791
 
 
792
 
        # Generate the scriptable object constructors.
793
 
        types_needed = []
794
 
        ctors = []
795
 
 
796
 
        for so in so_needed:
797
 
            if so.explicitly_bound:
798
 
                continue
799
 
 
800
 
            so_type = so.scripted_type
801
 
            args = self.args_as_string_list(so.args, so.kwargs)
802
 
 
803
 
            ctors.append("%s = %s(%s)" % (so.name, so_type.__name__, ", ".join(args)))
804
 
 
805
 
            # See if a new import is needed.
806
 
            if so_type not in types_needed:
807
 
                types_needed.append(so_type)
808
 
 
809
 
        ctors = "\n".join(ctors)
810
 
 
811
 
        # Generate the import statements.
812
 
        imports = []
813
 
 
814
 
        for so_type in types_needed:
815
 
            imports.append("from %s import %s" % (so_type.__module__, so_type.__name__))
816
 
 
817
 
        imports = "\n".join(imports)
818
 
 
819
 
        return "\n\n".join([header, imports, ctors, calls]) + "\n"