phlex 0.4.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of phlex might be problematic. Click here for more details.

Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +21 -1
  3. data/Gemfile +26 -13
  4. data/README.md +1 -1
  5. data/SECURITY.md +1 -1
  6. data/bench.rb +7 -0
  7. data/config/sus.rb +15 -0
  8. data/docs/assets/application.css +6 -0
  9. data/docs/build.rb +2 -0
  10. data/docs/components/callout.rb +1 -1
  11. data/docs/components/code_block.rb +2 -2
  12. data/docs/components/code_span.rb +1 -1
  13. data/docs/components/example.rb +4 -4
  14. data/docs/components/heading.rb +2 -2
  15. data/docs/components/layout.rb +55 -32
  16. data/docs/components/markdown.rb +13 -28
  17. data/docs/components/nav/item.rb +1 -1
  18. data/docs/components/nav.rb +1 -1
  19. data/docs/components/tabs/tab.rb +1 -1
  20. data/docs/components/tabs.rb +1 -1
  21. data/docs/components/title.rb +2 -2
  22. data/docs/pages/application_page.rb +1 -1
  23. data/docs/pages/helpers.rb +5 -5
  24. data/docs/pages/library/collections.rb +4 -22
  25. data/docs/pages/rails/getting_started.rb +7 -3
  26. data/docs/pages/rails/helpers.rb +3 -1
  27. data/docs/pages/rails/layouts.rb +2 -2
  28. data/docs/pages/rails/rendering_views.rb +1 -1
  29. data/docs/pages/templates.rb +6 -6
  30. data/docs/pages/testing/capybara.rb +48 -0
  31. data/docs/pages/testing/getting_started.rb +44 -0
  32. data/docs/pages/testing/nokogiri.rb +83 -0
  33. data/docs/pages/testing/rails.rb +17 -0
  34. data/docs/pages/translations.rb +81 -0
  35. data/docs/pages/views.rb +56 -8
  36. data/fixtures/compiler_test_helpers.rb +19 -0
  37. data/fixtures/content.rb +60 -0
  38. data/fixtures/dummy/app/views/application_view.rb +8 -0
  39. data/fixtures/dummy/app/views/articles/form.rb +1 -1
  40. data/fixtures/dummy/app/views/card.rb +1 -1
  41. data/fixtures/dummy/app/views/comments/comment.rb +1 -1
  42. data/fixtures/dummy/app/views/comments/reaction.rb +1 -1
  43. data/fixtures/dummy/app/views/heading.rb +1 -1
  44. data/fixtures/layout.rb +5 -5
  45. data/fixtures/page.rb +18 -24
  46. data/fixtures/{test_helper.rb → rails_helper.rb} +3 -8
  47. data/fixtures/standard_element.rb +87 -0
  48. data/fixtures/view_helper.rb +1 -1
  49. data/fixtures/void_element.rb +31 -0
  50. data/lib/generators/phlex/collection/templates/collection.rb.erb +2 -1
  51. data/lib/generators/phlex/controller/USAGE +10 -0
  52. data/lib/generators/phlex/controller/controller_generator.rb +54 -0
  53. data/lib/generators/phlex/controller/templates/controller.rb.erb +10 -0
  54. data/lib/generators/phlex/controller/templates/view.rb.erb +14 -0
  55. data/lib/generators/phlex/layout/templates/layout.rb.erb +2 -1
  56. data/lib/generators/phlex/page/templates/page.rb.erb +3 -1
  57. data/lib/generators/phlex/table/templates/table.rb.erb +3 -1
  58. data/lib/generators/phlex/view/templates/view.rb.erb +7 -1
  59. data/lib/generators/phlex/view/view_generator.rb +9 -1
  60. data/lib/install/phlex.rb +10 -1
  61. data/lib/phlex/block.rb +2 -4
  62. data/lib/phlex/buffered.rb +6 -8
  63. data/lib/phlex/callable.rb +9 -0
  64. data/lib/phlex/collection.rb +2 -27
  65. data/lib/phlex/compiler/elements.rb +49 -0
  66. data/lib/phlex/compiler/generators/content.rb +103 -0
  67. data/lib/phlex/compiler/generators/element.rb +61 -0
  68. data/lib/phlex/compiler/nodes/base.rb +19 -0
  69. data/lib/phlex/compiler/nodes/call.rb +9 -0
  70. data/lib/phlex/compiler/nodes/command.rb +13 -0
  71. data/lib/phlex/compiler/nodes/fcall.rb +18 -0
  72. data/lib/phlex/compiler/nodes/method_add_block.rb +33 -0
  73. data/lib/phlex/compiler/nodes/vcall.rb +9 -0
  74. data/lib/phlex/compiler/optimizer.rb +66 -0
  75. data/lib/phlex/compiler/visitors/base.rb +15 -0
  76. data/lib/phlex/compiler/visitors/file.rb +23 -11
  77. data/lib/phlex/compiler/visitors/stable_scope.rb +28 -0
  78. data/lib/phlex/compiler/visitors/statements.rb +36 -0
  79. data/lib/phlex/compiler/visitors/view.rb +19 -0
  80. data/lib/phlex/compiler/visitors/view_method.rb +59 -0
  81. data/lib/phlex/compiler.rb +23 -3
  82. data/lib/phlex/elements.rb +57 -0
  83. data/lib/phlex/helpers.rb +59 -0
  84. data/lib/phlex/html/callbacks.rb +11 -0
  85. data/lib/phlex/html.rb +208 -47
  86. data/lib/phlex/markdown.rb +76 -0
  87. data/lib/phlex/rails/form.rb +67 -0
  88. data/lib/phlex/rails/helpers.rb +39 -2
  89. data/lib/phlex/rails.rb +10 -0
  90. data/lib/phlex/renderable.rb +9 -3
  91. data/lib/phlex/testing/capybara.rb +25 -0
  92. data/lib/phlex/testing/nokogiri.rb +24 -0
  93. data/lib/phlex/testing/rails.rb +19 -0
  94. data/lib/phlex/testing/view_helper.rb +15 -0
  95. data/lib/phlex/translation.rb +23 -0
  96. data/lib/phlex/turbo/frame.rb +21 -0
  97. data/lib/phlex/turbo/stream.rb +18 -0
  98. data/lib/phlex/version.rb +1 -1
  99. data/lib/phlex.rb +23 -24
  100. metadata +62 -14
  101. data/.rspec +0 -1
  102. data/fixtures/compilation/vcall.rb +0 -38
  103. data/lib/phlex/compiler/generators/standard_element.rb +0 -30
  104. data/lib/phlex/compiler/generators/void_element.rb +0 -29
  105. data/lib/phlex/compiler/optimizers/base_optimizer.rb +0 -34
  106. data/lib/phlex/compiler/optimizers/vcall.rb +0 -29
  107. data/lib/phlex/compiler/visitors/base_visitor.rb +0 -19
  108. data/lib/phlex/compiler/visitors/component.rb +0 -28
  109. data/lib/phlex/compiler/visitors/component_method.rb +0 -28
  110. data/lib/phlex/view.rb +0 -229
