phlex 0.4.0 → 0.5.0
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +21 -1
- data/Gemfile +26 -13
- data/README.md +1 -1
- data/SECURITY.md +1 -1
- data/bench.rb +7 -0
- data/config/sus.rb +15 -0
- data/docs/assets/application.css +6 -0
- data/docs/build.rb +2 -0
- data/docs/components/callout.rb +1 -1
- data/docs/components/code_block.rb +2 -2
- data/docs/components/code_span.rb +1 -1
- data/docs/components/example.rb +4 -4
- data/docs/components/heading.rb +2 -2
- data/docs/components/layout.rb +55 -32
- data/docs/components/markdown.rb +13 -28
- data/docs/components/nav/item.rb +1 -1
- data/docs/components/nav.rb +1 -1
- data/docs/components/tabs/tab.rb +1 -1
- data/docs/components/tabs.rb +1 -1
- data/docs/components/title.rb +2 -2
- data/docs/pages/application_page.rb +1 -1
- data/docs/pages/helpers.rb +5 -5
- data/docs/pages/library/collections.rb +4 -22
- data/docs/pages/rails/getting_started.rb +1 -1
- data/docs/pages/rails/helpers.rb +3 -1
- data/docs/pages/rails/layouts.rb +2 -2
- data/docs/pages/rails/rendering_views.rb +1 -1
- data/docs/pages/templates.rb +6 -6
- data/docs/pages/testing/capybara.rb +48 -0
- data/docs/pages/testing/getting_started.rb +44 -0
- data/docs/pages/testing/nokogiri.rb +83 -0
- data/docs/pages/testing/rails.rb +17 -0
- data/docs/pages/translations.rb +81 -0
- data/docs/pages/views.rb +56 -8
- data/fixtures/compiler_test_helpers.rb +19 -0
- data/fixtures/content.rb +60 -0
- data/fixtures/dummy/app/views/application_view.rb +8 -0
- data/fixtures/dummy/app/views/articles/form.rb +1 -1
- data/fixtures/dummy/app/views/card.rb +1 -1
- data/fixtures/dummy/app/views/comments/comment.rb +1 -1
- data/fixtures/dummy/app/views/comments/reaction.rb +1 -1
- data/fixtures/dummy/app/views/heading.rb +1 -1
- data/fixtures/layout.rb +5 -5
- data/fixtures/page.rb +18 -24
- data/fixtures/{test_helper.rb → rails_helper.rb} +3 -8
- data/fixtures/standard_element.rb +87 -0
- data/fixtures/view_helper.rb +1 -1
- data/fixtures/void_element.rb +31 -0
- data/lib/generators/phlex/collection/templates/collection.rb.erb +2 -1
- data/lib/generators/phlex/controller/USAGE +10 -0
- data/lib/generators/phlex/controller/controller_generator.rb +54 -0
- data/lib/generators/phlex/controller/templates/controller.rb.erb +10 -0
- data/lib/generators/phlex/controller/templates/view.rb.erb +14 -0
- data/lib/generators/phlex/layout/templates/layout.rb.erb +2 -1
- data/lib/generators/phlex/page/templates/page.rb.erb +3 -1
- data/lib/generators/phlex/table/templates/table.rb.erb +3 -1
- data/lib/generators/phlex/view/templates/view.rb.erb +7 -1
- data/lib/generators/phlex/view/view_generator.rb +9 -1
- data/lib/install/phlex.rb +10 -1
- data/lib/phlex/block.rb +2 -4
- data/lib/phlex/buffered.rb +6 -8
- data/lib/phlex/callable.rb +9 -0
- data/lib/phlex/collection.rb +2 -27
- data/lib/phlex/compiler/elements.rb +49 -0
- data/lib/phlex/compiler/generators/content.rb +103 -0
- data/lib/phlex/compiler/generators/element.rb +61 -0
- data/lib/phlex/compiler/nodes/base.rb +19 -0
- data/lib/phlex/compiler/nodes/call.rb +9 -0
- data/lib/phlex/compiler/nodes/command.rb +13 -0
- data/lib/phlex/compiler/nodes/fcall.rb +18 -0
- data/lib/phlex/compiler/nodes/method_add_block.rb +33 -0
- data/lib/phlex/compiler/nodes/vcall.rb +9 -0
- data/lib/phlex/compiler/optimizer.rb +66 -0
- data/lib/phlex/compiler/visitors/base.rb +15 -0
- data/lib/phlex/compiler/visitors/file.rb +23 -11
- data/lib/phlex/compiler/visitors/stable_scope.rb +28 -0
- data/lib/phlex/compiler/visitors/statements.rb +36 -0
- data/lib/phlex/compiler/visitors/view.rb +19 -0
- data/lib/phlex/compiler/visitors/view_method.rb +59 -0
- data/lib/phlex/compiler.rb +23 -3
- data/lib/phlex/elements.rb +57 -0
- data/lib/phlex/helpers.rb +59 -0
- data/lib/phlex/html/callbacks.rb +11 -0
- data/lib/phlex/html.rb +208 -47
- data/lib/phlex/markdown.rb +76 -0
- data/lib/phlex/rails/form.rb +67 -0
- data/lib/phlex/rails/helpers.rb +39 -2
- data/lib/phlex/rails.rb +10 -0
- data/lib/phlex/renderable.rb +9 -3
- data/lib/phlex/testing/capybara.rb +25 -0
- data/lib/phlex/testing/nokogiri.rb +24 -0
- data/lib/phlex/testing/rails.rb +19 -0
- data/lib/phlex/testing/view_helper.rb +15 -0
- data/lib/phlex/translation.rb +23 -0
- data/lib/phlex/turbo/frame.rb +21 -0
- data/lib/phlex/turbo/stream.rb +18 -0
- data/lib/phlex/version.rb +1 -1
- data/lib/phlex.rb +22 -24
- metadata +62 -14
- data/.rspec +0 -1
- data/fixtures/compilation/vcall.rb +0 -38
- data/lib/phlex/compiler/generators/standard_element.rb +0 -30
- data/lib/phlex/compiler/generators/void_element.rb +0 -29
- data/lib/phlex/compiler/optimizers/base_optimizer.rb +0 -34
- data/lib/phlex/compiler/optimizers/vcall.rb +0 -29
- data/lib/phlex/compiler/visitors/base_visitor.rb +0 -19
- data/lib/phlex/compiler/visitors/component.rb +0 -28
- data/lib/phlex/compiler/visitors/component_method.rb +0 -28
- data/lib/phlex/view.rb +0 -229
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlex::Compiler::Nodes
|
4
|
+
class FCall < Base
|
5
|
+
def name
|
6
|
+
@node.value.value.to_sym
|
7
|
+
end
|
8
|
+
|
9
|
+
def arguments
|
10
|
+
case @node.arguments
|
11
|
+
in SyntaxTree::Args
|
12
|
+
@node.arguments
|
13
|
+
in SyntaxTree::ArgParen
|
14
|
+
@node.arguments.arguments
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlex::Compiler::Nodes
|
4
|
+
class MethodAddBlock < Base
|
5
|
+
def name
|
6
|
+
method_call.name
|
7
|
+
end
|
8
|
+
|
9
|
+
def arguments
|
10
|
+
method_call.arguments
|
11
|
+
end
|
12
|
+
|
13
|
+
def method_call
|
14
|
+
@method_call ||= case @node.call
|
15
|
+
in SyntaxTree::FCall
|
16
|
+
Phlex::Compiler::Nodes::FCall.new(@node.call)
|
17
|
+
in SyntaxTree::Command
|
18
|
+
Phlex::Compiler::Nodes::Command.new(@node.call)
|
19
|
+
in SyntaxTree::Call
|
20
|
+
Phlex::Compiler::Nodes::Call.new(@node.call)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def content
|
25
|
+
case @node.block
|
26
|
+
in SyntaxTree::BraceBlock
|
27
|
+
@node.block.statements
|
28
|
+
in SyntaxTree::DoBlock
|
29
|
+
@node.block.bodystmt.statements
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Phlex::Compiler
|
4
|
+
class Optimizer
|
5
|
+
def initialize(node, compiler:)
|
6
|
+
@node = node
|
7
|
+
@compiler = compiler
|
8
|
+
end
|
9
|
+
|
10
|
+
def call
|
11
|
+
return optimize_element if optimize_element?
|
12
|
+
|
13
|
+
false
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def optimize_element
|
19
|
+
case @node
|
20
|
+
in Nodes::VCall
|
21
|
+
@node.node.extend(Phlex::Compiler::Elements::VCall)
|
22
|
+
in Nodes::FCall
|
23
|
+
@node.node.extend(Phlex::Compiler::Elements::FCall)
|
24
|
+
in Nodes::Command
|
25
|
+
@node.node.extend(Phlex::Compiler::Elements::Command)
|
26
|
+
in Nodes::MethodAddBlock
|
27
|
+
optimize_add_method_block_element
|
28
|
+
end
|
29
|
+
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
def optimize_add_method_block_element
|
34
|
+
visitor = Phlex::Compiler::Visitors::Statements.new(@compiler)
|
35
|
+
visitor.visit(@node.content)
|
36
|
+
|
37
|
+
if visitor.mutating?
|
38
|
+
@node.node.extend(Phlex::Compiler::Elements::MutatingMethodAddBlock)
|
39
|
+
else
|
40
|
+
@node.node.extend(Phlex::Compiler::Elements::MethodAddBlock)
|
41
|
+
end
|
42
|
+
|
43
|
+
Phlex::Compiler::Visitors::ViewMethod.new(@compiler).visit(@node.content)
|
44
|
+
end
|
45
|
+
|
46
|
+
def optimize_element?
|
47
|
+
element? && !redefined?
|
48
|
+
end
|
49
|
+
|
50
|
+
def element?
|
51
|
+
standard_element? || void_element?
|
52
|
+
end
|
53
|
+
|
54
|
+
def redefined?
|
55
|
+
@compiler.redefined?(@node.name)
|
56
|
+
end
|
57
|
+
|
58
|
+
def standard_element?
|
59
|
+
Phlex::HTML::STANDARD_ELEMENTS.key?(@node.name)
|
60
|
+
end
|
61
|
+
|
62
|
+
def void_element?
|
63
|
+
Phlex::HTML::VOID_ELEMENTS.key?(@node.name)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlex::Compiler::Visitors
|
4
|
+
class Base < SyntaxTree::Visitor
|
5
|
+
def initialize(compiler = nil)
|
6
|
+
@compiler = compiler
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def format(node)
|
12
|
+
Phlex::Compiler::Formatter.format("", node)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -1,17 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module Phlex
|
4
|
-
class
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
3
|
+
module Phlex::Compiler::Visitors
|
4
|
+
class File < Base
|
5
|
+
def initialize(compiler)
|
6
|
+
@scope = []
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
visit_method def visit_class(node)
|
11
|
+
@scope.push(node)
|
12
|
+
|
13
|
+
if node.location.start_line == @compiler.line
|
14
|
+
@compiler.scope = @scope
|
15
|
+
View.new(@compiler).visit_all(node.child_nodes)
|
16
|
+
else
|
17
|
+
super
|
14
18
|
end
|
19
|
+
|
20
|
+
@scope.pop
|
21
|
+
end
|
22
|
+
|
23
|
+
visit_method def visit_module(node)
|
24
|
+
@scope.push(node)
|
25
|
+
super
|
26
|
+
@scope.pop
|
15
27
|
end
|
16
28
|
end
|
17
29
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# A mixin for visitors that stops them from visiting other scopes.
|
4
|
+
|
5
|
+
module Phlex::Compiler::Visitors::StableScope
|
6
|
+
def visit_class(node)
|
7
|
+
nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def visit_module(node)
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def visit_brace_block(node)
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def visit_do_block(node)
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def visit_method_add_block(node)
|
23
|
+
node = Phlex::Compiler::Nodes::MethodAddBlock.new(node)
|
24
|
+
if node.method_call.name == :render
|
25
|
+
visit(node.content)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlex::Compiler::Visitors
|
4
|
+
class Statements < Base
|
5
|
+
MUTATING_METHODS = [:raw, :whitespace, :comment, :text, :doctype]
|
6
|
+
|
7
|
+
include StableScope
|
8
|
+
|
9
|
+
def mutating?
|
10
|
+
!!@mutating
|
11
|
+
end
|
12
|
+
|
13
|
+
visit_method def visit_vcall(node)
|
14
|
+
check Phlex::Compiler::Nodes::VCall.new(node)
|
15
|
+
end
|
16
|
+
|
17
|
+
visit_method def visit_fcall(node)
|
18
|
+
check Phlex::Compiler::Nodes::FCall.new(node)
|
19
|
+
end
|
20
|
+
|
21
|
+
visit_method def visit_command(node)
|
22
|
+
check Phlex::Compiler::Nodes::Command.new(node)
|
23
|
+
end
|
24
|
+
|
25
|
+
visit_method def visit_method_add_block(node)
|
26
|
+
check Phlex::Compiler::Nodes::MethodAddBlock.new(node)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def check(node)
|
32
|
+
@mutating = true if @compiler.tag_method?(node.name)
|
33
|
+
@mutating = true if MUTATING_METHODS.include?(node.name) && !@compiler.redefined?(node.name)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlex::Compiler::Visitors
|
4
|
+
class View < Base
|
5
|
+
include StableScope
|
6
|
+
|
7
|
+
visit_method def visit_def(node)
|
8
|
+
visitor = ViewMethod.new(@compiler)
|
9
|
+
visitor.visit_all(node.child_nodes)
|
10
|
+
|
11
|
+
if visitor.optimized_something?
|
12
|
+
@compiler.redefine(
|
13
|
+
format(node),
|
14
|
+
line: node.location.start_line
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlex::Compiler::Visitors
|
4
|
+
class ViewMethod < Base
|
5
|
+
include StableScope
|
6
|
+
|
7
|
+
def optimized_something?
|
8
|
+
!!@optimized_something
|
9
|
+
end
|
10
|
+
|
11
|
+
visit_method def visit_method_add_block(node)
|
12
|
+
return super if node.call.is_a?(SyntaxTree::Call)
|
13
|
+
|
14
|
+
optimizer = Phlex::Compiler::Optimizer.new(
|
15
|
+
Phlex::Compiler::Nodes::MethodAddBlock.new(node),
|
16
|
+
compiler: @compiler
|
17
|
+
)
|
18
|
+
|
19
|
+
if optimizer.call
|
20
|
+
@optimized_something = true
|
21
|
+
end
|
22
|
+
|
23
|
+
super
|
24
|
+
end
|
25
|
+
|
26
|
+
visit_method def visit_vcall(node)
|
27
|
+
optimizer = Phlex::Compiler::Optimizer.new(
|
28
|
+
Phlex::Compiler::Nodes::VCall.new(node),
|
29
|
+
compiler: @compiler
|
30
|
+
)
|
31
|
+
|
32
|
+
if optimizer.call
|
33
|
+
@optimized_something = true
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
visit_method def visit_fcall(node)
|
38
|
+
optimizer = Phlex::Compiler::Optimizer.new(
|
39
|
+
Phlex::Compiler::Nodes::FCall.new(node),
|
40
|
+
compiler: @compiler
|
41
|
+
)
|
42
|
+
|
43
|
+
if optimizer.call
|
44
|
+
@optimized_something = true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
visit_method def visit_command(node)
|
49
|
+
optimizer = Phlex::Compiler::Optimizer.new(
|
50
|
+
Phlex::Compiler::Nodes::Command.new(node),
|
51
|
+
compiler: @compiler
|
52
|
+
)
|
53
|
+
|
54
|
+
if optimizer.call
|
55
|
+
@optimized_something = true
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/phlex/compiler.rb
CHANGED
@@ -6,6 +6,8 @@ module Phlex
|
|
6
6
|
@view = view
|
7
7
|
end
|
8
8
|
|
9
|
+
attr_writer :scope
|
10
|
+
|
9
11
|
def inspect
|
10
12
|
"#{self.class.name} for #{@view.name} view class"
|
11
13
|
end
|
@@ -14,15 +16,33 @@ module Phlex
|
|
14
16
|
Visitors::File.new(self).visit(tree)
|
15
17
|
end
|
16
18
|
|
19
|
+
def tag_method?(method_name)
|
20
|
+
(HTML::STANDARD_ELEMENTS.key?(method_name) || HTML::VOID_ELEMENTS.key?(method_name)) && !redefined?(method_name)
|
21
|
+
end
|
22
|
+
|
17
23
|
def redefined?(method_name)
|
18
24
|
prototype = @view.allocate
|
19
25
|
|
20
26
|
@view.instance_method(method_name).bind(prototype) !=
|
21
|
-
Phlex::
|
27
|
+
Phlex::HTML.instance_method(method_name).bind(prototype)
|
28
|
+
end
|
29
|
+
|
30
|
+
def redefine(method, line:)
|
31
|
+
patch = scope + method + unscope
|
32
|
+
eval(patch, Kernel.binding, file, (line - 1))
|
33
|
+
end
|
34
|
+
|
35
|
+
def scope
|
36
|
+
@scope.map do |scope|
|
37
|
+
case scope
|
38
|
+
in SyntaxTree::ModuleDeclaration then "module #{scope.constant.constant.value};"
|
39
|
+
in SyntaxTree::ClassDeclaration then "class #{scope.constant.constant.value};"
|
40
|
+
end
|
41
|
+
end.join + "\n"
|
22
42
|
end
|
23
43
|
|
24
|
-
def
|
25
|
-
@
|
44
|
+
def unscope
|
45
|
+
"; end" * @scope.size
|
26
46
|
end
|
27
47
|
|
28
48
|
def line
|
@@ -0,0 +1,57 @@
|
|
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
|
+
module Elements
|
9
|
+
def register_element(element, tag: element.name.tr("_", "-"))
|
10
|
+
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
11
|
+
# frozen_string_literal: true
|
12
|
+
|
13
|
+
def #{element}(content = nil, **attributes, &block)
|
14
|
+
if content
|
15
|
+
raise ArgumentError, %(👋 You can no longer pass content to #{element} as a positional argument.\n Instead, you can pass it as a block, e.g. #{element} { "Hello" })
|
16
|
+
end
|
17
|
+
|
18
|
+
if attributes.length > 0
|
19
|
+
if block_given?
|
20
|
+
@_target << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes.hash] || _attributes(**attributes)) << ">"
|
21
|
+
yield_content(&block)
|
22
|
+
@_target << "</#{tag}>"
|
23
|
+
else
|
24
|
+
@_target << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes.hash] || _attributes(**attributes)) << "></#{tag}>"
|
25
|
+
end
|
26
|
+
else
|
27
|
+
if block_given?
|
28
|
+
@_target << "<#{tag}>"
|
29
|
+
yield_content(&block)
|
30
|
+
@_target << "</#{tag}>"
|
31
|
+
else
|
32
|
+
@_target << "<#{tag}></#{tag}>"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
RUBY
|
39
|
+
end
|
40
|
+
|
41
|
+
def register_void_element(element, tag: element.name.tr("_", "-"))
|
42
|
+
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
43
|
+
# frozen_string_literal: true
|
44
|
+
|
45
|
+
def #{element}(**attributes)
|
46
|
+
if attributes.length > 0
|
47
|
+
@_target << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes.hash] || _attributes(**attributes)) << ">"
|
48
|
+
else
|
49
|
+
@_target << "<#{tag}>"
|
50
|
+
end
|
51
|
+
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
RUBY
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,59 @@
|
|
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::Helpers
|
8
|
+
def tokens(*tokens, **conditional_tokens)
|
9
|
+
conditional_tokens.each do |condition, token|
|
10
|
+
case condition
|
11
|
+
when Symbol then next unless send(condition)
|
12
|
+
when Proc then next unless condition.call
|
13
|
+
else raise ArgumentError,
|
14
|
+
"The class condition must be a Symbol or a Proc."
|
15
|
+
end
|
16
|
+
|
17
|
+
case token
|
18
|
+
when Symbol then tokens << token.name
|
19
|
+
when String then tokens << token
|
20
|
+
when Array then tokens.concat(token)
|
21
|
+
else raise ArgumentError,
|
22
|
+
"Conditional classes must be Symbols, Strings, or Arrays of Symbols or Strings."
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
tokens.compact.join(" ")
|
27
|
+
end
|
28
|
+
|
29
|
+
def classes(*tokens, **conditional_tokens)
|
30
|
+
tokens = self.tokens(*tokens, **conditional_tokens)
|
31
|
+
|
32
|
+
if tokens.empty?
|
33
|
+
{}
|
34
|
+
else
|
35
|
+
{ class: tokens }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def mix(*args)
|
40
|
+
args.each_with_object({}) do |object, result|
|
41
|
+
result.merge!(object) do |_key, old, new|
|
42
|
+
case new
|
43
|
+
when Hash
|
44
|
+
old.is_a?(Hash) ? mix(old, new) : new
|
45
|
+
when Array
|
46
|
+
old.is_a?(Array) ? (old + new) : new
|
47
|
+
when String
|
48
|
+
old.is_a?(String) ? "#{old} #{new}" : new
|
49
|
+
else
|
50
|
+
new
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
result.transform_keys! do |key|
|
55
|
+
key.end_with?("!") ? key.name.chop.to_sym : key
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|