[node-patches] Change in ovirt-node[master]: Introduce abstract UI Elements
fabiand at fedoraproject.org
fabiand at fedoraproject.org
Tue Dec 11 20:09:34 UTC 2012
Fabian Deutsch has uploaded a new change for review.
Change subject: Introduce abstract UI Elements
......................................................................
Introduce abstract UI Elements
Change-Id: I53fe904095f99397b96dcaefa50e3dd40cb33805
Signed-off-by: Fabian Deutsch <fabiand at fedoraproject.org>
---
A scripts/tui/src/.pylintrc
M scripts/tui/src/Makefile
M scripts/tui/src/app
A scripts/tui/src/ovirt/node/exceptions.py
M scripts/tui/src/ovirt/node/plugins/__init__.py
M scripts/tui/src/ovirt/node/plugins/example.py
M scripts/tui/src/ovirt/node/plugins/features.py
M scripts/tui/src/ovirt/node/plugins/kdump.py
M scripts/tui/src/ovirt/node/plugins/ping.py
M scripts/tui/src/ovirt/node/plugins/status.py
M scripts/tui/src/ovirt/node/plugins/usage.py
M scripts/tui/src/ovirt/node/tui.py
A scripts/tui/src/ovirt/node/ui/__init__.py
A scripts/tui/src/ovirt/node/ui/builder.py
R scripts/tui/src/ovirt/node/ui/widgets.py
M scripts/tui/src/ovirt/node/utils/process.py
M scripts/tui/src/ovirt/node/valid.py
17 files changed, 854 insertions(+), 419 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/ovirt-node refs/changes/83/9883/1
diff --git a/scripts/tui/src/.pylintrc b/scripts/tui/src/.pylintrc
new file mode 100644
index 0000000..bfafc62
--- /dev/null
+++ b/scripts/tui/src/.pylintrc
@@ -0,0 +1,249 @@
+[MASTER]
+
+# Specify a configuration file.
+#rcfile=
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+#init-hook=
+
+# Profiled execution.
+profile=no
+
+# Add files or directories to the blacklist. They should be base names, not
+# paths.
+ignore=CVS
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# List of plugins (as comma separated values of python modules names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+
+[MESSAGES CONTROL]
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time.
+#enable=
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifier separated by comma (,) or put this option
+# multiple time (only on the command line, not in the configuration file where
+# it should appear only once).
+#disable=
+
+
+[REPORTS]
+
+# Set the output format. Available formats are text, parseable, colorized, msvs
+# (visual studio) and html
+output-format=text
+
+# Include message's id in output
+include-ids=no
+
+# Put messages in a separate file for each module / package specified on the
+# command line instead of printing them on stdout. Reports (if any) will be
+# written in a file name "pylint_global.[txt|html]".
+files-output=no
+
+# Tells whether to display a full report or only the messages
+reports=yes
+
+# Python expression which should return a note less than 10 (10 is the highest
+# note). You have access to the variables errors warning, statement which
+# respectively contain the number of errors / warnings messages and the total
+# number of statements analyzed. This is used by the global evaluation report
+# (RP0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Add a comment according to your evaluation note. This is used by the global
+# evaluation report (RP0004).
+comment=no
+
+
+[TYPECHECK]
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# List of classes names for which member attributes should not be checked
+# (useful for classes with attributes dynamically set).
+ignored-classes=SQLObject
+
+# When zope mode is activated, add a predefined set of Zope acquired attributes
+# to generated-members.
+zope=no
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E0201 when accessed. Python regular
+# expressions are accepted.
+generated-members=REQUEST,acl_users,aq_parent
+
+
+[BASIC]
+
+# Required attributes for module, separated by a comma
+required-attributes=
+
+# List of builtins function names that should not be used, separated by a comma
+bad-functions=map,filter,apply,input
+
+# Regular expression which should only match correct module names
+module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Regular expression which should only match correct module level names
+const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Regular expression which should only match correct class names
+class-rgx=[A-Z_][a-zA-Z0-9]+$
+
+# Regular expression which should only match correct function names
+function-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct method names
+method-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct instance attribute names
+attr-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct argument names
+argument-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct variable names
+variable-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct list comprehension /
+# generator expression variable names
+inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
+
+# Good variable names which should always be accepted, separated by a comma
+good-names=i,j,k,ex,Run,_
+
+# Bad variable names which should always be refused, separated by a comma
+bad-names=foo,bar,baz,toto,tutu,tata
+
+# Regular expression which should only match functions or classes name which do
+# not require a docstring
+no-docstring-rgx=__.*__
+
+
+[SIMILARITIES]
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+
+[FORMAT]
+
+# Maximum number of characters on a single line.
+max-line-length=80
+
+# Maximum number of lines in a module
+max-module-lines=1000
+
+# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
+# tab).
+indent-string=' '
+
+
+[VARIABLES]
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# A regular expression matching the beginning of the name of dummy variables
+# (i.e. not used).
+dummy-variables-rgx=_|dummy
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid to define new builtins when possible.
+additional-builtins=
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,XXX,TODO
+
+
+[CLASSES]
+
+# List of interface methods to ignore, separated by a comma. This is used for
+# instance to not check methods defines in Zope's Interface base class.
+ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,__new__,setUp
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls
+
+
+[IMPORTS]
+
+# Deprecated modules which should not be used, separated by a comma
+deprecated-modules=regsub,string,TERMIOS,Bastion,rexec
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report RP0402 must not be disabled)
+import-graph=
+
+# Create a graph of external dependencies in the given file (report RP0402 must
+# not be disabled)
+ext-import-graph=
+
+# Create a graph of internal dependencies in the given file (report RP0402 must
+# not be disabled)
+int-import-graph=
+
+
+[DESIGN]
+
+# Maximum number of arguments for function / method
+max-args=5
+
+# Argument names that match this expression will be ignored. Default to name
+# with leading underscore
+ignored-argument-names=_.*
+
+# Maximum number of locals for function / method body
+max-locals=15
+
+# Maximum number of return / yield for function / method body
+max-returns=6
+
+# Maximum number of branch for function / method body
+max-branchs=12
+
+# Maximum number of statements in function / method body
+max-statements=50
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=7
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=2
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+
+[EXCEPTIONS]
+
+# Exceptions that will emit a warning when being caught. Defaults to
+# "Exception"
+overgeneral-exceptions=Exception
diff --git a/scripts/tui/src/Makefile b/scripts/tui/src/Makefile
index 17262af..54a930c 100644
--- a/scripts/tui/src/Makefile
+++ b/scripts/tui/src/Makefile
@@ -3,7 +3,7 @@
XMLSOURCES=$(shell find . -name \*.xml -or -name \*.xsl)
CLEANFILES=$(shell find . -name \*.pyc -o -name \*.pyo -o -name \*~)
-check-local: doctests pep8 pyflakes pylint
+check-local: doctests pep8 pyflakes
@echo -e "---\n Passed.\n---"
doctests:
diff --git a/scripts/tui/src/app b/scripts/tui/src/app
index 1ea2e40..844a59a 100755
--- a/scripts/tui/src/app
+++ b/scripts/tui/src/app
@@ -1,3 +1,5 @@
#!/bin/bash
-PYTHONPATH=$PYTHONPATH:. python -B -m ovirt.node.main "$@"
+export PYTHONPATH=$PYTHONPATH:
+export PYTHONDONTWRITEBYTECODE=x
+python -B -m ovirt.node.main "$@"
diff --git a/scripts/tui/src/ovirt/node/exceptions.py b/scripts/tui/src/ovirt/node/exceptions.py
new file mode 100644
index 0000000..ad31931
--- /dev/null
+++ b/scripts/tui/src/ovirt/node/exceptions.py
@@ -0,0 +1,44 @@
+#!/usr/bin/python
+#
+# __init__.py - Copyright (C) 2012 Red Hat, Inc.
+# Written by Fabian Deutsch <fabiand at redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the 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
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA. A copy of the GNU General Public License is
+# also available at http://www.gnu.org/copyleft/gpl.html.
+
+"""
+oVirt Node specific exceptions
+"""
+
+
+class InvalidData(Exception):
+ """E.g. if a string contains characters which are not allowed
+ """
+ def __init__(self, msg):
+ self.message = msg
+
+ def __str__(self):
+ return repr(self.message)
+
+
+class Concern(InvalidData):
+ """E.g. if a password is not secure enough
+ FIXME very ... unspecific
+ """
+ def __init__(self, msg):
+ self.message = msg
+
+ def __str__(self):
+ return repr(self.message)
diff --git a/scripts/tui/src/ovirt/node/plugins/__init__.py b/scripts/tui/src/ovirt/node/plugins/__init__.py
index 6c6f541..2560fdc 100644
--- a/scripts/tui/src/ovirt/node/plugins/__init__.py
+++ b/scripts/tui/src/ovirt/node/plugins/__init__.py
@@ -25,6 +25,7 @@
import logging
import ovirt.node.plugins
+import ovirt.node.exceptions
LOGGER = logging.getLogger(__name__)
@@ -123,7 +124,7 @@
msg = self.validators()[path](value)
# True and None are allowed values
if msg not in [True, None]:
- raise ovirt.node.plugins.InvalidData(msg)
+ raise ovirt.node.exceptions.InvalidData(msg)
return True
def has_ui(self):
@@ -144,17 +145,6 @@
List of (path, widget)
"""
raise NotImplementedError()
-
- def ui_config(self):
- """Specifies additional details for the UI
- E.g. if some defaults should be omitted (default save button).
-
- save_button: If the save button shall be displayed (True)
-
- Returns:
- A dict of config items and their values.
- """
- return {}
def on_change(self, changes):
"""Applies the changes to the plugins model, will do all required logic
@@ -186,7 +176,7 @@
self.on_change(model)
except NotImplementedError:
LOGGER.debug("Plugin has no model")
- except InvalidData:
+ except ovirt.node.exceptions.InvalidData:
LOGGER.warning("Plugin has invalid model")
is_valid = False
return is_valid
@@ -234,190 +224,3 @@
else:
LOGGER.debug("No changes detected")
return self.on_merge(real_changes)
-
-
-# http://stackoverflow.com/questions/739654/understanding-python-decorators
-class Widget(object):
- _signal_cbs = None
-
- def __init__(self):
- """Registers all widget signals.
- All signals must be given in self.signals
- """
- LOGGER.debug("Initializing new %s" % self)
-
- @staticmethod
- def signal_change(func):
- """A decorator for methods which should emit signals
- """
- def wrapper(self, userdata=None, *args, **kwargs):
- signame = func.__name__
- self._register_signal(signame)
- self.emit_signal(signame, userdata)
- return func(self, userdata)
- return wrapper
-
- def _register_signal(self, name):
- """Each signal that get's emitted must be registered using this
- function.
-
- This is just to have an overview over the signals.
- """
- if self._signal_cbs is None:
- self._signal_cbs = {}
- if name not in self._signal_cbs:
- self._signal_cbs[name] = []
- LOGGER.debug("Registered new signal '%s' for '%s'" % (name, self))
-
- def connect_signal(self, name, cb):
- """Connect an callback to a signal
- """
- if not self._signal_cbs:
- raise Exception("Signals not initialized %s for %s" % (name, self))
- if name not in self._signal_cbs:
- raise Exception("Unregistered signal %s for %s" % (name, self))
- self._signal_cbs[name].append(cb)
-
- def emit_signal(self, name, userdata=None):
- """Emit a signal
- """
- if self._signal_cbs is None or name not in self._signal_cbs:
- return False
- for cb in self._signal_cbs[name]:
- LOGGER.debug("CB for sig %s: %s" % (name, cb))
- cb(self, userdata)
-
- def set_text(self, value):
- """A general way to set the "text" of a widget
- """
- raise NotImplementedError
-
-
-class InputWidget(Widget):
- """
- """
-
- def __init__(self, is_enabled):
- self.enabled(is_enabled)
- super(InputWidget, self).__init__()
-
- @Widget.signal_change
- def enabled(self, is_enabled=None):
- if is_enabled in [True, False]:
- self._enabled = is_enabled
- return self._enabled
-
- @Widget.signal_change
- def text(self, text=None):
- if text != None:
- self._text = text
- return self._text
-
- def set_text(self, txt):
- self.text(txt)
-
-
-class Label(Widget):
- """Represents a r/o label
- """
-
- def __init__(self, text):
- self.text(text)
- super(Label, self).__init__()
-
- @Widget.signal_change
- def text(self, text=None):
- if text != None:
- self._text = text
- return self._text
-
- def set_text(self, txt):
- self.text(txt)
-
-
-class Header(Label):
- pass
-
-
-class KeywordLabel(Label):
- """A label consisting of a prominent keyword and a value.
- E.g.: <b>Networking:</b> Enabled
- """
-
- def __init__(self, keyword, text=""):
- super(Label, self).__init__()
- self.keyword = keyword
- self.text(text)
-
-
-class Entry(InputWidget):
- """Represents an entry field
- TODO multiline
- """
-
- def __init__(self, label, value=None, enabled=True):
- self.label = label
- self._text = value
- super(Entry, self).__init__(enabled)
-
-
-class PasswordEntry(Entry):
- pass
-
-
-class Button(Label):
- def __init__(self, label, enabled=True):
- Label.__init__(self, label)
-# InputWidget.__init__(self, enabled)
-
-
-class SaveButton(Button):
- def __init__(self, enabled=True):
- super(SaveButton, self).__init__(self, "Save", enabled)
-
-
-class Divider(Widget):
- def __init__(self, char=u" "):
- self.char = char
-
-
-class Options(Widget):
-
- def __init__(self, label, options):
- self.label = label
- self.options = options
- self.option(options[0])
- super(Options, self).__init__()
-
- @Widget.signal_change
- def option(self, option=None):
- if option in self.options:
- self._option = option
- return self._option
-
- def set_text(self, txt):
- self.option(txt)
-
-
-class InvalidData(Exception):
- """E.g. if a string contains characters which are not allowed
- """
- def __init__(self, msg):
- self.message = msg
-
- def __str__(self):
- return repr(self.message)
-
-
-class Concern(InvalidData):
- """E.g. if a password is not secure enough
- """
- def __init__(self, msg):
- self.message = msg
-
- def __str__(self):
- return repr(self.message)
-
-
-class ContentRefreshRequest(Exception):
- pass
diff --git a/scripts/tui/src/ovirt/node/plugins/example.py b/scripts/tui/src/ovirt/node/plugins/example.py
index e46df24..aacbe86 100644
--- a/scripts/tui/src/ovirt/node/plugins/example.py
+++ b/scripts/tui/src/ovirt/node/plugins/example.py
@@ -24,6 +24,8 @@
import logging
import ovirt.node.plugins
+import ovirt.node.ui
+import ovirt.node.exceptions
import ovirt.node.valid
LOGGER = logging.getLogger(__name__)
@@ -63,16 +65,17 @@
"""
widgets = [
("foo.section",
- ovirt.node.plugins.Header("Subsection")),
+ ovirt.node.ui.Header("Subsection")),
("foo.hostname",
- ovirt.node.plugins.Entry(label="Hostname")),
+ ovirt.node.ui.Entry(label="Hostname")),
("foo.port",
- ovirt.node.plugins.Entry(label="Port")),
+ ovirt.node.ui.Entry(label="Port")),
("foo.password",
- ovirt.node.plugins.PasswordEntry(label="Password")),
+ ovirt.node.ui.PasswordEntry(label="Password")),
]
self._widgets = dict(widgets)
- return widgets
+ page = ovirt.node.ui.Page(widgets)
+ return page
def on_change(self, changes):
"""Applies the changes to the plugins model, will do all required logic
@@ -82,10 +85,11 @@
LOGGER.debug("Found foo.hostname")
if "/" in changes["foo.hostname"]:
- raise ovirt.node.plugins.InvalidData("No slash allowed")
+ raise ovirt.node.exceptions.InvalidData("No slash allowed")
if len(changes["foo.hostname"]) < 5:
- raise ovirt.node.plugins.Concern("Should be at least 5 chars")
+ raise ovirt.node.exceptions.Concern(
+ "Should be at least 5 chars")
self._model.update(changes)
@@ -101,7 +105,7 @@
LOGGER.debug("Found foo.port")
if "/" in changes["foo.port"]:
- raise ovirt.node.plugins.InvalidData("No slashes allowed")
+ raise ovirt.node.exceptions.InvalidData("No slashes allowed")
return True
@@ -111,3 +115,7 @@
LOGGER.debug("saving %s" % effective_changes)
# Look for conflicts etc
self._model.update(effective_changes)
+
+ page = ovirt.node.ui.Dialog("Saved!",
+ [("foo.text", ovirt.node.ui.Label("Saved"))])
+ return page
diff --git a/scripts/tui/src/ovirt/node/plugins/features.py b/scripts/tui/src/ovirt/node/plugins/features.py
index 20e8379..69cfc2b 100644
--- a/scripts/tui/src/ovirt/node/plugins/features.py
+++ b/scripts/tui/src/ovirt/node/plugins/features.py
@@ -24,6 +24,7 @@
import logging
import ovirt.node.plugins
+import ovirt.node.ui
LOGGER = logging.getLogger(__name__)
@@ -44,11 +45,9 @@
def ui_content(self):
widgets = [
- ("features.info", ovirt.node.plugins.Label(features))
+ ("features.info", ovirt.node.ui.Label(features))
]
- return widgets
- def ui_config(self):
- return {
- "save_button": False
- }
+ page = ovirt.node.ui.Page(widgets)
+ page.has_save_button = False
+ return page
diff --git a/scripts/tui/src/ovirt/node/plugins/kdump.py b/scripts/tui/src/ovirt/node/plugins/kdump.py
index 219050f..88ca080 100644
--- a/scripts/tui/src/ovirt/node/plugins/kdump.py
+++ b/scripts/tui/src/ovirt/node/plugins/kdump.py
@@ -25,7 +25,7 @@
import ovirt.node.plugins
import ovirt.node.valid
-import ovirt.node.plugins
+import ovirt.node.ui
import ovirt.node.utils
LOGGER = logging.getLogger(__name__)
@@ -75,14 +75,16 @@
This is an ordered list of (path, widget) tuples.
"""
widgets = [
- ("kdump.header", ovirt.node.plugins.Header("Configure Kdump")),
- ("kdump.type", ovirt.node.plugins.Options("Type", self._types)),
- ("kdump.ssh_location", ovirt.node.plugins.Entry("SSH Location")),
- ("kdump.nfs_location", ovirt.node.plugins.Entry("NFS Location")),
+ ("kdump.header", ovirt.node.ui.Header("Configure Kdump")),
+ ("kdump.type", ovirt.node.ui.Options("Type", self._types)),
+ ("kdump.ssh_location", ovirt.node.ui.Entry("SSH Location")),
+ ("kdump.nfs_location", ovirt.node.ui.Entry("NFS Location")),
]
# Save it "locally" as a dict, for better accessability
self._widgets = dict(widgets)
- return widgets
+
+ page = ovirt.node.ui.Page(widgets)
+ return page
def on_change(self, changes):
"""Applies the changes to the plugins model, will do all required logic
diff --git a/scripts/tui/src/ovirt/node/plugins/ping.py b/scripts/tui/src/ovirt/node/plugins/ping.py
index ff24354..e3a3bb7 100644
--- a/scripts/tui/src/ovirt/node/plugins/ping.py
+++ b/scripts/tui/src/ovirt/node/plugins/ping.py
@@ -25,7 +25,7 @@
import ovirt.node.plugins
import ovirt.node.valid
-import ovirt.node.plugins
+import ovirt.node.ui
import ovirt.node.utils.process
LOGGER = logging.getLogger(__name__)
@@ -69,21 +69,19 @@
This is an ordered list of (path, widget) tuples.
"""
widgets = [
- ("ping.header", ovirt.node.plugins.Header("Ping a remote host")),
- ("ping.address", ovirt.node.plugins.Entry("Address")),
- ("ping.count", ovirt.node.plugins.Entry("Count")),
- ("ping.do_ping", ovirt.node.plugins.Button("Ping")),
- ("ping.result-divider", ovirt.node.plugins.Divider("-")),
- ("ping.result", ovirt.node.plugins.Label("Result:")),
+ ("ping.header", ovirt.node.ui.Header("Ping a remote host")),
+ ("ping.address", ovirt.node.ui.Entry("Address")),
+ ("ping.count", ovirt.node.ui.Entry("Count")),
+ ("ping.do_ping", ovirt.node.ui.Button("Ping")),
+ ("ping.result-divider", ovirt.node.ui.Divider("-")),
+ ("ping.result", ovirt.node.ui.Label("Result:")),
]
# Save it "locally" as a dict, for better accessability
self._widgets = dict(widgets)
- return widgets
- def ui_config(self):
- return {
- "save_button": False
- }
+ page = ovirt.node.ui.Page(widgets)
+ page.has_save_button = False
+ return page
def on_change(self, changes):
"""Applies the changes to the plugins model, will do all required logic
diff --git a/scripts/tui/src/ovirt/node/plugins/status.py b/scripts/tui/src/ovirt/node/plugins/status.py
index e334ad8..82bb6bb 100644
--- a/scripts/tui/src/ovirt/node/plugins/status.py
+++ b/scripts/tui/src/ovirt/node/plugins/status.py
@@ -25,13 +25,18 @@
import ovirt.node.plugins
import ovirt.node.valid
-import ovirt.node.plugins
+import ovirt.node.ui
import ovirt.node.utils
LOGGER = logging.getLogger(__name__)
class Plugin(ovirt.node.plugins.NodePlugin):
+ """This is the summary page, summarizing all sorts of informations
+
+ There are no validators, as there is no input.
+ """
+
_model = None
_widgets = None
@@ -50,28 +55,21 @@
}
return self._model
- def validators(self):
- """Validators validate the input on change and give UI feedback
- """
- return {}
-
- def ui_config(self):
- return {
- "save_button": False
- }
-
def ui_content(self):
"""Describes the UI this plugin requires
This is an ordered list of (path, widget) tuples.
"""
widgets = [
("status.networking",
- ovirt.node.plugins.KeywordLabel("Networking")),
+ ovirt.node.ui.KeywordLabel("Networking")),
("status.logs",
- ovirt.node.plugins.KeywordLabel("Logs")),
+ ovirt.node.ui.KeywordLabel("Logs")),
("status.vms.running",
- ovirt.node.plugins.KeywordLabel("Running VMs")),
+ ovirt.node.ui.KeywordLabel("Running VMs")),
]
# Save it "locally" as a dict, for better accessability
self._widgets = dict(widgets)
- return widgets
+
+ page = ovirt.node.ui.Page(widgets)
+ page.has_save_button = False
+ return page
diff --git a/scripts/tui/src/ovirt/node/plugins/usage.py b/scripts/tui/src/ovirt/node/plugins/usage.py
index 42080e2..8d0254c 100644
--- a/scripts/tui/src/ovirt/node/plugins/usage.py
+++ b/scripts/tui/src/ovirt/node/plugins/usage.py
@@ -24,6 +24,7 @@
import logging
import ovirt.node.plugins
+import ovirt.node.ui
LOGGER = logging.getLogger(__name__)
@@ -46,11 +47,9 @@
def ui_content(self):
widgets = [
- ("usage.info", ovirt.node.plugins.Label(usage))
+ ("usage.info", ovirt.node.ui.Label(usage))
]
- return widgets
- def ui_config(self):
- return {
- "save_button": False
- }
+ page = ovirt.node.ui.Page(widgets)
+ page.has_save_button = False
+ return page
diff --git a/scripts/tui/src/ovirt/node/tui.py b/scripts/tui/src/ovirt/node/tui.py
index 76a092d..54c24c3 100644
--- a/scripts/tui/src/ovirt/node/tui.py
+++ b/scripts/tui/src/ovirt/node/tui.py
@@ -21,13 +21,17 @@
"""
The urwid TUI base library
"""
+
import urwid
import logging
import ovirt.node
-import ovirt.node.widgets
import ovirt.node.plugins
+import ovirt.node.ui
+import ovirt.node.ui.widgets
+import ovirt.node.ui.builder
+import ovirt.node.exceptions
import ovirt.node.utils
LOGGER = logging.getLogger(__name__)
@@ -70,10 +74,10 @@
self.app = app
def __build_menu(self):
- self.__menu = ovirt.node.widgets.PluginMenu(self.__pages)
+ self.__menu = ovirt.node.ui.widgets.PluginMenu(self.__pages)
def menu_item_changed(plugin):
- self.__change_to_page(plugin)
+ self.__change_to_plugin(plugin)
urwid.connect_signal(self.__menu, 'changed', menu_item_changed)
def __create_screen(self):
@@ -87,159 +91,36 @@
footer = urwid.Text(self.footer, wrap='clip')
return urwid.Frame(body, header, footer)
- def __build_widget_for_item(self, plugin, path, item):
- item_to_widget_map = {
- ovirt.node.plugins.Label: ovirt.node.widgets.Label,
- ovirt.node.plugins.Header: ovirt.node.widgets.Header,
- ovirt.node.plugins.Entry: ovirt.node.widgets.Entry,
- ovirt.node.plugins.PasswordEntry: ovirt.node.widgets.PasswordEntry,
- ovirt.node.plugins.Button: ovirt.node.widgets.Button,
- ovirt.node.plugins.SaveButton: ovirt.node.widgets.Button,
- ovirt.node.plugins.Divider: ovirt.node.widgets.Divider,
- ovirt.node.plugins.Options: ovirt.node.widgets.Options,
- ovirt.node.plugins.KeywordLabel: ovirt.node.widgets.KeywordLabel,
- }
+ def __change_to_plugin(self, plugin):
+ page = ovirt.node.ui.builder.page_from_plugin(self, plugin)
+ self._display_page(page)
- assert type(item) in item_to_widget_map.keys(), \
- "No widget for item type"
-
- widget = None
- widget_class = item_to_widget_map[type(item)]
-
- if type(item) in [ovirt.node.plugins.Entry, \
- ovirt.node.plugins.PasswordEntry]:
- widget = widget_class(item.label)
-
- widget.enable(item.enabled)
-
- def on_item_enabled_change_cb(w, v):
- LOGGER.debug("Model changed, updating widget '%s': %s" % (w,
- v))
- if widget.selectable() != v:
- widget.enable(v)
- item.connect_signal("enabled", on_item_enabled_change_cb)
-
- def on_widget_value_change(widget, new_value):
- LOGGER.debug("Widget changed, updating model '%s'" % path)
-
- try:
- change = {path: new_value}
- plugin.validate(change)
- plugin._on_ui_change(change)
- widget.notice = ""
- widget.valid(True)
- plugin.__save_button.enable(True)
-
- except ovirt.node.plugins.Concern as e:
- LOGGER.error("Concern when updating: %s" % e)
-
- except ovirt.node.plugins.InvalidData as e:
- LOGGER.error("Invalid data when updating: %s" % e)
- widget.notice = e.message
- widget.valid(False)
- plugin.__save_button.enable(False)
-
- # FIXME page validation must happen within tui, not plugin
- # as UI data should be handled in tui
-
- self.__loop.draw_screen()
- urwid.connect_signal(widget, 'change', on_widget_value_change)
-
- elif type(item) in [ovirt.node.plugins.Header, \
- ovirt.node.plugins.Label,
- ovirt.node.plugins.KeywordLabel]:
- if type(item) is ovirt.node.plugins.KeywordLabel:
- widget = widget_class(item.keyword, item.text())
- else:
- widget = widget_class(item.text())
-
- def on_item_text_change_cb(w, v):
- LOGGER.debug("Model changed, updating widget '%s': %s" % (w,
- v))
- widget.text(v)
- self.__loop.draw_screen()
- item.connect_signal("text", on_item_text_change_cb)
-
- elif type(item) in [ovirt.node.plugins.Button,
- ovirt.node.plugins.SaveButton]:
- widget = widget_class(item.text())
-
- def on_widget_click_cb(widget, data=None):
- if type(item) is ovirt.node.plugins.SaveButton:
- plugin._on_ui_save()
- else:
-# Nit propagating the signal as a signal to the plugin
-# item.emit_signal("click", widget)
- plugin._on_ui_change({path: True})
- urwid.connect_signal(widget, "click", on_widget_click_cb)
-
- elif type(item) in [ovirt.node.plugins.Divider]:
- widget = widget_class(item.char)
-
- elif type(item) in [ovirt.node.plugins.Options]:
- widget = widget_class(item.label, item.options,
- plugin.model()[path])
-
- def on_widget_change_cb(widget, data):
- LOGGER.debug(data)
- item.option(data)
- plugin._on_ui_change({path: data})
- urwid.connect_signal(widget, "change", on_widget_change_cb)
-
- if type(item) in [ovirt.node.plugins.Entry,
- ovirt.node.plugins.PasswordEntry,
- ovirt.node.plugins.KeywordLabel,
- ovirt.node.plugins.Options]:
- widget.set_text(plugin.model()[path])
-
- return widget
-
- def __build_plugin_widget(self, plugin):
- """This method is building the widget for a plugin
- """
- widgets = []
- config = {
- "save_button": True
- }
-
- ui_content = plugin.ui_content()
- config.update(plugin.ui_config())
-
- # Always create the SaveButton, but only display it if requested
- # FIXME introduce a widget for the plugin page
- save = ovirt.node.widgets.Button("Save")
- urwid.connect_signal(save, 'click', lambda x: plugin._on_ui_save())
- plugin.__save_button = save
-
- for path, item in ui_content:
- widget = self.__build_widget_for_item(plugin, path, item)
- widgets.append(("flow", widget))
-
- if config["save_button"]:
- widgets.append(urwid.Filler(save))
-
- widgets.append(urwid.Filler(urwid.Text("")))
-
- LOGGER.debug("Triggering initial sematic checks for '%s'" % plugin)
- try:
- plugin.check_semantics()
- except:
- self.notify("error", "Initial model validation failed.")
-
- pile = urwid.Pile(widgets)
+ def _display_page(self, page):
# FIXME why is this fixed?
- widget = urwid.Filler(pile, ("fixed top", 1), height=20)
- return widget
+ filler = urwid.Filler(page, ("fixed top", 1), height=20)
+ self.__page_frame.body = filler
- def __change_to_page(self, plugin):
- plugin_widget = self.__build_plugin_widget(plugin)
- page = plugin_widget
- self.__page_frame.body = page
+ def _display_dialog(self, body, title):
+ filler = urwid.Filler(body, ("fixed top", 1), height=20)
+ dialog = ovirt.node.ui.widgets.ModalDialog(filler,
+ title, "esc",
+ self.__loop.widget)
+ self.__loop.widget = dialog
+
+ def display(self, widget):
+ return {
+ ovirt.node.ui.widgets.PageWidget: self._display_page
+ }[type(widget)](widget)
+
+ def popup(self, title, msg, buttons=None):
+ LOGGER.debug("Launching popup")
+ body = urwid.Filler(urwid.Text(msg))
+ self._display_dialog(body)
def __filter_hotkeys(self, keys, raw):
key = str(keys)
LOGGER.debug("Keypress: %s" % key)
- if type(self.__loop.widget) is ovirt.node.widgets.ModalDialog:
+ if type(self.__loop.widget) is ovirt.node.ui.widgets.ModalDialog:
LOGGER.debug("Modal dialog escape: %s" % key)
dialog = self.__loop.widget
if dialog.escape_key in keys:
@@ -255,17 +136,17 @@
self.register_hotkey(["esc"], self.quit)
self.register_hotkey(["q"], self.quit)
+ def draw_screen(self):
+ self.__loop.draw_screen()
+
+ def watch_pipe(self, cb):
+ """Return a fd to be used as stdout, cb called for each line
+ """
+ return self.__loop.watch_pipe(cb)
+
def notify(self, category, msg):
LOGGER.info("UI notification (%s): %s" % (category, msg))
# FIXME do notification
-
- def popup(self, title, msg, buttons=None):
- LOGGER.debug("Launching popup")
-
- dialog = ovirt.node.widgets.ModalDialog(urwid.Filler(urwid.Text(msg)),
- title, "esc",
- self.__loop.widget)
- self.__loop.widget = dialog
def suspended(self):
"""Supspends the screen to do something in the foreground
diff --git a/scripts/tui/src/ovirt/node/ui/__init__.py b/scripts/tui/src/ovirt/node/ui/__init__.py
new file mode 100644
index 0000000..5141734
--- /dev/null
+++ b/scripts/tui/src/ovirt/node/ui/__init__.py
@@ -0,0 +1,227 @@
+#!/usr/bin/python
+#
+# __init__.py - Copyright (C) 2012 Red Hat, Inc.
+# Written by Fabian Deutsch <fabiand at redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the 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
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA. A copy of the GNU General Public License is
+# also available at http://www.gnu.org/copyleft/gpl.html.
+
+"""
+This contains abstract UI Elements
+"""
+import logging
+
+LOGGER = logging.getLogger(__name__)
+
+
+# http://stackoverflow.com/questions/739654/understanding-python-decorators
+class Element(object):
+ """An abstract UI Element.
+ This basically provides signals to communicate between real UI widget and
+ the plugins
+ """
+ _signal_cbs = None
+
+ def __init__(self):
+ """Registers all widget signals.
+ All signals must be given in self.signals
+ """
+ LOGGER.debug("Initializing new %s" % self)
+
+ @staticmethod
+ def signal_change(func):
+ """A decorator for methods which should emit signals
+ """
+ def wrapper(self, userdata=None, *args, **kwargs):
+ signame = func.__name__
+ self._register_signal(signame)
+ self.emit_signal(signame, userdata)
+ return func(self, userdata)
+ return wrapper
+
+ def _register_signal(self, name):
+ """Each signal that get's emitted must be registered using this
+ function.
+
+ This is just to have an overview over the signals.
+ """
+ if self._signal_cbs is None:
+ self._signal_cbs = {}
+ if name not in self._signal_cbs:
+ self._signal_cbs[name] = []
+ LOGGER.debug("Registered new signal '%s' for '%s'" % (name, self))
+
+ def connect_signal(self, name, cb):
+ """Connect an callback to a signal
+ """
+ if not self._signal_cbs:
+ raise Exception("Signals not initialized %s for %s" % (name, self))
+ if name not in self._signal_cbs:
+ raise Exception("Unregistered signal %s for %s" % (name, self))
+ self._signal_cbs[name].append(cb)
+
+ def emit_signal(self, name, userdata=None):
+ """Emit a signal
+ """
+ if self._signal_cbs is None or name not in self._signal_cbs:
+ return False
+ LOGGER.debug("Emitting '%s'" % name)
+ for cb in self._signal_cbs[name]:
+ LOGGER.debug("... %s" % cb)
+ cb(self, userdata)
+
+ def set_text(self, value):
+ """A general way to set the "text" of a widget
+ """
+ raise NotImplementedError
+
+
+class InputElement(Element):
+ """An abstract UI Element pfor user input
+ """
+
+ def __init__(self, is_enabled):
+ self.enabled(is_enabled)
+ super(InputElement, self).__init__()
+
+ @Element.signal_change
+ def enabled(self, is_enabled=None):
+ if is_enabled in [True, False]:
+ self._enabled = is_enabled
+ return self._enabled
+
+ @Element.signal_change
+ def text(self, text=None):
+ if text != None:
+ self._text = text
+ return self._text
+
+ def set_text(self, txt):
+ self.text(txt)
+
+
+class ContainerElement(Element):
+ """An abstract container Element containing other Elements
+ """
+ widgets = []
+ pass
+
+
+class Page(ContainerElement):
+ """An abstract page with a couple of widgets
+ """
+ has_save_button = True
+
+ def __init__(self, widgets):
+ self.widgets = widgets
+
+
+class Dialog(Page):
+ """An abstract dialog, similar to a page
+ """
+ def __init__(self, title, widgets):
+ self.title = title
+ super(Dialog, self).__init__(widgets)
+
+
+class Label(Element):
+ """Represents a r/o label
+ """
+
+ def __init__(self, text):
+ self.text(text)
+ super(Label, self).__init__()
+
+ @Element.signal_change
+ def text(self, text=None):
+ if text != None:
+ self._text = text
+ return self._text
+
+ def set_text(self, txt):
+ self.text(txt)
+
+
+class Header(Label):
+ pass
+
+
+class KeywordLabel(Label):
+ """A label consisting of a prominent keyword and a value.
+ E.g.: <b>Networking:</b> Enabled
+ """
+
+ def __init__(self, keyword, text=""):
+ super(Label, self).__init__()
+ self.keyword = keyword
+ self.text(text)
+
+
+class Entry(InputElement):
+ """Represents an entry field
+ TODO multiline
+ """
+
+ def __init__(self, label, value=None, enabled=True):
+ self.label = label
+ self._text = value
+ super(Entry, self).__init__(enabled)
+
+
+class PasswordEntry(Entry):
+ pass
+
+
+class Button(InputElement):
+ def __init__(self, label, enabled=True):
+ super(Button, self).__init__(enabled)
+ self.text(label)
+
+ @Element.signal_change
+ def label(self, label=None):
+ if label != None:
+ self._label = label
+ return self._label
+
+ def set_text(self, txt):
+ self.text(txt)
+
+
+class SaveButton(Button):
+ def __init__(self, enabled=True):
+ super(SaveButton, self).__init__("Save", enabled)
+
+
+class Divider(Element):
+ def __init__(self, char=u" "):
+ self.char = char
+
+
+class Options(Element):
+
+ def __init__(self, label, options):
+ self.label = label
+ self.options = options
+ self.option(options[0])
+ super(Options, self).__init__()
+
+ @Element.signal_change
+ def option(self, option=None):
+ if option in self.options:
+ self._option = option
+ return self._option
+
+ def set_text(self, txt):
+ self.option(txt)
diff --git a/scripts/tui/src/ovirt/node/ui/builder.py b/scripts/tui/src/ovirt/node/ui/builder.py
new file mode 100644
index 0000000..6b35c61
--- /dev/null
+++ b/scripts/tui/src/ovirt/node/ui/builder.py
@@ -0,0 +1,219 @@
+#!/usr/bin/python
+#
+# tui.py - Copyright (C) 2012 Red Hat, Inc.
+# Written by Fabian Deutsch <fabiand at redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the 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
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA. A copy of the GNU General Public License is
+# also available at http://www.gnu.org/copyleft/gpl.html.
+
+"""
+A visitor to build the urwid TUI from the abstract UI definitions.
+Is based on the visitor pattern
+"""
+
+import urwid
+
+import logging
+
+import ovirt.node
+import ovirt.node.plugins
+import ovirt.node.ui
+import ovirt.node.ui.widgets
+import ovirt.node.ui.builder
+import ovirt.node.exceptions
+import ovirt.node.utils
+
+LOGGER = logging.getLogger(__name__)
+
+
+def page_from_plugin(tui, plugin):
+ element = plugin.ui_content()
+ widget = None
+
+ # FIXME could also be done using dict.
+ if type(element) is ovirt.node.ui.Page:
+ widget = build_page(tui, plugin, element)
+ else:
+ raise Exception("Unknown element container: %s" % element)
+
+ return widget
+
+
+def build_page(tui, plugin, container):
+ widgets = []
+
+ # Always create the SaveButton, but only display it if requested
+ #save = ovirt.node.ui.widgets.Button("Save")
+ #urwid.connect_signal(save, 'click', lambda x: plugin._on_ui_save())
+ save = build_button("", ovirt.node.ui.SaveButton(), tui, plugin)
+ plugin._save_button = save
+
+ for path, item in container.widgets:
+ widget = widget_for_item(tui, plugin, path, item)
+ widgets.append(("flow", widget))
+
+ if container.has_save_button:
+ widgets.append(urwid.Filler(save))
+
+ widgets.append(urwid.Filler(urwid.Text("")))
+
+ LOGGER.debug("Triggering initial sematic checks for '%s'" % plugin)
+ try:
+ plugin.check_semantics()
+ except:
+ tui.notify("error", "Initial model validation failed.")
+
+ page = ovirt.node.ui.widgets.PageWidget(widgets)
+
+ return page
+
+
+def widget_for_item(tui, plugin, path, item):
+ item_to_builder = {
+ ovirt.node.ui.Label: build_label,
+ ovirt.node.ui.Header: build_label,
+ ovirt.node.ui.KeywordLabel: build_label,
+ ovirt.node.ui.Entry: build_entry,
+ ovirt.node.ui.PasswordEntry: build_entry,
+ ovirt.node.ui.Button: build_button,
+ ovirt.node.ui.SaveButton: build_button,
+ ovirt.node.ui.Divider: build_divider,
+ ovirt.node.ui.Options: build_options,
+ }
+
+ # Check if builder is available for UI Element
+ assert type(item) in item_to_builder.keys(), \
+ "No widget for item type"
+
+ # Build widget from UI Element
+ build_func = item_to_builder[type(item)]
+ widget = build_func(path, item, tui, plugin)
+
+ # Populate with values
+ if type(item) in [ovirt.node.ui.Entry,
+ ovirt.node.ui.PasswordEntry,
+ ovirt.node.ui.KeywordLabel,
+ ovirt.node.ui.Options]:
+ widget.set_text(plugin.model()[path])
+
+ return widget
+
+
+def build_entry(path, item, tui, plugin):
+ widget = None
+ if type(item) is ovirt.node.ui.Entry:
+ widget = ovirt.node.ui.widgets.Entry(item.label)
+ else:
+ widget = ovirt.node.ui.widgets.PasswordEntry(item.label)
+
+ widget.enable(item.enabled)
+
+ def on_item_enabled_change_cb(w, v):
+ LOGGER.debug("Model changed, updating widget '%s': %s" % (w,
+ v))
+ if widget.selectable() != v:
+ widget.enable(v)
+ item.connect_signal("enabled", on_item_enabled_change_cb)
+
+ def on_widget_value_change(widget, new_value):
+ LOGGER.debug("Widget changed, updating model '%s'" % path)
+
+ try:
+ change = {path: new_value}
+ plugin.validate(change)
+ plugin._on_ui_change(change)
+ widget.notice = ""
+ widget.valid(True)
+ LOGGER.debug(plugin.__dict__)
+ plugin._save_button.enable(True)
+
+ except ovirt.node.exceptions.Concern as e:
+ LOGGER.error("Concern when updating: %s" % e)
+
+ except ovirt.node.exceptions.InvalidData as e:
+ LOGGER.error("Invalid data when updating: %s" % e)
+ widget.notice = e.message
+ widget.valid(False)
+ plugin._save_button.enable(False)
+
+ # FIXME page validation must happen within tui, not plugin
+ # as UI data should be handled in tui
+
+ tui.draw_screen()
+ urwid.connect_signal(widget, 'change', on_widget_value_change)
+
+ return widget
+
+
+def build_label(path, item, tui, plugin):
+ if type(item) is ovirt.node.ui.KeywordLabel:
+ widget = ovirt.node.ui.widgets.KeywordLabel(item.keyword,
+ item.text())
+ elif type(item) is ovirt.node.ui.Header:
+ widget = ovirt.node.ui.widgets.Header(item.text())
+ else:
+ widget = ovirt.node.ui.widgets.Label(item.text())
+
+ def on_item_text_change_cb(w, v):
+ LOGGER.debug("Model changed, updating widget '%s': %s" % (w,
+ v))
+ widget.text(v)
+ # Redraw the screen if widget text is updated "outside" of the
+ # mainloop
+ tui.draw_screen()
+ item.connect_signal("text", on_item_text_change_cb)
+
+ return widget
+
+
+def build_button(path, item, tui, plugin):
+ widget = ovirt.node.ui.widgets.Button(item.text())
+
+ def on_widget_click_cb(widget, data=None):
+ LOGGER.debug("Button click: %s" % widget)
+ if type(item) is ovirt.node.ui.SaveButton:
+ r = plugin._on_ui_save()
+ LOGGER.debug("Got save: %s" % r)
+ # FIXME hacks to display page or dialog
+ if type(r) in [ovirt.node.ui.Page]:
+ w = build_page(tui, plugin, r)
+ tui.display(w)
+ elif type(r) in [ovirt.node.ui.Dialog]:
+ w = build_page(tui, plugin, r)
+ tui._display_dialog(w, r.title)
+ else:
+# Not propagating the signal as a signal to the plugin
+# item.emit_signal("click", widget)
+ plugin._on_ui_change({path: True})
+ urwid.connect_signal(widget, "click", on_widget_click_cb)
+
+ return widget
+
+
+def build_divider(path, item, tui, plugin):
+ return ovirt.node.ui.widgets.Divider(item.char)
+
+
+def build_options(path, item, tui, plugin):
+ widget = ovirt.node.ui.widgets.Options(item.label, item.options,
+ plugin.model()[path])
+
+ def on_widget_change_cb(widget, data):
+ LOGGER.debug(data)
+ item.option(data)
+ plugin._on_ui_change({path: data})
+ urwid.connect_signal(widget, "change", on_widget_change_cb)
+
+ return widget
diff --git a/scripts/tui/src/ovirt/node/widgets.py b/scripts/tui/src/ovirt/node/ui/widgets.py
similarity index 99%
rename from scripts/tui/src/ovirt/node/widgets.py
rename to scripts/tui/src/ovirt/node/ui/widgets.py
index 360f91b..35d2476 100644
--- a/scripts/tui/src/ovirt/node/widgets.py
+++ b/scripts/tui/src/ovirt/node/ui/widgets.py
@@ -351,3 +351,7 @@
self.callback(self.choices[key])
else:
return key
+
+
+class PageWidget(urwid.Pile):
+ save_button = None
diff --git a/scripts/tui/src/ovirt/node/utils/process.py b/scripts/tui/src/ovirt/node/utils/process.py
index 87f54f1..b433ef8 100644
--- a/scripts/tui/src/ovirt/node/utils/process.py
+++ b/scripts/tui/src/ovirt/node/utils/process.py
@@ -28,7 +28,7 @@
LOGGER = logging.getLogger(__name__)
-def popen_closefds(*args, **kwargs):
+def popen(*args, **kwargs):
"""subprocess.Popen wrapper to not leak file descriptors
Args:
@@ -52,7 +52,7 @@
Returns:
retval of the process
"""
- return popen_closefds(cmd, shell=True).wait()
+ return popen(cmd, shell=True).wait()
def pipe(cmd, stdin=None):
@@ -67,8 +67,8 @@
"""
LOGGER.debug("run '%s'" % cmd)
- system_cmd = popen_closefds(cmd, shell=True, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
+ system_cmd = popen(cmd, shell=True, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
stdout, stderr = system_cmd.communicate(stdin)
if stdout:
LOGGER.debug("out '%s'" % stdout)
@@ -88,9 +88,10 @@
Yields:
Lines read from stdout
"""
+ # https://github.com/wardi/urwid/blob/master/examples/subproc.py
LOGGER.debug("run async '%s'" % cmd)
- process = popen_closefds(cmd, shell=True, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE, stdin=subprocess.PIPE)
+ process = popen(cmd, shell=True, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE, stdin=stdin)
if stdin:
process.stdin.write(stdin)
while process.poll() != 0:
diff --git a/scripts/tui/src/ovirt/node/valid.py b/scripts/tui/src/ovirt/node/valid.py
index 54da6fb..5953525 100644
--- a/scripts/tui/src/ovirt/node/valid.py
+++ b/scripts/tui/src/ovirt/node/valid.py
@@ -26,6 +26,7 @@
import socket
import ovirt.node.plugins
+import ovirt.node.exceptions
logging.basicConfig(level=logging.DEBUG)
@@ -66,7 +67,7 @@
def raise_exception(self):
msg = self.__exception_msg.format(description=self.description)
- raise ovirt.node.plugins.InvalidData(msg)
+ raise ovirt.node.exceptions.InvalidData(msg)
class RegexValidator(Validator):
--
To view, visit http://gerrit.ovirt.org/9883
To unsubscribe, visit http://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I53fe904095f99397b96dcaefa50e3dd40cb33805
Gerrit-PatchSet: 1
Gerrit-Project: ovirt-node
Gerrit-Branch: master
Gerrit-Owner: Fabian Deutsch <fabiand at fedoraproject.org>
More information about the node-patches
mailing list