rhales 0.4.0 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/.github/renovate.json5 +52 -0
  3. data/.github/workflows/ci.yml +123 -0
  4. data/.github/workflows/claude-code-review.yml +69 -0
  5. data/.github/workflows/claude.yml +49 -0
  6. data/.github/workflows/code-smells.yml +146 -0
  7. data/.github/workflows/ruby-lint.yml +78 -0
  8. data/.github/workflows/yardoc.yml +126 -0
  9. data/.gitignore +55 -0
  10. data/.pr_agent.toml +63 -0
  11. data/.pre-commit-config.yaml +89 -0
  12. data/.prettierignore +8 -0
  13. data/.prettierrc +38 -0
  14. data/.reek.yml +98 -0
  15. data/.rubocop.yml +428 -0
  16. data/.serena/.gitignore +3 -0
  17. data/.yardopts +56 -0
  18. data/CHANGELOG.md +44 -0
  19. data/CLAUDE.md +1 -1
  20. data/Gemfile +29 -0
  21. data/Gemfile.lock +189 -0
  22. data/README.md +686 -868
  23. data/Rakefile +46 -0
  24. data/debug_context.rb +25 -0
  25. data/demo/rhales-roda-demo/.gitignore +7 -0
  26. data/demo/rhales-roda-demo/Gemfile +32 -0
  27. data/demo/rhales-roda-demo/Gemfile.lock +151 -0
  28. data/demo/rhales-roda-demo/MAIL.md +405 -0
  29. data/demo/rhales-roda-demo/README.md +376 -0
  30. data/demo/rhales-roda-demo/RODA-TEMPLATE-ENGINES.md +192 -0
  31. data/demo/rhales-roda-demo/Rakefile +49 -0
  32. data/demo/rhales-roda-demo/app.rb +325 -0
  33. data/demo/rhales-roda-demo/bin/rackup +26 -0
  34. data/demo/rhales-roda-demo/config.ru +13 -0
  35. data/demo/rhales-roda-demo/db/migrate/001_create_rodauth_tables.rb +266 -0
  36. data/demo/rhales-roda-demo/db/migrate/002_create_rodauth_password_tables.rb +79 -0
  37. data/demo/rhales-roda-demo/db/migrate/003_add_admin_account.rb +68 -0
  38. data/demo/rhales-roda-demo/templates/change_login.rue +31 -0
  39. data/demo/rhales-roda-demo/templates/change_password.rue +36 -0
  40. data/demo/rhales-roda-demo/templates/close_account.rue +31 -0
  41. data/demo/rhales-roda-demo/templates/create_account.rue +40 -0
  42. data/demo/rhales-roda-demo/templates/dashboard.rue +150 -0
  43. data/demo/rhales-roda-demo/templates/home.rue +78 -0
  44. data/demo/rhales-roda-demo/templates/layouts/main.rue +168 -0
  45. data/demo/rhales-roda-demo/templates/login.rue +65 -0
  46. data/demo/rhales-roda-demo/templates/logout.rue +25 -0
  47. data/demo/rhales-roda-demo/templates/reset_password.rue +26 -0
  48. data/demo/rhales-roda-demo/templates/verify_account.rue +27 -0
  49. data/demo/rhales-roda-demo/test_full_output.rb +27 -0
  50. data/demo/rhales-roda-demo/test_simple.rb +24 -0
  51. data/docs/.gitignore +9 -0
  52. data/docs/architecture/data-flow.md +499 -0
  53. data/examples/dashboard-with-charts.rue +271 -0
  54. data/examples/form-with-validation.rue +180 -0
  55. data/examples/simple-page.rue +61 -0
  56. data/examples/vue.rue +136 -0
  57. data/generate-json-schemas.ts +158 -0
  58. data/json_schemer_migration_summary.md +172 -0
  59. data/lib/rhales/adapters/base_auth.rb +2 -0
  60. data/lib/rhales/adapters/base_request.rb +2 -0
  61. data/lib/rhales/adapters/base_session.rb +2 -0
  62. data/lib/rhales/adapters.rb +7 -0
  63. data/lib/rhales/configuration.rb +47 -0
  64. data/lib/rhales/core/context.rb +354 -0
  65. data/lib/rhales/{rue_document.rb → core/rue_document.rb} +56 -38
  66. data/lib/rhales/{template_engine.rb → core/template_engine.rb} +66 -59
  67. data/lib/rhales/{view.rb → core/view.rb} +112 -135
  68. data/lib/rhales/{view_composition.rb → core/view_composition.rb} +78 -8
  69. data/lib/rhales/core.rb +9 -0
  70. data/lib/rhales/errors/hydration_collision_error.rb +2 -0
  71. data/lib/rhales/errors.rb +2 -0
  72. data/lib/rhales/{earliest_injection_detector.rb → hydration/earliest_injection_detector.rb} +4 -0
  73. data/lib/rhales/hydration/hydration_data_aggregator.rb +487 -0
  74. data/lib/rhales/{hydration_endpoint.rb → hydration/hydration_endpoint.rb} +16 -12
  75. data/lib/rhales/{hydration_injector.rb → hydration/hydration_injector.rb} +4 -0
  76. data/lib/rhales/{hydration_registry.rb → hydration/hydration_registry.rb} +2 -0
  77. data/lib/rhales/hydration/hydrator.rb +102 -0
  78. data/lib/rhales/{link_based_injection_detector.rb → hydration/link_based_injection_detector.rb} +4 -0
  79. data/lib/rhales/{mount_point_detector.rb → hydration/mount_point_detector.rb} +4 -0
  80. data/lib/rhales/{safe_injection_validator.rb → hydration/safe_injection_validator.rb} +4 -0
  81. data/lib/rhales/hydration.rb +13 -0
  82. data/lib/rhales/{refinements → integrations/refinements}/require_refinements.rb +3 -1
  83. data/lib/rhales/{tilt.rb → integrations/tilt.rb} +22 -15
  84. data/lib/rhales/integrations.rb +6 -0
  85. data/lib/rhales/middleware/json_responder.rb +191 -0
  86. data/lib/rhales/middleware/schema_validator.rb +300 -0
  87. data/lib/rhales/middleware.rb +6 -0
  88. data/lib/rhales/parsers/handlebars_parser.rb +2 -0
  89. data/lib/rhales/parsers/rue_format_parser.rb +9 -7
  90. data/lib/rhales/parsers.rb +9 -0
  91. data/lib/rhales/{csp.rb → security/csp.rb} +27 -3
  92. data/lib/rhales/utils/json_serializer.rb +114 -0
  93. data/lib/rhales/utils/logging_helpers.rb +75 -0
  94. data/lib/rhales/utils/schema_extractor.rb +132 -0
  95. data/lib/rhales/utils/schema_generator.rb +194 -0
  96. data/lib/rhales/utils.rb +40 -0
  97. data/lib/rhales/version.rb +3 -1
  98. data/lib/rhales.rb +41 -24
  99. data/lib/tasks/rhales_schema.rake +197 -0
  100. data/package.json +10 -0
  101. data/pnpm-lock.yaml +345 -0
  102. data/pnpm-workspace.yaml +2 -0
  103. data/proofs/error_handling.rb +79 -0
  104. data/proofs/expanded_object_inheritance.rb +82 -0
  105. data/proofs/partial_context_scoping_fix.rb +168 -0
  106. data/proofs/ui_context_partial_inheritance.rb +236 -0
  107. data/rhales.gemspec +14 -6
  108. data/schema_vs_data_comparison.md +254 -0
  109. data/test_direct_access.rb +36 -0
  110. metadata +141 -23
  111. data/CLAUDE.locale.txt +0 -7
  112. data/lib/rhales/context.rb +0 -239
  113. data/lib/rhales/hydration_data_aggregator.rb +0 -221
  114. data/lib/rhales/hydrator.rb +0 -141
  115. data/lib/rhales/parsers/handlebars-grammar-review.txt +0 -39
