Add value parser + use it for the gui
replaces eval which is used so far Change-Id: Ie5ff8c82175786e233d52bc0faac4e72e3bc27e9 Reviewed-on: https://forge.frm2.tum.de/review/17271 Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de> Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
This commit is contained in:
parent
c146f477aa
commit
66503e8975
@ -150,7 +150,6 @@ class Value(object):
|
|||||||
r = []
|
r = []
|
||||||
if self.t is not None:
|
if self.t is not None:
|
||||||
r.append("timestamp=%r" % format_time(self.t))
|
r.append("timestamp=%r" % format_time(self.t))
|
||||||
print("Check 3")
|
|
||||||
if self.u is not None:
|
if self.u is not None:
|
||||||
r.append('unit=%r' % self.u)
|
r.append('unit=%r' % self.u)
|
||||||
if self.e is not None:
|
if self.e is not None:
|
||||||
@ -523,7 +522,8 @@ class Client(object):
|
|||||||
datatype = self._getDescribingParameterData(module,
|
datatype = self._getDescribingParameterData(module,
|
||||||
parameter)['datatype']
|
parameter)['datatype']
|
||||||
|
|
||||||
value = datatype.export_value(datatype.validate(value))
|
value = datatype.from_string(value)
|
||||||
|
value = datatype.export_value(value)
|
||||||
self.communicate('change', '%s:%s' % (module, parameter), value)
|
self.communicate('change', '%s:%s' % (module, parameter), value)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -21,10 +21,12 @@
|
|||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
"""Define validated data types."""
|
"""Define validated data types."""
|
||||||
|
|
||||||
from ast import literal_eval
|
|
||||||
from base64 import b64encode, b64decode
|
from base64 import b64encode, b64decode
|
||||||
|
|
||||||
from .errors import ProgrammingError
|
from .errors import ProgrammingError, ParsingError
|
||||||
|
from .parse import Parser
|
||||||
|
|
||||||
|
Parser = Parser()
|
||||||
|
|
||||||
# Only export these classes for 'from secop.datatypes import *'
|
# Only export these classes for 'from secop.datatypes import *'
|
||||||
__all__ = [
|
__all__ = [
|
||||||
@ -426,8 +428,9 @@ class ArrayOf(DataType):
|
|||||||
return [self.subtype.import_value(elem) for elem in value]
|
return [self.subtype.import_value(elem) for elem in value]
|
||||||
|
|
||||||
def from_string(self, text):
|
def from_string(self, text):
|
||||||
# XXX: parse differntly than using eval!
|
value, rem = Parser.parse(text)
|
||||||
value = eval(text) # pylint: disable=W0123
|
if rem:
|
||||||
|
raise ParsingError('trailing garbage: %r' % rem)
|
||||||
return self.validate(value)
|
return self.validate(value)
|
||||||
|
|
||||||
|
|
||||||
@ -469,8 +472,10 @@ class TupleOf(DataType):
|
|||||||
return [sub.import_value(elem) for sub, elem in zip(self.subtypes, value)]
|
return [sub.import_value(elem) for sub, elem in zip(self.subtypes, value)]
|
||||||
|
|
||||||
def from_string(self, text):
|
def from_string(self, text):
|
||||||
value = literal_eval(text)
|
value, rem = Parser.parse(text)
|
||||||
return self.validate(tuple(value))
|
if rem:
|
||||||
|
raise ParsingError('trailing garbage: %r' % rem)
|
||||||
|
return self.validate(value)
|
||||||
|
|
||||||
|
|
||||||
class StructOf(DataType):
|
class StructOf(DataType):
|
||||||
@ -525,7 +530,9 @@ class StructOf(DataType):
|
|||||||
for k, v in value.items())
|
for k, v in value.items())
|
||||||
|
|
||||||
def from_string(self, text):
|
def from_string(self, text):
|
||||||
value = literal_eval(text)
|
value, rem = Parser.parse(text)
|
||||||
|
if rem:
|
||||||
|
raise ParsingError('trailing garbage: %r' % rem)
|
||||||
return self.validate(dict(value))
|
return self.validate(dict(value))
|
||||||
|
|
||||||
|
|
||||||
@ -577,7 +584,9 @@ class Command(DataType):
|
|||||||
raise ProgrammingError('values of type command can not be transported!')
|
raise ProgrammingError('values of type command can not be transported!')
|
||||||
|
|
||||||
def from_string(self, text):
|
def from_string(self, text):
|
||||||
value = literal_eval(text)
|
value, rem = Parser.parse(text)
|
||||||
|
if rem:
|
||||||
|
raise ParsingError('trailing garbage: %r' % rem)
|
||||||
return self.validate(value)
|
return self.validate(value)
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,6 +37,10 @@ class ProgrammingError(SECoPServerError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ParsingError(SECoPServerError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# to be exported for remote operation
|
# to be exported for remote operation
|
||||||
class SECoPError(SECoPServerError):
|
class SECoPError(SECoPServerError):
|
||||||
pass
|
pass
|
||||||
|
@ -24,8 +24,8 @@
|
|||||||
import pprint
|
import pprint
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from PyQt4.QtGui import QWidget, QTextCursor, QFont, QFontMetrics, QLabel, QPushButton, QLineEdit, QMessageBox, QCheckBox, QSizePolicy
|
from PyQt4.QtGui import QWidget, QTextCursor, QFont, QFontMetrics, QLabel, QMessageBox
|
||||||
from PyQt4.QtCore import pyqtSignature as qtsig, Qt, pyqtSignal
|
from PyQt4.QtCore import pyqtSignature as qtsig, Qt
|
||||||
|
|
||||||
from secop.gui.util import loadUi
|
from secop.gui.util import loadUi
|
||||||
from secop.protocol.errors import SECOPError
|
from secop.protocol.errors import SECOPError
|
||||||
@ -221,10 +221,10 @@ class ReadableWidget(QWidget):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
def _init_status_widgets(self):
|
def _init_status_widgets(self):
|
||||||
self.update_status(self._get('status', (999, '<not supported>')))
|
self.update_status(self._get('status', (999, '<not supported>')), {})
|
||||||
# XXX: also connect update_status signal to LineEdit ??
|
# XXX: also connect update_status signal to LineEdit ??
|
||||||
|
|
||||||
def update_status(self, status, qualifiers={}):
|
def update_status(self, status, qualifiers):
|
||||||
display_string = self._status_type.subtypes[0].entries.get(status[0])
|
display_string = self._status_type.subtypes[0].entries.get(status[0])
|
||||||
if status[1]:
|
if status[1]:
|
||||||
display_string += ':' + status[1]
|
display_string += ':' + status[1]
|
||||||
@ -232,9 +232,9 @@ class ReadableWidget(QWidget):
|
|||||||
# may change meaning of cmdPushButton
|
# may change meaning of cmdPushButton
|
||||||
|
|
||||||
def _init_current_widgets(self):
|
def _init_current_widgets(self):
|
||||||
self.update_current(self._get('value', ''))
|
self.update_current(self._get('value', ''), {})
|
||||||
|
|
||||||
def update_current(self, value, qualifiers={}):
|
def update_current(self, value, qualifiers):
|
||||||
self.currentLineEdit.setText(str(value))
|
self.currentLineEdit.setText(str(value))
|
||||||
|
|
||||||
def _init_target_widgets(self):
|
def _init_target_widgets(self):
|
||||||
@ -243,13 +243,15 @@ class ReadableWidget(QWidget):
|
|||||||
self.targetComboBox.setHidden(True)
|
self.targetComboBox.setHidden(True)
|
||||||
self.cmdPushButton.setHidden(True)
|
self.cmdPushButton.setHidden(True)
|
||||||
|
|
||||||
def update_target(self, target, qualifiers={}):
|
def update_target(self, target, qualifiers):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def target_go(self, target):
|
def target_go(self, target):
|
||||||
|
print self, target
|
||||||
try:
|
try:
|
||||||
self._node.setParameter(self._module, 'target', target)
|
self._node.setParameter(self._module, 'target', target)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
self.log.exception(e)
|
||||||
QMessageBox.warning(self.parent(), 'Operation failed', str(e))
|
QMessageBox.warning(self.parent(), 'Operation failed', str(e))
|
||||||
|
|
||||||
def _updateValue(self, module, parameter, value):
|
def _updateValue(self, module, parameter, value):
|
||||||
@ -266,7 +268,6 @@ class ReadableWidget(QWidget):
|
|||||||
class DrivableWidget(ReadableWidget):
|
class DrivableWidget(ReadableWidget):
|
||||||
|
|
||||||
def _init_target_widgets(self):
|
def _init_target_widgets(self):
|
||||||
params = self._node.getProperties(self._module, 'target')
|
|
||||||
if self._is_enum:
|
if self._is_enum:
|
||||||
# EnumType: disable Linedit
|
# EnumType: disable Linedit
|
||||||
self.targetLineEdit.setHidden(True)
|
self.targetLineEdit.setHidden(True)
|
||||||
@ -280,13 +281,13 @@ class DrivableWidget(ReadableWidget):
|
|||||||
else:
|
else:
|
||||||
self.update_target(target)
|
self.update_target(target)
|
||||||
|
|
||||||
def update_current(self, value, qualifiers={}):
|
def update_current(self, value, qualifiers):
|
||||||
if self._is_enum:
|
if self._is_enum:
|
||||||
self.currentLineEdit.setText(self._map[self._revmap[value]][0])
|
self.currentLineEdit.setText(self._map[self._revmap[value]][0])
|
||||||
else:
|
else:
|
||||||
self.currentLineEdit.setText(str(value))
|
self.currentLineEdit.setText(str(value))
|
||||||
|
|
||||||
def update_target(self, target, qualifiers={}):
|
def update_target(self, target, qualifiers):
|
||||||
if self._is_enum:
|
if self._is_enum:
|
||||||
# update selected item
|
# update selected item
|
||||||
if target in self._revmap:
|
if target in self._revmap:
|
||||||
|
167
secop/parse.py
Normal file
167
secop/parse.py
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# *****************************************************************************
|
||||||
|
#
|
||||||
|
# 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; either version 2 of the License, or (at your option) any later
|
||||||
|
# version.
|
||||||
|
#
|
||||||
|
# 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.,
|
||||||
|
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Module authors:
|
||||||
|
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||||
|
#
|
||||||
|
# *****************************************************************************
|
||||||
|
"""parser. used for config files and the gui
|
||||||
|
|
||||||
|
can't use ast.literal_eval as it can't handle enums :(
|
||||||
|
|
||||||
|
parsing rules:
|
||||||
|
(...) -> tuple
|
||||||
|
[...] -> tuple
|
||||||
|
{text:...} -> dict
|
||||||
|
{text=...} -> dict
|
||||||
|
..., ... -> tuple
|
||||||
|
digits -> float or int (try int, if it fails: take float)
|
||||||
|
text -> string
|
||||||
|
'text' -> string
|
||||||
|
"text" -> string
|
||||||
|
|
||||||
|
further convertions are done by the validator of the datatype....
|
||||||
|
"""
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
|
||||||
|
class Parser(object):
|
||||||
|
# all parsing methods return (parsed value, remaining string)
|
||||||
|
# or (None, remaining_text) if parsing error
|
||||||
|
|
||||||
|
def parse_number(self, text):
|
||||||
|
text = text.strip()
|
||||||
|
l = 1
|
||||||
|
number = None
|
||||||
|
while l <= len(text):
|
||||||
|
try:
|
||||||
|
number = float(text[:l])
|
||||||
|
length = l
|
||||||
|
l += 1
|
||||||
|
except ValueError:
|
||||||
|
if text[l - 1] in u'eE+-':
|
||||||
|
l += 1
|
||||||
|
continue
|
||||||
|
if number is None:
|
||||||
|
return None, text
|
||||||
|
try:
|
||||||
|
return int(text[:length]), text[length:]
|
||||||
|
except ValueError:
|
||||||
|
return number, text[length:]
|
||||||
|
return number, ''
|
||||||
|
|
||||||
|
def parse_string(self, orgtext):
|
||||||
|
# handle quoted and unquoted strings correctly
|
||||||
|
text = orgtext.strip()
|
||||||
|
if text[0] in (u'"', u"'"):
|
||||||
|
# quoted string
|
||||||
|
quote = text[0]
|
||||||
|
idx = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
idx = text.find(quote, idx + 1)
|
||||||
|
if idx == -1:
|
||||||
|
return None, orgtext
|
||||||
|
# check escapes!
|
||||||
|
if text[idx - 1] == u'\\':
|
||||||
|
continue
|
||||||
|
return text[1:idx], text[idx + 1:]
|
||||||
|
|
||||||
|
# unquoted strings are terminated by comma or whitespace
|
||||||
|
idx = 0
|
||||||
|
while idx < len(text):
|
||||||
|
if text[idx] in u'\x09 ,.;:()[]{}<>-+*/\\!"§$%&=?#~+*\'´`^°|-':
|
||||||
|
break
|
||||||
|
idx += 1
|
||||||
|
return text[:idx] or None, text[idx:]
|
||||||
|
|
||||||
|
def parse_tuple(self, orgtext):
|
||||||
|
text = orgtext.strip()
|
||||||
|
bra = text[0]
|
||||||
|
if bra not in u'([<':
|
||||||
|
return None, orgtext
|
||||||
|
# convert to closing bracket
|
||||||
|
bra = u')]>'[u'([<'.index(bra)]
|
||||||
|
reslist = []
|
||||||
|
# search for cosing bracket, collecting results
|
||||||
|
text = text[1:]
|
||||||
|
while text:
|
||||||
|
if bra not in text:
|
||||||
|
return None, text
|
||||||
|
res, rem = self.parse_sub(text)
|
||||||
|
if res is None:
|
||||||
|
return None, text
|
||||||
|
reslist.append(res)
|
||||||
|
if rem[0] == bra:
|
||||||
|
return tuple(reslist), rem[1:]
|
||||||
|
# eat separator
|
||||||
|
if rem[0] in u',;':
|
||||||
|
text = rem[1:]
|
||||||
|
else:
|
||||||
|
return None, rem
|
||||||
|
return None, orgtext
|
||||||
|
|
||||||
|
def parse_dict(self, orgtext):
|
||||||
|
text = orgtext.strip()
|
||||||
|
if text[0] != u'{':
|
||||||
|
return None, orgtext
|
||||||
|
# keep ordering
|
||||||
|
result = OrderedDict()
|
||||||
|
# search for cosing bracket, collecting results
|
||||||
|
# watch for key=value or key:value pairs, separated by ,
|
||||||
|
text = text[1:]
|
||||||
|
while u'}' in text:
|
||||||
|
# first part is always a string
|
||||||
|
key, rem = self.parse_string(text)
|
||||||
|
if not key:
|
||||||
|
return None, orgtext
|
||||||
|
if rem[0] not in u':=':
|
||||||
|
return None, rem
|
||||||
|
# eat separator
|
||||||
|
text = rem[1:]
|
||||||
|
value, rem = self.parse_sub(text)
|
||||||
|
if not value:
|
||||||
|
return None, orgtext
|
||||||
|
result[key] = value
|
||||||
|
if rem[0] == u'}':
|
||||||
|
return result, rem[1:]
|
||||||
|
|
||||||
|
if rem[0] not in u',;':
|
||||||
|
return None, rem
|
||||||
|
# eat separator
|
||||||
|
text = rem[1:]
|
||||||
|
return None, text
|
||||||
|
|
||||||
|
def parse_sub(self, orgtext):
|
||||||
|
text = orgtext.strip()
|
||||||
|
if not text:
|
||||||
|
return None, orgtext
|
||||||
|
if text[0] in u'+-.0123456789':
|
||||||
|
return self.parse_number(orgtext)
|
||||||
|
elif text[0] == u'{':
|
||||||
|
return self.parse_dict(orgtext)
|
||||||
|
elif text[0] in u'([<':
|
||||||
|
return self.parse_tuple(orgtext)
|
||||||
|
return self.parse_string(orgtext)
|
||||||
|
|
||||||
|
def parse(self, orgtext):
|
||||||
|
print "parsing %r" % orgtext
|
||||||
|
res, rem = self.parse_sub(orgtext)
|
||||||
|
if rem and rem[0] in u',;':
|
||||||
|
return self.parse_sub(u'[%s]' % orgtext)
|
||||||
|
return res, rem
|
96
test/test_parse.py
Normal file
96
test/test_parse.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# *****************************************************************************
|
||||||
|
#
|
||||||
|
# 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; either version 2 of the License, or (at your option) any later
|
||||||
|
# version.
|
||||||
|
#
|
||||||
|
# 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.,
|
||||||
|
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Module authors:
|
||||||
|
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||||
|
#
|
||||||
|
# *****************************************************************************
|
||||||
|
"""test data types."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, sys.path[0] + '/..')
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from secop.parse import Parser
|
||||||
|
|
||||||
|
# pylint: disable=redefined-outer-name
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def parser():
|
||||||
|
return Parser()
|
||||||
|
|
||||||
|
|
||||||
|
def test_Parser_parse_number(parser):
|
||||||
|
assert parser.parse_number('1') == (1, '')
|
||||||
|
assert parser.parse_number('') == (None, '')
|
||||||
|
assert parser.parse_number('a') == (None, 'a')
|
||||||
|
assert parser.parse_number('1,2,3') == (1, ',2,3')
|
||||||
|
assert parser.parse_number('1.23e-04:9') == (1.23e-4, ':9')
|
||||||
|
|
||||||
|
|
||||||
|
def test_Parser_parse_string(parser):
|
||||||
|
assert parser.parse_string('%') == (None, '%')
|
||||||
|
assert parser.parse_string('a') == ('a', '')
|
||||||
|
assert parser.parse_string('Hello World!') == ('Hello', ' World!')
|
||||||
|
assert parser.parse_string('Hello<World!') == ('Hello', '<World!')
|
||||||
|
assert parser.parse_string('"Hello World!\'') == (None, '"Hello World!\'')
|
||||||
|
assert parser.parse_string('"Hello World!\\"') == (
|
||||||
|
None, '"Hello World!\\"')
|
||||||
|
assert parser.parse_string('"Hello World!"X') == ("Hello World!", 'X')
|
||||||
|
|
||||||
|
|
||||||
|
def test_Parser_parse_tuple(parser):
|
||||||
|
assert parser.parse_tuple('(1,2.3)') == ((1, 2.3), '')
|
||||||
|
assert parser.parse_tuple('[1,a]') == ((1, 'a'), '')
|
||||||
|
assert parser.parse_tuple('[1,a>]') == (None, '>]')
|
||||||
|
assert parser.parse_tuple('x') == (None, 'x')
|
||||||
|
assert parser.parse_tuple('2') == (None, '2')
|
||||||
|
|
||||||
|
|
||||||
|
def test_Parser_parse_dict(parser):
|
||||||
|
assert (parser.parse_dict('{a:1,b=2,"X y":\'a:9=3\'}') ==
|
||||||
|
(OrderedDict([('a', 1), ('b', 2), ('X y', 'a:9=3')]), ''))
|
||||||
|
|
||||||
|
|
||||||
|
def test_Parser_parse(parser):
|
||||||
|
assert parser.parse('1') == (1, '')
|
||||||
|
assert parser.parse('') == (None, '')
|
||||||
|
assert parser.parse('a') == ('a', '')
|
||||||
|
assert parser.parse('1,2,3') == ((1, 2, 3), '')
|
||||||
|
assert parser.parse('1.23e-04:9') == (1.23e-4, ':9')
|
||||||
|
|
||||||
|
assert parser.parse('%') == (None, '%')
|
||||||
|
assert parser.parse('Hello World!') == ('Hello', ' World!')
|
||||||
|
assert parser.parse('Hello<World!') == ('Hello', '<World!')
|
||||||
|
assert parser.parse('"Hello World!\'') == (None, '"Hello World!\'')
|
||||||
|
assert parser.parse('"Hello World!\\"') == (None, '"Hello World!\\"')
|
||||||
|
assert parser.parse('"Hello World!"X') == ("Hello World!", 'X')
|
||||||
|
|
||||||
|
assert parser.parse('(1,2.3)') == ((1, 2.3), '')
|
||||||
|
assert parser.parse('[1,a]') == ((1, 'a'), '')
|
||||||
|
assert parser.parse('[1,a>]') == (None, '>]')
|
||||||
|
assert parser.parse('x') == ('x', '')
|
||||||
|
assert parser.parse('2') == (2, '')
|
||||||
|
|
||||||
|
assert (parser.parse('{a:1,b=2,"X y":\'a:9=3\'}') ==
|
||||||
|
(OrderedDict([('a', 1), ('b', 2), ('X y', 'a:9=3')]), ''))
|
||||||
|
|
||||||
|
assert parser.parse('1, 2,a,c') == ((1, 2, 'a', 'c'), '')
|
||||||
|
|
||||||
|
assert parser.parse('"\x09 \r"') == ('\t \r', '')
|
Loading…
x
Reference in New Issue
Block a user