~azzar1/unity/add-show-desktop-key

1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
1
#!/usr/bin/python
2
# IVLE - Informatics Virtual Learning Environment
3
# Copyright (C) 2007-2009 The University of Melbourne
4
#
5
# This program is free software; you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 2 of the License, or
8
# (at your option) any later version.
9
#
10
# This program 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
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with this program; if not, write to the Free Software
17
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
19
# Author:  Nicholas Chadwick
20
21
"""Script to upload an exercise file into the database"""
22
1099.1.192 by Nick Chadwick
Moved the tutorial templates in a new directory to keep tutorial cleaner
23
import os, sys, traceback
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
24
import xml.dom.minidom as minidom
25
1201 by William Grant
ivle.database.get_store() now takes a configuration object.
26
from ivle.config import Config
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
27
from ivle.database import Exercise, TestSuite, TestCase, TestSuiteVar, TestCasePart, get_store
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
28
29
class XMLMalformedError(Exception):
30
    """Error thrown when encountering malformed data."""
31
    
32
    def __init__(self, text):
33
        self.msg = text
34
35
def getTextData(element):
36
    """ Get the text and cdata inside an element
37
    Leading and trailing whitespace are stripped
38
    """
39
    data = ''
40
    for child in element.childNodes:
41
        if child.nodeType == child.CDATA_SECTION_NODE:
42
            data += child.data
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
43
        elif child.nodeType == child.TEXT_NODE:
44
            data += child.data
45
        else:
46
            data += child.toxml()
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
47
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
48
    return unicode(data.strip())
49
50
def add_var(store, var_type, var_name=u"", var_value=u"", arg_no=0):
51
    """Given an var node, parse it into a storm object."""
52
    new_var = TestSuiteVar()
53
    new_var.var_name = unicode(var_name)
54
    new_var.var_value = unicode(var_value)
55
    new_var.var_type = unicode(var_type)
56
    new_var.arg_no = arg_no
57
    store.add(new_var)
58
    return new_var
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
59
60
def add_test_suite(suite_node, suite_num, store):
61
    """Given a test suite element, get all the cases it contains."""
62
    cases = []
63
    case_num = 0
64
    for case_node in suite_node.getElementsByTagName('function'):
1099.1.226 by Nick Chadwick
Fixed a slight bug in addexercise, which was assigning sequence
65
        cases.append(add_test_case(case_node, case_num, store))
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
66
        case_num += 1
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
67
68
    ## ALLOWED TAGS ##
69
    # stdin     - Stdin for the suite - Unique - Text inside element
70
    # file      - File to add to the filespace - Name - List
71
    # var       - Variable for the suite - Name/Value - List
72
    # arg       - Argument to functions - Name/Value - ORDERED List
73
    # exception - Allowed exception name - Name - List
74
    # function  - An actual test case
75
    suite_vars = []
76
    
77
    # Add file nodes
78
    file_nodes = suite_node.getElementsByTagName('file')
79
    for file_node in file_nodes:
80
        suite_vars.append(add_var(store, 'file', file_node.getAttribute('name')))
81
    
82
    # Add vars
83
    var_nodes = suite_node.getElementsByTagName('var')
84
    for var_node in var_nodes:
85
        var_name = var_node.getAttribute('name')
86
        var_value = var_node.getAttribute('value')
87
        suite_vars.append(add_var(store, 'var', var_name, var_value))
88
    
89
    # Args need to be numbered as they are found, as this order matters
90
    arg_num = 0
91
    for arg_node in suite_node.getElementsByTagName('arg'):
92
        suite_vars.append(add_var(store, 'arg', arg_node.getAttribute('name'),
93
                          arg_node.getAttribute('value'), arg_num))
94
        arg_num += 1
95
         
96
    # Add allowed exceptions
97
    exception_nodes = suite_node.getElementsByTagName('exception')
98
    for exception_node in exception_nodes:
99
        name = exception_node.getAttribute('name')
100
        suite_vars.append(add_var(store, 'exception', name))
101
    
102
    # Can only have 0-1 stdin elements
103
    stdin = suite_node.getElementsByTagName('stdin')
104
    if len(stdin) > 1:
105
        raise XMLMalformedError('Too many stdin tags found.')
106
    if stdin:
107
        stdin = getTextData(stdin[0])
108
    else:
109
        stdin = ""
110
    
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
111
    new_suite = TestSuite()
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
112
    new_suite.description = unicode(suite_node.getAttribute('name'))
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
113
    new_suite.seq_no = suite_num
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
114
    new_suite.function = unicode(suite_node.getAttribute('function'))
115
    new_suite.stdin = unicode(stdin)
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
116
    for testcase in cases:
117
        new_suite.test_cases.add(testcase)
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
118
    for var in suite_vars:
119
        new_suite.variables.add(var)
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
120
    store.add(new_suite)
121
    return new_suite
122
1099.1.150 by Nick Chadwick
Modified worksheets to properly link attempts to worksheets and
123
def add_part(store, part_type, test_type, data, filename=u""):
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
124
    new_part = TestCasePart()
1099.1.150 by Nick Chadwick
Modified worksheets to properly link attempts to worksheets and
125
    new_part.part_type = unicode(part_type)
126
    new_part.test_type = unicode(test_type)
127
    new_part.data = unicode(data)
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
128
    new_part.filename = unicode(filename)
129
    store.add(new_part)
130
    return new_part
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
131
132
def add_test_case(case_node, case_num, store):
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
133
    """Given a test case node, parse it int a storm object."""
134
    
135
    ## ALLOWED TAGS ##
136
    # A function is allowed to contain the following elements 
137
    # stdout
138
    # stderr
139
    # result
140
    # exception
141
    # file
142
    # code
143
    allowed_parts = ['stdout', 'stderr', 'result', 'exception', 'file', 'code']
144
    parts = []
145
    for child_node in case_node.childNodes:
146
        if child_node.nodeType != child_node.ELEMENT_NODE:
147
            continue
148
        
149
        if child_node.tagName == 'file':
150
            part_type = 'file'
151
            test_type = child_node.getAttribute('type')
152
            data = getTextData(child_node)
153
            filename = child_node.getAttribute('name')
154
            if filename == "":
155
                raise XMLMalformedException('file tag must have names')
1099.1.186 by Nick Chadwick
fixed a slight bug on line 155, which referenced a non-existant variable
156
            parts.append(add_part(store, part_type, test_type, data,
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
157
                                        filename))
158
            
159
        elif child_node.tagName in allowed_parts:
160
            part_type = child_node.tagName
161
            test_type = child_node.getAttribute('type')
162
            data = getTextData(child_node)    
163
            parts.append(add_part(store, part_type, test_type, data))
164
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
165
    #Now create the object to hold the data
166
    new_test_case = TestCase()
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
167
    new_test_case.passmsg = unicode(case_node.getAttribute(u'pass'))
168
    new_test_case.failmsg = unicode(case_node.getAttribute(u'fail'))
169
    new_test_case.test_default = unicode(case_node.getAttribute(u'default'))
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
170
    new_test_case.seq_no = case_num
171
    store.add(new_test_case)
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
172
    for part in parts:
173
        new_test_case.parts.add(part)
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
174
    return new_test_case
175
176
xmlfile = sys.argv[1]
1099.1.150 by Nick Chadwick
Modified worksheets to properly link attempts to worksheets and
177
178
def add_exercise(xmlfile):
1099.1.196 by William Grant
ivle-addexercise: Skip existing exercises, and allow empty solutions and tests.
179
    # Skip existing ones.
180
    if store.find(Exercise, id=unicode(xmlfile)).count():
181
        return
182
183
    print "Adding exercise", xmlfile
1099.1.150 by Nick Chadwick
Modified worksheets to properly link attempts to worksheets and
184
    try:
185
        filedom = minidom.parse(xmlfile)
186
    except IOError, e:
187
        raise Exception('ivle-addexercise: error opening file ' + xmlfile + ': ' + e[1])
188
189
    for child in filedom.childNodes:
190
        if child.nodeType == child.ELEMENT_NODE and child.tagName == 'exercise':
191
            exercise = child
192
        else:
193
            raise XMLMalformedError('ivle-addexercise: error parsing XML: root node must be "exercise"')
194
195
    exercisename = exercise.getAttribute('name')
196
    rows = exercise.getAttribute('rows')
197
    if rows == '':
198
        rows = 4
199
    solution = None
200
    partial_solution = None
201
    include_code = None
202
    description = None
203
    test_suite_nodes = []
204
    for child in exercise.childNodes:
205
        if child.nodeType != child.ELEMENT_NODE:
206
            continue
207
        if child.tagName == 'solution':
208
            if solution is not None:
209
                raise XMLMalformedError('ivle-addexercise: error parsing XML: multiple "solution" nodes')
210
            solution = getTextData(child)
211
        elif child.tagName == 'include':
212
            if include_code is not None:
213
                raise XMLMalformedError('ivle-addexercise: error parsing XML: multiple "include" nodes')
214
            include_code = getTextData(child)
215
        elif child.tagName == 'partial':
216
            if partial_solution is not None:
217
                raise XMLMalformedError('ivle-addexercise: error parsing XML: multiple "include" nodes')
218
            partial_solution = getTextData(child)
219
        elif child.tagName == 'case':
220
            test_suite_nodes.append(child)
221
        elif child.tagName == 'desc':
222
            description = getTextData(child)
223
224
    new_exercise = Exercise()
225
    new_exercise.id = unicode(xmlfile)
226
    new_exercise.name = exercisename
227
    new_exercise.num_rows = int(rows)
228
    new_exercise.partial = partial_solution
229
    new_exercise.solution = solution
230
    new_exercise.include = include_code
231
    new_exercise.description = description
232
    new_exercise.partial = partial_solution
233
    store.add(new_exercise)
234
    suite_num = 0
235
    for suite in test_suite_nodes:
236
        new_exercise.test_suites.add(add_test_suite(suite, suite_num, store))
237
        suite_num += 1
238
239
    store.add(new_exercise)
240
    store.commit()
1201 by William Grant
ivle.database.get_store() now takes a configuration object.
241
242
store = get_store(Config())
243
1099.1.150 by Nick Chadwick
Modified worksheets to properly link attempts to worksheets and
244
xmlfiles = sys.argv[1:]
245
for xmlfile in xmlfiles:
1099.1.187 by Nick Chadwick
Modified ivle-addexercise. It will now (once more) attempt to add every
246
    try:
247
        add_exercise(xmlfile)
248
    except Exception, e:
249
        print "ERROR: Could not add file", xmlfile