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 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 |
|
41 | def 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 |
|
50 | def 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 |
|
65 | def strerror(e):
|
66 | # type: (Union[IOError, OSError]) -> str
|
67 | return posix.strerror(e.errno)
|
68 |
|
69 |
|
70 | def 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 |
|
78 | class _ResourceLoader(object):
|
79 |
|
80 | def Get(self, rel_path):
|
81 | # type: (str) -> str
|
82 | raise NotImplementedError()
|
83 |
|
84 |
|
85 | class _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 |
|
99 | class _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 |
|
111 | def 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 |
|
124 | def 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 |
|
154 | def 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 |
|
161 | def 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.
|
210 | def ShowFdState():
|
211 | # type: () -> None
|
212 | import subprocess
|
213 | import posix_ as posix
|
214 | subprocess.call(['ls', '-l', '/proc/%d/fd' % posix.getpid()])
|