From 7c69c3446ac18d916d76c1368db21df8047da7a7 Mon Sep 17 00:00:00 2001 From: eyalz Date: Sun, 10 Aug 2025 12:56:52 +0300 Subject: [PATCH] Add --var option --- commands/create_spec.py | 20 +++++- cxk.py | 3 +- tests/README.md | 14 ++++ tests/e2e/test_e2e.py | 143 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 174 insertions(+), 6 deletions(-) diff --git a/commands/create_spec.py b/commands/create_spec.py index 7e5633b..957acb1 100644 --- a/commands/create_spec.py +++ b/commands/create_spec.py @@ -6,7 +6,7 @@ from prompt import collect_var_value -async def handle_create_spec(spec_template: str, output_file: str | None = None): +async def handle_create_spec(spec_template: str, output_file: str | None = None, var_overrides: list[str] | None = None): """Handle the create-spec command""" # Resolve relative paths against current working directory @@ -24,13 +24,27 @@ async def handle_create_spec(spec_template: str, output_file: str | None = None) # Get variables from template variables = template_engine.get_variables() + # Parse var_overrides into a dictionary + provided_vars = {} + if var_overrides: + for var_override in var_overrides: + if '=' not in var_override: + print(f"Error: Invalid variable format '{var_override}'. Use KEY=VALUE format.", file=sys.stderr) + sys.exit(1) + key, value = var_override.split('=', 1) + provided_vars[key] = value + # Collect values for each variable collected_vars = {} if variables: print("Collecting values for template variables:") for var in sorted(variables): - raw_value = await collect_var_value(var) - print(f" {var}: {raw_value}") + if var in provided_vars: + raw_value = provided_vars[var] + print(f" {var}: {raw_value}") + else: + raw_value = await collect_var_value(var) + print(f" {var}: {raw_value}") # Try to parse as JSON if it looks like JSON if raw_value and (raw_value.strip().startswith('{') or raw_value.strip().startswith('[')): diff --git a/cxk.py b/cxk.py index f99f6e3..146f3aa 100644 --- a/cxk.py +++ b/cxk.py @@ -25,6 +25,7 @@ async def main(): create_spec_parser = subparsers.add_parser("create-spec", help="Create spec from template") create_spec_parser.add_argument("spec_template", help="Path to the spec template file") create_spec_parser.add_argument("--output", help="Output file path (defaults to stdout if not specified)") + create_spec_parser.add_argument("--var", action="append", help="Set template variable value (format: key=value)") # cxk mcp mcp_parser = subparsers.add_parser("mcp", help="Manage MCP servers") @@ -59,7 +60,7 @@ async def main(): await handle_init(state) elif args.command == "create-spec": - await handle_create_spec(args.spec_template, args.output) + await handle_create_spec(args.spec_template, args.output, args.var) elif args.command == "mcp": if not args.mcp_command: diff --git a/tests/README.md b/tests/README.md index bc2cdf3..8b9a00b 100644 --- a/tests/README.md +++ b/tests/README.md @@ -3,4 +3,18 @@ ## Running Tests ``` uv run pytest +``` + +## Manual Testing + +``` +uv run cxk.py create-spec tests/templates/spec1.md +``` + +``` +uv run cxk.py create-spec tests/templates/spec1.md --output result.md +``` + +``` +uv run cxk.py create-spec tests/templates/spec1.md --var additional_context=aa --var ticket='{"id":1}' ``` \ No newline at end of file diff --git a/tests/e2e/test_e2e.py b/tests/e2e/test_e2e.py index b38d633..7630398 100644 --- a/tests/e2e/test_e2e.py +++ b/tests/e2e/test_e2e.py @@ -305,7 +305,6 @@ def test_create_spec_with_variables(self, temp_non_git_dir): assert 'weather: {"condition": "sunny", "temp": "75F"}' in result.stdout # Verify rendered template output - assert "Rendered template:" in result.stdout assert "Hello John!" in result.stdout assert "Your age is 25 and you live in New York." in result.stdout assert "Today's weather is sunny with temperature 75F." in result.stdout @@ -342,7 +341,6 @@ def test_create_spec_relative_path(self, temp_non_git_dir): assert "username: testuser" in result.stdout # Verify rendered template output - assert "Rendered template:" in result.stdout assert "Hello testuser!" in result.stdout def test_create_spec_file_not_found(self, temp_non_git_dir): @@ -442,3 +440,144 @@ def test_create_spec_stdout_vs_file_output(self, temp_non_git_dir): file_content = output_file.read_text().strip() assert stdout_rendered == file_content assert "Hello John! You are 25 years old." in file_content + + def test_create_spec_with_var_override_single(self, temp_non_git_dir): + """Test create-spec with single --var override.""" + # Create a test template with variables + template_content = "Hello {{ name }}! You are {{ age }} years old." + template_file = temp_non_git_dir / "var_override_template.j2" + template_file.write_text(template_content) + + # Run create-spec command with --var override + result = self.run_cli( + ["create-spec", str(template_file), "--var", "name=Alice"], + use_test_runner=True + ) + + assert result.returncode == 0 + + # Verify rendered template output + assert "Hello Alice! You are 25 years old." in result.stdout + + def test_create_spec_with_var_override_multiple(self, temp_non_git_dir): + """Test create-spec with multiple --var overrides.""" + # Create a test template with variables + template_content = "Hello {{ name }}! You are {{ age }} years old and live in {{ city }}." + template_file = temp_non_git_dir / "multiple_var_override_template.j2" + template_file.write_text(template_content) + + # Run create-spec command with multiple --var overrides + result = self.run_cli( + ["create-spec", str(template_file), "--var", "name=Bob", "--var", "city=Boston"], + use_test_runner=True + ) + + assert result.returncode == 0 + + # Verify rendered template output + assert "Hello Bob! You are 25 years old and live in Boston." in result.stdout + + def test_create_spec_with_var_override_all_variables(self, temp_non_git_dir): + """Test create-spec with all variables provided via --var (no interactive prompts).""" + # Create a test template with variables + template_content = "{{ greeting }} {{ name }}! Your score is {{ score }}." + template_file = temp_non_git_dir / "all_var_override_template.j2" + template_file.write_text(template_content) + + # Run create-spec command with all variables provided + result = self.run_cli( + [ + "create-spec", str(template_file), + "--var", "greeting=Hi", + "--var", "name=Charlie", + "--var", "score=100" + ], + use_test_runner=True + ) + + assert result.returncode == 0 + + # Verify rendered template output + assert "Hi Charlie! Your score is 100." in result.stdout + + def test_create_spec_with_var_override_json_value(self, temp_non_git_dir): + """Test create-spec with --var containing JSON value.""" + # Create a test template with JSON variable + template_content = "User: {{ user.name }}, Email: {{ user.email }}" + template_file = temp_non_git_dir / "json_var_override_template.j2" + template_file.write_text(template_content) + + # Run create-spec command with JSON --var + json_value = '{"name": "Dave", "email": "dave@example.com"}' + result = self.run_cli( + ["create-spec", str(template_file), "--var", f"user={json_value}"], + use_test_runner=True + ) + + assert result.returncode == 0 + + # Verify rendered template output + assert "User: Dave, Email: dave@example.com" in result.stdout + + def test_create_spec_with_var_invalid_format(self, temp_non_git_dir): + """Test create-spec with invalid --var format.""" + # Create a test template + template_content = "Hello {{ name }}!" + template_file = temp_non_git_dir / "invalid_var_format_template.j2" + template_file.write_text(template_content) + + # Run create-spec command with invalid --var format (missing =) + result = self.run_cli( + ["create-spec", str(template_file), "--var", "invalid_format"], + use_test_runner=True + ) + + assert result.returncode != 0 + assert "Error: Invalid variable format 'invalid_format'. Use KEY=VALUE format." in result.stderr + + def test_create_spec_with_var_equals_in_value(self, temp_non_git_dir): + """Test create-spec with --var value containing equals sign.""" + # Create a test template + template_content = "Equation: {{ equation }}" + template_file = temp_non_git_dir / "equals_in_value_template.j2" + template_file.write_text(template_content) + + # Run create-spec command with --var value containing equals + result = self.run_cli( + ["create-spec", str(template_file), "--var", "equation=x=y+z"], + use_test_runner=True + ) + + assert result.returncode == 0 + + # Verify rendered template output + assert "Equation: x=y+z" in result.stdout + + def test_create_spec_with_var_and_output_file(self, temp_non_git_dir): + """Test create-spec with --var and --output together.""" + # Create a test template + template_content = "Project: {{ project }}, Version: {{ version }}" + template_file = temp_non_git_dir / "var_and_output_template.j2" + template_file.write_text(template_content) + + # Define output file + output_file = temp_non_git_dir / "var_output.md" + + # Run create-spec command with both --var and --output + result = self.run_cli( + [ + "create-spec", str(template_file), + "--var", "project=MyApp", + "--var", "version=1.0.0", + "--output", str(output_file) + ], + use_test_runner=True + ) + + assert result.returncode == 0 + assert f"Rendered template saved to: {output_file}" in result.stdout + + # Verify file was created and contains expected content + assert output_file.exists() + content = output_file.read_text() + assert "Project: MyApp, Version: 1.0.0" in content