ruby2html 1.0.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 57887705c24091d9cf76d5f1b7e41da4167077c9c61b5a408756af3804e0df1b
4
- data.tar.gz: e27fa7060204b4da1c95998451a186cf84492e4a896440baa9ce78eceaaf6f9c
3
+ metadata.gz: 8e43d3eb304652b48e727067ce4361d654936cc0258589efd82adf55136a687d
4
+ data.tar.gz: 25900c6ed9f1a441f3f5a2b6d457e13caa5bfca3db99a7573daa89af92351544
5
5
  SHA512:
6
- metadata.gz: 3267b43294cac4d37620a16076fbb97eaaf7d6e8c639b1b83e7b7657e597739617f5ee051c83a2d2394758a9ab19f0d9dd66243ad36eb2c4057308fec64d9ec2
7
- data.tar.gz: 583b0c34f859915a57de3b7c1518ed7df16225eef381203c53b73fdf8664401567f564a5dd5c42b870b9b75d3c4f37f119dc7fae51b7707907ded372d97b52c7
6
+ metadata.gz: 214487bc59cf8ebc35698f7c16ffca8add3d59dfb52142ef1273e5c1dc67d8351fad286f1b417bfd6e9913ae3c06be8e3bba06c81b9670a5a5561cb47e1ba683
7
+ data.tar.gz: 430e623e958a739a33cdfd16d9d0039ff3354f1461cfe8d7db0a2418ce6fbff4a0352e37a67227e36058088c9c26fac0ffd2aaad50c3842a1ea71a536133610f
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Ruby2html 🔮✨
2
2
 
3
- Transform your Ruby code into beautiful, structured HTML with ease! 🚀
3
+ Transform your view logic into elegant, semantic HTML with the power of pure Ruby! 🚀✨
4
4
 
5
5
  ## 🌟 What is Ruby2html?
6
6
 
@@ -25,6 +25,32 @@ Or install it yourself as:
25
25
 
26
26
  ## 🎨 Usage
27
27
 
28
+ ### In your views
29
+
30
+ File: `app/views/your_view.html.rb`
31
+
32
+ ```ruby
33
+ div class: 'container' do
34
+ h1 'Welcome to Ruby2html! 🎉', class: 'main-title', 'data-controller': 'welcome'
35
+ link_to 'Home Sweet Home 🏠', root_path, class: 'btn btn-primary', 'data-turbo': false
36
+
37
+ @items.each do |item|
38
+ h2 class: 'item-title', id: "item-#{item[:id]}" do
39
+ item[:title]
40
+ end
41
+ p class: 'item-description' do
42
+ item[:description]
43
+ end
44
+ end
45
+ end
46
+
47
+ plain '<div>Inline html</div>'.html_safe
48
+
49
+ render partial: 'shared/navbar'
50
+ ```
51
+
52
+ #### Or use your current .erb views
53
+
28
54
  ### In your ApplicationController
29
55
 
30
56
  File: `app/controllers/application_controller.rb`
@@ -37,8 +63,6 @@ class ApplicationController < ActionController::Base
37
63
  end
38
64
  ```
39
65
 
40
- ### In your views
41
-
42
66
  File: `app/views/your_view.html.erb`
43
67
 
44
68
  Replace your ERB with beautiful Ruby code:
@@ -46,27 +70,47 @@ Replace your ERB with beautiful Ruby code:
46
70
  ```erb
47
71
  <%=
48
72
  html(self) do
49
- h1 class: 'main-title', 'data-controller': 'welcome' do
50
- "Welcome to Ruby2html! 🎉"
51
- end
73
+ h1 "Welcome to Ruby2html! 🎉", class: 'main-title', 'data-controller': 'welcome'
52
74
  div id: 'content', class: 'container' do
53
75
  link_to 'Home Sweet Home 🏠', root_path, class: 'btn btn-primary', 'data-turbo': false
54
76
  end
55
77
 
56
78
  @items.each do |item|
