# class Hoe

Hoe is a simple rake/rubygems helper for project Rakefiles. It helps generate rubygems and includes a dynamic plug-in system allowing for easy extensibility. Hoe ships with plug-ins for all your usual project tasks including rdoc generation, testing, packaging, and deployment.

## Using Hoe¶ ↑

### Basics¶ ↑

Sow generates a new project from scratch. Sow uses a simple ERB templating system allowing you to capture patterns common to your projects. Run `sow` and then see ~/.hoe_template for more info:

    % sow project_name
    ...
    % cd project_name

and have at it.

### Extra Configuration Options:¶ ↑

Hoe maintains a config file for cross-project values. The file is located at `~/.hoerc`. The file is a YAML formatted config file with the following settings (extended by plugins):

exclude

  A regular expression of files to exclude from `check_manifest`.

Run ``rake config_hoe`` and see ~/.hoerc for examples.

## Extending Hoe¶ ↑

Hoe can be extended via its plugin system. Hoe searches out all installed files matching `'hoe/*.rb'` and loads them. Those files are expected to define a module matching the file name. The module must define a define task method and can optionally define an initialize method. Both methods must be named to match the file. eg

    module Hoe::Blah
      def initialize_blah # optional
        # ...
      end

      def define_blah_tasks
        # ...
      end
    end

### Hoe Plugin Loading Sequence¶ ↑

    Hoe.spec
      Hoe.load_plugins
        require
      activate_plugins
        extend plugin_module
        initialize_plugins
          initialize_XXX
        activate_plugin_deps
          activate_XXX_deps
      yield spec
      post_initialize
        define_spec # gemspec, not hoespec
        load_plugin_tasks
        add_dependencies

### Constants

DEFAULT_CONFIG

  Default configuration values for .hoerc. Plugins should populate this on load.

RUBY_DEBUG

  Used to add extra flags to RUBY_FLAGS.

RUBY_FLAGS

  Used to specify flags to ruby [has smart default].

VERSION

  duh

WINDOZE

  True if you're a masochistic developer. Used for building commands.

### Attributes

author[RW]

MANDATORY: The author(s) of the package. (can be array)

Use the developer method to fill in both author and email cleanly.

changes[RW]

Optional: A description of the release's latest changes. Auto-populates to the top entry of History.txt.

description[RW]

Optional: A description of the project. Auto-populates from the first paragraph of the DESCRIPTION section of README.txt.

See also: #summary and #paragraphs_of.

description_sections[RW]

Optional: What sections from the readme to use for auto-description. Defaults to %w(description).

email[RW]

MANDATORY: The author's email address(es). (can be array)

Use the developer method to fill in both author and email cleanly.

extra_deps[RW]

Optional: An array of rubygem dependencies.

    extra_deps << ['blah', '~> 1.0']

extra_dev_deps[RW]

Optional: An array of rubygem developer dependencies.

extra_rdoc_files[RW]

Optional: Extra files you want to add to RDoc.

.txt files are automatically included (excluding the obvious).

group_name[RW]

Optional: The name of the group authoring the project. [default: name.downcase]

history_file[RW]

Optional: The filename for the project history. [default: History.txt]

licenses[RW]

Optional: An array containing the license(s) under which this gem is released.

Warns and defaults to “MIT” if not set.

name[RW]

MANDATORY: The name of the release.

Set via ::spec.

post_install_message[RW]

Optional: A post-install message to be displayed when gem is installed.

readme_file[RW]

Optional: The filename for the project readme. [default: README.txt]

spec_extras[RW]

Optional: A hash of extra values to set in the gemspec. Value may be a proc.

    spec_extras[:required_rubygems_version] = '>= 1.3.2'

