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
18 import xml.dom.minidom
19 from xml.parsers.expat import ExpatError
20 from io import StringIO
21 from optparse import OptionParser
23 class ConfigurationException(Exception): pass
25 class PLCConfiguration:
27 Configuration file store. Optionally instantiate with a file path
30 plc = PLCConfiguration()
31 plc = PLCConfiguration(fileobj)
32 plc = PLCConfiguration("/etc/planetlab/plc_config.xml")
34 You may load() additional files later, which will be merged into
35 the current configuration:
37 plc.load("/etc/planetlab/local.xml")
39 You may also save() the configuration. If a file path or object is
40 not specified, the configuration will be written to the file path
41 or object that was first loaded.
44 plc.save("/etc/planetlab/plc_config.xml")
47 def __init__(self, file = None):
48 impl = xml.dom.minidom.getDOMImplementation()
49 self._dom = impl.createDocument(None, "configuration", None)
58 def _get_text(self, node):
60 Get the text of a text node.
63 if node.firstChild and \
64 node.firstChild.nodeType == node.TEXT_NODE:
65 if node.firstChild.data is None:
66 # Interpret simple presence of node as "", not NULL
69 return node.firstChild.data
74 def _get_text_of_child(self, parent, name):
76 Get the text of a (direct) child text node.
79 for node in parent.childNodes:
80 if node.nodeType == node.ELEMENT_NODE and \
82 return self._get_text(node)
87 def _set_text(self, node, data):
89 Set the text of a text node.
92 if node.firstChild and \
93 node.firstChild.nodeType == node.TEXT_NODE:
95 node.removeChild(node.firstChild)
97 node.firstChild.data = data
98 elif data is not None:
101 node.appendChild(text)
104 def _set_text_of_child(self, parent, name, data):
106 Set the text of a (direct) child text node.
109 for node in parent.childNodes:
110 if node.nodeType == node.ELEMENT_NODE and \
111 node.tagName == name:
112 self._set_text(node, data)
115 child = TrimTextElement(name)
116 self._set_text(child, data)
117 parent.appendChild(child)
120 def _category_element_to_dict(self, category_element):
122 Turn a <category> element into a dictionary of its attributes
123 and child text nodes.
127 category['id'] = category_element.getAttribute('id').lower()
128 for node in category_element.childNodes:
129 if node.nodeType == node.ELEMENT_NODE and \
130 node.tagName in ['name', 'description']:
131 category[node.tagName] = self._get_text_of_child(category_element, node.tagName)
132 category['element'] = category_element
137 def _variable_element_to_dict(self, variable_element):
139 Turn a <variable> element into a dictionary of its attributes
140 and child text nodes.
144 variable['id'] = variable_element.getAttribute('id').lower()
145 if variable_element.hasAttribute('type'):
146 variable['type'] = variable_element.getAttribute('type')
147 for node in variable_element.childNodes:
148 if node.nodeType == node.ELEMENT_NODE and \
149 node.tagName in ['name', 'value', 'description']:
150 variable[node.tagName] = self._get_text_of_child(variable_element, node.tagName)
151 variable['element'] = variable_element
156 def _group_element_to_dict(self, group_element):
158 Turn a <group> element into a dictionary of its attributes
159 and child text nodes.
163 for node in group_element.childNodes:
164 if node.nodeType == node.ELEMENT_NODE and \
165 node.tagName in ['id', 'name', 'default', 'description', 'uservisible']:
166 group[node.tagName] = self._get_text_of_child(group_element, node.tagName)
167 group['element'] = group_element
172 def _packagereq_element_to_dict(self, packagereq_element):
174 Turns a <packagereq> element into a dictionary of its attributes
175 and child text nodes.
179 if packagereq_element.hasAttribute('type'):
180 package['type'] = packagereq_element.getAttribute('type')
181 package['name'] = self._get_text(packagereq_element)
182 package['element'] = packagereq_element
187 def load(self, file = "/etc/planetlab/plc_config.xml"):
189 Merge file into configuration store.
193 dom = xml.dom.minidom.parse(file)
194 except ExpatError as e:
195 raise ConfigurationException(e)
197 if isinstance(file, str):
198 self._files.append(os.path.abspath(file))
200 # Parse <variables> section
201 for variables_element in dom.getElementsByTagName('variables'):
202 for category_element in variables_element.getElementsByTagName('category'):
203 category = self._category_element_to_dict(category_element)
204 self.set(category, None)
206 for variablelist_element in category_element.getElementsByTagName('variablelist'):
207 for variable_element in variablelist_element.getElementsByTagName('variable'):
208 variable = self._variable_element_to_dict(variable_element)
209 self.set(category, variable)
211 # Parse <comps> section
212 for comps_element in dom.getElementsByTagName('comps'):
213 for group_element in comps_element.getElementsByTagName('group'):
214 group = self._group_element_to_dict(group_element)
215 self.add_package(group, None)
217 for packagereq_element in group_element.getElementsByTagName('packagereq'):
218 package = self._packagereq_element_to_dict(packagereq_element)
219 self.add_package(group, package)
222 def save(self, file = None):
224 Write configuration store to file.
229 file = self._files[0]
231 file = "/etc/planetlab/plc_config.xml"
233 if isinstance(file, str):
234 fileobj = open(file, 'w')
239 fileobj.write(self.output_xml())
244 def verify(self, default, read, verify_variables={}):
245 """ Confirm that the existing configuration is consistent
246 according to the checks below.
248 It looks for filled-in values in the order of, local object (self),
249 followed by cread (read values), and finally default values.
253 default configuration
255 list of category/variable tuples to validate in these configurations
259 dict of values for the category/variables passed in
260 If an exception is found, ConfigurationException is raised.
264 validated_variables = {}
265 for category_id, variable_id in verify_variables.items():
266 category_id = category_id.lower()
267 variable_id = variable_id.lower()
268 variable_value = None
269 sources = (self, read, default)
270 for source in sources:
271 (category_value, variable_value) = source.get(category_id,variable_id)
272 if variable_value != None:
273 entry = validated_variables.get(category_id,[])
274 entry.append(variable_value['value'])
275 validated_variables["%s_%s"%(category_id.upper(),variable_id.upper())]=entry
277 if variable_value == None:
278 raise ConfigurationException("Cannot find %s_%s)" % \
279 (category_id.upper(),
280 variable_id.upper()))
281 return validated_variables
283 def get(self, category_id, variable_id):
285 Get the specified variable in the specified category.
289 category_id = unique category identifier (e.g., 'plc_www')
290 variable_id = unique variable identifier (e.g., 'port')
294 variable = { 'id': "variable_identifier",
295 'type': "variable_type",
296 'value': "variable_value",
297 'name': "Variable name",
298 'description': "Variable description" }
301 if category_id.lower() in self._variables:
302 (category, variables) = self._variables[category_id]
303 if variable_id.lower() in variables:
304 variable = variables[variable_id]
311 return (category, variable)
314 def delete(self, category_id, variable_id):
316 Delete the specified variable from the specified category. If
317 variable_id is None, deletes all variables from the specified
318 category as well as the category itself.
322 category_id = unique category identifier (e.g., 'plc_www')
323 variable_id = unique variable identifier (e.g., 'port')
326 if category_id.lower() in self._variables:
327 (category, variables) = self._variables[category_id]
328 if variable_id is None:
329 category['element'].parentNode.removeChild(category['element'])
330 del self._variables[category_id]
331 elif variable_id.lower() in variables:
332 variable = variables[variable_id]
333 variable['element'].parentNode.removeChild(variable['element'])
334 del variables[variable_id]
337 def set(self, category, variable):
339 Add and/or update the specified variable. The 'id' fields are
340 mandatory. If a field is not specified and the category and/or
341 variable already exists, the field will not be updated. If
342 'variable' is None, only adds and/or updates the specified
347 category = { 'id': "category_identifier",
348 'name': "Category name",
349 'description': "Category description" }
351 variable = { 'id': "variable_identifier",
352 'type': "variable_type",
353 'value': "variable_value",
354 'name': "Variable name",
355 'description': "Variable description" }
358 if ('id' not in category
359 or not isinstance(category['id'], str)):
362 category_id = category['id'].lower()
364 if category_id in self._variables:
366 (old_category, variables) = self._variables[category_id]
368 # Merge category attributes
369 for tag in ['name', 'description']:
371 old_category[tag] = category[tag]
372 self._set_text_of_child(old_category['element'], tag, category[tag])
374 category_element = old_category['element']
377 category_element = self._dom.createElement('category')
378 category_element.setAttribute('id', category_id)
379 for tag in ['name', 'description']:
381 self._set_text_of_child(category_element, tag, category[tag])
383 if self._dom.documentElement.getElementsByTagName('variables'):
384 variables_element = self._dom.documentElement.getElementsByTagName('variables')[0]
386 variables_element = self._dom.createElement('variables')
387 self._dom.documentElement.appendChild(variables_element)
388 variables_element.appendChild(category_element)
391 category['element'] = category_element
393 self._variables[category_id] = (category, variables)
395 if (variable is None or 'id' not in variable
396 or not isinstance(variable['id'], str)):
399 variable_id = variable['id'].lower()
401 if variable_id in variables:
403 old_variable = variables[variable_id]
405 # Merge variable attributes
406 for attribute in ['type']:
407 if attribute in variable:
408 old_variable[attribute] = variable[attribute]
409 old_variable['element'].setAttribute(attribute, variable[attribute])
410 for tag in ['name', 'value', 'description']:
412 old_variable[tag] = variable[tag]
413 self._set_text_of_child(old_variable['element'], tag, variable[tag])
416 variable_element = self._dom.createElement('variable')
417 variable_element.setAttribute('id', variable_id)
418 for attribute in ['type']:
419 if attribute in variable:
420 variable_element.setAttribute(attribute, variable[attribute])
421 for tag in ['name', 'value', 'description']:
423 self._set_text_of_child(variable_element, tag, variable[tag])
425 if category_element.getElementsByTagName('variablelist'):
426 variablelist_element = category_element.getElementsByTagName('variablelist')[0]
428 variablelist_element = self._dom.createElement('variablelist')
429 category_element.appendChild(variablelist_element)
430 variablelist_element.appendChild(variable_element)
433 variable['element'] = variable_element
434 variables[variable_id] = variable
437 def locate_varname (self, varname):
439 Locates category and variable from a variable's (shell) name
442 (variable, category) when found
443 (None, None) otherwise
446 for (category_id, (category, variables)) in self._variables.items():
447 for variable in list(variables.values()):
448 (id, name, value, comments) = self._sanitize_variable(category_id, variable)
450 return (category,variable)
453 def get_package(self, group_id, package_name):
455 Get the specified package in the specified package group.
459 group_id - unique group id (e.g., 'plc')
460 package_name - unique package name (e.g., 'postgresql')
464 package = { 'name': "package_name",
465 'type': "mandatory|optional" }
468 if group_id.lower() in self._packages:
469 (group, packages) = self._packages[group_id]
470 if package_name in packages:
471 package = packages[package_name]
478 return (group, package)
481 def delete_package(self, group_id, package_name):
483 Deletes the specified variable from the specified category. If
484 variable_id is None, deletes all variables from the specified
485 category as well as the category itself.
489 group_id - unique group id (e.g., 'plc')
490 package_name - unique package name (e.g., 'postgresql')
493 if group_id in self._packages:
494 (group, packages) = self._packages[group_id]
495 if package_name is None:
496 group['element'].parentNode.removeChild(group['element'])
497 del self._packages[group_id]
498 elif package_name.lower() in packages:
499 package = packages[package_name]
500 package['element'].parentNode.removeChild(package['element'])
501 del packages[package_name]
504 def add_package(self, group, package):
506 Add and/or update the specified package. The 'id' and 'name'
507 fields are mandatory. If a field is not specified and the
508 package or group already exists, the field will not be
509 updated. If package is None, only adds/or updates the
514 group = { 'id': "group_identifier",
515 'name': "Group name",
516 'default': "true|false",
517 'description': "Group description",
518 'uservisible': "true|false" }
520 package = { 'name': "package_name",
521 'type': "mandatory|optional" }
524 if 'id' not in group:
527 group_id = group['id']
529 if group_id in self._packages:
531 (old_group, packages) = self._packages[group_id]
533 # Merge group attributes
534 for tag in ['id', 'name', 'default', 'description', 'uservisible']:
536 old_group[tag] = group[tag]
537 self._set_text_of_child(old_group['element'], tag, group[tag])
539 group_element = old_group['element']
542 group_element = self._dom.createElement('group')
543 for tag in ['id', 'name', 'default', 'description', 'uservisible']:
545 self._set_text_of_child(group_element, tag, group[tag])
547 if self._dom.documentElement.getElementsByTagName('comps'):
548 comps_element = self._dom.documentElement.getElementsByTagName('comps')[0]
550 comps_element = self._dom.createElement('comps')
551 self._dom.documentElement.appendChild(comps_element)
552 comps_element.appendChild(group_element)
555 group['element'] = group_element
557 self._packages[group_id] = (group, packages)
559 if package is None or 'name' not in package:
562 package_name = package['name']
563 if package_name in packages:
565 old_package = packages[package_name]
567 # Merge variable attributes
568 for attribute in ['type']:
569 if attribute in package:
570 old_package[attribute] = package[attribute]
571 old_package['element'].setAttribute(attribute, package[attribute])
574 packagereq_element = TrimTextElement('packagereq')
575 self._set_text(packagereq_element, package_name)
576 for attribute in ['type']:
577 if attribute in package:
578 packagereq_element.setAttribute(attribute, package[attribute])
580 if group_element.getElementsByTagName('packagelist'):
581 packagelist_element = group_element.getElementsByTagName('packagelist')[0]
583 packagelist_element = self._dom.createElement('packagelist')
584 group_element.appendChild(packagelist_element)
585 packagelist_element.appendChild(packagereq_element)
588 package['element'] = packagereq_element
589 packages[package_name] = package
594 Return all variables.
598 variables = { 'category_id': (category, variablelist) }
600 category = { 'id': "category_identifier",
601 'name': "Category name",
602 'description': "Category description" }
604 variablelist = { 'variable_id': variable }
606 variable = { 'id': "variable_identifier",
607 'type': "variable_type",
608 'value': "variable_value",
609 'name': "Variable name",
610 'description': "Variable description" }
613 return self._variables
622 packages = { 'group_id': (group, packagelist) }
624 group = { 'id': "group_identifier",
625 'name': "Group name",
626 'default': "true|false",
627 'description': "Group description",
628 'uservisible': "true|false" }
630 packagelist = { 'package_name': package }
632 package = { 'name': "package_name",
633 'type': "mandatory|optional" }
636 return self._packages
639 def _sanitize_variable(self, category_id, variable):
640 assert 'id' in variable
641 # Prepend variable name with category label
642 id = category_id + "_" + variable['id']
646 if 'type' in variable:
647 type = variable['type']
651 if 'name' in variable:
652 name = variable['name']
656 if 'value' in variable and variable['value'] is not None:
657 value = variable['value']
658 if type == "int" or type == "double":
659 # bash, Python, and PHP do not require that numbers be quoted
661 elif type == "boolean":
662 # bash, Python, and PHP can all agree on 0 and 1
668 # bash, Python, and PHP all support strong single quoting
669 value = "'" + value.replace("'", "\\'") + "'"
673 if 'description' in variable and variable['description'] is not None:
674 description = variable['description']
675 # Collapse consecutive whitespace
676 description = re.sub(r'\s+', ' ', description)
677 # Wrap comments at 70 columns
678 wrapper = textwrap.TextWrapper()
679 comments = wrapper.wrap(description)
683 return (id, name, value, comments)
688 DO NOT EDIT. This file was automatically generated at
692 """ % (time.asctime(), os.linesep.join(self._files))
694 # Get rid of the surrounding newlines
695 return header.strip().split(os.linesep)
698 def output_shell(self, show_comments = True, encoding = "utf-8"):
700 Return variables as a shell script.
703 buf = codecs.lookup(encoding)[3](StringIO())
704 buf.writelines(["# " + line + os.linesep for line in self._header()])
706 for (category_id, (category, variables)) in self._variables.items():
707 for variable in list(variables.values()):
708 (id, name, value, comments) = self._sanitize_variable(category_id, variable)
710 buf.write(os.linesep)
712 buf.write("# " + name + os.linesep)
713 if comments is not None:
714 buf.writelines(["# " + line + os.linesep for line in comments])
715 # bash does not have the concept of NULL
716 if value is not None:
717 buf.write(id + "=" + value + os.linesep)
719 return buf.getvalue()
722 def output_php(self, encoding = "utf-8"):
724 Return variables as a PHP script.
727 buf = codecs.lookup(encoding)[3](StringIO())
728 buf.write("<?php" + os.linesep)
729 buf.writelines(["// " + line + os.linesep for line in self._header()])
731 for (category_id, (category, variables)) in self._variables.items():
732 for variable in list(variables.values()):
733 (id, name, value, comments) = self._sanitize_variable(category_id, variable)
734 buf.write(os.linesep)
736 buf.write("// " + name + os.linesep)
737 if comments is not None:
738 buf.writelines(["// " + line + os.linesep for line in comments])
741 buf.write("define('%s', %s);" % (id, value) + os.linesep)
743 buf.write("?>" + os.linesep)
745 return buf.getvalue()
748 def output_xml(self, encoding = "utf-8"):
750 Return variables in original XML format.
753 buf = codecs.lookup(encoding)[3](StringIO())
754 self._dom.writexml(buf, addindent = " ", indent = "", newl = "\n", encoding = encoding)
756 return buf.getvalue()
759 def output_variables(self, encoding = "utf-8"):
761 Return list of all variable names.
764 buf = codecs.lookup(encoding)[3](StringIO())
766 for (category_id, (category, variables)) in self._variables.items():
767 for variable in list(variables.values()):
768 (id, name, value, comments) = self._sanitize_variable(category_id, variable)
769 buf.write(id + os.linesep)
771 return buf.getvalue()
774 def output_packages(self, encoding = "utf-8"):
776 Return list of all packages.
779 buf = codecs.lookup(encoding)[3](StringIO())
781 for (group, packages) in list(self._packages.values()):
782 buf.write(os.linesep.join(list(packages.keys())))
785 buf.write(os.linesep)
787 return buf.getvalue()
790 def output_groups(self, encoding = "utf-8"):
792 Return list of all package group names.
795 buf = codecs.lookup(encoding)[3](StringIO())
797 for (group, packages) in list(self._packages.values()):
798 buf.write(group['name'] + os.linesep)
800 return buf.getvalue()
803 def output_comps(self, encoding = "utf-8"):
805 Return <comps> section of configuration.
808 if self._dom is None or \
809 not self._dom.getElementsByTagName("comps"):
811 comps = self._dom.getElementsByTagName("comps")[0]
813 impl = xml.dom.minidom.getDOMImplementation()
814 doc = impl.createDocument(None, "comps", None)
816 buf = codecs.lookup(encoding)[3](StringIO())
818 # Pop it off the DOM temporarily
819 parent = comps.parentNode
820 parent.removeChild(comps)
822 doc.replaceChild(comps, doc.documentElement)
823 doc.writexml(buf, encoding = encoding)
826 parent.appendChild(comps)
828 return buf.getvalue()
830 def validate_type(self, variable_type, value):
832 # ideally we should use the "validate_*" methods in PLCAPI or
833 # even declare some checks along with the default
834 # configuration (using RELAX NG?) but this shall work for now.
835 def ip_validator(val):
838 socket.inet_aton(val)
842 def email_validator(val):
843 return re.match('\A[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9._\-]+\.[a-zA-Z]+\Z', val)
845 def boolean_validator (val):
846 return val in ['true', 'false']
849 'email' : email_validator,
851 'boolean': boolean_validator,
854 # validate it if not a know type.
855 validator = validators.get(variable_type, lambda x: True)
856 return validator(value)
860 # xml.dom.minidom.Text.writexml adds surrounding whitespace to textual
861 # data when pretty-printing. Override this behavior.
862 class TrimText(xml.dom.minidom.Text):
863 def writexml(self, writer, indent="", addindent="", newl=""):
864 xml.dom.minidom.Text.writexml(self, writer, "", "", "")
867 class TrimTextElement(xml.dom.minidom.Element):
868 def writexml(self, writer, indent="", addindent="", newl=""):
870 xml.dom.minidom.Element.writexml(self, writer, "", "", "")
882 def noop_validator(validated_variables):
886 # historically we could also configure the devel pkg....
887 def init_configuration ():
888 global g_configuration
889 global usual_variables, config_dir, service
891 usual_variables=g_configuration["usual_variables"]
892 config_dir=g_configuration["config_dir"]
893 service=g_configuration["service"]
895 global def_default_config, def_site_config, def_consolidated_config
896 def_default_config= "%s/default_config.xml" % config_dir
897 def_site_config = "%s/configs/site.xml" % config_dir
898 def_consolidated_config = "%s/%s_config.xml" % (config_dir, service)
900 global mainloop_usage
901 mainloop_usage= """Available commands:
902 Uppercase versions give variables comments, when available
903 u/U\t\t\tEdit usual variables
905 r\t\t\tRestart %(service)s service
906 R\t\t\tReload %(service)s service (rebuild config files for sh, python....)
907 q\t\t\tQuit (without saving)
910 l/L [<cat>|<var>]\tShow Locally modified variables/values
911 s/S [<cat>|<var>]\tShow variables/values (all, in category, single)
912 e/E [<cat>|<var>]\tEdit variables (all, in category, single)
914 c\t\t\tList categories
915 v/V [<cat>|<var>]\tList Variables (all, in category, single)
917 Typical usage involves: u, [l,] w, r, q
921 command_usage="%prog [options] [default-xml [site-xml [consolidated-xml]]]"
922 init_configuration ()
924 \t default-xml defaults to %s
925 \t site-xml defaults to %s
926 \t consolidated-xml defaults to %s""" % (def_default_config,def_site_config, def_consolidated_config)
930 variable_usage= """Edit Commands :
931 #\tShow variable comments
932 .\tStops prompting, return to mainloop
933 /\tCleans any site-defined value, reverts to default
934 =\tShows default value
935 >\tSkips to next category
940 def get_value (config, category_id, variable_id):
941 (category, variable) = config.get (category_id, variable_id)
942 return variable['value']
944 def get_type (config, category_id, variable_id):
945 (category, variable) = config.get (category_id, variable_id)
946 return variable['type']
948 def get_current_value (cread, cwrite, category_id, variable_id):
949 # the value stored in cwrite, if present, is the one we want
951 result=get_value (cwrite,category_id,variable_id)
953 result=get_value (cread,category_id,variable_id)
956 # refrain from using plc_config's _sanitize
957 def get_varname (config, category_id, variable_id):
958 (category, variable) = config.get (category_id, variable_id)
959 return (category_id+"_"+variable['id']).upper()
961 # could not avoid using _sanitize here..
962 def get_name_comments (config, cid, vid):
964 (category, variable) = config.get (cid, vid)
965 (id, name, value, comments) = config._sanitize_variable (cid,variable)
966 return (name,comments)
970 def print_name_comments (config, cid, vid):
971 (name,comments)=get_name_comments(config,cid,vid)
973 print("### %s" % name)
975 for line in comments:
978 print("!!! No comment associated to %s_%s" % (cid,vid))
981 def list_categories (config):
983 for (category_id, (category, variables)) in config.variables().items():
984 result += [category_id]
987 def print_categories (config):
988 print("Known categories")
989 for cid in list_categories(config):
990 print("%s" % (cid.upper()))
993 def list_category (config, cid):
995 for (category_id, (category, variables)) in config.variables().items():
996 if (cid == category_id):
997 for variable in list(variables.values()):
998 result += ["%s_%s" %(cid,variable['id'])]
1001 def print_category (config, cid, show_comments=True):
1004 vids=list_category(config,cid)
1005 if (len(vids) == 0):
1006 print("%s : no such category"%CID)
1008 print("Category %s contains" %(CID))
1012 ####################
1013 def consolidate (default_config, site_config, consolidated_config):
1016 conso = PLCConfiguration (default_config)
1017 conso.load (site_config)
1018 conso.save (consolidated_config)
1019 except Exception as inst:
1020 print("Could not consolidate, %s" % (str(inst)))
1022 print(("Merged\n\t%s\nand\t%s\ninto\t%s"%(default_config,site_config,
1023 consolidated_config)))
1025 def reload_service ():
1027 os.system("set -x ; systemctl reload %s" % service)
1029 ####################
1030 def restart_service ():
1032 print(("==================== Stopping %s" % service))
1033 os.system("systemctl stop %s" % service)
1034 print(("==================== Starting %s" % service))
1035 os.system("systemctl start %s" % service)
1037 ####################
1038 def prompt_variable (cdef, cread, cwrite, category, variable,
1039 show_comments, support_next=False):
1041 assert 'id' in category
1042 assert 'id' in variable
1044 category_id = category ['id']
1045 variable_id = variable['id']
1048 default_value = get_value(cdef,category_id,variable_id)
1049 variable_type = get_type(cdef,category_id,variable_id)
1050 current_value = get_current_value(cread,cwrite,category_id, variable_id)
1051 varname = get_varname (cread,category_id, variable_id)
1054 print_name_comments (cdef, category_id, variable_id)
1055 prompt = "== %s : [%s] " % (varname,current_value)
1057 answer = input(prompt).strip()
1059 raise Exception ('BailOut')
1060 except KeyboardInterrupt:
1062 raise Exception ('BailOut')
1065 if (answer == "") or (answer == current_value):
1067 elif (answer == "."):
1068 raise Exception ('BailOut')
1069 elif (answer == "#"):
1070 print_name_comments(cread,category_id,variable_id)
1071 elif (answer == "?"):
1072 print(variable_usage.strip())
1073 elif (answer == "="):
1074 print(("%s defaults to %s" %(varname,default_value)))
1075 # revert to default : remove from cwrite (i.e. site-config)
1076 elif (answer == "/"):
1077 cwrite.delete(category_id,variable_id)
1078 print(("%s reverted to %s" %(varname,default_value)))
1080 elif (answer == ">"):
1082 raise Exception ('NextCategory')
1084 print("No support for next category")
1086 if cdef.validate_type(variable_type, answer):
1087 variable['value'] = answer
1088 cwrite.set(category,variable)
1091 print("Not a valid value")
1093 def prompt_variables_all (cdef, cread, cwrite, show_comments):
1095 for (category_id, (category, variables)) in cread.variables().items():
1096 print(("========== Category = %s" % category_id.upper()))
1097 for variable in list(variables.values()):
1099 newvar = prompt_variable (cdef, cread, cwrite, category, variable,
1100 show_comments, True)
1101 except Exception as inst:
1102 if (str(inst) == 'NextCategory'): break
1105 except Exception as inst:
1106 if (str(inst) == 'BailOut'): return
1109 def prompt_variables_category (cdef, cread, cwrite, cid, show_comments):
1113 print(("========== Category = %s" % CID))
1114 for vid in list_category(cdef,cid):
1115 (category,variable) = cdef.locate_varname(vid.upper())
1116 newvar = prompt_variable (cdef, cread, cwrite, category, variable,
1117 show_comments, False)
1118 except Exception as inst:
1119 if (str(inst) == 'BailOut'): return
1122 ####################
1123 def show_variable (cdef, cread, cwrite,
1124 category, variable,show_value,show_comments):
1125 assert 'id' in category
1126 assert 'id' in variable
1128 category_id = category ['id']
1129 variable_id = variable['id']
1131 default_value = get_value(cdef,category_id,variable_id)
1132 current_value = get_current_value(cread,cwrite,category_id,variable_id)
1133 varname = get_varname (cread,category_id, variable_id)
1135 print_name_comments (cdef, category_id, variable_id)
1137 print("%s = %s" % (varname,current_value))
1139 print("%s" % (varname))
1141 def show_variables_all (cdef, cread, cwrite, show_value, show_comments):
1142 for (category_id, (category, variables)) in cread.variables().items():
1143 print(("========== Category = %s" % category_id.upper()))
1144 for variable in list(variables.values()):
1145 show_variable (cdef, cread, cwrite,
1146 category, variable,show_value,show_comments)
1148 def show_variables_category (cdef, cread, cwrite, cid, show_value,show_comments):
1151 print(("========== Category = %s" % CID))
1152 for vid in list_category(cdef,cid):
1153 (category,variable) = cdef.locate_varname(vid.upper())
1154 show_variable (cdef, cread, cwrite, category, variable,
1155 show_value,show_comments)
1157 ####################
1158 re_mainloop_0arg="^(?P<command>[uUwrRqlLsSeEcvVhH\?])[ \t]*$"
1159 re_mainloop_1arg="^(?P<command>[sSeEvV])[ \t]+(?P<arg>\w+)$"
1160 matcher_mainloop_0arg=re.compile(re_mainloop_0arg)
1161 matcher_mainloop_1arg=re.compile(re_mainloop_1arg)
1163 def mainloop (cdef, cread, cwrite, default_config, site_config, consolidated_config):
1167 answer = input("Enter command (u for usual changes, w to save, ? for help) ").strip()
1170 except KeyboardInterrupt:
1174 if (answer == "") or (answer in "?hH"):
1175 print(mainloop_usage)
1177 groups_parse = matcher_mainloop_0arg.match(answer)
1180 command = groups_parse.group('command')
1183 groups_parse = matcher_mainloop_1arg.match(answer)
1185 command = groups_parse.group('command')
1186 arg=groups_parse.group('arg')
1188 print(("Unknown command >%s< -- use h for help" % answer))
1191 show_comments=command.isupper()
1197 variables=list_category (cdef,arg)
1199 # category_id as the category name
1200 # variables as the list of variable names
1204 (category,variable)=cdef.locate_varname(arg)
1206 # category/variable as output by locate_varname
1209 print("%s: no such category or variable" % arg)
1213 # todo check confirmation
1215 elif command == "w":
1217 # Confirm that various constraints are met before saving file.
1218 validate_variables = g_configuration.get('validate_variables',{})
1219 validated_variables = cwrite.verify(cdef, cread, validate_variables)
1220 validator = g_configuration.get('validator',noop_validator)
1221 validator(validated_variables)
1222 cwrite.save(site_config)
1223 except ConfigurationException as e:
1224 print("Save failed due to a configuration exception: %s" % e)
1227 print(traceback.print_exc())
1228 print(("Could not save -- fix write access on %s" % site_config))
1230 print(("Wrote %s" % site_config))
1231 consolidate(default_config, site_config, consolidated_config)
1232 print(("You might want to type 'r' (restart %s), 'R' (reload %s) or 'q' (quit)" % \
1233 (service, service)))
1234 elif command in "uU":
1235 global usual_variables
1237 for varname in usual_variables:
1238 (category,variable) = cdef.locate_varname(varname)
1239 if not (category is None and variable is None):
1240 prompt_variable(cdef, cread, cwrite, category, variable, False)
1241 except Exception as inst:
1242 if (str(inst) != 'BailOut'):
1244 elif command == "r":
1246 elif command == "R":
1248 elif command == "c":
1249 print_categories(cread)
1250 elif command in "eE":
1252 prompt_variables_all(cdef, cread, cwrite,show_comments)
1253 elif mode == 'CATEGORY':
1254 prompt_variables_category(cdef,cread,cwrite,category_id,show_comments)
1255 elif mode == 'VARIABLE':
1257 prompt_variable (cdef,cread,cwrite,category,variable,
1258 show_comments,False)
1259 except Exception as inst:
1260 if str(inst) != 'BailOut':
1262 elif command in "vVsSlL":
1263 show_value=(command in "sSlL")
1264 (c1,c2,c3) = (cdef, cread, cwrite)
1266 (c1,c2,c3) = (cwrite,cwrite,cwrite)
1268 show_variables_all(c1,c2,c3,show_value,show_comments)
1269 elif mode == 'CATEGORY':
1270 show_variables_category(c1,c2,c3,category_id,show_value,show_comments)
1271 elif mode == 'VARIABLE':
1272 show_variable (c1,c2,c3,category,variable,show_value,show_comments)
1274 print(("Unknown command >%s< -- use h for help" % answer))
1276 ####################
1277 # creates directory for file if not yet existing
1278 def check_dir (config_file):
1279 dirname = os.path.dirname (config_file)
1280 if (not os.path.exists (dirname)):
1282 os.makedirs(dirname,0o755)
1283 except OSError as e:
1284 print("Cannot create dir %s due to %s - exiting" % (dirname,e))
1287 if (not os.path.exists (dirname)):
1288 print("Cannot create dir %s - exiting" % dirname)
1291 print("Created directory %s" % dirname)
1293 ####################
1294 def optParserSetup(configuration):
1295 parser = OptionParser(usage=usage())
1296 parser.set_defaults(config_dir=configuration['config_dir'],
1297 service=configuration['service'],
1298 usual_variables=configuration['usual_variables'])
1299 parser.add_option("","--configdir",dest="config_dir",help="specify configuration directory")
1300 parser.add_option("","--service",dest="service",help="specify /etc/init.d style service name")
1301 parser.add_option("","--usual_variable",dest="usual_variables",action="append", help="add a usual variable")
1304 def main(command,argv,configuration):
1305 global g_configuration
1306 g_configuration=configuration
1308 parser = optParserSetup(configuration)
1309 (config,args) = parser.parse_args()
1311 parser.error("too many arguments")
1313 configuration['service']=config.service
1314 configuration['usual_variables']=config.usual_variables
1315 configuration['config_dir']=config.config_dir
1316 # add in new usual_variables defined on the command line
1317 for usual_variable in config.usual_variables:
1318 if usual_variable not in configuration['usual_variables']:
1319 configuration['usual_variables'].append(usual_variable)
1321 # intialize configuration
1322 init_configuration()
1324 (default_config,site_config,consolidated_config) = (def_default_config, def_site_config, def_consolidated_config)
1326 default_config=args[0]
1330 consolidated_config=args[2]
1332 for c in (default_config,site_config,consolidated_config):
1336 # the default settings only - read only
1337 cdef = PLCConfiguration(default_config)
1339 # in effect : default settings + local settings - read only
1340 cread = PLCConfiguration(default_config)
1342 except ConfigurationException as e:
1343 print(("Error %s in default config file %s" %(e,default_config)))
1346 print(traceback.print_exc())
1347 print(("default config files %s not found, is myplc installed ?" % default_config))
1351 # local settings only, will be modified & saved
1352 cwrite=PLCConfiguration()
1355 cread.load(site_config)
1356 cwrite.load(site_config)
1358 cwrite = PLCConfiguration()
1360 mainloop (cdef, cread, cwrite, default_config, site_config, consolidated_config)
1363 if __name__ == '__main__':
1365 if len(sys.argv) > 1 and sys.argv[1] in ['build', 'install', 'uninstall']:
1366 from distutils.core import setup
1367 setup(py_modules=["plc_config"])