component_embedded_ruby 0.2.0 → 0.3.0
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 +4 -4
- data/.github/workflows/ruby.yml +22 -5
- data/.rubocop.yml +44 -0
- data/Gemfile +3 -1
- data/Gemfile.lock +22 -1
- data/README.md +1 -1
- data/Rakefile +3 -1
- data/bin/console +1 -0
- data/component_embedded_ruby.gemspec +9 -6
- data/lib/component_embedded_ruby.rb +6 -1
- data/lib/component_embedded_ruby/{renderer.rb → compiler.rb} +21 -21
- data/lib/component_embedded_ruby/eval.rb +2 -0
- data/lib/component_embedded_ruby/lexer.rb +18 -39
- data/lib/component_embedded_ruby/lexer/input_reader.rb +5 -3
- data/lib/component_embedded_ruby/lexer/ruby_code_reader.rb +45 -0
- data/lib/component_embedded_ruby/lexer/string_reader.rb +40 -0
- data/lib/component_embedded_ruby/node.rb +2 -0
- data/lib/component_embedded_ruby/parser.rb +2 -0
- data/lib/component_embedded_ruby/parser/attribute_parser.rb +4 -4
- data/lib/component_embedded_ruby/parser/base.rb +7 -13
- data/lib/component_embedded_ruby/parser/root_parser.rb +9 -8
- data/lib/component_embedded_ruby/parser/tag_parser.rb +12 -12
- data/lib/component_embedded_ruby/parser/token_reader.rb +2 -0
- data/lib/component_embedded_ruby/rails_handler.rb +13 -0
- data/lib/component_embedded_ruby/template.rb +4 -2
- data/lib/component_embedded_ruby/unexpected_token_error.rb +21 -3
- data/lib/component_embedded_ruby/version.rb +3 -1
- metadata +36 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 00cd94a77693e37fb2723ead82ea26bb40f2e03fd605b33cb41a0f1075bc6ef0
|
4
|
+
data.tar.gz: 00b7ebbfe21f7e9b057b83493016e71409a9657f85f30fd719f95c632827f8a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6619a460eaf27c83545de59f2b8fa5475592d41af7e082371b2c616f6fcb686518dfeb0e5223c5de7a63ba2672abf4f3ab0e0d77ff02a4dbda8a22cbf146af52
|
7
|
+
data.tar.gz: 2f48caa5773e5ab51f52aebc485a32a344bf60feb6515c3287aa0cbe2be3bcd8f8ca2befc3a1384eb9e480209c3fa37a41513b3b86265d105174d07a5702ea3e
|
data/.github/workflows/ruby.yml
CHANGED
@@ -3,18 +3,35 @@ name: Ruby
|
|
3
3
|
on: [push]
|
4
4
|
|
5
5
|
jobs:
|
6
|
-
|
7
|
-
|
6
|
+
test:
|
8
7
|
runs-on: ubuntu-latest
|
9
|
-
|
8
|
+
strategy:
|
9
|
+
matrix:
|
10
|
+
ruby_version: [2.5.x, 2.6.x, 2.7.x]
|
10
11
|
steps:
|
11
12
|
- uses: actions/checkout@v1
|
12
13
|
- name: Set up Ruby 2.6
|
13
14
|
uses: actions/setup-ruby@v1
|
14
15
|
with:
|
15
|
-
ruby-version:
|
16
|
+
ruby-version: ${{ matrix.ruby_version }}
|
16
17
|
- name: Build and test with Rake
|
17
18
|
run: |
|
18
|
-
gem install bundler
|
19
|
+
gem install bundler:1.17.3
|
19
20
|
bundle install --jobs 4 --retry 3
|
20
21
|
bundle exec rake
|
22
|
+
- name: Rubocop
|
23
|
+
run: |
|
24
|
+
bundle exec rubocop
|
25
|
+
lint:
|
26
|
+
runs-on: ubuntu-latest
|
27
|
+
steps:
|
28
|
+
- uses: actions/checkout@v1
|
29
|
+
- name: Set up Ruby 2.6
|
30
|
+
uses: actions/setup-ruby@v1
|
31
|
+
with:
|
32
|
+
ruby-version: 2.6.x
|
33
|
+
- name: Rubocop
|
34
|
+
run: |
|
35
|
+
gem install bundler
|
36
|
+
bundle install --jobs 4 --retry 3
|
37
|
+
bundle exec rubocop
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.5
|
3
|
+
|
4
|
+
Metrics/MethodLength:
|
5
|
+
Max: 20
|
6
|
+
Exclude:
|
7
|
+
- 'test/**/*.rb'
|
8
|
+
|
9
|
+
Metrics/ClassLength:
|
10
|
+
Max: 150
|
11
|
+
Exclude:
|
12
|
+
- 'test/**/*.rb'
|
13
|
+
|
14
|
+
Metrics/AbcSize:
|
15
|
+
Enabled: false
|
16
|
+
|
17
|
+
Security/Eval:
|
18
|
+
Exclude:
|
19
|
+
- 'test/**/*.rb'
|
20
|
+
|
21
|
+
Lint/MissingSuper:
|
22
|
+
Enabled: false
|
23
|
+
|
24
|
+
Style/Documentation:
|
25
|
+
Enabled: false
|
26
|
+
|
27
|
+
Style/NegatedWhile:
|
28
|
+
Enabled: false
|
29
|
+
|
30
|
+
Style/StringLiterals:
|
31
|
+
EnforcedStyle: double_quotes
|
32
|
+
|
33
|
+
Style/CaseLikeIf:
|
34
|
+
Enabled: false
|
35
|
+
|
36
|
+
Style/MultipleComparison:
|
37
|
+
Enabled: false
|
38
|
+
|
39
|
+
Naming/MemoizedInstanceVariableName:
|
40
|
+
EnforcedStyleForLeadingUnderscores: required
|
41
|
+
|
42
|
+
Naming/AccessorMethodName:
|
43
|
+
Exclude:
|
44
|
+
- 'test/**/*.rb'
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -86,11 +86,12 @@ GIT
|
|
86
86
|
PATH
|
87
87
|
remote: .
|
88
88
|
specs:
|
89
|
-
component_embedded_ruby (0.
|
89
|
+
component_embedded_ruby (0.3.0)
|
90
90
|
|
91
91
|
GEM
|
92
92
|
remote: https://rubygems.org/
|
93
93
|
specs:
|
94
|
+
ast (2.4.1)
|
94
95
|
builder (3.2.4)
|
95
96
|
coderay (1.1.2)
|
96
97
|
concurrent-ruby (1.1.7)
|
@@ -115,6 +116,9 @@ GEM
|
|
115
116
|
nio4r (2.5.4)
|
116
117
|
nokogiri (1.10.10)
|
117
118
|
mini_portile2 (~> 2.4.0)
|
119
|
+
parallel (1.19.2)
|
120
|
+
parser (2.7.2.0)
|
121
|
+
ast (~> 2.4.1)
|
118
122
|
pry (0.12.2)
|
119
123
|
coderay (~> 1.1.0)
|
120
124
|
method_source (~> 0.9.0)
|
@@ -126,7 +130,22 @@ GEM
|
|
126
130
|
nokogiri (>= 1.6)
|
127
131
|
rails-html-sanitizer (1.3.0)
|
128
132
|
loofah (~> 2.3)
|
133
|
+
rainbow (3.0.0)
|
129
134
|
rake (13.0.1)
|
135
|
+
regexp_parser (1.8.2)
|
136
|
+
rexml (3.2.4)
|
137
|
+
rubocop (1.0.0)
|
138
|
+
parallel (~> 1.10)
|
139
|
+
parser (>= 2.7.1.5)
|
140
|
+
rainbow (>= 2.2.2, < 4.0)
|
141
|
+
regexp_parser (>= 1.8)
|
142
|
+
rexml
|
143
|
+
rubocop-ast (>= 0.6.0)
|
144
|
+
ruby-progressbar (~> 1.7)
|
145
|
+
unicode-display_width (>= 1.4.0, < 2.0)
|
146
|
+
rubocop-ast (1.0.1)
|
147
|
+
parser (>= 2.7.1.5)
|
148
|
+
ruby-progressbar (1.10.1)
|
130
149
|
sprockets (4.0.2)
|
131
150
|
concurrent-ruby (~> 1.0)
|
132
151
|
rack (> 1, < 3)
|
@@ -137,6 +156,7 @@ GEM
|
|
137
156
|
thor (1.0.1)
|
138
157
|
tzinfo (2.0.2)
|
139
158
|
concurrent-ruby (~> 1.0)
|
159
|
+
unicode-display_width (1.7.0)
|
140
160
|
view_component (2.20.0)
|
141
161
|
activesupport (>= 5.0.0, < 7.0)
|
142
162
|
websocket-driver (0.7.3)
|
@@ -154,6 +174,7 @@ DEPENDENCIES
|
|
154
174
|
pry (~> 0.12.2)
|
155
175
|
rails!
|
156
176
|
rake (~> 13.0)
|
177
|
+
rubocop (~> 1.0)
|
157
178
|
view_component (> 2.15)
|
158
179
|
|
159
180
|
BUNDLED WITH
|
data/README.md
CHANGED
@@ -9,7 +9,7 @@ Strict HTML templating with support for components.
|
|
9
9
|
extra_classes %>`, instead this logic should be pushed up to components.
|
10
10
|
* Component rendering has a single dependency, a `render` method being present
|
11
11
|
in the rendering context.
|
12
|
-
*
|
12
|
+
* automatic Rails support for `crb` extension
|
13
13
|
|
14
14
|
### Usage
|
15
15
|
|
data/Rakefile
CHANGED
data/bin/console
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
|
-
lib = File.expand_path("
|
3
|
+
lib = File.expand_path("lib", __dir__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require "component_embedded_ruby/version"
|
5
6
|
|
@@ -9,8 +10,8 @@ Gem::Specification.new do |spec|
|
|
9
10
|
spec.authors = ["Blake Williams"]
|
10
11
|
spec.email = ["blake@blakewilliams.me"]
|
11
12
|
|
12
|
-
spec.summary =
|
13
|
-
spec.description =
|
13
|
+
spec.summary = "HTML templates with embedded Ruby components"
|
14
|
+
spec.description = "HTML templates with embedded Ruby components"
|
14
15
|
spec.homepage = "https://github.com/blakewilliams/component_embedded_ruby"
|
15
16
|
spec.license = "MIT"
|
16
17
|
|
@@ -26,17 +27,19 @@ Gem::Specification.new do |spec|
|
|
26
27
|
|
27
28
|
# Specify which files should be added to the gem when it is released.
|
28
29
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
29
|
-
spec.files
|
30
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
30
31
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
31
32
|
end
|
32
33
|
spec.bindir = "exe"
|
33
34
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
34
35
|
spec.require_paths = ["lib"]
|
36
|
+
spec.required_ruby_version = ">= 2.5"
|
35
37
|
|
36
38
|
spec.add_development_dependency "bundler", "~> 1.17"
|
37
|
-
spec.add_development_dependency "rake", "~> 13.0"
|
38
39
|
spec.add_development_dependency "minitest", "~> 5.0"
|
40
|
+
spec.add_development_dependency "pry", "~> 0.12.2"
|
39
41
|
spec.add_development_dependency "rails", "> 6.0"
|
42
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
43
|
+
spec.add_development_dependency "rubocop", "~> 1.0"
|
40
44
|
spec.add_development_dependency "view_component", "> 2.15"
|
41
|
-
spec.add_development_dependency "pry", "~> 0.12.2"
|
42
45
|
end
|
@@ -1,12 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "component_embedded_ruby/version"
|
2
4
|
require "component_embedded_ruby/lexer"
|
3
5
|
require "component_embedded_ruby/parser"
|
4
6
|
require "component_embedded_ruby/eval"
|
5
7
|
require "component_embedded_ruby/node"
|
6
|
-
require "component_embedded_ruby/
|
8
|
+
require "component_embedded_ruby/compiler"
|
7
9
|
require "component_embedded_ruby/template"
|
8
10
|
require "component_embedded_ruby/unexpected_token_error"
|
9
11
|
|
12
|
+
# Rails support
|
13
|
+
require "component_embedded_ruby/rails_handler" if defined?(Rails::Railtie)
|
14
|
+
|
10
15
|
module ComponentEmbeddedRuby
|
11
16
|
class Error < StandardError; end
|
12
17
|
end
|
@@ -1,5 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ComponentEmbeddedRuby
|
2
|
-
class
|
4
|
+
class Compiler
|
3
5
|
def initialize(nodes, output_var_name: "__crb_out", skip_return: false)
|
4
6
|
@nodes = Array(nodes)
|
5
7
|
@functions = {}
|
@@ -8,25 +10,25 @@ module ComponentEmbeddedRuby
|
|
8
10
|
end
|
9
11
|
|
10
12
|
def to_ruby
|
11
|
-
<<~
|
13
|
+
<<~RUBY
|
12
14
|
#{output_var_name} = '';
|
13
15
|
|
14
16
|
#{nodes.map(&method(:render)).join("\n")}
|
15
17
|
|
16
|
-
#{output_var_name
|
17
|
-
|
18
|
+
#{output_var_name unless @skip_return};
|
19
|
+
RUBY
|
18
20
|
end
|
19
21
|
|
20
22
|
private
|
21
23
|
|
22
|
-
def render(node)
|
24
|
+
def render(node) # rubocop:disable Metrics/PerceivedComplexity
|
23
25
|
if node.component?
|
24
|
-
<<~
|
26
|
+
<<~RUBY
|
25
27
|
#{children_to_ruby(node)}
|
26
|
-
#{output_var_name}.<< render(#{node.component_class}.new(#{attributes_for_component(node).join(
|
27
|
-
__c_#{node.hash.to_s.gsub(
|
28
|
+
#{output_var_name}.<< render(#{node.component_class}.new(#{attributes_for_component(node).join(',')})) { |component|
|
29
|
+
__c_#{node.hash.to_s.gsub('-', '_')}
|
28
30
|
};
|
29
|
-
|
31
|
+
RUBY
|
30
32
|
elsif node.ruby?
|
31
33
|
if node.output_ruby?
|
32
34
|
"#{output_var_name}.<< (#{node.children.value}).to_s;\n"
|
@@ -36,22 +38,22 @@ module ComponentEmbeddedRuby
|
|
36
38
|
elsif node.text?
|
37
39
|
"#{output_var_name}.<< \"#{node.children}\";\n"
|
38
40
|
elsif node.html?
|
39
|
-
<<~
|
41
|
+
<<~RUBY
|
40
42
|
#{output_var_name}.<< \"<#{node.tag}\";
|
41
43
|
#{attributes_for_tag(node).join("\n")};
|
42
44
|
#{output_var_name}.<< \">\";
|
43
45
|
#{node.children.map(&method(:render)).join("\n")}
|
44
46
|
#{output_var_name}.<< \"</#{node.tag}>\";
|
45
|
-
|
47
|
+
RUBY
|
46
48
|
end
|
47
49
|
end
|
48
50
|
|
49
|
-
attr_reader :output_var_name
|
51
|
+
attr_reader :output_var_name, :nodes
|
50
52
|
|
51
53
|
def children_to_ruby(node)
|
52
54
|
self.class.new(
|
53
55
|
node.children,
|
54
|
-
output_var_name: "__c_#{node.hash.to_s.gsub(
|
56
|
+
output_var_name: "__c_#{node.hash.to_s.gsub('-', '_')}",
|
55
57
|
skip_return: true
|
56
58
|
).to_ruby
|
57
59
|
end
|
@@ -69,17 +71,15 @@ module ComponentEmbeddedRuby
|
|
69
71
|
def attributes_for_tag(node)
|
70
72
|
node.attributes.map do |key, value|
|
71
73
|
if value.is_a?(Eval)
|
72
|
-
<<~
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
74
|
+
<<~RUBY
|
75
|
+
#{output_var_name}.<< " #{key}=\\"";
|
76
|
+
#{output_var_name}.<< (#{value.value}).to_s;
|
77
|
+
#{output_var_name}.<< "\\"";
|
78
|
+
RUBY
|
77
79
|
else
|
78
|
-
%W
|
80
|
+
%W[#{output_var_name}.<< "key="";\n]
|
79
81
|
end
|
80
82
|
end
|
81
83
|
end
|
82
|
-
|
83
|
-
attr_reader :nodes
|
84
84
|
end
|
85
85
|
end
|
@@ -1,4 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "component_embedded_ruby/lexer/input_reader"
|
4
|
+
require "component_embedded_ruby/lexer/string_reader"
|
5
|
+
require "component_embedded_ruby/lexer/ruby_code_reader"
|
2
6
|
|
3
7
|
module ComponentEmbeddedRuby
|
4
8
|
class Lexer
|
@@ -11,7 +15,7 @@ module ComponentEmbeddedRuby
|
|
11
15
|
@tokens = []
|
12
16
|
end
|
13
17
|
|
14
|
-
def lex
|
18
|
+
def lex # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
15
19
|
while !reader.eof?
|
16
20
|
char = reader.current_char
|
17
21
|
|
@@ -24,7 +28,7 @@ module ComponentEmbeddedRuby
|
|
24
28
|
elsif char == "="
|
25
29
|
add_token(:equals, "=")
|
26
30
|
reader.next
|
27
|
-
elsif char == "
|
31
|
+
elsif char == '"'
|
28
32
|
add_token(:string, read_quoted_string)
|
29
33
|
elsif char == "/"
|
30
34
|
add_token(:slash, "/")
|
@@ -36,11 +40,13 @@ module ComponentEmbeddedRuby
|
|
36
40
|
else
|
37
41
|
add_token(:ruby, read_ruby_string)
|
38
42
|
end
|
39
|
-
elsif
|
43
|
+
elsif letter?(char)
|
44
|
+
position = Position.new(reader.current_line, reader.current_column)
|
45
|
+
|
40
46
|
if @tokens[-1]&.type == :close_carrot
|
41
|
-
add_token(:string, read_body_string)
|
47
|
+
add_token(:string, read_body_string, position)
|
42
48
|
else
|
43
|
-
add_token(:identifier, read_string)
|
49
|
+
add_token(:identifier, read_string, position)
|
44
50
|
end
|
45
51
|
else
|
46
52
|
reader.next
|
@@ -53,17 +59,16 @@ module ComponentEmbeddedRuby
|
|
53
59
|
private
|
54
60
|
|
55
61
|
attr_reader :reader
|
56
|
-
attr_accessor :position
|
57
62
|
|
58
|
-
def add_token(type, value)
|
59
|
-
token = Token.new(type, value,
|
63
|
+
def add_token(type, value, position = Position.new(reader.current_line, reader.current_column))
|
64
|
+
token = Token.new(type, value, position)
|
60
65
|
@tokens << token
|
61
66
|
end
|
62
67
|
|
63
68
|
def read_string
|
64
69
|
string = ""
|
65
70
|
|
66
|
-
while
|
71
|
+
while letter?(reader.current_char) && !reader.eof?
|
67
72
|
string += reader.current_char
|
68
73
|
reader.next
|
69
74
|
end
|
@@ -79,6 +84,7 @@ module ComponentEmbeddedRuby
|
|
79
84
|
|
80
85
|
while !unescaped_quote?
|
81
86
|
raise "unterminated string" if reader.eof?
|
87
|
+
|
82
88
|
string += reader.current_char
|
83
89
|
reader.next
|
84
90
|
end
|
@@ -103,41 +109,14 @@ module ComponentEmbeddedRuby
|
|
103
109
|
end
|
104
110
|
|
105
111
|
def read_ruby_string
|
106
|
-
|
107
|
-
inner_bracket_count = 0
|
108
|
-
|
109
|
-
string = ""
|
110
|
-
|
111
|
-
reader.next
|
112
|
-
|
113
|
-
previous_token = nil
|
114
|
-
|
115
|
-
loop do
|
116
|
-
break if inner_bracket_count == 0 && inner_string_count % 2 == 0 && reader.current_char == "}"
|
117
|
-
char = reader.current_char
|
118
|
-
string += char
|
119
|
-
|
120
|
-
# TODO handle " and ' separately
|
121
|
-
if inner_string_count % 2 == 0 && char == "{"
|
122
|
-
inner_bracket_count += 1
|
123
|
-
elsif inner_string_count % 2 == 0 && char == "}"
|
124
|
-
inner_bracket_count -= 1
|
125
|
-
elsif previous_token != "\\" && char == "\"" || char == "'"
|
126
|
-
inner_string_count -= 1
|
127
|
-
end
|
128
|
-
|
129
|
-
previous_token = char
|
130
|
-
reader.next
|
131
|
-
end
|
132
|
-
|
133
|
-
string
|
112
|
+
RubyCodeReader.new(reader).read_until_closing_tag
|
134
113
|
end
|
135
114
|
|
136
115
|
def unescaped_quote?
|
137
|
-
reader.current_char == "
|
116
|
+
reader.current_char == '"' && reader.peek_behind != '\\'
|
138
117
|
end
|
139
118
|
|
140
|
-
def
|
119
|
+
def letter?(char)
|
141
120
|
ascii = char.ord
|
142
121
|
(ascii >= 48 && ascii <= 57) || (ascii >= 65 && ascii <= 122) || ascii == 45 || ascii == 95 || ascii == 58
|
143
122
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ComponentEmbeddedRuby
|
2
4
|
class Lexer
|
3
5
|
class InputReader
|
@@ -7,8 +9,8 @@ module ComponentEmbeddedRuby
|
|
7
9
|
@input = input.freeze
|
8
10
|
@position = 0
|
9
11
|
|
10
|
-
@current_line =
|
11
|
-
@current_column =
|
12
|
+
@current_line = 1
|
13
|
+
@current_column = 1
|
12
14
|
end
|
13
15
|
|
14
16
|
def eof?
|
@@ -30,7 +32,7 @@ module ComponentEmbeddedRuby
|
|
30
32
|
def next
|
31
33
|
if current_char == "\n"
|
32
34
|
@current_line += 1
|
33
|
-
@current_column =
|
35
|
+
@current_column = 1
|
34
36
|
else
|
35
37
|
@current_column += 1
|
36
38
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ComponentEmbeddedRuby
|
4
|
+
class Lexer
|
5
|
+
class RubyCodeReader
|
6
|
+
def initialize(input_reader)
|
7
|
+
@input_reader = input_reader
|
8
|
+
end
|
9
|
+
|
10
|
+
def read_until_closing_tag
|
11
|
+
string = ""
|
12
|
+
|
13
|
+
loop do
|
14
|
+
input_reader.next
|
15
|
+
current_char = input_reader.current_char
|
16
|
+
|
17
|
+
break if current_char == "}"
|
18
|
+
|
19
|
+
string += current_char
|
20
|
+
|
21
|
+
if current_char == "{"
|
22
|
+
string += RubyCodeReader.new(input_reader).read_until_closing_tag
|
23
|
+
|
24
|
+
# RubyCodeReader reads until "}", so we have to re-add that
|
25
|
+
# character when reading nested code
|
26
|
+
string += "}"
|
27
|
+
elsif current_char == "'" || current_char == '"'
|
28
|
+
# TODO: this *may* need to handle % syntax strings too
|
29
|
+
string += StringReader.new(
|
30
|
+
input_reader,
|
31
|
+
quote_type: current_char,
|
32
|
+
can_interpolate: current_char == '"'
|
33
|
+
).read_until_closing_quote
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
string
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
attr_reader :input_reader
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ComponentEmbeddedRuby
|
4
|
+
class Lexer
|
5
|
+
class StringReader
|
6
|
+
def initialize(input_reader, quote_type:, can_interpolate:)
|
7
|
+
@input_reader = input_reader
|
8
|
+
@quote_type = quote_type
|
9
|
+
@can_interpolate = can_interpolate
|
10
|
+
end
|
11
|
+
|
12
|
+
def read_until_closing_quote
|
13
|
+
string = ""
|
14
|
+
|
15
|
+
previous_char = nil
|
16
|
+
current_char = input_reader.current_char
|
17
|
+
|
18
|
+
loop do
|
19
|
+
input_reader.next
|
20
|
+
previous_char = current_char
|
21
|
+
current_char = input_reader.current_char
|
22
|
+
|
23
|
+
string += current_char
|
24
|
+
break if current_char == quote_type && string[-1] != "\\"
|
25
|
+
|
26
|
+
if can_interpolate && string[-2..-1] == '#{' && string[-3] != "\\"
|
27
|
+
string += RubyCodeReader.new(input_reader).read_until_closing_tag
|
28
|
+
string += "}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
string
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
attr_reader :input_reader, :quote_type, :can_interpolate
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ComponentEmbeddedRuby
|
2
4
|
module Parser
|
3
5
|
# Internal: Parses an HTML tag attributes into a hash of key values
|
@@ -17,9 +19,7 @@ module ComponentEmbeddedRuby
|
|
17
19
|
def call
|
18
20
|
attributes = {}
|
19
21
|
|
20
|
-
|
21
|
-
attributes.merge!(parse_attribute)
|
22
|
-
end
|
22
|
+
attributes.merge!(parse_attribute) while current_token.type == :identifier
|
23
23
|
|
24
24
|
attributes
|
25
25
|
end
|
@@ -37,7 +37,7 @@ module ComponentEmbeddedRuby
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def parse_value
|
40
|
-
value_token = expect_any(:string, :ruby)
|
40
|
+
value_token = expect_any(:string, :ruby, expected_message: "a string or ruby code")
|
41
41
|
|
42
42
|
if value_token.type == :string
|
43
43
|
value_token.value
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ComponentEmbeddedRuby
|
2
4
|
module Parser
|
3
5
|
class Base
|
@@ -19,25 +21,17 @@ module ComponentEmbeddedRuby
|
|
19
21
|
|
20
22
|
def expect(type)
|
21
23
|
token = current_token
|
24
|
+
raise UnexpectedTokenError.new(type, current_token) if token.type != type
|
22
25
|
|
23
|
-
|
24
|
-
raise UnexpectedTokenError.new(:string, current_token)
|
25
|
-
else
|
26
|
-
token_reader.next
|
27
|
-
end
|
28
|
-
|
26
|
+
token_reader.next
|
29
27
|
token
|
30
28
|
end
|
31
29
|
|
32
|
-
def expect_any(*types)
|
30
|
+
def expect_any(*types, expected_message:)
|
33
31
|
token = current_token
|
32
|
+
raise UnexpectedTokenError.new(expected_message, token) unless types.include?(token.type)
|
34
33
|
|
35
|
-
|
36
|
-
raise UnexpectedTokenError.new(:string, token)
|
37
|
-
else
|
38
|
-
token_reader.next
|
39
|
-
end
|
40
|
-
|
34
|
+
token_reader.next
|
41
35
|
token
|
42
36
|
end
|
43
37
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ComponentEmbeddedRuby
|
2
4
|
module Parser
|
3
5
|
# Internal: Used for parsing multiple adjacent tag, string, and emedded
|
@@ -12,7 +14,8 @@ module ComponentEmbeddedRuby
|
|
12
14
|
results = []
|
13
15
|
|
14
16
|
while current_token
|
15
|
-
|
17
|
+
case current_token.type
|
18
|
+
when :open_carrot
|
16
19
|
# If we run into a </ we are likely at the end of parsing a tag so
|
17
20
|
# this should return and let the `TagParser` complete parsing
|
18
21
|
#
|
@@ -22,17 +25,15 @@ module ComponentEmbeddedRuby
|
|
22
25
|
# children, and will use another instance of `RootParser` to reads its children
|
23
26
|
# 3. The new RootParser reads `Hello`, then runs into `</`, so it should return `["Hello"]`
|
24
27
|
# and allow the `TagParser` to finish reading `</h1>`
|
25
|
-
if peek_token.type == :slash
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
end
|
30
|
-
elsif current_token.type == :string || current_token.type == :identifier
|
28
|
+
return results if peek_token.type == :slash
|
29
|
+
|
30
|
+
results << TagParser.new(token_reader).call
|
31
|
+
when :string, :identifier
|
31
32
|
# If we're reading a string, or some other identifier that is on
|
32
33
|
# its own, we can skip instantiating a new parser and parse it directly ourselves
|
33
34
|
results << Node.new(nil, nil, current_token.value)
|
34
35
|
token_reader.next
|
35
|
-
|
36
|
+
when :ruby, :ruby_no_eval
|
36
37
|
# If we run into Ruby code that should be evaluated inside of the
|
37
38
|
# template, we want to create an `Eval`. The compliation step
|
38
39
|
# handles `Eval` objects specially since it's making template
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ComponentEmbeddedRuby
|
2
4
|
module Parser
|
3
5
|
# Internal: Parses an HTML tag into a Node object
|
@@ -39,9 +41,7 @@ module ComponentEmbeddedRuby
|
|
39
41
|
expect(:slash)
|
40
42
|
close_tag = expect(:identifier).value
|
41
43
|
|
42
|
-
if close_tag != tag
|
43
|
-
raise "Mismatched tags. expected #{tag}, got #{current_token.value}"
|
44
|
-
end
|
44
|
+
raise "Mismatched tags. expected #{tag}, got #{current_token.value}" if close_tag != tag
|
45
45
|
|
46
46
|
expect(:close_carrot)
|
47
47
|
|
@@ -51,23 +51,23 @@ module ComponentEmbeddedRuby
|
|
51
51
|
|
52
52
|
private
|
53
53
|
|
54
|
+
def parse_children
|
55
|
+
if children?
|
56
|
+
RootParser.new(token_reader).call
|
57
|
+
else
|
58
|
+
[]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
54
62
|
# If the next two elements are </, we can safely asume it's meant to
|
55
63
|
# close the current tag and lets us avoid having to attempt parsing
|
56
64
|
# children.
|
57
|
-
def
|
65
|
+
def children?
|
58
66
|
return true if current_token.type != :open_carrot
|
59
67
|
return true if peek_token&.type != :slash
|
60
68
|
|
61
69
|
false
|
62
70
|
end
|
63
|
-
|
64
|
-
def parse_children
|
65
|
-
if has_children?
|
66
|
-
RootParser.new(token_reader).call
|
67
|
-
else
|
68
|
-
[]
|
69
|
-
end
|
70
|
-
end
|
71
71
|
end
|
72
72
|
end
|
73
73
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ComponentEmbeddedRuby
|
4
|
+
class RailsHandler
|
5
|
+
def self.call(template, source = nil)
|
6
|
+
source ||= template.source
|
7
|
+
|
8
|
+
Template.new(source).to_ruby
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
ActionView::Template.register_template_handler(:crb, ComponentEmbeddedRuby::RailsHandler)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ComponentEmbeddedRuby
|
2
4
|
class Template
|
3
5
|
def initialize(template)
|
@@ -7,11 +9,11 @@ module ComponentEmbeddedRuby
|
|
7
9
|
def to_ruby
|
8
10
|
tokens = Lexer.new(@template).lex
|
9
11
|
nodes = Parser.parse(tokens)
|
10
|
-
|
12
|
+
Compiler.new(nodes).to_ruby
|
11
13
|
end
|
12
14
|
|
13
15
|
def to_s(binding: TOPLEVEL_BINDING)
|
14
|
-
eval(to_ruby, binding)
|
16
|
+
eval(to_ruby, binding) # rubocop:disable Security/Eval
|
15
17
|
end
|
16
18
|
end
|
17
19
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ComponentEmbeddedRuby
|
2
4
|
class UnexpectedTokenError < StandardError
|
3
5
|
attr_reader :expected, :got
|
@@ -8,14 +10,30 @@ module ComponentEmbeddedRuby
|
|
8
10
|
end
|
9
11
|
|
10
12
|
def message
|
11
|
-
|
13
|
+
<<~MESSAGE.strip
|
14
|
+
Unexpected token at line #{got.position.line}, column #{got.position.column}
|
15
|
+
Got `#{got.value}`#{expected_message}
|
16
|
+
MESSAGE
|
12
17
|
end
|
13
18
|
|
14
19
|
private
|
15
20
|
|
16
21
|
def expected_message
|
17
|
-
|
18
|
-
|
22
|
+
" but expected #{user_readable_expected}" unless expected.nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
def user_readable_expected
|
26
|
+
case expected
|
27
|
+
when :open_carrot
|
28
|
+
"`<`"
|
29
|
+
when :close_carrot
|
30
|
+
"`>`"
|
31
|
+
when :equals
|
32
|
+
"`=`"
|
33
|
+
when :slash
|
34
|
+
"`/`"
|
35
|
+
else
|
36
|
+
expected
|
19
37
|
end
|
20
38
|
end
|
21
39
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: component_embedded_ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Blake Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-11-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -25,33 +25,33 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.17'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: minitest
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '5.0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '5.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: pry
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: 0.12.2
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
54
|
+
version: 0.12.2
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rails
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -67,33 +67,47 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '6.0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: rake
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - "
|
73
|
+
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
75
|
+
version: '13.0'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- - "
|
80
|
+
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
82
|
+
version: '13.0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
84
|
+
name: rubocop
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: 0
|
89
|
+
version: '1.0'
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: 0
|
96
|
+
version: '1.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: view_component
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2.15'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '2.15'
|
97
111
|
description: HTML templates with embedded Ruby components
|
98
112
|
email:
|
99
113
|
- blake@blakewilliams.me
|
@@ -103,6 +117,7 @@ extra_rdoc_files: []
|
|
103
117
|
files:
|
104
118
|
- ".github/workflows/ruby.yml"
|
105
119
|
- ".gitignore"
|
120
|
+
- ".rubocop.yml"
|
106
121
|
- ".travis.yml"
|
107
122
|
- Gemfile
|
108
123
|
- Gemfile.lock
|
@@ -113,9 +128,12 @@ files:
|
|
113
128
|
- bin/setup
|
114
129
|
- component_embedded_ruby.gemspec
|
115
130
|
- lib/component_embedded_ruby.rb
|
131
|
+
- lib/component_embedded_ruby/compiler.rb
|
116
132
|
- lib/component_embedded_ruby/eval.rb
|
117
133
|
- lib/component_embedded_ruby/lexer.rb
|
118
134
|
- lib/component_embedded_ruby/lexer/input_reader.rb
|
135
|
+
- lib/component_embedded_ruby/lexer/ruby_code_reader.rb
|
136
|
+
- lib/component_embedded_ruby/lexer/string_reader.rb
|
119
137
|
- lib/component_embedded_ruby/node.rb
|
120
138
|
- lib/component_embedded_ruby/parser.rb
|
121
139
|
- lib/component_embedded_ruby/parser/attribute_parser.rb
|
@@ -123,7 +141,7 @@ files:
|
|
123
141
|
- lib/component_embedded_ruby/parser/root_parser.rb
|
124
142
|
- lib/component_embedded_ruby/parser/tag_parser.rb
|
125
143
|
- lib/component_embedded_ruby/parser/token_reader.rb
|
126
|
-
- lib/component_embedded_ruby/
|
144
|
+
- lib/component_embedded_ruby/rails_handler.rb
|
127
145
|
- lib/component_embedded_ruby/template.rb
|
128
146
|
- lib/component_embedded_ruby/unexpected_token_error.rb
|
129
147
|
- lib/component_embedded_ruby/version.rb
|
@@ -141,7 +159,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
141
159
|
requirements:
|
142
160
|
- - ">="
|
143
161
|
- !ruby/object:Gem::Version
|
144
|
-
version: '
|
162
|
+
version: '2.5'
|
145
163
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
146
164
|
requirements:
|
147
165
|
- - ">="
|