Skip to content

Commit 0978de0

Browse files
committed
Refactor emulator command arguments and actions
Simplifies emulator command interface by replacing multiple flags (--launch, --create, --delete) with positional 'action' and 'emulator' arguments. Adds validation for emulator names, improves error handling, and updates help messages and output to reflect new usage. This change streamlines command usage and improves user feedback for invalid input.
1 parent ebc7c67 commit 0978de0

File tree

1 file changed

+64
-62
lines changed
  • sdk/python/packages/flet-cli/src/flet_cli/commands

1 file changed

+64
-62
lines changed

sdk/python/packages/flet-cli/src/flet_cli/commands/emulators.py

Lines changed: 64 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import argparse
22
import os
3+
import re
34
from pathlib import Path
45

56
from rich.console import Group
@@ -18,79 +19,65 @@ class Command(BaseFlutterCommand):
1819

1920
def __init__(self, parser: argparse.ArgumentParser) -> None:
2021
super().__init__(parser)
21-
self.launch_target = None
22+
self.action = None
23+
self.emulator_target = None
2224
self.cold_boot = False
23-
self.create_emulator = False
24-
self.delete_emulator = None
2525
self.emulator_name = None
2626

2727
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
2828
parser.add_argument(
29-
"--launch",
30-
dest="launch",
29+
"action",
30+
type=str.lower,
31+
nargs="?",
32+
choices=["start", "create", "delete"],
33+
help="Action to perform: start an emulator, create a new one, "
34+
"or delete it.",
35+
)
36+
parser.add_argument(
37+
"emulator",
3138
type=str,
32-
help="Launch a specific emulator by ID or name.",
39+
nargs="?",
40+
help="Emulator ID or name (required for start, create, and delete).",
3341
)
3442
parser.add_argument(
3543
"--cold",
3644
dest="cold",
3745
action="store_true",
3846
default=False,
39-
help="Cold boot the emulator when launching.",
40-
)
41-
parser.add_argument(
42-
"--create",
43-
dest="create",
44-
action="store_true",
45-
default=False,
46-
help="Create a new emulator using Flutter defaults.",
47-
)
48-
parser.add_argument(
49-
"--delete",
50-
dest="delete",
51-
type=str,
52-
help="Delete an emulator by ID or name (Android only).",
53-
)
54-
parser.add_argument(
55-
"--name",
56-
dest="name",
57-
type=str,
58-
help="Name for the new emulator (used with --create).",
47+
help="Cold boot the emulator when starting.",
5948
)
6049
super().add_arguments(parser)
6150

6251
def handle(self, options: argparse.Namespace) -> None:
6352
super().handle(options)
6453
if self.options:
65-
self.launch_target = self.options.launch
54+
self.action = self.options.action
55+
self.emulator_target = self.options.emulator
56+
self.emulator_name = self.options.emulator
6657
self.cold_boot = bool(self.options.cold)
67-
self.create_emulator = bool(self.options.create)
68-
self.delete_emulator = self.options.delete
69-
self.emulator_name = self.options.name
70-
71-
selected_actions = [
72-
bool(self.create_emulator),
73-
bool(self.launch_target),
74-
bool(self.delete_emulator),
75-
]
76-
if sum(selected_actions) > 1:
77-
msg = "Please choose only one action: --create, --launch, or --delete."
78-
console.log(msg, style=verbose2_style)
79-
self.cleanup(1, msg)
8058

8159
self.status = console.status(
8260
"[bold blue]Initializing environment...",
8361
spinner="bouncingBall",
8462
)
8563
with Live(Group(self.status, self.progress), console=console) as self.live:
8664
self.initialize_command()
87-
if self.delete_emulator:
65+
if self.action == "delete":
66+
if not self.emulator_target:
67+
self.skip_flutter_doctor = True
68+
self.cleanup(1, "Provide emulator ID or name to delete.")
8869
self._delete_emulator()
8970
return
90-
if self.create_emulator:
71+
if self.action == "create":
72+
if not self.emulator_name:
73+
self.skip_flutter_doctor = True
74+
self.cleanup(1, "Provide emulator name to create.")
9175
self._create_emulator()
9276
return
93-
if self.launch_target:
77+
if self.action == "start":
78+
if not self.emulator_target:
79+
self.skip_flutter_doctor = True
80+
self.cleanup(1, "Provide emulator ID or name to start.")
9481
self._launch_emulator()
9582
return
9683
self._list_emulators()
@@ -131,7 +118,7 @@ def _list_emulators(self):
131118
emulators = self._parse_emulators_output(output)
132119
if not emulators:
133120
footer = (
134-
'\nRun [green]"flet emulators --create"[/green] '
121+
'\nRun [green]"flet emulators create <name>"[/green] '
135122
"to create a new emulator.\n"
136123
)
137124
self.cleanup(0, Group(Panel("No emulators found."), footer), no_border=True)
@@ -157,11 +144,11 @@ def _list_emulators(self):
157144
footer = (
158145
"\n"
159146
"Launch an emulator with "
160-
'[green]"flet emulators --launch <emulator-id>"[/green].\n'
147+
'[green]"flet emulators start <emulator-id>"[/green].\n'
161148
"Create a new emulator with "
162-
'[green]"flet emulators --create [--name <name>]"[/green].\n'
149+
'[green]"flet emulators create <name>"[/green].\n'
163150
"Delete an Android emulator with "
164-
'[green]"flet emulators --delete <emulator-id>"[/green].\n'
151+
'[green]"flet emulators delete <emulator-id>"[/green].\n'
165152
"\n"
166153
"You can find more information on managing emulators at the links below:\n"
167154
" https://developer.android.com/studio/run/managing-avds\n"
@@ -171,15 +158,15 @@ def _list_emulators(self):
171158
self.cleanup(0, message=Group(table, footer), no_border=True)
172159

173160
def _launch_emulator(self):
174-
assert self.launch_target
161+
assert self.emulator_target
175162
self.update_status(
176-
f"[bold blue]Launching emulator [cyan]{self.launch_target}[/cyan]..."
163+
f"[bold blue]Starting emulator [cyan]{self.emulator_target}[/cyan]..."
177164
)
178165
args = [
179166
self.flutter_exe,
180167
"emulators",
181168
"--launch",
182-
self.launch_target,
169+
self.emulator_target,
183170
"--no-version-check",
184171
"--suppress-analytics",
185172
]
@@ -200,7 +187,7 @@ def _launch_emulator(self):
200187
(
201188
error_output
202189
if error_output
203-
else f"Failed to launch emulator '{self.launch_target}'."
190+
else f"Failed to start emulator '{self.emulator_target}'."
204191
),
205192
)
206193
return
@@ -209,12 +196,12 @@ def _launch_emulator(self):
209196
console.log(output, style=verbose2_style)
210197

