OILS / core / pyutil.py View on Github | oilshell.org

228 lines, 105 significant
1"""pyutil.py: Code that's only needed in Python.
2
3C++ will use other mechanisms.
4"""
5from __future__ import print_function
6
7import sys
8import zipimport # NOT the zipfile module.
9
10from mycpp import mylib
11from pgen2 import grammar
12from pylib import os_path
13
14import posix_ as posix
15
16from typing import List, Union
17
18# Copied from 'string' module
19_PUNCT = """!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"""
20
21
22def nan():
23 # type: () -> float
24
25 # note: Python 3 has math.nan
26 return float('nan')
27
28
29def infinity():
30 # type: () -> float
31
32 # note: Python 3 has math.inf
33 return float('inf')
34
35
36def IsValidCharEscape(ch):
37 # type: (str) -> bool
38 """Is this a valid character escape when unquoted?"""
39 # These punctuation chars never needs to be escaped. (Note that _ is a
40 # keyword sometimes.)
41 if ch == '/' or ch == '.' or ch == '-':
42 return False
43
44 if ch == ' ': # foo\ bar is idiomatic
45 return True
46
47 # Borderline: ^ and %. But ^ is used for history?
48 # ! is an operator. And used for history.
49
50 # What about ^(...) or %(...) or /(...) .(1+2), etc.?
51
52 return ch in _PUNCT # like ispunct() in C
53
54
55def ChArrayToString(ch_array):
56 # type: (List[int]) -> str
57 """We avoid allocating 1 byte string objects in the C++ implementation.
58
59 'ch' is short for an integer that represents a character.
60 """
61 return ''.join(chr(ch) for ch in ch_array)
62
63
64def BackslashEscape(s, meta_chars):
65 # type: (str, str) -> str
66 """Escaped certain characters with backslashes.
67
68 Used for shell syntax (i.e. quoting completed filenames), globs, and
69 EREs.
70 """
71 escaped = []
72 for c in s:
73 if c in meta_chars:
74 escaped.append('\\')
75 escaped.append(c)
76 return ''.join(escaped)
77
78
79def strerror(e):
80 # type: (Union[IOError, OSError]) -> str
81 return posix.strerror(e.errno)
82
83
84def LoadYshGrammar(loader):
85 # type: (_ResourceLoader) -> grammar.Grammar
86 g = grammar.Grammar()
87 contents = loader.Get('_devbuild/gen/grammar.marshal')
88 g.loads(contents)
89 return g
90
91
92class _ResourceLoader(object):
93
94 def Get(self, rel_path):
95 # type: (str) -> str
96 raise NotImplementedError()
97
98
99class _FileResourceLoader(_ResourceLoader):
100 """Open resources relative to argv[0]."""
101
102 def __init__(self, root_dir):
103 # type: (str) -> None
104 self.root_dir = root_dir
105
106 def Get(self, rel_path):
107 # type: (str) -> str
108 with open(os_path.join(self.root_dir, rel_path)) as f:
109 contents = f.read()
110 return contents
111
112
113class _ZipResourceLoader(_ResourceLoader):
114 """Open resources INSIDE argv[0] as a zip file."""
115
116 def __init__(self, argv0):
117 # type: (str) -> None
118 self.z = zipimport.zipimporter(argv0)
119
120 def Get(self, rel_path):
121 # type: (str) -> str
122 return self.z.get_data(rel_path)
123
124
125def IsAppBundle():
126 # type: () -> bool
127 """Are we running inside Oil's patched version of CPython?
128
129 As opposed to a "stock" Python interpreter.
130 """
131 # Ovm_Main in main.c sets this.
132 return posix.environ.get('_OVM_IS_BUNDLE') == '1'
133
134
135_loader = None # type: _ResourceLoader
136
137
138def GetResourceLoader():
139 # type: () -> _ResourceLoader
140 global _loader
141 if _loader:
142 return _loader
143
144 if IsAppBundle():
145 ovm_path = posix.environ.get('_OVM_PATH')
146 _loader = _ZipResourceLoader(ovm_path)
147
148 # Now clear them so we don't pollute the environment. In Python, this
149 # calls unsetenv().
150 del posix.environ['_OVM_IS_BUNDLE']
151 del posix.environ['_OVM_PATH']
152
153 elif posix.environ.get('_OVM_RESOURCE_ROOT'): # Unit tests set this
154 root_dir = posix.environ.get('_OVM_RESOURCE_ROOT')
155 _loader = _FileResourceLoader(root_dir)
156
157 else:
158 # Find resources relative to the binary, e.g.
159 # ~/git/oilshell/oil/bin/oil.py. But it also assumes that all unit tests
160 # that use resources are are one directory deep, e.g. core/util_test.py.
161 bin_dir = os_path.dirname(os_path.abspath(sys.argv[0]))
162 root_dir = os_path.join(bin_dir, '..') # ~/git/oilshell/oil
163 _loader = _FileResourceLoader(root_dir)
164
165 return _loader
166
167
168def GetVersion(loader):
169 # type: (_ResourceLoader) -> str
170 contents = loader.Get('oil-version.txt')
171 version_str, _ = mylib.split_once(contents, '\n')
172 return version_str
173
174
175def PrintVersionDetails(loader):
176 # type: (_ResourceLoader) -> None
177 """Show version and platform information."""
178 try:
179 contents = loader.Get('release-date.txt')
180 release_date, _ = mylib.split_once(contents, '\n')
181 except IOError:
182 release_date = '-' # in dev tree
183
184 try:
185 contents = loader.Get('pyc-version.txt')
186 pyc_version, _ = mylib.split_once(contents, '\n')
187 except IOError:
188 pyc_version = '-' # in dev tree
189
190 # node is like 'hostname'
191 # release is the kernel version
192 system, unused_node, unused_release, platform_version, machine = posix.uname(
193 )
194
195 # The platform.py module has a big regex that parses sys.version, but we
196 # don't want to depend on regular expressions. So we will do our own parsing
197 # here.
198 version_line, py_compiler = sys.version.splitlines()
199
200 # Pick off the first part of '2.7.12 (default, ...)'
201 py_version = version_line.split()[0]
202
203 assert py_compiler.startswith('['), py_compiler
204 assert py_compiler.endswith(']'), py_compiler
205 py_compiler = py_compiler[1:-1]
206
207 # We removed sys.executable from sysmodule.c.
208 py_impl = 'CPython' if hasattr(sys, 'executable') else 'OVM'
209
210 # Call it OSH because "Oil" is deprecated
211 print('Release Date: %s' % release_date)
212 print('Arch: %s' % machine)
213 print('OS: %s' % system)
214 print('Platform: %s' % platform_version)
215 print('Compiler: %s' % py_compiler)
216 print('Interpreter: %s' % py_impl)
217 print('Interpreter version: %s' % py_version)
218 print('Bytecode: %s' % pyc_version)
219
220 # TODO: advertise oils-for-unix when it's ready
221
222
223# This was useful for debugging.
224def ShowFdState():
225 # type: () -> None
226 import subprocess
227 import posix_ as posix
228 subprocess.call(['ls', '-l', '/proc/%d/fd' % posix.getpid()])