MuranoCLI 2.2.4 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (159) hide show
  1. checksums.yaml +4 -4
  2. data/.agignore +3 -0
  3. data/.gitignore +18 -1
  4. data/.rubocop.yml +222 -0
  5. data/.trustme.sh +185 -0
  6. data/.trustme.vim +24 -0
  7. data/Gemfile +23 -4
  8. data/LICENSE.txt +1 -1
  9. data/MuranoCLI.gemspec +43 -8
  10. data/README.markdown +9 -11
  11. data/Rakefile +187 -143
  12. data/TODO.taskpaper +2 -2
  13. data/bin/murano +51 -52
  14. data/docs/basic_example.rst +436 -0
  15. data/docs/completions/murano_completion-bash +3484 -0
  16. data/docs/demo.md +32 -32
  17. data/docs/develop.rst +391 -0
  18. data/lib/MrMurano.rb +21 -7
  19. data/lib/MrMurano/Account.rb +159 -174
  20. data/lib/MrMurano/Business.rb +381 -0
  21. data/lib/MrMurano/Config-Migrate.rb +32 -26
  22. data/lib/MrMurano/Config.rb +407 -128
  23. data/lib/MrMurano/Content.rb +191 -0
  24. data/lib/MrMurano/Gateway.rb +489 -0
  25. data/lib/MrMurano/Keystore.rb +48 -0
  26. data/lib/MrMurano/Passwords.rb +103 -0
  27. data/lib/MrMurano/ProjectFile.rb +121 -79
  28. data/lib/MrMurano/ReCommander.rb +114 -10
  29. data/lib/MrMurano/Setting.rb +90 -0
  30. data/lib/MrMurano/Solution-ServiceConfig.rb +89 -45
  31. data/lib/MrMurano/Solution-Services.rb +461 -166
  32. data/lib/MrMurano/Solution-Users.rb +70 -31
  33. data/lib/MrMurano/Solution.rb +372 -13
  34. data/lib/MrMurano/SolutionId.rb +73 -0
  35. data/lib/MrMurano/SyncRoot.rb +137 -0
  36. data/lib/MrMurano/SyncUpDown.rb +594 -284
  37. data/lib/MrMurano/Webservice-Cors.rb +71 -0
  38. data/lib/MrMurano/Webservice-Endpoint.rb +234 -0
  39. data/lib/MrMurano/Webservice-File.rb +193 -0
  40. data/lib/MrMurano/Webservice.rb +51 -0
  41. data/lib/MrMurano/commands.rb +18 -15
  42. data/lib/MrMurano/commands/business.rb +300 -6
  43. data/lib/MrMurano/commands/completion-bash.erb +166 -0
  44. data/lib/MrMurano/commands/{zshcomplete.erb → completion-zsh.erb} +0 -0
  45. data/lib/MrMurano/commands/completion.rb +76 -39
  46. data/lib/MrMurano/commands/config.rb +108 -44
  47. data/lib/MrMurano/commands/content.rb +115 -72
  48. data/lib/MrMurano/commands/cors.rb +29 -14
  49. data/lib/MrMurano/commands/devices.rb +286 -0
  50. data/lib/MrMurano/commands/domain.rb +52 -12
  51. data/lib/MrMurano/commands/gb.rb +24 -9
  52. data/lib/MrMurano/commands/globals.rb +64 -0
  53. data/lib/MrMurano/commands/init.rb +377 -155
  54. data/lib/MrMurano/commands/keystore.rb +92 -82
  55. data/lib/MrMurano/commands/link.rb +300 -0
  56. data/lib/MrMurano/commands/login.rb +74 -11
  57. data/lib/MrMurano/commands/logs.rb +63 -32
  58. data/lib/MrMurano/commands/mock.rb +57 -29
  59. data/lib/MrMurano/commands/password.rb +57 -39
  60. data/lib/MrMurano/commands/postgresql.rb +127 -94
  61. data/lib/MrMurano/commands/settings.rb +203 -0
  62. data/lib/MrMurano/commands/show.rb +79 -38
  63. data/lib/MrMurano/commands/solution.rb +423 -5
  64. data/lib/MrMurano/commands/solution_picker.rb +547 -0
  65. data/lib/MrMurano/commands/status.rb +195 -61
  66. data/lib/MrMurano/commands/sync.rb +78 -39
  67. data/lib/MrMurano/commands/timeseries.rb +71 -55
  68. data/lib/MrMurano/commands/tsdb.rb +113 -87
  69. data/lib/MrMurano/commands/usage.rb +57 -15
  70. data/lib/MrMurano/hash.rb +100 -10
  71. data/lib/MrMurano/http.rb +187 -43
  72. data/lib/MrMurano/makePretty.rb +16 -14
  73. data/lib/MrMurano/optparse.rb +2178 -0
  74. data/lib/MrMurano/progress.rb +138 -0
  75. data/lib/MrMurano/schema/resource-v1.0.0.yaml +32 -0
  76. data/lib/MrMurano/template/projectFile.murano.erb +16 -13
  77. data/lib/MrMurano/verbosing.rb +166 -29
  78. data/lib/MrMurano/version.rb +30 -1
  79. data/spec/Account-Passwords_spec.rb +21 -4
  80. data/spec/Account_spec.rb +69 -146
  81. data/spec/Business_spec.rb +290 -0
  82. data/spec/ConfigFile_spec.rb +1 -0
  83. data/spec/ConfigMigrate_spec.rb +12 -8
  84. data/spec/Config_spec.rb +40 -34
  85. data/spec/Content_spec.rb +363 -0
  86. data/spec/GatewayBase_spec.rb +54 -0
  87. data/spec/GatewayDevice_spec.rb +321 -0
  88. data/spec/GatewayResource_spec.rb +266 -0
  89. data/spec/GatewaySettings_spec.rb +120 -0
  90. data/spec/Http_spec.rb +18 -8
  91. data/spec/Mock_spec.rb +2 -2
  92. data/spec/ProjectFile_spec.rb +25 -14
  93. data/spec/Setting_spec.rb +110 -0
  94. data/spec/Solution-ServiceConfig_spec.rb +44 -5
  95. data/spec/Solution-ServiceEventHandler_spec.rb +23 -14
  96. data/spec/Solution-ServiceModules_spec.rb +47 -37
  97. data/spec/Solution-UsersRoles_spec.rb +10 -8
  98. data/spec/Solution_spec.rb +17 -8
  99. data/spec/SyncRoot_spec.rb +46 -20
  100. data/spec/SyncUpDown_spec.rb +437 -201
  101. data/spec/Verbosing_spec.rb +12 -4
  102. data/spec/{Solution-Cors_spec.rb → Webservice-Cors_spec.rb} +23 -20
  103. data/spec/{Solution-Endpoint_spec.rb → Webservice-Endpoint_spec.rb} +43 -41
  104. data/spec/{Solution-File_spec.rb → Webservice-File_spec.rb} +44 -33
  105. data/spec/Webservice-Setting_spec.rb +89 -0
  106. data/spec/_workspace.rb +4 -4
  107. data/spec/cmd_business_spec.rb +9 -4
  108. data/spec/cmd_common.rb +44 -1
  109. data/spec/cmd_content_spec.rb +43 -17
  110. data/spec/cmd_cors_spec.rb +4 -4
  111. data/spec/cmd_device_spec.rb +61 -16
  112. data/spec/cmd_domain_spec.rb +29 -6
  113. data/spec/cmd_init_spec.rb +281 -126
  114. data/spec/cmd_keystore_spec.rb +3 -3
  115. data/spec/cmd_link_spec.rb +98 -0
  116. data/spec/cmd_password_spec.rb +1 -1
  117. data/spec/cmd_setting_application_spec.rb +260 -0
  118. data/spec/cmd_setting_product_spec.rb +220 -0
  119. data/spec/cmd_status_spec.rb +223 -114
  120. data/spec/cmd_syncdown_spec.rb +115 -35
  121. data/spec/cmd_syncup_spec.rb +68 -15
  122. data/spec/cmd_usage_spec.rb +35 -8
  123. data/spec/fixtures/dumped_config +6 -4
  124. data/spec/fixtures/gateway_resource_files/resources.notyaml +12 -0
  125. data/spec/fixtures/gateway_resource_files/resources.yaml +13 -0
  126. data/spec/fixtures/gateway_resource_files/resources_invalid.yaml +13 -0
  127. data/spec/fixtures/mrmuranorc_deleted_bob +0 -2
  128. data/spec/fixtures/product_spec_files/lightbulb.yaml +20 -13
  129. data/spec/fixtures/{syncable_content → syncable_conflict}/services/devdata.lua +1 -1
  130. data/spec/fixtures/{syncable_content → syncable_conflict}/services/timers.lua +0 -0
  131. data/spec/spec_helper.rb +5 -0
  132. metadata +262 -171
  133. data/bin/mr +0 -8
  134. data/lib/MrMurano/Product-1P-Device.rb +0 -145
  135. data/lib/MrMurano/Product-Resources.rb +0 -205
  136. data/lib/MrMurano/Product.rb +0 -358
  137. data/lib/MrMurano/Solution-Cors.rb +0 -47
  138. data/lib/MrMurano/Solution-Endpoint.rb +0 -191
  139. data/lib/MrMurano/Solution-File.rb +0 -166
  140. data/lib/MrMurano/commands/assign.rb +0 -57
  141. data/lib/MrMurano/commands/businessList.rb +0 -45
  142. data/lib/MrMurano/commands/product.rb +0 -14
  143. data/lib/MrMurano/commands/productCreate.rb +0 -39
  144. data/lib/MrMurano/commands/productDelete.rb +0 -33
  145. data/lib/MrMurano/commands/productDevice.rb +0 -87
  146. data/lib/MrMurano/commands/productDeviceIdCmds.rb +0 -89
  147. data/lib/MrMurano/commands/productList.rb +0 -45
  148. data/lib/MrMurano/commands/productWrite.rb +0 -27
  149. data/lib/MrMurano/commands/solutionCreate.rb +0 -41
  150. data/lib/MrMurano/commands/solutionDelete.rb +0 -34
  151. data/lib/MrMurano/commands/solutionList.rb +0 -45
  152. data/spec/ProductBase_spec.rb +0 -113
  153. data/spec/ProductContent_spec.rb +0 -162
  154. data/spec/ProductResources_spec.rb +0 -329
  155. data/spec/Product_1P_Device_spec.rb +0 -202
  156. data/spec/Product_1P_RPC_spec.rb +0 -175
  157. data/spec/Product_spec.rb +0 -153
  158. data/spec/Solution-ServiceDevice_spec.rb +0 -176
  159. data/spec/cmd_assign_spec.rb +0 -51
