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