In Files

Parent

Namespace

Included Modules

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

VERSION

duh

RUBY_DEBUG

Used to add extra flags to RUBY_FLAGS.

RUBY_FLAGS

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

DEFAULT_CONFIG

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

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 # 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: Hoe#summary and Hoe.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 # 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).

history_file[RW]

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

name[RW]

MANDATORY: The name of the release.

Set via Hoe.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]

rubyforge_name[RW]

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

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 # 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: Hoe#description and Hoe.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 274
274:   def self.add_include_dirs(*dirs)
275:     dirs = dirs.flatten
276:     $:.unshift(*dirs)
277:     s = File::PATH_SEPARATOR
278:     RUBY_FLAGS.sub!(/-I/, "-I#{dirs.join(s)}#{s}")
279:   end
bad_plugins() click to toggle source

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

     # File lib/hoe.rb, line 284
284:   def self.bad_plugins
285:     @bad_plugins
286:   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 293
293:   def self.load_plugins plugins = Hoe.plugins
294:     @found  ||= {}
295:     @loaded ||= {}
296:     @files  ||= Gem.find_files "hoe/*.rb"
297: 
298:     @files.reverse.each do |path|
299:       @found[File.basename(path, ".rb").intern] = path
300:     end
301: 
302:     :keep_doing_this while @found.map { |name, plugin|
303:       next unless plugins.include? name
304:       next if @loaded[name]
305:       begin
306:         warn "loading #{plugin}" if $DEBUG
307:         @loaded[name] = require plugin
308:       rescue LoadError => e
309:         warn "error loading #{plugin.inspect}: #{e.message}. skipping..."
310:       end
311:     }.any?
312: 
313:     bad_plugins = plugins - @loaded.keys
314:     bad_plugins.each do |bad_plugin|
315:       plugins.delete bad_plugin
316:     end
317: 
318:     @bad_plugins.concat bad_plugins
319:     @bad_plugins.uniq!
320: 
321:     return @loaded, @found
322:   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 353
353:   def self.plugin *plugins
354:     self.plugins.concat plugins
355:     self.plugins.uniq!
356:   end
plugins() click to toggle source

The list of active plugins.

     # File lib/hoe.rb, line 361
361:   def self.plugins
362:     @@plugins
363:   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 374
374:   def self.spec name, &block
375:     Hoe.load_plugins
376: 
377:     spec = self.new name
378:     spec.activate_plugins
379:     spec.instance_eval(&block)
380:     spec.post_initialize
381:     spec # TODO: remove?
382:   end

Public Instance Methods

activate_plugin_deps() click to toggle source
     # File lib/hoe.rb, line 417
417:   def activate_plugin_deps
418:     Hoe.plugins.each do |plugin|
419:       msg = "activate_#{plugin}_deps"
420:       warn msg if $DEBUG
421:       send msg if self.respond_to? msg
422:     end
423:   end
activate_plugins() click to toggle source

Activate plugin modules and add them to the current instance.

     # File lib/hoe.rb, line 387
387:   def activate_plugins
388:     with_config do |config, _|
389:       config_plugins = config['plugins']
390:       break unless config_plugins
391:       Hoe.plugins.concat config_plugins.map { |plugin| plugin.intern }
392:     end
393: 
394:     Hoe.load_plugins Hoe.plugins
395: 
396:     names = Hoe.constants.map { |s| s.to_s }
397:     names.reject! { |n| n =~ /^[A-Z_]+$/ }
398: 
399:     names.each do |name|
400:       next unless Hoe.plugins.include? name.downcase.intern
401:       warn "extend #{name}" if $DEBUG
402:       self.extend Hoe.const_get(name)
403:     end
404: 
405:     initialize_plugins
406:     activate_plugin_deps
407:   end
add_dependencies() click to toggle source

Add standard and user defined dependencies to the spec.

     # File lib/hoe.rb, line 445
445:   def add_dependencies
446:     self.extra_deps     = normalize_deps extra_deps
447:     self.extra_dev_deps = normalize_deps extra_dev_deps
448: 
449:     case name
450:     when 'hoe' then
451:       dependency "rake", "~> 0.8"
452:     else
453:       version = VERSION.split(/\./).first(2).join(".")
454:       dependency "hoe", "~> #{version}", :development
455:     end
456: 
457:     seen = {}
458: 
459:     extra_deps.each do |dep|
460:       next if seen[dep.first]
461:       seen[dep.first] = true
462: 
463:       spec.add_dependency(*dep)
464:     end
465: 
466:     extra_dev_deps.each do |dep|
467:       next if seen[dep.first]
468:       seen[dep.first] = true
469: 
470:       spec.add_development_dependency(*dep)
471:     end
472:   end
define_spec() click to toggle source

Define the Gem::Specification.

     # File lib/hoe.rb, line 484
