MrMurano 1.3.1 → 1.3.2

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: 9ba16e5f2344bf987358a56dada5f3e3b8c4cc88
4
- data.tar.gz: 6528a533043deae3b1e81493e52fee94772506c1
3
+ metadata.gz: 02d6e091a69dd09c071539a97a2b9d3755ad9202
4
+ data.tar.gz: 46f2c3f6836c39648de3c08beef0eaf7c4ceb54b
5
5
  SHA512:
6
- metadata.gz: c66eccf67ca91de551b15493faa4059c648eb83f60c6b6fc39839a6938ab91a35795e3291c3630df1ac660cb0fcb0ff82b96afcea8353b84ef5a0d1855f1cfbe
7
- data.tar.gz: 29c38a617d405be2c6d732e19369c4f46147f98ffa82bc86394ffaabc34693d7696fcd38df139506732829948f9f26c3090650adf381b1fb49f57ccb606fbe34
6
+ metadata.gz: fa6b24a0ce013cb732a5563efbe86c54bbeb2cb9264cf69cc0dd1a7a43528f3fb22ef005af188ce7400ac2a7be40a9f716d850b395a24076edd550d8faf47d5c
7
+ data.tar.gz: 2633547b8be35f46ee9239b3092f8d6ebb7242fea6e8c62c53bdf9df93c461b57c22945a8fbabc556d054d64d85208052b8545d8a59bae85c464532e2b9ab389
data/TODO.taskpaper CHANGED
@@ -2,12 +2,13 @@ Readme:
2
2
  - Add more walk-thrus of common actions. (create new solution, …)
3
3
 
4
4
  Commands:
5
- - Status will show things have changes when they don’t.
5
+ - Status will show {modules,eventhandlers} have changes when they don’t.
6
6
  - First time run needs to be smoothed out. @done(2016-08-09)
7
7
  - Default for most commands should be -same @done(2016-08-02)
8
8
  - Add Diff Command @done(2016-07-27)
9
9
 
10
10
  Account:
11
+ - token is refetched many times. Do this once per run. @done(2016-09-12)
11
12
  - Netrc library (or the netrc format) doesn't allow '#' in passwords. @done(2016-08-10)
12
13
 
13
14
  Endpoints:
@@ -31,9 +32,15 @@ CORS:
31
32
  - Get working with sync up/down.
32
33
  - GET&PUT /cors data @done(2016-09-08)
33
34
 
35
+ Timeseries:
36
+ - Add CSV output option. @done(2016-09-09)
37
+
34
38
  Product:
35
39
  - Need to add way to set the product ID on a device eventhandler. @done(2016-08-01)
36
40
 
41
+ Service Device:
42
+ - When listing and bussiness.id is missing, gracefully fall back to --idonly @done(2016-09-12)
43
+
37
44
  Config:
38
45
  - Think about adding dev,staging,prod system; how would that work?
39
46
  Would it work ok to just use the --configfile option?
@@ -43,6 +50,7 @@ Config:
43
50
  - Maybe add dotenv support.
44
51
 
45
52
  SolutionBase:
53
+ - Errors from the server should be displayed prettier.
46
54
  - All network traffic is serialized. Make some parallel.
47
55
  - JSON parse should use symbols for keys. @done(2016-09-01)
48
56
  - Add the --curl verbose option. @done(2016-08-12)
@@ -35,10 +35,10 @@ module MrMurano
35
35
  end
36
36
  hd = @data[host]
37
37
  if hd.nil? or not hd.kind_of?(Hash) then
38
- hd = {user=>pass}
38
+ @data[host] = {user=>pass}
39
39
  return
40
40
  end
41
- hd[user] = pass
41
+ @data[host][user] = pass
42
42
  return
43
43
  end
44
44
  def get(host, user)
@@ -51,14 +51,7 @@ module MrMurano
51
51
  end
52
52
 
53
53
  class Account
