1 | """pyutil.py: Code that's only needed in Python.
|
2 |
|
3 | C++ will use other mechanisms.
|
4 | """
|
5 | from __future__ import print_function
|
6 |
|
7 | import sys
|
8 | import zipimport # NOT the zipfile module.
|
9 |
|
10 | from mycpp import mylib
|
11 | from pgen2 import grammar
|
12 | from pylib import os_path
|
13 |
|
14 | import posix_ as posix
|
15 |
|
16 | from typing import List, Union
|
17 |
|
18 | # Copied from 'string' module
|
19 | _PUNCT = """!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"""
|
20 |
|
21 |
|
22 | def nan():
|
23 | # type: () -> float
|
24 |
|
25 | # note: Python 3 has math.nan
|
26 | return float('nan')
|
27 |
|
28 |
|
29 | def infinity():
|
30 | # type: () -> float
|
31 |
|
32 | # note: Python 3 has math.inf
|
33 | return float('inf')
|
34 |
|
35 |
|
36 | def 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 |
|
55 | def 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 |
|
64 | def 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 |
|
79 | def strerror(e):
|
80 | # type: (Union[IOError, OSError]) -> str
|
81 | return posix.strerror(e.errno)
|
82 |
|
83 |
|
84 | def 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 |
|
92 | class _ResourceLoader(object):
|
93 |
|
94 | def Get(self, rel_path):
|
95 | # type: (str) -> str
|
96 | raise NotImplementedError()
|
97 |
|
98 |
|
99 | class _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 |
|
113 | class _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 |
|
125 | def 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 |
|
138 | def 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 |
|
168 | def 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 |
|
175 | def 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.
|
224 | def ShowFdState():
|
225 | # type: () -> None
|
226 | import subprocess
|
227 | import posix_ as posix
|
228 | subprocess.call(['ls', '-l', '/proc/%d/fd' % posix.getpid()])
|