inochi 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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