Class: Hoe

Inherits:
Object
  • Object
show all
Includes:
Rake::DSL
Defined in:
lib/hoe.rb

Overview

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

Defined Under Namespace

Modules: Clean, Compiler, Debug, Deps, Flay, Flog, GemPreludeSucks, Gemcutter, Inline, Newb, Package, Publish, RCov, Racc, RubyForge, Signing, Test

Constant Summary

VERSION =

duh

'2.9.4'
RUBY_DEBUG =

Used to add extra flags to RUBY_FLAGS.

ENV['RUBY_DEBUG']
RUBY_FLAGS =

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

ENV['RUBY_FLAGS'] || default_ruby_flags
DEFAULT_CONFIG =

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

{
  "exclude" => /tmp$|CVS|\.svn|\.log$/,
}
WINDOZE =
/mswin|mingw/ =~ RUBY_PLATFORM
@@plugins =
[:clean, :debug, :deps, :flay, :flog, :newb, :package,
:publish, :rcov, :gemcutter, :signing, :test]

Instance Attribute Summary (collapse)

Class Method Summary (collapse)

Instance Method Summary (collapse)

Constructor Details

- (Hoe) initialize(name, version = nil)

Create a newly initialized hoe spec. If a block is given, yield on it and finish post_initialize steps. This is deprecated behavior and should be switched from Hoe.new to Hoe.spec.



472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
# File 'lib/hoe.rb', line 472

def initialize name, version = nil # :nodoc:
  self.name                 = name
  self.version              = version

  self.author               = []
  self.changes              = nil
  self.description          = nil
  self.description_sections = %w(description)
  self.email                = []
  self.extra_deps           = []
  self.extra_dev_deps       = []
  self.extra_rdoc_files     = []
  self.history_file         = "History.txt"
  self.post_install_message = nil
  self.readme_file          = "README.txt"
  self.rubyforge_name       = name.downcase
  self.spec                 = nil
  self.spec_extras          = {}
  self.summary              = nil
  self.summary_sentences    = 1
  self.test_globs           = ['test/**/test_*.rb']
  self.url                  = nil

  if block_given? then
    warn "Hoe.new {...} deprecated. Switch to Hoe.spec."
    Hoe.load_plugins
    self.activate_plugins
    yield self
    post_initialize
  end
end

Instance Attribute Details

- (Object) author

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

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



106
107
108
# File 'lib/hoe.rb', line 106

def author
  @author
end

- (Object) changes

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



112
113
114
# File 'lib/hoe.rb', line 112

def changes
  @changes
end

- (Object) description

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.



120
121
122
# File 'lib/hoe.rb', line 120

def description
  @description
end

- (Object) description_sections

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



126
127
128
# File 'lib/hoe.rb', line 126

def description_sections
  @description_sections
end

- (Object) email

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

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



133
134
135
# File 'lib/hoe.rb', line 133

def email
  @email
end

- (Object) extra_deps

Optional: An array of rubygem dependencies.

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


140
141
142
# File 'lib/hoe.rb', line 140

def extra_deps
  @extra_deps
end

- (Object) extra_dev_deps

Optional: An array of rubygem developer dependencies.



145
146
147
# File 'lib/hoe.rb', line 145

def extra_dev_deps
  @extra_dev_deps
end

- (Object) extra_rdoc_files

Optional: Extra files you want to add to RDoc.

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



152
153
154
# File 'lib/hoe.rb', line 152

def extra_rdoc_files
  @extra_rdoc_files
end

- (Object) history_file

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



157
158
159
# File 'lib/hoe.rb', line 157

def history_file
  @history_file
end

- (Object) name

MANDATORY: The name of the release.

Set via Hoe.spec.



164
165
166
# File 'lib/hoe.rb', line 164

def name
  @name
end

- (Object) post_install_message

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



169
170
171
# File 'lib/hoe.rb', line 169

def post_install_message
  @post_install_message
end

- (Object) readme_file

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



174
175
176
# File 'lib/hoe.rb', line 174

def readme_file
  @readme_file
end

- (Object) rubyforge_name

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



179
180
181
# File 'lib/hoe.rb', line 179

def rubyforge_name
  @rubyforge_name
end

- (Object) spec

The Gem::Specification.



184
185
186
# File 'lib/hoe.rb', line 184

def spec
  @spec
end

- (Object) spec_extras

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)



