phlex 0.2.2 → 0.3.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/.editorconfig +5 -0
- data/.rubocop.yml +6 -0
- data/CONTRIBUTING.md +23 -0
- data/Rakefile +2 -2
- data/bench.rb +1 -1
- data/docs/assets/application.css +2 -0
- data/docs/build.rb +4 -4
- data/docs/components/callout.rb +5 -5
- data/docs/components/code_block.rb +18 -18
- data/docs/components/example.rb +23 -23
- data/docs/components/heading.rb +5 -5
- data/docs/components/layout.rb +42 -41
- data/docs/components/markdown.rb +19 -19
- data/docs/components/tabs/tab.rb +18 -18
- data/docs/components/tabs.rb +21 -21
- data/docs/components/title.rb +5 -5
- data/docs/page_builder.rb +32 -32
- data/docs/pages/application_page.rb +3 -3
- data/docs/pages/index.rb +23 -25
- data/docs/pages/rails_integration.rb +58 -0
- data/docs/pages/templates.rb +238 -238
- data/docs/pages/views.rb +175 -0
- data/fixtures/compilation/vcall.rb +38 -0
- data/fixtures/dummy/app/views/articles/form.rb +9 -9
- data/fixtures/dummy/app/views/card.rb +8 -8
- data/fixtures/dummy/app/views/heading.rb +5 -5
- data/fixtures/dummy/config/routes.rb +1 -1
- data/fixtures/dummy/db/schema.rb +2 -2
- data/fixtures/layout.rb +24 -24
- data/fixtures/page.rb +34 -34
- data/fixtures/test_helper.rb +2 -2
- data/fixtures/view_helper.rb +16 -0
- data/lib/generators/phlex/component/USAGE +1 -1
- data/lib/generators/phlex/component/component_generator.rb +8 -8
- data/lib/generators/phlex/component/templates/{component.rb.erb → view.rb.erb} +1 -1
- data/lib/install/phlex.rb +18 -0
- data/lib/overrides/symbol/name.rb +1 -1
- data/lib/phlex/block.rb +12 -12
- data/lib/phlex/buffered.rb +13 -13
- data/lib/phlex/compiler/formatter.rb +91 -0
- data/lib/phlex/compiler/generators/standard_element.rb +30 -0
- data/lib/phlex/compiler/generators/void_element.rb +29 -0
- data/lib/phlex/compiler/optimizers/base_optimizer.rb +34 -0
- data/lib/phlex/compiler/optimizers/vcall.rb +29 -0
- data/lib/phlex/compiler/visitors/base_visitor.rb +19 -0
- data/lib/phlex/compiler/visitors/component.rb +28 -0
- data/lib/phlex/compiler/visitors/component_method.rb +28 -0
- data/lib/phlex/compiler/visitors/file.rb +17 -0
- data/lib/phlex/compiler.rb +50 -0
- data/lib/phlex/configuration.rb +3 -3
- data/lib/phlex/engine.rb +11 -0
- data/lib/phlex/html.rb +128 -17
- data/lib/phlex/rails/tag_helpers.rb +23 -23
- data/lib/phlex/renderable.rb +32 -32
- data/lib/phlex/version.rb +1 -1
- data/lib/phlex/view.rb +223 -0
- data/lib/phlex.rb +27 -12
- data/lib/tasks/phlex_tasks.rake +10 -0
- metadata +39 -10
- data/CHANGELOG.md +0 -5
- data/docs/pages/components.rb +0 -175
- data/fixtures/component_helper.rb +0 -16
- data/lib/phlex/component.rb +0 -196
- data/lib/phlex/rails.rb +0 -8
data/docs/pages/views.rb
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pages
|
4
|
+
class Views < ApplicationPage
|
5
|
+
def template
|
6
|
+
render Layout.new(title: "Components in Phlex") do
|
7
|
+
render Markdown.new(<<~MD)
|
8
|
+
# Views
|
9
|
+
|
10
|
+
## Yielding content
|
11
|
+
|
12
|
+
Your views can accept content as a block passed to the template method. You can capture the content block and pass it to the `content` method to yield it.
|
13
|
+
MD
|
14
|
+
|
15
|
+
render Example.new do |e|
|
16
|
+
e.tab "card.rb", <<~RUBY
|
17
|
+
class Card < Phlex::View
|
18
|
+
def template(&)
|
19
|
+
article(class: "drop-shadow rounded p-5") {
|
20
|
+
h1 "Amazing content!"
|
21
|
+
yield_content(&)
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
RUBY
|
26
|
+
|
27
|
+
e.execute "Card.new.call { 'Your content here.\n' }"
|
28
|
+
end
|
29
|
+
|
30
|
+
render Markdown.new(<<~MD)
|
31
|
+
## Delegating content
|
32
|
+
|
33
|
+
Alternatively, you can pass the content down as an argument to another view or tag.
|
34
|
+
MD
|
35
|
+
|
36
|
+
render Example.new do |e|
|
37
|
+
e.tab "card.rb", <<~RUBY
|
38
|
+
class Card < Phlex::View
|
39
|
+
def template(&)
|
40
|
+
article(class: "drop-shadow rounded p-5", &)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
RUBY
|
44
|
+
|
45
|
+
e.execute "Card.new.call { 'Your content here.' }"
|
46
|
+
end
|
47
|
+
|
48
|
+
render Markdown.new(<<~MD)
|
49
|
+
## Nested views
|
50
|
+
|
51
|
+
Components can render other views and optionally pass them content as a block.
|
52
|
+
MD
|
53
|
+
|
54
|
+
render Example.new do |e|
|
55
|
+
e.tab "example.rb", <<~RUBY
|
56
|
+
class Example < Phlex::View
|
57
|
+
def template
|
58
|
+
render Card.new do
|
59
|
+
h1 "Hello"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
RUBY
|
64
|
+
|
65
|
+
e.tab "card.rb", <<~RUBY
|
66
|
+
class Card < Phlex::View
|
67
|
+
def template(&)
|
68
|
+
article(class: "drop-shadow rounded p-5", &)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
RUBY
|
72
|
+
|
73
|
+
e.execute "Example.new.call"
|
74
|
+
end
|
75
|
+
|
76
|
+
render Markdown.new(<<~MD)
|
77
|
+
If the block just wraps a string, the string is treated as _text content_.
|
78
|
+
MD
|
79
|
+
|
80
|
+
render Example.new do |e|
|
81
|
+
e.tab "example.rb", <<~RUBY
|
82
|
+
class Example < Phlex::View
|
83
|
+
def template
|
84
|
+
render(Card.new) { "Hi" }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
RUBY
|
88
|
+
|
89
|
+
e.tab "card.rb", <<~RUBY
|
90
|
+
class Card < Phlex::View
|
91
|
+
def template(&)
|
92
|
+
article(class: "drop-shadow rounded p-5", &)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
RUBY
|
96
|
+
|
97
|
+
e.execute "Example.new.call"
|
98
|
+
end
|
99
|
+
|
100
|
+
render Markdown.new(<<~MD)
|
101
|
+
## Component attributes
|
102
|
+
|
103
|
+
Besides content, views can define attributes in an initializer, which can then be rendered in the template.
|
104
|
+
MD
|
105
|
+
|
106
|
+
render Example.new do |e|
|
107
|
+
e.tab "hello.rb", <<~RUBY
|
108
|
+
class Hello < Phlex::View
|
109
|
+
def initialize(name:)
|
110
|
+
@name = name
|
111
|
+
end
|
112
|
+
|
113
|
+
def template
|
114
|
+
h1 "Hello \#{@name}!"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
RUBY
|
118
|
+
|
119
|
+
e.tab "example.rb", <<~RUBY
|
120
|
+
class Example < Phlex::View
|
121
|
+
def template
|
122
|
+
render Hello.new(name: "Joel")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
RUBY
|
126
|
+
|
127
|
+
e.execute "Example.new.call"
|
128
|
+
end
|
129
|
+
|
130
|
+
render Markdown.new(<<~MD)
|
131
|
+
It’s usually a good idea to use instance variables directly rather than creating accessor methods for them. Otherwise it’s easy to run into naming conflicts. For example, your layout view might have the attribute `title`, to render into a `<title>` element in the document head. If you define `attr_accessor :title`, that would overwrite the `title` method for creating `<title>` elements.
|
132
|
+
|
133
|
+
## Calculations with methods
|
134
|
+
|
135
|
+
Views are just Ruby classes, so you can perform calculations on view attributes by defining your own methods.
|
136
|
+
MD
|
137
|
+
|
138
|
+
render Example.new do |e|
|
139
|
+
e.tab "status.rb", <<~RUBY
|
140
|
+
class Status < Phlex::View
|
141
|
+
def initialize(status:)
|
142
|
+
@status = status
|
143
|
+
end
|
144
|
+
|
145
|
+
def template
|
146
|
+
span status_emoji
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
def status_emoji
|
152
|
+
case @status
|
153
|
+
when :success
|
154
|
+
"✅"
|
155
|
+
when :failure
|
156
|
+
"❌"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
RUBY
|
161
|
+
|
162
|
+
e.tab "example.rb", <<~RUBY
|
163
|
+
class Example < Phlex::View
|
164
|
+
def template
|
165
|
+
render Status.new(status: :success)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
RUBY
|
169
|
+
|
170
|
+
e.execute "Example.new.call"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,38 @@
|
|
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,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Views
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
4
|
+
module Articles
|
5
|
+
class Form < Phlex::View
|
6
|
+
def template
|
7
|
+
form_with url: "test" do |f|
|
8
|
+
f.text_field :name
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
13
|
end
|
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Views
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
class Card < Phlex::View
|
5
|
+
def template(&block)
|
6
|
+
article class: "drop-shadow p-5 rounded", &block
|
7
|
+
end
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
def title(text)
|
10
|
+
h3 text, class: "font-bold"
|
11
|
+
end
|
12
|
+
end
|
13
13
|
end
|
data/fixtures/dummy/db/schema.rb
CHANGED
data/fixtures/layout.rb
CHANGED
@@ -1,31 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Example
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
class LayoutComponent < Phlex::View
|
5
|
+
def initialize(title: "Example")
|
6
|
+
@title = title
|
7
|
+
end
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
9
|
+
def template(&block)
|
10
|
+
html do
|
11
|
+
head do
|
12
|
+
title @title
|
13
|
+
meta name: "viewport", content: "width=device-width,initial-scale=1"
|
14
|
+
link href: "/assets/tailwind.css", rel: "stylesheet"
|
15
|
+
end
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
17
|
+
body class: "bg-zinc-100" do
|
18
|
+
nav class: "p-5", id: "main_nav" do
|
19
|
+
ul do
|
20
|
+
li(class: "p-5") { a "Home", href: "/" }
|
21
|
+
li(class: "p-5") { a "About", href: "/about" }
|
22
|
+
li(class: "p-5") { a "Contact", href: "/contact" }
|
23
|
+
end
|
24
|
+
end
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
26
|
+
div class: "container mx-auto p-5", &block
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
31
|
end
|
data/fixtures/page.rb
CHANGED
@@ -1,41 +1,41 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Example
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
class Page < Phlex::View
|
5
|
+
def template
|
6
|
+
render LayoutComponent.new do
|
7
|
+
h1 "Hi"
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
9
|
+
5.times do
|
10
|
+
div do
|
11
|
+
10.times do
|
12
|
+
a "Test", href: "something", unique: SecureRandom.uuid, data: { value: 1 }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
17
|
+
table do
|
18
|
+
thead do
|
19
|
+
10.times do
|
20
|
+
tr do
|
21
|
+
th "Hi"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
26
|
+
tbody do
|
27
|
+
100.times do
|
28
|
+
tr class: "a b c d e f g", id: "something" do
|
29
|
+
10.times do
|
30
|
+
td class: "f g h i j k l" do
|
31
|
+
span "Test"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
41
|
end
|
data/fixtures/test_helper.rb
CHANGED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewHelper
|
4
|
+
def self.extended(parent)
|
5
|
+
parent.class_exec do
|
6
|
+
let(:output) { example.call }
|
7
|
+
let(:example) { view.new }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def view(&block)
|
12
|
+
let :view do
|
13
|
+
Class.new(Phlex::View, &block)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Phlex
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
module Generators
|
5
|
+
class ViewGenerator < ::Rails::Generators::NamedBase
|
6
|
+
source_root File.expand_path("templates", __dir__)
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
def create_view
|
9
|
+
template "view.rb.erb", File.join("app/views", class_path, "#{file_name}.rb")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
13
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
say "Installing Phlex..."
|
4
|
+
|
5
|
+
application_configuration_path = Rails.root.join("config/application.rb")
|
6
|
+
application_configuration_content = File.read(application_configuration_path)
|
7
|
+
|
8
|
+
pattern = %r(config.autoload_paths << (Rails.root.join\(.app.\)|.\#{root}/app.)\n)
|
9
|
+
|
10
|
+
unless application_configuration_content.match?(pattern)
|
11
|
+
inject_into_class(
|
12
|
+
application_configuration_path,
|
13
|
+
"Application",
|
14
|
+
%( config.autoload_paths << "\#{root}/app"\n)
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
say "Phlex successfully installed!"
|
data/lib/phlex/block.rb
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Phlex
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
class Block
|
5
|
+
def initialize(context, &block)
|
6
|
+
@context = context
|
7
|
+
@block = block
|
8
|
+
end
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
def to_proc
|
11
|
+
method(:call).to_proc
|
12
|
+
end
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
def call(*args, **kwargs)
|
15
|
+
@context.instance_exec(*args, **kwargs, &@block)
|
16
|
+
end
|
17
|
+
end
|
18
18
|
end
|
data/lib/phlex/buffered.rb
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Phlex
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
class Buffered
|
5
|
+
def initialize(object, buffer:)
|
6
|
+
@object, @buffer = object, buffer
|
7
|
+
end
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
9
|
+
def method_missing(name, *args, **kwargs, &block)
|
10
|
+
output = @object.public_send(name, *args, **kwargs, &block)
|
11
|
+
@buffer << output if output.is_a? String
|
12
|
+
nil
|
13
|
+
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
def respond_to_missing?(name)
|
16
|
+
@object.respond_to?(name)
|
17
|
+
end
|
18
|
+
end
|
19
19
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlex
|
4
|
+
class Compiler
|
5
|
+
class Formatter < SyntaxTree::Formatter
|
6
|
+
def genspace
|
7
|
+
-> (n) { "\t" * (n / 2) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def format(node, stackable: true)
|
11
|
+
stack << node if stackable
|
12
|
+
doc = node.format(self)
|
13
|
+
stack.pop if stackable
|
14
|
+
doc
|
15
|
+
end
|
16
|
+
|
17
|
+
def flush
|
18
|
+
text "" if @open_string_append
|
19
|
+
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
def breakable(*args, **kwargs)
|
24
|
+
if !@texting && @open_string_append
|
25
|
+
@broken = kwargs
|
26
|
+
else
|
27
|
+
super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def chain_append(&block)
|
32
|
+
@appending = true
|
33
|
+
|
34
|
+
if @open_string_append
|
35
|
+
text '" << '
|
36
|
+
elsif @open_chain_append
|
37
|
+
text " << "
|
38
|
+
else
|
39
|
+
text "@_target << "
|
40
|
+
end
|
41
|
+
|
42
|
+
@open_string_append = false
|
43
|
+
@open_chain_append = true
|
44
|
+
|
45
|
+
yield(self)
|
46
|
+
|
47
|
+
@appending = false
|
48
|
+
end
|
49
|
+
|
50
|
+
def append(&block)
|
51
|
+
@appending = true
|
52
|
+
|
53
|
+
unless @open_string_append
|
54
|
+
if @open_chain_append
|
55
|
+
text ' << "'
|
56
|
+
else
|
57
|
+
text '@_target << "'
|
58
|
+
end
|
59
|
+
|
60
|
+
@open_chain_append = false
|
61
|
+
@open_string_append = true
|
62
|
+
end
|
63
|
+
|
64
|
+
yield(self)
|
65
|
+
|
66
|
+
@appending = false
|
67
|
+
end
|
68
|
+
|
69
|
+
def text(value, ...)
|
70
|
+
@texting = true
|
71
|
+
|
72
|
+
unless @appending
|
73
|
+
if @open_string_append
|
74
|
+
super('"')
|
75
|
+
@open_string_append = false
|
76
|
+
end
|
77
|
+
|
78
|
+
@open_chain_append = false
|
79
|
+
|
80
|
+
breakable(**@broken) if @broken
|
81
|
+
end
|
82
|
+
|
83
|
+
@broken = false
|
84
|
+
|
85
|
+
super
|
86
|
+
|
87
|
+
@texting = false
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|