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.
@@ -0,0 +1,77 @@
1
+ class << Inochi
2
+ ##
3
+ # Returns the name of the main program executable, which
4
+ # is the same as the project name fully in lowercase.
5
+ #
6
+ def calc_program_name project_symbol
7
+ camel_to_snake_case(project_symbol).downcase
8
+ end
9
+
10
+ ##
11
+ # Calculates the name of the project module from the given project name.
12
+ #
13
+ def calc_project_symbol project_name
14
+ name = project_name.to_s.gsub(/\W+/, '_').squeeze('_').gsub(/^_|_$/, '')
15
+ (name[0,1].upcase + name[1..-1]).to_sym
16
+ end
17
+
18
+ ##
19
+ # Transforms the given input from CamelCase to snake_case.
20
+ #
21
+ def camel_to_snake_case input
22
+ input = input.to_s.dup
23
+
24
+ # handle camel case like FooBar => Foo_Bar
25
+ while input.gsub!(/([a-z]+)([A-Z])(\w+)/) { $1 + '_' + $2 + $3 }
26
+ end
27
+
28
+ # handle abbreviations like XMLParser => XML_Parser
29
+ while input.gsub!(/([A-Z]+)([A-Z])([a-z]+)/) { $1 + '_' + $2 + $3 }
30
+ end
31
+
32
+ input
33
+ end
34
+
35
+ private
36
+
37
+ INOCHI_LIBRARY_PATH = File.dirname(__FILE__)
38
+
39
+ ##
40
+ # Returns the path of the first file outside
41
+ # Inochi's core from which this method was called.
42
+ #
43
+ def first_caller_file
44
+ caller.each do |step|
45
+ if file = step[/^.+(?=:\d+$)/]
46
+ file = File.expand_path(file)
47
+ base = File.dirname(file)
48
+
49
+ break file unless base.index(INOCHI_LIBRARY_PATH) == 0
50
+ end
51
+ end
52
+ end
53
+
54
+ ##
55
+ # Returns the project module corresponding to the given symbol.
56
+ # A new module is created if none already exists.
57
+ #
58
+ def fetch_project_module project_symbol
59
+ if Object.const_defined? project_symbol
60
+ project_module = Object.const_get(project_symbol)
61
+ else
62
+ project_module = Module.new
63
+ Object.const_set project_symbol, project_module
64
+ end
65
+
66
+ project_module
67
+ end
68
+ end
69
+
70
+ unless File.respond_to? :write
71
+ ##
72
+ # Writes the given content to the given file.
73
+ #
74
+ def File.write path, content
75
+ open(path, 'wb') {|f| f.write content }
76
+ end
77
+ end
File without changes
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inochi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Suraj N. Kurapati
@@ -9,9 +9,19 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-01-25 00:00:00 -08:00
12
+ date: 2009-02-12 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: erbook
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: "6"
24
+ version:
15
25
  - !ruby/object:Gem::Dependency
16
26
  name: minitest
17
27
  type: :runtime
@@ -55,6 +65,16 @@ dependencies:
55
65
  - !ruby/object:Gem::Version
56
66
  version: "0"
57
67
  version:
68
+ - !ruby/object:Gem::Dependency
69
+ name: babelfish
70
+ type: :runtime
71
+ version_requirement:
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ version: "0"
77
+ version:
58
78
  - !ruby/object:Gem::Dependency
59
79
  name: yard
60
80
  type: :runtime
@@ -105,12 +125,15 @@ extra_rdoc_files: []
105
125
 
106
126
  files:
107
127
  - test
108
- - test/inochi
109
- - test/inochi/inochi.rb
128
+ - test/inochi.rb
110
129
  - lib
111
130
  - lib/inochi.rb
112
131
  - lib/inochi
113
- - lib/inochi/inochi.rb
132
+ - lib/inochi/main.rb
133
+ - lib/inochi/book.rb
134
+ - lib/inochi/rake.rb
135
+ - lib/inochi/util.rb
136
+ - lib/inochi/init.rb
114
137
  - Rakefile
115
138
  - bin
116
139
  - bin/inochi
@@ -130,6 +153,10 @@ files:
130
153
  - doc/api/app.js
131
154
  - doc/api/readme.html
132
155
  - doc/api/all-namespaces.html
156
+ - doc/api/Inochi
157
+ - doc/api/Inochi/Phrases.html
158
+ - doc/api/Inochi/Manual.html
159
+ - doc/api/Inochi/Version.html
133
160
  - doc/api/index.html
134
161
  - doc/api/Inochi.html
135
162
  - doc/usage.erb
