MuranoCLI 3.0.0 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +50 -27
  3. data/.trustme.vim +12 -8
  4. data/bin/murano +23 -8
  5. data/docs/basic_example.rst +1 -1
  6. data/docs/completions/murano_completion-bash +88 -0
  7. data/lib/MrMurano/Account.rb +3 -3
  8. data/lib/MrMurano/Business.rb +6 -6
  9. data/lib/MrMurano/Config-Migrate.rb +1 -3
  10. data/lib/MrMurano/Config.rb +16 -8
  11. data/lib/MrMurano/Content.rb +56 -45
  12. data/lib/MrMurano/Gateway.rb +62 -21
  13. data/lib/MrMurano/Mock.rb +27 -19
  14. data/lib/MrMurano/Passwords.rb +7 -7
  15. data/lib/MrMurano/ReCommander.rb +171 -28
  16. data/lib/MrMurano/Setting.rb +38 -40
  17. data/lib/MrMurano/Solution-ServiceConfig.rb +2 -1
  18. data/lib/MrMurano/Solution-Services.rb +196 -61
  19. data/lib/MrMurano/Solution-Users.rb +7 -7
  20. data/lib/MrMurano/Solution.rb +22 -8
  21. data/lib/MrMurano/SolutionId.rb +10 -4
  22. data/lib/MrMurano/SubCmdGroupContext.rb +14 -5
  23. data/lib/MrMurano/SyncAllowed.rb +42 -0
  24. data/lib/MrMurano/SyncUpDown.rb +122 -65
  25. data/lib/MrMurano/Webservice-Cors.rb +9 -3
  26. data/lib/MrMurano/Webservice-Endpoint.rb +39 -33
  27. data/lib/MrMurano/Webservice-File.rb +35 -24
  28. data/lib/MrMurano/commands/business.rb +18 -18
  29. data/lib/MrMurano/commands/content.rb +3 -2
  30. data/lib/MrMurano/commands/devices.rb +137 -22
  31. data/lib/MrMurano/commands/globals.rb +8 -2
  32. data/lib/MrMurano/commands/keystore.rb +3 -2
  33. data/lib/MrMurano/commands/link.rb +13 -13
  34. data/lib/MrMurano/commands/login.rb +3 -2
  35. data/lib/MrMurano/commands/mock.rb +4 -3
  36. data/lib/MrMurano/commands/password.rb +4 -2
  37. data/lib/MrMurano/commands/postgresql.rb +5 -3
  38. data/lib/MrMurano/commands/settings.rb +78 -62
  39. data/lib/MrMurano/commands/show.rb +79 -74
  40. data/lib/MrMurano/commands/solution.rb +6 -4
  41. data/lib/MrMurano/commands/solution_picker.rb +5 -4
  42. data/lib/MrMurano/commands/status.rb +15 -4
  43. data/lib/MrMurano/commands/timeseries.rb +3 -2
  44. data/lib/MrMurano/commands/tsdb.rb +3 -2
  45. data/lib/MrMurano/hash.rb +6 -6
  46. data/lib/MrMurano/http.rb +66 -67
  47. data/lib/MrMurano/makePretty.rb +18 -12
  48. data/lib/MrMurano/progress.rb +9 -2
  49. data/lib/MrMurano/verbosing.rb +14 -2
  50. data/lib/MrMurano/version.rb +2 -2
  51. data/spec/GatewayDevice_spec.rb +190 -149
  52. data/spec/Mock_spec.rb +3 -3
  53. data/spec/Solution-ServiceEventHandler_spec.rb +170 -137
  54. data/spec/SyncUpDown_spec.rb +205 -191
  55. metadata +3 -2
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.08.16 /coding: utf-8
1
+ # Last Modified: 2017.08.22 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -50,7 +50,9 @@ global_option('--exclude-scopes SCOPES', Array, exclude_help) do |value|
50
50
  end
51
51
 
52
52
  # --no-page is handled early on, in bin/murano.
