MuranoCLI 2.0.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.
Files changed (151) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +28 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +21 -0
  5. data/Gemfile +27 -0
  6. data/LICENSE.txt +19 -0
  7. data/MuranoCLI.gemspec +50 -0
  8. data/MuranoCLI.iss +50 -0
  9. data/README.markdown +208 -0
  10. data/Rakefile +188 -0
  11. data/TODO.taskpaper +122 -0
  12. data/bin/mr +8 -0
  13. data/bin/murano +84 -0
  14. data/docs/demo.md +109 -0
  15. data/lib/MrMurano/Account.rb +211 -0
  16. data/lib/MrMurano/Config-Migrate.rb +47 -0
  17. data/lib/MrMurano/Config.rb +286 -0
  18. data/lib/MrMurano/Mock.rb +63 -0
  19. data/lib/MrMurano/Product-1P-Device.rb +145 -0
  20. data/lib/MrMurano/Product-Resources.rb +195 -0
  21. data/lib/MrMurano/Product.rb +358 -0
  22. data/lib/MrMurano/ProjectFile.rb +349 -0
  23. data/lib/MrMurano/Solution-Cors.rb +46 -0
  24. data/lib/MrMurano/Solution-Endpoint.rb +177 -0
  25. data/lib/MrMurano/Solution-File.rb +150 -0
  26. data/lib/MrMurano/Solution-ServiceConfig.rb +140 -0
  27. data/lib/MrMurano/Solution-Services.rb +326 -0
  28. data/lib/MrMurano/Solution-Users.rb +129 -0
  29. data/lib/MrMurano/Solution.rb +59 -0
  30. data/lib/MrMurano/SubCmdGroupContext.rb +49 -0
  31. data/lib/MrMurano/SyncUpDown.rb +565 -0
  32. data/lib/MrMurano/commands/assign.rb +57 -0
  33. data/lib/MrMurano/commands/businessList.rb +45 -0
  34. data/lib/MrMurano/commands/completion.rb +152 -0
  35. data/lib/MrMurano/commands/config.rb +67 -0
  36. data/lib/MrMurano/commands/content.rb +130 -0
  37. data/lib/MrMurano/commands/cors.rb +30 -0
  38. data/lib/MrMurano/commands/domain.rb +17 -0
  39. data/lib/MrMurano/commands/gb.rb +33 -0
  40. data/lib/MrMurano/commands/init.rb +138 -0
  41. data/lib/MrMurano/commands/keystore.rb +157 -0
  42. data/lib/MrMurano/commands/logs.rb +78 -0
  43. data/lib/MrMurano/commands/mock.rb +63 -0
  44. data/lib/MrMurano/commands/password.rb +88 -0
  45. data/lib/MrMurano/commands/postgresql.rb +41 -0
  46. data/lib/MrMurano/commands/product.rb +14 -0
  47. data/lib/MrMurano/commands/productCreate.rb +39 -0
  48. data/lib/MrMurano/commands/productDelete.rb +33 -0
  49. data/lib/MrMurano/commands/productDevice.rb +84 -0
  50. data/lib/MrMurano/commands/productDeviceIdCmds.rb +86 -0
  51. data/lib/MrMurano/commands/productList.rb +45 -0
  52. data/lib/MrMurano/commands/productWrite.rb +27 -0
  53. data/lib/MrMurano/commands/show.rb +80 -0
  54. data/lib/MrMurano/commands/solution.rb +14 -0
  55. data/lib/MrMurano/commands/solutionCreate.rb +39 -0
  56. data/lib/MrMurano/commands/solutionDelete.rb +34 -0
  57. data/lib/MrMurano/commands/solutionList.rb +45 -0
  58. data/lib/MrMurano/commands/status.rb +92 -0
  59. data/lib/MrMurano/commands/sync.rb +60 -0
  60. data/lib/MrMurano/commands/timeseries.rb +115 -0
  61. data/lib/MrMurano/commands/tsdb.rb +271 -0
  62. data/lib/MrMurano/commands/usage.rb +23 -0
  63. data/lib/MrMurano/commands/zshcomplete.erb +112 -0
  64. data/lib/MrMurano/commands.rb +32 -0
  65. data/lib/MrMurano/hash.rb +20 -0
  66. data/lib/MrMurano/http.rb +153 -0
  67. data/lib/MrMurano/makePretty.rb +75 -0
  68. data/lib/MrMurano/schema/pf-v1.0.0.yaml +114 -0
  69. data/lib/MrMurano/schema/sf-v0.2.0.yaml +77 -0
  70. data/lib/MrMurano/schema/sf-v0.3.0.yaml +78 -0
  71. data/lib/MrMurano/template/mock.erb +9 -0
  72. data/lib/MrMurano/template/projectFile.murano.erb +81 -0
  73. data/lib/MrMurano/verbosing.rb +99 -0
  74. data/lib/MrMurano/version.rb +4 -0
  75. data/lib/MrMurano.rb +20 -0
  76. data/spec/Account-Passwords_spec.rb +242 -0
  77. data/spec/Account_spec.rb +272 -0
  78. data/spec/ConfigFile_spec.rb +50 -0
  79. data/spec/ConfigMigrate_spec.rb +89 -0
  80. data/spec/Config_spec.rb +409 -0
  81. data/spec/Http_spec.rb +204 -0
  82. data/spec/MakePretties_spec.rb +118 -0
  83. data/spec/Mock_spec.rb +53 -0
  84. data/spec/ProductBase_spec.rb +113 -0
  85. data/spec/ProductContent_spec.rb +162 -0
  86. data/spec/ProductResources_spec.rb +329 -0
  87. data/spec/Product_1P_Device_spec.rb +202 -0
  88. data/spec/Product_1P_RPC_spec.rb +175 -0
  89. data/spec/Product_spec.rb +153 -0
  90. data/spec/ProjectFile_spec.rb +324 -0
  91. data/spec/Solution-Cors_spec.rb +164 -0
  92. data/spec/Solution-Endpoint_spec.rb +581 -0
  93. data/spec/Solution-File_spec.rb +212 -0
  94. data/spec/Solution-ServiceConfig_spec.rb +202 -0
  95. data/spec/Solution-ServiceDevice_spec.rb +176 -0
  96. data/spec/Solution-ServiceEventHandler_spec.rb +385 -0
  97. data/spec/Solution-ServiceModules_spec.rb +465 -0
  98. data/spec/Solution-UsersRoles_spec.rb +207 -0
  99. data/spec/Solution_spec.rb +92 -0
  100. data/spec/SyncRoot_spec.rb +83 -0
  101. data/spec/SyncUpDown_spec.rb +495 -0
  102. data/spec/Verbosing_spec.rb +279 -0
  103. data/spec/_workspace.rb +27 -0
  104. data/spec/cmd_assign_spec.rb +51 -0
  105. data/spec/cmd_business_spec.rb +59 -0
  106. data/spec/cmd_common.rb +72 -0
  107. data/spec/cmd_config_spec.rb +68 -0
  108. data/spec/cmd_content_spec.rb +71 -0
  109. data/spec/cmd_cors_spec.rb +50 -0
  110. data/spec/cmd_device_spec.rb +96 -0
  111. data/spec/cmd_domain_spec.rb +32 -0
  112. data/spec/cmd_init_spec.rb +30 -0
  113. data/spec/cmd_keystore_spec.rb +97 -0
  114. data/spec/cmd_password_spec.rb +62 -0
  115. data/spec/cmd_status_spec.rb +239 -0
  116. data/spec/cmd_syncdown_spec.rb +86 -0
  117. data/spec/cmd_syncup_spec.rb +62 -0
  118. data/spec/cmd_usage_spec.rb +36 -0
  119. data/spec/fixtures/.mrmuranorc +9 -0
  120. data/spec/fixtures/ProjectFiles/invalid.yaml +9 -0
  121. data/spec/fixtures/ProjectFiles/only_meta.yaml +24 -0
  122. data/spec/fixtures/ProjectFiles/with_routes.yaml +27 -0
  123. data/spec/fixtures/SolutionFiles/0.2.0.json +20 -0
  124. data/spec/fixtures/SolutionFiles/0.2.0_invalid.json +18 -0
  125. data/spec/fixtures/SolutionFiles/0.2.json +21 -0
  126. data/spec/fixtures/SolutionFiles/0.3.0.json +20 -0
  127. data/spec/fixtures/SolutionFiles/0.3.0_invalid.json +19 -0
  128. data/spec/fixtures/SolutionFiles/0.3.json +20 -0
  129. data/spec/fixtures/SolutionFiles/basic.json +20 -0
  130. data/spec/fixtures/SolutionFiles/secret.json +6 -0
  131. data/spec/fixtures/configfile +9 -0
  132. data/spec/fixtures/dumped_config +42 -0
  133. data/spec/fixtures/mrmuranorc_deleted_bob +8 -0
  134. data/spec/fixtures/mrmuranorc_tool_bob +3 -0
  135. data/spec/fixtures/product_spec_files/example.exoline.spec.yaml +116 -0
  136. data/spec/fixtures/product_spec_files/example.murano.spec.yaml +14 -0
  137. data/spec/fixtures/product_spec_files/gwe.exoline.spec.yaml +21 -0
  138. data/spec/fixtures/product_spec_files/gwe.murano.spec.yaml +16 -0
  139. data/spec/fixtures/product_spec_files/lightbulb-no-state.yaml +11 -0
  140. data/spec/fixtures/product_spec_files/lightbulb.yaml +14 -0
  141. data/spec/fixtures/roles-three.yaml +11 -0
  142. data/spec/fixtures/syncable_content/assets/icon.png +0 -0
  143. data/spec/fixtures/syncable_content/assets/index.html +0 -0
  144. data/spec/fixtures/syncable_content/assets/js/script.js +0 -0
  145. data/spec/fixtures/syncable_content/modules/table_util.lua +58 -0
  146. data/spec/fixtures/syncable_content/routes/manyRoutes.lua +11 -0
  147. data/spec/fixtures/syncable_content/routes/singleRoute.lua +5 -0
  148. data/spec/fixtures/syncable_content/services/devdata.lua +18 -0
  149. data/spec/fixtures/syncable_content/services/timers.lua +4 -0
  150. data/spec/spec_helper.rb +119 -0
  151. metadata +498 -0
