Add computed variables support (packagePath derivation) #19

Closed
opened 2026-01-03 23:39:40 +00:00 by KofTwentyTwo · 2 comments
KofTwentyTwo commented 2026-01-03 23:39:40 +00:00 (Migrated from github.com)

Part of #12

User Story

As a template author, I want to define computed variables in the manifest so that derived values (like packagePath from packageName) are automatically available without user input.

Design

Computed Variables Section

Add computed section to template manifest:

prompts:
  - name: packageName
    message: "Java package"
    default: "com.example.myapp"

computed:
  packagePath: "${packageName.replace('.', '/')}"
  packagePrefix: "${packageName.substring(0, packageName.lastIndexOf('.'))}"
  simpleClassName: "${appName.substring(0,1).toUpperCase()}${appName.substring(1)}App"

Expression Engine

Use Velocity for computed expressions:

  • Access prompt values: $packageName
  • Call String methods: $packageName.replace('.', '/')
  • Chain operations: $appName.substring(0,1).toUpperCase()

Evaluation Order

  1. Collect prompt values from user
  2. Evaluate computed variables in order (top to bottom)
  3. Add all variables to Velocity context
  4. Render templates

Circular Dependency Detection

Computed variables cannot reference each other circularly. Engine validates the dependency graph before evaluation.

Files to Create/Modify

File Action Description
TemplateManifest.java Modify Add Map<String, String> computed field
ComputedVariableEvaluator.java Create Evaluates computed expressions using Velocity
TemplateEngine.java Modify Integrate computed variable evaluation
InitCommand.java Modify Pass computed vars to engine

Implementation Tasks

  • Add computed field to TemplateManifest record
  • Create ComputedVariableEvaluator class
  • Implement expression parsing with Velocity
  • Add dependency graph validation (detect cycles)
  • Evaluate computed variables after prompt collection
  • Merge computed variables into template context
  • Add error handling for invalid expressions
  • Update manifest schema documentation
  • Add unit tests for computed variable evaluation
  • Test with complex expressions (method chains)

Acceptance Criteria

  • packagePath derives correctly from packageName
  • Method calls work in expressions (.replace(), .substring())
  • Computed variables available in template rendering
  • Circular dependency detected and reported with clear error
  • Invalid expressions produce helpful error messages
  • Computed variables can reference other computed variables (in order)
Part of #12 ## User Story As a **template author**, I want to define computed variables in the manifest so that derived values (like `packagePath` from `packageName`) are automatically available without user input. ## Design ### Computed Variables Section Add `computed` section to template manifest: ```yaml prompts: - name: packageName message: "Java package" default: "com.example.myapp" computed: packagePath: "${packageName.replace('.', '/')}" packagePrefix: "${packageName.substring(0, packageName.lastIndexOf('.'))}" simpleClassName: "${appName.substring(0,1).toUpperCase()}${appName.substring(1)}App" ``` ### Expression Engine Use Velocity for computed expressions: - Access prompt values: `$packageName` - Call String methods: `$packageName.replace('.', '/')` - Chain operations: `$appName.substring(0,1).toUpperCase()` ### Evaluation Order 1. Collect prompt values from user 2. Evaluate computed variables in order (top to bottom) 3. Add all variables to Velocity context 4. Render templates ### Circular Dependency Detection Computed variables cannot reference each other circularly. Engine validates the dependency graph before evaluation. ## Files to Create/Modify | File | Action | Description | |------|--------|-------------| | `TemplateManifest.java` | Modify | Add `Map<String, String> computed` field | | `ComputedVariableEvaluator.java` | Create | Evaluates computed expressions using Velocity | | `TemplateEngine.java` | Modify | Integrate computed variable evaluation | | `InitCommand.java` | Modify | Pass computed vars to engine | ## Implementation Tasks - [ ] Add `computed` field to `TemplateManifest` record - [ ] Create `ComputedVariableEvaluator` class - [ ] Implement expression parsing with Velocity - [ ] Add dependency graph validation (detect cycles) - [ ] Evaluate computed variables after prompt collection - [ ] Merge computed variables into template context - [ ] Add error handling for invalid expressions - [ ] Update manifest schema documentation - [ ] Add unit tests for computed variable evaluation - [ ] Test with complex expressions (method chains) ## Acceptance Criteria - [ ] `packagePath` derives correctly from `packageName` - [ ] Method calls work in expressions (`.replace()`, `.substring()`) - [ ] Computed variables available in template rendering - [ ] Circular dependency detected and reported with clear error - [ ] Invalid expressions produce helpful error messages - [ ] Computed variables can reference other computed variables (in order)
KofTwentyTwo commented 2026-01-04 01:29:26 +00:00 (Migrated from github.com)

Implementation Approach