53
- global_option('--no-page', %(Do not page --help output))
53
+ global_option('--[no-]page', %(Do not page --help output)) do |value|
54
+ $cfg['tool.no-page'] = !value
55
+ end
54
56
 
55
57
  global_option('--[no-]plugins', %(Do not load plugins. Good for when one goes bad))
56
58
 
@@ -58,6 +60,10 @@ global_option('--[no-]progress', %(Disable spinner and progress message)) do |va
58
60
  $cfg['tool.no-progress'] = !value
59
61
  end
60
62
 
63
+ global_option('--[no-]ascii', %(Use only ASCII in output)) do |value|
64
+ $cfg['tool.ascii'] = value
65
+ end
66
+
61
67
  global_option('-V', '--verbose', %(Be chatty)) do
62
68
  $cfg['tool.verbose'] = true
63
69
  end
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.08.16 /coding: utf-8
1
+ # Last Modified: 2017.08.22 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -18,9 +18,10 @@ in a solution. This allows for easier debugging, being able to quickly get and
18
18
  set data. As well as calling any of the other supported REDIS commands.
19
19
  ).strip
20
20
  c.project_not_required = true
21
+ c.subcmdgrouphelp = true
21
22
 
22
23
  c.action do |_args, _options|
23
- ::Commander::UI.enable_paging
24
+ ::Commander::UI.enable_paging unless $cfg['tool.no-page']
24
25
  say MrMurano::SubCmdGroupHelp.new(c).get_help
25
26
  end
26
27
  end
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.08.17 /coding: utf-8
1
+ # Last Modified: 2017.08.23 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -13,16 +13,17 @@ require 'MrMurano/SolutionId'
13
13
 
14
14
  MSG_SERVICE_LINKS_NONE_FOUND = 'No service links found' unless defined? MSG_SERVICE_LINKS_NONE_FOUND
15
15
 
16
- command 'link' do |c|
16
+ command :link do |c|
17
17
  c.syntax = %(murano link)
18
18
  c.summary = %(Use the link commands to manage solution links)
19
19
  c.description = %(
20
20
  Use the link commands to manage solution links.
21
21
  ).strip
22
22
  c.project_not_required = true
23
+ c.subcmdgrouphelp = true
23
24
 
24
25
  c.action do |_args, _options|
25
- ::Commander::UI.enable_paging
26
+ ::Commander::UI.enable_paging unless $cfg['tool.no-page']
26
27
  say(MrMurano::SubCmdGroupHelp.new(c).get_help)
27
28
  end
28
29
  end
@@ -189,9 +190,8 @@ def link_solutions(sol_a, sol_b, options)
189
190
  elsif response.is_a?(Net::HTTPConflict)
190
191
  svc_cfg_exists = true
191
192
  else
192
- MrMurano::Verbose.error(
193
- "Unable to link solutions: ‘#{Rainbow(response.message).underline}"
194
- )
193
+ resp_msg = MrMurano::Verbose.fancy_ticks(Rainbow(response.message).underline)
194
+ MrMurano::Verbose.error("Unable to link solutions: #{resp_msg}")
195
195
  sercfg.showHttpError(request, response)
196
196
  end
197
197
  end
@@ -226,9 +226,8 @@ def link_solutions(sol_a, sol_b, options)
226
226
  elsif response.is_a?(Net::HTTPConflict)
227
227
  evt_hlr_exists = true
228
228
  else
229
- MrMurano::Verbose.error(
230
- "Failed to create default event handler: ‘#{Rainbow(response.message).underline}"
231
- )
229
+ resp_msg = MrMurano::Verbose.fancy_ticks(Rainbow(response.message).underline)
230
+ MrMurano::Verbose.error("Failed to create default event handler: #{resp_msg}")
232
231
  evthlr.showHttpError(request, response)
233
232
  end
234
233
  end
@@ -264,11 +263,11 @@ def unlink_solutions(sol_a, sol_b)
264
263
  sercfg.debug "Deleting #{svc[:service]} : #{svc[:script_key]} : #{svc[:id]}"
