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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 703fa9100249c54f63c8e567d5d8cde7b3908b6777dfb555f2d2e72a949d4daa
4
- data.tar.gz: f175e943945bc31a3d4da2be22290e7f843546c0bb6dc0426ad22d1433472fe6
3
+ metadata.gz: 9b8286c7a2de504f1e3320186e144fbeadd0c36cf2b0e56b7c2a677994d3ff80
4
+ data.tar.gz: a03f8ac74485749cf933bab0d0fb3abe16e042c41ba065fd6f259d2379a58225
5
5
  SHA512:
6
- metadata.gz: dcd338b43347af1e971b99d50298d425da85193856f0362a4b7358ad19fbe3b059b5808b6c7ba0bf0f53944244d2bf2a5a7fd8e2283d6d13912f138c683d6b36
7
- data.tar.gz: c902814035da4a002d4006df67b07e8e71256084a9bc3585a256d1b14281eb5bee41c4941b0d7956f4c6d87330bf72cfdfb5edef2b03321a94e99c676af2e331
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
- ```html
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
- ```html
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="{@progress_data}" />
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
- <MyComponent title="Hello World">... </MyComponent>
142
+ <Card title="Hello World">... </Card>
101
143
  ```
102
144
 
103
145
  becomes:
104
146
 
105
147
  ```ruby
106
- <%= render MyComponentComponent.new(title: "Hello World") do %>
148
+ <%= render CardComponent.new(title: "Hello World") do %>
107
149
  ... (processed content) ...
108
150
  <% end %>
109
151
  ```
110
152
 
111
153
  ```erb
112
- <%=<button text="Click Me" />
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
- TAG_REGEX = %r{<([A-Z][a-zA-Z_]*)([^>]*)/?>}
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
- CLOSE_TAG_REGEX = %r{</([A-Z][a-zA-Z_]*)>}
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
- identifier => { source: source, result: result }
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[2].strip
110
+ attributes_str = match[3].strip
97
111
 
98
112
  # Determine the full component class name
99
- component_class_name = if tag_name.end_with?("Component")
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 != closing_tag_name
164
- raise ArgumentError, "Mismatched tags: expected </#{component_class_name}>, got </#{closing_tag_name}> in template #{template.identifier}"
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("")
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EvcRails
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: evc_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - scttymn