-
Notifications
You must be signed in to change notification settings - Fork 0
Add create-spec cli pipe support #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,36 +7,56 @@ | |||||||||
| from prompt import collect_var_value | ||||||||||
|
|
||||||||||
|
|
||||||||||
| async def handle_create_spec(spec_template: str, output_file: str | None = None, var_overrides: list[str] | None = None, verbose: bool = False): | ||||||||||
| """Handle the create-spec command""" | ||||||||||
|
|
||||||||||
| # Configure logging based on verbose flag | ||||||||||
| async def handle_create_spec( | ||||||||||
| spec_template: str | None, | ||||||||||
| output_file: str | None = None, | ||||||||||
| var_overrides: list[str] | None = None, | ||||||||||
| verbose: bool = False, | ||||||||||
| ): | ||||||||||
| log_level = logging.DEBUG if verbose else logging.WARNING | ||||||||||
| logging.basicConfig(level=log_level, format='%(message)s', stream=sys.stdout, force=True) | ||||||||||
| logging.basicConfig(level=log_level, format="%(message)s", force=True) | ||||||||||
|
|
||||||||||
| # Detect piped input (stdin not a TTY) and ensure there's data before using it | ||||||||||
| stdin_piped = not sys.stdin.isatty() | ||||||||||
|
|
||||||||||
| # Determine template source | ||||||||||
| template_engine: TemplateEngine | ||||||||||
| if spec_template: | ||||||||||
| # Resolve relative paths against current working directory | ||||||||||
| template_path = os.path.abspath(spec_template) | ||||||||||
|
|
||||||||||
| # Resolve relative paths against current working directory | ||||||||||
| template_path = os.path.abspath(spec_template) | ||||||||||
| # Check if template file exists | ||||||||||
| if not os.path.exists(template_path): | ||||||||||
| logging.error(f"Error: Template file '{spec_template}' not found") | ||||||||||
| sys.exit(1) | ||||||||||
|
|
||||||||||
| # Check if template file exists | ||||||||||
| if not os.path.exists(template_path): | ||||||||||
| print(f"Error: Template file '{spec_template}' not found", file=sys.stderr) | ||||||||||
| template_engine = TemplateEngine.from_file(template_path) | ||||||||||
| elif stdin_piped: | ||||||||||
| try: | ||||||||||
| template_str = sys.stdin.read() | ||||||||||
| except Exception as e: | ||||||||||
| logging.error(f"Error: Failed to read from stdin: {e}") | ||||||||||
| sys.exit(1) | ||||||||||
| if not template_str: | ||||||||||
| logging.error("Error: No data received on stdin for template") | ||||||||||
| sys.exit(1) | ||||||||||
|
|
||||||||||
| template_engine = TemplateEngine.from_string(template_str) | ||||||||||
| else: | ||||||||||
| logging.error("Error: Missing spec_template argument (or provide template via stdin)") | ||||||||||
| sys.exit(1) | ||||||||||
|
|
||||||||||
| try: | ||||||||||
| # Initialize template engine | ||||||||||
| template_engine = TemplateEngine(template_path) | ||||||||||
|
|
||||||||||
| # 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) | ||||||||||
| if "=" not in var_override: | ||||||||||
| logging.error(f"Error: Invalid variable format '{var_override}'. Use KEY=VALUE format.") | ||||||||||
| sys.exit(1) | ||||||||||
| key, value = var_override.split('=', 1) | ||||||||||
| key, value = var_override.split("=", 1) | ||||||||||
| provided_vars[key] = value | ||||||||||
|
|
||||||||||
| # Collect values for each variable | ||||||||||
|
|
@@ -46,13 +66,13 @@ async def handle_create_spec(spec_template: str, output_file: str | None = None, | |||||||||
| for var in sorted(variables): | ||||||||||
| if var in provided_vars: | ||||||||||
| raw_value = provided_vars[var] | ||||||||||
| logging.info(f" {var}: {raw_value}") | ||||||||||
|
|
||||||||||
| else: | ||||||||||
| raw_value = await collect_var_value(var) | ||||||||||
| logging.info(f" {var}: {raw_value}") | ||||||||||
| logging.info(f" {var}: {raw_value}") | ||||||||||
|
|
||||||||||
|
||||||||||
| else: | |
| raw_value = await collect_var_value(var) | |
| logging.info(f" {var}: {raw_value}") |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -23,7 +23,7 @@ async def main(): | |||||||||
|
|
||||||||||
| # cxk create-spec [spec-template] | ||||||||||
| 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("spec_template", nargs=argparse.OPTIONAL, help="Path to the spec template file") | ||||||||||
|
||||||||||
| create_spec_parser.add_argument("spec_template", nargs=argparse.OPTIONAL, help="Path to the spec template file") | |
| create_spec_parser.add_argument("spec_template", nargs='?', help="Path to the spec template file") |
Copilot
AI
Aug 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The constant argparse.OPTIONAL does not exist. Use nargs='?' instead to make the argument optional.
| create_spec_parser.add_argument("spec_template", nargs=argparse.OPTIONAL, help="Path to the spec template file") | |
| create_spec_parser.add_argument("spec_template", nargs='?', help="Path to the spec template file") |
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,36 +1,126 @@ | ||||||||||||
| import os | ||||||||||||
| from pathlib import Path | ||||||||||||
| from typing import Any | ||||||||||||
|
|
||||||||||||
| from jinja2 import Environment, FileSystemLoader, meta, select_autoescape | ||||||||||||
| from jinja2 import Environment, FileSystemLoader, Template, meta, select_autoescape | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| class TemplateParseError(Exception): | ||||||||||||
| """Raised when template parsing fails""" | ||||||||||||
|
|
||||||||||||
| pass | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| class TemplateEngine: | ||||||||||||
| """Abstract away the jinja2 template engine""" | ||||||||||||
| """Abstract away the jinja2 template engine with clean factory methods""" | ||||||||||||
|
|
||||||||||||
| def __init__( | ||||||||||||
| self, env: Environment, template: Template, source_path: Path | None = None, source_string: str | None = None | ||||||||||||
| ): | ||||||||||||
| """Private constructor - use from_file() or from_string() instead""" | ||||||||||||
|
||||||||||||
| """Private constructor - use from_file() or from_string() instead""" | |
| """Constructor for TemplateEngine. Direct instantiation is not recommended; use from_file() or from_string() instead.""" |
Copilot
AI
Aug 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message should be more descriptive about the internal state that caused this issue, such as 'Internal error: TemplateEngine has no source content (neither file path nor string)'.
| raise AssertionError("No template source available") | |
| raise AssertionError( | |
| f"Internal error: TemplateEngine has no source content (neither file path nor string). " | |
| f"_source_path={self._source_path!r}, _source_string={self._source_string!r}" | |
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logging statement for provided variables was removed but the variable assignment remains without logging. This creates inconsistent behavior where user-provided variables aren't logged while prompted variables are logged on line 75.