parselly 1.2.0 → 1.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.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Parselly
4
- VERSION = '1.2.0'
4
+ VERSION = '1.3.0'
5
5
  end
data/lib/parselly.rb CHANGED
@@ -1,14 +1,48 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'strscan'
4
-
5
- require_relative 'parselly/lexer'
6
- require_relative 'parselly/node'
7
- require_relative 'parselly/parser'
8
- require_relative 'parselly/version'
4
+ require 'set'
9
5
 
10
6
  module Parselly
11
- ParseResult = Struct.new(:ast, :errors)
7
+ class ParseResult
8
+ attr_accessor :ast, :errors
9
+
10
+ def initialize(ast = nil, errors = nil, **keywords)
11
+ @ast = keywords.fetch(:ast, ast)
12
+ @errors = keywords.fetch(:errors, errors || [])
13
+ end
14
+
15
+ def success?
16
+ errors.empty? && !ast.nil?
17
+ end
18
+
19
+ def failure?
20
+ !success?
21
+ end
22
+
23
+ def empty?
24
+ ast.nil?
25
+ end
26
+
27
+ def first_error
28
+ errors.first
29
+ end
30
+
31
+ def to_a
32
+ [ast, errors]
33
+ end
34
+
35
+ def deconstruct
36
+ to_a
37
+ end
38
+
39
+ def deconstruct_keys(keys)
40
+ hash = { ast: ast, errors: errors }
41
+ return hash if keys.nil?
42
+
43
+ keys.each_with_object({}) { |key, result| result[key] = hash[key] if hash.key?(key) }
44
+ end
45
+ end
12
46
 
13
47
  class ParseError < StandardError
14
48
  attr_reader :error
@@ -19,8 +53,18 @@ module Parselly
19
53
  end
20
54
  end
21
55
 
22
- def parse(selector, tolerant: false)
23
- Parser.new.parse(selector, tolerant: tolerant)
56
+ class LexError < ParseError; end
57
+ class SyntaxError < ParseError; end
58
+ end
59
+
60
+ require_relative 'parselly/lexer'
61
+ require_relative 'parselly/node'
62
+ require_relative 'parselly/parser'
63
+ require_relative 'parselly/version'
64
+
65
+ module Parselly
66
+ def parse(selector, **options)
67
+ Parser.new.parse(selector, **options)
24
68
  end
25
69
 
26
70
  def sanitize(selector)
@@ -42,12 +86,15 @@ module Parselly
42
86
  elsif scanner.pos.zero? && scanner.scan(/\d/)
43
87
  result << escaped_hex(scanner.matched)
44
88
  # Second character is a digit and first is `-`
45
- elsif scanner.pos == 1 && scanner.scan(/\d/) &&
46
- scanner.pre_match == '-'
89
+ elsif scanner.pos == 1 && scanner.peek(1).match?(/\d/) &&
90
+ scanner.string.start_with?('-') && scanner.scan(/\d/)
47
91
  result << escaped_hex(scanner.matched)
48
92
  # Alphanumeric characters, `-`, `_`
49
93
  elsif scanner.scan(/[a-zA-Z0-9\-_]/)
50
94
  result << scanner.matched
95
+ # Non-ASCII characters are valid CSS identifier characters.
96
+ elsif scanner.scan(/[^\x00-\x7F]/)
97
+ result << scanner.matched
51
98
  # Any other characters, escape them
52
99
  elsif scanner.scan(/./)
53
100
  result << "\\#{scanner.matched}"