Skip to content

Integrate Inja Code Generation - #5

Open
cerupcat wants to merge 29 commits into
rive-app:mainfrom
Whatnot-Inc:wn/codegen
Open

Integrate Inja Code Generation#5
cerupcat wants to merge 29 commits into
rive-app:mainfrom
Whatnot-Inc:wn/codegen

Conversation

@cerupcat

@cerupcat cerupcat commented Oct 21, 2025

Copy link
Copy Markdown

Integrate Inja Template Engine

Overview

This PR adds support for the Inja template engine as an alternative to Mustache, providing more powerful templating capabilities for code generation while maintaining full backward compatibility with existing Mustache templates.

Motivation

Mustache templating is limited in terms of logic and requires the data source to provide granular details (e.g., array size counts, whether a property exists, last element detection). Inja is a modern C++ templating language that provides more control flow and expressions without requiring the data source to be overly detailed.

Key Features

1. Dual Template Engine Support

  • Added -e, --engine flag to choose between mustache (default) and inja
  • Full backward compatibility - existing Mustache templates continue to work
  • New Inja template engine with advanced features:
    • Conditional logic: {% if condition %}
    • Loops with context: {% for item in items %} with loop.is_first, loop.is_last
    • Built-in functions: length(), upper(), lower(), etc.
    • Template includes: {% include "partial.inja" %}

2. Enhanced Metadata & Relationships

  • Added relationship information to track:
    • Default artboard, state machine, and view model chains
    • Whether elements are defaults
    • Original file names for proper naming
  • Improved null safety with comprehensive null pointer checks throughout the codebase

3. Private Element Filtering

  • New --ignore-private flag to exclude elements based on naming conventions:
    • Elements starting with _ (underscore)
    • Elements starting with internal (case-insensitive)
    • Elements starting with private (case-insensitive)
  • Applies to artboards, animations, state machines, view models, and properties

4. Modular Swift Templates

  • Example modular Swift template structure using Inja's {% include %} feature
  • Demonstrates separation of concerns with partials for:
    • Header documentation
    • Imports
    • Metadata structs
    • Properties and enums
    • Initializers
    • Type-safe switching extensions

5. Template Organization

  • Reorganized templates into subdirectories:
    • templates/mustache/ - Mustache templates
    • templates/inja/ - Inja templates
    • templates/custom/Swift/ - Modular Swift template example

Changes Summary

37 files changed, 32,265 insertions(+), 63 deletions(-)

Core Changes

  • src/main.cpp (+726 lines):
    • Integrated Inja template engine alongside Mustache
    • Added TemplateEngine enum and rendering logic
    • Enhanced metadata with relationship tracking
    • Added shouldIncludeElement() for private filtering
    • Comprehensive null pointer safety improvements

Template Infrastructure

  • external/inja.hpp (+3,075 lines): Inja template engine library
  • external/nlohmann/json.hpp (+25,677 lines): JSON library for Inja

Templates & Examples

  • templates/inja/: New Inja versions of dart, json, and viewmodel templates
  • templates/custom/Swift/: 15 modular Swift template files demonstrating advanced Inja features
  • templates/mustache/: Moved existing Mustache templates to subdirectory

Testing & Documentation

  • test/: Added 4 new tests for Inja templates (7 total tests, all passing)
  • examples/example.sh: Updated with Inja examples
  • README.md: Comprehensive documentation of new features

Usage Examples

Basic Inja Template Usage

# Generate code using Inja template
./rive_code_generator -i samples/rating.riv -o output.swift \
  -t templates/custom/Swift/swift_example.inja -e inja

With Private Filtering

# Exclude private elements
./rive_code_generator -i samples/ -o output.dart \
  -t templates/inja/dart_template.inja -e inja --ignore-private

Modular Templates with Includes

The Swift templates demonstrate using includes for modular organization:

{% for file in riv_files %}
{% include "templates/custom/Swift/_header.inja" %}
{% include "templates/custom/Swift/_imports.inja" %}

// MARK: - {{ file.riv_pascal_case }} ViewModel
{% if length(file.view_models) > 0 %}
{% include "templates/custom/Swift/_viewmodel_with_databinding.inja" %}
{% else %}
{% include "templates/custom/Swift/_viewmodel_no_databinding.inja" %}
{% endif %}
{% endfor %}

Testing

All 7 tests pass:

  • ✅ Help text generation
  • ✅ JSON output (Mustache)
  • ✅ Dart output (Mustache)
  • ✅ Swift output (Inja) - all files
  • ✅ Swift output (Inja) - single file
  • ✅ JSON output (Inja)
  • ✅ Dart output (Inja)

Backward Compatibility

  • ✅ Existing Mustache templates work unchanged
  • ✅ Default behavior remains Mustache
  • ✅ No breaking changes to existing functionality

Additional Improvements

  • Fixed null pointer crashes in view model property access
  • Fixed null pointer crashes in enum property handling
  • Fixed null pointer crashes in nested artboard instances
  • Added premake5 binary for easier builds
  • Updated rive-runtime submodule

Note: This implementation maintains full compatibility with upstream while adding powerful new templating capabilities that don't require data source modifications.

@cerupcat cerupcat changed the title Wn/codegen Integrate Inja Code Generation Oct 23, 2025
@cerupcat
cerupcat changed the base branch from add-data-binding to main October 30, 2025 21:05
Seth Sandler added 5 commits November 7, 2025 13:15
…nV3.riv. The root cause was missing null pointer checks in three places:

1. Nested ViewModel Property Access (main.cpp:691-693)
Problem: Chained calls viewModel->createInstance()->propertyViewModel() without null checks
Fix: Added null checks for createInstance(), propertyViewModel(), instance(), and viewModel()
2. Enum Property Access (main.cpp:730-736)
Problem: Chained calls accessing enum properties without null checks
Fix: Added null checks for createViewModelInstance(), propertyValue(), and all subsequent property accesses
3. Default Value Property Access (main.cpp:786-818)
Problem: Accessing property values without null checks for boolean, number, string, and color types
Fix: Added null checks for each property value access
4. Nested Artboard Instance Access (main.cpp:525)
Problem: nested->artboardInstance() could return null causing crash during recursion
Fix: Added null check before recursively calling getNestedTextValueRunPathsFromArtboard()
5. Default Artboard Instance Access (main.cpp:882)
Problem: defaultArtboard->instance()->defaultStateMachine() without null check on instance()
Fix: Added null check for instance() before accessing defaultStateMachine()
Resolved conflicts:
- build/build.sh: Kept template paths with mustache/ subdirectory structure
- src/main.cpp: Kept Inja integration and metadata features
- Removed duplicate templates/viewmodel_template.mustache (we have templates/mustache/viewmodel_template.mustache)
@cerupcat
cerupcat marked this pull request as ready for review January 6, 2026 00:47
cerupcat and others added 9 commits April 19, 2026 14:33
Adds a --lint mode to the code generator that validates .riv files
against asset format and size rules, and checks that every artboard
has at least one state machine.

New CLI flags:
  --lint                  Enable lint validation; exits non-zero on violations
  --allowed-extensions    Comma-separated list of allowed image formats (default: webp)
  --max-asset-size        Max embedded asset size in bytes (default: 204800 = 200 KB)
  --lint-format           Output format: text (default) or json

-o/--output is now optional; omitting it with --lint runs lint-only
with no code generation.

Implementation details:
- LintingFactory subclasses NoOpFactory and overrides decodeImage() to
  capture embedded image byte sizes during File::import()
- Embedded vs CDN-hosted assets are distinguished via cdnUuidStr();
  only embedded images have a corresponding factory entry
- embeddedByteSize added to AssetInfo and populated via factory size
  correlation in getAssetsFromFile()
