Class: QB::Role

Inherits:
Object
  • Object
show all
Includes:
SemanticLogger::Loggable
Defined in:
lib/qb/role.rb,
lib/qb/role/name.rb,
lib/qb/role/errors.rb,
lib/qb/role/matches.rb,
lib/qb/role/default_dir.rb,
lib/qb/role/search_path.rb

Overview

Definitions

Defined Under Namespace

Classes: MetadataError, MultipleMatchesError, NoMatchesError

Constant Summary collapse

BUILTIN_PATH =

"Factory defaults" that PATH is initialized to, and what it gets reset to when reset_path! is called.

Read the PATH docs for details on how QB role paths work.

This value is deeply frozen, and you should not attempt to change it - mess with PATH instead.

Returns:

  • (Array<String>)
[
  
  # Development Paths
  # =================
  # 
  # These come first because:
  # 
  # 1.  They are working dir-local.
  # 
  # 2.  They should only be present in local development, and should be
  #     capable of overriding roles in other local directories to allow
  #     custom development behavior (the same way `./dev/bin` is put in
  #     front or `./bin`).
  # 
  
  # Role paths declared in ./dev/ansible.cfg, if it exists.
  File.join('.', 'dev', 'ansible.cfg'),
  
  # Roles in ./dev/roles
  File.join('.', 'dev', 'roles'),
  
  
  # Working Directory Paths
  # =======================
  # 
  # Next up, `ansible.cfg` and `roles` directory in the working dir.
  # Makes sense, right?
  # 
  
  # ./ansible.cfg
  File.join('.', 'ansible.cfg'),
  
  # ./roles
  File.join('.', 'roles'),
  
  
  # Working Directory-Local Ansible Directory
  # =========================================
  # 
  # `ansible.cfg` and `roles` in a `./ansible` directory, making a common
  # place to put Ansible stuff in an project accessible when running from
  # the project root.
  # 
  
  # ./ansible/ansible.cfg
  File.join('.', 'ansible', 'ansible.cfg'),
  
  # ./ansible/roles
  File.join('.', 'ansible', 'roles'),
  
  # TODO  Git repo root relative?
  #       Some sort of flag file for a find-up?
  #       System Ansible locations?
  
  
  # QB Gem Role Directories
  # =======================
  # 
  # Last, but far from least, paths provided by the QB Gem to the user's
  # QB role install location and the roles that come built-in to the gem.
  
  QB::USER_ROLES_DIR,
  
  QB::GEM_ROLES_DIR,
].freeze
PATH =

Array of string paths to directories to search for roles or paths to ansible.cfg files to look for an extract role paths from.

Value is a duplicate of the frozen BUILTIN_PATH. You can reset to those values at any time via reset_path!.

For the moment at least you can just mutate this value like you would $LOAD_PATH:

QB::Role::PATH.unshift '~/where/some/roles/be'
QB::Role::PATH.unshift '~/my/ansible.cfg'

The paths are searched from first to last.

WARNING

Search is deep - don't point this at large directory trees and expect any sort of reasonable performance (any directory that contains node_modules is usually a terrible idea for instance).

Returns:

  • (Array<String>)
BUILTIN_PATH.dup

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path, search_dir: nil) ⇒ Role

Instantiate a Role.

Parameters:

  • path (String|Pathname)

    location of the role directory

  • search_dir (nil, Pathname) (defaults to: nil)

    Directory in search_path that the role was found in. Used to figure out it's name correctly when using directory-structure namespacing.



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/qb/role.rb', line 216

def initialize path, search_dir: nil
  @path = if path.is_a?(Pathname) then path else Pathname.new(path) end
  
  # check it...
  unless @path.exist?
    raise Errno::ENOENT.new @path.to_s
  end
  
  unless @path.directory?
    raise Errno::ENOTDIR.new @path.to_s
  end
  
  @display_path = self.class.to_display_path @path
  
  @meta_path = if (@path + 'meta' + 'qb').exist?
    @path + 'meta' + 'qb'
  elsif (@path + 'meta' + 'qb.yml').exist?
    @path + 'meta' + 'qb.yml'
  else
    raise Errno::ENOENT.new "#{ @path.join('meta').to_s }/[qb|qb.yml]"
  end
  
  
  if search_dir.nil?
    @name = @path.to_s.split(File::SEPARATOR).last
  else
    @name = @path.relative_path_from(search_dir).to_s
  end
end

Instance Attribute Details

