inochi 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,3 @@
1
- <% STDERR.puts "hey: #{ERBook::VERSION.object_id}" %>
2
-
3
1
  <% chapter "Setup" do %>
4
2
  <% section "Requirements" do %>
5
3
  Your system needs the following software to run **<%= $project %>**.
@@ -24,7 +24,7 @@
24
24
  output_file=$1
25
25
  shift
26
26
 
27
- kdiff3 --auto "$old_file" "$new_file" --output "$output_file"
27
+ kdiff3 --merge "$old_file" "$new_file" --output "$output_file"
28
28
 
29
29
  2. Make the file executable:
30
30
 
@@ -120,47 +120,60 @@
120
120
  <% cd "word_count" %>
121
121
  </pre>
122
122
 
123
- View the available Rake tasks:
123
+ <% paragraph "View Rake tasks" do %>
124
+ <pre>
125
+ # rake -T
126
+ <%= verbatim `rake -T` %>
127
+ </pre>
128
+ <% end %>
124
129
 
125
- <pre>
126
- # rake -T
127
- <%= verbatim `rake -T` %>
128
- </pre>
130
+ <% paragraph "Run unit tests" do %>
131
+ <pre>
132
+ # rake test
133
+ <%= verbatim `rake test` %>
134
+ </pre>
135
+ <% end %>
129
136
 
130
- Try the main project executable:
137
+ <% paragraph "Run project executable" do %>
138
+ <pre>
139
+ <% command = main_executable %>
140
+ # ruby <%= command %>
141
+ <%= verbatim `ruby #{command}` %>
142
+ </pre>
131
143
 
132
- <pre>
133
- <% command = main_executable %>
134
- # ruby <%= command %>
135
- <%= verbatim `ruby #{command}` %>
136
- </pre>
144
+ See usage information:
137
145
 
138
- See usage information:
146
+ <pre>
147
+ <% command = "#{main_executable} --help" %>
148
+ # ruby <%= command %>
149
+ <%= verbatim `ruby #{command}` %>
150
+ </pre>
139
151
 
140
- <pre>
141
- <% command = "#{main_executable} --help" %>
142
- # ruby <%= command %>
143
- <%= verbatim `ruby #{command}` %>
144
- </pre>
152
+ See project & version information:
145
153
 
146
- See project & version information:
154
+ <pre>
155
+ <% command = "#{main_executable} --version" %>
156
+ # ruby <%= command %>
157
+ <%= verbatim `ruby #{command}` %>
158
+ </pre>
159
+ <% end %>
147
160
 
148
- <pre>
149
- <% command = "#{main_executable} --version" %>
150
- # ruby <%= command %>
151
- <%= verbatim `ruby #{command}` %>
152
- </pre>
161
+ <% paragraph "Show user manual" do %>
162
+ Build the user manual (please disregard any "unclosed span" warnings):
153
163
 
154
- See the user manual:
164
+ <pre>
165
+ # rake doc:man
166
+ </pre>
155
167
 
156
- <pre>
157
- # rake doc:man 2>/dev/null
168
+ Launch the user manual:
158
169
 
159
- <% command = "#{main_executable} --manual" %>
160
- # ruby <%= command %>
161
- </pre>
170
+ <pre>
171
+ <% command = "#{main_executable} --manual" %>
172
+ # ruby <%= command %>
173
+ </pre>
162
174
 
163
- The manual will now appear in your default web browser.
175
+ The manual will now appear in your default web browser.
176
+ <% end %>
164
177
  <% end %>
165
178
 
166
179
  <% section "Configure your project" do %>
@@ -224,7 +237,7 @@
224
237
  <% end %>
225
238
 
226
239
  <% section "Implement your project" do %>
227
- Add the following code to the bottom of the main project library:
240
+ Add the following code to the bottom of <tt>lib/word_count.rb</tt>, the main project library:
228
241
 
229
242
  <code>
230
243
  module WordCount
@@ -235,7 +248,7 @@
235
248
  end
236
249
  </code>
237
250
 
238
- Add the following code to the bottom of the main project executable:
251
+ Add the following code to the bottom of <tt>bin/word_count</tt>, the main project executable:
239
252
 
240
253
  <code>
241
254
  input = ARGF.read
@@ -243,6 +256,39 @@
243
256
  puts "There are #{total} words in the input."
244
257
  </code>
245
258
 
