refactored the bulk of plc-config-tty into plc_config.py
Marc Fiuczynski [Sat, 20 Jun 2009 18:17:54 +0000 (18:17 +0000)]
plc-config-tty
plc_config.py

index 6b0fe60..9e8ec4e 100755 (executable)
 #!/bin/env python
 
-# Interactively prompts for variable values
-# expected arguments are
-# command -d [default-xml [custom-xml [ consolidated-xml ]]]
-
-# we use 3 instances of PLCConfiguration throughout:
-# cdef : models the defaults, from plc_default.xml
-# cread : merged from plc_default & configs/site.xml
-# cwrite : site.xml + pending changes
-
 import sys
-import os
-import re
-import readline
-import traceback
-from optparse import OptionParser
-
-from plc_config import PLCConfiguration
-from plc_config import ConfigurationException
-
-####################
-release_id = "$Id$"
-release_rev = "$Revision$"
-release_url = "$URL$"
+import plc_config
 
-flavours={}
-
-def noop_validator(v):
-    pass
-
-def plc_validator(validated_variables):
+def validator(validated_variables):
     maint_user = validated_variables["PLC_API_MAINTENANCE_USER"]
     root_user = validated_variables["PLC_ROOT_USER"]
     if maint_user == root_user:
-        raise ConfigurationException("PLC_API_MAINTENANCE_USER=%s cannot be the same as PLC_ROOT_USER=%s"%(maint_user,root_user))
-
-flavours["plc"]={'service':"plc",
-                 'usual_variables':["PLC_NAME",
-                                    "PLC_SHORTNAME",
-                                    "PLC_SLICE_PREFIX",
-                                    "PLC_ROOT_USER",
-                                    "PLC_ROOT_PASSWORD",
-                                    "PLC_MAIL_ENABLED",
-                                    "PLC_MAIL_SUPPORT_ADDRESS",
-                                    "PLC_DB_HOST",
-                                    "PLC_API_HOST",
-                                    "PLC_WWW_HOST",
-                                    "PLC_BOOT_HOST",
-                                    "PLC_NET_DNS1",
-                                    "PLC_NET_DNS2",
-                                    ],
-                 'config_dir':"/etc/planetlab",
-                 'validate_variables':{"PLC_API":"MAINTENANCE_USER","PLC":"ROOT_USER"},
-                 'validator':plc_validator,
-                 }
-
-defined_flavour = "plc"
-
-# historically we could also configure the devel pkg....
-def init_flavour (flavour):
-    global service, usual_variables
-    
-    global defined_flavour
-    if flavours.has_key(flavour):
-        defined_flavour = flavour
-    else:
-        defined_flavour = "plc"
-
-    flav=flavours.get(flavour,flavours["plc"])
-    service=flav["service"]
-    usual_variables=flav["usual_variables"]
-    config_dir=flav["config_dir"]
-
-    global def_default_config, def_site_config, def_consolidated_config
-    def_default_config= "%s/default_config.xml" % config_dir
-    def_site_config = "%s/configs/site.xml" % config_dir
-    def_consolidated_config = "%s/%s_config.xml" % (config_dir, service)
-
-    global mainloop_usage
-    mainloop_usage= """Available commands:
- Uppercase versions give variables comments, when available
- u/U\t\t\tEdit usual variables
- w/W\t\t\tWrite / Write & reload
- r\t\t\tRestart %s service
- q\t\t\tQuit (without saving)
- h/?\t\t\tThis help
----
- l/L [<cat>|<var>]\tShow Locally modified variables/values
- s/S [<cat>|<var>]\tShow variables/values (all, in category, single)
- e/E [<cat>|<var>]\tEdit variables (all, in category, single)
----
- c\t\t\tList categories
- v/V [<cat>|<var>]List Variables (all, in category, single)
----
-Typical usage involves: u, [l,] w, r, q
-""" % service
-
-def usage ():
-    command_usage="%prog [options] [default-xml [site-xml [consolidated-xml]]]"
-    init_flavour ("plc")
-    command_usage +="""
-\t default-xml defaults to %s
-\t site-xml defaults to %s
-\t consolidated-xml defaults to %s""" % (def_default_config,def_site_config, def_consolidated_config)
-    return command_usage
-
-####################
-variable_usage= """Edit Commands :
-#\tShow variable comments
-.\tStops prompting, return to mainloop
-/\tCleans any site-defined value, reverts to default
-=\tShows default value
->\tSkips to next category
-?\tThis help
-"""
-
-####################
-def get_value (config,  category_id, variable_id):
-    (category, variable) = config.get (category_id, variable_id)
-    return variable['value']
-
-def get_current_value (cread, cwrite, category_id, variable_id):
-    # the value stored in cwrite, if present, is the one we want
-    try:
-        result=get_value (cwrite,category_id,variable_id)
-    except:
-        result=get_value (cread,category_id,variable_id)
-    return result
-
-# refrain from using plc_config's _sanitize 
-def get_varname (config,  category_id, variable_id):
-    (category, variable) = config.get (category_id, variable_id)
-    return (category_id+"_"+variable['id']).upper()
-
-# could not avoid using _sanitize here..
-def get_name_comments (config, cid, vid):
-    try:
-        (category, variable) = config.get (cid, vid)
-        (id, name, value, comments) = config._sanitize_variable (cid,variable)
-        return (name,comments)
-    except:
-        return (None,[])
-
-def print_name_comments (config, cid, vid):
-    (name,comments)=get_name_comments(config,cid,vid)
-    if name:
-        print "### %s" % name
-    if comments:
-        for line in comments:
-            print "# %s" % line
-    else:
-        print "!!! No comment associated to %s_%s" % (cid,vid)
-
-####################
-def list_categories (config):
-    result=[]
-    for (category_id, (category, variables)) in config.variables().iteritems():
-        result += [category_id]
-    return result
-
-def print_categories (config):
-    print "Known categories"
-    for cid in list_categories(config):
-        print "%s" % (cid.upper())
-
-####################
-def list_category (config, cid):
-    result=[]
-    for (category_id, (category, variables)) in config.variables().iteritems():
-        if (cid == category_id):
-            for variable in variables.values():
-                result += ["%s_%s" %(cid,variable['id'])]
-    return result
-    
-def print_category (config, cid, show_comments=True):
-    cid=cid.lower()
-    CID=cid.upper()
-    vids=list_category(config,cid)
-    if (len(vids) == 0):
-        print "%s : no such category"%CID
-    else:
-        print "Category %s contains" %(CID)
-        for vid in vids:
-            print vid.upper()
-
-####################
-def consolidate (default_config, site_config, consolidated_config):
-    global service
-    try:
-        conso = PLCConfiguration (default_config)
-        conso.load (site_config)
-        conso.save (consolidated_config)
-    except Exception, inst:
-        print "Could not consolidate, %s" % (str(inst))
-        return
-    print ("Merged\n\t%s\nand\t%s\ninto\t%s"%(default_config,site_config,
-                                              consolidated_config))
-
-def reload_service ():
-    global service
-    os.system("set -x ; service %s reload" % service)
-        
-####################
-def restart_service ():
-    global service
-    print ("==================== Stopping %s" % service)
-    os.system("service %s stop" % service)
-    print ("==================== Starting %s" % service)
-    os.system("service %s start" % service)
-
-####################
-def prompt_variable (cdef, cread, cwrite, category, variable,
-                     show_comments, support_next=False):
-
-    assert category.has_key('id')
-    assert variable.has_key('id')
-
-    category_id = category ['id']
-    variable_id = variable['id']
-
-    while True:
-        default_value = get_value(cdef,category_id,variable_id)
-        current_value = get_current_value(cread,cwrite,category_id, variable_id)
-        varname = get_varname (cread,category_id, variable_id)
-        
-        if show_comments :
-            print_name_comments (cdef, category_id, variable_id)
-        prompt = "== %s : [%s] " % (varname,current_value)
-        try:
-            answer = raw_input(prompt).strip()
-        except EOFError :
-            raise Exception ('BailOut')
-        except KeyboardInterrupt:
-            print "\n"
-            raise Exception ('BailOut')
-
-        # no change
-        if (answer == "") or (answer == current_value):
-            return None
-        elif (answer == "."):
-            raise Exception ('BailOut')
-        elif (answer == "#"):
-            print_name_comments(cread,category_id,variable_id)
-        elif (answer == "?"):
-            print variable_usage.strip()
-        elif (answer == "="):
-            print ("%s defaults to %s" %(varname,default_value))
-        # revert to default : remove from cwrite (i.e. site-config)
-        elif (answer == "/"):
-            cwrite.delete(category_id,variable_id)
-            print ("%s reverted to %s" %(varname,default_value))
-            return
-        elif (answer == ">"):
-            if support_next:
-                raise Exception ('NextCategory')
-            else:
-                print "No support for next category"
-        else:
-            variable['value'] = answer
-            cwrite.set(category,variable)
-            return
-
-def prompt_variables_all (cdef, cread, cwrite, show_comments):
-    try:
-        for (category_id, (category, variables)) in cread.variables().iteritems():
-            print ("========== Category = %s" % category_id.upper())
-            for variable in variables.values():
-                try:
-                    newvar = prompt_variable (cdef, cread, cwrite, category, variable,
-                                              show_comments, True)
-                except Exception, inst:
-                    if (str(inst) == 'NextCategory'): break
-                    else: raise
-                    
-    except Exception, inst:
-        if (str(inst) == 'BailOut'): return
-        else: raise
-
-def prompt_variables_category (cdef, cread, cwrite, cid, show_comments):
-    cid=cid.lower()
-    CID=cid.upper()
-    try:
-        print ("========== Category = %s" % CID)
-        for vid in list_category(cdef,cid):
-            (category,variable) = cdef.locate_varname(vid.upper())
-            newvar = prompt_variable (cdef, cread, cwrite, category, variable,
-                                      show_comments, False)
-    except Exception, inst:
-        if (str(inst) == 'BailOut'): return
-        else: raise
-
-####################
-def show_variable (cdef, cread, cwrite,
-                   category, variable,show_value,show_comments):
-    assert category.has_key('id')
-    assert variable.has_key('id')
-
-    category_id = category ['id']
-    variable_id = variable['id']
-
-    default_value = get_value(cdef,category_id,variable_id)
-    current_value = get_current_value(cread,cwrite,category_id,variable_id)
-    varname = get_varname (cread,category_id, variable_id)
-    if show_comments :
-        print_name_comments (cdef, category_id, variable_id)
-    if show_value:
-        print "%s = %s" % (varname,current_value)
-    else:
-        print "%s" % (varname)
-
-def show_variables_all (cdef, cread, cwrite, show_value, show_comments):
-    for (category_id, (category, variables)) in cread.variables().iteritems():
-        print ("========== Category = %s" % category_id.upper())
-        for variable in variables.values():
-            show_variable (cdef, cread, cwrite,
-                           category, variable,show_value,show_comments)
-
-def show_variables_category (cdef, cread, cwrite, cid, show_value,show_comments):
-    cid=cid.lower()
-    CID=cid.upper()
-    print ("========== Category = %s" % CID)
-    for vid in list_category(cdef,cid):
-        (category,variable) = cdef.locate_varname(vid.upper())
-        show_variable (cdef, cread, cwrite, category, variable,
-                       show_value,show_comments)
-
-####################
-re_mainloop_0arg="^(?P<command>[uUwrRqlLsSeEcvVhH\?])[ \t]*$"
-re_mainloop_1arg="^(?P<command>[sSeEvV])[ \t]+(?P<arg>\w+)$"
-matcher_mainloop_0arg=re.compile(re_mainloop_0arg)
-matcher_mainloop_1arg=re.compile(re_mainloop_1arg)
-
-def mainloop (cdef, cread, cwrite, default_config, site_config, consolidated_config):
-    global service
-    while True:
-        try:
-            answer = raw_input("Enter command (u for usual changes, w to save, ? for help) ").strip()
-        except EOFError:
-            answer =""
-        except KeyboardInterrupt:
-            print "\nBye"
-            sys.exit()
-
-        if (answer == "") or (answer in "?hH"):
-            print mainloop_usage
-            continue
-        groups_parse = matcher_mainloop_0arg.match(answer)
-        command=None
-        if (groups_parse):
-            command = groups_parse.group('command')
-            arg=None
-        else:
-            groups_parse = matcher_mainloop_1arg.match(answer)
-            if (groups_parse):
-                command = groups_parse.group('command')
-                arg=groups_parse.group('arg')
-        if not command:
-            print ("Unknown command >%s< -- use h for help" % answer)
-            continue
-
-        show_comments=command.isupper()
-        command=command.lower()
-
-        mode='ALL'
-        if arg:
-            mode=None
-            arg=arg.lower()
-            variables=list_category (cdef,arg)
-            if len(variables):
-                # category_id as the category name
-                # variables as the list of variable names
-                mode='CATEGORY'
-                category_id=arg
-            arg=arg.upper()
-            (category,variable)=cdef.locate_varname(arg)
-            if variable:
-                # category/variable as output by locate_varname
-                mode='VARIABLE'
-            if not mode:
-                print "%s: no such category or variable" % arg
-                continue
-
-        if (command in "qQ"):
-            # todo check confirmation
-            return
-        elif (command == "w"):
-            global defined_flavour
-            try:
-                # Confirm that various constraints are met before saving file.
-                validate_variables = flavours[defined_flavour].get('validate_variables',{})
-                validated_variables = cwrite.verify(cdef, cread, validate_variables)
-                validator = flavours[defined_flavour].get('validator',noop_validator)
-                validator(validated_variables)
-                cwrite.save(site_config)
-            except ConfigurationException, e:
-                print "Save failed due to a configuration exception: %s" % e
-                break;
-            except:
-                print traceback.print_exc()
-                print ("Could not save -- fix write access on %s" % site_config)
-                break
-            print ("Wrote %s" % site_config)
-            consolidate(default_config, site_config, consolidated_config)
-            print ("You might want to type 'r' (restart %s), 'R' (reload %s) or 'q' (quit)" % \
-                   (service,service))
-        elif (command == "u"):
-            try:
-                for varname in usual_variables:
-                    (category,variable) = cdef.locate_varname(varname)
-                    prompt_variable(cdef, cread, cwrite, category, variable, False)
-            except Exception, inst:
-                if (str(inst) != 'BailOut'):
-                    raise
-        elif (command == "r"):
-            restart_service()
-        elif (command == "R"):
-            reload_service()
-        elif (command == "c"):
-            print_categories(cread)
-        elif (command in "eE"):
-            if mode == 'ALL':
-                prompt_variables_all(cdef, cread, cwrite,show_comments)
-            elif mode == 'CATEGORY':
-                prompt_variables_category(cdef,cread,cwrite,category_id,show_comments)
-            elif mode == 'VARIABLE':
-                try:
-                    prompt_variable (cdef,cread,cwrite,category,variable,
-                                     show_comments,False)
-                except Exception, inst:
-                    if (str(inst) != 'BailOut'):
-                        raise
-        elif (command in "vVsSlL"):
-            show_value=(command in "sSlL")
-            (c1,c2,c3) = (cdef, cread, cwrite)
-            if (command in "lL"):
-                (c1,c2,c3) = (cwrite,cwrite,cwrite)
-            if mode == 'ALL':
-                show_variables_all(c1,c2,c3,show_value,show_comments)
-            elif mode == 'CATEGORY':
-                show_variables_category(c1,c2,c3,category_id,show_value,show_comments)
-            elif mode == 'VARIABLE':
-                show_variable (c1,c2,c3,category,variable,show_value,show_comments)
-        else:
-            print ("Unknown command >%s< -- use h for help" % answer)
-
-####################
-# creates directory for file if not yet existing
-def check_dir (config_file):
-    dirname = os.path.dirname (config_file)
-    if (not os.path.exists (dirname)):
-        try:
-            os.makedirs(dirname,0755)
-        except OSError, e:
-            print "Cannot create dir %s due to %s - exiting" % (dirname,e)
-            sys.exit(1)
-            
-        if (not os.path.exists (dirname)):
-            print "Cannot create dir %s - exiting" % dirname
-            sys.exit(1)
-        else:
-            print "Created directory %s" % dirname
-                
-####################
-def main ():
+        errStr="PLC_API_MAINTENANCE_USER=%s cannot be the same as PLC_ROOT_USER=%s"%(maint_user,root_user)
+        raise plc_config.ConfigurationException(errStr)
+
+usual_variables = [ \
+    "PLC_NAME",
+    "PLC_SHORTNAME",
+    "PLC_SLICE_PREFIX",
+    "PLC_ROOT_USER",
+    "PLC_ROOT_PASSWORD",
+    "PLC_MAIL_ENABLED",
+    "PLC_MAIL_SUPPORT_ADDRESS",
+    "PLC_DB_HOST",
+    "PLC_API_HOST",
+    "PLC_WWW_HOST",
+    "PLC_BOOT_HOST",
+    "PLC_NET_DNS1",
+    "PLC_NET_DNS2",
+    ]
+
+configuration={ \
+    'name':'plc',
+    'service':"plc",
+    'usual_variables':usual_variables,
+    'config_dir':"/etc/planetlab",
+    'validate_variables':{"PLC_API":"MAINTENANCE_USER","PLC":"ROOT_USER"},
+    'validator':validator,
+    }
 