#display_pathObject (readonly)

the path to the role that we display. we only show the directory name for QB roles, and use Util.compact_path to show . and ~ for paths relative to the current directory and home directory, respectively.

@return [Pathname]



193
194
195
# File 'lib/qb/role.rb', line 193

def display_path
  @display_path
end

#meta_pathObject (readonly)

Returns the value of attribute meta_path.



200
201
202
# File 'lib/qb/role.rb', line 200

def meta_path
  @meta_path
end

#nameObject (readonly)

Returns the value of attribute name.



183
184
185
# File 'lib/qb/role.rb', line 183

def name
  @name
end

#pathObject (readonly)

Returns the value of attribute path.



177
178
179
# File 'lib/qb/role.rb', line 177

def path
  @path
end

Class Method Details

.availableArray<QB::Role>

All QB::Role found in search path.

Does it's best to remove duplicates that end up being reached though multiple search paths (happens most in development).

Returns:



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/qb/role.rb', line 60

def self.available
  self.search_path.
    select {|search_dir|
      # make sure it's there (and a directory)
      search_dir.directory?
    }.
    map {|search_dir|
      ['', '.yml', '.yaml'].flat_map { |ext|
        Pathname.glob(search_dir.join '**', 'meta', "qb#{ ext }").
          map {|meta_path|
            [meta_path.dirname.dirname, search_dir: search_dir]
          }
      }
    }.
    flatten( 1 ).
    map { |args| QB::Role.new *args }.
    uniq
end

.default_name_for(path) ⇒ String

Do our best to figure out a role name from a path (that might not exist).

We needs this when we're creating a role.

Parameters:

  • path (String | Pathname)

Returns:

  • (String)


61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/qb/role/name.rb', line 61

def self.default_name_for path
  resolved_path = QB::Util.resolve path
  
  # Find the first directory in the search path that contains the path,
  # if any do.
  # 
  # It *could* be in more than one in funky situations like overlapping
  # search paths or link silliness, but that doesn't matter - we consider
  # the first place we find it to be the relevant once, since the search
  # path is most-important-first.
  # 
  search_dir = search_path.find { |pathname|
    resolved_path.fnmatch? ( pathname / '**' ).to_s
  }
  
  if search_dir.nil?
    # It's not in any of the search directories
    # 
    # If it has 'roles' as a segment than use what's after the last occurrence
    # of that (unless there isn't anything).
    # 
    segments = resolved_path.to_s.split File::SEPARATOR
    
    if index = segments.rindex( 'roles' )
      name_segs = segments[( index + 1 )..( -1 )]
      
      unless name_segs.empty?
        return File.join name_segs
      end
    end
    
    # Ok, that didn't work... just return the basename I guess...
    return File.basename resolved_path
    
  end
  
  # it's in the search path, return the relative path from the containing
  # search dir to the resolved path (string version of it).
  resolved_path.relative_path_from( search_dir ).to_s

end

.get_include_path(role, option_meta, current_include_path) ⇒ Array<string>

Get the include path for an included role based on the option metadata that defines the include and the current include path.

Parameters:

  • role (Role)

    the role to include.

  • option_meta (Hash)

    the entry for the option in qb.yml

  • current_include_path (Array<string>)

Returns:

  • (Array<string>)

    include path for the included role.



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/qb/role.rb', line 136

def self.get_include_path role, option_meta, current_include_path
  new_include_path = if option_meta.key? 'as'
    case option_meta['as']
    when nil, false
      # include it in with the parent role's options
      current_include_path
    when String
      current_include_path + [option_meta['as']]
    else
      raise QB::Role::MetadataError.new,
        "bad 'as' value: #{ option_meta.inspect }"
    end
  else
    current_include_path + [role.namespaceless]
  end
end

.matches(input) ⇒ Array<QB::Role>

Get an array of QB::Role that match an input string.

This is the meat of whats needed to support require.

How it works is... tricky. Read the comments and play around with it is the bast I can offer right now.

Parameters:

  • input (String)

    The input string to match against role paths and names. Primarily what the user typed after qb run on the CLI.

Returns:



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/qb/role/matches.rb', line 37

