File: attribute_parser.rb

package info (click to toggle)
ruby-hamlit 2.15.1-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 1,996 kB
  • sloc: ruby: 10,548; ansic: 536; sh: 23; makefile: 8
file content (110 lines) | stat: -rw-r--r-- 2,603 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# frozen_string_literal: true
require 'hamlit/ruby_expression'

module Hamlit
  class AttributeParser
    class ParseSkip < StandardError
    end

    def self.parse(text)
      self.new.parse(text)
    end

    def parse(text)
      exp = wrap_bracket(text)
      return if RubyExpression.syntax_error?(exp)

      hash = {}
      tokens = Ripper.lex(exp)[1..-2] || []
      each_attr(tokens) do |attr_tokens|
        key = parse_key!(attr_tokens)
        hash[key] = attr_tokens.map { |t| t[2] }.join.strip
      end
      hash
    rescue ParseSkip
      nil
    end

    private

    def wrap_bracket(text)
      text = text.strip
      return text if text[0] == '{'
      "{#{text}}"
    end

    def parse_key!(tokens)
      _, type, str = tokens.shift
      case type
      when :on_sp
        parse_key!(tokens)
      when :on_label
        str.tr(':', '')
      when :on_symbeg
        _, _, key = tokens.shift
        assert_type!(tokens.shift, :on_tstring_end) if str != ':'
        skip_until_hash_rocket!(tokens)
        key
      when :on_tstring_beg
        _, _, key = tokens.shift
        next_token = tokens.shift
        unless next_token[1] == :on_label_end
          assert_type!(next_token, :on_tstring_end)
          skip_until_hash_rocket!(tokens)
        end
        key
      else
        raise ParseSkip
      end
    end

    def assert_type!(token, type)
      raise ParseSkip if token[1] != type
    end

    def skip_until_hash_rocket!(tokens)
      until tokens.empty?
        _, type, str = tokens.shift
        break if type == :on_op && str == '=>'
      end
    end

    def each_attr(tokens)
      attr_tokens = []
      open_tokens = Hash.new { |h, k| h[k] = 0 }

      tokens.each do |token|
        _, type, _ = token
        case type
        when :on_comma
          if open_tokens.values.all?(&:zero?)
            yield(attr_tokens)
            attr_tokens = []
            next
          end
        when :on_lbracket
          open_tokens[:array] += 1
        when :on_rbracket
          open_tokens[:array] -= 1
        when :on_lbrace
          open_tokens[:block] += 1
        when :on_rbrace
          open_tokens[:block] -= 1
        when :on_lparen
          open_tokens[:paren] += 1
        when :on_rparen
          open_tokens[:paren] -= 1
        when :on_embexpr_beg
          open_tokens[:embexpr] += 1
        when :on_embexpr_end
          open_tokens[:embexpr] -= 1
        when :on_sp
          next if attr_tokens.empty?
        end

        attr_tokens << token
      end
      yield(attr_tokens) unless attr_tokens.empty?
    end
  end
end