57
79
  h2 class: 'item-title', id: "item-#{item[:id]}" do
58
- plain item[:title]
80
+ item[:title]
59
81
  end
60
82
  p class: 'item-description' do
61
- plain item[:description]
83
+ item[:description]
62
84
  end
63
85
  end
64
86
 
87
+ plain "<div>Inline html</div>".html_safe
88
+
65
89
  render partial: 'shared/navbar'
66
90
  end
67
91
  %>
68
92
  ```
69
93
 
94
+ ### Benchmark
95
+
96
+ ```bash
97
+ ruby 3.3.3 (2024-06-12 revision f1c7b6f435) +YJIT [x86_64-linux]
98
+ Warming up --------------------------------------
99
+ GET /benchmark/html (ERB)
100
+ 2.000 i/100ms
101
+ GET /benchmark/ruby (Ruby2html)
102
+ 1.000 i/100ms
103
+ Calculating -------------------------------------
104
+ GET /benchmark/html (ERB)
105
+ 20.989 (±19.1%) i/s - 102.000 in 5.077353s
106
+ GET /benchmark/ruby (Ruby2html)
107
+ 20.438 (±19.6%) i/s - 97.000 in 5.010249s
108
+
109
+ Comparison:
110
+ GET /benchmark/html (ERB): 21.0 i/s
111
+ GET /benchmark/ruby (Ruby2html): 20.4 i/s - same-ish: difference falls within error
112
+ ```
113
+
70
114
  ### With ViewComponents
71
115
 
72
116
  Ruby2html seamlessly integrates with ViewComponents, offering flexibility in how you define your component's HTML structure. You can use the `call` method with Ruby2html syntax, or stick with traditional `.erb` template files.
@@ -96,10 +140,10 @@ class GreetingComponent < ApplicationComponent
96
140
  def call
97
141
  html(self) do
98
142
  h1 class: 'greeting', 'data-user': @name do
99
- plain "Hello, #{@name}! 👋"
143
+ "Hello, #{@name}! 👋"
100
144
  end
101
145
  p class: 'welcome-message' do
102
- plain 'Welcome to the wonderful world of Ruby2html!'
146
+ 'Welcome to the wonderful world of Ruby2html!'
103
147
  end
104
148
  end
105
149
  end
@@ -155,13 +199,13 @@ class FirstComponent < ApplicationComponent
155
199
  def call
156
200
  html(self) do
157
201
  h1 id: 'first-component-title' do
158
- plain 'first component'
202
+ 'first component'
159
203
  end
160
204
  div class: 'content-wrapper' do
161
205
  h2 'A subheading'
162
206
  end
163
207
  p class: 'greeting-text', 'data-testid': 'greeting' do
164
- plain @item
208
+ @item
165
209
  end
166
210
  end
167
211
  end
@@ -177,7 +221,7 @@ class SecondComponent < ApplicationComponent
177
221
  def call
178
222
  html(self) do
179
223
  h1 class: 'my-class', id: 'second-component-title', 'data-controller': 'second' do
180
- plain 'second component'
224
+ 'second component'
181
225
  end
182
226
  link_to 'Home', root_path, class: 'nav-link', 'data-turbo-frame': false
183
227
  end
@@ -187,13 +231,18 @@ end
187
231
 
188
232
  ## Without Rails
189
233
  ```ruby
190
- html = Ruby2html::Render.new(nil) do # nil is the context, you can use self or any other object
234
+ renderer = Ruby2html::Render.new(nil) do # nil is the context, you can use self or any other object
191
235
  html do
192
- h1 'Hello, World!'
236
+ head do
237
+ title 'Ruby2html Example'
238
+ end
239
+ body do
240
+ h1 'Hello, World!'
241
+ end
193
242
  end
194
243
  end
195
244
 
