diff --git a/README.rst b/README.rst index 101a86ded..821899da0 100644 --- a/README.rst +++ b/README.rst @@ -19,7 +19,6 @@ The apptools project includes a set of packages that Enthought has found useful in creating a number of applications. They implement functionality that is commonly needed by many applications -- **apptools.appscripting**: Framework for scripting applications. - **apptools.help**: Provides a plugin for displaying documents and examples and running demos in Envisage Workbench applications. - **apptools.io**: Provides an abstraction for files and folders in a file diff --git a/apptools/appscripting/__init__.py b/apptools/appscripting/__init__.py deleted file mode 100644 index 1d62a19cf..000000000 --- a/apptools/appscripting/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2005-2011, Enthought, Inc. -# All rights reserved. -# -# This software is provided without warranty under the terms of the BSD -# license included in LICENSE.txt and may be redistributed only -# under the conditions described in the aforementioned license. The license -# is also available online at http://www.enthought.com/licenses/BSD.txt -# Thanks for using Enthought open source! -# -# Author: Enthought, Inc. -""" Application scripting framework, part of the AppTools project - of the Enthought Tool Suite. -""" diff --git a/apptools/appscripting/action/__init__.py b/apptools/appscripting/action/__init__.py deleted file mode 100644 index bc54012db..000000000 --- a/apptools/appscripting/action/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2005-2011, Enthought, Inc. -# All rights reserved. -# -# This software is provided without warranty under the terms of the BSD -# license included in LICENSE.txt and may be redistributed only -# under the conditions described in the aforementioned license. The license -# is also available online at http://www.enthought.com/licenses/BSD.txt -# Thanks for using Enthought open source! -# -# Author: Enthought, Inc. diff --git a/apptools/appscripting/action/api.py b/apptools/appscripting/action/api.py deleted file mode 100644 index 21662f389..000000000 --- a/apptools/appscripting/action/api.py +++ /dev/null @@ -1,16 +0,0 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2008, Riverbank Computing Limited -# All rights reserved. -# -# This software is provided without warranty under the terms of the BSD -# license included in enthought/LICENSE.txt and may be redistributed only -# under the conditions described in the aforementioned license. The license -# is also available online at http://www.enthought.com/licenses/BSD.txt -# Thanks for using Enthought open source! -# -# Author: Riverbank Computing Limited -# Description: -#------------------------------------------------------------------------------ - -from .start_recording_action import StartRecordingAction -from .stop_recording_action import StopRecordingAction diff --git a/apptools/appscripting/action/start_recording_action.py b/apptools/appscripting/action/start_recording_action.py deleted file mode 100644 index e906decfa..000000000 --- a/apptools/appscripting/action/start_recording_action.py +++ /dev/null @@ -1,39 +0,0 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2008, Riverbank Computing Limited -# All rights reserved. -# -# This software is provided without warranty under the terms of the BSD -# license included in enthought/LICENSE.txt and may be redistributed only -# under the conditions described in the aforementioned license. The license -# is also available online at http://www.enthought.com/licenses/BSD.txt -# Thanks for using Enthought open source! -# -# Author: Riverbank Computing Limited -# Description: -#------------------------------------------------------------------------------ - - -# Enthought library imports. -from pyface.action.api import Action -from traits.api import Unicode - -# Local imports. -from apptools.appscripting.package_globals import get_script_manager - - -class StartRecordingAction(Action): - """An action that starts the recording of changes to scriptable objects to - a script.""" - - #### 'Action' interface ################################################### - - name = Unicode("Start recording") - - ########################################################################### - # 'Action' interface. - ########################################################################### - - def perform(self, event): - """ Perform the action. """ - - get_script_manager().start_recording() diff --git a/apptools/appscripting/action/stop_recording_action.py b/apptools/appscripting/action/stop_recording_action.py deleted file mode 100644 index adf7d17b8..000000000 --- a/apptools/appscripting/action/stop_recording_action.py +++ /dev/null @@ -1,61 +0,0 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2008, Riverbank Computing Limited -# All rights reserved. -# -# This software is provided without warranty under the terms of the BSD -# license included in enthought/LICENSE.txt and may be redistributed only -# under the conditions described in the aforementioned license. The license -# is also available online at http://www.enthought.com/licenses/BSD.txt -# Thanks for using Enthought open source! -# -# Author: Riverbank Computing Limited -# Description: -#------------------------------------------------------------------------------ - - -# Enthought library imports. -from pyface.action.api import Action -from traits.api import Bool, Unicode - -# Local imports. -from apptools.appscripting.package_globals import get_script_manager - - -class StopRecordingAction(Action): - """An action that stops the recording of changes to scriptable objects to a - script.""" - - #### 'Action' interface ################################################### - - enabled = Bool(False) - - name = Unicode("Stop recording") - - ########################################################################### - # 'object' interface. - ########################################################################### - - def __init__(self, **traits): - """ Initialise the instance. """ - - super(StopRecordingAction, self).__init__(**traits) - - get_script_manager().on_trait_change(self._on_recording, 'recording') - - ########################################################################### - # 'Action' interface. - ########################################################################### - - def perform(self, event): - """ Perform the action. """ - - get_script_manager().stop_recording() - - ########################################################################### - # Private interface. - ########################################################################### - - def _on_recording(self, new): - """ Handle a change to the script manager's recording trait. """ - - self.enabled = new diff --git a/apptools/appscripting/api.py b/apptools/appscripting/api.py deleted file mode 100644 index b4447a055..000000000 --- a/apptools/appscripting/api.py +++ /dev/null @@ -1,20 +0,0 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2008, Riverbank Computing Limited -# All rights reserved. -# -# This software is provided without warranty under the terms of the BSD -# license included in enthought/LICENSE.txt and may be redistributed only -# under the conditions described in the aforementioned license. The license -# is also available online at http://www.enthought.com/licenses/BSD.txt -# Thanks for using Enthought open source! -# -# Author: Riverbank Computing Limited -# Description: -#------------------------------------------------------------------------------ - -from .scriptable_type import create_scriptable_type, make_object_scriptable -from .i_bind_event import IBindEvent -from .i_script_manager import IScriptManager -from .package_globals import get_script_manager, set_script_manager -from .script_manager import ScriptManager -from .scriptable import scriptable, Scriptable diff --git a/apptools/appscripting/bind_event.py b/apptools/appscripting/bind_event.py deleted file mode 100644 index 2c289bf7d..000000000 --- a/apptools/appscripting/bind_event.py +++ /dev/null @@ -1,36 +0,0 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2008, Riverbank Computing Limited -# All rights reserved. -# -# This software is provided without warranty under the terms of the BSD -# license included in enthought/LICENSE.txt and may be redistributed only -# under the conditions described in the aforementioned license. The license -# is also available online at http://www.enthought.com/licenses/BSD.txt -# Thanks for using Enthought open source! -# -# Author: Riverbank Computing Limited -# Description: -#------------------------------------------------------------------------------ - - -# Enthought library imports. -from traits.api import Any, HasTraits, provides, Str - -# Local imports. -from .i_bind_event import IBindEvent - - -@provides(IBindEvent) -class BindEvent(HasTraits): - """The default implementation of the bind event interface.""" - - - - #### 'IBindEvent' interface ############################################### - - # This is the name being bound or unbound. - name = Str - - # This is the object being bound to the name. It is None if the name is - # being unbound. - obj = Any diff --git a/apptools/appscripting/i_bind_event.py b/apptools/appscripting/i_bind_event.py deleted file mode 100644 index b45553802..000000000 --- a/apptools/appscripting/i_bind_event.py +++ /dev/null @@ -1,32 +0,0 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2008, Riverbank Computing Limited -# All rights reserved. -# -# This software is provided without warranty under the terms of the BSD -# license included in enthought/LICENSE.txt and may be redistributed only -# under the conditions described in the aforementioned license. The license -# is also available online at http://www.enthought.com/licenses/BSD.txt -# Thanks for using Enthought open source! -# -# Author: Riverbank Computing Limited -# Description: -#------------------------------------------------------------------------------ - - -# Enthought library imports. -from traits.api import Any, Interface, Str - - -class IBindEvent(Interface): - """The bind event interface. A corresponding instance is the value of the - event fired when a scriptable object is bound or unbound to or from a name. - """ - - #### 'IBindEvent' interface ############################################### - - # This is the name being bound or unbound. - name = Str - - # This is the object being bound to the name. It is None if the name is - # being unbound. - obj = Any diff --git a/apptools/appscripting/i_script_manager.py b/apptools/appscripting/i_script_manager.py deleted file mode 100644 index cf029ba09..000000000 --- a/apptools/appscripting/i_script_manager.py +++ /dev/null @@ -1,106 +0,0 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2008, Riverbank Computing Limited -# All rights reserved. -# -# This software is provided without warranty under the terms of the BSD -# license included in enthought/LICENSE.txt and may be redistributed only -# under the conditions described in the aforementioned license. The license -# is also available online at http://www.enthought.com/licenses/BSD.txt -# Thanks for using Enthought open source! -# -# Author: Riverbank Computing Limited -# Description: -#------------------------------------------------------------------------------ - - -# Enthought library imports. -from traits.api import Bool, Event, Instance, Interface, Unicode - -# Local imports. -from .i_bind_event import IBindEvent - - -class IScriptManager(Interface): - """ The script manager interface. A script manager is responsible for the - recording of appropriately annotated user actions as scripts that can be - executed without user intervention at a later time. Typically an - application would have a single script manager. - """ - - #### 'IScriptManager' interface ########################################### - - # This event is fired whenever a scriptable object is bound or unbound. It - # is intended to be used by an interactive Python shell to give the - # advanced user access to the scriptable objects. If an object is created - # via a factory then the event is fired when the factory is called, and not - # when the factory is bound. - bind_event = Event(IBindEvent) - - # This is set if user actions are being recorded as a script. It is - # maintained by the script manager. - recording = Bool(False) - - # This is the text of the script currently being recorded (or the last - # recorded script if none is currently being recorded). It is updated - # automatically as the user performs actions. - script = Unicode - - # This event is fired when the recorded script changes. The value of the - # event will be the ScriptManager instance. - script_updated = Event(Instance('apptools.appscripting.api.IScriptManager')) - - ########################################################################### - # 'IScriptManager' interface. - ########################################################################### - - def bind(self, obj, name=None, bind_policy='unique', api=None, - includes=None, excludes=None): - """ Bind obj to name and make (by default) its public methods and - traits (ie. those not beginning with an underscore) scriptable. The - default value of name is the type of obj with the first character - forced to lower case. - - bind_policy determines what happens if the name is already bound. If - the policy is 'auto' then a numerical suffix will be added to the name, - if necessary, to make it unique. If the policy is 'unique' then an - exception is raised. If the policy is 'rebind' then the previous - binding is discarded. The default is 'unique' - - If api is given then it is a class, or a list of classes, that define - the attributes that will be made scriptable. - - Otherwise if includes is given it is a list of names of attributes that - will be made scriptable. - - Otherwise all the public attributes of scripted_type will be made - scriptable except those in the excludes list. - """ - - def bind_factory(self, factory, name, bind_policy='unique', api=None, - includes=None, excludes=None): - """ Bind factory to name. This does the same as the bind() method - except that it uses a factory that will be called later on to create - the object only if the object is needed. - - See the documentation for bind() for a description of the remaining - arguments. - """ - - def run(self, script): - """ Run the given script, either a string or a file-like object. - """ - - def run_file(self, file_name): - """ Run the given script file. - """ - - def start_recording(self): - """ Start the recording of user actions. The 'script' trait is cleared - and all subsequent actions are added to 'script'. The 'recording' - trait is updated appropriately. - """ - - def stop_recording(self): - """ Stop the recording of user actions. The 'recording' trait is - updated appropriately. - """ diff --git a/apptools/appscripting/lazy_namespace.py b/apptools/appscripting/lazy_namespace.py deleted file mode 100644 index af04c9400..000000000 --- a/apptools/appscripting/lazy_namespace.py +++ /dev/null @@ -1,123 +0,0 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2008, Riverbank Computing Limited -# All rights reserved. -# -# This software is provided without warranty under the terms of the BSD -# license included in enthought/LICENSE.txt and may be redistributed only -# under the conditions described in the aforementioned license. The license -# is also available online at http://www.enthought.com/licenses/BSD.txt -# Thanks for using Enthought open source! -# -# Author: Riverbank Computing Limited -# Description: -#------------------------------------------------------------------------------ - - -# Enthought library imports. -from traits.api import Any, Callable, HasTraits - -# Local imports. -from .bind_event import BindEvent -from .package_globals import get_script_manager -from .scriptable_type import make_object_scriptable - - -class FactoryWrapper(HasTraits): - """The FactoryWrapper class wraps a factory for an object.""" - - #### 'FactoryWrapper' interface ########################################### - - # The optional object that defines the scripting API. - api = Any - - # The object factory. - factory = Callable - - # The optional attribute include list. - includes = Any - - # The optional attribute exclude list. - excludes = Any - - ########################################################################### - # 'FactoryWrapper' interface. - ########################################################################### - - def create_scriptable_object(self, name): - """Invoke the factory to create the object then make it scriptable.""" - - obj = self.factory() - - sm = get_script_manager() - sm.bind_event = BindEvent(name=name, obj=obj) - - make_object_scriptable(obj, self.api, self.includes, self.excludes) - - return obj - - -class _LazyNode(object): - """The _LazyNode class implements a node in a lazy namespace that will - automatically invoke a factory if one is referenced.""" - - def __getattribute__(self, name): - - value = super(_LazyNode, self).__getattribute__(name) - - if isinstance(value, FactoryWrapper): - value = value.create_scriptable_object(name) - setattr(self, name, value) - - return value - - -class LazyNamespace(dict): - """The LazyNamespace class implements a lazy namespace that will - automatically invoke a factory if one is referenced.""" - - def __getitem__(self, name): - - value = super(LazyNamespace, self).__getitem__(name) - - if isinstance(value, FactoryWrapper): - value = value.create_scriptable_object(name) - self[name] = value - - return value - - -def add_to_namespace(so, name, nspace): - """Add a named scriptable object (or a factory for one) to a lazy - namespace. If a name is a dotted name then intermediary nodes in the - namespace are created as required.""" - - def save_obj(obj, name): - - if isinstance(nspace, LazyNamespace): - nspace[name] = obj - else: - setattr(nspace, name, obj) - - parts = name.split('.') - - for part in parts[:-1]: - if isinstance(nspace, LazyNamespace): - try: - next_nspace = nspace[part] - except KeyError: - next_nspace = _LazyNode() - elif isinstance(nspace, _LazyNode): - try: - next_nspace = nspace[part] - except KeyError: - next_nspace = _LazyNode() - else: - raise NameError - - save_obj(next_nspace, part) - nspace = next_nspace - - if not isinstance(nspace, (LazyNamespace, _LazyNode)): - raise NameError - - save_obj(so, parts[-1]) diff --git a/apptools/appscripting/package_globals.py b/apptools/appscripting/package_globals.py deleted file mode 100644 index 635a8fc5c..000000000 --- a/apptools/appscripting/package_globals.py +++ /dev/null @@ -1,39 +0,0 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2008, Riverbank Computing Limited -# All rights reserved. -# -# This software is provided without warranty under the terms of the BSD -# license included in enthought/LICENSE.txt and may be redistributed only -# under the conditions described in the aforementioned license. The license -# is also available online at http://www.enthought.com/licenses/BSD.txt -# Thanks for using Enthought open source! -# -# Author: Riverbank Computing Limited -# Description: -#------------------------------------------------------------------------------ - - -# The script manager. -_script_manager = None - - -def get_script_manager(): - """Return the IScriptManager implementation, creating a ScriptManager - instance if no other implementation has been set.""" - - global _script_manager - - if _script_manager is None: - from .script_manager import ScriptManager - - _script_manager = ScriptManager() - - return _script_manager - - -def set_script_manager(script_manager): - """Set the IScriptManager implementation to use.""" - - global _script_manager - - _script_manager = script_manager diff --git a/apptools/appscripting/script_manager.py b/apptools/appscripting/script_manager.py deleted file mode 100644 index 4b83f1d99..000000000 --- a/apptools/appscripting/script_manager.py +++ /dev/null @@ -1,820 +0,0 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2008, Riverbank Computing Limited -# All rights reserved. -# -# This software is provided without warranty under the terms of the BSD -# license included in enthought/LICENSE.txt and may be redistributed only -# under the conditions described in the aforementioned license. The license -# is also available online at http://www.enthought.com/licenses/BSD.txt -# Thanks for using Enthought open source! -# -# Author: Riverbank Computing Limited -# Description: -#------------------------------------------------------------------------------ - - -# Standard library imports. -import datetime -import types -import weakref - -# Enthought library imports. -from traits.api import Any, Bool, Callable, Dict, Event, HasTraits, \ - Instance, Int, List, Property, provides, Str, Unicode - -# Local imports. -from .bind_event import BindEvent -from .i_bind_event import IBindEvent -from .i_script_manager import IScriptManager -from .lazy_namespace import add_to_namespace, FactoryWrapper, LazyNamespace -from .scriptable_type import make_object_scriptable - - -@provides(IScriptManager) -class _BoundObject(HasTraits): - """The base class for any object that can be bound to a name.""" - - #### '_BoundObject' interface ############################################# - - # Set if the object was explicitly bound. - explicitly_bound = Bool(True) - - # The name the object is bound to. - name = Str - - # The object being bound. - obj = Any - - -class _ScriptObject(_BoundObject): - """The _ScriptObject class encapsulates a scriptable object.""" - - #### '_BoundObject' interface ############################################# - - # The object being bound. - obj = Property - - #### '_ScriptObject' interface ############################################ - - # The positional arguments passed to __init__ after being converted to - # strings. A particular argument may be an exception if it couldn't be - # converted. - args = List - - # The keyword arguments passed to __init__ after being converted to - # strings. A particular argument may be an exception if it couldn't be - # converted. - kwargs = Dict - - # The id of the object. - obj_id = Int - - # A weak reference to the object. - obj_ref = Any - - # The type of the scriptable object. - scripted_type = Any - - ########################################################################### - # Private interface. - ########################################################################### - - def _get_obj(self): - """The property getter.""" - - return self.obj_ref() - - -class _ScriptCall(HasTraits): - """ The _ScriptCall class is the base class for all script calls. """ - - #### '_ScriptCall' interface ############################################## - - # The name of the call. - name = Str - - # The scriptable object. - so = Any - - ########################################################################### - # '_ScriptCall' interface. - ########################################################################### - - def as_str(self, sm, so_needed): - """ Return the string equivalent of the call, updated the list of - needed scriptable objects if required. - """ - - raise NotImplementedError - - -class _ScriptTraitGet(_ScriptCall): - """ The _ScriptTraitGet class encapsulates a single call to the get of a - scriptable trait. - """ - - #### '_ScriptTraitGet' interface ########################################## - - # Set if the getter has side effects. - has_side_effects = Bool(False) - - # The result of the get. Keeping a reference to it means that the memory - # can't get reused. - result = Any - - ########################################################################### - # '_ScriptCall' interface. - ########################################################################### - - def as_str(self, sm, so_needed): - """ Return the string equivalent of the call, updated the list of - needed scriptable objects if required. - """ - - # Ignore if it is no longer bound. - if not self.so.name: - return None - - if self.result is None: - rstr = "" - else: - nr, _ = sm._results[id(self.result)] - - if nr >= 0: - rstr = "r%d = " % nr - else: - rstr = "" - - # Unless getter has side effects, if the result is not needed then - # don't bother including it in the script. - if not self.has_side_effects and rstr == "": - return None - - so = sm.arg_as_string(self.so, so_needed) - - return "%s%s.%s" % (rstr, so, self.name) - - -class _ScriptTraitSet(_ScriptCall): - """ The _ScriptTraitSet class encapsulates a single call to the set of a - scriptable trait. - """ - - #### '_ScriptTraitSet' interface ########################################## - - # The value the trait is set to. - value = Any - - ########################################################################### - # '_ScriptCall' interface. - ########################################################################### - - def as_str(self, sm, so_needed): - """ Return the string equivalent of the call, updated the list of - needed scriptable objects if required. - """ - - # Ignore if it is no longer bound. - if not self.so.name: - return None - - so = sm.arg_as_string(self.so, so_needed) - value = sm.arg_as_string(self.value, so_needed) - - return "%s.%s = %s" % (so, self.name, value) - - -class _ScriptMethod(_ScriptCall): - """ The _ScriptMethod class encapsulates a single call to a scriptable - method. - """ - - #### '_ScriptMethod' interface ############################################ - - # The positional arguments passed to the method after being converted to - # strings. A particular argument may be an exception if it couldn't be - # converted. - args = List - - # The keyword arguments passed to the method after being converted to - # strings. A particular argument may be an exception if it couldn't be - # converted. - kwargs = Dict - - # The result of the method call. Keeping a reference to it means that the - # memory can't get reused. - result = Any - - ########################################################################### - # '_ScriptCall' interface. - ########################################################################### - - def as_str(self, sm, so_needed): - """ Return the string equivalent of the call, updated the list of - needed scriptable objects if required. - """ - - # Ignore if it is no longer bound. - if self.so and not self.so.name: - return None - - if self.result is None: - rstr = "" - elif type(self.result) is type(()): - rlist = [] - needed = False - - for r in self.result: - nr, _ = sm._results[id(r)] - - if nr >= 0: - rlist.append("r%d" % nr) - needed = True - else: - rlist.append("_") - - if needed: - rstr = ", ".join(rlist) + " = " - else: - rstr = "" - else: - nr, _ = sm._results[id(self.result)] - - if nr >= 0: - rstr = "r%d = " % nr - else: - rstr = "" - - if self.so: - so = sm.arg_as_string(self.so, so_needed) + '.' - else: - so = '' - - args = sm.args_as_string_list(self.args, self.kwargs, so_needed) - - return "%s%s%s(%s)" % (rstr, so, self.name, ", ".join(args)) - - -class _FactoryObject(_BoundObject): - """ The _FactoryObject class wraps a factory that lazily creates - scriptable objects. - """ - - #### '_BoundObject' interface ############################################# - - # The object being bound. - obj = Property - - #### '_FactoryObject' interface ########################################### - - # The optional object that defines the scripting API. - api = Any - - # The scriptable object factory. - factory = Callable - - # The optional attribute include list. - includes = Any - - # The optional attribute exclude list. - excludes = Any - - ########################################################################### - # Private interface. - ########################################################################### - - def _get_obj(self): - """The property getter.""" - - return FactoryWrapper(factory=self.factory, api=self.api, - includes=self.includes, excludes=self.excludes) - - -class ScriptManager(HasTraits): - """ The ScriptManager class is the default implementation of - IScriptManager. - """ - - - - #### 'IScriptManager' interface ########################################### - - # This event is fired whenever a scriptable object is bound or unbound. It - # is intended to be used by an interactive Python shell to give the - # advanced user access to the scriptable objects. If an object is created - # via a factory then the event is fired when the factory is called, and not - # when the factory is bound. - bind_event = Event(IBindEvent) - - # This is set if user actions are being recorded as a script. It is - # maintained by the script manager. - recording = Bool(False) - - # This is the text of the script currently being recorded (or the last - # recorded script if none is currently being recorded). It is updated - # automatically as the user performs actions. - script = Property(Unicode) - - # This event is fired when the recorded script changes. The value of the - # event will be the ScriptManager instance. - script_updated = Event(IScriptManager) - - #### Private interface #################################################### - - # The list of calls to scriptable calls. - _calls = List(Instance(_ScriptCall)) - - # The dictionary of bound names. The value is the next numerical suffix - # to use when the binding policy is 'auto'. - _names = Dict - - # The dictionary of _BoundObject instances keyed by the name the object is - # bound to. - _namespace = Dict - - # The next sequential result number. - _next_result_nr = Int - - # The results returned by previous scriptable calls. The key is the id() - # of the result object. The value is a two element tuple of the sequential - # result number (easier for the user to use than the id()) and the result - # object itself. - _results = Dict - - # The dictionary of _ScriptObject instances keyed by the object's id(). - _so_by_id = Dict - - # The dictionary of _ScriptObject instances keyed by the a weak reference - # to the object. - _so_by_ref = Dict - - # The date and time when the script was recorded. - _when_started = Any - - ########################################################################### - # 'IScriptManager' interface. - ########################################################################### - - def bind(self, obj, name=None, bind_policy='unique', api=None, - includes=None, excludes=None): - """ Bind obj to name and make (by default) its public methods and - traits (ie. those not beginning with an underscore) scriptable. The - default value of name is the type of obj with the first character - forced to lower case. name may be a dotted name (eg. 'abc.def.xyz'). - - bind_policy determines what happens if the name is already bound. If - the policy is 'auto' then a numerical suffix will be added to the name, - if necessary, to make it unique. If the policy is 'unique' then an - exception is raised. If the policy is 'rebind' then the previous - binding is discarded. The default is 'unique' - - If api is given then it is a class, or a list of classes, that define - the attributes that will be made scriptable. - - Otherwise if includes is given it is a list of names of attributes that - will be made scriptable. - - Otherwise all the public attributes of scripted_type will be made - scriptable except those in the excludes list. - """ - - # Register the object. - self.new_object(obj, obj.__class__, name=name, bind_policy=bind_policy) - - # Make it scriptable. - make_object_scriptable(obj, api=api, includes=includes, - excludes=excludes) - - def bind_factory(self, factory, name, bind_policy='unique', api=None, - includes=None, excludes=None): - """ Bind factory to name. This does the same as the bind() method - except that it uses a factory that will be called later on to create - the object only if the object is needed. - - See the documentation for bind() for a description of the remaining - arguments. - """ - - name = self._unique_name(name, bind_policy) - self._namespace[name] = _FactoryObject(name=name, factory=factory, - api=api, includes=includes, excludes=excludes) - - def run(self, script): - """ Run the given script, either a string or a file-like object. - """ - - # Initialise the namespace with all explicitly bound objects. - nspace = LazyNamespace() - for name, bo in self._namespace.items(): - if bo.explicitly_bound: - add_to_namespace(bo.obj, name, nspace) - - exec(script, nspace) - - def run_file(self, file_name): - """ Run the given script file. - """ - - f = open(file_name) - self.run(f) - f.close() - - def start_recording(self): - """ Start the recording of user actions. The 'script' trait is cleared - and all subsequent actions are added to 'script'. The 'recording' - trait is updated appropriately. - """ - - self._calls = [] - self._next_result_nr = 0 - self._results = {} - - self.recording = True - self.script_updated = self - - def stop_recording(self): - """ Stop the recording of user actions. The 'recording' trait is - updated appropriately. - """ - - self.recording = False - - ########################################################################### - # 'ScriptManager' interface. - ########################################################################### - - def record_method(self, func, args, kwargs): - """ Record the call of a method of a ScriptableObject instance and - return the result. This is intended to be used only by the scriptable - decorator. - """ - if self.recording: - # Record the arguments before the function has a chance to modify - # them. - srec = self._new_method(func, args, kwargs) - result = func(*args, **kwargs) - self._add_method(srec, result) - - self.script_updated = self - else: - result = func(*args, **kwargs) - - return result - - def record_trait_get(self, obj, name, result): - """ Record the get of a trait of a scriptable object. This is intended - to be used only by the Scriptable trait getter. - """ - - if self.recording: - side_effects = self._add_trait_get(obj, name, result) - - # Don't needlessly fire the event if there are no side effects. - if side_effects: - self.script_updated = self - - def record_trait_set(self, obj, name, value): - """ Record the set of a trait of a scriptable object. This is intended - to be used only by the Scriptable trait getter. - """ - - if self.recording: - self._add_trait_set(obj, name, value) - - self.script_updated = self - - def new_object(self, obj, scripted_type, args=None, kwargs=None, name=None, - bind_policy='auto'): - """ Register a scriptable object and the arguments used to create it. - If no arguments were provided then assume the object is being - explicitly bound. - """ - - # The name defaults to the type name. - if not name: - name = scripted_type.__name__ - name = name[0].lower() + name[1:] - - name = self._unique_name(name, bind_policy) - - obj_id = id(obj) - obj_ref = weakref.ref(obj, self._gc_script_obj) - - so = _ScriptObject(name=name, obj_id=obj_id, obj_ref=obj_ref, - scripted_type=scripted_type) - - # If we are told how to create the object then it must be implicitly - # bound. - if args is not None: - # Convert each argument to its string representation if possible. - # Doing this now avoids problems with mutable arguments. - so.args = [self._scriptable_object_as_string(a) for a in args] - - for n, value in kwargs.items(): - so.kwargs[n] = self._scriptable_object_as_string(value) - - so.explicitly_bound = False - - # Remember the scriptable object via the different access methods. - self._so_by_id[obj_id] = so - self._so_by_ref[obj_ref] = so - self._namespace[name] = so - - # Note that if anything listening to this event doesn't use weak - # references then the object will be kept alive. - self.bind_event = BindEvent(name=name, obj=obj) - - @staticmethod - def args_as_string_list(args, kwargs, so_needed=None): - """ Return a complete argument list from sets of positional and keyword - arguments. Update the optional so_needed list for those arguments that - refer to a scriptable object. - """ - - if so_needed is None: - so_needed = [] - - all_args = [] - - for arg in args: - s = ScriptManager.arg_as_string(arg, so_needed) - all_args.append(s) - - for name, value in kwargs.items(): - s = ScriptManager.arg_as_string(value, so_needed) - all_args.append('%s=%s' % (name, s)) - - return all_args - - @staticmethod - def arg_as_string(arg, so_needed): - """ Return the string representation of an argument. Update the - so_needed list if the argument refers to a scriptable object. Any - delayed conversion exception is handled here. - """ - - if isinstance(arg, Exception): - raise arg - - if isinstance(arg, _ScriptObject): - # Check it hasn't been unbound. - if not arg.name: - raise NameError("%s has been unbound but is needed by the script" % arg.obj_ref()) - - # Add it to the needed list if it isn't already there. - if arg not in so_needed: - so_needed.append(arg) - - arg = arg.name - - return arg - - ########################################################################### - # Private interface. - ########################################################################### - - def _new_method(self, func, args, kwargs): - """ Return an object that encapsulates a call to a scriptable method. - _add_method() must be called to add it to the current script. - """ - - # Convert each argument to its string representation if possible. - # Doing this now avoids problems with mutable arguments. - nargs = [self._object_as_string(arg) for arg in args] - - if type(func) is types.FunctionType: - so = None - else: - so = nargs[0] - nargs = nargs[1:] - - nkwargs = {} - for name, value in kwargs.items(): - nkwargs[name] = self._object_as_string(value) - - return _ScriptMethod(name=func.__name__, so=so, args=nargs, - kwargs=nkwargs) - - def _add_method(self, entry, result): - """ Add a method call (returned by _new_method()), with it's associated - result and ID, to the current script. - """ - - self._start_script() - - if result is not None: - # Assume that a tuple represents multiple returned values - not - # necessarily a valid assumption unless we make it a rule for - # scriptable functions. - if type(result) is type(()): - for r in result: - self._save_result(r) - else: - self._save_result(result) - - entry.result = result - - self._calls.append(entry) - - def _add_trait_get(self, obj, name, result): - """ Add a call to a trait getter, with it's associated result and ID, - to the current script. Return True if the get had side effects. - """ - - self._start_script() - - side_effects = obj.trait(name).has_side_effects - - if side_effects is None: - side_effects = False - - so = self._object_as_string(obj) - - if result is not None: - self._save_result(result) - - self._calls.append(_ScriptTraitGet(so=so, name=name, result=result, - has_side_effects=side_effects)) - - return side_effects - - def _add_trait_set(self, obj, name, value): - """ Add a call to a trait setter, with it's associated value and ID, - to the current script. - """ - - self._start_script() - - so = self._object_as_string(obj) - value = self._object_as_string(value) - - self._calls.append(_ScriptTraitSet(so=so, name=name, value=value)) - - def _unique_name(self, name, bind_policy): - """ Return a name that is guaranteed to be unique according to the bind - policy. - """ - - # See if the name is already is use. - bo = self._namespace.get(name) - - if bo is None: - self._names[name] = 1 - elif bind_policy == 'auto': - suff = self._names[name] - self._names[name] = suff + 1 - - name = '%s%d' % (name, suff) - elif bind_policy == 'rebind': - self._unbind(bo) - else: - raise NameError("\"%s\" is already bound to a scriptable object" % name) - - return name - - def _unbind(self, bo): - """Unbind the given bound object.""" - - # Tell everybody it is no longer bound. Don't bother if it is a - # factory because the corresponding bound event wouldn't have been - # fired. - if not isinstance(bo, _FactoryObject): - self.bind_event = BindEvent(name=bo.name, obj=None) - - # Forget about it. - del self._namespace[bo.name] - bo.name = '' - - @staticmethod - def _gc_script_obj(obj_ref): - """ The callback invoked when a scriptable object is garbage collected. - """ - - # Avoid recursive imports. - from .package_globals import get_script_manager - - sm = get_script_manager() - so = sm._so_by_ref[obj_ref] - - if so.name: - sm._unbind(so) - - del sm._so_by_id[so.obj_id] - del sm._so_by_ref[so.obj_ref] - - def _start_script(self): - """ Save when a script recording is started. """ - - if len(self._calls) == 0: - self._when_started = datetime.datetime.now().strftime('%c') - - def _object_as_string(self, obj): - """ Convert an object to a string as it will appear in a script. An - exception may be returned (not raised) if there was an error in the - conversion. - """ - - obj_id = id(obj) - - # See if the argument is the result of a previous call. - nr, _ = self._results.get(obj_id, (None, None)) - - if nr is not None: - if nr < 0: - nr = self._next_result_nr - self._next_result_nr += 1 - - # Key on the ID of the argument (which is hashable) rather than - # the argument itself (which might not be). - self._results[obj_id] = (nr, obj) - - return "r%d" % nr - - return self._scriptable_object_as_string(obj) - - def _scriptable_object_as_string(self, obj): - """ Convert an object to a string as it will appear in a script. An - exception may be returned (not raised) if there was an error in the - conversion. - """ - - obj_id = id(obj) - - # If it is a scriptable object we return the object and convert it to a - # string later when we know it is really needed. - so = self._so_by_id.get(obj_id) - - if so is not None: - return so - - # Use the repr result if it doesn't appear to be the generic response, - # ie. it doesn't contain its own address as a hex string. - s = repr(obj) - - if hex(obj_id) not in s: - return s - - # We don't know how to represent the argument as a string. This is - # most likely because an appropriate __init__ hasn't been made - # scriptable. We don't raise an exception until the user decides to - # convert the calls to a script. - return ValueError("unable to create a script representation of %s" % obj) - - def _save_result(self, result): - """ Save the result of a call to a scriptable method so that it can be - recognised later. - """ - - if id(result) not in self._results: - self._results[id(result)] = (-1, result) - - def _get_script(self): - """ Convert the current list of calls to a script. """ - - # Handle the trivial case. - if len(self._calls) == 0: - return "" - - # Generate the header. - header = "# Script generated %s" % self._when_started - - # Generate the calls. - so_needed = [] - calls = [] - - for call in self._calls: - s = call.as_str(self, so_needed) - - if s: - calls.append(s) - - calls = "\n".join(calls) - - # Generate the scriptable object constructors. - types_needed = [] - ctors = [] - - for so in so_needed: - if so.explicitly_bound: - continue - - so_type = so.scripted_type - args = self.args_as_string_list(so.args, so.kwargs) - - ctors.append("%s = %s(%s)" % (so.name, so_type.__name__, ", ".join(args))) - - # See if a new import is needed. - if so_type not in types_needed: - types_needed.append(so_type) - - ctors = "\n".join(ctors) - - # Generate the import statements. - imports = [] - - for so_type in types_needed: - imports.append("from %s import %s" % (so_type.__module__, so_type.__name__)) - - imports = "\n".join(imports) - - return "\n\n".join([header, imports, ctors, calls]) + "\n" diff --git a/apptools/appscripting/scriptable.py b/apptools/appscripting/scriptable.py deleted file mode 100644 index 329e18fde..000000000 --- a/apptools/appscripting/scriptable.py +++ /dev/null @@ -1,121 +0,0 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2008, Riverbank Computing Limited -# All rights reserved. -# -# This software is provided without warranty under the terms of the BSD -# license included in enthought/LICENSE.txt and may be redistributed only -# under the conditions described in the aforementioned license. The license -# is also available online at http://www.enthought.com/licenses/BSD.txt -# Thanks for using Enthought open source! -# -# Author: Riverbank Computing Limited -# Description: -#------------------------------------------------------------------------------ - - -# Enthought library imports. -from traits.api import Any, Property, Undefined -from traits.traits import trait_cast - -# Local imports. -from .package_globals import get_script_manager - - -# This is the guard that ensures that only outermost scriptable methods get -# recorded. -_outermost_call = True - - -def scriptable(func): - """ This is the decorator applied to functions and methods to mark them as - being scriptable. - """ - - def _scripter(*args, **kwargs): - """ This is the wrapper that is returned in place of the scriptable - method. - """ - - global _outermost_call - - if _outermost_call: - _outermost_call = False - - # See if there is an script manager set. - sm = get_script_manager() - - if func.__name__ == '__init__': - sm.new_object(args[0], type(args[0]), args[1:], kwargs) - - try: - result = func(*args, **kwargs) - finally: - _outermost_call = True - else: - # Record the ordinary method. - try: - result = sm.record_method(func, args, kwargs) - finally: - _outermost_call = True - else: - # We aren't at the outermost call so just invoke the method. - result = func(*args, **kwargs) - - return result - - # Be a good citizen. - _scripter.__name__ = func.__name__ - _scripter.__doc__ = func.__doc__ - _scripter.__dict__.update(func.__dict__) - - return _scripter - - -def _scriptable_get(obj, name): - """ The getter for a scriptable trait. """ - - global _outermost_call - - saved_outermost = _outermost_call - _outermost_call = False - - try: - result = getattr(obj, '_' + name, None) - - if result is None: - result = obj.trait(name).default - finally: - _outermost_call = saved_outermost - - if saved_outermost: - get_script_manager().record_trait_get(obj, name, result) - - return result - - -def _scriptable_set(obj, name, value): - """ The setter for a scriptable trait. """ - - if _outermost_call: - get_script_manager().record_trait_set(obj, name, value) - - _name = '_' + name - old_value = getattr(obj, _name, Undefined) - - if old_value is not value: - setattr(obj, _name, value) - obj.trait_property_changed(name, old_value, value) - - -def Scriptable(trait=Any, **metadata): - """ Scriptable is a wrapper around another trait that makes it scriptable, - ie. changes to its value can be recorded. If a trait is read, but the - value isn't set to another scriptable trait or passed to a scriptable - method then the read will not be included in the recorded script. To make - sure a read is always recorded set the 'has_side_effects' argument to True. - """ - - trait = trait_cast(trait) - metadata['default'] = trait.default_value()[1] - - return Property(_scriptable_get, _scriptable_set, trait=trait, **metadata) diff --git a/apptools/appscripting/scriptable_type.py b/apptools/appscripting/scriptable_type.py deleted file mode 100644 index e55dc43ed..000000000 --- a/apptools/appscripting/scriptable_type.py +++ /dev/null @@ -1,151 +0,0 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2008, Riverbank Computing Limited -# All rights reserved. -# -# This software is provided without warranty under the terms of the BSD -# license included in enthought/LICENSE.txt and may be redistributed only -# under the conditions described in the aforementioned license. The license -# is also available online at http://www.enthought.com/licenses/BSD.txt -# Thanks for using Enthought open source! -# -# Author: Riverbank Computing Limited -# Description: -#------------------------------------------------------------------------------ - - -# Standard library imports. -import inspect -import types - -# Enthought library imports. -from traits.api import HasTraits - -# Local imports. -from .package_globals import get_script_manager -from .scriptable import scriptable, Scriptable - - -def create_scriptable_type(scripted_type, name=None, bind_policy='auto', - api=None, includes=None, excludes=None, script_init=True): - """Create and return a new type based on the given scripted_type that will - (by default) have its public methods and traits (ie. those not beginning - with an underscore) made scriptable. - - name is the name that objects of this type will be bound to. It defaults - to the name of scripted_type with the first character forced to lower case. - It is ignored if script_init is False. - - bind_policy determines what happens if a name is already bound. If the - policy is 'auto' then a numerical suffix will be added to the name, if - necessary, to make it unique. If the policy is 'unique' then an exception - is raised. If the policy is 'rebind' then the previous binding is - discarded. It is ignored if script_init is False. The default is 'auto'. - - If api is given then it is a class, or a list of classes, that define the - attributes that will be made scriptable. - - Otherwise if includes is given it is a list of names of attributes that - will be made scriptable. - - Otherwise all the public attributes of scripted_type will be made - scriptable except those in the excludes list. - - Irrespective of any other arguments, if script_init is set then the - __init__() method will always be made scriptable. - """ - - def __init__(self, *args, **kwargs): - """Initialise the dynamic sub-class instance.""" - - get_script_manager().new_object(self, scripted_type, args, kwargs, - name, bind_policy) - scripted_type.__init__(self, *args, **kwargs) - - # See if we need to pull all attribute names from a type. - if api is not None: - if isinstance(api, list): - src = api - else: - src = [api] - elif includes is None: - src = [scripted_type] - else: - names = includes - src = None - - if src: - ndict = {} - - for cls in src: - if issubclass(cls, HasTraits): - for n in cls.class_traits().keys(): - if not n.startswith('_') and not n.startswith('trait'): - ndict[n] = None - - for c in inspect.getmro(cls): - if c is HasTraits: - break - - for n in c.__dict__.keys(): - if not n.startswith('_'): - ndict[n] = None - - # Respect the excludes so long as there was no explicit API. - if api is None and excludes is not None: - for n in excludes: - try: - del ndict[n] - except KeyError: - pass - - names = list(ndict.keys()) - - # Create the type dictionary containing replacements for everything that - # needs to be scriptable. - type_dict = {} - if script_init: - type_dict['__init__'] = __init__ - - if issubclass(scripted_type, HasTraits): - traits = scripted_type.class_traits() - - for n in names: - trait = traits.get(n) - - if trait is not None: - type_dict[n] = Scriptable(trait) - - for n in names: - try: - attr = getattr(scripted_type, n) - except AttributeError: - continue - - if type(attr) is types.MethodType: - type_dict[n] = scriptable(attr) - - type_name = 'Scriptable(%s)' % scripted_type.__name__ - - return type(type_name, (scripted_type, ), type_dict) - - -def make_object_scriptable(obj, api=None, includes=None, excludes=None): - """Make (by default) an object's public methods and traits (ie. those not - beginning with an underscore) scriptable. - - If api is given then it is a class, or a list of classes, that define the - attributes that will be made scriptable. - - Otherwise if includes is given it is a list of names of attributes that - will be made scriptable. - - Otherwise all the public attributes of scripted_type will be made - scriptable except those in the excludes list. - """ - - # Create the new scriptable type. - new_type = create_scriptable_type(obj.__class__, api=api, - includes=includes, excludes=excludes, script_init=False) - - # Fix the object's type to make it scriptable. - obj.__class__ = new_type diff --git a/apptools/scripting/recordable.py b/apptools/scripting/recordable.py index 13cb75865..c5b9d211b 100644 --- a/apptools/scripting/recordable.py +++ b/apptools/scripting/recordable.py @@ -18,9 +18,6 @@ def recordable(func): This will record the function only if the global recorder has been set via a `set_recorder` function call. - - This is almost entirely copied from the - apptools.appscripting.scriptable.scriptable decorator. """ def _wrapper(*args, **kw): diff --git a/apptools/scripting/recorder.py b/apptools/scripting/recorder.py index d2f4c20da..36eb1bdaa 100644 --- a/apptools/scripting/recorder.py +++ b/apptools/scripting/recorder.py @@ -659,8 +659,8 @@ def _function_as_string(self, func, args, kw): def _is_arbitrary_object(self, object): """Return True if the object is an arbitrary non-primitive object. - As done in appscripting, we assume that if the hex id of the object is - in its string representation then it is an arbitrary object. + We assume that if the hex id of the object is in its string + representation then it is an arbitrary object. """ ob_id = id(object) orepr = repr(object) diff --git a/docs/source/appscripting/Introduction.rst b/docs/source/appscripting/Introduction.rst deleted file mode 100644 index 16a18f1b0..000000000 --- a/docs/source/appscripting/Introduction.rst +++ /dev/null @@ -1,422 +0,0 @@ -Application Scripting Framework -=============================== - -The Application Scripting Framework is a component of the Enthought Tool Suite -that provides developers with an API that allows traits based objects to be -made scriptable. Operations on a scriptable object can be recorded in a -script and subsequently replayed. - -The framework is completely configurable. Alternate implementations of all -major components can be provided if necessary. - - -Framework Concepts ------------------- - -The following are the concepts supported by the framework. - -- Scriptable Type - - A scriptable type is a sub-type of ``HasTraits`` that has scriptable methods - and scriptable traits. If a scriptable method is called, or a scriptable - trait is set, then that action can be recorded in a script and subsequently - replayed. - - If the ``__init__()`` method is scriptable then the creation of an object - from the type can be recorded. - - Scriptable types can be explicitly defined or created dynamically from any - sub-type of ``HasTraits``. - -- Scriptable API - - The set of a scriptable type's scriptable methods and traits constitutes the - type's scriptable API. - - The API can be defined explicitly using the ``scriptable`` decorator (for - methods) or the ``Scriptable`` wrapper (for traits). - - For scriptable types that are created dynamically then the API can be - defined in terms of one or more types or interfaces or an explicit list of - method and trait names. By default, all public methods and traits (ie. - those whose name does not begin with an underscore) are part of the API. It - is also possible to then explicitly exclude a list of method and trait - names. - -- Scriptable Object - - A scriptable object is an instance of a scriptable type. - - Scriptable objects can be explicitly created by calling the scriptable type. - Alternatively a non-scriptable object can be made scriptable dynamically. - -- Script - - A script is a Python script and may be a recording or written from scratch. - - If the creation of scriptable objects can be recorded, then it may be - possible for a recording to be run directly by the Python interpreter and - independently of the application that made the recording. Otherwise the - application must run the script and first create any scriptable objects - referred to in the script. - -- Binding - - A script runs in a namespace which is, by default, empty. If the scriptable - objects referred to in a script are not created by the script (because their - type's ``__init__()`` method isn't scriptable) then they must be created by - the application and added to the namespace. Adding an object to the - namespace is called binding. - - Scriptable objects whose creation can be recorded will automatically bind - themselves when they are created. - - It also possible to bind an object factory rather than the object itself. - The factory will be called, and the object created, only if the object is - needed by the script when it is run. This is typically used by plugins. - - The name that an object is bound to need bear no relation to the object's - name within the application. Names may be dotted names (eg. ``aaa.bbb.ccc``) - and appropriate objects representing the intermediate parts of such a name - will be created automatically. - - An event is fired whenever an object is bound (or when a bound factory is - invoked). This allows other objects (eg. an embedded Python shell) to - expose scriptable objects in other ways. - -- Script Manager - - A script manager is responsible for the recording and subsequent playback of - scripts. An application has a single script manager instance which can be - explicitly set or created automatically. - - -Limitations ------------ - -In the current implementation scriptable Trait container types (eg. List, -Dict) may only contain objects corresponding to fundamental Python types (eg. -int, bool, str). - - -API Overview ------------- - -This section gives an overview of the API implemented by the framework. The -complete API_ documentation is available as endo generated HTML. - -The example_ application demonstrates some the features of the framework. - - -Module Level Objects -.................... - -``get_script_manager()`` - The application's script manager is returned. One will be created - automatically if needed. - -``set_script_manager(script_manager)`` - The application's script manager will be set to ``script_manager`` - replacing any existing script manager. - -``scriptable`` - This is a decorator used to explicitly mark methods as being scriptable. - Any call to a scriptable method is recorded. If a type's ``__init__()`` - method is decorated then the creation of the object will be recorded. - -``Scriptable`` - This is a wrapper for a trait to explicitly mark it as being scriptable. - Any change to the value of the trait will be recorded. Simple reads of the - trait will not be recorded unless unless the value read is bound to another - scriptable trait or passed as an argument to a scriptable method. Passing - ``has_side_effects=True`` when wrapping the trait will ensure that a read - will always be recorded. - -``create_scriptable_type(script_type, name=None, bind_policy='auto', api=None, includes=None, excludes=None, script_init=True)`` - This creates a new type based on an existing type but with certain methods - and traits marked as being scriptable. Scriptable objects can then be - created by calling the type. - - ``script_type`` is the existing, non-scriptable, type. The new type will - be a sub-type of it. The ``api``, ``includes`` and ``excludes`` arguments - determine which methods and traits are made scriptable. By default, all - public methods and traits (ie. those whose name does not begin with an - underscore) are made scriptable. - - The ``name`` and ``bind_policy`` arguments determine how scriptable - objects are bound when they are created. ``name`` is the name that an - object will be bound to. It defaults to the name of ``script_type`` with - the first character forced to lower case. ``name`` may be a dotted name, - eg. ``aaa.bb.c``. - - ``bind_policy`` determines what happens if an object is already bound to - the name. If it is ``auto`` then a numerical suffix will be added to the - name of the new object. If it is ``unique`` then an exception will be - raised. If it is ``rebind`` then the object currently bound to the name - will be unbound. - - ``api`` is a class or interface (or a list of classes or interfaces) that - is used to provide the names of the methods and traits to be made - scriptable. The class or interface effectively defines the scripting API. - - If ``api`` is not specified then ``includes`` is a list of method and - trait names that are made scriptable. - - If ``api`` and ``includes`` are not specified then ``excludes`` is a list - of method and trait names that are *not* made scriptable. - - If ``script_init`` is set then the ``__init__()`` method is made scriptable - irrespective of the ``api``, ``includes`` and ``excludes`` arguments. - - If ``script_init`` is not set then objects must be explicitly bound and - ``name`` and ``bind_policy`` are ignored. - -``make_object_scriptable(obj, api=None, includes=None, excludes=None)`` - This takes an existing unscriptable object and makes it scriptable. It - works by calling ``create_scriptable_type()`` on the the objects existing - type and replacing that existing type with the new scriptable type. - - See the description of ``create_scriptable_type()`` for an explanation of - the ``api``, ``includes`` and ``excludes`` arguments. - - -ScriptManager -............. - -The ``ScriptManager`` class is the default implementation of the -``IScriptManager`` interface. - -``bind_event`` - This event is fired whenever an object is bound or unbound. The event's - argument implements the ``IBindEvent`` interface. - -``recording`` - This trait is set if a script is currently being recorded. It is updated - automatically by the script manager. - -``script`` - This trait contains the text of the script currently being recorded (or - the last recorded script if one is not being currently recorded). It is - updated automatically by the script manager. - -``script_updated`` - This event is fired whenever the ``script`` trait is updated. The event's - argument is the script manager. - -``bind(self, obj, name=None, bind_policy='unique', api=None, includes=None, excludes=None)`` - This method makes an object scriptable and binds it to a name. See the - description of ``create_scriptable_type()`` for an explanation of the - ``api``, ``includes``, ``excludes``, ``name`` and ``bind_policy`` - arguments. - -``bind_factory(self, factory, name, bind_policy='unique', api=None, includes=None, excludes=None)`` - This method binds an object factory to a name. The factory is called to - create the object (and make it scriptable) only when the object is needed - by a running script. See the description of ``create_scriptable_type()`` - for an explanation of the ``name`` and ``bind_policy`` arguments. - -``run(self, script)`` - This method runs a script in a namespace containing all currently bound - objects. ``script`` is any object that can be used by Python's ``exec`` - statement including a string or a file-like object. - -``run_file(self, file_name)`` - This method runs a script in a namespace containing all currently bound - objects. ``file_name`` is the name of a file containing the script. - -``start_recording(self)`` - This method starts the recording of a script. - -``stop_recording(self)`` - This method stops the recording of the current script. - - -IBindEvent -.......... - -The ``IBindEvent`` interface defines the interface that is implemented by the -object passed when the script manager's ``bind_event`` is fired. - -``name`` - This trait is the name being bound or unbound. - -``obj`` - This trait is the obj being bound to ``name`` or None if ``name`` is being - unbound. - - -StartRecordingAction -.................... - -The ``StartRecordingAction`` class is a canned PyFace action that starts the -recording of changes to scriptable objects to a script. - - -StopRecordingAction -................... - -The ``StopRecordingAction`` class is a canned PyFace action that ends the -recording of changes to scriptable objects to a script. - - -Implementing Application Scripting ----------------------------------- - -The key part of supporting application scripting is to design an appropriate -scripting API and to ensure than the application itself uses the API so that -changes to the data can be recorded. The framework provides many ways to -specify the scripting API. Which approach is appropriate in a particular case -will depend on when it is a new application, or whether scripting is being -added to an existing application, and how complex the application's data model -is. - -Static Specification -.................... - -A scripting API is specified statically by the explicit use of the -``scriptable`` decorator and the ``Scriptable`` trait wrapper. For example:: - - from apptools.appscripting.api import scriptable, Scriptable - from traits.api import HasTraits, Int, Str - - class DataModel(HasTraits): - - foo = Scriptable(Str) - - bar = Scriptable(Int, has_side_effects=True) - - @scriptable - def baz(self): - pass - - def weeble(self) - pass - - # Create the scriptable object. It's creation won't be recorded because - # __init__() isn't decorated. - obj = DataModel() - - # These will be recorded. - obj.foo = '' - obj.bar = 10 - obj.baz() - - # This will not be recorded. - obj.weeble() - - # This won't be recorded unless 'f' is passed to something that is - # recorded. - f = obj.foo - - # This will be recorded because we set 'has_side_effects'. - b = obj.bar - - -Dynamic Specification -..................... - -A scripting API can also be specified dynamically. The following example -produces a scriptable object with the same scriptable API as above (with the -exception that ``has_side_effects`` cannot be specified dynamically):: - - from apptools.appscripting.api import create_scriptable_type - from traits.api import HasTraits, Int, Str - - class DataModel(HasTraits): - - foo = Str - - bar = Int - - def baz(self): - pass - - def weeble(self) - pass - - # Create a scriptable type based on the above. - ScriptableDataModel = create_scriptable_type(DataModel, excludes=['weeble']) - - # Now create scriptable objects from the scriptable type. Note that each - # object has the same type. - obj1 = ScriptableDataModel() - obj2 = ScriptableDataModel() - -Instead we could bypass the type and make the objects themselves scriptable as -follows:: - - from apptools.appscripting.api import make_object_scriptable - from traits.api import HasTraits, Int, Str - - class DataModel(HasTraits): - - foo = Str - - bar = Int - - def baz(self): - pass - - def weeble(self) - pass - - # Create unscriptable objects. - obj1 = DataModel() - obj2 = DataModel() - - # Now make the objects scriptable. Note that each object has a different - # type, each a sub-type of 'DataModel'. - make_object_scriptable(obj1, excludes=['weeble']) - make_object_scriptable(obj2, excludes=['weeble']) - -With a more sophisticated design we may choose to specify the scriptable API as -an interface as follows:: - - from apptools.appscripting.api import make_object_scriptable - from traits.api import HasTraits, Int, Interface, Str - - class DataModel(HasTraits): - - foo = Str - - bar = Int - - def baz(self): - pass - - def weeble(self) - pass - - class IScriptableDataModel(Interface): - - foo = Str - - bar = Int - - def baz(self): - pass - - # Create an unscriptable object. - obj = DataModel() - - # Now make the object scriptable. - make_object_scriptable(obj, api=IScriptableDataModel) - - -Scripting __init__() -.................... - -Making a type's ``__init__()`` method has advantages and disadvantages. It -means that the creation of scriptable objects will be recorded in a script -(along with the necessary ``import`` statements). This means that the script -can be run independently of your application by the standard Python -interpreter. - -The disadvantage is that, if you have a complex data model, with many -interdependencies, then defining a complete and consistent scripting API that -allows a script to run independently may prove difficult. In such cases it is -better to have the application create and bind the scriptable objects itself. - - -.. _API: api/index.html -.. _example: https://svn.enthought.com/enthought/browser/AppTools/trunk/examples/appscripting/ diff --git a/docs/source/index.rst b/docs/source/index.rst index 9f69ab94c..2b4d3650b 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -5,7 +5,6 @@ AppTools Documentation :maxdepth: 2 :glob: - appscripting/* permissions/Introduction permissions/ApplicationAPI permissions/DefaultPolicyManagerDataAPI diff --git a/examples/appscripting/actions.py b/examples/appscripting/actions.py deleted file mode 100644 index 57550ddc0..000000000 --- a/examples/appscripting/actions.py +++ /dev/null @@ -1,231 +0,0 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2008, Riverbank Computing Limited -# All rights reserved. -# -# This software is provided without warranty under the terms of the BSD -# license included in enthought/LICENSE.txt and may be redistributed only -# under the conditions described in the aforementioned license. The license -# is also available online at http://www.enthought.com/licenses/BSD.txt -# Thanks for using Enthought open source! -# -# Author: Riverbank Computing Limited -# Description: -#------------------------------------------------------------------------------ - - -# Enthought library imports. -from traits.api import Any, Instance, Str, Unicode -from pyface.action.api import Action -from pyface.workbench.api import WorkbenchWindow - -# Local imports. -from model import Label - - -class BoundAction(Action): - """An action with a bound object. The action is automatically disabled if - the bound object is None.""" - - #### 'BoundAction' interface ############################################## - - # The bound object. - obj = Any - - # The optional trait on obj that we are synch'ed with. - trait_name = Str - - ########################################################################### - # 'object' interface. - ########################################################################### - - def __init__(self, **traits): - """Initialise the object.""" - - super(BoundAction, self).__init__(**traits) - - # Fake an obj change to set the initial state. - self._obj_changed(None, self.obj) - - ########################################################################### - # Traits handlers. - ########################################################################### - - def _obj_changed(self, old, new): - """Invoked when the bound object changes.""" - - if old is not None: - if self.trait_name: - # Ignore any changes to the old object. - old.on_trait_change(self._trait_changed, self.trait_name, - remove=True) - - enabled = False - - if new is not None: - if self.trait_name: - # Check for any changes on the new object. - new.on_trait_change(self._trait_changed, self.trait_name) - - # Get the current state. - if getattr(new, self.trait_name): - enabled = True - else: - enabled = True - - self.enabled = enabled - - def _trait_changed(self, new): - """Invoked when the trait on the bound object changes.""" - - self.enabled = new - - -class BoundWorkbenchAction(BoundAction): - """A bound action whose object is being edited in a workbench editor. The - action is automatically rebound when the active editor changes.""" - - #### 'BoundWorkbenchAction' interface ##################################### - - # The type of the object that we will be enabled for. If it is None then - # we will be enabled for all types. - trait_type = Any - - # The workbench window containing the action. - window = Instance(WorkbenchWindow) - - ########################################################################### - # 'object' interface. - ########################################################################### - - def __init__(self, **traits): - """Initialise the object.""" - - super(BoundWorkbenchAction, self).__init__(**traits) - - self.window.on_trait_change(self._editor_changed, 'active_editor') - - # Fake an editor change to set the initial state. - self._editor_changed(self.window.active_editor) - - ########################################################################### - # Traits handlers. - ########################################################################### - - def _editor_changed(self, new): - """Invoked when the active editor changes.""" - - obj = None - - if new is not None: - if self.trait_type is None: - obj = new.obj - elif isinstance(new.obj, self.trait_type): - obj = new.obj - - self.obj = obj - - -class LabelAction(BoundWorkbenchAction): - """ The LabelAction class is the base class for all actions that operate on - a Label. - """ - - #### 'BoundWorkbenchAction' interface ##################################### - - # The type of the object that we will be enabled for. - trait_type = Label - - #### 'BoundAction' interface ############################################## - - # The bound object. - obj = Instance(Label) - - -class LabelIncrementSizeAction(LabelAction): - """ The LabelIncrementSizeAction class is a action that increases the size - of a label's text. - """ - - #### 'Action' interface ################################################### - - # The name of the action. - name = Unicode("&Increment size") - - ########################################################################### - # 'Action' interface. - ########################################################################### - - def perform(self, event): - self.obj.increment_size(1) - - -class LabelDecrementSizeAction(LabelAction): - """ The LabelDecrementSizeAction class is a action that decreases the size - of a label's text. - """ - - #### 'Action' interface ################################################### - - # The name of the action. - name = Unicode("&Decrement size") - - ########################################################################### - # 'Action' interface. - ########################################################################### - - def perform(self, event): - self.obj.decrement_size(1) - - -class LabelNormalFontAction(LabelAction): - """ The LabelNormalFontAction class is a action that sets a normal font for - a label's text. - """ - - #### 'Action' interface ################################################### - - # The name of the action. - name = Unicode("&Normal font") - - ########################################################################### - # 'Action' interface. - ########################################################################### - - def perform(self, event): - self.obj.style = 'normal' - - -class LabelBoldFontAction(LabelAction): - """ The LabelNormalFontAction class is a action that sets a bold font for a - label's text. - """ - - #### 'Action' interface ################################################### - - # The name of the action. - name = Unicode("&Bold font") - - ########################################################################### - # 'Action' interface. - ########################################################################### - - def perform(self, event): - self.obj.style = 'bold' - - -class LabelItalicFontAction(LabelAction): - """ The LabelNormalFontAction class is a action that sets an italic font - for a label's text. - """ - - #### 'Action' interface ################################################### - - # The name of the action. - name = Unicode("&Italic font") - - ########################################################################### - # 'Action' interface. - ########################################################################### - - def perform(self, event): - self.obj.style = 'italic' diff --git a/examples/appscripting/example.py b/examples/appscripting/example.py deleted file mode 100644 index c4c5dbcc4..000000000 --- a/examples/appscripting/example.py +++ /dev/null @@ -1,88 +0,0 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2008, Riverbank Computing Limited -# All rights reserved. -# -# This software is provided without warranty under the terms of the BSD -# license included in enthought/LICENSE.txt and may be redistributed only -# under the conditions described in the aforementioned license. The license -# is also available online at http://www.enthought.com/licenses/BSD.txt -# Thanks for using Enthought open source! -# -# Author: Riverbank Computing Limited -# Description: -#------------------------------------------------------------------------------ - - -# Standard library imports. -import logging - -# Enthought library imports. -from pyface.api import GUI, YES -from pyface.workbench.api import Workbench - -# Local imports. -from example_script_window import ExampleScriptWindow -from model import Label - - -# Log to stderr. -logging.getLogger().addHandler(logging.StreamHandler()) -logging.getLogger().setLevel(logging.DEBUG) - - -class ExampleScript(Workbench): - """ The ExampleScript class is a workbench that creates ExampleScriptWindow - windows. - """ - - #### 'Workbench' interface ################################################ - - # The factory (in this case simply a class) that is used to create - # workbench windows. - window_factory = ExampleScriptWindow - - ########################################################################### - # Private interface. - ########################################################################### - - def _exiting_changed(self, event): - """ Called when the workbench is exiting. """ - - if self.active_window.confirm('Ok to exit?') != YES: - event.veto = True - - return - - -def main(argv): - """ A simple example of using the the application scripting framework in a - workbench. - """ - - # Create the GUI. - gui = GUI() - - # Create the workbench. - workbench = ExampleScript(state_location=gui.state_location) - - window = workbench.create_window(position=(300, 300), size=(400, 300)) - window.open() - - # Create some objects to edit. - # FIXME v3: The need to explicitly set the style to its default value is - # due to a bug in the implementation of Scriptable. - label = Label(text="Label", style='normal') - label2 = Label(text="Label2", style='normal') - - # Edit the objects. - window.edit(label) - window.edit(label2) - - # Start the GUI event loop. - gui.start_event_loop() - - return - - -if __name__ == '__main__': - import sys; main(sys.argv) diff --git a/examples/appscripting/example_editor_manager.py b/examples/appscripting/example_editor_manager.py deleted file mode 100644 index 94c2eea52..000000000 --- a/examples/appscripting/example_editor_manager.py +++ /dev/null @@ -1,140 +0,0 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2008, Riverbank Computing Limited -# All rights reserved. -# -# This software is provided without warranty under the terms of the BSD -# license included in enthought/LICENSE.txt and may be redistributed only -# under the conditions described in the aforementioned license. The license -# is also available online at http://www.enthought.com/licenses/BSD.txt -# Thanks for using Enthought open source! -# -# Author: Riverbank Computing Limited -# Description: -#------------------------------------------------------------------------------ - - -# Enthought library imports. -from traits.etsconfig.api import ETSConfig -from pyface.workbench.api import Editor, EditorManager - - -class _wxLabelEditor(Editor): - """ _wxLabelEditor is the wx implementation of a label editor. """ - - def create_control(self, parent): - import wx - - w = wx.TextCtrl(parent,style=wx.TE_RICH2) - style = w.GetDefaultStyle() - style.SetAlignment(wx.TEXT_ALIGNMENT_CENTER) - w.SetDefaultStyle(style) - - self._set_text(w) - self._set_size_and_style(w) - - self.obj.on_trait_change(self._update_text, 'text') - self.obj.on_trait_change(self._update_size, 'size') - self.obj.on_trait_change(self._update_style, 'style') - - return w - - def _name_default(self): - return self.obj.text - - def _update_text(self): - self._set_text(self.control) - - def _set_text(self, w): - w.SetValue("") - w.WriteText("%s(%d points, %s)" % (self.obj.text, self.obj.size, self.obj.style)) - - def _update_size(self): - self._set_size_and_style(self.control) - - def _update_style(self): - self._set_size_and_style(self.control) - - def _set_size_and_style(self, w): - import wx - if self.obj.style == 'normal': - style, weight = wx.NORMAL, wx.NORMAL - elif self.obj.style == 'italic': - style, weight = wx.ITALIC, wx.NORMAL - elif self.obj.style == 'bold': - style, weight = wx.NORMAL, wx.BOLD - else: - raise NotImplementedError, 'style "%s" not supported' % self.obj.style - - f = wx.Font(self.obj.size, wx.ROMAN, style, weight, False) - style = wx.TextAttr("BLACK",wx.NullColour,f) - w.SetDefaultStyle(style) - self._set_text(w) - - -class _PyQt4LabelEditor(Editor): - """ _PyQt4LabelEditor is the PyQt implementation of a label editor. """ - - def create_control(self, parent): - - from PyQt4 import QtCore, QtGui - - w = QtGui.QLabel(parent) - w.setAlignment(QtCore.Qt.AlignCenter) - - self._set_text(w) - self._set_size(w) - self._set_style(w) - - self.obj.on_trait_change(self._update_text, 'text') - self.obj.on_trait_change(self._update_size, 'size') - self.obj.on_trait_change(self._update_style, 'style') - - return w - - def _name_default(self): - return self.obj.text - - def _update_text(self): - self._set_text(self.control) - - def _set_text(self, w): - w.setText("%s\n(%d points, %s)" % (self.obj.text, self.obj.size, self.obj.style)) - - def _update_size(self): - self._set_size(self.control) - - def _set_size(self, w): - f = w.font() - f.setPointSize(self.obj.size) - w.setFont(f) - - self._set_text(w) - - def _update_style(self): - self._set_style(self.control) - - def _set_style(self, w): - f = w.font() - f.setBold(self.obj.style == 'bold') - f.setItalic(self.obj.style == 'italic') - w.setFont(f) - - self._set_text(w) - - -class ExampleEditorManager(EditorManager): - """ The ExampleEditorManager class creates the example editors. """ - - def create_editor(self, window, obj, kind): - - # Create the toolkit specific editor. - tk_name = ETSConfig.toolkit - - if tk_name == 'wx': - ed = _wxLabelEditor(window=window, obj=obj) - elif tk_name == 'qt4': - ed = _PyQt4LabelEditor(window=window, obj=obj) - else: - raise NotImplementedError, "unsupported toolkit: %s" % tk_name - - return ed diff --git a/examples/appscripting/example_script_window.py b/examples/appscripting/example_script_window.py deleted file mode 100644 index 4a36cde76..000000000 --- a/examples/appscripting/example_script_window.py +++ /dev/null @@ -1,118 +0,0 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2008, Riverbank Computing Limited -# All rights reserved. -# -# This software is provided without warranty under the terms of the BSD -# license included in enthought/LICENSE.txt and may be redistributed only -# under the conditions described in the aforementioned license. The license -# is also available online at http://www.enthought.com/licenses/BSD.txt -# Thanks for using Enthought open source! -# -# Author: Riverbank Computing Limited -# Description: -#------------------------------------------------------------------------------ - - -from __future__ import print_function - -# Enthought library imports. -from pyface.action.api import Action, Group, MenuManager -from pyface.workbench.api import WorkbenchWindow -from pyface.workbench.action.api import MenuBarManager, \ - ToolBarManager -from traits.api import Instance, on_trait_change -from apptools.appscripting.api import get_script_manager -from apptools.appscripting.action.api import StartRecordingAction, \ - StopRecordingAction - -# Local imports. -from example_editor_manager import ExampleEditorManager -from actions import LabelIncrementSizeAction, LabelDecrementSizeAction, \ - LabelNormalFontAction, LabelBoldFontAction, LabelItalicFontAction - - -class ExampleScriptWindow(WorkbenchWindow): - """ The ExampleScriptWindow class is a workbench window that contains - example editors that demonstrate the use of the application scripting - framework. - """ - - #### Private interface #################################################### - - # The action that exits the application. - _exit_action = Instance(Action) - - # The File menu. - _file_menu = Instance(MenuManager) - - # The Label menu. - _label_menu = Instance(MenuManager) - - # The Scripts menu. - _scripts_menu = Instance(MenuManager) - - ########################################################################### - # Private interface. - ########################################################################### - - #### Trait initialisers ################################################### - - def __file_menu_default(self): - """ Trait initialiser. """ - - return MenuManager(self._exit_action, name="&File") - - def __label_menu_default(self): - """ Trait initialiser. """ - - size_group = Group(LabelIncrementSizeAction(window=self), - LabelDecrementSizeAction(window=self)) - - normal = LabelNormalFontAction(window=self, id='normal', style='radio', - checked=True) - bold = LabelBoldFontAction(window=self, id='bold', style='radio') - italic = LabelItalicFontAction(window=self, id='italic', style='radio') - - style_group = Group(normal, bold, italic, id='style') - - return MenuManager(size_group, style_group, name="&Label") - - def __scripts_menu_default(self): - """ Trait initialiser. """ - - # ZZZ: This is temporary until we put the script into a view. - get_script_manager().on_trait_event(self._on_script_updated, - 'script_updated') - - return MenuManager(StartRecordingAction(), StopRecordingAction(), - name="&Scripts") - - def __exit_action_default(self): - """ Trait initialiser. """ - - return Action(name="E&xit", on_perform=self.workbench.exit) - - def _editor_manager_default(self): - """ Trait initialiser. """ - - return ExampleEditorManager() - - def _menu_bar_manager_default(self): - """ Trait initialiser. """ - - return MenuBarManager(self._file_menu, self._label_menu, - self._scripts_menu, window=self) - - def _tool_bar_manager_default(self): - """ Trait initialiser. """ - - return ToolBarManager(self._exit_action, show_tool_names=False) - - # ZZZ: This is temporary until we put the script into a view. - def _on_script_updated(self, script_manager): - script = script_manager.script - - if script: - print(script, end="") - else: - print("Script empty") diff --git a/examples/appscripting/model.py b/examples/appscripting/model.py deleted file mode 100644 index 8fc4db15f..000000000 --- a/examples/appscripting/model.py +++ /dev/null @@ -1,60 +0,0 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2008, Riverbank Computing Limited -# All rights reserved. -# -# This software is provided without warranty under the terms of the BSD -# license included in enthought/LICENSE.txt and may be redistributed only -# under the conditions described in the aforementioned license. The license -# is also available online at http://www.enthought.com/licenses/BSD.txt -# Thanks for using Enthought open source! -# -# Author: Riverbank Computing Limited -# Description: -#------------------------------------------------------------------------------ - - -# Enthought library imports. -from traits.api import Enum, HasTraits, Int, Unicode -from apptools.appscripting.api import scriptable, Scriptable - - -class Label(HasTraits): - """ The Label class implements the data model for a label. """ - - #### 'Label' interface #################################################### - - # The name. - name = Unicode - - # The size in points. - size = Int(18) - - # The style. - style = Scriptable(Enum('normal', 'bold', 'italic')) - - ########################################################################### - # 'Label' interface. - ########################################################################### - - @scriptable - def __init__(self, **traits): - """ Initialise the object. We only implement this so that it can be - decorated and so the script manager knows how to recreate it. """ - - super(Label, self).__init__(**traits) - - @scriptable - def increment_size(self, by): - """ Increment the current font size. This demonstrates a scriptable - method. - """ - - self.size += by - - @scriptable - def decrement_size(self, by): - """ decrement the current font size. This demonstrates a scriptable - method. - """ - - self.size -= by