Skip to content

Commit fb27af6

Browse files
committed
Add new feature: query frame (#7)
Signed-off-by: Lukas Bednar <[email protected]>
1 parent 497afe5 commit fb27af6

File tree

7 files changed

+308
-6
lines changed

7 files changed

+308
-6
lines changed

otopimdp/__init__.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,23 @@
11
from otopimdp.parser import MachineDialogParser
2+
from otopimdp.errors import (
3+
ParseError,
4+
UnexpectedEOF,
5+
UnexpectedInputError,
6+
HeadDoesNotMatch,
7+
DialogError,
8+
UnexpectedEventError,
9+
IncompleteQueryFrameError,
10+
)
211
from otopimdp.constants import (
312
ABORT_KEY,
413
ATTRIBUTES_KEY,
514
REGEX_KEY,
615
TYPE_KEY,
716
REPLY_KEY,
17+
DEFAULT_KEY,
18+
HIDDEN_KEY,
19+
VALID_VALUES_KEY,
20+
FRAME_NAME_KEY,
821
)
922
from otopimdp.constants import (
1023
CONFIRM_EVENT,
@@ -21,11 +34,22 @@
2134

2235
__all__ = [
2336
'MachineDialogParser',
37+
'ParseError',
38+
'UnexpectedEOF',
39+
'UnexpectedInputError',
40+
'HeadDoesNotMatch',
41+
'DialogError',
42+
'UnexpectedEventError',
43+
'IncompleteQueryFrameError',
2444
'ABORT_KEY',
2545
'ATTRIBUTES_KEY',
2646
'REGEX_KEY',
2747
'TYPE_KEY',
2848
'REPLY_KEY',
49+
'HIDDEN_KEY',
50+
'DEFAULT_KEY',
51+
'VALID_VALUES_KEY',
52+
'FRAME_NAME_KEY',
2953
'CONFIRM_EVENT',
3054
'DISPLAY_MULTI_STRING_EVENT',
3155
'DISPLAY_VALUE_EVENT',

otopimdp/constants.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
TERMINATE_EVENT = 'terminate'
3232
QUERY_STRING_EVENT = 'query_string'
3333
QUERY_MULTI_STRING_EVENT = 'query_multi_string'
34+
QUERY_FRAME_EVENT = 'query_frame'
3435
QUERY_VALUE_EVENT = 'query_value'
3536
CONFIRM_EVENT = 'confirm'
3637
DISPLAY_VALUE_EVENT = 'display_value'
@@ -41,6 +42,29 @@
4142
ATTRIBUTES_KEY = 'attributes'
4243
REPLY_KEY = 'reply'
4344
ABORT_KEY = 'abort'
45+
DEFAULT_KEY = 'default'
46+
HIDDEN_KEY = 'hidden'
47+
VALID_VALUES_KEY = 'valid_values'
48+
FRAME_NAME_KEY = 'frame_name'
49+
50+
QUERY_FRAME_PART_START = 'qfstart'
51+
QUERY_FRAME_PART_END = 'qfend'
52+
QUERY_FRAME_PART_DEFAULT = 'qfdefault'
53+
QUERY_FRAME_PART_HIDDEN = 'qfhidden'
54+
QUERY_FRAME_PART_VALID_VALUES = 'qfvalidvalues'
55+
56+
QUERY_FRAME_PATTERNS = {
57+
QUERY_FRAME_PART_START: re.compile(r'^[*]{2}%QStart: (?P<frame_name>.*)$'),
58+
QUERY_FRAME_PART_END: re.compile(r'^[*]{2}%QEnd: (?P<frame_name>.*)$'),
59+
QUERY_FRAME_PART_DEFAULT: re.compile(
60+
r'^[*]{2}%QDefault: (?P<default>.*)$'
61+
),
62+
QUERY_FRAME_PART_HIDDEN: re.compile(r'^[*]{2}%QHidden: (?P<hidden>.*)$'),
63+
QUERY_FRAME_PART_VALID_VALUES: re.compile(
64+
r'^[*]{2}%QValidValues: (?P<valid>.*)$'
65+
),
66+
}
67+
4468

4569
TRANSLATION = (
4670
{
@@ -57,6 +81,10 @@
5781
TYPE_KEY: TERMINATE_EVENT,
5882
REGEX_KEY: re.compile(r'^[*]{3}TERMINATE$'),
5983
},
84+
{
85+
TYPE_KEY: QUERY_FRAME_EVENT,
86+
REGEX_KEY: QUERY_FRAME_PATTERNS[QUERY_FRAME_PART_START],
87+
},
6088
{
6189
TYPE_KEY: QUERY_STRING_EVENT,
6290
REGEX_KEY: re.compile(r'^[*]{3}Q:STRING (?P<name>.*)$'),

otopimdp/errors.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ class UnexpectedEOF(ParseError):
3030
pass
3131

3232

33+
class UnexpectedInputError(ParseError):
34+
pass
35+
36+
3337
class HeadDoesNotMatch(ParseError):
3438
pass
3539

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

4145
class UnexpectedEventError(DialogError):
4246
pass
47+
48+
49+
class IncompleteQueryFrameError(ParseError):
50+
pass

otopimdp/parser.py

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from otopi import util
3030
from otopimdp import errors
3131
from otopimdp import constants as c
32+
from otopimdp import utils
3233

3334

3435
@util.export
@@ -85,7 +86,11 @@ def next_event(self):
8586
"""
8687
Returns instance of Event
8788
"""
88-
line = self.next_line()
89+
return self._next_event()
90+
91+
def _next_event(self, line=None):
92+
if not line:
93+
line = self.next_line()
8994
for event_type in c.TRANSLATION:
9095
match = event_type[c.REGEX_KEY].match(line)
9196
if not match:
@@ -96,15 +101,16 @@ def next_event(self):
96101
(c.ATTRIBUTES_KEY, match.groupdict()),
97102
)
98103
)
99-
self._process_event(
100-
event_type[c.TYPE_KEY], event[c.ATTRIBUTES_KEY],
101-
)
104+
self._process_event(event)
102105
self.logger.debug("Next event: %s", event)
103106
return event
104107
# W/A for hosted-engine deploy job
105108
self.logger.warning("This line doesn't match no event: %s", line)
106109

107-
def _process_event(self, event_type, attributes):
110+
def _process_event(self, event):
111+
event_type = event[c.TYPE_KEY]
112+
attributes = event[c.ATTRIBUTES_KEY]
113+
108114
if event_type == c.DISPLAY_VALUE_EVENT:
109115
type_ = attributes['type'].lower()
110116
if type_ == 'none':
@@ -133,6 +139,66 @@ def _process_event(self, event_type, attributes):
133139
break
134140
attributes['value'] = lines
135141

142+
if event_type == c.QUERY_FRAME_EVENT:
143+
self._process_frame_event(event)
144+
145+
def _process_frame_event(self, event):
146+
attributes = event[c.ATTRIBUTES_KEY]
147+
framed_event = None
148+
while True:
149+
line = self.next_line()
150+
m = c.QUERY_FRAME_PATTERNS[c.QUERY_FRAME_PART_END].match(line)
151+
if m:
152+
if m.group(c.FRAME_NAME_KEY) != attributes[c.FRAME_NAME_KEY]:
153+
self.logger.warning(
154+
"The QStart:NAME != QEnd:NAME (%s != %s)",
155+
m.group(c.FRAME_NAME_KEY),
156+
attributes[c.FRAME_NAME_KEY],
157+
)
158+
break
159+
m = c.QUERY_FRAME_PATTERNS[c.QUERY_FRAME_PART_DEFAULT].match(line)
160+
if m:
161+
attributes[c.DEFAULT_KEY] = m.group(c.DEFAULT_KEY)
162+
continue
163+
m = c.QUERY_FRAME_PATTERNS[c.QUERY_FRAME_PART_HIDDEN].match(line)
164+
if m:
165+
if m.group(c.HIDDEN_KEY) == "TRUE":
166+
attributes[c.HIDDEN_KEY] = True
167+
elif m.group(c.HIDDEN_KEY) == "FALSE":
168+
attributes[c.HIDDEN_KEY] = False
169+
else:
170+
self.logger.warning(
171+
"The QHidden(%s) has invalid option: "
172+
"%s not in (TRUE, FALSE)",
173+
attributes[c.FRAME_NAME_KEY],
174+
m.group(c.HIDDEN_KEY),
175+
)
176+
attributes[c.HIDDEN_KEY] = False
177+
continue
178+
m = c.QUERY_FRAME_PATTERNS[
179+
c.QUERY_FRAME_PART_VALID_VALUES
180+
].match(line)
181+
if m:
182+
attributes[c.VALID_VALUES_KEY] = utils.split_valid_options(
183+
m.group('valid')
184+
)
185+
continue
186+
187+
framed_event = self._next_event(line)
188+
if framed_event is None:
189+
raise errors.UnexpectedInputError(
190+
c.QUERY_FRAME_EVENT, attributes, line,
191+
)
192+
193+
event[c.ATTRIBUTES_KEY].update(framed_event[c.ATTRIBUTES_KEY])
194+
event[c.TYPE_KEY] = framed_event[c.TYPE_KEY]
195+
196+
if framed_event is None:
197+
raise errors.IncompleteQueryFrameError(
198+
"The frame %s doesn't contain query.",
199+
event,
200+
)
201+
136202
def send_response(self, event):
137203
"""
138204
Sends response for replyable events.
@@ -241,7 +307,7 @@ def cli_download_log(self):
241307
"""
242308
self._write('log')
243309
event = self.next_event()
244-
if event[c.TYPE_KEY] == c. DISPLAY_MULTI_STRING_EVENT:
310+
if event[c.TYPE_KEY] == c.DISPLAY_MULTI_STRING_EVENT:
245311
return '\n'.join(event[c.ATTRIBUTES_KEY]['value']) + '\n'
246312
raise errors.UnexpectedEventError(event)
247313

otopimdp/utils.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#
2+
# otopi -- plugable installer
3+
# Copyright (C) 2012-2014 Red Hat, Inc.
4+
#
5+
# This library is free software; you can redistribute it and/or
6+
# modify it under the terms of the GNU Lesser General Public
7+
# License as published by the Free Software Foundation; either
8+
# version 2.1 of the License, or (at your option) any later version.
9+
#
10+
# This library is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
# Lesser General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU Lesser General Public
16+
# License along with this library; if not, write to the Free Software
17+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18+
#
19+
20+
21+
"""
22+
Module contains set of helpers used in this library.
23+
"""
24+
25+
26+
def split_valid_options(string):
27+
"""
28+
This function is used to unescape and split QValidValues data.
29+
"""
30+
voptions = []
31+
option = ""
32+
last_c = None
33+
for c in string:
34+
if last_c == '\\':
35+
if c == '\\':
36+
option += '\\'
37+
last_c = None
38+
elif c == '|':
39+
option += '|'
40+
last_c = c
41+
else:
42+
raise ValueError(
43+
"Unescaped '\\' in the valid options: %s" % string
44+
)
45+
elif c == '\\':
46+
last_c = c
47+
elif c == '|':
48+
voptions.append(option)
49+
option = ""
50+
last_c = c
51+
else:
52+
option += c
53+
last_c = c
54+
if option:
55+
voptions.append(option)
56+
return voptions

0 commit comments

Comments
 (0)