@@ -1,1064 +0,0 @@
1
- require 'rubygems'
2
-
3
- module Inochi
4
- class << self
5
- ##
6
- # Establishes your project in Ruby's runtime environment by defining
7
- # the project module (which serves as a namespace for all code in the
8
- # project) and providing a common configuration for the project module:
9
- #
10
- # * Adds the project lib/ directory to the Ruby load path.
11
- #
12
- # * Defines the INOCHI constant in the project module. This constant
13
- # contains the effective configuration parameters (@see project_config).
14
- #
15
- # * Defines all configuration parameters as constants in the project module.
16
- #
17
- # This method must be invoked from immediately within (that is, not from
18
- # within any of its descendant directories) the project lib/ directory.
19
- # Ideally, this method would be invoked from the main project library.
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] project_config
26
- # Project configuration parameters:
27
- #
28
- # [String] :project =>
29
- # Name of the project.
30
- #
31
- # The default value is the value of the project_symbol parameter.
32
- #
33
- # [String] :tagline =>
34
- # An enticing, single line description of the project.
35
- #
36
- # The default value is an empty string.
37
- #
38
- # [String] :website =>
39
- # URL of the published project website.
40
- #
41
- # The default value is an empty string.
42
- #
43
- # [String] :docsite =>
44
- # URL of the published user manual.
45
- #
46
- # The default value is the same value as the :website parameter.
47
- #
48
- # [String] :program =>
49
- # Name of the main project executable.
50
- #
51
- # The default value is the value of the :project parameter
52
- # in lowercase and CamelCase converted into snake_case.
53
- #
54
- # [String] :version =>
55
- # Version of the project.
56
- #
57
- # The default value is "0.0.0".
58
- #
59
- # [String] :release =>
60
- # Date when this version was released.
61
- #
62
- # The default value is the current time.
63
- #
64
- # [String] :display =>
65
- # How the project name should be displayed.
66
- #
67
- # The default value is the project name and version together.
68
- #
69
- # [String] :install =>
70
- # Path to the directory which contains the project.
71
- #
72
- # The default value is one directory above the parent
73
- # directory of the file from which this method was called.
74
- #
75
- # [Hash] :require =>
76
- # The names and version constraints of ruby gems required by
77
- # this project. This information must be expressed as follows:
78
- #
79
- # * Each hash key must be the name of a ruby gem.
80
- #
81
- # * Each hash value must be either +nil+, a single version number
82
- # requirement string (see Gem::Requirement) or an Array thereof.
83
- #
84
- # The default value is an empty Hash.
85
- #
86
- # @return [Module] The newly configured project module.
87
- #
88
- def init project_symbol, project_config = {}
89
- project_module = fetch_project_module(project_symbol)
90
-
91
- # this method is not re-entrant
92
- @already_seen ||= []
93
- return project_module if @already_seen.include? project_module
94
- @already_seen << project_module
95
-
96
- # put project on Ruby load path
97
- project_file = File.expand_path(first_caller_file)
98
- project_libs = File.dirname(project_file)
99
- $LOAD_PATH << project_libs unless $LOAD_PATH.include? project_libs
100
-
101
- # supply configuration defaults
102
- project_config[:project] ||= project_symbol.to_s
103
- project_config[:tagline] ||= ''
104
- project_config[:version] ||= '0.0.0'
105
- project_config[:release] ||= Time.now.strftime('%F')
106
- project_config[:website] ||= ''
107
- project_config[:docsite] ||= project_config[:website]
108
- project_config[:display] ||= "#{project_config[:project]} #{project_config[:version]}"
109
- project_config[:program] ||= calc_program_name(project_symbol)
110
- project_config[:install] ||= File.dirname(project_libs)
111
- project_config[:require] ||= {}
112
-
113
- # establish gem version dependencies and
114
- # sanitize the values while we're at it
115
- src = project_config[:require].dup
116
- dst = project_config[:require].clear
117
-
118
- src.each_pair do |gem_name, version_reqs|
119
- gem_name = gem_name.to_s
120
- version_reqs = [version_reqs].flatten.compact
121
-
122
- dst[gem_name] = version_reqs
123
- gem gem_name, *version_reqs
124
- end
125
-
126
- # make configuration parameters available as constants
127
- project_config[:inochi] = project_config
128
-
129
- class << project_config[:version]
130
- # Returns the major number in this version.
131
- def major
132
- to_s[/^\d+/]
133
- end
134
-
135
- # Returns a string describing any version with the current major number.
136
- def series
137
- "#{major}.x.x"
138
- end
139
-
140
- # Returns a Gem::Requirement expression.
141
- def requirement
142
- "~> #{major}"
143
- end
144
- end
145
-
146
- project_config.each_pair do |param, value|
147
- project_module.const_set param.to_s.upcase, value
148
- end
149
-
150
- project_module
151
- end
152
-
153
- ##
154
- # Provides a common configuration for the main project executable:
155
- #
156
- # * The program description (the sequence of non-blank lines at the
157
- # top of the file in which this method is invoked) is properly
158
- # formatted and displayed at the top of program's help information.
159
- #
160
- # * The program version information is fetched from the project module
161
- # and formatted in YAML fashion for easy consumption by other tools.
162
- #
163
- # * A list of command-line options is displayed at
164
- # the bottom of the program's help information.
165
- #
166
- # It is assumed that this method is invoked from only within
167
- # the main project executable (in the project bin/ directory).
168
- #
169
- # @param [Symbol] project_symbol
170
- # Name of the Ruby constant which serves
171
- # as a namespace for the entire project.
172
- #
173
- # @param trollop_args
174
- # Optional arguments for Trollop::options().
175
- #
176
- # @param trollop_config
177
- # Optional block argument for Trollop::options().
178
- #
179
- # @return The result of Trollop::options().
180
- #
181
- def main project_symbol, *trollop_args, &trollop_config
182
- program_file = first_caller_file
183
- program_name = File.basename(program_file)
184
- program_home = File.dirname(File.dirname(program_file))
185
-
186
- # load the project module
187
- require File.join(program_home, 'lib', program_name)
188
- project_module = fetch_project_module(project_symbol)
189
-
190
- # parse command-line options
191
- require 'trollop'
192
-
193
- options = Trollop.options(*trollop_args) do
194
-
195
- # show project description
196
- text "#{project_module::PROJECT} - #{project_module::TAGLINE}"
197
- text ''
198
-
199
- # show program description
200
- text File.read(program_file)[/\A.*?^$\n/m]. # grab the header
201
- gsub(/^# ?/, ''). # strip the comment markers
202
- sub(/\A!.*?\n/, '').lstrip # omit the shebang line
203
- text ''
204
-
205
- instance_eval(&trollop_config) if trollop_config
206
-
207
- # show version information
208
- version %w[PROJECT VERSION RELEASE WEBSITE INSTALL].map {|c|
209
- "#{c.downcase}: #{project_module.const_get c}"
210
- }.join("\n")
211
-
212
- opt :manual, 'Show the user manual'
213
- end
214
-
215
- if options[:manual]
216
- require 'launchy'
217
- Launchy::Browser.run "#{project_module::INSTALL}/doc/index.xhtml"
218
- exit
219
- end
220
-
221
- options
222
- end
223
-
224
- ##
225
- # Provides Rake tasks for packaging, publishing, and announcing your project.
226
- #
227
- # * An AUTHORS constant (which has the form "[[name, info]]"
228
- # where "name" is the name of a copyright holder and "info" is
229
- # their contact information) is added to the project module.
230
- #
231
- # Unless this information is supplied via the :authors option,
232
- # it is automatically extracted from copyright notices in the
233
- # project license file, where the first copyright notice is
234
- # expected to correspond to the primary project maintainer.
235
- #
236
- # Copyright notices must be in the following form:
237
- #
238
- # Copyright YEAR HOLDER <EMAIL>
239
- #
240
- # Where HOLDER is the name of the copyright holder, YEAR is the year
241
- # when the copyright holder first began working on the project, and
242
- # EMAIL is (optional) the email address of the copyright holder.
243
- #
244
- # @param [Symbol] project_symbol
245
- # Name of the Ruby constant which serves
246
- # as a namespace for the entire project.
247
- #
248
- # @param [Hash] options
249
- # Additional method parameters, which are all optional:
250
- #
251
- # [Array] :authors =>
252
- # A list of project authors and their contact information. This
253
- # list must have the form "[[name, info]]" where "name" is the name
254
- # of a project author and "info" is their contact information.
255
- #
256
- # [String] :license_file =>
257
- # Path (relative to the main project directory which contains the
258
- # project Rakefile) to the file which contains the project license.
259
- #
260
- # The default value is "LICENSE".
261
- #
262
- # [String] :logins_file =>
263
- # Path to the YAML file which contains login
264
- # information for publishing release announcements.
265
- #
266
- # The default value is "~/.config/inochi/logins.yaml"
267
- # where "~" is the path to your home directory.
268
- #
269
- # [String] :rubyforge_project =>
270
- # Name of the RubyForge project where
271
- # release packages will be published.
272
- #
273
- # The default value is the value of the PROGRAM constant.
274
- #
275
- # [String] :rubyforge_section =>
276
- # Name of the RubyForge project's File Release System
277
- # section where release packages will be published.
278
- #
279
- # The default value is the value of the :rubyforge_project parameter.
280
- #
281
- # [String] :raa_project =>
282
- # Name of the RAA (Ruby Application Archive) entry for this project.
283
- #
284
- # The default value is the value of the PROGRAM constant.
285
- #
286
- # [String] :upload_target =>
287
- # Where to upload the project documentation.
288
- # See "destination" in the rsync manual.
289
- #
290
- # The default value is nil.
291
- #
292
- # [String] :upload_delete =>
293
- # Delete unknown files at the upload target location?
294
- #
295
- # The default value is false.
296
- #
297
- # [Array] :upload_options =>
298
- # Additional command-line arguments to the rsync command.
299
- #
300
- # The default value is an empty array.
301
- #
302
- # @param gem_config
303
- # Block that is passed to Gem::specification.new()
304
- # for additonal gem configuration.
305
- #
306
- # @yieldparam [Gem::Specification] gem_spec the gem specification
307
- #
308
- def rake project_symbol, options = {}, &gem_config
309
- program_file = first_caller_file
310
- program_home = File.dirname(program_file)
311
-
312
- # load the project module
313
- program_name = File.basename(program_home)
314
- project_libs = File.join('lib', program_name)
315
-
316
- require project_libs
317
- project_module = fetch_project_module(project_symbol)
318
-
319
- # supply default options
320
- options[:rubyforge_project] ||= program_name
321
- options[:rubyforge_section] ||= program_name
322
- options[:raa_project] ||= program_name
323
- options[:license_file] ||= 'LICENSE'
324
- options[:logins_file] ||= File.join(ENV['HOME'], '.config', 'inochi', 'logins.yaml')
325
- options[:upload_delete] ||= false
326
- options[:upload_options] ||= []
327
-
328
- # add AUTHORS constant to the project module
329
- copyright_holders = options[:authors] ||
330
- File.read(options[:license_file]).
331
- scan(/Copyright.*?\d+\s+(.*)/).flatten.
332
- map {|s| (s =~ /\s*<(.*?)>/) ? [$`, $1] : [s, ''] }
333
-
334
- project_module.const_set :AUTHORS, copyright_holders
335
-
336
- require 'rake/clean'
337
-
338
- hide_rake_task = lambda do |name|
339
- Rake::Task[name].instance_variable_set :@comment, nil
340
- end
341
-
342
- # testing
343
- desc 'Run all unit tests.'
344
- task :test do
345
- ruby '-w', '-I.', '-Ilib', '-r', program_name, '-e', %q{
346
- # set title of test suite
347
- $0 = File.basename(Dir.pwd)
348
-
349
- require 'minitest/unit'
350
- require 'minitest/spec'
351
- require 'minitest/mock'
352
- MiniTest::Unit.autorun
353
-
354
- Dir['test/**/*.rb'].sort.each do |test|
355
- unit = test.sub('test/', 'lib/')
356
-
357
- if File.exist? unit
358
- # strip file extension because require()
359
- # does not normalize its input and it
360
- # will think that the two paths (with &
361
- # without file extension) are different
362
- unit_path = unit.sub(/\.rb$/, '').sub('lib/', '')
363
- test_path = test.sub(/\.rb$/, '')
364
-
365
- require unit_path
366
- require test_path
367
- else
368
- warn "Skipped test #{test.inspect} because it lacks a corresponding #{unit.inspect} unit."
369
- end
370
- end
371
- }
372
- end
373
-
374
- # documentation
375
- desc 'Build all documentation.'
376
- task :doc => %w[ doc:api doc:man ]
377
-
378
- # user manual
379
- doc_man_src = 'doc/index.erb'
380
- doc_man_dst = 'doc/index.xhtml'
381
- doc_man_deps = FileList['doc/*.erb']
382
-
383
- doc_man_doc = nil
384
- task :doc_man_doc => doc_man_src do
385
- unless doc_man_doc
386
- unless project_symbol == :ERBook
387
- gem 'erbook', '~> 6'
388
- require 'erbook'
389
- end
390
-
391
- doc_man_txt = File.read(doc_man_src)
392
- doc_man_doc = ERBook::Document.new(:xhtml, doc_man_txt, doc_man_src, :unindent => true)
393
- end
394
- end
395
-
396
- desc 'Build the user manual.'
397
- task 'doc:man' => doc_man_dst
398
-
399
- file doc_man_dst => doc_man_deps do
400
- Rake::Task[:doc_man_doc].invoke
401
- File.write doc_man_dst, doc_man_doc
402
- end
403
-
404
- CLOBBER.include doc_man_dst
405
-
406
- # API reference
407
- doc_api_dst = 'doc/api'
408
-
409
- desc 'Build API reference.'
410
- task 'doc:api' => doc_api_dst
411
-
412
- require 'yard'
413
- YARD::Rake::YardocTask.new doc_api_dst do |t|
414
- t.options.push '--protected',
415
- '--output-dir', doc_api_dst,
416
- '--readme', options[:license_file]
417
-
418
- task doc_api_dst => options[:license_file]
419
- end
420
-
421
- hide_rake_task[doc_api_dst]
422
-
423
- CLEAN.include '.yardoc'
424
- CLOBBER.include doc_api_dst
425
-
426
- # announcements
427
- desc 'Build all release announcements.'
428
- task :ann => %w[ ann:feed ann:html ann:text ann:mail ]
429
-
430
- # it has long been a tradition to use an "[ANN]" prefix
431
- # when announcing things on the ruby-talk mailing list
432
- ann_prefix = '[ANN] '
433
- ann_subject = ann_prefix + project_module::DISPLAY
434
- ann_project = ann_prefix + project_module::PROJECT
435
-
436
- # fetch the project summary from user manual
437
- ann_nfo_doc = nil
438
- task :ann_nfo_doc => :doc_man_doc do
439
- ann_nfo_doc = $project_summary_node
440
- end
441
-
442
- # fetch release notes from user manual
443
- ann_rel_doc = nil
444
- task :ann_rel_doc => :doc_man_doc do
445
- unless ann_rel_doc
446
- if parent = $project_history_node
447
- if child = parent.children.first
448
- ann_rel_doc = child
449
- else
450
- raise 'The "project_history" node in the user manual lacks child nodes.'
451
- end
452
- else
453
- raise 'The user manual lacks a "project_history" node.'
454
- end
455
- end
456
- end
457
-
458
- # build release notes in HTML and plain text
459
- # converts the given HTML into plain text. we do this using
460
- # lynx because (1) it outputs a list of all hyperlinks used
461
- # in the HTML document and (2) it runs on all major platforms
462
- convert_html_to_text = lambda do |html|
463
- require 'tempfile'
464
-
465
- begin
466
- # lynx's -dump option requires a .html file
467
- tmp_file = Tempfile.new(Inochi::PROGRAM).path + '.html'
468
-
469
- File.write tmp_file, html
470
- text = `lynx -dump #{tmp_file} -width 70`
471
- ensure
472
- File.delete tmp_file
473
- end
474
-
475
- # improve readability of list items
476
- # by adding a blank line between them
477
- text.gsub! %r{(\r?\n)( +\* \S)}, '\1\1\2'
478
-
479
- text
480
- end
481
-
482
- # binds relative addresses in the given HTML to the project docsite
483
- resolve_html_links = lambda do |html|
484
- # resolve relative URLs into absolute URLs
485
- # see http://en.wikipedia.org/wiki/URI_scheme#Generic_syntax
486
- require 'addressable/uri'
487
- uri = Addressable::URI.parse(project_module::DOCSITE)
488
- doc_url = uri.to_s
489
- dir_url = uri.path =~ %r{/$|^$} ? doc_url : File.dirname(doc_url)
490
-
491
- html.to_s.gsub %r{(href=|src=)(.)(.*?)(\2)} do |match|
492
- a, b = $1 + $2, $3.to_s << $4
493
-
494
- case $3
495
- when %r{^[[:alpha:]][[:alnum:]\+\.\-]*://} # already absolute
496
- match
497
-
498
- when /^#/
499
- a << File.join(doc_url, b)
500
-
501
- else
502
- a << File.join(dir_url, b)
503
- end
504
- end
505
- end
506
-
507
- ann_html = nil
508
- task :ann_html => [:doc_man_doc, :ann_nfo_doc, :ann_rel_doc] do
509
- unless ann_html
510
- ann_html = %{
511
- <center>
512
- <h1>#{project_module::DISPLAY}</h1>
513
- <p>#{project_module::TAGLINE}</p>
514
- <p>#{project_module::WEBSITE}</p>
515
- </center>
516
- #{ann_nfo_doc}
517
- #{ann_rel_doc}
518
- }
519
-
520
- # remove heading navigation menus
521
- ann_html.gsub! %r{<div class="nav"[^>]*>(.*?)</div>}, ''
522
-
523
- # remove latex-style heading numbers
524
- ann_html.gsub! %r"(<(h\d)[^>]*>).+?(?:&nbsp;){2}(.+?)(</\2>)"m, '\1\3\4'
525
-
526
- ann_html = resolve_html_links[ann_html]
527
- end
528
- end
529
-
530
- ann_text = nil
531
- task :ann_text => :ann_html do
532
- unless ann_text
533
- ann_text = convert_html_to_text[ann_html]
534
- end
535
- end
536
-
537
- ann_nfo_text = nil
538
- task :ann_nfo_text => :ann_nfo_doc do
539
- unless ann_nfo_text
540
- ann_nfo_html = resolve_html_links[ann_nfo_doc]
541
- ann_nfo_text = convert_html_to_text[ann_nfo_html]
542
- end
543
- end
544
-
545
- # HTML
546
- ann_html_dst = 'ANN.html'
547
-
548
- desc "Build HTML announcement: #{ann_html_dst}"
549
- task 'ann:html' => ann_html_dst
550
-
551
- file ann_html_dst => doc_man_deps do
552
- Rake::Task[:ann_html].invoke
553
- File.write ann_html_dst, ann_html
554
- end
555
-
556
- CLEAN.include ann_html_dst
557
-
558
- # RSS feed
559
- ann_feed_dst = 'doc/ann.xml'
560
-
561
- desc "Build RSS announcement: #{ann_feed_dst}"
562
- task 'ann:feed' => ann_feed_dst
563
-
564
- file ann_feed_dst => doc_man_deps do
565
- require 'time'
566
- require 'rss/maker'
567
-
568
- feed = RSS::Maker.make('2.0') do |feed|
569
- feed.channel.title = ann_project
570
- feed.channel.link = project_module::WEBSITE
571
- feed.channel.description = project_module::TAGLINE
572
-
573
- Rake::Task[:ann_rel_doc].invoke
574
- Rake::Task[:ann_html].invoke
575
-
576
- item = feed.items.new_item
577
- item.title = ann_rel_doc.title
578
- item.link = project_module::DOCSITE + '#' + ann_rel_doc.here_frag
579
- item.date = Time.parse(item.title)
580
- item.description = ann_html
581
- end
582
-
583
- File.write ann_feed_dst, feed
584
- end
585
-
586
- CLOBBER.include ann_feed_dst
587
-
588
- # plain text
589
- ann_text_dst = 'ANN.txt'
590
-
591
- desc "Build plain text announcement: #{ann_text_dst}"
592
- task 'ann:text' => ann_text_dst
593
-
594
- file ann_text_dst => doc_man_deps do
595
- Rake::Task[:ann_text].invoke
596
- File.write ann_text_dst, ann_text
597
- end
598
-
599
- CLEAN.include ann_text_dst
600
-
601
- # e-mail
602
- ann_mail_dst = 'ANN.eml'
603
-
604
- desc "Build e-mail announcement: #{ann_mail_dst}"
605
- task 'ann:mail' => ann_mail_dst
606
-
607
- file ann_mail_dst => doc_man_deps do
608
- File.open ann_mail_dst, 'w' do |f|
609
- require 'time'
610
- f.puts "Date: #{Time.now.rfc822}"
611
-
612
- f.puts 'To: ruby-talk@ruby-lang.org'
613
- f.puts 'From: "%s" <%s>' % project_module::AUTHORS.first
614
- f.puts "Subject: #{ann_subject}"
615
-
616
- Rake::Task[:ann_text].invoke
617
- f.puts '', ann_text
618
- end
619
- end
620
-
621
- CLEAN.include ann_mail_dst
622
-
623
- # packaging
624
- desc 'Build a release.'
625
- task :pak => [:clobber, :doc] do
626
- sh $0, 'package'
627
- end
628
- CLEAN.include 'pkg'
629
-
630
- # ruby gem
631
- require 'rake/gempackagetask'
632
-
633
- gem = Gem::Specification.new do |gem|
634
- authors = project_module::AUTHORS
635
-
636
- if author = authors.first
637
- gem.author, gem.email = author
638
- end
639
-
640
- if authors.length > 1
641
- gem.authors = authors.map {|name, mail| name }
642
- end
643
-
644
- gem.rubyforge_project = options[:rubyforge_project]
645
-
646
- # XXX: In theory, `gem.name` should be assigned to
647
- # ::PROJECT instead of ::PROGRAM
648
- #
649
- # In practice, PROJECT may contain non-word
650
- # characters and may also contain a mixture
651
- # of lowercase and uppercase letters.
652
- #
653
- # This makes it difficult for people to
654
- # install the project gem because they must
655
- # remember the exact spelling used in
656
- # `gem.name` when running `gem install ____`.
657
- #
658
- # For example, consider the "RedCloth" gem.
659
- #
660
- gem.name = project_module::PROGRAM
661
-
662
- gem.version = project_module::VERSION
663
- gem.summary = project_module::TAGLINE
664
- gem.description = gem.summary
665
- gem.homepage = project_module::WEBSITE
666
- gem.files = FileList['**/*'].exclude('_darcs') - CLEAN
667
- gem.executables = project_module::PROGRAM
668
- gem.has_rdoc = true
669
-
670
- unless project_module == Inochi
671
- gem.add_dependency 'inochi', Inochi::VERSION.requirement
672
- end
673
-
674
- project_module::REQUIRE.each_pair do |gem_name, version_reqs|
675
- gem.add_dependency gem_name, *version_reqs
676
- end
677
-
678
- # additional configuration is done by user
679
- yield gem if gem_config
680
- end
681
-
682
- Rake::GemPackageTask.new(gem).define
683
-
684
- # XXX: hide the tasks defined by the above gem packaging library
685
- %w[gem package repackage clobber_package].each {|t| hide_rake_task[t] }
686
-
687
- # releasing
688
- desc 'Publish a release.'
689
- task 'pub' => %w[ pub:pak pub:doc pub:ann ]
690
-
691
- # connect to RubyForge services
692
- pub_forge = nil
693
- pub_forge_project = options[:rubyforge_project]
694
- pub_forge_section = options[:rubyforge_section]
695
-
696
- task :pub_forge do
697
- require 'rubyforge'
698
- pub_forge = RubyForge.new
699
- pub_forge.configure('release_date' => project_module::RELEASE)
700
-
701
- unless pub_forge.autoconfig['group_ids'].key? pub_forge_project
702
- 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."
703
- end
704
-
705
- pub_forge.login
706
- end
707
-
708
- # documentation
709
- desc 'Publish documentation to project website.'
710
- task 'pub:doc' => [:doc, 'ann:feed'] do
711
- target = options[:upload_target]
712
-
713
- unless target
714
- require 'addressable/uri'
715
- docsite = Addressable::URI.parse(project_module::DOCSITE)
716
-
717
- # provide uploading capability to websites hosted on RubyForge
718
- if docsite.host.include? '.rubyforge.org'
719
- target = "#{pub_forge.userconfig['username']}@rubyforge.org:#{File.join '/var/www/gforge-projects', options[:rubyforge_project], docsite.path}"
720
- end
721
- end
722
-
723
- if target
724
- cmd = ['rsync', '-auvz', 'doc/', "#{target}/"]
725
- cmd.push '--delete' if options[:upload_delete]
726
- cmd.concat options[:upload_options]
727
-
728
- p cmd
729
- sh(*cmd)
730
- end
731
- end
732
-
733
- # announcement
734
- desc 'Publish all announcements.'
735
- task 'pub:ann' => %w[ pub:ann:forge pub:ann:raa pub:ann:talk ]
736
-
737
- # login information
738
- ann_logins_file = options[:logins_file]
739
- ann_logins = nil
740
-
741
- task :ann_logins do
742
- ann_logins = begin
743
- require 'yaml'
744
- YAML.load_file ann_logins_file
745
- rescue => e
746
- warn "Could not read login information from #{ann_logins_file.inspect}:"
747
- warn e
748
- warn "** You will NOT be able to publish release announcements! **"
749
- {}
750
- end
751
- end
752
-
753
- desc 'Announce to RubyForge news.'
754
- task 'pub:ann:forge' => :pub_forge do
755
- project = options[:rubyforge_project]
756
-
757
- if group_id = pub_forge.autoconfig['group_ids'][project]
758
- # check if this release was already announced
759
- require 'mechanize'
760
- www = WWW::Mechanize.new
761
- page = www.get "http://rubyforge.org/news/?group_id=#{group_id}"
762
-
763
- posts = (page/'//a[starts-with(./@href, "/forum/forum.php?forum_id=")]/text()').map {|e| e.to_s.gsub("\302\240", '').strip }
764
-
765
- already_announced = posts.include? ann_subject
766
-
767
- if already_announced
768
- warn "This release was already announced to RubyForge news, so I will NOT announce it there again."
769
- else
770
- # make the announcement
771
- Rake::Task[:ann_text].invoke
772
- pub_forge.post_news project, ann_subject, ann_text
773
-
774
- puts "Successfully announced to RubyForge news:"
775
- puts page.uri
776
- end
777
- else
778
- raise "Could not determine the group_id of the #{project.inspect} RubyForge project. Run `rubyforge config` and try again."
779
- end
780
- end
781
-
782
- desc 'Announce to ruby-talk mailing list.'
783
- task 'pub:ann:talk' => :ann_logins do
784
- host = 'http://ruby-forum.com'
785
- ruby_talk = 4 # ruby-talk forum ID
786
-
787
- require 'mechanize'
788
- www = WWW::Mechanize.new
789
-
790
- # check if this release was already announced
791
- already_announced =
792
- begin
793
- page = www.get "#{host}/forum/#{ruby_talk}", :filter => %{"#{ann_subject}"}
794
-
795
- posts = (page/'//div[@class="forum"]//a[starts-with(./@href, "/topic/")]/text()').map {|e| e.to_s.strip }
796
- posts.include? ann_subject
797
- rescue
798
- false
799
- end
800
-
801
- if already_announced
802
- warn "This release was already announced to the ruby-talk mailing list, so I will NOT announce it there again."
803
- else
804
- # log in to RubyForum
805
- page = www.get "#{host}/user/login"
806
- form = page.forms.first
807
-
808
- if login = ann_logins['www.ruby-forum.com']
809
- form['name'] = login['user']
810
- form['password'] = login['pass']
811
- end
812
-
813
- page = form.click_button # use the first submit button
814
-
815
- if (page/'//a[@href="/user/logout"]').empty?
816
- 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."
817
- else
818
- # make the announcement
819
- page = www.get "#{host}/topic/new?forum_id=#{ruby_talk}"
820
- form = page.forms.first
821
-
822
- Rake::Task[:ann_text].invoke
823
- form['post[subject]'] = ann_subject
824
- form['post[text]'] = ann_text
825
-
826
- form.checkboxes.first.check # enable email notification
827
- page = form.submit
828
-
829
- errors = [page/'//div[@class="error"]/text()'].flatten
830
- if errors.empty?
831
- puts "Successfully announced to ruby-talk mailing list:"
832
- puts page.uri
833
- else
834
- warn "Could not announce to ruby-talk mailing list:"
835
- warn errors.join("\n")
836
- end
837
- end
838
- end
839
- end
840
-
841
- desc 'Announce to RAA (Ruby Application Archive).'
842
- task 'pub:ann:raa' => :ann_logins do
843
- show_page_error = lambda do |page, message|
844
- warn "#{message}, so I can NOT announce this release to RAA:"
845
- warn "#{(page/'h2').text} -- #{(page/'p').first.text.strip}"
846
- end
847
-
848
- resource = "#{options[:raa_project].inspect} project entry on RAA"
849
-
850
- require 'mechanize'
851
- www = WWW::Mechanize.new
852
- page = www.get "http://raa.ruby-lang.org/update.rhtml?name=#{options[:raa_project]}"
853
-
854
- if form = page.forms[1]
855
- resource << " (owned by #{form.owner.inspect})"
856
-
857
- Rake::Task[:ann_nfo_text].invoke
858
- form['description'] = ann_nfo_text
859
- form['description_style'] = 'Pre-formatted'
860
- form['short_description'] = project_module::TAGLINE
861
- form['version'] = project_module::VERSION
862
- form['url'] = project_module::WEBSITE
863
- form['pass'] = ann_logins['raa.ruby-lang.org']['pass']
864
-
865
- page = form.submit
866
-
867
- if page.title =~ /error/i
868
- show_page_error[page, "Could not update #{resource}"]
869
- else
870
- puts "Successfully announced to RAA (Ruby Application Archive)."
871
- end
872
- else
873
- show_page_error[page, "Could not access #{resource}"]
874
- end
875
- end
876
-
877
- # release packages
878
- desc 'Publish release packages to RubyForge.'
879
- task 'pub:pak' => :pub_forge do
880
- # check if this release was already published
881
- version = project_module::VERSION
882
- packages = pub_forge.autoconfig['release_ids'][pub_forge_section]
883
-
884
- if packages and packages.key? version
885
- warn "The release packages were already published, so I will NOT publish them again."
886
- else
887
- # create the FRS package section
888
- unless pub_forge.autoconfig['package_ids'].key? pub_forge_section
889
- pub_forge.create_package pub_forge_project, pub_forge_section
890
- end
891
-
892
- # publish the package to the section
893
- uploader = lambda do |command, *files|
894
- pub_forge.__send__ command, pub_forge_project, pub_forge_section, version, *files
895
- end
896
-
897
- Rake::Task[:pak].invoke
898
- packages = Dir['pkg/*.[a-z]*']
899
-
900
- unless packages.empty?
901
- # NOTE: use the 'add_release' command ONLY for the first
902
- # file because it creates a new sub-section on the
903
- # RubyForge download page; we do not want one package
904
- # per sub-section on the RubyForge download page!
905
- #
906
- uploader[:add_release, packages.shift]
907
-
908
- unless packages.empty?
909
- uploader[:add_file, *packages]
910
- end
911
-
912
- puts "Successfully published release packages to RubyForge."
913
- end
914
- end
915
- end
916
- end
917
-
918
- ##
919
- # Provides a common configuration for the project's user manual:
920
- #
921
- # * Assigns the title, subtitle, date, and authors for the document.
922
- #
923
- # You may override these assignments by reassigning these
924
- # document parameters AFTER this method is invoked.
925
- #
926
- # Refer to the "document parameters" for the XHTML
927
- # format in the "erbook" user manual for details.
928
- #
929
- # * Provides the project's configuration as global variables in the document.
930
- #
931
- # For example, <%= $version %> is the same as
932
- # <%= project_module::VERSION %> in the document.
933
- #
934
- # * Defines a "project_summary" node for use in the document. The body
935
- # of this node should contain a brief introduction to the project.
936
- #
937
- # * Defines a "project_history" node for use in the document. The body
938
- # of this node should contain other nodes, each of which represent a
939
- # single set of release notes for one of the project's releases.
940
- #
941
- # It is assumed that this method is called
942
- # from within the Inochi.rake() environment.
943
- #
944
- # @param [Symbol] project_symbol
945
- # Name of the Ruby constant which serves
946
- # as a namespace for the entire project.
947
- #
948
- # @param [ERBook::Document::Template] book_template
949
- # The eRuby template which serves as the documentation for the project.
950
- #
951
- def book project_symbol, book_template
952
- project_module = fetch_project_module(project_symbol)
953
-
954
- # provide project constants as global variables to the user manual
955
- project_module::INOCHI.each_pair do |param, value|
956
- eval "$#{param} = value", binding
957
- end
958
-
959
- # set document parameters for the user manual
960
- $title = project_module::DISPLAY
961
- $subtitle = project_module::TAGLINE
962
- $feeds = { File.join(project_module::DOCSITE, 'ann.xml') => :rss }
963
- $authors = Hash[
964
- *project_module::AUTHORS.map do |name, addr|
965
- # convert raw e-mail addresses into URLs for the erbook XHTML format
966
- addr = "mailto:#{addr}" unless addr =~ /^\w+:/
967
-
968
- [name, addr]
969
- end.flatten
970
- ]
971
-
972
- class << book_template
973
- def project_summary
974
- raise ArgumentError, 'block must be given' unless block_given?
975
- node do
976
- $project_summary_node = @nodes.last
977
- yield
978
- end
979
- end
980
-
981
- def project_history
982
- raise ArgumentError, 'block must be given' unless block_given?
983
- node do
984
- $project_history_node = @nodes.last
985
- yield
986
- end
987
- end
988
- end
989
- end
990
-
991
- ##
992
- # Returns the name of the main program executable, which
993
- # is the same as the project name fully in lowercase.
994
- #
995
- def calc_program_name project_symbol
996
- camel_to_snake_case(project_symbol).downcase
997
- end
998
-
999
- ##
1000
- # Calculates the name of the project module from the given project name.
1001
- #
1002
- def calc_project_symbol project_name
1003
- name = project_name.to_s.gsub(/\W+/, '_').squeeze('_').gsub(/^_|_$/, '')
1004
- (name[0,1].upcase + name[1..-1]).to_sym
1005
- end
1006
-
1007
- ##
1008
- # Transforms the given input from CamelCase to snake_case.
1009
- #
1010
- def camel_to_snake_case input
1011
- input = input.to_s.dup
1012
-
1013
- # handle camel case like FooBar => Foo_Bar
1014
- while input.gsub!(/([a-z]+)([A-Z])(\w+)/) { $1 + '_' + $2 + $3 }
1015
- end
1016
-
1017
- # handle abbreviations like XMLParser => XML_Parser
1018
- while input.gsub!(/([A-Z]+)([A-Z])([a-z]+)/) { $1 + '_' + $2 + $3 }
1019
- end
1020
-
1021
- input
1022
- end
1023
-
1024
- private
1025
-
1026
- ##
1027
- # Returns the path of the file in which this method was called. Calls
1028
- # to this method from within *THIS* file are excluded from the search.
1029
- #
1030
- def first_caller_file
1031
- File.expand_path caller.each {|s| !s.include? __FILE__ and s =~ /^(.*?):\d+/ and break $1 }
1032
- end
1033
-
1034
- ##
1035
- # Returns the project module corresponding to the given symbol.
1036
- # A new module is created if none already exists.
1037
- #
1038
- def fetch_project_module project_symbol
1039
- if Object.const_defined? project_symbol
1040
- project_module = Object.const_get(project_symbol)
1041
- else
1042
- project_module = Module.new
1043
- Object.const_set project_symbol, project_module
1044
- end
1045
-
1046
- project_module
1047
- end
1048
- end
1049
- end
1050
-
1051
- ##
1052
- # utility methods
1053
- #
1054
-
1055
- unless File.respond_to? :write
1056
- ##
1057
- # Writes the given content to the given file.
1058
- #
1059
- # @return number of bytes written
1060
- #
1061
- def File.write path, content
1062
- File.open(path, 'wb') {|f| f.write content.to_s }
1063
- end
1064
- end