Skip to content

Commit

Permalink
Add new feature: query frame (#7)
Browse files Browse the repository at this point in the history
Signed-off-by: Lukas Bednar <[email protected]>
  • Loading branch information
lukas-bednar committed May 26, 2016
1 parent 497afe5 commit fb27af6
Show file tree
Hide file tree
Showing 7 changed files with 308 additions and 6 deletions.
24 changes: 24 additions & 0 deletions otopimdp/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
from otopimdp.parser import MachineDialogParser
from otopimdp.errors import (
ParseError,
UnexpectedEOF,
UnexpectedInputError,
HeadDoesNotMatch,
DialogError,
UnexpectedEventError,
IncompleteQueryFrameError,
)
from otopimdp.constants import (
ABORT_KEY,
ATTRIBUTES_KEY,
REGEX_KEY,
TYPE_KEY,
REPLY_KEY,
DEFAULT_KEY,
HIDDEN_KEY,
VALID_VALUES_KEY,
FRAME_NAME_KEY,
)
from otopimdp.constants import (
CONFIRM_EVENT,
Expand All @@ -21,11 +34,22 @@

__all__ = [
'MachineDialogParser',
'ParseError',
'UnexpectedEOF',
'UnexpectedInputError',
'HeadDoesNotMatch',
'DialogError',
'UnexpectedEventError',
'IncompleteQueryFrameError',
'ABORT_KEY',
'ATTRIBUTES_KEY',
'REGEX_KEY',
'TYPE_KEY',
'REPLY_KEY',
'HIDDEN_KEY',
'DEFAULT_KEY',
'VALID_VALUES_KEY',
'FRAME_NAME_KEY',
'CONFIRM_EVENT',
'DISPLAY_MULTI_STRING_EVENT',
'DISPLAY_VALUE_EVENT',
Expand Down
28 changes: 28 additions & 0 deletions otopimdp/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
TERMINATE_EVENT = 'terminate'
QUERY_STRING_EVENT = 'query_string'
QUERY_MULTI_STRING_EVENT = 'query_multi_string'
QUERY_FRAME_EVENT = 'query_frame'
QUERY_VALUE_EVENT = 'query_value'
CONFIRM_EVENT = 'confirm'
DISPLAY_VALUE_EVENT = 'display_value'
Expand All @@ -41,6 +42,29 @@
ATTRIBUTES_KEY = 'attributes'
REPLY_KEY = 'reply'
ABORT_KEY = 'abort'
DEFAULT_KEY = 'default'
HIDDEN_KEY = 'hidden'
VALID_VALUES_KEY = 'valid_values'
FRAME_NAME_KEY = 'frame_name'

QUERY_FRAME_PART_START = 'qfstart'
QUERY_FRAME_PART_END = 'qfend'
QUERY_FRAME_PART_DEFAULT = 'qfdefault'
QUERY_FRAME_PART_HIDDEN = 'qfhidden'
QUERY_FRAME_PART_VALID_VALUES = 'qfvalidvalues'

QUERY_FRAME_PATTERNS = {
QUERY_FRAME_PART_START: re.compile(r'^[*]{2}%QStart: (?P<frame_name>.*)$'),
QUERY_FRAME_PART_END: re.compile(r'^[*]{2}%QEnd: (?P<frame_name>.*)$'),
QUERY_FRAME_PART_DEFAULT: re.compile(
r'^[*]{2}%QDefault: (?P<default>.*)$'
),
QUERY_FRAME_PART_HIDDEN: re.compile(r'^[*]{2}%QHidden: (?P<hidden>.*)$'),
QUERY_FRAME_PART_VALID_VALUES: re.compile(
r'^[*]{2}%QValidValues: (?P<valid>.*)$'
),
}


TRANSLATION = (
{
Expand All @@ -57,6 +81,10 @@
TYPE_KEY: TERMINATE_EVENT,
REGEX_KEY: re.compile(r'^[*]{3}TERMINATE$'),
},
{
TYPE_KEY: QUERY_FRAME_EVENT,
REGEX_KEY: QUERY_FRAME_PATTERNS[QUERY_FRAME_PART_START],
},
{
TYPE_KEY: QUERY_STRING_EVENT,
REGEX_KEY: re.compile(r'^[*]{3}Q:STRING (?P<name>.*)$'),
Expand Down
8 changes: 8 additions & 0 deletions otopimdp/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ class UnexpectedEOF(ParseError):
pass


class UnexpectedInputError(ParseError):
pass


class HeadDoesNotMatch(ParseError):
pass

Expand All @@ -40,3 +44,7 @@ class DialogError(ParseError):

class UnexpectedEventError(DialogError):
pass


class IncompleteQueryFrameError(ParseError):
pass
78 changes: 72 additions & 6 deletions otopimdp/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from otopi import util
from otopimdp import errors
from otopimdp import constants as c
from otopimdp import utils


@util.export
Expand Down Expand Up @@ -85,7 +86,11 @@ def next_event(self):
"""
Returns instance of Event
"""
line = self.next_line()
return self._next_event()

def _next_event(self, line=None):
if not line:
line = self.next_line()
for event_type in c.TRANSLATION:
match = event_type[c.REGEX_KEY].match(line)
if not match:
Expand All @@ -96,15 +101,16 @@ def next_event(self):
(c.ATTRIBUTES_KEY, match.groupdict()),
)
)
self._process_event(
event_type[c.TYPE_KEY], event[c.ATTRIBUTES_KEY],
)
self._process_event(event)
self.logger.debug("Next event: %s", event)
return event
# W/A for hosted-engine deploy job
self.logger.warning("This line doesn't match no event: %s", line)

def _process_event(self, event_type, attributes):
def _process_event(self, event):
event_type = event[c.TYPE_KEY]
attributes = event[c.ATTRIBUTES_KEY]

if event_type == c.DISPLAY_VALUE_EVENT:
type_ = attributes['type'].lower()
if type_ == 'none':
Expand Down Expand Up @@ -133,6 +139,66 @@ def _process_event(self, event_type, attributes):
break
attributes['value'] = lines

if event_type == c.QUERY_FRAME_EVENT:
self._process_frame_event(event)

def _process_frame_event(self, event):
attributes = event[c.ATTRIBUTES_KEY]
framed_event = None
while True:
line = self.next_line()
m = c.QUERY_FRAME_PATTERNS[c.QUERY_FRAME_PART_END].match(line)
if m:
if m.group(c.FRAME_NAME_KEY) != attributes[c.FRAME_NAME_KEY]:
self.logger.warning(
"The QStart:NAME != QEnd:NAME (%s != %s)",
m.group(c.FRAME_NAME_KEY),
attributes[c.FRAME_NAME_KEY],
)
break
m = c.QUERY_FRAME_PATTERNS[c.QUERY_FRAME_PART_DEFAULT].match(line)
if m:
attributes[c.DEFAULT_KEY] = m.group(c.DEFAULT_KEY)
continue
m = c.QUERY_FRAME_PATTERNS[c.QUERY_FRAME_PART_HIDDEN].match(line)
if m:
if m.group(c.HIDDEN_KEY) == "TRUE":
attributes[c.HIDDEN_KEY] = True
elif m.group(c.HIDDEN_KEY) == "FALSE":
attributes[c.HIDDEN_KEY] = False
else:
self.logger.warning(
"The QHidden(%s) has invalid option: "
"%s not in (TRUE, FALSE)",
attributes[c.FRAME_NAME_KEY],
m.group(c.HIDDEN_KEY),
)
attributes[c.HIDDEN_KEY] = False
continue
m = c.QUERY_FRAME_PATTERNS[
c.QUERY_FRAME_PART_VALID_VALUES
].match(line)
if m:
attributes[c.VALID_VALUES_KEY] = utils.split_valid_options(
m.group('valid')
)
continue

framed_event = self._next_event(line)
if framed_event is None:
raise errors.UnexpectedInputError(
c.QUERY_FRAME_EVENT, attributes, line,
)

event[c.ATTRIBUTES_KEY].update(framed_event[c.ATTRIBUTES_KEY])
event[c.TYPE_KEY] = framed_event[c.TYPE_KEY]

if framed_event is None:
raise errors.IncompleteQueryFrameError(
"The frame %s doesn't contain query.",
event,
)

def send_response(self, event):
"""
Sends response for replyable events.
Expand Down Expand Up @@ -241,7 +307,7 @@ def cli_download_log(self):
"""
self._write('log')
event = self.next_event()
if event[c.TYPE_KEY] == c. DISPLAY_MULTI_STRING_EVENT:
if event[c.TYPE_KEY] == c.DISPLAY_MULTI_STRING_EVENT:
return '\n'.join(event[c.ATTRIBUTES_KEY]['value']) + '\n'
raise errors.UnexpectedEventError(event)

Expand Down
56 changes: 56 additions & 0 deletions otopimdp/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#
# otopi -- plugable installer
# Copyright (C) 2012-2014 Red Hat, Inc.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#


"""
Module contains set of helpers used in this library.
"""


def split_valid_options(string):
"""
This function is used to unescape and split QValidValues data.
"""
voptions = []
option = ""
last_c = None
for c in string:
if last_c == '\\':
if c == '\\':
option += '\\'
last_c = None
elif c == '|':
option += '|'
last_c = c
else:
raise ValueError(
"Unescaped '\\' in the valid options: %s" % string
)
elif c == '\\':
last_c = c
elif c == '|':
voptions.append(option)
option = ""
last_c = c
else:
option += c
last_c = c
if option:
voptions.append(option)
return voptions
Loading

0 comments on commit fb27af6

Please sign in to comment.