# spry-mkcomponent Tool Design ## Overview A new tool called `spry-mkcomponent` that processes Vala component files by injecting HTML markup into the `markup` property getter. This separates the HTML template from the Vala code, making components easier to maintain. ## Tool Behavior ### Input/Output ``` Input: UserContentComponent.vala (Vala source with empty markup getter) UserContentComponent.vala.html (HTML template - auto-discovered) Output: UserContentComponent.vala (Generated Vala with markup getter populated) ``` ### Command Line Interface ```bash spry-mkcomponent [OPTIONS] Options: -o, --output FILE Output file path (default: input filename in build directory) -h, --html FILE HTML template file (default: .html) -v, --version Show version information --help Show help message ``` ### Processing Logic 1. Read the input `.vala` file 2. Locate the HTML template file: - If `-h/--html` is specified, use that path - Otherwise, append `.html` to the input vala file path 3. Parse the Vala file to find the `markup` property with an empty getter: - Pattern: `public override string markup { get; }` - The property MUST have exactly this form (empty getter) 4. Read the HTML template content 5. Generate the output by replacing the empty getter with a getter that returns the HTML as a verbatim string 6. Write the output file ### Error Conditions | Condition | Error Message | |-----------|---------------| | Input file not found | `Error: Input file 'PATH' does not exist` | | HTML file not found | `Error: HTML template file 'PATH' does not exist` | | No markup property found | `Error: No 'public override string markup' property found in 'PATH'` | | Markup property has non-empty getter | `Error: The markup property in 'PATH' already has a getter defined. Expected empty getter: { get; }` | ## Example Transformation ### Input: [`UserContentComponent.vala`](examples/UserContentComponent.vala) ```vala class UserContentComponent : Component { public override string markup { get; } private HttpContext http_context = inject(); public async override void handle_action(string action) throws Error { this["message"].text_content = http_context.request.query_params.get_any_or_default("message") ?? "No message provided!"; this["action"].text_content = action; } } ``` ### Input: [`UserContentComponent.vala.html`](examples/UserContentComponent.vala.html) ```html

You said: (via )

``` ### Output: `UserContentComponent.vala` (generated) ```vala class UserContentComponent : Component { public override string markup { get { return """

You said: (via )

"""; }} private HttpContext http_context = inject(); public async override void handle_action(string action) throws Error { this["message"].text_content = http_context.request.query_params.get_any_or_default("message") ?? "No message provided!"; this["action"].text_content = action; } } ``` ## Meson Integration: generator() vs custom_target() ### generator() **Best for:** Multiple files with the same transformation pattern. **Pros:** - Reusable - define once, use for many files - Cleaner syntax when processing multiple components - Meson automatically handles output file naming **Cons:** - Less flexibility per-file (same command arguments for all files) - Output filename is automatically determined **Example Usage:** ```meson # Define the generator once spry_mkcomponent_gen = generator(spry_mkcomponent, output: '@BASENAME@.vala', arguments: ['@INPUT@', '@OUTPUT@'] ) # Process multiple component files generated_components = spry_mkcomponent_gen.process(files( 'Components/UserContentComponent.vala', 'Components/LoginFormComponent.vala', 'Components/NavSidebarComponent.vala', )) # Use in executable executable('myapp', sources: [other_sources, generated_components], dependencies: [spry_dep] ) ``` ### custom_target() **Best for:** Single files or when you need per-file customization. **Pros:** - Full control over command arguments per file - Can specify custom output filename - Better for complex dependencies **Cons:** - More verbose for multiple files - Each file needs its own custom_target declaration **Example Usage:** ```meson # Single component with custom HTML path user_content_component = custom_target('user-content-component', input: 'Components/UserContentComponent.vala', output: 'UserContentComponent.vala', command: [spry_mkcomponent, '@INPUT@', '@OUTPUT@'] ) # With explicit HTML file login_form_component = custom_target('login-form-component', input: 'Components/LoginFormComponent.vala', output: 'LoginFormComponent.vala', command: [spry_mkcomponent, '@INPUT@', '@OUTPUT@', '--html', 'Templates/LoginForm.html'] ) executable('myapp', sources: [user_content_component, login_form_component, other_sources], dependencies: [spry_dep] ) ``` ### Recommendation For this use case, I recommend **`generator()`** as the primary pattern because: 1. Components typically follow the same convention (`.vala` + `.vala.html`) 2. Most use cases will process multiple component files 3. The pattern matches how `spry-mkssr` is used in the demo build However, the tool should support both patterns by: - Accepting input/output as positional arguments (for generator) - Supporting `--html` flag for explicit HTML file paths (for custom_target when needed) ## Implementation Architecture ```mermaid flowchart TD A[Command Line Args] --> B[Argument Parser] B --> C[File Discovery] C --> D{HTML file specified?} D -->|Yes| E[Use specified HTML path] D -->|No| F[Append .html to vala path] E --> G[Read Input Files] F --> G G --> H[Parse Vala Source] H --> I{Find markup property} I -->|Found| J{Is getter empty?} I -->|Not Found| K[Error: No markup property] J -->|Yes| L[Generate Output] J -->|No| M[Error: Getter not empty] L --> N[Write Output File] N --> O[Success] ``` ## File Structure ``` tools/ ├── meson.build └── spry-mkcomponent/ ├── meson.build └── spry-mkcomponent.vala ``` ## Dependencies The tool will need: - `glib-2.0` - Core GLib functionality - `gobject-2.0` - GObject type system - `gio-2.0` - File I/O operations Note: Unlike `spry-mkssr`, this tool does NOT need `invercargill`, `astralis`, `inversion`, or compression libraries since it only does text processing. ## Implementation Tasks 1. Create `tools/spry-mkcomponent/` directory structure 2. Implement `spry-mkcomponent.vala` with: - Command-line argument parsing using `OptionContext` - File reading/writing using `gio` - Vala source parsing to find `markup` property - HTML template injection - Error handling with descriptive messages 3. Create `tools/spry-mkcomponent/meson.build` 4. Update `tools/meson.build` to include the new subdirectory 5. Add example usage documentation ## Parsing Strategy The Vala source parsing will use a simple regex-based approach: 1. Search for pattern: `public\s+override\s+string\s+markup\s*\{\s*get\s*;\s*\}` 2. Replace with the generated getter containing the HTML This is sufficient because: - The pattern is well-defined and consistent - Full Vala parsing would add unnecessary complexity - The tool validates that the property exists and has an empty getter ### Edge Cases to Handle - Multiple spaces/tabs between keywords - Different indentation styles - Windows vs Unix line endings (normalize to Unix) - Preserve original indentation in output - Escape triple quotes in HTML content (rare but possible)