- openFile() now accepts a Factory& instead of using a static NoOpFactory,
  enabling per-call factory instances
- runLint() checks format (fileExtension()), size (embeddedByteSize), and
  state machine presence against LintConfig
- outputLintViolations() supports both text (stderr) and json (stdout) output

Assisted by AI

Co-Authored-By: Claude <noreply@anthropic.com>
…d add --lint flag

Extracts the 1800-line main.cpp into:
- types.h: all shared enums and structs (RiveFileData, LintConfig, LintViolation, etc.)
- string_utils.h: inline case-conversion and sanitization helpers
- rive_file_parser.h/cpp: file discovery, Rive runtime parsing, asset/artboard extraction
- lint.h/cpp: LintingFactory, runLint(), outputLintViolations()
- template_renderer.h/cpp: loadTemplate(), renderTemplate(), Mustache/Inja data builders
- main.cpp: lean CLI setup and top-level flow

Adds --lint flag with configurable --allowed-extensions, --max-asset-size, and
--lint-format (text/json). Can run lint-only (no -o) or lint + codegen combined.

Assisted by AI

Co-Authored-By: Claude <noreply@anthropic.com>
…t.inja template

Adds fields that the template expects but were never populated:
- file.has_view_model
- artboard.is_default / artboard.has_view_model
- vm.is_first
- prop.last

Assisted by AI

Co-Authored-By: Claude <noreply@anthropic.com>
asset->fileExtension() returns the source filename extension set at import
time (e.g. "png"), not the actual embedded format. Re-encoding to webp in
the Rive editor changes the bytes but not the metadata, causing false
positive lint errors.

LintingFactory now sniffs the first 12 bytes of each embedded image in
decodeImage() and stores the detected format alongside the byte size.
getAssetsFromFile uses the detected extension for embedded images, falling
back to asset->fileExtension() only for unrecognized formats.

Matches rive-runtime's own Bitmap::RecognizeImageFormat() approach.

Assisted by AI

Co-Authored-By: Claude <noreply@anthropic.com>
…nced assets

The previous approach correlated decodeImage calls with file->assets() by index,
which breaks when referenced assets (no in-band bytes) are interspersed with
embedded ones — the index shifts and the wrong asset gets the embedded info.

Replace LintingFactory (decodeImage override) with LintingAssetLoader (FileAssetLoader):
- loadContents receives (asset, inBandBytes, factory) for every asset
- inBandBytes.size() > 0 → embedded; captures size + magic-byte format by asset name
- inBandBytes.size() == 0 → referenced or CDN; skipped
- Returns false so normal decode proceeds via NoOpFactory

getAssetsFromFile now looks up asset info by name (unordered_map) instead of index,
which is correct regardless of asset ordering in the file.

lint.cpp already guards format/size checks with embeddedByteSize > 0; referenced
and CDN assets now correctly have embeddedByteSize == 0 in all cases.

Assisted by AI

Co-Authored-By: Claude <noreply@anthropic.com>
…n map insert

Assisted by AI

Co-Authored-By: Claude <noreply@anthropic.com>
…nary update)

Assisted by AI

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Codex <codex@openai.com>
cerupcat and others added 2 commits July 21, 2026 12:12
Codegen disambiguates duplicate asset names for identifier generation (a
second "SF-Pro" becomes "SF-ProU1"), but runtimes resolve referenced assets
by the file's real "<name>-<assetId>" key (the exported uniqueFilename).
Templates that bake asset keys into generated code — like rive-assets'
fontAssetIds/fontAssetNames — had only the uniquified name available, so
generated font keys never matched what the runtime looks up and text bound
to those fonts rendered without glyphs.

asset_name and the case variants stay uniquified for identifiers;
asset_raw_name carries the name exactly as authored in the .riv, in both
the inja and mustache renderers.

Assisted by AI

Co-Authored-By: Claude <noreply@anthropic.com>
Expose raw (non-uniquified) asset names to templates as asset_raw_name
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants