date-performance 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|