54
- def initialize
55
- @json_opts = {
56
- :allow_nan => true,
57
- :symbolize_names => true,
58
- :create_additions => false
59
- }
60
- end
61
-
54
+ include Http
62
55
 
63
56
  def endPoint(path)
64
57
  URI('https://' + $cfg['net.host'] + '/api:1/' + path.to_s)
@@ -86,84 +79,46 @@ module MrMurano
86
79
  }
87
80
  end
88
81
 
82
+ # Store the token in a class variable so that we only fetch it once per run
83
+ # session of this tool
84
+ @@token = nil
89
85
  def token
90
- if @token.nil? then
91
- r = endPoint('token/')
92
- Net::HTTP.start(r.host, r.port, :use_ssl=>true) do |http|
93
- request = Net::HTTP::Post.new(r)
94
- request.content_type = 'application/json'
95
- #request.basic_auth(username(), password())
96
- request.body = JSON.generate(_loginInfo)
97
-
98
- response = http.request(request)
99
- case response
100
- when Net::HTTPSuccess
101
- token = JSON.parse(response.body, @json_opts)
102
- @token = token[:token]
103
- else
104
- say_error "No token! because: #{response}"
105
- @token = nil
106
- raise response
107
- end
108
- end
109
- end
110
- @token
111
- end
86
+ if @@token.nil? then
87
+ # Cannot have token call token, so cannot use workit.
88
+ uri = endPoint('token/')
89
+ request = Net::HTTP::Post.new(uri)
90
+ curldebug(request)
112
91
 
113
- def businesses
114
- r = endPoint('user/' + $cfg['user.name'] + '/membership/')
115
- Net::HTTP.start(r.host, r.port, :use_ssl=>true) do |http|
116
- request = Net::HTTP::Get.new(r)
117
92
  request.content_type = 'application/json'
118
- request['authorization'] = 'token ' + token
93
+ #request.basic_auth(username(), password())
94
+ request.body = JSON.generate(_loginInfo)
119
95
 
120
96
  response = http.request(request)
121
97
  case response
122
98
  when Net::HTTPSuccess
123
- busy = JSON.parse(response.body, @json_opts)
124
- return busy
99
+ token = JSON.parse(response.body, json_opts)
100
+ @@token = token[:token]
125
101
  else
102
+ say_error "No token! because: #{response}"
103
+ @@token = nil
126
104
  raise response
127
105
  end
128
106
  end
107
+ @@token
108
+ end
109
+
110
+ def businesses
111
+ get('user/' + $cfg['user.name'] + '/membership/')
129
112
  end
130
113
 
131
114
  def products
132
115
  raise "Missing Bussiness ID" if $cfg['business.id'].nil?
133
- r = endPoint('business/' + $cfg['business.id'] + '/product/')
134
- Net::HTTP.start(r.host, r.port, :use_ssl=>true) do |http|
135
- request = Net::HTTP::Get.new(r)
136
- request.content_type = 'application/json'
137
- request['authorization'] = 'token ' + token
138
-
139
- response = http.request(request)
140
- case response
141
- when Net::HTTPSuccess
142
- busy = JSON.parse(response.body, @json_opts)
143
- return busy
144
- else
145
- raise response
146
- end
147
- end
116
+ get('business/' + $cfg['business.id'] + '/product/')
148
117
  end
149
118
 
150
119
  def solutions
151
120
  raise "Missing Bussiness ID" if $cfg['business.id'].nil?
152
- r = endPoint('business/' + $cfg['business.id'] + '/solution/')
153
- Net::HTTP.start(r.host, r.port, :use_ssl=>true) do |http|
154
- request = Net::HTTP::Get.new(r)
155
- request.content_type = 'application/json'
156
- request['authorization'] = 'token ' + token
157
-
158
- response = http.request(request)
159
- case response
160
- when Net::HTTPSuccess
161
- busy = JSON.parse(response.body, @json_opts)
162
- return busy
163
- else
164
- raise response
165
- end
166
- end
121
+ get('business/' + $cfg['business.id'] + '/solution/')
167
122
  end
