Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupport News AboutSign UpSign In
| Download
Views: 39558
1
#!/usr/bin/env python
2
###############################################################################
3
#
4
# CoCalc: Collaborative Calculation in the Cloud
5
#
6
# Copyright (C) 2016, Sagemath Inc.
7
#
8
# This program is free software: you can redistribute it and/or modify
9
# it under the terms of the GNU General Public License as published by
10
# the Free Software Foundation, either version 3 of the License, or
11
# (at your option) any later version.
12
#
13
# This program is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
# GNU General Public License for more details.
17
#
18
# You should have received a copy of the GNU General Public License
19
# along with this program. If not, see <http://www.gnu.org/licenses/>.
20
#
21
###############################################################################
22
23
MARKERS = {'cell':u"\uFE20", 'output':u"\uFE21"}
24
25
import cPickle, json, os, sys
26
27
from uuid import uuid4
28
def uuid():
29
return unicode(uuid4())
30
31
def process_html(html):
32
if '"div-interact-1"' in html:
33
# probably an interact
34
return ""
35
else:
36
return html
37
38
def process_output(s):
39
s = s.strip()
40
if not s:
41
return []
42
i = s.find("Traceback (most recent call last):")
43
if i != -1:
44
s0 = s[:i]
45
s1 = s[i:]
46
if s0:
47
return [{'stdout':s0}, {'stderr':s1}]
48
else:
49
return [{'stderr':s1}]
50
else:
51
return [{'stdout':s}]
52
53
54
DISPLAY_MATH = {'open':'<html><script type=\"math/tex; mode=display\">', 'close':'</script></html>', 'display':True}
55
INLINE_MATH = {'open':'<html><script type=\"math/tex\">', 'close':'</script></html>', 'display':False}
56
INLINE_MATH_2009 = {'open':'<html><span class=\"math\">', 'close':'</span></html>', 'display':False}
57
HTML = {'open':'<html>', 'close':'</html>'}
58
mnames = ['DISPLAY_MATH', 'INLINE_MATH', 'INLINE_MATH_2009']
59
def output_messages(output):
60
messages = []
61
62
while len(output) > 0:
63
found = False
64
for ii, marker in enumerate([DISPLAY_MATH, INLINE_MATH, INLINE_MATH_2009]):
65
i = output.find(marker['open'])
66
if i != -1:
67
#print('found',mnames[ii])
68
messages.extend(process_output(output[:i]))
69
j = output.find(marker['close'])
70
if j != -1:
71
messages.append({'tex':{'tex':output[i+len(marker['open']):j], 'display':marker['display']}})
72
output = output[j+len(marker['close']):]
73
found = True
74
break
75
if found: continue
76
77
i = output.find(HTML['open'])
78
if i != -1:
79
messages.extend(process_output(output[:i]))
80
j = output.find(HTML['close'])
81
if j != -1:
82
messages.append({'html':process_html(output[i+len(HTML['open']):j])})
83
output = output[j+len(HTML['close']):]
84
continue
85
86
messages.extend(process_output(output))
87
output = ''
88
89
return MARKERS['output'].join(unicode(json.dumps(x)) for x in messages)
90
91
def migrate_input(s):
92
# Given the input to a cell, possibly make modifications heuristically to it to make it more
93
# Sagemath Cloud friendly.
94
return s
95
96
def sws_body_to_sagews(body):
97
98
out = u""
99
i = 0
100
while i!=-1 and i <len(body):
101
j = body.find("{{{", i)
102
if j == -1:
103
j = len(body)
104
html = body[i:j]
105
k = body.find("\n", j+3)
106
if k == -1:
107
break
108
k2 = body.find("///", k)
109
if k2 == -1:
110
output = ""
111
k2 = body.find("}}}", k)
112
if k2 == -1:
113
input = ""
114
k2 = len(body)
115
i = len(body)
116
else:
117
input = body[k+1:k2]
118
i = k2+4
119
else:
120
input = body[k+1:k2]
121
k3 = body.find("}}}", k2+4)
122
if k3 == -1:
123
output = ""
124
i = len(body)
125
else:
126
output = body[k2+4:k3]
127
i = k3+4
128
129
html = unicode(html.strip(), encoding='utf8')
130
input = unicode(migrate_input(input.strip()), encoding='utf8')
131
output = unicode(output.strip(), encoding='utf8')
132
133
134
if html:
135
out += MARKERS['cell'] + uuid() + 'i' + MARKERS['cell'] + u'\n'
136
out += '%html\n'
137
out += html + u'\n'
138
out += (u'\n' + MARKERS['output'] + uuid() + MARKERS['output'] +
139
json.dumps({'html':html}) + MARKERS['output']) + u'\n'
140
141
if input or output:
142
modes = ''
143
if '%auto' in input:
144
modes += 'a'
145
if '%hide' in input:
146
modes += 'i'
147
if '%hideall' in input:
148
modes += 'o'
149
out += MARKERS['cell'] + uuid() + modes + MARKERS['cell'] + u'\n'
150
out += input
151
out += (u'\n' + MARKERS['output'] + uuid() + MARKERS['output'] +
152
output_messages(output) + MARKERS['output']) + u'\n'
153
154
return out
155
156
def extra_modes(meta):
157
s = ''
158
if 'pretty_print' in meta:
159
s += u'typeset_mode(True, display=False)\n'
160
if 'system' in meta and meta['system'] != 'sage':
161
s += u'%%default_mode %s\n'%meta['system']
162
if not s:
163
return ''
164
# The 'a' means "auto".
165
return MARKERS['cell'] + uuid() + 'a' + MARKERS['cell'] + u'\n%auto\n' + s
166
167
def write_data_files(t, pfx = 'sage_worksheet'):
168
prefix = '{}/data/'.format(pfx)
169
data = [p for p in t if p.startswith(prefix)]
170
out = []
171
target = "foo.data"
172
if data:
173
if not os.path.exists(target):
174
os.makedirs(target)
175
for p in data:
176
dest = os.path.join(target, p[len(prefix):])
177
out.append(dest)
178
open(dest,'wb').write(t.extractfile(p).read())
179
return out, target
180
181
def sws_to_sagews(filename):
182
"""
183
Convert a Sage Notebook sws file to a SageMath Cloud sagews file.
184
185
INPUT:
186
- ``filename`` -- the name of an sws file, say foo.sws
187
188
OUTPUT:
189
- creates a file foo[-n].sagews and returns the name of the output file
190
191
.. NOTE::
192
193
sws files from around 2009 are bzip2 archives with the following layout:
194
19/worksheet.txt
195
19/data/
196
19/conf.sobj
197
19/snapshots/1252938265.bz2
198
19/snapshots/1252940938.bz2
199
19/snapshots/1252940986.bz2
200
19/code/
201
19/cells/
202
19/cells/13/
203
19/cells/14/
204
...
205
sws files from 2012 and later have a layout like this:
206
sage_worksheet/worksheet_conf.pickle
207
sage_worksheet/worksheet.html
208
sage_worksheet/worksheet.txt
209
sage_worksheet/data/fcla.css
210
211
"""
212
out = ''
213
214
import os, tarfile
215
t = tarfile.open(name=filename, mode='r:bz2', bufsize=10240)
216
tfiles = t.getnames()
217
fmt_2011 = True
218
if 'sage_worksheet/worksheet.html' in tfiles:
219
pfx = 'sage_worksheet'
220
wkfile = 'sage_worksheet/worksheet.html'
221
else:
222
# older format files will not have 'sage_worksheet' at top level
223
pfx = tfiles[0]
224
wkfile = os.path.join(pfx,'worksheet.txt')
225
if wkfile in tfiles:
226
fmt_2011 = False # 2009 format
227
else:
228
raise ValueError('could not find sage_worksheet/worksheet.html or {} in {}'.format(wkfile, filename))
229
230
body = t.extractfile(wkfile).read()
231
data_files, data_path = write_data_files(pfx, t)
232
if data_files:
233
out += MARKERS['cell'] + uuid() + 'ai' + MARKERS['cell'] + u'\n%%hide\n%%auto\nDATA="%s/"\n'%data_path
234
out += sws_body_to_sagews(body)
235
236
meta = {}
237
if fmt_2011:
238
try:
239
meta = cPickle.loads(t.extractfile('sage_worksheet/worksheet_conf.pickle').read())
240
except KeyError:
241
if INLINE_MATH['open'] in body:
242
meta['pretty_print'] = True
243
else:
244
if INLINE_MATH_2009['open'] in body:
245
meta['pretty_print'] = True
246
out = extra_modes(meta) + out
247
248
base = os.path.splitext(filename)[0]
249
i = 0
250
outfile = base + '.sagews'
251
if os.path.exists(outfile):
252
sys.stderr.write("%s: Warning --Sagemath cloud worksheet '%s' already exists. Not overwriting.\n"%(sys.argv[0], outfile))
253
sys.stderr.flush()
254
else:
255
sys.stdout.write("%s: Creating Sagemath cloud worksheet '%s'\n"%(sys.argv[0], outfile))
256
sys.stdout.flush()
257
open(outfile,'w').write(out.encode('utf8'))
258
259
260
def main():
261
if len(sys.argv) == 1:
262
sys.stderr.write("""
263
Convert a Sage Notebook sws file to a SageMath Cloud sagews file.
264
265
Usage: %s path/to/filename.sws [path/to/filename2.sws] ...
266
267
Creates corresponding file path/to/filename.sagews, if it doesn't exist.
268
Also, a data/ directory may be created in the current directory, which contains
269
the contents of the data path in filename.sws.
270
"""%sys.argv[0])
271
sys.exit(1)
272
273
for path in sys.argv[1:]:
274
sws_to_sagews(path)
275
276
if __name__ == "__main__":
277
main()
278
279
280
281
282
283
284
285