You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
497 lines
16 KiB
Python
497 lines
16 KiB
Python
1 year ago
|
############################################################################
|
||
|
# 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
|