Object
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.
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.
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.
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.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
duh
Used to add extra flags to RUBY_FLAGS.
Used to specify flags to ruby [has smart default].
Default configuration values for .hoerc. Plugins should populate this on load.
True if you’re a masochistic developer. Used for building commands.
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: Hoe#summary and Hoe.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 # method to fill in both author and email cleanly.
Optional: Extra files you want to add to RDoc.
.txt files are automatically included (excluding the obvious).
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)
Optional: A short summary of the project. Auto-populates from the first sentence of the description.
See also: Hoe#description and Hoe.paragraphs_of.
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
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
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
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
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
The list of active plugins.
# File lib/hoe.rb, line 361 361: def self.plugins 362: @@plugins 363: 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 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
# 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 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 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 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
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
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
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
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
# 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 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 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
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 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
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 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
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
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
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
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
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
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
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
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
# 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
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
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.
Generated with the Darkfish Rdoc Generator 1.1.6.