def self.matches input
  # keep this here to we don't re-gen every loop
  available = self.available
  
  # first off, see if input matches any relative paths exactly
  available.each {|role|
    return [role] if role.display_path.to_s == input
  }
  
  # create an array of "separator" variations to try *exact* matching
  # against. in order of preference:
  # 
  # 1.  exact input
  #     -   this means if you ended up with roles that actually *are*
  #         differentiated by '_/-' differences (which, IMHO, is a
  #         horrible fucking idea), you can get exactly what you ask for
  #         as a first priority
  # 2.  input with '-' changed to '_'
  #     -   prioritized because convention is to underscore-separate
  #         role names.
  # 3.  input with '_' changed to '-'
  #     -   really just for convenience's sake so you don't really have to
  #         remember what separator is used.
  #     
  separator_variations = [
    input,
    input.gsub('-', '_'),
    input.gsub('_', '-'),
  ]
  
  # {QB::Role} method names to check against, from highest to lowest
  # precedence
  method_names = [
    # 1.  The path we display to the user. This comes first because typing
    #     in exactly what they see should always work.
    :display_name,
    
    # 2.  The role's full name (with namespace) as it is likely to be used
    #     in Ansible
    :name,
    
    # 3.  The part of the role after the namespace, which is far less
    #     specific, but nice short-hand if it's unique
    :namespaceless
  ]
  
  # 1.  Exact matches (allowing `-`/`_` substitution)
  #     
  #     Highest precedence, guaranteeing that exact verbatim matches will
  #     always work (or that's the intent).
  # 
  method_names.each { |method_name|
    separator_variations.each { |variation|
      matches = available.select { |role|
        role.public_send( method_name ) == variation
      }
      return matches unless matches.empty?
    }
  }
  
  # 2.  Prefix matches
  #     
  #     Do any of {#display_path}, {#name} or {#namespaceless} or start with
  #     the input pattern?
  # 
  method_names.each { |method_name|
    separator_variations.each { |variation|
      matches = available.select { |role|
        role.public_send( method_name ).start_with? variation
      }
      return matches unless matches.empty?
    }
  }
  
  # 3.  Word slice full matches
  #     
  #     Split the {#display_name} and input first by `/` and `.` segments,
  #     then {String#downcase} each segments and split it into words (using
  #     {NRSER.words}).
  #     
  #     Then see if the input appears in the role name.
  #     
  #     We test only {#display_name} because it should always contain
  #     {#name} and {#namesaceless}, so it's pointless to test the other
  #     two after it).
  # 
  
  word_parse = ->( string ) {
    string.split( /[\/\.]/ ).map { |seg| seg.downcase.words }
  }
  
  input_parse = word_parse.call input
  
  exact_word_slice_matches = available.select { |role|
    word_parse.call( role.display_name ).slice? input_parse
  }
  
  return exact_word_slice_matches unless exact_word_slice_matches.empty?
  
  # 4.  Word slice prefix matches
  #     
  #     Same thing as (3), but do a prefix match instead of the entire
  #     words.
  # 
  name_word_matches = available.select { |role|
    word_parse.call( role.display_name ).
      slice?( input_parse ) { |role_words, input_words|
        # Use a custom match block to implement prefix matching
        # 
        # We want to match if each input word is the start of the
        # corresponding role name word
        # 
        if role_words.length >= input_words.length
          input_words.each_with_index.all? { |input_word, index|
            role_words[index].start_with? input_word
          }
        else
          false
        end
      }
    QB::Util.words_start_with? role.display_path.to_s, input
  }
  return name_word_matches unless name_word_matches.empty?
  
  # nada
  []
end

.namespace_for(name) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
# File 'lib/qb/role/name.rb', line 107

def self.namespace_for name
  *namespace_segments, last = name.split File::Separator
  
  namespace_segments << last.split('.').first if last.include?('.')
   
  if namespace_segments.empty?
    nil
  else
    File.join *namespace_segments
  end
end

.namespaceless_for(name) ⇒ Object



120
121
122
# File 'lib/qb/role/name.rb', line 120

def self.namespaceless_for name
  File.basename( name ).split('.', 2).last
end

.require(input) ⇒ QB::Role

Find exactly one matching role for the input string or raise.

Where we look is determined by PATH via search_path.

Parameters:

  • input (String)

    Input string term used to search (what we got off the CLI args).

Returns:

  • (QB::Role)

    The single matching role.

Raises:



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/qb/role.rb', line 96

def self.require input
  as_pathname = Pathname.new(input)
    
  # allow a path to a role dir
  if role_dir? as_pathname
    return QB::Role.new as_pathname
  end
  
  matches = self.matches input
  
  role = case matches.length
  when 0
    raise QB::Role::NoMatchesError.new input
  when 1
    matches[0]
  else
    raise QB::Role::MultipleMatchesError.new input, matches
  end
  
  QB.debug "role match" => role
  
  role
