flet-cli 0.81.0.dev7148__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.
- flet_cli-0.81.0.dev7148/PKG-INFO +35 -0
- flet_cli-0.81.0.dev7148/README.md +17 -0
- flet_cli-0.81.0.dev7148/pyproject.toml +28 -0
- flet_cli-0.81.0.dev7148/setup.cfg +4 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/__pyinstaller/__init__.py +5 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/__pyinstaller/config.py +1 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/__pyinstaller/hook-flet.py +9 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/__pyinstaller/macos_utils.py +120 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/__pyinstaller/rthooks/pyi_rth_localhost_fletd.py +9 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/__pyinstaller/utils.py +25 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/__pyinstaller/win_utils.py +109 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/cli.py +119 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/commands/base.py +91 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/commands/build.py +136 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/commands/build_base.py +1952 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/commands/create.py +128 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/commands/debug.py +156 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/commands/devices.py +190 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/commands/doctor.py +39 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/commands/emulators.py +321 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/commands/flutter_base.py +394 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/commands/options.py +28 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/commands/pack.py +328 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/commands/publish.py +328 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/commands/run.py +429 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/commands/serve.py +76 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/utils/android_sdk.py +285 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/utils/distros.py +87 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/utils/flutter.py +72 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/utils/hash_stamp.py +22 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/utils/jdk.py +134 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/utils/merge.py +7 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/utils/processes.py +70 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/utils/project_dependencies.py +96 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/utils/pyproject_toml.py +29 -0
- flet_cli-0.81.0.dev7148/src/flet_cli/version.py +1 -0
- flet_cli-0.81.0.dev7148/src/flet_cli.egg-info/PKG-INFO +35 -0
- flet_cli-0.81.0.dev7148/src/flet_cli.egg-info/SOURCES.txt +40 -0
- flet_cli-0.81.0.dev7148/src/flet_cli.egg-info/dependency_links.txt +1 -0
- flet_cli-0.81.0.dev7148/src/flet_cli.egg-info/entry_points.txt +2 -0
- flet_cli-0.81.0.dev7148/src/flet_cli.egg-info/requires.txt +8 -0
- flet_cli-0.81.0.dev7148/src/flet_cli.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flet-cli
|
|
3
|
+
Version: 0.81.0.dev7148
|
|
4
|
+
Summary: Flet CLI
|
|
5
|
+
Author-email: "Appveyor Systems Inc." <hello@flet.dev>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://flet.dev
|
|
8
|
+
Project-URL: Repository, https://github.com/flet-dev/flet
|
|
9
|
+
Project-URL: Documentation, https://flet.dev/docs
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: flet
|
|
13
|
+
Requires-Dist: watchdog>=4.0.0
|
|
14
|
+
Requires-Dist: packaging>=25.0
|
|
15
|
+
Requires-Dist: qrcode>=7.4.2
|
|
16
|
+
Requires-Dist: tomli>=1.1.0; python_version < "3.11"
|
|
17
|
+
Requires-Dist: cookiecutter>=2.6.0
|
|
18
|
+
|
|
19
|
+
# Flet CLI
|
|
20
|
+
|
|
21
|
+
Flet CLI is a command-line interface tool for Flet, a framework for building interactive multi-platform applications using Python.
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
- Create new Flet projects
|
|
26
|
+
- Run Flet applications
|
|
27
|
+
- Package and deploy Flet apps
|
|
28
|
+
|
|
29
|
+
## Basic Usage
|
|
30
|
+
|
|
31
|
+
To create a new Flet project:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
flet create myapp
|
|
35
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Flet CLI
|
|
2
|
+
|
|
3
|
+
Flet CLI is a command-line interface tool for Flet, a framework for building interactive multi-platform applications using Python.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Create new Flet projects
|
|
8
|
+
- Run Flet applications
|
|
9
|
+
- Package and deploy Flet apps
|
|
10
|
+
|
|
11
|
+
## Basic Usage
|
|
12
|
+
|
|
13
|
+
To create a new Flet project:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
flet create myapp
|
|
17
|
+
```
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "flet-cli"
|
|
3
|
+
version = "0.81.0.dev7148"
|
|
4
|
+
description = "Flet CLI"
|
|
5
|
+
authors = [{ name = "Appveyor Systems Inc.", email = "hello@flet.dev" }]
|
|
6
|
+
license = "Apache-2.0"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
requires-python = ">=3.10"
|
|
9
|
+
dependencies = [
|
|
10
|
+
"flet",
|
|
11
|
+
"watchdog >=4.0.0",
|
|
12
|
+
"packaging >=25.0",
|
|
13
|
+
"qrcode >=7.4.2",
|
|
14
|
+
"tomli >= 1.1.0 ; python_version < '3.11'",
|
|
15
|
+
"cookiecutter >=2.6.0"
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.urls]
|
|
19
|
+
Homepage = "https://flet.dev"
|
|
20
|
+
Repository = "https://github.com/flet-dev/flet"
|
|
21
|
+
Documentation = "https://flet.dev/docs"
|
|
22
|
+
|
|
23
|
+
[project.entry-points."pyinstaller40"]
|
|
24
|
+
hook-dirs = "flet_cli.__pyinstaller:get_hook_dirs"
|
|
25
|
+
|
|
26
|
+
[build-system]
|
|
27
|
+
requires = ["setuptools"]
|
|
28
|
+
build-backend = "setuptools.build_meta"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
temp_bin_dir = None
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import flet_cli.__pyinstaller.config as hook_config
|
|
2
|
+
from flet_cli.__pyinstaller.utils import get_flet_bin_path
|
|
3
|
+
|
|
4
|
+
bin_path = hook_config.temp_bin_dir
|
|
5
|
+
if not bin_path:
|
|
6
|
+
bin_path = get_flet_bin_path()
|
|
7
|
+
|
|
8
|
+
if bin_path:
|
|
9
|
+
datas = [(bin_path, "flet_desktop/app")]
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import plistlib
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
5
|
+
import tarfile
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from flet.utils import safe_tar_extractall
|
|
9
|
+
from PyInstaller.building.icon import normalize_icon_type
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def unpack_app_bundle(tar_path):
|
|
13
|
+
bin_dir = str(Path(tar_path).parent)
|
|
14
|
+
|
|
15
|
+
with tarfile.open(tar_path, "r:gz") as tar_arch:
|
|
16
|
+
safe_tar_extractall(tar_arch, bin_dir)
|
|
17
|
+
os.remove(tar_path)
|
|
18
|
+
|
|
19
|
+
return os.path.join(bin_dir, "Flet.app")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def update_flet_view_icon(app_path, icon_path):
|
|
23
|
+
print("Updating Flet View icon", app_path, icon_path)
|
|
24
|
+
|
|
25
|
+
icon_file = "AppIcon.icns"
|
|
26
|
+
|
|
27
|
+
# normalize icon
|
|
28
|
+
normalized_icon_path = normalize_icon_type(
|
|
29
|
+
icon_path, ("icns",), "icns", os.getcwd()
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# patch icon
|
|
33
|
+
print("Copying icons from", normalized_icon_path)
|
|
34
|
+
shutil.copy(
|
|
35
|
+
normalized_icon_path,
|
|
36
|
+
os.path.join(app_path, "Contents", "Resources", icon_file),
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# update icon file name
|
|
40
|
+
pl = __load_info_plist(app_path)
|
|
41
|
+
pl["CFBundleIconFile"] = icon_file
|
|
42
|
+
del pl["CFBundleIconName"]
|
|
43
|
+
__save_info_plist(app_path, pl)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def update_flet_view_version_info(
|
|
47
|
+
app_path,
|
|
48
|
+
bundle_id,
|
|
49
|
+
product_name,
|
|
50
|
+
product_version,
|
|
51
|
+
copyright,
|
|
52
|
+
):
|
|
53
|
+
print("Updating Flet View plist", app_path)
|
|
54
|
+
|
|
55
|
+
pl = __load_info_plist(app_path)
|
|
56
|
+
|
|
57
|
+
if bundle_id:
|
|
58
|
+
pl["CFBundleIdentifier"] = bundle_id
|
|
59
|
+
if product_name:
|
|
60
|
+
pl["CFBundleName"] = product_name
|
|
61
|
+
pl["CFBundleDisplayName"] = product_name
|
|
62
|
+
|
|
63
|
+
# rename app bundle
|
|
64
|
+
new_app_path = os.path.join(Path(app_path).parent, f"{product_name}.app")
|
|
65
|
+
os.rename(app_path, new_app_path)
|
|
66
|
+
app_path = new_app_path
|
|
67
|
+
if product_version:
|
|
68
|
+
pl["CFBundleShortVersionString"] = product_version
|
|
69
|
+
if copyright:
|
|
70
|
+
pl["NSHumanReadableCopyright"] = copyright
|
|
71
|
+
|
|
72
|
+
__save_info_plist(app_path, pl)
|
|
73
|
+
|
|
74
|
+
return app_path
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def assemble_app_bundle(app_path, tar_path):
|
|
78
|
+
# sign app bundle
|
|
79
|
+
print(f"Signing file {app_path}")
|
|
80
|
+
cmd_args = [
|
|
81
|
+
"codesign",
|
|
82
|
+
"-s",
|
|
83
|
+
"-",
|
|
84
|
+
"--force",
|
|
85
|
+
"--all-architectures",
|
|
86
|
+
"--timestamp",
|
|
87
|
+
"--deep",
|
|
88
|
+
app_path,
|
|
89
|
+
]
|
|
90
|
+
p = subprocess.run(
|
|
91
|
+
cmd_args,
|
|
92
|
+
stdout=subprocess.PIPE,
|
|
93
|
+
stderr=subprocess.STDOUT,
|
|
94
|
+
universal_newlines=True,
|
|
95
|
+
)
|
|
96
|
+
if p.returncode:
|
|
97
|
+
raise SystemError(
|
|
98
|
+
f"codesign command ({cmd_args}) failed with error code {p.returncode}!\noutput: {p.stdout}"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# pack tar
|
|
102
|
+
with tarfile.open(tar_path, "w:gz") as tar:
|
|
103
|
+
tar.add(app_path, arcname=os.path.basename(app_path))
|
|
104
|
+
|
|
105
|
+
# cleanup
|
|
106
|
+
shutil.rmtree(app_path, ignore_errors=True)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def __load_info_plist(app_path):
|
|
110
|
+
with open(__get_plist_path(app_path), "rb") as fp:
|
|
111
|
+
return plistlib.load(fp)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def __save_info_plist(app_path, pl):
|
|
115
|
+
with open(__get_plist_path(app_path), "wb") as fp:
|
|
116
|
+
plistlib.dump(pl, fp)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def __get_plist_path(app_path):
|
|
120
|
+
return os.path.join(app_path, "Contents", "Info.plist")
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import tempfile
|
|
3
|
+
import uuid
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from flet.utils import copy_tree
|
|
7
|
+
from flet_desktop import get_package_bin_dir
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_flet_bin_path():
|
|
11
|
+
bin_path = get_package_bin_dir()
|
|
12
|
+
if not os.path.exists(bin_path):
|
|
13
|
+
return None
|
|
14
|
+
return bin_path
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def copy_flet_bin():
|
|
18
|
+
bin_path = get_flet_bin_path()
|
|
19
|
+
if not bin_path:
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
# create temp bin dir
|
|
23
|
+
temp_bin_dir = Path(tempfile.gettempdir()).joinpath(str(uuid.uuid4()))
|
|
24
|
+
copy_tree(bin_path, str(temp_bin_dir))
|
|
25
|
+
return str(temp_bin_dir)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import tempfile
|
|
3
|
+
import uuid
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import pefile
|
|
7
|
+
from packaging import version
|
|
8
|
+
from PyInstaller.building.icon import normalize_icon_type
|
|
9
|
+
from PyInstaller.compat import win32api
|
|
10
|
+
from PyInstaller.utils.win32 import versioninfo
|
|
11
|
+
from PyInstaller.utils.win32.icon import IconFile, normalize_icon_type
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def update_flet_view_icon(exe_path, icon_path):
|
|
15
|
+
print("Updating Flet View icon", exe_path, icon_path)
|
|
16
|
+
|
|
17
|
+
RT_ICON = 3
|
|
18
|
+
RT_GROUP_ICON = 14
|
|
19
|
+
|
|
20
|
+
normalized_icon_path = normalize_icon_type(
|
|
21
|
+
icon_path, ("exe", "ico"), "ico", os.getcwd()
|
|
22
|
+
)
|
|
23
|
+
icon = IconFile(normalized_icon_path)
|
|
24
|
+
print("Copying icons from", normalized_icon_path)
|
|
25
|
+
|
|
26
|
+
hdst = win32api.BeginUpdateResource(exe_path, 0)
|
|
27
|
+
|
|
28
|
+
iconid = 1
|
|
29
|
+
# Each step in the following enumerate() will instantiate an IconFile object, as a result of deferred execution
|
|
30
|
+
# of the map() above.
|
|
31
|
+
i = 101
|
|
32
|
+
data = icon.grp_icon_dir()
|
|
33
|
+
data = data + icon.grp_icondir_entries(iconid)
|
|
34
|
+
win32api.UpdateResource(hdst, RT_GROUP_ICON, i, data, 1033)
|
|
35
|
+
print("Writing RT_GROUP_ICON %d resource with %d bytes", i, len(data))
|
|
36
|
+
for data in icon.images:
|
|
37
|
+
win32api.UpdateResource(hdst, RT_ICON, iconid, data, 1033)
|
|
38
|
+
print("Writing RT_ICON %d resource with %d bytes", iconid, len(data))
|
|
39
|
+
iconid = iconid + 1
|
|
40
|
+
|
|
41
|
+
win32api.EndUpdateResource(hdst, 0)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def update_flet_view_version_info(
|
|
45
|
+
exe_path,
|
|
46
|
+
product_name,
|
|
47
|
+
file_description,
|
|
48
|
+
product_version,
|
|
49
|
+
file_version,
|
|
50
|
+
company_name,
|
|
51
|
+
copyright,
|
|
52
|
+
):
|
|
53
|
+
print("Updating Flet View version info", exe_path)
|
|
54
|
+
|
|
55
|
+
# load versioninfo from exe
|
|
56
|
+
if versioninfo.read_version_info_from_executable:
|
|
57
|
+
vs = versioninfo.read_version_info_from_executable(exe_path)
|
|
58
|
+
else:
|
|
59
|
+
vs = versioninfo.decode(exe_path)
|
|
60
|
+
|
|
61
|
+
# update file version
|
|
62
|
+
if file_version:
|
|
63
|
+
pv = version.parse(file_version)
|
|
64
|
+
filevers = (pv.major, pv.minor, pv.micro, 0)
|
|
65
|
+
vs.ffi.fileVersionMS = (filevers[0] << 16) | (filevers[1] & 0xFFFF)
|
|
66
|
+
vs.ffi.fileVersionLS = (filevers[2] << 16) | (filevers[3] & 0xFFFF)
|
|
67
|
+
|
|
68
|
+
# update string props
|
|
69
|
+
for k in vs.kids[0].kids[0].kids:
|
|
70
|
+
if k.name == "ProductName":
|
|
71
|
+
k.val = product_name if product_name else ""
|
|
72
|
+
elif k.name == "FileDescription":
|
|
73
|
+
k.val = file_description if file_description else ""
|
|
74
|
+
if k.name == "ProductVersion":
|
|
75
|
+
k.val = product_version if product_version else ""
|
|
76
|
+
if k.name == "FileVersion" and file_version:
|
|
77
|
+
k.val = file_version if file_version else ""
|
|
78
|
+
if k.name == "CompanyName":
|
|
79
|
+
k.val = company_name if company_name else ""
|
|
80
|
+
if k.name == "LegalCopyright":
|
|
81
|
+
k.val = copyright if copyright else ""
|
|
82
|
+
|
|
83
|
+
version_info_path = str(Path(tempfile.gettempdir()).joinpath(str(uuid.uuid4())))
|
|
84
|
+
with open(version_info_path, "w", encoding="utf-8") as f:
|
|
85
|
+
f.write(str(vs))
|
|
86
|
+
|
|
87
|
+
# Remember overlay
|
|
88
|
+
pe = pefile.PE(exe_path, fast_load=True)
|
|
89
|
+
overlay_before = pe.get_overlay()
|
|
90
|
+
pe.close()
|
|
91
|
+
|
|
92
|
+
hdst = win32api.BeginUpdateResource(exe_path, 0)
|
|
93
|
+
win32api.UpdateResource(
|
|
94
|
+
hdst, pefile.RESOURCE_TYPE["RT_VERSION"], 1, vs.toRaw(), 1033
|
|
95
|
+
)
|
|
96
|
+
win32api.EndUpdateResource(hdst, 0)
|
|
97
|
+
|
|
98
|
+
if overlay_before:
|
|
99
|
+
# Check if the overlay is still present
|
|
100
|
+
pe = pefile.PE(exe_path, fast_load=True)
|
|
101
|
+
overlay_after = pe.get_overlay()
|
|
102
|
+
pe.close()
|
|
103
|
+
|
|
104
|
+
# If the update removed the overlay data, re-append it
|
|
105
|
+
if not overlay_after:
|
|
106
|
+
with open(exe_path, "ab", encoding="utf-8") as exef:
|
|
107
|
+
exef.write(overlay_before)
|
|
108
|
+
|
|
109
|
+
return version_info_path
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import flet.version
|
|
5
|
+
import flet_cli.commands.build
|
|
6
|
+
import flet_cli.commands.create
|
|
7
|
+
import flet_cli.commands.debug
|
|
8
|
+
import flet_cli.commands.devices
|
|
9
|
+
import flet_cli.commands.doctor
|
|
10
|
+
import flet_cli.commands.emulators
|
|
11
|
+
import flet_cli.commands.pack
|
|
12
|
+
import flet_cli.commands.publish
|
|
13
|
+
import flet_cli.commands.run
|
|
14
|
+
import flet_cli.commands.serve
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Source https://stackoverflow.com/a/26379693
|
|
18
|
+
def set_default_subparser(
|
|
19
|
+
parser: argparse.ArgumentParser, name: str, args: list = None, index: int = 0
|
|
20
|
+
):
|
|
21
|
+
"""
|
|
22
|
+
Set a default subparser when no subparser is provided.
|
|
23
|
+
This should be called after setting up the argument parser but before
|
|
24
|
+
`parse_args()`.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
name: The name of the default subparser to use.
|
|
28
|
+
args: A list of arguments passed to `parse_args()`.
|
|
29
|
+
index: Position in `sys.argv` where the default subparser should be
|
|
30
|
+
inserted.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
# exit if help or version flags are present
|
|
34
|
+
if any(flag in sys.argv[1:] for flag in {"-h", "--help", "-V", "--version"}):
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
# all subparser actions
|
|
38
|
+
subparser_actions = [
|
|
39
|
+
action
|
|
40
|
+
for action in parser._subparsers._actions
|
|
41
|
+
if isinstance(action, argparse._SubParsersAction)
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
# all subparser names
|
|
45
|
+
subparser_names = [
|
|
46
|
+
sp_name for action in subparser_actions for sp_name in action._name_parser_map
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
# if an existing subparser is provided, skip setting a default
|
|
50
|
+
if any(arg in subparser_names for arg in sys.argv[1:]):
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
# if the default subparser doesn't exist, register it in the first subparser action
|
|
54
|
+
if (name not in subparser_names) and subparser_actions:
|
|
55
|
+
subparser_actions[0].add_parser(name)
|
|
56
|
+
|
|
57
|
+
# insert the default subparser into the appropriate argument list
|
|
58
|
+
if args is None:
|
|
59
|
+
if len(sys.argv) > 1:
|
|
60
|
+
sys.argv.insert(index, name)
|
|
61
|
+
else:
|
|
62
|
+
args.insert(index, name)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def get_parser() -> argparse.ArgumentParser:
|
|
66
|
+
"""Construct and return the CLI argument parser."""
|
|
67
|
+
parser = argparse.ArgumentParser(
|
|
68
|
+
formatter_class=argparse.RawDescriptionHelpFormatter
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# add version flag
|
|
72
|
+
parser.add_argument(
|
|
73
|
+
"--version",
|
|
74
|
+
"-V",
|
|
75
|
+
action="version",
|
|
76
|
+
version=(
|
|
77
|
+
f"Flet: {flet.version.flet_version}\n"
|
|
78
|
+
f"Flutter: {flet.version.flutter_version}\n"
|
|
79
|
+
f"Pyodide: {flet.version.pyodide_version}"
|
|
80
|
+
),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
sp = parser.add_subparsers(dest="command")
|
|
84
|
+
|
|
85
|
+
# register subcommands
|
|
86
|
+
flet_cli.commands.create.Command.register_to(sp, "create")
|
|
87
|
+
flet_cli.commands.run.Command.register_to(sp, "run")
|
|
88
|
+
flet_cli.commands.build.Command.register_to(sp, "build")
|
|
89
|
+
flet_cli.commands.debug.Command.register_to(sp, "debug")
|
|
90
|
+
flet_cli.commands.pack.Command.register_to(sp, "pack")
|
|
91
|
+
flet_cli.commands.publish.Command.register_to(sp, "publish")
|
|
92
|
+
flet_cli.commands.serve.Command.register_to(sp, "serve")
|
|
93
|
+
flet_cli.commands.emulators.Command.register_to(sp, "emulators")
|
|
94
|
+
flet_cli.commands.devices.Command.register_to(sp, "devices")
|
|
95
|
+
flet_cli.commands.doctor.Command.register_to(sp, "doctor")
|
|
96
|
+
|
|
97
|
+
# set "run" as the default subparser
|
|
98
|
+
set_default_subparser(parser, name="run", index=1)
|
|
99
|
+
|
|
100
|
+
return parser
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def main():
|
|
104
|
+
parser = get_parser()
|
|
105
|
+
|
|
106
|
+
# print usage/help if called without arguments
|
|
107
|
+
if len(sys.argv) == 1:
|
|
108
|
+
parser.print_help(sys.stdout)
|
|
109
|
+
sys.exit(1)
|
|
110
|
+
|
|
111
|
+
# parse arguments
|
|
112
|
+
args = parser.parse_args()
|
|
113
|
+
|
|
114
|
+
# execute command
|
|
115
|
+
args.handler(args)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
if __name__ == "__main__":
|
|
119
|
+
main()
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from typing import Any, Optional
|
|
3
|
+
|
|
4
|
+
from flet_cli.commands.options import Option, verbose_option
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CustomArgumentDefaultsHelpFormatter(argparse.HelpFormatter):
|
|
8
|
+
"""
|
|
9
|
+
An argparse help formatter that appends default values to help text
|
|
10
|
+
selectively.
|
|
11
|
+
|
|
12
|
+
Defaults are added only when they are informative and not already
|
|
13
|
+
present in the help string. Noisy or redundant defaults (such as
|
|
14
|
+
None, empty lists, booleans for flag arguments, or suppressed values)
|
|
15
|
+
are omitted.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def _get_help_string(self, action: argparse.Action) -> str:
|
|
19
|
+
help_text = action.help or ""
|
|
20
|
+
default = action.default
|
|
21
|
+
|
|
22
|
+
# skip appending a default
|
|
23
|
+
if (
|
|
24
|
+
default is None
|
|
25
|
+
or default == []
|
|
26
|
+
or isinstance(default, bool) # store_true / store_false flags
|
|
27
|
+
or default is argparse.SUPPRESS
|
|
28
|
+
or any(token in help_text for token in ("%(default)", "(default:"))
|
|
29
|
+
):
|
|
30
|
+
return help_text
|
|
31
|
+
|
|
32
|
+
# only add defaults for optionals or for nargs implying optional values
|
|
33
|
+
defaulting_nargs = (argparse.OPTIONAL, argparse.ZERO_OR_MORE)
|
|
34
|
+
if action.option_strings or action.nargs in defaulting_nargs:
|
|
35
|
+
help_text += " (default: %(default)s)"
|
|
36
|
+
|
|
37
|
+
return help_text
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class BaseCommand:
|
|
41
|
+
"""A CLI subcommand"""
|
|
42
|
+
|
|
43
|
+
# The subcommand's name
|
|
44
|
+
name: Optional[str] = None
|
|
45
|
+
# The subcommand's help string, if not given, __doc__ will be used.
|
|
46
|
+
description: Optional[str] = None
|
|
47
|
+
# A list of pre-defined options which will be loaded on initializing
|
|
48
|
+
# Rewrite this if you don't want the default ones
|
|
49
|
+
arguments: list[Option] = [verbose_option]
|
|
50
|
+
|
|
51
|
+
def __init__(self, parser: argparse.ArgumentParser) -> None:
|
|
52
|
+
for arg in self.arguments:
|
|
53
|
+
arg.add_to_parser(parser)
|
|
54
|
+
self.add_arguments(parser)
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def register_to(
|
|
58
|
+
cls,
|
|
59
|
+
subparsers: argparse._SubParsersAction,
|
|
60
|
+
name: Optional[str] = None,
|
|
61
|
+
**kwargs: Any,
|
|
62
|
+
) -> None:
|
|
63
|
+
"""Register a subcommand to the subparsers,
|
|
64
|
+
with an optional name of the subcommand.
|
|
65
|
+
"""
|
|
66
|
+
help_text = cls.description or cls.__doc__
|
|
67
|
+
name = name or cls.name or ""
|
|
68
|
+
|
|
69
|
+
# Remove the existing subparser as it will raise an error on Python 3.11+
|
|
70
|
+
subparsers._name_parser_map.pop(name, None)
|
|
71
|
+
subactions = subparsers._get_subactions()
|
|
72
|
+
subactions[:] = [action for action in subactions if action.dest != name]
|
|
73
|
+
parser = subparsers.add_parser(
|
|
74
|
+
name,
|
|
75
|
+
description=help_text,
|
|
76
|
+
help=help_text,
|
|
77
|
+
formatter_class=CustomArgumentDefaultsHelpFormatter,
|
|
78
|
+
**kwargs,
|
|
79
|
+
)
|
|
80
|
+
command = cls(parser)
|
|
81
|
+
parser.set_defaults(handler=command.handle)
|
|
82
|
+
|
|
83
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
84
|
+
"""Manipulate the argument parser to add more arguments"""
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
def handle(self, options: argparse.Namespace) -> None:
|
|
88
|
+
"""The command handler function.
|
|
89
|
+
:param options: the parsed Namespace object
|
|
90
|
+
"""
|
|
91
|
+
raise NotImplementedError
|