inochi 0.2.0 → 0.3.0
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/LICENSE +2 -0
- data/bin/inochi +30 -29
- data/doc/api/Inochi.html +439 -323
- data/doc/api/Inochi/Manual.html +230 -0
- data/doc/api/Inochi/Phrases.html +409 -0
- data/doc/api/Inochi/Version.html +222 -0
- data/doc/api/all-methods.html +97 -9
- data/doc/api/all-namespaces.html +7 -1
- data/doc/api/readme.html +3 -0
- data/doc/history.erb +50 -20
- data/doc/index.xhtml +355 -141
- data/doc/intro.erb +14 -14
- data/doc/setup.erb +8 -7
- data/doc/usage.erb +110 -5
- data/lib/inochi.rb +14 -3
- data/lib/inochi/book.rb +84 -0
- data/lib/inochi/init.rb +239 -0
- data/lib/inochi/main.rb +75 -0
- data/lib/inochi/rake.rb +777 -0
- data/lib/inochi/util.rb +77 -0
- data/test/{inochi/inochi.rb → inochi.rb} +0 -0
- metadata +32 -5
- data/lib/inochi/inochi.rb +0 -1064
data/lib/inochi/main.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
##
|
2
|
+
# Provides a common configuration for the main project executable:
|
3
|
+
#
|
4
|
+
# * The program description (the sequence of non-blank lines at the
|
5
|
+
# top of the file in which this method is invoked) is properly
|
6
|
+
# formatted and displayed at the top of program's help information.
|
7
|
+
#
|
8
|
+
# * The program version information is fetched from the project module
|
9
|
+
# and formatted in YAML fashion for easy consumption by other tools.
|
10
|
+
#
|
11
|
+
# * A list of command-line options is displayed at
|
12
|
+
# the bottom of the program's help information.
|
13
|
+
#
|
14
|
+
# It is assumed that this method is invoked from only within
|
15
|
+
# the main project executable (in the project bin/ directory).
|
16
|
+
#
|
17
|
+
# @param [Symbol] project_symbol
|
18
|
+
# Name of the Ruby constant which serves
|
19
|
+
# as a namespace for the entire project.
|
20
|
+
#
|
21
|
+
# @param trollop_args
|
22
|
+
# Optional arguments for Trollop::options().
|
23
|
+
#
|
24
|
+
# @param trollop_config
|
25
|
+
# Optional block argument for Trollop::options().
|
26
|
+
#
|
27
|
+
# @return The result of Trollop::options().
|
28
|
+
#
|
29
|
+
def Inochi.main project_symbol, *trollop_args, &trollop_config
|
30
|
+
program_file = first_caller_file
|
31
|
+
program_name = File.basename(program_file)
|
32
|
+
program_home = File.dirname(File.dirname(program_file))
|
33
|
+
|
34
|
+
# load the project module
|
35
|
+
require File.join(program_home, 'lib', program_name)
|
36
|
+
project_module = fetch_project_module(project_symbol)
|
37
|
+
|
38
|
+
# parse command-line options
|
39
|
+
require 'trollop'
|
40
|
+
|
41
|
+
options = Trollop.options(*trollop_args) do
|
42
|
+
|
43
|
+
# show project description
|
44
|
+
text "#{project_module::PROJECT} - #{project_module::TAGLINE}"
|
45
|
+
text ''
|
46
|
+
|
47
|
+
# show program description
|
48
|
+
text File.read(program_file)[/\A.*?^$\n/m]. # grab the header
|
49
|
+
gsub(/^# ?/, ''). # strip the comment markers
|
50
|
+
sub(/\A!.*?\n/, '').lstrip # omit the shebang line
|
51
|
+
text ''
|
52
|
+
|
53
|
+
instance_eval(&trollop_config) if trollop_config
|
54
|
+
|
55
|
+
# show version information
|
56
|
+
version %w[PROJECT VERSION RELEASE WEBSITE INSTALL].map {|c|
|
57
|
+
"#{c.downcase}: #{project_module.const_get c}"
|
58
|
+
}.join("\n")
|
59
|
+
|
60
|
+
opt :manual, 'Show the user manual'
|
61
|
+
opt :locale, 'Set preferred language', :type => :string
|
62
|
+
end
|
63
|
+
|
64
|
+
if options[:manual]
|
65
|
+
require 'launchy'
|
66
|
+
Launchy::Browser.run "#{project_module::INSTALL}/doc/index.xhtml"
|
67
|
+
exit
|
68
|
+
end
|
69
|
+
|
70
|
+
if locale = options[:locale]
|
71
|
+
project_module::PHRASES.locale = locale
|
72
|
+
end
|
73
|
+
|
74
|
+
options
|
75
|
+
end
|
data/lib/inochi/rake.rb
ADDED
@@ -0,0 +1,777 @@
|
|
1
|
+
##
|
2
|
+
# Provides Rake tasks for packaging, publishing, and announcing your project.
|
3
|
+
#
|
4
|
+
# * An AUTHORS constant (which has the form "[[name, info]]"
|
5
|
+
# where "name" is the name of a copyright holder and "info" is
|
6
|
+
# their contact information) is added to the project module.
|
7
|
+
#
|
8
|
+
# Unless this information is supplied via the :authors option,
|
9
|
+
# it is automatically extracted from copyright notices in the
|
10
|
+
# project license file, where the first copyright notice is
|
11
|
+
# expected to correspond to the primary project maintainer.
|
12
|
+
#
|
13
|
+
# Copyright notices must be in the following form:
|
14
|
+
#
|
15
|
+
# Copyright YEAR HOLDER <EMAIL>
|
16
|
+
#
|
17
|
+
# Where HOLDER is the name of the copyright holder, YEAR is the year
|
18
|
+
# when the copyright holder first began working on the project, and
|
19
|
+
# EMAIL is (optional) the email address of the copyright holder.
|
20
|
+
#
|
21
|
+
# @param [Symbol] project_symbol
|
22
|
+
# Name of the Ruby constant which serves
|
23
|
+
# as a namespace for the entire project.
|
24
|
+
#
|
25
|
+
# @param [Hash] options
|
26
|
+
# Additional method parameters, which are all optional:
|
27
|
+
#
|
28
|
+
# [Array] :authors =>
|
29
|
+
# A list of project authors and their contact information. This
|
30
|
+
# list must have the form "[[name, info]]" where "name" is the name
|
31
|
+
# of a project author and "info" is their contact information.
|
32
|
+
#
|
33
|
+
# [String] :license_file =>
|
34
|
+
# Path (relative to the main project directory which contains the
|
35
|
+
# project Rakefile) to the file which contains the project license.
|
36
|
+
#
|
37
|
+
# The default value is "LICENSE".
|
38
|
+
#
|
39
|
+
# [String] :logins_file =>
|
40
|
+
# Path to the YAML file which contains login
|
41
|
+
# information for publishing release announcements.
|
42
|
+
#
|
43
|
+
# The default value is "~/.config/inochi/logins.yaml"
|
44
|
+
# where "~" is the path to your home directory.
|
45
|
+
#
|
46
|
+
# [String] :rubyforge_project =>
|
47
|
+
# Name of the RubyForge project where
|
48
|
+
# release packages will be published.
|
49
|
+
#
|
50
|
+
# The default value is the value of the PROGRAM constant.
|
51
|
+
#
|
52
|
+
# [String] :rubyforge_section =>
|
53
|
+
# Name of the RubyForge project's File Release System
|
54
|
+
# section where release packages will be published.
|
55
|
+
#
|
56
|
+
# The default value is the value of the :rubyforge_project parameter.
|
57
|
+
#
|
58
|
+
# [String] :raa_project =>
|
59
|
+
# Name of the RAA (Ruby Application Archive) entry for this project.
|
60
|
+
#
|
61
|
+
# The default value is the value of the PROGRAM constant.
|
62
|
+
#
|
63
|
+
# [String] :upload_target =>
|
64
|
+
# Where to upload the project documentation.
|
65
|
+
# See "destination" in the rsync manual.
|
66
|
+
#
|
67
|
+
# The default value is nil.
|
68
|
+
#
|
69
|
+
# [String] :upload_delete =>
|
70
|
+
# Delete unknown files at the upload target location?
|
71
|
+
#
|
72
|
+
# The default value is false.
|
73
|
+
#
|
74
|
+
# [Array] :upload_options =>
|
75
|
+
# Additional command-line arguments to the rsync command.
|
76
|
+
#
|
77
|
+
# The default value is an empty array.
|
78
|
+
#
|
79
|
+
# @param gem_config
|
80
|
+
# Block that is passed to Gem::specification.new()
|
81
|
+
# for additonal gem configuration.
|
82
|
+
#
|
83
|
+
# @yieldparam [Gem::Specification] gem_spec the gem specification
|
84
|
+
#
|
85
|
+
def Inochi.rake project_symbol, options = {}, &gem_config
|
86
|
+
program_file = first_caller_file
|
87
|
+
program_home = File.dirname(program_file)
|
88
|
+
|
89
|
+
# load the project module
|
90
|
+
program_name = File.basename(program_home)
|
91
|
+
project_libs = File.join('lib', program_name)
|
92
|
+
|
93
|
+
require project_libs
|
94
|
+
project_module = fetch_project_module(project_symbol)
|
95
|
+
|
96
|
+
# supply default options
|
97
|
+
options[:rubyforge_project] ||= program_name
|
98
|
+
options[:rubyforge_section] ||= program_name
|
99
|
+
options[:raa_project] ||= program_name
|
100
|
+
|
101
|
+
options[:license_file] ||= 'LICENSE'
|
102
|
+
options[:logins_file] ||= File.join(
|
103
|
+
ENV['HOME'] || ENV['USERPROFILE'] || '.',
|
104
|
+
'.config', 'inochi', 'logins.yaml'
|
105
|
+
)
|
106
|
+
|
107
|
+
options[:upload_delete] ||= false
|
108
|
+
options[:upload_options] ||= []
|
109
|
+
|
110
|
+
# add AUTHORS constant to the project module
|
111
|
+
copyright_holders = options[:authors] ||
|
112
|
+
File.read(options[:license_file]).
|
113
|
+
scan(/Copyright.*?\d+\s+(.*)/).flatten.
|
114
|
+
map {|s| (s =~ /\s*<(.*?)>/) ? [$`, $1] : [s, ''] }
|
115
|
+
|
116
|
+
project_module.const_set :AUTHORS, copyright_holders
|
117
|
+
|
118
|
+
require 'rake/clean'
|
119
|
+
|
120
|
+
hide_rake_task = lambda do |name|
|
121
|
+
Rake::Task[name].instance_variable_set :@comment, nil
|
122
|
+
end
|
123
|
+
|
124
|
+
# translation
|
125
|
+
directory 'lang'
|
126
|
+
|
127
|
+
lang_dump_deps = 'lang'
|
128
|
+
lang_dump_file = 'lang/phrases.yaml'
|
129
|
+
|
130
|
+
desc 'Extract language phrases for translation.'
|
131
|
+
task 'lang:dump' => lang_dump_file
|
132
|
+
|
133
|
+
file lang_dump_file => lang_dump_deps do
|
134
|
+
ENV['dump_lang_phrases'] = '1'
|
135
|
+
Rake::Task[:test].invoke
|
136
|
+
end
|
137
|
+
|
138
|
+
lang_conv_delim = "\n" * 5
|
139
|
+
|
140
|
+
desc 'Translate extracted language phrases (from=LANGUAGE_CODE).'
|
141
|
+
task 'lang:conv' => lang_dump_file do |t|
|
142
|
+
require 'babelfish'
|
143
|
+
|
144
|
+
unless
|
145
|
+
src_lang = ENV['from'] and
|
146
|
+
BabelFish::LANGUAGE_CODES.include? src_lang
|
147
|
+
then
|
148
|
+
message = ['The "from" parameter must be specified as follows:']
|
149
|
+
|
150
|
+
BabelFish::LANGUAGE_CODES.each do |c|
|
151
|
+
n = BabelFish::LANGUAGE_NAMES[c]
|
152
|
+
message << " rake #{t.name} from=#{c} # from #{n}"
|
153
|
+
end
|
154
|
+
|
155
|
+
raise ArgumentError, message.join("\n")
|
156
|
+
end
|
157
|
+
|
158
|
+
begin
|
159
|
+
require 'yaml'
|
160
|
+
phrases = YAML.load_file(lang_dump_file).keys.sort
|
161
|
+
rescue
|
162
|
+
warn "Could not load phrases from #{lang_dump_file.inspect}"
|
163
|
+
raise
|
164
|
+
end
|
165
|
+
|
166
|
+
src_lang_name = BabelFish::LANGUAGE_NAMES[src_lang]
|
167
|
+
|
168
|
+
BabelFish::LANGUAGE_PAIRS[src_lang].each do |dst_lang|
|
169
|
+
dst_file = "lang/#{dst_lang}.yaml"
|
170
|
+
dst_lang_name = BabelFish::LANGUAGE_NAMES[dst_lang]
|
171
|
+
|
172
|
+
puts "Translating phrases from #{src_lang_name} into #{dst_lang_name} as #{dst_file.inspect}"
|
173
|
+
|
174
|
+
translations = BabelFish.translate(
|
175
|
+
phrases.join(lang_conv_delim), src_lang, dst_lang
|
176
|
+
).split(lang_conv_delim)
|
177
|
+
|
178
|
+
File.open(dst_file, 'w') do |f|
|
179
|
+
f.puts "# #{dst_lang} (#{dst_lang_name})"
|
180
|
+
|
181
|
+
phrases.zip(translations).each do |a, b|
|
182
|
+
f.puts "#{a}: #{b}"
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# testing
|
189
|
+
desc 'Run all unit tests.'
|
190
|
+
task :test do
|
191
|
+
ruby '-w', '-I.', '-Ilib', '-r', program_name, '-e', %q{
|
192
|
+
# dump language phrases *after* exercising all code (and
|
193
|
+
# thereby populating the phrases cache) in the project
|
194
|
+
at_exit do
|
195
|
+
if ENV['dump_lang_phrases'] == '1'
|
196
|
+
file = %s
|
197
|
+
list = %s::PHRASES.phrases
|
198
|
+
data = list.map {|s| s + ':' }.join("\n")
|
199
|
+
|
200
|
+
File.write file, data
|
201
|
+
|
202
|
+
puts "Extracted #{list.length} language phrases into #{file.inspect}"
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# set title of test suite
|
207
|
+
$0 = File.basename(Dir.pwd)
|
208
|
+
|
209
|
+
require 'minitest/unit'
|
210
|
+
require 'minitest/spec'
|
211
|
+
require 'minitest/mock'
|
212
|
+
MiniTest::Unit.autorun
|
213
|
+
|
214
|
+
Dir['test/**/*.rb'].sort.each do |test|
|
215
|
+
unit = test.sub('test/', 'lib/')
|
216
|
+
|
217
|
+
if File.exist? unit
|
218
|
+
# strip file extension because require()
|
219
|
+
# does not normalize its input and it
|
220
|
+
# will think that the two paths (with &
|
221
|
+
# without file extension) are different
|
222
|
+
unit_path = unit.sub(/\.rb$/, '').sub('lib/', '')
|
223
|
+
test_path = test.sub(/\.rb$/, '')
|
224
|
+
|
225
|
+
require unit_path
|
226
|
+
require test_path
|
227
|
+
else
|
228
|
+
warn "Skipped test #{test.inspect} because it lacks a corresponding #{unit.inspect} unit."
|
229
|
+
end
|
230
|
+
end
|
231
|
+
} % [lang_dump_file.inspect, project_symbol]
|
232
|
+
end
|
233
|
+
|
234
|
+
# documentation
|
235
|
+
desc 'Build all documentation.'
|
236
|
+
task :doc => %w[ doc:api doc:man ]
|
237
|
+
|
238
|
+
# user manual
|
239
|
+
doc_man_src = 'doc/index.erb'
|
240
|
+
doc_man_dst = 'doc/index.xhtml'
|
241
|
+
doc_man_deps = FileList['doc/*.erb']
|
242
|
+
|
243
|
+
doc_man_doc = nil
|
244
|
+
task :doc_man_doc => doc_man_src do
|
245
|
+
unless doc_man_doc
|
246
|
+
require 'erbook' unless defined? ERBook
|
247
|
+
|
248
|
+
doc_man_txt = File.read(doc_man_src)
|
249
|
+
doc_man_doc = ERBook::Document.new(:xhtml, doc_man_txt, doc_man_src, :unindent => true)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
desc 'Build the user manual.'
|
254
|
+
task 'doc:man' => doc_man_dst
|
255
|
+
|
256
|
+
file doc_man_dst => doc_man_deps do
|
257
|
+
Rake::Task[:doc_man_doc].invoke
|
258
|
+
File.write doc_man_dst, doc_man_doc
|
259
|
+
end
|
260
|
+
|
261
|
+
CLOBBER.include doc_man_dst
|
262
|
+
|
263
|
+
# API reference
|
264
|
+
doc_api_dst = 'doc/api'
|
265
|
+
|
266
|
+
desc 'Build API reference.'
|
267
|
+
task 'doc:api' => doc_api_dst
|
268
|
+
|
269
|
+
require 'yard'
|
270
|
+
YARD::Rake::YardocTask.new doc_api_dst do |t|
|
271
|
+
t.options.push '--protected',
|
272
|
+
'--output-dir', doc_api_dst,
|
273
|
+
'--readme', options[:license_file]
|
274
|
+
|
275
|
+
task doc_api_dst => options[:license_file]
|
276
|
+
end
|
277
|
+
|
278
|
+
hide_rake_task[doc_api_dst]
|
279
|
+
|
280
|
+
CLEAN.include '.yardoc'
|
281
|
+
CLOBBER.include doc_api_dst
|
282
|
+
|
283
|
+
# announcements
|
284
|
+
desc 'Build all release announcements.'
|
285
|
+
task :ann => %w[ ann:feed ann:html ann:text ann:mail ]
|
286
|
+
|
287
|
+
# it has long been a tradition to use an "[ANN]" prefix
|
288
|
+
# when announcing things on the ruby-talk mailing list
|
289
|
+
ann_prefix = '[ANN] '
|
290
|
+
ann_subject = ann_prefix + project_module::DISPLAY
|
291
|
+
ann_project = ann_prefix + project_module::PROJECT
|
292
|
+
|
293
|
+
# fetch the project summary from user manual
|
294
|
+
ann_nfo_doc = nil
|
295
|
+
task :ann_nfo_doc => :doc_man_doc do
|
296
|
+
ann_nfo_doc = $project_summary_node
|
297
|
+
end
|
298
|
+
|
299
|
+
# fetch release notes from user manual
|
300
|
+
ann_rel_doc = nil
|
301
|
+
task :ann_rel_doc => :doc_man_doc do
|
302
|
+
unless ann_rel_doc
|
303
|
+
if parent = $project_history_node
|
304
|
+
if child = parent.children.first
|
305
|
+
ann_rel_doc = child
|
306
|
+
else
|
307
|
+
raise 'The "project_history" node in the user manual lacks child nodes.'
|
308
|
+
end
|
309
|
+
else
|
310
|
+
raise 'The user manual lacks a "project_history" node.'
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
# build release notes in HTML and plain text
|
316
|
+
# converts the given HTML into plain text. we do this using
|
317
|
+
# lynx because (1) it outputs a list of all hyperlinks used
|
318
|
+
# in the HTML document and (2) it runs on all major platforms
|
319
|
+
convert_html_to_text = lambda do |html|
|
320
|
+
require 'tempfile'
|
321
|
+
|
322
|
+
begin
|
323
|
+
# lynx's -dump option requires a .html file
|
324
|
+
tmp_file = Tempfile.new(Inochi::PROGRAM).path + '.html'
|
325
|
+
|
326
|
+
File.write tmp_file, html
|
327
|
+
text = `lynx -dump #{tmp_file} -width 70`
|
328
|
+
ensure
|
329
|
+
File.delete tmp_file
|
330
|
+
end
|
331
|
+
|
332
|
+
# improve readability of list items
|
333
|
+
# by adding a blank line between them
|
334
|
+
text.gsub! %r{(\r?\n)( +\* \S)}, '\1\1\2'
|
335
|
+
|
336
|
+
text
|
337
|
+
end
|
338
|
+
|
339
|
+
# binds relative addresses in the given HTML to the project docsite
|
340
|
+
resolve_html_links = lambda do |html|
|
341
|
+
# resolve relative URLs into absolute URLs
|
342
|
+
# see http://en.wikipedia.org/wiki/URI_scheme#Generic_syntax
|
343
|
+
require 'addressable/uri'
|
344
|
+
uri = Addressable::URI.parse(project_module::DOCSITE)
|
345
|
+
doc_url = uri.to_s
|
346
|
+
dir_url = uri.path =~ %r{/$|^$} ? doc_url : File.dirname(doc_url)
|
347
|
+
|
348
|
+
html.to_s.gsub %r{(href=|src=)(.)(.*?)(\2)} do |match|
|
349
|
+
a, b = $1 + $2, $3.to_s << $4
|
350
|
+
|
351
|
+
case $3
|
352
|
+
when %r{^[[:alpha:]][[:alnum:]\+\.\-]*://} # already absolute
|
353
|
+
match
|
354
|
+
|
355
|
+
when /^#/
|
356
|
+
a << File.join(doc_url, b)
|
357
|
+
|
358
|
+
else
|
359
|
+
a << File.join(dir_url, b)
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
ann_html = nil
|
365
|
+
task :ann_html => [:doc_man_doc, :ann_nfo_doc, :ann_rel_doc] do
|
366
|
+
unless ann_html
|
367
|
+
ann_html = %{
|
368
|
+
<center>
|
369
|
+
<h1>#{project_module::DISPLAY}</h1>
|
370
|
+
<p>#{project_module::TAGLINE}</p>
|
371
|
+
<p>#{project_module::WEBSITE}</p>
|
372
|
+
</center>
|
373
|
+
#{ann_nfo_doc}
|
374
|
+
#{ann_rel_doc}
|
375
|
+
}
|
376
|
+
|
377
|
+
# remove heading navigation menus
|
378
|
+
ann_html.gsub! %r{<div class="nav"[^>]*>(.*?)</div>}, ''
|
379
|
+
|
380
|
+
# remove latex-style heading numbers
|
381
|
+
ann_html.gsub! %r"(<(h\d)[^>]*>).+?(?: ){2}(.+?)(</\2>)"m, '\1\3\4'
|
382
|
+
|
383
|
+
ann_html = resolve_html_links[ann_html]
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
ann_text = nil
|
388
|
+
task :ann_text => :ann_html do
|
389
|
+
unless ann_text
|
390
|
+
ann_text = convert_html_to_text[ann_html]
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
ann_nfo_text = nil
|
395
|
+
task :ann_nfo_text => :ann_nfo_doc do
|
396
|
+
unless ann_nfo_text
|
397
|
+
ann_nfo_html = resolve_html_links[ann_nfo_doc]
|
398
|
+
ann_nfo_text = convert_html_to_text[ann_nfo_html]
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
# HTML
|
403
|
+
ann_html_dst = 'ANN.html'
|
404
|
+
|
405
|
+
desc "Build HTML announcement: #{ann_html_dst}"
|
406
|
+
task 'ann:html' => ann_html_dst
|
407
|
+
|
408
|
+
file ann_html_dst => doc_man_deps do
|
409
|
+
Rake::Task[:ann_html].invoke
|
410
|
+
File.write ann_html_dst, ann_html
|
411
|
+
end
|
412
|
+
|
413
|
+
CLEAN.include ann_html_dst
|
414
|
+
|
415
|
+
# RSS feed
|
416
|
+
ann_feed_dst = 'doc/ann.xml'
|
417
|
+
|
418
|
+
desc "Build RSS announcement: #{ann_feed_dst}"
|
419
|
+
task 'ann:feed' => ann_feed_dst
|
420
|
+
|
421
|
+
file ann_feed_dst => doc_man_deps do
|
422
|
+
require 'time'
|
423
|
+
require 'rss/maker'
|
424
|
+
|
425
|
+
feed = RSS::Maker.make('2.0') do |feed|
|
426
|
+
feed.channel.title = ann_project
|
427
|
+
feed.channel.link = project_module::WEBSITE
|
428
|
+
feed.channel.description = project_module::TAGLINE
|
429
|
+
|
430
|
+
Rake::Task[:ann_rel_doc].invoke
|
431
|
+
Rake::Task[:ann_html].invoke
|
432
|
+
|
433
|
+
item = feed.items.new_item
|
434
|
+
item.title = ann_rel_doc.title
|
435
|
+
item.link = project_module::DOCSITE + '#' + ann_rel_doc.here_frag
|
436
|
+
item.date = Time.parse(item.title)
|
437
|
+
item.description = ann_html
|
438
|
+
end
|
439
|
+
|
440
|
+
File.write ann_feed_dst, feed
|
441
|
+
end
|
442
|
+
|
443
|
+
CLOBBER.include ann_feed_dst
|
444
|
+
|
445
|
+
# plain text
|
446
|
+
ann_text_dst = 'ANN.txt'
|
447
|
+
|
448
|
+
desc "Build plain text announcement: #{ann_text_dst}"
|
449
|
+
task 'ann:text' => ann_text_dst
|
450
|
+
|
451
|
+
file ann_text_dst => doc_man_deps do
|
452
|
+
Rake::Task[:ann_text].invoke
|
453
|
+
File.write ann_text_dst, ann_text
|
454
|
+
end
|
455
|
+
|
456
|
+
CLEAN.include ann_text_dst
|
457
|
+
|
458
|
+
# e-mail
|
459
|
+
ann_mail_dst = 'ANN.eml'
|
460
|
+
|
461
|
+
desc "Build e-mail announcement: #{ann_mail_dst}"
|
462
|
+
task 'ann:mail' => ann_mail_dst
|
463
|
+
|
464
|
+
file ann_mail_dst => doc_man_deps do
|
465
|
+
File.open ann_mail_dst, 'w' do |f|
|
466
|
+
require 'time'
|
467
|
+
f.puts "Date: #{Time.now.rfc822}"
|
468
|
+
|
469
|
+
f.puts 'To: ruby-talk@ruby-lang.org'
|
470
|
+
f.puts 'From: "%s" <%s>' % project_module::AUTHORS.first
|
471
|
+
f.puts "Subject: #{ann_subject}"
|
472
|
+
|
473
|
+
Rake::Task[:ann_text].invoke
|
474
|
+
f.puts '', ann_text
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
CLEAN.include ann_mail_dst
|
479
|
+
|
480
|
+
# packaging
|
481
|
+
desc 'Build a release.'
|
482
|
+
task :pak => [:clobber, :doc] do
|
483
|
+
sh $0, 'package'
|
484
|
+
end
|
485
|
+
CLEAN.include 'pkg'
|
486
|
+
|
487
|
+
# ruby gem
|
488
|
+
require 'rake/gempackagetask'
|
489
|
+
|
490
|
+
gem = Gem::Specification.new do |gem|
|
491
|
+
authors = project_module::AUTHORS
|
492
|
+
|
493
|
+
if author = authors.first
|
494
|
+
gem.author, gem.email = author
|
495
|
+
end
|
496
|
+
|
497
|
+
if authors.length > 1
|
498
|
+
gem.authors = authors.map {|name, mail| name }
|
499
|
+
end
|
500
|
+
|
501
|
+
gem.rubyforge_project = options[:rubyforge_project]
|
502
|
+
|
503
|
+
# XXX: In theory, `gem.name` should be assigned to
|
504
|
+
# ::PROJECT instead of ::PROGRAM
|
505
|
+
#
|
506
|
+
# In practice, PROJECT may contain non-word
|
507
|
+
# characters and may also contain a mixture
|
508
|
+
# of lowercase and uppercase letters.
|
509
|
+
#
|
510
|
+
# This makes it difficult for people to
|
511
|
+
# install the project gem because they must
|
512
|
+
# remember the exact spelling used in
|
513
|
+
# `gem.name` when running `gem install ____`.
|
514
|
+
#
|
515
|
+
# For example, consider the "RedCloth" gem.
|
516
|
+
#
|
517
|
+
gem.name = project_module::PROGRAM
|
518
|
+
|
519
|
+
gem.version = project_module::VERSION
|
520
|
+
gem.summary = project_module::TAGLINE
|
521
|
+
gem.description = gem.summary
|
522
|
+
gem.homepage = project_module::WEBSITE
|
523
|
+
gem.files = FileList['**/*'].exclude('_darcs') - CLEAN
|
524
|
+
gem.executables = project_module::PROGRAM
|
525
|
+
gem.has_rdoc = true
|
526
|
+
|
527
|
+
unless project_module == Inochi
|
528
|
+
gem.add_dependency 'inochi', Inochi::VERSION.requirement
|
529
|
+
end
|
530
|
+
|
531
|
+
project_module::REQUIRE.each_pair do |gem_name, version_reqs|
|
532
|
+
gem.add_dependency gem_name, *version_reqs
|
533
|
+
end
|
534
|
+
|
535
|
+
# additional configuration is done by user
|
536
|
+
yield gem if gem_config
|
537
|
+
end
|
538
|
+
|
539
|
+
Rake::GemPackageTask.new(gem).define
|
540
|
+
|
541
|
+
# XXX: hide the tasks defined by the above gem packaging library
|
542
|
+
%w[gem package repackage clobber_package].each {|t| hide_rake_task[t] }
|
543
|
+
|
544
|
+
# releasing
|
545
|
+
desc 'Publish a release.'
|
546
|
+
task 'pub' => %w[ pub:pak pub:doc pub:ann ]
|
547
|
+
|
548
|
+
# connect to RubyForge services
|
549
|
+
pub_forge = nil
|
550
|
+
pub_forge_project = options[:rubyforge_project]
|
551
|
+
pub_forge_section = options[:rubyforge_section]
|
552
|
+
|
553
|
+
task :pub_forge do
|
554
|
+
require 'rubyforge'
|
555
|
+
pub_forge = RubyForge.new
|
556
|
+
pub_forge.configure('release_date' => project_module::RELEASE)
|
557
|
+
|
558
|
+
unless pub_forge.autoconfig['group_ids'].key? pub_forge_project
|
559
|
+
raise "The #{pub_forge_project.inspect} project was not recognized by the RubyForge client. Either specify a different RubyForge project by passing the :rubyforge_project option to Inochi.rake(), or ensure that the client is configured correctly (see `rubyforge --help` for help) and try again."
|
560
|
+
end
|
561
|
+
|
562
|
+
pub_forge.login
|
563
|
+
end
|
564
|
+
|
565
|
+
# documentation
|
566
|
+
desc 'Publish documentation to project website.'
|
567
|
+
task 'pub:doc' => [:doc, 'ann:feed'] do
|
568
|
+
target = options[:upload_target]
|
569
|
+
|
570
|
+
unless target
|
571
|
+
require 'addressable/uri'
|
572
|
+
docsite = Addressable::URI.parse(project_module::DOCSITE)
|
573
|
+
|
574
|
+
# provide uploading capability to websites hosted on RubyForge
|
575
|
+
if docsite.host.include? '.rubyforge.org'
|
576
|
+
target = "#{pub_forge.userconfig['username']}@rubyforge.org:#{File.join '/var/www/gforge-projects', options[:rubyforge_project], docsite.path}"
|
577
|
+
end
|
578
|
+
end
|
579
|
+
|
580
|
+
if target
|
581
|
+
cmd = ['rsync', '-auvz', 'doc/', "#{target}/"]
|
582
|
+
cmd.push '--delete' if options[:upload_delete]
|
583
|
+
cmd.concat options[:upload_options]
|
584
|
+
|
585
|
+
p cmd
|
586
|
+
sh(*cmd)
|
587
|
+
end
|
588
|
+
end
|
589
|
+
|
590
|
+
# announcement
|
591
|
+
desc 'Publish all announcements.'
|
592
|
+
task 'pub:ann' => %w[ pub:ann:forge pub:ann:raa pub:ann:talk ]
|
593
|
+
|
594
|
+
# login information
|
595
|
+
ann_logins_file = options[:logins_file]
|
596
|
+
ann_logins = nil
|
597
|
+
|
598
|
+
task :ann_logins do
|
599
|
+
ann_logins = begin
|
600
|
+
require 'yaml'
|
601
|
+
YAML.load_file ann_logins_file
|
602
|
+
rescue => e
|
603
|
+
warn "Could not read login information from #{ann_logins_file.inspect}:"
|
604
|
+
warn e
|
605
|
+
warn "** You will NOT be able to publish release announcements! **"
|
606
|
+
{}
|
607
|
+
end
|
608
|
+
end
|
609
|
+
|
610
|
+
desc 'Announce to RubyForge news.'
|
611
|
+
task 'pub:ann:forge' => :pub_forge do
|
612
|
+
puts 'Announcing to RubyForge news...'
|
613
|
+
|
614
|
+
project = options[:rubyforge_project]
|
615
|
+
|
616
|
+
if group_id = pub_forge.autoconfig['group_ids'][project]
|
617
|
+
# check if this release was already announced
|
618
|
+
require 'mechanize'
|
619
|
+
www = WWW::Mechanize.new
|
620
|
+
page = www.get "http://rubyforge.org/news/?group_id=#{group_id}"
|
621
|
+
|
622
|
+
posts = (page/'//a[starts-with(./@href, "/forum/forum.php?forum_id=")]/text()').map {|e| e.to_s.gsub("\302\240", '').strip }
|
623
|
+
|
624
|
+
already_announced = posts.include? ann_subject
|
625
|
+
|
626
|
+
if already_announced
|
627
|
+
warn 'This release was already announced to RubyForge news, so I will NOT announce it there again.'
|
628
|
+
else
|
629
|
+
# make the announcement
|
630
|
+
Rake::Task[:ann_text].invoke
|
631
|
+
pub_forge.post_news project, ann_subject, ann_text
|
632
|
+
|
633
|
+
puts 'Successfully announced to RubyForge news:', page.uri
|
634
|
+
end
|
635
|
+
else
|
636
|
+
raise "Could not determine the group_id of the #{project.inspect} RubyForge project. Run `rubyforge config` and try again."
|
637
|
+
end
|
638
|
+
end
|
639
|
+
|
640
|
+
desc 'Announce to ruby-talk mailing list.'
|
641
|
+
task 'pub:ann:talk' => :ann_logins do
|
642
|
+
puts 'Announcing to ruby-talk mailing list...'
|
643
|
+
|
644
|
+
host = 'http://ruby-forum.com'
|
645
|
+
ruby_talk = 4 # ruby-talk forum ID
|
646
|
+
|
647
|
+
require 'mechanize'
|
648
|
+
www = WWW::Mechanize.new
|
649
|
+
|
650
|
+
# check if this release was already announced
|
651
|
+
already_announced =
|
652
|
+
begin
|
653
|
+
page = www.get "#{host}/forum/#{ruby_talk}", :filter => %{"#{ann_subject}"}
|
654
|
+
|
655
|
+
posts = (page/'//div[@class="forum"]//a[starts-with(./@href, "/topic/")]/text()').map {|e| e.to_s.strip }
|
656
|
+
posts.include? ann_subject
|
657
|
+
rescue
|
658
|
+
false
|
659
|
+
end
|
660
|
+
|
661
|
+
if already_announced
|
662
|
+
warn 'This release was already announced to the ruby-talk mailing list, so I will NOT announce it there again.'
|
663
|
+
else
|
664
|
+
# log in to RubyForum
|
665
|
+
page = www.get "#{host}/user/login"
|
666
|
+
form = page.forms.first
|
667
|
+
|
668
|
+
if login = ann_logins['www.ruby-forum.com']
|
669
|
+
form['name'] = login['user']
|
670
|
+
form['password'] = login['pass']
|
671
|
+
end
|
672
|
+
|
673
|
+
page = form.click_button # use the first submit button
|
674
|
+
|
675
|
+
if (page/'//a[@href="/user/logout"]').empty?
|
676
|
+
warn "Could not log in to RubyForum using the login information in #{ann_logins_file.inspect}, so I can NOT announce this release to the ruby-talk mailing list."
|
677
|
+
else
|
678
|
+
# make the announcement
|
679
|
+
page = www.get "#{host}/topic/new?forum_id=#{ruby_talk}"
|
680
|
+
form = page.forms.first
|
681
|
+
|
682
|
+
Rake::Task[:ann_text].invoke
|
683
|
+
form['post[subject]'] = ann_subject
|
684
|
+
form['post[text]'] = ann_text
|
685
|
+
|
686
|
+
form.checkboxes.first.check # enable email notification
|
687
|
+
page = form.submit
|
688
|
+
|
689
|
+
errors = [page/'//div[@class="error"]/text()'].flatten
|
690
|
+
if errors.empty?
|
691
|
+
puts 'Successfully announced to ruby-talk mailing list:', page.uri
|
692
|
+
else
|
693
|
+
warn 'Could not announce to ruby-talk mailing list:'
|
694
|
+
warn errors.join("\n")
|
695
|
+
end
|
696
|
+
end
|
697
|
+
end
|
698
|
+
end
|
699
|
+
|
700
|
+
desc 'Announce to RAA (Ruby Application Archive).'
|
701
|
+
task 'pub:ann:raa' => :ann_logins do
|
702
|
+
puts 'Announcing to RAA (Ruby Application Archive)...'
|
703
|
+
|
704
|
+
show_page_error = lambda do |page, message|
|
705
|
+
warn "#{message}, so I can NOT announce this release to RAA:"
|
706
|
+
warn "#{(page/'h2').text} -- #{(page/'p').first.text.strip}"
|
707
|
+
end
|
708
|
+
|
709
|
+
resource = "#{options[:raa_project].inspect} project entry on RAA"
|
710
|
+
|
711
|
+
require 'mechanize'
|
712
|
+
www = WWW::Mechanize.new
|
713
|
+
page = www.get "http://raa.ruby-lang.org/update.rhtml?name=#{options[:raa_project]}"
|
714
|
+
|
715
|
+
if form = page.forms[1]
|
716
|
+
resource << " (owned by #{form.owner.inspect})"
|
717
|
+
|
718
|
+
Rake::Task[:ann_nfo_text].invoke
|
719
|
+
form['description'] = ann_nfo_text
|
720
|
+
form['description_style'] = 'Pre-formatted'
|
721
|
+
form['short_description'] = project_module::TAGLINE
|
722
|
+
form['version'] = project_module::VERSION
|
723
|
+
form['url'] = project_module::WEBSITE
|
724
|
+
form['pass'] = ann_logins['raa.ruby-lang.org']['pass']
|
725
|
+
|
726
|
+
page = form.submit
|
727
|
+
|
728
|
+
if page.title =~ /error/i
|
729
|
+
show_page_error[page, "Could not update #{resource}"]
|
730
|
+
else
|
731
|
+
puts 'Successfully announced to RAA (Ruby Application Archive).'
|
732
|
+
end
|
733
|
+
else
|
734
|
+
show_page_error[page, "Could not access #{resource}"]
|
735
|
+
end
|
736
|
+
end
|
737
|
+
|
738
|
+
# release packages
|
739
|
+
desc 'Publish release packages to RubyForge.'
|
740
|
+
task 'pub:pak' => :pub_forge do
|
741
|
+
# check if this release was already published
|
742
|
+
version = project_module::VERSION
|
743
|
+
packages = pub_forge.autoconfig['release_ids'][pub_forge_section]
|
744
|
+
|
745
|
+
if packages and packages.key? version
|
746
|
+
warn "The release packages were already published, so I will NOT publish them again."
|
747
|
+
else
|
748
|
+
# create the FRS package section
|
749
|
+
unless pub_forge.autoconfig['package_ids'].key? pub_forge_section
|
750
|
+
pub_forge.create_package pub_forge_project, pub_forge_section
|
751
|
+
end
|
752
|
+
|
753
|
+
# publish the package to the section
|
754
|
+
uploader = lambda do |command, *files|
|
755
|
+
pub_forge.__send__ command, pub_forge_project, pub_forge_section, version, *files
|
756
|
+
end
|
757
|
+
|
758
|
+
Rake::Task[:pak].invoke
|
759
|
+
packages = Dir['pkg/*.[a-z]*']
|
760
|
+
|
761
|
+
unless packages.empty?
|
762
|
+
# NOTE: use the 'add_release' command ONLY for the first
|
763
|
+
# file because it creates a new sub-section on the
|
764
|
+
# RubyForge download page; we do not want one package
|
765
|
+
# per sub-section on the RubyForge download page!
|
766
|
+
#
|
767
|
+
uploader[:add_release, packages.shift]
|
768
|
+
|
769
|
+
unless packages.empty?
|
770
|
+
uploader[:add_file, *packages]
|
771
|
+
end
|
772
|
+
|
773
|
+
puts "Successfully published release packages to RubyForge."
|
774
|
+
end
|
775
|
+
end
|
776
|
+
end
|
777
|
+
end
|