influxparser 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 79b8236a2441da883a688e7d4284a1c191358db2
4
+ data.tar.gz: b2d6bc730f78faad33e35429c5e68e9c19c292ed
5
+ SHA512:
6
+ metadata.gz: db897eaf4b325a0ed1c0bffde1162ed1e23d6ff1800c6762b90749117af45a782e33795cd117a9a44e74754af7725980c5d8fc384537216178c0c74bfb061596
7
+ data.tar.gz: 83a972e25f5e5259230c2bb9cb6e3a46b3faadb46694d4a11e76a25deb3b5cb3eff27e06ef0be1f3da0664653f96950bbc6c5b43b338cbacaf7cbd3fa9eda982
@@ -0,0 +1,93 @@
1
+ # TODO: the tags hash should be absent when there are no tags
2
+ # TODO: time key shouldn't exist if there is no time
3
+ # TODO: deal with improper line protocol
4
+ class InfluxParser
5
+ class << self
6
+ def parse_point(s, options = {})
7
+ default_options = {:parse_types => true, :time_format => nil}
8
+ options = default_options.merge(options)
9
+
10
+ point = {}
11
+ point['_raw'] = s
12
+ s = s.strip # trim whitespace
13
+
14
+ measurement_end = s.index /(?<!\\) /
15
+
16
+
17
+ mparts = s[0..measurement_end-1].split(/(?<!\\),/) # split on unescaped commas for the measurement name and tags
18
+ point['series'] = unescape_measurement mparts[0]
19
+
20
+ # if any tags were attached to the measurement iterate over them now
21
+ point['tags'] = {}
22
+ mparts.drop(1).each do |t|
23
+ tag = t.split(/(?<!\\)=/)
24
+ point['tags'][unescape_tag tag[0]] = unescape_tag tag[1]
25
+ end
26
+
27
+ # now iterate over the values
28
+ last_value_raw = ''
29
+ last_key = ''
30
+ point['values'] = {}
31
+ vparts = s[measurement_end+1..-1].split(/(?<!\\),/)
32
+ # puts "vparts:#{vparts}"
33
+ vparts.each do |v|
34
+ value = v.split(/(?<!\\)=/)
35
+ last_value_raw = value[1]
36
+ last_key = unescape_tag value[0]
37
+ # puts "last k/v #{last_key}==#{last_value_raw}"
38
+ point['values'][last_key] = unescape_point(value[1],options)
39
+ end
40
+ # puts "-----\n#{point['values'].to_yaml}\n"
41
+
42
+ # check for a timestamp in the last value
43
+ # TODO: I hate this, but it's late and I just want to move past it for now
44
+ # TODO: this level of nesting would fill rubocop with rage
45
+ has_space = last_value_raw.rindex(/ /)
46
+ if has_space
47
+ time_stamp = last_value_raw[has_space+1..-1] # take everything from the space to the end
48
+ if time_stamp.index(/"/)
49
+ point['timestamp'] = nil
50
+ else
51
+ # it was a timestamp, strip it from the last value and set the timestamp
52
+ point['values'][last_key] = unescape_point(last_value_raw[0..has_space-1],options)
53
+ point['timestamp'] = time_stamp
54
+ if options[:time_format]
55
+ n_time = time_stamp.to_f / 1000000000
56
+ t = Time.at(n_time).utc
57
+ point['timestamp'] = t.strftime(options[:time_format])
58
+ end
59
+ end
60
+ else
61
+ point['timestamp'] = nil
62
+ end
63
+
64
+ point
65
+ end
66
+ def unescape_measurement(s)
67
+ s.gsub(/\\ /,' ').gsub(/\\,/,',')
68
+ end
69
+ def unescape_tag(s)
70
+ t = unescape_measurement s
71
+ t.gsub(/\\=/,'=')
72
+ end
73
+ def unescape_point(s,options)
74
+ # puts "unescape:#{s}"
75
+ # s = s.gsub(/\\\\/,'\\').gsub(/\\"/,'""') # handle escaped characters if present
76
+
77
+ # it is a string, return it
78
+ return s[1..-2].gsub(/\\\\/,'\\').gsub(/\\"/,'""') if s[0,1] == '"'
79
+
80
+ return s.sub(/(?<=\d)i/,'') if (!options[:parse_types]) # the customer doesn't care about types so just return it, but strip the trailing i from an integer because we care
81
+
82
+ # handle the booleans
83
+ return true if ['t','T','true','True','TRUE'].include?(s)
84
+ return false if ['f','F','false','False','FALSE'].include?(s)
85
+
86
+ # by here we have either an unquoted string or some numeric
87
+
88
+ return s.to_f if s =~ /^(\d|\.)+$/ # floats are only digits and dots
89
+ return s.chomp('i').to_i if s[0..-2] =~ /^(\d)+$/ # trailing i is an integer remove it
90
+ return s.gsub(/\\\\/,'\\').gsub(/\\"/,'"')
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,293 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
4
+ require 'test/unit'
5
+ require 'influxparser'
6
+ class TestParsePointFromDocs < Test::Unit::TestCase # def setup
7
+ # end
8
+
9
+
10
+ # def teardown
11
+ # end
12
+
13
+ def test_two_tags
14
+ point = InfluxParser.parse_point('weather,location=us-midwest,season=summer temperature=82 1465839830100400200')
15
+ assert_not_equal(false,point) # a straight up parse error will false
16
+
17
+ # measurement
18
+ assert_equal('weather',point['series'])
19
+
20
+ # tags
21
+ assert_equal(2,point['tags'].length)
22
+
23
+ # check location
24
+ assert_equal(true,point['tags'].key?('location'))
25
+ assert_equal('us-midwest',point['tags']['location'])
26
+
27
+ # check season
28
+ assert_equal(true,point['tags'].key?('season'))
29
+ assert_equal('summer',point['tags']['season'])
30
+
31
+ # value
32
+ assert_equal(true,point['values'].key?('temperature'))
33
+ assert_equal(82,point['values']['temperature'])
34
+
35
+ # time
36
+ assert_equal('1465839830100400200',point['timestamp'])
37
+
38
+ end
39
+
40
+ def test_no_tags
41
+ point = InfluxParser.parse_point('weather temperature=82 1465839830100400200')
42
+ assert_not_equal(false,point) # a straight up parse error will false
43
+
44
+ # measurement
45
+ assert_equal('weather',point['series'])
46
+
47
+ # no tags
48
+ assert_equal(true,point.key?('tags'))
49
+ assert_equal(0,point['tags'].length)
50
+
51
+ # value
52
+ assert_equal(true,point['values'].key?('temperature'))
53
+ assert_equal(82,point['values']['temperature'])
54
+
55
+ # time
56
+ assert_equal('1465839830100400200',point['timestamp'])
57
+
58
+ end
59
+
60
+ def test_two_values
61
+ point = InfluxParser.parse_point('weather,location=us-midwest temperature=82,humidity=71 1465839830100400200')
62
+ assert_not_equal(false,point) # a straight up parse error will false
63
+
64
+ # measurement
65
+ assert_equal('weather',point['series'])
66
+
67
+ # check location
68
+ assert_equal(true,point['tags'].key?('location'))
69
+ assert_equal('us-midwest',point['tags']['location'])
70
+
71
+ # values
72
+ assert_equal(2,point['values'].length)
73
+
74
+ assert_equal(true,point['values'].key?('temperature'))
75
+ assert_equal(82,point['values']['temperature'])
76
+
77
+ assert_equal(true,point['values'].key?('humidity'))
78
+ assert_equal(71,point['values']['humidity'])
79
+
80
+ # time
81
+ assert_equal('1465839830100400200',point['timestamp'])
82
+
83
+ end
84
+ def test_timestamp
85
+ # no timestamp
86
+ point = InfluxParser.parse_point('weather,location=us-midwest temperature=82')
87
+ assert_not_equal(false,point) # a straight up parse error will false
88
+ assert_nil(point['timestamp'])
89
+
90
+ # unformatted time
91
+ point = InfluxParser.parse_point('weather,location=us-midwest temperature=82 1465839830100400200')
92
+ assert_not_equal(false,point) # a straight up parse error will false
93
+ assert_equal('1465839830100400200',point['timestamp'])
94
+
95
+ # time
96
+ point = InfluxParser.parse_point('weather,location=us-midwest temperature=82 1465839830100400200',{:time_format => "%Y-%d-%mT%H:%M:%S.%NZ"})
97
+ assert_not_equal(false,point) # a straight up parse error will false
98
+ assert_equal('2016-13-06T17:43:50.100400209Z',point['timestamp'])
99
+
100
+ end
101
+
102
+ def test_float
103
+ point = InfluxParser.parse_point('weather,location=us-midwest temperature=82 1465839830100400200')
104
+ assert_not_equal(false,point) # a straight up parse error will false
105
+ assert_equal(82.0,point['values']['temperature'])
106
+ end
107
+ def test_integer
108
+ point = InfluxParser.parse_point('weather,location=us-midwest temperature=82i 1465839830100400200')
109
+ assert_not_equal(false,point) # a straight up parse error will false
110
+ assert_equal(82,point['values']['temperature'])
111
+ end
112
+
113
+ def test_string
114
+ point = InfluxParser.parse_point('weather,location=us-midwest temperature="too warm" 1465839830100400200')
115
+ assert_not_equal(false,point) # a straight up parse error will false
116
+ assert_equal('too warm',point['values']['temperature'])
117
+ end
118
+
119
+ def test_invalid_time
120
+ # TODO: this should be throwing a parse error
121
+ point = InfluxParser.parse_point('weather,location=us-midwest temperature=82 "1465839830100400200"')
122
+ assert_not_equal(false,point) # a straight up parse error will false
123
+
124
+ end
125
+
126
+ def test_invalid_string
127
+ # TODO: this should be throwing a parse error
128
+ point = InfluxParser.parse_point("weather,location=us-midwest temperature='too warm' 1465839830100400200")
129
+ assert_not_equal(false,point) # a straight up parse error will false
130
+ end
131
+
132
+ def test_boolean
133
+ point = InfluxParser.parse_point("weather,location=us-midwest temperature=t 1465839830100400200")
134
+ assert_not_equal(false,point) # a straight up parse error will false
135
+ assert_equal(true,point['values']['temperature'])
136
+
137
+ point = InfluxParser.parse_point("weather,location=us-midwest temperature=T 1465839830100400200")
138
+ assert_not_equal(false,point) # a straight up parse error will false
139
+ assert_equal(true,point['values']['temperature'])
140
+
141
+ point = InfluxParser.parse_point("weather,location=us-midwest temperature=true 1465839830100400200")
142
+ assert_not_equal(false,point) # a straight up parse error will false
143
+ assert_equal(true,point['values']['temperature'])
144
+
145
+ point = InfluxParser.parse_point("weather,location=us-midwest temperature=True 1465839830100400200")
146
+ assert_not_equal(false,point) # a straight up parse error will false
147
+ assert_equal(true,point['values']['temperature'])
148
+
149
+ point = InfluxParser.parse_point("weather,location=us-midwest temperature=TRUE 1465839830100400200")
150
+ assert_not_equal(false,point) # a straight up parse error will false
151
+ assert_equal(true,point['values']['temperature'])
152
+
153
+ point = InfluxParser.parse_point("weather,location=us-midwest temperature=f 1465839830100400200")
154
+ assert_not_equal(false,point) # a straight up parse error will false
155
+ assert_equal(false,point['values']['temperature'])
156
+
157
+ point = InfluxParser.parse_point("weather,location=us-midwest temperature=F 1465839830100400200")
158
+ assert_not_equal(false,point) # a straight up parse error will false
159
+ assert_equal(false,point['values']['temperature'])
160
+
161
+ point = InfluxParser.parse_point("weather,location=us-midwest temperature=false 1465839830100400200")
162
+ assert_not_equal(false,point) # a straight up parse error will false
163
+ assert_equal(false,point['values']['temperature'])
164
+
165
+ point = InfluxParser.parse_point("weather,location=us-midwest temperature=False 1465839830100400200")
166
+ assert_not_equal(false,point) # a straight up parse error will false
167
+ assert_equal(false,point['values']['temperature'])
168
+
169
+ point = InfluxParser.parse_point("weather,location=us-midwest temperature=FALSE 1465839830100400200")
170
+ assert_not_equal(false,point) # a straight up parse error will false
171
+ assert_equal(false,point['values']['temperature'])
172
+
173
+ end
174
+ def test_ridiculous_quotes
175
+ # from the influx docs: Do not double or single quote measurement names, tag keys, tag values, and field keys. It is valid line protocol but InfluxDB assumes that the quotes are part of the name.
176
+ point = InfluxParser.parse_point('"weather","location"="us-midwest" "temperature"=82 1465839830100400200')
177
+
178
+ assert_not_equal(false,point) # a straight up parse error will false
179
+ # measurement
180
+ assert_equal('"weather"',point['series'])
181
+
182
+ # check tag
183
+ assert_equal(true,point['tags'].key?('"location"'))
184
+ assert_equal('"us-midwest"',point['tags']['"location"'])
185
+
186
+ # check values
187
+
188
+ assert_equal(true,point['values'].key?('"temperature"'))
189
+ assert_equal(82,point['values']['"temperature"'])
190
+
191
+ # time
192
+ assert_equal('1465839830100400200',point['timestamp'])
193
+
194
+
195
+
196
+ # Do the same thing for ridiculous single quotes
197
+ point = InfluxParser.parse_point("'weather','location'='us-midwest' 'temperature'=82 1465839830100400200")
198
+
199
+ assert_not_equal(false,point) # a straight up parse error will false
200
+ # measurement
201
+ assert_equal("'weather'",point['series'])
202
+
203
+ # check tag
204
+ assert_equal(true,point['tags'].key?("'location'"))
205
+ assert_equal("'us-midwest'",point['tags']["'location'"])
206
+
207
+ # check values
208
+
209
+ assert_equal(true,point['values'].key?("'temperature'"))
210
+ assert_equal(82,point['values']["'temperature'"])
211
+
212
+ # time
213
+ assert_equal('1465839830100400200',point['timestamp'])
214
+
215
+ end
216
+
217
+ def test_escaping
218
+ point = InfluxParser.parse_point('weather,location=us\,midwest temperature=82 1465839830100400200')
219
+ assert_not_equal(false,point) # a straight up parse error will false
220
+ assert_equal('us,midwest',point['tags']['location'])
221
+
222
+ point = InfluxParser.parse_point('weather,location=us-midwest temp\=rature=82 1465839830100400200')
223
+ assert_not_equal(false,point) # a straight up parse error will false
224
+ assert_equal(82,point['values']['temp=rature'])
225
+
226
+ point = InfluxParser.parse_point('weather,location\ place=us-midwest temperature=82 1465839830100400200')
227
+ assert_not_equal(false,point) # a straight up parse error will false
228
+ assert_equal(true,point['tags'].key?('location place'))
229
+
230
+ point = InfluxParser.parse_point('wea\,ther,location=us-midwest temperature=82 1465839830100400200')
231
+ assert_not_equal(false,point) # a straight up parse error will false
232
+ assert_equal('wea,ther',point['series'])
233
+
234
+ point = InfluxParser.parse_point('wea\ ther,location=us-midwest temperature=82 1465839830100400200')
235
+ assert_not_equal(false,point) # a straight up parse error will false
236
+ assert_equal('wea ther',point['series'])
237
+
238
+ point = InfluxParser.parse_point('weather temperature=toohot\"')
239
+ assert_not_equal(false,point) # a straight up parse error will false
240
+ assert_equal('toohot"',point['values']['temperature'])
241
+
242
+
243
+ # so many slashes -- note they're extra terrible because I need to escape ruby slashes in the test strings
244
+
245
+ # forward slash
246
+ point = InfluxParser.parse_point('weather,location=us-midwest temperature_str="too hot/cold" 1465839830100400201')
247
+ assert_not_equal(false,point) # a straight up parse error will false
248
+ assert_equal('too hot/cold',point['values']['temperature_str'])
249
+
250
+ # one slash
251
+ point = InfluxParser.parse_point('weather,location=us-midwest temperature_str="too hot\cold" 1465839830100400201')
252
+ assert_not_equal(false,point) # a straight up parse error will false
253
+ assert_equal('too hot\cold',point['values']['temperature_str'])
254
+
255
+ # two slashes
256
+ point = InfluxParser.parse_point('weather,location=us-midwest temperature_str="too hot\\\cold" 1465839830100400201')
257
+ assert_not_equal(false,point) # a straight up parse error will false
258
+ assert_equal('too hot\cold',point['values']['temperature_str'])
259
+
260
+ # three slashes
261
+ point = InfluxParser.parse_point('weather,location=us-midwest temperature_str="too hot\\\\\cold" 1465839830100400201')
262
+ assert_not_equal(false,point) # a straight up parse error will false
263
+ assert_equal("too hot\\\\cold",point['values']['temperature_str'])
264
+
265
+ # four slashes
266
+ point = InfluxParser.parse_point('weather,location=us-midwest temperature_str="too hot\\\\\\\cold" 1465839830100400201')
267
+ assert_not_equal(false,point) # a straight up parse error will false
268
+ assert_equal("too hot\\\\cold",point['values']['temperature_str'])
269
+
270
+ # five slashes
271
+ point = InfluxParser.parse_point('weather,location=us-midwest temperature_str="too hot\\\\\\\\\cold" 1465839830100400201')
272
+ assert_not_equal(false,point) # a straight up parse error will false
273
+ assert_equal("too hot\\\\\\cold",point['values']['temperature_str'])
274
+
275
+ end
276
+ def test_types
277
+ point = InfluxParser.parse_point('weather float=82,integer=82i,impliedstring=helloworld,explicitstring="hello world" 1465839830100400200')
278
+ assert_not_equal(false,point) # a straight up parse error will false
279
+ assert_equal(82.0,point['values']['float'])
280
+ assert_equal(82,point['values']['integer'])
281
+ assert_equal('helloworld',point['values']['impliedstring'])
282
+ assert_equal('hello world',point['values']['explicitstring'])
283
+
284
+ # do it again but this time without type parsing
285
+ point = InfluxParser.parse_point('weather float=82,integer=82i,impliedstring=helloworld,explicitstring="hello world" 1465839830100400200',{:parse_types => false})
286
+ assert_not_equal(false,point) # a straight up parse error will false
287
+ assert_equal('82',point['values']['float'])
288
+ assert_equal('82',point['values']['integer'])
289
+ assert_equal('helloworld',point['values']['impliedstring'])
290
+ assert_equal('hello world',point['values']['explicitstring'])
291
+
292
+ end
293
+ end
metadata ADDED
@@ -0,0 +1,45 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: influxparser
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.5
5
+ platform: ruby
6
+ authors:
7
+ - Robert Labrie
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-02-03 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Unofficial influx line protocol parser
14
+ email: robert.labrie@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/influxparser.rb
20
+ - test/test_parse_point_from_docs.rb
21
+ homepage:
22
+ licenses: []
23
+ metadata: {}
24
+ post_install_message:
25
+ rdoc_options: []
26
+ require_paths:
27
+ - lib
28
+ required_ruby_version: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ required_rubygems_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ requirements: []
39
+ rubyforge_project:
40
+ rubygems_version: 2.5.2.1
41
+ signing_key:
42
+ specification_version: 3
43
+ summary: InfluxDB line protocol parser
44
+ test_files:
45
+ - test/test_parse_point_from_docs.rb