• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

avonderluft / occams / #30

23 Sep 2023 03:50PM UTC coverage: 34.256% (-63.8%) from 98.056%
#30

push

web-flow
Merge pull request #11 from avonderluft/version108

Switch CI updates from Buildkite to Github actions

953 of 2782 relevant lines covered (34.26%)

8.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/lib/occams/content/params_parser.rb
1
# frozen_string_literal: true
2

3
require 'strscan'
×
4

5
class Occams::Content::ParamsParser
×
6
  class Error < StandardError; end
×
7

8
  STRING_LITERAL  = %r{'[^']*'|"[^"]*"}.freeze
×
9
  IDENTIFIER      = %r{[a-z0-9][\w\-/.]*}i.freeze
×
10
  HASH_KEY        = %r{#{IDENTIFIER}:}.freeze
×
11
  COMMA           = %r{,}.freeze
×
12
  HASH_OPEN       = %r{\{}.freeze
×
13
  HASH_CLOSE      = %r{\}}.freeze
×
14
  ARRAY_OPEN      = %r{\[}.freeze
×
15
  ARRAY_CLOSE     = %r{\]}.freeze
×
16
  INTEGER         = %r{\b[0-9]+\b}i.freeze
×
17

18
  # @param <String> string
19
  def initialize(string = '')
×
20
    @string = string
×
21
  end
×
22

23
  # Takes CMS content tag parameters and converts them into array of strings,
24
  # hashes and arrays.
25
  #
26
  # @return [Array<String, {String => String}>]
27
  # @raise [Error] if the given `text` is malformed.
28
  #
29
  # @example
30
  #   new("a, b, c").parse
31
  #   #=> ["a", "b", "c"]
32
  #
33
  #   new("a, b: c, d: e").parse
34
  #   #=> ["a", {"b" => "c", "d" => "e"}]
35
  #
36
  #   new("a, b: {c: [d, e]}").parse
37
  #   #=> ["a", {"b" => {"c" => ["d", "e"]}}]
38
  #
39
  def params
×
40
    @tokens = tokenize(@string)
×
41
    parse_params
×
42
  end
×
43

44
private
×
45

46
  # Contructs root-level list of arguments sent via content tag
47
  def parse_params
×
48
    params = []
×
49
    while (token = @tokens.shift)
×
50
      params << parse_value(token)
×
51
    end
×
52
    params
×
53
  end
×
54

55
  # Gets token value. Will trigger parsing of hash and array structures
56
  def parse_value(token)
×
57
    case token&.first
×
58
    when :string
×
59
      token[1]
×
60
    when :hash_key
×
61
      @tokens.unshift(token)
×
62
      parse_hash
×
63
    when :hash_open
×
64
      parse_hash
×
65
    when :array_open
×
66
      parse_array
×
67
    else
×
68
      raise Error, "Invalid params: #{@string}"
×
69
    end
×
70
  end
×
71

72
  # Hash constructor method. Will handle nested hashes as well
73
  def parse_hash
×
74
    opens = 1
×
75
    hash = {}
×
76

77
    while (token = @tokens.shift)
×
78
      case token&.first
×
79
      when :hash_key
×
80
        hash[token[1]] = parse_value(@tokens.shift)
×
81
      when :hash_close
×
82
        opens -= 1
×
83
      when :hash_open
×
84
        opens += 1
×
85
      else
×
86
        raise Error, "Invalid params: #{@string}"
×
87
      end
×
88

89
      return hash if opens.zero?
×
90
    end
×
91

92
    # We're can't really detect unclosed hashes as we can construct them without
93
    # opening brakets. For example, `a: b, c: d` is same as `{a: b, c: d}` and
94
    # `{a: b, c: d` is also ends up to be a valid hash. It will error out if
95
    # unclosed hash is followed by some other unexpected token. Like: `{a: b, c`
96
    hash
×
97
  end
×
98

99
  # Array construction method. Will handle nested arrays
100
  def parse_array
×
101
    opens = 1
×
102
    array = []
×
103
    while (token = @tokens.shift)
×
104
      case token&.first
×
105
      when :array_close
×
106
        opens -= 1
×
107
      else
×
108
        array << parse_value(token)
×
109
      end
×
110

111
      return array if opens.zero?
×
112
    end
×
113

114
    raise Error, "Unclosed array param: #{@string}"
×
115
  end
×
116

117
  # Tokenizing input string into a list of touples
118
  # Also args_string is stripped of "smart" quotes coming from wysiwyg
119
  #
120
  # @param [String] args_string
121
  # @return [Array<String>] tokens
122
  def tokenize(args_string)
×
123
    args_string = args_string.tr('“”‘’', %q(""''))
×
124
    ss = StringScanner.new(args_string)
×
125
    tokens = []
×
126
    loop do
×
127
      ss.skip(%r{\s*})
×
128
      break if ss.eos?
×
129

130
      # commas are just separators like spaces
131
      next if ss.scan(COMMA)
×
132

133
      tokens <<
×
134
        if    (t = ss.scan(STRING_LITERAL)) then [:string, t[1...-1]]
×
135
        elsif (t = ss.scan(HASH_KEY))       then [:hash_key, t[0...-1]]
×
136
        elsif (t = ss.scan(INTEGER))        then [:string, t.to_i]
×
137
        elsif (t = ss.scan(IDENTIFIER))     then [:string, t]
×
138
        elsif (t = ss.scan(HASH_OPEN))      then [:hash_open, t]
×
139
        elsif (t = ss.scan(HASH_CLOSE))     then [:hash_close, t]
×
140
        elsif (t = ss.scan(ARRAY_OPEN))     then [:array_open, t]
×
141
        elsif (t = ss.scan(ARRAY_CLOSE))    then [:array_close, t]
×
142
        else
×
143
          raise Error, "Unexpected char: #{ss.getch}"
×
144
        end
×
145
    end
×
146

147
    tokens
×
148
  end
×
149
end
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc