Skip to content

Commit a641175

Browse files
authored
Merge pull request #669 from algorandfoundation/chore/generate-args
chore: support passing client generator args to the language
2 parents b9781fe + 1c1712f commit a641175

9 files changed

+191
-25
lines changed

docs/cli/index.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
- [-v, --version ](#-v---version--1)
7171
- [Arguments](#arguments-7)
7272
- [APP_SPEC_PATH_OR_DIR](#app_spec_path_or_dir)
73+
- [ARGS](#args)
7374
- [goal](#goal)
7475
- [Options](#options-11)
7576
- [--console](#--console)
@@ -588,7 +589,7 @@ Supply the path to an application specification file or a directory to recursive
588589
for "application.json" files
589590

590591
```shell
591-
algokit generate client [OPTIONS] APP_SPEC_PATH_OR_DIR
592+
algokit generate client [OPTIONS] [APP_SPEC_PATH_OR_DIR] [ARGS]...
592593
```
593594

594595
### Options
@@ -615,7 +616,11 @@ The client generator version to pin to, for example, 1.0.0. If no version is spe
615616

616617

617618
### APP_SPEC_PATH_OR_DIR
618-
Required argument
619+
Optional argument
620+
621+
622+
### ARGS
623+
Optional argument(s)
619624

620625
## goal
621626

src/algokit/cli/generate.py

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,17 @@ def generate_group() -> None:
106106
"""Generate code for an Algorand project."""
107107

108108

109-
@generate_group.command("client")
109+
@generate_group.command(
110+
"client",
111+
context_settings={
112+
"ignore_unknown_options": True,
113+
},
114+
add_help_option=False,
115+
)
110116
@click.argument(
111117
"app_spec_path_or_dir",
112-
type=click.Path(exists=True, dir_okay=True, resolve_path=True, path_type=Path),
118+
type=click.Path(dir_okay=True, resolve_path=True, path_type=Path),
119+
required=False,
113120
)
114121
@click.option(
115122
"output_path_pattern",
@@ -138,13 +145,20 @@ def generate_group() -> None:
138145
"If a version is specified, AlgoKit checks if an installed version matches and runs the installed version. "
139146
"Otherwise, AlgoKit runs the specified version.",
140147
)
148+
@click.argument("args", nargs=-1, type=click.UNPROCESSED)
141149
def generate_client(
142-
output_path_pattern: str | None, app_spec_path_or_dir: Path, language: str | None, version: str | None
150+
app_spec_path_or_dir: Path | None,
151+
output_path_pattern: str | None,
152+
language: str | None,
153+
version: str | None,
154+
args: tuple[str, ...],
143155
) -> None:
144156
"""Create a typed ApplicationClient from an ARC-32/56 application.json
145157
146158
Supply the path to an application specification file or a directory to recursively search
147159
for "application.json" files"""
160+
161+
generator = None
148162
if language is not None:
149163
generator = ClientGenerator.create_for_language(language, version)
150164
elif output_path_pattern is not None:
@@ -156,16 +170,41 @@ def generate_client(
156170
"Could not determine language from file extension, Please use the --language option to specify a "
157171
"target language"
158172
) from ex
173+
174+
help_in_args = any(_is_help_flag(arg) for arg in args)
175+
help_in_positional = app_spec_path_or_dir is not None and _is_help_flag(app_spec_path_or_dir)
176+
177+
if help_in_positional:
178+
ctx = click.get_current_context()
179+
click.echo(ctx.get_help())
180+
elif generator:
181+
if help_in_args:
182+
generator.show_help()
183+
return
184+
185+
if app_spec_path_or_dir is None:
186+
raise click.ClickException("Missing argument 'APP_SPEC_PATH_OR_DIR'.")
187+
188+
try:
189+
generator.generate_all(
190+
app_spec_path_or_dir,
191+
output_path_pattern,
192+
list(args),
193+
raise_on_path_resolution_failure=False,
194+
)
195+
except AppSpecsNotFoundError as ex:
196+
raise click.ClickException("No app specs found") from ex
159197
else:
160198
raise click.ClickException(
161199
"One of --language or --output is required to determine the client language to generate"
162200
)
163201

164-
try:
165-
generator.generate_all(
166-
app_spec_path_or_dir,
167-
output_path_pattern,
168-
raise_on_path_resolution_failure=False,
169-
)
170-
except AppSpecsNotFoundError as ex:
171-
raise click.ClickException("No app specs found") from ex
202+
203+
HELP_FLAGS = ("--help", "-h")
204+
205+
206+
def _is_help_flag(value: str | Path) -> bool:
207+
"""Check if a value is a help flag (--help or -h)."""
208+
if isinstance(value, Path):
209+
return any(str(value).endswith(flag) for flag in HELP_FLAGS)
210+
return value in HELP_FLAGS

src/algokit/cli/project/link.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ def _link_projects(
9090
generator.generate_all(
9191
contract_project_root,
9292
output_path_pattern,
93+
None, # no additional args for project link
9394
raise_on_path_resolution_failure=fail_fast,
9495
)
9596
except AppSpecsNotFoundError:

src/algokit/core/typed_client_generation.py

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,10 @@ def resolve_output_path(self, app_spec: Path, output_path_pattern: str | None) -
9595
return (output_path, app_spec_type)
9696

9797
@abc.abstractmethod
98-
def generate(self, app_spec: Path, output: Path) -> None: ...
98+
def generate(self, app_spec: Path, output: Path, args: list[str] | None = None) -> None: ...
99+
100+
@abc.abstractmethod
101+
def show_help(self) -> None: ...
99102

100103
@property
101104
@abc.abstractmethod
@@ -111,6 +114,7 @@ def generate_all(
111114
self,
112115
app_spec_path_or_dir: Path,
113116
output_path_pattern: str | None,
117+
args: list[str] | None,
114118
*,
115119
raise_on_path_resolution_failure: bool,
116120
) -> None:
@@ -142,11 +146,11 @@ def accumulate_items_to_generate(
142146

143147
items_to_generate: dict[Path, tuple[Path, AppSpecType]] = reduce(accumulate_items_to_generate, app_specs, {})
144148
for output_path, (app_spec, _) in items_to_generate.items():
145-
self.generate(app_spec, output_path)
149+
self.generate(app_spec, output_path, args)
146150

147151

148152
class PythonClientGenerator(ClientGenerator, language="python", extension=".py"):
149-
def generate(self, app_spec: Path, output: Path) -> None:
153+
def generate(self, app_spec: Path, output: Path, args: list[str] | None = None) -> None:
150154
logger.info(f"Generating Python client code for application specified in {app_spec} and writing to {output}")
151155
cmd = [
152156
*self.command,
@@ -155,6 +159,9 @@ def generate(self, app_spec: Path, output: Path) -> None:
155159
"-o",
156160
str(output),
157161
]
162+
if args:
163+
cmd.extend(args)
164+
158165
run_result = proc.run(cmd)
159166
click.echo(run_result.output)
160167

@@ -166,6 +173,39 @@ def generate(self, app_spec: Path, output: Path) -> None:
166173
)
167174
raise click.exceptions.Exit(run_result.exit_code)
168175

176+
def show_help(self) -> None:
177+
"""Show help for the Python client generator."""
178+
cmd = [*self.command, "--help"]
179+
run_result = proc.run(cmd)
180+
181+
# Filter out unwanted lines from the help output
182+
filtered_lines = []
183+
skip_next_lines = False
184+
185+
for line in run_result.output.splitlines():
186+
# Skip usage line
187+
if "usage: algokitgen-py" in line:
188+
continue
189+
190+
# Start skipping when we encounter the -a option
191+
if "-a APP_SPEC" in line:
192+
skip_next_lines = True
193+
continue
194+
195+
# If we're skipping and encounter a line that starts a new option (starts with a dash), stop skipping
196+
if skip_next_lines:
197+
is_new_option = line.lstrip().startswith("-")
198+
if is_new_option:
199+
skip_next_lines = False
200+
else:
201+
continue
202+
203+
filtered_lines.append(line)
204+
205+
click.echo("\n".join(filtered_lines))
206+
if run_result.exit_code != 0:
207+
raise click.exceptions.Exit(run_result.exit_code)
208+
169209
@property
170210
def default_output_pattern(self) -> str:
171211
return f"{{contract_name}}_client{self.extension}"
@@ -257,8 +297,11 @@ def find_generate_command(self, version: str | None) -> list[str]:
257297

258298

259299
class TypeScriptClientGenerator(ClientGenerator, language="typescript", extension=".ts"):
260-
def generate(self, app_spec: Path, output: Path) -> None:
300+
def generate(self, app_spec: Path, output: Path, args: list[str] | None = None) -> None:
261301
cmd = [*self.command, "generate", "-a", str(app_spec), "-o", str(output)]
302+
if args:
303+
cmd.extend(args)
304+
262305
logger.info(
263306
f"Generating TypeScript client code for application specified in {app_spec} and writing to {output}"
264307
)
@@ -273,6 +316,21 @@ def generate(self, app_spec: Path, output: Path) -> None:
273316
)
274317
raise click.exceptions.Exit(run_result.exit_code)
275318

319+
def show_help(self) -> None:
320+
"""Show help for the TypeScript client generator."""
321+
cmd = [*self.command, "generate", "--help"]
322+
run_result = proc.run(cmd)
323+
324+
# Filter out unwanted lines from the help output
325+
filtered_lines = []
326+
for line in run_result.output.splitlines():
327+
if "-a" not in line and "Usage: algokitgen" not in line:
328+
filtered_lines.append(line)
329+
330+
click.echo("\n".join(filtered_lines))
331+
if run_result.exit_code != 0:
332+
raise click.exceptions.Exit(run_result.exit_code)
333+
276334
def find_project_generate_command(
277335
self, npm_command: list[str], npx_command: list[str], version: str | None
278336
) -> list[str] | None:

tests/generate/test_generate_client.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ def test_generate_no_options(application_json: Path) -> None:
112112
("-o client.ts --language python", "client.ts"),
113113
("-o client.py --language python --version 1.1.2", "client.py"),
114114
("-l python -v 1.1.0", "hello_world_app_client.py"),
115+
("-o client.py -p --mode minimal", "client.py"),
115116
],
116117
)
117118
def test_generate_client_python(
@@ -124,7 +125,7 @@ def test_generate_client_python(
124125
proc_mock.should_bad_exit_on(["poetry", "show", PYTHON_PYPI_PACKAGE, "--tree"])
125126
proc_mock.should_bad_exit_on(["pipx", "list", "--short"])
126127

127-
result = invoke(f"generate client {options} {application_json.name}", cwd=application_json.parent)
128+
result = invoke(f"generate client {application_json.name} {options}", cwd=application_json.parent)
128129
assert result.exit_code == 0
129130
verify(
130131
_normalize_output(result.output),
@@ -133,9 +134,8 @@ def test_generate_client_python(
133134
)
134135
version = options.split()[-1] if "--version" in options or "-v" in options else None
135136
assert len(proc_mock.called) == 4 # noqa: PLR2004
136-
assert (
137-
proc_mock.called[3].command
138-
== _get_python_generate_command(version, application_json, expected_output_path).split()
137+
assert " ".join(proc_mock.called[3].command).startswith(
138+
_get_python_generate_command(version, application_json, expected_output_path)
139139
)
140140

141141

@@ -278,6 +278,7 @@ def test_generate_client_python_multiple_app_specs_in_directory(
278278
("-o client.py --language typescript", "client.py"),
279279
("-o client.ts --language typescript --version 3.0.0", "client.ts"),
280280
("-l typescript -v 2.6.0", "HelloWorldAppClient.ts"),
281+
("-o client.ts -pn --mode minimal", "client.ts"),
281282
],
282283
)
283284
def test_generate_client_typescript(
@@ -291,7 +292,7 @@ def test_generate_client_typescript(
291292
proc_mock.should_bad_exit_on([npm_command, "ls"])
292293
proc_mock.should_bad_exit_on([npm_command, "ls", "--global"])
293294

294-
result = invoke(f"generate client {options} {application_json.name}", cwd=application_json.parent)
295+
result = invoke(f"generate client {application_json.name} {options}", cwd=application_json.parent)
295296

296297
assert result.exit_code == 0
297298
verify(
@@ -301,9 +302,8 @@ def test_generate_client_typescript(
301302
)
302303
version = options.split()[-1] if "--version" in options or "-v" in options else "latest"
303304
assert len(proc_mock.called) == 3 # noqa: PLR2004
304-
assert (
305-
proc_mock.called[2].command
306-
== _get_typescript_generate_command(version, application_json, expected_output_path).split()
305+
assert " ".join(proc_mock.called[2].command).startswith(
306+
_get_typescript_generate_command(version, application_json, expected_output_path)
307307
)
308308

309309

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
DEBUG: Searching for project installed client generator
2+
DEBUG: Running 'poetry show algokit-client-generator --tree' in '{current_working_directory}'
3+
DEBUG: poetry: STDOUT
4+
DEBUG: poetry: STDERR
5+
DEBUG: Running 'pipx --version' in '{current_working_directory}'
6+
DEBUG: pipx: STDOUT
7+
DEBUG: pipx: STDERR
8+
DEBUG: Searching for globally installed client generator
9+
DEBUG: Running 'pipx list --short' in '{current_working_directory}'
10+
DEBUG: pipx: STDOUT
11+
DEBUG: pipx: STDERR
12+
DEBUG: No matching installed client generator found, run client generator via pipx
13+
Generating Python client code for application specified in {current_working_directory}/application.json and writing to client.py
14+
DEBUG: Running 'pipx run --spec=algokit-client-generator algokitgen-py -a {current_working_directory}/application.json -o client.py -p --mode minimal' in '{current_working_directory}'
15+
DEBUG: pipx: STDOUT
16+
DEBUG: pipx: STDERR
17+
STDOUT
18+
STDERR
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
DEBUG: Searching for project installed client generator
2+
DEBUG: Running 'npm ls --no-unicode' in '{current_working_directory}'
3+
DEBUG: npm: STDOUT
4+
DEBUG: npm: STDERR
5+
DEBUG: Searching for globally installed client generator
6+
DEBUG: Running 'npm --global ls --no-unicode' in '{current_working_directory}'
7+
DEBUG: npm: STDOUT
8+
DEBUG: npm: STDERR
9+
DEBUG: No matching installed client generator found, run client generator via npx
10+
Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to client.ts
11+
DEBUG: Running 'npx --yes @algorandfoundation/algokit-client-generator@latest generate -a {current_working_directory}/application.json -o client.ts -pn --mode minimal' in '{current_working_directory}'
12+
DEBUG: npx: STDOUT
13+
DEBUG: npx: STDERR
14+
STDOUT
15+
STDERR
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
DEBUG: Searching for project installed client generator
2+
DEBUG: Running 'npm ls --no-unicode' in '{current_working_directory}'
3+
DEBUG: npm: STDOUT
4+
DEBUG: npm: STDERR
5+
DEBUG: Searching for globally installed client generator
6+
DEBUG: Running 'npm --global ls --no-unicode' in '{current_working_directory}'
7+
DEBUG: npm: STDOUT
8+
DEBUG: npm: STDERR
9+
DEBUG: No matching installed client generator found, run client generator via npx
10+
Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to client.ts
11+
DEBUG: Running 'npx --yes @algorandfoundation/algokit-client-generator@latest generate -a {current_working_directory}/application.json -o client.ts -pn --mode minimal' in '{current_working_directory}'
12+
DEBUG: npx: STDOUT
13+
DEBUG: npx: STDERR
14+
STDOUT
15+
STDERR
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
DEBUG: Searching for project installed client generator
2+
DEBUG: Running 'npm.cmd ls --no-unicode' in '{current_working_directory}'
3+
DEBUG: npm.cmd: STDOUT
4+
DEBUG: npm.cmd: STDERR
5+
DEBUG: Searching for globally installed client generator
6+
DEBUG: Running 'npm.cmd --global ls --no-unicode' in '{current_working_directory}'
7+
DEBUG: npm.cmd: STDOUT
8+
DEBUG: npm.cmd: STDERR
9+
DEBUG: No matching installed client generator found, run client generator via npx
10+
Generating TypeScript client code for application specified in {current_working_directory}/application.json and writing to client.ts
11+
DEBUG: Running 'npx.cmd --yes @algorandfoundation/algokit-client-generator@latest generate -a {current_working_directory}/application.json -o client.ts -pn --mode minimal' in '{current_working_directory}'
12+
DEBUG: npx.cmd: STDOUT
13+
DEBUG: npx.cmd: STDERR
14+
STDOUT
15+
STDERR

0 commit comments

Comments
 (0)