265
264
  ret = sercfg.remove(svc[:id])
266
265
  if !ret.nil?
267
- msg = "Unlinked ‘#{svc[:script_key]}"
266
+ msg = "Unlinked #{MrMurano::Verbose.fancy_ticks(svc[:script_key])}"
268
267
  msg += " from #{sol_a.quoted_name}" unless sol_a.quoted_name.to_s.empty?
269
268
  say(msg)
270
269
  else
271
- sercfg.warning "Failed to unlink ‘#{svc[:id]}"
270
+ sercfg.warning "Failed to unlink #{MrMurano::Verbose.fancy_ticks(svc[:id])}"
272
271
  end
273
272
  end
274
273
 
@@ -289,11 +288,12 @@ def unlink_solutions(sol_a, sol_b)
289
288
  evthlr.debug "Deleting #{evth[:service]} : #{evth[:alias]} : #{evth[:id]}"
290
289
  ret = evthlr.remove(evth[:id])
291
290
  if !ret.nil?
292
- msg = "Removed ‘#{evth[:alias]}"
291
+ msg = "Removed #{MrMurano::Verbose.fancy_ticks(evth[:alias])}"
293
292
  msg += " from #{sol_a.quoted_name}" unless sol_a.quoted_name.to_s.empty?
294
293
  say(msg)
295
294
  else
296
- MrMurano::Verbose.warning "Failed to remove handler ‘#{svc[:id]}’"
295
+ svc_id = MrMurano::Verbose.fancy_ticks(svc[:id])
296
+ MrMurano::Verbose.warning "Failed to remove handler #{svc_id}"
297
297
  end
298
298
  end
299
299
  end
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.08.16 /coding: utf-8
1
+ # Last Modified: 2017.08.23 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -75,7 +75,8 @@ Essentially, this command is the same as:
75
75
  cfg_val = $cfg.get(cfg_key)
76
76
  if cfg_val.to_s.empty?
77
77
  cfg_val = nil
78
- MrMurano::Verbose.warning("No config key ‘#{cfg_key}’: no password to delete")
78
+ cfg_key_q = MrMurano::Verbose.fancy_ticks(cfg_key)
79
+ MrMurano::Verbose.warning("No config key #{cfg_key_q}: no password to delete")
79
80
  end
80
81
  cfg_val
81
82
  end
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.08.16 /coding: utf-8
1
+ # Last Modified: 2017.08.22 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -8,16 +8,17 @@
8
8
  require 'MrMurano/Mock'
9
9
  require 'MrMurano/ReCommander'
10
10
 
11
- command 'mock' do |c|
11
+ command :mock do |c|
12
12
  c.syntax = %(murano mock)
13
13
  c.summary = %(Enable or disable testpoint, or show current UUID)
14
14
  c.description = %(
15
15
  The mock command lets you enable testpoints to do local Lua development.
16
16
  ).strip
17
17
  c.project_not_required = true
18
+ c.subcmdgrouphelp = true
18
19
 
19
20
  c.action do |_args, _options|
20
- ::Commander::UI.enable_paging
21
+ ::Commander::UI.enable_paging unless $cfg['tool.no-page']
21
22
  say MrMurano::SubCmdGroupHelp.new(c).get_help
22
23
  end
23
24
  end
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.08.16 /coding: utf-8
1
+ # Last Modified: 2017.08.22 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -14,8 +14,10 @@ command :password do |c|
14
14
  Commands for working with usernames and passwords.
15
15
  ).strip
16
16
  c.project_not_required = true
17
+ c.subcmdgrouphelp = true
18
+
17
19
  c.action do |_args, _options|
18
- ::Commander::UI.enable_paging
20
+ ::Commander::UI.enable_paging unless $cfg['tool.no-page']
19
21
  say MrMurano::SubCmdGroupHelp.new(c).get_help
20
22
  end
21
23
  end
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.08.16 /coding: utf-8
1
+ # Last Modified: 2017.08.23 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -125,7 +125,9 @@ extra table in your database. (__murano_cli__.migrate_version)
125
125
 
