3 # Merge PlanetLab Central (PLC) configuration files into a variety of
4 # output formats. These files represent the global configuration for a
7 # Mark Huang <mlhuang@cs.princeton.edu>
8 # Copyright (C) 2006 The Trustees of Princeton University
21 import xml.dom.minidom
22 from xml.parsers.expat import ExpatError
23 from StringIO import StringIO
24 from optparse import OptionParser
26 class ConfigurationException(Exception): pass
28 class PLCConfiguration:
30 Configuration file store. Optionally instantiate with a file path
33 plc = PLCConfiguration()
34 plc = PLCConfiguration(fileobj)
35 plc = PLCConfiguration("/etc/planetlab/plc_config.xml")
37 You may load() additional files later, which will be merged into
38 the current configuration:
40 plc.load("/etc/planetlab/local.xml")
42 You may also save() the configuration. If a file path or object is
43 not specified, the configuration will be written to the file path
44 or object that was first loaded.
47 plc.save("/etc/planetlab/plc_config.xml")
50 def __init__(self, file = None):
51 impl = xml.dom.minidom.getDOMImplementation()
52 self._dom = impl.createDocument(None, "configuration", None)
61 def _get_text(self, node):
63 Get the text of a text node.
66 if node.firstChild and \
67 node.firstChild.nodeType == node.TEXT_NODE:
68 if node.firstChild.data is None:
69 # Interpret simple presence of node as "", not NULL
72 return node.firstChild.data
77 def _get_text_of_child(self, parent, name):
79 Get the text of a (direct) child text node.
82 for node in parent.childNodes:
83 if node.nodeType == node.ELEMENT_NODE and \
85 return self._get_text(node)
90 def _set_text(self, node, data):
92 Set the text of a text node.
95 if node.firstChild and \
96 node.firstChild.nodeType == node.TEXT_NODE:
98 node.removeChild(node.firstChild)
100 node.firstChild.data = data
101 elif data is not None:
104 node.appendChild(text)
107 def _set_text_of_child(self, parent, name, data):
109 Set the text of a (direct) child text node.
112 for node in parent.childNodes:
113 if node.nodeType == node.ELEMENT_NODE and \
114 node.tagName == name:
115 self._set_text(node, data)
118 child = TrimTextElement(name)
119 self._set_text(child, data)
120 parent.appendChild(child)
123 def _category_element_to_dict(self, category_element):
125 Turn a <category> element into a dictionary of its attributes
126 and child text nodes.
130 category['id'] = category_element.getAttribute('id').lower()
131 for node in category_element.childNodes:
132 if node.nodeType == node.ELEMENT_NODE and \
133 node.tagName in ['name', 'description']:
134 category[node.tagName] = self._get_text_of_child(category_element, node.tagName)
135 category['element'] = category_element
140 def _variable_element_to_dict(self, variable_element):
142 Turn a <variable> element into a dictionary of its attributes
143 and child text nodes.
147 variable['id'] = variable_element.getAttribute('id').lower()
148 if variable_element.hasAttribute('type'):
149 variable['type'] = variable_element.getAttribute('type')
150 for node in variable_element.childNodes:
151 if node.nodeType == node.ELEMENT_NODE and \
152 node.tagName in ['name', 'value', 'description']:
153 variable[node.tagName] = self._get_text_of_child(variable_element, node.tagName)
154 variable['element'] = variable_element
159 def _group_element_to_dict(self, group_element):
161 Turn a <group> element into a dictionary of its attributes
162 and child text nodes.
166 for node in group_element.childNodes:
167 if node.nodeType == node.ELEMENT_NODE and \
168 node.tagName in ['id', 'name', 'default', 'description', 'uservisible']:
169 group[node.tagName] = self._get_text_of_child(group_element, node.tagName)
170 group['element'] = group_element
175 def _packagereq_element_to_dict(self, packagereq_element):
177 Turns a <packagereq> element into a dictionary of its attributes
178 and child text nodes.
182 if packagereq_element.hasAttribute('type'):
183 package['type'] = packagereq_element.getAttribute('type')
184 package['name'] = self._get_text(packagereq_element)
185 package['element'] = packagereq_element
190 def load(self, file = "/etc/planetlab/plc_config.xml"):
192 Merge file into configuration store.
196 dom = xml.dom.minidom.parse(file)
197 except ExpatError, e:
198 raise ConfigurationException, e
200 if type(file) in types.StringTypes:
201 self._files.append(os.path.abspath(file))
203 # Parse <variables> section
204 for variables_element in dom.getElementsByTagName('variables'):
205 for category_element in variables_element.getElementsByTagName('category'):
206 category = self._category_element_to_dict(category_element)
207 self.set(category, None)
209 for variablelist_element in category_element.getElementsByTagName('variablelist'):
210 for variable_element in variablelist_element.getElementsByTagName('variable'):
211 variable = self._variable_element_to_dict(variable_element)
212 self.set(category, variable)
214 # Parse <comps> section
215 for comps_element in dom.getElementsByTagName('comps'):
216 for group_element in comps_element.getElementsByTagName('group'):
217 group = self._group_element_to_dict(group_element)
218 self.add_package(group, None)
220 for packagereq_element in group_element.getElementsByTagName('packagereq'):
221 package = self._packagereq_element_to_dict(packagereq_element)
222 self.add_package(group, package)
225 def save(self, file = None):
227 Write configuration store to file.
232 file = self._files[0]
234 file = "/etc/planetlab/plc_config.xml"
236 if type(file) in types.StringTypes:
237 fileobj = open(file, 'w')
242 fileobj.write(self.output_xml())
247 def verify(self, default, read, verify_variables={}):
248 """ Confirm that the existing configuration is consistent
249 according to the checks below.
251 It looks for filled-in values in the order of, local object (self),
252 followed by cread (read values), and finally default values.
256 default configuration
258 list of category/variable tuples to validate in these configurations
262 dict of values for the category/variables passed in
263 If an exception is found, ConfigurationException is raised.
267 validated_variables = {}
268 for category_id, variable_id in verify_variables.iteritems():
269 category_id = category_id.lower()
270 variable_id = variable_id.lower()
271 variable_value = None
272 sources = (self, read, default)
273 for source in sources:
274 (category_value, variable_value) = source.get(category_id,variable_id)
275 if variable_value <> None:
276 entry = validated_variables.get(category_id,[])
277 entry.append(variable_value['value'])
278 validated_variables["%s_%s"%(category_id.upper(),variable_id.upper())]=entry
280 if variable_value == None:
281 raise ConfigurationException("Cannot find %s_%s)" % \
282 (category_id.upper(),
283 variable_id.upper()))
284 return validated_variables
286 def get(self, category_id, variable_id):
288 Get the specified variable in the specified category.
292 category_id = unique category identifier (e.g., 'plc_www')
293 variable_id = unique variable identifier (e.g., 'port')
297 variable = { 'id': "variable_identifier",
298 'type': "variable_type",
299 'value': "variable_value",
300 'name': "Variable name",
301 'description': "Variable description" }
304 if self._variables.has_key(category_id.lower()):
305 (category, variables) = self._variables[category_id]
306 if variables.has_key(variable_id.lower()):
307 variable = variables[variable_id]
314 return (category, variable)
317 def delete(self, category_id, variable_id):
319 Delete the specified variable from the specified category. If
320 variable_id is None, deletes all variables from the specified
321 category as well as the category itself.
325 category_id = unique category identifier (e.g., 'plc_www')
326 variable_id = unique variable identifier (e.g., 'port')
329 if self._variables.has_key(category_id.lower()):
330 (category, variables) = self._variables[category_id]
331 if variable_id is None:
332 category['element'].parentNode.removeChild(category['element'])
333 del self._variables[category_id]
334 elif variables.has_key(variable_id.lower()):
335 variable = variables[variable_id]
336 variable['element'].parentNode.removeChild(variable['element'])
337 del variables[variable_id]
340 def set(self, category, variable):
342 Add and/or update the specified variable. The 'id' fields are
343 mandatory. If a field is not specified and the category and/or
344 variable already exists, the field will not be updated. If
345 'variable' is None, only adds and/or updates the specified
350 category = { 'id': "category_identifier",
351 'name': "Category name",
352 'description': "Category description" }
354 variable = { 'id': "variable_identifier",
355 'type': "variable_type",
356 'value': "variable_value",
357 'name': "Variable name",
358 'description': "Variable description" }
361 if not category.has_key('id') or type(category['id']) not in types.StringTypes:
364 category_id = category['id'].lower()
366 if self._variables.has_key(category_id):
368 (old_category, variables) = self._variables[category_id]
370 # Merge category attributes
371 for tag in ['name', 'description']:
372 if category.has_key(tag):
373 old_category[tag] = category[tag]
374 self._set_text_of_child(old_category['element'], tag, category[tag])
376 category_element = old_category['element']
379 category_element = self._dom.createElement('category')
380 category_element.setAttribute('id', category_id)
381 for tag in ['name', 'description']:
382 if category.has_key(tag):
383 self._set_text_of_child(category_element, tag, category[tag])
385 if self._dom.documentElement.getElementsByTagName('variables'):
386 variables_element = self._dom.documentElement.getElementsByTagName('variables')[0]
388 variables_element = self._dom.createElement('variables')
389 self._dom.documentElement.appendChild(variables_element)
390 variables_element.appendChild(category_element)
393 category['element'] = category_element
395 self._variables[category_id] = (category, variables)
397 if variable is None or not variable.has_key('id') or type(variable['id']) not in types.StringTypes:
400 variable_id = variable['id'].lower()
402 if variables.has_key(variable_id):
404 old_variable = variables[variable_id]
406 # Merge variable attributes
407 for attribute in ['type']:
408 if variable.has_key(attribute):
409 old_variable[attribute] = variable[attribute]
410 old_variable['element'].setAttribute(attribute, variable[attribute])
411 for tag in ['name', 'value', 'description']:
412 if variable.has_key(tag):
413 old_variable[tag] = variable[tag]
414 self._set_text_of_child(old_variable['element'], tag, variable[tag])
417 variable_element = self._dom.createElement('variable')
418 variable_element.setAttribute('id', variable_id)
419 for attribute in ['type']:
420 if variable.has_key(attribute):
421 variable_element.setAttribute(attribute, variable[attribute])
422 for tag in ['name', 'value', 'description']:
423 if variable.has_key(tag):
424 self._set_text_of_child(variable_element, tag, variable[tag])
426 if category_element.getElementsByTagName('variablelist'):
427 variablelist_element = category_element.getElementsByTagName('variablelist')[0]
429 variablelist_element = self._dom.createElement('variablelist')
430 category_element.appendChild(variablelist_element)
431 variablelist_element.appendChild(variable_element)
434 variable['element'] = variable_element
435 variables[variable_id] = variable
438 def locate_varname (self, varname):
440 Locates category and variable from a variable's (shell) name
443 (variable, category) when found
444 (None, None) otherwise
447 for (category_id, (category, variables)) in self._variables.iteritems():
448 for variable in variables.values():
449 (id, name, value, comments) = self._sanitize_variable(category_id, variable)
451 return (category,variable)
454 def get_package(self, group_id, package_name):
456 Get the specified package in the specified package group.
460 group_id - unique group id (e.g., 'plc')
461 package_name - unique package name (e.g., 'postgresql')
465 package = { 'name': "package_name",
466 'type': "mandatory|optional" }
469 if self._packages.has_key(group_id.lower()):
470 (group, packages) = self._packages[group_id]
471 if packages.has_key(package_name):
472 package = packages[package_name]
479 return (group, package)
482 def delete_package(self, group_id, package_name):
484 Deletes the specified variable from the specified category. If
485 variable_id is None, deletes all variables from the specified
486 category as well as the category itself.
490 group_id - unique group id (e.g., 'plc')
491 package_name - unique package name (e.g., 'postgresql')
494 if self._packages.has_key(group_id):
495 (group, packages) = self._packages[group_id]
496 if package_name is None:
497 group['element'].parentNode.removeChild(group['element'])
498 del self._packages[group_id]
499 elif packages.has_key(package_name.lower()):
500 package = packages[package_name]
501 package['element'].parentNode.removeChild(package['element'])
502 del packages[package_name]
505 def add_package(self, group, package):
507 Add and/or update the specified package. The 'id' and 'name'
508 fields are mandatory. If a field is not specified and the
509 package or group already exists, the field will not be
510 updated. If package is None, only adds/or updates the
515 group = { 'id': "group_identifier",
516 'name': "Group name",
517 'default': "true|false",
518 'description': "Group description",
519 'uservisible': "true|false" }
521 package = { 'name': "package_name",
522 'type': "mandatory|optional" }
525 if not group.has_key('id'):
528 group_id = group['id']
530 if self._packages.has_key(group_id):
532 (old_group, packages) = self._packages[group_id]
534 # Merge group attributes
535 for tag in ['id', 'name', 'default', 'description', 'uservisible']:
536 if group.has_key(tag):
537 old_group[tag] = group[tag]
538 self._set_text_of_child(old_group['element'], tag, group[tag])
540 group_element = old_group['element']
543 group_element = self._dom.createElement('group')
544 for tag in ['id', 'name', 'default', 'description', 'uservisible']:
545 if group.has_key(tag):
546 self._set_text_of_child(group_element, tag, group[tag])
548 if self._dom.documentElement.getElementsByTagName('comps'):
549 comps_element = self._dom.documentElement.getElementsByTagName('comps')[0]
551 comps_element = self._dom.createElement('comps')
552 self._dom.documentElement.appendChild(comps_element)
553 comps_element.appendChild(group_element)
556 group['element'] = group_element
558 self._packages[group_id] = (group, packages)
560 if package is None or not package.has_key('name'):
563 package_name = package['name']
564 if packages.has_key(package_name):
566 old_package = packages[package_name]
568 # Merge variable attributes
569 for attribute in ['type']:
570 if package.has_key(attribute):
571 old_package[attribute] = package[attribute]
572 old_package['element'].setAttribute(attribute, package[attribute])
575 packagereq_element = TrimTextElement('packagereq')
576 self._set_text(packagereq_element, package_name)
577 for attribute in ['type']:
578 if package.has_key(attribute):
579 packagereq_element.setAttribute(attribute, package[attribute])
581 if group_element.getElementsByTagName('packagelist'):
582 packagelist_element = group_element.getElementsByTagName('packagelist')[0]
584 packagelist_element = self._dom.createElement('packagelist')
585 group_element.appendChild(packagelist_element)
586 packagelist_element.appendChild(packagereq_element)
589 package['element'] = packagereq_element
590 packages[package_name] = package
595 Return all variables.
599 variables = { 'category_id': (category, variablelist) }
601 category = { 'id': "category_identifier",
602 'name': "Category name",
603 'description': "Category description" }
605 variablelist = { 'variable_id': variable }
607 variable = { 'id': "variable_identifier",
608 'type': "variable_type",
609 'value': "variable_value",
610 'name': "Variable name",
611 'description': "Variable description" }
614 return self._variables
623 packages = { 'group_id': (group, packagelist) }
625 group = { 'id': "group_identifier",
626 'name': "Group name",
627 'default': "true|false",
628 'description': "Group description",
629 'uservisible': "true|false" }
631 packagelist = { 'package_name': package }
633 package = { 'name': "package_name",
634 'type': "mandatory|optional" }
637 return self._packages
640 def _sanitize_variable(self, category_id, variable):
641 assert variable.has_key('id')
642 # Prepend variable name with category label
643 id = category_id + "_" + variable['id']
647 if variable.has_key('type'):
648 type = variable['type']
652 if variable.has_key('name'):
653 name = variable['name']
657 if variable.has_key('value') and variable['value'] is not None:
658 value = variable['value']
659 if type == "int" or type == "double":
660 # bash, Python, and PHP do not require that numbers be quoted
662 elif type == "boolean":
663 # bash, Python, and PHP can all agree on 0 and 1
669 # bash, Python, and PHP all support strong single quoting
670 value = "'" + value.replace("'", "\\'") + "'"
674 if variable.has_key('description') and variable['description'] is not None:
675 description = variable['description']
676 # Collapse consecutive whitespace
677 description = re.sub(r'\s+', ' ', description)
678 # Wrap comments at 70 columns
679 wrapper = textwrap.TextWrapper()
680 comments = wrapper.wrap(description)
684 return (id, name, value, comments)
689 DO NOT EDIT. This file was automatically generated at
693 """ % (time.asctime(), os.linesep.join(self._files))
695 # Get rid of the surrounding newlines
696 return header.strip().split(os.linesep)
699 def output_shell(self, show_comments = True, encoding = "utf-8"):
701 Return variables as a shell script.
704 buf = codecs.lookup(encoding)[3](StringIO())
705 buf.writelines(["# " + line + os.linesep for line in self._header()])
707 for (category_id, (category, variables)) in self._variables.iteritems():
708 for variable in variables.values():
709 (id, name, value, comments) = self._sanitize_variable(category_id, variable)
711 buf.write(os.linesep)
713 buf.write("# " + name + os.linesep)
714 if comments is not None:
715 buf.writelines(["# " + line + os.linesep for line in comments])
716 # bash does not have the concept of NULL
717 if value is not None:
718 buf.write(id + "=" + value + os.linesep)
720 return buf.getvalue()
723 def output_php(self, encoding = "utf-8"):
725 Return variables as a PHP script.
728 buf = codecs.lookup(encoding)[3](StringIO())
729 buf.write("<?php" + os.linesep)
730 buf.writelines(["// " + line + os.linesep for line in self._header()])
732 for (category_id, (category, variables)) in self._variables.iteritems():
733 for variable in variables.values():
734 (id, name, value, comments) = self._sanitize_variable(category_id, variable)
735 buf.write(os.linesep)
737 buf.write("// " + name + os.linesep)
738 if comments is not None:
739 buf.writelines(["// " + line + os.linesep for line in comments])
742 buf.write("define('%s', %s);" % (id, value) + os.linesep)
744 buf.write("?>" + os.linesep)
746 return buf.getvalue()
749 def output_xml(self, encoding = "utf-8"):
751 Return variables in original XML format.
754 buf = codecs.lookup(encoding)[3](StringIO())
755 self._dom.writexml(buf, addindent = " ", indent = "", newl = "\n", encoding = encoding)
757 return buf.getvalue()
760 def output_variables(self, encoding = "utf-8"):
762 Return list of all variable names.
765 buf = codecs.lookup(encoding)[3](StringIO())
767 for (category_id, (category, variables)) in self._variables.iteritems():
768 for variable in variables.values():
769 (id, name, value, comments) = self._sanitize_variable(category_id, variable)
770 buf.write(id + os.linesep)
772 return buf.getvalue()
775 def output_packages(self, encoding = "utf-8"):
777 Return list of all packages.
780 buf = codecs.lookup(encoding)[3](StringIO())
782 for (group, packages) in self._packages.values():
783 buf.write(os.linesep.join(packages.keys()))
786 buf.write(os.linesep)
788 return buf.getvalue()
791 def output_groups(self, encoding = "utf-8"):
793 Return list of all package group names.
796 buf = codecs.lookup(encoding)[3](StringIO())
798 for (group, packages) in self._packages.values():
799 buf.write(group['name'] + os.linesep)
801 return buf.getvalue()
804 def output_comps(self, encoding = "utf-8"):
806 Return <comps> section of configuration.
809 if self._dom is None or \
810 not self._dom.getElementsByTagName("comps"):
812 comps = self._dom.getElementsByTagName("comps")[0]
814 impl = xml.dom.minidom.getDOMImplementation()
815 doc = impl.createDocument(None, "comps", None)
817 buf = codecs.lookup(encoding)[3](StringIO())
819 # Pop it off the DOM temporarily
820 parent = comps.parentNode
821 parent.removeChild(comps)
823 doc.replaceChild(comps, doc.documentElement)
824 doc.writexml(buf, encoding = encoding)
827 parent.appendChild(comps)
829 return buf.getvalue()
831 def validate_type(self, variable_type, value):
833 # ideally we should use the "validate_*" methods in PLCAPI or
834 # even declare some checks along with the default
835 # configuration (using RELAX NG?) but this shall work for now.
836 def ip_validator(val):
839 socket.inet_aton(val)
843 def email_validator(val):
844 return re.match('\A[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9._\-]+\.[a-zA-Z]+\Z', val)
846 def boolean_validator (val):
847 return val in ['true', 'false']
850 'email' : email_validator,
852 'boolean': boolean_validator,
855 # validate it if not a know type.
856 validator = validators.get(variable_type, lambda x: True)
857 return validator(value)
861 # xml.dom.minidom.Text.writexml adds surrounding whitespace to textual
862 # data when pretty-printing. Override this behavior.
863 class TrimText(xml.dom.minidom.Text):
864 def writexml(self, writer, indent="", addindent="", newl=""):
865 xml.dom.minidom.Text.writexml(self, writer, "", "", "")
868 class TrimTextElement(xml.dom.minidom.Element):
869 def writexml(self, writer, indent="", addindent="", newl=""):
871 xml.dom.minidom.Element.writexml(self, writer, "", "", "")
879 release_rev = "$Revision$"
880 release_url = "$URL$"
887 def noop_validator(validated_variables):
891 # historically we could also configure the devel pkg....
892 def init_configuration ():
893 global g_configuration
894 global usual_variables, config_dir, service
896 usual_variables=g_configuration["usual_variables"]
897 config_dir=g_configuration["config_dir"]
898 service=g_configuration["service"]
900 global def_default_config, def_site_config, def_consolidated_config
901 def_default_config= "%s/default_config.xml" % config_dir
902 def_site_config = "%s/configs/site.xml" % config_dir
903 def_consolidated_config = "%s/%s_config.xml" % (config_dir, service)
905 global mainloop_usage
906 mainloop_usage= """Available commands:
907 Uppercase versions give variables comments, when available
908 u/U\t\t\tEdit usual variables
910 r\t\t\tRestart %(service)s service
911 R\t\t\tReload %(service)s service (rebuild config files for sh, python....)
912 q\t\t\tQuit (without saving)
915 l/L [<cat>|<var>]\tShow Locally modified variables/values
916 s/S [<cat>|<var>]\tShow variables/values (all, in category, single)
917 e/E [<cat>|<var>]\tEdit variables (all, in category, single)
919 c\t\t\tList categories
920 v/V [<cat>|<var>]\tList Variables (all, in category, single)
922 Typical usage involves: u, [l,] w, r, q
926 command_usage="%prog [options] [default-xml [site-xml [consolidated-xml]]]"
927 init_configuration ()
929 \t default-xml defaults to %s
930 \t site-xml defaults to %s
931 \t consolidated-xml defaults to %s""" % (def_default_config,def_site_config, def_consolidated_config)
935 variable_usage= """Edit Commands :
936 #\tShow variable comments
937 .\tStops prompting, return to mainloop
938 /\tCleans any site-defined value, reverts to default
939 =\tShows default value
940 >\tSkips to next category
945 def get_value (config, category_id, variable_id):
946 (category, variable) = config.get (category_id, variable_id)
947 return variable['value']
949 def get_type (config, category_id, variable_id):
950 (category, variable) = config.get (category_id, variable_id)
951 return variable['type']
953 def get_current_value (cread, cwrite, category_id, variable_id):
954 # the value stored in cwrite, if present, is the one we want
956 result=get_value (cwrite,category_id,variable_id)
958 result=get_value (cread,category_id,variable_id)
961 # refrain from using plc_config's _sanitize
962 def get_varname (config, category_id, variable_id):
963 (category, variable) = config.get (category_id, variable_id)
964 return (category_id+"_"+variable['id']).upper()
966 # could not avoid using _sanitize here..
967 def get_name_comments (config, cid, vid):
969 (category, variable) = config.get (cid, vid)
970 (id, name, value, comments) = config._sanitize_variable (cid,variable)
971 return (name,comments)
975 def print_name_comments (config, cid, vid):
976 (name,comments)=get_name_comments(config,cid,vid)
978 print "### %s" % name
980 for line in comments:
983 print "!!! No comment associated to %s_%s" % (cid,vid)
986 def list_categories (config):
988 for (category_id, (category, variables)) in config.variables().iteritems():
989 result += [category_id]
992 def print_categories (config):
993 print "Known categories"
994 for cid in list_categories(config):
995 print "%s" % (cid.upper())
998 def list_category (config, cid):
1000 for (category_id, (category, variables)) in config.variables().iteritems():
1001 if (cid == category_id):
1002 for variable in variables.values():
1003 result += ["%s_%s" %(cid,variable['id'])]
1006 def print_category (config, cid, show_comments=True):
1009 vids=list_category(config,cid)
1010 if (len(vids) == 0):
1011 print "%s : no such category"%CID
1013 print "Category %s contains" %(CID)
1017 ####################
1018 def consolidate (default_config, site_config, consolidated_config):
1021 conso = PLCConfiguration (default_config)
1022 conso.load (site_config)
1023 conso.save (consolidated_config)
1024 except Exception, inst:
1025 print "Could not consolidate, %s" % (str(inst))
1027 print ("Merged\n\t%s\nand\t%s\ninto\t%s"%(default_config,site_config,
1028 consolidated_config))
1030 def reload_service ():
1032 os.system("set -x ; service %s reload" % service)
1034 ####################
1035 def restart_service ():
1037 print ("==================== Stopping %s" % service)
1038 os.system("service %s stop" % service)
1039 print ("==================== Starting %s" % service)
1040 os.system("service %s start" % service)
1042 ####################
1043 def prompt_variable (cdef, cread, cwrite, category, variable,
1044 show_comments, support_next=False):
1046 assert category.has_key('id')
1047 assert variable.has_key('id')
1049 category_id = category ['id']
1050 variable_id = variable['id']
1053 default_value = get_value(cdef,category_id,variable_id)
1054 variable_type = get_type(cdef,category_id,variable_id)
1055 current_value = get_current_value(cread,cwrite,category_id, variable_id)
1056 varname = get_varname (cread,category_id, variable_id)
1059 print_name_comments (cdef, category_id, variable_id)
1060 prompt = "== %s : [%s] " % (varname,current_value)
1062 answer = raw_input(prompt).strip()
1064 raise Exception ('BailOut')
1065 except KeyboardInterrupt:
1067 raise Exception ('BailOut')
1070 if (answer == "") or (answer == current_value):
1072 elif (answer == "."):
1073 raise Exception ('BailOut')
1074 elif (answer == "#"):
1075 print_name_comments(cread,category_id,variable_id)
1076 elif (answer == "?"):
1077 print variable_usage.strip()
1078 elif (answer == "="):
1079 print ("%s defaults to %s" %(varname,default_value))
1080 # revert to default : remove from cwrite (i.e. site-config)
1081 elif (answer == "/"):
1082 cwrite.delete(category_id,variable_id)
1083 print ("%s reverted to %s" %(varname,default_value))
1085 elif (answer == ">"):
1087 raise Exception ('NextCategory')
1089 print "No support for next category"
1091 if cdef.validate_type(variable_type, answer):
1092 variable['value'] = answer
1093 cwrite.set(category,variable)
1096 print "Not a valid value"
1098 def prompt_variables_all (cdef, cread, cwrite, show_comments):
1100 for (category_id, (category, variables)) in cread.variables().iteritems():
1101 print ("========== Category = %s" % category_id.upper())
1102 for variable in variables.values():
1104 newvar = prompt_variable (cdef, cread, cwrite, category, variable,
1105 show_comments, True)
1106 except Exception, inst:
1107 if (str(inst) == 'NextCategory'): break
1110 except Exception, inst:
1111 if (str(inst) == 'BailOut'): return
1114 def prompt_variables_category (cdef, cread, cwrite, cid, show_comments):
1118 print ("========== Category = %s" % CID)
1119 for vid in list_category(cdef,cid):
1120 (category,variable) = cdef.locate_varname(vid.upper())
1121 newvar = prompt_variable (cdef, cread, cwrite, category, variable,
1122 show_comments, False)
1123 except Exception, inst:
1124 if (str(inst) == 'BailOut'): return
1127 ####################
1128 def show_variable (cdef, cread, cwrite,
1129 category, variable,show_value,show_comments):
1130 assert category.has_key('id')
1131 assert variable.has_key('id')
1133 category_id = category ['id']
1134 variable_id = variable['id']
1136 default_value = get_value(cdef,category_id,variable_id)
1137 current_value = get_current_value(cread,cwrite,category_id,variable_id)
1138 varname = get_varname (cread,category_id, variable_id)
1140 print_name_comments (cdef, category_id, variable_id)
1142 print "%s = %s" % (varname,current_value)
1144 print "%s" % (varname)
1146 def show_variables_all (cdef, cread, cwrite, show_value, show_comments):
1147 for (category_id, (category, variables)) in cread.variables().iteritems():
1148 print ("========== Category = %s" % category_id.upper())
1149 for variable in variables.values():
1150 show_variable (cdef, cread, cwrite,
1151 category, variable,show_value,show_comments)
1153 def show_variables_category (cdef, cread, cwrite, cid, show_value,show_comments):
1156 print ("========== Category = %s" % CID)
1157 for vid in list_category(cdef,cid):
1158 (category,variable) = cdef.locate_varname(vid.upper())
1159 show_variable (cdef, cread, cwrite, category, variable,
1160 show_value,show_comments)
1162 ####################
1163 re_mainloop_0arg="^(?P<command>[uUwrRqlLsSeEcvVhH\?])[ \t]*$"
1164 re_mainloop_1arg="^(?P<command>[sSeEvV])[ \t]+(?P<arg>\w+)$"
1165 matcher_mainloop_0arg=re.compile(re_mainloop_0arg)
1166 matcher_mainloop_1arg=re.compile(re_mainloop_1arg)
1168 def mainloop (cdef, cread, cwrite, default_config, site_config, consolidated_config):
1172 answer = raw_input("Enter command (u for usual changes, w to save, ? for help) ").strip()
1175 except KeyboardInterrupt:
1179 if (answer == "") or (answer in "?hH"):
1180 print mainloop_usage
1182 groups_parse = matcher_mainloop_0arg.match(answer)
1185 command = groups_parse.group('command')
1188 groups_parse = matcher_mainloop_1arg.match(answer)
1190 command = groups_parse.group('command')
1191 arg=groups_parse.group('arg')
1193 print ("Unknown command >%s< -- use h for help" % answer)
1196 show_comments=command.isupper()
1202 variables=list_category (cdef,arg)
1204 # category_id as the category name
1205 # variables as the list of variable names
1209 (category,variable)=cdef.locate_varname(arg)
1211 # category/variable as output by locate_varname
1214 print "%s: no such category or variable" % arg
1218 # todo check confirmation
1220 elif command == "w":
1222 # Confirm that various constraints are met before saving file.
1223 validate_variables = g_configuration.get('validate_variables',{})
1224 validated_variables = cwrite.verify(cdef, cread, validate_variables)
1225 validator = g_configuration.get('validator',noop_validator)
1226 validator(validated_variables)
1227 cwrite.save(site_config)
1228 except ConfigurationException, e:
1229 print "Save failed due to a configuration exception: %s" % e
1232 print traceback.print_exc()
1233 print ("Could not save -- fix write access on %s" % site_config)
1235 print ("Wrote %s" % site_config)
1236 consolidate(default_config, site_config, consolidated_config)
1237 print ("You might want to type 'r' (restart %s), 'R' (reload %s) or 'q' (quit)" % \
1239 elif command in "uU":
1240 global usual_variables
1242 for varname in usual_variables:
1243 (category,variable) = cdef.locate_varname(varname)
1244 if not (category is None and variable is None):
1245 prompt_variable(cdef, cread, cwrite, category, variable, False)
1246 except Exception, inst:
1247 if (str(inst) != 'BailOut'):
1249 elif command == "r":
1251 elif command == "R":
1253 elif command == "c":
1254 print_categories(cread)
1255 elif command in "eE":
1257 prompt_variables_all(cdef, cread, cwrite,show_comments)
1258 elif mode == 'CATEGORY':
1259 prompt_variables_category(cdef,cread,cwrite,category_id,show_comments)
1260 elif mode == 'VARIABLE':
1262 prompt_variable (cdef,cread,cwrite,category,variable,
1263 show_comments,False)
1264 except Exception, inst:
1265 if str(inst) != 'BailOut':
1267 elif command in "vVsSlL":
1268 show_value=(command in "sSlL")
1269 (c1,c2,c3) = (cdef, cread, cwrite)
1271 (c1,c2,c3) = (cwrite,cwrite,cwrite)
1273 show_variables_all(c1,c2,c3,show_value,show_comments)
1274 elif mode == 'CATEGORY':
1275 show_variables_category(c1,c2,c3,category_id,show_value,show_comments)
1276 elif mode == 'VARIABLE':
1277 show_variable (c1,c2,c3,category,variable,show_value,show_comments)
1279 print ("Unknown command >%s< -- use h for help" % answer)
1281 ####################
1282 # creates directory for file if not yet existing
1283 def check_dir (config_file):
1284 dirname = os.path.dirname (config_file)
1285 if (not os.path.exists (dirname)):
1287 os.makedirs(dirname,0755)
1289 print "Cannot create dir %s due to %s - exiting" % (dirname,e)
1292 if (not os.path.exists (dirname)):
1293 print "Cannot create dir %s - exiting" % dirname
1296 print "Created directory %s" % dirname
1298 ####################
1299 def optParserSetup(configuration):
1300 parser = OptionParser(usage=usage(), version="%prog " + release_rev + release_url )
1301 parser.set_defaults(config_dir=configuration['config_dir'],
1302 service=configuration['service'],
1303 usual_variables=configuration['usual_variables'])
1304 parser.add_option("","--configdir",dest="config_dir",help="specify configuration directory")
1305 parser.add_option("","--service",dest="service",help="specify /etc/init.d style service name")
1306 parser.add_option("","--usual_variable",dest="usual_variables",action="append", help="add a usual variable")
1309 def main(command,argv,configuration):
1310 global g_configuration
1311 g_configuration=configuration
1313 parser = optParserSetup(configuration)
1314 (config,args) = parser.parse_args()
1316 parser.error("too many arguments")
1318 configuration['service']=config.service
1319 configuration['usual_variables']=config.usual_variables
1320 configuration['config_dir']=config.config_dir
1321 # add in new usual_variables defined on the command line
1322 for usual_variable in config.usual_variables:
1323 if usual_variable not in configuration['usual_variables']:
1324 configuration['usual_variables'].append(usual_variable)
1326 # intialize configuration
1327 init_configuration()
1329 (default_config,site_config,consolidated_config) = (def_default_config, def_site_config, def_consolidated_config)
1331 default_config=args[0]
1335 consolidated_config=args[2]
1337 for c in (default_config,site_config,consolidated_config):
1341 # the default settings only - read only
1342 cdef = PLCConfiguration(default_config)
1344 # in effect : default settings + local settings - read only
1345 cread = PLCConfiguration(default_config)
1347 except ConfigurationException, e:
1348 print ("Error %s in default config file %s" %(e,default_config))
1351 print traceback.print_exc()
1352 print ("default config files %s not found, is myplc installed ?" % default_config)
1356 # local settings only, will be modified & saved
1357 cwrite=PLCConfiguration()
1360 cread.load(site_config)
1361 cwrite.load(site_config)
1363 cwrite = PLCConfiguration()
1365 mainloop (cdef, cread, cwrite, default_config, site_config, consolidated_config)
1368 if __name__ == '__main__':
1370 if len(sys.argv) > 1 and sys.argv[1] in ['build', 'install', 'uninstall']:
1371 from distutils.core import setup
1372 setup(py_modules=["plc_config"])