filter_lexer 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Dependency Status](https://gemnasium.com/MaienM/FilterLexer.svg)](https://gemnasium.com/MaienM/FilterLexer) [![Code Climate](https://codeclimate.com/github/MaienM/FilterLexer/badges/gpa.svg)](https://codeclimate.com/github/MaienM/FilterLexer) [![Inline docs](http://inch-ci.org/github/MaienM/FilterLexer.svg?branch=master)](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
|