class Drud::Readme

Evaluates an opscode chef cookbook's metadata and github history to generate a README.md file. The README.rb is placed in the root level of the cookbook. This forces cookbook developers to properly use metadata to document their cookbooks efficiently. Additionally, it provides proper attribution for all committers in the project with links back to the contributors github profile. It is written to take advantage of cookbooks that properly utilize both Rake tasks and metadata.

Attributes

commits[RW]

A scaled down version of the logs, eg: {commit, author}

cookbook[RW]

The path to the cookbook being processed.

credit[RW]

A hash containing credit information by author keyed by the authors github profile uri.

logs[RW]

The raw git log for the cookbook.

metadata[RW]

The cookbook as represented by Chef::Cookbook::Metadata.

tasks[RW]

Rake task information extracted from the target cookbook.

Public Class Methods

new(path) click to toggle source

Initialize a new instance of Drud::Readme

Attributes

  • :path - The local path of the cookbook.

Examples

This can be placed in a convenient location, such as a Rake task inside the cookbook. When the render method is called, it will overwrite the cookbooks README.md

readme = Drud::Readme.new(File.dirname(__FILE__))
readme.render
# File lib/drud/readme.rb, line 64
def initialize(path)
  @cookbook = path
  @type = :chef
  @metadata = load_metadata
  @logs = load_logs
  @commits, @credit, @tasks = {}, {}, {}
  @octokit_auth = { access_token: ENV['DRUD_OAUTH'] } if ENV['DRUD_OAUTH']
  parse_rake_tasks
  parse_commits
  parse_credit
end

Public Instance Methods

render() click to toggle source

Renders the README.md file and saves it in the cookbooks path.

# File lib/drud/readme.rb, line 77
def render
  markdown = ReadmeTemplate.new(
    metadata: @metadata, tasks: @tasks, credit: @credit
  )
  template_path = File.join(
    File.dirname(File.expand_path(__FILE__)),
    '../../templates/readme.md.erb'
  )
  readme = markdown.render(File.read(template_path))
  File.open("#{@cookbook}/README.md", 'w') { |file| file.write(readme) }
end

Private Instance Methods

format_credit(credit) click to toggle source

Formats the hash generated by parse_credit.

Attributes

  • :credit - A hash of credit information to parse.

# File lib/drud/readme.rb, line 135
def format_credit(credit) # :doc:
  f = @credit
  credit.each do |name, data|
    f[data[:html_url]] = {} if f[data[:html_url]].nil?
    f[data[:html_url]][:names] = Set.new if f[data[:html_url]][:names].nil?
    clean_name = /([^<]*)/.match(name)[1].strip
    f[data[:html_url]][:names].add(clean_name)
    count = f[data[:html_url]][:count]
    f[data[:html_url]][:count] += data[:count] unless count.nil?
    f[data[:html_url]][:count] = data[:count] if count.nil?
  end
end
github_html_url(commit) click to toggle source

Uses the Ocktokit client to read information about a commit from the cookbooks origin. This is an unauthenticated request to the github API and might be throttled if you exceed github's limits. It is only called once per author in a cookbooks project. This only returns the html_url to the authors github profile.

If the environment variable DRUD_OAUTH is set, it will be used as an OAUTH token for accessing GITHUB

Attributes

  • :commit - The commit hash to get information from.

# File lib/drud/readme.rb, line 160
def github_html_url(commit) # :doc:
  info = `cd #{@cookbook} && git remote -v`
  origin = /^origin.+?:([^\.+]*)/.match(info)[1]
  if @octokit_auth
    client = Octokit::Client.new @octokit_auth
  else
    client = Octokit::Client.new
  end
  
  begin
    detail = client.commit(origin, commit)
  rescue Octokit::NotFound
    puts "ERROR: Accessing Github origin: #{origin} commit: #{commit} @octokit_auth: #{@octokit_auth.inspect}"
    unless @octokit_auth
      puts "\tNeed to set environment variable DRUD_OAUTH to a valid Github access token for private repos"
      puts "\tSee https://help.github.com/articles/creating-an-access-token-for-command-line-use"
    end
    exit(-1)
  end
  detail[:author][:html_url]
end
load_logs() click to toggle source

Uses the git log command to extract the cookbook's log information.

# File lib/drud/readme.rb, line 100
def load_logs # :doc:
  logs = `cd #{@cookbook} && git log`.split('commit ')
  logs.shift
  logs
end
load_metadata() click to toggle source

Reads the cookbooks metadata and instantiates an instance of Chef::Cookbook::Metadata

# File lib/drud/readme.rb, line 93
def load_metadata # :doc:
  metadata = Chef::Cookbook::Metadata.new
  metadata.from_file("#{@cookbook}/metadata.rb")
  metadata
end
load_rake() click to toggle source

Loads an instance of Rake::Application from the cookbooks Rakefile for additional processing.

# File lib/drud/readme.rb, line 184
def load_rake # :doc:
  Dir.chdir @cookbook
  rake = Rake::Application.new
  Rake::TaskManager.record_task_metadata = true
  Rake.application = rake
  rake.init
  rake.load_rakefile
end
parse_commits() click to toggle source

Parses the results of git log and creates a simplified hash, eg: {commit, author}.

# File lib/drud/readme.rb, line 108
def parse_commits # :doc:
  @logs.map do |log|
    @commits[log.split("\n").shift] = /(^A[a-z]+: )(.+)$/.match(log)[2]
  end
end
parse_credit() click to toggle source

Parses a hash of commit information and generates a hash containing the authors github profile path and number of commits. Some authors may have multiple author names so the resulting hash is passed to format_credit for additional processing.

# File lib/drud/readme.rb, line 118
def parse_credit # :doc:
  credit = {}
  @commits.each do |commit, author|
    credit[author][:count] += 1 unless credit[author].nil?
    next unless credit[author].nil?
    credit[author] = {}
    credit[author][:count] = 1
    credit[author][:html_url] = github_html_url commit
  end
  format_credit credit
end
parse_rake_tasks() click to toggle source

Parses metadata from the cookbooks Rake Tasks.

# File lib/drud/readme.rb, line 194
def parse_rake_tasks # :doc:
  load_rake
  Rake.application.tasks.each do |t|
    @tasks[t] = {
      'sources' => t.sources, 'full_comment' => t.full_comment,
      'actions' => t.actions, 'application' => t.application,
      'comment' => t.comment, 'prerequisites' => t.prerequisites,
      'scope' => t.scope
    }
  end
end