slimi 0.2.0 → 0.4.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce4329befc9d19fb756c5609a28a3169c4f5a5aa2698771df14541b88b792da1
4
- data.tar.gz: d99512e7191942f2fabb8667cb4de369dcad5d773c1e162cc039f2911b4b1dc9
3
+ metadata.gz: a503c590465e63f43b6d9ef8047700ff415bfea923df65be323f2d37f5d94b71
4
+ data.tar.gz: 4ad486738d38c81b670cb693afa31c9c7f8acb92ab05a844d47cf3df1d0219c8
5
5
  SHA512:
6
- metadata.gz: 6c1b46a4afdfc4ce2a8ce816898a17e6bd448e0e76a001efbebfe7fc498375a2c1c71aaa5f40cce11012f28e7e87f5c7eef06b70b6dbcd560a6343d5494ee71c
7
- data.tar.gz: 7554a443efa9d30952d3446b92c494dd6ecf1caeb78d77aea347eef1ab862e64c35e93d4ef76c1423728734eb3b23913e75497c3358b0df2543229e05b177c07
6
+ metadata.gz: '08dc1c84baa6a338564757e6c28143a77f1ecc03eb4ba7f72bf2af82ce673eb2c573debbbbf5eaf16673b53e07a49dd3493888875e6b9e5016557ab7f8373368'
7
+ data.tar.gz: dd3bb74308363284ff648f80d2dcc37fe9b7faa75df2b31210ca4567fa088debca11b5831bb8ec745800bd15e9886e5ff5bf6074bcb6aa673ca826421e10bc31
data/.rubocop.yml CHANGED
@@ -1,3 +1,6 @@
1
+ require:
2
+ - rubocop-rspec
3
+
1
4
  AllCops:
2
5
  NewCops: enable
3
6
  SuggestExtensions: false
@@ -12,5 +15,17 @@ Lint/InterpolationCheck:
12
15
  Metrics:
13
16
  Enabled: false
14
17
 
18
+ RSpec/AnyInstance:
19
+ Enabled: false
20
+
21
+ RSpec/ImplicitSubject:
22
+ Enabled: false
23
+
24
+ RSpec/MultipleExpectations:
25
+ Enabled: false
26
+
27
+ RSpec/NamedSubject:
28
+ Enabled: false
29
+
15
30
  Style/Documentation:
16
31
  Enabled: false
data/CHANGELOG.md CHANGED
@@ -2,6 +2,42 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.4.2 - 2021-12-27
6
+
7
+ ### Fixed
8
+
9
+ - Fix bug on quoted attribute parser.
10
+ - Remove trailing line-ending from source line of syntax error message.
11
+ - Support multi-line attributes.
12
+
13
+ ## 0.4.1 - 2021-12-26
14
+
15
+ ### Fixed
16
+
17
+ - Fix bug on parsing Ruby attribute value.
18
+ - Fix bug on empty line in text block.
19
+
20
+ ## 0.4.0 - 2021-12-25
21
+
22
+ ### Added
23
+
24
+ - Support :file option on parser for showing correct file path on syntax error.
25
+
26
+ ### Fixed
27
+
28
+ - Fix NameError on unknown line indicator.
29
+ - Fix bug that default parser options are not used.
30
+
31
+ ## 0.3.0 - 2021-12-24
32
+
33
+ ### Added
34
+
35
+ - Support Ruby attributes.
36
+
37
+ ### Fixed
38
+
39
+ - Fix bug about blank line handling.
40
+
5
41
  ## 0.2.0 - 2021-12-23
6
42
 
7
43
  ### Added
data/Gemfile CHANGED
@@ -6,6 +6,7 @@ source 'https://rubygems.org'
6
6
  gemspec
7
7
 
8
8
  gem 'rake', '~> 13.0'
9
- gem 'rspec', '~> 3.0'
10
- gem 'rubocop', '~> 1.21'
9
+ gem 'rspec'
10
+ gem 'rubocop'
11
+ gem 'rubocop-rspec'
11
12
  gem 'slim'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- slimi (0.2.0)
4
+ slimi (0.4.2)
5
5
  temple
6
6
 
7
7
  GEM
@@ -40,6 +40,8 @@ GEM
40
40
  unicode-display_width (>= 1.4.0, < 3.0)
41
41
  rubocop-ast (1.15.0)
42
42
  parser (>= 3.0.1.1)
43
+ rubocop-rspec (2.6.0)
44
+ rubocop (~> 1.19)
43
45
  ruby-progressbar (1.11.0)
44
46
  slim (4.1.0)