484:   def define_spec
485:     self.spec = Gem::Specification.new do |s|
486:       dirs = Dir['lib']
487: 
488:       manifest = read_manifest
489: 
490:       abort [
491:              "Manifest is missing or couldn't be read.",
492:              "The Manifest is kind of a big deal.",
493:              "Maybe you're using a gem packaged by a linux project.",
494:              "It seems like they enjoy breaking other people's code."
495:              ].join "\n" unless manifest
496: 
497:       s.name                 = name
498:       s.version              = version if version
499:       s.summary              = summary
500:       s.email                = email
501:       s.homepage             = case urls
502:                                when Hash then
503:                                  urls["home"] || urls.values.first
504:                                when Array then
505:                                  urls.first
506:                                else
507:                                  raise "unknown urls format: #{urls.inspect}"
508:                                end
509:       s.rubyforge_project    = rubyforge_name
510:       s.description          = description
511:       s.files                = manifest
512:       s.executables          = s.files.grep(/^bin/) { |f| File.basename(f) }
513:       s.bindir               = "bin"
514:       s.require_paths        = dirs unless dirs.empty?
515:       s.rdoc_options         = ['--main', readme_file]
516:       s.post_install_message = post_install_message
517:       s.test_files           = Dir[*self.test_globs]
518: 
519:       missing "Manifest.txt" if s.files.empty?
520: 
521:       case author
522:       when Array
523:         s.authors = author
524:       else
525:         s.author  = author
526:       end
527: 
528:       s.extra_rdoc_files += s.files.grep(/(txt|rdoc)$/)
529:       s.extra_rdoc_files.reject! { |f| f =~ %^(test|spec|vendor|template|data|tmp)/% }
530:       s.extra_rdoc_files += @extra_rdoc_files
531:     end
532: 
533:     unless self.version then
534:       version    = nil
535:       version_re = /VERSION += +([\"\'])([\d][\w\.]+)\11//
536: 
537:       spec.files.each do |file|
538:         next unless File.exist? file
539:         version = File.read_utf(file)[version_re, 2] rescue nil
540:         break if version
541:       end
542: 
543:       spec.version = self.version = version if version
544: 
545:       unless self.version then
546:         spec.version = self.version = "0.borked"
547:         warn "** Add 'VERSION = \"x.y.z\"' to your code,"
548:         warn "   add a version to your hoe spec,"
549:         warn "   or fix your Manifest.txt"
550:       end
551:     end
552: 
553:     # Do any extra stuff the user wants
554:     spec_extras.each do |msg, val|
555:       case val
556:       when Proc
557:         val.call spec.send(msg)
558:       else
559:         spec.send "#{msg}=", val
560:       end
561:     end
562:   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 429
429:   def dependency name, version, type = :runtime
430:     raise "Unknown dependency type: #{type}" unless
431:       [:runtime, :dev, :development, :developer].include? type
432: 
433:     ary = if type == :runtime then
434:             extra_deps
435:           else
436:             extra_dev_deps
437:           end
438: 
439:     ary << [name, version]
440:   end
dependency_target() click to toggle source

Returns the proper dependency list for the thingy.

     # File lib/hoe.rb, line 477
477:   def dependency_target
478:     self.name == 'hoe' ? extra_deps : extra_dev_deps
479:   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 567
567:   def developer name, email
568:     self.author << name
569:     self.email  << email
570:   end
have_gem?(name) click to toggle source

Returns true if the gem name is installed.

     # File lib/hoe.rb, line 575
575:   def have_gem? name
576:     Gem::Specification.find_by_name name
577:   rescue Gem::LoadError
578:     false
579:   end
initialize_plugins() click to toggle source
     # File lib/hoe.rb, line 409
409:   def initialize_plugins
410:     Hoe.plugins.each do |plugin|
411:       msg = "initialize_#{plugin}"
412:       warn msg if $DEBUG
413:       send msg if self.respond_to? msg
414:     end
415:   end
intuit_values() click to toggle source

Intuit values from the readme and history files.

     # File lib/hoe.rb, line 618
618:   def intuit_values
619:     header_re = /^((?:=+|#+) .*)$/
620:     readme    = File.read_utf(readme_file).split(header_re)[1..1] rescue ''
621: 
622:     unless readme.empty? then
623:       sections = Hash[*readme.map { |s|
624:         s =~ /^[=#]/ ? s.strip.downcase.chomp(':').split.last : s.strip
625:       }]
626:       desc     = sections.values_at(*description_sections).join("\n\n")
627:       summ     = desc.split(/\.\s+/).first(summary_sentences).join(". ")
628:       urls     = parse_urls(readme[1])
629: 
630:       self.urls        ||= urls
631:       self.description ||= desc
632:       self.summary     ||= summ
633:     else
634:       missing readme_file
635:     end
636: 
637:     self.changes ||= begin
638:                        h = File.read_utf(history_file)
639:                        h.split(/^(={2,}|\#{2,})/)[1..2].join.strip
640:                      rescue
641:                        missing history_file
642:                        ''
643:                      end
644:   end
load_plugin_tasks() click to toggle source

Load activated plugins by calling their define tasks method.

     # File lib/hoe.rb, line 676
676:   def load_plugin_tasks
677:     bad = []
678: 
679:     $plugin_max = self.class.plugins.map { |s| s.to_s.size }.max
680: 
681:     self.class.plugins.each do |plugin|
682:       warn "define: #{plugin}" if $DEBUG
683: 
684:       old_tasks = Rake::Task.tasks.dup
685: 
686:       begin
687:         send "define_#{plugin}_tasks"
688:       rescue NoMethodError
689:         warn "warning: couldn't activate the #{plugin} plugin, skipping"
690: 
691:         bad << plugin
692:         next
693:       end
694: 
695:       (Rake::Task.tasks - old_tasks).each do |task|
696:         task.plugin = plugin
697:       end
698:     end
699:     @@plugins -= bad
700:   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 705
705:   def missing name
706:     warn "** #{name} is missing or in the wrong format for auto-intuiting."
707:     warn "   run `sow blah` and look at its text files"
708:   end
normalize_deps(deps) click to toggle source

Normalize the dependencies.

     # File lib/hoe.rb, line 713
713:   def normalize_deps deps
714:     deps = Array(deps)
715: 
716:     deps.each do |o|
717:       abort "ERROR: Add '~> x.y' to the '#{o}' dependency." if String === o
718:     end
719: 
720:     deps
721:   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 729
729:   def paragraphs_of path, *paragraphs
730:     File.read_utf(path).delete("\r").split(/\n\n+/).values_at(*paragraphs)
731:   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 664
664:   def parse_urls text
665:     lines = text.gsub(/^\* /, '').split(/\n/).grep(/\S+/)
666:     if lines.first =~ /::/ then
667:       Hash[lines.map { |line| line.split(/\s*::\s*/) }]
668:     else
669:       lines
670:     end
671:   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 739
739:   def pluggable!
740:     abort "update rubygems to >= 1.3.1" unless  Gem.respond_to? :find_files
741:     require_rubygems_version '>= 1.3.1'
742:   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 754
754:   def plugin? name
755:     self.class.plugins.include? name
756:   end
post_initialize() click to toggle source

Finalize configuration

     # File lib/hoe.rb, line 761
761:   def post_initialize
762:     intuit_values
763:     validate_fields
764:     define_spec
765:     load_plugin_tasks
766:     add_dependencies
767:   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 774
774:   def read_manifest
775:     File.read_utf("Manifest.txt").split(/\r?\n\r?/) rescue nil
776:   end
require_ruby_version(version) click to toggle source

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

     # File lib/hoe.rb, line 788
788:   def require_ruby_version version
789:     spec_extras[:required_ruby_version] = version
790:   end
require_rubygems_version(version) click to toggle source

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

     # File lib/hoe.rb, line 781
781:   def require_rubygems_version version
782:     spec_extras[:required_rubygems_version] = version
783:   end
timebomb(n, m, finis = '2010-04-01', start = '2009-03-14') click to toggle source

Provide a linear degrading value from n to m over start to finis dates.

     # File lib/hoe.rb, line 795
795:   def timebomb n, m, finis = '2010-04-01', start = '2009-03-14'
796:     require 'time'
797:     finis = Time.parse finis
798:     start = Time.parse start
799:     rest  = (finis - Time.now)
800:     full  = (finis - start)
801: 
802:     [((n - m) * rest / full).to_i + m, m].max
803:   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 245
245:   def url
246:     warn "NOTE: Hoe#url is deprecated, use urls. It will be removed on or after 2012-06-01."
247:     warn "Used from #{caller.first}"
248:     @url
249:   end
url=(o) click to toggle source
     # File lib/hoe.rb, line 251
251:   def url=o
252:     warn "NOTE: Hoe#url= is deprecated, use urls=. It will be removed on or after 2012-06-01."
253:     warn "Used from #{caller.first}"
254:     @url=o
255:   end
validate_fields() click to toggle source

Verify that mandatory fields are set.

     # File lib/hoe.rb, line 808
808:   def validate_fields
809:     %(email author).each do |field|
810:       value = self.send(field)
811:       abort "Hoe #{field} value not set. aborting" if value.nil? or value.empty?
812:     end
813:   end
with_config() 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 819
819:   def with_config
820:     config = Hoe::DEFAULT_CONFIG
821: 
822:     rc = File.expand_path("~/.hoerc")
823:     exists = File.exist? rc
824:     homeconfig = exists ? YAML.load_file(rc) : {}
825: 
826:     config = config.merge homeconfig
827: 
828:     localrc = File.join Dir.pwd, '.hoerc'
829:     exists = File.exist? localrc
830:     localconfig = exists ? YAML.load_file(localrc) : {}
831: 
832:     config = config.merge localconfig
833: 
834:     yield config, rc
835:   end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.