date-performance 0.4.6
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +1 -0
- data/BENCHMARKS +30 -0
- data/COPYING +22 -0
- data/README.txt +134 -0
- data/Rakefile +90 -0
- data/doc/asciidoc.conf +21 -0
- data/doc/changes.html +32 -0
- data/doc/changes.txt +7 -0
- data/doc/images/icons/callouts/1.png +0 -0
- data/doc/images/icons/callouts/10.png +0 -0
- data/doc/images/icons/callouts/11.png +0 -0
- data/doc/images/icons/callouts/12.png +0 -0
- data/doc/images/icons/callouts/13.png +0 -0
- data/doc/images/icons/callouts/14.png +0 -0
- data/doc/images/icons/callouts/15.png +0 -0
- data/doc/images/icons/callouts/2.png +0 -0
- data/doc/images/icons/callouts/3.png +0 -0
- data/doc/images/icons/callouts/4.png +0 -0
- data/doc/images/icons/callouts/5.png +0 -0
- data/doc/images/icons/callouts/6.png +0 -0
- data/doc/images/icons/callouts/7.png +0 -0
- data/doc/images/icons/callouts/8.png +0 -0
- data/doc/images/icons/callouts/9.png +0 -0
- data/doc/images/icons/caution.png +0 -0
- data/doc/images/icons/example.png +0 -0
- data/doc/images/icons/home.png +0 -0
- data/doc/images/icons/important.png +0 -0
- data/doc/images/icons/next.png +0 -0
- data/doc/images/icons/note.png +0 -0
- data/doc/images/icons/prev.png +0 -0
- data/doc/images/icons/tip.png +0 -0
- data/doc/images/icons/up.png +0 -0
- data/doc/images/icons/warning.png +0 -0
- data/doc/license.html +42 -0
- data/doc/license.txt +1 -0
- data/doc/stylesheets/handbookish-manpage.css +36 -0
- data/doc/stylesheets/handbookish-quirks.css +2 -0
- data/doc/stylesheets/handbookish.css +119 -0
- data/ext/date_performance.c +401 -0
- data/ext/extconf.rb +9 -0
- data/lib/date/memoize.rb +86 -0
- data/lib/date/performance.rb +44 -0
- data/misc/asciidoc.rake +121 -0
- data/misc/project.rake +922 -0
- data/test/date_memoize_test.rb +70 -0
- data/test/extension_test.rb +120 -0
- metadata +110 -0
data/ext/extconf.rb
ADDED
data/lib/date/memoize.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# See Date::Memoize.
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
require 'date/performance'
|
5
|
+
|
6
|
+
class Date #:nodoc:
|
7
|
+
|
8
|
+
# Adds memoization to Date. This can speed things up significantly in cases where a lot
|
9
|
+
# of the same Date objects are created.
|
10
|
+
module Memoize
|
11
|
+
|
12
|
+
# Memoized version of Date::strptime.
|
13
|
+
def strptime(str='-4712-01-01', fmt='%F', sg=ITALY)
|
14
|
+
@__memoized_strptime_dates[ [ str, fmt, sg ] ]
|
15
|
+
end
|
16
|
+
|
17
|
+
# Memoized version Date::parse.
|
18
|
+
def parse(str='-4712-01-01', comp=false, sg=ITALY)
|
19
|
+
@__memoized_parse_dates[ [ str, comp, sg ] ]
|
20
|
+
end
|
21
|
+
|
22
|
+
# Memoized version of Date::civil.
|
23
|
+
def civil(y=-4712, m=1, d=1, sg=ITALY)
|
24
|
+
@__memoized_civil_dates[ [ y, m, d, sg ] ]
|
25
|
+
end
|
26
|
+
|
27
|
+
alias_method :new, :civil
|
28
|
+
|
29
|
+
public
|
30
|
+
|
31
|
+
# The methods we'll be replacing on the Date singleton.
|
32
|
+
def self.methods_replaced
|
33
|
+
[ :new, :civil, :strptime, :parse ]
|
34
|
+
end
|
35
|
+
|
36
|
+
# Overridden to move the existing methods out of the way before copying this module's
|
37
|
+
# methods.
|
38
|
+
def self.extend_object(base)
|
39
|
+
singleton = (class<<base;self;end)
|
40
|
+
methods_replaced.each do |method|
|
41
|
+
singleton.send :alias_method, "#{method}_without_memoization", method
|
42
|
+
singleton.send :remove_method, method
|
43
|
+
end
|
44
|
+
base.send :instance_variable_set, :@__memoized_civil_dates,
|
45
|
+
Hash.new{|h,key| h[key]=Date.new_without_memoization(*key)}
|
46
|
+
base.send :instance_variable_set, :@__memoized_strptime_dates,
|
47
|
+
Hash.new{|h,key| h[key]=Date.strptime_without_memoization(*key)}
|
48
|
+
base.send :instance_variable_set, :@__memoized_parse_dates,
|
49
|
+
Hash.new{|h,key| h[key]=Date.parse_without_memoization(*key)}
|
50
|
+
super
|
51
|
+
end
|
52
|
+
|
53
|
+
# Removes memoization methods from singleton of the class provided.
|
54
|
+
def self.unextend_object(base)
|
55
|
+
singleton = (class<<base;self;end)
|
56
|
+
methods_replaced.each do |method|
|
57
|
+
singleton.send :alias_method, method, "#{method}_without_memoization"
|
58
|
+
singleton.send :remove_method, "#{method}_without_memoization"
|
59
|
+
end
|
60
|
+
base.send :remove_instance_variable, :@__memoized_civil_dates
|
61
|
+
base.send :remove_instance_variable, :@__memoized_strptime_dates
|
62
|
+
base.send :remove_instance_variable, :@__memoized_parse_dates
|
63
|
+
base
|
64
|
+
end
|
65
|
+
|
66
|
+
# Is Date memoization currently installed and active?
|
67
|
+
def self.installed?
|
68
|
+
Date.respond_to? :civil_without_memoization
|
69
|
+
end
|
70
|
+
|
71
|
+
# Extend the Date class with memoized versions of +new+ and +civil+ but only if
|
72
|
+
# memoization has not yet been installed.
|
73
|
+
def self.install!
|
74
|
+
Date.extend self unless installed?
|
75
|
+
end
|
76
|
+
|
77
|
+
# Remove memoized methods and free up memo cache. This method is idempotent.
|
78
|
+
def self.uninstall!
|
79
|
+
unextend_object Date if installed?
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
Memoize.install!
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
# Loading this file is not idemponent and can cause damage when loaded twice.
|
4
|
+
# Fail hard and fast.
|
5
|
+
fail "Date::Performance already loaded." if defined? Date::Performance
|
6
|
+
|
7
|
+
class Date
|
8
|
+
|
9
|
+
# The Date::Performance module is present when the performance enhacing extension
|
10
|
+
# has been loaded. It serves no other purpose.
|
11
|
+
module Performance
|
12
|
+
VERSION = "0.4.6"
|
13
|
+
end
|
14
|
+
|
15
|
+
# The extension replaces Date#strftime but falls back on the stock version when
|
16
|
+
# strftime(3) cannot handle the format.
|
17
|
+
alias_method :strftime_without_performance, :strftime
|
18
|
+
|
19
|
+
class << self
|
20
|
+
# Ruby 1.8.6 introduced Date.new! and the extension uses it. The method was
|
21
|
+
# called new0 in <= 1.8.5.
|
22
|
+
alias_method :new!, :new0 unless Date.respond_to?(:new!)
|
23
|
+
|
24
|
+
# The extension replaces Date.strptime but falls back on the stock version when
|
25
|
+
# strptime(3) can't handle the format.
|
26
|
+
alias_method :strptime_without_performance, :strptime
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
# Load up the extension but bring the Date class back to its original state
|
32
|
+
# if the extension fails to load properly.
|
33
|
+
begin
|
34
|
+
require 'date_performance.so'
|
35
|
+
rescue
|
36
|
+
class Date
|
37
|
+
remove_const :Performance
|
38
|
+
remove_method :strftime_without_performance
|
39
|
+
class << self
|
40
|
+
remove_method :strptime_without_performance
|
41
|
+
end
|
42
|
+
end
|
43
|
+
raise
|
44
|
+
end
|
data/misc/asciidoc.rake
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
|
2
|
+
# Run asciidoc.
|
3
|
+
def asciidoc(source, dest, *args)
|
4
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
5
|
+
options[:verbose] = verbose if options[:verbose].nil?
|
6
|
+
attributes = options[:attributes] || {}
|
7
|
+
config_file = options[:config_file]
|
8
|
+
if source_dir = options[:source_dir]
|
9
|
+
source = source.sub(/^#{source_dir}/, '.')
|
10
|
+
dest = dest.sub(/^#{source_dir}/, '.')
|
11
|
+
config_file = config_file.sub(/^#{source_dir}/, '.') if config_file
|
12
|
+
end
|
13
|
+
command = [
|
14
|
+
'asciidoc',
|
15
|
+
('--unsafe' unless options[:safe]),
|
16
|
+
('--verbose' if options[:verbose]),
|
17
|
+
('--no-header-footer' if options[:suppress_header]),
|
18
|
+
("-a theme=#{options[:theme]}" if options[:theme]),
|
19
|
+
("-a stylesdir='#{options[:styles]}'" if options[:styles]),
|
20
|
+
"-a linkcss -a quirks\!",
|
21
|
+
("-f '#{config_file}'" if config_file),
|
22
|
+
attributes.map{|k,v| "-a #{k}=#{v.inspect}" },
|
23
|
+
("-d #{options[:doc_type]}" if options[:doc_type]),
|
24
|
+
"-o", dest,
|
25
|
+
args,
|
26
|
+
source
|
27
|
+
].flatten.compact
|
28
|
+
chdir(options[:source_dir] || Dir.getwd) { sh command.join(' ') }
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
class AsciiDocTasks
|
33
|
+
|
34
|
+
attr_reader :task_name
|
35
|
+
|
36
|
+
# The directory where asciidoc sources are stored. Defaults to +doc+.
|
37
|
+
attr_accessor :source_dir
|
38
|
+
|
39
|
+
# A list of source files to build.
|
40
|
+
attr_accessor :source_files
|
41
|
+
|
42
|
+
# Set true to disable potentially unsafe document instructions. For example,
|
43
|
+
# the asciidoc sys:: macro is disabled when safe mode is enabled.
|
44
|
+
attr_accessor :safe
|
45
|
+
|
46
|
+
# Override the default verbosity.
|
47
|
+
attr_accessor :verbose
|
48
|
+
|
49
|
+
# Do not output header and footer.
|
50
|
+
attr_accessor :suppress_header
|
51
|
+
|
52
|
+
# An asciidoc configuration file.
|
53
|
+
attr_accessor :config_file
|
54
|
+
|
55
|
+
# One of :article, :manpage, or :book
|
56
|
+
attr_accessor :doc_type
|
57
|
+
|
58
|
+
# Hash of asciidoc attributes passed in via the -a argument.
|
59
|
+
attr_accessor :attributes
|
60
|
+
|
61
|
+
def initialize(task_name)
|
62
|
+
@task_name = task_name
|
63
|
+
@source_dir = 'doc'
|
64
|
+
@source_files = FileList.new
|
65
|
+
@safe = false
|
66
|
+
@verbose = nil
|
67
|
+
@suppress_header = false
|
68
|
+
@doc_type = :article
|
69
|
+
@config_file = nil
|
70
|
+
yield self if block_given?
|
71
|
+
define!
|
72
|
+
end
|
73
|
+
|
74
|
+
def define!
|
75
|
+
task task_name => "#{task_name}:build"
|
76
|
+
task "#{task_name}:clean"
|
77
|
+
task "#{task_name}:build"
|
78
|
+
task "#{task_name}:rebuild"
|
79
|
+
task :clean => "#{task_name}:clean"
|
80
|
+
source_and_destination_files.each do |source,dest|
|
81
|
+
file dest => [ source, config_file ].compact do |f|
|
82
|
+
asciidoc source, dest, options
|
83
|
+
end
|
84
|
+
task("#{task_name}:build" => dest)
|
85
|
+
task("#{task_name}:clean") { rm_f dest }
|
86
|
+
task("#{task_name}:rebuild" => [ "#{task_name}:clean", "#{task_name}:build" ])
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def destination_files
|
93
|
+
source_files.collect { |path| path.sub(/\.\w+$/, ".html") }
|
94
|
+
end
|
95
|
+
|
96
|
+
def source_and_destination_files
|
97
|
+
source_files.zip(destination_files)
|
98
|
+
end
|
99
|
+
|
100
|
+
undef :config_file
|
101
|
+
|
102
|
+
def config_file
|
103
|
+
@config_file ||
|
104
|
+
if File.exist?("#{source_dir}/asciidoc.conf")
|
105
|
+
"#{source_dir}/asciidoc.conf"
|
106
|
+
else
|
107
|
+
nil
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def options
|
112
|
+
{ :safe => safe,
|
113
|
+
:suppress_header => suppress_header,
|
114
|
+
:verbose => self.verbose,
|
115
|
+
:source_dir => source_dir,
|
116
|
+
:config_file => config_file,
|
117
|
+
:doc_type => doc_type,
|
118
|
+
:attributes => attributes }
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
data/misc/project.rake
ADDED
@@ -0,0 +1,922 @@
|
|
1
|
+
# An Intelligent Ruby Project Template
|
2
|
+
#
|
3
|
+
# === Synopsis
|
4
|
+
#
|
5
|
+
# This file should be loaded at the top of your project Rakefile as follows:
|
6
|
+
#
|
7
|
+
# load "misc/project.rake"
|
8
|
+
#
|
9
|
+
# Project.new "Test Project", "1.0" do |p|
|
10
|
+
# p.package_name = 'test-project'
|
11
|
+
# p.author = 'John Doe <jdoe@example.com>'
|
12
|
+
# p.summary = 'A Project That Does Nothing'
|
13
|
+
# p.description = <<-end
|
14
|
+
# This project does nothing other than serve as an example of
|
15
|
+
# how to use this default project thingy. By the way, any leading
|
16
|
+
# space included in this description is automatically stripped.
|
17
|
+
#
|
18
|
+
# Even when text spans multiple paragraphs.
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# p.depends_on 'some-package', '~> 1.0'
|
22
|
+
# p.remote_dist_location = "example.com:/dist/#{p.package_name}"
|
23
|
+
# p.remote_doc_location = "example.com:/doc/#{p.package_name}"
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# A default set of Rake tasks are created based on the attributes specified.
|
27
|
+
# See the documentation for the Project class for more information.
|
28
|
+
#
|
29
|
+
# === MIT License
|
30
|
+
#
|
31
|
+
# Copyright (C) 2007 by Ryan Tomayko
|
32
|
+
#
|
33
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
34
|
+
# of this software and associated documentation files (the "Software"), to deal
|
35
|
+
# in the Software without restriction, including without limitation the rights
|
36
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
37
|
+
# copies of the Software, and to permit persons to whom the Software is
|
38
|
+
# furnished to do so, subject to the following conditions:
|
39
|
+
#
|
40
|
+
# The above copyright notice and this permission notice shall be included in all
|
41
|
+
# copies or substantial portions of the Software.
|
42
|
+
#
|
43
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
44
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
45
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
46
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
47
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
48
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
49
|
+
# SOFTWARE.
|
50
|
+
|
51
|
+
require 'rake'
|
52
|
+
require 'rake/clean'
|
53
|
+
require 'rake/gempackagetask'
|
54
|
+
require 'rake/rdoctask'
|
55
|
+
require 'rake/testtask'
|
56
|
+
|
57
|
+
# The Project class stores various aspects of project configuration information
|
58
|
+
# and attempts to make best-guess assumptions about the current environment.
|
59
|
+
class Project
|
60
|
+
|
61
|
+
# Array of project attribute names.
|
62
|
+
@project_attributes = []
|
63
|
+
|
64
|
+
class << self #:nodoc:
|
65
|
+
|
66
|
+
# The first Project instance that's created is stored here. Messages sent
|
67
|
+
# to the Project class are delegated here.
|
68
|
+
attr_accessor :current
|
69
|
+
|
70
|
+
# An array of attribute names available on project objects.
|
71
|
+
attr_reader :project_attributes
|
72
|
+
|
73
|
+
# Project class delegates missing messages to the current instance so let
|
74
|
+
# the world know.
|
75
|
+
def respond_to?(name, include_private=false) #:nodoc:
|
76
|
+
super || (current && current.respond_to?(name, include_private))
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
# Pass messages over to the current Project instance if we don't have an
|
82
|
+
# implementation.
|
83
|
+
def method_missing(name, *args, &b)
|
84
|
+
if current && current.respond_to?(name)
|
85
|
+
current.send(name, *args, &b)
|
86
|
+
else
|
87
|
+
super
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# An attr_writer that, when given a String, creates a FileList with the
|
92
|
+
# given value.
|
93
|
+
def file_list_attr_writer(*names)
|
94
|
+
names.each do |name|
|
95
|
+
class_eval <<-end_ruby
|
96
|
+
undef #{name}=
|
97
|
+
def #{name}=(value)
|
98
|
+
value = FileList[value.to_str] if value.respond_to?(:to_str)
|
99
|
+
@#{name} = value
|
100
|
+
end
|
101
|
+
end_ruby
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Track attributes as they're declares with attr_accessor.
|
106
|
+
def attr_accessor_with_tracking(*names) #:nodoc:
|
107
|
+
project_attributes.concat names
|
108
|
+
attr_accessor_without_tracking(*names)
|
109
|
+
end
|
110
|
+
|
111
|
+
public
|
112
|
+
|
113
|
+
send :alias_method, :attr_accessor_without_tracking, :attr_accessor
|
114
|
+
send :alias_method, :attr_accessor, :attr_accessor_with_tracking
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
# The project's name. This should be short but may include spaces and
|
120
|
+
# puncuation.
|
121
|
+
attr_accessor :name
|
122
|
+
|
123
|
+
alias :project_name :name
|
124
|
+
|
125
|
+
# The package name that should be used when building distributables or when a
|
126
|
+
# UNIX friendly name is otherwise needed. If this variable is not set, an
|
127
|
+
# attempt is made to derive it from the +name+ attribute.
|
128
|
+
attr_accessor :package_name
|
129
|
+
|
130
|
+
# The project version as a string. This is typically read from a source
|
131
|
+
# file.
|
132
|
+
attr_accessor :version
|
133
|
+
|
134
|
+
# The source file that's used to keep the project version. The file will
|
135
|
+
# typically include a +VERSION+ constant. With no explicit #version set,
|
136
|
+
# this file will be inspected.
|
137
|
+
#
|
138
|
+
# The #version_pattern attribute can be used to describe how to locate the
|
139
|
+
# version.
|
140
|
+
attr_accessor :version_file
|
141
|
+
|
142
|
+
# A string pattern that describes how the #version can be established from
|
143
|
+
# the #version_file. The default pattern is
|
144
|
+
# <code>/^\s*VERSION\s*=\s*['"]([\.\d]+)['"]/</code>
|
145
|
+
attr_accessor :version_pattern
|
146
|
+
|
147
|
+
# A short, one/two sentence, description of the project.
|
148
|
+
attr_accessor :summary
|
149
|
+
|
150
|
+
# A longer -- single paragraph -- description of the project.
|
151
|
+
attr_accessor :description
|
152
|
+
|
153
|
+
# The directory where programs live. This is detected automatically if a
|
154
|
+
# +bin+ or +scripts+ directory exists off the project root.
|
155
|
+
attr_accessor :bin_dir
|
156
|
+
|
157
|
+
# A list of programs (under bin_dir) to install. This is detected
|
158
|
+
# automatically.
|
159
|
+
attr_accessor :programs
|
160
|
+
|
161
|
+
# The directory where tests live. Default is +tests+.
|
162
|
+
attr_accessor :test_dir
|
163
|
+
|
164
|
+
# By default, this attribute returns a FileList of all test files living
|
165
|
+
# under +test_dir+ that match +test_pattern+ but can be set explicitly if
|
166
|
+
# needed.
|
167
|
+
attr_accessor :tests
|
168
|
+
|
169
|
+
# A glob string used to find test cases. Default: '**/*_test.rb'
|
170
|
+
attr_accessor :test_pattern
|
171
|
+
|
172
|
+
# Directory where library files. Default: +lib+
|
173
|
+
attr_accessor :lib_dir
|
174
|
+
|
175
|
+
# A FileList of library files. Defaults to all .rb files under +lib_dir+
|
176
|
+
attr_accessor :lib_files
|
177
|
+
|
178
|
+
# The top-level documentation directory. Default: +doc+.
|
179
|
+
attr_accessor :doc_dir
|
180
|
+
|
181
|
+
# All documentation files. Default is to include everything under +doc_dir+
|
182
|
+
# except for API documentation.
|
183
|
+
attr_accessor :doc_files
|
184
|
+
|
185
|
+
# Additional files to include in generated API documentation
|
186
|
+
attr_accessor :rdoc_files
|
187
|
+
|
188
|
+
# The author's name and email address: "Mr Foo <foo@example.com>"
|
189
|
+
attr_accessor :author
|
190
|
+
|
191
|
+
# The author's name. If this is not set, an attempt is made to establish it
|
192
|
+
# from the +author+ attribute.
|
193
|
+
attr_accessor :author_name
|
194
|
+
|
195
|
+
# The author's email. If this is not set, an attempt is made to establish it
|
196
|
+
# from the +author+ attribute.
|
197
|
+
attr_accessor :author_email
|
198
|
+
|
199
|
+
# The project's homepage URL.
|
200
|
+
attr_accessor :project_url
|
201
|
+
|
202
|
+
# Directory where distributables are built.
|
203
|
+
attr_accessor :package_dir
|
204
|
+
|
205
|
+
# Any additional files to include in the package.
|
206
|
+
attr_accessor :extra_files
|
207
|
+
|
208
|
+
# Native extension files. Default: <tt>FileList['ext/**/*.{c,h,rb}']</tt>.
|
209
|
+
attr_accessor :extension_files
|
210
|
+
|
211
|
+
# An Array of gem dependencies. The #depends_on is the simplest way of getting
|
212
|
+
# new dependencies defined.
|
213
|
+
attr_accessor :dependencies
|
214
|
+
|
215
|
+
# The rubyforge project
|
216
|
+
attr_accessor :rubyforge_project
|
217
|
+
|
218
|
+
# A remote location where packages should published. This should be of the
|
219
|
+
# form host.domain:/path/to/dir
|
220
|
+
attr_accessor :remote_dist_location
|
221
|
+
|
222
|
+
# The shell command to run on the remote dist host that reindexes the gem
|
223
|
+
# repository. This is typically something like
|
224
|
+
# <code>'cd ~/www/gems && index_gem_repository.rb -v'</code>. If you don't
|
225
|
+
# maintain a gem repository, this is safe to ignore.
|
226
|
+
attr_accessor :index_gem_repository_command #:nodoc:
|
227
|
+
|
228
|
+
# The remote location where built documentation should be published. This
|
229
|
+
# should be of the form host.domain:/path/to/dir
|
230
|
+
attr_accessor :remote_doc_location
|
231
|
+
|
232
|
+
# The remote location where the git repo should be published. This should
|
233
|
+
# be of the form host.domain:/path/to/dir.git The local .git directory is copied
|
234
|
+
# to the destination directly.
|
235
|
+
attr_accessor :remote_branch_location
|
236
|
+
|
237
|
+
# Generate README file based on project information.
|
238
|
+
attr_accessor :generate_readme
|
239
|
+
|
240
|
+
file_list_attr_writer :tests, :lib_files, :doc_files, :extra_files, :rdoc_files,
|
241
|
+
:extension_files
|
242
|
+
|
243
|
+
def initialize(project_name, options={}, &b)
|
244
|
+
self.class.current ||= self
|
245
|
+
@name = project_name
|
246
|
+
@version = options.delete(:version)
|
247
|
+
@version_file = nil
|
248
|
+
@version_pattern = /^\s*VERSION\s*=\s*['"]([\.\d]+)['"]/
|
249
|
+
@summary, @description, @package_name = nil
|
250
|
+
@author, @author_email, @author_name = nil
|
251
|
+
@bin_dir, @programs = nil
|
252
|
+
@test_dir, @tests = nil
|
253
|
+
@test_pattern = 'test/**/*_test.rb'
|
254
|
+
@lib_dir = 'lib'
|
255
|
+
@lib_files = nil
|
256
|
+
@doc_dir = 'doc'
|
257
|
+
@doc_files = nil
|
258
|
+
@rdoc_dir = nil
|
259
|
+
@rdoc_files = Rake::FileList['{README,LICENSE,COPYING}*']
|
260
|
+
@package_dir = 'dist'
|
261
|
+
@extra_files = Rake::FileList['Rakefile', 'Change{Log,s}*', 'NEWS*', 'misc/*.rake']
|
262
|
+
@extension_files = nil
|
263
|
+
@project_url = nil
|
264
|
+
@dependencies = []
|
265
|
+
@remote_dist_location, @remote_doc_location, @remote_branch_location = nil
|
266
|
+
@index_gem_repository_command = nil
|
267
|
+
@generate_readme = false
|
268
|
+
@package_configurator = nil
|
269
|
+
@rdoc_configurator = nil
|
270
|
+
@test_configurator = nil
|
271
|
+
@rubyforge_project = nil
|
272
|
+
enhance(options, &b)
|
273
|
+
define_tasks
|
274
|
+
end
|
275
|
+
|
276
|
+
def enhance(options={})
|
277
|
+
options.each { |k,v| send("#{k}=", v) }
|
278
|
+
yield self if block_given?
|
279
|
+
end
|
280
|
+
|
281
|
+
undef :package_name, :description=, :bin_dir, :test_dir, :programs, :tests,
|
282
|
+
:lib_files, :doc_files
|
283
|
+
|
284
|
+
def package_name #:nodoc:
|
285
|
+
read_attr(:package_name) { name.downcase.gsub(/[:\s]+/, '-') }
|
286
|
+
end
|
287
|
+
|
288
|
+
undef version
|
289
|
+
|
290
|
+
def version #:nodoc:
|
291
|
+
read_attr(:version, true) { read_version_from_version_file }
|
292
|
+
end
|
293
|
+
|
294
|
+
# Read the version from version_file using the version_pattern.
|
295
|
+
def read_version_from_version_file #:nodoc:
|
296
|
+
if version_file
|
297
|
+
if match = File.read(version_file).match(version_pattern)
|
298
|
+
match[1]
|
299
|
+
else
|
300
|
+
fail "No version match %p in %p." % [ version_file, version_pattern ]
|
301
|
+
end
|
302
|
+
else
|
303
|
+
fail "No version or version_file specified."
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
# Writes the currently set version to the version file.
|
308
|
+
def update_version_and_write_to_version_file(version) #:nodoc:
|
309
|
+
contents = File.read(version_file)
|
310
|
+
if match = contents.match(version_pattern)
|
311
|
+
old_version_line = match[0]
|
312
|
+
new_version_line = old_version_line.sub(match[1], version)
|
313
|
+
contents.sub! old_version_line, new_version_line
|
314
|
+
File.open(version_file, 'wb') { |io| io.write(contents) }
|
315
|
+
else
|
316
|
+
fail "Project version not found in #{version_file} (pattern: %p)." % version_pattern
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
# The next version, calculated by incrementing the last version component by
|
321
|
+
# 1. For instance, the version after +0.2.9+ is +0.2.10+.
|
322
|
+
def next_version #:nodoc:
|
323
|
+
parts = version.split('.')
|
324
|
+
parts[-1] = (parts[-1].to_i + 1).to_s
|
325
|
+
parts.join('.')
|
326
|
+
end
|
327
|
+
|
328
|
+
def description=(value) #:nodoc:
|
329
|
+
@description =
|
330
|
+
if value.respond_to? :to_str
|
331
|
+
value.to_str.strip_leading_indentation.strip
|
332
|
+
else
|
333
|
+
value
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
def bin_dir #:nodoc:
|
338
|
+
read_attr(:bin_dir) { FileList['bin', 'scripts'].existing.first }
|
339
|
+
end
|
340
|
+
|
341
|
+
def test_dir #:nodoc:
|
342
|
+
read_attr(:test_dir) { FileList['test'].existing.first }
|
343
|
+
end
|
344
|
+
|
345
|
+
def programs #:nodoc:
|
346
|
+
read_attr(:programs, true) { bin_dir ? FileList["#{bin_dir}/*"] : [] }
|
347
|
+
end
|
348
|
+
|
349
|
+
def tests #:nodoc:
|
350
|
+
read_attr(:tests, true) { test_dir ? FileList[test_pattern] : [] }
|
351
|
+
end
|
352
|
+
|
353
|
+
def lib_files #:nodoc:
|
354
|
+
read_attr(:lib_files, true) { FileList["#{lib_dir}/**/*.rb"] }
|
355
|
+
end
|
356
|
+
|
357
|
+
def doc_files #:nodoc:
|
358
|
+
read_attr(:doc_files) {
|
359
|
+
FileList["doc/**/*"] - FileList["#{rdoc_dir}/**/*", rdoc_dir] }
|
360
|
+
end
|
361
|
+
|
362
|
+
def rdoc_dir #:nodoc:
|
363
|
+
read_attr(:rdoc_dir) { File.join(doc_dir, 'api') }
|
364
|
+
end
|
365
|
+
|
366
|
+
def extensions_dir
|
367
|
+
'ext'
|
368
|
+
end
|
369
|
+
|
370
|
+
undef extension_files
|
371
|
+
|
372
|
+
def extension_files
|
373
|
+
read_attr(:extension_files) { FileList["#{extensions_dir}/**/*.{h,c,rb}"] }
|
374
|
+
end
|
375
|
+
|
376
|
+
undef author
|
377
|
+
|
378
|
+
def author #:nodoc:
|
379
|
+
read_attr(:author, true) {
|
380
|
+
if @author_name && @author_email
|
381
|
+
"#{@author_name} <#{@author_email}>"
|
382
|
+
elsif @author_name
|
383
|
+
@author_name
|
384
|
+
elsif @author_email
|
385
|
+
@author_email
|
386
|
+
end
|
387
|
+
}
|
388
|
+
end
|
389
|
+
|
390
|
+
undef :author_name
|
391
|
+
|
392
|
+
def author_name #:nodoc:
|
393
|
+
read_attr(:author_name) { author && author[/[^<]*/].strip }
|
394
|
+
end
|
395
|
+
|
396
|
+
undef :author_email
|
397
|
+
|
398
|
+
def author_email #:nodoc:
|
399
|
+
read_attr(:author_email) {
|
400
|
+
if author && author =~ /<(.*)>$/
|
401
|
+
$1
|
402
|
+
else
|
403
|
+
nil
|
404
|
+
end
|
405
|
+
}
|
406
|
+
end
|
407
|
+
|
408
|
+
# All files to be included in built packages. This includes +lib_files+,
|
409
|
+
# +tests+, +rdoc_files+, +programs+, and +extra_files+.
|
410
|
+
def package_files
|
411
|
+
(rdoc_files + lib_files + tests + doc_files +
|
412
|
+
programs + extra_files + extension_files).uniq
|
413
|
+
end
|
414
|
+
|
415
|
+
# The basename of the current distributable package.
|
416
|
+
def package_basename(extension='.gem')
|
417
|
+
[ package_name, version ].join('-') + extension
|
418
|
+
end
|
419
|
+
|
420
|
+
# The path from the project root to a package distributable.
|
421
|
+
def package_path(extension='.gem')
|
422
|
+
File.join(package_dir, package_basename(extension))
|
423
|
+
end
|
424
|
+
|
425
|
+
# A list of built package distributables for the current project version.
|
426
|
+
def packages
|
427
|
+
FileList[package_path('.*')]
|
428
|
+
end
|
429
|
+
|
430
|
+
# Declare that this project depends on the specified package.
|
431
|
+
def depends_on(package, *version)
|
432
|
+
dependencies << [ package, *version ]
|
433
|
+
end
|
434
|
+
|
435
|
+
def configure_package(&block)
|
436
|
+
@package_configurator = block
|
437
|
+
end
|
438
|
+
|
439
|
+
def configure_rdoc(&block)
|
440
|
+
@rdoc_configurator = block
|
441
|
+
end
|
442
|
+
|
443
|
+
def configure_tests(&block)
|
444
|
+
@test_configurator = block
|
445
|
+
end
|
446
|
+
|
447
|
+
public
|
448
|
+
|
449
|
+
# An Array of attribute names available for the project.
|
450
|
+
def project_attribute_array
|
451
|
+
self.class.project_attributes.collect do |name|
|
452
|
+
[ name.to_sym, send(name) ]
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
alias :to_a :project_attribute_array
|
457
|
+
|
458
|
+
# A Hash of attribute name to attribute values -- one for each project
|
459
|
+
# attribute.
|
460
|
+
def project_attribute_hash
|
461
|
+
to_a.inject({}) { |hash,(k,v)| hash[k] = v }
|
462
|
+
end
|
463
|
+
|
464
|
+
alias :to_hash :project_attribute_hash
|
465
|
+
|
466
|
+
public
|
467
|
+
|
468
|
+
# A Gem::Specification instance with values based on the attributes defined on
|
469
|
+
# the project
|
470
|
+
def gem_spec
|
471
|
+
Gem::Specification.new do |s|
|
472
|
+
s.name = package_name
|
473
|
+
s.version = version
|
474
|
+
s.platform = Gem::Platform::RUBY
|
475
|
+
s.summary = summary
|
476
|
+
s.description = description || summary
|
477
|
+
s.author = author_name
|
478
|
+
s.email = author_email
|
479
|
+
s.homepage = project_url
|
480
|
+
s.require_path = lib_dir
|
481
|
+
s.files = package_files
|
482
|
+
s.test_files = tests
|
483
|
+
s.bindir = bin_dir
|
484
|
+
s.executables = programs.map{|p| File.basename(p)}
|
485
|
+
s.extensions = FileList['ext/**/extconf.rb']
|
486
|
+
s.has_rdoc = true
|
487
|
+
s.extra_rdoc_files = rdoc_files
|
488
|
+
s.rdoc_options.concat(rdoc_options)
|
489
|
+
s.test_files = tests
|
490
|
+
s.rubyforge_project = rubyforge_project
|
491
|
+
dependencies.each { |args| s.add_dependency(*args) }
|
492
|
+
@package_configurator.call(s) if @package_configurator
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
496
|
+
private
|
497
|
+
|
498
|
+
# Read and return the instance variable with name if it is defined and is non
|
499
|
+
# nil. When the variable's value is a Proc, invoke it and return the
|
500
|
+
# result. When the variable's value is nil, yield to the block and return the
|
501
|
+
# result.
|
502
|
+
def read_attr(name, record=false)
|
503
|
+
result =
|
504
|
+
case value = instance_variable_get("@#{name}")
|
505
|
+
when nil
|
506
|
+
yield if block_given?
|
507
|
+
when Proc
|
508
|
+
value.to_proc.call(self)
|
509
|
+
else
|
510
|
+
value
|
511
|
+
end
|
512
|
+
instance_variable_set("@#{name}", result) if record
|
513
|
+
result
|
514
|
+
end
|
515
|
+
|
516
|
+
# Define Rake tasks for this project.
|
517
|
+
def define_tasks
|
518
|
+
private_methods.grep(/^define_(\w+)_tasks$/).each do |meth|
|
519
|
+
namespace_name = meth.match(/^define_(\w+)_tasks$/)[1]
|
520
|
+
send(meth)
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
# Project Tasks ===========================================================
|
525
|
+
|
526
|
+
def define_project_tasks
|
527
|
+
namespace :project do
|
528
|
+
desc "Show high level project information"
|
529
|
+
task :info do |t|
|
530
|
+
puts [ project_name, version ].compact.join('/')
|
531
|
+
puts summary if summary
|
532
|
+
puts "\n%s" % [ description ] if description
|
533
|
+
end
|
534
|
+
|
535
|
+
desc "Write project version to STDOUT"
|
536
|
+
task :version do |t|
|
537
|
+
puts version
|
538
|
+
end
|
539
|
+
|
540
|
+
if version_file
|
541
|
+
bump_version_to = ENV['VERSION'] || next_version
|
542
|
+
desc "Bump project version (to %s) in %s" % [ bump_version_to, version_file ]
|
543
|
+
task :revise => [ version_file ] do |t|
|
544
|
+
message = "bumping version to %s in %s" % [ bump_version_to, version_file ]
|
545
|
+
STDERR.puts message if verbose
|
546
|
+
update_version_and_write_to_version_file(bump_version_to)
|
547
|
+
end
|
548
|
+
else
|
549
|
+
task :bump
|
550
|
+
end
|
551
|
+
|
552
|
+
desc "Write project / package attributes to STDOUT"
|
553
|
+
task :attributes do |t|
|
554
|
+
puts project_attribute_array.collect { |k,v| "%s: %p" % [ k, v ] }.join("\n")
|
555
|
+
end
|
556
|
+
end
|
557
|
+
end
|
558
|
+
|
559
|
+
# Test Tasks ===============================================================
|
560
|
+
|
561
|
+
def define_test_tasks
|
562
|
+
desc "Run all tests under #{test_dir}"
|
563
|
+
Rake::TestTask.new :test do |t|
|
564
|
+
t.libs = [ lib_dir, test_dir ]
|
565
|
+
t.ruby_opts = [ '-rubygems' ]
|
566
|
+
t.warning = true
|
567
|
+
t.test_files = tests
|
568
|
+
t.verbose = false
|
569
|
+
@test_configurator.call(t) if @test_configurator
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
# Package Tasks ===========================================================
|
574
|
+
|
575
|
+
def define_package_tasks
|
576
|
+
@package_config =
|
577
|
+
Rake::GemPackageTask.new(gem_spec) do |p|
|
578
|
+
p.package_dir = package_dir
|
579
|
+
p.need_tar_gz = true
|
580
|
+
p.need_zip = true
|
581
|
+
end
|
582
|
+
|
583
|
+
Rake::Task[:package].comment = nil
|
584
|
+
Rake::Task[:repackage].comment = nil
|
585
|
+
Rake::Task[:clobber_package].comment = nil
|
586
|
+
Rake::Task[:gem].comment = nil
|
587
|
+
|
588
|
+
namespace :package do
|
589
|
+
desc "Build distributable packages under #{package_dir}"
|
590
|
+
task :build => :package
|
591
|
+
|
592
|
+
desc "Rebuild distributable packages..."
|
593
|
+
task :rebuild => :repackage
|
594
|
+
|
595
|
+
desc "Remove most recent package files"
|
596
|
+
task :clean => :clobber_package
|
597
|
+
|
598
|
+
desc "Dump package manifest to STDOUT"
|
599
|
+
task :manifest do |t|
|
600
|
+
puts package_files.sort.join("\n")
|
601
|
+
end
|
602
|
+
|
603
|
+
desc "Install #{package_basename} (requires sudo)"
|
604
|
+
task :install => package_path do |t|
|
605
|
+
sh "sudo gem install #{package_path}"
|
606
|
+
end
|
607
|
+
|
608
|
+
desc "Uninstall #{package_basename} (requires sudo)"
|
609
|
+
task :uninstall do |t|
|
610
|
+
sh "sudo gem uninstall #{package_name} --version #{version}"
|
611
|
+
end
|
612
|
+
end
|
613
|
+
end
|
614
|
+
|
615
|
+
# Doc Tasks ===============================================================
|
616
|
+
|
617
|
+
def define_doc_tasks
|
618
|
+
desc "Remove all generated documentation"
|
619
|
+
task 'doc:clean'
|
620
|
+
|
621
|
+
desc "Build all documentation under #{doc_dir}"
|
622
|
+
task 'doc:build'
|
623
|
+
|
624
|
+
desc "Rebuild all documentation ..."
|
625
|
+
task 'doc:rebuild'
|
626
|
+
|
627
|
+
task :clean => 'doc:clean'
|
628
|
+
task :build => 'doc:build'
|
629
|
+
task :rebuild => 'doc:rebuild'
|
630
|
+
task :doc => 'doc:build'
|
631
|
+
end
|
632
|
+
|
633
|
+
def rdoc_options
|
634
|
+
[
|
635
|
+
"--title", "#{project_name} API Documentation",
|
636
|
+
'--extension', 'rake=rb',
|
637
|
+
'--line-numbers',
|
638
|
+
'--inline-source',
|
639
|
+
'--tab-width=4'
|
640
|
+
]
|
641
|
+
end
|
642
|
+
|
643
|
+
def define_rdoc_tasks
|
644
|
+
namespace :doc do
|
645
|
+
Rake::RDocTask.new do |r|
|
646
|
+
r.rdoc_dir = rdoc_dir
|
647
|
+
r.main = project_name if project_name =~ /::/
|
648
|
+
r.title = "#{project_name} API Documentation"
|
649
|
+
r.rdoc_files.add lib_files
|
650
|
+
r.options = rdoc_options
|
651
|
+
end
|
652
|
+
end
|
653
|
+
|
654
|
+
Rake::Task['doc:rdoc'].comment = nil
|
655
|
+
Rake::Task['doc:rerdoc'].comment = nil
|
656
|
+
Rake::Task['doc:clobber_rdoc'].comment = nil
|
657
|
+
|
658
|
+
task 'doc:api:rebuild' => 'doc:rerdoc'
|
659
|
+
task 'doc:api:clean' => 'doc:clobber_rdoc'
|
660
|
+
|
661
|
+
desc "Build API / RDoc under #{rdoc_dir}"
|
662
|
+
task 'doc:api' => 'doc:rdoc'
|
663
|
+
|
664
|
+
task 'doc:clean' => 'doc:api:clean'
|
665
|
+
task 'doc:build' => 'doc:api'
|
666
|
+
task 'doc:rebuild' => 'doc:api:rebuild'
|
667
|
+
end
|
668
|
+
|
669
|
+
# AsciiDoc Tasks ==========================================================
|
670
|
+
|
671
|
+
def asciidoc_available?
|
672
|
+
@asciidoc_available ||=
|
673
|
+
system 'asciidoc --version > /dev/null 2>&1'
|
674
|
+
end
|
675
|
+
|
676
|
+
# Attributes passed to asciidoc for use in documents.
|
677
|
+
def asciidoc_attributes
|
678
|
+
{ 'author' => author_name,
|
679
|
+
'email' => author_email,
|
680
|
+
'project-name' => project_name,
|
681
|
+
'package-name' => package_name,
|
682
|
+
'package-version' => version,
|
683
|
+
'package-description' => description,
|
684
|
+
'package-summary' => summary,
|
685
|
+
'project-url' => project_url
|
686
|
+
}.reject{|k,v| v.nil? }
|
687
|
+
end
|
688
|
+
|
689
|
+
# Defines tasks for building HTML documentation with AsciiDoc.
|
690
|
+
def define_asciidoc_tasks
|
691
|
+
if defined?(AsciiDocTasks) && File.exist?("#{doc_dir}/asciidoc.conf") && asciidoc_available?
|
692
|
+
man_pages = FileList["#{doc_dir}/*.[0-9].txt"]
|
693
|
+
articles = FileList["#{doc_dir}/*.txt"] - man_pages
|
694
|
+
desc "Build AsciiDoc under #{doc_dir}"
|
695
|
+
AsciiDocTasks.new('doc:asciidoc') do |t|
|
696
|
+
t.source_dir = doc_dir
|
697
|
+
t.source_files = articles
|
698
|
+
t.doc_type = :article
|
699
|
+
t.config_file = "#{doc_dir}/asciidoc.conf"
|
700
|
+
t.attributes = asciidoc_attributes
|
701
|
+
end
|
702
|
+
AsciiDocTasks.new('doc:asciidoc') do |t|
|
703
|
+
t.source_dir = doc_dir
|
704
|
+
t.source_files = man_pages
|
705
|
+
t.doc_type = :manpage
|
706
|
+
t.config_file = "#{doc_dir}/asciidoc.conf"
|
707
|
+
t.attributes = asciidoc_attributes
|
708
|
+
end
|
709
|
+
else
|
710
|
+
desc "Build AsciiDoc (disabled)"
|
711
|
+
task 'asciidoc'
|
712
|
+
task 'asciidoc:build'
|
713
|
+
task 'asciidoc:clean'
|
714
|
+
task 'asciidoc:rebuild'
|
715
|
+
end
|
716
|
+
task 'doc:build' => 'doc:asciidoc:build'
|
717
|
+
task 'doc:clean' => 'doc:asciidoc:clean'
|
718
|
+
task 'doc:rebuild' => 'doc:asciidoc:rebuild'
|
719
|
+
end
|
720
|
+
|
721
|
+
|
722
|
+
# Publishing ==============================================================
|
723
|
+
|
724
|
+
class RemoteLocation #:nodoc:
|
725
|
+
attr_reader :user, :host, :path
|
726
|
+
def initialize(location)
|
727
|
+
if location =~ /^([\w\.]+):(.+)$/
|
728
|
+
@user, @host, @path = ENV['USER'], $1, $2
|
729
|
+
elsif location =~ /^(\w+)@([\w\.]+):(.+)$/
|
730
|
+
@user, @host, @path = $1, $2, $3
|
731
|
+
else
|
732
|
+
raise ArgumentError, "Invalid remote location: %p" % location
|
733
|
+
end
|
734
|
+
end
|
735
|
+
def to_s
|
736
|
+
"#{user}@#{host}:#{path}"
|
737
|
+
end
|
738
|
+
def inspect
|
739
|
+
to_s.inspect
|
740
|
+
end
|
741
|
+
def self.[](location)
|
742
|
+
if location.respond_to?(:to_str)
|
743
|
+
new(location.to_str)
|
744
|
+
else
|
745
|
+
location
|
746
|
+
end
|
747
|
+
end
|
748
|
+
end
|
749
|
+
|
750
|
+
public
|
751
|
+
|
752
|
+
undef :remote_dist_location=
|
753
|
+
|
754
|
+
def remote_dist_location=(value) #:nodoc:
|
755
|
+
@remote_dist_location = RemoteLocation[value]
|
756
|
+
end
|
757
|
+
|
758
|
+
undef :remote_doc_location=
|
759
|
+
|
760
|
+
def remote_doc_location=(value) #:nodoc:
|
761
|
+
@remote_doc_location = RemoteLocation[value]
|
762
|
+
end
|
763
|
+
|
764
|
+
undef :remote_branch_location=
|
765
|
+
|
766
|
+
def remote_branch_location=(value) #:nodoc:
|
767
|
+
@remote_branch_location = RemoteLocation[value]
|
768
|
+
end
|
769
|
+
|
770
|
+
private
|
771
|
+
|
772
|
+
def remote_index_gem_repository_command #:nodoc:
|
773
|
+
if index_gem_repository_command
|
774
|
+
"ssh #{remote_dist_location.user}@#{remote_dist_location.host}" +
|
775
|
+
"'#{index_gem_repository_command}'"
|
776
|
+
end
|
777
|
+
end
|
778
|
+
|
779
|
+
def rsync_packages_command #:nodoc:
|
780
|
+
"rsync -aP #{packages.join(' ')} #{remote_dist_location}"
|
781
|
+
end
|
782
|
+
|
783
|
+
def publish_packages_command #:nodoc:
|
784
|
+
[ rsync_packages_command, remote_index_gem_repository_command ].compact.join(' && ')
|
785
|
+
end
|
786
|
+
|
787
|
+
def publish_doc_command
|
788
|
+
"rsync -azP #{doc_dir}/ #{remote_doc_location}"
|
789
|
+
end
|
790
|
+
|
791
|
+
def temporary_git_repo_dir
|
792
|
+
".git-#{Process.pid}"
|
793
|
+
end
|
794
|
+
|
795
|
+
def publish_branch_command
|
796
|
+
[
|
797
|
+
"git repack -d",
|
798
|
+
"git clone --bare -l . #{temporary_git_repo_dir}",
|
799
|
+
"git --bare --git-dir=#{temporary_git_repo_dir} update-server-info",
|
800
|
+
"rsync -azP --delete --hard-links #{temporary_git_repo_dir}/ #{remote_branch_location}"
|
801
|
+
].join(' && ')
|
802
|
+
end
|
803
|
+
|
804
|
+
def define_publish_tasks
|
805
|
+
desc 'Publish'
|
806
|
+
task 'publish'
|
807
|
+
|
808
|
+
if remote_dist_location
|
809
|
+
desc "Publish packages to #{remote_dist_location}"
|
810
|
+
task 'publish:packages' => 'package:build' do |t|
|
811
|
+
sh publish_packages_command, :verbose => true
|
812
|
+
end
|
813
|
+
desc 'packages'
|
814
|
+
task 'publish' => 'publish:packages'
|
815
|
+
end
|
816
|
+
|
817
|
+
if remote_branch_location && File.exist?('.git')
|
818
|
+
desc "Publish branch to #{remote_branch_location}"
|
819
|
+
task 'publish:branch' => '.git' do |t|
|
820
|
+
sh publish_branch_command, :verbose => true do |res,ok|
|
821
|
+
rm_rf temporary_git_repo_dir
|
822
|
+
fail "publish git repository failed." if ! ok
|
823
|
+
end
|
824
|
+
end
|
825
|
+
desc 'branch'
|
826
|
+
task 'publish' => 'publish:branch'
|
827
|
+
end
|
828
|
+
|
829
|
+
if remote_doc_location
|
830
|
+
desc "Publish doc to #{remote_doc_location}"
|
831
|
+
task 'publish:doc' => 'doc:build' do |t|
|
832
|
+
sh publish_doc_command, :verbose => true
|
833
|
+
end
|
834
|
+
desc 'doc'
|
835
|
+
task 'publish' => 'publish:doc'
|
836
|
+
end
|
837
|
+
|
838
|
+
end
|
839
|
+
|
840
|
+
# Tags ====================================================================
|
841
|
+
|
842
|
+
def define_tags_tasks
|
843
|
+
|
844
|
+
task :ctags => (lib_files + tests) do |t|
|
845
|
+
args = [
|
846
|
+
"--recurse",
|
847
|
+
"-f tags",
|
848
|
+
"--extra=+f",
|
849
|
+
"--links=yes",
|
850
|
+
"--tag-relative=yes",
|
851
|
+
"--totals=yes",
|
852
|
+
"--regex-ruby='/.*alias(_method)?[[:space:]]+:([[:alnum:]_=!?]+),?[[:space:]]+:([[:alnum:]_=!]+)/\\2/f/'"
|
853
|
+
].join(' ')
|
854
|
+
sh 'ctags', *args
|
855
|
+
end
|
856
|
+
|
857
|
+
CLEAN.include [ 'tags' ]
|
858
|
+
|
859
|
+
task :ftags => FileList['Rakefile', '**/*.{r*,conf,c,h,ya?ml}'] do |t|
|
860
|
+
pattern = '.*(Rakefile|\.(rb|rhtml|rtxt|conf|c|h|ya?ml))'
|
861
|
+
command = [
|
862
|
+
"find",
|
863
|
+
"-regex '#{pattern.gsub(/[()|]/){|m| '\\' + m}}'",
|
864
|
+
"-type f",
|
865
|
+
"-printf '%f\t%p\t1\n'",
|
866
|
+
"| sort -f > #{t.name}"
|
867
|
+
].join(' ')
|
868
|
+
sh command
|
869
|
+
end
|
870
|
+
|
871
|
+
CLEAN.include [ '.ftags' ]
|
872
|
+
|
873
|
+
desc "Generate tags file with ctags"
|
874
|
+
task :tags => [ :ctags, :ftags ]
|
875
|
+
end
|
876
|
+
|
877
|
+
end
|
878
|
+
|
879
|
+
class String
|
880
|
+
|
881
|
+
# The number of characters of leading indent. The line with the least amount
|
882
|
+
# of indent wins:
|
883
|
+
#
|
884
|
+
# text = <<-end
|
885
|
+
# this is line 1
|
886
|
+
# this is line 2
|
887
|
+
# this is line 3
|
888
|
+
# this is line 4
|
889
|
+
# end
|
890
|
+
# assert 2 == text.leading_indent
|
891
|
+
#
|
892
|
+
# This is used by the #strip_leading_indent method.
|
893
|
+
def leading_indentation_length
|
894
|
+
infinity = 1.0 / 0.0
|
895
|
+
inject infinity do |indent,line|
|
896
|
+
case line
|
897
|
+
when /^[^\s]/
|
898
|
+
# return immediately on first non-indented line
|
899
|
+
return 0
|
900
|
+
when /^\s+$/
|
901
|
+
# ignore lines that are all whitespace
|
902
|
+
next indent
|
903
|
+
else
|
904
|
+
this = line[/^[\t ]+/].length
|
905
|
+
this < indent ? this : indent
|
906
|
+
end
|
907
|
+
end
|
908
|
+
end
|
909
|
+
|
910
|
+
# Removes the same amount of leading space from each line in the string. The
|
911
|
+
# number of spaces removed is based on the #leading_indent_length.
|
912
|
+
#
|
913
|
+
# This is most useful for reformatting inline strings created with here docs.
|
914
|
+
def strip_leading_indentation
|
915
|
+
if (indent = leading_indentation_length) > 0
|
916
|
+
collect { |line| line =~ /^[\s]+[^\s]/ ? line[indent..-1] : line }.join
|
917
|
+
else
|
918
|
+
self
|
919
|
+
end
|
920
|
+
end
|
921
|
+
|
922
|
+
end
|