Design Decisions

  1. Evaluate in order: Computed variables are processed in manifest declaration order. If a computed var references another that hasn't been computed yet, Velocity throws TemplateRenderException. This implicitly catches circular dependencies without DAG complexity.

  2. Manifest storage: Add computed field to TemplateManifest record. Computed variables are defined alongside prompts in manifest.yaml.

Manifest Schema

computed:
  - name: packagePath
    expression: "$str.replace($packageName, '.', '/')"
  - name: appClassName  
    expression: "$str.pascal($appName)"

Implementation

  1. Add List<ComputedVariable> to TemplateManifest
  2. Create ComputedVariable record with name and expression fields
  3. Add evaluateComputed() method to TemplateEngine
  4. Call evaluation after prompts collected, before rendering

Files to Modify

  • TemplateManifest.java - Add computed field
  • ComputedVariable.java - NEW record
  • TemplateEngine.java - Add evaluateComputed() method
  • InitCommand.java - Call evaluation in workflow

Test Coverage

  • Basic expression evaluation
  • StringTool usage in expressions
  • Variable chaining (computed using other computed)
  • Undefined variable detection (implicit cycle detection)
  • Empty/null handling
## Implementation Approach ### Design Decisions 1. **Evaluate in order**: Computed variables are processed in manifest declaration order. If a computed var references another that hasn't been computed yet, Velocity throws `TemplateRenderException`. This implicitly catches circular dependencies without DAG complexity. 2. **Manifest storage**: Add `computed` field to `TemplateManifest` record. Computed variables are defined alongside prompts in `manifest.yaml`. ### Manifest Schema ```yaml computed: - name: packagePath expression: "$str.replace($packageName, '.', '/')" - name: appClassName expression: "$str.pascal($appName)" ``` ### Implementation 1. Add `List<ComputedVariable>` to `TemplateManifest` 2. Create `ComputedVariable` record with `name` and `expression` fields 3. Add `evaluateComputed()` method to `TemplateEngine` 4. Call evaluation after prompts collected, before rendering ### Files to Modify - `TemplateManifest.java` - Add computed field - `ComputedVariable.java` - NEW record - `TemplateEngine.java` - Add `evaluateComputed()` method - `InitCommand.java` - Call evaluation in workflow ### Test Coverage - Basic expression evaluation - StringTool usage in expressions - Variable chaining (computed using other computed) - Undefined variable detection (implicit cycle detection) - Empty/null handling
KofTwentyTwo commented 2026-01-04 01:36:37 +00:00 (Migrated from github.com)

Implementation Complete

Files Created/Modified

File Change
StringTool.java Added replace() method
ComputedVariable.java NEW - Record for computed variable definitions
TemplateManifest.java Added computed field (List)
TemplatesHub.java Added computed field to TemplateEntry record
TemplateEngine.java Added evaluateComputed() method
InitCommand.java Integrated computed variable evaluation

Test Coverage

  • 9 new tests in ComputedVariableTest.java
  • 7 new tests for StringTool.replace() in StringToolTest.java
  • All 67 qctl-qqq tests pass (86 total project tests)

Design Decisions

  1. Evaluate in order: Computed variables processed in manifest order. Undefined references throw TemplateRenderException (implicit cycle detection).
  2. KISS approach: No DAG validation - simple ordered evaluation catches cycles naturally.

Usage

prompts:
  - name: packageName
    message: "Package name"
    
computed:
  - name: packagePath
    expression: "$str.replace($packageName, '.', '/')"
  - name: appClassName
    expression: "$str.pascal($appName)"
## Implementation Complete ### Files Created/Modified | File | Change | |------|--------| | `StringTool.java` | Added `replace()` method | | `ComputedVariable.java` | NEW - Record for computed variable definitions | | `TemplateManifest.java` | Added `computed` field (List<ComputedVariable>) | | `TemplatesHub.java` | Added `computed` field to TemplateEntry record | | `TemplateEngine.java` | Added `evaluateComputed()` method | | `InitCommand.java` | Integrated computed variable evaluation | ### Test Coverage - 9 new tests in `ComputedVariableTest.java` - 7 new tests for `StringTool.replace()` in `StringToolTest.java` - All 67 qctl-qqq tests pass (86 total project tests) ### Design Decisions 1. **Evaluate in order**: Computed variables processed in manifest order. Undefined references throw `TemplateRenderException` (implicit cycle detection). 2. **KISS approach**: No DAG validation - simple ordered evaluation catches cycles naturally. ### Usage ```yaml prompts: - name: packageName message: "Package name" computed: - name: packagePath expression: "$str.replace($packageName, '.', '/')" - name: appClassName expression: "$str.pascal($appName)" ```
Sign in to join this conversation.