evc_rails 0.1.0 → 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 +4 -4
- data/README.md +59 -11
- data/lib/evc_rails/template_handler.rb +30 -16
- data/lib/evc_rails/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9b8286c7a2de504f1e3320186e144fbeadd0c36cf2b0e56b7c2a677994d3ff80
|
4
|
+
data.tar.gz: a03f8ac74485749cf933bab0d0fb3abe16e042c41ba065fd6f259d2379a58225
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9f1256d0f4b2d3ae9d9ac59182dab66c32898f6294e739d23819fc8c590e935f8c2ac5f27a3d5622e50aa5e4657af7618fe8b6bfe97da5baaacb0b296a3d0de3
|
7
|
+
data.tar.gz: f6284a6a2d33cbbffa09485895cfbd8b5a9b680073565281b95bdbf32caeb599254daece8fe837e13e1c2f96f3eb4f6b2731be6e1a6ea75cb69a2eb5b07399dc
|
data/README.md
CHANGED
@@ -6,11 +6,13 @@ EvcRails is a Rails gem that introduces a custom .evc template handler, allowing
|
|
6
6
|
|
7
7
|
PascalCase Component Tags: Define and use your View Components with <MyComponent> or self-closing <MyComponent /> syntax directly in your .evc templates.
|
8
8
|
|
9
|
+
Namespaced Components: Support for namespaced components like <Ui::Button /> or <Forms::Input /> for better organization.
|
10
|
+
|
9
11
|
Attribute Handling: Pass attributes to your components using standard HTML-like key="value", key='value', or Ruby expressions key={@variable}.
|
10
12
|
|
11
13
|
Content Blocks: Components can accept content blocks (<MyComponent>content</MyComponent>) which are passed to the View Component via a block.
|
12
14
|
|
13
|
-
Automatic Component Resolution: Automatically appends "Component" to your tag name if it's not already present (e.g., <Button> resolves to ButtonComponent).
|
15
|
+
Automatic Component Resolution: Automatically appends "Component" to your tag name if it's not already present (e.g., <Button> resolves to ButtonComponent, <Ui::Button> resolves to Ui::ButtonComponent).
|
14
16
|
|
15
17
|
Performance Optimized: Includes in-memory caching of compiled templates and memoization of component class lookups for efficient rendering in production.
|
16
18
|
|
@@ -57,7 +59,7 @@ end
|
|
57
59
|
|
58
60
|
And its associated template app/components/my_component_component.html.erb (if you're using separate templates):
|
59
61
|
|
60
|
-
```
|
62
|
+
```erb
|
61
63
|
<!-- app/components/my_component_component.html.erb -->
|
62
64
|
<div class="my-component">
|
63
65
|
<h2><%= @title %></h2>
|
@@ -68,7 +70,7 @@ And its associated template app/components/my_component_component.html.erb (if y
|
|
68
70
|
2. Create an .evc Template
|
69
71
|
Now, create a template file with the .evc extension. For instance, app/views/pages/home.html.evc:
|
70
72
|
|
71
|
-
```
|
73
|
+
```erb
|
72
74
|
<!-- app/views/pages/home.html.evc -->
|
73
75
|
<h1>Welcome to My App</h1>
|
74
76
|
|
@@ -77,8 +79,15 @@ And its associated template app/components/my_component_component.html.erb (if y
|
|
77
79
|
<button text="Click Me" />
|
78
80
|
</MyComponent>
|
79
81
|
|
82
|
+
<%# Namespaced components for better organization %>
|
83
|
+
<Ui::Button text="Click Me" />
|
84
|
+
<Forms::Input name="email" placeholder="Enter your email" />
|
85
|
+
<Layout::Container>
|
86
|
+
<p>Content inside a layout container</p>
|
87
|
+
</Layout::Container>
|
88
|
+
|
80
89
|
<%# A more concise way to render your DoughnutChartComponent %>
|
81
|
-
<DoughnutChart rings=
|
90
|
+
<DoughnutChart rings={@progress_data} />
|
82
91
|
|
83
92
|
<%# You can still use standard ERB within .evc files %>
|
84
93
|
<p><%= link_to "Go somewhere", some_path %></p>
|
@@ -92,24 +101,57 @@ And its associated template app/components/my_component_component.html.erb (if y
|
|
92
101
|
config.eager_load_paths << Rails.root.join("app/components")
|
93
102
|
```
|
94
103
|
|
104
|
+
## Component Organization
|
105
|
+
|
106
|
+
You can organize your components into namespaces for better structure:
|
107
|
+
|
108
|
+
```
|
109
|
+
app/components/
|
110
|
+
├── ui/
|
111
|
+
│ ├── button_component.rb
|
112
|
+
│ ├── card_component.rb
|
113
|
+
│ └── input_component.rb
|
114
|
+
├── forms/
|
115
|
+
│ ├── input_component.rb
|
116
|
+
│ ├── select_component.rb
|
117
|
+
│ └── checkbox_component.rb
|
118
|
+
├── layout/
|
119
|
+
│ ├── container_component.rb
|
120
|
+
│ ├── header_component.rb
|
121
|
+
│ └── footer_component.rb
|
122
|
+
└── my_component_component.rb
|
123
|
+
```
|
124
|
+
|
125
|
+
Then use them in your .evc templates:
|
126
|
+
|
127
|
+
```erb
|
128
|
+
<Ui::Button text="Submit" />
|
129
|
+
<Forms::Input name="username" />
|
130
|
+
<Layout::Container>
|
131
|
+
<Layout::Header title="My App" />
|
132
|
+
<p>Main content</p>
|
133
|
+
<Layout::Footer />
|
134
|
+
</Layout::Container>
|
135
|
+
```
|
136
|
+
|
95
137
|
## How it Works
|
96
138
|
|
97
139
|
When Rails processes an .evc template, EvcRails intercepts it and performs the following transformations:
|
98
140
|
|
99
141
|
```erb
|
100
|
-
<
|
142
|
+
<Card title="Hello World">... </Card>
|
101
143
|
```
|
102
144
|
|
103
145
|
becomes:
|
104
146
|
|
105
147
|
```ruby
|
106
|
-
<%= render
|
148
|
+
<%= render CardComponent.new(title: "Hello World") do %>
|
107
149
|
... (processed content) ...
|
108
150
|
<% end %>
|
109
151
|
```
|
110
152
|
|
111
153
|
```erb
|
112
|
-
|
154
|
+
<Button text="Click Me" />
|
113
155
|
```
|
114
156
|
|
115
157
|
becomes:
|
@@ -118,6 +160,16 @@ becomes:
|
|
118
160
|
<%= render ButtonComponent.new(text: "Click Me") %>
|
119
161
|
```
|
120
162
|
|
163
|
+
```erb
|
164
|
+
<Ui::Button text="Click Me" />
|
165
|
+
```
|
166
|
+
|
167
|
+
becomes:
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
<%= render Ui::ButtonComponent.new(text: "Click Me") %>
|
171
|
+
```
|
172
|
+
|
121
173
|
```erb
|
122
174
|
<DoughnutChart rings={@progress_data} />
|
123
175
|
```
|
@@ -147,7 +199,3 @@ Bug reports and pull requests are welcome on GitHub at [your-gem-repo-link]. Thi
|
|
147
199
|
## License
|
148
200
|
|
149
201
|
The gem is available as open source under the terms of the MIT License
|
150
|
-
|
151
|
-
```
|
152
|
-
|
153
|
-
```
|
@@ -12,16 +12,18 @@ module EvcRails
|
|
12
12
|
module TemplateHandlers
|
13
13
|
class Evc
|
14
14
|
# Regex to match opening or self-closing PascalCase component tags with attributes
|
15
|
-
|
15
|
+
# Updated to support namespaced components like UI::Button
|
16
|
+
TAG_REGEX = %r{<([A-Z][a-zA-Z_]*(::[A-Z][a-zA-Z_]*)*)([^>]*)/?>}
|
16
17
|
|
17
18
|
# Regex to match closing PascalCase component tags
|
18
|
-
|
19
|
+
# Updated to support namespaced components like UI::Button
|
20
|
+
CLOSE_TAG_REGEX = %r{</([A-Z][a-zA-Z_]*(::[A-Z][a-zA-Z_]*)*)>}
|
19
21
|
|
20
22
|
# Regex for attributes: Supports string literals (key="value", key='value')
|
21
23
|
# and Ruby expressions (key={@variable}).
|
22
24
|
# Group 1: Attribute key, Group 2: Double-quoted value, Group 3: Single-quoted value,
|
23
25
|
# Group 4: Ruby expression.
|
24
|
-
ATTRIBUTE_REGEX = /(\w+)=(
|
26
|
+
ATTRIBUTE_REGEX = /(\w+)=(?:"([^"]*)"|'([^']*)'|\{([^}]*)\})/
|
25
27
|
|
26
28
|
# Cache for compiled templates to improve performance.
|
27
29
|
# @cache will store { identifier: { source: original_source, result: compiled_result } }
|
@@ -56,8 +58,8 @@ module EvcRails
|
|
56
58
|
# In development, templates change frequently, so caching would hinder development flow.
|
57
59
|
unless Rails.env.development?
|
58
60
|
self.class.instance_variable_set(:@cache, self.class.instance_variable_get(:@cache).merge({
|
59
|
-
|
60
|
-
|
61
|
+
identifier => { source: source, result: result }
|
62
|
+
}))
|
61
63
|
end
|
62
64
|
result
|
63
65
|
end
|
@@ -72,6 +74,18 @@ module EvcRails
|
|
72
74
|
@component_class_memo = {}
|
73
75
|
end
|
74
76
|
|
77
|
+
# Helper method to determine the full component class name
|
78
|
+
def resolve_component_class_name(tag_name)
|
79
|
+
if tag_name.include?("::")
|
80
|
+
# For namespaced components, just append Component
|
81
|
+
"#{tag_name}Component"
|
82
|
+
elsif tag_name.end_with?("Component")
|
83
|
+
tag_name
|
84
|
+
else
|
85
|
+
"#{tag_name}Component"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
75
89
|
# Processes the .evc template source, converting custom tags into Rails View Component render calls.
|
76
90
|
# This method is recursive to handle nested components.
|
77
91
|
def process_template(source, template)
|
@@ -93,14 +107,10 @@ module EvcRails
|
|
93
107
|
|
94
108
|
is_self_closing = match[0].end_with?("/>")
|
95
109
|
tag_name = match[1]
|
96
|
-
attributes_str = match[
|
110
|
+
attributes_str = match[3].strip
|
97
111
|
|
98
112
|
# Determine the full component class name
|
99
|
-
component_class_name =
|
100
|
-
tag_name
|
101
|
-
else
|
102
|
-
"#{tag_name}Component"
|
103
|
-
end
|
113
|
+
component_class_name = resolve_component_class_name(tag_name)
|
104
114
|
|
105
115
|
# Validate if the component class exists using memoization.
|
106
116
|
# The component class will only be constantized once per unique class name
|
@@ -159,9 +169,15 @@ module EvcRails
|
|
159
169
|
# Pop the corresponding opening tag from the stack
|
160
170
|
component_class_name, render_params_str, start_pos = stack.pop
|
161
171
|
|
172
|
+
# Apply the same transformation to the closing tag name for comparison
|
173
|
+
expected_closing_component_name = resolve_component_class_name(closing_tag_name)
|
174
|
+
|
162
175
|
# Check for mismatched tags (e.g., <div></p>)
|
163
|
-
if component_class_name !=
|
164
|
-
|
176
|
+
if component_class_name != expected_closing_component_name
|
177
|
+
# Extract the original tag name from the component class name for the error message
|
178
|
+
expected_tag_name = component_class_name.gsub(/Component$/, "")
|
179
|
+
raise ArgumentError,
|
180
|
+
"Mismatched tags: expected </#{expected_tag_name}>, got </#{closing_tag_name}> in template #{template.identifier}"
|
165
181
|
end
|
166
182
|
|
167
183
|
# Recursively process the content between the opening and closing tags.
|
@@ -179,9 +195,7 @@ module EvcRails
|
|
179
195
|
end
|
180
196
|
|
181
197
|
# After parsing, if the stack is not empty, it means there are unclosed tags.
|
182
|
-
unless stack.empty?
|
183
|
-
raise ArgumentError, "Unclosed tag <#{stack.last[0]}> in template #{template.identifier}"
|
184
|
-
end
|
198
|
+
raise ArgumentError, "Unclosed tag <#{stack.last[0]}> in template #{template.identifier}" unless stack.empty?
|
185
199
|
|
186
200
|
# Join all the collected parts to form the final ERB string.
|
187
201
|
parts.join("")
|
data/lib/evc_rails/version.rb
CHANGED