Class: Halite::Gem

Inherits:
Object
  • Object
show all
Defined in:
lib/halite/gem.rb

Overview

A model for a gem/cookbook within Halite.

Examples:

g = Halite::Gem.new('chef-mycookbook', '1.1.0')
puts(g.cookbook_name) #=> mycookbook

Since:

  • 1.0.0

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, version = nil) ⇒ Gem

name can be either a string name, Gem::Dependency, or Gem::Specification

Parameters:

  • name (String, Gem::Dependency, Gem::Specification)

Since:

  • 1.0.0



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/halite/gem.rb', line 45

def initialize(name, version=nil)
  # Allow passing a Dependency by just grabbing its spec.
  name = dependency_to_spec(name) if name.is_a?(::Gem::Dependency)
  # Stubs don't load enough data for us, grab the real spec. RIP IOPS.
  name = name.to_spec if name.is_a?(::Gem::StubSpecification) || (defined?(Bundler::StubSpecification) && name.is_a?(Bundler::StubSpecification))
  if name.is_a?(::Gem::Specification)
    raise Error.new("Cannot pass version when using an explicit specficiation") if version
    @spec = name
    @name = spec.name
  else
    @name = name
    @version = version
    raise Error.new("Gem #{name}#{version ? " v#{version}" : ''} not found") unless spec
  end
end

Instance Attribute Details

#nameObject (readonly)

Since:

  • 1.0.0



41
42
43
# File 'lib/halite/gem.rb', line 41

def name
  @name
end

Instance Method Details

#as_cookbook_versionChef::CookbookVersion

Create a Chef::CookbookVersion object that represents this gem. This can be injected in to Chef to simulate the cookbook being available.

Examples:

run_context.cookbook_collection[gem.cookbook_name] = gem.as_cookbook_version

Returns:

  • (Chef::CookbookVersion)

Since:

  • 1.0.0



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/halite/gem.rb', line 158

def as_cookbook_version
  # Put this in a local variable for a closure below.
  path = spec.full_gem_path
  Chef::CookbookVersion.new(cookbook_name, File.join(path, 'chef')).tap do |c|
    c.attribute_filenames = each_file('chef/attributes').map(&:first)
    c.file_filenames = each_file('chef/files').map(&:first)
    c.recipe_filenames = each_file('chef/recipes').map(&:first)
    c.template_filenames = each_file('chef/templates').map(&:first)
    # Haxx, rewire the filevendor for this cookbook to look up in our folder.
    # This is touching two different internal interfaces, but ¯\_(ツ)_/¯
    c.send(:file_vendor).define_singleton_method(:get_filename) do |filename|
      File.join(path, 'chef', filename)
    end
    # Store the true root for use in other tools.
    c.define_singleton_method(:halite_root) { path }
  end
end

#cookbook_dependenciesObject

Since:

  • 1.0.0



143
144
145
# File 'lib/halite/gem.rb', line 143

def cookbook_dependencies
  @cookbook_dependencies ||= Dependencies.extract(spec)
end

#cookbook_nameObject

Since:

  • 1.0.0



69
70
71
72
73
74
75
# File 'lib/halite/gem.rb', line 69

def cookbook_name
  if spec..include?('halite_name')
    spec.['halite_name']
  else
    spec.name.gsub(/(^(chef|cookbook)[_-])|([_-](chef|cookbook))$/, '')
  end
end

#cookbook_versionString

Version of the gem sanitized for Chef. This means no non-numeric tags and only three numeric components.

Returns:

  • (String)

Since:

  • 1.0.0



81
82
83
84
85
86
87
# File 'lib/halite/gem.rb', line 81

def cookbook_version
  if match = version.match(/^(\d+\.\d+\.(\d+)?)/)
    match[1]
  else
    raise Halite::Error.new("Unable to parse #{version.inspect} as a Chef cookbook version")
  end
end

#each_file(prefix_paths = nil, &block) ⇒ Array<Array<String>>

Iterate over all the files in the gem, with an optional prefix. Each element in the iterable will be [full_path, relative_path], where relative_path is relative to the prefix or gem path.

Examples:

gem_data.each_file do |full_path, rel_path|
  # ...
end

Parameters:

  • prefix_paths (String, Array<String>, nil) (defaults to: nil)

    Option prefix paths.

  • block (Proc)

    Callable for iteration.

Returns:

  • (Array<Array<String>>)

Since:

  • 1.0.0



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/halite/gem.rb', line 114

def each_file(prefix_paths=nil, &block)
  globs = if prefix_paths
    Array(prefix_paths).map {|path| File.join(spec.full_gem_path, path) }
  else
    [spec.full_gem_path]
  end
  [].tap do |files|
    globs.each do |glob|
      Dir[File.join(glob, '**', '*')].each do |path|
        next unless File.file?(path)
        val = [path, path[glob.length+1..-1]]
        block.call(*val) if block
        files << val
      end
    end
    # Make sure the order is stable for my tests. Probably overkill, I think
    # Dir#[] sorts already.
    files.sort!
  end
end

#each_library_file(&block) ⇒ Array<Array<String>>

Special case of the #each_file the gem's require paths.

Parameters:

  • block (Proc)

    Callable for iteration.

Returns:

  • (Array<Array<String>>)

Since:

  • 1.0.0



139
140
141
# File 'lib/halite/gem.rb', line 139

def each_library_file(&block)
  each_file(spec.require_paths, &block)
end

#find_misc_path(name) ⇒ String+

Search for a file like README.md or LICENSE.txt in the gem.

Examples:

gem.misc_file('Readme') => /path/to/readme.txt

Parameters:

  • name (String)

    Basename to search for.

Returns:

  • (String, Array<String>)

Since:

  • 1.0.0



182
183
184
185
186
187
188
189
190
191
# File 'lib/halite/gem.rb', line 182

def find_misc_path(name)
  [name, name.upcase, name.downcase].each do |base|
    ['.md', '', '.txt', '.html'].each do |suffix|
      path = File.join(spec.full_gem_path, base+suffix)
      return path if File.exist?(path) && Dir.entries(File.dirname(path)).include?(File.basename(path))
    end
  end
  # Didn't find anything
  nil
end

#is_halite_cookbook?Boolean

Is this gem really a cookbook? (anything that depends directly on halite and doesn't have the ignore flag)

Returns:

  • (Boolean)

Since:

  • 1.0.0



148
149
150
# File 'lib/halite/gem.rb', line 148

def is_halite_cookbook?
  spec.dependencies.any? {|subdep| subdep.name == 'halite'} && !spec..include?('halite_ignore')
end

#license_headerObject

Since:

  • 1.0.0



99
100
101
# File 'lib/halite/gem.rb', line 99

def license_header
  IO.readlines(spec_file).take_while { |line| line.strip.empty? || line.strip.start_with?('#') }.join('')
end

#specObject

Since:

  • 1.0.0



61
62
63
# File 'lib/halite/gem.rb', line 61

def spec
  @spec ||= dependency_to_spec(::Gem::Dependency.new(@name, ::Gem::Requirement.new(@version)))
end

#spec_fileString

Path to the .gemspec for this gem. This is different from Gem::Specification#spec_file because the Rubygems API is shit and just assumes the file layout matches normal, which is not the case with Bundler and path or git sources.

Returns:

  • (String)

Since:

  • 1.0.0



95
96
97
# File 'lib/halite/gem.rb', line 95

def spec_file
  File.join(spec.full_gem_path, spec.name + '.gemspec')
end

#versionObject

Since:

  • 1.0.0



65
66
67
# File 'lib/halite/gem.rb', line 65

def version
  spec.version.to_s
end