data/lib/phlex.rb CHANGED
@@ -1,34 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "cgi"
3
+ require "hescape"
4
4
  require "zeitwerk"
5
5
  require "syntax_tree"
6
6
 
7
- loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
8
- loader.ignore("#{__dir__}/generators")
9
- loader.ignore("#{__dir__}/install")
10
- loader.inflector.inflect("html" => "HTML")
11
- loader.inflector.inflect("vcall" => "VCall")
12
- loader.inflector.inflect("fcall" => "FCall")
13
- loader.setup
14
-
15
7
  module Phlex
8
+ Loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false).tap do |loader|
9
+ loader.ignore("#{__dir__}/generators")
10
+ loader.ignore("#{__dir__}/install")
11
+
12
+ loader.ignore("#{__dir__}/phlex/testing")
13
+ loader.ignore("#{__dir__}/phlex/markdown")
14
+
15
+ loader.ignore("#{__dir__}/phlex/rails.rb")
16
+ loader.ignore("#{__dir__}/phlex/rails")
17
+
18
+ loader.inflector.inflect("html" => "HTML")
19
+ loader.inflector.inflect("vcall" => "VCall")
20
+ loader.inflector.inflect("fcall" => "FCall")
21
+ loader.setup
22
+ end
23
+
16
24
  Error = Module.new
17
25
  ArgumentError = Class.new(ArgumentError) { include Error }
18
26
  NameError = Class.new(NameError) { include Error }
19
27
 
28
+ def self.const_missing(name)
29
+ if name == :View
30
+ raise NameError, "👋 Phlex::View has been renamed (again 🙄) to Phlex::HTML."
31
+ end
32
+ end
33
+
20
34
  extend self
21
35
 
22
36
  ATTRIBUTE_CACHE = {}
23
37
 
24
- def const_missing(name)
25
- if name == :Component
26
- raise NameError, "👋 Phlex::Component is now Phlex::View"
27
- else
28
- super
29
- end
30
- end
31
-
32
38
  def configuration
33
39
  @configuration ||= Configuration.new
34
40
  end
@@ -37,10 +43,3 @@ module Phlex
37
43
  yield configuration
38
44
  end
39
45
  end
40
-
41
- begin
42
- require "rails"
43
- require "phlex/engine"
44
- rescue LoadError
45
- # Rails isn't in this env, don't load the engine.
46
- end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: phlex
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Drapper
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-10-12 00:00:00.000000000 Z
11
+ date: 2022-11-16 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: hescape
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: syntax_tree
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -60,7 +74,6 @@ extensions: []
60
74
  extra_rdoc_files: []
61
75
  files:
62
76
  - ".editorconfig"
63
- - ".rspec"
64
77
  - ".rubocop.yml"
65
78
  - CODE_OF_CONDUCT.md
66
79
  - CONTRIBUTING.md
@@ -72,6 +85,7 @@ files:
72
85
  - SECURITY.md
73
86
  - bench.rb
74
87
  - config.ru
88
+ - config/sus.rb
75
89
  - docs/assets/application.css
76
90
  - docs/assets/logo.png
77
91
  - docs/build.rb
@@ -99,8 +113,14 @@ files:
99
113
  - docs/pages/rails/rendering_views.rb
100
114
  - docs/pages/rails_integration.rb
101
115
  - docs/pages/templates.rb
116
+ - docs/pages/testing/capybara.rb
117
+ - docs/pages/testing/getting_started.rb
118
+ - docs/pages/testing/nokogiri.rb
119
+ - docs/pages/testing/rails.rb
120
+ - docs/pages/translations.rb
102
121
  - docs/pages/views.rb
103
- - fixtures/compilation/vcall.rb
122
+ - fixtures/compiler_test_helpers.rb
123
+ - fixtures/content.rb
104
124
  - fixtures/dummy/app/assets/config/manifest.js
105
125
  - fixtures/dummy/app/components/comment_component.html.erb
106
126
  - fixtures/dummy/app/components/comment_component.rb
@@ -108,6 +128,7 @@ files:
108
128
  - fixtures/dummy/app/components/reaction_component.rb
109
129
  - fixtures/dummy/app/controllers/articles_controller.rb
110
130
  - fixtures/dummy/app/controllers/comments_controller.rb
131
+ - fixtures/dummy/app/views/application_view.rb
111
132
  - fixtures/dummy/app/views/articles/form.rb
112
133
  - fixtures/dummy/app/views/articles/index.html.erb
113
134
  - fixtures/dummy/app/views/articles/new.html.erb
@@ -125,11 +146,17 @@ files:
125
146
  - fixtures/dummy/public/favicon.ico
126
147
  - fixtures/layout.rb
127
148
  - fixtures/page.rb
128
- - fixtures/test_helper.rb
149
+ - fixtures/rails_helper.rb
150
+ - fixtures/standard_element.rb
129
151
  - fixtures/view_helper.rb
152
+ - fixtures/void_element.rb
130
153
  - lib/generators/phlex/collection/USAGE
131
154
  - lib/generators/phlex/collection/collection_generator.rb
132
155
  - lib/generators/phlex/collection/templates/collection.rb.erb
156
+ - lib/generators/phlex/controller/USAGE
157
+ - lib/generators/phlex/controller/controller_generator.rb
158
+ - lib/generators/phlex/controller/templates/controller.rb.erb
159
+ - lib/generators/phlex/controller/templates/view.rb.erb
133
160
  - lib/generators/phlex/layout/USAGE
134
161
  - lib/generators/phlex/layout/layout_generator.rb
135
162
  - lib/generators/phlex/layout/templates/layout.rb.erb
@@ -147,26 +174,47 @@ files:
147
174
  - lib/phlex.rb
148
175
  - lib/phlex/block.rb
149
176
  - lib/phlex/buffered.rb
177
+ - lib/phlex/callable.rb
150
178
  - lib/phlex/collection.rb
151
179
  - lib/phlex/compiler.rb
180
+ - lib/phlex/compiler/elements.rb
152
181
  - lib/phlex/compiler/formatter.rb
153
- - lib/phlex/compiler/generators/standard_element.rb
154
- - lib/phlex/compiler/generators/void_element.rb
155
- - lib/phlex/compiler/optimizers/base_optimizer.rb
156
- - lib/phlex/compiler/optimizers/vcall.rb
157
- - lib/phlex/compiler/visitors/base_visitor.rb
158
- - lib/phlex/compiler/visitors/component.rb
159
- - lib/phlex/compiler/visitors/component_method.rb
182
+ - lib/phlex/compiler/generators/content.rb
183
+ - lib/phlex/compiler/generators/element.rb
184
+ - lib/phlex/compiler/nodes/base.rb
185
+ - lib/phlex/compiler/nodes/call.rb
186
+ - lib/phlex/compiler/nodes/command.rb
187
+ - lib/phlex/compiler/nodes/fcall.rb
188
+ - lib/phlex/compiler/nodes/method_add_block.rb
189
+ - lib/phlex/compiler/nodes/vcall.rb
190
+ - lib/phlex/compiler/optimizer.rb
191
+ - lib/phlex/compiler/visitors/base.rb
160
192
  - lib/phlex/compiler/visitors/file.rb
193
+ - lib/phlex/compiler/visitors/stable_scope.rb
194
+ - lib/phlex/compiler/visitors/statements.rb
195
+ - lib/phlex/compiler/visitors/view.rb
196
+ - lib/phlex/compiler/visitors/view_method.rb
161
197
  - lib/phlex/configuration.rb
198
+ - lib/phlex/elements.rb
162
199
  - lib/phlex/engine.rb
200
+ - lib/phlex/helpers.rb
163
201
  - lib/phlex/html.rb
202
+ - lib/phlex/html/callbacks.rb
203
+ - lib/phlex/markdown.rb
204
+ - lib/phlex/rails.rb
205
+ - lib/phlex/rails/form.rb
164
206
  - lib/phlex/rails/helpers.rb
165
207
  - lib/phlex/rails/layout.rb
166
208
  - lib/phlex/renderable.rb
167
209
  - lib/phlex/table.rb
210
+ - lib/phlex/testing/capybara.rb
211
+ - lib/phlex/testing/nokogiri.rb
212
+ - lib/phlex/testing/rails.rb
213
+ - lib/phlex/testing/view_helper.rb
214
+ - lib/phlex/translation.rb
215
+ - lib/phlex/turbo/frame.rb
216
+ - lib/phlex/turbo/stream.rb
168
217
  - lib/phlex/version.rb
169
- - lib/phlex/view.rb
170
218
  - lib/tasks/phlex_tasks.rake
171
219
  - package-lock.json
172
220
  - package.json
@@ -197,7 +245,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
197
245
  - !ruby/object:Gem::Version
198
246
  version: '0'
199
247
  requirements: []
200
- rubygems_version: 3.3.7
248
+ rubygems_version: 3.3.25
201
249
  signing_key:
202
250
  specification_version: 4
203
251
  summary: A framework for building views with a Ruby DSL.
data/.rspec DELETED
@@ -1 +0,0 @@
1
- --require spec_helper
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Fixtures
4
- module Compilation
5
- module VCall
6
- class WithStandardElement < Phlex::View
7
- def template
8
- div
9
- end
10
- end
11
-
12
- class WithVoidElement < Phlex::View
13
- def template
14
- img
15
- end
16
- end
17
-
18
- class WithAnotherMethodCall < Phlex::View
19
- def template
20
- article
21
- some_other_method
22
- article
23
- end
24
- end
25
-
26
- class WithRedefinedTagMethod < Phlex::View
27
- def template
28
- title
29
- article
30
- end
31
-
32
- def title
33
- h1
34
- end
35
- end
36
- end
37
- end
38
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlex
4
- class Compiler
5
- module Generators
6
- class StandardElement
7
- def initialize(formatter, method_name:, arguments: nil)
8
- @formatter = formatter
9
- @method_name = method_name
10
- end
11
-
12
- def call
13
- @formatter.append do |f|
14
- f.text "<"
15
- f.text tag
16
- f.text ">"
17
-
18
- f.text "</"
19
- f.text tag
20
- f.text ">"
21
- end
22
- end
23
-
24
- def tag
25
- HTML::STANDARD_ELEMENTS[@method_name]
26
- end
27
- end
28
- end
29
- end
30
- end
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlex
4
- class Compiler
5
- module Generators
6
- class VoidElement
7
- def initialize(formatter, method_name:, arguments: nil)
8
- @formatter = formatter
9
- @method_name = method_name
10
- @arguments = arguments
11
- end
12
-
13
- def call
14
- @formatter.append do |f|
15
- f.text "<"
16
- f.text tag
17
- f.text " />"
18
- end
19
- end
20
-
21
- private
22
-
23
- def tag
24
- HTML::VOID_ELEMENTS[@method_name]
25
- end
26
- end
27
- end
28
- end
29
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlex
4
- class Compiler
5
- module Optimizers
6
- class BaseOptimizer
7
- def initialize(node, compiler:)
8
- @node = node
9
- @compiler = compiler
10
- end
11
-
12
- def call
13
- if standard_element?
14
- @node.extend(self.class::StandardElement)
15
- elsif void_element?
16
- @node.extend(self.class::VoidElement)
17
- else
18
- false
19
- end
20
- end
21
-
22
- private
23
-
24
- def standard_element?
25
- HTML::STANDARD_ELEMENTS[name] && !@compiler.redefined?(name)
26
- end
27
-
28
- def void_element?
29
- HTML::VOID_ELEMENTS[name] && !@compiler.redefined?(name)
30
- end
31
- end
32
- end
33
- end
34
- end
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlex
4
- class Compiler
5
- module Optimizers
6
- class VCall < BaseOptimizer
7
- module StandardElement
8
- def format(formatter)
9
- Generators::StandardElement.new(formatter,
10
- method_name: value.value.to_sym).call
11
- end
12
- end
13
-
14
- module VoidElement
15
- def format(formatter)
16
- Generators::VoidElement.new(formatter,
17
- method_name: value.value.to_sym).call
18
- end
19
- end
20
-
21
- private
22
-
23
- def name
24
- @node.value.value.to_sym
25
- end
26
- end
27
- end
28
- end
29
- end
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlex
4
- class Compiler
5
- module Visitors
6
- class BaseVisitor < SyntaxTree::Visitor
7
- def initialize(compiler = nil)
8
- @compiler = compiler
9
- end
10
-
11
- private
12
-
13
- def format(node)
14
- Formatter.format("", node)
15
- end
16
- end
17
- end
18
- end
19
- end
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlex
4
- class Compiler
5
- module Visitors
6
- class Component < BaseVisitor
7
- visit_method def visit_def(node)
8
- visitor = Visitors::ComponentMethod.new(@compiler)
9
- visitor.visit_all(node.child_nodes)
10
-
11
- if visitor.optimized_something?
12
- @compiler.redefine(
13
- format(node)
14
- )
15
- end
16
- end
17
-
18
- visit_method def visit_class(node)
19
- nil
20
- end
21
-
22
- visit_method def visit_module(node)
23
- nil
24
- end
25
- end
26
- end
27
- end
28
- end
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlex
4
- class Compiler
5
- module Visitors
6
- class ComponentMethod < BaseVisitor
7
- def optimized_something?
8
- !!@optimized_something
9
- end
10
-
11
- visit_method def visit_vcall(node)
12
- @optimized_something = Optimizers::VCall.new(node,
13
- compiler: @compiler).call
14
-
15
- super
16
- end
17
-
18
- visit_method def visit_class(node)
19
- nil
20
- end
21
-
22
- visit_method def visit_module(node)
23
- nil
24
- end
25
- end
26
- end
27
- end
28
- end
data/lib/phlex/view.rb DELETED
@@ -1,229 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.0")
4
- using Overrides::Symbol::Name
5
- end
6
-
7
- module Phlex
8
- class View
9
- extend HTML
10
- include Renderable
11
-
12
- class << self
13
- attr_accessor :rendered_at_least_once
14
-
15
- def compile
16
- return if @compiled
17
- return unless name
18
- return if name.start_with? "#"
19
-
20
- Compiler.new(self).call
21
-
22
- @compiled = true
23
- end
24
-
25
- def compiled?
26
- !!@compiled
27
- end
28
- end
29
-
30
- def call(buffer = +"", view_context: nil, parent: nil, &block)
31
- raise "The same view instance shouldn't be rendered twice" if rendered?
32
-
33
- @_rendered = true
34
- @_target = buffer
35
- @_view_context = view_context
36
- @_parent = parent
37
- @output_buffer = self
38
-
39
- template(&block)
40
-
41
- self.class.rendered_at_least_once ||= true
42
-
43
- buffer
44
- end
45
-
46
- def rendered?
47
- @_rendered ||= false
48
- end
49
-
50
- HTML::STANDARD_ELEMENTS.each do |method_name, tag|
51
- register_element(method_name, tag: tag)
52
- end
53
-
54
- HTML::VOID_ELEMENTS.each do |method_name, tag|
55
- register_void_element(method_name, tag: tag)
56
- end
57
-
58
- def yield_content(&block)
59
- return unless block_given?
60
-
61
- original_length = @_target.length
62
- output = yield(self)
63
- unchanged = (original_length == @_target.length)
64
-
65
- if unchanged
66
- case output
67
- when String, Symbol, Integer, Float
68
- text(output)
69
- end
70
- end
71
-
72
- nil
73
- end
74
-
75
- def text(content)
76
- @_target << case content
77
- when String then CGI.escape_html(content)
78
- when Symbol then CGI.escape_html(content.name)
79
- else CGI.escape_html(content.to_s)
80
- end
81
-
82
- nil
83
- end
84
-
85
- def whitespace
86
- @_target << " "
87
- nil
88
- end
89
-
90
- def comment(content = "")
91
- @_target << "<!-- " << CGI.escape_html(content.to_s) << " -->"
92
- nil
93
- end
94
-
95
- def doctype
96
- @_target << HTML::DOCTYPE
97
- nil
98
- end
99
-
100
- def raw(content = nil, &block)
101
- @_target << (content || instance_exec(&block))
102
- nil
103
- end
104
-
105
- def html_safe?
106
- true
107
- end
108
-
109
- def safe_append=(value)
110
- return unless value
111
-
112
- @_target << case value
113
- when String then value
114
- when Symbol then value.name
115
- else value.to_s
116
- end
117
- end
118
-
119
- def append=(value)
120
- return unless value
121
-
122
- if value.html_safe?
123
- self.safe_append = value
124
- else
125
- @_target << case value
126
- when String then CGI.escape_html(value)
127
- when Symbol then CGI.escape_html(value.name)
128
- else CGI.escape_html(value.to_s)
129
- end
130
- end
131
- end
132
-
133
- def capture(&block)
134
- return unless block_given?
135
-
136
- original_buffer = @_target
137
- new_buffer = +""
138
- @_target = new_buffer
139
-
140
- yield
141
-
142
- @_target = original_buffer
143
- new_buffer.html_safe
144
- end
145
-
146
- def classes(*tokens, **conditional_tokens)
147
- tokens = self.tokens(*tokens, **conditional_tokens)
148
-
149
- if tokens.present?
150
- { class: tokens }
151
- else
152
- {}
153
- end
154
- end
155
-
156
- def tokens(*tokens, **conditional_tokens)
157
- conditional_tokens.each do |condition, token|
158
- case condition
159
- when Symbol then next unless send(condition)
160
- when Proc then next unless condition.call
161
- else raise ArgumentError,
162
- "The class condition must be a Symbol or a Proc."
163
- end
164
-
165
- case token
166
- when Symbol then tokens << token.name
167
- when String then tokens << token
168
- when Array then tokens.concat(token)
169
- else raise ArgumentError,
170
- "Conditional classes must be Symbols, Strings, or Arrays of Symbols or Strings."
171
- end
172
- end
173
-
174
- tokens.compact.join(" ")
175
- end
176
-
177
- def helpers
178
- @_view_context
179
- end
180
-
181
- def _attributes(attributes, buffer: +"")
182
- if attributes[:href]&.start_with?(/\s*javascript/)
183
- attributes[:href] = attributes[:href].sub(/^\s*(javascript:)+/, "")
184
- end
185
-
186
- _build_attributes(attributes, buffer: buffer)
187
-
188
- unless self.class.rendered_at_least_once
189
- Phlex::ATTRIBUTE_CACHE[attributes.hash] = buffer.freeze
190
- end
191
-
192
- buffer
193
- end
194
-
195
- def _build_attributes(attributes, buffer:)
196
- attributes.each do |k, v|
197
- next unless v
198
-
199
- name = case k
200
- when String
201
- k
202
- when Symbol
203
- k.name.tr("_", "-")
204
- else
205
- k.to_s
206
- end
207
-
208
- if HTML::EVENT_ATTRIBUTES[name] || name.match?(/[<>&"']/)
209
- raise ArgumentError, "Unsafe attribute name detected: #{k}."
210
- end
211
-
212
- case v
213
- when true
214
- buffer << " " << name
215
- when String
216
- buffer << " " << name << '="' << CGI.escape_html(v) << '"'
217
- when Symbol
218
- buffer << " " << name << '="' << CGI.escape_html(v.name) << '"'
219
- when Hash
220
- _build_attributes(v.transform_keys { "#{k}-#{_1.name.tr('_', '-')}" }, buffer: buffer)
221
- else
222
- buffer << " " << name << '="' << CGI.escape_html(v.to_s) << '"'
223
- end
224
- end
225
-
226
- buffer
227
- end
228
- end
229
- end