259
+ Add the following code to the bottom of <tt>test/word_count.rb</tt>, a unit test for the main project library:
260
+
261
+ <code>
262
+ describe WordCount do
263
+ it 'handles empty input' do
264
+ WordCount.count(nil).must_equal(0)
265
+ WordCount.count('').must_equal(0)
266
+ WordCount.count(' ').must_equal(0)
267
+ end
268
+
269
+ it 'handles single words' do
270
+ WordCount.count('a').must_equal(1)
271
+ WordCount.count('foo').must_equal(1)
272
+ WordCount.count('bar').must_equal(1)
273
+ end
274
+
275
+ it 'handles multiple words' do
276
+ WordCount.count('a b').must_equal(2)
277
+ WordCount.count('a-b').must_equal(2)
278
+ WordCount.count('a/b').must_equal(2)
279
+ end
280
+
281
+ it 'ignores punctuation and space' do
282
+ WordCount.count('!').must_equal(0)
283
+ WordCount.count('! @ # % #!@#').must_equal(0)
284
+ WordCount.count(' !').must_equal(0)
285
+ WordCount.count('! ').must_equal(0)
286
+ WordCount.count(' ! ').must_equal(0)
287
+ WordCount.count(' ! ').must_equal(0)
288
+ end
289
+ end
290
+ </code>
291
+
246
292
  <% paragraph "Goodbye `$LOAD_PATH`, hello `require()`" do %>
247
293
  Notice that, in the Ruby files that you modified so far, there were no `$LOAD_PATH` manipulations and no explicit `require()` statements to pull in the various parts of your project. That is because **<%= $project %>** does this for you automatically.
248
294
 
@@ -261,9 +307,41 @@
261
307
  <% end %>
262
308
 
263
309
  <% section "Test your project" do %>
264
- > TODO: show how to write a unit test for the code
310
+ To reduce the amount of code you have to write, **<%= $project %>** defines the following convention for unit tests.
311
+
312
+ <% paragraph "Units and tests" do %>
313
+ Every Ruby source file in your project's <tt>lib/</tt> directory is considered to be a **unit**. Likewise, every Ruby source file in your project's <tt>test/</tt> is considered to be a **test**.
314
+
315
+ As a result, your project's <tt>test/</tt> directory structure *mirrors* the structure of your project's <tt>lib/</tt> directory. For example, if your project has a <tt>lib/foo/bar.rb</tt> unit, then <tt>test/foo/bar.rb</tt> would be its the corresponding test.
316
+ <% end %>
265
317
 
266
- > TODO: integrate minitest tasks into Inochi.rake()
318
+ <% paragraph "Test execution" do %>
319
+ <pre>rake test</pre>
320
+
321
+ The above command begins the testing process, during which:
322
+
323
+ * Tests which lack corresponding units are *skipped* and not executed. A message specifying which test file was skipped is printed to the standard error stream whenever this occurs.
324
+
325
+ * Before a test is executed, its corresponding unit is automatically loaded into the Ruby environment using `require()`.
326
+
327
+ As for the details of test execution:
328
+
329
+ * Tests are executed by the [minitest] library, which allows you to write unit tests in a combination of styles: traditional TDD, modern BDD, alternative rSpec BDD, and mock-based validation styles.
330
+
331
+ * Within each test, test cases are executed in random order. This is the default behavior of the [minitest] library.
332
+
333
+ [minitest]: http://rubyforge.org/projects/bfts/
334
+ <% end %>
335
+
336
+ <% paragraph "Helper libraries" do %>
337
+ Your project's main directory is added to Ruby's load path. So if your tests have helper libraries stored in your project's <tt>test/</tt> directory, you can load them into your tests by adding a "test/" prefix.
338
+
339
+ For example, if your <tt>test/foo/bar.rb</tt> test has a <tt>test/foo/qux.rb</tt> helper library, then you would write the following code inside the test to load the helper library:
340
+
341
+ <code>
342
+ require 'test/foo/qux'
343
+ </code>
344
+ <% end %>
267
345
  <% end %>
268
346
 
269
347
  <% section "Publish your project" do %>
@@ -1,16 +1,18 @@
1
- require File.join(File.dirname(File.expand_path(__FILE__)), 'inochi', 'inochi')
1
+ $LOAD_PATH << File.dirname(__FILE__)
2
+ require 'inochi/inochi'
2
3
 
3
4
  Inochi.init :Inochi,
