Esempio n. 1
0
def static_qml_basic_types(file_i, file_o):
    """ 统计 qml 的基本类型有哪些.
    
    Args:
        file_i: 'blueprint/resources/no5_all_qml_widgets.json'
            structure: {
                module: {
                    qmltype: {
                        'parent': ...,
                        'props': {prop: type, ...}
                    }, ...              ^--^ 我们统计的是这个.
                }, ...
            }
        file_o: *.txt or empty str. the empty string means 'donot dump to file,
            just print it on the console'.
            structure: [type, ...]. 一个去重后的列表, 按照字母表顺序排列.
            
    Outputs:
        data_w: {type: [(module, qmltype, prop), ...], ...}
    """
    data_r = loads(file_i)
    data_w = defaultdict(set)  # type: dict[str, set[tuple[str, str, str]]]

    for k1, v1 in data_r.items():
        for k2, v2 in v1.items():
            for k3, v3 in v2['props'].items():
                # k1: module; k2: qmltype; k3: prop; v3: type
                data_w[v3].add((k1, k2, k3))

    [print(i, k) for i, k in enumerate(sorted(data_w.keys()), 1)]
    if file_o:
        data_w = {k: sorted(data_w[k]) for k in sorted(data_w.keys())}
        dumps(data_w, file_o, pretty_dump=True)
Esempio n. 2
0
 def __init__(self):
     from lk_utils.read_and_write import loads
     self.module_namespace = loads(  # type: Hint.ModuleNameSpace
         '../../data/pyml_import_namespaces.json')
     #   e.g. {'pyml.qtquick': {'Text': {'imp}}}
     self.comp_namespace = {}  # type: Hint.CompNameSpace
     self.imports = []  # DEL
Esempio n. 3
0
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
Esempio n. 4
0
    def __init__(self, python_interpreter=''):
        """
        
        Args:
            python_interpreter: 如果启用了虚拟环境, 则这里传入 `.venv_builder:
                VEnvBuilder:get_embed_python:returns`; 如果没使用虚拟环境, 则留
                空, 它将使用默认 (全局的) python 解释器和 pyarmor. 此时请确保该
                解释器和 pyarmor 都可以在 cmd 中访问 (测试命令: `python
                --version`, `pyarmor --version`).
                参考: https://pyarmor.readthedocs.io/zh/latest/advanced.html
                    子章节: "使用不同版本 Python 加密脚本"
        
        Warnings:
            Currently only supports Windows platform.
        """
        self._liscense = 'trial'
        #   TODO: see https://pyarmor.readthedocs.io/zh/latest/license.html

        if python_interpreter:
            # warnings: `docs/devnote/warnings-about-embed-python.md`
            self._interpreter = python_interpreter.replace('/', '\\')
            # create pyarmor file copy
            origin_file = self._locate_pyarmor_script()
            backup_file = origin_file.removesuffix('.py') + '_copy.py'
            if not ospath.exists(backup_file):
                from lk_utils.read_and_write import loads, dumps
                content = loads(origin_file)
                dumps(
                    '\n'.join([
                        'from os.path import dirname',
                        'from sys import path as syspath',
                        'syspath.append(dirname(__file__))', content
                    ]), backup_file)
            self._pyarmor = backup_file
            self._head = f'"{self._interpreter}" "{self._pyarmor}"'
        else:
            self._interpreter = 'python'
            self._pyarmor = 'pyarmor'
            self._head = 'pyarmor'
