plunk 0.3.2 → 0.3.3

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: 2166c154fb85131ea652ccc4409725b29b4dc250
4
- data.tar.gz: 75503b3934ea91d1ba81ff934684ccd724af7a84
3
+ metadata.gz: f9783fb61da735628ac2266ff16fd4234d146bed
4
+ data.tar.gz: 8fa8d01bffed0cc5351afef868a2e22caa3ddf88
5
5
  SHA512:
6
- metadata.gz: bbeff1a1e3c8e36d0857662715b2b8c9392d6543ff7de11527f6748852feba47f449bb1e66c9a125918884c4d95bfa2d3cb952370c77cb87d9f8e1a3ae00f91f
7
- data.tar.gz: 1d281d2a627594100c9ed502a96e82e01192248585cba14a8de91174fb5c8381145dd8d8a192a9a7259b0653eed029629fc2642593011fee14c6c7bee4b3aa14
6
+ metadata.gz: aaf0493c53bfee8441676b7e72697fb72d47e32b5e6abd8e505e6ef89483a7976bc6ec3a5fb5b34b5959fff0a4d5074cb836b9acde565417737589c704c0fb54
7
+ data.tar.gz: 3767a5c241aa94a9c55faef62742f6e57862377cb2e662cc0c58cd7a952dd1ac5716f3ddea6a894adf73f806c0db5481c95e4f99c1804033a59d9f1875a7a2bf
data/Gemfile.lock CHANGED
@@ -1,8 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- plunk (0.3.1)
4
+ plunk (0.3.2)
5
5
  activesupport (~> 4.0, >= 4.0.0)
6
+ chronic (~> 0.10, >= 0.10.0)
6
7
  elasticsearch (~> 0.4, >= 0.4.3)
7
8
  json (~> 1.8, >= 1.8.0)
8
9
  parslet (~> 1.5, >= 1.5.0)
@@ -18,6 +19,7 @@ GEM
18
19
  tzinfo (~> 0.3.37)
19
20
  atomic (1.1.14)
20
21
  blankslate (2.1.2.4)
22
+ chronic (0.10.2)
21
23
  diff-lcs (1.2.5)
22
24
  elasticsearch (0.4.3)
23
25
  elasticsearch-api (= 0.4.3)
data/README.md CHANGED
@@ -5,43 +5,82 @@ Human-friendly query language for Elasticsearch
5
5
 
6
6
  ## About
7
7
 
8
- Plunk is a ruby gem to take a human-friendly search command and translate it
9
- to something Elasticsearch understands. Currently it only supports a few
10
- commands, but the framework is in place
8
+ Plunk is a ruby gem to take a human-friendly, one-line search command and
9
+ translate it to full-fledged JSON to send to Elasticsearch. Currently it only
10
+ supports a few commands, but the goal is to support a large subset of what
11
+ Elasticsearch offers.
11
12
 
12
13
  ## Installation
13
14
  ```
14
15
  gem install plunk
15
16
  ```
16
17
 