@@ -0,0 +1,57 @@
1
+ require 'terminal-table'
2
+
3
+ command 'assign list' do |c|
4
+ c.syntax = 'murano assign list [options]'
5
+ c.description = 'List the products that are assigned'
6
+ c.option '--idonly', 'Only return the ids'
7
+
8
+ c.action do |args, options|
9
+ sol = MrMurano::SC_Device.new
10
+
11
+ trigs = sol.showTriggers()
12
+ options.idonly = true if $cfg['business.id'].nil?
13
+
14
+ if options.idonly then
15
+ say trigs.join(' ')
16
+ else
17
+ acc = MrMurano::Account.new
18
+ products = acc.products
19
+ products.select!{|p| trigs.include? p[:modelId] }
20
+ if products.empty? then
21
+ say trigs.join(' ')
22
+ else
23
+ busy = products.map{|r| [r[:label], r[:modelId]]}
24
+ table = Terminal::Table.new :rows => busy, :headings => ['Label', 'ModelID']
25
+ say table
26
+ end
27
+ end
28
+ end
29
+ end
30
+ alias_command :assign, 'assign list'
31
+
32
+ command 'assign set' do |c|
33
+ c.syntax = 'murano assign set [product]'
34
+ c.description = 'Assign a product to a eventhandler'
35
+
36
+ c.action do |args, options|
37
+ sol = MrMurano::SC_Device.new
38
+
39
+ prname = args.shift
40
+ if prname.nil? then
41
+ prid = $cfg['product.id']
42
+ else
43
+ acc = MrMurano::Account.new
44
+ products = acc.products
45
+ products.select!{|p|
46
+ p[:label] == prname or p[:modelId] == prname or p[:pid] == prname
47
+ }
48
+ prid = products.map{|p| p[:modelId]}
49
+ end
50
+ raise "No product ID!" if prid.nil?
51
+ say "Assigning #{prid} to solution" if $cfg['tool.verbose']
52
+ sol.assignTriggers(prid) unless $cfg['tool.dry']
53
+ end
54
+
55
+ end
56
+
57
+ # vim: set ai et sw=2 ts=2 :
@@ -0,0 +1,45 @@
1
+ require 'MrMurano/Account'
2
+
3
+ command 'business list' do |c|
4
+ c.syntax = %{murano business list [options]}
5
+ c.description = %{List businesses}
6
+ c.option '--idonly', 'Only return the ids'
7
+ c.option '--[no-]all', 'Show all fields'
8
+ c.option '-o', '--output FILE', %{Download to file instead of STDOUT}
9
+
10
+ c.action do |args, options|
11
+ acc = MrMurano::Account.new
12
+ data = acc.businesses
13
+
14
+ io=nil
15
+ if options.output then
16
+ io = File.open(options.output, 'w')
17
+ end
18
+
19
+ if options.idonly then
20
+ headers = [:bizid]
21
+ data = data.map{|row| [row[:bizid]]}
22
+ elsif not options.all then
23
+ headers = [:bizid, :role, :name]
24
+ data = data.map{|r| [r[:bizid], r[:role], r[:name]]}
25
+ else
26
+ headers = data[0].keys
27
+ data = data.map{|r| headers.map{|h| r[h]}}
28
+ end
29
+
30
+ acc.outf(data, io) do |dd, ios|
31
+ if options.idonly then
32
+ ios.puts dd.join(' ')
33
+ else
34
+ acc.tabularize({
35
+ :headers=>headers.map{|h| h.to_s},
36
+ :rows=>dd
37
+ }, ios)
38
+ end
39
+ end
40
+ io.close unless io.nil?
41
+
42
+ end
43
+ end
44
+
45
+ # vim: set ai et sw=2 ts=2 :
@@ -0,0 +1,152 @@
1
+ require 'pp'
2
+ require 'erb'
3
+
4
+ class CompletionContext < ::Commander::HelpFormatter::Context
5
+ end
6
+
7
+ class ::Commander::Runner
8
+
9
+ # Not so sure this should go in Runner, but where else?
10
+
11
+ ##
12
+ # Change the '--[no-]foo' switch into '--no-foo' and '--foo'
13
+ def flatswitches(option)
14
+ # if there is a --[no-]foo format, break that into two switches.
15
+ option[:switches].map{ |switch|
16
+ switch = switch.sub(/\s.*$/,'') # drop argument spec if exists.
17
+ if switch =~ /\[no-\]/ then
18
+ [switch.sub(/\[no-\]/, ''), switch.gsub(/[\[\]]/,'')]
19
+ else
20
+ switch
21
+ end
22
+ }.flatten
23
+ end
24
+
25
+ ##
26
+ # If the switches take an argument, retun =
27
+ def takesArg(option, yes='=', no='')
28
+ if option[:switches].select { |switch| switch =~ /\s\S+$/ }.empty? then
29
+ no
30
+ else
31
+ yes
32
+ end
33
+ end
34
+
35
+ ##
36
+ # truncate the description of an option
37
+ def optionDesc(option)
38
+ option[:description].sub(/\n.*$/,'')
39
+ end
40
+
41
+ ##
42
+ # Get a tree of all commands and sub commands
43
+ def cmdTree
44
+ tree={}
45
+ @commands.sort.each do |name,cmd|
46
+ levels = name.split
47
+ pos = tree
48
+ levels.each do |step|
49
+ pos[step] = {} unless pos.has_key? step
50
+ pos = pos[step]
51
+ end
52
+ pos["\0cmd"] = cmd
53
+ end
54
+ tree
55
+ end
56
+
57
+ ##
58
+ # Get maximum depth of sub-commands.
59
+ def cmdMaxDepth
60
+ depth=0
61
+ @commands.sort.each do |name,cmd|
62
+ levels = name.split
63
+ depth = levels.count if levels.count > depth
64
+ end
65
+ depth
66
+ end
67
+
68
+ ##
69
+ # Alternate tree of sub-commands.
70
+ def cmdTreeB
71
+ tree={}
72
+ @commands.sort.each do |name,cmd|
73
+ levels = name.split
74
+ tree[levels.join(' ')] = {:cmd=>cmd}
75
+
76
+ # load parent.
77
+ left = levels[0..-2]
78
+ right = levels[-1]
79
+ key = left.join(' ')
80
+ tree[key] = {} unless tree.has_key? key
81
+ if tree[key].has_key?(:subs) then
82
+ tree[key][:subs] << right
83
+ else
84
+ tree[key][:subs] = [right]
85
+ end
86
+ end
87
+ tree
88
+ end
89
+
90
+ end
91
+
92
+ command :completion do |c|
93
+ c.syntax = %{murano completion}
94
+ c.summary = %{Generate a completion file}
95
+ c.description = %{For starts, this is zsh only. Because that is what I use.
96
+
97
+ eval "$(murano completion)"
98
+ or
99
+ murano completion > _murano
100
+ source _murano
101
+ }
102
+ c.option '--subs', 'List sub commands'
103
+ #c.option '--opts CMD', 'List options for subcommand'
104
+ #c.option '--gopts', 'List global options'
105
+
106
+ # Changing direction.
107
+ # Will poop out the file to be included as the completion script.
108
+
109
+ c.action do |args, options|
110
+
111
+ runner = ::Commander::Runner.instance
112
+
113
+ if options.gopts then
114
+ opts = runner.instance_variable_get(:@options)
115
+ pp opts.first
116
+ pp runner.takesArg(opts.first)
117
+ # opts.each do |o|
118
+ # puts runner.optionLine o, 'GlobalOption'
119
+ # end
120
+
121
+ elsif options.subs then
122
+ runner.instance_variable_get(:@commands).sort.each do |name,cmd|
123
+ #desc = cmd.instance_variable_get(:@summary) #.lines[0]
124
+ #say "#{name}:'#{desc}'"
125
+ say "#{name}"
126
+ end
127
+
128
+ elsif options.opts then
129
+ cmds = runner.instance_variable_get(:@commands)
130
+ cmd = cmds[options.opts]
131
+ pp cmd.syntax
132
+ # looking at OptionParser to help figure out what kind of params a switch
133
+ # gets. And hopefully derive a completer for it
134
+ # !!!!! OptionParser::Completion what is this?
135
+ opts = OptionParser.new
136
+ cmds[options.opts].options.each do |o|
137
+ pp opts.make_switch(o[:args])
138
+ end
139
+
140
+ else
141
+
142
+ tmpl=ERB.new(File.read(File.join(File.dirname(__FILE__), "zshcomplete.erb")), nil, '-<>')
143
+
144
+ pc = CompletionContext.new(runner)
145
+ puts tmpl.result(pc.get_binding)
146
+ end
147
+
148
+
149
+ end
150
+ end
151
+
152
+ # vim: set ai et sw=2 ts=2 :
@@ -0,0 +1,67 @@
1
+
2
+ command :config do |c|
3
+ c.syntax = %{murano config [options] <key> [<new value>]}
4
+ c.summary = %{Get and set options}
5
+ c.description = %{
6
+ You can get, set, or query config options with this command. All config
7
+ options are in a 'section.key' format. There is also a layer of scopes
8
+ that the keys can be saved in.
9
+
10
+ If section is left out, then key is assumed to be in the 'tool' section.
11
+
12
+ }
13
+
14
+ c.example %{See what the current combined config is}, 'murano config --dump'
15
+ c.example %{Query a value}, 'murano config solution.id'
16
+ c.example %{Set a new value; writing to the project config file}, 'murano config solution.id XXXXXXXX'
17
+ c.example %{Set a new value; writing to the user config file}, 'murano config --user user.name my@email.address'
18
+ c.example %{Unset a value in a configfile. (lower scopes will become visible when unset)},
19
+ 'murano config diff.cmd --unset'
20
+
21
+
22
+ c.option '--user', 'Use only the config file in $HOME (.mrmuranorc)'
23
+ c.option '--project', 'Use only the config file in the project (.mrmuranorc)'
24
+ c.option '--env', 'Use only the config file from $MR_CONFIGFILE'
25
+ c.option '--specified', 'Use only the config file from the --config option.'
26
+
27
+ c.option '--unset', 'Remove key from config file.'
28
+ c.option '--dump', 'Dump the current combined view of the config'
29
+
30
+ c.action do |args, options|
31
+
32
+ if options.dump then
33
+ puts $cfg.dump()
34
+ elsif args.count == 0 then
35
+ say_error "Need a config key"
36
+ elsif args.count == 1 and not options.unset then
37
+ options.default :user=>false, :project=>false,
38
+ :specified=>false, :env=>false
39
+
40
+ # For read, if no scopes, than all. Otherwise just those specified
41
+ scopes = []
42
+ scopes << :user if options.user
43
+ scopes << :project if options.project
44
+ scopes << :env if options.env
45
+ scopes << :specified if options.specified
46
+ scopes = MrMurano::Config::CFG_SCOPES if scopes.empty?
47
+
48
+ say $cfg.get(args[0], scopes)
49
+ else
50
+
51
+ options.default :user=>false, :project=>false,
52
+ :specified=>false, :env=>false
53
+ # For write, if scope is specified, only write to that scope.
54
+ scope = :project
55
+ scope = :user if options.user
56
+ scope = :project if options.project
57
+ scope = :env if options.env
58
+ scope = :specified if options.specified
59
+
60
+ args[1] = nil if options.unset
61
+ $cfg.set(args[0], args[1], scope)
62
+ end
63
+ end
64
+
65
+ end
66
+
67
+ # vim: set ai et sw=2 ts=2 :
@@ -0,0 +1,130 @@
1
+ require 'MrMurano/Product'
2
+
3
+ command :content do |c|
4
+ c.syntax = %{murano content}
5
+ c.summary = %{About Content Area}
6
+ c.description = %{This set of commands let you interact with the content area for a product.
7
+
8
+ This is where OTA data can be stored so that devices can easily download it.
9
+ }
10
+
11
+ c.action do |args, options|
12
+ ::Commander::UI.enable_paging
13
+ say MrMurano::SubCmdGroupHelp.new(c).get_help
14
+ end
15
+ end
16
+
17
+ command 'content list' do |c|
18
+ c.syntax = %{murano content list}
19
+ c.summary = %{List downloadable content for a product}
20
+ c.description = %{List downloadable content for a product
21
+
22
+ Data uploaded to a product's content area can be downloaded by devices using
23
+ the HTTP Device API.
24
+ }
25
+ c.action do |args, options|
26
+ prd = MrMurano::ProductContent.new
27
+ prd.outf prd.list
28
+ end
29
+ end
30
+
31
+ command 'content info' do |c|
32
+ c.syntax = %{murano content info <content id>}
33
+ c.summary = %{Show more info for a content item}
34
+ c.description = %{Show more info for a content item
35
+
36
+ Data uploaded to a product's content area can be downloaded by devices using
37
+ the HTTP Device API.
38
+ }
39
+ c.action do |args, options|
40
+ prd = MrMurano::ProductContent.new
41
+ if args[0].nil? then
42
+ prd.error "Missing <content id>"
43
+ else
44
+ prd.tabularize prd.info(args[0])
45
+ end
46
+ end
47
+ end
48
+
49
+ command 'content delete' do |c|
50
+ c.syntax = %{murano content delete <content id>}
51
+ c.summary = %{Delete a content item}
52
+ c.description = %{Delete a content item
53
+
54
+ Data uploaded to a product's content area can be downloaded by devices using
55
+ the HTTP Device API.
56
+ }
57
+ c.action do |args, options|
58
+ prd = MrMurano::ProductContent.new
59
+ if args[0].nil? then
60
+ prd.error "Missing <content id>"
61
+ else
62
+ ret = prd.remove(args[0])
63
+ prd.outf(ret) unless ret.nil? or ret.empty?
64
+ end
65
+ end
66
+ end
67
+
68
+ command 'content upload' do |c|
69
+ c.syntax = %{murano content upload <content id> <file>}
70
+ c.summary = %{Upload content}
71
+ c.description = %{Upload a content item
72
+
73
+ Data uploaded to a product's content area can be downloaded by devices using
74
+ the HTTP Device API.
75
+ }
76
+ c.option '--meta STRING', %{Add extra meta info to the content item}
77
+
78
+ c.action do |args, options|
79
+ options.default :meta=>' '
80
+ prd = MrMurano::ProductContent.new
81
+
82
+ if args[0].nil? then
83
+ prd.error "Missing <content id>"
84
+ elsif args[1].nil? then
85
+ prd.error "Missing <file>"
86
+ else
87
+
88
+ ret = prd.info(args[0])
89
+ if ret.nil? then
90
+ ret = prd.create(args[0], options.meta)
91
+ prd.outf(ret) unless ret.nil? or ret.empty?
92
+ end
93
+
94
+ ret = prd.upload(args[0], args[1])
95
+ prd.outf(ret) unless ret.nil? or ret.empty?
96
+ end
97
+ end
98
+ end
99
+
100
+ command 'content download' do |c|
101
+ c.syntax = %{murano content download <content id>}
102
+ c.summary = %{Download a content item}
103
+ c.description = %{Download a content item
104
+
105
+ Data uploaded to a product's content area can be downloaded by devices using
106
+ the HTTP Device API.
107
+ }
108
+ c.option '-o','--output FILE',%{save to this file}
109
+ c.action do |args, options|
110
+ prd = MrMurano::ProductContent.new
111
+ if args[0].nil? then
112
+ prd.error "Missing <content id>"
113
+ else
114
+
115
+ if options.output.nil? then
116
+ prd.download(args[0]) # to stdout
117
+ else
118
+ outFile = Pathname.new(options.output)
119
+ outFile.open('w') do |io|
120
+ prd.download(args[0]) do |chunk|
121
+ io << chunk
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+
130
+ # vim: set ai et sw=2 ts=2 :