qb 0.3.4 → 0.3.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.
@@ -0,0 +1,57 @@
1
+ # Requirements
2
+ # =====================================================================
3
+
4
+ # package
5
+ require 'qb/ansible/cmds/playbook'
6
+
7
+
8
+ # Declarations
9
+ # =======================================================================
10
+
11
+ module QB; end
12
+
13
+
14
+ # Definitions
15
+ # =======================================================================
16
+
17
+ module QB::CLI
18
+
19
+ # Play `//dev/setup.yml`
20
+ #
21
+ # @param [Array<String>] args
22
+ # CLI arguments to use.
23
+ #
24
+ # @return [Fixnum]
25
+ # The `ansible-playbook` command exit code.
26
+ #
27
+ def self.setup args = []
28
+ project_root = NRSER.git_root '.'
29
+ playbook_path = project_root / 'dev' / 'setup.qb.yml'
30
+
31
+ unless playbook_path.file?
32
+ raise "Can't find QB setup playbook at `#{ playbook_path.to_s }`"
33
+ end
34
+
35
+ cmd = QB::Ansible::Cmds::Playbook.new \
36
+ chdir: project_root,
37
+ extra_vars: {
38
+ project_root: project_root,
39
+ qb_dir: project_root,
40
+ qb_cwd: Pathname.getwd,
41
+ qb_user_roles_dir: QB::USER_ROLES_DIR,
42
+ },
43
+ playbook_path: playbook_path
44
+
45
+ puts cmd.prepare
46
+
47
+ status = cmd.stream
48
+
49
+ if status != 0
50
+ $stderr.puts "ERROR QB setup failed."
51
+ end
52
+
53
+ exit status
54
+
55
+ end # .setup
56
+
57
+ end # module QB::CLI
@@ -1,857 +1,945 @@
1
+
2
+ # Requirements
3
+ # =======================================================================
4
+
5
+ # stdlib
1
6
  require 'yaml'
2
7
  require 'cmds'
3
- require 'parseconfig'
4
8
 
5
- require_relative 'role/errors'
9
+ # deps
6
10
 
7
- require 'nrser/refinements'
11
+ # package
12
+ require_relative './role/errors'
13
+
14
+
15
+ # Refinements
16
+ # =======================================================================
8
17
 
18
+ require 'nrser/refinements'
9
19
  using NRSER
10
20
 
11
- module QB
12
- # contains info on a QB role.
21
+
22
+ # Declarations
23
+ # =======================================================================
24
+
25
+ module QB; end
26
+
27
+
28
+ # Contains info on a QB role.
29
+ #
30
+ class QB::Role
31
+
32
+ # Constants
33
+ # =====================================================================
34
+
35
+ # Array of string paths to directories to search for roles or paths to
36
+ # `ansible.cfg` files to look for an extract role paths from.
13
37
  #
38
+ # For the moment at least you can just mutate this value like you would
39
+ # `$LOAD_PATH`:
14
40
  #
