Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

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

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

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

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

# -*- encoding: utf-8 -*- 

r""" 

Output Buffer 

 

This is the fundamental unit of rich output, a single immutable buffer 

(either in-memory or as a file). Rich output always consists of one or 

more buffers. Ideally, the Sage library always uses the buffer object 

as an in-memory buffer. But you can also ask it for a filename, and it 

will save the data to a file if necessary. Either way, the buffer 

object presents the same interface for getting the content of an 

in-memory buffer or a temporary file. So any rich output backends do 

not need to know where the buffer content is actually stored. 

 

EXAMPLES:: 

 

sage: from sage.repl.rich_output.buffer import OutputBuffer 

sage: buf = OutputBuffer('this is the buffer content'); buf 

buffer containing 26 bytes 

sage: buf.get().decode('ascii') 

u'this is the buffer content' 

sage: type(buf.get()) is bytes 

True 

""" 

#***************************************************************************** 

# Copyright (C) 2015 Volker Braun <vbraun.name@gmail.com> 

# 

# Distributed under the terms of the GNU General Public License (GPL) 

# as published by the Free Software Foundation; either version 2 of 

# the License, or (at your option) any later version. 

# http://www.gnu.org/licenses/ 

#***************************************************************************** 

 

 

import os 

import six 

from sage.structure.sage_object import SageObject 

 

 

class OutputBuffer(SageObject): 

 

def __init__(self, data): 

""" 

Data stored either in memory or as a file 

 

This class is an abstraction for "files", in that they can 

either be defined by a bytes array (Python 3) or string 

(Python 2) or by a file (see :meth:`from_file`). 

 

INPUT: 

 

- ``data`` -- bytes. The data that is stored in the buffer. 

 

EXAMPLES:: 

 

sage: from sage.repl.rich_output.buffer import OutputBuffer 

sage: buf = OutputBuffer('this is the buffer content'); buf 

buffer containing 26 bytes 

 

sage: buf2 = OutputBuffer(buf); buf2 

buffer containing 26 bytes 

 

sage: buf.get_str() 

'this is the buffer content' 

sage: buf.filename(ext='.txt') 

'/....txt' 

""" 

if isinstance(data, OutputBuffer): 

self._filename = data._filename 

self._data = data._data 

else: 

self._filename = None 

if not isinstance(data, bytes): 

self._data = data.encode('utf-8') 

else: 

self._data = data 

 

@classmethod 

def from_file(cls, filename): 

""" 

Construct buffer from data in file. 

 

.. WARNING:: 

 

The buffer assumes that the file content remains the same 

during the lifetime of the Sage session. To communicate 

this to the user, the file permissions will be changed to 

read only. 

 

INPUT: 

 

- ``filename`` -- string. The filename under which the data is 

stored. 

 

OUTPUT: 

 

String containing the buffer data. 

 

EXAMPLES:: 

 

sage: from sage.repl.rich_output.buffer import OutputBuffer 

sage: name = sage.misc.temporary_file.tmp_filename() 

sage: with open(name, 'wb') as f: 

....: _ = f.write(b'file content') 

sage: buf = OutputBuffer.from_file(name); buf 

buffer containing 12 bytes 

 

sage: buf.filename() == name 

True 

sage: buf.get_str() 

'file content' 

""" 

buf = cls.__new__(cls) 

buf._filename = filename 

buf._data = None 

buf._chmod_readonly(buf._filename) 

return buf 

 

@classmethod 

def _chmod_readonly(cls, filename): 

""" 

Make file readonly 

 

INPUT: 

 

- ``filename`` -- string. Name of an already-existing file. 

 

EXAMPLES:: 

 

sage: from sage.repl.rich_output.buffer import OutputBuffer 

sage: tmp = sage.misc.temporary_file.tmp_filename() 

sage: with open(tmp, 'wb') as f: 

....: _ = f.write(b'file content') 

sage: OutputBuffer._chmod_readonly(tmp) 

sage: import os, stat 

sage: stat.S_IMODE(os.stat(tmp).st_mode) & (stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH) 

0 

""" 

import os 

from sage.env import SAGE_EXTCODE 

filename = os.path.abspath(filename) 

if filename.startswith(os.path.abspath(SAGE_EXTCODE)): 

# Do not change permissions on the sample rich output 

# files, as it will cause trouble when upgrading Sage 

return 

import stat 

mode = os.stat(filename).st_mode 

mode = stat.S_IMODE(mode) & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH) 

os.chmod(filename, mode) 

 

def _repr_(self): 

