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.
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)
spry-mkcomponent [OPTIONS] <INPUT_VALA_FILE>
Options:
-o, --output FILE Output file path (default: input filename in build directory)
-h, --html FILE HTML template file (default: <input_vala>.html)
-v, --version Show version information
--help Show help message
.vala file-h/--html is specified, use that path.html to the input vala file pathmarkup property with an empty getter:
public override string markup { get; }| 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; } |
UserContentComponent.valaclass UserContentComponent : Component {
public override string markup { get; }
private HttpContext http_context = inject<HttpContext>();
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;
}
}
UserContentComponent.vala.html<p>You said: <strong sid="message"></strong> <em>(via <span sid="action"></span>)</em></p>
UserContentComponent.vala (generated)class UserContentComponent : Component {
public override string markup { get {
return """
<p>You said: <strong sid="message"></strong> <em>(via <span sid="action"></span>)</em></p>
""";
}}
private HttpContext http_context = inject<HttpContext>();
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;
}
}
Best for: Multiple files with the same transformation pattern.
Pros:
Cons:
Example Usage:
# 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]
)
Best for: Single files or when you need per-file customization.
Pros:
Cons:
Example Usage:
# 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]
)
For this use case, I recommend generator() as the primary pattern because:
.vala + .vala.html)spry-mkssr is used in the demo buildHowever, the tool should support both patterns by:
--html flag for explicit HTML file paths (for custom_target when needed)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]
tools/
├── meson.build
└── spry-mkcomponent/
├── meson.build
└── spry-mkcomponent.vala
The tool will need:
glib-2.0 - Core GLib functionalitygobject-2.0 - GObject type systemgio-2.0 - File I/O operationsNote: Unlike spry-mkssr, this tool does NOT need invercargill, astralis, inversion, or compression libraries since it only does text processing.
tools/spry-mkcomponent/ directory structurespry-mkcomponent.vala with:
OptionContextgiomarkup propertytools/spry-mkcomponent/meson.buildtools/meson.build to include the new subdirectoryThe Vala source parsing will use a simple regex-based approach:
public\s+override\s+string\s+markup\s*\{\s*get\s*;\s*\}This is sufficient because: