filter_lexer 0.1.0 → 0.1.1
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 +4 -4
- data/.codeclimate.yml +11 -0
- data/.inch.yml +6 -0
- data/.rubocop.yml +34 -0
- data/README.md +1 -1
- data/Rakefile +44 -1
- data/filter_lexer.gemspec +5 -3
- data/lib/filter_lexer.rb +22 -12
- data/lib/filter_lexer/exceptions.rb +9 -10
- data/lib/filter_lexer/formatters/sql.rb +30 -20
- data/lib/filter_lexer/nodes.rb +27 -19
- data/lib/filter_lexer/version.rb +1 -1
- metadata +32 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 47fbb2acdc5535e2e74b0355dbb042e289de54b1
|
4
|
+
data.tar.gz: 4be18e0e750a3b298e4572e175e2b2e43fbeea3e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9e83318063753adab91aeddb75fbe34f3405a35f47b82dd01eed69a9276b5b97e9110a830da5f981a1a8d633159da158ce38a636562ce82d0da591a48c8e4503
|
7
|
+
data.tar.gz: a7ff4f435b9ab633daf95f8b31c1a98b5c001b8b86cd82dfcfaf69749b4e2b61bcbbfffb4f8ff020af0f2cee9d081297d12060c9b2a6008b2628a3599829c513
|
data/.codeclimate.yml
ADDED
data/.inch.yml
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
AllCops:
|
2
|
+
Exclude:
|
3
|
+
- 'bin/**/*'
|
4
|
+
|
5
|
+
Metrics/AbcSize:
|
6
|
+
Max: 20
|
7
|
+
|
8
|
+
Metrics/LineLength:
|
9
|
+
Max: 120
|
10
|
+
|
11
|
+
Style/CaseIndentation:
|
12
|
+
IndentWhenRelativeTo: 'case'
|
13
|
+
IndentOneStep: true
|
14
|
+
|
15
|
+
Style/IndentHash:
|
16
|
+
Enabled: false
|
17
|
+
|
18
|
+
Style/Documentation:
|
19
|
+
Enabled: false
|
20
|
+
|
21
|
+
Style/FirstParameterIndentation:
|
22
|
+
Enabled: false
|
23
|
+
|
24
|
+
Style/IndentationWidth:
|
25
|
+
Enabled: false
|
26
|
+
|
27
|
+
Style/MultilineOperationIndentation:
|
28
|
+
EnforcedStyle: 'indented'
|
29
|
+
|
30
|
+
Style/RedundantReturn:
|
31
|
+
Enabled: false
|
32
|
+
|
33
|
+
Style/TrailingComma:
|
34
|
+
EnforcedStyleForMultiline: comma
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# FilterLexer
|
1
|
+
# FilterLexer [](https://gemnasium.com/MaienM/FilterLexer) [](https://codeclimate.com/github/MaienM/FilterLexer) [](http://inch-ci.org/github/MaienM/FilterLexer)
|
2
2
|
|
3
3
|
This is a simple treetop implementation for a basic SQL-like filtering syntax.
|
4
4
|
|
data/Rakefile
CHANGED
@@ -1 +1,44 @@
|
|
1
|
-
require
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rubocop/rake_task'
|
3
|
+
|
4
|
+
RuboCop::RakeTask.new(:rubocop, [:options]) do |task|
|
5
|
+
task.options.push '--cache=false'
|
6
|
+
end
|
7
|
+
module RuboCop
|
8
|
+
# We use tabs for indentation, not spaces. As many of the RuboCop cops seem
|
9
|
+
# to depend on two spaces being used for indentation, we pretend this is
|
10
|
+
# what we do. To allow us to still check indentation for correctness, we
|
11
|
+
# also change two spaces into tabs, so RuboCop can detect it as invalid
|
12
|
+
# indentation.
|
13
|
+
class File < ::File
|
14
|
+
SPACES = ' '
|
15
|
+
TABS = "\t"
|
16
|
+
PLACEHOLDER = "\0"
|
17
|
+
|
18
|
+
def self.read(*args)
|
19
|
+
source = super(*args)
|
20
|
+
source = swap_indents(source)
|
21
|
+
return source
|
22
|
+
end
|
23
|
+
|
24
|
+
def write(source, *args)
|
25
|
+
source = self.class.swap_indents(source)
|
26
|
+
return super(source, *args)
|
27
|
+
end
|
28
|
+
|
29
|
+
class << self
|
30
|
+
def swap_indents(source)
|
31
|
+
source = change_to(source, SPACES, PLACEHOLDER)
|
32
|
+
source = change_to(source, TABS, SPACES)
|
33
|
+
source = change_to(source, PLACEHOLDER, TABS)
|
34
|
+
return source
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def change_to(source, from, to)
|
40
|
+
return source.gsub(/^((#{from})+)/) { |m| to * (m.size / from.size).to_i }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/filter_lexer.gemspec
CHANGED
@@ -9,15 +9,15 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ['Michon van Dooren']
|
10
10
|
spec.email = ['michon1992@gmail.com']
|
11
11
|
|
12
|
-
spec.summary =
|
13
|
-
spec.description =
|
12
|
+
spec.summary = 'A basic lexer that supports basic sql-like filtering logic.'
|
13
|
+
spec.description = 'Simple treetop lexer that supports writing filters in an SQL-like manner.'
|
14
14
|
spec.homepage = 'http://github.com/MaienM/FilterLexer'
|
15
15
|
spec.license = 'MIT'
|
16
16
|
|
17
17
|
if spec.respond_to?(:metadata)
|
18
18
|
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
19
19
|
else
|
20
|
-
|
20
|
+
fail 'RubyGems 2.0 or newer is required to protect against public gem pushes.'
|
21
21
|
end
|
22
22
|
|
23
23
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
@@ -28,5 +28,7 @@ Gem::Specification.new do |spec|
|
|
28
28
|
spec.add_development_dependency 'bundler', '~> 1.10'
|
29
29
|
spec.add_development_dependency 'rake', '~> 10.0'
|
30
30
|
spec.add_development_dependency 'rspec'
|
31
|
+
spec.add_development_dependency 'rubocop'
|
32
|
+
spec.add_development_dependency 'pry'
|
31
33
|
spec.add_dependency 'treetop', '~> 1.6'
|
32
34
|
end
|
data/lib/filter_lexer.rb
CHANGED
@@ -1,24 +1,30 @@
|
|
1
1
|
require 'treetop'
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
3
|
+
require 'filter_lexer/version'
|
4
|
+
require 'filter_lexer/exceptions'
|
5
|
+
require 'filter_lexer/nodes'
|
6
6
|
require 'filter_lexer/syntax.treetop'
|
7
7
|
|
8
8
|
module FilterLexer
|
9
|
+
# The parser is your main entrypoint for the lexer
|
10
|
+
#
|
11
|
+
# You never instantiate it, you just use it's static methods
|
9
12
|
class Parser
|
10
|
-
|
13
|
+
@parser = FilterLexerParser.new
|
11
14
|
|
12
15
|
class << self
|
16
|
+
# The bread and butter of the entire thing: parsing, and it's pretty simple
|
17
|
+
#
|
18
|
+
# Just pass in a string, and out comes a parsed tree, or an exception
|
19
|
+
#
|
20
|
+
# The parsed tree will always be an FilterLexer::Expression object
|
21
|
+
# The exception will always be an FilterLexer::ParseException object
|
13
22
|
def parse(data)
|
14
23
|
# Pass the data over to the parser instance
|
15
|
-
tree =
|
24
|
+
tree = @parser.parse(data)
|
16
25
|
|
17
26
|
# If the AST is nil then there was an error during parsing
|
18
|
-
|
19
|
-
if tree.nil?
|
20
|
-
raise ParseException, @@parser
|
21
|
-
end
|
27
|
+
fail ParseException, @parser if tree.nil?
|
22
28
|
|
23
29
|
# Cleanup the tree
|
24
30
|
clean_tree(tree)
|
@@ -26,6 +32,10 @@ module FilterLexer
|
|
26
32
|
return tree
|
27
33
|
end
|
28
34
|
|
35
|
+
# When using the lexer, it may be useful to have a visual representation of the tree
|
36
|
+
#
|
37
|
+
# Just pass the tree (or any node in it, if you're only interested in that part) to this function, and a visual
|
38
|
+
# representation of the tree will magically appear in stdout
|
29
39
|
def output_tree(element, indent = '')
|
30
40
|
puts "#{indent}#{element.class}: #{element.text_value}"
|
31
41
|
(element.elements || []).each do |e|
|
@@ -35,7 +45,7 @@ module FilterLexer
|
|
35
45
|
|
36
46
|
private
|
37
47
|
|
38
|
-
def
|
48
|
+
def syn?(node)
|
39
49
|
return node.class.name == 'Treetop::Runtime::SyntaxNode'
|
40
50
|
end
|
41
51
|
|
@@ -48,9 +58,9 @@ module FilterLexer
|
|
48
58
|
# Clean child elements
|
49
59
|
root_node.elements.each { |node| clean_tree(node) }
|
50
60
|
# Remove empty syntax elements
|
51
|
-
root_node.elements.reject! { |node| node_elem(node).empty? &&
|
61
|
+
root_node.elements.reject! { |node| node_elem(node).empty? && syn?(node) }
|
52
62
|
# Replace syntax elements with just one child with that child
|
53
|
-
root_node.elements.map! { |node| (node_elem(node).size == 1 &&
|
63
|
+
root_node.elements.map! { |node| (node_elem(node).size == 1 && syn?(node)) ? node.elements.first : node }
|
54
64
|
end
|
55
65
|
end
|
56
66
|
end
|
@@ -10,19 +10,18 @@ module FilterLexer
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def context
|
13
|
-
|
14
|
-
i1 = 0
|
15
|
-
i2 = @index + 40
|
16
|
-
i2 = @input.size if i2 > @input.size
|
13
|
+
size = @input.size
|
14
|
+
i1 = [0, @index - 40].max
|
15
|
+
i2 = [size, @index + 40].min
|
17
16
|
|
18
17
|
context = @input.slice(i1..i2)
|
19
|
-
if i1 > 0
|
20
|
-
|
21
|
-
i1 -= 3
|
22
|
-
end
|
23
|
-
context = "#{context}..." if i2 < @input.size
|
18
|
+
context = "...#{context}" if i1 > 0
|
19
|
+
context = "#{context}..." if i2 < size
|
24
20
|
|
25
|
-
|
21
|
+
relpos = @index - i1
|
22
|
+
relpos += 1 if i1 > 0
|
23
|
+
|
24
|
+
return context + "\r\n" + ' ' * relpos + '^'
|
26
25
|
end
|
27
26
|
end
|
28
27
|
end
|
@@ -5,40 +5,46 @@ module FilterLexer
|
|
5
5
|
end
|
6
6
|
|
7
7
|
def query_string
|
8
|
-
return
|
8
|
+
return elements.map(&:query_string).join(' ')
|
9
9
|
end
|
10
10
|
|
11
11
|
def query_variables
|
12
|
-
return
|
12
|
+
return elements.map(&:query_variables).flatten
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
16
|
class Group
|
17
17
|
def query_string
|
18
|
-
return "(#{
|
18
|
+
return "(#{elements.first.query_string})"
|
19
19
|
end
|
20
20
|
|
21
21
|
def query_variables
|
22
|
-
return
|
22
|
+
return elements.first.query_variables
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
26
|
class Filter
|
27
27
|
def query_string
|
28
|
-
return "#{
|
28
|
+
return "#{identifier.sql} #{operator.sql} ?"
|
29
29
|
end
|
30
30
|
|
31
|
-
def query_variables
|
32
|
-
return [
|
31
|
+
def query_variables
|
32
|
+
return [data.sql]
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
class
|
36
|
+
class Identifier
|
37
|
+
def sql
|
38
|
+
return "`#{text_value}`"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class LogicOperator
|
37
43
|
def query_string
|
38
|
-
return
|
44
|
+
return sql
|
39
45
|
end
|
40
46
|
|
41
|
-
def query_variables
|
47
|
+
def query_variables
|
42
48
|
return []
|
43
49
|
end
|
44
50
|
end
|
@@ -55,16 +61,26 @@ module FilterLexer
|
|
55
61
|
end
|
56
62
|
end
|
57
63
|
|
64
|
+
class Operator
|
65
|
+
def query_string
|
66
|
+
return sql
|
67
|
+
end
|
68
|
+
|
69
|
+
def query_variables
|
70
|
+
return []
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
58
74
|
class EQOperator
|
59
75
|
def sql
|
60
|
-
return 'IS' if parent.
|
76
|
+
return 'IS' if parent.data.is_a?(ValueSpecial)
|
61
77
|
return '='
|
62
78
|
end
|
63
79
|
end
|
64
80
|
|
65
81
|
class NEQOperator
|
66
82
|
def sql
|
67
|
-
return 'IS NOT' if parent.
|
83
|
+
return 'IS NOT' if parent.data.is_a?(ValueSpecial)
|
68
84
|
return '<>'
|
69
85
|
end
|
70
86
|
end
|
@@ -125,19 +141,13 @@ module FilterLexer
|
|
125
141
|
|
126
142
|
class StringLiteral
|
127
143
|
def sql
|
128
|
-
return
|
144
|
+
return text_value
|
129
145
|
end
|
130
146
|
end
|
131
147
|
|
132
148
|
class NumberLiteral
|
133
149
|
def sql
|
134
|
-
return
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
class Identifier
|
139
|
-
def sql
|
140
|
-
return self.text_value
|
150
|
+
return text_value
|
141
151
|
end
|
142
152
|
end
|
143
153
|
end
|
data/lib/filter_lexer/nodes.rb
CHANGED
@@ -1,37 +1,52 @@
|
|
1
1
|
module FilterLexer
|
2
|
+
# The root element, expression is a collection of other expressions and and/or operators
|
2
3
|
class Expression < Treetop::Runtime::SyntaxNode
|
3
4
|
end
|
4
5
|
|
6
|
+
# A group is simply an expression in parenthesis
|
5
7
|
class Group < Treetop::Runtime::SyntaxNode
|
6
8
|
end
|
7
9
|
|
10
|
+
# A filter is the core object of the lexer: an indentifier, an operator and a data
|
8
11
|
class Filter < Treetop::Runtime::SyntaxNode
|
12
|
+
# The identifier element
|
13
|
+
#
|
14
|
+
# Of type FilterLexer::Identifier
|
9
15
|
def identifier
|
10
|
-
return elements[0]
|
16
|
+
return elements[0]
|
11
17
|
end
|
12
18
|
|
19
|
+
# The operator element
|
20
|
+
#
|
21
|
+
# Subclass of FilterLexer::Operator
|
13
22
|
def operator
|
14
|
-
return elements[1]
|
23
|
+
return elements[1]
|
15
24
|
end
|
16
25
|
|
17
|
-
|
18
|
-
|
26
|
+
# The value element
|
27
|
+
#
|
28
|
+
# Subclass of FilterLexer::Value
|
29
|
+
def data
|
30
|
+
return elements[2]
|
19
31
|
end
|
32
|
+
end
|
20
33
|
|
21
|
-
|
22
|
-
|
23
|
-
end
|
34
|
+
# An logic operator is the glue between filters
|
35
|
+
class LogicOperator < Treetop::Runtime::SyntaxNode
|
24
36
|
end
|
25
37
|
|
26
|
-
|
38
|
+
class OrOperator < LogicOperator
|
39
|
+
end
|
27
40
|
|
28
|
-
class
|
41
|
+
class AndOperator < LogicOperator
|
29
42
|
end
|
30
43
|
|
31
|
-
|
44
|
+
# An identifier is the target (variable) of the filter
|
45
|
+
class Identifier < Treetop::Runtime::SyntaxNode
|
32
46
|
end
|
33
47
|
|
34
|
-
|
48
|
+
# An operator is the type (function) of the filter
|
49
|
+
class Operator < Treetop::Runtime::SyntaxNode
|
35
50
|
end
|
36
51
|
|
37
52
|
class EQOperator < Operator
|
@@ -58,8 +73,7 @@ module FilterLexer
|
|
58
73
|
class LikeOperator < Operator
|
59
74
|
end
|
60
75
|
|
61
|
-
#
|
62
|
-
|
76
|
+
# A value is the data of the filter
|
63
77
|
class Value < Treetop::Runtime::SyntaxNode
|
64
78
|
end
|
65
79
|
|
@@ -83,10 +97,4 @@ module FilterLexer
|
|
83
97
|
|
84
98
|
class NumberLiteral < ValueScalar
|
85
99
|
end
|
86
|
-
|
87
|
-
# Identifier
|
88
|
-
|
89
|
-
class Identifier < Treetop::Runtime::SyntaxNode
|
90
|
-
end
|
91
100
|
end
|
92
|
-
|
data/lib/filter_lexer/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: filter_lexer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michon van Dooren
|
@@ -52,6 +52,34 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
55
83
|
- !ruby/object:Gem::Dependency
|
56
84
|
name: treetop
|
57
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -73,7 +101,10 @@ executables: []
|
|
73
101
|
extensions: []
|
74
102
|
extra_rdoc_files: []
|
75
103
|
files:
|
104
|
+
- ".codeclimate.yml"
|
76
105
|
- ".gitignore"
|
106
|
+
- ".inch.yml"
|
107
|
+
- ".rubocop.yml"
|
77
108
|
- ".travis.yml"
|
78
109
|
- Gemfile
|
79
110
|
- LICENSE
|