18
+ Plunk uses [Parslet](https://github.com/kschiess/parslet) to first parse your
19
+ query, and then [Elasticsearch's official ruby library](https://github.com/elasticsearch/elasticsearch-ruby)
20
+ to send it to Elasticsearch.
21
+
17
22
  ## Usage
18
23
  ```ruby
19
24
  require 'plunk'
20
25
 
21
- # configure is required
22
- # elasticsearch_options accepts the same params as Elasticsearch::Client
26
+ #
27
+ # Configuration is required before using Plunk
28
+ #
29
+ # Elasticsearch_options accepts the same params as Elasticsearch::Client
30
+ # from the elasticsearch-ruby library
23
31
  Plunk.configure do |config|
24
32
  config.elasticsearch_options = { host: 'localhost' }
25
33
  end
26
34
 
27
- # all documents of type syslog from the last week
28
- Plunk.search 'last 1w _type = syslog'
35
+ # Restrict timeframe to last 1 week and match documents with _type=syslog
36
+ # s = seconds
37
+ # m = minutes
38
+ # h = hours
39
+ # d = days
40
+ # w = weeks
41
+ # All times in Plunk are converted to UTC
42
+ Plunk.search 'last 1w AND _type = syslog'
43
+
44
+ # The ```window``` command can also be used to filter by time
45
+ Plunk.search 'window -2d to -1d'
46
+
47
+ # Plunk tries to parse the date with Chronic, so this works too. Note the
48
+ # double quotes around the time string. This is needed if it contains a space.
49
+ Plunk.search 'window "last monday" to "last thursday"'
50
+
51
+ # Of course, absolute dates are supported as well. Date format is American style
52
+ # e.g. MM/DD/YY
53
+ Plunk.search 'window 3/14/12 to 3/15/12'
54
+
55
+ # Use double quotes to wrap space-containing strings
56
+ Plunk.search 'http.header = "UserAgent: Mozilla/5.0"'
57
+
58
+ # Commands are joined using parenthesized booleans
59
+ Plunk.search '(last 1h AND severity = 5) OR (last 1w AND severity = 3)'
29
60
 
30
- # nested search. first runs the '_type=access_logs' search, extracts the values
31
- # for fields user.name, user.nickname, and user.email, then ORs these together
32
- # as the query for the outer search.
33
- Plunk.search 'user = `_type=access_logs | user.name, user.nickname, user.email`'
61
+ # "AND" is aliased to "and" and "&". Similarly, "OR" is aliased to "or" and "|".
62
+ # The following queries are identical to one above
63
+ Plunk.search '(last 1h and severity = 5) or (last 1w and severity = 3)'
64
+ Plunk.search '(last 1h & severity = 5) | (last 1w & severity = 3)'
34
65
 
35
- # booleans are supported
36
- Plunk.search 'foo.field = (bar OR baz)'
66
+ # Use the NOT keyword to negate the following command or boolean chain
67
+ Plunk.search 'NOT message = Error'
68
+
69
+ # Like AND and OR, "NOT" is aliased to "not" and "~"
70
+ Plunk.search 'not message = Error'
71
+ Plunk.search '~ message = Error'
72
+
73
+ # Regexp is supported as well
74
+ Plunk.search 'http.headers = /.*User-Agent: Mozilla.*/ OR http.headers = /.*application\/json.*/'
37
75
  ```
38
76
 
39
77
 
40
78
  ## Translation
41
79
 
42
- Plunk takes your command and translates
80
+ Under the hood, Plunk takes your query and translates it to
81
+ Elasticsearch-compatible JSON. For example,
43
82
 
44
- ```last 24h _type=syslog```
83
+ ```last 24h & _type=syslog```
45
84
 
46
85
  gets translated to:
47
86
 
@@ -49,11 +88,6 @@ gets translated to:
49
88
  {
50
89
  "query": {
51
90
  "filtered": {
52
- "query": {
53
- "query_string": {
54
- "query": "_type:syslog"
55
- }
56
- },
57
91
  "filter": {
58
92
  "and": [
59
93
  {
@@ -63,6 +97,13 @@ gets translated to:
63
97
  "lte": "2013-08-24T05:43:13.770Z"
64
98
  }
65
99
  }
100
+ },
101
+ {
102
+ "query": {
103
+ "query_string": {
104
+ "query": "_type:syslog"
105
+ }
106
+ }
66
107
  }
67
108
  ]
68
109
  }
@@ -70,3 +111,6 @@ gets translated to:
70
111
  }
71
112
  }
72
113
  ```
114
+
115
+ In general, commands are combined into a single filter using Elasticsearch's,
116
+ ```and```, ```or```, and ```not``` filters.
data/lib/plunk/helper.rb CHANGED
@@ -30,17 +30,34 @@ module Plunk
30
30
  }
31
31
  end
32
32
 
33
+ def self.limit_builder(limit)
34
+ {
35
+ limit: {
36
+ value: limit
37
+ }
38
+ }
39
+ end
40
+
33
41
  def self.range_builder(range_min, range_max)
34
42
  {
35
43
  range: {
36
44
  Plunk.timestamp_field => {
37
45
  gte: range_min,
38
- gte: range_max
46
+ lte: range_max
39
47
  }
40
48
  }
41
49
  }
42
50
  end
43
51
 
52
+ def self.regexp_builder(field, regexp, flags=nil)
53
+ {
54
+ regexp: {
55
+ field => regexp,
56
+ flags: flags || 'ALL'
57
+ }
58
+ }
59
+ end
60
+
44
61
  def self.time_query_to_timestamp(int_quantity, quantifier)
45
62
  case quantifier
46
63
  when 's'
data/lib/plunk/parser.rb CHANGED
@@ -13,7 +13,9 @@ module Plunk
13
13
  rule(:space?) { space.maybe }
14
14
 
15
15
  # Numbers
16
- rule(:integer) { str('-').maybe >> digit.repeat(1) >> space? }
16
+ rule(:positive_integer) { digit.repeat(1) >> space? }
17
+ rule(:negative_integer) { str('-') >> positive_integer }
18
+ rule(:integer) { negative_integer | positive_integer }
17
19
  rule(:float) {
18
20
  str('-').maybe >> digit.repeat(1) >> str('.') >> digit.repeat(1) >> space?
19
21
  }
@@ -62,6 +64,16 @@ module Plunk
62
64
  rule(:rhs) {
63
65
  regexp | query_value
64
66
  }
67
+ rule(:chronic_time) {
68
+ string | match('[^\s]').repeat
69
+ }
70
+ rule(:relative_time) {
71
+ integer.as(:quantity) >>
72
+ match('s|m|h|d|w').as(:quantifier)
73
+ }
74
+ rule(:absolute_time) {
75
+ datetime.as(:datetime) | chronic_time.as(:chronic_time)
76
+ }
65
77
 
66
78
  # Field = Value
67
79
  rule(:field_value) {
@@ -72,20 +84,31 @@ module Plunk
72
84
 
73
85
  # Value-only
74
86
  rule(:value_only) {
75
- rhs.as(:value)
87
+ query_value.as(:value)
88
+ }
89
+
90
+ # Window
91
+ rule(:window) {
92
+ str('window') >>
93
+ space >>
94
+ (relative_time | absolute_time).as(:window_start) >>
95
+ space >> str('to') >> space >>
96
+ (relative_time | absolute_time).as(:window_end)
97
+ }
98
+
99
+ # Limit
100
+ rule(:limit) {
101
+ str('limit') >> space >> positive_integer.as(:limit)
76
102
  }
77
103
 
78
104
  # Regexp
79
105
  rule(:regexp) {
80
- str('/') >> (str('\/') | match('[^/]')).repeat >> str('/')
106
+ str('/') >> (str('\/') | match('[^/]')).repeat.as(:regexp) >> str('/')
81
107
  }
82
108
 
83
109
  # Last
84
110
  rule(:last) {
85
- str('last') >>
86
- space >>
87
- integer.as(:quantity) >>
88
- match('s|m|h|d|w').as(:quantifier)
111
+ (str('last') >> space >> relative_time).as(:last)
89
112
  }
90
113
 
91
114
  # Subsearch
@@ -101,7 +124,9 @@ module Plunk
101
124
  # NOTE: order matters!
102
125
  rule(:command) {
103
126
  (
127
+ window |
104
128
  last |
129
+ limit |
105
130
  field_value |
106
131
  value_only
107
132
  ).as(:command) >> space?
@@ -1,41 +1,84 @@
1
1
  require 'parslet'
2
+ require 'chronic'
2
3
 
3
4
  module Plunk
4
5
  class Transformer < Parslet::Transform
6
+ # Base
7
+ rule(command: subtree(:command)) do
8
+ command
9
+ end
10
+
5
11
  # Field = Value
6
- rule(command: {
12
+ rule(
7
13
  field: simple(:field),
8
14
  value: simple(:value)
9
- }) do
15
+ ) do
10
16
  Helper.query_builder(
11
17
  String(field) + ":" + String(value)
12
18
  )
13
19
  end
14
20
 
21
+ # Limit
22
+ rule(limit: simple(:limit)) do
23
+ Helper.limit_builder(Integer(limit))
24
+ end
25
+
26
+ # Regexp
27
+ rule(
28
+ field: simple(:field),
29
+ value: {
30
+ regexp: simple(:regexp)
31
+ }
32
+ ) do
33
+ Helper.regexp_builder(String(field), String(regexp))
34
+ end
35
+
15
36
  # Value-only
16
- rule(command: { value: simple(:value) }) do
37
+ rule(value: simple(:value)) do
17
38
  Helper.query_builder(String(value))
18
39
  end
19
40
 
20
- rule(command: {
41
+ # Last
42
+ rule(last: subtree(:last)) do
43
+ start_time = last
44
+ end_time = Helper.timestamp_format(Time.now)
45
+
46
+ Helper.range_builder(start_time, end_time)
47
+ end
48
+
49
+ # Window
50
+ rule(
51
+ window_start: subtree(:window_start),
52
+ window_end: subtree(:window_end)
53
+ ) do
54
+ Helper.range_builder(window_start, window_end)
55
+ end
56
+
57
+ # Time parts
58
+ rule(datetime: simple(:datetime)) do
59
+ String(datetime)
60
+ end
61
+ rule(
21
62
  quantity: simple(:quantity),
22
63
  quantifier: simple(:quantifier)
23
- }) do
24
- start_timestamp = Helper.time_query_to_timestamp(
25
- Integer(quantity),
64
+ ) do
65
+ timestamp = Helper.time_query_to_timestamp(
66
+ Integer(quantity).abs, # last -1h same as last 1h
26
67
  String(quantifier)
27
68
  )
28
69
 
29
- start_time = Helper.timestamp_format start_timestamp
30
- end_time = Helper.timestamp_format(Time.now)
31
-
32
- Helper.range_builder(start_time, end_time)
70
+ Helper.timestamp_format timestamp
71
+ end
72
+ rule(chronic_time: simple(:chronic_time)) do
73
+ Helper.timestamp_format Chronic.parse(chronic_time)
33
74
  end
34
75
 
35
- rule(:negate => subtree(:not)) do
76
+ # Negate
77
+ rule(negate: subtree(:not)) do
36
78
  { not: negate }
37
79
  end
38
80
 
81
+ # Command joining
39
82
  rule(:or => {
40
83
  left: subtree(:left),
41
84
  right: subtree(:right)
data/plunk.gemspec CHANGED
@@ -1,10 +1,11 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "plunk"
3
- s.version = "0.3.2"
3
+ s.version = "0.3.3"
4
4
  s.add_runtime_dependency "json", "~> 1.8", ">= 1.8.0"
5
5
  s.add_runtime_dependency "parslet", "~> 1.5", ">= 1.5.0"
6
6
  s.add_runtime_dependency "elasticsearch", "~> 0.4", ">= 0.4.3"
7
7
  s.add_runtime_dependency "activesupport", "~> 4.0", ">= 4.0.0"
8
+ s.add_runtime_dependency "chronic", "~> 0.10", ">= 0.10.0"
8
9
  s.add_development_dependency "rspec", "~> 2.0", ">= 2.14.1"
9
10
  s.add_development_dependency "timecop", "~> 0.7", ">= 0.7.1"
10
11
  s.summary = "Elasticsearch query language"
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'the limit command' do
4
+ it 'should parse limit 1' do
5
+ result = Plunk.search 'limit 1'
6
+ expected = Plunk::Helper.filter_builder(
7
+ Plunk::Helper.limit_builder(1)
8
+ )
9
+ expect(result).to eq(expected)
10
+ end
11
+ end
data/spec/regexp_spec.rb CHANGED
@@ -5,7 +5,10 @@ describe 'regexp searches' do
5
5
  it 'should parse foo=/blah foo/' do
6
6
  result = Plunk.search 'foo=/blah foo/'
7
7
  expected = Plunk::Helper.filter_builder(
8
- Plunk::Helper.query_builder('foo:/blah foo/')
8
+ Plunk::Helper.regexp_builder(
9
+ 'foo',
10
+ 'blah foo'
11
+ )
9
12
  )
10
13
  expect(result).to eq(expected)
11
14
  end
@@ -13,7 +16,10 @@ describe 'regexp searches' do
13
16
  it 'should parse foo=/blah\/ foo/' do
14
17
  result = Plunk.search 'foo=/blah\/ foo/'
15
18
  expected = Plunk::Helper.filter_builder(
16
- Plunk::Helper.query_builder('foo:/blah\/ foo/')
19
+ Plunk::Helper.regexp_builder(
20
+ 'foo',
21
+ 'blah\/ foo'
22
+ )
17
23
  )
18
24
  expect(result).to eq(expected)
19
25
  end
@@ -21,15 +27,21 @@ describe 'regexp searches' do
21
27
  it 'should parse foo=/blah\. foo/' do
22
28
  result = Plunk.search 'foo=/blah\. foo/'
23
29
  expected = Plunk::Helper.filter_builder(
24
- Plunk::Helper.query_builder('foo:/blah\. foo/')
30
+ Plunk::Helper.regexp_builder(
31
+ 'foo',
32
+ 'blah\. foo'
33
+ )
25
34
  )
26
35
  expect(result).to eq(expected)
27
36
  end
28
37
 
29
- it 'should parse /.*User\-Agent\: Microsoft\-WebDAV.*/' do
30
- result = Plunk.search '/.*User\-Agent\: Microsoft\-WebDAV.*/'
38
+ it 'should parse http.headers=/.*User\-Agent\: Microsoft\-WebDAV.*/' do
39
+ result = Plunk.search 'http.headers=/.*User\-Agent\: Microsoft\-WebDAV.*/'
31
40
  expected = Plunk::Helper.filter_builder(
32
- Plunk::Helper.query_builder('/.*User\-Agent\: Microsoft\-WebDAV.*/')
41
+ Plunk::Helper.regexp_builder(
42
+ 'http.headers',
43
+ '.*User\-Agent\: Microsoft\-WebDAV.*'
44
+ )
33
45
  )
34
46
  expect(result).to eq(expected)
35
47
  end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+ require 'shared/time_stubs'
3
+ require 'shared/plunk_stubs'
4
+ require 'chronic'
5
+
6
+ describe 'the window command' do
7
+ include_context 'time stubs'
8
+ include_context 'plunk stubs'
9
+
10
+ it 'should parse window 3/11/13 to 3/12/13' do
11
+ result = Plunk.search 'window 3/11/13 to 3/12/13'
12
+ expected = Plunk::Helper.filter_builder(
13
+ Plunk::Helper.range_builder(
14
+ Plunk::Helper.timestamp_format(Chronic.parse('3/11/13')),
15
+ Plunk::Helper.timestamp_format(Chronic.parse('3/12/13'))
16
+ )
17
+ )
18
+ expect(result).to eq(expected)
19
+ end
20
+
21
+ it 'should parse window "last monday" to "last thursday"' do
22
+ result = Plunk.search 'window "last monday" to "last thursday"'
23
+ expected = Plunk::Helper.filter_builder(
24
+ Plunk::Helper.range_builder(
25
+ Plunk::Helper.timestamp_format(Chronic.parse('last monday')),
26
+ Plunk::Helper.timestamp_format(Chronic.parse('last thursday'))
27
+ )
28
+ )
29
+ expect(result).to eq(expected)
30
+ end
31
+
32
+ it 'should parse window -60m to -30m' do
33
+ result = Plunk.search 'window -60m to -30m'
34
+ expected = Plunk::Helper.filter_builder(
35
+ Plunk::Helper.range_builder(
36
+ Plunk::Helper.timestamp_format(@time - 60.minutes),
37
+ Plunk::Helper.timestamp_format(@time - 30.minutes)
38
+ )
39
+ )
40
+ expect(result).to eq(expected)
41
+ end
42
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plunk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ram Mehta
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2014-03-20 00:00:00.000000000 Z
13
+ date: 2014-03-21 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: json
@@ -92,6 +92,26 @@ dependencies:
92
92
  - - '>='
93
93
  - !ruby/object:Gem::Version
94
94
  version: 4.0.0
95
+ - !ruby/object:Gem::Dependency
96
+ name: chronic
97
+ requirement: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: '0.10'
102
+ - - '>='
103
+ - !ruby/object:Gem::Version
104
+ version: 0.10.0
105
+ type: :runtime
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ~>
110
+ - !ruby/object:Gem::Version
111
+ version: '0.10'
112
+ - - '>='
113
+ - !ruby/object:Gem::Version
114
+ version: 0.10.0
95
115
  - !ruby/object:Gem::Dependency
96
116
  name: rspec
97
117
  requirement: !ruby/object:Gem::Requirement
@@ -161,12 +181,14 @@ files:
161
181
  - spec/chained_search_spec.rb
162
182
  - spec/field_value_spec.rb
163
183
  - spec/last_spec.rb
184
+ - spec/limit_spec.rb
164
185
  - spec/nested_search_spec.rb
165
186
  - spec/regexp_spec.rb
166
187
  - spec/shared/dummy_client.rb
167
188
  - spec/shared/plunk_stubs.rb
168
189
  - spec/shared/time_stubs.rb
169
190
  - spec/spec_helper.rb
191
+ - spec/window_spec.rb
170
192
  homepage: https://github.com/elbii/plunk
171
193
  licenses:
172
194
  - MIT