@@ -1,31 +1,50 @@
1
- require 'pp'
1
+ # Last Modified: 2017.08.16 /coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ # Copyright © 2016-2017 Exosite LLC.
5
+ # License: MIT. See LICENSE.txt.
6
+ # vim:tw=0:ts=2:sw=2:et:ai
7
+
2
8
  require 'erb'
9
+ require 'pp'
10
+ require 'MrMurano/ReCommander'
11
+
12
+ # USAGE: See: lib/MrMurano/commands.rb
3
13
 
4
14
  class CompletionContext < ::Commander::HelpFormatter::Context
5
15
  end
6
16
 
17
+ # rubocop:disable Style/ClassAndModuleChildren
18
+ # "Use nested module/class definitions instead of compact style."
19
+ # except that nested style (class [::]Commander\nclass Runner)
20
+ # does not work.
7
21
  class ::Commander::Runner
8
-
9
22
  # Not so sure this should go in Runner, but where else?
10
23
 
24
+ # rubocop:disable Style/MethodName "Use snake_case for method names."
25
+
26
+ # The shells for which we have completion templates.
27
+ SHELL_TYPES = %i[bash zsh].freeze
28
+
11
29
  ##
12
30
  # Change the '--[no-]foo' switch into '--no-foo' and '--foo'
13
31
  def flatswitches(option)
14
32
  # 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(/[\[\]]/,'')]
33
+ switches = option[:switches].map do |switch|
34
+ switch = switch.sub(/\s.*$/, '') # drop argument spec if exists.
35
+ if switch =~ /\[no-\]/
36
+ [switch.sub(/\[no-\]/, ''), switch.gsub(/[\[\]]/, '')]
19
37
  else
20
38
  switch
21
39
  end
22
- }.flatten
40
+ end
41
+ switches.flatten
23
42
  end
24
43
 
25
44
  ##
26
- # If the switches take an argument, retun =
45
+ # If the switches take an argument, return =
27
46
  def takesArg(option, yes='=', no='')
28
- if option[:switches].select { |switch| switch =~ /\s\S+$/ }.empty? then
47
+ if option[:switches].select { |switch| switch =~ /\s\S+$/ }.empty?
29
48
  no
30
49
  else
31
50
  yes
@@ -35,18 +54,18 @@ class ::Commander::Runner
35
54
  ##
36
55
  # truncate the description of an option
37
56
  def optionDesc(option)
38
- option[:description].sub(/\n.*$/,'')
57
+ option[:description].sub(/\n.*$/, '')
39
58
  end
40
59
 
41
60
  ##
42
61
  # Get a tree of all commands and sub commands
43
62
  def cmdTree
44
- tree={}
45
- @commands.sort.each do |name,cmd|
63
+ tree = {}
64
+ @commands.sort.each do |name, cmd|
46
65
  levels = name.split
47
66
  pos = tree
48
67
  levels.each do |step|
49
- pos[step] = {} unless pos.has_key? step
68
+ pos[step] = {} unless pos.key? step
50
69
  pos = pos[step]
51
70
  end
52
71
  pos["\0cmd"] = cmd
@@ -57,8 +76,8 @@ class ::Commander::Runner
57
76
  ##
58
77
  # Get maximum depth of sub-commands.
59
78
  def cmdMaxDepth
60
- depth=0
61
- @commands.sort.each do |name,cmd|
79
+ depth = 0
80
+ @commands.sort.each do |name, _cmd|
62
81
  levels = name.split
63
82
  depth = levels.count if levels.count > depth
64
83
  end
@@ -68,17 +87,17 @@ class ::Commander::Runner
68
87
  ##
69
88
  # Alternate tree of sub-commands.
70
89
  def cmdTreeB
71
- tree={}
72
- @commands.sort.each do |name,cmd|
90
+ tree = {}
91
+ @commands.sort.each do |name, cmd|
73
92
  levels = name.split
74
- tree[levels.join(' ')] = {:cmd=>cmd}
93
+ tree[levels.join(' ')] = { cmd: cmd }
75
94
 
76
95
  # load parent.
77
96
  left = levels[0..-2]
78
97
  right = levels[-1]
79
98
  key = left.join(' ')
80
- tree[key] = {} unless tree.has_key? key
81
- if tree[key].has_key?(:subs) then
99
+ tree[key] = {} unless tree.key? key
100
+ if tree[key].key?(:subs)
82
101
  tree[key][:subs] << right
83
102
  else
84
103
  tree[key][:subs] = [right]
@@ -86,31 +105,44 @@ class ::Commander::Runner
86
105
  end
87
106
  tree
88
107
  end
89
-
90
108
  end
91
109
 
92
110
  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.
111
+ c.syntax = %(murano completion)
112
+ c.summary = %(Generate a completion file)
113
+ c.description = %(
114
+ Create a Tab completion file for either the Bash or Z shell.
115
+
116
+ E.g.,
117
+
118
+ eval "$(murano completion)"
96
119
 
97
- eval "$(murano completion)"
98
120
  or
99
- murano completion > _murano
100
- source _murano
101
- }
121
+
122
+ murano completion > _murano
123
+ source _murano
124
+ ).strip
125
+ c.project_not_required = true
126
+
102
127
  c.option '--subs', 'List sub commands'
103
128
  #c.option '--opts CMD', 'List options for subcommand'
104
129
  #c.option '--gopts', 'List global options'
130
+ c.option(
131
+ '--shell TYPE',
132
+ Commander::Runner::SHELL_TYPES,
133
+ %(Shell flavor of output (default: Bash))
134
+ )
105
135
 
106
136
  # Changing direction.
107
137
  # Will poop out the file to be included as the completion script.
108
138
 
109
139
  c.action do |args, options|
140
+ c.verify_arg_count!(args)
141
+ options.default shell: :bash
110
142
 
111
143
  runner = ::Commander::Runner.instance
112
144
 
113
- if options.gopts then
145
+ if options.gopts
114
146
  opts = runner.instance_variable_get(:@options)
115
147
  pp opts.first
116
148
  pp runner.takesArg(opts.first)
@@ -118,14 +150,14 @@ source _murano
118
150
  # puts runner.optionLine o, 'GlobalOption'
119
151
  # end
120
152
 
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]
153
+ elsif options.subs
154
+ runner.instance_variable_get(:@commands).sort.each do |name, _cmd|
155
+ #desc = _cmd.instance_variable_get(:@summary) #.lines[0]
124
156
  #say "#{name}:'#{desc}'"
125
- say "#{name}"
157
+ say name.to_s
126
158
  end
127
159
 
128
- elsif options.opts then
160
+ elsif options.opts
129
161
  cmds = runner.instance_variable_get(:@commands)
130
162
  cmd = cmds[options.opts]
131
163
  pp cmd.syntax
@@ -138,15 +170,20 @@ source _murano
138
170
  end
139
171
 
140
172
  else
141
-
142
- tmpl=ERB.new(File.read(File.join(File.dirname(__FILE__), "zshcomplete.erb")), nil, '-<>')
173
+ case options.shell
174
+ when :bash
175
+ cmpltn_tmplt = 'completion-bash.erb'
176
+ when :zsh
177
+ cmpltn_tmplt = 'completion-zsh.erb'
178
+ else
179
+ MrMurano::Verbose.error "Impossible shell option specified: #{options.shell}"
180
+ exit 2
181
+ end
182
+ tmpl = ERB.new(File.read(File.join(File.dirname(__FILE__), cmpltn_tmplt)), nil, '-<>')
143
183
 
144
184
  pc = CompletionContext.new(runner)
145
185
  puts tmpl.result(pc.get_binding)
146
186
  end
147
-
148
-
149
187
  end
150
188
  end
151
189
 
152
- # vim: set ai et sw=2 ts=2 :
@@ -1,23 +1,54 @@
1
+ # Last Modified: 2017.08.16 /coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ # Copyright © 2016-2017 Exosite LLC.
5
+ # License: MIT. See LICENSE.txt.
6
+ # vim:tw=0:ts=2:sw=2:et:ai
7
+
8
+ require 'MrMurano/ReCommander'
1
9
 
2
10
  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.
11
+ c.syntax = %(murano config [--options] [<key> [<new value>]])
12
+ c.summary = %(Get and set options)
13
+ c.description = %(
14
+ Get, set, or query config options.
15
+
16
+ All config options are in a 'section.key' format.
17
+
18
+ There is also a layer of scopes that the keys can be saved in.
19
+
20
+ If section is left out, then key is assumed to be in the 'tool' section.
21
+ ).strip
22
+
23
+ c.example %(
24
+ View the combined config
25
+ ).strip, 'murano config --dump'
26
+
27
+ c.example %(
28
+ View the config and path for each config file
29
+ ).strip, 'murano config --locations'
9
30
 
10
- If section is left out, then key is assumed to be in the 'tool' section.
31
+ c.example %(
32
+ Query a value
33
+ ).strip, 'murano config application.id'
11
34
 
12
- }
35
+ c.example %(
36
+ Show all ID values
37
+ ).strip, %(murano config '*.id')
13
38
 
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'
39
+ c.example %(
40
+ Set a new value, which writes to the project config file
41
+ ).strip, 'murano config application.id XXXXXXXX'
20
42
 
43
+ c.example %(
44
+ Set a new value, and write it to the user config file
45
+ ).strip, 'murano config user.name my@email.address --user'
46
+
47
+ c.example %(
48
+ Unset a value. If the value is set in multiple config files,
49
+ # this unsets it from the outermost config file and unmasks
50
+ # a value set in a lower scope
51
+ ).strip, 'murano config diff.cmd --unset'
21
52
 
22
53
  c.option '--user', 'Use only the config file in $HOME (.mrmuranorc)'
23
54
  c.option '--project', 'Use only the config file in the project (.mrmuranorc)'
@@ -26,42 +57,75 @@ command :config do |c|
26
57
 
27
58
  c.option '--unset', 'Remove key from config file.'
28
59
  c.option '--dump', 'Dump the current combined view of the config'
60
+ c.option '--locations', 'List the locations of all known configs'
61
+
62
+ c.project_not_required = true
29
63
 
30
64
  c.action do |args, options|
65
+ c.verify_arg_count!(args, 2)
66
+ options.default(
67
+ user: false,
68
+ project: false,
69
+ env: false,
70
+ specified: false,
71
+ unset: false,
72
+ dump: false,
73
+ locations: false,
74
+ )
31
75
 
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)
76
+ if options.dump
77
+ puts $cfg.dump
78
+ elsif options.locations
79
+ puts 'This list is ordered. The first value found for a key is the value used.'
80
+ puts $cfg.locations
81
+ elsif args.count.zero?
82
+ say_error 'Need a config key'
49
83
  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)
84
+ scope = get_scope_from_options(options)
85
+ if args.count == 1 && !options.unset
86
+ # For read, if no scopes, than all. Otherwise just those specified
87
+ scopes = []
88
+ scopes << scope unless scope.nil?
89
+ scopes = MrMurano::Config::CFG_SCOPES if scopes.empty?
90
+ is_wild = $cfg.wild?(args[0])
91
+ if !is_wild
92
+ puts $cfg.get(args[0], scopes)
93
+ else
94
+ kvals = $cfg.get_wild(args[0], scopes)
95
+ # LATER/2017-08-16: Honor --json option.
96
+ kvals.each { |kv| puts %(#{kv[0]} => "#{kv[1]}" (#{kv[2]})) }
97
+ end
98
+ else
99
+ # For write, if scope is specified, only write to that scope.
100
+ scope = :project if scope.nil?
101
+ args[1] = nil if options.unset
102
+ $cfg.set(args[0], args[1], scope)
103
+ end
62
104
  end
63
105
  end
64
106
 
107
+ def get_scope_from_options(options)
108
+ num_scopes = verify_scope_options!(options)
109
+ return nil if num_scopes.zero?
110
+ scope = nil
111
+ scope = :user if options.user
112
+ scope = :project if options.project
113
+ scope = :env if options.env
114
+ scope = :specified if options.specified
115
+ scope
116
+ end
117
+
118
+ def verify_scope_options!(options)
119
+ num_scopes = 0
120
+ num_scopes += 1 if options.user
121
+ num_scopes += 1 if options.project
122
+ num_scopes += 1 if options.env
123
+ num_scopes += 1 if options.specified
124
+ return num_scopes unless num_scopes > 1
125
+ MrMurano::Verbose.error(
126
+ 'Ambiguous: Please specify only one of --user, --project, --env, or --specified'
127
+ )
128
+ exit 1
129
+ end
65
130
  end
66
131
 
67
- # vim: set ai et sw=2 ts=2 :
@@ -1,130 +1,173 @@
1
- require 'MrMurano/Product'
1
+ # Last Modified: 2017.08.16 /coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ # Copyright © 2016-2017 Exosite LLC.
5
+ # License: MIT. See LICENSE.txt.
6
+ # vim:tw=0:ts=2:sw=2:et:ai
7
+
8
+ require 'MrMurano/Content'
9
+ require 'MrMurano/ReCommander'
2
10
 
3
11
  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.
12
+ c.syntax = %(murano content)
13
+ c.summary = %(About Content Area)
14
+ c.description = %(
15
+ This set of commands let you interact with the content area for a product.
7
16
 
8
17
  This is where OTA data can be stored so that devices can easily download it.
9
- }
18
+ ).strip
19
+ c.project_not_required = true
10
20
 
11
- c.action do |args, options|
21
+ c.action do |_args, _options|
12
22
  ::Commander::UI.enable_paging
13
23
  say MrMurano::SubCmdGroupHelp.new(c).get_help
14
24
  end
15
25
  end
16
26
 
17
27
  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
28
+ c.syntax = %(murano content list)
29
+ c.summary = %(List downloadable content for a product)
30
+ c.description = %(
31
+ List downloadable content for a product.
21
32
 
22
33
  Data uploaded to a product's content area can be downloaded by devices using
23
34
  the HTTP Device API.
24
- }
35
+ ).strip
36
+
37
+ c.option '-l', '--long', %(Include more info for each file)
38
+
25
39
  c.action do |args, options|
26
- prd = MrMurano::ProductContent.new
27
- prd.outf prd.list
40
+ c.verify_arg_count!(args)
41
+
42
+ prd = MrMurano::Content::Base.new
43
+
44
+ MrMurano::Verbose.whirly_start 'Looking for content...'
45
+ items = prd.list
46
+ exit 2 if items.nil?
47
+ MrMurano::Verbose.whirly_stop
48
+ if !items.empty?
49
+ prd.outf(items) do |dd, ios|
50
+ if options.long
51
+ headers = %i[Name Size 'Last Modified' MIME]
52
+ rows = dd.map { |d| [d[:id], d[:length], d[:last_modified], d[:type]] }
53
+ else
54
+ headers = %i[Name Size]
55
+ rows = dd.map { |d| [d[:id], d[:length]] }
56
+ end
57
+ prd.tabularize({ headers: headers, rows: rows }, ios)
58
+ end
59
+ else
60
+ prd.warning 'Did not find any content'
61
+ end
28
62
  end
29
63
  end
30
64
 
31
65
  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
