component_embedded_ruby 0.2.0 → 0.3.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: 834b63b9cc7bf826b998ff5397391e477001e549da31d058243745db63d633e4
4
- data.tar.gz: 6742bc330ce2dd5b9fdce7325759338bc966cc016955ca2156ee44e2187e1187
3
+ metadata.gz: 00cd94a77693e37fb2723ead82ea26bb40f2e03fd605b33cb41a0f1075bc6ef0
4
+ data.tar.gz: 00b7ebbfe21f7e9b057b83493016e71409a9657f85f30fd719f95c632827f8a8
5
5
  SHA512:
6
- metadata.gz: 24c07b6614384d476903ab99d892373625cf017b3428c6a5ba2b00a328f07b0dd614ce178235e23d4a333662379719acdef3dc5f28be74eb0d1e68f4144e4cf8
7
- data.tar.gz: 181d8e02bc4746860064231b6e19cc834c1b550c0c686aca1f12703bda0894030d9e1c444c2b104edec7e364606a675b8746e789331e52789c807ec2c812a3f7
6
+ metadata.gz: 6619a460eaf27c83545de59f2b8fa5475592d41af7e082371b2c616f6fcb686518dfeb0e5223c5de7a63ba2672abf4f3ab0e0d77ff02a4dbda8a22cbf146af52
7
+ data.tar.gz: 2f48caa5773e5ab51f52aebc485a32a344bf60feb6515c3287aa0cbe2be3bcd8f8ca2befc3a1384eb9e480209c3fa37a41513b3b86265d105174d07a5702ea3e
@@ -3,18 +3,35 @@ name: Ruby
3
3
  on: [push]
4
4
 
5
5
  jobs:
6
- build:
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: 2.6.x
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
@@ -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
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source "https://rubygems.org"
2
4
 
3
- git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
6
 
5
7
  gem "rails", github: "rails/rails"
6
8
 
@@ -86,11 +86,12 @@ GIT
86
86
  PATH
87
87
  remote: .
88
88
  specs:
89
- component_embedded_ruby (0.2.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
- * Easy Rails integration by registering `crb` as a template handler.
12
+ * automatic Rails support for `crb` extension
13
13
 
14
14
  ### Usage
15
15
 
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/gem_tasks"
2
4
  require "rake/testtask"
3
5
 
@@ -7,4 +9,4 @@ Rake::TestTask.new(:test) do |t|
7
9
  t.test_files = FileList["test/**/*_test.rb"]
8
10
  end
9
11
 
10
- task :default => :test
12
+ task default: :test
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require "bundler/setup"
4
5
  require "component_embedded_ruby"
@@ -1,5 +1,6 @@
1
+ # frozen_string_literal: true
1
2
 
2
- lib = File.expand_path("../lib", __FILE__)
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 = %q{HTML templates with embedded Ruby components}
13
- spec.description = %q{HTML templates with embedded Ruby components}
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 = Dir.chdir(File.expand_path('..', __FILE__)) do
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/renderer"
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 Renderer
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
- <<~EOF
13
+ <<~RUBY
12
14
  #{output_var_name} = '';
13
15
 
14
16
  #{nodes.map(&method(:render)).join("\n")}
15
17
 
16
- #{output_var_name if !@skip_return};
17
- EOF
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
- <<~EOF
26
+ <<~RUBY
25
27
  #{children_to_ruby(node)}
26
- #{output_var_name}.<< render(#{node.component_class}.new(#{attributes_for_component(node).join(",")})) { |component|
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
- EOF
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
- <<~EOF
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
- EOF
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
- <<~EOF
73
- #{output_var_name}.<< " #{key}=\\"";
74
- #{output_var_name}.<< (#{value.value}).to_s;
75
- #{output_var_name}.<< "\\"";
76
- EOF
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(#{output_var_name}.<< "key="";\n)
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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ComponentEmbeddedRuby
2
4
  class Eval
3
5
  attr_reader :value, :output
@@ -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 is_letter?(char)
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, Position.new(reader.current_line, reader.current_column))
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 is_letter?(reader.current_char) && !reader.eof?
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
- inner_string_count = 0
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 == "\"" && reader.peek_behind != "\\"
116
+ reader.current_char == '"' && reader.peek_behind != '\\'
138
117
  end
139
118
 
140
- def is_letter?(char)
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 = 0
11
- @current_column = 0
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 = 0
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
  class Node
3
5
  attr_reader :tag, :attributes, :children
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "component_embedded_ruby/parser/base"
2
4
  require "component_embedded_ruby/parser/root_parser"
3
5
  require "component_embedded_ruby/parser/attribute_parser"
@@ -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
- while current_token.type != :close_carrot && current_token.type != :slash
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
- if token.type != type
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
- if !types.include?(token.type)
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
- if current_token.type == :open_carrot
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
- return results
27
- else
28
- results << TagParser.new(token_reader).call
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
- elsif current_token.type == :ruby || current_token.type == :ruby_no_eval
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 has_children?
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ComponentEmbeddedRuby
2
4
  module Parser
3
5
  class TokenReader
@@ -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
- Renderer.new(nodes).to_ruby
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
- "Unexpected token at column #{got.position}, got #{got.value}#{expected_message}."
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
- if expected != nil
18
- " but expected #{expected}"
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ComponentEmbeddedRuby
2
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
3
5
  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.2.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-10-25 00:00:00.000000000 Z
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: rake
28
+ name: minitest
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '13.0'
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: '13.0'
40
+ version: '5.0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: minitest
42
+ name: pry
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '5.0'
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: '5.0'
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: view_component
70
+ name: rake
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ">"
73
+ - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '2.15'
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: '2.15'
82
+ version: '13.0'
83
83
  - !ruby/object:Gem::Dependency
84
- name: pry
84
+ name: rubocop
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 0.12.2
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.12.2
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/renderer.rb
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: '0'
162
+ version: '2.5'
145
163
  required_rubygems_version: !ruby/object:Gem::Requirement
146
164
  requirements:
147
165
  - - ">="