4
- :version => '0.1.0',
5
- :release => '2009-01-19',
6
- :tagline => 'Gives life to RubyGems-based software',
5
+ :version => '0.2.0',
6
+ :release => '2009-01-25',
7
7
  :website => 'http://snk.tuxfamily.org/lib/inochi',
8
+ :tagline => 'Gives life to RubyGems-based software',
8
9
  :require => {
9
10
  'rake' => '~> 0',
10
- 'rubyforge' => '~> 1', # for publishing gems to RubyForge
11
- 'mechanize' => '~> 0', # for automating web browsing
12
- 'trollop' => '~> 1', # for parsing command-line options
13
- 'launchy' => '~> 0', # for launching a web browser
14
- 'yard' => nil, # for generating API documentation
15
- 'addressable' => '~> 2', # for parsing URIs properly
11
+ 'rubyforge' => '~> 1', # for publishing gems to RubyForge
12
+ 'mechanize' => '~> 0', # for automating web browsing
13
+ 'trollop' => '~> 1', # for parsing command-line options
14
+ 'launchy' => '~> 0', # for launching a web browser
15
+ 'yard' => nil, # for generating API documentation
16
+ 'addressable' => '~> 2', # for parsing URIs properly
17
+ 'minitest' => ['>= 1.3.1', '< 2'], # for unit testing
16
18
  }
@@ -228,9 +228,10 @@ class << self
228
228
  # where "name" is the name of a copyright holder and "info" is
229
229
  # their contact information) is added to the project module.
230
230
  #
231
- # This information is extracted from copyright notices in
232
- # the project license file. NOTE that the first copyright
233
- # notice must correspond to the primary project maintainer.
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.
234
235
  #
235
236
  # Copyright notices must be in the following form:
236
237
  #
@@ -247,6 +248,11 @@ class << self
247
248
  # @param [Hash] options
248
249
  # Additional method parameters, which are all optional:
249
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
+ #
250
256
  # [String] :license_file =>
251
257
  # Path (relative to the main project directory which contains the
252
258
  # project Rakefile) to the file which contains the project license.
@@ -305,24 +311,24 @@ class << self
305
311
 
306
312
  # load the project module
307
313
  program_name = File.basename(program_home)
314
+ project_libs = File.join('lib', program_name)
308
315
 
309
- require File.join('lib', program_name)
316
+ require project_libs
310
317
  project_module = fetch_project_module(project_symbol)
311
318
 
312
319
  # supply default options
313
320
  options[:rubyforge_project] ||= program_name
314
321
  options[:rubyforge_section] ||= program_name
315
- options[:raa_project] ||= program_name
316
- options[:license_file] ||= 'LICENSE'
317
- options[:logins_file] ||= File.join(ENV['HOME'], '.config', 'inochi', 'logins.yaml')
318
- options[:upload_delete] ||= false
319
- options[:upload_options] ||= []
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] ||= []
320
327
 
321
328
  # add AUTHORS constant to the project module