45
47
  temple (>= 0.7.6, < 0.9)
@@ -53,8 +55,9 @@ PLATFORMS
53
55
 
54
56
  DEPENDENCIES
55
57
  rake (~> 13.0)
56
- rspec (~> 3.0)
57
- rubocop (~> 1.21)
58
+ rspec
59
+ rubocop
60
+ rubocop-rspec
58
61
  slim
59
62
  slimi!
60
63
 
data/lib/slimi/errors.rb CHANGED
@@ -23,7 +23,7 @@ module Slimi
23
23
  def to_s
24
24
  <<~TEXT
25
25
  #{error_type} at #{@file_path}:#{@line_number}:#{@column}
26
- #{@line}
26
+ #{@line.rstrip}
27
27
  #{' ' * (@column - 1)}^
28
28
  TEXT
29
29
  end
@@ -36,12 +36,21 @@ module Slimi
36
36
  end
37
37
  end
38
38
 
39
+ class AttributeClosingDelimiterNotFoundError < SlimSyntaxError
40
+ end
41
+
42
+ class InvalidEmptyAttributeError < SlimSyntaxError
43
+ end
44
+
39
45
  class LineEndingNotFoundError < SlimSyntaxError
40
46
  end
41
47
 
42
48
  class MalformedIndentationError < SlimSyntaxError
43
49
  end
44
50
 
51
+ class RubyAttributeClosingDelimiterNotFoundError < SlimSyntaxError
52
+ end
53
+
45
54
  class UnexpectedEosError < SlimSyntaxError
46
55
  end
47
56
 
@@ -51,7 +60,7 @@ module Slimi
51
60
  class UnexpectedTextAfterClosedTagError < SlimSyntaxError
52
61
  end
53
62
 
54
- class UnknownLineIndicator < SlimSyntaxError
63
+ class UnknownLineIndicatorError < SlimSyntaxError
55
64
  end
56
65
  end
57
66
  end
data/lib/slimi/parser.rb CHANGED
@@ -6,22 +6,30 @@ require 'temple'
6
6
  module Slimi
7
7
  class Parser < ::Temple::Parser
8
8
  define_options(
9
+ :file,
9
10
  attr_list_delims: {
10
11
  '(' => ')',
11
12
  '[' => ']',
12
13
  '{' => '}'
13
14
  },
15
+ code_attr_delims: {
16
+ '(' => ')',
17
+ '[' => ']',
18
+ '{' => '}'
19
+ },
14
20
  shortcut: {
15
21
  '#' => { attr: 'id' },
16
22
  '.' => { attr: 'class' }
17
23
  }
18
24
  )
19
25
 
20
- def initialize(options = {})
26
+ def initialize(_options = {})
21
27
  super
28
+ @file_path = options[:file] || '(__TEMPLATE__)'
22
29
  factory = Factory.new(
23
30
  attribute_delimiters: options[:attr_list_delims] || {},
24
31
  default_tag: options[:default_tag] || 'div',
32
+ ruby_attribute_delimiters: options[:code_attr_delims] || {},
25
33
  shortcut: options[:shortcut] || {}
26
34
  )
27
35
  @attribute_delimiters = factory.attribute_delimiters
@@ -32,6 +40,9 @@ module Slimi
32
40
  @quoted_attribute_regexp = factory.quoted_attribute_regexp
33
41
  @tag_name_regexp = factory.tag_name_regexp
34
42
  @attribute_name_regexp = factory.attribute_name_regexp
43
+ @ruby_attribute_regexp = factory.ruby_attribute_regexp
44
+ @ruby_attribute_delimiter_regexp = factory.ruby_attribute_delimiter_regexp
45
+ @ruby_attribute_delimiters = factory.ruby_attribute_delimiters
35
46
  @embedded_template_regexp = factory.embedded_template_regexp
36
47
  end
37
48
 
@@ -46,10 +57,11 @@ module Slimi
46
57
  private
47
58
 
48
59
  def parse_block
60
+ return if parse_blank_line
61
+
49
62
  parse_indent
50
63
 
51
- parse_line_ending ||
52
- parse_html_comment ||
64
+ parse_html_comment ||
53
65
  parse_html_conditional_comment ||
54
66
  parse_slim_comment_block ||
55
67
  parse_verbatim_text_block ||
@@ -59,7 +71,18 @@ module Slimi
59
71
  parse_embedded_template ||
60
72
  parse_doctype ||
61
73
  parse_tag ||
62
- raise(Errors::UnknownLineIndicatorError)
74
+ syntax_error!(Errors::UnknownLineIndicatorError)
75
+ end
76
+
77
+ # Parse blank line.
78
+ # @return [Boolean] True if it could parse a blank line.
79
+ def parse_blank_line
80
+ if @scanner.skip(/[ \t]*$/)
81
+ parse_line_ending
82
+ true
83
+ else
84
+ false
85
+ end
63
86
  end
64
87
 
65
88
  def parse_indent
@@ -177,8 +200,6 @@ module Slimi
177
200
  marker = @scanner[1]
178
201
  attribute_value = @scanner[2]
179
202
  attribute_names = @attribute_shortcuts[marker]
180
- raise 'Illegal shortcut' unless attribute_names
181
-
182
203
  attribute_names.map do |attribute_name|
183
204
  result << [:html, :attr, attribute_name.to_s, [:static, attribute_value]]
184
205
  end
@@ -199,18 +220,24 @@ module Slimi
199
220
  value = +''
200
221
  count = 0
201
222
  loop do
202
- if @scanner.match?(/#{quote}/) && count.zero?
203
- end_ = @scanner.charpos
204
- @scanner.pos += @scanner.matched_size
205
- break
206
- end
207
-
208
- if @scanner.skip(/\{/)
223
+ if @scanner.match?(/#{quote}/)
224
+ if count.zero?
225
+ end_ = @scanner.charpos
226
+ @scanner.pos += @scanner.matched_size
227
+ break
228
+ else
229
+ @scanner.pos += @scanner.matched_size
230
+ value << @scanner.matched
231
+ end
232
+ elsif @scanner.skip(/\{/)
209
233
  count += 1
234
+ value << @scanner.matched
210
235
  elsif @scanner.skip(/\}/)
211
236
  count -= 1
237
+ value << @scanner.matched
238
+ else
239
+ value << @scanner.scan(/[^{}#{quote}]*/)
212
240
  end
213
- value << @scanner.scan(/[^{}#{quote}]*/)
214
241
  end
215
242
  [:slimi, :interpolate, begin_, end_, value]
216
243
  end
@@ -232,12 +259,19 @@ module Slimi
232
259
  attribute_delimiter_closing_part_regexp = /[ \t]*#{attribute_delimiter_closing_regexp}/
233
260
  end
234
261
 
262
+ # TODO: Support splat attributes.
235
263
  loop do
236
264
  if @scanner.skip(@quoted_attribute_regexp)
237
265
  attribute_name = @scanner[1]
238
266
  escape = @scanner[2].empty?
239
267
  quote = @scanner[3]
240
268
  attributes << [:html, :attr, attribute_name, [:escape, escape, parse_quoted_attribute_value(quote)]]
269
+ elsif @scanner.skip(@ruby_attribute_regexp)
270
+ attribute_name = @scanner[1]
271
+ escape = @scanner[2].empty?
272
+ attribute_value = parse_ruby_attribute_value(attribute_delimiter_closing)
273
+ syntax_error!(Errors::InvalidEmptyAttributeError) if attribute_value.empty?
274
+ attributes << [:html, :attr, attribute_name, [:slim, :attrvalue, escape, attribute_value]]
241
275
  elsif !attribute_delimiter_closing_part_regexp
242
276
  break
243
277
  elsif @scanner.skip(boolean_attribute_regexp)
@@ -245,13 +279,56 @@ module Slimi
245
279
  elsif @scanner.skip(attribute_delimiter_closing_part_regexp) # rubocop:disable Lint/DuplicateBranch
246
280
  break
247
281
  else
248
- raise ::NotImplementedError
282
+ @scanner.skip(/[ \t]+/)
283
+ expect_line_ending
284
+
285
+ syntax_error!(Errors::AttributeClosingDelimiterNotFoundError) if @scanner.eos?
249
286
  end
250
287
  end
251
288
 
252
289
  attributes
253
290
  end
254
291
 
292
+ # Parse Ruby attribute value part.
293
+ # e.g. div class=foo
294
+ # ^^^
295
+ # `- Ruby attribute value
296
+ # @param [String] attribute_delimiter_closing
297
+ # @return [String]
298
+ def parse_ruby_attribute_value(attribute_delimiter_closing)
299
+ ending_regexp = /\s/
300
+ ending_regexp = ::Regexp.union(ending_regexp, attribute_delimiter_closing) if attribute_delimiter_closing
301
+ count = 0
302
+ attribute_value = +''
303
+ opening_delimiter = nil
304
+ closing_delimiter = nil
305
+ loop do
306
+ break if count.zero? && @scanner.match?(ending_regexp)
307
+
308
+ if @scanner.skip(/([,\\])\r?\n/)
309
+ attribute_value << @scanner[1] << "\n"
310
+ else
311
+ if count.positive?
312
+ if opening_delimiter && @scanner.match?(/#{::Regexp.escape(opening_delimiter)}/)
313
+ count += 1
314
+ elsif closing_delimiter && @scanner.match?(/#{::Regexp.escape(closing_delimiter)}/)
315
+ count -= 1
316
+ end
317
+ elsif @scanner.match?(@ruby_attribute_delimiter_regexp)
318
+ count = 1
319
+ opening_delimiter = @scanner.matched
320
+ closing_delimiter = @ruby_attribute_delimiters[opening_delimiter]
321
+ end
322
+ if (character = @scanner.scan(/./))
323
+ attribute_value << character
324
+ end
325
+ end
326
+ end
327
+ syntax_error!(Errors::RubyAttributeClosingDelimiterNotFoundError) if count != 0
328
+
329
+ attribute_value
330
+ end
331
+
255
332
  # @return [Boolean]
256
333
  def parse_html_comment
257
334
  if @scanner.skip(%r{/!})
@@ -435,9 +512,13 @@ module Slimi
435
512
  interpolate = parse_interpolate_line
436
513
  result << interpolate if interpolate
437
514
 
438
- loop do
439
- break unless @scanner.match?(/\r?\n[ \t]*/)
515
+ until @scanner.eos?
516
+ if @scanner.skip(/\r?\n[ \t]*(?=\r?\n)/)
517
+ result << [:newline]
518
+ next
519
+ end
440
520
 
521
+ @scanner.match?(/\r?\n[ \t]*/)
441
522
  indent = indent_from_last_match
442
523
  break if indent <= @indents.last
443
524
 
@@ -488,7 +569,7 @@ module Slimi
488
569
  range = Range.new(index: @scanner.charpos, source: @scanner.string)
489
570
  raise syntax_error_class.new(
490
571
  column: range.column,
491
- file_path: '(__TEMPLATE__)',
572
+ file_path: @file_path,
492
573
  line: range.line,
493
574
  line_number: range.line_number
494
575
  )
@@ -512,12 +593,17 @@ module Slimi
512
593
  # @return [Hash]
513
594
  attr_reader :attribute_delimiters
514
595
 
596
+ # @return [Hash]
597
+ attr_reader :ruby_attribute_delimiters
598
+
515
599
  # @param [Hash] attribute_delimiters
516
600
  # @param [String] default_tag
601
+ # @param [Hash] ruby_attribute_delimiters
517
602
  # @param [Hash] shortcut
518
- def initialize(attribute_delimiters:, default_tag:, shortcut:)
603
+ def initialize(attribute_delimiters:, default_tag:, ruby_attribute_delimiters:, shortcut:)
519
604
  @attribute_delimiters = attribute_delimiters
520
605
  @default_tag = default_tag
606
+ @ruby_attribute_delimiters = ruby_attribute_delimiters
521
607
  @shortcut = shortcut
522
608
  end
523
609
 
@@ -573,6 +659,11 @@ module Slimi
573
659
  %r{(#{markers_regexp}+)((?:\p{Word}|-|/\d+|:(\w|-)+)*)}
574
660
  end
575
661
 
662
+ # @return [Regexp]
663
+ def ruby_attribute_regexp
664
+ /#{attribute_name_regexp}[ \t]*=(=?)[ \t]*/
665
+ end
666
+
576
667
  # @return [Regexp]
577
668
  def embedded_template_regexp
578
669
  /(#{::Regexp.union(EMBEDDED_TEMPLAE_ENGINE_NAMES)})(?:[ \t]*(?:(.*)))?:([ \t]*)/
@@ -583,6 +674,11 @@ module Slimi
583
674
  /#{attribute_name_regexp}[ \t]*=(=?)[ \t]*("|')/
584
675
  end
585
676
 
677
+ # @return [Regexp]
678
+ def ruby_attribute_delimiter_regexp
679
+ ::Regexp.union(@ruby_attribute_delimiters.keys)
680
+ end
681
+
586
682
  # @return [Regexp] Pattern that matches to tag header part.
587
683
  def tag_name_regexp
588
684
  markers = tag_shortcuts.keys.sort_by { |marker| -marker.size }
data/lib/slimi/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Slimi
4
- VERSION = '0.2.0'
4
+ VERSION = '0.4.2'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slimi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryo Nakamura
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-12-22 00:00:00.000000000 Z
11
+ date: 2021-12-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: temple