168
123
 
169
124
  end
@@ -20,7 +20,7 @@ module MrMurano
20
20
 
21
21
  def fetch(id)
22
22
  ret = get('/' + id.to_s)
23
- aheader = ret[:script].lines.first.chomp
23
+ aheader = (ret[:script].lines.first or "").chomp
24
24
  dheader = /^--#ENDPOINT (?i:#{ret[:method]}) #{ret[:path]}$/
25
25
  rheader = %{--#ENDPOINT #{ret[:method]} #{ret[:path]}\n}
26
26
  if block_given? then
@@ -86,7 +86,11 @@ module MrMurano
86
86
  path = Pathname.new(path) unless path.kind_of? Pathname
87
87
  aheader = path.readlines().first
88
88
  md = /--#ENDPOINT (\S+) (.*)/.match(aheader)
89
- raise "Not an Endpoint: #{path.to_s}" if md.nil?
89
+ if md.nil? then
90
+ rp = path.relative_path_from(Pathname.new(Dir.pwd))
91
+ say_warning "Not an Endpoint: #{rp.to_s}"
92
+ return nil
93
+ end
90
94
  {:method=>md[1], :path=>md[2]}
91
95
  end
92
96
 
@@ -125,12 +125,13 @@ module MrMurano
125
125
  #sha1 = Digest::SHA1.file(path.to_s).hexdigest
126
126
  def hexit(str)
127
127
  ret=''
128
+ # TODO: find a faster way to do this.
128
129
  str.each_byte{|b| ret << "%02x" % b}
129
130
  ret
130
131
  end
131
132
  sha1 = Digest::SHA1.new
132
133
  path.open('rb:ASCII-8BIT') do |io|
133
- while chunk = io.read(4096) do
134
+ while chunk = io.read(1048576) do
134
135
  sha1 << hexit(chunk)
135
136
  end
136
137
  end
@@ -25,23 +25,23 @@ module MrMurano
25
25
  return @scid unless @scid.nil?
26
26
  @scid = scid_for_name(@serviceName)
27
27
  end
28
+ end
28
29
 
29
- # Below is Device ServiceConfig Specific. Should it be in its own class?
30
+ class SC_Device < ServiceConfig
31
+ def initialize
32
+ super
33
+ @serviceName = 'device'
34
+ end
30
35
 
31
36
  def assignTriggers(products)
32
- scid = scid_for_name('device')
33
-
34
37
  details = fetch(scid)
35
38
  products = [products] unless products.kind_of? Array
36
- details[:triggers] = {:pid=>products}
39
+ details[:triggers] = {:pid=>products, :vendor=>products}
37
40
 
38
41
  put('/'+scid, details)
39
-
40
42
  end
41
43
 
42
44
  def showTriggers
43
- scid = scid_for_name('device')
44
-
45
45
  details = fetch(scid)
46
46
 
47
47
  return [] if details[:triggers].nil?
@@ -51,46 +51,55 @@ module MrMurano
51
51
  end
52
52
  end
53
53
 
54
- command :assign do |c|
55
- c.syntax = 'mr assign [product]'
56
- c.description = 'Assign a product to a eventhandler'
57
-
58
- c.option '--list', %{List assigned products}
54
+ command 'assign list' do |c|
55
+ c.syntax = 'mr assign list [options]'
56
+ c.description = 'List the products that are assigned'
59
57
  c.option '--idonly', 'Only return the ids'
60
-
61
58
  c.action do |args, options|
62
- sol = MrMurano::ServiceConfig.new
59
+ sol = MrMurano::SC_Device.new
63
60
 
64
- if options.list then
65
- trigs = sol.showTriggers()
66
- if options.idonly then
61
+ trigs = sol.showTriggers()
62
+ if options.idonly or $cfg['business.id'].nil? then
63
+ say trigs.join(' ')
64
+ else
65
+ acc = MrMurano::Account.new
66
+ products = acc.products
67
+ products.select!{|p| trigs.include? p[:modelId] }
68
+ if products.empty? then
67
69
  say trigs.join(' ')
68
70
  else
69
- acc = MrMurano::Account.new
70
- products = acc.products
71
- products.select!{|p| trigs.include? p[:pid] }
72
71
  busy = products.map{|r| [r[:label], r[:type], r[:pid], r[:modelId]]}
73
72
  table = Terminal::Table.new :rows => busy, :headings => ['Label', 'Type', 'PID', 'ModelID']
74
73
  say table
75
74
  end
76
-
77
- else
78
- prname = args.shift
79
- if prname.nil? then
80
- prid = $cfg['product.id']
81
- else
82
- acc = MrMurano::Account.new
83
- products = acc.products
84
- products.select!{|p| p[:label] == prname or p[:pid] == prname }
85
- prid = products.map{|p| p[:pid]}
86
- end
87
- raise "No product ID!" if prid.nil?
88
- say "Assigning #{prid} to solution" if $cfg['tool.verbose']
89
- sol.assignTriggers(prid) unless $cfg['tool.dry']
90
75
  end
76
+ end
77
+ end
78
+ alias_command :assign, 'assign list'
91
79
 
80
+ command 'assign set' do |c|
81
+ c.syntax = 'mr assign set [product]'
82
+ c.description = 'Assign a product to a eventhandler'
92
83
 
84
+ c.action do |args, options|
85
+ sol = MrMurano::SC_Device.new
86
+
87
+ prname = args.shift
88
+ if prname.nil? then
89
+ prid = $cfg['product.id']
90
+ else
91
+ acc = MrMurano::Account.new
92
+ products = acc.products
93
+ products.select!{|p|
94
+ p[:label] == prname or p[:modelId] == prname or p[:pid] == prname
95
+ }
96
+ prid = products.map{|p| p[:modelId]}
97
+ end
98
+ raise "No product ID!" if prid.nil?
99
+ say "Assigning #{prid} to solution" if $cfg['tool.verbose']
100
+ sol.assignTriggers(prid) unless $cfg['tool.dry']
93
101
  end
102
+
94
103
  end
95
104
 
96
105
  # vim: set ai et sw=2 ts=2 :
@@ -153,7 +153,11 @@ module MrMurano
153
153
  path = Pathname.new(path) unless path.kind_of? Pathname
154
154
  aheader = path.readlines().first
155
155
  md = /--#EVENT (\S+) (\S+)/.match(aheader)
156
- raise "Not an Event handler: #{path.to_s}" if md.nil?
156
+ if md.nil? then
157
+ rp = path.relative_path_from(Pathname.new(Dir.pwd))
158
+ say_warning "Not an Event handler: #{rp}"
159
+ return nil
160
+ end
157
161
  {:service=>md[1], :event=>md[2]}
158
162
  end
159
163
 
@@ -9,110 +9,27 @@ module MrMurano
9
9
  class SolutionBase
10
10
  # This might also be a valid ProductBase.
11
11
  def initialize
12
- @token = Account.new.token
13
- raise "Not logged in!" if @token.nil?
14
12
  @sid = $cfg['solution.id']
15
13
  raise "No solution!" if @sid.nil?
16
14
  @uriparts = [:solution, @sid]
17
15
  @itemkey = :id
18
16
  @locationbase = $cfg['location.base']
19
17
  @location = nil
20
- @json_opts = {
21
- :allow_nan => true,
22
- :symbolize_names => true,
23
- :create_additions => false
24
- }
25
18
  end
26
19
 
20
+ include Http
21
+
27
22
  def verbose(msg)
28
23
  if $cfg['tool.verbose'] then
29
24
  say msg
30
25
  end
31
26
  end
32
27
 
33
- def curldebug(request)
34
- if $cfg['tool.curldebug'] then
35
- a = []
36
- a << %{curl -s -H 'Authorization: #{request['authorization']}'}
37
- a << %{-H 'User-Agent: #{request['User-Agent']}'}
38
- a << %{-H 'Content-Type: #{request.content_type}'}
39
- a << %{-X #{request.method}}
40
- a << %{'#{request.uri.to_s}'}
41
- a << %{-d '#{request.body}'} unless request.body.nil?
42
- puts a.join(' ')
43
- end
44
- end
45
-
46
28
  def endPoint(path='')
47
29
  parts = ['https:/', $cfg['net.host'], 'api:1'] + @uriparts
48
30
  s = parts.map{|v| v.to_s}.join('/')
49
31
  URI(s + path.to_s)
50
32
  end
51
- def http
52
- uri = URI('https://' + $cfg['net.host'])
53
- if @http.nil? then
54
- @http = Net::HTTP.new(uri.host, uri.port)
55
- @http.use_ssl = true
56
- @http.start
57
- end
58
- @http
59
- end
60
-
61
- def set_req_defaults(request)
62
- request.content_type = 'application/json'
63
- request['authorization'] = 'token ' + @token
64
- request['User-Agent'] = "MrMurano/#{MrMurano::VERSION}"
65
- request
66
- end
67
-
68
- def workit(request, &block)
69
- set_req_defaults(request)
70
- curldebug(request)
71
- if block_given? then
72
- yield request, http()
73
- else
74
- response = http().request(request)
75
- case response
76
- when Net::HTTPSuccess
77
- return {} if response.body.nil?
78
- begin
79
- return JSON.parse(response.body, @json_opts)
80
- rescue
81
- return response.body
82
- end
83
- else
84
- say_error "got #{response} from #{request} #{request.uri.to_s}"
85
- say_error ":: #{response.body}"
86
- say_error '==='
87
- raise response
88
- end
89
- end
90
- end
91
-
92
- def get(path='', &block)
93
- uri = endPoint(path)
94
- workit(Net::HTTP::Get.new(uri), &block)
95
- end
96
-
97
- def post(path='', body={}, &block)
98
- uri = endPoint(path)
99
- req = Net::HTTP::Post.new(uri)
100
- req.body = JSON.generate(body)
101
- workit(req, &block)
102
- end
103
-
104
- def put(path='', body={}, &block)
105
- uri = endPoint(path)
106
- req = Net::HTTP::Put.new(uri)
107
- req.body = JSON.generate(body)
108
- workit(req, &block)
109
- end
110
-
111
- def delete(path='', &block)
112
- uri = endPoint(path)
113
- workit(Net::HTTP::Delete.new(uri), &block)
114
- end
115
-
116
33
  # …
117
34
 
118
35
  ##
@@ -215,9 +132,11 @@ module MrMurano
215
132
  # sometimes this is a name, sometimes it is an item.
216
133
  # do I want to keep that? NO.
217
134
  name = toRemoteItem(from, path)
218
- name[:local_path] = path
219
- name
220
- end
135
+ unless name.nil? then
136
+ name[:local_path] = path
137
+ name
138
+ end
139
+ end.flatten.compact
221
140
  end
222
141
 
223
142
  def synckey(item)
@@ -0,0 +1,103 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ require 'json'
4
+
5
+ module MrMurano
6
+ module Http
7
+ def token
8
+ return @token unless @token.nil?
9
+ @token = Account.new.token
10
+ raise "Not logged in!" if @token.nil?
11
+ @token
12
+ end
13
+
14
+ def json_opts
15
+ return @json_opts unless @json_opts.nil?
16
+ @json_opts = {
17
+ :allow_nan => true,
18
+ :symbolize_names => true,
19
+ :create_additions => false
20
+ }
21
+ end
22
+
23
+ def curldebug(request)
24
+ if $cfg['tool.curldebug'] then
25
+ a = []
26
+ a << %{curl -s -H 'Authorization: #{request['authorization']}'}
27
+ a << %{-H 'User-Agent: #{request['User-Agent']}'}
28
+ a << %{-H 'Content-Type: #{request.content_type}'}
29
+ a << %{-X #{request.method}}
30
+ a << %{'#{request.uri.to_s}'}
31
+ a << %{-d '#{request.body}'} unless request.body.nil?
32
+ puts a.join(' ')
33
+ end
34
+ end
35
+
36
+ def http
37
+ uri = URI('https://' + $cfg['net.host'])
38
+ if @http.nil? then
39
+ @http = Net::HTTP.new(uri.host, uri.port)
40
+ @http.use_ssl = true
41
+ @http.start
42
+ end
43
+ @http
44
+ end
45
+
46
+ def set_req_defaults(request)
47
+ request.content_type = 'application/json'
48
+ request['authorization'] = 'token ' + token
49
+ request['User-Agent'] = "MrMurano/#{MrMurano::VERSION}"
50
+ request
51
+ end
52
+
53
+ def workit(request, &block)
54
+ set_req_defaults(request)
55
+ curldebug(request)
56
+ if block_given? then
57
+ yield request, http()
58
+ else
59
+ response = http().request(request)
60
+ case response
61
+ when Net::HTTPSuccess
62
+ return {} if response.body.nil?
63
+ begin
64
+ return JSON.parse(response.body, json_opts)
65
+ rescue
66
+ return response.body
67
+ end
68
+ else
69
+ say_error "got #{response} from #{request} #{request.uri.to_s}"
70
+ say_error ":: #{response.body}"
71
+ say_error '==='
72
+ raise response
73
+ end
74
+ end
75
+ end
76
+
77
+ def get(path='', &block)
78
+ uri = endPoint(path)
79
+ workit(Net::HTTP::Get.new(uri), &block)
80
+ end
81
+
82
+ def post(path='', body={}, &block)
83
+ uri = endPoint(path)
84
+ req = Net::HTTP::Post.new(uri)
85
+ req.body = JSON.generate(body)
86
+ workit(req, &block)
87
+ end
88
+
89
+ def put(path='', body={}, &block)
90
+ uri = endPoint(path)
91
+ req = Net::HTTP::Put.new(uri)
92
+ req.body = JSON.generate(body)
93
+ workit(req, &block)
94
+ end
95
+
96
+ def delete(path='', &block)
97
+ uri = endPoint(path)
98
+ workit(Net::HTTP::Delete.new(uri), &block)
99
+ end
100
+
101
+ end
102
+ end
103
+ # vim: set ai et sw=2 ts=2 :
@@ -87,6 +87,7 @@ alias_command 'keystore rm', 'keystore delete'
87
87
 
88
88
  command 'keystore command' do |c|
89
89
  c.syntax = %{mr keystore command <key> <command> <args...>}
90
+ c.summary = %{Call some Redis commands in the Keystore}
90
91
  c.description = %{Call some Redis commands in the Keystore.
91
92
 
92
93
  Only a subset of all Redis commands is supported.
data/lib/MrMurano/logs.rb CHANGED
@@ -55,6 +55,9 @@ command :logs do |c|
55
55
  elsif line.kind_of?(Hash) then
56
56
  out=""
57
57
 
58
+ if line.has_key?(:type) then
59
+ out << "#{line[:type]} ".upcase.color(:red).background(:aliceblue)
60
+ end
58
61
  out << "[#{line[:subject]}]".color(:red).background(:aliceblue)
59
62
  out << " "
60
63
  if options.localtime then
@@ -63,24 +66,37 @@ command :logs do |c|
63
66
  curtime = Time.at(line[:timestamp]).to_datetime.iso8601(3)
64
67
  end
65
68
  out << curtime.color(:blue)
66
- out << ":"
67
- out << "\n---------\nrequest:"
68
- if options.pretty then
69
- ret = JSON.pretty_generate(line[:data][:request]).to_s
70
- ret[0] = ret[0].color(:magenta)
71
- ret[-1] = ret[-1].color(:magenta)
72
- out << ret
73
- else
74
- out << line[:data][:request].to_json
75
- end
76
- out << "\n---------\nresponse:"
77
- if options.pretty then
78
- ret = JSON.pretty_generate(line[:data][:response]).to_s
79
- ret[0] = ret[0].color(:magenta)
80
- ret[-1] = ret[-1].color(:magenta)
81
- out << ret
69
+ out << ":\n"
70
+ if line.has_key?(:data) then
71
+ data = line[:data]
72
+
73
+ if data.kind_of?(Hash) and data.has_key?(:request) and data.has_key?(:response) then
74
+ out << "---------\nrequest:"
75
+ if options.pretty then
76
+ ret = JSON.pretty_generate(data[:request]).to_s
77
+ ret[0] = ret[0].color(:magenta)
78
+ ret[-1] = ret[-1].color(:magenta)
79
+ out << ret
80
+ else
81
+ out << data[:request].to_json
82
+ end
83
+
84
+ out << "\n---------\nresponse:"
85
+ if options.pretty then
86
+ ret = JSON.pretty_generate(data[:response]).to_s
87
+ ret[0] = ret[0].color(:magenta)
88
+ ret[-1] = ret[-1].color(:magenta)
89
+ out << ret
90
+ else
91
+ out << data[:response].to_json
92
+ end
93
+
94
+ else
95
+ out << data.to_s
96
+ end
97
+
82
98
  else
83
- out << line[:data][:response].to_json
99
+ out << line.to_s
84
100
  end
85
101
 
86
102
  end
@@ -1,3 +1,4 @@
1
+ require 'csv'
1
2
 
2
3
  module MrMurano
3
4
  class Timeseries < ServiceConfig
@@ -25,14 +26,25 @@ command 'timeseries query' do |c|
25
26
  c.syntax = %{mr timeseries query <query string>}
26
27
  c.description = %{Query the timeseries database}
27
28
  c.option '--[no-]json', %{Display results as raw json}
28
- #c.option '--csv', %{Display results as csv}
29
- # Should get a CSV formatter to make sure cells are properly escapped.
29
+ c.option '--[no-]csv', %{Display results as CSV}
30
+
30
31
  c.action do |args,options|
31
- options.defalts :json=>false
32
+ options.defalts :json=>false, :csv=>false
32
33
  sol = MrMurano::Timeseries.new
33
34
  ret = sol.query args.join(' ')
34
35
  if options.json then
35
36
  puts ret.to_json
37
+
38
+ elsif options.csv then
39
+ (ret[:results] or []).each do |res|
40
+ (res[:series] or []).each do |ser|
41
+ cols = ser[:columns] #.map{|h| "#{ser[:name]}.#{h}"}
42
+ CSV($stdout, :headers=>cols, :write_headers=>true) do |csv|
43
+ ser[:values].each{|v| csv << v}
44
+ end
45
+ end
46
+ end
47
+
36
48
  else
37
49
  (ret[:results] or []).each do |res|
38
50
  (res[:series] or []).each do |ser|
@@ -1,4 +1,4 @@
1
1
  module MrMurano
2
- VERSION = '1.3.1'.freeze
2
+ VERSION = '1.3.2'.freeze
3
3
  end
4
4
 
data/lib/MrMurano.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'MrMurano/version'
2
2
  require 'MrMurano/hash.rb'
3
+ require 'MrMurano/http'
3
4
  require 'MrMurano/configFile'
4
5
  require 'MrMurano/Account'
5
6
  require 'MrMurano/Solution'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: MrMurano
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Conrad Tadpol Tilstra
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-09-08 00:00:00.000000000 Z
11
+ date: 2016-09-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: commander
@@ -179,6 +179,7 @@ files:
179
179
  - lib/MrMurano/configFile.rb
180
180
  - lib/MrMurano/cors.rb
181
181
  - lib/MrMurano/hash.rb
182
+ - lib/MrMurano/http.rb
182
183
  - lib/MrMurano/keystore.rb
183
184
  - lib/MrMurano/logs.rb
184
185
  - lib/MrMurano/shelledCommand.rb