196
- puts html.render
245
+ puts renderer.render # => "<html><head><title>Ruby2html Example</title></head><body><h1>Hello, World!</h1></body></html>"
197
246
  ```
198
247
 
199
248
  ## 🐢 Gradual Adoption
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ h1 'first component'
4
+
5
+ div do
6
+ h2 'A subheading'
7
+ end
8
+
9
+ p @item
@@ -2,7 +2,7 @@
2
2
 
3
3
  class SecondComponent < ApplicationComponent
4
4
  def call
5
- html(self) do
5
+ html do
6
6
  h1 class: 'my-class' do
7
7
  plain 'Second Component'
8
8
  end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ h1 'Third component'
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ThirdComponent < ApplicationComponent
4
+ def initialize
5
+ @item = 'Hello, World!'
6
+ end
7
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BenchmarkController < ApplicationController
4
+ def normal_html
5
+ @complex_data = generate_complex_data
6
+ end
7
+
8
+ def ruby_2html
9
+ @complex_data = generate_complex_data
10
+ end
11
+
12
+ private
13
+ def generate_complex_data
14
+ {
15
+ users: 50.times.map do |i|
16
+ {
17
+ id: i + 1,
18
+ name: Faker::Name.name,
19
+ email: Faker::Internet.email,
20
+ address: {
21
+ street: Faker::Address.street_address,
22
+ city: Faker::Address.city,
23
+ country: Faker::Address.country
24
+ },
25
+ orders: rand(1..5).times.map do
26
+ {
27
+ id: Faker::Alphanumeric.alphanumeric(number: 10),
28
+ total: Faker::Commerce.price(range: 10..1000.0),
29
+ items: rand(1..10).times.map do
30
+ {
31
+ name: Faker::Commerce.product_name,
32
+ price: Faker::Commerce.price(range: 5..500.0),
33
+ quantity: rand(1..5)
34
+ }
35
+ end
36
+ }
37
+ end
38
+ }
39
+ end,
40
+ stats: {
41
+ total_users: 50,
42
+ average_orders_per_user: rand(1.0..5.0).round(2),
43
+ most_expensive_item: Faker::Commerce.product_name,
44
+ most_popular_country: Faker::Address.country
45
+ }
46
+ }
47
+ end
48
+ end
@@ -9,4 +9,8 @@ class HomeController < ApplicationController
9
9
  }
10
10
  ]
11
11
  end
12
+
13
+ def rb_files
14
+ @value = 'value'
15
+ end
12
16
  end
@@ -0,0 +1,31 @@
1
+ <h1>Benchmark: Normal HTML (ERB)</h1>
2
+
3
+ <h2>User Statistics</h2>
4
+ <ul>
5
+ <li>Total Users: <%= @complex_data[:stats][:total_users] %></li>
6
+ <li>Average Orders per User: <%= @complex_data[:stats][:average_orders_per_user] %></li>
7
+ <li>Most Expensive Item: <%= @complex_data[:stats][:most_expensive_item] %></li>
8
+ <li>Most Popular Country: <%= @complex_data[:stats][:most_popular_country] %></li>
9
+ </ul>
10
+
11
+ <h2>User List</h2>
12
+ <% @complex_data[:users].each do |user| %>
13
+ <div class="user-card">
14
+ <h3><%= user[:name] %></h3>
15
+ <p>Email: <%= user[:email] %></p>
16
+ <p>Address: <%= "#{user[:address][:street]}, #{user[:address][:city]}, #{user[:address][:country]}" %></p>
17
+
18
+ <h4>Orders</h4>
19
+ <% user[:orders].each do |order| %>
20
+ <div class="order">
21
+ <p>Order ID: <%= order[:id] %></p>
22
+ <p>Total: $<%= order[:total] %></p>
23
+ <ul>
24
+ <% order[:items].each do |item| %>
25
+ <li><%= item[:name] %> - $<%= item[:price] %> (Quantity: <%= item[:quantity] %>)</li>
26
+ <% end %>
27
+ </ul>
28
+ </div>
29
+ <% end %>
30
+ </div>
31
+ <% end %>
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ h1 'Benchmark: Ruby2html'
4
+
5
+ h2 'User Statistics'
6
+ ul do
7
+ li { plain "Total Users: #{@complex_data[:stats][:total_users]}" }
8
+ li { plain "Average Orders per User: #{@complex_data[:stats][:average_orders_per_user]}" }
9
+ li { plain "Most Expensive Item: #{@complex_data[:stats][:most_expensive_item]}" }
10
+ li { plain "Most Popular Country: #{@complex_data[:stats][:most_popular_country]}" }
11
+ end
12
+
13
+ h2 'User List'
14
+ @complex_data[:users].each do |user|
15
+ div class: 'user-card' do
16
+ h3 user[:name]
17
+ p { plain "Email: #{user[:email]}" }
18
+ p { plain "Address: #{user[:address][:street]}, #{user[:address][:city]}, #{user[:address][:country]}" }
19
+
20
+ h4 'Orders'
21
+ user[:orders].each do |order|
22
+ div class: 'order' do
23
+ p { plain "Order ID: #{order[:id]}" }
24
+ p { plain "Total: $#{order[:total]}" }
25
+ ul do
26
+ order[:items].each do |item|
27
+ li { plain "#{item[:name]} - $#{item[:price]} (Quantity: #{item[:quantity]})" }
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -2,10 +2,12 @@
2
2
 
3
3
  <%= render FirstComponent.new %>
4
4
  <%= render SecondComponent.new %>
5
+ <%= render ThirdComponent.new %>
5
6
 
6
7
  <%=
7
8
  html(self) do
8
- h1(id: 4) { plain "ok" }
9
+ h1(id: 4) { "Inside" }
10
+ h2 "Inside 2", id: '44'
9
11
  h1 "ok"
10
12
  h1 "ok"
11
13
  h1 "ok"
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ h1 'RbFiles'
4
+ h1 'ok'
5
+ h1 'ok'
6
+
7
+ div @value
8
+
9
+ link_to 'Home', root_url
10
+ render partial: 'shared/navbar'
data/config/routes.rb CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  Rails.application.routes.draw do
4
4
  root to: 'home#index'
5
+ get '/benchmark/html', to: 'benchmark#normal_html'
6
+ get '/benchmark/ruby', to: 'benchmark#ruby_2html'
7
+
8
+ get '/rb_files', to: 'home#rb_files'
9
+
5
10
  # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
6
11
 
7
12
  # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
@@ -2,8 +2,8 @@
2
2
 
3
3
  module Ruby2html
4
4
  module ComponentHelper
5
- def html(context, &block)
6
- Ruby2html::Render.new(context, &block).render.html_safe
5
+ def html(&block)
6
+ Ruby2html::Render.new(self, &block).render.html_safe
7
7
  end
8
8
  end
9
9
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruby2html
4
+ class TemplateHandler
5
+ class_attribute :default_format
6
+ self.default_format = :html
7
+
8
+ def self.call(template, source)
9
+ new.call(template, source)
10
+ end
11
+
12
+ def call(_template, source)
13
+ <<-RUBY
14
+ Ruby2html::Render.new(self) do
15
+ #{source}
16
+ end.render
17
+ RUBY
18
+ end
19
+ end
20
+
21
+ class Railtie < Rails::Railtie
22
+ initializer 'ruby2html.initializer' do
23
+ Rails.autoloaders.main.ignore(
24
+ Rails.root.join('app/views/**/*.html.rb'),
25
+ Rails.root.join('app/components/**/*.html.rb')
26
+ )
27
+ end
28
+ end
29
+ end
30
+
31
+ ActionView::Template.register_template_handler :rb, Ruby2html::TemplateHandler if defined? ActionView::Template
@@ -35,18 +35,24 @@ module Ruby2html
35
35
  return plain @context.render(*args, **options, &block)
36
36
  end
37
37
  instance_exec(&@root)
38
- @output.string
38
+ result = @output.string
39
+ if defined?(ActiveSupport)
40
+ result = ActiveSupport::SafeBuffer.new(result)
41
+ end
42
+
43
+ result
39
44
  end
40
45
 
41
46
  HTML5_TAGS.each do |tag|
42
- define_method(tag) do |*args, &block|
43
- html!(tag, *args, &block)
47
+ define_method(tag) do |*args, **options, &block|
48
+ html!(tag, *args, **options, &block)
44
49
  end
45
50
  end
46
51
 
47
- def html!(name, *args, &block)
48
- attributes = args.first.is_a?(Hash) ? args.shift : {}
49
- content = args.first.to_s
52
+ def html!(name, *args, **options, &block)
53
+ content = args.first.is_a?(String) ? args.shift : nil
54
+ attributes = options
55
+
50
56
  tag_content = StringIO.new
51
57
  tag_content << '<'
52
58
  tag_content << name
@@ -61,10 +67,10 @@ module Ruby2html
61
67
  prev_output = @current_output
62
68
  nested_content = StringIO.new
63
69
  @current_output = nested_content
64
- instance_exec(&block)
70
+ block_result = yield
65
71
  @current_output = prev_output
66
- tag_content << nested_content.string
67
- else
72
+ tag_content << (block_result.is_a?(String) ? escape_html(block_result) : nested_content.string)
73
+ elsif content
68
74
  tag_content << escape_html(content)
69
75
  end
70
76
 
@@ -105,7 +111,7 @@ module Ruby2html
105
111
  define_method(method) do |*args, &block|
106
112
  plain @context.send(method, *args, &block)
107
113
  end
108
- end
114
+ end if defined?(ActionView)
109
115
 
110
116
  def attributes_to_s(attributes)
111
117
  return '' if attributes.empty?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ruby2html
4
- VERSION = '1.0.0'
4
+ VERSION = '1.2.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby2html
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - sebi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-08-08 00:00:00.000000000 Z
11
+ date: 2024-08-09 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Ruby2HTML empowers developers to write view logic in pure Ruby, seamlessly
14
14
  converting it into clean, well-formatted HTML. Enhance your templating workflow,
@@ -33,10 +33,13 @@ files:
33
33
  - app/channels/application_cable/channel.rb
34
34
  - app/channels/application_cable/connection.rb
35
35
  - app/components/application_component.rb
36
- - app/components/first_component.html.erb
36
+ - app/components/first_component.html.rb
37
37
  - app/components/first_component.rb
38
38
  - app/components/second_component.rb
39
+ - app/components/third_component.rb
40
+ - app/components/third_component/third_component.html.rb
39
41
  - app/controllers/application_controller.rb
42
+ - app/controllers/benchmark_controller.rb
40
43
  - app/controllers/concerns/.keep
41
44
  - app/controllers/home_controller.rb
42
45
  - app/helpers/application_helper.rb
@@ -44,7 +47,10 @@ files:
44
47
  - app/mailers/application_mailer.rb
45
48
  - app/models/application_record.rb
46
49
  - app/models/concerns/.keep
50
+ - app/views/benchmark/normal_html.html.erb
51
+ - app/views/benchmark/ruby_2html.html.rb
47
52
  - app/views/home/index.html.erb
53
+ - app/views/home/rb_files.html.rb
48
54
  - app/views/layouts/application.html.erb
49
55
  - app/views/layouts/mailer.html.erb
50
56
  - app/views/layouts/mailer.text.erb
@@ -76,6 +82,7 @@ files:
76
82
  - lib/gem/ruby2html.rb
77
83
  - lib/gem/ruby2html/component_helper.rb
78
84
  - lib/gem/ruby2html/rails_helper.rb
85
+ - lib/gem/ruby2html/railtie.rb
79
86
  - lib/gem/ruby2html/render.rb
80
87
  - lib/gem/ruby2html/version.rb
81
88
  - lib/tasks/.keep
@@ -1,9 +0,0 @@
1
- <%= html(self) do
2
- h1 "first component"
3
-
4
- div do
5
- h2 "A subheading"
6
- end
7
-
8
- p @item
9
- end %>