(tho, see pluggable! if that's all you want to do)

summary[RW]

Optional: A short summary of the project. Auto-populates from the first sentence of the description.

See also: #description and #paragraphs_of.

summary_sentences[RW]

Optional: Number of sentences from description for summary. Defaults to 1.

test_globs[RW]

Optional: An array of test file patterns [default: test /test_*.rb]

urls[RW]

Optional: The urls of the project. This can be an array or (preferably) a hash. Auto-populates to the urls read from the beginning of README.txt.

See #parse_urls for more details

version[RW]

MANDATORY: The version. Don't hardcode! use a constant in the project.

### Public Class Methods

add_include_dirs(*dirs) click to toggle source

Add extra dirs to both $: and RUBY_FLAGS (for test runs and rakefile deps)

    # File lib/hoe.rb, line 280
    def self.add_include_dirs(*dirs)
      dirs = dirs.flatten
      $:.unshift(*dirs)
      s = File::PATH_SEPARATOR
      RUBY_FLAGS.sub!(/-I/, "-I#{dirs.join(s)}#{s}")
    end

bad_plugins() click to toggle source

Returns plugins that could not be loaded by ::load_plugins.

    # File lib/hoe.rb, line 290
    def self.bad_plugins
      @bad_plugins
    end

load_plugins(plugins = Hoe.plugins) click to toggle source

Find and load all plugin files.

It is called at the end of hoe.rb

    # File lib/hoe.rb, line 299
    def self.load_plugins plugins = Hoe.plugins
      @found  ||= {}
      @loaded ||= {}
      @files  ||= Gem.find_files "hoe/*.rb"

      @files.reverse.each do |path|
        @found[File.basename(path, ".rb").intern] = path
      end

      :keep_doing_this while @found.map { |name, plugin|
        next unless plugins.include? name
        next if @loaded[name]
        begin
          warn "loading #{plugin}" if $DEBUG
          @loaded[name] = require plugin
        rescue LoadError => e
          warn "error loading #{plugin.inspect}: #{e.message}. skipping..."
        end
      }.any?

      bad_plugins = plugins - @loaded.keys
      bad_plugins.each do |bad_plugin|
        plugins.delete bad_plugin
      end

      @bad_plugins.concat bad_plugins
      @bad_plugins.uniq!

      return @loaded, @found
    end

plugin(*plugins) click to toggle source

Activates `plugins`. If a plugin cannot be loaded it will be ignored.

Plugins may also be activated through a `plugins` array in `~/.hoerc`. This should only be used for plugins that aren't critical to your project and plugins that you want to use on other projects.

    # File lib/hoe.rb, line 362
    def self.plugin *plugins
      self.plugins.concat plugins
      self.plugins.uniq!
    end

plugins() click to toggle source

The list of active plugins.

    # File lib/hoe.rb, line 370
    def self.plugins
      @@plugins
    end

spec(name, &block) click to toggle source

Execute the Hoe DSL to define your project's Hoe specification (which interally creates a gem specification). All hoe attributes and methods are available within `block`. Eg:

    Hoe.spec name do
      # ... project specific data ...
    end

    # File lib/hoe.rb, line 383
    def self.spec name, &block
      Hoe.load_plugins

      spec = self.new name
      spec.activate_plugins
      spec.instance_eval(&block)
      spec.post_initialize
      spec # TODO: remove?
    end

### Public Instance Methods

activate_plugin_deps() click to toggle source

Run all activate_*_deps methods for plugins

    # File lib/hoe.rb, line 432
    def activate_plugin_deps
      Hoe.plugins.each do |plugin|
        msg = "activate_#{plugin}_deps"
        warn msg if $DEBUG
        send msg if self.respond_to? msg
      end
    end

activate_plugins() click to toggle source

Activate plugin modules and add them to the current instance.

    # File lib/hoe.rb, line 396
    def activate_plugins
      with_config do |config, _|
        config_plugins = config['plugins']
        break unless config_plugins
        Hoe.plugins.concat config_plugins.map { |plugin| plugin.intern }
      end

      Hoe.load_plugins Hoe.plugins

      names = Hoe.constants.map { |s| s.to_s }
      names.reject! { |n| n =~ /^[A-Z_]+$/ }

      names.each do |name|
        next unless Hoe.plugins.include? name.downcase.intern
        warn "extend #{name}" if $DEBUG
        self.extend Hoe.const_get(name)
      end

      initialize_plugins
      activate_plugin_deps
    end

add_dependencies() click to toggle source

Add standard and user defined dependencies to the spec.

    # File lib/hoe.rb, line 468
    def add_dependencies
      self.extra_deps     = normalize_deps extra_deps
      self.extra_dev_deps = normalize_deps extra_dev_deps

      case name
      when 'hoe' then
        dependency "rake", [">= 0.8", "< 11.0"]
      else
        version = VERSION.split(/\./).first(2).join(".")
        dependency "hoe", "~> #{version}", :development
      end

      seen = {}

      extra_deps.each do |dep|
        next if seen[dep.first]
        seen[dep.first] = true

        spec.add_dependency(*dep)
      end

      extra_dev_deps.each do |dep|
        next if seen[dep.first]
        seen[dep.first] = true

        spec.add_development_dependency(*dep)
      end
    end

define_spec() click to toggle source

Define the Gem::Specification.

    # File lib/hoe.rb, line 507
    def define_spec
      self.spec = Gem::Specification.new do |s|
        dirs = Dir['lib']

        manifest = read_manifest

        abort [
               "Manifest is missing or couldn't be read.",
               "The Manifest is kind of a big deal.",
               "Maybe you're using a gem packaged by a linux project.",
               "It seems like they enjoy breaking other people's code."
               ].join "\n" unless manifest

        s.name                 = name
        s.version              = version if version
        s.summary              = summary
        s.email                = email
        s.homepage             = case urls
                                 when Hash then
                                   urls["home"] || urls.values.first
                                 when Array then
                                   urls.first
                                 else
                                   raise "unknown urls format: #{urls.inspect}"
                                 end
        s.description          = description
        s.files                = manifest
        s.executables          = s.files.grep(/^bin/) { |f| File.basename(f) }
        s.bindir               = "bin"
        s.require_paths        = dirs unless dirs.empty?
        s.rdoc_options         = ['--main', readme_file]
        s.post_install_message = post_install_message
        s.test_files           = Dir[*self.test_globs].uniq

        missing "Manifest.txt" if s.files.empty?

        case author
        when Array
          s.authors = author
        else
          s.author  = author
        end

        s.extra_rdoc_files += s.files.grep(/\.(txt|rdoc|md)$/)
        s.extra_rdoc_files.reject! { |f| f =~ %r^(test|spec|vendor|template|data|tmp)/% }
        s.extra_rdoc_files += @extra_rdoc_files
      end

      check_for_version

      if licenses.empty?
        warn "Defaulting gemspec to MIT license."
        warn "Call license in hoe spec to change."
        license "MIT"
      end

      spec.licenses = licenses

      run_spec_extras
    end

dependency(name, version, type = :runtime) click to toggle source

Add a dependency declaration to your spec. Pass :dev to `type` for developer dependencies.

    # File lib/hoe.rb, line 452
    def dependency name, version, type = :runtime
      raise "Unknown dependency type: #{type}" unless
        [:runtime, :dev, :development, :developer].include? type

      ary = if type == :runtime then
              extra_deps
            else
              extra_dev_deps
            end

      ary << [name, version]
    end

dependency_target() click to toggle source

Returns the proper dependency list for the thingy.

    # File lib/hoe.rb, line 500
    def dependency_target
      self.name == 'hoe' ? extra_deps : extra_dev_deps
    end

developer(name, email) click to toggle source

Convenience method to set add to both the author and email fields.

    # File lib/hoe.rb, line 605
    def developer name, email
      self.author << name
      self.email  << email
    end

have_gem?(name) click to toggle source

Returns true if the gem `name` is installed.

    # File lib/hoe.rb, line 613
    def have_gem? name
      Gem::Specification.find_by_name name.to_s
    rescue Gem::LoadError
      false
    end

initialize_plugins() click to toggle source

Run all initialize_* methods for plugins

    # File lib/hoe.rb, line 421
    def initialize_plugins
      Hoe.plugins.each do |plugin|
        msg = "initialize_#{plugin}"
        warn msg if $DEBUG
        send msg if self.respond_to? msg
      end
    end

intuit_values() click to toggle source

Intuit values from the readme and history files.

    # File lib/hoe.rb, line 658
    def intuit_values
      header_re = /^((?:=+|#+) .*)$/
      readme    = File.read_utf(readme_file).split(header_re)[1..-1] rescue ''

      unless readme.empty? then
        sections = Hash[*readme.map { |s|
          s =~ /^[=#]/ ? s.strip.downcase.chomp(':').split.last : s.strip
        }]
        desc     = sections.values_at(*description_sections).join("\n\n")
        summ     = desc.split(/\.\s+/).first(summary_sentences).join(". ")
        urls     = parse_urls(readme[1])

        self.urls        ||= urls
        self.description ||= desc
        self.summary     ||= summ
      else
        missing readme_file
      end

      self.changes ||= begin
                         h = File.read_utf(history_file)
                         h.split(/^(={2,}|\#{2,})/)[1..2].join.strip
                       rescue
                         missing history_file
                         ''
                       end
    end

license(name) click to toggle source

Specify a license for your gem. Call it multiple times if you are releasing under multiple licenses.

    # File lib/hoe.rb, line 444
    def license name
      self.licenses << name
    end

load_plugin_tasks() click to toggle source

Load activated plugins by calling their define tasks method.

    # File lib/hoe.rb, line 718
    def load_plugin_tasks
      bad = []

      $plugin_max = self.class.plugins.map { |s| s.to_s.size }.max

      self.class.plugins.each do |plugin|
        warn "define: #{plugin}" if $DEBUG

        old_tasks = Rake::Task.tasks.dup

        begin
          send "define_#{plugin}_tasks"
        rescue NoMethodError
          warn "warning: couldn't activate the #{plugin} plugin, skipping"

          bad << plugin
          next
        end

        (Rake::Task.tasks - old_tasks).each do |task|
          task.plugin = plugin
        end
      end
      @@plugins -= bad
    end

missing(name) click to toggle source

Bitch about a file that is missing data or unparsable for intuiting values.

    # File lib/hoe.rb, line 747
    def missing name
      warn "** #{name} is missing or in the wrong format for auto-intuiting."
      warn "   run `sow blah` and look at its text files"
    end

normalize_deps(deps) click to toggle source

Normalize the dependencies.

    # File lib/hoe.rb, line 755
    def normalize_deps deps
      deps = Array(deps)

      deps.each do |o|
        abort "ERROR: Add '~> x.y' to the '#{o}' dependency." if String === o
      end

      deps
    end

paragraphs_of(path, *paragraphs) click to toggle source

Reads a file at `path` and spits out an array of the `paragraphs` specified.

    changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
    summary, *description = p.paragraphs_of('README.txt', 3, 3..8)

    # File lib/hoe.rb, line 771
    def paragraphs_of path, *paragraphs
      File.read_utf(path).delete("\r").split(/\n\n+/).values_at(*paragraphs)
    end

parse_urls(text) click to toggle source

Parse the urls section of the readme file. Returns a hash or an array depending on the format of the section.

    label1 :: url1
    label2 :: url2
    label3 :: url3

vs:

    * url1
    * url2
    * url3

The hash format is preferred as it will be used to populate gem metadata. The array format will work, but will warn that you should update the readme.

    # File lib/hoe.rb, line 704
    def parse_urls text

      lines = text.gsub(/^\* /, '').delete("<>").split(/\n/).grep(/\S+/)

      if lines.first =~ /::/ then
        Hash[lines.map { |line| line.split(/\s*::\s*/) }]
      else
        lines
      end
    end

pluggable!() click to toggle source

Tell the world you're a pluggable package (ie you require rubygems 1.3.1+)

This uses require_rubygems_version. Last one wins. Make sure you account for that.

    # File lib/hoe.rb, line 781
    def pluggable!
      abort "update rubygems to >= 1.3.1" unless  Gem.respond_to? :find_files
      require_rubygems_version '>= 1.3.1'
    end

plugin?(name) click to toggle source

Is a plugin activated? Used for guarding missing plugins in your hoe spec:

    Hoe.spec "blah" do
      if plugin? :enhancement then
        self.enhancement = true # or whatever...
      end
    end

    # File lib/hoe.rb, line 796
    def plugin? name
      self.class.plugins.include? name
    end

post_initialize() click to toggle source

Finalize configuration

    # File lib/hoe.rb, line 803
    def post_initialize
      intuit_values
      validate_fields
      define_spec
      load_plugin_tasks
      add_dependencies
    end

read_manifest() click to toggle source

Reads Manifest.txt and returns an Array of lines in the manifest.

Returns nil if no manifest was found.

    # File lib/hoe.rb, line 816
    def read_manifest
      File.read_utf("Manifest.txt").split(/\r?\n\r?/) rescue nil
    end

require_ruby_version(*versions) click to toggle source

Declare that your gem requires a specific ruby version. Last one wins.

    # File lib/hoe.rb, line 830
    def require_ruby_version *versions
      spec_extras[:required_ruby_version] = versions
    end

require_rubygems_version(*versions) click to toggle source

Declare that your gem requires a specific rubygems version. Last one wins.

    # File lib/hoe.rb, line 823
    def require_rubygems_version *versions
      spec_extras[:required_rubygems_version] = versions
    end

timebomb(n, m, finis = nil, start = nil) click to toggle source

Provide a linear degrading value from n to m over start to finis dates. If not provided, start and finis will default to 1/1 and 12/31 of the current year.

    # File lib/hoe.rb, line 839
    def timebomb n, m, finis = nil, start = nil
      require 'time'
      finis = Time.parse(finis || "#{Time.now.year}-12-31")
      start = Time.parse(start || "#{Time.now.year}-01-01")
      rest  = (finis - Time.now)
      full  = (finis - start)

      [((n - m) * rest / full).to_i + m, m].max
    end

url() click to toggle source

Deprecated: Optional: The url(s) of the project. (can be array). Auto-populates to a list of urls read from the beginning of README.txt.

    # File lib/hoe.rb, line 251
    def url
      warn "NOTE: Hoe#url is deprecated, use urls. It will be removed on or after 2012-06-01."
      warn "Used from #{caller.first}"
      @url
    end

validate_fields() click to toggle source

Verify that mandatory fields are set.

    # File lib/hoe.rb, line 852
    def validate_fields
      %w(email author).each do |field|
        value = self.send(field)
        abort "Hoe #{field} value not set. aborting" if value.nil? or value.empty?
      end
    end

with_config() { |config, rc| ... } click to toggle source

Loads ~/.hoerc, merges it with a .hoerc in the current pwd (if any) and yields the configuration and its path

    # File lib/hoe.rb, line 863
    def with_config
      config = Hoe::DEFAULT_CONFIG

      rc = File.expand_path("~/.hoerc")
      exists = File.exist? rc
      homeconfig = exists ? YAML.load_file(rc) : {}

      config = config.merge homeconfig

      localrc = File.join Dir.pwd, '.hoerc'
      exists = File.exist? localrc
      localconfig = exists ? YAML.load_file(localrc) : {}

      config = config.merge localconfig

      yield config, rc
    end