end

.reset_path!Array<String>

Reset PATH to the original built-in values in BUILTIN_PATH.

Created for testing but might be useful elsewhere as well.

Returns:

  • (Array<String>)

    The reset PATH.



139
140
141
142
143
# File 'lib/qb/role/search_path.rb', line 139

def self.reset_path!
  PATH.clear
  BUILTIN_PATH.each { |path| PATH << path }
  PATH
end

.role_dir?(pathname) ⇒ Boolean

true if pathname is a QB role directory.

Returns:

  • (Boolean)


45
46
47
48
49
50
# File 'lib/qb/role.rb', line 45

def self.role_dir? pathname
  # must be a directory
  pathname.directory? &&
  # and must have meta/qb.yml or meta/qb file
  ['qb.yml', 'qb'].any? {|filename| pathname.join('meta', filename).file?}
end

.search_pathArray<Pathname>

Gets the array of paths to search for QB roles based on PATH and the working directory at the time it's called.

QB then uses the returned value to figure out what roles are available.

The process:

  1. Resolve relative paths against the working directory.

  2. Load up any ansible.cfg files on the path and add any roles_path they define where the ansible.cfg entry was in PATH.

Returns:

  • (Array<Pathname>)

    Directories to search for QB roles.



161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/qb/role/search_path.rb', line 161

def self.search_path
  QB::Role::PATH.
    map { |path|
      if QB::Ansible::ConfigFile.end_with_config_file?(path)
        if File.file?(path)
          QB::Ansible::ConfigFile.new(path).defaults.roles_path
        end
      else
        QB::Util.resolve path
      end
    }.
    flatten.
    reject(&:nil?)
end

.to_display_path(path) ⇒ Pathname

The path we display in the CLI, see #display_path.

Parameters:

  • path (Pathname | String)

    input path to transform.

Returns:

  • (Pathname)

    path to display.



162
163
164
165
166
167
168
# File 'lib/qb/role.rb', line 162

def self.to_display_path path
  if path.realpath.start_with? QB::GEM_ROLES_DIR
    path.realpath.sub (QB::GEM_ROLES_DIR.to_s + '/'), ''
  else
    QB::Util.contract_path path
  end
end

Instance Method Details

#==(other) ⇒ Object Also known as: eql?



585
586
587
# File 'lib/qb/role.rb', line 585

def == other
  other.is_a?(self.class) && other.path.realpath == path.realpath
end

#ask_vault_pass?Boolean

should qb ask for an ansible vault password?

Returns:

  • (Boolean)

    true if qb should ask for a vault password.

See Also:



479
480
481
# File 'lib/qb/role.rb', line 479

def ask_vault_pass?
  !!@meta['ask_vault_pass']
end

get the CLI banner for the role



411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
# File 'lib/qb/role.rb', line 411

def banner
  lines = []
  
  name_line = "#{ name } role"
  lines << name_line
  lines << "=" * name_line.length
  lines << ''
  if meta['description']
    lines << meta['description']
    lines << ''
  end
  lines << 'Usage:'
  lines << ''
  lines << "  #{ usage }"
  lines << ''
  lines << ''
  
  lines.join("\n")
end

#check_requirementsnil

Check the role's requirements.

Returns:

  • (nil)

Raises:

  • (QB::AnsibleVersionError)

    If the version of Ansible found does not satisfy the role's requirements.

  • (QB::QBVersionError)

    If the the version of QB we're running does not satisfy the role's requirements.



528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
# File 'lib/qb/role.rb', line 528

def check_requirements
  if ansible_req = requirements['ansible']
    unless ansible_req.satisfied_by? QB.ansible_version
      raise QB::AnsibleVersionError.squished <<-END
        QB #{ QB::VERSION } requires Ansible #{ ansible_req },
        found version #{ QB.ansible_version  } at #{ `which ansible` }
      END
    end
  end
  
  if qb_req = requirements.dig( 'gems', 'qb' )
    unless qb_req.satisfied_by? QB.gem_version
      raise QB::QBVersionError.squished <<-END
        Role #{ self } requires QB #{ qb_req },
        using QB #{ QB.gem_version } from #{ QB::ROOT }.
      END
    end
  end
  
  nil
end