126
126
  c.action do |args, options|
127
127
  c.verify_arg_count!(args, 2, ['Missing direction'])
128
- options.default(dir: File.join($cfg['location.base'], ($cfg['postgresql.migrations_dir'] || '')))
128
+ options.default(
129
+ dir: File.join($cfg['location.base'], ($cfg['postgresql.migrations_dir'] || '')),
130
+ )
129
131
 
130
132
  pg = MrMurano::Postgresql.new
131
133
 
@@ -135,7 +137,7 @@ extra table in your database. (__murano_cli__.migrate_version)
135
137
  elsif direction =~ /up/i
136
138
  direction = 'up'
137
139
  else
138
- pg.error "Unrecogized direction: ‘#{direction}"
140
+ pg.error "Unrecognized direction: #{MrMurano::Verbose.fancy_ticks(direction)}"
139
141
  exit 1
140
142
  end
141
143
 
@@ -1,18 +1,40 @@
1
+ # Last Modified: 2017.08.22 /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
+
1
8
  require 'vine'
2
9
  require 'yaml'
3
10
  require 'MrMurano/hash'
4
11
  require 'MrMurano/ReCommander'
5
12
  require 'MrMurano/Setting'
6
13
 
14
+ command :setting do |c|
15
+ c.syntax = %(murano setting)
16
+ c.summary = %(Use the setting commands to manage service settings)
17
+ c.description = %(
18
+ Use the setting commands to manage service settings.
19
+ ).strip
20
+ c.project_not_required = true
21
+ c.subcmdgrouphelp = true
22
+
23
+ c.action do |_args, _options|
24
+ ::Commander::UI.enable_paging unless $cfg['tool.no-page']
25
+ say(MrMurano::SubCmdGroupHelp.new(c).get_help)
26
+ end
27
+ end
28
+
7
29
  command 'setting list' do |c|
8
- c.syntax = %{murano setting list}
9
- c.summary = %{List which services and settings are avalible.}
10
- c.description = %{
11
- List which services and settings are avalible.
12
- }.strip
30
+ c.syntax = %(murano setting list)
31
+ c.summary = %(List which services and settings are available)
32
+ c.description = %(
33
+ List which services and settings are available.
34
+ ).strip
13
35
  c.project_not_required = true
14
36
 
15
- c.action do |args, options|
37
+ c.action do |args, _options|
16
38
  c.verify_arg_count!(args)
17
39
 
18
40
  setting = MrMurano::Setting.new
@@ -20,7 +42,7 @@ List which services and settings are avalible.
20
42
  ret = setting.list
21
43
 
22
44
  dd = []
23
- ret.each_pair { |k,v| v.each { |s| dd << "#{k}.#{s}" } }
45
+ ret.each_pair { |k, v| v.each { |s| dd << "#{k}.#{s}" } }
24
46
 
25
47
  setting.outf dd
26
48
  end
@@ -28,13 +50,13 @@ end
28
50
  alias_command 'settings list', 'setting list'
29
51
 
30
52
  command 'setting read' do |c|
31
- c.syntax = %{murano setting read <service>.<setting> [<sub-key>]}
32
- c.summary = %{Read a setting on a Service}
33
- c.description = %{
53
+ c.syntax = %(murano setting read <service>.<setting> [<sub-key>])
54
+ c.summary = %(Read a setting on a Service)
55
+ c.description = %(
34
56
  Read a setting on a Service.
35
- }.strip
57
+ ).strip
36
58
 
37
- c.option '-o', '--output FILE', String, %{File to save output to}
59
+ c.option '-o', '--output FILE', String, %(File to save output to)
38
60
 
39
61
  c.action do |args, options|
40
62
  c.verify_arg_count!(args, 2, ['Missing <service>.<setting>'])
@@ -49,18 +71,16 @@ Read a setting on a Service.
49
71
  ret = ret.access(subkey) unless subkey.nil?
50
72
 
51
73
  io = nil
