MrMurano 1.8.1 → 1.9.0

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: 9b4a5cac4ca6bec3161d9d83abda05976c8fd7a6
4
- data.tar.gz: b1c881c673de6875dfe0e0aea2e22a41b9f06803
3
+ metadata.gz: c1ab13a9e2ec31727024cb8776e466d1764e592f
4
+ data.tar.gz: 7a1d7d1f1b6d135b89af5053d0277fa2917c62ea
5
5
  SHA512:
6
- metadata.gz: d988176730a12c3350fe1338e6adfa120d158f44bcf2b8156a6f087bcb90250b376ad70fad26519082076d15cc57d7a357b933a4f846fb54c37f8ade7c513dfc
7
- data.tar.gz: f063e60b3f7b2e94ff48fdee1e679d3457dae261989b39ac6b880e595ee43d5cedcde723dcc4b91d7f5e8d6834c423d21ab6fa5ac925d4abb244c24d0d259a93
6
+ metadata.gz: 8b51b475707e85b63490e8f547674651cd47666ddb1cade28ade86f3b08724dba7a6df65e850975f9a630161cb1bc0b7b3277ea8c2262a8f5f8315b47fb03e1b
7
+ data.tar.gz: 0c61906ec034c36d5551ac790042e4b3df6127c4f8075e5f9f70167f85eb11f6c6925e89d19497ca2d2186fd1f38d41ab19728b6dd16587efa083cc01bfb3cbe
data/Gemfile CHANGED
@@ -17,3 +17,7 @@ group :test do
17
17
  gem 'webmock', '~> 2.1.0'
18
18
  end
19
19
 
20
+ group :windows do
21
+ gem 'ocra', '~> 1.3.6'
22
+ end
23
+
data/README.markdown CHANGED
@@ -212,3 +212,12 @@ MrMurano uses git flow for managing branches.
212
212
 