#default_ansible_optionsHash<String, *>

Returns default ansible-playbook CLI options from role qb metadata. Hash of option name to value.

Returns:

  • (Hash<String, *>)

    default ansible-playbook CLI options from role qb metadata. Hash of option name to value.



497
498
499
# File 'lib/qb/role.rb', line 497

def default_ansible_options
  meta_or 'ansible_options', {}
end

#default_dir(cwd, options) ⇒ Pathname

Gets the default qb_dir value, raising an error if the role doesn't define how to get one or there is a problem getting it.

It uses a "strategy" value found at the 'default_dir' key in the role's QB metadata (in <role_path>/meta/qb.yml or returned by a <role_path>/meta/qb executable).

See the default_dir documentation for details on the accepted strategy values.

Parameters:

Returns:

  • (Pathname)

    The directory to target.

Raises:

  • When we can't determine a directory due to role meta settings or target system state.



71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/qb/role/default_dir.rb', line 71

def default_dir cwd, options
  logger.debug "CALLING default_dir",
    role: self.instance_variables.assoc_to { |key|
      self.instance_variable_get key
    },
    cwd: cwd,
    options: options
  
  default_dir_for(
    strategy: self.meta['default_dir'],
    cwd: cwd,
    options: options
  ).to_pn
end

#defaultsObject

gets the role variable defaults from defaults/main.yml, or {}



362
363
364
# File 'lib/qb/role.rb', line 362

def defaults
  @defaults || load_defaults
end

#descriptionString

Returns The description value from the role's QB metadata, or '' if it doesn't have one.

Returns:

  • (String)

    The description value from the role's QB metadata, or '' if it doesn't have one



554
555
556
# File 'lib/qb/role.rb', line 554

def description
  meta['description'].to_s
end

#display_nameObject

Just a string version of #display_path



252
253
254
# File 'lib/qb/role.rb', line 252

def display_name
  display_path.to_s
end

#examplesObject



432
433
434
# File 'lib/qb/role.rb', line 432

def examples
  @meta['examples']
end

#format_examplesString

format the meta.examples hash into a string suitable for cli output.

Returns:

  • (String)

    the CLI-formatted examples.



442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
# File 'lib/qb/role.rb', line 442

def format_examples
  examples.
    map {|title, body|
      [
        "#{ title }:",
        body.lines.map {|l|
          # only indent non-empty lines
          # makes compacting newline sequences easier (see below)
          if l.match(/^\s*$/)
            l
          else
            '  ' + l
          end
        },
        ''
      ]
    }.
    flatten.
    join("\n").
    # compact newline sequences
    gsub(/\n\n+/, "\n\n")
end

#has_dir_arg?Boolean

Test if the QB::Role uses a directory argument (that gets assigned to the qb_dir variable in Ansible).

Returns:

  • (Boolean)


489
490
491
# File 'lib/qb/role.rb', line 489

def has_dir_arg?
  meta['default_dir'] != false
end

#hashObject

Language Inter-Op



580
581
582
# File 'lib/qb/role.rb', line 580

def hash
  path.realpath.hash
end

#load_defaults(cache = true) ⇒ Object

loads the defaults from vars/main.yml and defaults/main.yml, caching by default. vars override defaults values.



337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
# File 'lib/qb/role.rb', line 337

def load_defaults cache = true
  defaults_path = @path + 'defaults' + 'main.yml'
  defaults = if defaults_path.file?
    YAML.load(defaults_path.read) || {}
  else
    {}
  end
  
  vars_path = @path + 'vars' + 'main.yml'
  vars = if vars_path.file?
    YAML.load(vars_path.read) || {}
  else
    {}
  end
  
  defaults = defaults.merge! vars
  
  if cache
    @defaults = defaults
  end
  
  defaults
end

#load_meta(cache = true) ⇒ Object

load qb metadata from meta/qb.yml or from executing meta/qb and parsing the YAML written to stdout.

if cache is true caches it as @meta



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/qb/role.rb', line 266

def load_meta cache = true
  meta = if @meta_path.extname == '.yml'
    contents = begin
      @meta_path.read
    rescue Exception => error
      raise QB::Role::MetadataError,
        "Failed to read metadata file at #{ @meta_path.to_s }, " +
        "error: #{ error.inspect }"
    end
    
    begin
      YAML.load(contents) || {}
    rescue Exception => error
      raise QB::Role::MetadataError,
        "Failed to load metadata YAML from #{ @meta_path.to_s }, " +
        "error: #{ error.inspect }"
    end
  else
    YAML.load(Cmds.out!(@meta_path.realpath.to_s)) || {}
  end
  
  if cache
    @meta = meta
  end
  
  meta