66
+ c.syntax = %(murano content info <content name>)
67
+ c.summary = %(Show more info for a content item)
68
+ c.description = %(
69
+ Show more info for a content item.
35
70
 
36
71
  Data uploaded to a product's content area can be downloaded by devices using
37
72
  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])
73
+ ).strip
74
+
75
+ c.action do |args, _options|
76
+ c.verify_arg_count!(args, 1, ['Missing <content name>'])
77
+ prd = MrMurano::Content::Base.new
78
+ info = prd.info(args[0])
79
+ prd.outf(info) do |dd, ios|
80
+ ios.puts Hash.transform_keys_to_strings(dd).to_yaml
45
81
  end
46
82
  end
47
83
  end
48
84
 
49
85
  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
86
+ c.syntax = %(murano content delete <content name>)
87
+ c.summary = %(Delete a content item)
88
+ c.description = %(
89
+ Delete a content item.
53
90
 
54
91
  Data uploaded to a product's content area can be downloaded by devices using
55
92
  the HTTP Device API.
56
- }
93
+ ).strip
94
+
95
+ c.option('-y', '--[no-]yes', %(Answer "yes" to all prompts and run non-interactively))
96
+
57
97
  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
98
+ c.verify_arg_count!(args, 1, ['Missing <content name>'])
99
+ prd = MrMurano::Content::Base.new
100
+ prd.cmd_confirm_delete!(args[0], options.yes, 'abort!')
101
+ ret = prd.remove(args[0])
102
+ prd.outf(ret) if !ret.nil? && ret.count > 1
65
103
  end
66
104
  end
67
105
 
68
106
  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
107
+ tags = {}
108
+ c.syntax = %(murano content upload <file>)
109
+ c.summary = %(Upload content)
110
+ c.description = %(
111
+ Upload a content item.
72
112
 
73
113
  Data uploaded to a product's content area can be downloaded by devices using
74
114
  the HTTP Device API.
75
- }
76
- c.option '--meta STRING', %{Add extra meta info to the content item}
115
+ ).strip
116
+
117
+ c.option('--tags KEY=VALUE', %(Add extra meta info to the content item)) do |ec|
118
+ key, value = ec.split('=', 2)
119
+ # a=b :> ["a","b"]
120
+ # a= :> ["a",""]
121
+ # a :> ["a"]
122
+ raise "Bad tag key '#{param}'" if key.to_s.empty?
123
+ raise "Bad tag value '#{param}'" if value.to_s.empty?
124
+ key = key.downcase if key.casecmp('name').zero?
125
+ tags[key] = value
126
+ end
77
127
 
78
128
  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
129
+ c.verify_arg_count!(args, 1, ['Missing <file>'])
130
+ options.default meta: ' '
87
131
 
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
132
+ prd = MrMurano::Content::Base.new
93
133
 
94
- ret = prd.upload(args[0], args[1])
95
- prd.outf(ret) unless ret.nil? or ret.empty?
134
+ name = ::File.basename(args[0])
135
+ name = tags['name'] if tags.key? 'name'
136
+ if name.to_s.empty?
137
+ prd.error 'Bad file name'
138
+ exit 2
96
139
  end
140
+
141
+ tags = nil if tags.empty?
142
+ prd.upload(name, args[0], tags)
97
143
  end
98
144
  end
99
145
 
100
146
  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
147
+ c.syntax = %(murano content download <content name>)
148
+ c.summary = %(Download a content item)
149
+ c.description = %(
150
+ Download a content item.
104
151
 
105
152
  Data uploaded to a product's content area can be downloaded by devices using
106
153
  the HTTP Device API.
107
- }
108
- c.option '-o','--output FILE',%{save to this file}
154
+ ).strip
155
+
156
+ c.option '-o', '--output FILE', %(Save content to a file)
157
+
109
158
  c.action do |args, options|
110
- prd = MrMurano::ProductContent.new
111
- if args[0].nil? then
112
- prd.error "Missing <content id>"
159
+ c.verify_arg_count!(args, 1, ['Missing <content name>'])
160
+ prd = MrMurano::Content::Base.new
161
+ if options.output.nil?
162
+ prd.download(args[0]) # to stdout
113
163
  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
164
+ out_file = Pathname.new(options.output)
165
+ out_file.open('w') do |io|
166
+ prd.download(args[0]) do |chunk|
167
+ io << chunk
123
168
  end
124
169
  end
125
170
  end
126
171
  end
127
172
  end
128
173
 
129
-
130
- # vim: set ai et sw=2 ts=2 :