""" 

Return a string representation 

 

OUTPUT: 

 

String 

 

EXAMPLES:: 

 

sage: from sage.repl.rich_output.buffer import OutputBuffer 

sage: OutputBuffer('test1234') 

buffer containing 8 bytes 

""" 

return 'buffer containing {0} bytes'.format(len(self.get())) 

 

def get(self): 

""" 

Return the buffer content 

 

OUTPUT: 

 

Bytes. A string in Python 2.x. 

 

EXAMPLES:: 

 

sage: from sage.repl.rich_output.buffer import OutputBuffer 

sage: c = OutputBuffer('test1234').get(); c.decode('ascii') 

u'test1234' 

sage: type(c) is bytes 

True 

sage: c = OutputBuffer(u'été').get() 

sage: type(c) is bytes 

True 

""" 

if self._data is None: 

with open(self._filename, 'rb') as f: 

self._data = f.read() 

return self._data 

 

def get_unicode(self): 

""" 

Return the buffer content as string 

 

OUTPUT: 

 

String. Unicode in Python 2.x. Raises a ``UnicodeEncodeError`` 

if the data is not valid utf-8. 

 

EXAMPLES:: 

 

sage: from sage.repl.rich_output.buffer import OutputBuffer 

sage: OutputBuffer('test1234').get().decode('ascii') 

u'test1234' 

sage: OutputBuffer('test1234').get_unicode() 

u'test1234' 

""" 

return self.get().decode('utf-8') 

 

def get_str(self): 

""" 

Return the buffer content as a ``str`` object for the current Python 

version. 

 

That is, returns a Python 2-style encoding-agnostic ``str`` on Python 

2, and returns a unicode ``str`` on Python 3 with the buffer content 

decoded from UTF-8. In other words, this is equivalent to 

``OutputBuffer.get`` on Python 2 and ``OutputBuffer.get_unicode`` on 

Python 3. This is useful in some cases for cross-compatible code. 

 

OUTPUT: 

 

A ``str`` object. 

 

EXAMPLES:: 

 

sage: from sage.repl.rich_output.buffer import OutputBuffer 

sage: c = OutputBuffer('test1234').get_str(); c 

'test1234' 

sage: type(c) is str 

True 

sage: c = OutputBuffer(u'été').get_str() 

sage: type(c) is str 

True 

""" 

 

if six.PY2: 

return self.get() 

else: 

return self.get_unicode() 

 

def filename(self, ext=None): 

""" 

Return the filename. 

 

INPUT: 

 

- ``ext`` -- string. The file extension. 

 

OUTPUT: 

 

Name of a file, most likely a temporary file. If ``ext`` is 

specified, the filename will have that extension. 

 

You must not modify the returned file. Its permissions are set 

to readonly to help with that. 

 

EXAMPLES:: 

 

sage: from sage.repl.rich_output.buffer import OutputBuffer 

sage: buf = OutputBuffer('test') 

sage: buf.filename() # random output 

'/home/user/.sage/temp/hostname/26085/tmp_RNSfAc' 

 

sage: os.path.isfile(buf.filename()) 

True 

sage: buf.filename(ext='txt') # random output 

'/home/user/.sage/temp/hostname/26085/tmp_Rjjp4V.txt' 

sage: buf.filename(ext='txt').endswith('.txt') 

True 

""" 

if ext is None: 

ext = '' 

elif not ext.startswith('.'): 

ext = '.' + ext 

 

if self._filename is None or not self._filename.endswith(ext): 

from sage.misc.temporary_file import tmp_filename 

output = tmp_filename(ext=ext) 

else: 

output = self._filename 

 

if self._filename is None: 

assert self._data is not None 

with open(output, 'wb') as f: 

f.write(self._data) 

self._filename = output 

elif self._filename != output: 

try: 

os.link(self._filename, output) 

except (OSError, AttributeError): 

import shutil 

shutil.copy2(self._filename, output) 

 

self._chmod_readonly(output) 

return output 

 

def save_as(self, filename): 

""" 

Save a copy of the buffer content. 

 

You may edit the returned file, unlike the file returned by 

:meth:`filename`. 

 

INPUT: 

 

- ``filename`` -- string. The file name to save under. 

 

EXAMPLES:: 

 

sage: from sage.repl.rich_output.buffer import OutputBuffer 

sage: buf = OutputBuffer('test') 

sage: buf.filename(ext='txt') # random output 

sage: tmp = tmp_dir() 

sage: filename = os.path.join(tmp, 'foo.txt') 

sage: buf.save_as(filename) 

sage: with open(filename, 'r') as f: 

....: f.read() 

'test' 

""" 

with open(filename, 'wb') as f: 

f.write(self.get())