end

#metaHash{String => Object}

Returns the QB metadata for the role.

Returns:

  • (Hash{String => Object})

    the QB metadata for the role.



297
298
299
# File 'lib/qb/role.rb', line 297

def meta
  @meta || load_meta
end

#mkdirObject

if the exe should auto-make the directory. this is nice for most roles but some need it to be missing



372
373
374
# File 'lib/qb/role.rb', line 372

def mkdir
  !!meta_or('mkdir', true)
end

#namespaceObject

Instance Methods



128
129
130
# File 'lib/qb/role/name.rb', line 128

def namespace
  self.class.namespace_for @name
end

#namespacelessObject



133
134
135
# File 'lib/qb/role/name.rb', line 133

def namespaceless
  self.class.namespaceless_for @name
end

#option_metasObject

get the options from the metadata, defaulting to [] if none defined



314
315
316
# File 'lib/qb/role.rb', line 314

def option_metas
  meta_or ['options', 'opts', 'vars'], []
end

#options(include_path = []) ⇒ Array<QB::Options::Option> an array of Option for the role, including any included roles.

Returns ArrayQB::Options::Option an array of Option for the role, including any included roles.

Returns:



322
323
324
325
326
327
328
329
330
331
332
# File 'lib/qb/role.rb', line 322

def options include_path = []
  option_metas.map {|option_meta|
    if option_meta.key? 'include'
      role_name = option_meta['include']
      role = QB::Role.require role_name
      role.options QB::Role.get_include_path(role, option_meta, include_path)
    else
      QB::Options::Option.new self, option_meta, include_path
    end
  }.flatten
end

#options_keyObject



257
258
259
# File 'lib/qb/role.rb', line 257

def options_key
  display_name
end

#puts_examplesObject

examples text



466
467
468
469
470
# File 'lib/qb/role.rb', line 466

def puts_examples
  return unless examples
  
  puts "\n" + format_examples + "\n"
end

#requirementsHash

Parsed tree structure of version requirements of the role from the requirements value in the QB meta data.

Returns:

  • (Hash)

    Tree where the leaves are Gem::Requirement.



508
509
510
511
512
513
514
# File 'lib/qb/role.rb', line 508

def requirements
  @requirements ||= NRSER.map_leaves(
    meta_or 'requirements', {'gems' => {}}
  ) { |key_path, req_str|
    Gem::Requirement.new req_str
  }
end

#save_optionsObject



366
367
368
# File 'lib/qb/role.rb', line 366

def save_options
  !!meta_or('save_options', true)
end

#summaryString

Short summary pulled from the role description - first line if it's multi-line, or first sentence if it's a single line.

Will be an empty string if the role doesn't have a description.

Returns:

  • (String)


566
567
568
569
570
571
572
573
574
# File 'lib/qb/role.rb', line 566

def summary
  description.lines.first.thru { |line|
    if line
      line.split( '. ', 2 ).first
    else
      ''
    end
  }
end

#to_sString

Returns #display_path.

Returns:



595
596
597
# File 'lib/qb/role.rb', line 595

def to_s
  @display_path.to_s
end

#usageString

Returns usage information formatted as plain text for the CLI.

Returns:

  • (String)

    usage information formatted as plain text for the CLI.



379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# File 'lib/qb/role.rb', line 379

def usage
  # Split up options by required and optional.
  required_options = []
  optional_options = []
  
  options.each { |option|
    if option.required?
      required_options << option
    else
      optional_options << option
    end
  }
  
  parts = ['qb [run]', name]
  
  required_options.each { |option|
    parts << option.usage
  }
  
  unless optional_options.empty?
    parts << '[OPTIONS]'
  end
  
  if has_dir_arg?
    parts << 'DIRECTORY'
  end
  
  parts.join ' '
end

#var_prefixObject

gets the variable prefix that will be appended to cli options before passing them to the role. defaults to #namespaceless unless specified in meta.



305
306
307
308
309
310
# File 'lib/qb/role.rb', line 305

def var_prefix
  # ugh, i was generating meta/qb.yml files that set 'var_prefix' to
  # `null`, but it would be nice to
  # 
  meta_or 'var_prefix', namespaceless
end