322
- license = File.read(options[:license_file])
323
-
324
- copyright_holders =
325
- license.scan(/Copyright.*?\d+\s+(.*)/).flatten.
329
+ copyright_holders = options[:authors] ||
330
+ File.read(options[:license_file]).
331
+ scan(/Copyright.*?\d+\s+(.*)/).flatten.
326
332
  map {|s| (s =~ /\s*<(.*?)>/) ? [$`, $1] : [s, ''] }
327
333
 
328
334
  project_module.const_set :AUTHORS, copyright_holders
@@ -333,6 +339,38 @@ class << self
333
339
  Rake::Task[name].instance_variable_set :@comment, nil
334
340
  end
335
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
+
336
374
  # documentation
337
375
  desc 'Build all documentation.'
338
376
  task :doc => %w[ doc:api doc:man ]
@@ -434,9 +472,9 @@ class << self
434
472
  File.delete tmp_file
435
473
  end
436
474
 
437
- # improve readability of list items that span multiple
438
- # lines by adding a blank line between such items
439
- text.gsub! %r{^( *[^\*\s].*)(\r?\n)( *\* \S)}, '\1\2\2\3'
475
+ # improve readability of list items
476
+ # by adding a blank line between them
477
+ text.gsub! %r{(\r?\n)( +\* \S)}, '\1\1\2'
440
478
 
441
479
  text
442
480
  end
@@ -482,6 +520,9 @@ class << self
482
520
  # remove heading navigation menus
483
521
  ann_html.gsub! %r{<div class="nav"[^>]*>(.*?)</div>}, ''
484
522
 
523
+ # remove latex-style heading numbers
524
+ ann_html.gsub! %r"(<(h\d)[^>]*>).+?(?:&nbsp;){2}(.+?)(</\2>)"m, '\1\3\4'
525
+
485
526
  ann_html = resolve_html_links[ann_html]
486
527
  end
487
528
  end
@@ -0,0 +1,106 @@
1
+ describe 'Inochi.calc_program_name' do
2
+ it 'converts input into lower-case' do
3
+ c('foo').must_equal('foo')
4
+ c('foO').must_equal('foo')
5
+ c('Foo').must_equal('foo')
6
+ c('FoO').must_equal('foo')
7
+ end
8
+
9
+ it 'converts camel case into snake case' do
10
+ c('FooBar').must_equal('foo_bar')
11
+ c('AnXMLParser').must_equal('an_xml_parser')
12
+ c('fOo').must_equal('f_oo')
13
+ c('FOo').must_equal('f_oo')
14
+ end
15
+
16
+ private
17
+
18
+ def c *args
19
+ Inochi.calc_program_name(*args)
20
+ end
21
+ end
22
+
23
+ describe 'Inochi.calc_project_symbol' do
24
+ it 'capitalizes first letter like a ruby constant' do
25
+ c('foo').must_equal('Foo')
26
+ end
27
+
28
+ it 'preserves exisitng capitalization' do
29
+ c('FoO').must_equal('FoO')
30
+ c('fooBaR').must_equal('FooBaR')
31
+ end
32
+
33
+ it 'converts non-word characters into underscores' do
34
+ c('a!b#c').must_equal('A_b_c')
35
+ end
36
+
37
+ it 'squeezes mulitple underscores' do
38
+ c('foo!!bar$$qux').must_equal('Foo_bar_qux')
39
+ end
40
+
41
+ it 'ignores surrounding whitespace' do
42
+ c(' a ').must_equal('A')
43
+ end
44
+
45
+ it 'ignores surrounding underscores' do
46
+ c('_a').must_equal('A')
47
+ c('a_').must_equal('A')
48
+ c('_a_').must_equal('A')
49
+ c('__a__').must_equal('A')
50
+ end
51
+
52
+ it 'ignores surrounding non-word characters' do
53
+ c('!a').must_equal('A')
54
+ c('a!').must_equal('A')
55
+ c('!a!').must_equal('A')
56
+ c('!!a!!').must_equal('A')
57
+ c('!@a#$').must_equal('A')
58
+ end
59
+
60
+ private
61
+
62
+ def c *args
63
+ Inochi.calc_project_symbol(*args).to_s
64
+ end
65
+ end
66
+
67
+ describe 'Inochi.camel_to_snake_case' do
68
+ it 'supports empty input' do
69
+ c('').must_equal('')
70
+ end
71
+
72
+ it 'supports normal camel case' do
73
+ c('fooBar').must_equal('foo_Bar')
74
+ c('FooBar').must_equal('Foo_Bar')
75
+ c('Foobar').must_equal('Foobar')
76
+ end
77
+
78
+ it 'supports nested abbreviations' do
79
+ c('AnXMLParser').must_equal('An_XML_Parser')
80
+ c('ANXMLParser').must_equal('ANXML_Parser')
81
+ c('AnXmLPaRsEr').must_equal('An_Xm_L_Pa_Rs_Er')
82
+ end
83
+
84
+ it 'preserves non-word characters' do
85
+ c(' a!!b#c').must_equal(' a!!b#c')
86
+ end
87
+
88
+ it 'preserves exsiting underscores' do
89
+ c('foo_bar').must_equal('foo_bar')
90
+ c('foo_Bar').must_equal('foo_Bar')
91
+ c('Foo_Bar').must_equal('Foo_Bar')
92
+ c('Foo_bar').must_equal('Foo_bar')
93
+
94
+ c('Foo___b_a__r').must_equal('Foo___b_a__r')
95
+ c('_').must_equal('_')
96
+ c('_a').must_equal('_a')
97
+ c('a_').must_equal('a_')
98
+ c('_a_').must_equal('_a_')
99
+ end
100
+
101
+ private
102
+
103
+ def c *args
104
+ Inochi.camel_to_snake_case(*args)
105
+ end
106
+ end