+if __name__ == '__main__':
     command=sys.argv[0]
     argv = sys.argv[1:]
-    save = True
-    parser = OptionParser(usage=usage(), version="%prog 1.0" + release_rev + release_url )
-    parser.set_defaults(flavour="plc",
-                        config="flavour.config",
-                        config_dir=None,
-                        service=None,
-                        usual_variables=[])
-    parser.add_option("","--configdir",dest="config_dir",help="specify configuration directory")
-    parser.add_option("","--service",dest="service",help="specify /etc/init.d style service name")
-    parser.add_option("","--usual_variable",dest="usual_variables",action="append", help="add a usual variable")
-    parser.add_option("","--flavour",dest="flavour", help="Sets the configuration flavour")
-
-    (config,args) = parser.parse_args()
-    if len(args)>3:
-        parser.error("too many arguments")
-
-    if config.flavour not in flavours:
-        if config.service==None:
-            parser.error("unknown flavour '%s'" % config.flavour)
-        else:
-            flavours[config.flavour]={}
-            flavour=flavours[config.flavour]
-            flavour['service']=config.service
-            flavour['usual_variables']=config.usual_variables
-            if config.config_dir==None:
-                flavour['config_dir']="/etc/%s"%config.service
-            else:
-                flavour['config_dir']=config.config_dir
-    else:
-        flavour=flavours[config.flavour]
-
-        # in case the config dir should be something other than /etc/planetlab
-        if config.config_dir <> None:
-            flavour['config_dir']=config.config_dir
-
-        # add in new usual_variables defined on the command line
-        for usual_variable in config.usual_variables:
-            if usual_variable not in flavour['usual_variables']:
-                flavour['usual_variables'].append(usual_variable)
-
-    # intialize flavour
-    init_flavour(config.flavour)
-
-    (default_config,site_config,consolidated_config) = (def_default_config, def_site_config, def_consolidated_config)
-    if len(args) >= 1:
-        default_config=args[0]
-    if len(args) >= 2:
-        site_config=args[1]
-    if len(args) == 3:
-        consolidated_config=args[2]
-
-    for c in (default_config,site_config,consolidated_config):
-        check_dir (c)
-
-    try:
-        # the default settings only - read only
-        cdef = PLCConfiguration(default_config)
-
-        # in effect : default settings + local settings - read only
-        cread = PLCConfiguration(default_config)
-
-    except ConfigurationException, e:
-        print ("Error %s in default config file %s" %(e,default_config))
-        return 1
-    except:
-        print traceback.print_exc()
-        print ("default config files %s not found, is myplc installed ?" % default_config)
-        return 1
-
-
-    # local settings only, will be modified & saved
-    cwrite=PLCConfiguration()
-    
-    try:
-        cread.load(site_config)
-        cwrite.load(site_config)
-    except:
-        cwrite = PLCConfiguration()
-
-    mainloop (cdef, cread, cwrite, default_config, site_config, consolidated_config)
-    return 0
-
-if __name__ == '__main__':
-    main()
+    plc_config.main(command,argv,configuration)
index 45805b9..cedd66c 100644 (file)
 # $Id$
 #
 
