Class: Hoe

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

Overview

:nodoc:

Constant Summary collapse

VERSION =
'1.5.3'
PREFIX =

Used to specify a custom install location (for rake install).

ENV['PREFIX'] || ruby_prefix
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
FILTER =

Used to add flags to test_unit (e.g., -n test_borked).

ENV['FILTER']
RUBYLIB =

:stopdoc:

if PREFIX == ruby_prefix then
  sitelibdir
else
  File.join(PREFIX, sitelibdir[ruby_prefix.size..-1])
end
DLEXT =
Config::CONFIG['DLEXT']
WINDOZE =
/djgpp|(cyg|ms|bcc)win|mingw/ =~ RUBY_PLATFORM
DIFF =
if WINDOZE
  'diff.exe'
else
  if system("gdiff", __FILE__, __FILE__)
    'gdiff' # solaris and kin suck
  else
    'diff'
  end
end

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, version) {|_self| ... } ⇒ Hoe

:nodoc:

Yields:

  • (_self)

Yield Parameters:

  • _self (Hoe)

    the object that the method was called on



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/hoe.rb', line 217

def initialize(name, version) # :nodoc:
  self.name = name
  self.version = version

  # Defaults
  self.author = []
  self.clean_globs = %w(diff diff.txt email.txt ri
                        *.gem *~ **/*~ *.rbc **/*.rbc)
  self.description_sections = %w(description)
  self.email = []
  self.extra_deps = []
  self.multiruby_skip = []
  self.need_tar = true
  self.need_zip = false
  self.rdoc_pattern = /^(lib|bin|ext)|(rdoc|txt)$/
  self.remote_rdoc_dir = name
  self.rsync_args = '-av --delete'
  self.rubyforge_name = name.downcase
  self.spec_extras = {}
  self.summary_sentences = 1
  self.test_globs = ['test/**/test_*.rb']
  self.post_install_message = nil

  yield self if block_given?

  # Intuit values:

  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

  readme   = File.read("README.rdoc").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.rdoc'
  end

  self.changes ||= begin
                     h = File.read("CHANGELOG")
                     h.split(/^(===.*)/)[1..2].join.strip
                   rescue
                     missing 'CHANGELOG'
                     ''
                   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
  end

  self.extra_deps = Array(extra_deps).map { |o| String === o ? [o] : o }

  define_tasks
end

Instance Attribute Details

#authorObject

Recommended: The author(s) of the package. (can be array) Really. Set this or we’ll tease you.



90
91
92
# File 'lib/hoe.rb', line 90

def author
  @author
end

#bin_filesObject

Populated automatically from the manifest. List of executables.



95
96
97
# File 'lib/hoe.rb', line 95

def bin_files
  @bin_files
end

#changesObject

Optional: A description of the release’s latest changes. Auto-populates.



100
101
102
# File 'lib/hoe.rb', line 100

def changes
  @changes
end

#clean_globsObject

Optional: An array of file patterns to delete on clean.



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

def clean_globs
  @clean_globs
end

#descriptionObject

Optional: A description of the project. Auto-populates.



110
111
112
# File 'lib/hoe.rb', line 110

def description
  @description
end

#description_sectionsObject

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



115
116
117
# File 'lib/hoe.rb', line 115

def description_sections
  @description_sections
end

#emailObject

Recommended: The author’s email address(es). (can be array)



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

def email
  @email
end

#extra_depsObject

Optional: An array of rubygem dependencies.



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

def extra_deps
  @extra_deps
end

#lib_filesObject

Populated automatically from the manifest. List of library files.



130
131
132
# File 'lib/hoe.rb', line 130

def lib_files
  @lib_files
end

#multiruby_skipObject

Optional: Array of incompatible versions for multiruby filtering. Used as a regex.



135
136
137
# File 'lib/hoe.rb', line 135

def multiruby_skip
  @multiruby_skip
end

#nameObject

MANDATORY: The name of the release.



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

def name
  @name
end

#need_tarObject

Optional: Should package create a tarball? [default: true]



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

def need_tar
  @need_tar
end

#need_zipObject

Optional: Should package create a zipfile? [default: false]



150
151
152
# File 'lib/hoe.rb', line 150

def need_zip
  @need_zip
end

#post_install_messageObject

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



155
156
157
# File 'lib/hoe.rb', line 155

def post_install_message
  @post_install_message
end

#rdoc_patternObject

Optional: A regexp to match documentation files against the manifest.



160
161
162
# File 'lib/hoe.rb', line 160

def rdoc_pattern
  @rdoc_pattern
end

#remote_rdoc_dirObject

Optional: Name of RDoc destination directory on Rubyforge. [default: name]



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

def remote_rdoc_dir
  @remote_rdoc_dir
end

#rsync_argsObject

Optional: Flags for RDoc rsync. [default: “-av –delete”]



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

def rsync_args
  @rsync_args
end

#rubyforge_nameObject

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



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

def rubyforge_name
  @rubyforge_name
end

#specObject

The Gem::Specification.



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

def spec
  @spec
end

#spec_extrasObject

Optional: A hash of extra values to set in the gemspec. Value may be a proc.



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

def spec_extras
  @spec_extras
end

#summaryObject

Optional: A short summary of the project. Auto-populates.



190
191
192
# File 'lib/hoe.rb', line 190

def summary
  @summary
end

#summary_sentencesObject

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



195
196
197
# File 'lib/hoe.rb', line 195

def summary_sentences
  @summary_sentences
end

#test_filesObject

Populated automatically from the manifest. List of tests.



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

def test_files
  @test_files
end

#test_globsObject

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



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

def test_globs
  @test_globs
end

#urlObject

Optional: The url(s) of the project. (can be array). Auto-populates.



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

def url
  @url
end

#versionObject

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



215
216
217
# File 'lib/hoe.rb', line 215

def version
  @version
end

Instance Method Details

#announcementObject

end define



694
695
696
697
698
699
700
701
702
703
# File 'lib/hoe.rb', line 694

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

  return subject, title, body, urls
end

#define_tasksObject

:nodoc:



295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
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
458
459
460
461
462
463
464
465
466
467
468
469
470
471
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
503
504
505
506
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
533
534
535
536
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
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
# File 'lib/hoe.rb', line 295

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

  desc 'Run the default tasks.'
  task :default => :test

  desc 'Run the test suite. Use FILTER to add to the command line.'
  task :test do
    run_tests
  end

  desc 'Show which test files fail when run alone.'
  task :test_deps do
    tests = Dir["test/**/test_*.rb"]  +  Dir["test/**/*_test.rb"]

    tests.each do |test|
      if not system "ruby -Ibin:lib:test #{test} &> /dev/null" then
        puts "Dependency Issues: #{test}"
      end
    end
  end

  desc 'Run the test suite using multiruby.'
  task :multi do
    run_tests :multi
  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
    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

    s.files = File.read("MANIFEST").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.rdoc']
    s.extra_rdoc_files = s.files.grep(/(txt|rdoc)$/)
    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[*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
      # 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

      # Try collecting Inline extensions for +name+
      if defined?(Inline) then
        directory 'lib/inline'

        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

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

  desc 'Show information about the gem.'
  task :debug_gem do
    puts spec.to_ruby
  end
  
  desc "Generate #{name}.gemspec"
  task :gemspec do
    File.open("#{name}.gemspec", "w") do |f|
      f.write spec.to_ruby
    end
  end

  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] do
    sh "#{'sudo ' unless WINDOZE}gem install --local pkg/*.gem"
  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\""
    end

    rf = RubyForge.new.configure
    puts "Logging in"
    rf.

    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
  end

  ############################################################
  # Doco

  Rake::RDocTask.new(:docs) do |rd|
    rd.main = "README.rdoc"
    rd.options << '-d' if RUBY_PLATFORM !~ /win32/ and `which dot` =~ /\/dot/ and not ENV['NODOT']
    rd.rdoc_dir = 'doc'
    files = spec.files.grep(rdoc_pattern)
    files -= ['MANIFEST', 'lib/hoe.rb']
    rd.rdoc_files.push(*files)

    title = "#{name}-#{version} Documentation"
    title = "#{rubyforge_name}'s " + title if rubyforge_name != name

    rd.options << "-t #{title}"
  end

  desc 'Generate ri locally for testing.'
  task :ridocs => :clean do
    sh %q{ rdoc --ri -o ri . }
  end

  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}}
  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

  ############################################################
  # Misc/Maintenance:

  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

  desc 'Clean up all the extras.'
  task :clean => [ :clobber_docs, :clobber_package ] do
    clean_globs.each do |pattern|
      files = Dir[pattern]
      rm_rf files, :verbose => true unless files.empty?
    end
  end

  desc 'Create a fresh ~/.hoerc file.'
  task :config_hoe do
    with_config do |config, path|
      default_config = {
        "exclude" => /tmp$|CVS|\.svn/,
        "publish_on_announce" => false,
        "signing_key_file" => "~/.gem/gem-private_key.pem",
        "signing_cert_file" => "~/.gem/gem-public_cert.pem",
        "blogs" => [ {
                       "user" => "user",
                       "url" => "url",
                       "extra_headers" => {
                         "mt_convert_breaks" => "markdown"
                       },
                       "blog_id" => "blog_id",
                       "password"=>"password",
                     } ],
      }
      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)
        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.
    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 #{f}"
      rm f
    end
  end

  desc 'Generate a key for signing your gems.'
  task :generate_key do
    email = 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
      sh "gem cert --build #{email}"
      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.

      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.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."
    end
  end

end

#developer(name, email) ⇒ Object



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

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

#missing(name) ⇒ Object

Intuit values:



244
245
246
247
# File 'lib/hoe.rb', line 244

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

#paragraphs_of(path, *paragraphs) ⇒ Object

Reads a file at path and spits out an array of the paragraphs specified.

changes = p.paragraphs_of('CHANGELOG', 0..1).join("\n\n")
summary, *description = p.paragraphs_of('README.rdoc', 3, 3..8)


728
729
730
# File 'lib/hoe.rb', line 728

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

#run_tests(multi = false) ⇒ Object

:nodoc:



705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
# File 'lib/hoe.rb', line 705

def run_tests(multi=false) # :nodoc:
  msg = multi ? :sh : :ruby
  cmd = if test ?f, 'test/test_all.rb' then
          "#{RUBY_FLAGS} test/test_all.rb #{FILTER}"
        else
          tests = ['test/unit'] + test_globs.map { |g| Dir.glob(g) }.flatten
          tests.map! {|f| %Q(require "#{f}")}
          "#{RUBY_FLAGS} -e '#{tests.join("; ")}' #{FILTER}"
        end

  excludes = multiruby_skip.join(":")
  ENV['EXCLUDED_VERSIONS'] = excludes
  cmd = "multiruby #{cmd}" if multi

  send msg, cmd
end

#with_config {|config, rc| ... } ⇒ Object

:nodoc:

Yields:

  • (config, rc)


296
297
298
299
300
301
# File 'lib/hoe.rb', line 296

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