Moved config handling commands from hooke_cli to hooke.plugin.config.
authorW. Trevor King <wking@drexel.edu>
Wed, 12 May 2010 19:57:33 +0000 (15:57 -0400)
committerW. Trevor King <wking@drexel.edu>
Wed, 12 May 2010 19:57:33 +0000 (15:57 -0400)
Also:
  * Moved construct_graph and IsSubclass from hooke.plugin to
    hooke.util.pluggable to avoid import cycles and cleanup
    hooke.plugin.
  * Added hooke.interaction.ReloadUserInterfaceConfig to trigger UI
    reconfigure.
  * Added hooke.ui.UserInterface.reload_config (triggered by above).
  * Pass CommandLine instance to HookeCmd, so it can reconfigure
    the UI.

hooke/driver/__init__.py
hooke/hooke_cli.py
hooke/interaction.py
hooke/plugin/__init__.py
hooke/plugin/config.py [new file with mode: 0644]
hooke/ui/__init__.py
hooke/ui/commandline.py
hooke/util/pluggable.py [new file with mode: 0644]

index beaa991da4d41296b1c2a75c3f19cf7bafb061a9..c8a85e7ef489c406e2c3008f62de900b3266d454 100644 (file)
@@ -7,7 +7,8 @@ to write your own to handle your lab's specific format.
 """
 
 from ..config import Setting
-from ..plugin import construct_graph, IsSubclass
+from ..util.pluggable import IsSubclass, construct_graph
+
 
 DRIVER_MODULES = [
 #    ('csvdriver', True),
index 1ab299ec49ece30ddd5cceee77193fe9002fa9c9..95c8ea1eab0cded0ae1a1f965c506e72fa58d66d 100644 (file)
@@ -119,40 +119,3 @@ class HookeCli(cmd.Cmd, object):
         point.absolute_coords=xvector[index],yvector[index]
         point.find_graph_coords(xvector,yvector)
         return point
-
-#HERE COMMANDS BEGIN
-
-    def help_set(self):
-        print '''
-SET
-Sets a local configuration variable
--------------
-Syntax: set [variable] [value]
-        '''
-    def do_set(self,args):
-        #FIXME: some variables in self.config should be hidden or intelligently configurated...
-        args=args.split()
-        if len(args)==0:
-            print 'You must specify a variable and a value'
-            print 'Available variables:'
-            print self.config.keys()
-            return
-        if args[0] not in self.config.keys():
-            print 'This is not an internal Hooke variable!'
-            return
-        if len(args)==1:
-            #FIXME:we should reload the config file and reset the config value
-            print self.config[args[0]]
-            return
-        key=args[0]
-        try: #try to have a numeric value
-            value=float(args[1])
-        except ValueError: #if it cannot be converted to float, it's None, or a string...
-            value=args[1]
-            if value.lower()=='none':
-                value=None
-            else:
-                value=args[1]
-
-        self.config[key]=value
-        self.do_plot(0)
index 6904f7d49e86ff5abc474f1ddf014655215cdf32..0f91d6a08185e05a2a5c9d2cfce39e778694468e 100644 (file)
@@ -109,3 +109,14 @@ class PointRequest (Request):
 class PointResponse (Response):
     def __init__(self, value):
         super(PointResponse, self).__init__('point', value)
+
+
+class Notification (object):
+    def __init__(self, type):
+        self.type = type
+
+class ReloadUserInterfaceConfig (Notification):
+    def __init__(self, config):
+        super(ReloadUserInterfaceConfig, self).__init__(
+            'reload user interface config')
+        self.config = config
index 440c3bfa785f2f5fedbcb8508aa0111f8fb0ef9f..98862c684fb459cea872479b19fdd48fcdbc311b 100644 (file)
@@ -7,7 +7,7 @@ All of the science happens in here.
 import ConfigParser as configparser
 
 from ..config import Setting
-from ..util.graph import Node, Graph
+from ..util.pluggable import IsSubclass, construct_graph
 
 
 PLUGIN_MODULES = [
@@ -38,6 +38,7 @@ default.  TODO: autodiscovery
 """
 
 BUILTIN_MODULES = [
+    'config',
     'debug',
     'note',
     'playlist',
@@ -93,81 +94,6 @@ class Builtin (Plugin):
 
 # Construct plugin dependency graph and load plugin instances.
 
-def construct_graph(this_modname, submodnames, class_selector,
-                    assert_name_match=True):
-    """Search the submodules `submodnames` of a module `this_modname`
-    for class objects for which `class_selector(class)` returns
-    `True`.  These classes are instantiated, and the `instance.name`
-    is compared to the `submodname` (if `assert_name_match` is
-    `True`).
-
-    The instances are further arranged into a dependency
-    :class:`hooke.util.graph.Graph` according to their
-    `instance.dependencies()` values.  The topologically sorted graph
-    is returned.
-    """
-    instances = {}
-    for submodname in submodnames:
-        count = len([s for s in submodnames if s == submodname])
-        assert count > 0, 'No %s entries: %s' % (submodname, submodnames)
-        assert count == 1, 'Multiple (%d) %s entries: %s' \
-            % (count, submodname, submodnames)
-        this_mod = __import__(this_modname, fromlist=[submodname])
-        submod = getattr(this_mod, submodname)
-        for objname in dir(submod):
-            obj = getattr(submod, objname)
-            if class_selector(obj):
-                instance = obj()
-                if assert_name_match == True and instance.name != submodname:
-                    raise Exception(
-                        'Instance name %s does not match module name %s'
-                        % (instance.name, submodname))
-                instances[instance.name] = instance
-    nodes = {}
-    for i in instances.values():     # make nodes for each instance
-        nodes[i.name] = Node(data=i)
-    for n in nodes.values():         # fill in dependencies for each node
-        n.extend([nodes[name] for name in n.data.dependencies()])
-    graph = Graph(nodes.values())
-    graph.topological_sort()
-    return graph
-
-class IsSubclass (object):
-    """A safe subclass comparator.
-    
-    Examples
-    --------
-
-    >>> class A (object):
-    ...     pass
-    >>> class B (A):
-    ...     pass
-    >>> C = 5
-    >>> is_subclass = IsSubclass(A)
-    >>> is_subclass(A)
-    True
-    >>> is_subclass = IsSubclass(A, blacklist=[A])
-    >>> is_subclass(A)
-    False
-    >>> is_subclass(B)
-    True
-    >>> is_subclass(C)
-    False
-    """
-    def __init__(self, base_class, blacklist=None):
-        self.base_class = base_class
-        if blacklist == None:
-            blacklist = []
-        self.blacklist = blacklist
-    def __call__(self, other):
-        try:
-            subclass = issubclass(other, self.base_class)
-        except TypeError:
-            return False
-        if other in self.blacklist:
-            return False
-        return subclass
-
 PLUGIN_GRAPH = construct_graph(
     this_modname=__name__,
     submodnames=[name for name,include in PLUGIN_MODULES] + BUILTIN_MODULES,
diff --git a/hooke/plugin/config.py b/hooke/plugin/config.py
new file mode 100644 (file)
index 0000000..594ffa9
--- /dev/null
@@ -0,0 +1,88 @@
+"""The `config` module provides :class:`ConfigPlugin` and several
+associated :class:`hooke.command.Command`\s for handling
+:mod:`hooke.config` classes.
+"""
+
+from StringIO import StringIO
+
+from ..command import Command, Argument, Failure
+from ..interaction import ReloadUserInterfaceConfig
+from ..plugin import Builtin
+
+
+class ConfigPlugin (Builtin):
+    def __init__(self):
+        super(ConfigPlugin, self).__init__(name='config')
+
+    def commands(self):
+        return [GetCommand(), SetCommand(), PrintCommand()]
+
+
+# Define common or complicated arguments
+
+SectionArgument = Argument(
+    name='section', type='string', optional=False,
+    help="""
+Configuration section to act on.
+""".strip())
+
+OptionArgument = Argument(
+    name='option', type='string', optional=False,
+    help="""
+Configuration option to act on.
+""".strip())
+
+
+# Define commands
+
+class GetCommand (Command):
+    """Get the current value of a configuration option.
+    """
+    def __init__(self):
+        super(GetCommand, self).__init__(
+            name='get config',
+            arguments=[SectionArgument, OptionArgument],
+            help=self.__doc__)
+
+    def _run(self, hooke, inqueue, outqueue, params):
+       outqueue.put(hooke.config.get(params['section'], params['option']))
+
+class SetCommand (Command):
+    """Set the current value of a configuration option.
+
+    Currently many config options are read at startup time, and config
+    dicts are passed out to their target classes.  This means that changes
+    to the central :attr:`hooke.hooke.Hooke.config` location *will not* be
+    noticed by the target classes unless the configuration is reloaded.
+    This reloading may cause problems in poorly written UIs.
+    """
+    def __init__(self):
+        super(SetCommand, self).__init__(
+            name='set config',
+            arguments=[
+                SectionArgument, OptionArgument,
+                Argument(
+                    name='value', type='string', optional=False,
+                    help='Value to set.'),
+                ],
+            help=self.__doc__)
+
+    def _run(self, hooke, inqueue, outqueue, params):
+       hooke.config.set(params['section'], params['option'], params['value'])
+        # push config changes
+        hooke.load_plugins()
+        hooke.load_drivers()
+        # notify UI to update config
+        outqueue.put(ReloadUserInterfaceConfig(hooke.config))
+
+class PrintCommand (Command):
+    """Get the current value of a configuration option.
+    """
+    def __init__(self):
+        super(PrintCommand, self).__init__(
+            name='print config', help=self.__doc__)
+
+    def _run(self, hooke, inqueue, outqueue, params):
+        out = StringIO()
+        hooke.config.write(out)
+        outqueue.put(out.getvalue())
index 778eadfab8d3b6e27bd16f0d836e53b556701da9..0912edc7df7c6cf90bfdcbb1ae19755ef619c4bb 100644 (file)
@@ -6,7 +6,7 @@ import ConfigParser as configparser
 from .. import version
 from ..compat.odict import odict
 from ..config import Setting
-from ..plugin import IsSubclass
+from ..util.pluggable import IsSubclass
 
 
 USER_INTERFACE_MODULES = [
@@ -20,6 +20,7 @@ USER_INTERFACE_SETTING_SECTION = 'user interfaces'
 """Name of the config section which controls UI selection.
 """
 
+
 class QueueMessage (object):
     def __str__(self):
         return self.__class__.__name__
@@ -42,9 +43,9 @@ class UserInterface (object):
     """
     def __init__(self, name):
         self.name = name
-        self.setting_section = '%s user interface' % self.name
+        self.setting_section = '%s user interface' % (self.name)
         self.config = {}
-    
+
     def default_settings(self):
         """Return a list of :class:`hooke.config.Setting`\s for any
         configurable UI settings.
@@ -55,6 +56,17 @@ class UserInterface (object):
         """
         return []
 
+    def reload_config(self, config):
+        """Update the user interface for new config settings.
+
+        Should be called with the new `config` upon recipt of
+        `ReloadUserInterfaceConfig` from the `CommandEngine`.
+        """
+        try:
+            self.config = dict(config.items(self.setting_section))
+        except configparser.NoSectionError:
+            self.config = {}
+
     def run(self, hooke, ui_to_command_queue, command_to_ui_queue):
         return
 
index 8d85a957052a51737ab63c74ffe138873313da1f..6767cb4356cb34014fa6b69ac50d8b32a19a448e 100644 (file)
@@ -8,7 +8,7 @@ import readline # including readline makes cmd.Cmd.cmdloop() smarter
 import shlex
 
 from ..command import CommandExit, Exit, Command, Argument, StoreValue
-from ..interaction import Request, BooleanRequest
+from ..interaction import Request, BooleanRequest, ReloadUserInterfaceConfig
 from ..ui import UserInterface, CommandMessage
 
 
@@ -91,6 +91,9 @@ class DoCommand (CommandMethod):
                 self.cmd.stdout.write(msg.__class__.__name__+'\n')
                 self.cmd.stdout.write(str(msg).rstrip()+'\n')
                 break
+            elif isinstance(msg, ReloadUserInterfaceConfig):
+                self.cmd.ui.reload_config(msg.config)
+                continue
             elif isinstance(msg, Request):
                 self._handle_request(msg)
                 continue
@@ -311,8 +314,9 @@ typing mistakes ;).
 # Now onto the main attraction.
 
 class HookeCmd (cmd.Cmd):
-    def __init__(self, commands, inqueue, outqueue):
+    def __init__(self, ui, commands, inqueue, outqueue):
         cmd.Cmd.__init__(self)
+        self.ui = ui
         self.commands = commands
         self.local_commands = [LocalExitCommand(), LocalHelpCommand()]
         self.prompt = 'hooke> '
@@ -343,7 +347,7 @@ class CommandLine (UserInterface):
         super(CommandLine, self).__init__(name='command line')
 
     def run(self, commands, ui_to_command_queue, command_to_ui_queue):
-        cmd = HookeCmd(commands,
+        cmd = HookeCmd(self, commands,
                        inqueue=ui_to_command_queue,
                        outqueue=command_to_ui_queue)
         cmd.cmdloop(self._splash_text())
diff --git a/hooke/util/pluggable.py b/hooke/util/pluggable.py
new file mode 100644 (file)
index 0000000..4f595c0
--- /dev/null
@@ -0,0 +1,80 @@
+"""`pluggable`
+"""
+
+from ..util.graph import Node, Graph
+
+
+class IsSubclass (object):
+    """A safe subclass comparator.
+    
+    Examples
+    --------
+
+    >>> class A (object):
+    ...     pass
+    >>> class B (A):
+    ...     pass
+    >>> C = 5
+    >>> is_subclass = IsSubclass(A)
+    >>> is_subclass(A)
+    True
+    >>> is_subclass = IsSubclass(A, blacklist=[A])
+    >>> is_subclass(A)
+    False
+    >>> is_subclass(B)
+    True
+    >>> is_subclass(C)
+    False
+    """
+    def __init__(self, base_class, blacklist=None):
+        self.base_class = base_class
+        if blacklist == None:
+            blacklist = []
+        self.blacklist = blacklist
+    def __call__(self, other):
+        try:
+            subclass = issubclass(other, self.base_class)
+        except TypeError:
+            return False
+        if other in self.blacklist:
+            return False
+        return subclass
+
+def construct_graph(this_modname, submodnames, class_selector,
+                    assert_name_match=True):
+    """Search the submodules `submodnames` of a module `this_modname`
+    for class objects for which `class_selector(class)` returns
+    `True`.  These classes are instantiated, and the `instance.name`
+    is compared to the `submodname` (if `assert_name_match` is
+    `True`).
+
+    The instances are further arranged into a dependency
+    :class:`hooke.util.graph.Graph` according to their
+    `instance.dependencies()` values.  The topologically sorted graph
+    is returned.
+    """
+    instances = {}
+    for submodname in submodnames:
+        count = len([s for s in submodnames if s == submodname])
+        assert count > 0, 'No %s entries: %s' % (submodname, submodnames)
+        assert count == 1, 'Multiple (%d) %s entries: %s' \
+            % (count, submodname, submodnames)
+        this_mod = __import__(this_modname, fromlist=[submodname])
+        submod = getattr(this_mod, submodname)
+        for objname in dir(submod):
+            obj = getattr(submod, objname)
+            if class_selector(obj):
+                instance = obj()
+                if assert_name_match == True and instance.name != submodname:
+                    raise Exception(
+                        'Instance name %s does not match module name %s'
+                        % (instance.name, submodname))
+                instances[instance.name] = instance
+    nodes = {}
+    for i in instances.values():     # make nodes for each instance
+        nodes[i.name] = Node(data=i)
+    for n in nodes.values():         # fill in dependencies for each node
+        n.extend([nodes[name] for name in n.data.dependencies()])
+    graph = Graph(nodes.values())
+    graph.topological_sort()
+    return graph