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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4eb20adfd043c015d21dbc1639c4669945131cef
4
- data.tar.gz: a0f2855582ae1094e6ef329b82d04f4b8b03fcec
3
+ metadata.gz: 47fbb2acdc5535e2e74b0355dbb042e289de54b1
4
+ data.tar.gz: 4be18e0e750a3b298e4572e175e2b2e43fbeea3e
5
5
  SHA512:
6
- metadata.gz: ccc25baabaa2ca2af95d5b7653dd996ab674c6822708bef2ff92db4fd26d6c7b25dad79c1c2fd0e71cd472763c05abbf78c2af6b5f7c8693b0837e325c7c9d65
7
- data.tar.gz: 71f924d03e1af187be0e270c0ceab349f092cb02cb8ff0fcdcfe3ba09864f82c693595ddd4b01c82fb821e5a7a1e2ec6755558a31a0699eeff14f065b12330a8
6
+ metadata.gz: 9e83318063753adab91aeddb75fbe34f3405a35f47b82dd01eed69a9276b5b97e9110a830da5f981a1a8d633159da158ce38a636562ce82d0da591a48c8e4503
7
+ data.tar.gz: a7ff4f435b9ab633daf95f8b31c1a98b5c001b8b86cd82dfcfaf69749b4e2b61bcbbfffb4f8ff020af0f2cee9d081297d12060c9b2a6008b2628a3599829c513
data/.codeclimate.yml ADDED
@@ -0,0 +1,11 @@
1
+ ---
2
+ engines:
3
+ fixme:
4
+ enabled: true
5
+ rubocop:
6
+ enabled: true
7
+ ratings:
8
+ paths:
9
+ - "**.rb"
10
+ exclude_paths:
11
+ - spec/**/*
data/.inch.yml ADDED
@@ -0,0 +1,6 @@
1
+ files:
2
+ included:
3
+ - lib/**/.rb
4
+ excluded:
5
+ - lib/filter_lexer/formatters/**/*.rb
6
+
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 "bundler/gem_tasks"
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 = %q{A basic lexer that supports basic sql-like filtering logic.}
13
- spec.description = %q{Simple treetop lexer that supports writing filters in an SQL-like manner.}
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
- raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.'
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 "filter_lexer/version"
4
- require "filter_lexer/exceptions"
5
- require "filter_lexer/nodes"
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
- @@parser = FilterLexerParser.new
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 = @@parser.parse(data)
24
+ tree = @parser.parse(data)
16
25
 
17
26
  # If the AST is nil then there was an error during parsing
18
- # we need to report a simple error message to help the user
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 is_syn(node)
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? && is_syn(node) }
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 && is_syn(node)) ? node.elements.first : node }
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
- i1 = @index - 40
14
- i1 = 0 if 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
- context = "...#{context}"
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
- return context + "\r\n" + ' ' * (@index - i1) + '^'
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 self.elements.map { |e| e.query_string }.join(' ')
8
+ return elements.map(&:query_string).join(' ')
9
9
  end
10
10
 
11
11
  def query_variables
12
- return self.elements.map { |e| e.query_variables }.flatten
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 "(#{self.elements.first.query_string})"
18
+ return "(#{elements.first.query_string})"
19
19
  end
20
20
 
21
21
  def query_variables
22
- return self.elements.first.query_variables
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 "#{elements[0].sql} #{elements[1].sql} ?"
28
+ return "#{identifier.sql} #{operator.sql} ?"
29
29
  end
30
30
 
31
- def query_variables
32
- return [elements[2].sql]
31
+ def query_variables
32
+ return [data.sql]
33
33
  end
34
34
  end
35
35
 
36
- class Operator
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 self.sql
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.value_class < ValueSpecial
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.value_class < ValueSpecial
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 self.text_value
144
+ return text_value
129
145
  end
130
146
  end
131
147
 
132
148
  class NumberLiteral
133
149
  def sql
134
- return self.text_value
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
@@ -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].sql
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].sql
23
+ return elements[1]
15
24
  end
16
25
 
17
- def value
18
- return elements[2].sql
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
- def value_class
22
- return elements[2].class
23
- end
34
+ # An logic operator is the glue between filters
35
+ class LogicOperator < Treetop::Runtime::SyntaxNode
24
36
  end
25
37
 
26
- # Operators
38
+ class OrOperator < LogicOperator
39
+ end
27
40
 
28
- class Operator < Treetop::Runtime::SyntaxNode
41
+ class AndOperator < LogicOperator
29
42
  end
30
43
 
31
- class OrOperator < Operator
44
+ # An identifier is the target (variable) of the filter
45
+ class Identifier < Treetop::Runtime::SyntaxNode
32
46
  end
33
47
 
34
- class AndOperator < Operator
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
- # Values
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
-
@@ -1,3 +1,3 @@
1
1
  module FilterLexer
2
- VERSION = "0.1.0"
2
+ VERSION = '0.1.1'
3
3
  end
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.0
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