qb 0.3.4 → 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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