orb_template 0.1.1 → 0.1.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b74243313337dd63535ee8506813a2f389bae51dc9acd867af03daa1698a4460
4
- data.tar.gz: 7afda4bc5343e9ce87aebb99f0adaca66f9c46fb6cb47df861a3fb6db90753d6
3
+ metadata.gz: 72cfaec138706b6a2a92cec82fc558a6dfe13a56e21dbad5fa18fd5234104a08
4
+ data.tar.gz: 8fc11d77077c47693176181594c1ade6e39de19300ad66acfddbe9383bc8da3f
5
5
  SHA512:
6
- metadata.gz: 46d2138975a21d7011c519f5621ca98058d6823c6c1fa1a33634e0acfd5baa18cdddf15a68902c00562721017ded9bf6cb7d489875586c4eb15f991bed4d3cf7
7
- data.tar.gz: 2ffee3abc581416c96b294e4d8d2d9b6fc46f6b332fbb75d2eb91a74d1c67d5907624c5f3e7ecff8f954fbe5dd13765dcb3f76f026a52487e9547c251800bd07
6
+ metadata.gz: 3cd200ecc1e3292f31fa5d90120a97850b5e6494bc157ba77cad66ea77662be835723fb2fe6a1bc7056052ee120c8e73bde4abf62177e36acebc6d11e5eebbd2
7
+ data.tar.gz: eb9a1b1edb4e41cbbfeac8ff9f2230fb05ad1c9ea932c0a4bd2cdddb16697508394758a1b752121e1b2ea096f84dcdc9a6a1a245e87193657fb210d91be4a2a2
data/README.md CHANGED
@@ -2,12 +2,8 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/orb_template.svg)](https://badge.fury.io/rb/orb_template)
4
4
 
5
-
6
-
7
5
  https://github.com/user-attachments/assets/8380b9a8-2063-40f3-a9b6-1b5d623d6f31
8
6
 
9
-
10
-
11
7
  **ORB** is a template language for Ruby with the express goal of providing a first-class DSL for rendering [ViewComponents](https://viewcomponent.org). It is heavily inspired by [React JSX](https://react.dev/learn/writing-markup-with-jsx) and [Surface](https://surace-ui.org).
12
8
 
13
9
  ## Show me an Example
@@ -63,7 +59,6 @@ https://github.com/user-attachments/assets/8380b9a8-2063-40f3-a9b6-1b5d623d6f31
63
59
  - [Editor Support](#editor-support)
64
60
  - [Roadmap](#roadmap)
65
61
 
66
-
67
62
  ## Installation
68
63
 
69
64
  In your `Gemfile`, add:
@@ -78,13 +73,13 @@ then run:
78
73
  bundle install
79
74
  ```
80
75
 
81
- The gem automatically registers the `ORB` template engine as the renderer for `*.orb` template files through a Railtie.
76
+ The gem automatically registers the `ORB` template engine as the renderer for `*.html.orb` view template files.
82
77
 
83
78
  ## Motivation
84
79
 
85
- There already exist a plethora of fast, battle-proven template langauges for the Ruby/Rails ecosystem, so why invent another one?
80
+ There already exist a plethora of fast, battle-proven template languages for the Ruby/Rails ecosystem, so why invent another one?
86
81
 
87
- ORB was born out of the frustration that instantiating and rendering view components with existing template engines quickly becomes tedious. This hindered adoption of ViewComponents in our projects, impacted velocity, maintainance and new-developer onboarding. These effects were especially pronounced with highly customizable view components with long argument lists, as well as deeply nested components, and component trees - like a Design System / Component Library.
82
+ ORB was born out of the frustration that instantiating and rendering view components with existing template engines quickly becomes tedious. This hindered adoption of ViewComponents in our projects, impacted velocity, maintenance and new-developer onboarding. These effects were especially pronounced with highly customizable view components with long argument lists, as well as deeply nested components, and component trees - like a Design System / Component Library.
88
83
 
89
84
  A common solution to making the rendering of view components less verbose is to define component-specific view helpers like so:
90
85
 
@@ -106,7 +101,7 @@ module ComponentsHelper
106
101
  end
107
102
  ```
108
103
 
109
- You can then using these view helpers in your front-end code, wherever a view components needs to be rendered:
104
+ You can then use these view helpers in your front-end code, wherever a view components needs to be rendered:
110
105
 
111
106
  ```erb
112
107
  <%= card(title: "Your friends") do %>
@@ -128,17 +123,17 @@ The `ORB` template language takes another path and allows you to write component
128
123
  <Card:Section title="Birthdays today">
129
124
  <List>
130
125
  <List.Item>
131
- <Link url={member_path(2)}>Carl Schwartz (27)</Link>
126
+ <Link url={member_path(2)} Carl Schwartz (27) />
132
127
  </List.Item>
133
128
  <List.Item>
134
- <Link url={member_path(3)}>Floralie Brain (38)</Link>
129
+ <Link url={member_path(3)} Floralie Brain (38) />
135
130
  </List.Item>
136
131
  </List>
137
132
  </Card:Section>
138
133
  </Card>
139
134
  ```
140
135
 
141
- Your code becomes more focussed, grokable and maintainable. Front-end teams that may already be familiar with JSX or VUE can become productive faster, and can make use of their existing editor tooling for HTML like `Emmet` when writing templates.
136
+ Your code becomes more focused, grokable and maintainable. Front-end teams that may already be familiar with JSX or VUE can become productive faster, and can make use of their existing editor tooling for HTML like `Emmet` when writing templates.
142
137
 
143
138
  ### Core Values
144
139
 
@@ -151,11 +146,10 @@ We believe that any template language should be enjoyable to the user:
151
146
  - Dynamic: Rendering with a context of local variables and objects is fast.
152
147
  - Polite: guide the user with useful error messages when their templates contain problems.
153
148
 
154
-
155
149
  ### Conventions
156
150
 
157
151
  - Components rendered by the `ORB` engine live under the configured namespace and omit the `Component` suffix from their class names.
158
- - Templates have a file extension of `.orb`, for example: `my_template.html.orb` for a template named `:my_template` that outputs in `format: :html`.
152
+ - HTML view templates have a file extension of `.*.orb`, for example: `my_template.html.orb` for a template named `:my_template` that outputs in `format: :html`.
159
153
 
160
154
  ---
161
155
 
@@ -164,6 +158,7 @@ We believe that any template language should be enjoyable to the user:
164
158
  ORB fully supports the HTML5 markup language standard and extends HTML with additional syntax for expressions, dynamic attributes, blocks, components, slots, and comments.
165
159
 
166
160
  ### Configuration
161
+
167
162
  You can configure the `ORB` engine in your Rails application with an initializer, e.g., `config/initializers/orb.rb`:
168
163
 
169
164
  ```ruby
@@ -174,7 +169,7 @@ This will instruct the `ORB` engine to look for components under the `MyComponen
174
169
 
175
170
  ### HTML5
176
171
 
177
- Regular HTML tags are fully supported by `ORB`. Just write your HTML tags as you are used to and your are good to go.
172
+ Regular HTML tags are fully supported by `ORB`. Just write your HTML tags as you are used to and you are good to go.
178
173
 
179
174
  ```html
180
175
  <div id="page-banner" class="banner" aria-role="alert">
@@ -184,7 +179,7 @@ Regular HTML tags are fully supported by `ORB`. Just write your HTML tags as you
184
179
 
185
180
  ### Expressions
186
181
 
187
- `ORB` supports Ruby expressions in the code through double curly-braces (mustachy syntax). The code inside the curly-braces will be evaluated at render time, and the result will be HTML-safe escaped and inserted into the output.:
182
+ `ORB` supports Ruby expressions in the code through double curly-braces (mustache-like syntax). The code inside the curly-braces will be evaluated at render time, and the result will be HTML-safe escaped and inserted into the output.:
188
183
 
189
184
  ```html
190
185
  <div id="page-banner" class="banner" aria-role="alert">
@@ -226,38 +221,44 @@ For example, a `Banner` may be conditionally rendered through an `{#if}` block c
226
221
 
227
222
  ```html
228
223
  {#if banner.urgent?}
229
- <div id="{dom_id(banner)}" class="{banner.classNames}">
230
- <span class="message">{{banner.message}}</span>
231
- </div>
224
+ <div id={dom_id(banner)} class={banner.classNames}>
225
+ <span class="message">{{banner.message}}</span>
226
+ </div>
232
227
  {/if}
233
228
  ```
234
229
 
235
230
  Since control flow is such a common thing in templates, `ORB` provides special syntactic sugar for the `{#if}` and `{#for}` blocks through the `:if` and `:for` directives on HTML tags. The above example can thus be rewritten as:
236
231
 
237
232
  ```html
238
- <div id="{dom_id(banner)}" class="{banner.classNames}" :if={banner.urgent?}>
233
+ <div id={dom_id(banner)} class={banner.classNames} :if={banner.urgent?}>
239
234
  <span class="message">{{banner.message}}</span>
240
235
  </div>
241
236
  ```
242
237
 
243
238
  ### Splatted Attributes
244
- `ORB` supports attribute splatting for both HTML tags and view components through the `**attributes` syntax. The expression provided for the splat must evaluate to a `Hash`, whose key-value pairs will be added as attributes to the tag. For example:
239
+
240
+ `ORB` supports attribute splatting for both HTML tags and view components through the `**attributes` syntax. The attribute name for the splat must be a `Hash`; all key-value pairs will be added as attributes to the tag. For example:
245
241
 
246
242
  ```html
247
- <div **banner_attributes>
248
- ... content ...
249
- </div>
243
+ <div **banner_attributes>... content ...</div>
250
244
  ```
251
245
 
246
+ ### Splatted Attribute Expressions
247
+
248
+ You can also use an expression to define the splatted attributes through the `**{expression}` syntax. The expression must evaluate to a `Hash`, and all key-value pairs will be added as attributes to the tag. For example:
249
+
250
+ ```html
251
+ <div **{dynamic_attributes}>... content ...</div>
252
+ ```
252
253
 
253
254
  ### View Components
254
255
 
255
- In `ORB` templates, you can render your view components as if they were HTML tags. The component class name is mapped to the tag name by omitting the configured namespace and the `Component` suffix.
256
+ In `ORB` templates, you can render ViewComponents as if they were HTML tags. The `Component` suffix on ViewComponents may be omitted to make the markup nicer.
256
257
 
257
258
  For example, if you have a `Button` view component that may be defined as:
258
259
 
259
260
  ```ruby
260
- class MyComponents::Button < ::ViewComponent::Base
261
+ class MyComponents::Button < ViewComponent::Base
261
262
  def initialize(url: "#", **options)
262
263
  @url = url
263
264
  @options = options
@@ -271,12 +272,14 @@ class MyComponents::Button < ::ViewComponent::Base
271
272
  end
272
273
  ```
273
274
 
274
- you can render the component in an ORB template `button.html.orb` as:
275
+ you can render the component in an ORB template `show.html.orb` as:
275
276
 
276
277
  ```jsx
277
278
  <Button url="/click_me">I am a button</Button>
278
279
  ```
279
280
 
281
+ Tip: you can also define the markup for a component inline as an `orb_template <<-ORB ... ORB` block to use the `ORB` template language for the component's own view template.
282
+
280
283
  ### ViewComponent Slots
281
284
 
282
285
  `ORB` also provides a DSL for invoking a component slot and passing content to the slot through the `Component:Slot` syntax. For example, if you have a `Card` component that defines a `Sections` slot via `renders_many :sections, Card::Section`, you can invoke and fill the slot in an `ORB` template like this:
@@ -289,6 +292,8 @@ you can render the component in an ORB template `button.html.orb` as:
289
292
  </Card>
290
293
  ```
291
294
 
295
+ Tip: Slot names are case-insensitive, so `<Card:section> ... </Card:section>` also works.
296
+
292
297
  ### Namespaces
293
298
 
294
299
  Sometimes, you may want to organize your components in sub-namespaces, or use components from other libraries. `ORB` supports this through dot notation in the tag names. For example, if you have a `MyComponents::Admin::Button` component, you can render it in an `ORB` template like this:
@@ -317,14 +322,14 @@ Namespaces defined in this way will be searched in order of definition when reso
317
322
 
318
323
  **Public comments** are sent to the browser, and can be read by users inspecting the page source. ORB considers default HTML comments `<!-- -->` to be public comments.
319
324
 
320
- ```heex
325
+ ```html
321
326
  <!-- I will be sent to the browser -->
322
327
  <p>Hello World!</p>
323
328
  ```
324
329
 
325
330
  **Private comments**, unlike public comments, won't be sent to the browser. Use private comments to mark up your ORB template with annotations that you do not wish users to see.
326
331
 
327
- ```heex
332
+ ```ruby-orb
328
333
  {!-- I won't be sent to the browser --}
329
334
  <p>Hello World!</p>
330
335
  ```
@@ -337,6 +342,24 @@ Namespaces defined in this way will be searched in order of definition when reso
337
342
 
338
343
  Your favorite editor is not listed? Feel free to contribute an extension/plugin for your editor of choice!
339
344
 
345
+ ### Visual Studio Code
346
+
347
+ To enable `Emmet` support for ORB, add this to your `settings.json`:
348
+
349
+ ```json
350
+ "emmet.includeLanguages": {
351
+ "ruby-orb": "html",
352
+ }
353
+ ```
354
+
355
+ To enable `Tailwindcss` support for ORB, add this to your `settings.json`:
356
+
357
+ ```json
358
+ "tailwindCSS.includeLanguages": {
359
+ "ruby-orb": "html"
360
+ }
361
+ ```
362
+
340
363
  ## Roadmap
341
364
 
342
365
  - [x] **Step 1: Make it work**
@@ -361,6 +384,7 @@ Your favorite editor is not listed? Feel free to contribute an extension/plugin
361
384
  - [ ] **Step 2: Make it nice**
362
385
  - [x] improved errors with super helpful error messages and locations throughout the entire stack, possibly custom rendered error pages
363
386
  - [x] `**attribute` splats for html tags, components and slots
387
+ - [x] `**{expression}` splats for html tags, components and slots
364
388
  - [x] `:if` directive
365
389
  - [x] `:for` directive
366
390
  - [x] verbatim tags
@@ -391,12 +415,12 @@ Your favorite editor is not listed? Feel free to contribute an extension/plugin
391
415
  - [ ] support additional block constructs
392
416
  - [ ] support additional language constructs
393
417
 
394
-
395
418
  > This library is in beta stage and demonstrates the technical aspects of a custom DSL for rendering ViewComponent objects in an HTML-like manner. It is meant as a kick-off point for further discussion on the definition and implementation of the template language. It may contain critical bugs that could compromise the security and integrity of your application. Additionally, the API and DSL are likely to change as the library evolves to a stable state. Don't say we didn't warn you!
396
419
 
397
420
  ## Development
398
421
 
399
422
  To set up your development environment, follow these steps:
423
+
400
424
  1. Clone the repository:
401
425
 
402
426
  ```bash
@@ -414,20 +438,18 @@ To set up your development environment, follow these steps:
414
438
 
415
439
  ```bash
416
440
  make test
417
- ```
441
+ ```
418
442
 
419
443
  4. Start the development server for the test application:
420
444
 
421
- ```bash
422
- bin/rails server
423
- ```
424
-
445
+ ```bash
446
+ bin/rails server
447
+ ```
425
448
 
426
449
  ## Contributing
427
450
 
428
451
  This project is intended to be a safe, welcoming space for collaboration. Contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. We recommend reading the [contributing guide](./docs/CONTRIBUTING.md) as well.
429
452
 
430
-
431
453
  ## License
432
454
 
433
455
  ORB is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/lib/orb/patterns.rb CHANGED
@@ -28,7 +28,8 @@ module ORB
28
28
  ATTRIBUTE_ASSIGN = /=/
29
29
  SINGLE_QUOTE = /'/
30
30
  DOUBLE_QUOTE = /"/
31
- SPLAT_START = /\*/
31
+ SPLAT_ATTRIBUTE = %r{\*[^\s>/=]+}
32
+ SPLAT_EXPRESSION_START = /\*\*\{/
32
33
  BRACE_OPEN = /\{/
33
34
  BRACE_CLOSE = /\}/
34
35
  CR = /\r/
@@ -39,7 +39,32 @@ module ORB
39
39
  end
40
40
 
41
41
  # Pipe through the ORB Temple engine
42
- ORB::Temple::Engine.new(options).call(source)
42
+ code = ORB::Temple::Engine.new(options).call(source)
43
+
44
+ # Validate generated code with Prism to catch syntax errors BEFORE Rails does.
45
+ # This is a workaround for a Rails 8.1.1 bug where SyntaxErrorProxy fails
46
+ # is_a?(Exception) checks in ActiveSupport::ErrorReporter#report.
47
+ # See: rails-syntax-error-bug.md for details.
48
+ #
49
+ # Only run in development mode to avoid performance impact in production.
50
+ # In production, syntax errors will still be caught but with less friendly display.
51
+ if defined?(Prism) && defined?(Rails) && Rails.env.local?
52
+ result = Prism.parse(code)
53
+ if result.failure?
54
+ first_error = result.errors.first
55
+ error_line = first_error.location.start_line
56
+ message = first_error.message
57
+
58
+ # Return code that raises the error when executed.
59
+ # This way Rails' normal error handling will kick in, providing proper
60
+ # extracted source display and backtrace. We add newlines to position
61
+ # the error at the correct line number in the template.
62
+ escaped_message = message.gsub('\\', '\\\\\\\\').gsub("'", "\\\\'")
63
+ return "#{'\\n' * (error_line - 1)}raise ORB::CompilerError.new('#{escaped_message}', #{error_line})"
64
+ end
65
+ end
66
+
67
+ code
43
68
  end
44
69
 
45
70
  # See https://github.com/rails/rails/pull/47005
@@ -139,6 +139,7 @@ module ORB
139
139
  end
140
140
 
141
141
  # Read next token in :tag_open_content state
142
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
142
143
  def next_in_tag_open_content
143
144
  if @source.scan(NEWLINE) || @source.scan(CRLF) || @source.scan(BLANK)
144
145
  move_by_matched
@@ -163,7 +164,13 @@ module ORB
163
164
  transition_to(:initial)
164
165
  elsif @source.scan(START_TAG_START)
165
166
  syntax_error!("Unexpected start of tag")
166
- elsif @source.scan(%r{\*[^\s>/=]+})
167
+ elsif @source.scan(Patterns::SPLAT_EXPRESSION_START)
168
+ @attributes << [nil, :splat, nil]
169
+ clear_braces
170
+ clear_buffer
171
+ move_by_matched
172
+ transition_to(:splat_attribute_expression)
173
+ elsif @source.scan(Patterns::SPLAT_ATTRIBUTE)
167
174
  splat = @source.matched
168
175
  @attributes << [nil, :splat, splat]
169
176
  move_by_matched
@@ -173,6 +180,7 @@ module ORB
173
180
  syntax_error!("Unexpected '#{@source.peek(1)}'")
174
181
  end
175
182
  end
183
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
176
184
 
177
185
  # Read next token in :attribute_name state
178
186
  def next_in_attribute_name
@@ -275,6 +283,32 @@ module ORB
275
283
  end
276
284
  end
277
285
 
286
+ # Read next token in :splat_attribute_expression state
287
+ def next_in_splat_attribute_expression
288
+ if @source.scan(BRACE_OPEN)
289
+ @braces << "{"
290
+ buffer_matched
291
+ move_by_matched
292
+ elsif @source.scan(BRACE_CLOSE)
293
+ if @braces.any?
294
+ @braces.pop
295
+ buffer_matched
296
+ move_by_matched
297
+ else
298
+ splat_expression = consume_buffer
299
+ current_attribute[2] = "**#{splat_expression.strip}"
300
+ move_by_matched
301
+ clear_braces
302
+ transition_to(:tag_open_content)
303
+ end
304
+ elsif @source.scan(NEWLINE) || @source.scan(CRLF) || @source.scan(BLANK) || @source.scan(OTHER)
305
+ buffer_matched
306
+ move_by_matched
307
+ else
308
+ syntax_error!("Unexpected end of input while reading splat attribute value")
309
+ end
310
+ end
311
+
278
312
  # Read next token in :attribute_value_unquoted state
279
313
  def next_in_attribute_value_unquoted
280
314
  if @source.scan(UNQUOTED_VALUE)
data/lib/orb/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ORB
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: orb_template
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - KUY.io Inc.