@@ -0,0 +1,376 @@
1
+ # Rhales Roda Demo
2
+
3
+ This demo application showcases the power of Rhales RSFC (Ruby Single File Components) templates integrated with a Roda web application using Rodauth for authentication.
4
+
5
+ ## Features Demonstrated
6
+
7
+ - **RSFC Templates**: Single file components with `<data>`, `<template>`, `<schema>`, and `<logic>` sections
8
+ - **Schema Validation**: Runtime validation of hydration data against Zod schemas
9
+ - **Client-Side Hydration**: Secure data injection with CSP nonce support
10
+ - **Authentication Integration**: Rodauth with custom Rhales adapters
11
+ - **Dynamic Content**: Posts management with CRUD operations
12
+ - **Handlebars Syntax**: Conditionals, iteration, and partials
13
+ - **API Integration**: Client-side fetching with hydrated configuration
14
+
15
+ ## Prerequisites
16
+
17
+ Before starting, ensure you have:
18
+ - Ruby 3.0 or higher
19
+ - Bundler gem installed (`gem install bundler`)
20
+ - SQLite3 installed on your system
21
+ - Node.js and pnpm (for schema generation)
22
+
23
+ ## Step-by-Step Setup Instructions
24
+
25
+ ### Step 1: Navigate to the demo directory
26
+
27
+ From the root of the rhales gem repository:
28
+ ```bash
29
+ cd demo/rhales-roda-demo
30
+ ```
31
+
32
+ ### Step 2: Install Ruby dependencies
33
+
34
+ ```bash
35
+ bundle install
36
+ ```
37
+
38
+ If you encounter errors:
39
+ ```bash
40
+ # Update bundler
41
+ gem install bundler
42
+
43
+ # Try again
44
+ bundle install
45
+ ```
46
+
47
+ ### Step 3: Install Node.js dependencies (for schema generation)
48
+
49
+ ```bash
50
+ pnpm install
51
+ ```
52
+
53
+ If you don't have pnpm installed:
54
+ ```bash
55
+ npm install -g pnpm
56
+ pnpm install
57
+ ```
58
+
59
+ ### Step 4: Generate JSON Schemas from templates
60
+
61
+ ```bash
62
+ bundle exec rake rhales:schema:generate
63
+ ```
64
+
65
+ This will:
66
+ - Parse all `.rue` templates with `<schema>` sections
67
+ - Execute Zod schema code to generate JSON Schemas
68
+ - Save generated schemas to `public/schemas/`
69
+
70
+ Expected output:
71
+ ```
72
+ Schema Generation
73
+ ============================================================
74
+ Templates: ./templates
75
+ Output: ./public/schemas
76
+ Zod: (using pnpm exec tsx)
77
+
78
+ Found 2 schema section(s):
79
+ - login (js-zod)
80
+ - dashboard (js-zod)
81
+
82
+ Generating JSON Schemas...
83
+ ✓ Generated schema for: login
84
+ ✓ Generated schema for: dashboard
85
+
86
+ Results:
87
+ ------------------------------------------------------------
88
+ ✓ Successfully generated 2 schema(s)
89
+ ✓ Output directory: /path/to/demo/rhales-roda-demo/public/schemas
90
+ ```
91
+
92
+ You can verify the generated schemas:
93
+ ```bash
94
+ ls -la public/schemas/
95
+ cat public/schemas/login.json | jq .
96
+ ```
97
+
98
+ ### Step 5: Start the web server
99
+
100
+ ```bash
101
+ bundle exec rackup
102
+ ```
103
+
104
+ Expected output:
105
+ ```
106
+ Puma starting in single mode...
107
+ * Puma version: 6.4.2 (ruby 3.0.0-p0) ("The Eagle of Durango")
108
+ * Min threads: 0
109
+ * Max threads: 5
110
+ * Environment: development
111
+ * PID: 12345
112
+ * Listening on http://127.0.0.1:9292
113
+ * Listening on http://[::1]:9292
114
+ Use Ctrl-C to stop
115
+ ```
116
+
117
+ ### Step 6: Open your browser
118
+
119
+ Visit: http://localhost:9292
120
+
121
+ You should see the Rhales Demo homepage with feature cards.
122
+
123
+ ## Testing the Demo
124
+
125
+ ### Quick Test with Demo Account
126
+
127
+ 1. Click **"Login"** in the top navigation
128
+ 2. Enter credentials:
129
+ - Email: `demo@example.com`
130
+ - Password: `demo123`
131
+ 3. Click **"Login"** button
132
+ 4. You should see a dashboard with:
133
+ - Welcome message with the user's name
134
+ - Stats showing Total Posts, Published, and Drafts
135
+ - List of sample posts
136
+
137
+ ### Create Your Own Account
138
+
139
+ 1. From the homepage, click **"Register"**
140
+ 2. Fill out the form:
141
+ - Full Name: `Test User` (or your name)
142
+ - Email: `test@example.com` (any email works)
143
+ - Password: `testpass` (min 6 characters)
144
+ - Confirm Password: `testpass`
145
+ 3. Click **"Create Account"**
146
+ 4. You'll be automatically logged in and redirected to an empty dashboard
147
+
148
+ ### Test Post Management
149
+
150
+ 1. Click **"New Post"** button on the dashboard
151
+ 2. Fill out the form:
152
+ - Title: `My First Post`
153
+ - Content: `This is a test post using Rhales RSFC templates!`
154
+ - Status: `Published`
155
+ 3. Click **"Create Post"**
156
+ 4. You should see:
157
+ - Success message: "Post created successfully!"
158
+ - Your post in the dashboard list
159
+ 5. Try editing the post:
160
+ - Click **"Edit"** on your post
161
+ - Change the title or content
162
+ - Click **"Update Post"**
163
+ 6. Try deleting:
164
+ - Click **"Delete"**
165
+ - Confirm the deletion
166
+ - Post should disappear with success message
167
+
168
+ ### Test Client-Side Hydration
169
+
170
+ 1. Click **"Profile"** in the navigation
171
+ 2. You'll see your account information
172
+ 3. Click **"Fetch Stats"** button
173
+ 4. Watch as it makes an API call and displays JSON response
174
+ 5. Open browser console (F12) to see hydration logs
175
+
176
+ ### Logout
177
+
178
+ Click **"Logout"** in the navigation to end your session.
179
+
180
+ ## Development Mode
181
+
182
+ For automatic server restarts when files change:
183
+
184
+ ```bash
185
+ bundle exec rerun rackup
186
+ ```
187
+
188
+ Now when you edit any `.rb` or `.rue` file, the server will restart automatically.
189
+
190
+ ## Schema Generation and Validation
191
+
192
+ ### Generating Schemas
193
+
194
+ Rhales uses Zod schemas defined in `.rue` templates to generate JSON Schemas for runtime validation:
195
+
196
+ ```bash
197
+ # Generate all schemas
198
+ bundle exec rake rhales:schema:generate
199
+
200
+ # Custom paths
201
+ bundle exec rake rhales:schema:generate \
202
+ TEMPLATES_DIR=./templates \
203
+ OUTPUT_DIR=./public/schemas
204
+
205
+ # View statistics
206
+ bundle exec rake rhales:schema:stats
207
+
208
+ # Validate existing schemas
209
+ bundle exec rake rhales:schema:validate
210
+ ```
211
+
212
+ ### Schema Sections in Templates
213
+
214
+ Templates with `<schema>` sections define the shape of data they expect:
215
+
216
+ ```html
217
+ <schema lang="js-zod" window="appData">
218
+ const schema = z.object({
219
+ user: z.object({
220
+ name: z.string(),
221
+ email: z.string().email(),
222
+ }),
223
+ posts: z.array(z.object({
224
+ id: z.number(),
225
+ title: z.string(),
226
+ })),
227
+ });
228
+ </schema>
229
+ ```
230
+
231
+ ### Runtime Validation
232
+
233
+ When schema validation is enabled (see `app.rb`), the middleware will:
234
+ - Extract hydration data from HTML responses
235
+ - Validate against the generated JSON Schema
236
+ - In development: Fail loudly with detailed error messages
237
+ - In production: Log warnings but continue serving
238
+
239
+ ### Directory Structure
240
+
241
+ ```
242
+ demo/rhales-roda-demo/
243
+ ├── templates/ # Source .rue files
244
+ │ ├── login.rue # With <schema> section
245
+ │ └── dashboard.rue # With <schema> section
246
+ ├── public/
247
+ │ └── schemas/ # Generated JSON Schemas (gitignored)
248
+ │ ├── login.json
249
+ │ └── dashboard.json
250
+ └── app.rb # Schema validation middleware config
251
+ ```
252
+
253
+ ## Understanding the Code
254
+
255
+ ### Key Files to Explore
256
+
257
+ 1. **app.rb** - Main application file
258
+ - Shows Rhales configuration
259
+ - Custom auth/session adapters
260
+ - Route definitions
261
+ - `rhales_render` helper method
262
+
263
+ 2. **templates/layouts/main.rue** - Layout template
264
+ - Shows conditional navigation
265
+ - CSP nonce usage in styles
266
+ - Flash message handling
267
+
268
+ 3. **templates/home.rue** - Homepage
269
+ - Data hydration with `window` attribute
270
+ - Feature iteration
271
+ - Client-side JavaScript integration
272
+
273
+ 4. **templates/dashboard/index.rue** - Dashboard
274
+ - Uses partials (`{{> post_item}}`)
275
+ - Conditional rendering
276
+ - Stats display
277
+
278
+ ## Troubleshooting
279
+
280
+ ### Port 9292 already in use
281
+
282
+ ```bash
283
+ # Use a different port
284
+ bundle exec rackup -p 9293
285
+
286
+ # Or find and kill the process using port 9292
287
+ lsof -i :9292
288
+ kill -9 <PID>
289
+ ```
290
+
291
+ ### Bundle install fails
292
+
293
+ ```bash
294
+ # Check Ruby version
295
+ ruby -v # Should be 3.0 or higher
296
+
297
+ # Update RubyGems
298
+ gem update --system
299
+
300
+ # Try installing problematic gems individually
301
+ gem install sqlite3 -v '2.0.0'
302
+ gem install rack-session -v '2.0.0'
303
+ ```
304
+
305
+ ### Rack::Session errors
306
+
307
+ If you see "uninitialized constant Rack::Session" or "invalid secret" errors:
308
+ ```bash
309
+ bundle install # Make sure rack-session and rackup gems are installed
310
+ bundle binstub rack # Fix rackup binstub conflicts if needed
311
+ bundle exec rackup # Try again
312
+ ```
313
+
314
+ ### SQLite3 LoadError
315
+
316
+ Install SQLite3 for your system:
317
+
318
+ ```bash
319
+ # macOS
320
+ brew install sqlite3
321
+
322
+ # Ubuntu/Debian
323
+ sudo apt-get install sqlite3 libsqlite3-dev
324
+
325
+ # Fedora/RHEL
326
+ sudo dnf install sqlite sqlite-devel
327
+ ```
328
+
329
+ Then reinstall the gem:
330
+ ```bash
331
+ gem uninstall sqlite3
332
+ bundle install
333
+ ```
334
+
335
+ ### Template not found
336
+
337
+ Verify template files exist:
338
+ ```bash
339
+ find templates -name "*.rue" | sort
340
+ ```
341
+
342
+ Should show:
343
+ ```
344
+ templates/auth/login.rue
345
+ templates/auth/register.rue
346
+ templates/dashboard/index.rue
347
+ templates/dashboard/post_form.rue
348
+ templates/dashboard/profile.rue
349
+ templates/home.rue
350
+ templates/layouts/main.rue
351
+ templates/partials/post_item.rue
352
+ ```
353
+
354
+ ### No CSS styling
355
+
356
+ The demo includes inline CSS in the layout. If styles aren't loading:
357
+ 1. Check browser console for CSP errors
358
+ 2. Verify the `{{runtime.nonce}}` is being replaced in templates
359
+ 3. Try hard refresh (Ctrl+Shift+R or Cmd+Shift+R)
360
+
361
+ ## Next Steps
362
+
363
+ 1. Explore the `.rue` template files to understand RSFC structure
364
+ 2. Modify templates and see live changes
365
+ 3. Add new routes and templates
366
+ 4. Integrate Rhales into your own Ruby applications
367
+
368
+ ## Architecture Overview
369
+
370
+ - **Roda**: Lightweight Ruby web framework
371
+ - **Rodauth**: Full-featured authentication
372
+ - **Sequel**: Database toolkit with models
373
+ - **SQLite**: Zero-config database
374
+ - **Rhales**: RSFC template engine with hydration
375
+
376
+ This demo shows how Rhales integrates seamlessly with existing Ruby tools while adding powerful component-based templating.
@@ -0,0 +1,192 @@
1
+ # Custom Template Engines in Roda
2
+
3
+ Roda's render plugin uses Tilt to support multiple template engines. You can use any template engine that Tilt supports, or integrate custom template engines in several ways.
4
+
5
+ ## Using Template Engines Already Supported by Tilt
6
+
7
+ Tilt supports 25+ template engines out of the box. To use a different engine, specify it in the `:engine` option:
8
+
9
+ ```ruby
10
+ plugin :render, engine: 'haml'
11
+ plugin :render, engine: 'slim'
12
+ plugin :render, engine: 'liquid'
13
+ ```
14
+
15
+ You can also specify the engine per template:
16
+
17
+ ```ruby
18
+ render('template', engine: 'haml')
19
+ view('template', engine: 'slim')
20
+ ```
21
+
22
+ ## Adding New Template Engines to Tilt
23
+
24
+ As of 2025, Tilt is actively maintained by Jeremy Evans but no longer accepts new community template engine integrations in the main gem. If you want to add support for a template engine not currently supported by Tilt, you should:
25
+
26
+ 1. Create a separate gem that provides Tilt integration for your template engine
27
+ 2. Have the template engine itself ship with Tilt integration
28
+
29
+ To register a new template engine with Tilt:
30
+
31
+ ```ruby
32
+ # In your gem or application
33
+ require 'tilt'
34
+
35
+ class MyTemplateEngine < Tilt::Template
36
+ def prepare
37
+ # Parse/compile template during template creation
38
+ end
39
+
40
+ def evaluate(scope, locals, &block)
41
+ # Render template with given scope and locals
42
+ end
43
+ end
44
+
45
+ # Register with Tilt
46
+ Tilt.register MyTemplateEngine, 'myengine'
47
+
48
+ # Or for multiple extensions
49
+ Tilt.register MyTemplateEngine, 'myengine', 'mytempl'
50
+ ```
51
+
52
+ Then use it in Roda:
53
+
54
+ ```ruby
55
+ plugin :render, engine: 'myengine'
56
+ render('template') # renders template.myengine
57
+ ```
58
+
59
+ ## Layouts
60
+
61
+ In Roda, you can specify layouts and create defaults through the render plugin configuration:
62
+
63
+ Specifying a Layout File
64
+ ```ruby
65
+ # Set a specific layout file
66
+ plugin :render, layout: 'my_layout'
67
+
68
+ # Or specify per-render
69
+ render('template', layout: 'specific_layout')
70
+ ```
71
+
72
+ Creating Default Layout
73
+
74
+ Roda automatically looks for layout.erb (or .engine extension) in your views directory. To create a default layout:
75
+
76
+ ```html
77
+ # views/layout.erb
78
+ <!DOCTYPE html>
79
+ <html>
80
+ <head>
81
+ <title>My App</title>
82
+ </head>
83
+ <body>
84
+ <%= yield %>
85
+ </body>
86
+ </html>
87
+ ```
88
+
89
+ Layout Configuration Options
90
+
91
+ ```ruby
92
+ plugin :render,
93
+ layout: 'layout', # Default layout file
94
+ layout_opts: {engine: 'erb'}, # Layout-specific options
95
+ views: 'views' # Views directory
96
+ ```
97
+
98
+ The layout file receives the rendered template content via yield and can access the same instance variables and locals as your templates.
99
+
100
+
101
+ ## Using Custom Template Classes Directly
102
+
103
+ For maximum control, you can bypass Tilt entirely and provide your own template class using the `:template_class` option:
104
+
105
+ ```ruby
106
+ class MyCustomTemplate
107
+ def initialize(file, line, options={}, &block)
108
+ @template_string = block ? block.call : File.read(file)
109
+ # Custom initialization
110
+ end
111
+
112
+ def render(scope, locals={}, &block)
113
+ # Custom rendering logic
114
+ # Return rendered string
115
+ end
116
+ end
117
+
118
+ # Use globally
119
+ plugin :render, template_class: MyCustomTemplate
120
+
121
+ # Or per-template
122
+ render('template', template_class: MyCustomTemplate)
123
+ ```
124
+
125
+ ## Engine-Specific Options
126
+
127
+ Configure options for specific template engines using `:engine_opts`:
128
+
129
+ ```ruby
130
+ plugin :render,
131
+ engine_opts: {
132
+ 'erb' => {default_encoding: 'UTF-8'},
133
+ 'haml' => {format: :html5, escape_html: true},
134
+ 'slim' => {pretty: true}
135
+ }
136
+ ```
137
+
138
+ ## Example: Adding Mustache Support
139
+
140
+ Here's a complete example of adding Mustache template support:
141
+
142
+ ```ruby
143
+ require 'mustache'
144
+ require 'tilt'
145
+
146
+ class TiltMustacheTemplate < Tilt::Template
147
+ def prepare
148
+ @engine = Mustache.new
149
+ @engine.template = data
150
+ end
151
+
152
+ def evaluate(scope, locals, &block)
153
+ @engine.render(locals)
154
+ end
155
+ end
156
+
157
+ Tilt.register TiltMustacheTemplate, 'mustache'
158
+
159
+ # In your Roda app
160
+ plugin :render, engine: 'mustache'
161
+
162
+ route do |r|
163
+ r.get do
164
+ view('template', locals: {name: 'World'}) # renders template.mustache
165
+ end
166
+ end
167
+ ```
168
+
169
+ ## Tilt Maintenance Status
170
+
171
+ **Current Status**: Tilt is actively maintained by Jeremy Evans (also the maintainer of Roda) as of 2025.
172
+
173
+ **Recent Activity**:
174
+ - Version 2.6.0 released January 13, 2025
175
+ - Version 2.5.0 released December 20, 2024
176
+ - 0 open issues and pull requests (excellent maintenance)
177
+
178
+ **Policy on New Engines**: The Tilt team no longer accepts new community-maintained template integrations into the main gem. New template engines should implement their own Tilt compatibility in separate gems.
179
+
180
+ **Supported Engines**: Tilt currently supports 25+ template engines including ERB, Erubi, Haml, Slim, Markdown engines (CommonMarker, Kramdown, Redcarpet), Sass/SCSS, CoffeeScript, TypeScript, Liquid, Builder, Nokogiri, and many more.
181
+
182
+ ## Best Practices
183
+
184
+ 1. **Use existing engines**: Check if Tilt already supports your desired template engine before creating a custom integration.
185
+
186
+ 2. **Separate gems**: If you need a new template engine, create a separate gem with Tilt integration rather than modifying Tilt directly.
187
+
188
+ 3. **Performance**: Use the `:template_class` option for maximum performance if you don't need Tilt's generic interface.
189
+
190
+ 4. **Caching**: Custom template classes should support Roda's caching mechanisms for best performance.
191
+
192
+ 5. **Error handling**: Ensure your custom template engines provide proper error messages with filename and line number information.
@@ -0,0 +1,49 @@
1
+ # Rakefile for rhales-roda-demo
2
+
3
+ # Load Rhales rake tasks for schema generation
4
+ # Note: In a real project, these would be loaded from the gem
5
+ # For this demo, we load from the local gem directory
6
+ begin
7
+ # Try loading from local gem development directory
8
+ rake_tasks = File.expand_path('../../lib/tasks/rhales_schema.rake', __dir__)
9
+ load rake_tasks if File.exist?(rake_tasks)
10
+ rescue LoadError
11
+ # Fallback: load from installed gem
12
+ spec = Gem::Specification.find_by_name('rhales')
13
+ load "#{spec.gem_dir}/lib/tasks/rhales_schema.rake"
14
+ end
15
+
16
+ desc 'Run CI integration test (replicates GitHub Actions workflow)'
17
+ task :ci_test do
18
+ puts "Installing demo dependencies..."
19
+ system('bundle install') || (puts "Failed to install dependencies" && exit(1))
20
+
21
+ puts "Starting demo server..."
22
+ server_pid = spawn('bundle exec rackup -p 9393')
23
+ puts "Server PID: #{server_pid}"
24
+
25
+ begin
26
+ # Wait for server to start
27
+ sleep 1
28
+
29
+ # Test the server (replicates: curl -f http://localhost:9393/ || exit 1)
30
+ puts "Testing demo server..."
31
+ success = system('curl -f http://localhost:9393/')
32
+ puts "Curl exit status: #{$?.exitstatus}"
33
+
34
+ if success
35
+ puts "✓ Demo integration test passed"
36
+ else
37
+ puts "✗ Demo integration test failed"
38
+ exit 1
39
+ end
40
+ ensure
41
+ # Clean up (replicates: pkill -f rackup || true)
42
+ if server_pid
43
+ Process.kill('TERM', server_pid) rescue nil
44
+ Process.wait(server_pid) rescue nil
45
+ end
46
+ end
47
+ end
48
+
49
+ task default: :ci_test