15
- class Role
16
-
17
- # Constants
18
- # =====================================================================
41
+ # QB::Role::PATH.unshift '~/where/some/roles/be'
42
+ # QB::Role::PATH.unshift '~/my/ansible.cfg'
43
+ #
44
+ # The paths are searched from first to last.
45
+ #
46
+ # **WARNING**
47
+ #
48
+ # Search is **deep** - don't point this at large directory trees and
49
+ # expect any sort of reasonable performance (any directory that
50
+ # contains `node_modules` is usually a terrible idea for instance).
51
+ #
52
+ BUILTIN_PATH = [
19
53
 
20
- # Array of string paths to directories to search for roles or paths to
21
- # `ansible.cfg` files to look for an extract role paths from.
54
+ # Development Paths
55
+ # =================
22
56
  #
23
- # For the moment at least you can just mutate this value like you would
24
- # `$LOAD_PATH`:
57
+ # These come first because:
25
58
  #
26
- # QB::Role::PATH.unshift '~/where/some/roles/be'
27
- # QB::Role::PATH.unshift '~/my/ansible.cfg'
59
+ # 1. They are working dir-local.
28
60
  #
29
- # The paths are searched from first to last.
61
+ # 2. They should only be present in local development, and should be
62
+ # capable of overriding roles in other local directories to allow
63
+ # custom development behavior (the same way `./dev/bin` is put in
64
+ # front or `./bin`).
30
65
  #
31
- # **WARNING**
32
- #
33
- # Search is **deep** - don't point this at large directory trees and
34
- # expect any sort of reasonable performance (any directory that
35
- # contains `node_modules` is usually a terrible idea for instance).
36
- #
37
- BUILTIN_PATH = [
38
-
39
- # Development Paths
40
- # =================
41
- #
42
- # These come first because:
43
- #
44
- # 1. They are working dir-local.
45
- #
46
- # 2. They should only be present in local development, and should be
47
- # capable of overriding roles in other local directories to allow
48
- # custom development behavior (the same way `./dev/bin` is put in
49
- # front or `./bin`).
50
- #
51
-
52
- # Role paths declared in ./dev/ansible.cfg, if it exists.
53
- File.join('.', 'dev', 'ansible.cfg'),
54
-
55
- # Roles in ./dev/roles
56
- File.join('.', 'dev', 'roles'),
57
-
58
-
59
- # Working Directory Paths
60
- # =======================
61
- #
62
- # Next up, `ansible.cfg` and `roles` directory in the working dir.
63
- # Makes sense, right?
64
- #
65
-
66
- # ./ansible.cfg
67
- File.join('.', 'ansible.cfg'),
68
-
69
- # ./roles
70
- File.join('.', 'roles'),
71
-
72
-
73
- # Working Directory-Local Ansible Directory
74
- # =========================================
75
- #
76
- # `ansible.cfg` and `roles` in a `./ansible` directory, making a common
77
- # place to put Ansible stuff in an project accessible when running from
78
- # the project root.
79
- #
80
-
81
- # ./ansible/ansible.cfg
82
- File.join('.', 'ansible', 'ansible.cfg'),
83
-
84
- # ./ansible/roles
85
- File.join('.', 'ansible', 'roles'),
86
-
87
- # TODO Git repo root relative?
88
- # Some sort of flag file for a find-up?
89
- # System Ansible locations?
90
-
91
-
92
- # QB Gem Role Directories
93
- # =======================
94
- #
95
- # Last, but far from least, paths provided by the QB Gem to the user's
96
- # QB role install location and the roles that come built-in to the gem.
97
-
98
- QB::USER_ROLES_DIR,
99
-
100
- QB::GEM_ROLES_DIR,
101
- ].freeze
102
66
 
67
+ # Role paths declared in ./dev/ansible.cfg, if it exists.
68
+ File.join('.', 'dev', 'ansible.cfg'),
103
69
 
104
- # Array of string paths to directories to search for roles or paths to
105
- # `ansible.cfg` files to look for an extract role paths from.
106
- #
107
- # Value is a duplicate of the frozen {QB::Role::BUILTIN_PATH}. You can
108
- # reset to those values at any time via {QB::Role.reset_path!}.
109
- #
110
- # For the moment at least you can just mutate this value like you would
111
- # `$LOAD_PATH`:
112
- #
113
- # QB::Role::PATH.unshift '~/where/some/roles/be'
114
- # QB::Role::PATH.unshift '~/my/ansible.cfg'
115
- #
116
- # The paths are searched from first to last.
117
- #
118
- # **WARNING**
70
+ # Roles in ./dev/roles
71
+ File.join('.', 'dev', 'roles'),
72
+
73
+
74
+ # Working Directory Paths
75
+ # =======================
119
76
  #
120
- # Search is **deep** - don't point this at large directory trees and
121
- # expect any sort of reasonable performance (any directory that
122
- # contains `node_modules` is usually a terrible idea for instance).
77
+ # Next up, `ansible.cfg` and `roles` directory in the working dir.
78
+ # Makes sense, right?
123
79
  #
124
- PATH = BUILTIN_PATH.dup
125
-
126
80
 
127
- # Class Methods
128
- # =======================================================================
81
+ # ./ansible.cfg
82
+ File.join('.', 'ansible.cfg'),
129
83
 
84
+ # ./roles
85
+ File.join('.', 'roles'),
130
86
 
131
- # Reset {QB::Role::PATH} to the original built-in values in
132
- # {QB::Role::BUILTIN_PATH}.
133
- #
134
- # Created for testing but might be useful elsewhere as well.
135
- #
136
- # @return [Array<String>]
137
- # The reset {QB::Role::PATH}.
138
- #
139
- def self.reset_path!
140
- PATH.clear
141
- BUILTIN_PATH.each { |path| PATH << path }
142
- PATH
143
- end # .reset_path!
144
-
145
-
146
- # true if pathname is a QB role directory.
147
- def self.role_dir? pathname
148
- # must be a directory
149
- pathname.directory? &&
150
- # and must have meta/qb.yml or meta/qb file
151
- ['qb.yml', 'qb'].any? {|filename| pathname.join('meta', filename).file?}
152
- end
153
87
 
154
- # Get role paths from ansible.cfg if it exists in a directory.
88
+ # Working Directory-Local Ansible Directory
89
+ # =========================================
155
90
  #
156
- # @param dir [Pathname] directory to look for ansible.cfg in.
91
+ # `ansible.cfg` and `roles` in a `./ansible` directory, making a common
92
+ # place to put Ansible stuff in an project accessible when running from
93
+ # the project root.
157
94
  #
158
- # @return [Array<String>]
159
- # Absolute role paths.
160
- #
161
- def self.cfg_roles_paths path
162
- path = File.join(path, 'ansible.cfg') unless File.basename(path) == 'ansible.cfg'
163
-
164
- if path.file?
165
- config = ParseConfig.new path.to_s
166
- config['defaults']['roles_path'].split(':').map {|path|
167
- QB::Util.resolve dir, path
168
- }
169
- else
170
- []
171
- end
172
- end
173
95
 
174
- # @param dir [Pathname] dir to include.
175
- def self.roles_paths dir
176
- cfg_roles_path(dir) + [
177
- dir.join('roles'),
178
- dir.join('roles', 'tmp'),
179
- ]
180
- end
96
+ # ./ansible/ansible.cfg
97
+ File.join('.', 'ansible', 'ansible.cfg'),
181
98
 
182
- # places to look for qb role directories. these paths are also included
183
- # when qb runs a playbook.
184
- #
185
- # TODO resolution order:
186
- #
187
- # 1. paths specific to this run:
188
- # a. TODO paths provided on the cli.
189
- # 2. paths specific to the current directory:
190
- # a. paths specified in ./ansible.cfg (if it exists)
191
- # b. ./roles
192
- # d. paths specified in ./ansible/ansible.cfg (if it exists)
193
- # e. ./ansible/roles
194
- # g. paths specified in ./dev/ansible.cfg (if it exists)
195
- # h. ./dev/roles
196
- # i. ./dev/roles/tmp
197
- # - used for roles that are downloaded but shouldn't be included
198
- # in source control.
199
- # 3.
200
- #
201
- # @return [Array<Pathname>]
202
- # places to look for role dirs.
203
- #
204
- def self.search_path
205
- QB::Role::PATH.
206
- map { |path|
207
- if QB::Ansible::ConfigFile.end_with_config_file?(path)
208
- if File.file?(path)
209
- QB::Ansible::ConfigFile.new(path).defaults.roles_path
210
- end
211
- else
212
- QB::Util.resolve path
213
- end
214
- }.
215
- flatten.
216
- reject(&:nil?)
217
- end
99
+ # ./ansible/roles
100
+ File.join('.', 'ansible', 'roles'),
218
101
 
219
- # array of QB::Role found in search path.
220
- def self.available
221
- search_path.
222
- select {|search_dir|
223
- # make sure it's there (and a directory)
224
- search_dir.directory?
225
- }.
226
- map {|search_dir|
227
- Pathname.glob(search_dir.join '**', 'meta', 'qb.yml').
228
- map {|meta_path|
229
- [meta_path.dirname.dirname, search_dir: search_dir]
230
- }
231
- }.
232
- flatten(1).
233
- map {|args|
234
- QB::Role.new *args
235
- }.
236
- uniq
237
- end
102
+ # TODO Git repo root relative?
103
+ # Some sort of flag file for a find-up?
104
+ # System Ansible locations?
238
105
 
239
- # Get an array of QB::Role that match an input string.
240
- #
241
- # @return [Array<QB::Role>]
106
+
107
+ # QB Gem Role Directories
108
+ # =======================
242
109
  #
243
- def self.matches input
244
- # keep this here to we don't re-gen every loop
245
- available = self.available
246
-
247
- # first off, see if input matches any relative paths exactly
248
- available.each {|role|
249
- return [role] if role.display_path.to_s == input
250
- }
251
-
252
- # create an array of "separator" variations to try *exact* matching
253
- # against. in order of preference:
254
- #
255
- # 1. exact input
256
- # - this means if you ended up with roles that actually *are*
257
- # differnetiated by '_/-' differences (which, IMHO, is a
258
- # horrible fucking idea), you can get exactly what you ask for
259
- # as a first priority
260
- # 2. input with '-' changed to '_'
261
- # - prioritized because convetion is to underscore-separate
262
- # role names.
263
- # 3. input with '_' changed to '-'
264
- # - really just for convience's sake so you don't really have to
265
- # remember what separator is used.
266
- #
267
- separator_variations = [
268
- input,
269
- input.gsub('-', '_'),
270
- input.gsub('_', '-'),
271
- ]
272
-
273
- separator_variations.each { |variation|
274
- available.each { |role|
275
- # exact match to full name
276
- return [role] if role.name == variation
277
- }.each { |role|
278
- # exact match without the namespace prefix ('qb.' or similar)
279
- return [role] if role.namespaceless == variation
280
- }
281
- }
282
-
283
- # see if we prefix match any full names
284
- separator_variations.each { |variation|
285
- name_prefix_matches = available.select { |role|
286
- role.name.start_with? variation
287
- }
288
- return name_prefix_matches unless name_prefix_matches.empty?
289
- }
290
-
291
- # see if we prefix match any name
292
- separator_variations.each { |variation|
293
- namespaceless_prefix_matches = available.select { |role|
294
- role.namespaceless.start_with? variation
295
- }
296
- unless namespaceless_prefix_matches.empty?
297
- return namespaceless_prefix_matches
298
- end
299
- }
300
-
301
- # see if we prefix match any display paths
302
- separator_variations.each { |variation|
303
- path_prefix_matches = available.select { |role|
304
- role.display_path.start_with? variation
305
- }
306
- return path_prefix_matches unless path_prefix_matches.empty?
307
- }
308
-
309
- # see if we word match any display paths
310
- name_word_matches = available.select { |role|
311
- QB::Util.words_start_with? role.display_path.to_s, input
312
- }
313
- return name_word_matches unless name_word_matches.empty?
314
-
315
- # nada
316
- []
317
- end # .matches
110
+ # Last, but far from least, paths provided by the QB Gem to the user's
111
+ # QB role install location and the roles that come built-in to the gem.
318
112
 
319
- # find exactly one matching role for the input string or raise.
320
- def self.require input
321
-
322
- as_pathname = Pathname.new(input)
323
-
324
- # allow a path to a role dir
325
- if role_dir? as_pathname
326
- return Role.new as_pathname
327
- end
328
-
329
- matches = self.matches input
330
-
331
- role = case matches.length
332
- when 0
333
- raise NoMatchesError.new input
334
- when 1
335
- matches[0]
336
- else
337
- raise MultipleMatchesError.new input, matches
338
- end
339
-
340
- QB.debug "role match" => role
341
-
342
- role
343
- end
113
+ QB::USER_ROLES_DIR,
344
114
 
345
- # get the include path for an included role based on the
346
- # option metadata that defines the include and the current
347
- # include path.
348
- #
349
- # @param role [Role]
350
- # the role to include.
351
- #
352
- # @param option_meta [Hash]
353
- # the entry for the option in qb.yml
354
- #
355
- # @param current_include_path [Array<string>]
356
- #
357
- # @return [Array<string>]
358
- # include path for the included role.
359
- #
360
- def self.get_include_path role, option_meta, current_include_path
361
- new_include_path = if option_meta.key? 'as'
362
- case option_meta['as']
363
- when nil, false
364
- # include it in with the parent role's options
365
- current_include_path
366
- when String
367
- current_include_path + [option_meta['as']]
115
+ QB::GEM_ROLES_DIR,
116
+ ].freeze
117
+
118
+
119
+ # Array of string paths to directories to search for roles or paths to
120
+ # `ansible.cfg` files to look for an extract role paths from.
121
+ #
122
+ # Value is a duplicate of the frozen {QB::Role::BUILTIN_PATH}. You can
123
+ # reset to those values at any time via {QB::Role.reset_path!}.
124
+ #
125
+ # For the moment at least you can just mutate this value like you would
126
+ # `$LOAD_PATH`:
127
+ #
128
+ # QB::Role::PATH.unshift '~/where/some/roles/be'
129
+ # QB::Role::PATH.unshift '~/my/ansible.cfg'
130
+ #
131
+ # The paths are searched from first to last.
132
+ #
133
+ # **WARNING**
134
+ #
135
+ # Search is **deep** - don't point this at large directory trees and
136
+ # expect any sort of reasonable performance (any directory that
137
+ # contains `node_modules` is usually a terrible idea for instance).
138
+ #
139
+ PATH = BUILTIN_PATH.dup
140
+
141
+
142
+ # Class Methods
143
+ # =======================================================================
144
+
145
+ # Reset {QB::Role::PATH} to the original built-in values in
146
+ # {QB::Role::BUILTIN_PATH}.
147
+ #
148
+ # Created for testing but might be useful elsewhere as well.
149
+ #
150
+ # @return [Array<String>]
151
+ # The reset {QB::Role::PATH}.
152
+ #
153
+ def self.reset_path!
154
+ PATH.clear
155
+ BUILTIN_PATH.each { |path| PATH << path }
156
+ PATH
157
+ end # .reset_path!
158
+
159
+
160
+ # true if pathname is a QB role directory.
161
+ def self.role_dir? pathname
162
+ # must be a directory
163
+ pathname.directory? &&
164
+ # and must have meta/qb.yml or meta/qb file
165
+ ['qb.yml', 'qb'].any? {|filename| pathname.join('meta', filename).file?}
166
+ end
167
+
168
+
169
+ # @param dir [Pathname] dir to include.
170
+ def self.roles_paths dir
171
+ cfg_roles_path(dir) + [
172
+ dir.join('roles'),
173
+ dir.join('roles', 'tmp'),
174
+ ]
175
+ end
176
+
177
+ # places to look for qb role directories. these paths are also included
178
+ # when qb runs a playbook.
179
+ #
180
+ # TODO resolution order:
181
+ #
182
+ # 1. paths specific to this run:
183
+ # a. TODO paths provided on the cli.
184
+ # 2. paths specific to the current directory:
185
+ # a. paths specified in ./ansible.cfg (if it exists)
186
+ # b. ./roles
187
+ # d. paths specified in ./ansible/ansible.cfg (if it exists)
188
+ # e. ./ansible/roles
189
+ # g. paths specified in ./dev/ansible.cfg (if it exists)
190
+ # h. ./dev/roles
191
+ # i. ./dev/roles/tmp
192
+ # - used for roles that are downloaded but shouldn't be included
193
+ # in source control.
194
+ # 3.
195
+ #
196
+ # @return [Array<Pathname>]
197
+ # places to look for role dirs.
198
+ #
199
+ def self.search_path
200
+ QB::Role::PATH.
201
+ map { |path|
202
+ if QB::Ansible::ConfigFile.end_with_config_file?(path)
203
+ if File.file?(path)
204
+ QB::Ansible::ConfigFile.new(path).defaults.roles_path
205
+ end
368
206
  else
369
- raise QB::Role::MetadataError.new,
370
- "bad 'as' value: #{ option_meta.inspect }"
207
+ QB::Util.resolve path
371
208
  end
372
- else
373
- current_include_path + [role.namespaceless]
374
- end
375
- end
376
-
209
+ }.
210
+ flatten.
211
+ reject(&:nil?)
212
+ end
213
+
214
+ # array of QB::Role found in search path.
215
+ def self.available
216
+ search_path.
217
+ select {|search_dir|
218
+ # make sure it's there (and a directory)
219
+ search_dir.directory?
220
+ }.
221
+ map {|search_dir|
222
+ Pathname.glob(search_dir.join '**', 'meta', 'qb.yml').
223
+ map {|meta_path|
224
+ [meta_path.dirname.dirname, search_dir: search_dir]
225
+ }
226
+ }.
227
+ flatten(1).
228
+ map {|args|
229
+ QB::Role.new *args
230
+ }.
231
+ uniq
232
+ end
233
+
234
+ # Get an array of QB::Role that match an input string.
235
+ #
236
+ # @return [Array<QB::Role>]
237
+ #
238
+ def self.matches input
239
+ # keep this here to we don't re-gen every loop
240
+ available = self.available
241
+
242
+ # first off, see if input matches any relative paths exactly
243
+ available.each {|role|
244
+ return [role] if role.display_path.to_s == input
245
+ }
246
+
247
+ # create an array of "separator" variations to try *exact* matching
248
+ # against. in order of preference:
249
+ #
250
+ # 1. exact input
251
+ # - this means if you ended up with roles that actually *are*
252
+ # differnetiated by '_/-' differences (which, IMHO, is a
253
+ # horrible fucking idea), you can get exactly what you ask for
254
+ # as a first priority
255
+ # 2. input with '-' changed to '_'
256
+ # - prioritized because convetion is to underscore-separate
257
+ # role names.
258
+ # 3. input with '_' changed to '-'
259
+ # - really just for convience's sake so you don't really have to
260
+ # remember what separator is used.
261
+ #
262
+ separator_variations = [
263
+ input,
264
+ input.gsub('-', '_'),
265
+ input.gsub('_', '-'),
266
+ ]
267
+
268
+ separator_variations.each { |variation|
269
+ available.each { |role|
270
+ # exact match to full name
271
+ return [role] if role.name == variation
272
+ }.each { |role|
273
+ # exact match without the namespace prefix ('qb.' or similar)
274
+ return [role] if role.namespaceless == variation
275
+ }
276
+ }
277
+
278
+ # see if we prefix match any full names
279
+ separator_variations.each { |variation|
280
+ name_prefix_matches = available.select { |role|
281
+ role.name.start_with? variation
282
+ }
283
+ return name_prefix_matches unless name_prefix_matches.empty?
284
+ }
377
285
 
