lib/hoe.rb in hoe-1.12.2 vs lib/hoe.rb in hoe-2.0.0
- old
+ new
@@ -17,140 +17,63 @@
gem 'rdoc'
rescue Gem::LoadError
end
##
-# hoe - a tool to help rake
+# 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.
#
-# Hoe is a simple rake/rubygems helper for project Rakefiles. It
-# generates all the usual tasks for projects including rdoc generation,
-# testing, packaging, and deployment.
-#
# == Using Hoe
#
# === Basics
#
-# Use this as a minimal starting point:
+# 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:
#
-# require 'hoe'
-# require './lib/project.rb'
+# % sow project_name
+# ...
+# % cd project_name
#
-# Hoe.new("project_name", Project::VERSION) do |p|
-# p.rubyforge_name = "rf_project"
-# p.developer("Joe Blow", "joe@example.com")
-# # add other details here
-# end
+# and have at it.
#
-# # add other tasks here
-#
-# === Tasks Provided:
-#
-# announce:: Create news email file and post to rubyforge.
-# audit:: Run ZenTest against the package.
-# check_extra_deps:: Install missing dependencies.
-# check_manifest:: Verify the manifest.
-# clean:: Clean up all the extras.
-# config_hoe:: Create a fresh ~/.hoerc file.
-# debug_gem:: Show information about the gem.
-# default:: Run the default task(s).
-# deps:email:: Print a contact list for gems dependent on this gem
-# deps:fetch:: Fetch all the dependent gems of this gem into tarballs
-# deps:list:: List all the dependent gems of this gem
-# docs:: Build the docs HTML Files
-# email:: Generate email announcement file.
-# flay:: Analyze for code duplication.
-# flog:: Analyze code complexity.
-# gem:: Build the gem file hoe-1.9.0.gem
-# generate_key:: Generate a key for signing your gems.
-# install_gem:: Install the package as a gem.
-# multi:: Run the test suite using multiruby.
-# package:: Build all the packages
-# post_blog:: Post announcement to blog.
-# post_news:: Post announcement to rubyforge.
-# publish_docs:: Publish RDoc to RubyForge.
-# rcov:: Analyze code coverage with tests
-# release:: Package and upload the release to rubyforge.
-# ridocs:: Generate ri locally for testing.
-# tasks:: Generate a list of tasks for doco.
-# test:: Run the test suite.
-# test_deps:: Show which test files fail when run alone.
-#
# === Extra Configuration Options:
#
-# Run +config_hoe+ to generate a new ~/.hoerc file. The file is a
-# YAML formatted config file with the following settings:
+# Hoe maintains a config file for cross-project values. The file is
+# located at <tt>~/.hoerc</tt>. 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+.
-# publish_on_announce:: Run +publish_docs+ when you run +release+.
-# signing_key_file:: Signs your gems with this private key.
-# signing_cert_file:: Signs your gem with this certificate.
-# blogs:: An array of hashes of blog settings.
+# exclude:: A regular expression of files to exclude from +check_manifest+.
#
-# Run +config_hoe+ and see ~/.hoerc for examples.
+# Run <tt>`rake config_hoe`</tt> and see ~/.hoerc for examples.
#
-# === Signing Gems:
+# == Extending Hoe
#
-# Run the 'generate_key' task. This will:
+# Hoe can be extended via its plugin system. Hoe searches out all
+# installed files matching <tt>'hoe/*.rb'</tt> 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
#
-# 1. Configure your ~/.hoerc.
-# 2. Generate a signing key and certificate.
-# 3. Install the private key and public certificate files into ~/.gem.
-# 4. Upload the certificate to RubyForge.
+# module Hoe::Blah
+# def initialize_blah # optional
+# # ...
+# end
#
-# Hoe will now generate signed gems when the package task is run. If you have
-# multiple machines you build gems on, be sure to install your key and
-# certificate on each machine.
-#
-# Keep your private key secret! Keep your private key safe!
-#
-# To make sure your gems are signed run:
-#
-# rake package; tar tf pkg/yourproject-1.2.3.gem
-#
-# If your gem is signed you will see:
-#
-# data.tar.gz
-# data.tar.gz.sig
-# metadata.gz
-# metadata.gz.sig
-#
-# === Platform awareness
-#
-# Hoe allows bundling of pre-compiled extensions in the +package+ task.
-#
-# To create a package for your current platform:
-#
-# rake package INLINE=1
-#
-# This will force Hoe analize your +Inline+ already compiled
-# extensions and include them in your gem.
-#
-# If somehow you need to force a specific platform:
-#
-# rake package INLINE=1 FORCE_PLATFORM=mswin32
-#
-# This will set the +Gem::Specification+ platform to the one indicated in
-# +FORCE_PLATFORM+ (instead of default Gem::Platform::CURRENT)
-#
+# def define_blah_tasks
+# # ...
+# end
+# end
class Hoe
- VERSION = '1.12.2'
- GEMURL = URI.parse 'http://gems.rubyforge.org' # for namespace :deps below
+ # duh
+ VERSION = '2.0.0'
- ruby_prefix = Config::CONFIG['prefix']
- sitelibdir = Config::CONFIG['sitelibdir']
-
##
- # Configuration for the supported test frameworks for test task.
-
- SUPPORTED_TEST_FRAMEWORKS = {
- :testunit => "test/unit",
- :minitest => "minitest/autorun",
- }
-
- ##
# Used to add extra flags to RUBY_FLAGS.
RUBY_DEBUG = ENV['RUBY_DEBUG']
default_ruby_flags = "-w -I#{%w(lib ext bin test).join(File::PATH_SEPARATOR)}" +
@@ -160,66 +83,40 @@
# Used to specify flags to ruby [has smart default].
RUBY_FLAGS = ENV['RUBY_FLAGS'] || default_ruby_flags
##
- # Used to add flags to test_unit (e.g., -n test_borked).
- #
- # eg FILTER="-n test_blah"
+ # Default configuration values for .hoerc. Plugins should populate
+ # this on load.
- FILTER = ENV['FILTER'] || ENV['TESTOPTS']
+ DEFAULT_CONFIG = {
+ "exclude" => /tmp$|CVS|\.svn|\.log$/,
+ }
- # :stopdoc:
+ ##
+ # True if you're a masochist developer. Used for building commands.
- DLEXT = Config::CONFIG['DLEXT']
-
WINDOZE = /mswin|mingw/ =~ RUBY_PLATFORM unless defined? WINDOZE
- DIFF = if WINDOZE
- 'diff.exe'
- else
- if system("gdiff", __FILE__, __FILE__)
- 'gdiff' # solaris and kin suck
- else
- 'diff'
- end
- end unless defined? DIFF
-
- # :startdoc:
-
##
# *MANDATORY*: The author(s) of the package. (can be array)
attr_accessor :author
##
- # Populated automatically from the manifest. List of executables.
-
- attr_accessor :bin_files # :nodoc:
-
- ##
- # *Optional*: An array of the project's blog categories. Defaults to project name.
-
- attr_accessor :blog_categories
-
- ##
# Optional: A description of the release's latest changes. Auto-populates.
attr_accessor :changes
##
- # Optional: An array of file patterns to delete on clean.
-
- attr_accessor :clean_globs
-
- ##
# Optional: A description of the project. Auto-populates.
attr_accessor :description
##
- # Optional: What sections from the readme to use for auto-description. Defaults to %w(description).
+ # Optional: What sections from the readme to use for
+ # auto-description. Defaults to %w(description).
attr_accessor :description_sections
##
# *MANDATORY*: The author's email address(es). (can be array)
@@ -242,80 +139,30 @@
# .txt files are automatically included (excluding the obvious).
attr_accessor :extra_rdoc_files
##
- # Optional: flay threshold to determine threshold failure. [default: 1200-100]
-
- attr_accessor :flay_threshold
-
- ##
- # Optional: flog threshold to determine threshold failure. [default: 1500-200]
-
- attr_accessor :flog_threshold
-
- ##
# Optional: The filename for the project history. [default: History.txt]
attr_accessor :history_file
##
- # Populated automatically from the manifest. List of library files.
-
- attr_accessor :lib_files # :nodoc:
-
- ##
- # Optional: Array of incompatible versions for multiruby filtering. Used as a regex.
-
- attr_accessor :multiruby_skip
-
- ##
# *MANDATORY*: The name of the release.
attr_accessor :name
##
- # Optional: Should package create a tarball? [default: true]
-
- attr_accessor :need_tar
-
- ##
- # Optional: Should package create a zipfile? [default: false]
-
- attr_accessor :need_zip
-
- ##
# Optional: A post-install message to be displayed when gem is installed.
attr_accessor :post_install_message
##
# Optional: The filename for the project readme. [default: README.txt]
attr_accessor :readme_file
##
- # Optional: Name of RDoc destination directory on Rubyforge. [default: +name+]
-
- attr_accessor :remote_rdoc_dir
-
- ##
- # Optional: RSpec dirs. [default: %w(spec lib)]
-
- attr_accessor :rspec_dirs
-
- ##
- # Optional: RSpec options. [default: []]
-
- attr_accessor :rspec_options
-
- ##
- # Optional: Flags for RDoc rsync. [default: "-av --delete"]
-
- attr_accessor :rsync_args
-
- ##
# Optional: The name of the rubyforge project. [default: name.downcase]
attr_accessor :rubyforge_name
##
@@ -337,144 +184,111 @@
# Optional: Number of sentences from description for summary. Defaults to 1.
attr_accessor :summary_sentences
##
- # Populated automatically from the manifest. List of tests.
-
- attr_accessor :test_files # :nodoc:
-
- ##
# Optional: An array of test file patterns [default: test/**/test_*.rb]
attr_accessor :test_globs
##
- # Optional: What test library to require [default: :testunit]
-
- attr_accessor :testlib
-
- ##
# Optional: The url(s) of the project. (can be array). Auto-populates.
attr_accessor :url
##
# *MANDATORY*: The version. Don't hardcode! use a constant in the project.
attr_accessor :version
##
- # Add extra dirs to both $: and RUBY_FLAGS (for test runs)
+ # Add extra dirs to both $: and RUBY_FLAGS (for test runs and rakefile deps)
def self.add_include_dirs(*dirs)
dirs = dirs.flatten
$:.unshift(*dirs)
s = File::PATH_SEPARATOR
- Hoe::RUBY_FLAGS.sub!(/-I/, "-I#{dirs.join(s)}#{s}")
+ RUBY_FLAGS.sub!(/-I/, "-I#{dirs.join(s)}#{s}")
end
- def self.normalize_names project
+ ##
+ # Find and load all plugin files.
+ #
+ # It is called at the end of hoe.rb
+
+ def self.load_plugins
+ loaded = {}
+
+ Gem.find_files("hoe/*.rb").each do |plugin|
+ name = File.basename plugin
+ next if loaded[name]
+ begin
+ load plugin
+ loaded[name] = true
+ rescue LoadError => e
+ warn "error loading #{plugin.inspect}: #{e.message}. skipping..."
+ end
+ end
+ end
+
+ ##
+ # 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.
+
+ 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
- def normalize_deps deps
- Array(deps).map { |o| String === o ? [o] : o }
- end
+ ##
+ # Activate a plugin.
- 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"
+ def self.plugin name
+ self.plugins << name
end
- def timebomb n, m, finis = '2010-04-01', start = '2009-03-14'
- finis = Time.parse finis
- start = Time.parse start
- rest = (finis - Time.now)
- full = (finis - start)
+ ##
+ # Return the list of activated plugins.
- ((n - m) * rest / full).to_i + m
+ def self.plugins
+ @@plugins ||= []
end
- def initialize(name, version) # :nodoc:
- self.name = name
- self.version = version
+ ##
+ # Create a new hoe-specification executing the supplied block
- # Defaults
- self.author = []
- self.blog_categories = [name]
- self.clean_globs = %w(diff diff.txt email.txt ri deps .source_index
- *.gem **/*~ **/.*~ **/*.rbc coverage*)
- self.description_sections = %w(description)
- self.email = []
- self.extra_deps = []
- self.extra_dev_deps = []
- self.extra_rdoc_files = []
- self.flay_threshold = timebomb 1200, 100 # 80% of average :(
- self.flog_threshold = timebomb 1500, 1000 # 80% of average :(
- self.history_file = "History.txt"
- self.multiruby_skip = []
- self.need_tar = true
- self.need_zip = false
- self.post_install_message = nil
- self.readme_file = "README.txt"
- self.remote_rdoc_dir = name
- self.rspec_dirs = %w(spec lib)
- self.rspec_options = []
- self.rsync_args = '-av --delete'
- self.rubyforge_name = name.downcase
- self.spec_extras = {}
- self.summary_sentences = 1
- self.test_globs = ['test/**/test_*.rb']
- self.testlib = :testunit
+ def self.spec name, &block
+ spec = self.new name
+ spec.activate_plugins
+ spec.instance_eval(&block)
+ spec.post_initialize
+ spec # TODO: remove?
+ end
- yield self if block_given?
+ ##
+ # Activate plugin modules and add them to the current instance.
- # Intuit values:
-
- readme = File.read(readme_file).split(/^(=+ .*)$/)[1..-1] rescue ''
- unless readme.empty? then
- sections = readme.map { |s|
- s =~ /^=/ ? s.strip.downcase.chomp(':').split.last : s.strip
- }
- sections = Hash[*sections]
- 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
+ def activate_plugins
+ self.class.constants.reject { |n| n =~ /^[A-Z_]+$/ }.each do |name|
+ self.extend Hoe.const_get(name)
end
- self.changes ||= begin
- h = File.read(history_file)
- h.split(/^(===.*)/)[1..2].join.strip
- rescue
- missing history_file
- ''
- end
-
- %w(email author).each do |field|
- value = self.send(field)
- if value.nil? or value.empty? then
- if Time.now < Time.local(2008, 4, 1) then
- warn "Hoe #{field} value not set - Fix by 2008-04-01!"
- self.send "#{field}=", "doofus"
- else
- abort "Hoe #{field} value not set. aborting"
- end
- end
+ self.class.plugins.each do |plugin|
+ send "initialize_#{plugin}" rescue nil
end
+ end
+ ##
+ # Add standard and user defined dependencies to the spec.
+
+ def add_dependencies
hoe_deps = {
- 'rake' => ">= #{RAKEVERSION}",
+ 'rake' => ">= #{RAKEVERSION}",
'rubyforge' => ">= #{::RubyForge::VERSION}",
}
self.extra_deps = normalize_deps extra_deps
self.extra_dev_deps = normalize_deps extra_dev_deps
@@ -484,218 +298,54 @@
extra_deps << [pkg, vers]
end
else
extra_dev_deps << ['hoe', ">= #{VERSION}"] unless hoe_deps.has_key? name
end
-
- define_tasks
end
- def developer name, email
- self.author << name
- self.email << email
- end
+ ##
+ # Define the Gem::Specification.
- def with_config # :nodoc:
- rc = File.expand_path("~/.hoerc")
- exists = File.exist? rc
- config = exists ? YAML.load_file(rc) : {}
- yield(config, rc)
- end
+ def define_spec
+ self.spec = Gem::Specification.new do |s|
+ dirs = Dir['{lib,ext}']
- def define_tasks # :nodoc:
- default_tasks = []
+ 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 = File.read("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.has_rdoc = true
+ s.post_install_message = post_install_message
+ s.test_files = Dir[*self.test_globs]
- if File.directory? "test" then
- desc 'Run the test suite. Use FILTER or TESTOPTS to add flags/args.'
- task :test do
- ruby make_test_cmd
- end
-
- desc 'Run the test suite using multiruby.'
- task :multi do
- ruby make_test_cmd(:multi)
- end
-
- desc 'Show which test files fail when run alone.'
- task :test_deps do
- tests = Dir["test/**/test_*.rb"] + Dir["test/**/*_test.rb"]
-
- paths = ['bin', 'lib', 'test'].join(File::PATH_SEPARATOR)
- null_dev = WINDOZE ? '> NUL 2>&1' : '&> /dev/null'
-
- tests.each do |test|
- if not system "ruby -I#{paths} #{test} #{null_dev}" then
- puts "Dependency Issues: #{test}"
- end
- end
- end
-
- default_tasks << :test
- end
-
- if File.directory? "spec" then
- begin
- require 'spec/rake/spectask'
-
- desc "Run all specifications"
- Spec::Rake::SpecTask.new(:spec) do |t|
- t.libs = self.rspec_dirs
- t.spec_opts = self.rspec_options
- end
- rescue LoadError
- # do nothing
- end
- default_tasks << :spec
- end
-
- desc 'Run the default task(s).'
- task :default => default_tasks
-
- begin # take a whack at defining rcov tasks
- require 'rcov/rcovtask'
-
- Rcov::RcovTask.new do |t|
- pattern = ENV['PATTERN'] || 'test/test_*.rb'
-
- t.test_files = FileList[pattern]
- t.verbose = true
- t.rcov_opts << "--no-color"
- t.rcov_opts << "--save coverage.info"
- t.rcov_opts << "-x ^/"
- end
-
- # this is for my emacs rcov overlay stuff on emacswiki.
- task :rcov_overlay do
- path = ENV["FILE"]
- rcov, eol = Marshal.load(File.read("coverage.info")).last[path], 1
- puts rcov[:lines].zip(rcov[:coverage]).map { |line, coverage|
- bol, eol = eol, eol + line.length
- [bol, eol, "#ffcccc"] unless coverage
- }.compact.inspect
- end
- rescue LoadError
- # skip
- end
-
- begin
- require 'flay'
- require 'flay_task'
- FlayTask.new :flay, self.flay_threshold
- rescue LoadError
- # skip
- end
-
- begin
- require 'flog'
- require 'flog_task'
- FlogTask.new :flog, self.flog_threshold
- rescue LoadError
- # skip
- end
-
- ############################################################
- # Packaging and Installing
-
- signing_key = nil
- cert_chain = []
-
- with_config do |config, path|
- break unless config['signing_key_file'] and config['signing_cert_file']
- key_file = File.expand_path config['signing_key_file'].to_s
- signing_key = key_file if File.exist? key_file
-
- cert_file = File.expand_path config['signing_cert_file'].to_s
- cert_chain << cert_file if File.exist? cert_file
- end
-
- self.spec = Gem::Specification.new do |s|
- s.name = name
- s.version = version
- s.summary = summary
case author
when Array
s.authors = author
else
- s.author = author
+ s.author = author
end
- s.email = email
- s.homepage = Array(url).first
- s.rubyforge_project = rubyforge_name
- s.description = description
-
extra_deps.each do |dep|
s.add_dependency(*dep)
end
extra_dev_deps.each do |dep|
s.add_development_dependency(*dep)
end
- s.files = File.read("Manifest.txt").delete("\r").split(/\n/)
- s.executables = s.files.grep(/^bin/) { |f| File.basename(f) }
-
- s.bindir = "bin"
- dirs = Dir['{lib,ext}']
- s.require_paths = dirs unless dirs.empty?
-
- s.rdoc_options = ['--main', readme_file]
-
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
- s.has_rdoc = true
- s.post_install_message = post_install_message
-
- if test ?f, "test/test_all.rb" then
- s.test_file = "test/test_all.rb"
- else
- s.test_files = Dir[*self.test_globs]
- end
-
- if signing_key and cert_chain then
- s.signing_key = signing_key
- s.cert_chain = cert_chain
- end
-
- ############################################################
- # Allow automatic inclusion of compiled extensions
- if ENV['INLINE'] then
- s.platform = ENV['FORCE_PLATFORM'] || Gem::Platform::CURRENT
-
- # Try collecting Inline extensions for +name+
- if defined?(Inline) then
- directory 'lib/inline'
-
- Inline.registered_inline_classes.each do |cls|
- name = cls.name # TODO: what about X::Y::Z?
- # name of the extension is CamelCase
- alternate_name = if name =~ /[A-Z]/ then
- name.gsub(/([A-Z])/, '_\1').downcase.sub(/^_/, '')
- elsif name =~ /_/ then
- name.capitalize.gsub(/_([a-z])/) { $1.upcase }
- end
-
- extensions = Dir.chdir(Inline::directory) {
- Dir["Inline_{#{name},#{alternate_name}}_*.#{DLEXT}"]
- }
-
- extensions.each do |ext|
- # add the inlined extension to the spec files
- s.files += ["lib/inline/#{ext}"]
-
- # include the file in the tasks
- file "lib/inline/#{ext}" => ["lib/inline"] do
- cp File.join(Inline::directory, ext), "lib/inline"
- end
- end
- end
- end
- end
-
# Do any extra stuff the user wants
spec_extras.each do |msg, val|
case val
when Proc
val.call(s.send(msg))
@@ -703,527 +353,180 @@
s.send "#{msg}=", val
end
end
end
- desc 'Show information about the gem.'
- task :debug_gem do
- puts spec.to_ruby
- end
+ unless self.version then
+ version = nil
- self.lib_files = spec.files.grep(/^(lib|ext)/)
- self.bin_files = spec.files.grep(/^bin/)
- self.test_files = spec.files.grep(/^test/)
-
- Rake::GemPackageTask.new spec do |pkg|
- pkg.need_tar = @need_tar
- pkg.need_zip = @need_zip
- end
-
- desc 'Install the package as a gem.'
- task :install_gem => [:clean, :package, :check_extra_deps] do
- install_gem Dir['pkg/*.gem'].first
- end
-
- desc 'Package and upload the release to rubyforge.'
- task :release => [:clean, :package] do |t|
- v = ENV["VERSION"] or abort "Must supply VERSION=x.y.z"
- abort "Versions don't match #{v} vs #{version}" if v != version
- pkg = "pkg/#{name}-#{version}"
-
- if $DEBUG then
- puts "release_id = rf.add_release #{rubyforge_name.inspect}, #{name.inspect}, #{version.inspect}, \"#{pkg}.tgz\""
- puts "rf.add_file #{rubyforge_name.inspect}, #{name.inspect}, release_id, \"#{pkg}.gem\""
+ spec.files.each do |file|
+ next unless File.exist? file
+ version = File.read(file)[/VERSION = ([\"\'])([\d\.]+)\1/, 2]
+ break if version
end
- rf = RubyForge.new.configure
- puts "Logging in"
- rf.login
-
- c = rf.userconfig
- c["release_notes"] = description if description
- c["release_changes"] = changes if changes
- c["preformatted"] = true
-
- files = [(@need_tar ? "#{pkg}.tgz" : nil),
- (@need_zip ? "#{pkg}.zip" : nil),
- "#{pkg}.gem"].compact
-
- puts "Releasing #{name} v. #{version}"
- rf.add_release rubyforge_name, name, version, *files
+ spec.version = self.version = version
end
- ############################################################
- # Doco
+ raise "Need version" unless self.version
+ end
- Rake::RDocTask.new(:docs) do |rd|
- rd.main = readme_file
- rd.options << '-d' if (`which dot` =~ /\/dot/) unless
- ENV['NODOT'] || WINDOZE
- rd.rdoc_dir = 'doc'
+ ##
+ # Convenience method to set add to both the author and email fields.
- rd.rdoc_files += spec.require_paths
- rd.rdoc_files += spec.extra_rdoc_files
+ def developer name, email
+ self.author << name
+ self.email << email
+ end
- title = "#{name}-#{version} Documentation"
- title = "#{rubyforge_name}'s " + title if rubyforge_name != name
+ ##
+ # 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.
- rd.options << "-t" << title
- end
+ def initialize name, version = nil # :nodoc:
+ self.name = name
+ self.version = version
- desc 'Generate ri locally for testing.'
- task :ridocs => :clean do
- sh %q{ rdoc --ri -o ri . }
- end
+ 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
- desc 'Publish RDoc to RubyForge.'
- task :publish_docs => [:clean, :docs] do
- config = YAML.load(File.read(File.expand_path("~/.rubyforge/user-config.yml")))
- host = "#{config["username"]}@rubyforge.org"
-
- remote_dir = "/var/www/gforge-projects/#{rubyforge_name}/#{remote_rdoc_dir}"
- local_dir = 'doc'
-
- sh %{rsync #{rsync_args} #{local_dir}/ #{host}:#{remote_dir}}
+ if block_given? then
+ warn "Hoe.new {...} deprecated. Switch to Hoe.spec."
+ self.activate_plugins
+ yield self
+ post_initialize
end
+ end
- # no doco for this one
- task :publish_on_announce do
- with_config do |config, _|
- Rake::Task['publish_docs'].invoke if config["publish_on_announce"]
- end
- end
+ ##
+ # Intuit values from the readme and history files.
- ############################################################
- # Dependencies:
+ def intuit_values
+ readme = File.read(readme_file).split(/^(=+ .*)$/)[1..-1] rescue ''
+ unless readme.empty? then
+ sections = readme.map { |s|
+ s =~ /^=/ ? s.strip.downcase.chomp(':').split.last : s.strip
+ }
+ sections = Hash[*sections]
+ desc = sections.values_at(*description_sections).join("\n\n")
+ summ = desc.split(/\.\s+/).first(summary_sentences).join(". ")
- namespace :deps do
- require 'zlib' # HACK for rubygems 1.3.0
- require 'rubygems/remote_fetcher'
-
- @@index = nil
-
- def self.get_source_index
- return @@index if @@index
-
- dump = unless File.exist? '.source_index' then
- url = GEMURL + "Marshal.#{Gem.marshal_version}.Z"
- dump = Gem::RemoteFetcher.fetcher.fetch_path url
- dump = Gem.inflate dump
- open '.source_index', 'wb' do |io| io.write dump end
- dump
- else
- open '.source_index', 'rb' do |io| io.read end
- end
-
- @@index = Marshal.load dump
- end
-
- def self.get_latest_gems
- @@cache ||= get_source_index.latest_specs
- end
-
- def self.get_gems_by_name
- @@by_name ||= Hash[*get_latest_gems.map { |gem|
- [gem.name, gem, gem.full_name, gem]
- }.flatten]
- end
-
- def self.dependent_upon name
- get_latest_gems.find_all { |gem|
- gem.dependencies.any? { |dep| dep.name == name }
- }
- end
-
-
- desc "List all the dependent gems of this gem"
- task :list do
- gems = self.get_gems_by_name
- gem = gems[self.name]
-
- abort "Couldn't find gem: #{self.name}" unless gem
-
- deps = self.dependent_upon self.name
- max = deps.map { |s| s.full_name.size }.max
-
- puts " dependents:"
- unless deps.empty? then
- deps.sort_by { |spec| spec.full_name }.each do |spec|
- vers = spec.dependencies.find {|s| s.name == name }.requirement_list
- puts " %-*s - %s" % [max, spec.full_name, vers.join(", ")]
- end
- else
- puts " none"
- end
- end
-
- desc "Print a contact list for gems dependent on this gem"
- task :email do
- gems = self.get_gems_by_name
- gem = gems[self.name]
-
- abort "Couldn't find gem: #{self.name}" unless gem
-
- deps = self.dependent_upon self.name
-
- email = deps.map { |s| s.email }.flatten.sort.uniq
- email = email.map { |s| s.split(/,\s*/) }.flatten.sort.uniq
-
- email.map! { |s| # don't you people realize how easy this is?
- s.gsub(/ at | _at_ |\s*(atmark|@nospam@|-at?-|@at?@|<at?>|\[at?\]|\(at?\))\s*/i, '@').gsub(/\s*(dot|\[d(ot)?\]|\.dot\.)\s*/i, '.').gsub(/\s+com$/, '.com')
- }
-
- bad, good = email.partition { |e| e !~ /^[\w.+-]+\@[\w.+-]+$/ }
-
- warn "Rejecting #{bad.size} email. I couldn't unmunge them." unless
- bad.empty?
-
- puts good.join(", ")
- end
-
- desc "Fetch all the dependent gems of this gem into tarballs"
- task :fetch do
- gems = self.get_gems_by_name
- gem = gems[self.name]
- deps = self.dependent_upon self.name
-
- mkdir "deps" unless File.directory? "deps"
- Dir.chdir "deps" do
- begin
- deps.sort_by { |spec| spec.full_name }.each do |spec|
- full_name = spec.full_name
- tgz_name = "#{full_name}.tgz"
- gem_name = "#{full_name}.gem"
-
- next if File.exist? tgz_name
- FileUtils.rm_rf [full_name, gem_name]
-
- begin
- warn "downloading #{full_name}"
- Gem::RemoteFetcher.fetcher.download(spec, GEMURL, Dir.pwd)
- FileUtils.mv "cache/#{gem_name}", '.'
- rescue Gem::RemoteFetcher::FetchError
- warn " failed"
- next
- end
-
- warn "converting #{gem_name} to tarball"
-
- system "gem unpack #{gem_name} 2> /dev/null"
- system "gem spec -l #{gem_name} > #{full_name}/gemspec.rb"
- system "tar zmcf #{tgz_name} #{full_name}"
- FileUtils.rm_rf [full_name, gem_name, "cache"]
- end
- ensure
- FileUtils.rm_rf "cache"
- end
- end
- end
+ self.description ||= desc
+ self.summary ||= summ
+ self.url ||= readme[1].gsub(/^\* /, '').split(/\n/).grep(/\S+/)
+ else
+ missing readme_file
end
- ############################################################
- # Misc/Maintenance:
+ self.changes ||= begin
+ h = File.read(history_file)
+ h.split(/^(===.*)/)[1..2].join.strip
+ rescue
+ missing history_file
+ ''
+ end
+ end
- desc 'Run ZenTest against the package.'
- task :audit do
- libs = %w(lib test ext).join(File::PATH_SEPARATOR)
- sh "zentest -I=#{libs} #{spec.files.grep(/^(lib|test)/).join(' ')}"
- end
+ ##
+ # Load activated plugins by calling their define tasks method.
- task :clobber_rcov # in case rcov didn't load
-
- desc 'Clean up all the extras.'
- task :clean => [ :clobber_docs, :clobber_package, :clobber_rcov ] do
- clean_globs.each do |pattern|
- files = Dir[pattern]
- rm_rf files, :verbose => true unless files.empty?
- end
+ def load_plugin_tasks
+ self.class.plugins.each do |plugin|
+ send "define_#{plugin}_tasks"
end
-
- desc 'Install missing dependencies.'
- task :check_extra_deps do
- # extra_deps = [["rubyforge", ">= 1.0.0"], ["rake", ">= 0.8.1"]]
- extra_deps.each do |dep|
- begin
- gem(*dep)
- rescue Gem::LoadError
- install_gem(*dep)
- end
- end
- end
-
- desc 'Create a fresh ~/.hoerc file.'
- task :config_hoe do
- with_config do |config, path|
- default_config = {
- "exclude" => /tmp$|CVS|\.svn|\.log$/,
- "publish_on_announce" => false,
- "signing_key_file" => "~/.gem/gem-private_key.pem",
- "signing_cert_file" => "~/.gem/gem-public_cert.pem",
- "blogs" => [
- {
- "user" => "user",
- "password" => "password",
- "url" => "url",
- "blog_id" => "blog_id",
- "extra_headers" => {
- "mt_convert_breaks" => "markdown"
- },
- }
- ],
- }
- File.open(path, "w") do |f|
- YAML.dump(default_config.merge(config), f)
- end
-
- editor = ENV['EDITOR'] || 'vi'
- system "#{editor} #{path}" if ENV['SHOW_EDITOR'] != 'no'
- end
- end
-
- desc 'Generate email announcement file.'
- task :email do
- require 'rubyforge'
- subject, title, body, urls = announcement
-
- File.open("email.txt", "w") do |mail|
- mail.puts "Subject: [ANN] #{subject}"
- mail.puts
- mail.puts title
- mail.puts
- mail.puts urls
- mail.puts
- mail.puts body
- mail.puts
- mail.puts urls
- end
- puts "Created email.txt"
- end
-
- desc 'Post announcement to blog.'
- task :post_blog do
- require 'xmlrpc/client'
-
- with_config do |config, path|
- break unless config['blogs']
-
- subject, title, body, urls = announcement
- body += "\n\n#{urls}"
-
- config['blogs'].each do |site|
- server = XMLRPC::Client.new2(site['url'])
- content = site['extra_headers'].merge(:title => title,
- :description => body,
- :categories => blog_categories)
-
- result = server.call('metaWeblog.newPost',
- site['blog_id'],
- site['user'],
- site['password'],
- content,
- true)
- end
- end
- end
-
- desc 'Post announcement to rubyforge.'
- task :post_news do
- require 'rubyforge'
- subject, title, body, urls = announcement
-
- rf = RubyForge.new.configure
- rf.login
- rf.post_news(rubyforge_name, subject, "#{title}\n\n#{body}")
- puts "Posted to rubyforge"
- end
-
- desc 'Create news email file and post to rubyforge.'
- task :announce => [:email, :post_news, :post_blog, :publish_on_announce ]
-
- desc 'Verify the manifest.'
- task :check_manifest => :clean do
- f = "Manifest.tmp"
- require 'find'
- files = []
- with_config do |config, _|
- exclusions = config["exclude"]
- abort "exclude entry missing from .hoerc. Aborting." if exclusions.nil?
- Find.find '.' do |path|
- next unless File.file? path
- next if path =~ exclusions
- files << path[2..-1]
- end
- files = files.sort.join "\n"
- File.open f, 'w' do |fp| fp.puts files end
- system "#{DIFF} -du Manifest.txt #{f}"
- rm f
- end
- end
-
- desc 'Generate a key for signing your gems.'
- task :generate_key do
- email = Array(spec.email)
- abort "No email in your gemspec" if email.nil? or email.empty?
-
- key_file = with_config { |config, _| config['signing_key_file'] }
- cert_file = with_config { |config, _| config['signing_cert_file'] }
-
- if key_file.nil? or cert_file.nil? then
- ENV['SHOW_EDITOR'] ||= 'no'
- Rake::Task['config_hoe'].invoke
-
- key_file = with_config { |config, _| config['signing_key_file'] }
- cert_file = with_config { |config, _| config['signing_cert_file'] }
- end
-
- key_file = File.expand_path key_file
- cert_file = File.expand_path cert_file
-
- unless File.exist? key_file or File.exist? cert_file then
- warn "NOTICE: using #{email.first} for certificate" if email.size > 1
-
- sh "gem cert --build #{email.first}"
- mv "gem-private_key.pem", key_file, :verbose => true
- mv "gem-public_cert.pem", cert_file, :verbose => true
-
- puts "Installed key and certificate."
-
- rf = RubyForge.new.configure
- rf.login
-
- cert_package = "#{rubyforge_name}-certificates"
-
- begin
- rf.lookup 'package', cert_package
- rescue
- rf.create_package rubyforge_name, cert_package
- end
-
- begin
- rf.lookup('release', cert_package)['certificates']
- rf.add_file rubyforge_name, cert_package, 'certificates', cert_file
- rescue
- rf.create_package rubyforge_name, cert_package
- rf.add_release rubyforge_name, cert_package, 'certificates', cert_file
- end
-
- puts "Uploaded certificate to release \"certificates\" in package #{cert_package}"
- else
- puts "Keys already exist: #{key_file} and #{cert_file}"
- end
- end
- end # end define
-
- def install_gem name, version = nil
- gem_cmd = Gem.default_exec_format % 'gem'
- sudo = 'sudo ' unless WINDOZE
- local = '--local' unless version
- version = "--version '#{version}'" if version
- sh "#{sudo}#{gem_cmd} install #{local} #{name} #{version}"
end
- def make_test_cmd multi = false # :nodoc:
- framework = SUPPORTED_TEST_FRAMEWORKS[testlib]
- raise "unsupported test framework #{testlib}" unless framework
+ ##
+ # Bitch about a file that is missing data or unparsable for intuiting values.
- tests = ["rubygems", framework] +
- test_globs.map { |g| Dir.glob(g) }.flatten
- tests.map! {|f| %(require "#{f}")}
-
- cmd = "#{RUBY_FLAGS} -e '#{tests.join("; ")}' #{FILTER}"
-
- if multi then
- ENV['EXCLUDED_VERSIONS'] = multiruby_skip.join ":"
- cmd = "-S multiruby #{cmd}"
- end
-
- cmd
+ 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
- def announcement # :nodoc:
- changes = self.changes.rdoc_to_markdown
- subject = "#{name} #{version} Released"
- title = "#{name} version #{version} has been released!"
- body = "#{description}\n\nChanges:\n\n#{changes}".rdoc_to_markdown
- urls = Array(url).map { |s| "* <#{s.strip.rdoc_to_markdown}>" }.join("\n")
+ ##
+ # Normalize the dependencies.
- return subject, title, body, urls
+ def normalize_deps deps
+ Array(deps).map { |o| String === o ? [o] : o }
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)
- def paragraphs_of(path, *paragraphs)
+ def paragraphs_of path, *paragraphs
File.read(path).delete("\r").split(/\n\n+/).values_at(*paragraphs)
end
-end
-module Rake
- module TaskManager
- ##
- # This gives us access to the tasks already defined in rake.
- def all_tasks
- @tasks
- end
+ ##
+ # Tell the world you're a pluggable package (ie you require rubygems 1.3.1+)
+
+ def pluggable!
+ abort "update rubygems to >= 1.3.1" unless Gem.respond_to? :find_files
+ spec_extras[:required_rubygems_version] = '>= 1.3.1'
end
##
- # Simple shortcut for Rake.application.all_tasks
- def self.all_tasks
- Rake.application.all_tasks
+ # Finalize configuration
+
+ def post_initialize
+ intuit_values
+ validate_fields
+ add_dependencies
+ define_spec
+ load_plugin_tasks
end
##
- # Hooks into rake and allows us to clear out a task by name or
- # regexp. Use this if you want to completely override a task instead
- # of extend it.
- def self.clear_tasks(*tasks)
- tasks.flatten.each do |name|
- case name
- when Regexp then
- all_tasks.delete_if { |k,_| k =~ name }
- else
- all_tasks.delete(name)
- end
- end
+ # Provide a linear degrading value from n to m over start to finis dates.
+
+ def timebomb n, m, finis = '2010-04-01', start = '2009-03-14'
+ finis = Time.parse finis
+ start = Time.parse start
+ rest = (finis - Time.now)
+ full = (finis - start)
+
+ ((n - m) * rest / full).to_i + m
end
##
- # Removes the last action added to a task. Use this when two
- # libraries define the same task and you only want one of the
- # actions.
- #
- # require 'hoe'
- # require 'tasks/rails'
- # Rake.undo("test") # rolls out rails' test task
- def self.undo(*names)
- names.each do |name|
- all_tasks[name].actions.delete_at(-1)
+ # Verify that mandatory fields are set.
+
+ 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
-end
-# :enddoc:
+ ##
+ # Load or create a default config and yield it
-class ::Rake::SshDirPublisher # :nodoc:
- attr_reader :host, :remote_dir, :local_dir
-end
-
-class String
- def rdoc_to_markdown
- self.gsub(/^mailto:/, '').gsub(/^(=+)/) { "#" * $1.size }
+ def with_config # :nodoc:
+ rc = File.expand_path("~/.hoerc")
+ exists = File.exist? rc
+ config = exists ? YAML.load_file(rc) : {}
+ yield(config, rc)
end
-end
-if $0 == __FILE__ then
- out = `rake -T | egrep -v "redocs|repackage|clobber|trunk"`
- if ARGV.empty? then
- # # default:: Run the default tasks.
- puts out.gsub(/(\s*)\#/, '::\1').gsub(/^rake /, '# ')
- else
- # * default - Run the default tasks.
- puts out.gsub(/\#/, '-').gsub(/^rake /, '* ')
- end
+ Hoe.load_plugins
end