standalone_editor.py 12.3 KB
Newer Older
1
2
3
4
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
    TASTE ASN.1 Value Editor
Maxime Perrotin's avatar
Maxime Perrotin committed
5
6
7
8

    PySide widget that opens an ASN.1 variable editor
    and returns a GSER string when done

9
    Example of use from the command line:
Maxime Perrotin's avatar
Maxime Perrotin committed
10
11
12
13
14
    ./standalone_editor.py  -a test/TPos.asn -t T-POS \
          -d 'myIntSet: { data4 -1310720, data1 0, data3 -1024, data2 -100.0 }'

    The SingleValueEditor widget can also be embedded in another Qt application

15
16
17
18
19
20
21
22
23
24
    This version needs ASN.1 version 3.x.x

    Copyright (c) 2012-2015 European Space Agency

    Designed and implemented by Maxime Perrotin

    Contact: maxime.perrotin@esa.int

    License is LGPLv3 - Check the LICENSE file
"""
Maxime Perrotin's avatar
Maxime Perrotin committed
25
26
27
28
29
30
31
32

import subprocess
import signal
import sys
import optparse
import os
import logging
import tempfile
Maxime Perrotin's avatar
Maxime Perrotin committed
33
import traceback
Maxime Perrotin's avatar
Maxime Perrotin committed
34
35
36
37
from shutil import rmtree

import resources

38
__author__  = 'Maxime Perrotin'
39
__licence__ = 'LGPL v3'
40
__url__     = 'http://taste.tuxfamily.org'
Maxime Perrotin's avatar
Maxime Perrotin committed
41

Maxime Perrotin's avatar
Maxime Perrotin committed
42
43
44
from ColorFormatter import ColorFormatter

# Set up the logging facilities
45
logger  = logging.getLogger(__name__)
Maxime Perrotin's avatar
Maxime Perrotin committed
46
47
48
49
console = logging.StreamHandler(sys.__stdout__)
console.setFormatter(ColorFormatter())
logger.addHandler(console)

Maxime Perrotin's avatar
Maxime Perrotin committed
50
51
52
53
54
try:
    from PySide.QtCore import QFile
    from PySide.QtCore import QObject
    from PySide.QtGui import QApplication, QToolButton
    from PySide.QtUiTools import QUiLoader
Maxime Perrotin's avatar
Maxime Perrotin committed
55
56
    import opengeode.Asn1scc as asn1scc
    asn1scc.LOG = logger
Maxime Perrotin's avatar
Maxime Perrotin committed
57
except ImportError:
58
59
    logger.error('PySide or OpenGEODE import error')
    sys.exit(1)
Maxime Perrotin's avatar
Maxime Perrotin committed
60
61
62
63

try:
    UI_FILE = ':/singleEditor.ui'
    from asn1_value_editor import asn1Editor
Maxime Perrotin's avatar
Maxime Perrotin committed
64
    from vn import valueNotationToCTypes
65
66
67
except ImportError:
    logger.error('ASN1 Value Editor import error')
    sys.exit(1)
Maxime Perrotin's avatar
Maxime Perrotin committed
68

69
70
71
72
73
74
75
# DV.py is generated from asn2dataModel (DMT tools)
# It containts constants related to the enumerated and choice types
# If not present it will be generated on the fly using opengeode's API
try:
    import DV
except ImportError:
    DV = None
Maxime Perrotin's avatar
Maxime Perrotin committed
76

77
78
79
80

def import_DV(asnFile=''):  # type: str -> Module
    ''' Generate DV.py from the user-provided asn1 file '''
    global DV
81
    if not DV or asnFile:
82
83
84
        try:
            module = asn1scc.asn2dataModel(asnFile)
            DV = module.DV
85
            return module
86
87
88
89
        except TypeError as err:
            logger.error('Could not execute asn2dataModel: ' + str(err))
            sys.exit(1)

Maxime Perrotin's avatar
Maxime Perrotin committed
90
91
92
def asn1sccToasn1ValueEditorTypes(dataview, name, asntype):
    ''' Convert an ASN.1 type from the Python AST of asn1scc v3
        to the AST of the value editor '''
93
    assert DV is not None
Maxime Perrotin's avatar
Maxime Perrotin committed
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
    result = {'nodeTypename': name}
    t = asntype
    # find basic type
    while t.kind == 'ReferenceType':
        t = dataview[t.ReferencedTypeName].type
    if t.kind == 'IntegerType':
        result['type'] = 'INTEGER'
        result['id'] = name
        result['minR'] = int(t.Min)
        result['maxR'] = int(t.Max)
    elif t.kind == 'RealType':
        result['type'] = 'REAL'
        result['id'] = name
        result['minR'] = float(t.Min)
        result['maxR'] = float(t.Max)
    elif t.kind == 'BooleanType':
        result['type'] = 'BOOLEAN'
        result['id'] = name
        result['default'] = 'False'
    elif t.kind == 'SequenceType':
        result['type'] = 'SEQUENCE'
        result['id'] = name
        result['children'] = []
        for child, childtype in t.Children.viewitems():
            result['children'].append(
                asn1sccToasn1ValueEditorTypes(dataview, child, childtype.type))
    elif t.kind == 'SequenceOfType':
        result['type'] = 'SEQOF'
        result['id'] = name
        result['minSize'] = int(t.Min)
        result['maxSize'] = int(t.Max)
        result['seqoftype'] = \
                asn1sccToasn1ValueEditorTypes(dataview, '', t.type)
    elif t.kind == 'EnumeratedType':
        result['type'] = 'ENUMERATED'
        result['id'] = name
        result['values'] = t.EnumValues.keys()
Maxime Perrotin's avatar
Maxime Perrotin committed
131
132
        result['valuesInt'] = {val: num.IntValue
                               for val, num in t.EnumValues.viewitems()}
Maxime Perrotin's avatar
Maxime Perrotin committed
133
134
135
136
137
138
139
    elif t.kind == 'ChoiceType':
        result['type'] = 'CHOICE'
        result['id'] = name
        result['choices'] = []
        for child, childtype in t.Children.viewitems():
            result['choices'].append(
                asn1sccToasn1ValueEditorTypes(dataview, child, childtype.type))
140
        result['choiceIdx'] = {enumerant: getattr(DV, chty.EnumID)
Maxime Perrotin's avatar
Maxime Perrotin committed
141
                               for enumerant, chty in t.Children.viewitems()}
Maxime Perrotin's avatar
Maxime Perrotin committed
142
143
144
145
146
147
148
149
150
151
152
153
154
    elif t.kind.endswith('StringType'):
        result['type'] = 'STRING'
        result['id'] = name
        result['minSize'] = int(t.Min)
        result['maxSize'] = int(t.Max)
    else:
        print('[ERROR] Unsupported type:', t.kind)
        sys.exit(-1)
    return result


class SingleValueEditor(QObject):
    ''' Main class '''
155
    def __init__(self, asnFile, parent=None):
Maxime Perrotin's avatar
Maxime Perrotin committed
156
157
158
159
160
161
162
163
164
165
166
167
168
        '''
        Load the ASN.1 file and create the widgets for all parameters passed
        as arguments (once for all).
        When editParam value is called with parameter not defined in the
        constructor, then a new widget is created por this parameter.
        '''
        super(SingleValueEditor, self).__init__(parent)
        self.widgets = {}
        self.types = None
        self.initializedWidgets = False
        self.parent = parent
        self.tc = {}

169
170
        # Make sure DV.py is present and imported
        self.asn1ctypes = import_DV(asnFile)
171

Maxime Perrotin's avatar
Maxime Perrotin committed
172
        try:
Maxime Perrotin's avatar
Maxime Perrotin committed
173
            logger.info(asnFile)
174
175
176
177
178
            self.dataView = asn1scc.parse_asn1([asnFile],
                              rename_policy=asn1scc.ASN1.RenameOnlyConflicting,
                              ast_version=asn1scc.ASN1.UniqueEnumeratedNames,
                              flags=[asn1scc.ASN1.AstOnly])
            self.ASN1_AST = self.dataView.types
Maxime Perrotin's avatar
Maxime Perrotin committed
179
        except (ImportError, NameError, TypeError) as err:
180
181
182
            logger.error('Error loading ASN.1 model')
            logger.debug(str(err))
            logger.debug(traceback.format_exc())
Maxime Perrotin's avatar
Maxime Perrotin committed
183
            raise
184

Maxime Perrotin's avatar
Maxime Perrotin committed
185
        # convert dataView types to a list of tc as needed by asn1-value-editor
186
187
188
        for t, v in self.ASN1_AST.viewitems():
            self.tc[t] = asn1sccToasn1ValueEditorTypes(self.ASN1_AST,
                                                       t, v.type)
Maxime Perrotin's avatar
Maxime Perrotin committed
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203

        logger.debug("Types found in '%s' asn file: \n\t%s",
                 asnFile, self.tc.keys())

    def initWidgets(self):
        uiFile = QFile(UI_FILE)
        loader = QUiLoader()
        loader.registerCustomWidget(asn1Editor)
        for asnType in self.tc.keys():
            uiFile.open(QFile.ReadOnly)
            self.widgets[asnType] = {'widget': loader.load(uiFile, parent=self)}
            uiFile.close()

            widget = self.widgets[asnType]['widget']
            try:
204
205
                # If the parent is a widget, this will place the window
                # in the center of the parent
Maxime Perrotin's avatar
Maxime Perrotin committed
206
207
208
209
210
211
                widget.setParent(self.parent)
            except:
                logger.info('No parent widget - window will be placed randomly')

            pyType = self.tc[asnType]

212
213
            self.widgets[asnType]['editor'] = widget.findChild(asn1Editor,
                                                               'ASN1EDITOR')
Maxime Perrotin's avatar
Maxime Perrotin committed
214
215
216
217
218
219
220
221
222
            self.widgets[asnType]['editor'].log = logger
            okButton = widget.findChild(QToolButton, 'okButton')
            cancelButton = widget.findChild(QToolButton, 'cancelButton')
            okButton.clicked.connect(widget.accept)
            cancelButton.clicked.connect(widget.reject)
            # Hide columns showing the ASN.1 type and constraint
            self.widgets[asnType]['editor'].hideExtraColumns()
            self.widgets[asnType]['editor'].setAsn1Model(pyType)
        self.initializedWidgets = True
Maxime Perrotin's avatar
Maxime Perrotin committed
223
        logger.info("Succesfully created new Single Value Editor")
Maxime Perrotin's avatar
Maxime Perrotin committed
224
225
226


    def _asnTypeWidget(self, name, asnType):
227
        logger.debug("Creating new asnType widget. Name: %(pname)s, asnType: %(asnType)s.",
Maxime Perrotin's avatar
Maxime Perrotin committed
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
                     {"pname": name, "asnType": asnType})

        if name in self.widgets:
            logger.warning("""Try to create widget "%(pname)s" that exist.""",
                           {"pname": name})
            return

        # Create widget
        uiFile = QFile(UI_FILE)
        loader = QUiLoader()
        loader.registerCustomWidget(asn1Editor)
        uiFile.open(QFile.ReadOnly)
        self.widgets[name] = {'widget': loader.load(uiFile)}
        uiFile.close()
        widget = self.widgets[name]['widget']
        pyType = self.tc[name.replace('_', '-')]
        self.widgets[name]['editor'] = widget.findChild(asn1Editor, 'ASN1EDITOR')
        self.widgets[name]['editor'].log = logger
        okButton = widget.findChild(QToolButton, 'okButton')
        cancelButton = widget.findChild(QToolButton, 'cancelButton')
        okButton.clicked.connect(widget.accept)
        cancelButton.clicked.connect(widget.reject)
        # Hide columns showing the ASN.1 type and constraint
        self.widgets[name]['editor'].hideExtraColumns()
        self.widgets[name]['editor'].setAsn1Model(pyType)

254
255
    def setupEdit(self, param, defValue=None, asnType=None):
        ''' Initialize the parameter edition '''
Maxime Perrotin's avatar
Maxime Perrotin committed
256
257
258
259
260
261
262
263
264
265
        if not self.initializedWidgets:
            self.initWidgets()

        if not asnType:
            asnType = param

        if param not in self.widgets:
            self._asnTypeWidget(param, asnType)

        widget = self.widgets[param]
266
        # Create a ctypes instance of the variable and set it to the editor
Maxime Perrotin's avatar
Maxime Perrotin committed
267
268
        instance = getattr(self.asn1ctypes, asnType.replace('-', '_'))()
        widget['editor'].asn1Instance = instance
269
        logger.info('Created instance of ' + asnType)
Maxime Perrotin's avatar
Maxime Perrotin committed
270
        if defValue:
Maxime Perrotin's avatar
Maxime Perrotin committed
271
272
273
274
275
276
            valueNotationToCTypes(gser=defValue,
                                  dest=instance,
                                  sort=self.ASN1_AST[asnType].type,
                                  ASN1Mod=self.asn1ctypes,
                                  ASN1_AST=self.ASN1_AST)
            widget['editor'].updateVariable()
277
278
279
280
281
282
283
284
        return widget

    def editValue(self, param, defValue=None):
        return self.editParam(param, defValue)

    def editParam(self, param, defValue=None, asnType=None):
        ''' Edit parameter (open widget,  wait for OK/Cancel and read value) '''
        widget = setupEdit(param, defValue, asnType)
Maxime Perrotin's avatar
Maxime Perrotin committed
285
286
        response = widget['widget'].exec_()
        if response:
287
            data = widget['editor'].getVariable(dest=widget['editor'].asn1Instance)
Maxime Perrotin's avatar
Maxime Perrotin committed
288
            return widget['editor'].asn1Instance.GSER()
Maxime Perrotin's avatar
Maxime Perrotin committed
289
290
291
292
293
294
295
        else:
            return None

    def asn1Types(self):
        u""" Return the list of types declared within asn file """
        return self.tc.keys()

296
297

def main():
Maxime Perrotin's avatar
Maxime Perrotin committed
298
299
300
    # Exit app on Ctrl-C
    signal.signal(signal.SIGINT, signal.SIG_DFL)

301
302
    usage = 'usage: asn1_value_editor -a file.asn -t type [-d default value]'
    version = 'ASN.1 Value Editor'
Maxime Perrotin's avatar
Maxime Perrotin committed
303

Maxime Perrotin's avatar
Maxime Perrotin committed
304
    logger.info('Starting ASN.1 Value Editor')
Maxime Perrotin's avatar
Maxime Perrotin committed
305
306
307
308
309
310
311
312
313

    # Parse the command line
    parser = optparse.OptionParser(usage=usage, version=version)
    parser.add_option('-v', '--verbose', action='store_true', default=False, help='Display debug information')
    parser.add_option('-a', '--asn', dest='asn', metavar='ASN.1 File', help='ASN.1 module')
    parser.add_option('-t', '--type', dest='asnType', help='Type of the variable you want to edit')
    parser.add_option('-d', '--value', dest='asnValue', help='Value of the variable you want to edit')
    options, args = parser.parse_args()
    if options.verbose:
Maxime Perrotin's avatar
Maxime Perrotin committed
314
        logger.setLevel(logging.DEBUG)
Maxime Perrotin's avatar
Maxime Perrotin committed
315
    else:
Maxime Perrotin's avatar
Maxime Perrotin committed
316
        logger.setLevel(logging.INFO)
Maxime Perrotin's avatar
Maxime Perrotin committed
317
318

    if options.asn is None or options.asnType is None:
Maxime Perrotin's avatar
Maxime Perrotin committed
319
        logger.error('You must specify an ASN.1 file and specify a type. Check --help for more info')
Maxime Perrotin's avatar
Maxime Perrotin committed
320
321
        sys.exit(-1)

Maxime Perrotin's avatar
Maxime Perrotin committed
322
323
    logger.debug('ASN.1 File  = ' + options.asn)
    logger.debug('ASN.1 Type  = ' + options.asnType)
Maxime Perrotin's avatar
Maxime Perrotin committed
324
    if options.asnValue is not None:
Maxime Perrotin's avatar
Maxime Perrotin committed
325
        logger.debug('ASN.1 Value = ' + options.asnValue)
Maxime Perrotin's avatar
Maxime Perrotin committed
326
327
328
329
330
331
332
333

    # Define a Qt application (mandatory)
    app = QApplication(sys.argv)

    # Select a nice style for uniform rendering among all platforms
    app.setStyle("cleanlooks")

    # Initialize the editor with a dictionary of pairs ParamName/ParamType:
334
    s = SingleValueEditor(options.asn)
Maxime Perrotin's avatar
Maxime Perrotin committed
335
336
337
338
339
340
341
342
343

    # Open the value editor and retrieve the value, if Cancel has not been pressed
    result = s.editParam(options.asnType, options.asnValue)
    if result is not None:
        print result
        sys.exit(0)
    else:
        sys.exit(1)
    app.exec_()
344
345
346
347


if __name__ == '__main__':
    main()