213
213
  MrMurano also uses [bunder](http://bundler.io).
214
214
 
215
+ ## Windows
216
+
217
+ The MrMurano gem will install on Windows.
218
+
219
+ You can install Ruby on Windows with [RubyInstaller](http://rubyinstaller.org).
220
+ You might run into a [known SSL cert issue](http://guides.rubygems.org/ssl-certificate-update/).
221
+ If so follow the steps there to update the certs.
222
+
223
+
data/Rakefile CHANGED
@@ -25,9 +25,18 @@ task :gitpush do
25
25
  sh %{git push --tags}
26
26
  end
27
27
 
28
- #task :gempush do
29
- # sh %{gem push pkg/MrMurano-#{Bundler::GemHelper.gemspec.version}.gem}
30
- #end
28
+ task :gempush do
29
+ sh %{gem push pkg/MrMurano-#{Bundler::GemHelper.gemspec.version}.gem}
30
+ end
31
+
32
+ task :gemit do
33
+ mrt=Bundler::GemHelper.gemspec.version
34
+ sh %{git checkout v#{mrt}}
35
+ Rake::Task[:build].invoke
36
+ Rake::Task[:bob].invoke
37
+ Rake::Task[:gempush].invoke
38
+ sh %{git checkout develop}
39
+ end
31
40
 
32
41
  desc "Prints a cmd to test this in another directory"
33
42
  task :testwith do
data/TODO.taskpaper CHANGED
@@ -3,7 +3,7 @@ Readme:
3
3
  - Look into using VCR for testing. @pri(low)
4
4
 
5
5
  Commands:
6
- - Init command.
6
+ - Init command. @done(2016-11-28)
7
7
  - Empty sub-commands should return help. @done(2016-11-21)
8
8
  There are a bunch of empty sub-commands that prefix another layer. Such as
9
9
  assign, content, product, and others. Those should be impemented as a ‘help’
@@ -23,6 +23,7 @@ Account:
23
23
  - Netrc library (or the netrc format) doesn't allow '#' in passwords. @done(2016-08-10)
24
24
 
25
25
  Endpoints:
26
+ - In fetch(); add content_type to script header if not application/json
26
27
  - Add support for multiple endpoints in one file @pri(high) @done(2016-11-18)
27
28
  - Add directory support like in modules @done(2016-07-26)
28
29
 
@@ -54,6 +55,7 @@ Timeseries:
54
55
  - Add CSV output option. @done(2016-09-09)
55
56
 
56
57
  Product:
58
+ - Add option for progress bar when uploading content files.
57
59
  - Support multiple products.
58
60
  Think about how this would work. There is the syncing of the resoruces, and then
59
61
  some of the commands that use product.id.
@@ -68,6 +70,9 @@ Service Device:
68
70
  Config:
69
71
  - Add config tool.default_sync to set which things sync{up,down} by default
70
72
  It is internally hardcoded to be -s, -a, -m, -e right now.
73
+ - Store passwords in system Keychain on system that have a Keychain.
74
+ mac OS does, various Linux desktops have a couple differnet ones. Not sure about
75
+ Windows.
71
76
  - Add ENV['MR_CONFIGFILE'] path to file to load like --configfile @done(2016-09-22)
72
77
  - Maybe add dotenv support. @done(2016-09-22)
73
78
  - Think about adding dev,staging,prod system; how would that work? @done(2016-09-16)
data/bin/mr CHANGED
@@ -8,6 +8,8 @@ require 'pp'
8
8
  require 'dotenv'
9
9
  Dotenv.load
10
10
 
11
+ Signal.trap('INT', 'EXIT') # Don't drop traces on ^C
12
+
11
13
  program :version, MrMurano::VERSION
12
14
  program :description, %{Manage a Solution and Product in Exosite's Murano}
13
15
 
@@ -74,6 +74,7 @@ module MrMurano
74
74
  set('tool.verbose', false, :defaults)
75
75
  set('tool.debug', false, :defaults)
76
76
  set('tool.dry', false, :defaults)
77
+ set('tool.fullerror', false, :defaults)
77
78
  set('tool.outformat', 'best', :defaults)
78
79
 
79
80
  set('net.host', 'bizapi.hosted.exosite.io', :defaults)
@@ -0,0 +1,129 @@
1
+ require 'MrMurano/Product'
2
+
3
+ module MrMurano
4
+ class Product1PDevice < ProductBase
5
+ include ProductOnePlatformRpcShim
6
+
7
+ def initialize
8
+ super
9
+ @uriparts << :proxy
10
+ @uriparts << 'onep:v1'
11
+ @uriparts << :rpc
12
+ @uriparts << :process
13
+ @model_rid = nil
14
+ @sn_rid = nil
15
+ end
16
+
17
+ ## Get the internal protocol identifier for a device
18
+ # +sn+:: Identifier for a device
19
+ def sn_rid(sn)
20
+ return @sn_rid unless @sn_rid.nil?
21
+ prd = Product.new()
22
+ found = []
23
+
24
+ offset = 0
25
+ loop do
26
+ listing = prd.list(offset)
27
+ break if listing.empty?
28
+ found = listing.select{|item| item[:sn] == sn}
29
+ break unless found.empty?
30
+
31
+ offset += 50
32
+ end
33
+
34
+ @sn_rid = found.first[:rid]
35
+ @sn_rid
36
+ end
37
+
38
+ ## Get information about a device
39
+ # +sn+:: Identifier for a device
40
+ def info(sn)
41
+ do_rpc({:id=>1,
42
+ :procedure=>:info,
43
+ :arguments=>[sn_rid(sn), {}]
44
+ }, sn_rid(sn))
45
+ end
46
+
47
+ ## List resources on a device
48
+ # +sn+:: Identifier for a device
49
+ def list(sn)
50
+ data = info(sn)
51
+ dt = {}
52
+ data[:aliases].each{|k,v| v.each{|a| dt[a] = k.to_s}}
53
+ dt
54
+ end
55
+
56
+ def listing(sn)
57
+ do_rpc({:id=>1,
58
+ :procedure=>:listing,
59
+ :arguments=>[sn_rid(sn), [:dataport], {}]
60
+ }, sn_rid(sn))
61
+ end
62
+
63
+ ## Read the last value for resources on a device
64
+ # +sn+:: Identifier for a device
65
+ # +aliases+:: Array of resource names
66
+ def read(sn, aliases)
67
+ aliases = [aliases] unless aliases.kind_of? Array
68
+ calls = aliases.map do |a|
69
+ {
70
+ :procedure=>:read,
71
+ :arguments=>[ {:alias=>a}, {} ]
72
+ }
73
+ end
74
+ do_mrpc(calls, sn_rid(sn)).map do |i|
75
+ if i.has_key?(:result) and i[:result].count > 0 and i[:result][0].count > 1 then
76
+ i[:result][0][1]
77
+ else
78
+ nil
79
+ end
80
+ end
81
+ end
82
+
83
+ ## Get a tree of info for a device and its resources.
84
+ # +sn+:: Identifier for a device
85
+ def twee(sn)
86
+ inf = info(sn)
87
+
88
+ aliases = inf[:aliases].keys
89
+ # information for all
90
+ info_calls = aliases.map do |rid|
91
+ {:procedure=>:info, :arguments=>[rid, {}]}
92
+ end
93
+
94
+ limitkeys = [:basic, :description, :usage, :children, :storage]
95
+
96
+ isubs = do_mrpc(info_calls, sn_rid(sn))
97
+ children = isubs.map{|i| i[:result].select{|k,v| limitkeys.include? k} }
98
+
99
+ # most current value
100
+ read_calls = aliases.map do |rid|
101
+ {:procedure=>:read, :arguments=>[rid, {}]}
102
+ end
103
+ ivalues = do_mrpc(read_calls, sn_rid(sn))
104
+
105
+ rez = aliases.zip(children, ivalues).map do |d|
106
+ dinf = d[1]
107
+ dinf[:rid] = d[0]
108
+ dinf[:alias] = inf[:aliases][d[0]].first
109
+
110
+ iv = d[2]
111
+ if iv.has_key?(:result) and iv[:result].count > 0 and iv[:result][0].count > 1 then
112
+ dinf[:value] = iv[:result][0][1]
113
+ else
114
+ dinf[:value] = nil
115
+ end
116
+
117
+ dinf
118
+ end
119
+
120
+ inf[:children] = rez
121
+ inf.select!{|k,v| limitkeys.include? k }
122
+ inf
123
+ end
124
+
125
+ end
126
+
127
+ end
128
+
129
+ # vim: set ai et sw=2 ts=2 :
@@ -9,6 +9,7 @@ module MrMurano
9
9
  # Or better stated, that's all okami-shim is.
10
10
  class ProductResources < ProductBase
11
11
  include SyncUpDown
12
+ include ProductOnePlatformRpcShim
12
13
 
13
14
  def initialize
14
15
  super
@@ -42,35 +43,6 @@ module MrMurano
42
43
  name
43
44
  end
44
45
 
45
- ## The model RID for this product.
46
- def model_rid
47
- return @model_rid unless @model_rid.nil?
48
- prd = Product.new
49
- data = prd.info
50
- if data.kind_of?(Hash) and data.has_key?(:modelrid) then
51
- @model_rid = data[:modelrid]
52
- else
53
- raise "Bad info; #{data}"
54
- end
55
- @model_rid
56
- end
57
-
58
- ## Do a 1P RPC call
59
- #
60
- # While this will take an array of calls, don't. Only pass one.
61
- def do_rpc(calls)
62
- calls = [calls] unless calls.kind_of?(Array)
63
- r = post('', {
64
- :auth=>{:client_id=>model_rid},
65
- :calls=>calls
66
- })
67
- return r if not r.kind_of?(Array) or r.count < 1
68
- r = r[0]
69
- return r if not r.kind_of?(Hash) or r[:status] != 'ok'
70
- r[:result]
71
- end
72
- private :do_rpc
73
-
74
46
  ## Get 1P info about the prodcut
75
47
  def info
76
48
  do_rpc({:id=>1,
@@ -29,6 +29,52 @@ module MrMurano
29
29
  end
30
30
  end
31
31
 
32
+ module ProductOnePlatformRpcShim
33
+ ## The model RID for this product.
34
+ def model_rid
35
+ return @model_rid unless @model_rid.nil?
36
+ prd = Product.new
37
+ data = prd.info
38
+ if data.kind_of?(Hash) and data.has_key?(:modelrid) then
39
+ @model_rid = data[:modelrid]
40
+ else
41
+ raise "Bad info; #{data}"
42
+ end
43
+ @model_rid
44
+ end
45
+
46
+ ## Do a 1P RPC call
47
+ #
48
+ # While this will take an array of calls, don't. Only pass one.
49
+ # This only returns the result of the first call. Results from other calls are
50
+ # dropped.
51
+ def do_rpc(calls, cid=model_rid)
52
+ calls = [calls] unless calls.kind_of?(Array)
53
+ r = post('', {
54
+ :auth=>{:client_id=>cid},
55
+ :calls=>calls
56
+ })
57
+ return r if not r.kind_of?(Array) or r.count < 1
58
+ r = r[0]
59
+ return r if not r.kind_of?(Hash) or r[:status] != 'ok'
60
+ r[:result]
61
+ end
62
+ private :do_rpc
63
+
64
+ ## Do many 1P RPC calls
65
+ def do_mrpc(calls, cid=model_rid)
66
+ calls = [calls] unless calls.kind_of?(Array)
67
+ maxid = ((calls.max_by{|c| c[:id] or 0 }[:id]) or 0)
68
+ calls.map!{|c| c[:id] = (maxid += 1) unless c.has_key?(:id); c}
69
+ post('', {
70
+ :auth=>{:client_id=>cid},
71
+ :calls=>calls
72
+ })
73
+ end
74
+ private :do_mrpc
75
+
76
+ end
77
+
32
78
  class Product < ProductBase
33
79
  ## Get info about the product
34
80
  def info
@@ -25,9 +25,10 @@ module MrMurano
25
25
  def fetch(id)
26
26
  ret = get('/' + id.to_s)
27
27
  ret[:content_type] = 'application/json' if ret[:content_type].empty?
28
+ # TODO: add content_type to header if not application/json
28
29
  aheader = (ret[:script].lines.first or "").chomp
29
30
  dheader = /^--#ENDPOINT (?i:#{ret[:method]}) #{ret[:path]}$/
30
- rheader = %{--#ENDPOINT #{ret[:method]} #{ret[:path]}\n}
31
+ rheader = %{--#ENDPOINT #{ret[:method].upcase} #{ret[:path]}\n}
31
32
  if block_given? then
32
33
  yield rheader unless dheader =~ aheader
33
34
  yield ret[:script]
@@ -32,7 +32,7 @@ command :config do |c|
32
32
  elsif args.count == 0 then
33
33
  say_error "Need a config key"
34
34
  elsif args.count == 1 and not options.unset then
35
- options.defaults :system=>false, :user=>false, :project=>false,
35
+ options.default :system=>false, :user=>false, :project=>false,
36
36
  :specified=>false, :env=>false
37
37
 
38
38
  # For read, if no scopes, than all. Otherwise just those specified
@@ -47,7 +47,7 @@ command :config do |c|
47
47
  say $cfg.get(args[0], scopes)
48
48
  else
49
49
 
50
- options.defaults :system=>false, :user=>false, :project=>true,
50
+ options.default :system=>false, :user=>false, :project=>true,
51
51
  :specified=>false, :env=>false
52
52
  # For write, if scope is specified, only write to that scope.
53
53
  scope = :project
@@ -75,7 +75,7 @@ command 'content upload' do |c|
75
75
  c.option '--meta STRING', %{Add extra meta info to the content item}
76
76
 
77
77
  c.action do |args, options|
78
- options.defaults :meta=>' '
78
+ options.default :meta=>' '
79
79
  prd = MrMurano::ProductContent.new
80
80
 
81
81
  if args[0].nil? then
@@ -4,7 +4,7 @@ command :domain do |c|
4
4
  c.summary = %{Print the domain for this solution}
5
5
  c.option '--[no-]raw', %{Don't add scheme}
6
6
  c.action do |args,options|
7
- options.defaults :raw=>true
7
+ options.default :raw=>true
8
8
  sol = MrMurano::Solution.new
9
9
  ret = sol.info()
10
10
  if options.raw then
@@ -1,6 +1,9 @@
1
1
  require 'pathname'
2
2
  require 'json'
3
+ require 'yaml'
3
4
  require 'fileutils'
5
+ require 'MrMurano/dir'
6
+ require 'MrMurano/Account'
4
7
 
5
8
  command 'config export' do |c|
6
9
  c.syntax = %{mr config export}
@@ -17,7 +20,7 @@ command 'config export' do |c|
17
20
  c.option '--[no-]merge', "Merge endpoints into a single routes.lua file"
18
21
 
19
22
  c.action do |args, options|
20
- options.defaults :merge => true
23
+ options.default :merge => true
21
24
 
22
25
  solfile = Pathname.new($cfg['location.base'] + 'Solutionfile.json')
23
26
  solsecret = Pathname.new($cfg['location.base'] + '.Solutionfile.secret')
@@ -91,10 +94,17 @@ command 'config import' do |c|
91
94
  by the exosite-cli tool.
92
95
  }
93
96
 
97
+ c.option '--[no-]move', %{Move files into expected places if needed}
98
+
94
99
  c.action do |args, options|
100
+ options.default :move=>true
101
+
95
102
  solfile = ($cfg['location.base'] + 'Solutionfile.json')
96
103
  solsecret = ($cfg['location.base'] + '.Solutionfile.secret')
97
104
 
105
+ acc = MrMurano::Account.new
106
+ fuopts = {:noop=>$cfg['tool.dry'], :verbose=>$cfg['tool.verbose']}
107
+
98
108
  if solfile.exist? then
99
109
  # Is in JSON, which as a subset of YAML, so use YAML parser
100
110
  solfile.open do |io|
@@ -102,6 +112,83 @@ command 'config import' do |c|
102
112
  $cfg.set('location.files', sf['assets']) if sf.has_key? 'assets'
103
113
  $cfg.set('location.files', sf['file_dir']) if sf.has_key? 'file_dir'
104
114
  $cfg.set('files.default_page', sf['default_page']) if sf.has_key? 'default_page'
115
+
116
+ # look at :routes/:custom_api if in a subdir, set location.endpoints
117
+ # Otherwise to move it
118
+ routes = (sf['custom_api'] or sf['routes'] or '')
119
+ if routes == '' then
120
+ acc.verbose "No endpoints to import"
121
+ elsif File.dirname(routes) == '.' then
122
+ acc.warning "Routes file #{File.basename(routes)} not in endpoints directory"
123
+ if options.move then
124
+ acc.warning "Moving it to #{$cfg['location.endpoints']}"
125
+ FileUtils.mkpath($cfg['location.endpoints'], fuopts)
126
+ FileUtils.mv(routes, File.join($cfg['location.endpoints'], File.basename(routes)), fuopts)
127
+ end
128
+ else
129
+ # Otherwise just use the location they already have
130
+ routeDir = File.dirname(routes)
131
+ acc.verbose "For endpoints using #{routeDir}"
132
+ if $cfg['location.endpoints'] != routeDir then
133
+ $cfg.set('location.endpoints', routeDir)
134
+ end
135
+ end
136
+
137
+ # if has :cors, export it
138
+ if sf.has_key?('cors') then
139
+ acc.verbose "Exporting CORS to #{$cfg['location.cors']}"
140
+ File.open($cfg['location.cors'], 'w') do |cio|
141
+ cio << sf['cors'].to_yaml
142
+ end
143
+ end
144
+
145
+ def update_or_stop(paths, cfgkey, what, acc=MrMurano::Account.new)
146
+ crd = Dir.common_root(paths)
147
+ acc.debug "crd => #{crd}"
148
+ if crd.empty? then
149
+ acc.error "#{what.capitalize} in multiple directories! #{crd.join(', ')}"
150
+ acc.error "Please move them manually into #{$cfg[cfgkey]}"
151
+ exit(1)
152
+ else
153
+ maxd = Dir.max_depth(paths) - crd.count
154
+ if maxd > 2 then
155
+ acc.error "Some #{what} are in directories too deep."
156
+ acc.error "Please move them manually to #{$cfg[cfgkey]}"
157
+ exit(1)
158
+ else
159
+ crd = File.join(crd)
160
+ acc.verbose "For #{what} using #{crd}"
161
+ if $cfg[cfgkey] != crd then
162
+ $cfg.set(cfgkey, crd)
163
+ end
164
+ end
165
+ end
166
+ end
167
+
168
+ # scan modules for common sub-dir. Set if found. Otherwise warn.
169
+ modules = (sf['modules'] or {})
170
+ update_or_stop(modules.values, 'location.modules', 'modules')
171
+
172
+ # scan eventhandlers for common sub-dir. Set if found. Otherwise warn.
173
+ eventhandlers = (sf['event_handler'] or sf['services'] or {})
174
+ evd = eventhandlers.values.map{|e| e.values}.flatten
175
+ update_or_stop(evd, 'location.eventhandlers', 'eventhandlers')
176
+
177
+ # add header to each eventhandler
178
+ eventhandlers.each do |service, events|
179
+ events.each do |event, path|
180
+ # open path, if no header, add it
181
+ data = IO.readlines(path)
182
+ dheader = "--#EVENT #{service} #{event}"
183
+ aheader = (data.first or "").chomp
184
+ if aheader != dheader then
185
+ acc.verbose "Adding event header to #{path}"
186
+ data.insert(0, dheader)
187
+ File.open(path, 'w'){|eio| eio.puts(data)} unless $cfg['tool.dry']
188
+ end
189
+ end
190
+ end
191
+
105
192
  end
106
193
  end
107
194
 
@@ -133,7 +220,7 @@ command 'config import' do |c|
133
220
  end
134
221
 
135
222
  say "Configuration items have been imported."
136
- say "Use `mr syncdown` get get all endpoints, modules, and event handlers"
223
+ #say "Use `mr syncdown` get get all endpoints, modules, and event handlers"
137
224
  end
138
225
 
139
226
  end
@@ -0,0 +1,117 @@
1
+ require 'MrMurano/Account'
2
+
3
+
4
+ command :init do |c|
5
+ c.syntax = %{mr init}
6
+ c.summary = %{The easy way to start a project}
7
+ c.description = %{}
8
+
9
+ c.option '--force', %{Override existing business, solution, or product ids}
10
+ c.option '--[no-]mkdirs', %{Create default directories}
11
+
12
+ c.action do |args, options|
13
+ options.default :force=>false, :mkdirs=>true
14
+
15
+ if not options.force and ($cfg['location.base'] + 'Solutionfile.json').exist? then
16
+ y=ask("A Solutionfile.json exists, Do you want exit and run `mr config import` instead? [yN]")
17
+ exit 0 unless y =~ /^n/i
18
+ end
19
+
20
+
21
+ # If they have never logged in, then asking for the business.id will also ask
22
+ # for their username and password.
23
+ acc = MrMurano::Account.new
24
+
25
+
26
+ # 1. Get business id
27
+ if not options.force and not $cfg['business.id'].nil? then
28
+ say "Using Business ID already set to #{$cfg['business.id']}"
29
+ else
30
+ bizz = acc.businesses
31
+ if bizz.count == 1 then
32
+ bizid = bizz.first
33
+ say "You are only part of one business; using #{bizid[:name]}"
34
+ $cfg.set('businesses.id', bizid[:bizid], :project)
35
+
36
+ else
37
+ choose do |menu|
38
+ menu.prompt = "Select which Business to use:"
39
+ menu.flow = :columns_across
40
+ bizz.sort{|a,b| a[:name]<=>b[:name]}.each do |b|
41
+ menu.choice(b[:name]) do
42
+ $cfg.set('business.id', b[:bizid], :project)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ puts '' # blank line
49
+
50
+ # 2. Get Solution id
51
+ if not options.force and not $cfg['solution.id'].nil? then
52
+ say "Using Solution ID already set to #{$cfg['solution.id']}"
53
+ else
54
+ solz = acc.solutions
55
+ if solz.count == 1 then
56
+ sol = solz.first
57
+ say "You only have one solution; using #{sol[:domain]}"
58
+ $cfg.set('solution.id', sol[:apiId], :project)
59
+ else
60
+ choose do |menu|
61
+ menu.prompt = "Select which Solution to use:"
62
+ menu.flow = :columns_across
63
+ solz.sort{|a,b| a[:domain]<=>b[:domain]}.each do |s|
64
+ menu.choice(s[:domain].sub(/\..*$/,'')) do
65
+ $cfg.set('solution.id', s[:apiId], :project)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ puts '' # blank line
72
+
73
+ # 3. Get Product id
74
+ if not options.force and not $cfg['product.id'].nil? then
75
+ say "Using Product ID already set to #{$cfg['product.id']}"
76
+ else
77
+ podz = acc.products
78
+ if podz.count == 1 then
79
+ prd = podz.first
80
+ say "You only have one product; using #{prd[:label]}"
81
+ $cfg.set('product.id', prd[:modelId], :project)
82
+ else
83
+ choose do |menu|
84
+ menu.prompt = "Select which Product to use:"
85
+ menu.flow = :columns_across
86
+ podz.sort{|a,b| a[:label]<=>b[:label]}.each do |p|
87
+ menu.choice(p[:label]) do
88
+ $cfg.set('product.id', p[:modelId], :project)
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+
96
+ puts ''
97
+ say "Ok, In business ID: #{$cfg['business.id']} using Solution ID: #{$cfg['solution.id']} with Product ID: #{$cfg['product.id']}"
98
+
99
+ if options.mkdirs then
100
+ %w{
101
+ location.files
102
+ location.endpoints
103
+ location.modules
104
+ location.eventhandlers
105
+ location.specs
106
+ }.each do |cfgi|
107
+ path = $cfg[cfgi]
108
+ path = Pathname.new(path) unless path.kind_of? Pathname
109
+ path.mkpath unless path.exist?
110
+ end
111
+ say "Default directories created"
112
+ end
113
+
114
+ end
115
+ end
116
+
117
+ # vim: set ai et sw=2 ts=2 :
@@ -0,0 +1,67 @@
1
+ require 'MrMurano/Product-1P-Device'
2
+
3
+ command 'product device' do |c|
4
+ c.syntax = %{mr product device}
5
+ c.summary = %{Interact with a device in a product}
6
+ c.description = %{}
7
+
8
+ c.action do |a,o|
9
+ ::Commander::UI.enable_paging
10
+ say MrMurano::SubCmdGroupHelp.new(c).get_help
11
+ end
12
+ end
13
+
14
+ command 'product device read' do |c|
15
+ c.syntax = %{mr product device read <identifier> (<resources>)}
16
+ c.summary = %{Read recources on a device}
17
+ c.option '-o', '--output FILE', %{Download to file instead of STDOUT}
18
+
19
+ c.action do |args,options|
20
+ snid = args.shift
21
+ prd = MrMurano::Product1PDevice.new
22
+
23
+ if args.count == 0 then
24
+ # fetch list and read all
25
+ args = prd.list(snid).keys
26
+ end
27
+
28
+ io=nil
29
+ io = File.open(options.output, 'w') if options.output
30
+ data = prd.read(snid, args)
31
+ prd.outf(data, io)
32
+ io.close unless io.nil?
33
+
34
+ end
35
+ end
36
+
37
+ command 'product device twee' do |c|
38
+ c.syntax = %{mr product device twee <identifier>}
39
+ c.summary = %{Show info about a device}
40
+
41
+
42
+ c.action do |args,options|
43
+ options.default :width=>HighLine::SystemExtensions.terminal_size[0]
44
+ snid = args.shift
45
+ prd = MrMurano::Product1PDevice.new
46
+ data = prd.twee(snid)
47
+
48
+ io=nil
49
+ io = File.open(options.output, 'w') if options.output
50
+ prd.outf(data, io) do |dd,ios|
51
+ data={}
52
+ data[:title] = "#{snid} #{dd[:description][:name]} #{dd[:basic][:status]}"
53
+ data[:headers] = [:Resource, :Format, :Modified, :Value]
54
+ data[:rows] = dd[:children].map do |child|
55
+ [ (child[:description][:name] or child[:alias]),
56
+ child[:description][:format],
57
+ child[:basic][:modified],
58
+ (child[:value] or "").to_s[0..22] # TODO adjust based on terminal width
59
+ ]
60
+ end
61
+ prd.tabularize(data, ios)
62
+ end
63
+ io.close unless io.nil?
64
+ end
65
+ end
66
+
67
+ # vim: set ai et sw=2 ts=2 :
@@ -1,8 +1,8 @@
1
1
  require 'MrMurano/Product'
2
2
 
3
- command 'product write' do |c|
4
- c.syntax = %{mr product write <sn> <alias> <value> ([<alias> <value>]…)}
5
- c.summary = %{Write values into the product}
3
+ command 'product device write' do |c|
4
+ c.syntax = %{mr product device write <identifier> <alias> <value> ([<alias> <value>]…)}
5
+ c.summary = %{Write values into a device}
6
6
 
7
7
  c.action do |args,options|
8
8
  sn = args.shift
@@ -194,12 +194,30 @@ end
194
194
  command 'tsdb list tags' do |c|
195
195
  c.syntax = %{mr tsdb list tags [options]}
196
196
  c.summary = %{List tags}
197
+ c.option '--values', %{Also return the known tag values}
197
198
 
198
199
  c.action do |args, options|
200
+ options.default :values=>false
201
+
199
202
  sol = MrMurano::ServiceConfigs::Tsdb.new
200
203
  ret = sol.listTags
201
204
  # TODO: handle looping if :next != nil
202
- sol.outf ret[:tags].keys
205
+
206
+ if options.values then
207
+ sol.outf(ret[:tags]) do |dd, ios|
208
+ data={}
209
+ data[:headers] = dd.keys
210
+ data[:rows] = dd.keys.map{|k| dd[k]}
211
+ len = data[:rows].map{|i| i.length}.max
212
+ data[:rows].each{|r| r.fill(nil, r.length, len - r.length)}
213
+ data[:rows] = data[:rows].transpose
214
+ sol.tabularize(data, ios)
215
+ end
216
+ else
217
+ sol.outf ret[:tags].keys
218
+ end
219
+
220
+
203
221
  end
204
222
  end
205
223
 
@@ -5,11 +5,13 @@ require 'MrMurano/commands/content'
5
5
  require 'MrMurano/commands/cors'
6
6
  require 'MrMurano/commands/domain'
7
7
  require 'MrMurano/commands/exportImport'
8
+ require 'MrMurano/commands/init'
8
9
  require 'MrMurano/commands/keystore'
9
10
  require 'MrMurano/commands/logs'
10
11
  require 'MrMurano/commands/product'
11
12
  require 'MrMurano/commands/productCreate'
12
13
  require 'MrMurano/commands/productDelete'
14
+ require 'MrMurano/commands/productDevice'
13
15
  require 'MrMurano/commands/productList'
14
16
  require 'MrMurano/commands/productSpec'
15
17
  require 'MrMurano/commands/productWrite'
@@ -0,0 +1,47 @@
1
+
2
+ ## Get the root-most common directories from paths
3
+ def Dir.common_dirs(paths)
4
+ paths = paths.map do |p|
5
+ if p.kind_of? Array then
6
+ p
7
+ else
8
+ p.to_s.split(File::SEPARATOR)
9
+ end
10
+ end
11
+
12
+ paths.map{|p| p.first}.uniq
13
+ end
14
+
15
+ ## How deep is deepest directory path? (not including the file)
16
+ def Dir.max_depth(paths)
17
+ paths = paths.map do |p|
18
+ if p.kind_of? Array then
19
+ p
20
+ else
21
+ p.to_s.split(File::SEPARATOR)
22
+ end
23
+ end
24
+
25
+ paths.map{|p| p.count - 1}.max
26
+
27
+ end
28
+
29
+ ## Get the deepest common root directory for all paths
30
+ def Dir.common_root(paths, root=[])
31
+ paths = paths.map do |p|
32
+ if p.kind_of? Array then
33
+ p
34
+ else
35
+ p.to_s.split(File::SEPARATOR)
36
+ end
37
+ end
38
+
39
+ base = Dir.common_dirs(paths)
40
+ if base.count == 1 then
41
+ return common_root(paths.map{|p| p[1..-1]}, root + base)
42
+ else
43
+ return root
44
+ end
45
+ end
46
+
47
+ # vim: set ai et sw=2 ts=2 :
@@ -1,4 +1,4 @@
1
1
  module MrMurano
2
- VERSION = '1.8.1'.freeze
2
+ VERSION = '1.9.0'.freeze
3
3
  end
4
4
 
data/lib/MrMurano.rb CHANGED
@@ -11,6 +11,7 @@ require 'MrMurano/Solution-Services'
11
11
  require 'MrMurano/Solution-Users'
12
12
  require 'MrMurano/Solution-ServiceConfig'
13
13
  require 'MrMurano/Product'
14
+ require 'MrMurano/Product-1P-Device'
14
15
  require 'MrMurano/Product-Resources'
15
16
 
16
17
  require 'MrMurano/commands'
@@ -51,66 +51,6 @@ RSpec.describe MrMurano::ProductResources do
51
51
  end
52
52
  end
53
53
 
54
- context "do_rpc" do
55
- # Note, do_rpc is private.
56
- it "Accepts an object" do
57
- stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process").
58
- to_return(body: [{
59
- :id=>1, :status=>"ok", :result=>{}
60
- }])
61
-
62
- ret = nil
63
- @prd.instance_eval{ ret = do_rpc({:id=>1}) }
64
- expect(ret).to eq({})
65
- end
66
-
67
- it "Accepts an Array" do
68
- stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process").
69
- to_return(body: [{:id=>1, :status=>"ok", :result=>{:one=>1}},
70
- {:id=>2, :status=>"ok", :result=>{:two=>2}}])
71
-
72
- ret = nil
73
- @prd.instance_eval{ ret = do_rpc([{:id=>1}, {:id=>2}]) }
74
- expect(ret).to eq({:one=>1})
75
- # yes it only returns first.
76
- end
77
-
78
- it "returns res if not Array" do
79
- stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process").
80
- to_return(body: {:not=>'an array'}.to_json)
81
-
82
- ret = nil
83
- @prd.instance_eval{ ret = do_rpc({:id=>1}) }
84
- expect(ret).to eq({:not=>'an array'})
85
- end
86
-
87
- it "returns res if count less than 1" do
88
- stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process").
89
- to_return(body: [])
90
-
91
- ret = nil
92
- @prd.instance_eval{ ret = do_rpc({:id=>1}) }
93
- expect(ret).to eq([])
94
- end
95
-
96
- it "returns res[0] if not Hash" do
97
- stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process").
98
- to_return(body: ["foo"])
99
-
100
- ret = nil
101
- @prd.instance_eval{ ret = do_rpc({:id=>1}) }
102
- expect(ret).to eq("foo")
103
- end
104
-
105
- it "returns res[0] if not status ok" do
106
- stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process").
107
- to_return(body: [{:id=>1, :status=>'error'}])
108
-
109
- ret = nil
110
- @prd.instance_eval{ ret = do_rpc({:id=>1}) }
111
- expect(ret).to eq({:id=>1, :status=>'error'})
112
- end
113
- end
114
54
 
115
55
  context "queries" do
116
56
  it "gets info" do
@@ -0,0 +1,136 @@
1
+ require 'MrMurano/version'
2
+ require 'MrMurano/Config'
3
+ require 'MrMurano/Product-1P-Device'
4
+
5
+ RSpec.describe MrMurano::Product1PDevice do
6
+ before(:example) do
7
+ $cfg = MrMurano::Config.new
8
+ $cfg.load
9
+ $cfg['net.host'] = 'bizapi.hosted.exosite.io'
10
+ $cfg['product.id'] = 'XYZ'
11
+ $cfg['product.spec'] = 'XYZ.yaml'
12
+
13
+ @prd = MrMurano::Product1PDevice.new
14
+ allow(@prd).to receive(:token).and_return("TTTTTTTTTT")
15
+ allow(@prd).to receive(:sn_rid).and_return("LLLLLLLLLL")
16
+ end
17
+
18
+ it "initializes" do
19
+ uri = @prd.endPoint('')
20
+ expect(uri.to_s).to eq("https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process")
21
+ end
22
+
23
+ it "gets info" do
24
+ stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process").
25
+ with(body: {:auth=>{:client_id=>"LLLLLLLLLL"},
26
+ :calls=>[{:id=>1,
27
+ :procedure=>"info",
28
+ :arguments=>["LLLLLLLLLL", {}]} ]}).
29
+ to_return(body: [{:id=>1, :status=>"ok", :result=>{:comments=>[]}}])
30
+
31
+ ret = @prd.info("12")
32
+ expect(ret).to eq({:comments=>[]})
33
+ end
34
+
35
+ it "lists resources" do
36
+ stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process").
37
+ with(body: {:auth=>{:client_id=>"LLLLLLLLLL"},
38
+ :calls=>[{:id=>1,
39
+ :procedure=>"info",
40
+ :arguments=>["LLLLLLLLLL", {}]} ]}).
41
+ to_return(body: [{:id=>1, :status=>"ok", :result=>{:aliases=>{
42
+ :s2143rt4regf=>["one"],:njilh32o78rnq=>["two"]}}}])
43
+
44
+ ret = @prd.list("12")
45
+ expect(ret).to eq({"one"=>"s2143rt4regf", "two"=>"njilh32o78rnq"})
46
+ end
47
+
48
+ context "reads resources" do
49
+ it "single" do
50
+ stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process").
51
+ with(body: {:auth=>{:client_id=>"LLLLLLLLLL"},
52
+ :calls=>[{:id=>1,
53
+ :procedure=>"read",
54
+ :arguments=>[{"alias"=>"one"}, {}]} ]}).
55
+ to_return(body: [{:id=>1, :status=>"ok", :result=>[ [12345678,10] ]}])
56
+ ret = @prd.read("12", "one")
57
+ expect(ret).to eq([10])
58
+ end
59
+
60
+ it "multiple" do
61
+ stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process").
62
+ with(body: {:auth=>{:client_id=>"LLLLLLLLLL"},
63
+ :calls=>[{:id=>1,
64
+ :procedure=>"read",
65
+ :arguments=>[{"alias"=>"two"}, {}]},
66
+ {:id=>2,
67
+ :procedure=>"read",
68
+ :arguments=>[{"alias"=>"three"}, {}]},
69
+ {:id=>3,
70
+ :procedure=>"read",
71
+ :arguments=>[{"alias"=>"one"}, {}]},
72
+ ]}).
73
+ to_return(body: [
74
+ {:id=>1, :status=>"ok", :result=>[ [12345678,10] ]},
75
+ {:id=>2, :status=>"ok", :result=>[ [12345678,15] ]},
76
+ {:id=>3, :status=>"ok", :result=>[ [12345678,20] ]},
77
+ ])
78
+ ret = @prd.read("12", ["two","three","one"])
79
+ expect(ret).to eq([10,15,20])
80
+ end
81
+
82
+ end
83
+
84
+ it "gets tree info for device" do
85
+ # this makes three https calls, info on root, info and read on children
86
+
87
+ # root info.
88
+ stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process").
89
+ with(body: {:auth=>{:client_id=>"LLLLLLLLLL"},
90
+ :calls=>[{:id=>1,
91
+ :procedure=>"info",
92
+ :arguments=>["LLLLLLLLLL", {}]} ]}).
93
+ to_return(body: [{:id=>1, :status=>"ok", :result=>{:aliases=>{
94
+ :s2143rt4regf=>["one"],:njilh32o78rnq=>["two"]}}}])
95
+
96
+ # children info
97
+ stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process").
98
+ with(body: {:auth=>{:client_id=>"LLLLLLLLLL"},
99
+ :calls=>[{:id=>1,
100
+ :procedure=>"info",
101
+ :arguments=>["s2143rt4regf", {}]},
102
+ {:id=>2,
103
+ :procedure=>"info",
104
+ :arguments=>["njilh32o78rnq", {}]}
105
+ ]}).
106
+ to_return(body: [
107
+ {:id=>1, :status=>"ok", :result=>{:basic=>{:type=>"dataport"}}},
108
+ {:id=>2, :status=>"ok", :result=>{:basic=>{:type=>"dataport"}}}
109
+ ])
110
+
111
+ # children read
112
+ stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process").
113
+ with(body: {:auth=>{:client_id=>"LLLLLLLLLL"},
114
+ :calls=>[{:id=>1,
115
+ :procedure=>"read",
116
+ :arguments=>["s2143rt4regf", {}]},
117
+ {:id=>2,
118
+ :procedure=>"read",
119
+ :arguments=>["njilh32o78rnq", {}]},
120
+ ]}).
121
+ to_return(body: [
122
+ {:id=>1, :status=>"ok", :result=>[ [12345678,10] ]},
123
+ {:id=>2, :status=>"ok", :result=>[ [12345678,15] ]},
124
+ ])
125
+
126
+ ret = @prd.twee('12')
127
+ expect(ret).to eq({:children=>[{:basic=>{:type=>"dataport"},
128
+ :rid=>:s2143rt4regf, :alias=>"one", :value=>10},
129
+ {:basic=>{:type=>"dataport"},
130
+ :rid=>:njilh32o78rnq, :alias=>"two",
131
+ :value=>15}]})
132
+ end
133
+
134
+ end
135
+
136
+ # vim: set ai et sw=2 ts=2 :
@@ -0,0 +1,173 @@
1
+ require 'MrMurano/version'
2
+ require 'MrMurano/Config'
3
+ require 'MrMurano/Product-Resources'
4
+
5
+ RSpec.describe MrMurano::ProductResources, "#1PshimTests" do
6
+ before(:example) do
7
+ $cfg = MrMurano::Config.new
8
+ $cfg.load
9
+ $cfg['net.host'] = 'bizapi.hosted.exosite.io'
10
+ $cfg['product.id'] = 'XYZ'
11
+ $cfg['product.spec'] = 'XYZ.yaml'
12
+
13
+ @prd = MrMurano::ProductResources.new
14
+ allow(@prd).to receive(:token).and_return("TTTTTTTTTT")
15
+ allow(@prd).to receive(:model_rid).and_return("LLLLLLLLLL")
16
+ end
17
+
18
+ context "do_rpc" do
19
+ # Note, do_rpc is private.
20
+ it "Accepts an object" do
21
+ stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process").
22
+ to_return(body: [{
23
+ :id=>1, :status=>"ok", :result=>{}
24
+ }])
25
+
26
+ ret = nil
27
+ @prd.instance_eval{ ret = do_rpc({:id=>1}) }
28
+ expect(ret).to eq({})
29
+ end
30
+
31
+ it "Accepts an Array" do
32
+ stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process").
33
+ to_return(body: [{:id=>1, :status=>"ok", :result=>{:one=>1}},
34
+ {:id=>2, :status=>"ok", :result=>{:two=>2}}])
35
+
36
+ ret = nil
37
+ @prd.instance_eval{ ret = do_rpc([{:id=>1}, {:id=>2}]) }
38
+ expect(ret).to eq({:one=>1})
39
+ # yes it only returns first.
40
+ end
41
+
42
+ it "returns res if not Array" do
43
+ stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process").
44
+ to_return(body: {:not=>'an array'}.to_json)
45
+
46
+ ret = nil
47
+ @prd.instance_eval{ ret = do_rpc({:id=>1}) }
48
+ expect(ret).to eq({:not=>'an array'})
49
+ end
50
+
51
+ it "returns res if count less than 1" do
52
+ stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process").
53
+ to_return(body: [])
54
+
55
+ ret = nil
56
+ @prd.instance_eval{ ret = do_rpc({:id=>1}) }
57
+ expect(ret).to eq([])
58
+ end
59
+
60
+ it "returns res[0] if not Hash" do
61
+ stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process").
62
+ to_return(body: ["foo"])
63
+
64
+ ret = nil
65
+ @prd.instance_eval{ ret = do_rpc({:id=>1}) }
66
+ expect(ret).to eq("foo")
67
+ end
68
+
69
+ it "returns res[0] if not status ok" do
70
+ stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process").
71
+ to_return(body: [{:id=>1, :status=>'error'}])
72
+
73
+ ret = nil
74
+ @prd.instance_eval{ ret = do_rpc({:id=>1}) }
75
+ expect(ret).to eq({:id=>1, :status=>'error'})
76
+ end
77
+ end
78
+
79
+ context "do_mrpc" do
80
+ # Note, do_rpc is private.
81
+ it "Accepts an object" do
82
+ stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process").
83
+ to_return(body: [{
84
+ :id=>1, :status=>"ok", :result=>{}
85
+ }])
86
+
87
+ ret = nil
88
+ @prd.instance_eval{ ret = do_mrpc({:id=>1}) }
89
+ expect(ret).to eq([{:id=>1, :status=>"ok", :result=>{}}])
90
+ end
91
+
92
+ it "Accepts an Array" do
93
+ stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process").
94
+ to_return(body: [{:id=>1, :status=>"ok", :result=>{:one=>1}},
95
+ {:id=>2, :status=>"ok", :result=>{:two=>2}}])
96
+
97
+ ret = nil
98
+ @prd.instance_eval{ ret = do_mrpc([{:id=>1}, {:id=>2}]) }
99
+ expect(ret).to eq([{:id=>1, :status=>"ok", :result=>{:one=>1}},
100
+ {:id=>2, :status=>"ok", :result=>{:two=>2}}])
101
+ end
102
+
103
+ it "fills in all ids if missing" do
104
+ stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process").
105
+ to_return(body: [{:id=>1, :status=>"ok", :result=>{:one=>1}},
106
+ {:id=>2, :status=>"ok", :result=>{:two=>2}},
107
+ {:id=>3, :status=>"ok", :result=>{:three=>3}}])
108
+
109
+ ret = nil
110
+ @prd.instance_eval{ ret = do_mrpc([{}, {}, {}]) }
111
+ expect(ret).to eq([{:id=>1, :status=>"ok", :result=>{:one=>1}},
112
+ {:id=>2, :status=>"ok", :result=>{:two=>2}},
113
+ {:id=>3, :status=>"ok", :result=>{:three=>3}}])
114
+ end
115
+
116
+ it "fills in missing ids" do
117
+ stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process").
118
+ with(body: {:auth=>{:client_id=>"LLLLLLLLLL"},
119
+ :calls=>[{:id=>5, :procedure=>"info"},
120
+ {:id=>4, :procedure=>"info"},
121
+ {:id=>6, :procedure=>"info"} ]}).
122
+ to_return(body: [{:id=>5, :status=>"ok", :result=>{:one=>1}},
123
+ {:id=>4, :status=>"ok", :result=>{:two=>2}},
124
+ {:id=>6, :status=>"ok", :result=>{:three=>3}}])
125
+
126
+ ret = nil
127
+ @prd.instance_eval{ ret = do_mrpc([{:procedure=>"info"},
128
+ {:procedure=>"info", :id=>4},
129
+ {:procedure=>"info"}]) }
130
+ expect(ret).to eq([{:id=>5, :status=>"ok", :result=>{:one=>1}},
131
+ {:id=>4, :status=>"ok", :result=>{:two=>2}},
132
+ {:id=>6, :status=>"ok", :result=>{:three=>3}}])
133
+ end
134
+
135
+ it "returns res if not Array" do
136
+ stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process").
137
+ to_return(body: {:not=>'an array'}.to_json)
138
+
139
+ ret = nil
140
+ @prd.instance_eval{ ret = do_mrpc({:id=>1}) }
141
+ expect(ret).to eq({:not=>'an array'})
142
+ end
143
+
144
+ it "returns res if count less than 1" do
145
+ stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process").
146
+ to_return(body: [])
147
+
148
+ ret = nil
149
+ @prd.instance_eval{ ret = do_mrpc({:id=>1}) }
150
+ expect(ret).to eq([])
151
+ end
152
+
153
+ it "returns res[0] if not Hash" do
154
+ stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process").
155
+ to_return(body: ["foo"])
156
+
157
+ ret = nil
158
+ @prd.instance_eval{ ret = do_mrpc({:id=>1}) }
159
+ expect(ret).to eq(["foo"])
160
+ end
161
+
162
+ it "returns res[0] if not status ok" do
163
+ stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process").
164
+ to_return(body: [{:id=>1, :status=>'error'}])
165
+
166
+ ret = nil
167
+ @prd.instance_eval{ ret = do_mrpc({:id=>1}) }
168
+ expect(ret).to eq([{:id=>1, :status=>'error'}])
169
+ end
170
+ end
171
+ end
172
+
173
+ # vim: set ai et sw=2 ts=2 :
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.8.1
4
+ version: 1.9.0
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-11-21 00:00:00.000000000 Z
11
+ date: 2016-11-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: commander
@@ -200,6 +200,7 @@ files:
200
200
  - lib/MrMurano.rb
201
201
  - lib/MrMurano/Account.rb
202
202
  - lib/MrMurano/Config.rb
203
+ - lib/MrMurano/Product-1P-Device.rb
203
204
  - lib/MrMurano/Product-Resources.rb
204
205
  - lib/MrMurano/Product.rb
205
206
  - lib/MrMurano/Solution-Cors.rb
@@ -220,11 +221,13 @@ files:
220
221
  - lib/MrMurano/commands/cors.rb
221
222
  - lib/MrMurano/commands/domain.rb
222
223
  - lib/MrMurano/commands/exportImport.rb
224
+ - lib/MrMurano/commands/init.rb
223
225
  - lib/MrMurano/commands/keystore.rb
224
226
  - lib/MrMurano/commands/logs.rb
225
227
  - lib/MrMurano/commands/product.rb
226
228
  - lib/MrMurano/commands/productCreate.rb
227
229
  - lib/MrMurano/commands/productDelete.rb
230
+ - lib/MrMurano/commands/productDevice.rb
228
231
  - lib/MrMurano/commands/productList.rb
229
232
  - lib/MrMurano/commands/productSpec.rb
230
233
  - lib/MrMurano/commands/productWrite.rb
@@ -238,6 +241,7 @@ files:
238
241
  - lib/MrMurano/commands/timeseries.rb
239
242
  - lib/MrMurano/commands/tsdb.rb
240
243
  - lib/MrMurano/commands/zshcomplete.erb
244
+ - lib/MrMurano/dir.rb
241
245
  - lib/MrMurano/hash.rb
242
246
  - lib/MrMurano/http.rb
243
247
  - lib/MrMurano/makePretty.rb
@@ -251,6 +255,8 @@ files:
251
255
  - spec/ProductBase_spec.rb
252
256
  - spec/ProductContent_spec.rb
253
257
  - spec/ProductResources_spec.rb
258
+ - spec/Product_1P_Device_spec.rb
259
+ - spec/Product_1P_RPC_spec.rb
254
260
  - spec/Product_spec.rb
255
261
  - spec/Solution-Cors_spec.rb
256
262
  - spec/Solution-ServiceConfig_spec.rb
@@ -300,6 +306,8 @@ test_files:
300
306
  - spec/ProductBase_spec.rb
301
307
  - spec/ProductContent_spec.rb
302
308
  - spec/ProductResources_spec.rb
309
+ - spec/Product_1P_Device_spec.rb
310
+ - spec/Product_1P_RPC_spec.rb
303
311
  - spec/Product_spec.rb
304
312
  - spec/Solution-Cors_spec.rb
305
313
  - spec/Solution-ServiceConfig_spec.rb