193
194
195
# File 'lib/hoe.rb', line 193

def spec_extras
  @spec_extras
end

- (Object) summary

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

See also: Hoe#description and Hoe.paragraphs_of.



201
202
203
# File 'lib/hoe.rb', line 201

def summary
  @summary
end

- (Object) summary_sentences

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



206
207
208
# File 'lib/hoe.rb', line 206

def summary_sentences
  @summary_sentences
end

- (Object) test_globs

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



211
212
213
# File 'lib/hoe.rb', line 211

def test_globs
  @test_globs
end

- (Object) url

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



218
219
220
# File 'lib/hoe.rb', line 218

def url
  @url
end

- (Object) version

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



223
224
225
# File 'lib/hoe.rb', line 223

def version
  @version
end

Class Method Details

+ (Object) add_include_dirs(*dirs)

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



228
229
230
231
232
233
# File 'lib/hoe.rb', line 228

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

+ (Object) load_plugins(plugins = Hoe.plugins)

Find and load all plugin files.

It is called at the end of hoe.rb



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/hoe.rb', line 240

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?

  return @loaded, @found
end

+ (Object) normalize_names(project)

Normalize a project name into the project, file, and klass cases (?!?).

no, I have no idea how to describe this. Does the thing with the stuff.



268
269
270
271
272
273
274
# File 'lib/hoe.rb', line 268

def self.normalize_names project # :nodoc:
  project   = project.tr('-', '_').gsub(/([A-Z])/, '_\1').downcase.sub(/^_/, '')
  klass     = project.gsub(/(?:^|_)([a-z])/) { $1.upcase }
  file_name = project

  return project, file_name, klass
end

+ (Object) plugin(*plugins)

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.



284
285
286
287
# File 'lib/hoe.rb', line 284

def self.plugin *plugins
  self.plugins.concat plugins
  self.plugins.uniq!
end

+ (Object) plugins

The list of active plugins.



292
293
294
# File 'lib/hoe.rb', line 292

def self.plugins
  @@plugins
end

+ (Object) spec(name, &block)

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


305
306
307
308
309
310
311
312
313
# File 'lib/hoe.rb', line 305

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

Instance Method Details

- (Object) activate_plugins

Activate plugin modules and add them to the current instance.



318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# File 'lib/hoe.rb', line 318

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

  Hoe.plugins.each do |plugin|
    msg = "initialize_#{plugin}"
    warn msg if $DEBUG
    send msg if self.respond_to? msg
  end
end

- (Object) add_dependencies

Add standard and user defined dependencies to the spec.



362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/hoe.rb', line 362

def add_dependencies
  self.extra_deps     = normalize_deps extra_deps
  self.extra_dev_deps = normalize_deps extra_dev_deps

  case name
  when 'hoe' then
    extra_deps << ['rake', ">= #{RAKEVERSION}"]
  when 'rubyforge', 'rake', 'gemcutter' then
    # avoid circular dependencies for hoe's (potentially) hoe'd dependencies
  else
    extra_dev_deps << ['hoe', ">= #{VERSION}"]
  end
end

- (Object) define_spec

Define the Gem::Specification.



386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
# File 'lib/hoe.rb', line 386