211198
mode = " (cold boot)" if self.cold_boot else ""
212-
self.cleanup(0, f"Emulator [cyan]{self.launch_target}[/cyan] launched{mode}.")
199+
self.cleanup(0, f"Emulator [cyan]{self.emulator_target}[/cyan] started{mode}.")
213200

214201
def _delete_emulator(self):
215-
assert self.delete_emulator
202+
assert self.emulator_target
216203
self.update_status(
217-
f"[bold blue]Deleting emulator [cyan]{self.delete_emulator}[/cyan]..."
204+
f"[bold blue]Deleting emulator [cyan]{self.emulator_target}[/cyan]..."
218205
)
219206
home_dir = self.env.get("ANDROID_HOME") or AndroidSDK.android_home_dir()
220207
if not home_dir:
@@ -227,18 +214,25 @@ def _delete_emulator(self):
227214
)
228215

229216
try:
230-
sdk.delete_avd(Path(home_dir), self.delete_emulator)
217+
sdk.delete_avd(Path(home_dir), self.emulator_target)
231218
except Exception as exc: # pragma: no cover - defensive
232219
self.skip_flutter_doctor = True
233220
self.cleanup(
234-
1, f"Failed to delete emulator '{self.delete_emulator}': {exc}"
221+
1, f"Failed to delete emulator '{self.emulator_target}': {exc}"
235222
)
236223
return
237224

238-
self.cleanup(0, f"Deleted emulator [cyan]{self.delete_emulator}[/cyan].")
225+
self.cleanup(0, f"Deleted emulator [cyan]{self.emulator_target}[/cyan].")
239226

240227
def _create_emulator(self):
241228
self.update_status("[bold blue]Creating emulator...")
229+
if not self._is_valid_emulator_name(self.emulator_name):
230+
self.skip_flutter_doctor = True
231+
self.cleanup(
232+
1,
233+
"Emulator name is invalid. Allowed characters: a-z A-Z 0-9 . _ -",
234+
)
235+
242236
args = [
243237
self.flutter_exe,
244238
"emulators",
@@ -255,25 +249,33 @@ def _create_emulator(self):
255249
capture_output=True,
256250
)
257251
output = create_result.stdout or ""
258-
if create_result.returncode != 0:
259-
error_output = create_result.stderr or output
252+
error_output = create_result.stderr or output
253+
invalid_name = (
254+
"contains invalid characters" in (error_output or "").lower()
255+
or "contains invalid characters" in output.lower()
256+
)
257+
exit_code = create_result.returncode or (1 if invalid_name else 0)
258+
if exit_code != 0:
260259
self.cleanup(
261-
create_result.returncode,
260+
exit_code,
262261
error_output or "Failed to create emulator.",
263262
)
264263
return
265264

266265
if output and self.verbose >= 1:
267266
console.log(output, style=verbose2_style)
268267

269-
created_name = self.emulator_name or "emulator"
268+
created_name = self.emulator_name
270269
self.cleanup(
271270
0,
272271
f"Created emulator [cyan]{created_name}[/cyan]. "
273272
"Use `flet emulators` to list it or "
274-
f"`flet emulators --launch {created_name}` to start it.",
273+
f"`flet emulators start {created_name}` to start it.",
275274
)
276275

276+
def _is_valid_emulator_name(self, name: str) -> bool:
277+
return bool(re.match(r"^[A-Za-z0-9._-]+$", name or ""))
278+
277279
def _parse_emulators_output(self, output: str) -> list[dict]:
278280
emulators = []
279281
for raw_line in output.splitlines():

0 commit comments

Comments
 (0)