MuranoCLI 3.2.0.beta.1 → 3.2.0.beta.5
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 +4 -4
- data/.rubocop.yml +4 -1
- data/.trustme.plugin +137 -0
- data/.trustme.sh +217 -117
- data/.trustme.vim +9 -3
- data/Gemfile +9 -3
- data/MuranoCLI.gemspec +8 -5
- data/Rakefile +1 -0
- data/dockers/Dockerfile.2.2.9 +6 -3
- data/dockers/Dockerfile.2.3.6 +6 -3
- data/dockers/Dockerfile.2.4.3 +6 -3
- data/dockers/Dockerfile.2.5.0 +6 -3
- data/dockers/Dockerfile.GemRelease +10 -8
- data/dockers/Dockerfile.m4 +23 -5
- data/dockers/docker-test.sh +65 -28
- data/docs/completions/murano_completion-bash +751 -57
- data/docs/develop.rst +10 -9
- data/lib/MrMurano/AccountBase.rb +95 -6
- data/lib/MrMurano/Commander-Entry.rb +9 -4
- data/lib/MrMurano/Config-Migrate.rb +2 -0
- data/lib/MrMurano/Config.rb +94 -26
- data/lib/MrMurano/Content.rb +1 -1
- data/lib/MrMurano/Exchange.rb +77 -42
- data/lib/MrMurano/Gateway.rb +1 -1
- data/lib/MrMurano/HttpAuthed.rb +20 -7
- data/lib/MrMurano/Logs.rb +10 -1
- data/lib/MrMurano/ProjectFile.rb +1 -1
- data/lib/MrMurano/ReCommander.rb +129 -73
- data/lib/MrMurano/Solution-ServiceConfig.rb +18 -11
- data/lib/MrMurano/Solution-Services.rb +78 -50
- data/lib/MrMurano/Solution-Users.rb +1 -1
- data/lib/MrMurano/Solution.rb +13 -63
- data/lib/MrMurano/SyncUpDown-Core.rb +185 -77
- data/lib/MrMurano/SyncUpDown-Item.rb +29 -4
- data/lib/MrMurano/SyncUpDown.rb +11 -11
- data/lib/MrMurano/Webservice-Cors.rb +1 -1
- data/lib/MrMurano/Webservice-Endpoint.rb +28 -17
- data/lib/MrMurano/Webservice-File.rb +103 -43
- data/lib/MrMurano/commands/domain.rb +1 -0
- data/lib/MrMurano/commands/element.rb +585 -0
- data/lib/MrMurano/commands/exchange.rb +211 -204
- data/lib/MrMurano/commands/gb.rb +1 -0
- data/lib/MrMurano/commands/globals.rb +17 -7
- data/lib/MrMurano/commands/init.rb +115 -101
- data/lib/MrMurano/commands/keystore.rb +1 -1
- data/lib/MrMurano/commands/logs.rb +2 -1
- data/lib/MrMurano/commands/postgresql.rb +17 -7
- data/lib/MrMurano/commands/service.rb +572 -0
- data/lib/MrMurano/commands/show.rb +7 -3
- data/lib/MrMurano/commands/solution.rb +2 -1
- data/lib/MrMurano/commands/solution_picker.rb +31 -15
- data/lib/MrMurano/commands/status.rb +205 -169
- data/lib/MrMurano/commands/sync.rb +70 -38
- data/lib/MrMurano/commands/token.rb +59 -14
- data/lib/MrMurano/commands/usage.rb +1 -0
- data/lib/MrMurano/commands.rb +2 -0
- data/lib/MrMurano/hash.rb +91 -0
- data/lib/MrMurano/http.rb +55 -6
- data/lib/MrMurano/makePretty.rb +47 -0
- data/lib/MrMurano/optparse.rb +60 -45
- data/lib/MrMurano/variegated/TruthyFalsey.rb +48 -0
- data/lib/MrMurano/variegated/ruby_dig.rb +64 -0
- data/lib/MrMurano/verbosing.rb +113 -3
- data/lib/MrMurano/version.rb +1 -1
- data/spec/Account_spec.rb +34 -20
- data/spec/Business_spec.rb +12 -9
- data/spec/Config_spec.rb +7 -1
- data/spec/Content_spec.rb +17 -1
- data/spec/GatewayBase_spec.rb +5 -2
- data/spec/GatewayDevice_spec.rb +4 -2
- data/spec/GatewayResource_spec.rb +4 -1
- data/spec/GatewaySettings_spec.rb +4 -1
- data/spec/HttpAuthed_spec.rb +73 -0
- data/spec/Http_spec.rb +32 -35
- data/spec/ProjectFile_spec.rb +1 -1
- data/spec/Solution-ServiceConfig_spec.rb +4 -1
- data/spec/Solution-ServiceEventHandler_spec.rb +6 -3
- data/spec/Solution-ServiceModules_spec.rb +4 -1
- data/spec/Solution-UsersRoles_spec.rb +4 -1
- data/spec/Solution_spec.rb +4 -1
- data/spec/SyncUpDown_spec.rb +1 -1
- data/spec/Webservice-Cors_spec.rb +4 -1
- data/spec/Webservice-Endpoint_spec.rb +9 -6
- data/spec/Webservice-File_spec.rb +17 -4
- data/spec/Webservice-Setting_spec.rb +6 -2
- data/spec/_workspace.rb +2 -0
- data/spec/cmd_common.rb +42 -13
- data/spec/cmd_content_spec.rb +17 -7
- data/spec/cmd_device_spec.rb +1 -1
- data/spec/cmd_domain_spec.rb +2 -2
- data/spec/cmd_element_spec.rb +400 -0
- data/spec/cmd_exchange_spec.rb +2 -2
- data/spec/cmd_init_spec.rb +59 -25
- data/spec/cmd_keystore_spec.rb +6 -3
- data/spec/cmd_link_spec.rb +10 -5
- data/spec/cmd_logs_spec.rb +1 -1
- data/spec/cmd_setting_application_spec.rb +18 -15
- data/spec/cmd_setting_product_spec.rb +7 -7
- data/spec/cmd_status_spec.rb +27 -17
- data/spec/cmd_syncdown_application_spec.rb +30 -3
- data/spec/cmd_syncdown_both_spec.rb +72 -18
- data/spec/cmd_syncup_spec.rb +71 -5
- data/spec/cmd_token_spec.rb +2 -2
- data/spec/cmd_usage_spec.rb +2 -2
- data/spec/dry_run_formatter.rb +27 -0
- data/spec/fixtures/dumped_config +8 -0
- data/spec/fixtures/exchange_element/element-show.json +1 -0
- data/spec/fixtures/exchange_element/swagger-mur-6407__10k.yaml +282 -0
- data/spec/fixtures/exchange_element/swagger-mur-6407__20k.yaml +588 -0
- data/spec/variegated_TruthyFalsey_spec.rb +29 -0
- metadata +51 -25
|
@@ -24,7 +24,12 @@ def args_parse_user_host_pair(args)
|
|
|
24
24
|
[username, host]
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
def
|
|
27
|
+
def args_sole_token_maybe(args)
|
|
28
|
+
return unless args.length == 1
|
|
29
|
+
args.shift if MrMurano::HttpAuthed.instance.token_looks_legit(args[0])
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def args_opts_parse_token_maybe(args, options, must: false)
|
|
28
33
|
n_specified = 0
|
|
29
34
|
token = args.shift
|
|
30
35
|
n_specified += 1 unless token.nil?
|
|
@@ -43,10 +48,9 @@ def args_opts_parse_token!(args, options)
|
|
|
43
48
|
if n_specified > 1
|
|
44
49
|
MrMurano::Verbose.error(%(Please specify only one token, --token, or --from-env))
|
|
45
50
|
exit 1
|
|
46
|
-
elsif n_specified == 0
|
|
47
|
-
token = ask('Token: ')
|
|
48
51
|
end
|
|
49
|
-
if token.to_s.empty?
|
|
52
|
+
token = nil if token.to_s.empty?
|
|
53
|
+
if must && token.nil?
|
|
50
54
|
MrMurano::Verbose.error(%(Please specify a token!))
|
|
51
55
|
unless ENV['MURANO_TOKEN'].to_s.empty?
|
|
52
56
|
MrMurano::Verbose.warning(
|
|
@@ -58,6 +62,20 @@ def args_opts_parse_token!(args, options)
|
|
|
58
62
|
token
|
|
59
63
|
end
|
|
60
64
|
|
|
65
|
+
def args_opts_parse_token(args, options)
|
|
66
|
+
args_opts_parse_token_maybe(args, options, must: false)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def args_opts_parse_token!(args, options)
|
|
70
|
+
args_opts_parse_token_maybe(args, options, must: true)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def cmd_option_token_options(c)
|
|
74
|
+
# We cannot use --token, because it's already a global_option.
|
|
75
|
+
c.option '--value TOKEN', String, %(The token to use)
|
|
76
|
+
c.option '--from-env', %(Use token in MURANO_TOKEN)
|
|
77
|
+
end
|
|
78
|
+
|
|
61
79
|
command :token do |c|
|
|
62
80
|
c.syntax = %(murano token)
|
|
63
81
|
c.summary = %(About token commands)
|
|
@@ -164,20 +182,20 @@ end
|
|
|
164
182
|
alias_command 'token fetch', 'token get'
|
|
165
183
|
|
|
166
184
|
command 'token set' do |c|
|
|
167
|
-
c.syntax = %(murano token set [<username>] [<host>] [<token>])
|
|
185
|
+
c.syntax = %(murano token set [[[<username>] [<host>] [<token>]] | [<token>]])
|
|
168
186
|
c.summary = %(Set token for username and host)
|
|
169
187
|
c.description = %(
|
|
170
188
|
Set token for username and host.
|
|
171
189
|
).strip
|
|
172
|
-
|
|
173
|
-
c.option '--value TOKEN', String, %(The token to use)
|
|
174
|
-
c.option '--from-env', %(Use token in MURANO_TOKEN)
|
|
190
|
+
cmd_option_token_options(c)
|
|
175
191
|
c.project_not_required = true
|
|
176
192
|
|
|
177
193
|
c.action do |args, options|
|
|
178
194
|
c.verify_arg_count!(args, 3)
|
|
195
|
+
# If there's just one arg, might be the token (otherwise it's the username).
|
|
196
|
+
token = args_sole_token_maybe(args)
|
|
179
197
|
username, host = args_parse_user_host_pair(args)
|
|
180
|
-
token = args_opts_parse_token!(args, options)
|
|
198
|
+
token = args_opts_parse_token!(args, options) if token.nil?
|
|
181
199
|
token_file = MrMurano::Tokens.new
|
|
182
200
|
token_file.load
|
|
183
201
|
token_file.set(host, username, token)
|
|
@@ -186,24 +204,51 @@ command 'token set' do |c|
|
|
|
186
204
|
end
|
|
187
205
|
|
|
188
206
|
command 'token delete' do |c|
|
|
189
|
-
c.syntax = %(murano token delete [<username>] [<host>])
|
|
207
|
+
c.syntax = %(murano token delete [[[<username>] [<host>]] | [<token>]])
|
|
190
208
|
c.summary = %(Invalidate and delete token for username and host)
|
|
191
209
|
c.description = %(
|
|
192
210
|
Invalidate and delete token for username and host.
|
|
193
211
|
).strip
|
|
194
212
|
c.option '--no-invalidate', %(Do not also invalidate token on server)
|
|
213
|
+
cmd_option_token_options(c)
|
|
195
214
|
c.project_not_required = true
|
|
196
215
|
|
|
216
|
+
# Note: We confirm deleting passwords, but not throwaway tokens.
|
|
197
217
|
c.action do |args, options|
|
|
198
218
|
c.verify_arg_count!(args, 2)
|
|
199
|
-
|
|
200
|
-
|
|
219
|
+
clear_cache = false
|
|
220
|
+
# If there's just one arg, might be the token (otherwise it's the username).
|
|
221
|
+
token = args_sole_token_maybe(args)
|
|
222
|
+
if token.nil?
|
|
223
|
+
n_args = args.length
|
|
224
|
+
# First see if the user specified a username and/or host.
|
|
225
|
+
username, host = args_parse_user_host_pair(args)
|
|
226
|
+
# See if the user used --value or --from-env.
|
|
227
|
+
alt_token = args_opts_parse_token(args, options)
|
|
228
|
+
# If the user specified a user or host, they should not specify the
|
|
229
|
+
# token (user and host are used to lookup in the tokens file).
|
|
230
|
+
if !alt_token.nil?
|
|
231
|
+
if n_args > 0
|
|
232
|
+
MrMurano::Verbose.error %(
|
|
233
|
+
Please specify either token, or user and host, but not both
|
|
234
|
+
).strip
|
|
235
|
+
break
|
|
236
|
+
end
|
|
237
|
+
token = alt_token
|
|
238
|
+
else
|
|
239
|
+
token = MrMurano::HttpAuthed.instance.token_from_file(host, username)
|
|
240
|
+
clear_cache = true
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
if token.nil?
|
|
244
|
+
MrMurano::Verbose.warning %(No token specified or none found for user)
|
|
245
|
+
break
|
|
246
|
+
end
|
|
201
247
|
# We can create an authless instance, which won't ask user for
|
|
202
248
|
# login creds, because the delete command does not need creds.
|
|
203
249
|
acc = MrMurano::Account.new(authless: true)
|
|
204
|
-
token = MrMurano::HttpAuthed.instance.token_from_file(host, username)
|
|
205
250
|
acc.invalidate_token(token) unless options.no_invalidate
|
|
206
|
-
MrMurano::HttpAuthed.instance.token_remove(host, username)
|
|
251
|
+
MrMurano::HttpAuthed.instance.token_remove(host, username) if clear_cache
|
|
207
252
|
end
|
|
208
253
|
end
|
|
209
254
|
|
data/lib/MrMurano/commands.rb
CHANGED
|
@@ -17,6 +17,7 @@ require 'MrMurano/commands/content'
|
|
|
17
17
|
require 'MrMurano/commands/cors'
|
|
18
18
|
require 'MrMurano/commands/devices'
|
|
19
19
|
require 'MrMurano/commands/domain'
|
|
20
|
+
require 'MrMurano/commands/element'
|
|
20
21
|
require 'MrMurano/commands/exchange'
|
|
21
22
|
require 'MrMurano/commands/globals'
|
|
22
23
|
require 'MrMurano/commands/keystore'
|
|
@@ -28,6 +29,7 @@ require 'MrMurano/commands/mock'
|
|
|
28
29
|
require 'MrMurano/commands/postgresql'
|
|
29
30
|
require 'MrMurano/commands/password'
|
|
30
31
|
require 'MrMurano/commands/settings'
|
|
32
|
+
require 'MrMurano/commands/service'
|
|
31
33
|
require 'MrMurano/commands/show'
|
|
32
34
|
require 'MrMurano/commands/solution'
|
|
33
35
|
require 'MrMurano/commands/status'
|
data/lib/MrMurano/hash.rb
CHANGED
|
@@ -42,6 +42,97 @@ class Hash
|
|
|
42
42
|
end
|
|
43
43
|
self
|
|
44
44
|
end
|
|
45
|
+
|
|
46
|
+
# h = { :a => { :b => { :c => 1, :d => 2 }, :e => 3 }, :f => 4 }
|
|
47
|
+
# flat_hash(h) #=> {[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}
|
|
48
|
+
def self.flat_hash(hash, long_key=[], flat={})
|
|
49
|
+
return flat.update(long_key => hash) unless hash.is_a? Hash
|
|
50
|
+
hash.each { |key, val| flat_hash(val, long_key + [key], flat) }
|
|
51
|
+
flat
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Get all hash and nested hash keys.
|
|
55
|
+
def self.nested_keys(obj)
|
|
56
|
+
obj.each_with_object([]) do |(key, val), keys|
|
|
57
|
+
keys << key
|
|
58
|
+
keys.concat(nested_keys(val)) if val.is_a? Hash
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# deep_symbolize_keys is from rails:
|
|
63
|
+
#
|
|
64
|
+
# https://github.com/rails/rails/blob/f213e926892020f9ab6c8974612c59e2ba959253/
|
|
65
|
+
# activesupport/lib/active_support/core_ext/hash/keys.rb
|
|
66
|
+
|
|
67
|
+
# Returns a new hash with all keys converted by the block operation.
|
|
68
|
+
# This includes the keys from the root hash and from all
|
|
69
|
+
# nested hashes and arrays.
|
|
70
|
+
#
|
|
71
|
+
# hash = { person: { name: 'Rob', age: '28' } }
|
|
72
|
+
#
|
|
73
|
+
# hash.deep_transform_keys{ |key| key.to_s.upcase }
|
|
74
|
+
# # => {"PERSON"=>{"NAME"=>"Rob", "AGE"=>"28"}}
|
|
75
|
+
def deep_transform_keys(&block)
|
|
76
|
+
_deep_transform_keys_in_object(self, &block)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Destructively converts all keys by using the block operation.
|
|
80
|
+
# This includes the keys from the root hash and from all
|
|
81
|
+
# nested hashes and arrays.
|
|
82
|
+
def deep_transform_keys!(&block)
|
|
83
|
+
_deep_transform_keys_in_object!(self, &block)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Returns a new hash with all keys converted to symbols, as long as
|
|
87
|
+
# they respond to +to_sym+. This includes the keys from the root hash
|
|
88
|
+
# and from all nested hashes and arrays.
|
|
89
|
+
#
|
|
90
|
+
# hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } }
|
|
91
|
+
#
|
|
92
|
+
# hash.deep_symbolize_keys
|
|
93
|
+
# # => {:person=>{:name=>"Rob", :age=>"28"}}
|
|
94
|
+
def deep_symbolize_keys
|
|
95
|
+
# rubocop:disable Style/RescueModifier
|
|
96
|
+
deep_transform_keys { |key| key.to_sym rescue key }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Destructively converts all keys to symbols, as long as they respond
|
|
100
|
+
# to +to_sym+. This includes the keys from the root hash and from all
|
|
101
|
+
# nested hashes and arrays.
|
|
102
|
+
def deep_symbolize_keys!
|
|
103
|
+
deep_transform_keys! { |key| key.to_sym rescue key }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
# support methods for deep transforming nested hashes and arrays
|
|
109
|
+
def _deep_transform_keys_in_object(object, &block)
|
|
110
|
+
case object
|
|
111
|
+
when Hash
|
|
112
|
+
object.each_with_object({}) do |(key, value), result|
|
|
113
|
+
result[yield(key)] = _deep_transform_keys_in_object(value, &block)
|
|
114
|
+
end
|
|
115
|
+
when Array
|
|
116
|
+
object.map { |e| _deep_transform_keys_in_object(e, &block) }
|
|
117
|
+
else
|
|
118
|
+
object
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def _deep_transform_keys_in_object!(object, &block)
|
|
123
|
+
case object
|
|
124
|
+
when Hash
|
|
125
|
+
object.keys.each do |key|
|
|
126
|
+
value = object.delete(key)
|
|
127
|
+
object[yield(key)] = _deep_transform_keys_in_object!(value, &block)
|
|
128
|
+
end
|
|
129
|
+
object
|
|
130
|
+
when Array
|
|
131
|
+
object.map! { |e| _deep_transform_keys_in_object!(e, &block) }
|
|
132
|
+
else
|
|
133
|
+
object
|
|
134
|
+
end
|
|
135
|
+
end
|
|
45
136
|
end
|
|
46
137
|
|
|
47
138
|
def ordered_hash(dict)
|
data/lib/MrMurano/http.rb
CHANGED
|
@@ -62,25 +62,72 @@ module MrMurano
|
|
|
62
62
|
)
|
|
63
63
|
ccmd << %(-F #{m[:name]}=@#{m[:filename]}) unless m.nil?
|
|
64
64
|
else
|
|
65
|
-
|
|
65
|
+
req_body = request.body
|
|
66
|
+
unless $cfg['tool.show-password']
|
|
67
|
+
req_body = req_body.gsub(
|
|
68
|
+
/"password":(["'])(?:(?=(\\?))\2.)*?\1/, '"password":"<redacted>"'
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
ccmd << %(-d '#{req_body}')
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
MrMurano::Http.curldebug_log(ccmd.join(' '), stamp_it: true)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# (lb): Ideally, we'd refactor and these wouldn't be static class methods.
|
|
79
|
+
def self.curldebug_after(request, response)
|
|
80
|
+
@http_complete = [] unless defined? @http_complete
|
|
81
|
+
@http_complete.push([request, response])
|
|
82
|
+
return response unless $cfg['tool.curldebug'] && $cfg['tool.curlfancy']
|
|
83
|
+
MrMurano::Http.curldebug_elapsed
|
|
84
|
+
if response.nil?
|
|
85
|
+
formatted = '<nil>'
|
|
86
|
+
else
|
|
87
|
+
begin
|
|
88
|
+
formatted = "#{response.code} / #{response.body}"
|
|
89
|
+
rescue StandardError
|
|
90
|
+
formatted = response.to_s
|
|
66
91
|
end
|
|
67
92
|
end
|
|
68
|
-
|
|
93
|
+
formatted = "Response: #{formatted}"
|
|
94
|
+
MrMurano::Http.curldebug_log(formatted)
|
|
95
|
+
response
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def self.curldebug_elapsed
|
|
99
|
+
http_recv_at = Time.now
|
|
100
|
+
if !@http_sent_at.nil?
|
|
101
|
+
formatted = "ElapsedT: #{http_recv_at - @http_sent_at}"
|
|
102
|
+
else
|
|
103
|
+
formatted = 'ElapsedT: Overlapping command clobbered time.'
|
|
104
|
+
MrMurano::Verbose.warning(formatted) if $cfg['tool.debug']
|
|
105
|
+
end
|
|
106
|
+
MrMurano::Http.curldebug_log(formatted)
|
|
107
|
+
@http_sent_at = nil
|
|
69
108
|
end
|
|
70
109
|
|
|
71
|
-
def self.curldebug_log(
|
|
110
|
+
def self.curldebug_log(formatted, stamp_it: false)
|
|
72
111
|
if $cfg.curlfile_f.nil?
|
|
73
|
-
MrMurano::Progress.instance.whirly_interject { puts
|
|
112
|
+
MrMurano::Progress.instance.whirly_interject { puts formatted }
|
|
74
113
|
else
|
|
75
|
-
$cfg.curlfile_f <<
|
|
114
|
+
$cfg.curlfile_f << formatted + "\n\n"
|
|
76
115
|
$cfg.curlfile_f.flush
|
|
77
116
|
end
|
|
117
|
+
return unless stamp_it
|
|
118
|
+
@http_outstanding = [] unless defined? @http_outstanding
|
|
119
|
+
@http_outstanding.push(formatted)
|
|
120
|
+
@http_sent_at = Time.now
|
|
78
121
|
end
|
|
79
122
|
|
|
80
123
|
def http
|
|
81
124
|
uri = URI($cfg['net.protocol'] + '://' + $cfg['net.host'])
|
|
82
125
|
if !defined?(@http) || @http.nil?
|
|
83
126
|
@http = Net::HTTP.new(uri.host, uri.port)
|
|
127
|
+
# (lb): The HTTP timeouts default to 60 secs. But requests
|
|
128
|
+
# sometimes timeout, so experimenting with longer timeouts.
|
|
129
|
+
@http.open_timeout = 90 # default: 60
|
|
130
|
+
@http.read_timeout = 90 # default: 60
|
|
84
131
|
@http.use_ssl = true if $cfg['net.protocol'] == 'https'
|
|
85
132
|
begin
|
|
86
133
|
@http.start
|
|
@@ -148,9 +195,11 @@ module MrMurano
|
|
|
148
195
|
def workit(request)
|
|
149
196
|
curldebug(request)
|
|
150
197
|
if block_given?
|
|
151
|
-
yield request, http
|
|
198
|
+
response = yield request, http
|
|
199
|
+
MrMurano::Http.curldebug_after(request, response)
|
|
152
200
|
else
|
|
153
201
|
response = http.request(request)
|
|
202
|
+
MrMurano::Http.curldebug_after(request, response)
|
|
154
203
|
case response
|
|
155
204
|
when Net::HTTPSuccess
|
|
156
205
|
workit_response(response)
|
data/lib/MrMurano/makePretty.rb
CHANGED
|
@@ -307,6 +307,53 @@ module MrMurano
|
|
|
307
307
|
def self.body_prefix(options)
|
|
308
308
|
options.indent && ' ' || ''
|
|
309
309
|
end
|
|
310
|
+
|
|
311
|
+
def self.width_last_column(headers, elems)
|
|
312
|
+
return nil unless $stdout.tty?
|
|
313
|
+
# Calculate how much room (how many characters) are left for the
|
|
314
|
+
# description column.
|
|
315
|
+
width_taken = 0
|
|
316
|
+
# rubocop:disable Performance/FixedSize
|
|
317
|
+
# "Do not compute the size of statically sized objects."
|
|
318
|
+
width_taken += '| '.length
|
|
319
|
+
# Calculate the width of each column except the last (:description).
|
|
320
|
+
headers[0..-2].each do |key|
|
|
321
|
+
elem_with_max = elems.max { |a, b| a.send(key).length <=> b.send(key).length }
|
|
322
|
+
width_taken += elem_with_max.send(key).length unless elem_with_max.nil?
|
|
323
|
+
width_taken += ' | '.length
|
|
324
|
+
end
|
|
325
|
+
width_taken += ' | '.length
|
|
326
|
+
term_width, _rows = HighLine::SystemExtensions.terminal_size
|
|
327
|
+
width_avail = term_width - width_taken
|
|
328
|
+
# MAGIC_NUMBER: Tweak/change this if you want. 20 char min feels
|
|
329
|
+
# about right: don't wrap if column would be narrow or negative.
|
|
330
|
+
return nil if width_avail < 20
|
|
331
|
+
width_avail
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def self.split_text_on_whitespace(full, width_avail)
|
|
335
|
+
# The easiest method is to split at the width, and not on closest whitespace:
|
|
336
|
+
# elem.meta[key].scan(/.{1,#{width_avail}}/).join("\n")
|
|
337
|
+
# We'll instead split on whitespace.
|
|
338
|
+
parts = []
|
|
339
|
+
split_posit = width_avail - 1
|
|
340
|
+
until full.empty?
|
|
341
|
+
# Split the description on a space before the max width.
|
|
342
|
+
part = full[0..split_posit]
|
|
343
|
+
full = full[(split_posit + 1)..-1] || ''
|
|
344
|
+
leftover = ''
|
|
345
|
+
part, _space, leftover = part.rpartition(' ') unless full.empty?
|
|
346
|
+
if part.empty?
|
|
347
|
+
part = leftover.to_s
|
|
348
|
+
leftover = ''
|
|
349
|
+
else
|
|
350
|
+
full = leftover.to_s + full
|
|
351
|
+
full = full
|
|
352
|
+
end
|
|
353
|
+
parts.push(part.strip)
|
|
354
|
+
end
|
|
355
|
+
parts.join("\n")
|
|
356
|
+
end
|
|
310
357
|
end
|
|
311
358
|
end
|
|
312
359
|
|
data/lib/MrMurano/optparse.rb
CHANGED
|
@@ -1559,58 +1559,73 @@ XXX
|
|
|
1559
1559
|
def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc:
|
|
1560
1560
|
opt, arg, val, rest = nil
|
|
1561
1561
|
nonopt ||= proc {|a| throw :terminate, a}
|
|
1562
|
+
end_of_options = false
|
|
1563
|
+
|
|
1562
1564
|
argv.unshift(arg) if arg = catch(:terminate) {
|
|
1563
1565
|
while arg = argv.shift
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1566
|
+
is_nonopt = end_of_options
|
|
1567
|
+
|
|
1568
|
+
if !end_of_options
|
|
1569
|
+
case arg
|
|
1570
|
+
|
|
1571
|
+
# end of options
|
|
1572
|
+
when '--'
|
|
1573
|
+
end_of_options = true
|
|
1574
|
+
|
|
1575
|
+
# long option
|
|
1576
|
+
when /\A--([^=]*)(?:=(.*))?/m
|
|
1577
|
+
opt, rest = $1, $2
|
|
1578
|
+
begin
|
|
1579
|
+
sw, = complete(:long, opt, true)
|
|
1580
|
+
rescue ParseError
|
|
1581
|
+
raise $!.set_option(arg, true)
|
|
1582
|
+
end
|
|
1583
|
+
begin
|
|
1584
|
+
opt, cb, val = sw.parse(rest, argv) {|*exc| raise(*exc)}
|
|
1585
|
+
val = cb.call(val) if cb
|
|
1586
|
+
setter.call(sw.switch_name, val) if setter
|
|
1587
|
+
rescue ParseError
|
|
1588
|
+
raise $!.set_option(arg, rest)
|
|
1589
|
+
end
|
|
1580
1590
|
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1591
|
+
# short option
|
|
1592
|
+
when /\A-(.)((=).*|.+)?/m
|
|
1593
|
+
opt, has_arg, eq, val, rest = $1, $3, $3, $2, $2
|
|
1594
|
+
begin
|
|
1595
|
+
sw, = search(:short, opt)
|
|
1596
|
+
unless sw
|
|
1597
|
+
begin
|
|
1598
|
+
sw, = complete(:short, opt)
|
|
1599
|
+
# short option matched.
|
|
1600
|
+
val = arg.sub(/\A-/, '')
|
|
1601
|
+
has_arg = true
|
|
1602
|
+
rescue InvalidOption
|
|
1603
|
+
# if no short options match, try completion with long
|
|
1604
|
+
# options.
|
|
1605
|
+
sw, = complete(:long, opt)
|
|
1606
|
+
eq ||= !rest
|
|
1607
|
+
end
|
|
1597
1608
|
end
|
|
1609
|
+
rescue ParseError
|
|
1610
|
+
raise $!.set_option(arg, true)
|
|
1598
1611
|
end
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1612
|
+
begin
|
|
1613
|
+
opt, cb, val = sw.parse(val, argv) {|*exc| raise(*exc) if eq}
|
|
1614
|
+
raise InvalidOption, arg if has_arg and !eq and arg == "-#{opt}"
|
|
1615
|
+
argv.unshift(opt) if opt and (!rest or (opt = opt.sub(/\A-*/, '-')) != '-')
|
|
1616
|
+
val = cb.call(val) if cb
|
|
1617
|
+
setter.call(sw.switch_name, val) if setter
|
|
1618
|
+
rescue ParseError
|
|
1619
|
+
raise $!.set_option(arg, arg.length > 2)
|
|
1620
|
+
end
|
|
1621
|
+
|
|
1622
|
+
# non-option argument
|
|
1623
|
+
else
|
|
1624
|
+
is_nonopt = true
|
|
1610
1625
|
end
|
|
1626
|
+
end
|
|
1611
1627
|
|
|
1612
|
-
|
|
1613
|
-
else
|
|
1628
|
+
if is_nonopt
|
|
1614
1629
|
catch(:prune) do
|
|
1615
1630
|
visit(:each_option) do |sw0|
|
|
1616
1631
|
sw = sw0
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Copyright © 2016-2017 Exosite LLC. All Rights Reserved
|
|
2
|
+
# License: PROPRIETARY. See LICENSE.txt.
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# vim:tw=0:ts=2:sw=2:et:ai
|
|
6
|
+
# Unauthorized copying of this file is strictly prohibited.
|
|
7
|
+
|
|
8
|
+
class TruthyFalsey
|
|
9
|
+
attr_reader :rejoinder
|
|
10
|
+
|
|
11
|
+
def initialize(rejoinder)
|
|
12
|
+
@rejoinder = rejoinder
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
TRUTHY_WORDS = %w[1 on t true y yes +].freeze
|
|
16
|
+
|
|
17
|
+
FALSEY_WORDS = ['0', 'off', 'f', 'false', 'n', 'no', '-', ''].freeze
|
|
18
|
+
|
|
19
|
+
def truthy?
|
|
20
|
+
TRUTHY_WORDS.include? @rejoinder.to_s.downcase
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def falsey?
|
|
24
|
+
FALSEY_WORDS.include? @rejoinder.to_s.downcase
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.coerce_if_boolean(value, reference)
|
|
28
|
+
# Ruby doesn't have a Boolean type, just TrueClass and FalseClass.
|
|
29
|
+
return value unless [true, false].include? reference
|
|
30
|
+
tfy = TruthyFalsey.new(value)
|
|
31
|
+
if tfy.truthy?
|
|
32
|
+
true
|
|
33
|
+
elsif tfy.falsey?
|
|
34
|
+
false
|
|
35
|
+
else
|
|
36
|
+
value
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.coerce_boolean(value, reference, default: 'true')
|
|
41
|
+
if value.nil?
|
|
42
|
+
default
|
|
43
|
+
else
|
|
44
|
+
TruthyFalsey.coerce_if_boolean(value, reference)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Copyright © 2016-2017 Exosite LLC. All Rights Reserved
|
|
2
|
+
# License: PROPRIETARY. See LICENSE.txt.
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# vim:tw=0:ts=2:sw=2:et:ai
|
|
6
|
+
# Unauthorized copying of this file is strictly prohibited.
|
|
7
|
+
|
|
8
|
+
# The Safe Navigation Operator (&.) was introduced in Ruby 2.3.
|
|
9
|
+
#
|
|
10
|
+
# Here, we introduce a delegator that performs a similar dig operator,
|
|
11
|
+
# but it wraps each child hash in a delegator, and it allows one to
|
|
12
|
+
# safe-set a nested value.
|
|
13
|
+
#
|
|
14
|
+
# For more on the dig operator, see:
|
|
15
|
+
#
|
|
16
|
+
# http://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/
|
|
17
|
+
|
|
18
|
+
# NOTE: (lb): Rather than deriving from the built-in SimpleDelegator class,
|
|
19
|
+
# from the 'delegate' library, we use a third-party package, because
|
|
20
|
+
# SimpleDelegator maps the class and is_a? methods to the delegator, i.e.,
|
|
21
|
+
# some_delegator.is_a? Delegator == true. But we'd prefer that the delegator
|
|
22
|
+
# map to the wrapped object, e.g., MyDelegator.new(Hash.new).class == Hash.
|
|
23
|
+
|
|
24
|
+
# From:
|
|
25
|
+
# https://github.com/stevenharman/dumb_delegator
|
|
26
|
+
require 'dumb_delegator'
|
|
27
|
+
|
|
28
|
+
class HashDiggable < DumbDelegator
|
|
29
|
+
def initialize(obj=nil)
|
|
30
|
+
super(obj || {})
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def a_diggable_hash?
|
|
34
|
+
true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def fill_safe(value, *path)
|
|
38
|
+
sub_hash = self
|
|
39
|
+
sub_hash = dig_safe(*path[0..-2]) if path.length > 1
|
|
40
|
+
sub_hash[path[-1]] = value
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def dig_safe(key, *rest)
|
|
44
|
+
value = self[key]
|
|
45
|
+
|
|
46
|
+
if value.nil?
|
|
47
|
+
value = self[key] = ::HashDiggable.new
|
|
48
|
+
else
|
|
49
|
+
begin
|
|
50
|
+
value.a_diggable_hash?
|
|
51
|
+
# Okay!
|
|
52
|
+
rescue ::NoMethodError => _err
|
|
53
|
+
value = self[key] = ::HashDiggable.new(value) if value.is_a? ::Hash
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
if rest.empty?
|
|
58
|
+
value
|
|
59
|
+
else
|
|
60
|
+
value.dig_safe(*rest)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|