pyarmor.cli 9.2.5__2-py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyarmor/cli/__init__.py +46 -0
- pyarmor/cli/__main__.py +812 -0
- pyarmor/cli/batch.py +62 -0
- pyarmor/cli/bootstrap.py +207 -0
- pyarmor/cli/bug.py +137 -0
- pyarmor/cli/command.py +563 -0
- pyarmor/cli/config.py +306 -0
- pyarmor/cli/context.py +790 -0
- pyarmor/cli/core.data.1 +0 -0
- pyarmor/cli/core.data.2 +0 -0
- pyarmor/cli/core.data.3 +0 -0
- pyarmor/cli/default.cfg +564 -0
- pyarmor/cli/docker.py +192 -0
- pyarmor/cli/generate.py +256 -0
- pyarmor/cli/hdinfo.py +81 -0
- pyarmor/cli/merge.py +232 -0
- pyarmor/cli/mixer.py +109 -0
- pyarmor/cli/model.py +460 -0
- pyarmor/cli/plugin.py +292 -0
- pyarmor/cli/project.py +1281 -0
- pyarmor/cli/public_capsule.zip +0 -0
- pyarmor/cli/register.py +1093 -0
- pyarmor/cli/repack.py +793 -0
- pyarmor/cli/resource.py +265 -0
- pyarmor/cli/shell.py +481 -0
- pyarmor_cli-9.2.5.dist-info/METADATA +74 -0
- pyarmor_cli-9.2.5.dist-info/RECORD +30 -0
- pyarmor_cli-9.2.5.dist-info/WHEEL +6 -0
- pyarmor_cli-9.2.5.dist-info/entry_points.txt +3 -0
- pyarmor_cli-9.2.5.dist-info/top_level.txt +1 -0
pyarmor/cli/resource.py
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
#! /usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
#
|
|
4
|
+
#############################################################
|
|
5
|
+
# #
|
|
6
|
+
# Copyright @ 2023 - Dashingsoft corp. #
|
|
7
|
+
# All rights reserved. #
|
|
8
|
+
# #
|
|
9
|
+
# Pyarmor #
|
|
10
|
+
# #
|
|
11
|
+
# Version: 8.0.1 - #
|
|
12
|
+
# #
|
|
13
|
+
#############################################################
|
|
14
|
+
#
|
|
15
|
+
#
|
|
16
|
+
# @File: cli/resource.py
|
|
17
|
+
#
|
|
18
|
+
# @Author: Jondy Zhao (pyarmor@163.com)
|
|
19
|
+
#
|
|
20
|
+
# @Create Date: 2022-12-06
|
|
21
|
+
#
|
|
22
|
+
import ast
|
|
23
|
+
import os
|
|
24
|
+
|
|
25
|
+
from datetime import datetime
|
|
26
|
+
from fnmatch import fnmatch
|
|
27
|
+
from string import Template
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Resource(object):
|
|
31
|
+
|
|
32
|
+
def __init__(self, path, name=None, parent=None):
|
|
33
|
+
self.parent = parent
|
|
34
|
+
self.path = path
|
|
35
|
+
self.name = name if name else self._format_name(path)
|
|
36
|
+
|
|
37
|
+
def __str__(self):
|
|
38
|
+
return self.fullname
|
|
39
|
+
|
|
40
|
+
def _format_name(self, path):
|
|
41
|
+
return os.path.splitext(os.path.basename(path))[0]
|
|
42
|
+
|
|
43
|
+
def is_top(self):
|
|
44
|
+
return self.parent is None
|
|
45
|
+
|
|
46
|
+
def is_script(self):
|
|
47
|
+
return isinstance(self, (FileResource, PycResource))
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def fullname(self):
|
|
51
|
+
return self.name if self.is_top() else \
|
|
52
|
+
'.'.join([self.parent.fullname, self.name])
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def fullpath(self):
|
|
56
|
+
return self.path if self.is_top() else \
|
|
57
|
+
os.path.join(self.parent.fullpath, self.path)
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def pkgname(self):
|
|
61
|
+
# if input path is '.', then pkgname will start with '..'
|
|
62
|
+
suffix = '.__init__'
|
|
63
|
+
if self.fullname.endswith(suffix):
|
|
64
|
+
return self.fullname[:-len(suffix)]
|
|
65
|
+
return self.fullname
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def output_path(self):
|
|
69
|
+
return '' if self.is_top() else \
|
|
70
|
+
os.path.join(self.parent.output_path, self.parent.name)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class FileResource(Resource):
|
|
74
|
+
|
|
75
|
+
def __init__(self, path, name=None, parent=None):
|
|
76
|
+
super().__init__(path, name=name, parent=parent)
|
|
77
|
+
|
|
78
|
+
self.mtree = None
|
|
79
|
+
self.mco = None
|
|
80
|
+
|
|
81
|
+
self.shebang = ''
|
|
82
|
+
|
|
83
|
+
# Do not touch these nodes in final protector
|
|
84
|
+
self.exclude_nodes = set()
|
|
85
|
+
# Do not touch these code objects in final patcher
|
|
86
|
+
self.exclude_co_objects = set()
|
|
87
|
+
|
|
88
|
+
def __str__(self):
|
|
89
|
+
return 'file %s%s' % (self.name, self.pyext)
|
|
90
|
+
|
|
91
|
+
def __iter__(self):
|
|
92
|
+
yield self
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def pyext(self):
|
|
96
|
+
return os.path.splitext(self.path)[-1]
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def output_filename(self):
|
|
100
|
+
return os.path.join(self.output_path, self.name + self.pyext)
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def frozenname(self):
|
|
104
|
+
n = self.fullname.find('.__init__')
|
|
105
|
+
return '<frozen %s>' % self.fullname[:None if n == -1 else n]
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def is_pyc(self):
|
|
109
|
+
return self.pyext.lower() == '.pyc'
|
|
110
|
+
|
|
111
|
+
def _get_encoding(self, encoding):
|
|
112
|
+
# Maybe: from tokenize import detect_encoding
|
|
113
|
+
from codecs import BOM_UTF8
|
|
114
|
+
from re import search as research
|
|
115
|
+
|
|
116
|
+
def find_encoding(line):
|
|
117
|
+
try:
|
|
118
|
+
pat = r'coding[=:]\s*([-\w.]+)'
|
|
119
|
+
n = line.find(b'\n')
|
|
120
|
+
if n > -1:
|
|
121
|
+
m = research(pat, line[:n].decode())
|
|
122
|
+
if m:
|
|
123
|
+
return m.group(1)
|
|
124
|
+
if line[n+1] == 35:
|
|
125
|
+
k = n + 1
|
|
126
|
+
n = line.find(b'\n', k)
|
|
127
|
+
m = research(pat, line[k:n].decode())
|
|
128
|
+
if m:
|
|
129
|
+
return m.group(1)
|
|
130
|
+
except Exception:
|
|
131
|
+
# Ignore decoding error or index error
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
with open(self.fullpath, 'rb') as f:
|
|
135
|
+
line = f.read(80)
|
|
136
|
+
if line and line[:3] == BOM_UTF8:
|
|
137
|
+
return 'utf-8'
|
|
138
|
+
if line and line[0] == 35:
|
|
139
|
+
encoding = find_encoding(line) or encoding
|
|
140
|
+
return encoding
|
|
141
|
+
|
|
142
|
+
def readlines(self, encoding=None):
|
|
143
|
+
if not os.path.exists(self.fullpath):
|
|
144
|
+
raise RuntimeError('file "%s" doesn\'t exists' % self.fullpath)
|
|
145
|
+
|
|
146
|
+
with open(self.fullpath, encoding=self._get_encoding(encoding)) as f:
|
|
147
|
+
# file.read() can't read the whole data of big files
|
|
148
|
+
lines = f.readlines()
|
|
149
|
+
if lines and lines[0].startswith('#!'):
|
|
150
|
+
self.shebang = lines[0]
|
|
151
|
+
return lines
|
|
152
|
+
|
|
153
|
+
def reparse(self, lines=None, encoding=None):
|
|
154
|
+
if lines is None:
|
|
155
|
+
lines = self.readlines(encoding=encoding)
|
|
156
|
+
self.mtree = ast.parse(''.join(lines), self.frozenname, 'exec')
|
|
157
|
+
|
|
158
|
+
def _recompile_pyc(self):
|
|
159
|
+
from importlib._bootstrap_external import SourcelessFileLoader
|
|
160
|
+
path, name = self.fullpath, self.pkgname
|
|
161
|
+
self.mco = SourcelessFileLoader(name, path).get_code(name)
|
|
162
|
+
|
|
163
|
+
def recompile(self, mtree=None, optimize=1):
|
|
164
|
+
if self.is_pyc:
|
|
165
|
+
return self._recompile_pyc()
|
|
166
|
+
|
|
167
|
+
if mtree is None:
|
|
168
|
+
mtree = self.mtree
|
|
169
|
+
assert (mtree is not None)
|
|
170
|
+
self.mco = compile(mtree, self.frozenname, 'exec', optimize=optimize)
|
|
171
|
+
|
|
172
|
+
def clean(self):
|
|
173
|
+
self.lines = None
|
|
174
|
+
self.mtree = None
|
|
175
|
+
self.mco = None
|
|
176
|
+
if hasattr(self, 'jit_iv'):
|
|
177
|
+
self.jit_iv = None
|
|
178
|
+
if hasattr(self, 'jit_data'):
|
|
179
|
+
self.jit_data = None
|
|
180
|
+
|
|
181
|
+
def generate_output(self, tpl, code, relative=0, pkgname='pyarmor_runtime',
|
|
182
|
+
bootpath='__file__', rev=''):
|
|
183
|
+
if relative == 0:
|
|
184
|
+
prefix = ''
|
|
185
|
+
elif relative == 1:
|
|
186
|
+
prefix = '.' * self.fullname.count('.')
|
|
187
|
+
else:
|
|
188
|
+
assert (isinstance(relative, str))
|
|
189
|
+
prefix = relative + '.'
|
|
190
|
+
if self.fullname.startswith(prefix):
|
|
191
|
+
prefix = '.' * self.fullname.count('.')
|
|
192
|
+
elif prefix.startswith(self.pkgname + '.'):
|
|
193
|
+
prefix = prefix[len(self.pkgname):]
|
|
194
|
+
|
|
195
|
+
source = Template(tpl).safe_substitute(
|
|
196
|
+
timestamp=datetime.now().isoformat(),
|
|
197
|
+
package=prefix + pkgname,
|
|
198
|
+
path=bootpath,
|
|
199
|
+
code=repr(code),
|
|
200
|
+
rev=rev)
|
|
201
|
+
return (self.shebang + source) if self.shebang else source
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class PycResource(FileResource):
|
|
205
|
+
|
|
206
|
+
def recompile(self, mtree=None, optimize=1):
|
|
207
|
+
from importlib._bootstrap_external import SourcelessFileLoader
|
|
208
|
+
path, name = self.fullpath, self.pkgname
|
|
209
|
+
self.mco = SourcelessFileLoader(name, path).get_code(name)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class PathResource(Resource):
|
|
213
|
+
|
|
214
|
+
def __init__(self, path, name=None, parent=None):
|
|
215
|
+
super().__init__(path, name=os.path.basename(path), parent=parent)
|
|
216
|
+
self.respaths = []
|
|
217
|
+
self.resfiles = []
|
|
218
|
+
|
|
219
|
+
def __str__(self):
|
|
220
|
+
return 'path %s' % self.fullname
|
|
221
|
+
|
|
222
|
+
def __iter__(self):
|
|
223
|
+
for res in self.resfiles:
|
|
224
|
+
if res:
|
|
225
|
+
yield res
|
|
226
|
+
for child in self.respaths:
|
|
227
|
+
for res in child:
|
|
228
|
+
yield res
|
|
229
|
+
|
|
230
|
+
def rebuild(self, **options):
|
|
231
|
+
pyexts = options.get('pyexts', ['.py'])
|
|
232
|
+
recursive = options.get('recursive', False)
|
|
233
|
+
includes = options.get('includes', '').split()
|
|
234
|
+
excludes = options.get('excludes', '').split()
|
|
235
|
+
patterns = options.get('data_files', '').split()
|
|
236
|
+
|
|
237
|
+
def in_filter(path, name):
|
|
238
|
+
fullpath = os.path.join(path, name)
|
|
239
|
+
ext = os.path.splitext(name)[1]
|
|
240
|
+
return not ex_filter(path, name) and (
|
|
241
|
+
(ext and ext in pyexts)
|
|
242
|
+
or any([fnmatch(fullpath, x) for x in includes]))
|
|
243
|
+
|
|
244
|
+
def ex_filter(path, name):
|
|
245
|
+
fullpath = os.path.join(path, name)
|
|
246
|
+
return excludes and any([fnmatch(fullpath, x) for x in excludes])
|
|
247
|
+
|
|
248
|
+
def is_res(path, name):
|
|
249
|
+
s = os.path.join(path, name)
|
|
250
|
+
return any([fnmatch(s, x) for x in patterns])
|
|
251
|
+
|
|
252
|
+
for path, dirnames, filenames in os.walk(self.fullpath):
|
|
253
|
+
self.resfiles = [x for x in [
|
|
254
|
+
FileResource(name, parent=self) if in_filter(path, name)
|
|
255
|
+
else Resource(name, parent=self) if is_res(path, name)
|
|
256
|
+
else None for name in filenames
|
|
257
|
+
] if x]
|
|
258
|
+
self.respaths = [PathResource(name, parent=self)
|
|
259
|
+
for name in dirnames
|
|
260
|
+
if not ex_filter(path, name)]
|
|
261
|
+
break
|
|
262
|
+
|
|
263
|
+
if recursive:
|
|
264
|
+
for res in self.respaths:
|
|
265
|
+
res.rebuild(**options)
|