def _refmt_io(file_i, file_o='', *args, **kwargs): ext_i, ext_o = func.__name__.split('_2_') assert os.path.exists(file_i) and file_i.endswith(ext_i) if not file_o: file_o = '{}/{}.base64.{}'.format( os.path.dirname(file_i), os.path.splitext(os.path.basename(file_i))[0], ext_o) lk.logp(file_i, file_o) if os.path.exists(file_o): lk.loga('the target file already exists, it will be overriden') return func(file_i, file_o, *args, **kwargs)
def compile_one(self, src_file, dst_file): """ Compile `src_file` and generate `dst_file`. Args: src_file dst_file References: `cmd:pyarmor obfuscate -h` Results: the `dst_file` has the same content structure: from pytransform import pyarmor_runtime pyarmor_runtime() __pyarmor__(__name__, __file__, b'\\x50\\x59\\x41...') `pytransform` comes from `{dist}/lib`, it will be added to `sys.path` in the startup (see `pyportable_installer/template/ bootloader.txt` and `pyportable_installer/no3_build_pyproject.py:: func:_create_launcher`). Notes: table of `pyarmor obfuscate --bootstrap {0~4}` | command | result | | ================== | =========================================== | | `pyarmor obfuscate | each obfuscated file has a header of | | --bootstrap 0` | `from .pytransform import pyarmor_runtime` | | ------------------ | ------------------------------------------- | | `pyarmor obfuscate | only `__init__.py` has a header of | | --bootstrap 1` | `from .pytransform import pyarmor_runtime` | | ------------------ | ------------------------------------------- | | `pyarmor obfuscate | each obfuscated file has a header of | | --bootstrap 2` | `from pytransform import pyarmor_runtime` | | | **this is what we want** | | ------------------ | ------------------------------------------- | | `pyarmor obfuscate | *unknown* | | --bootstrap 3` | | | ------------------ | ------------------------------------------- | | `pyarmor obfuscate | *unknown* | | --bootstrap 4` | | """ lk.loga('compiling', ospath.basename(src_file)) if self._liscense == 'trial': # The limitation of content size is 32768 bytes (i.e. 32KB) in # pyarmor trial version. if (size := ospath.getsize(src_file)) > 32768: lk.logt( '[W0357]', f'该文件: "{src_file}" 的体积 ({size}) 超出了 pyarmor 试用' f'版的限制, 请购买个人版或商业版许可后重新编译! (本文件谨以' f'源码形式打包)') copyfile(src_file, dst_file) return
def __init__(self, pyml_text: str): self._keyx = 0 # keyx: mask holder key index self._mask = {} # type: Hint.MaskHolder self._conflicts = re.compile( r'{mask_holder_\d+}|{mask_holder_conflict}' ).findall(pyml_text) if self._conflicts: lk.loga('Found {} conflicts'.format(len(self._conflicts))) self._holder_pattern = re.compile(r'{mask_holder_\d+}') self._text = self._holder_pattern.sub( '{mask_holder_conflict}', pyml_text )
def plain_text(self): """ E.g. # origin_text = 'a = "x and y" \\ \n "and z"' self._text = 'a = {mask2}' self._mask = { 'mask1': '\\ \n', 'mask2': '"x and y" {mask1} "and z"', } -> result = 'a = "x and y" \\ \n "and z"' # assert result == origin_text """ pattern = self._holder_pattern text = self._text _error_stack = [] while pattern.search(text): _error_stack.append('---------------- ERROR STACK ----------------') _error_stack.append(text) try: for holder in set(pattern.findall(text)): text = text.replace( holder, self._mask[holder[1:-1]] # `holder[1:-1]` means `holder.strip("{}")` ) except Exception as e: from lk_utils import read_and_write read_and_write.dumps( _error_stack, f1 := './pyml_composer_error.txt' ) read_and_write.dumps( self._mask, f2 := './pyml_composer_error.json' ) raise Exception( e, f'Plese check dumped info from [{f1}] and [{f2}] for ' f'more infomation.' ) else: if text == self._text: lk.loga('No mask node found') for holder in self._conflicts: text = text.replace('{mask_holder_conflict}', holder, 1) del _error_stack return text
def main(conf: TConf): """ Create dist tree (all empty folders under dist root) """ _precheck(conf) # create build_dir, lib_dir, src_dir mkdir(conf['build']['dist_dir']) mkdir(conf['build']['dist_dir'] + '/build') mkdir(conf['build']['dist_dir'] + '/lib') mkdir(conf['build']['dist_dir'] + '/src') dist_tree = DistTree() """ Add to source dirs: 对相对路径敏感的目录需要保持原有的目录结构关系. 它们需要 被加入到 source dirs. 如下所列 (见加号标识): pyproject build + proj_dir target + file + attachments (partial, which value not includes 'dist:') Do not add to source dirs (见减号标识): pyproject build - dist_dir - icon - readme - attachments (partial, which value includes 'dist:') - module_paths """ dist_tree.add_src_dirs( conf['build']['proj_dir'], conf['build']['target']['file'], *(k for k, v in conf['build']['attachments'].items() if 'dist:' not in v), # *(v for v in conf['build']['module_paths'] # if not v.startswith('dist:')) ) src_root = dist_tree.suggest_src_root() lk.loga(src_root) dst_root = conf['build']['dist_dir'] dist_tree.build_dst_dirs(src_root, f'{dst_root}/src') from .global_dirs import init_global_dirs init_global_dirs(src_root, f'{dst_root}/src') return src_root, dst_root
def _recurse(tree: Hint.SourceTree): for node in tree.values(): if node['field'] in pseudo_fields: _recurse(node['children']) continue for match in pattern.finditer(node['line_stripped']): prop, oper, expr = \ match.group(1), match.group(2), match.group(3) lk.loga('{:15}\t{:^5}\t{:<}'.format(prop, oper, expr)) # ^A--^ ^B--^ ^C-^ # A: 右对齐, 宽度 15; B: 居中, 宽度 5; C: 左对齐, 宽度不限 if expr: """ 说明遇到了这类情况 (示例): width: height + 10 根据 pyml 语法要求, 单行的属性赋值, 不可以有子语法块, 也 就是说下面的情况是不允许的: width: height + 10 if height + 10 > 10: return 10 else: return height """ assert bool(node['children']) is False else: """ 说明遇到了这类情况 (示例): width: if height > 10: return 10 else: return height 其中 prop = 'width', oper = ':', expr 捕获到的是 '', 但 其实应该取它的块结构. 所以下面我们就做这个工作. """ def _recurse_expr_block(tree: Hint.SourceTree): nonlocal expr for node in tree.values(): expr += node['line'] + '\n' _recurse_expr_block(node['children']) _recurse_expr_block(node['children']) x = out.setdefault(node['context']['self'], {}) x[prop] = (oper, expr)
def target_file_exists(self, ofile: str): """ FIXME: 关于不同操作系统之间的困扰: 对于 Windows 系统, QML 的文件对话框返回的是: file:///d:/A/B/C 我们去掉 'file:///', 就能判断文件已存在; 对于 macOS 系统, QML 的文件对话框返回的是: file:///Users/A/B/C 如果去掉 'file:///', macOS 判断 'Users/A/B/C' 是不存在的, 而 '/Users/A/B/C' 才是存在的. 换句话说, 前者要求我们去掉 'file:///', 后者要求我们去掉 'file://', 该怎么 统一操作呢? """ lk.loga(ofile) return exists(ofile) and isfile(ofile)
def main(pyproj_file: str) -> TConf: """ Args: pyproj_file: see template at `./template/pyproject.json` References: docs/pyproject-template.md docs/devnote/difference-between-roots.md > h2:pyproj_root """ pyproj_root = pretty_path(ospath.dirname(pyproj_file)) lk.loga(pyproj_root) conf = loads(pyproj_file) # type: TConf conf = reformat_paths(conf, PathFormatter(pyproj_root)) return conf
def _register_method(self, method, name, narg): class_name = method.__qualname__.split('.')[-2] ''' e.g. class AAA: def mmm(self): pass class BBB: def nnn(self): pass print(AAA.mmm.__qualname__) # -> 'AAA.mmm' print(BBB.nnn.__qualname__) # -> 'AAA.BBB.nnn' don't use `class_name = method.__class__.__name__`, its value is always 'function'. ''' method_name = method.__name__ lk.loga(class_name, method_name) self._pyclass_holder[class_name][method_name] = (name, narg)
def main(file_i, file_o): """ Args: file_i: '~/blueprint/resources/no2_all_qml_types.html'. 该文件被我事先从 "{YourQtProgram}/Docs/Qt-{version}/qtdoc/qmltypes.html" 拷贝过来. file_o: 生成文件. "~/blueprint/resources/no3_all_qml_types.json" {module_group: {module: {type_name: path}, ...}, ...} # {模组: {模块: {类型: 路径}}} e.g. { 'qtquick': { 'qtquick': { 'Rectangle': 'qtquick/qml-qtquick-rectangle.html', 'Text': 'qtquick/qml-qtquick-text.html', ... }, 'qtquick-window': { 'Window': 'qtquick/qml-qtquick-window-window.html', ... }, ... }, ... } 思路: 1. 我们安装了 Qt 主程序以后, 在软件安装目录下的 'Docs/Qt-{version}' 中有 它的 API 文档 2. 其中 "~/Docs/Qt-{version}/qtdoc/qmltypes.html" 列出了全部的 qml types 3. 我们对 "qmltypes.html" 用 BeautifulSoup 解析, 从中获取每个 qml types 和它的链接, 最终我们将得到这些信息: 模组, 模块, 类型, 路径等 4. 将这些信息保存到本项目下的 "~/resources/qmltypes.json" 文件中 """ soup = BeautifulSoup(read_and_write.read_file(file_i), 'html.parser') # https://www.itranslater.com/qa/details/2325827141935563776 data = defaultdict(lambda: defaultdict(dict)) # {module_group: {module: {type_name: filename, ...}, ...}, ...} container = soup.find('div', 'flowListDiv') for e in container.find_all('dd'): link = e.a['href'] # type: str # e.g. "../qtdatavisualization/qml-qtdatavisualization- # abstract3dseries.html" match = re.search(r'\.\./(\w+)/([-\w]+)\.html', link) # | ^-1-^ ^--2---^ | # ^-------- group(0) -------^ # match.group(0): '../qtdatavisualization/qml-qtdatavisualization # -abstract3dseries.html' # match.group(1): 'qtdatavisualization' # match.group(2): 'qml-qtdatavisualization-abstract3dseries' assert match, e module_group = match.group(1) module = match.group(2) # see `blueprint/qml_modules_indexing/no1_all_qml_modules.py:comments # :针对 QtQuick Controls 的处理` if module_group == 'qtquickcontrols1': continue if 'qtquick-controls2' in module: # e.g. 'qml-qtquick-controls2-label' module = module.replace('controls2', 'controls') path = match.group(0).lstrip('../') # -> 'qtdatavisualization/qml-qtdatavisualization-abstract3dseries # .html' module_group = _correct_module_lettercase(module_group) # 'qtdatavisualization' -> 'QtDataVisualization' module = _correct_module_lettercase('-'.join(module.split('-')[1:-1])) # eg1: 'qml-qtdatavisualization-abstract3dseries' -> ['qml', # 'qtdatavisualization', 'abstract3dseries'] -> [ # 'qtdatavisualization'] -> 'qtdatavisualization' # -> 'QtDataVisualization' # eg2: 'qml-qt3d-input-abstractactioninput' -> ['qml', 'qt3d', # 'input', 'abstractactioninput'] -> ['qt3d', 'input', # 'abstractactioninput'] -> 'qt3d-input' -> 'Qt3D.Input' # 注: 为什么要舍去末尾的元素? 因为末尾的那个是 `type_name`, 不是 # `module`. 接下来我们会抽取 `type_name`. type_name = e.text.split(':', 1)[0] # 注意我们不使用 `correct_module_lettercase(match.group(2).split('-') # [-1])`, 是因为 `correct_module_lettercase` 的词库范围比较小, 仅对 # `module_group` 和 `module` 做了覆盖, 不能保证对 `type_name` 的处理正 # 确; 而 `soup` 是可以比较轻松地通过 tag 提取到它的, 所以通过 html 元 # 素获取. # e.g. 'RadioButton: QtQuickControls' -> 'RadioButton' lk.loga(module_group, module, type_name) data[module_group][module][type_name] = path read_and_write.dumps(data, file_o)