MrMurano 1.8.1 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
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