378
- # the path we display in the CLI, see {#display_path}.
379
- #
380
- # @param [Pathname | String] path
381
- # input path to transform.
382
- #
383
- # @return [Pathname]
384
- # path to display.
385
- #
386
- def self.to_display_path path
387
- if path.realpath.start_with? QB::GEM_ROLES_DIR
388
- path.realpath.sub (QB::GEM_ROLES_DIR.to_s + '/'), ''
389
- else
390
- QB::Util.contract_path path
286
+ # see if we prefix match any name
287
+ separator_variations.each { |variation|
288
+ namespaceless_prefix_matches = available.select { |role|
289
+ role.namespaceless.start_with? variation
290
+ }
291
+ unless namespaceless_prefix_matches.empty?
292
+ return namespaceless_prefix_matches
391
293
  end
392
- end
393
-
294
+ }
394
295
 
395
- def self.guess_role_name dest
396
- path = QB::Util.resolve dest
397
-
398
- search_dirs = search_path.find_all { |pathname|
399
- path.fnmatch? pathname.join('**')
296
+ # see if we prefix match any display paths
297
+ separator_variations.each { |variation|
298
+ path_prefix_matches = available.select { |role|
299
+ role.display_path.start_with? variation
400
300
  }
301
+ return path_prefix_matches unless path_prefix_matches.empty?
302
+ }
303
+
304
+ # see if we word match any display paths
305
+ name_word_matches = available.select { |role|
306
+ QB::Util.words_start_with? role.display_path.to_s, input
307
+ }
308
+ return name_word_matches unless name_word_matches.empty?
309
+
310
+ # nada
311
+ []
312
+ end # .matches
313
+
314
+
315
+ # Find exactly one matching role for the input string or raise.
316
+ #
317
+ # Where we look is determined by {QB::Role::PATH} via {QB::Role.search_path}.
318
+ #
319
+ # @param [String] input
320
+ # Input string term used to search (what we got off the CLI args).
321
+ #
322
+ # @return [QB::Role]
323
+ # The single matching role.
324
+ #
325
+ # @raise [QB::Role::NoMatchesError]
326
+ # If we didn't find any matches.
327
+ #
328
+ # @raise [QB::Role::MultipleMatchesError]
329
+ # If we matched more than one role.
330
+ #
331
+ def self.require input
332
+ as_pathname = Pathname.new(input)
401
333
 
402
- case search_dirs.length
403
- when 0
404
- # It's not in any of the search directories
405
- #
406
- # If it has 'roles' as a segment than use what's after that
407
- #
408
- split = path.to_s.split('/roles/')
409
-
410
- end
334
+ # allow a path to a role dir
335
+ if role_dir? as_pathname
336
+ return QB::Role.new as_pathname
411
337
  end
412
338
 
339
+ matches = self.matches input
413
340
 
414
- # Attributes
415
- # =======================================================================
416
-
417
- # @!attribute [r] path
418
- # @return [Pathname]
419
- # location of the role directory.
420
- attr_reader :path
421
-
422
-
423
- # @!attribute [r] name
424
- # @return [String]
425
- # the role's ansible "name", which is it's directory name.
426
- attr_reader :name
427
-
428
-
429
- # @!attribute [r] display_path
430
- #
431
- # the path to the role that we display. we only show the directory name
432
- # for QB roles, and use {QB::Util.compact_path} to show `.` and `~` for
433
- # paths relative to the current directory and home directory, respectively.
434
- #
435
- # @return [Pathname]
436
- attr_reader :display_path
437
-
438
-
439
- # @!attribute [r] meta_path
440
- # @return [String, nil]
441
- # the path qb metadata was load from. `nil` if it's never been loaded
442
- # or doesn't exist.
443
- attr_reader :meta_path
444
-
445
-
446
- # Constructor
447
- # =======================================================================
448
-
449
- # Instantiate a Role.
450
- #
451
- # @param [String|Pathname] path
452
- # location of the role directory
453
- #
454
- # @param [nil, Pathname] search_dir
455
- # Directory in {QB::Role.search_path} that the role was found in.
456
- # Used to figure out it's name correctly when using directory-structure
457
- # namespacing.
458
- #
459
- def initialize path, search_dir: nil
460
- @path = if path.is_a?(Pathname) then path else Pathname.new(path) end
461
-
462
- # check it...
463
- unless @path.exist?
464
- raise Errno::ENOENT.new @path.to_s
465
- end
466
-
467
- unless @path.directory?
468
- raise Errno::ENOTDIR.new @path.to_s
469
- end
470
-
471
- @display_path = self.class.to_display_path @path
472
-
473
- @meta_path = if (@path + 'meta' + 'qb').exist?
474
- @path + 'meta' + 'qb'
475
- elsif (@path + 'meta' + 'qb.yml').exist?
476
- @path + 'meta' + 'qb.yml'
477
- else
478
- raise Errno::ENOENT.new "#{ @path.join('meta').to_s }/[qb|qb.yml]"
479
- end
480
-
481
-
482
- if search_dir.nil?
483
- @name = @path.to_s.split(File::SEPARATOR).last
484
- else
485
- @name = @path.relative_path_from(search_dir).to_s
486
- end
487
- end # #initialize
488
-
341
+ role = case matches.length
342
+ when 0
343
+ raise QB::Role::NoMatchesError.new input
344
+ when 1
345
+ matches[0]
346
+ else
347
+ raise QB::Role::MultipleMatchesError.new input, matches
348
+ end
489
349
 
490
- # Instance Methods
491
- # =====================================================================
350
+ QB.debug "role match" => role
492
351
 
493
- def namespace
494
- *namespace_segments, last = @name.split File::Separator
495
-
496
- namespace_segments << last.split('.').first if last.include?('.')
497
-
498
- if namespace_segments.empty?
499
- nil
352
+ role
353
+ end # .require
354
+
355
+
356
+ # Get the include path for an included role based on the
357
+ # option metadata that defines the include and the current
358
+ # include path.
359
+ #
360
+ # @param role [Role]
361
+ # the role to include.
362
+ #
363
+ # @param option_meta [Hash]
364
+ # the entry for the option in qb.yml
365
+ #
366
+ # @param current_include_path [Array<string>]
367
+ #
368
+ # @return [Array<string>]
369
+ # include path for the included role.
370
+ #
371
+ def self.get_include_path role, option_meta, current_include_path
372
+ new_include_path = if option_meta.key? 'as'
373
+ case option_meta['as']
374
+ when nil, false
375
+ # include it in with the parent role's options
376
+ current_include_path
377
+ when String
378
+ current_include_path + [option_meta['as']]
500
379
  else
501
- File.join *namespace_segments
380
+ raise QB::Role::MetadataError.new,
381
+ "bad 'as' value: #{ option_meta.inspect }"
502
382
  end
383
+ else
384
+ current_include_path + [role.namespaceless]
503
385
  end
386
+ end
387
+
388
+
389
+ # The path we display in the CLI, see {#display_path}.
390
+ #
391
+ # @param [Pathname | String] path
392
+ # input path to transform.
393
+ #
394
+ # @return [Pathname]
395
+ # path to display.
396
+ #
397
+ def self.to_display_path path
398
+ if path.realpath.start_with? QB::GEM_ROLES_DIR
399
+ path.realpath.sub (QB::GEM_ROLES_DIR.to_s + '/'), ''
400
+ else
401
+ QB::Util.contract_path path
402
+ end
403
+ end
404
+
405
+
406
+ # @todo This was gonna be something at some point, but didn't get done.
407
+ #
408
+ def self.guess_role_name dest
409
+ raise "TODO: Not implemented!!!"
504
410
 
505
- def namespaceless
506
- File.basename(@name).split('.', 2).last
507
- end
411
+ path = QB::Util.resolve dest
508
412
 
509
- def options_key
510
- @display_path.to_s
511
- end
413
+ search_dirs = search_path.find_all { |pathname|
414
+ path.fnmatch? pathname.join('**')
415
+ }
512
416
 
513
- # load qb metadata from meta/qb.yml or from executing meta/qb and parsing
514
- # the YAML written to stdout.
515
- #
516
- # if `cache` is true caches it as `@meta`
517
- #
518
- def load_meta cache = true
519
- meta = if @meta_path.extname == '.yml'
520
- contents = begin
521
- @meta_path.read
522
- rescue Exception => error
523
- raise QB::Role::MetadataError,
524
- "Failed to read metadata file at #{ @meta_path.to_s }, " +
525
- "error: #{ error.inspect }"
526
- end
527
-
528
- begin
529
- YAML.load(contents) || {}
530
- rescue Exception => error
531
- raise QB::Role::MetadataError,
532
- "Failed to load metadata YAML from #{ @meta_path.to_s }, " +
533
- "error: #{ error.inspect }"
534
- end
535
- else
536
- YAML.load(Cmds.out!(@meta_path.realpath.to_s)) || {}
417
+ case search_dirs.length
418
+ when 0
419
+ # It's not in any of the search directories
420
+ #
421
+ # If it has 'roles' as a segment than use what's after that
422
+ #
423
+ split = path.to_s.split('/roles/')
424
+
425
+ end
426
+ end
427
+
428
+
429
+ # Instance Attributes
430
+ # =======================================================================
431
+
432
+ # @!attribute [r] path
433
+ # @return [Pathname]
434
+ # location of the role directory.
435
+ attr_reader :path
436
+
437
+
438
+ # @!attribute [r] name
439
+ # @return [String]
440
+ # the role's ansible "name", which is it's directory name.
441
+ attr_reader :name
442
+
443
+
444
+ # @!attribute [r] display_path
445
+ #
446
+ # the path to the role that we display. we only show the directory name
447
+ # for QB roles, and use {QB::Util.compact_path} to show `.` and `~` for
448
+ # paths relative to the current directory and home directory, respectively.
449
+ #
450
+ # @return [Pathname]
451
+ attr_reader :display_path
452
+
453
+
454
+ # @!attribute [r] meta_path
455
+ # @return [String, nil]
456
+ # the path qb metadata was load from. `nil` if it's never been loaded
457
+ # or doesn't exist.
458
+ attr_reader :meta_path
459
+
460
+
461
+ # Constructor
462
+ # =======================================================================
463
+
464
+ # Instantiate a Role.
465
+ #
466
+ # @param [String|Pathname] path
467
+ # location of the role directory
468
+ #
469
+ # @param [nil, Pathname] search_dir
470
+ # Directory in {QB::Role.search_path} that the role was found in.
471
+ # Used to figure out it's name correctly when using directory-structure
472
+ # namespacing.
473
+ #
474
+ def initialize path, search_dir: nil
475
+ @path = if path.is_a?(Pathname) then path else Pathname.new(path) end
476
+
477
+ # check it...
478
+ unless @path.exist?
479
+ raise Errno::ENOENT.new @path.to_s
480
+ end
481
+
482
+ unless @path.directory?
483
+ raise Errno::ENOTDIR.new @path.to_s
484
+ end
485
+
486
+ @display_path = self.class.to_display_path @path
487
+
488
+ @meta_path = if (@path + 'meta' + 'qb').exist?
489
+ @path + 'meta' + 'qb'
490
+ elsif (@path + 'meta' + 'qb.yml').exist?
491
+ @path + 'meta' + 'qb.yml'
492
+ else
493
+ raise Errno::ENOENT.new "#{ @path.join('meta').to_s }/[qb|qb.yml]"
494
+ end
495
+
496
+
497
+ if search_dir.nil?
498
+ @name = @path.to_s.split(File::SEPARATOR).last
499
+ else
500
+ @name = @path.relative_path_from(search_dir).to_s
501
+ end
502
+ end # #initialize
503
+
504
+
505
+ # Instance Methods
506
+ # =====================================================================
507
+
508
+ def namespace
509
+ *namespace_segments, last = @name.split File::Separator
510
+
511
+ namespace_segments << last.split('.').first if last.include?('.')
512
+
513
+ if namespace_segments.empty?
514
+ nil
515
+ else
516
+ File.join *namespace_segments
517
+ end
518
+ end
519
+
520
+ def namespaceless
521
+ File.basename(@name).split('.', 2).last
522
+ end
523
+
524
+ def options_key
525
+ @display_path.to_s
526
+ end
527
+
528
+ # load qb metadata from meta/qb.yml or from executing meta/qb and parsing
529
+ # the YAML written to stdout.
530
+ #
531
+ # if `cache` is true caches it as `@meta`
532
+ #
533
+ def load_meta cache = true
534
+ meta = if @meta_path.extname == '.yml'
535
+ contents = begin
536
+ @meta_path.read
537
+ rescue Exception => error
538
+ raise QB::Role::MetadataError,
539
+ "Failed to read metadata file at #{ @meta_path.to_s }, " +
540
+ "error: #{ error.inspect }"
537
541
  end
538
542
 
539
- if cache
540
- @meta = meta
543
+ begin
544
+ YAML.load(contents) || {}
545
+ rescue Exception => error
546
+ raise QB::Role::MetadataError,
547
+ "Failed to load metadata YAML from #{ @meta_path.to_s }, " +
548
+ "error: #{ error.inspect }"
541
549
  end
542
-
543
- meta
550
+ else
551
+ YAML.load(Cmds.out!(@meta_path.realpath.to_s)) || {}
544
552
  end
545
553
 
546
- # gets the qb metadata for the role
547
- def meta
548
- @meta || load_meta
554
+ if cache
555
+ @meta = meta
549
556
  end
550
557
 
551
- # gets the variable prefix that will be appended to cli options before
552
- # passing them to the role. defaults to `#namespaceless` unless specified
553
- # in meta.
554
- def var_prefix
555
- # ugh, i was generating meta/qb.yml files that set 'var_prefix' to
556
- # `null`, but it would be nice to
557
- #
558
- meta_or 'var_prefix', namespaceless
559
- end
560
-
561
- # get the options from the metadata, defaulting to [] if none defined
562
- def option_metas
563
- meta_or ['options', 'opts', 'vars'], []
564
- end
565
-
566
- # get an array of Option for the role, including any included roles
567
- def options include_path = []
568
- option_metas.map {|option_meta|
569
- if option_meta.key? 'include'
570
- role_name = option_meta['include']
571
- role = QB::Role.require role_name
572
- role.options Role.get_include_path(role, option_meta, include_path)
573
- else
574
- QB::Options::Option.new self, option_meta, include_path
575
- end
576
- }.flatten
577
- end
578
-
579
- # loads the defaults from vars/main.yml and defaults/main.yml,
580
- # caching by default. vars override defaults values.
581
- def load_defaults cache = true
582
- defaults_path = @path + 'defaults' + 'main.yml'
583
- defaults = if defaults_path.file?
584
- YAML.load(defaults_path.read) || {}
558
+ meta
559
+ end
560
+
561
+ # @return [Hash{String => Object}]
562
+ # the QB metadata for the role.
563
+ #
564
+ def meta
565
+ @meta || load_meta
566
+ end
567
+
568
+
569
+ # gets the variable prefix that will be appended to cli options before
570
+ # passing them to the role. defaults to `#namespaceless` unless specified
571
+ # in meta.
572
+ def var_prefix
573
+ # ugh, i was generating meta/qb.yml files that set 'var_prefix' to
574
+ # `null`, but it would be nice to
575
+ #
576
+ meta_or 'var_prefix', namespaceless
577
+ end
578
+
579
+
580
+ # get the options from the metadata, defaulting to [] if none defined
581
+ def option_metas
582
+ meta_or ['options', 'opts', 'vars'], []
583
+ end
584
+
585
+
586
+ # @return [Array<QB::Options::Option>
587
+ # an array of Option for the role, including any included roles.
588
+ #
589
+ def options include_path = []
590
+ option_metas.map {|option_meta|
591
+ if option_meta.key? 'include'
592
+ role_name = option_meta['include']
593
+ role = QB::Role.require role_name
594
+ role.options Role.get_include_path(role, option_meta, include_path)
585
595
  else
586
- {}
596
+ QB::Options::Option.new self, option_meta, include_path
587
597
  end
588
-
589
- vars_path = @path + 'vars' + 'main.yml'
590
- vars = if vars_path.file?
591
- YAML.load(vars_path.read) || {}
598
+ }.flatten
599
+ end # #options
600
+
601
+
602
+ # loads the defaults from vars/main.yml and defaults/main.yml,
603
+ # caching by default. vars override defaults values.
604
+ def load_defaults cache = true
605
+ defaults_path = @path + 'defaults' + 'main.yml'
606
+ defaults = if defaults_path.file?
607
+ YAML.load(defaults_path.read) || {}
608
+ else
609
+ {}
610
+ end
611
+
612
+ vars_path = @path + 'vars' + 'main.yml'
613
+ vars = if vars_path.file?
614
+ YAML.load(vars_path.read) || {}
615
+ else
616
+ {}
617
+ end
618
+
619
+ defaults = defaults.merge! vars
620
+
621
+ if cache
622
+ @defaults = defaults
623
+ end
624
+
625
+ defaults
626
+ end
627
+
628
+ # gets the role variable defaults from defaults/main.yml, or {}
629
+ def defaults
630
+ @defaults || load_defaults
631
+ end
632
+
633
+ def save_options
634
+ !!meta_or('save_options', true)
635
+ end
636
+
637
+ # if the exe should auto-make the directory. this is nice for most roles
638
+ # but some need it to be missing
639
+ def mkdir
640
+ !!meta_or('mkdir', true)
641
+ end
642
+
643
+ # @return [String]
644
+ # usage information formatted as plain text for the CLI.
645
+ #
646
+ def usage
647
+ # Split up options by required and optional.
648
+ required_options = []
649
+ optional_options = []
650
+
651
+ options.each { |option|
652
+ if option.required?
653
+ required_options << option
592
654
  else
593
- {}
655
+ optional_options << option
594
656
  end
595
-
596
- defaults = defaults.merge! vars
597
-
598
- if cache
599
- @defaults = defaults
600
- end
601
-
602
- defaults
603
- end
657
+ }
604
658
 
605
- # gets the role variable defaults from defaults/main.yml, or {}
606
- def defaults
607
- @defaults || load_defaults
608
- end
659
+ parts = ['qb [run]', name]
609
660
 
610
- def save_options
611
- !!meta_or('save_options', true)
612
- end
661
+ required_options.each { |option|
662
+ parts << option.usage
663
+ }
613
664
 
614
- # if the exe should auto-make the directory. this is nice for most roles
615
- # but some need it to be missing
616
- def mkdir
617
- !!meta_or('mkdir', true)
665
+ unless optional_options.empty?
666
+ parts << '[OPTIONS]'
618
667
  end
619
668
 
620
- def usage
621
- parts = ['qb', name]
622
- options.each {|option|
623
- if option.required?
624
- parts << option.usage
625
- end
626
- }
627
- parts << '[OPTIONS] DIRECTORY'
628
- parts.join(' ')
669
+ if uses_default_dir?
670
+ parts << 'DIRECTORY'
629
671
  end
630
672
 
631
- # get the CLI banner for the role
632
- def banner
633
- lines = []
634
-
635
- name_line = "#{ name } role"
636
- lines << name_line
637
- lines << "=" * name_line.length
638
- lines << ''
639
- if meta['description']
640
- lines << meta['description']
641
- lines << ''
642
- end
643
- lines << 'usage:'
644
- # lines << " qb #{ name } [OPTIONS] DIRECTORY"
645
- lines << " #{ usage }"
646
- lines << ''
647
- lines << 'options:'
648
-
649
- lines.join("\n")
650
- end
651
-
652
- def examples
653
- @meta['examples']
654
- end
655
-
656
- # format the `meta.examples` hash into a string suitable for cli
657
- # output.
658
- #
659
- # @return [String]
660
- # the CLI-formatted examples.
661
- #
662
- def format_examples
663
- examples.
664
- map {|title, body|
665
- [
666
- "#{ title }:",
667
- body.lines.map {|l|
668
- # only indent non-empty lines
669
- # makes compacting newline sequences easier (see below)
670
- if l.match(/^\s*$/)
671
- l
672
- else
673
- ' ' + l
674
- end
675
- },
676
- ''
677
- ]
678
- }.
679
- flatten.
680
- join("\n").
681
- # compact newline sequences
682
- gsub(/\n\n+/, "\n\n")
683
- end
673
+ parts.join ' '
674
+ end
675
+
676
+
677
+ # get the CLI banner for the role
678
+ def banner
679
+ lines = []
684
680
 
685
- # examples text
686
- def puts_examples
687
- return unless examples
688
-
689
- puts "\n" + format_examples + "\n"
690
- end
691
-
692
- # should qb ask for an ansible vault password?
693
- #
694
- # @see http://docs.ansible.com/ansible/playbooks_vault.html
695
- #
696
- # @return [Boolean]
697
- # `true` if qb should ask for a vault password.
698
- #
699
- def ask_vault_pass?
700
- !!@meta['ask_vault_pass']
681
+ name_line = "#{ name } role"
682
+ lines << name_line
683
+ lines << "=" * name_line.length
684
+ lines << ''
685
+ if meta['description']
686
+ lines << meta['description']
687
+ lines << ''
701
688
  end
702
-
703
- # gets the default `qb_dir` value, raising an error if the role doesn't
704
- # define how to get one or there is a problem getting it.
705
- #
706
- #
707
- def default_dir cwd, options
708
- QB.debug "get_default_dir", role: self,
709
- meta: self.meta,
710
- cwd: cwd,
711
- options: options
712
-
713
- key = 'default_dir'
714
- value = self.meta[key]
715
- case value
716
- when nil
717
- # there is no get_dir info in meta/qb.yml, can't get the dir
718
- raise QB::UserInputError.dedented <<-END
719
- unable to infer default directory: no '#{ key }' key in 'meta/qb.yml'
720
- for role #{ self }
721
- END
722
-
723
- when false
724
- # this method should not get called when the value is false (an entire
725
- # section is skipped in exe/qb when `default_dir = false`)
726
- raise QB::StateError.squished <<-END
727
- role does not use default directory (meta/qb.yml:default_dir = false)
728
- END
689
+ lines << 'usage:'
690
+ lines << " #{ usage }"
691
+ lines << ''
692
+ lines << 'options:'
693
+
694
+ lines.join("\n")
695
+ end
696
+
697
+
698
+ def examples
699
+ @meta['examples']
700
+ end
701
+
702
+ # format the `meta.examples` hash into a string suitable for cli
703
+ # output.
704
+ #
705
+ # @return [String]
706
+ # the CLI-formatted examples.
707
+ #
708
+ def format_examples
709
+ examples.
710
+ map {|title, body|
711
+ [
712
+ "#{ title }:",
713
+ body.lines.map {|l|
714
+ # only indent non-empty lines
715
+ # makes compacting newline sequences easier (see below)
716
+ if l.match(/^\s*$/)
717
+ l
718
+ else
719
+ ' ' + l
720
+ end
721
+ },
722
+ ''
723
+ ]
724
+ }.
725
+ flatten.
726
+ join("\n").
727
+ # compact newline sequences
728
+ gsub(/\n\n+/, "\n\n")
729
+ end
730
+
731
+ # examples text
732
+ def puts_examples
733
+ return unless examples
734
+
735
+ puts "\n" + format_examples + "\n"
736
+ end
737
+
738
+ # should qb ask for an ansible vault password?
739
+ #
740
+ # @see http://docs.ansible.com/ansible/playbooks_vault.html
741
+ #
742
+ # @return [Boolean]
743
+ # `true` if qb should ask for a vault password.
744
+ #
745
+ def ask_vault_pass?
746
+ !!@meta['ask_vault_pass']
747
+ end
748
+
749
+
750
+ # @return [Boolean]
751
+ # @todo Document return value.
752
+ #
753
+ def uses_default_dir?
754
+ meta['default_dir'] != false
755
+ end # #uses_default_dir?
756
+
757
+
758
+ # gets the default `qb_dir` value, raising an error if the role doesn't
759
+ # define how to get one or there is a problem getting it.
760
+ #
761
+ #
762
+ def default_dir cwd, options
763
+ QB.debug "get_default_dir",
764
+ role: self,
765
+ meta: self.meta,
766
+ cwd: cwd,
767
+ options: options
768
+
769
+ key = 'default_dir'
770
+ value = self.meta[key]
771
+ case value
772
+ when nil
773
+ # there is no get_dir info in meta/qb.yml, can't get the dir
774
+ raise QB::UserInputError.dedented <<-END
775
+ unable to infer default directory: no '#{ key }' key in 'meta/qb.yml'
776
+ for role #{ self }
777
+ END
778
+
779
+ when false
780
+ # this method should not get called when the value is false (an entire
781
+ # section is skipped in exe/qb when `default_dir = false`)
782
+ raise QB::StateError.squished <<-END
783
+ role does not use default directory (meta/qb.yml:default_dir = false)
784
+ END
785
+
786
+ when 'git_root'
787
+ QB.debug "returning the git root relative to cwd"
788
+ NRSER.git_root cwd
789
+
790
+ when 'cwd'
791
+ QB.debug "returning current working directory"
792
+ cwd
793
+
794
+ when Hash
795
+ QB.debug "qb meta option is a Hash"
796
+
797
+ unless value.length == 1
798
+ raise "#{ meta_path.to_s }:default_dir invalid: #{ value.inspect }"
799
+ end
729
800
 
730
- when 'git_root'
731
- QB.debug "returning the git root relative to cwd"
732
- NRSER.git_root cwd
801
+ hash_key, hash_value = value.first
733
802
 
734
- when 'cwd'
735
- QB.debug "returning current working directory"
736
- cwd
803
+ case hash_key
804
+ when 'exe'
805
+ exe_path = hash_value
737
806
 
738
- when Hash
739
- QB.debug "qb meta option is a Hash"
807
+ # supply the options to the exe so it can make work off those values
808
+ # if it wants.
809
+ exe_input_data = Hash[
810
+ options.map {|option|
811
+ [option.cli_option_name, option.value]
812
+ }
813
+ ]
740
814
 
741
- unless value.length == 1
742
- raise "#{ meta_path.to_s }:default_dir invalid: #{ value.inspect }"
815
+ unless exe_path.start_with?('~') || exe_path.start_with?('/')
816
+ exe_path = File.join(self.path, exe_path)
817
+ debug 'exe path is relative, basing off role dir', exe_path: exe_path
743
818
  end
744
819
 
745
- hash_key, hash_value = value.first
820
+ debug "found 'exe' key, calling", exe_path: exe_path,
821
+ exe_input_data: exe_input_data
746
822
 
747
- case hash_key
748
- when 'exe'
749
- exe_path = hash_value
750
-
751
- # supply the options to the exe so it can make work off those values
752
- # if it wants.
753
- exe_input_data = Hash[
754
- options.map {|option|
755
- [option.cli_option_name, option.value]
756
- }
757
- ]
758
-
759
- unless exe_path.start_with?('~') || exe_path.start_with?('/')
760
- exe_path = File.join(self.path, exe_path)
761
- debug 'exe path is relative, basing off role dir', exe_path: exe_path
762
- end
763
-
764
- debug "found 'exe' key, calling", exe_path: exe_path,
765
- exe_input_data: exe_input_data
766
-
767
- Cmds.chomp! exe_path do
768
- JSON.dump exe_input_data
769
- end
770
-
771
- when 'find_up'
772
- filename = hash_value
773
-
774
- unless filename.is_a? String
775
- raise "find_up filename must be string, found #{ filename.inspect }"
776
- end
777
-
778
- QB.debug "found 'find_up', looking for file named #{ filename }"
779
-
780
- QB::Util.find_up filename
781
-
782
- else
783
- raise QB::Role::MetadataError.squised <<-END
784
- bad key: #{ hash_key } in #{ self.meta_path.to_s }:default_dir
785
- END
786
-
823
+ Cmds.chomp! exe_path do
824
+ JSON.dump exe_input_data
787
825
  end
826
+
827
+ when 'find_up'
828
+ filename = hash_value
829
+
830
+ unless filename.is_a? String
831
+ raise "find_up filename must be string, found #{ filename.inspect }"
832
+ end
833
+
834
+ QB.debug "found 'find_up', looking for file named #{ filename }"
835
+
836
+ QB::Util.find_up filename
837
+
838
+ else
839
+ raise QB::Role::MetadataError.squised <<-END
840
+ bad key: #{ hash_key } in #{ self.meta_path.to_s }:default_dir
841
+ END
842
+
788
843
  end
789
- end # default_dir
790
-
791
- # @return [Hash<String, *>]
792
- # default `ansible-playbook` CLI options from role qb metadata.
793
- # Hash of option name to value.
794
- def default_ansible_options
795
- meta_or 'ansible_options', {}
796
844
  end
845
+ end # default_dir
846
+
847
+
848
+ # @return [Hash<String, *>]
849
+ # default `ansible-playbook` CLI options from role qb metadata.
850
+ # Hash of option name to value.
851
+ def default_ansible_options
852
+ meta_or 'ansible_options', {}
853
+ end
854
+
855
+
856
+ # Get the {Gem::Requirement} parse of the `qb_requirement` key in
857
+ # {#meta} (if it is defined), which specifies the required version of
858
+ # `qb` for the role.
859
+ #
860
+ # @return [Gem::Requirement, nil]
861
+ # The requirement if `required_qb_version` key is in {#meta}, else `nil`.
862
+ #
863
+ def qb_requirement
864
+ if meta['requirements'] &&
865
+ meta['requirements']['gems'] &&
866
+ meta['requirements']['gems']['qb']
867
+ Gem::Requirement.new meta['requirements']['gems']['qb']
868
+ end
869
+ end
870
+
871
+
872
+ # Language Inter-Op
873
+ # -----------------------------------------------------------------------
874
+
875
+ def hash
876
+ path.realpath.hash
877
+ end
878
+
879
+
880
+ def == other
881
+ other.is_a?(self.class) && other.path.realpath == path.realpath
882
+ end
883
+
884
+ alias_method :eql?, :==
885
+
886
+
887
+ # @return [String]
888
+ # {QB::Role#display_path}
889
+ #
890
+ def to_s
891
+ @display_path.to_s
892
+ end
893
+
894
+
895
+ private
896
+ # -----------------------------------------------------------------------
897
+
898
+ # get the value at the first found of the keys or the default.
899
+ #
900
+ # `nil` (`null` in yaml files) are treated like they're not there at
901
+ # all. you need to use `false` if you want to tell QB not to do something.
902
+ #
903
+ # @param [String | Symbol | Array<String | Symbol>] keys
904
+ # Single
905
+ #
906
+ # @return [Object]
907
+ #
908
+ def meta_or keys, default
909
+ keys.as_array.map(&:to_s).each do |key|
910
+ return meta[key] unless meta[key].nil?
911
+ end
912
+
913
+ # We didn't find anything (that wasn't explicitly or implicitly `nil`)
914
+ default
915
+ end # meta_or
797
916
 
798
917
 
799
- # Get the {Gem::Requirement} parse of the `qb_requirement` key in
800
- # {#meta} (if it is defined), which specifies the required version of
801
- # `qb` for the role.
918
+ # Find a non-null/nil value for one of `keys` or raise an error.
802
919
  #
803
- # @return [Gem::Requirement, nil]
804
- # The requirement if `required_qb_version` key is in {#meta}, else `nil`.
920
+ # @param [String | Symbol | Array<String | Symbol>] keys
921
+ # Possible key names. They will be searched in order and first
922
+ # non-null/nil value returned.
805
923
  #
806
- def qb_requirement
807
- if meta['requirements'] &&
808
- meta['requirements']['gems'] &&
809
- meta['requirements']['gems']['qb']
810
- Gem::Requirement.new meta['requirements']['gems']['qb']
811
- end
812
- end
813
-
814
-
815
- # language inter-op
816
- # -----------------------------------------------------------------------
817
-
818
- def hash
819
- path.realpath.hash
820
- end
821
-
822
-
823
- def == other
824
- other.is_a?(Role) && other.path.realpath == path.realpath
825
- end
826
-
827
- alias_method :eql?, :==
828
-
829
- # @return [String]
830
- # {QB::Role#display_path}
924
+ # @return [Object]
831
925
  #
832
- def to_s
833
- @display_path.to_s
834
- end
835
-
926
+ # @raise [QB::Role::MetadataError]
927
+ # If none of `keys` are found.
928
+ #
929
+ def need_meta keys
930
+ keys = keys.as_array.map(&:to_s)
931
+
932
+ keys.each do |key|
933
+ return meta[key] unless meta[key].nil?
934
+ end
935
+
936
+ raise QB::Role::MetadataError.squished <<-END
937
+ Expected metadata for role #{ self } to define (non-null) value for
938
+ one of keys #{ keys.inspect }.
939
+ END
940
+ end # need_meta
836
941
 
837
- private
838
- # -----------------------------------------------------------------------
839
942
 
840
- # get the value at the first found of the keys or the default.
841
- #
842
- # `nil` (`null` in yaml files) are treated like they're not there at
843
- # all. you need to use `false` if you want to tell QB not to do something.
844
- #
845
- def meta_or keys, default
846
- keys = [keys] if keys.is_a? String
847
- keys.each do |key|
848
- if meta.key?(key) && !meta[key].nil?
849
- return meta[key]
850
- end
851
- end
852
- default
853
- end # meta_or
854
-
855
- # end private
856
- end # Role
857
- end # QB
943
+ # end private
944
+
945
+ end # class QB::Role