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

""" 

Miscellaneous operating system functions 

""" 

  

from posix.unistd cimport dup, dup2, close 

from cpython.exc cimport PyErr_SetFromErrno 

  

import os 

import contextlib 

  

def have_program(program, path=None): 

""" 

Return ``True`` if a ``program`` executable is found in the path 

given by ``path``. 

  

INPUT: 

  

- ``program`` - a string, the name of the program to check. 

  

- ``path`` - string or None. Paths to search for ``program``, 

separated by ``os.pathsep``. If ``None``, use the :envvar:`PATH` 

environment variable. 

  

OUTPUT: bool 

  

EXAMPLES:: 

  

sage: from sage.misc.sage_ostools import have_program 

sage: have_program('ls') 

True 

sage: have_program('there_is_not_a_program_with_this_name') 

False 

sage: have_program('sage', path=SAGE_ROOT) 

True 

sage: have_program('ls', path=SAGE_ROOT) 

False 

""" 

if path is None: 

path = os.environ.get('PATH', "") 

for p in path.split(os.pathsep): 

try: 

if os.access(os.path.join(p, program), os.X_OK): 

return True 

except OSError: 

pass 

return False 

  

  

@contextlib.contextmanager 

def restore_cwd(chdir=None): 

""" 

Context manager that restores the original working directory upon exiting. 

  

INPUT: 

  

- ``chdir`` -- optionally change directories to the given directory 

upon entering the context manager 

  

EXAMPLES: 

  

sage: import os 

sage: from sage.misc.sage_ostools import restore_cwd 

sage: from sage.misc.misc import SAGE_TMP 

sage: cwd = os.getcwd() 

sage: with restore_cwd(str(SAGE_TMP)): 

....: print(os.getcwd() == SAGE_TMP) 

True 

sage: cwd == os.getcwd() 

True 

""" 

orig_cwd = os.getcwd() 

if chdir is not None: 

os.chdir(chdir) 

try: 

yield 

finally: 

os.chdir(orig_cwd) 

  

  

cdef file_and_fd(x, int* fd): 

""" 

If ``x`` is a file, return ``x`` and set ``*fd`` to its file 

descriptor. If ``x`` is an integer, return ``None`` and set 

``*fd`` to ``x``. Otherwise, set ``*fd = -1`` and raise a 

``TypeError``. 

""" 

fd[0] = -1 

try: 

m = x.fileno 

except AttributeError: 

try: 

fd[0] = x 

except Exception: # Some objects raise bizarre exceptions when calling int() 

raise TypeError(f"{x} must be a Python file or an integer") 

return None 

fd[0] = m() 

return x 

  

  

cdef class redirection: 