52
- if options.output then
53
- io = File.open(options.output, 'w')
54
- end
74
+ io = File.open(options.output, 'w') if options.output
55
75
  setting.outf(ret, io)
56
76
  io.close unless io.nil?
57
77
  end
58
78
  end
59
79
 
60
80
  command 'setting write' do |c|
61
- c.syntax = %{murano setting write <service>.<setting> <sub-key> [<value>...]}
62
- c.summary = %{Write a setting on a Service}
63
- c.description = %{
81
+ c.syntax = %(murano setting write <service>.<setting> <sub-key> [<value>...])
82
+ c.summary = %(Write a setting on a Service)
83
+ c.description = %(
64
84
  Write a setting on a Service, or just part of a setting.
65
85
 
66
86
  if <value> is omitted on command line, then it is read from STDIN.
@@ -68,25 +88,25 @@ if <value> is omitted on command line, then it is read from STDIN.
68
88
  This always does a read-modify-write.
69
89
 
70
90
  If a sub-key doesn't exist, that entire path will be created as dicts.
71
- }.strip
91
+ ).strip
72
92
 
73
- c.option '--bool', %{Set Value type to boolean}
74
- c.option '--num', %{Set Value type to number}
75
- c.option '--string', %{Set Value type to string (this is default)}
76
- c.option '--json', %{Value is parsed as JSON}
77
- c.option '--array', %{Set Value type to array of strings}
78
- c.option '--dict', %{Set Value type to a dictionary of strings}
93
+ c.option '--bool', %(Set Value type to boolean)
94
+ c.option '--num', %(Set Value type to number)
95
+ c.option '--string', %(Set Value type to string (this is default))
96
+ c.option '--json', %(Value is parsed as JSON)
97
+ c.option '--array', %(Set Value type to array of strings)
98
+ c.option '--dict', %(Set Value type to a dictionary of strings)
79
99
 
80
- c.option '--append', %{When sub-key is an array, append values instead of replacing}
81
- c.option '--merge', %{When sub-key is a dict, merge values instead of replacing (child dicts are also merged)}
100
+ c.option '--append', %(When sub-key is an array, append values instead of replacing)
101
+ c.option '--merge', %(When sub-key is a dict, merge values instead of replacing (child dicts are also merged))
82
102
 
83
- c.example %{murano setting write Gateway.protocol devmode --bool yes}, %{}
84
- c.example %{murano setting write Gateway.identity_format options.length --int 24}, %{}
103
+ c.example %(murano setting write Gateway.protocol devmode --bool yes), %()
104
+ c.example %(murano setting write Gateway.identity_format options.length --int 24), %()
85
105
 
86
- c.example %{murano setting write Webservice.cors methods --array HEAD GET POST}, %{}
87
- c.example %{murano setting write Webservice.cors headers --append --array X-My-Token}, %{}
106
+ c.example %(murano setting write Webservice.cors methods --array HEAD GET POST), %()
107
+ c.example %(murano setting write Webservice.cors headers --append --array X-My-Token), %()
88
108
 
89
- c.example %{murano setting write Webservice.cors --type=json - < }, %{}
109
+ c.example %(murano setting write Webservice.cors --type=json - < ), %()
90
110
 
91
111
  c.action do |args, options|
92
112
  c.verify_arg_count!(args, nil, ['Missing <service>.<setting>', 'Missing <sub-key>'])
@@ -108,63 +128,61 @@ If a sub-key doesn't exist, that entire path will be created as dicts.
108
128
  setting = MrMurano::Setting.new
109
129
 
110
130
  # If value is '-', pull from $stdin
111
- if value.nil? and args.count == 0 then
112
- value = $stdin.read
113
- end
131
+ value = $stdin.read if value.nil? && args.count == 0
114
132
 
115
133
  # Set value to correct type.
116
- if options.bool then
117
- if value =~ /(yes|y|true|t|1|on)/i then
134
+ if options.bool
135
+ if value =~ /(yes|y|true|t|1|on)/i
118
136
  value = true
119
- elsif value =~ /(no|n|false|f|0|off)/i then
137
+ elsif value =~ /(no|n|false|f|0|off)/i
120
138
  value = false
121
139
  else
122
- setting.error %{Value "#{value}" is not a bool type!}
140
+ setting.error %(Value "#{value}" is not a bool type!)
123
141
  exit 2
124
142
  end
125
- elsif options.num then
143
+ elsif options.num
126
144
  begin
127
145
  value = Integer(value)
128
- rescue Exception => e
146
+ rescue StandardError => e
129
147
  setting.debug e.to_s
130
148
  begin
131
149
  value = Float(value)
132
- rescue Exception => e
133
- setting.error %{Value "#{value}" is not a number}
150
+ rescue StandardError => e
151
+ setting.error %(Value "#{value}" is not a number)
134
152
  setting.debug e.to_s
135
153
  exit 2
136
154
  end
137
155
  end
138
- elsif options.json then
156
+ elsif options.json
139
157
  # We use the YAML parser since it will handle most common typos in json and
140
158
  # product the intended output.
141
159
  begin
142
160
  value = YAML.load(value)
143
- rescue Exception => e
144
- setting.error %{Value not valid JSON (or YAML)}
161
+ rescue StandardError => e
162
+ setting.error %(Value not valid JSON (or YAML))
145
163
  setting.debug e.to_s
146
164
  exit 2
147
165
  end
148
166
 
149
- elsif options.array then
167
+ elsif options.array
150
168
  # take remaining args as an array
151
169
  value = args
152
170
 
153
- elsif options.dict then
171
+ elsif options.dict
154
172
  # take remaining args and convert to hash
155
173
  begin
156
174
  value = Hash.transform_keys_to_symbols(Hash[*args])
157
175
  rescue ArgumentError => e
158
- setting.error %{Odd number of arguments to dictionary}
176
+ setting.error %(Odd number of arguments to dictionary)
159
177
  setting.debug e.to_s
160
178
  exit 2
161
- rescue Exception => e
162
- setting.error %{Cannot make dictionary from args}
179
+ rescue StandardError => e
180
+ setting.error %(Cannot make dictionary from args)
163
181
  setting.debug e.to_s
164
182
  exit 2
165
183
  end
166
184
 
167
- elsif options.string then
185
+ elsif options.string
168
186
  value = value.to_s
169
187
  else
170
188
  # is a string.
@@ -172,32 +190,30 @@ If a sub-key doesn't exist, that entire path will be created as dicts.
172
190
  end
173
191
 
174
192
  ret = setting.read(service, pref)
175
- setting.verbose %{Read value: #{ret}}
193
+ setting.verbose %(Read value: #{ret})
176
194
 
177
195
  # modify and merge.
178
- if options.append then
196
+ if options.append
179
197
  g = ret.access(subkey)
180
- unless g.kind_of? Array then
181
- setting.error %{Cannot append; "#{subkey}" is not an array.}
198
+ unless g.is_a? Array
199
+ setting.error %(Cannot append; "#{subkey}" is not an array.)
182
200
  exit 3
183
201
  end
184
202
  g.push(*value)
185
203
 
186
- elsif options.merge then
204
+ elsif options.merge
187
205
  g = ret.access(subkey)
188
- unless g.kind_of? Hash then
189
- setting.error %{Cannot append; "#{subkey}" is not a dictionary.}
206
+ unless g.is_a? Hash
207
+ setting.error %(Cannot append; "#{subkey}" is not a dictionary.)
190
208
  exit 3
191
209
  end
192
210
  g.deep_merge!(value)
193
211
  else
194
212
  ret.set(subkey, value)
195
213
  end
196
- setting.verbose %{Going to write composed value: #{ret}}
214
+ setting.verbose %(Going to write composed value: #{ret})
197
215
 
198
216
  setting.write(service, pref, ret)
199
217
  end
200
218
  end
201
219
 
202
- # vim: set ai et sw=2 ts=2 :
203
-