Esempio n. 5
0
def _create_launcher(app_name,
                     icon,
                     target,
                     root_dir,
                     pyversion,
                     extend_sys_paths=None,
                     enable_venv=True,
                     enable_console=True,
                     create_launch_bat=True):
    """ Create launcher ({srcdir}/bootloader.py).

    Args:
        app_name (str): application name, this will be used as exe file's name:
            e.g. `app_name = 'Hello World'` -> will generate 'Hello World.exe'
        icon (str): *.ico file
        target (dict): {
            'file': filepath,
            'function': str,
            'args': [...],
            'kwargs': {...}
        }
        root_dir (str):
        pyversion (str):
        extend_sys_paths (list[str]):
        enable_venv (bool):
        enable_console (bool):
        create_launch_bat (bool):
    
    详细说明
        启动器分为两部分, 一个是启动器图标, 一个引导装载程序.
        启动器图标位于: '{root_dir}/{app_name}.exe'
        引导装载程序位于: '{root_dir}/src/bootloader.pyc'
    
        1. 二者的体积都非常小
        2. 启动器本质上是一个带有自定义图标的 bat 脚本. 它指定了 Python 编译器的
           路径和 bootloader 的路径, 通过调用编译器执行 bootloader.pyc
        3. bootloader 主要完成了以下两项工作:
            1. 向 sys.path 中添加当前工作目录和自定义的模块目录
            2. 对主脚本加了一个 try catch 结构, 以便于捕捉主程序报错时的信息, 并
               以系统弹窗的形式给用户. 这样就摆脱了控制台打印的需要, 使我们的软
               件表现得更像是一款软件

    Notes:
        1. 启动器在调用主脚本 (`func:main::args:main_script`) 之前, 会通过
           `os.chdir` 切换到主脚本所在的目录, 这样我们项目源代码中的所有相对路
           径, 相对引用都能正常工作

    References:
        - template/launch_by_system.bat
        - template/launch_by_venv.bat
        - template/bootloader.txt
    
    Returns:
        launch_file: ``{root_dir}/src/{bootloader_name}.py``.

    """
    launcher_name = app_name
    bootloader_name = 'bootloader'

    target_path = target['file']  # type: str
    target_dir = global_dirs.to_dist(ospath.dirname(target_path))
    launch_dir = ospath.dirname(target_dir)
    #   the launcher dir is parent of target dir, i.e. we put the launcher file
    #   in the parent folder of target file's folder.

    # target_reldir: 'target relative directory' (starts from `launch_dir`)
    # PS: it is equivalent to f'{target_dir_name}/{target_file_name}'
    target_reldir = global_dirs.relpath(target_dir, launch_dir)
    target_pkg = target_reldir.replace('/', '.')
    target_name = filesniff.get_filename(target_path, suffix=False)

    extend_sys_paths = list(
        map(
            lambda d: global_dirs.relpath(
                global_dirs.to_dist(d)
                if not d.startswith('dist:') else d.replace(
                    'dist:root', f'{root_dir}'), launch_dir),
            extend_sys_paths))

    template = loads(global_dirs.template('bootloader.txt'))
    code = template.format(
        # see `template/bootloader.txt > docstring:placeholders`
        LIB_RELDIR=global_dirs.relpath(f'{root_dir}/lib', launch_dir),
        SITE_PACKAGES='../venv/site-packages' if enable_venv else '',
        EXTEND_SYS_PATHS=str(extend_sys_paths),
        TARGET_RELDIR=target_reldir,
        TARGET_PKG=target_pkg,
        TARGET_NAME=target_name,
        TARGET_FUNC=target['function'] or '_',
        #   注意这里的 `target['function']`, 它有以下几种情况:
        #       target['function'] = some_str
        #           对应于 `../template/bootloader.txt(后面简称 'bootloader')
        #           ::底部位置::cmt:CASE3`
        #       target['function'] = '*'
        #           对应于 `bootloader::底部位置::cmt:CASE2`
        #       target['function'] = ''
        #           对应于 `bootloader::底部位置::cmt:CASE1`
        #   对于 CASE 1, 也就是 `target['function']` 为空字符串的情况, 我们必须
        #   将其改为其他字符 (这里就用了下划线作替代). 否则, 会导致打包后的
        #   `bootloader.py::底部位置::cmt:CASE3` 语句无法通过 Python 解释器.
        TARGET_ARGS=str(target['args']),
        TARGET_KWARGS=str(target['kwargs']),
    )
    dumps(code, launch_file := f'{launch_dir}/{bootloader_name}.py')

    # --------------------------------------------------------------------------

    # template = loads(global_dirs.template('pytransform.txt'))
    # code = template.format(
    #     LIB_PARENT_RELDIR='../'
    # )
    # dumps(code, f'{root_dir}/src/pytransform.py')

    # --------------------------------------------------------------------------

    if create_launch_bat is False:
        return launch_file

    if enable_venv:  # suggest
        template = loads(global_dirs.template('launch_by_venv.bat'))
    else:
        template = loads(global_dirs.template('launch_by_system.bat'))
    code = template.format(
        PYVERSION=pyversion.replace('.', ''),  # ...|'37'|'38'|'39'|...
        VENV_RELDIR=global_dirs.relpath(f'{root_dir}/venv',
                                        launch_dir).replace('/', '\\'),
        LAUNCHER_RELDIR=global_dirs.relpath(launch_dir,
                                            root_dir).replace('/', '\\'),
        LAUNCHER_NAME=f'{bootloader_name}.py',
    )
    bat_file = f'{root_dir}/{launcher_name}.bat'
    # lk.logt('[D3432]', code)
    dumps(code, bat_file)

    # 这是一个耗时操作 (大约需要 10s), 我们把它放在子线程执行
    def generate_exe(bat_file, exe_file, icon_file, *options):
        from .bat_2_exe import bat_2_exe
        lk.loga('converting bat to exe... '
                'it may take several seconds ~ one minute...')
        bat_2_exe(bat_file, exe_file, icon_file, *options)
        lk.loga('convertion bat-to-exe done')

    global thread
    thread = runnin_new_thread(generate_exe, bat_file,
                               f'{root_dir}/{launcher_name}.exe', icon, '/x64',
                               '' if enable_console else '/invisible')

    return launch_file
Esempio n. 6
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)


# ------------------------------------------------------------------------------

qml_modules = read_and_write.loads('../resources/no2_all_qml_modules.json')
qml_modules = qml_modules['module_group'] | qml_modules['module']  # type: dict
qml_modules.update({  # 扩充
    '': '',
    'qtquick-controls-private': 'QtQuick.Controls.Private',
    'mediaplayer-qml': 'QtMediaPlayer',
    #   注: 这个其实是不存在的, 只是为了不报错所以加上去
})


def _correct_module_lettercase(module: str):
    """ 修正模块的大小写.
    
    示例:
        'qtquick-window' -> 'QtQuick.Window'
        'qtgraphicaleffects' -> 'QtGraphicalEffects