r""" 

Context to implement redirection of files, analogous to the 

``>file`` or ``1>&2`` syntax in POSIX shells. 

  

Unlike the ``redirect_stdout`` and ``redirect_stderr`` contexts in 

the Python 3.4 standard library, this acts on the OS level, not on 

the Python level. This implies that it only works for true files, 

not duck-type file objects such as ``StringIO``. 

  

INPUT: 

  

- ``source`` -- the file to be redirected 

  

- ``dest`` -- where the source file should be redirected to 

  

- ``close`` -- (boolean, default: ``True``) whether to close the 

destination file upon exiting the context. This is only supported 

if ``dest`` is a Python file. 

  

The ``source`` and ``dest`` arguments can be either Python files 

or file descriptors. 

  

EXAMPLES:: 

  

sage: from sage.misc.sage_ostools import redirection 

sage: fn = tmp_filename() 

  

:: 

  

sage: with redirection(sys.stdout, open(fn, 'w')): 

....: print("hello world!") 

sage: with open(fn) as f: 

....: sys.stdout.write(f.read()) 

hello world! 

  

We can do the same using a file descriptor as source:: 

  

sage: fd = sys.stdout.fileno() 

sage: with redirection(fd, open(fn, 'w')): 

....: _ = os.write(fd, "hello world!\n") 

sage: with open(fn) as f: 

....: sys.stdout.write(f.read()) 

hello world! 

  

The converse also works:: 

  

sage: with open(fn, 'w') as f: 

....: f.write("This goes to the file\n") 

....: with redirection(f, sys.stdout, close=False): 

....: f.write("This goes to stdout\n") 

....: f.write("This goes to the file again\n") 

This goes to stdout 

sage: with open(fn) as f: 

....: sys.stdout.write(f.read()) 

This goes to the file 

This goes to the file again 

  

The same :class:`redirection` instance can be reused multiple times, 

provided that ``close=False``:: 

  

sage: f = open(fn, 'w+') 

sage: r = redirection(sys.stdout, f, close=False) 

sage: with r: 

....: print("Line 1") 

sage: with r: 

....: print("Line 2") 

sage: with f: 

....: f.seek(0) 

....: sys.stdout.write(f.read()) 

Line 1 

Line 2 

  

The redirection also works for subprocesses:: 

  

sage: import subprocess 

sage: with redirection(sys.stdout, open(fn, 'w')): 

....: _ = subprocess.call(["echo", "hello world"]) 

sage: with open(fn) as f: 

....: sys.stdout.write(f.read()) 

hello world 

  

TESTS:: 

  

sage: from six.moves import cStringIO as StringIO 

sage: redirection(sys.stdout, StringIO()) 

Traceback (most recent call last): 

... 

TypeError: <...> must be a Python file or an integer 

  

The redirection is removed and the destination file is closed even 

in the case of errors:: 

  

sage: f = open(os.devnull, 'w') 

sage: with redirection(sys.stdout, f): 

....: raise KeyboardInterrupt 

Traceback (most recent call last): 

... 

KeyboardInterrupt 

sage: f.closed 

True 

  

Reusing a :class:`redirection` instance with ``close=True``:: 

  

sage: f = open(fn, 'w+') 

sage: r = redirection(sys.stdout, f, close=True) 

sage: with r: 

....: print("Line 1") 

sage: with r: 

....: print("Line 2") 

Traceback (most recent call last): 

... 

ValueError: invalid destination file 

  

Using ``close=True`` on a non-file:: 

  

sage: redirection(sys.stdout, 0, close=True) 

Traceback (most recent call last): 

... 

ValueError: close=True requires a Python destination file 

  

Passing invalid file descriptors:: 

  

sage: with redirection(-123, open(os.devnull, 'w')): 

....: pass 

Traceback (most recent call last): 

... 

OSError: [Errno 9] Bad file descriptor 

sage: with redirection(sys.stdout, -123): 

....: pass 

Traceback (most recent call last): 

... 

OSError: [Errno 9] Bad file descriptor 

  

Nesting the same :class:`redirection` object is not allowed:: 

  

sage: with redirection(sys.stdout, open(os.devnull, 'w')) as r: 

....: with r: 

....: pass 

Traceback (most recent call last): 

... 

ValueError: source already redirected 

""" 

cdef readonly source_file, dest_file 

cdef readonly int source_fd, dest_fd, dup_source_fd 

cdef bint close_dest 

  

def __cinit__(self): 

self.source_fd = -1 

self.dest_fd = -1 

self.dup_source_fd = -1 

  

def __init__(self, source, dest, close=None): 

self.source_file = file_and_fd(source, &self.source_fd) 

self.dest_file = file_and_fd(dest, &self.dest_fd) 

  

if self.dest_file is None: 

if close: 

raise ValueError("close=True requires a Python destination file") 

elif close is None: 

close = True 

self.close_dest = close 

  

cdef int flush(self) except -1: 

for f in (self.source_file, self.dest_file): 

if f is not None: 

f.flush() 

  

def __enter__(self): 

# Basic sanity checks 

if self.source_fd == -1: 

raise ValueError("invalid source file") 

if self.dest_fd == -1: 

raise ValueError("invalid destination file") 

if self.dup_source_fd != -1: 

raise ValueError("source already redirected") 

  

self.flush() 

  

dupsrc = dup(self.source_fd) 

if dupsrc == -1: 

PyErr_SetFromErrno(OSError) 

fd = dup2(self.dest_fd, self.source_fd) 

if fd == -1: 

try: 

PyErr_SetFromErrno(OSError) 

finally: 

close(dupsrc) 

self.dup_source_fd = dupsrc 

return self 

  

def __exit__(self, *args): 

try: 

self.flush() 

finally: 

fd = dup2(self.dup_source_fd, self.source_fd) 

if fd == -1: 

PyErr_SetFromErrno(OSError) 

close(self.dup_source_fd) 

self.dup_source_fd = -1 

if self.close_dest: 

self.dest_file.close() 

self.dest_fd = -1