def define_spec
  self.spec = Gem::Specification.new do |s|
    dirs = Dir['lib']

    s.name                 = name
    s.version              = version if version
    s.summary              = summary
    s.email                = email
    s.homepage             = Array(url).first
    s.rubyforge_project    = rubyforge_name
    s.description          = description
    s.files = files        = File.read_utf("Manifest.txt").split(/\r?\n\r?/)
    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]

    missing "Manifest.txt" if files.empty?

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

    extra_deps.each do |dep|
      s.add_dependency(*dep)
    end

    extra_dev_deps.each do |dep|
      s.add_development_dependency(*dep)
    end

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

  end

  unless self.version then
    version    = nil
    version_re = /VERSION += +([\"\'])([\d][\w\.]+)\1/

    spec.files.each do |file|
      next unless File.exist? file
      version = File.read_utf(file)[version_re, 2]
      break if version
    end

    spec.version = self.version = version if version

    unless self.version then
      spec.version = self.version = "0.borked"
      warn "** Add 'VERSION = \"x.y.z\"' to your code,"
      warn "   add a version to your hoe spec,"
      warn "   or fix your Manifest.txt"
    end
  end

  # Do any extra stuff the user wants
  spec_extras.each do |msg, val|
    case val
    when Proc
      val.call spec.send(msg)
    else
      spec.send "#{msg}=", val
    end
  end
end

- (Object) dependency(name, version, type = :runtime)

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



347
348
349
350
351
352
353
354
355
356
357
# File 'lib/hoe.rb', line 347

def dependency name, version, type = :runtime
  ary = case type
        when :runtime then
          extra_deps
        when :dev, :development, :developer then
          extra_dev_deps
        else
          raise "Unknown dependency type: #{type}"
        end
  ary << [name, version]
end

- (Object) dependency_target

Returns the proper dependency list for the thingy.



379
380
381
# File 'lib/hoe.rb', line 379

def dependency_target
  self.name == 'hoe' ? extra_deps : extra_dev_deps
end

- (Object) developer(name, email)

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



462
463
464
465
# File 'lib/hoe.rb', line 462

def developer name, email
  self.author << name
  self.email  << email
end

- (Object) intuit_values

Intuit values from the readme and history files.



507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
# File 'lib/hoe.rb', line 507

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(". ")

    self.description ||= desc
    self.summary     ||= summ
    self.url         ||= readme[1].gsub(/^\* /, '').split(/\n/).grep(/\S+/)
  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

- (Object) load_plugin_tasks

Load activated plugins by calling their define tasks method.



537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
# File 'lib/hoe.rb', line 537

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 => e
      warn "warning: couldn't activate the #{plugin} plugin, skipping" if
        Rake.application.options.trace or $DEBUG

      bad << plugin
      next
    end

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

- (Object) missing(name)

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



567
568
569
570
# File 'lib/hoe.rb', line 567

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

- (Object) normalize_deps(deps)

Normalize the dependencies.



575
576
577
578
579
580
581
582
583
584
# File 'lib/hoe.rb', line 575

def normalize_deps deps
  Array(deps).map { |o|
    if String === o then
      warn "WAR\NING: HOE DEPRECATION: Add '>= 0' to the '#{o}' dependency."
      [o, ">= 0"]
    else
      o
    end
  }
end

- (Object) paragraphs_of(path, *paragraphs)

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)


592
593
594
# File 'lib/hoe.rb', line 592

def paragraphs_of path, *paragraphs
  File.read_utf(path).delete("\r").split(/\n\n+/).values_at(*paragraphs)
end

- (Object) pluggable!

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.



602
603
604
605
# File 'lib/hoe.rb', line 602

def pluggable!
  abort "update rubygems to >= 1.3.1" unless  Gem.respond_to? :find_files
  require_rubygems_version '>= 1.3.1'
end

- (Boolean) plugin?(name)

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

Returns:

  • (Boolean)


617
618
619
# File 'lib/hoe.rb', line 617

def plugin? name
  self.class.plugins.include? name
end

- (Object) post_initialize

Finalize configuration



624
625
626
627
628
629
630
# File 'lib/hoe.rb', line 624

def post_initialize
  intuit_values
  validate_fields
  add_dependencies
  define_spec
  load_plugin_tasks
end

- (Object) require_ruby_version(version)

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



642
643
644
# File 'lib/hoe.rb', line 642

def require_ruby_version version
  spec_extras[:required_ruby_version] = version
end

- (Object) require_rubygems_version(version)

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



635
636
637
# File 'lib/hoe.rb', line 635

def require_rubygems_version version
  spec_extras[:required_rubygems_version] = version
end

- (Object) timebomb(n, m, finis = '2010-04-01', start = '2009-03-14')

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



649
650
651
652
653
654
655
656
657
# File 'lib/hoe.rb', line 649

def timebomb n, m, finis = '2010-04-01', start = '2009-03-14'
  require 'time'
  finis = Time.parse finis
  start = Time.parse start
  rest  = (finis - Time.now)
  full  = (finis - start)

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

- (Object) validate_fields

Verify that mandatory fields are set.



662
663
664
665
666
667
# File 'lib/hoe.rb', line 662

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

- (Object) with_config {|config, rc| ... }

Loads ~/.hoerc and yields the configuration and its path

Yields:

  • (config, rc)


672
673
674
675
676
677
# File 'lib/hoe.rb', line 672

def with_config
  rc = File.expand_path("~/.hoerc")
  exists = File.exist? rc
  config = exists ? YAML.load_file(rc) : {}
  yield(config, rc)
end