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

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