1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
1 |
# IVLE - Informatics Virtual Learning Environment
|
2 |
# Copyright (C) 2007-2008 The University of Melbourne
|
|
3 |
#
|
|
4 |
# This program is free software; you can redistribute it and/or modify
|
|
5 |
# it under the terms of the GNU General Public License as published by
|
|
6 |
# the Free Software Foundation; either version 2 of the License, or
|
|
7 |
# (at your option) any later version.
|
|
8 |
#
|
|
9 |
# This program is distributed in the hope that it will be useful,
|
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12 |
# GNU General Public License for more details.
|
|
13 |
#
|
|
14 |
# You should have received a copy of the GNU General Public License
|
|
15 |
# along with this program; if not, write to the Free Software
|
|
16 |
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17 |
||
18 |
# Module: util
|
|
19 |
# Author: Matt Giuca
|
|
20 |
# Date: 12/12/2007
|
|
21 |
||
22 |
# Contains common utility functions.
|
|
23 |
||
24 |
import os |
|
1165.3.75
by Matt Giuca
ivle/util.py: Added safe_rmtree, copied from Python2.6 shutil.rmtree (which |
25 |
import sys |
26 |
import stat |
|
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
27 |
|
28 |
class IVLEJailError(Exception): |
|
1213
by William Grant
Drop ivle.util.make_path (replaced by Request.make_path) and fix docstrings. |
29 |
"""Exception proxying an in-jail error.
|
30 |
||
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
31 |
This exception indicates an error that occurred inside an IVLE CGI script
|
32 |
inside the jail. It should never be raised directly - only by the
|
|
33 |
interpreter.
|
|
34 |
||
35 |
Information will be retrieved from it, and then treated as a normal
|
|
36 |
error.
|
|
37 |
"""
|
|
38 |
def __init__(self, type_str, message, info): |
|
39 |
self.type_str = type_str |
|
40 |
self.message = message |
|
41 |
self.info = info |
|
42 |
||
43 |
class FakeObject(object): |
|
44 |
""" A representation of an object that can't be Pickled """
|
|
45 |
def __init__(self, type, name, attrib={}): |
|
46 |
self.type = type |
|
47 |
self.name = name |
|
48 |
self.attrib = attrib |
|
49 |
||
50 |
def __repr__(self): |
|
51 |
return "<Fake %s %s>"%(self.type, self.name) |
|
52 |
||
53 |
def split_path(path): |
|
54 |
"""Given a path, returns a tuple consisting of the top-level directory in
|
|
55 |
the path, and the rest of the path. Note that both items in the tuple will
|
|
56 |
NOT begin with a slash, regardless of whether the original path did. Also
|
|
57 |
normalises the path.
|
|
58 |
||
59 |
Always returns a pair of strings, except for one special case, in which
|
|
60 |
the path is completely empty (or just a single slash). In this case the
|
|
61 |
return value will be (None, ''). But still always returns a pair.
|
|
62 |
||
63 |
Examples:
|
|
64 |
||
65 |
>>> split_path("")
|
|
66 |
(None, '')
|
|
67 |
>>> split_path("/")
|
|
68 |
(None, '')
|
|
69 |
>>> split_path("home")
|
|
70 |
('home', '')
|
|
71 |
>>> split_path("home/docs/files")
|
|
72 |
('home', 'docs/files')
|
|
73 |
>>> split_path("//home/docs/files")
|
|
74 |
('', 'home/docs/files')
|
|
75 |
"""
|
|
76 |
path = os.path.normpath(path) |
|
77 |
# Ignore the opening slash
|
|
78 |
if path.startswith(os.sep): |
|
79 |
path = path[len(os.sep):] |
|
80 |
if path == '' or path == '.': |
|
81 |
return (None, '') |
|
82 |
splitpath = path.split(os.sep, 1) |
|
83 |
if len(splitpath) == 1: |
|
84 |
return (splitpath[0], '') |
|
85 |
else: |
|
86 |
return tuple(splitpath) |
|
87 |
||
1165.3.81
by Matt Giuca
ivle.util: Backported os.path.relpath from Python2.6 (required). |
88 |
def relpath(path, start=os.path.curdir): |
89 |
"""Return a relative version of a path.
|
|
90 |
XXX Backported from Python 2.6 posixpath.py.
|
|
91 |
"""
|
|
92 |
||
93 |
if not path: |
|
94 |
raise ValueError("no path specified") |
|
95 |
||
96 |
start_list = os.path.abspath(start).split(os.path.sep) |
|
97 |
path_list = os.path.abspath(path).split(os.path.sep) |
|
98 |
||
99 |
# Work out how much of the filepath is shared by start and path.
|
|
100 |
i = len(os.path.commonprefix([start_list, path_list])) |
|
101 |
||
102 |
rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:] |
|
103 |
if not rel_list: |
|
104 |
return os.path.curdir |
|
105 |
return os.path.join(*rel_list) |
|
106 |
||
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
107 |
def incomplete_utf8_sequence(byteseq): |
1213
by William Grant
Drop ivle.util.make_path (replaced by Request.make_path) and fix docstrings. |
108 |
"""Calculate the completeness of a UTF-8 encoded string.
|
109 |
||
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
110 |
Given a UTF-8-encoded byte sequence (str), returns the number of bytes at
|
111 |
the end of the string which comprise an incomplete UTF-8 character
|
|
112 |
sequence.
|
|
113 |
||
114 |
If the string is empty or ends with a complete character OR INVALID
|
|
115 |
sequence, returns 0.
|
|
116 |
Otherwise, returns 1-3 indicating the number of bytes in the final
|
|
117 |
incomplete (but valid) character sequence.
|
|
118 |
||
119 |
Does not check any bytes before the final sequence for correctness.
|
|
120 |
||
121 |
>>> incomplete_utf8_sequence("")
|
|
122 |
0
|
|
123 |
>>> incomplete_utf8_sequence("xy")
|
|
124 |
0
|
|
125 |
>>> incomplete_utf8_sequence("xy\xc3\xbc")
|
|
126 |
0
|
|
127 |
>>> incomplete_utf8_sequence("\xc3")
|
|
128 |
1
|
|
129 |
>>> incomplete_utf8_sequence("\xbc\xc3")
|
|
130 |
1
|
|
131 |
>>> incomplete_utf8_sequence("xy\xbc\xc3")
|
|
132 |
1
|
|
133 |
>>> incomplete_utf8_sequence("xy\xe0\xa0")
|
|
134 |
2
|
|
135 |
>>> incomplete_utf8_sequence("xy\xf4")
|
|
136 |
1
|
|
137 |
>>> incomplete_utf8_sequence("xy\xf4\x8f")
|
|
138 |
2
|
|
139 |
>>> incomplete_utf8_sequence("xy\xf4\x8f\xa0")
|
|
140 |
3
|
|
141 |
"""
|
|
142 |
count = 0 |
|
143 |
expect = None |
|
144 |
for b in byteseq[::-1]: |
|
145 |
b = ord(b) |
|
146 |
count += 1 |
|
147 |
if b & 0x80 == 0x0: |
|
148 |
# 0xxxxxxx (single-byte character)
|
|
149 |
expect = 1 |
|
150 |
break
|
|
151 |
elif b & 0xc0 == 0x80: |
|
152 |
# 10xxxxxx (subsequent byte)
|
|
153 |
pass
|
|
154 |
elif b & 0xe0 == 0xc0: |
|
155 |
# 110xxxxx (start of 2-byte sequence)
|
|
156 |
expect = 2 |
|
157 |
break
|
|
158 |
elif b & 0xf0 == 0xe0: |
|
159 |
# 1110xxxx (start of 3-byte sequence)
|
|
160 |
expect = 3 |
|
161 |
break
|
|
162 |
elif b & 0xf8 == 0xf0: |
|
163 |
# 11110xxx (start of 4-byte sequence)
|
|
164 |
expect = 4 |
|
165 |
break
|
|
166 |
else: |
|
167 |
# Invalid byte
|
|
168 |
return 0 |
|
169 |
||
170 |
if count >= 4: |
|
171 |
# Seen too many "subsequent bytes", invalid
|
|
172 |
return 0 |
|
173 |
||
174 |
if expect is None: |
|
175 |
# We never saw a "first byte", invalid
|
|
176 |
return 0 |
|
177 |
||
178 |
# We now know expect and count
|
|
179 |
if count >= expect: |
|
180 |
# Complete, or we saw an invalid sequence
|
|
181 |
return 0 |
|
182 |
elif count < expect: |
|
183 |
# Incomplete
|
|
184 |
return count |
|
1080.1.7
by matt.giuca
The new ivle.database.User class is now used in Request and usrmgt, which |
185 |
|
1165.3.75
by Matt Giuca
ivle/util.py: Added safe_rmtree, copied from Python2.6 shutil.rmtree (which |
186 |
def safe_rmtree(path, ignore_errors=False, onerror=None): |
187 |
"""Recursively delete a directory tree.
|
|
188 |
||
189 |
Copied from shutil.rmtree from Python 2.6, which does not follow symbolic
|
|
190 |
links (it is otherwise unsafe to call as root on untrusted directories; do
|
|
191 |
not use shutil.rmtree in this case, as you may be running Python 2.5).
|
|
192 |
||
193 |
If ignore_errors is set, errors are ignored; otherwise, if onerror
|
|
194 |
is set, it is called to handle the error with arguments (func,
|
|
195 |
path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
|
|
196 |
path is the argument to that function that caused it to fail; and
|
|
197 |
exc_info is a tuple returned by sys.exc_info(). If ignore_errors
|
|
198 |
is false and onerror is None, an exception is raised.
|
|
199 |
||
200 |
"""
|
|
201 |
if ignore_errors: |
|
202 |
def onerror(*args): |
|
203 |
pass
|
|
204 |
elif onerror is None: |
|
205 |
def onerror(*args): |
|
206 |
raise
|
|
207 |
try: |
|
208 |
if os.path.islink(path): |
|
209 |
# symlinks to directories are forbidden, see bug #1669
|
|
210 |
raise OSError("Cannot call safe_rmtree on a symbolic link") |
|
211 |
except OSError: |
|
212 |
onerror(os.path.islink, path, sys.exc_info()) |
|
213 |
# can't continue even if onerror hook returns
|
|
214 |
return
|
|
215 |
names = [] |
|
216 |
try: |
|
217 |
names = os.listdir(path) |
|
218 |
except os.error, err: |
|
219 |
onerror(os.listdir, path, sys.exc_info()) |
|
220 |
for name in names: |
|
221 |
fullname = os.path.join(path, name) |
|
222 |
try: |
|
223 |
mode = os.lstat(fullname).st_mode |
|
224 |
except os.error: |
|
225 |
mode = 0 |
|
226 |
if stat.S_ISDIR(mode): |
|
227 |
safe_rmtree(fullname, ignore_errors, onerror) |
|
228 |
else: |
|
229 |
try: |
|
230 |
os.remove(fullname) |
|
231 |
except os.error, err: |
|
232 |
onerror(os.remove, fullname, sys.exc_info()) |
|
233 |
try: |
|
234 |
os.rmdir(path) |
|
235 |
except os.error: |
|
236 |
onerror(os.rmdir, path, sys.exc_info()) |
|
1515
by Matt Giuca
Submit view: The projects list is now identical (except for radio buttons) to the view on the subjects page. It is much clearer and contains more info. The code is somewhat different, because it's a table, not a list, so I didn't abstract it. Moved a function out of subject.py to ivle.util, as it is shared by both views. |
237 |
|
238 |
def format_submission_principal(user, principal): |
|
239 |
"""Render a list of users to fit in the offering project listing.
|
|
240 |
||
241 |
Given a user and a list of submitters, returns 'solo' if the
|
|
242 |
only submitter is the user, or a string of the form
|
|
243 |
'with A, B and C' if there are any other submitters.
|
|
244 |
||
245 |
If submitters is None, we assume that the list of members could
|
|
246 |
not be determined, so we just return 'group'.
|
|
247 |
"""
|
|
248 |
if principal is None: |
|
249 |
return 'group' |
|
250 |
||
251 |
if principal is user: |
|
252 |
return 'solo' |
|
253 |
||
254 |
display_names = sorted( |
|
255 |
member.display_name for member in principal.members |
|
256 |
if member is not user) |
|
257 |
||
258 |
if len(display_names) == 0: |
|
259 |
return 'solo (%s)' % principal.name |
|
260 |
elif len(display_names) == 1: |
|
261 |
return 'with %s (%s)' % (display_names[0], principal.name) |
|
262 |
elif len(display_names) > 5: |
|
263 |
return 'with %d others (%s)' % (len(display_names), principal.name) |
|
264 |
else: |
|
265 |
return 'with %s and %s (%s)' % (', '.join(display_names[:-1]), |
|
266 |
display_names[-1], principal.name) |