truth_serum 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: fe740d3a98649998bfc874b79cccc33b954ed0ad
4
+ data.tar.gz: 22759b2db9b456e1072b2abae771568308cef393
5
+ SHA512:
6
+ metadata.gz: 1caac98110f89c78249c1c7726eeeca0ba2c4cfc5f64bdc56602e00e8baa1300b971e33380d88ceea8729ec22bd14d997373ba6d420ef68643dfb26310b6b674
7
+ data.tar.gz: 043fe0f877d243500b715e6cde139fc4615cf76d8e0d31d2684b8e92c2545e4fe8faaaa2c4d793b97caa2ded3e007a39168185471f57788cce893a6abbe12464
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.gem
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.12.1
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,7 @@
1
+ clearing :on
2
+
3
+ guard :minitest do
4
+ watch(%r{^test/(.*)\/?(.*)_test\.rb$})
5
+ watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}#{m[2]}_test.rb" }
6
+ watch(%r{^test/test_helper\.rb$}) { 'test' }
7
+ end
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # TRUTH SERUM
2
+
3
+ Features
4
+
5
+ * Parses `keywords` as well as `filters:value` syntax.
6
+ * Handles `"quotes spaces ::: escapes \r\n newlines"`.
7
+ * Has proper lexing and parsing system instead of a regular expression.
8
+
9
+ # INSTALL
10
+
11
+ ```
12
+ gem install truth_serum
13
+ ```
14
+
15
+ # USAGE
16
+
17
+ ```rb
18
+ require 'truth_serum'
19
+
20
+ result = TruthSerum.parse("hello world key:value -negate:yes")
21
+ puts result.terms
22
+ puts result.filters
23
+ puts result.negative_filters
24
+ ```
25
+
26
+ # LICENSE
27
+
28
+ MIT
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "truth_serum"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,125 @@
1
+ module TruthSerum
2
+ class Lexer
3
+ def initialize(input)
4
+ @input = input
5
+ @index = 0
6
+ @tokens = []
7
+ @buffer = ''
8
+ end
9
+
10
+ def lex
11
+ @tokens = []
12
+ lex_line
13
+ @tokens
14
+ end
15
+
16
+ def lex_line
17
+ until eof?
18
+ case peek
19
+ when '"' then lex_quoted_word
20
+ when ':', '+', '-' then lex_symbol
21
+ when ' ' then lex_spaces
22
+ else
23
+ lex_word
24
+ end
25
+ end
26
+ end
27
+
28
+ def lex_symbol
29
+ case consume
30
+ when ':' then emit(:colon)
31
+ when '+' then emit(:plus)
32
+ when '-' then emit(:minus)
33
+ else
34
+ raise 'not a symbol'
35
+ end
36
+ end
37
+
38
+ def lex_spaces
39
+ consume while peek == ' '
40
+ emit(:space)
41
+ end
42
+
43
+ def lex_word
44
+ until eof?
45
+ case peek
46
+ when ' ', ':', '+', '-' then return emit(:term)
47
+ when '"' then discard
48
+ else
49
+ consume
50
+ end
51
+ end
52
+
53
+ emit(:term)
54
+ end
55
+
56
+ def lex_quoted_word
57
+ raise 'not in quoted word' unless discard == '"'
58
+
59
+ until eof?
60
+ case peek
61
+ when '\\' then lex_escaped_char
62
+ when '"'
63
+ discard
64
+ break
65
+ else
66
+ consume
67
+ end
68
+ end
69
+
70
+ emit(:term)
71
+ end
72
+
73
+ def lex_escaped_char
74
+ raise 'not in escaped char' unless discard == '\\'
75
+ return @buffer += '\\' if eof?
76
+
77
+ case peek
78
+ when 'r'
79
+ discard
80
+ @buffer += "\r"
81
+ when 'n'
82
+ discard
83
+ @buffer += "\n"
84
+ else # '\\' '"' and everything else is treated literally
85
+ consume
86
+ end
87
+ end
88
+
89
+ private
90
+
91
+ def eof?
92
+ @index >= @input.length
93
+ end
94
+
95
+ def chars_left
96
+ @input.length - @index
97
+ end
98
+
99
+ def peek
100
+ return nil if eof?
101
+ @input[@index]
102
+ end
103
+
104
+ def consume
105
+ raise 'consume beyond EOF' if eof?
106
+
107
+ @buffer += peek
108
+ discard
109
+ end
110
+
111
+ def discard
112
+ @index += 1
113
+ @input[@index - 1]
114
+ end
115
+
116
+ def emit(type)
117
+ return nil if @buffer == ''
118
+
119
+ token = Token.new(type, @buffer)
120
+ @tokens << token
121
+ @buffer = ''
122
+ token
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,85 @@
1
+ module TruthSerum
2
+ class Parser
3
+ def initialize(tokens)
4
+ @tokens = tokens
5
+ end
6
+
7
+ def reset
8
+ @result = {
9
+ terms: [],
10
+ negative_terms: [],
11
+ filters: {},
12
+ negative_filters: {},
13
+ }
14
+ end
15
+
16
+ def parse
17
+ reset
18
+ parse_line
19
+ @result
20
+ end
21
+
22
+ def parse_line
23
+ until eof?
24
+ token = peek
25
+
26
+ case
27
+ when token.plus? || token.minus?
28
+ parse_sign
29
+ when token.term?
30
+ parse_term_or_filter
31
+ else
32
+ consume # stray colons and spaces
33
+ end
34
+ end
35
+ end
36
+
37
+ def parse_sign
38
+ negate = consume.minus? while peek.plus? || peek.minus?
39
+ if peek.term?
40
+ parse_term_or_filter(negate: negate)
41
+ else
42
+ nil
43
+ end
44
+ end
45
+
46
+ def parse_term_or_filter(negate: false)
47
+ term = ''
48
+ value = ''
49
+
50
+ # first portion of text
51
+ term = consume.text
52
+ return emit_term(term, negate: negate) unless peek.colon?
53
+
54
+ # if we have a filter (detect `:`)
55
+ consume while peek.colon?
56
+
57
+ value = consume.text
58
+ emit_filter(term, value, negate: negate)
59
+ end
60
+
61
+ private
62
+
63
+ def emit_term(term, negate: false)
64
+ @result[:terms] << term unless negate
65
+ @result[:negative_terms] << term if negate
66
+ end
67
+
68
+ def emit_filter(key, value, negate: false)
69
+ @result[:filters][key] = value unless negate
70
+ @result[:negative_filters][key] = value if negate
71
+ end
72
+
73
+ def eof?
74
+ peek.is_a?(NilToken)
75
+ end
76
+
77
+ def peek
78
+ @tokens[0] || NilToken.new
79
+ end
80
+
81
+ def consume
82
+ @tokens.shift || NilToken.new
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,12 @@
1
+ module TruthSerum
2
+ class Result
3
+ attr_accessor :terms, :negative_terms, :filters, :negative_filters
4
+
5
+ def initialize(terms:, negative_terms:, filters:, negative_filters:)
6
+ @terms = terms
7
+ @negative_terms = negative_terms
8
+ @filters = filters
9
+ @negative_filters = negative_filters
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,36 @@
1
+ module TruthSerum
2
+ class Token
3
+ VALID_TYPES = [
4
+ :term,
5
+ :colon,
6
+ :space,
7
+ :plus,
8
+ :minus
9
+ ].freeze
10
+
11
+ attr_accessor :type, :text
12
+
13
+ def initialize(type, text)
14
+ @type = type
15
+ @text = text
16
+ end
17
+
18
+ def valid?
19
+ VALID_TYPES.include?(@type) &&
20
+ !@text.nil? &&
21
+ !@text.empty?
22
+ end
23
+
24
+ VALID_TYPES.each do |type|
25
+ define_method "#{type}?" do
26
+ @type == type
27
+ end
28
+ end
29
+ end
30
+
31
+ class NilToken < Token
32
+ def initialize
33
+ super(:nil, '')
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ module TruthSerum
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,11 @@
1
+ require "truth_serum/version"
2
+ require "truth_serum/result"
3
+ require "truth_serum/token"
4
+ require "truth_serum/lexer"
5
+ require "truth_serum/parser"
6
+
7
+ module TruthSerum
8
+ def self.parse(line)
9
+ Result.new(**Parser.new(Lexer.new(line).lex).parse)
10
+ end
11
+ end
@@ -0,0 +1,27 @@
1
+ # vim: filetype=ruby
2
+ # coding: utf-8
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'truth_serum/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "truth_serum"
9
+ spec.version = TruthSerum::VERSION
10
+ spec.authors = ["Chakrit Wichian"]
11
+ spec.email = ["chakrit@omise.co"]
12
+
13
+ spec.summary = "Parser for github-style search."
14
+ spec.homepage = "https://github.com/chakrit/truth_serum"
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
+ spec.bindir = "exe"
17
+ spec.licenses = ["MIT"]
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", '~> 1.12'
22
+ spec.add_development_dependency "rake", '~> 11.1'
23
+ spec.add_development_dependency "minitest", '~> 5.8'
24
+ spec.add_development_dependency "guard", '~> 2.13'
25
+ spec.add_development_dependency "guard-minitest", '~> 2.4'
26
+ spec.add_development_dependency "pry", '~> 0.10'
27
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: truth_serum
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Chakrit Wichian
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-05-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.12'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.12'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '11.1'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '11.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.8'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.8'
55
+ - !ruby/object:Gem::Dependency
56
+ name: guard
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.13'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.13'
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard-minitest
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.4'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.4'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.10'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.10'
97
+ description:
98
+ email:
99
+ - chakrit@omise.co
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".travis.yml"
106
+ - Gemfile
107
+ - Guardfile
108
+ - README.md
109
+ - Rakefile
110
+ - bin/console
111
+ - bin/setup
112
+ - lib/truth_serum.rb
113
+ - lib/truth_serum/lexer.rb
114
+ - lib/truth_serum/parser.rb
115
+ - lib/truth_serum/result.rb
116
+ - lib/truth_serum/token.rb
117
+ - lib/truth_serum/version.rb
118
+ - truth_serum.gemspec
119
+ homepage: https://github.com/chakrit/truth_serum
120
+ licenses:
121
+ - MIT
122
+ metadata: {}
123
+ post_install_message:
124
+ rdoc_options: []
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ required_rubygems_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ requirements: []
138
+ rubyforge_project:
139
+ rubygems_version: 2.5.1
140
+ signing_key:
141
+ specification_version: 4
142
+ summary: Parser for github-style search.
143
+ test_files: []