-import xml.dom.minidom
-from xml.parsers.expat import ExpatError
-from StringIO import StringIO
-import time
-import re
-import textwrap
 import codecs
 import os
+import re
+import readline
+import sys
+import textwrap
+import time
+import traceback
 import types
-
+import xml.dom.minidom
+from xml.parsers.expat import ExpatError
+from StringIO import StringIO
+from optparse import OptionParser
 
 class ConfigurationException(Exception): pass
 
@@ -841,6 +844,491 @@ class TrimTextElement(xml.dom.minidom.Element):
         writer.write(newl)
 
 
+####################
+# GLOBAL VARIABLES
+#
+release_id = "$Id:$"
+release_rev = "$Revision:$"
+release_url = "$URL:$"
+
+g_configuration=None
+usual_variables=None
+config_dir=None
+service=None
+
+def noop_validator(validated_variables):
+    pass
+
+
+# historically we could also configure the devel pkg....
+def init_configuration ():
+    global g_configuration
+    global usual_variables, config_dir, service
+
+    usual_variables=g_configuration["usual_variables"]
+    config_dir=g_configuration["config_dir"]
+    service=g_configuration["service"]
+
+    global def_default_config, def_site_config, def_consolidated_config
+    def_default_config= "%s/default_config.xml" % config_dir
+    def_site_config = "%s/configs/site.xml" % config_dir
+    def_consolidated_config = "%s/%s_config.xml" % (config_dir, service)
+
+    global mainloop_usage
+    mainloop_usage= """Available commands:
+ Uppercase versions give variables comments, when available
+ u/U\t\t\tEdit usual variables
+ w/W\t\t\tWrite / Write & reload
+ r\t\t\tRestart %s service
+ q\t\t\tQuit (without saving)
+ h/?\t\t\tThis help
+---
+ l/L [<cat>|<var>]\tShow Locally modified variables/values
+ s/S [<cat>|<var>]\tShow variables/values (all, in category, single)
+ e/E [<cat>|<var>]\tEdit variables (all, in category, single)
+---
+ c\t\t\tList categories
+ v/V [<cat>|<var>]List Variables (all, in category, single)
+---
+Typical usage involves: u, [l,] w, r, q
+""" % service
+
+def usage ():
+    command_usage="%prog [options] [default-xml [site-xml [consolidated-xml]]]"
+    init_configuration ()
+    command_usage +="""
+\t default-xml defaults to %s
+\t site-xml defaults to %s
+\t consolidated-xml defaults to %s""" % (def_default_config,def_site_config, def_consolidated_config)
+    return command_usage
+
+####################
+variable_usage= """Edit Commands :
+#\tShow variable comments
+.\tStops prompting, return to mainloop
+/\tCleans any site-defined value, reverts to default
+=\tShows default value
+>\tSkips to next category
+?\tThis help
+"""
+
+####################
+def get_value (config,  category_id, variable_id):
+    (category, variable) = config.get (category_id, variable_id)
+    return variable['value']
+
+def get_current_value (cread, cwrite, category_id, variable_id):
+    # the value stored in cwrite, if present, is the one we want
+    try:
+        result=get_value (cwrite,category_id,variable_id)
+    except:
+        result=get_value (cread,category_id,variable_id)
+    return result
+
+# refrain from using plc_config's _sanitize 
+def get_varname (config,  category_id, variable_id):
+    (category, variable) = config.get (category_id, variable_id)
+    return (category_id+"_"+variable['id']).upper()
+
+# could not avoid using _sanitize here..
+def get_name_comments (config, cid, vid):
+    try:
+        (category, variable) = config.get (cid, vid)
+        (id, name, value, comments) = config._sanitize_variable (cid,variable)
+        return (name,comments)
+    except:
+        return (None,[])
+
+def print_name_comments (config, cid, vid):
+    (name,comments)=get_name_comments(config,cid,vid)
+    if name:
+        print "### %s" % name
+    if comments:
+        for line in comments:
+            print "# %s" % line
+    else:
+        print "!!! No comment associated to %s_%s" % (cid,vid)
+
+####################
+def list_categories (config):
+    result=[]
+    for (category_id, (category, variables)) in config.variables().iteritems():
+        result += [category_id]
+    return result
+
+def print_categories (config):
+    print "Known categories"
+    for cid in list_categories(config):
+        print "%s" % (cid.upper())
+
+####################
+def list_category (config, cid):
+    result=[]
+    for (category_id, (category, variables)) in config.variables().iteritems():
+        if (cid == category_id):
+            for variable in variables.values():
+                result += ["%s_%s" %(cid,variable['id'])]
+    return result
+    
+def print_category (config, cid, show_comments=True):
+    cid=cid.lower()
+    CID=cid.upper()
+    vids=list_category(config,cid)
+    if (len(vids) == 0):
+        print "%s : no such category"%CID
+    else:
+        print "Category %s contains" %(CID)
+        for vid in vids:
+            print vid.upper()
+
+####################
+def consolidate (default_config, site_config, consolidated_config):
+    global service
+    try:
+        conso = PLCConfiguration (default_config)
+        conso.load (site_config)
+        conso.save (consolidated_config)
+    except Exception, inst:
+        print "Could not consolidate, %s" % (str(inst))
+        return
+    print ("Merged\n\t%s\nand\t%s\ninto\t%s"%(default_config,site_config,
+                                              consolidated_config))
+
+def reload_service ():
+    global service
+    os.system("set -x ; service %s reload" % service)
+        
+####################
+def restart_service ():
+    global service
+    print ("==================== Stopping %s" % service)
+    os.system("service %s stop" % service)
+    print ("==================== Starting %s" % service)
+    os.system("service %s start" % service)
+
+####################
+def prompt_variable (cdef, cread, cwrite, category, variable,
+                     show_comments, support_next=False):
+
+    assert category.has_key('id')
+    assert variable.has_key('id')
+
+    category_id = category ['id']
+    variable_id = variable['id']
+
+    while True:
+        default_value = get_value(cdef,category_id,variable_id)
+        current_value = get_current_value(cread,cwrite,category_id, variable_id)
+        varname = get_varname (cread,category_id, variable_id)
+        
+        if show_comments :
+            print_name_comments (cdef, category_id, variable_id)
+        prompt = "== %s : [%s] " % (varname,current_value)
+        try:
+            answer = raw_input(prompt).strip()
+        except EOFError :
+            raise Exception ('BailOut')
+        except KeyboardInterrupt:
+            print "\n"
+            raise Exception ('BailOut')
+
+        # no change
+        if (answer == "") or (answer == current_value):
+            return None
+        elif (answer == "."):
+            raise Exception ('BailOut')
+        elif (answer == "#"):
+            print_name_comments(cread,category_id,variable_id)
+        elif (answer == "?"):
+            print variable_usage.strip()
+        elif (answer == "="):
+            print ("%s defaults to %s" %(varname,default_value))
+        # revert to default : remove from cwrite (i.e. site-config)
+        elif (answer == "/"):
+            cwrite.delete(category_id,variable_id)
+            print ("%s reverted to %s" %(varname,default_value))
+            return
+        elif (answer == ">"):
+            if support_next:
+                raise Exception ('NextCategory')
+            else:
+                print "No support for next category"
+        else:
+            variable['value'] = answer
+            cwrite.set(category,variable)
+            return
+
+def prompt_variables_all (cdef, cread, cwrite, show_comments):
+    try:
+        for (category_id, (category, variables)) in cread.variables().iteritems():
+            print ("========== Category = %s" % category_id.upper())
+            for variable in variables.values():
+                try:
+                    newvar = prompt_variable (cdef, cread, cwrite, category, variable,
+                                              show_comments, True)
+                except Exception, inst:
+                    if (str(inst) == 'NextCategory'): break
+                    else: raise
+                    
+    except Exception, inst:
+        if (str(inst) == 'BailOut'): return
+        else: raise
+
+def prompt_variables_category (cdef, cread, cwrite, cid, show_comments):
+    cid=cid.lower()
+    CID=cid.upper()
+    try:
+        print ("========== Category = %s" % CID)
+        for vid in list_category(cdef,cid):
+            (category,variable) = cdef.locate_varname(vid.upper())
+            newvar = prompt_variable (cdef, cread, cwrite, category, variable,
+                                      show_comments, False)
+    except Exception, inst:
+        if (str(inst) == 'BailOut'): return
+        else: raise
+
+####################
+def show_variable (cdef, cread, cwrite,
+                   category, variable,show_value,show_comments):
+    assert category.has_key('id')
+    assert variable.has_key('id')
+
+    category_id = category ['id']
+    variable_id = variable['id']
+
+    default_value = get_value(cdef,category_id,variable_id)
+    current_value = get_current_value(cread,cwrite,category_id,variable_id)
+    varname = get_varname (cread,category_id, variable_id)
+    if show_comments :
+        print_name_comments (cdef, category_id, variable_id)
+    if show_value:
+        print "%s = %s" % (varname,current_value)
+    else:
+        print "%s" % (varname)
+
+def show_variables_all (cdef, cread, cwrite, show_value, show_comments):
+    for (category_id, (category, variables)) in cread.variables().iteritems():
+        print ("========== Category = %s" % category_id.upper())
+        for variable in variables.values():
+            show_variable (cdef, cread, cwrite,
+                           category, variable,show_value,show_comments)
+
+def show_variables_category (cdef, cread, cwrite, cid, show_value,show_comments):
+    cid=cid.lower()
+    CID=cid.upper()
+    print ("========== Category = %s" % CID)
+    for vid in list_category(cdef,cid):
+        (category,variable) = cdef.locate_varname(vid.upper())
+        show_variable (cdef, cread, cwrite, category, variable,
+                       show_value,show_comments)
+
+####################
+re_mainloop_0arg="^(?P<command>[uUwrRqlLsSeEcvVhH\?])[ \t]*$"
+re_mainloop_1arg="^(?P<command>[sSeEvV])[ \t]+(?P<arg>\w+)$"
+matcher_mainloop_0arg=re.compile(re_mainloop_0arg)
+matcher_mainloop_1arg=re.compile(re_mainloop_1arg)
+
+def mainloop (cdef, cread, cwrite, default_config, site_config, consolidated_config):
+    global service
+    while True:
+        try:
+            answer = raw_input("Enter command (u for usual changes, w to save, ? for help) ").strip()
+        except EOFError:
+            answer =""
+        except KeyboardInterrupt:
+            print "\nBye"
+            sys.exit()
+
+        if (answer == "") or (answer in "?hH"):
+            print mainloop_usage
+            continue
+        groups_parse = matcher_mainloop_0arg.match(answer)
+        command=None
+        if (groups_parse):
+            command = groups_parse.group('command')
+            arg=None
+        else:
+            groups_parse = matcher_mainloop_1arg.match(answer)
+            if (groups_parse):
+                command = groups_parse.group('command')
+                arg=groups_parse.group('arg')
+        if not command:
+            print ("Unknown command >%s< -- use h for help" % answer)
+            continue
+
+        show_comments=command.isupper()
+        command=command.lower()
+
+        mode='ALL'
+        if arg:
+            mode=None
+            arg=arg.lower()
+            variables=list_category (cdef,arg)
+            if len(variables):
+                # category_id as the category name
+                # variables as the list of variable names
+                mode='CATEGORY'
+                category_id=arg
+            arg=arg.upper()
+            (category,variable)=cdef.locate_varname(arg)
+            if variable:
+                # category/variable as output by locate_varname
+                mode='VARIABLE'
+            if not mode:
+                print "%s: no such category or variable" % arg
+                continue
+
+        if (command in "qQ"):
+            # todo check confirmation
+            return
+        elif (command == "w"):
+            try:
+                # Confirm that various constraints are met before saving file.
+                validate_variables = g_configuration.get('validate_variables',{})
+                validated_variables = cwrite.verify(cdef, cread, validate_variables)
+                validator = g_configuration.get('validator',noop_validator)
+                validator(validated_variables)
+                cwrite.save(site_config)
+            except ConfigurationException, e:
+                print "Save failed due to a configuration exception: %s" % e
+                break;
+            except:
+                print traceback.print_exc()
+                print ("Could not save -- fix write access on %s" % site_config)
+                break
+            print ("Wrote %s" % site_config)
+            consolidate(default_config, site_config, consolidated_config)
+            print ("You might want to type 'r' (restart %s), 'R' (reload %s) or 'q' (quit)" % \
+                   (service,service))
+        elif (command == "u"):
+            global usual_variables
+            try:
+                for varname in usual_variables:
+                    (category,variable) = cdef.locate_varname(varname)
+                    if not (category is None and variable is None):
+                        prompt_variable(cdef, cread, cwrite, category, variable, False)
+            except Exception, inst:
+                if (str(inst) != 'BailOut'):
+                    raise
+        elif (command == "r"):
+            restart_service()
+        elif (command == "R"):
+            reload_service()
+        elif (command == "c"):
+            print_categories(cread)
+        elif (command in "eE"):
+            if mode == 'ALL':
+                prompt_variables_all(cdef, cread, cwrite,show_comments)
+            elif mode == 'CATEGORY':
+                prompt_variables_category(cdef,cread,cwrite,category_id,show_comments)
+            elif mode == 'VARIABLE':
+                try:
+                    prompt_variable (cdef,cread,cwrite,category,variable,
+                                     show_comments,False)
+                except Exception, inst:
+                    if (str(inst) != 'BailOut'):
+                        raise
+        elif (command in "vVsSlL"):
+            show_value=(command in "sSlL")
+            (c1,c2,c3) = (cdef, cread, cwrite)
+            if (command in "lL"):
+                (c1,c2,c3) = (cwrite,cwrite,cwrite)
+            if mode == 'ALL':
+                show_variables_all(c1,c2,c3,show_value,show_comments)
+            elif mode == 'CATEGORY':
+                show_variables_category(c1,c2,c3,category_id,show_value,show_comments)
+            elif mode == 'VARIABLE':
+                show_variable (c1,c2,c3,category,variable,show_value,show_comments)
+        else:
+            print ("Unknown command >%s< -- use h for help" % answer)
+
+####################
+# creates directory for file if not yet existing
+def check_dir (config_file):
+    dirname = os.path.dirname (config_file)
+    if (not os.path.exists (dirname)):
+        try:
+            os.makedirs(dirname,0755)
+        except OSError, e:
+            print "Cannot create dir %s due to %s - exiting" % (dirname,e)
+            sys.exit(1)
+            
+        if (not os.path.exists (dirname)):
+            print "Cannot create dir %s - exiting" % dirname
+            sys.exit(1)
+        else:
+            print "Created directory %s" % dirname
+                
+####################
+def optParserSetup(configuration):
+    parser = OptionParser(usage=usage(), version="%prog 1.0" + release_rev + release_url )
+    parser.set_defaults(config_dir=configuration['config_dir'],
+                        service=configuration['service'],
+                        usual_variables=configuration['usual_variables'])
+    parser.add_option("","--configdir",dest="config_dir",help="specify configuration directory")
+    parser.add_option("","--service",dest="service",help="specify /etc/init.d style service name")
+    parser.add_option("","--usual_variable",dest="usual_variables",action="append", help="add a usual variable")
+    return parser
+
+def main(command,argv,configuration):
+    global g_configuration
+    g_configuration=configuration
+
+    parser = optParserSetup(configuration)
+    (config,args) = parser.parse_args()
+    if len(args)>3:
+        parser.error("too many arguments")
+
+    configuration['service']=config.service
+    configuration['usual_variables']=config.usual_variables
+    configuration['config_dir']=config.config_dir
+    # add in new usual_variables defined on the command line
+    for usual_variable in config.usual_variables:
+        if usual_variable not in configuration['usual_variables']:
+            configuration['usual_variables'].append(usual_variable)
+
+    # intialize configuration
+    init_configuration()
+
+    (default_config,site_config,consolidated_config) = (def_default_config, def_site_config, def_consolidated_config)
+    if len(args) >= 1:
+        default_config=args[0]
+    if len(args) >= 2:
+        site_config=args[1]
+    if len(args) == 3:
+        consolidated_config=args[2]
+
+    for c in (default_config,site_config,consolidated_config):
+        check_dir (c)
+
+    try:
+        # the default settings only - read only
+        cdef = PLCConfiguration(default_config)
+
+        # in effect : default settings + local settings - read only
+        cread = PLCConfiguration(default_config)
+
+    except ConfigurationException, e:
+        print ("Error %s in default config file %s" %(e,default_config))
+        return 1
+    except:
+        print traceback.print_exc()
+        print ("default config files %s not found, is myplc installed ?" % default_config)
+        return 1
+
+
+    # local settings only, will be modified & saved
+    cwrite=PLCConfiguration()
+    
+    try:
+        cread.load(site_config)
+        cwrite.load(site_config)
+    except:
+        cwrite = PLCConfiguration()
+
+    mainloop (cdef, cread, cwrite, default_config, site_config, consolidated_config)
+    return 0
+
 if __name__ == '__main__':
     import sys
     if len(sys.argv) > 1 and sys.argv[1] in ['build', 'install', 'uninstall']: