############################################################################ # Copyright (C) by gempa GmbH, GFZ Potsdam # # # # You can redistribute and/or modify this program under the # # terms of the SeisComP Public License. # # # # This program is distributed in the hope that it will be useful, # # but WITHOUT ANY WARRANTY; without even the implied warranty of # # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # # SeisComP Public License for more details. # ############################################################################ import os import sys import glob import getpass try: # Python 2.5 from xml.etree import ElementTree from xml.parsers.expat import ExpatError as ParseError except ImportError: from elementtree import ElementTree from xml.parsers.expat import ExpatError as ParseError from seiscomp import config # Python version depended string conversion if sys.version_info[0] < 3: py3input = raw_input #pylint: disable=E0602 else: py3input = input def tagname(element): names = element.tag.split("}") if len(names) == 0: return "" return names.pop() def oneliner(txt): return txt.strip().replace("\n", "") def block(txt, width=80): lines = [l.strip() for l in txt.strip().replace("\r", "").split('\n')] line = "\n".join(lines) current = 0 lines = [] while current < len(line): end = line.find('\n', current) if (end == -1) or (end - current > width): if len(line) - current > width: end = line.rfind(' ', current, current+width) if end == -1: end = line.find(' ', current) if end == -1: end = len(line) else: end = len(line) lines.append(line[current:end].strip()) current = end + 1 return lines class SetupNode: def __init__(self, parent, inp, next = None): self.parent = parent self.next = next self.child = None self.activeChild = None self.modname = "" self.groupname = "" self.input = inp self.value = "" self.path = "" self.optionValue = None self.isOption = False class Option: """ Setup input option wrapper. """ def __init__(self, value): self.value = value self.desc = None self.inputs = [] class Input: """ Setup input wrapper. """ def __init__(self, name, t, default_value=None): self.name = name self.type = t self.default_value = default_value self.text = None self.desc = None self.echo = None self.options = [] def dumpTree(cfg, node): if node.input: cfg.setString(node.modname + "." + node.path, node.value) if node.activeChild: if node.isOption: dumpTree(cfg, node.activeChild.child) else: dumpTree(cfg, node.activeChild) if not node.next is None: dumpTree(cfg, node.next) class Simple: """ Simple console setup handler that parses all description xml files and extracts the setup part. It asks for all available setting line by line and passes the resulting configuration back which is then passed to all init modules that have a setup method. """ def __init__(self): self.setupTree = SetupNode(None, None) self.paths = [] self.currentNode = None def run(self, env): desc_pattern = os.path.join( env.SEISCOMP_ROOT, "etc", "descriptions", "*.xml") xmls = glob.glob(desc_pattern) setup_groups = {} for f in xmls: try: tree = ElementTree.parse(f) except ParseError as xxx_todo_changeme: (err) = xxx_todo_changeme sys.stderr.write("%s: parsing XML failed: %s\n" % (f, err)) continue root = tree.getroot() if tagname(root) != "seiscomp": sys.stderr.write( "%s: wrong root tag, expected 'seiscomp'\n" % f) continue # Read all modules mods = tree.findall("module") for mod in mods: modname = mod.get('name') if not modname: sys.stderr.write("%s: skipping module without name\n" % f) continue if modname in setup_groups: raise Exception( "%s: duplicate module name: %s" % (f, modname)) setup = mod.find("setup") if setup is None: continue groups = setup.findall("group") if len(groups) == 0: continue setup_groups[modname] = groups # Read all plugin's plugins = tree.findall("plugin") for plugin in plugins: try: modname = plugin.find('extends').text.strip() except: raise Exception("%s: plugin does not define 'extends'" % f) if modname.find('\n') >= 0: raise Exception("%s: wrong module name in plugin." \ "extends: no newlines allowed" % f) if not modname: sys.stderr.write("%s: skipping module without name\n" % f) continue setup = plugin.find("setup") if setup is None: continue groups = setup.findall("group") if len(groups) == 0: continue if modname in setup_groups: setup_groups[modname] += groups else: setup_groups[modname] = groups for name, groups in sorted(setup_groups.items()): self.addGroups(self.setupTree, name, groups) # Always descend to the first child (if available) self.setupTree.activeChild = self.setupTree.child self.currentNode = self.setupTree.activeChild sys.stdout.write(''' ==================================================================== SeisComP setup ==================================================================== This initializes the configuration of your installation. If you already made adjustments to the configuration files be warned that this setup will overwrite existing parameters with default values. This is not a configurator for all options of your setup but helps to setup initial standard values. -------------------------------------------------------------------- Hint: Entered values starting with a dot (.) are handled as commands. Available commands are: quit: Quit setup without modification to your configuration. back: Go back to the previous parameter. help: Show help about the current parameter (if available). If you need to enter a value with a leading dot, escape it with backslash, e.g. "\.value". -------------------------------------------------------------------- ''') try: self.fillTree() except StopIteration: raise Exception("aborted by user") cfg = config.Config() dumpTree(cfg, self.setupTree) return cfg def addGroups(self, node, modname, groups): for g in groups: self.addInputs(None, node, modname, g.get( 'name'), g, g.get('name', "") + ".") def addInputs(self, obj, parent, modname, group, xml, prefix): childs = parent.child; if not childs is None: while not childs.next is None: childs = childs.next inputs = xml.findall("input") for inp in inputs: name = inp.get('name') if not name: raise Exception("%s: no name defined" % prefix) input_ = Input(name, inp.get('type'), inp.get('default')) try: input_.text = oneliner(inp.find('text').text) except Exception: input_.text = input_.name try: input_.desc = block(inp.find('description').text) except Exception: pass input_.echo = inp.get('echo') if obj: obj.inputs.append(input_) opts = inp.findall("option") node = SetupNode(parent, input_) node.path = prefix + input_.name node.value = input_.default_value node.modname = modname node.groupname = group node.isOption = len(opts) > 0 if childs is None: childs = node parent.child = childs else: childs.next = node childs = childs.next; options = node.child for opt in opts: value = opt.get('value') if not value: raise Exception("%s: option without value" % prefix) optionNode = SetupNode(node, input_) optionNode.path = node.path + "." + value optionNode.modname = modname optionNode.groupname = group optionNode.isOption = False optionNode.optionValue = value option = Option(value) try: option.desc = block(opt.find('description').text, 74) except Exception: pass input_.options.append(option) if options is None: options = optionNode node.child = options else: options.next = optionNode options = options.next self.addInputs(option, optionNode, modname, group, opt, node.path + ".") def fillTree(self): while True: if not self.currentNode: sys.stdout.write("\nFinished setup\n--------------\n\n") sys.stdout.write("P) Proceed to apply configuration\n") sys.stdout.write("B) Back to last parameter\n") sys.stdout.write("Q) Quit without changes\n") value = py3input('Command? [P]: ').upper() if value == "Q": raise StopIteration() if value == "P" or not value: sys.stdout.write("\nRunning setup\n-------------\n\n") return if value == "B": self.prevStep() continue sys.stdout.write("\nEnter either p, b or q\n") continue if not self.currentNode.input: self.nextStep() continue default_value = self.valueToString(self.currentNode) isChoice = False isPassword = False if self.currentNode.input.echo == "password": isPassword = True node_text = default_value prompt = self.currentNode.input.text if isPassword: node_text = '*' * len(node_text) prompt += " (input not echoed)" if (not self.currentNode.input.type or \ self.currentNode.input.type != "boolean") and \ len(self.currentNode.input.options) > 0: idx = 0 def_idx = 0 for opt in self.currentNode.input.options: sys.stdout.write("%2d) %s\n" % (idx, opt.value)) for l in opt.desc: sys.stdout.write(" %s\n" % l) if default_value == opt.value: def_idx = idx idx += 1 isChoice = True prompt += " [%d]: " % def_idx else: prompt += " [%s]: " % node_text if self.currentNode.input.echo == "password": value = getpass.getpass(prompt) else: value = py3input(prompt) if not value: value = default_value elif value == ".help": if self.currentNode.input.desc: sys.stdout.write("\n%s\n\n" % "\n".join(self.currentNode.input.desc)) else: sys.stdout.write("\nSorry, no help available.\n\n") continue elif value == ".back": self.prevStep() continue elif value == ".quit": raise StopIteration() elif value.startswith("."): sys.stdout.write("Unknown command. Values starting with '.' are handled has commands such as\n" "'.help', '.quit' or '.back'. To use a leading dot in a value, escape it with '\'\n" "e.g. '\.color'\n") continue else: # Replace leading \. with . if value.startswith('\\.'): value = value[1:] if isChoice: try: idx = int(value) except ValueError: idx = -1 if idx < 0 or idx >= len(self.currentNode.input.options): sys.stdout.write("\nEnter a number between 0 and %d\n\n" % ( len(self.currentNode.input.options)-1)) continue value = self.currentNode.input.options[idx].value if self.currentNode.input.type and self.currentNode.input.type == "boolean": if not value in ["yes", "no"]: sys.stdout.write("Please enter 'yes' or 'no'\n") continue if value == "yes": value = "true" else: value = "false" self.currentNode.value = value self.nextStep() @staticmethod def valueToString(node): if not node.input.type: if node.value is None: return "" return node.value if node.input.type == "boolean": if node.value == "true": return "yes" if node.value == "false": return "no" return "yes" if node.value is None: return "" return node.value def prevStep(self): if len(self.paths) == 0: sys.stdout.write("No previous step available\n") return self.currentNode = self.paths.pop() def nextStep(self): self.currentNode.activeChild = None self.paths.append(self.currentNode) # Choice? if self.currentNode.isOption: child = self.currentNode.child while not child is None: if child.optionValue == self.currentNode.value: if not child.child is None: self.currentNode.activeChild = child self.currentNode = child.child return break child = child.next next = self.currentNode.next while next is None and not self.currentNode.parent is None: self.currentNode = self.currentNode.parent if not self.currentNode.optionValue is None: continue next = self.currentNode.next self.currentNode = next