pyside-cli 0.1.1__tar.gz
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.
- pyside_cli-0.1.1/PKG-INFO +39 -0
- pyside_cli-0.1.1/README.md +28 -0
- pyside_cli-0.1.1/cli/__init__.py +0 -0
- pyside_cli-0.1.1/cli/__main__.py +86 -0
- pyside_cli-0.1.1/cli/args.py +37 -0
- pyside_cli-0.1.1/cli/builder/__init__.py +0 -0
- pyside_cli-0.1.1/cli/builder/build.py +24 -0
- pyside_cli-0.1.1/cli/builder/nuitka.py +44 -0
- pyside_cli-0.1.1/cli/builder/qt.py +193 -0
- pyside_cli-0.1.1/cli/cache.py +24 -0
- pyside_cli-0.1.1/cli/create.py +46 -0
- pyside_cli-0.1.1/cli/git.py +15 -0
- pyside_cli-0.1.1/cli/glob.py +42 -0
- pyside_cli-0.1.1/cli/pyproject.py +31 -0
- pyside_cli-0.1.1/cli/pytest.py +8 -0
- pyside_cli-0.1.1/pyproject.toml +19 -0
- pyside_cli-0.1.1/pyside_cli.egg-info/PKG-INFO +39 -0
- pyside_cli-0.1.1/pyside_cli.egg-info/SOURCES.txt +21 -0
- pyside_cli-0.1.1/pyside_cli.egg-info/dependency_links.txt +1 -0
- pyside_cli-0.1.1/pyside_cli.egg-info/entry_points.txt +2 -0
- pyside_cli-0.1.1/pyside_cli.egg-info/requires.txt +4 -0
- pyside_cli-0.1.1/pyside_cli.egg-info/top_level.txt +1 -0
- pyside_cli-0.1.1/setup.cfg +4 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: pyside-cli
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Requires-Python: >=3.8
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: glom>=24.11.0
|
|
8
|
+
Requires-Dist: nuitka>=2.7.16
|
|
9
|
+
Requires-Dist: pyside6>=6.6.3.1
|
|
10
|
+
Requires-Dist: toml>=0.10.2
|
|
11
|
+
|
|
12
|
+
# CLI for PySide Template
|
|
13
|
+
|
|
14
|
+
## Quick Overview
|
|
15
|
+
|
|
16
|
+
This is a companion CLI for **pyside\_template** (not an official PySide tool).
|
|
17
|
+
|
|
18
|
+
It helps you quickly create a template project:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
mkdir app && cd app
|
|
22
|
+
pip install pyside-cli
|
|
23
|
+
pys-cli --create .
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
You can also build the project or run tests with a single command.
|
|
27
|
+
Note: running tests requires **pytest**.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pys-cli --all --onefile # for build
|
|
31
|
+
pys-cli --test # for testing
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Links
|
|
35
|
+
|
|
36
|
+
- [PyPI - pyside-cli](https://pypi.org/project/pyside-cli/)
|
|
37
|
+
|
|
38
|
+
- [pyside\_template](https://github.com/SHIINASAMA/pyside_template)
|
|
39
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# CLI for PySide Template
|
|
2
|
+
|
|
3
|
+
## Quick Overview
|
|
4
|
+
|
|
5
|
+
This is a companion CLI for **pyside\_template** (not an official PySide tool).
|
|
6
|
+
|
|
7
|
+
It helps you quickly create a template project:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
mkdir app && cd app
|
|
11
|
+
pip install pyside-cli
|
|
12
|
+
pys-cli --create .
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
You can also build the project or run tests with a single command.
|
|
16
|
+
Note: running tests requires **pytest**.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pys-cli --all --onefile # for build
|
|
20
|
+
pys-cli --test # for testing
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Links
|
|
24
|
+
|
|
25
|
+
- [PyPI - pyside-cli](https://pypi.org/project/pyside-cli/)
|
|
26
|
+
|
|
27
|
+
- [pyside\_template](https://github.com/SHIINASAMA/pyside_template)
|
|
28
|
+
|
|
File without changes
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from cli.args import get_parser
|
|
6
|
+
from cli.builder.build import gen_version_py
|
|
7
|
+
from cli.builder.nuitka import build
|
|
8
|
+
from cli.builder.qt import build_i18n_ts, build_ui, build_i18n, build_assets, gen_init_py
|
|
9
|
+
from cli.cache import load_cache, save_cache
|
|
10
|
+
from cli.create import create
|
|
11
|
+
from cli.git import get_last_tag
|
|
12
|
+
from cli.glob import glob_files
|
|
13
|
+
from cli.pyproject import load_pyproject
|
|
14
|
+
from cli.pytest import run_test
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def main():
|
|
18
|
+
source_list = []
|
|
19
|
+
ui_list = []
|
|
20
|
+
asset_list = []
|
|
21
|
+
i18n_list = []
|
|
22
|
+
lang_list = []
|
|
23
|
+
cache = {}
|
|
24
|
+
opt_from_toml = ""
|
|
25
|
+
|
|
26
|
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
27
|
+
|
|
28
|
+
args = get_parser().parse_args()
|
|
29
|
+
if args.backend_args and args.backend_args[0] == "--":
|
|
30
|
+
args.backend_args = args.backend_args[1:]
|
|
31
|
+
if args.debug:
|
|
32
|
+
logging.info('Debug mode enabled.')
|
|
33
|
+
logging.getLogger().setLevel(logging.DEBUG)
|
|
34
|
+
|
|
35
|
+
if args.create:
|
|
36
|
+
create(args.create)
|
|
37
|
+
sys.exit(0)
|
|
38
|
+
|
|
39
|
+
# check working directory
|
|
40
|
+
# if 'app' is not in the current working directory, exit
|
|
41
|
+
if not os.path.exists('app'):
|
|
42
|
+
logging.error('Please run this script from the project root directory.')
|
|
43
|
+
sys.exit(1)
|
|
44
|
+
|
|
45
|
+
if args.test:
|
|
46
|
+
code = run_test(args)
|
|
47
|
+
sys.exit(code)
|
|
48
|
+
else:
|
|
49
|
+
(asset_list,
|
|
50
|
+
i18n_list,
|
|
51
|
+
source_list,
|
|
52
|
+
ui_list) = glob_files()
|
|
53
|
+
|
|
54
|
+
(opt_from_toml, lang_list) = load_pyproject()
|
|
55
|
+
|
|
56
|
+
if not args.no_cache:
|
|
57
|
+
cache = load_cache()
|
|
58
|
+
if args.i18n:
|
|
59
|
+
build_i18n_ts(
|
|
60
|
+
lang_list=lang_list,
|
|
61
|
+
cache=cache,
|
|
62
|
+
files_to_scan=[str(f) for f in ui_list + source_list]
|
|
63
|
+
)
|
|
64
|
+
if args.rc or args.all:
|
|
65
|
+
build_ui(
|
|
66
|
+
ui_list=ui_list,
|
|
67
|
+
cache=cache
|
|
68
|
+
)
|
|
69
|
+
build_i18n(
|
|
70
|
+
i18n_list=i18n_list,
|
|
71
|
+
cache=cache
|
|
72
|
+
)
|
|
73
|
+
build_assets(
|
|
74
|
+
asset_list=asset_list,
|
|
75
|
+
cache=cache,
|
|
76
|
+
no_cache=args.no_cache
|
|
77
|
+
)
|
|
78
|
+
gen_version_py(get_last_tag())
|
|
79
|
+
gen_init_py()
|
|
80
|
+
save_cache(cache)
|
|
81
|
+
if args.build or args.all:
|
|
82
|
+
build(args, opt_from_toml)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
if __name__ == '__main__':
|
|
86
|
+
main()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_parser():
|
|
5
|
+
"""parse command line arguments"""
|
|
6
|
+
# --help: show this help message
|
|
7
|
+
parser = argparse.ArgumentParser(description='Test and build your app.')
|
|
8
|
+
# --rc: only convert resources files to python files
|
|
9
|
+
# --build: only build the app
|
|
10
|
+
# --all: convert resources files and build the app
|
|
11
|
+
# --test: run test code
|
|
12
|
+
# --init: create new project
|
|
13
|
+
mode_group = parser.add_mutually_exclusive_group(required=True)
|
|
14
|
+
mode_group.add_argument('--all', action='store_true', help='Convert rc files and build the app')
|
|
15
|
+
mode_group.add_argument('--build', action='store_true', help='Build the app')
|
|
16
|
+
mode_group.add_argument('--create', type=str, metavar='NAME', help='Create your project with name')
|
|
17
|
+
mode_group.add_argument('--i18n', action='store_true',
|
|
18
|
+
help='Generate translation files (.ts) for all languages')
|
|
19
|
+
mode_group.add_argument('--rc', action='store_true', help='Convert rc files to python files')
|
|
20
|
+
mode_group.add_argument('--test', action='store_true', help='Run test')
|
|
21
|
+
# --onefile: create a single executable file
|
|
22
|
+
# --onedir: create a directory with the executable and all dependencies
|
|
23
|
+
package_format_group = parser.add_mutually_exclusive_group()
|
|
24
|
+
package_format_group.add_argument('--onefile', action='store_true',
|
|
25
|
+
help='(for build) Create a single executable file')
|
|
26
|
+
package_format_group.add_argument('--onedir', action='store_true',
|
|
27
|
+
help='(for build) Create a directory with the executable and all dependencies')
|
|
28
|
+
|
|
29
|
+
parser.add_argument('--no-cache', action='store_true', help='Ignore existing caches', required=False)
|
|
30
|
+
|
|
31
|
+
parser.add_argument('--debug', action='store_true',
|
|
32
|
+
help='Enable debug mode, which will output more information during the build process')
|
|
33
|
+
|
|
34
|
+
parser.add_argument('backend_args', nargs=argparse.REMAINDER,
|
|
35
|
+
help='Additional arguments for the build backend, e.g. -- --xxx=xxx')
|
|
36
|
+
|
|
37
|
+
return parser
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def gen_version_py(version):
|
|
6
|
+
with open('app/resources/version.py', 'w', encoding='utf-8') as f:
|
|
7
|
+
f.write(f'__version__ = "{version}"\n')
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def gen_filelist(root_dir: str, filelist_name: str):
|
|
11
|
+
paths = []
|
|
12
|
+
for current_path, dirs, files in os.walk(root_dir, topdown=False):
|
|
13
|
+
for file in files:
|
|
14
|
+
relative_path = os.path.relpath(os.path.join(current_path, file), root_dir)
|
|
15
|
+
logging.debug(relative_path)
|
|
16
|
+
paths.append(relative_path)
|
|
17
|
+
relative_path = os.path.relpath(os.path.join(current_path, ""), root_dir)
|
|
18
|
+
if relative_path != ".":
|
|
19
|
+
logging.debug(relative_path)
|
|
20
|
+
paths.append(relative_path)
|
|
21
|
+
|
|
22
|
+
with open(filelist_name, "w", encoding="utf-8") as f:
|
|
23
|
+
f.write("\n".join(paths))
|
|
24
|
+
f.write("\n")
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import shutil
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from cli.builder.build import gen_filelist
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def build(args, opt_from_toml):
|
|
12
|
+
# call nuitka to build the app
|
|
13
|
+
# include all files in app package and exclude the ui files
|
|
14
|
+
if sys.platform != 'win32':
|
|
15
|
+
path = Path('build/App')
|
|
16
|
+
if path.exists() and path.is_dir():
|
|
17
|
+
shutil.rmtree(path)
|
|
18
|
+
elif path.exists() and path.is_file():
|
|
19
|
+
path.unlink()
|
|
20
|
+
start = time.perf_counter()
|
|
21
|
+
logging.info('Building the app...')
|
|
22
|
+
cmd = ('nuitka '
|
|
23
|
+
'--output-dir=build '
|
|
24
|
+
'--output-filename="App" '
|
|
25
|
+
'app/__main__.py '
|
|
26
|
+
+ '--jobs={} '.format(os.cpu_count())
|
|
27
|
+
+ ('--onefile ' if args.onefile else '--standalone ')
|
|
28
|
+
+ opt_from_toml
|
|
29
|
+
+ (" ".join(args.backend_args)))
|
|
30
|
+
logging.debug(cmd)
|
|
31
|
+
rt = os.system(cmd)
|
|
32
|
+
end = time.perf_counter()
|
|
33
|
+
if rt == 0:
|
|
34
|
+
logging.info(f'Build complete in {end - start:.3f}s.')
|
|
35
|
+
if not args.onefile:
|
|
36
|
+
if os.path.exists('build/App'):
|
|
37
|
+
shutil.rmtree('build/App')
|
|
38
|
+
shutil.move('build/__main__.dist', 'build/App')
|
|
39
|
+
logging.info("Generate the filelist.")
|
|
40
|
+
gen_filelist('build/App', 'build/App/filelist.txt')
|
|
41
|
+
logging.info("Filelist has been generated.")
|
|
42
|
+
else:
|
|
43
|
+
logging.error(f'Failed to build app in {end - start:.3f}s.')
|
|
44
|
+
exit(1)
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def build_ui(ui_list, cache):
|
|
7
|
+
"""Compile *.ui files into Python files using pyside6-uic, preserving directory structure."""
|
|
8
|
+
ui_dir = Path("app/ui")
|
|
9
|
+
res_dir = Path("app/resources")
|
|
10
|
+
|
|
11
|
+
if not ui_list:
|
|
12
|
+
logging.info("No ui files found, skipping ui conversion.")
|
|
13
|
+
return
|
|
14
|
+
|
|
15
|
+
res_dir.mkdir(parents=True, exist_ok=True)
|
|
16
|
+
logging.info("Converting ui files to Python files...")
|
|
17
|
+
ui_cache = cache.get("ui", {})
|
|
18
|
+
|
|
19
|
+
for input_file in ui_list:
|
|
20
|
+
try:
|
|
21
|
+
rel_path = input_file.parent.relative_to(ui_dir)
|
|
22
|
+
except ValueError:
|
|
23
|
+
# input_file is not under app/ui, skip it
|
|
24
|
+
continue
|
|
25
|
+
|
|
26
|
+
output_dir = res_dir / rel_path
|
|
27
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
28
|
+
|
|
29
|
+
output_file = output_dir / (input_file.stem + "_ui.py")
|
|
30
|
+
|
|
31
|
+
# Check cache to avoid unnecessary recompilation
|
|
32
|
+
mtime = input_file.stat().st_mtime
|
|
33
|
+
if str(input_file) in ui_cache and ui_cache[str(input_file)] == mtime:
|
|
34
|
+
logging.info(f"{input_file} is up to date.")
|
|
35
|
+
continue
|
|
36
|
+
|
|
37
|
+
ui_cache[str(input_file)] = mtime
|
|
38
|
+
|
|
39
|
+
# Run pyside6-uic
|
|
40
|
+
cmd = f'pyside6-uic "{input_file}" -o "{output_file}"'
|
|
41
|
+
if 0 != os.system(cmd):
|
|
42
|
+
logging.error(f"Failed to convert {input_file}.")
|
|
43
|
+
exit(1)
|
|
44
|
+
|
|
45
|
+
logging.info(f"Converted {input_file} to {output_file}")
|
|
46
|
+
|
|
47
|
+
cache["ui"] = ui_cache
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def build_assets(asset_list, cache, no_cache=False):
|
|
51
|
+
"""Generate assets.qrc from files in app/assets and compile it with pyside6-rcc."""
|
|
52
|
+
assets_dir = Path('app/assets')
|
|
53
|
+
res_dir = Path('app/resources')
|
|
54
|
+
qrc_file = res_dir / 'assets.qrc'
|
|
55
|
+
py_res_file = res_dir / 'resource.py'
|
|
56
|
+
|
|
57
|
+
# Skip if assets directory does not exist
|
|
58
|
+
if not os.path.exists(assets_dir):
|
|
59
|
+
logging.info('No assets folder found, skipping assets conversion.')
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
if not os.path.exists(res_dir):
|
|
63
|
+
os.makedirs(res_dir)
|
|
64
|
+
|
|
65
|
+
logging.info('Converting assets to Python resources...')
|
|
66
|
+
|
|
67
|
+
assets_cache = cache.get('assets', {})
|
|
68
|
+
need_rebuild = False
|
|
69
|
+
for asset in asset_list:
|
|
70
|
+
mtime = asset.stat().st_mtime
|
|
71
|
+
asset_key = str(asset)
|
|
72
|
+
if asset_key in assets_cache and assets_cache[asset_key] == mtime:
|
|
73
|
+
logging.info(f'{asset} is up to date.')
|
|
74
|
+
continue
|
|
75
|
+
assets_cache[asset_key] = mtime
|
|
76
|
+
logging.info(f'{asset} is outdated.')
|
|
77
|
+
need_rebuild = True
|
|
78
|
+
|
|
79
|
+
# Force rebuild if cache is disabled
|
|
80
|
+
if no_cache:
|
|
81
|
+
need_rebuild = True
|
|
82
|
+
|
|
83
|
+
if need_rebuild:
|
|
84
|
+
# Generate assets.qrc dynamically
|
|
85
|
+
with open(qrc_file, 'w', encoding='utf-8') as f:
|
|
86
|
+
f.write('<!DOCTYPE RCC>\n')
|
|
87
|
+
f.write('<RCC version="1.0">\n')
|
|
88
|
+
f.write(' <qresource>\n')
|
|
89
|
+
for asset in asset_list:
|
|
90
|
+
posix_path = asset.as_posix()
|
|
91
|
+
# remove the leading "app/assets/" from the path
|
|
92
|
+
alias = posix_path[len('app/assets/'):]
|
|
93
|
+
# rel_path is the path relative to app/resources
|
|
94
|
+
rel_path = os.path.relpath(asset, res_dir)
|
|
95
|
+
f.write(f' <file alias="{alias}">{rel_path}</file>\n')
|
|
96
|
+
f.write(' </qresource>\n')
|
|
97
|
+
f.write('</RCC>\n')
|
|
98
|
+
logging.info(f'Generated {qrc_file}.')
|
|
99
|
+
|
|
100
|
+
# Compile qrc file to Python resource
|
|
101
|
+
if 0 != os.system(f'pyside6-rcc {qrc_file} -o {py_res_file}'):
|
|
102
|
+
logging.error('Failed to convert assets.qrc.')
|
|
103
|
+
exit(1)
|
|
104
|
+
logging.info(f'Converted {qrc_file} to {py_res_file}.')
|
|
105
|
+
else:
|
|
106
|
+
logging.info('Assets are up to date.')
|
|
107
|
+
|
|
108
|
+
cache['assets'] = assets_cache
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def build_i18n_ts(lang_list, files_to_scan, cache):
|
|
112
|
+
"""
|
|
113
|
+
Generate translation (.ts) files for all languages in lang_list
|
|
114
|
+
by scanning self.ui_list and self.source_list using pyside6-lupdate.
|
|
115
|
+
"""
|
|
116
|
+
i18n_dir = Path("app/i18n")
|
|
117
|
+
i18n_dir.mkdir(parents=True, exist_ok=True)
|
|
118
|
+
|
|
119
|
+
logging.info("Generating translation (.ts) files for languages: %s", ', '.join(lang_list))
|
|
120
|
+
|
|
121
|
+
i18n_cache = cache.get("i18n", {})
|
|
122
|
+
|
|
123
|
+
for lang in lang_list:
|
|
124
|
+
ts_file = i18n_dir / f"{lang}.ts"
|
|
125
|
+
logging.info("Generating %s ...", ts_file)
|
|
126
|
+
|
|
127
|
+
files_str = " ".join(f'"{f}"' for f in files_to_scan)
|
|
128
|
+
cmd = f'pyside6-lupdate -silent -locations absolute -extensions ui {files_str} -ts "{ts_file}"'
|
|
129
|
+
|
|
130
|
+
if 0 != os.system(cmd):
|
|
131
|
+
logging.error("Failed to generate translation file: %s", ts_file)
|
|
132
|
+
exit(1)
|
|
133
|
+
|
|
134
|
+
i18n_cache[lang] = ts_file.stat().st_mtime
|
|
135
|
+
logging.info("Generated translation file: %s", ts_file)
|
|
136
|
+
|
|
137
|
+
cache["i18n"] = i18n_cache
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def build_i18n(i18n_list, cache):
|
|
141
|
+
"""
|
|
142
|
+
Compile .ts translation files into .qm files under app/assets/i18n/.
|
|
143
|
+
Only regenerate .qm if the corresponding .ts file has changed.
|
|
144
|
+
"""
|
|
145
|
+
qm_root = Path("app/assets/i18n")
|
|
146
|
+
qm_root.mkdir(parents=True, exist_ok=True)
|
|
147
|
+
|
|
148
|
+
logging.info("Compiling translation files...")
|
|
149
|
+
|
|
150
|
+
# Get cache for i18n
|
|
151
|
+
i18n_cache = cache.get("i18n", {})
|
|
152
|
+
|
|
153
|
+
for ts_file in i18n_list:
|
|
154
|
+
try:
|
|
155
|
+
ts_file = Path(ts_file)
|
|
156
|
+
except Exception:
|
|
157
|
+
continue
|
|
158
|
+
|
|
159
|
+
qm_file = qm_root / (ts_file.stem + ".qm")
|
|
160
|
+
|
|
161
|
+
# Check modification time cache
|
|
162
|
+
ts_mtime = ts_file.stat().st_mtime
|
|
163
|
+
if str(ts_file) in i18n_cache and i18n_cache[str(ts_file)] == ts_mtime:
|
|
164
|
+
logging.info("%s is up to date.", ts_file)
|
|
165
|
+
continue
|
|
166
|
+
|
|
167
|
+
logging.info("Compiling %s to %s", ts_file, qm_file)
|
|
168
|
+
|
|
169
|
+
# Run pyside6-lrelease to compile ts -> qm
|
|
170
|
+
cmd = f'pyside6-lrelease "{ts_file}" -qm "{qm_file}"'
|
|
171
|
+
if 0 != os.system(cmd):
|
|
172
|
+
logging.error("Failed to compile translation file: %s", ts_file)
|
|
173
|
+
exit(1)
|
|
174
|
+
|
|
175
|
+
logging.info("Compiled %s", qm_file)
|
|
176
|
+
|
|
177
|
+
# Update cache
|
|
178
|
+
i18n_cache[str(ts_file)] = ts_mtime
|
|
179
|
+
|
|
180
|
+
cache["i18n"] = i18n_cache
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def gen_init_py():
|
|
184
|
+
"""Create __init__.py in every subdirectory if not exists"""
|
|
185
|
+
root = Path("app/resources")
|
|
186
|
+
init_file = root / "__init__.py"
|
|
187
|
+
if not init_file.exists():
|
|
188
|
+
init_file.touch()
|
|
189
|
+
for path in root.rglob("*"):
|
|
190
|
+
if path.is_dir():
|
|
191
|
+
init_file = path / "__init__.py"
|
|
192
|
+
if not init_file.exists():
|
|
193
|
+
init_file.touch()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def load_cache():
|
|
7
|
+
"""load cache from .cache/assets.json"""
|
|
8
|
+
cache = {}
|
|
9
|
+
if not os.path.exists('.cache'):
|
|
10
|
+
os.makedirs('.cache')
|
|
11
|
+
if os.path.exists('.cache/assets.json'):
|
|
12
|
+
logging.info('Cache found.')
|
|
13
|
+
with open('.cache/assets.json', 'r') as f:
|
|
14
|
+
cache = json.load(f)
|
|
15
|
+
if not cache:
|
|
16
|
+
logging.info('No cache found.')
|
|
17
|
+
return cache
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def save_cache(cache):
|
|
21
|
+
# save cache
|
|
22
|
+
with open('.cache/assets.json', 'w') as f:
|
|
23
|
+
json.dump(cache, f, indent=4)
|
|
24
|
+
logging.info('Cache saved.')
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import shutil
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import toml
|
|
7
|
+
from glom import assign
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def create(name: str):
|
|
11
|
+
dst = name
|
|
12
|
+
if name == '.':
|
|
13
|
+
name = Path.cwd().name
|
|
14
|
+
dst = '.'
|
|
15
|
+
|
|
16
|
+
logging.info(f"Creating ...")
|
|
17
|
+
|
|
18
|
+
rt = subprocess.run([
|
|
19
|
+
'git',
|
|
20
|
+
'clone',
|
|
21
|
+
'https://github.com/SHIINASAMA/pyside_template.git',
|
|
22
|
+
dst
|
|
23
|
+
])
|
|
24
|
+
if rt.returncode:
|
|
25
|
+
logging.error('Failed to clone template')
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
project_path = Path(dst)
|
|
29
|
+
pyproject_file = project_path / 'pyproject.toml'
|
|
30
|
+
data = toml.load(pyproject_file)
|
|
31
|
+
assign(data, 'project.name', name)
|
|
32
|
+
with pyproject_file.open('w', encoding='utf-8') as f:
|
|
33
|
+
toml.dump(data, f)
|
|
34
|
+
|
|
35
|
+
git_dir = project_path / '.git'
|
|
36
|
+
shutil.rmtree(git_dir)
|
|
37
|
+
|
|
38
|
+
subprocess.run(
|
|
39
|
+
[
|
|
40
|
+
'git',
|
|
41
|
+
'init'
|
|
42
|
+
],
|
|
43
|
+
cwd=project_path
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
logging.info(f"Project {name} created successfully.")
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_last_tag(default="0.0.0.0") -> str:
|
|
5
|
+
"""Get the last git tag as version, or return default if not found."""
|
|
6
|
+
try:
|
|
7
|
+
tag = subprocess.check_output(
|
|
8
|
+
["git", "describe", "--tags", "--abbrev=0", "--first-parent"],
|
|
9
|
+
stderr=subprocess.DEVNULL,
|
|
10
|
+
text=True
|
|
11
|
+
).strip()
|
|
12
|
+
except subprocess.CalledProcessError:
|
|
13
|
+
return default
|
|
14
|
+
|
|
15
|
+
return tag if tag else default
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def glob_files():
|
|
7
|
+
root = Path("app")
|
|
8
|
+
assets_dir = root / "assets"
|
|
9
|
+
i18n_dir = root / "i18n"
|
|
10
|
+
exclude_dirs = [
|
|
11
|
+
root / "resources",
|
|
12
|
+
root / "test"
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
asset_list = []
|
|
16
|
+
i18n_list = []
|
|
17
|
+
source_list = []
|
|
18
|
+
ui_list = []
|
|
19
|
+
|
|
20
|
+
for path in root.rglob("*"):
|
|
21
|
+
if any(ex in path.parents for ex in exclude_dirs):
|
|
22
|
+
continue
|
|
23
|
+
|
|
24
|
+
if assets_dir in path.parents and os.path.isfile(path):
|
|
25
|
+
asset_list.append(path)
|
|
26
|
+
continue
|
|
27
|
+
|
|
28
|
+
if i18n_dir in path.parents and os.path.isfile(path):
|
|
29
|
+
i18n_list.append(path)
|
|
30
|
+
continue
|
|
31
|
+
|
|
32
|
+
if path.suffix == ".py":
|
|
33
|
+
source_list.append(path)
|
|
34
|
+
elif path.suffix == ".ui":
|
|
35
|
+
ui_list.append(path)
|
|
36
|
+
|
|
37
|
+
logging.debug("Source list: %s", [str(x) for x in source_list])
|
|
38
|
+
logging.debug("UI list: %s", [str(x) for x in ui_list])
|
|
39
|
+
logging.debug("Asset list: %s", [str(x) for x in asset_list])
|
|
40
|
+
logging.debug("I18n list: %s", [str(x) for x in i18n_list])
|
|
41
|
+
|
|
42
|
+
return asset_list, i18n_list, source_list, ui_list
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
import glom
|
|
4
|
+
import toml
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def load_pyproject():
|
|
8
|
+
"""get and build vars from pyproject.toml
|
|
9
|
+
1. nuitka command options
|
|
10
|
+
2. enabled languages list"""
|
|
11
|
+
with open("pyproject.toml") as f:
|
|
12
|
+
data = toml.load(f)
|
|
13
|
+
config = glom.glom(data, "tool.build", default={})
|
|
14
|
+
platform_config = glom.glom(data, f"tool.build.{sys.platform}", default={})
|
|
15
|
+
config.update(platform_config)
|
|
16
|
+
|
|
17
|
+
nuitka_cmd = ""
|
|
18
|
+
for k, v in config.items():
|
|
19
|
+
if isinstance(v, list) and v:
|
|
20
|
+
cmd = f"--{k}={','.join(v)} "
|
|
21
|
+
nuitka_cmd += cmd
|
|
22
|
+
if isinstance(v, str) and v != "":
|
|
23
|
+
cmd = f"--{k}={v} "
|
|
24
|
+
nuitka_cmd += cmd
|
|
25
|
+
if type(v) is bool and v:
|
|
26
|
+
cmd = f"--{k} "
|
|
27
|
+
nuitka_cmd += cmd
|
|
28
|
+
|
|
29
|
+
lang_list = glom.glom(data, "tool.build.i18n.languages", default=[])
|
|
30
|
+
|
|
31
|
+
return nuitka_cmd, lang_list
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pyside-cli"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.8"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"glom>=24.11.0",
|
|
9
|
+
"nuitka>=2.7.16",
|
|
10
|
+
"pyside6>=6.6.3.1",
|
|
11
|
+
"toml>=0.10.2",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[project.scripts]
|
|
15
|
+
pyside-cli = "cli.__main__:main"
|
|
16
|
+
|
|
17
|
+
[build-system]
|
|
18
|
+
requires = ["setuptools", "wheel"]
|
|
19
|
+
build-backend = "setuptools.build_meta"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: pyside-cli
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Requires-Python: >=3.8
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: glom>=24.11.0
|
|
8
|
+
Requires-Dist: nuitka>=2.7.16
|
|
9
|
+
Requires-Dist: pyside6>=6.6.3.1
|
|
10
|
+
Requires-Dist: toml>=0.10.2
|
|
11
|
+
|
|
12
|
+
# CLI for PySide Template
|
|
13
|
+
|
|
14
|
+
## Quick Overview
|
|
15
|
+
|
|
16
|
+
This is a companion CLI for **pyside\_template** (not an official PySide tool).
|
|
17
|
+
|
|
18
|
+
It helps you quickly create a template project:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
mkdir app && cd app
|
|
22
|
+
pip install pyside-cli
|
|
23
|
+
pys-cli --create .
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
You can also build the project or run tests with a single command.
|
|
27
|
+
Note: running tests requires **pytest**.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pys-cli --all --onefile # for build
|
|
31
|
+
pys-cli --test # for testing
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Links
|
|
35
|
+
|
|
36
|
+
- [PyPI - pyside-cli](https://pypi.org/project/pyside-cli/)
|
|
37
|
+
|
|
38
|
+
- [pyside\_template](https://github.com/SHIINASAMA/pyside_template)
|
|
39
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
cli/__init__.py
|
|
4
|
+
cli/__main__.py
|
|
5
|
+
cli/args.py
|
|
6
|
+
cli/cache.py
|
|
7
|
+
cli/create.py
|
|
8
|
+
cli/git.py
|
|
9
|
+
cli/glob.py
|
|
10
|
+
cli/pyproject.py
|
|
11
|
+
cli/pytest.py
|
|
12
|
+
cli/builder/__init__.py
|
|
13
|
+
cli/builder/build.py
|
|
14
|
+
cli/builder/nuitka.py
|
|
15
|
+
cli/builder/qt.py
|
|
16
|
+
pyside_cli.egg-info/PKG-INFO
|
|
17
|
+
pyside_cli.egg-info/SOURCES.txt
|
|
18
|
+
pyside_cli.egg-info/dependency_links.txt
|
|
19
|
+
pyside_cli.egg-info/entry_points.txt
|
|
20
|
+
pyside_cli.egg-info/requires.txt
|
|
21
|
+
pyside_cli.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cli
|