realityforge/knife-cookbook-doc

View on GitHub
lib/knife_cookbook_doc/readme_model.rb

Summary

Maintainability
B
5 hrs
Test Coverage
begin
  require 'berkshelf'
rescue LoadError
  # won't be able to get :source_url for dependent cookbooks
end

module KnifeCookbookDoc
  class ReadmeModel
    DEFAULT_CONSTRAINT = ">= 0.0.0".freeze

    attr_reader :libraries

    def initialize(cookbook_dir, config)

      @metadata = Chef::Cookbook::Metadata.new
      @metadata.from_file("#{cookbook_dir}/#{config[:metadata]}")

      if (!@metadata.attributes.empty? rescue false)
        @attributes = @metadata.attributes.map do |attr, options|
          name = "node['#{attr.gsub("/", "']['")}']"
          [name, options['description'], options['default'], options['choice']]
        end
      else
        @attributes = []
        Dir["#{cookbook_dir}/attributes/*.rb"].sort.each do |attribute_filename|
          model = AttributesModel.new(attribute_filename, config)
          if !model.attributes.empty?
            @attributes += model.attributes
          end
        end
      end

      @libraries = Dir["#{cookbook_dir}/libraries/*.rb"].sort.map do |path|
        LibrariesModel.new(@metadata.name, path)
      end

      @resources = []
      Dir["#{cookbook_dir}/resources/*.rb"].sort.each do |resource_filename|
        @resources << ResourceModel.new(@metadata.name, resource_filename)
      end

      @definitions = []
      Dir["#{cookbook_dir}/definitions/*.rb"].sort.each do |def_filename|
        @definitions << DefinitionsModel.new(File.basename(def_filename, '.*'), def_filename)
      end

      @fragments = {}
      Dir["#{cookbook_dir}/doc/*.md"].sort.each do |resource_filename|
        @fragments[::File.basename(resource_filename,'.md')] = IO.read(resource_filename)
      end

      @recipes = []
      if !@metadata.recipes.empty?
        @metadata.recipes.each do |name, description|
          @recipes << RecipeModel.new(name, description, "#{cookbook_dir}/recipes/#{name.gsub(/^.*\:(.*)$/,'\1')}.rb")
        end
      else
        Dir["#{cookbook_dir}/recipes/*.rb"].sort.each do |recipe_filename|
          base_name = File.basename(recipe_filename, ".rb")
          if !base_name.start_with?("_")
            @recipes << RecipeModel.new("#{@metadata.name}::#{base_name}", recipe_filename)
          end
        end
      end
      @metadata = @metadata
      @constraints = config[:constraints]
    end

    def fragments
      @fragments
    end

    def resources
      @resources
    end

    def definitions
      @definitions
    end

    def cookbook_name
      @metadata.name
    end

    def description
      @metadata.description
    end

    def source_url
      if @metadata.methods.include? :source_url
        @metadata.source_url
      else
        ""
      end
    end

    def issues_url
      if @metadata.methods.include? :issues_url
        @metadata.issues_url
      else
        ""
      end
    end

    def platforms
      @metadata.platforms.map do |platform, version|
        format_constraint(platform, version)
      end
    end

    def chef_versions
      if @metadata.methods.include?(:chef_version)
        @metadata.chef_versions.map do |chef_version|
          constraints = chef_version.requirements_list.join(', ')
          format_constraint(chef_version.name, constraints)
        end
      else
        []
      end
    end

    def dependencies
      @metadata.dependencies.map do |cookbook, version|
        format_constraint(cookbook, version)
      end
    end

    def recommendations
      if @metadata.methods.include?(:recommendations)
        @metadata.recommendations.map do |cookbook, version|
          format_constraint(cookbook, version)
        end
      else
        []
      end
    end

    def suggestions
      if @metadata.methods.include?(:suggestions)
        @metadata.suggestions.map do |cookbook, version|
          format_constraint(cookbook, version)
        end
      else
        []
      end
    end

    def conflicting
      if @metadata.methods.include?(:conflicting)
        @metadata.conflicting.map do |cookbook, version|
          format_constraint(cookbook, version)
        end
      else
        []
      end
    end

    def attributes
      @attributes
    end

    def recipes
      @recipes
    end

    def maintainer
      @metadata.maintainer
    end

    def maintainer_email
      @metadata.maintainer_email
    end

    def license
      @metadata.license
    end

    def name
      @metadata.name
    end

    def get_binding
      binding
    end

    private

    def source_url_from_berkshelf(name)
      @source_url ||= begin
        if File.exist?('Berksfile') && defined?(::Berkshelf)
          ::Berkshelf::Berksfile
            .from_file('Berksfile')
            .install
            .map { |cb| [cb.cookbook_name, cb.metadata.source_url] }
            .to_h
        else
          {}
        end
      end
      @source_url[name]
    end

    def format_constraint(name, version)
      url = source_url_from_berkshelf(name)
      if !url.nil? && url.start_with?('http')
        # git:// and ssh:// URLs are not browsable
        name = "[#{name}](#{url})"
      end

      if @constraints && version != DEFAULT_CONSTRAINT
        "#{name} (#{version})"
      else
        name
      end
    end
  end
end