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
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
Optional: A description of the release's latest changes. Auto-populates to the top entry of History.txt.
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.
Optional: What sections from the readme to use for auto-description. Defaults to %w(description).
MANDATORY: The author's email address(es). (can be array)
Use the developer method to fill in both author and email cleanly.
Optional: An array of rubygem dependencies.
extra_deps << ['blah', '~> 1.0']
Optional: An array of rubygem developer dependencies.
Optional: Extra files you want to add to RDoc.
.txt files are automatically included (excluding the obvious).
Optional: The filename for the project history. [default: History.txt]
MANDATORY: The name of the release.
Set via ::spec.
Optional: A post-install message to be displayed when gem is installed.
Optional: The filename for the project readme. [default: README.txt]
Optional: The name of the rubyforge project. [default: name.downcase]
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)
Optional: A short summary of the project. Auto-populates from the first sentence of the description.
See also: #description and #paragraphs_of.
Optional: Number of sentences from description for summary. Defaults to 1.
Optional: An array of test file patterns [default: test /test_*.rb]
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.
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
MANDATORY: The version. Don't hardcode! use a constant in the project.
Public Class Methods
Add extra dirs to both $: and RUBY_FLAGS (for test runs and rakefile deps)
# File lib/hoe.rb, line 238 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
Find and load all plugin files.
It is called at the end of hoe.rb
# File lib/hoe.rb, line 250 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 return @loaded, @found end
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 307 def self.plugin *plugins self.plugins.concat plugins self.plugins.uniq! end
The list of active plugins.
# File lib/hoe.rb, line 315 def self.plugins @@plugins end
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 328 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 modules and add them to the current instance.
# File lib/hoe.rb, line 341 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
Add standard and user defined dependencies to the spec.
# File lib/hoe.rb, line 397 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" else version = VERSION.split(/\./).first(2).join(".") dependency "hoe", "~> #{version}", :development end end
Define the Gem::Specification.
# File lib/hoe.rb, line 420 def define_spec self.spec = Gem::Specification.new do |s| dirs = Dir['lib'] manifest = File.read_utf("Manifest.txt").split(/\r?\n\r?/) rescue nil 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 = Array(url).first s.rubyforge_project = rubyforge_name 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] missing "Manifest.txt" if s.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
Add a dependency declaration to your spec. Pass :dev to type
for developer dependencies.
# File lib/hoe.rb, line 370 def dependency name, version, type = :runtime raise "Unknown dependency type: #{type}" unless [:runtime, :dev, :development, :developer].include? type # spec has already been defined. A task wants to add a dependency after. if spec then msg = if type == :runtime then :add_dependency else :add_development_dependency end spec.send msg, name, version else ary = if type == :runtime then extra_deps else extra_dev_deps end ary << [name, version] end end
Returns the proper dependency list for the thingy.
# File lib/hoe.rb, line 413 def dependency_target self.name == 'hoe' ? extra_deps : extra_dev_deps end
Convenience method to set add to both the author and email fields.
# File lib/hoe.rb, line 504 def developer name, email self.author << name self.email << email end
Intuit values from the readme and history files.
# File lib/hoe.rb, line 549 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 self.url ||= case urls when Hash then urls["home"] || urls["repo"] || urls.values.first when Array then urls else raise "unknown urls format: #{urls.inspect}" end 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
Load activated plugins by calling their define tasks method.
# File lib/hoe.rb, line 615 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" 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
Bitch about a file that is missing data or unparsable for intuiting values.
# File lib/hoe.rb, line 645 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 the dependencies.
# File lib/hoe.rb, line 653 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
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 670 def paragraphs_of path, *paragraphs File.read_utf(path).delete("\r").split(/\n\n+/).values_at(*paragraphs) end
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 603 def parse_urls text lines = text.gsub(/^\* /, '').split(/\n/).grep(/\S+/) if lines.first =~ /::/ then Hash[lines.map { |line| line.split(/\s*::\s*/) }] else lines end end
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 680 def pluggable! abort "update rubygems to >= 1.3.1" unless Gem.respond_to? :find_files require_rubygems_version '>= 1.3.1' end
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 695 def plugin? name self.class.plugins.include? name end
Finalize configuration
# File lib/hoe.rb, line 702 def post_initialize intuit_values validate_fields add_dependencies define_spec load_plugin_tasks end
Declare that your gem requires a specific ruby version. Last one wins.
# File lib/hoe.rb, line 720 def require_ruby_version version spec_extras[:required_ruby_version] = version end
Declare that your gem requires a specific rubygems version. Last one wins.
# File lib/hoe.rb, line 713 def require_rubygems_version version spec_extras[:required_rubygems_version] = version end
Provide a linear degrading value from n to m over start to finis dates.
# File lib/hoe.rb, line 727 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
Verify that mandatory fields are set.
# File lib/hoe.rb, line 740 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
Loads ~/.hoerc and yields the configuration and its path
# File lib/hoe.rb, line 750 def with_config rc = File.expand_path("~/.hoerc") exists = File.exist? rc config = exists ? YAML.load_file(rc) : {} yield(config, rc) end