linguistics 1.0.9 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data.tar.gz.sig +0 -0
  2. data/.gemtest +0 -0
  3. data/ChangeLog +849 -342
  4. data/History.rdoc +11 -0
  5. data/LICENSE +9 -9
  6. data/Manifest.txt +44 -0
  7. data/README.rdoc +226 -0
  8. data/Rakefile +32 -349
  9. data/examples/endocs.rb +272 -0
  10. data/examples/generalize_sentence.rb +2 -1
  11. data/examples/klingon.rb +22 -0
  12. data/lib/linguistics.rb +130 -292
  13. data/lib/linguistics/en.rb +337 -1628
  14. data/lib/linguistics/en/articles.rb +138 -0
  15. data/lib/linguistics/en/conjugation.rb +2245 -0
  16. data/lib/linguistics/en/conjunctions.rb +202 -0
  17. data/lib/linguistics/en/{infinitive.rb → infinitives.rb} +41 -55
  18. data/lib/linguistics/en/linkparser.rb +41 -49
  19. data/lib/linguistics/en/numbers.rb +483 -0
  20. data/lib/linguistics/en/participles.rb +33 -0
  21. data/lib/linguistics/en/pluralization.rb +810 -0
  22. data/lib/linguistics/en/stemmer.rb +75 -0
  23. data/lib/linguistics/en/titlecase.rb +121 -0
  24. data/lib/linguistics/en/wordnet.rb +63 -97
  25. data/lib/linguistics/inflector.rb +89 -0
  26. data/lib/linguistics/iso639.rb +534 -448
  27. data/lib/linguistics/languagebehavior.rb +36 -0
  28. data/lib/linguistics/monkeypatches.rb +42 -0
  29. data/spec/lib/constants.rb +15 -0
  30. data/spec/lib/helpers.rb +38 -0
  31. data/spec/linguistics/en/articles_spec.rb +797 -0
  32. data/spec/linguistics/en/conjugation_spec.rb +2083 -0
  33. data/spec/linguistics/en/conjunctions_spec.rb +154 -0
  34. data/spec/linguistics/en/infinitives_spec.rb +518 -0
  35. data/spec/linguistics/en/linkparser_spec.rb +66 -0
  36. data/spec/linguistics/en/numbers_spec.rb +1295 -0
  37. data/spec/linguistics/en/participles_spec.rb +55 -0
  38. data/spec/linguistics/en/pluralization_spec.rb +4636 -0
  39. data/spec/linguistics/en/stemmer_spec.rb +72 -0
  40. data/spec/linguistics/en/titlecase_spec.rb +841 -0
  41. data/spec/linguistics/en/wordnet_spec.rb +85 -0
  42. data/spec/linguistics/en_spec.rb +45 -167
  43. data/spec/linguistics/inflector_spec.rb +40 -0
  44. data/spec/linguistics/iso639_spec.rb +49 -53
  45. data/spec/linguistics/monkeypatches_spec.rb +40 -0
  46. data/spec/linguistics_spec.rb +46 -76
  47. metadata +241 -113
  48. metadata.gz.sig +0 -0
  49. data/README +0 -166
  50. data/README.english +0 -245
  51. data/rake/191_compat.rb +0 -26
  52. data/rake/dependencies.rb +0 -76
  53. data/rake/documentation.rb +0 -123
  54. data/rake/helpers.rb +0 -502
  55. data/rake/hg.rb +0 -318
  56. data/rake/manual.rb +0 -787
  57. data/rake/packaging.rb +0 -129
  58. data/rake/publishing.rb +0 -341
  59. data/rake/style.rb +0 -62
  60. data/rake/svn.rb +0 -668
  61. data/rake/testing.rb +0 -152
  62. data/rake/verifytask.rb +0 -64
  63. data/tests/en/infinitive.tests.rb +0 -207
  64. data/tests/en/inflect.tests.rb +0 -1389
  65. data/tests/en/lafcadio.tests.rb +0 -77
  66. data/tests/en/linkparser.tests.rb +0 -42
  67. data/tests/en/lprintf.tests.rb +0 -77
  68. data/tests/en/titlecase.tests.rb +0 -73
  69. data/tests/en/wordnet.tests.rb +0 -95
data/rake/hg.rb DELETED
@@ -1,318 +0,0 @@
1
- #
2
- # Mercurial Rake Tasks
3
-
4
- require 'enumerator'
5
-
6
- #
7
- # Authors:
8
- # * Michael Granger <ged@FaerieMUD.org>
9
- #
10
-
11
- unless defined?( HG_DOTDIR )
12
-
13
- # Mercurial constants
14
- HG_DOTDIR = BASEDIR + '.hg'
15
- HG_STORE = HG_DOTDIR + 'store'
16
-
17
- IGNORE_FILE = BASEDIR + '.hgignore'
18
-
19
-
20
- ###
21
- ### Helpers
22
- ###
23
-
24
- module MercurialHelpers
25
- require './helpers.rb' unless defined?( RakefileHelpers )
26
- include RakefileHelpers
27
-
28
- ###############
29
- module_function
30
- ###############
31
-
32
- ### Generate a commit log from a diff and return it as a String.
33
- def make_commit_log
34
- diff = read_command_output( 'hg', 'diff' )
35
- fail "No differences." if diff.empty?
36
-
37
- return diff
38
- end
39
-
40
- ### Generate a commit log and invoke the user's editor on it.
41
- def edit_commit_log
42
- diff = make_commit_log()
43
-
44
- File.open( COMMIT_MSG_FILE, File::WRONLY|File::TRUNC|File::CREAT ) do |fh|
45
- fh.print( diff )
46
- end
47
-
48
- edit( COMMIT_MSG_FILE )
49
- end
50
-
51
- ### Generate a changelog.
52
- def make_changelog
53
- log = read_command_output( 'hg', 'log', '--style', 'compact' )
54
- return log
55
- end
56
-
57
- ### Get the 'tip' info and return it as a Hash
58
- def get_tip_info
59
- data = read_command_output( 'hg', 'tip' )
60
- return YAML.load( data )
61
- end
62
-
63
- ### Return the ID for the current rev
64
- def get_current_rev
65
- id = read_command_output( 'hg', '-q', 'identify' )
66
- return id.chomp
67
- end
68
-
69
- ### Read the list of existing tags and return them as an Array
70
- def get_tags
71
- taglist = read_command_output( 'hg', 'tags' )
72
- return taglist.split( /\n/ )
73
- end
74
-
75
-
76
- ### Read any remote repo paths known by the current repo and return them as a hash.
77
- def get_repo_paths
78
- paths = {}
79
- pathspec = read_command_output( 'hg', 'paths' )
80
- pathspec.split.each_slice( 3 ) do |name, _, url|
81
- paths[ name ] = url
82
- end
83
- return paths
84
- end
85
-
86
- ### Return the list of files which are not of status 'clean'
87
- def get_uncommitted_files
88
- list = read_command_output( 'hg', 'status', '-n', '--color', 'never' )
89
- list = list.split( /\n/ )
90
-
91
- trace "Changed files: %p" % [ list ]
92
- return list
93
- end
94
-
95
- ### Return the list of files which are of status 'unknown'
96
- def get_unknown_files
97
- list = read_command_output( 'hg', 'status', '-un', '--color', 'never' )
98
- list = list.split( /\n/ )
99
-
100
- trace "New files: %p" % [ list ]
101
- return list
102
- end
103
-
104
- ### Returns a human-scannable file list by joining and truncating the list if it's too long.
105
- def humanize_file_list( list, indent=FILE_INDENT )
106
- listtext = list[0..5].join( "\n#{indent}" )
107
- if list.length > 5
108
- listtext << " (and %d other/s)" % [ list.length - 5 ]
109
- end
110
-
111
- return listtext
112
- end
113
-
114
-
115
- ### Add the list of +pathnames+ to the .hgignore list.
116
- def hg_ignore_files( *pathnames )
117
- patterns = pathnames.flatten.collect do |path|
118
- '^' + Regexp.escape(path) + '$'
119
- end
120
- trace "Ignoring %d files." % [ pathnames.length ]
121
-
122
- IGNORE_FILE.open( File::CREAT|File::WRONLY|File::APPEND, 0644 ) do |fh|
123
- fh.puts( patterns )
124
- end
125
- end
126
-
127
-
128
- ### Delete the files in the given +filelist+ after confirming with the user.
129
- def delete_extra_files( filelist )
130
- description = humanize_file_list( filelist, ' ' )
131
- log "Files to delete:\n ", description
132
- ask_for_confirmation( "Really delete them?", false ) do
133
- filelist.each do |f|
134
- rm_rf( f, :verbose => true )
135
- end
136
- end
137
- end
138
-
139
- end # module MercurialHelpers
140
-
141
-
142
- ### Rakefile support
143
- def get_vcs_rev( dir='.' )
144
- return MercurialHelpers.get_current_rev
145
- end
146
- def make_changelog
147
- return MercurialHelpers.make_changelog
148
- end
149
-
150
-
151
- ###
152
- ### Tasks
153
- ###
154
-
155
- desc "Mercurial tasks"
156
- namespace :hg do
157
- include MercurialHelpers
158
-
159
- desc "Prepare for a new release"
160
- task :prep_release do
161
- uncommitted_files = get_uncommitted_files()
162
- unless uncommitted_files.empty?
163
- log "Uncommitted files:\n",
164
- *uncommitted_files.map {|fn| " #{fn}\n" }
165
- ask_for_confirmation( "\nRelease anyway?", true ) do
166
- log "Okay, releasing with uncommitted versions."
167
- end
168
- end
169
-
170
- tags = get_tags()
171
- rev = get_current_rev()
172
- pkg_version_tag = "v#{PKG_VERSION}"
173
-
174
- # Look for a tag for the current release version, and if it exists abort
175
- if tags.include?( pkg_version_tag )
176
- error "Version #{PKG_VERSION} already has a tag. Did you mean " +
177
- "to increment the version in #{VERSION_FILE}?"
178
- fail
179
- end
180
-
181
- # Sign the current rev
182
- log "Signing rev #{rev}"
183
- run 'hg', 'sign'
184
-
185
- # Tag the current rev
186
- log "Tagging rev #{rev} as #{pkg_version_tag}"
187
- run 'hg', 'tag', pkg_version_tag
188
-
189
- # Offer to push
190
- Rake::Task['hg:push'].invoke
191
- end
192
-
193
- desc "Check for new files and offer to add/ignore/delete them."
194
- task :newfiles do
195
- log "Checking for new files..."
196
-
197
- entries = get_unknown_files()
198
-
199
- unless entries.empty?
200
- files_to_add = []
201
- files_to_ignore = []
202
- files_to_delete = []
203
-
204
- entries.each do |entry|
205
- action = prompt_with_default( " #{entry}: (a)dd, (i)gnore, (s)kip (d)elete", 's' )
206
- case action
207
- when 'a'
208
- files_to_add << entry
209
- when 'i'
210
- files_to_ignore << entry
211
- when 'd'
212
- files_to_delete << entry
213
- end
214
- end
215
-
216
- unless files_to_add.empty?
217
- run 'hg', 'add', *files_to_add
218
- end
219
-
220
- unless files_to_ignore.empty?
221
- hg_ignore_files( *files_to_ignore )
222
- end
223
-
224
- unless files_to_delete.empty?
225
- delete_extra_files( files_to_delete )
226
- end
227
- end
228
- end
229
- task :add => :newfiles
230
-
231
-
232
- desc "Pull and update from the default repo"
233
- task :pull do
234
- paths = get_repo_paths()
235
- if origin_url = paths['default']
236
- ask_for_confirmation( "Pull and update from '#{origin_url}'?", false ) do
237
- Rake::Task['hg:pull_without_confirmation'].invoke
238
- end
239
- else
240
- trace "Skipping pull: No 'default' path."
241
- end
242
- end
243
-
244
-
245
- desc "Pull and update without confirmation"
246
- task :pull_without_confirmation do
247
- run 'hg', 'pull', '-u'
248
- end
249
-
250
-
251
- desc "Update to tip"
252
- task :update do
253
- run 'hg', 'update'
254
- end
255
-
256
-
257
- desc "Clobber all changes (hg up -C)"
258
- task :update_and_clobber do
259
- run 'hg', 'update', '-C'
260
- end
261
-
262
-
263
- desc "Check the current code in if tests pass"
264
- task :checkin => ['hg:pull', 'hg:newfiles', 'test', COMMIT_MSG_FILE] do
265
- targets = get_target_args()
266
- $stderr.puts '---', File.read( COMMIT_MSG_FILE ), '---'
267
- ask_for_confirmation( "Continue with checkin?" ) do
268
- run 'hg', 'ci', '-l', COMMIT_MSG_FILE, targets
269
- rm_f COMMIT_MSG_FILE
270
- end
271
- Rake::Task['hg:push'].invoke
272
- end
273
- task :commit => :checkin
274
- task :ci => :checkin
275
-
276
- CLEAN.include( COMMIT_MSG_FILE )
277
-
278
- desc "Push to the default origin repo (if there is one)"
279
- task :push do
280
- paths = get_repo_paths()
281
- if origin_url = paths['default']
282
- ask_for_confirmation( "Push to '#{origin_url}'?", false ) do
283
- Rake::Task['hg:push_without_confirmation'].invoke
284
- end
285
- else
286
- trace "Skipping push: No 'default' path."
287
- end
288
- end
289
-
290
- desc "Push to the default repo without confirmation"
291
- task :push_without_confirmation do
292
- run 'hg', 'push'
293
- end
294
-
295
- end
296
-
297
- if HG_DOTDIR.exist?
298
- trace "Defining mercurial VCS tasks"
299
-
300
- desc "Check in all the changes in your current working copy"
301
- task :ci => 'hg:ci'
302
- desc "Check in all the changes in your current working copy"
303
- task :checkin => 'hg:ci'
304
-
305
- desc "Tag and sign revision before a release"
306
- task :prep_release => 'hg:prep_release'
307
-
308
- file COMMIT_MSG_FILE do
309
- edit_commit_log()
310
- end
311
-
312
- else
313
- trace "Not defining mercurial tasks: no #{HG_DOTDIR}"
314
- end
315
-
316
- end
317
-
318
-
data/rake/manual.rb DELETED
@@ -1,787 +0,0 @@
1
- #
2
- # Manual-generation Rake tasks and classes
3
-
4
- #
5
- # Authors:
6
- # * Michael Granger <ged@FaerieMUD.org>
7
- # * Mahlon E. Smith <mahlon@martini.nu>
8
- #
9
- # This was born out of a frustration with other static HTML generation modules
10
- # and systems. I've tried webby, webgen, rote, staticweb, staticmatic, and
11
- # nanoc, but I didn't find any of them really suitable (except rote, which was
12
- # excellent but apparently isn't maintained and has a fundamental
13
- # incompatibilty with Rake because of some questionable monkeypatching.)
14
- #
15
- # So, since nothing seemed to scratch my itch, I'm going to scratch it myself.
16
- #
17
-
18
- require 'pathname'
19
- require 'singleton'
20
- require 'rake/tasklib'
21
- require 'erb'
22
-
23
-
24
- ### Namespace for Manual-generation classes
25
- module Manual
26
-
27
- ### Manual page-generation class
28
- class Page
29
-
30
- ### An abstract filter class for manual content transformation.
31
- class Filter
32
- include Singleton
33
-
34
- # A list of inheriting classes, keyed by normalized name
35
- @derivatives = {}
36
- class << self; attr_reader :derivatives; end
37
-
38
- ### Inheritance callback -- keep track of all inheriting classes for
39
- ### later.
40
- def self::inherited( subclass )
41
- key = subclass.name.
42
- sub( /^.*::/, '' ).
43
- gsub( /[^[:alpha:]]+/, '_' ).
44
- downcase.
45
- sub( /filter$/, '' )
46
-
47
- self.derivatives[ key ] = subclass
48
- self.derivatives[ key.to_sym ] = subclass
49
-
50
- super
51
- end
52
-
53
-
54
- ### Export any static resources required by this filter to the given +output_dir+.
55
- def export_resources( output_dir )
56
- # No-op by default
57
- end
58
-
59
-
60
- ### Process the +page+'s source with the filter and return the altered content.
61
- def process( source, page, metadata )
62
- raise NotImplementedError,
63
- "%s does not implement the #process method" % [ self.class.name ]
64
- end
65
- end # class Filter
66
-
67
-
68
- ### The default page configuration if none is specified.
69
- DEFAULT_CONFIG = {
70
- 'filters' => [ 'erb', 'links', 'textile' ],
71
- 'layout' => 'default.page',
72
- 'cleanup' => false,
73
- }.freeze
74
-
75
- # Pattern to match a source page with a YAML header
76
- PAGE_WITH_YAML_HEADER = /
77
- \A---\s*$ # It should should start with three hyphens
78
- (.*?) # ...have some YAML stuff
79
- ^---\s*$ # then have another three-hyphen line,
80
- (.*)\Z # then the rest of the document
81
- /xm
82
-
83
- # Options to pass to libtidy
84
- TIDY_OPTIONS = {
85
- :show_warnings => true,
86
- :indent => true,
87
- :indent_attributes => false,
88
- :indent_spaces => 4,
89
- :vertical_space => true,
90
- :tab_size => 4,
91
- :wrap_attributes => true,
92
- :wrap => 100,
93
- :char_encoding => 'utf8'
94
- }
95
-
96
-
97
- ### Create a new page-generator for the given +sourcefile+, which will use
98
- ### ones of the templates in +layouts_dir+ as a wrapper. The +basepath+
99
- ### is the path to the base output directory, and the +catalog+ is the
100
- ### Manual::PageCatalog to which the page belongs.
101
- def initialize( catalog, sourcefile, layouts_dir, basepath='.' )
102
- @catalog = catalog
103
- @sourcefile = Pathname.new( sourcefile )
104
- @layouts_dir = Pathname.new( layouts_dir )
105
- @basepath = basepath
106
-
107
- rawsource = @sourcefile.read
108
- @config, @source = self.read_page_config( rawsource )
109
-
110
- # $stderr.puts "Config is: %p" % [@config],
111
- # "Source is: %p" % [ @source[0,100] ]
112
- @filters = self.load_filters( @config['filters'] )
113
-
114
- super()
115
- end
116
-
117
-
118
- ######
119
- public
120
- ######
121
-
122
- # The Manual::PageCatalog to which the page belongs
123
- attr_reader :catalog
124
-
125
- # The relative path to the base directory, for prepending to page paths
126
- attr_reader :basepath
127
-
128
- # The Pathname object that specifys the page source file
129
- attr_reader :sourcefile
130
-
131
- # The configured layouts directory as a Pathname object.
132
- attr_reader :layouts_dir
133
-
134
- # The page configuration, as read from its YAML header
135
- attr_reader :config
136
-
137
- # The raw source of the page
138
- attr_reader :source
139
-
140
- # The filters the page will use to render itself
141
- attr_reader :filters
142
-
143
-
144
- ### Generate HTML output from the page and return it.
145
- def generate( metadata )
146
- content = self.generate_content( @source, metadata )
147
-
148
- layout = self.config['layout'].sub( /\.page$/, '' )
149
- templatepath = @layouts_dir + "#{layout}.page"
150
- template = nil
151
- if Object.const_defined?( :Encoding )
152
- template = ERB.new( templatepath.read(:encoding => 'UTF-8') )
153
- else
154
- template = ERB.new( templatepath.read )
155
- end
156
-
157
- page = self
158
- html = template.result( binding() )
159
-
160
- # Use Tidy to clean up the html if 'cleanup' is turned on, but remove the Tidy
161
- # meta-generator propaganda/advertising.
162
- html = self.cleanup( html ).sub( %r:<meta name="generator"[^>]*tidy[^>]*/>:im, '' ) if
163
- self.config['cleanup']
164
-
165
- return html
166
- end
167
-
168
-
169
- ### Return the page title as specified in the YAML options
170
- def title
171
- return self.config['title'] || self.sourcefile.basename
172
- end
173
-
174
-
175
- ### Run the various filters on the given input and return the transformed
176
- ### content.
177
- def generate_content( input, metadata )
178
- return @filters.inject( input ) do |source, filter|
179
- filter.process( source, self, metadata )
180
- end
181
- end
182
-
183
-
184
- ### Trim the YAML header from the provided page +source+, convert it to
185
- ### a Ruby object, and return it.
186
- def read_page_config( source )
187
- unless source =~ PAGE_WITH_YAML_HEADER
188
- return DEFAULT_CONFIG.dup, source
189
- end
190
-
191
- pageconfig = YAML.load( $1 )
192
- source = $2
193
-
194
- return DEFAULT_CONFIG.merge( pageconfig ), source
195
- end
196
-
197
-
198
- ### Clean up and return the given HTML +source+.
199
- def cleanup( source )
200
- require 'tidy'
201
-
202
- Tidy.path = '/usr/lib/libtidy.dylib'
203
- Tidy.open( TIDY_OPTIONS ) do |tidy|
204
- tidy.options.output_xhtml = true
205
-
206
- xml = tidy.clean( source )
207
- errors = tidy.errors
208
- error_message( errors.join ) unless errors.empty?
209
- trace tidy.diagnostics
210
- return xml
211
- end
212
- rescue LoadError => err
213
- trace "No cleanup: " + err.message
214
- return source
215
- end
216
-
217
-
218
- ### Get (singleton) instances of the filters named in +filterlist+ and return them.
219
- def load_filters( filterlist )
220
- filterlist.flatten.collect do |key|
221
- raise ArgumentError, "filter '#{key}' is not loaded" unless
222
- Manual::Page::Filter.derivatives.key?( key )
223
- Manual::Page::Filter.derivatives[ key ].instance
224
- end
225
- end
226
-
227
-
228
- ### Build the index relative to the receiving page and return it as a String
229
- def make_index_html
230
- items = [ '<div class="index">' ]
231
-
232
- @catalog.traverse_page_hierarchy( self ) do |type, title, path|
233
- case type
234
- when :section
235
- items << %Q{<div class="section">}
236
- items << %Q{<h2><a href="#{self.basepath + path}/">#{title}</a></h2>}
237
- items << '<ul class="index-section">'
238
-
239
- when :current_section
240
- items << %Q{<div class="section current-section">}
241
- items << %Q{<h2><a href="#{self.basepath + path}/">#{title}</a></h2>}
242
- items << '<ul class="index-section current-index-section">'
243
-
244
- when :section_end, :current_section_end
245
- items << '</ul></div>'
246
-
247
- when :entry
248
- items << %Q{<li><a href="#{self.basepath + path}.html">#{title}</a></li>}
249
-
250
- when :current_entry
251
- items << %Q{<li class="current-entry">#{title}</li>}
252
-
253
- else
254
- raise "Unknown index entry type %p" % [ type ]
255
- end
256
-
257
- end
258
-
259
- items << '</div>'
260
-
261
- return items.join("\n")
262
- end
263
-
264
- end
265
-
266
-
267
- ### A catalog of Manual::Page objects that can be referenced by various criteria.
268
- class PageCatalog
269
-
270
- ### Create a new PageCatalog that will load Manual::Page objects for .page files
271
- ### in the specified +sourcedir+.
272
- def initialize( sourcedir, layoutsdir )
273
- @sourcedir = sourcedir
274
- @layoutsdir = layoutsdir
275
-
276
- @pages = []
277
- @path_index = {}
278
- @uri_index = {}
279
- @title_index = {}
280
- @hierarchy = {}
281
-
282
- self.find_and_load_pages
283
- end
284
-
285
-
286
- ######
287
- public
288
- ######
289
-
290
- # An index of the pages in the catalog by Pathname
291
- attr_reader :path_index
292
-
293
- # An index of the pages in the catalog by title
294
- attr_reader :title_index
295
-
296
- # An index of the pages in the catalog by the URI of their source relative to the source
297
- # directory
298
- attr_reader :uri_index
299
-
300
- # The hierarchy of pages in the catalog, suitable for generating an on-page index
301
- attr_reader :hierarchy
302
-
303
- # An Array of all Manual::Page objects found
304
- attr_reader :pages
305
-
306
- # The Pathname location of the .page files.
307
- attr_reader :sourcedir
308
-
309
- # The Pathname location of look and feel templates.
310
- attr_reader :layoutsdir
311
-
312
-
313
- ### Traverse the catalog's #hierarchy, yielding to the given +builder+
314
- ### block for each entry, as well as each time a sub-hash is entered or
315
- ### exited, setting the +type+ appropriately. Valid values for +type+ are:
316
- ###
317
- ### :entry, :section, :section_end
318
- ###
319
- ### If the optional +from+ value is given, it should be the Manual::Page object
320
- ### which is considered "current"; if the +from+ object is the same as the
321
- ### hierarchy entry being yielded, it will be yielded with the +type+ set to
322
- ### one of:
323
- ###
324
- ### :current_entry, :current_section, :current_section_end
325
- ###
326
- ### each of which correspond to the like-named type from above.
327
- def traverse_page_hierarchy( from=nil, &builder ) # :yields: type, title, path
328
- raise LocalJumpError, "no block given" unless builder
329
- self.traverse_hierarchy( Pathname.new(''), self.hierarchy, from, &builder )
330
- end
331
-
332
-
333
- #########
334
- protected
335
- #########
336
-
337
- ### Sort and traverse the specified +hash+ recursively, yielding for each entry.
338
- def traverse_hierarchy( path, hash, from=nil, &builder )
339
- # Now generate the index in the sorted order
340
- sort_hierarchy( hash ).each do |subpath, page_or_section|
341
- if page_or_section.is_a?( Hash )
342
- self.handle_section_callback( path + subpath, page_or_section, from, &builder )
343
- else
344
- next if subpath == INDEX_PATH
345
- self.handle_page_callback( path + subpath, page_or_section, from, &builder )
346
- end
347
- end
348
- end
349
-
350
-
351
- ### Return the specified hierarchy of pages as a sorted Array of tuples.
352
- ### Sort the hierarchy using the 'index' config value of either the
353
- ### page, or the directory's index page if it's a directory.
354
- def sort_hierarchy( hierarchy )
355
- hierarchy.sort_by do |subpath, page_or_section|
356
-
357
- # Directory
358
- if page_or_section.is_a?( Hash )
359
-
360
- # Use the index of the index page if it exists
361
- if page_or_section[INDEX_PATH]
362
- idx = page_or_section[INDEX_PATH].config['index']
363
- trace "Index page's index for directory '%s' is: %p" % [ subpath, idx ]
364
- idx.to_s || subpath.to_s
365
- else
366
- trace "Using the path for the sort of directory %p" % [ subpath ]
367
- subpath.to_s
368
- end
369
-
370
- # Page
371
- else
372
- if subpath == INDEX_PATH
373
- trace "Sort index for index page %p is 0" % [ subpath ]
374
- '0'
375
- else
376
- idx = page_or_section.config['index']
377
- trace "Sort index for page %p is: %p" % [ subpath, idx ]
378
- idx.to_s || subpath.to_s
379
- end
380
- end
381
-
382
- end # sort_by
383
- end
384
-
385
-
386
- INDEX_PATH = Pathname.new('index')
387
-
388
- ### Build up the data structures necessary for calling the +builder+ callback
389
- ### for an index section and call it, then recurse into the section contents.
390
- def handle_section_callback( path, section, from=nil, &builder )
391
- from_current = false
392
- trace "Section handler: path=%p, section keys=%p, from=%s" %
393
- [ path, section.keys, from.sourcefile ]
394
-
395
- # Call the callback with :section -- determine the section title from
396
- # the 'index.page' file underneath it, or the directory name if no
397
- # index.page exists.
398
- if section.key?( INDEX_PATH )
399
- if section[INDEX_PATH].sourcefile.dirname == from.sourcefile.dirname
400
- from_current = true
401
- builder.call( :current_section, section[INDEX_PATH].title, path )
402
- else
403
- builder.call( :section, section[INDEX_PATH].title, path )
404
- end
405
- else
406
- title = File.dirname( path ).gsub( /_/, ' ' )
407
- builder.call( :section, title, path )
408
- end
409
-
410
- # Recurse
411
- self.traverse_hierarchy( path, section, from, &builder )
412
-
413
- # Call the callback with :section_end
414
- if from_current
415
- builder.call( :current_section_end, '', path )
416
- else
417
- builder.call( :section_end, '', path )
418
- end
419
- end
420
-
421
-
422
- ### Yield the specified +page+ to the builder
423
- def handle_page_callback( path, page, from=nil )
424
- if from == page
425
- yield( :current_entry, page.title, path )
426
- else
427
- yield( :entry, page.title, path )
428
- end
429
- end
430
-
431
-
432
- ### Find and store
433
-
434
- ### Find all .page files under the configured +sourcedir+ and create a new
435
- ### Manual::Page object for each one.
436
- def find_and_load_pages
437
- Pathname.glob( @sourcedir + '**/*.page' ).each do |pagefile|
438
- path_to_base = @sourcedir.relative_path_from( pagefile.dirname )
439
-
440
- page = Manual::Page.new( self, pagefile, @layoutsdir, path_to_base )
441
- hierpath = pagefile.relative_path_from( @sourcedir )
442
-
443
- @pages << page
444
- @path_index[ pagefile ] = page
445
- @title_index[ page.title ] = page
446
- @uri_index[ hierpath.to_s ] = page
447
-
448
- # Place the page in the page hierarchy by using inject to find and/or create the
449
- # necessary subhashes. The last run of inject will return the leaf hash in which
450
- # the page will live
451
- section = hierpath.dirname.split[1..-1].inject( @hierarchy ) do |hier, component|
452
- hier[ component ] ||= {}
453
- hier[ component ]
454
- end
455
-
456
- section[ pagefile.basename('.page') ] = page
457
- end
458
- end
459
-
460
- end
461
-
462
-
463
- ### A Textile filter for the manual generation tasklib, implemented using RedCloth.
464
- class TextileFilter < Manual::Page::Filter
465
-
466
- ### Load RedCloth when the filter is first created
467
- def initialize( *args )
468
- require 'redcloth'
469
- super
470
- end
471
-
472
-
473
- ### Process the given +source+ as Textile and return the resulting HTML
474
- ### fragment.
475
- def process( source, *ignored )
476
- formatter = RedCloth::TextileDoc.new( source )
477
- formatter.hard_breaks = false
478
- formatter.no_span_caps = true
479
- return formatter.to_html
480
- end
481
-
482
- end
483
-
484
-
485
- ### An ERB filter for the manual generation tasklib, implemented using Erubis.
486
- class ErbFilter < Manual::Page::Filter
487
-
488
- ### Process the given +source+ as ERB and return the resulting HTML
489
- ### fragment.
490
- def process( source, page, metadata )
491
- template_name = page.sourcefile.basename
492
- template = ERB.new( source )
493
- return template.result( binding() )
494
- end
495
-
496
- end
497
-
498
-
499
- ### Manual generation task library
500
- class GenTask < Rake::TaskLib
501
-
502
- # Default values for task config variables
503
- DEFAULT_NAME = :manual
504
- DEFAULT_BASE_DIR = Pathname.new( 'docs/manual' )
505
- DEFAULT_SOURCE_DIR = 'source'
506
- DEFAULT_LAYOUTS_DIR = 'layouts'
507
- DEFAULT_OUTPUT_DIR = 'output'
508
- DEFAULT_RESOURCE_DIR = 'resources'
509
- DEFAULT_LIB_DIR = 'lib'
510
- DEFAULT_METADATA = OpenStruct.new
511
-
512
-
513
- ### Define a new manual-generation task with the given +name+.
514
- def initialize( name=:manual )
515
- @name = name
516
-
517
- @source_dir = DEFAULT_SOURCE_DIR
518
- @layouts_dir = DEFAULT_LAYOUTS_DIR
519
- @output_dir = DEFAULT_OUTPUT_DIR
520
- @resource_dir = DEFAULT_RESOURCE_DIR
521
- @lib_dir = DEFAULT_LIB_DIR
522
- @metadata = DEFAULT_METADATA
523
-
524
- yield( self ) if block_given?
525
-
526
- self.define
527
- end
528
-
529
-
530
- ######
531
- public
532
- ######
533
-
534
- attr_accessor :base_dir,
535
- :source_dir,
536
- :layouts_dir,
537
- :output_dir,
538
- :resource_dir,
539
- :lib_dir,
540
- :metadata
541
-
542
- attr_reader :name
543
-
544
-
545
- ### Set up the tasks for building the manual
546
- def define
547
-
548
- # Set up a description if the caller hasn't already defined one
549
- unless Rake.application.last_comment
550
- desc "Generate the manual"
551
- end
552
-
553
- # Make Pathnames of the directories relative to the base_dir
554
- basedir = Pathname.new( @base_dir )
555
- sourcedir = basedir + @source_dir
556
- layoutsdir = basedir + @layouts_dir
557
- outputdir = @output_dir
558
- resourcedir = basedir + @resource_dir
559
- libdir = basedir + @lib_dir
560
-
561
- load_filter_libraries( libdir )
562
- catalog = Manual::PageCatalog.new( sourcedir, layoutsdir )
563
-
564
- # Declare the tasks outside the namespace that point in
565
- task @name => "#@name:build"
566
- task "clobber_#@name" => "#@name:clobber"
567
-
568
- namespace( self.name ) do
569
- setup_resource_copy_tasks( resourcedir, outputdir )
570
- manual_pages = setup_page_conversion_tasks( sourcedir, outputdir, catalog )
571
-
572
- desc "Build the manual"
573
- task :build => [ :apidocs, :copy_resources, :copy_apidocs, :generate_pages ]
574
-
575
- task :clobber do
576
- RakeFileUtils.verbose( $verbose ) do
577
- rm_f manual_pages.to_a
578
- end
579
- remove_dir( outputdir ) if ( outputdir + '.buildtime' ).exist?
580
- end
581
-
582
- desc "Remove any previously-generated parts of the manual and rebuild it"
583
- task :rebuild => [ :clobber, self.name ]
584
-
585
- desc "Watch for changes to the source files and rebuild when they change"
586
- task :autobuild do
587
- scope = [ self.name ]
588
- loop do
589
- t = Rake.application.lookup( :build, scope )
590
- t.reenable
591
- t.prerequisites.each do |pt|
592
- if task = Rake.application.lookup( pt, scope )
593
- task.reenable
594
- else
595
- trace "Hmmm... no %p task in scope %p?" % [ pt, scope ]
596
- end
597
- end
598
- t.invoke
599
- sleep 2
600
- trace " waking up..."
601
- end
602
- end
603
- end
604
-
605
- end # def define
606
-
607
-
608
- ### Load the filter libraries provided in the given +libdir+
609
- def load_filter_libraries( libdir )
610
- Pathname.glob( libdir + '*.rb' ) do |filterlib|
611
- trace " loading filter library #{filterlib}"
612
- require( filterlib )
613
- end
614
- end
615
-
616
-
617
- ### Set up the main HTML-generation task that will convert files in the given +sourcedir+ to
618
- ### HTML in the +outputdir+
619
- def setup_page_conversion_tasks( sourcedir, outputdir, catalog )
620
-
621
- # we need to figure out what HTML pages need to be generated so we can set up the
622
- # dependency that causes the rule to be fired for each one when the task is invoked.
623
- manual_sources = FileList[ catalog.path_index.keys.map {|pn| pn.to_s} ]
624
- trace " found %d source files" % [ manual_sources.length ]
625
-
626
- # Map .page files to their equivalent .html output
627
- html_pathmap = "%%{%s,%s}X.html" % [ sourcedir, outputdir ]
628
- manual_pages = manual_sources.pathmap( html_pathmap )
629
- trace "Mapping sources like so: \n %p -> %p" %
630
- [ manual_sources.first, manual_pages.first ]
631
-
632
- # Output directory task
633
- directory( outputdir.to_s )
634
- file outputdir.to_s do
635
- touch outputdir + '.buildtime'
636
- end
637
-
638
- # Rule to generate .html files from .page files
639
- rule(
640
- %r{#{outputdir}/.*\.html$} => [
641
- proc {|name| name.sub(/\.[^.]+$/, '.page').sub( outputdir, sourcedir) },
642
- outputdir.to_s
643
- ]) do |task|
644
-
645
- source = Pathname.new( task.source )
646
- target = Pathname.new( task.name )
647
- log " #{ source } -> #{ target }"
648
-
649
- page = catalog.path_index[ source ]
650
- #trace " page object is: %p" % [ page ]
651
-
652
- target.dirname.mkpath
653
- target.open( File::WRONLY|File::CREAT|File::TRUNC ) do |io|
654
- io.write( page.generate(metadata) )
655
- end
656
- end
657
-
658
- # Group all the manual page output files targets into a containing task
659
- desc "Generate any pages of the manual that have changed"
660
- task :generate_pages => manual_pages
661
- return manual_pages
662
- end
663
-
664
-
665
- ### Copy method for resources -- passed as a block to the various file tasks that copy
666
- ### resources to the output directory.
667
- def copy_resource( task )
668
- source = task.prerequisites[ 1 ]
669
- target = task.name
670
-
671
- when_writing do
672
- trace " #{source} -> #{target}"
673
- mkpath File.dirname( target ), :verbose => $trace unless
674
- File.directory?( File.dirname(target) )
675
- install source, target, :mode => 0644, :verbose => $trace
676
- end
677
- end
678
-
679
-
680
- ### Set up a rule for copying files from the resources directory to the output dir.
681
- def setup_resource_copy_tasks( resourcedir, outputdir )
682
- resources = FileList[ resourcedir + '**/*.{js,css,png,gif,jpg,html,svg,svgz,swf}' ]
683
- resources.exclude( /\.svn/ )
684
- target_pathmap = "%%{%s,%s}p" % [ resourcedir, outputdir ]
685
- targets = resources.pathmap( target_pathmap )
686
- copier = self.method( :copy_resource ).to_proc
687
-
688
- # Create a file task to copy each file to the output directory
689
- resources.each_with_index do |resource, i|
690
- file( targets[i] => [ outputdir.to_s, resource ], &copier )
691
- end
692
-
693
- desc "Copy API documentation to the manual output directory"
694
- task :copy_apidocs => :apidocs do
695
- cp_r( API_DOCSDIR, outputdir )
696
- end
697
-
698
- # Now group all the resource file tasks into a containing task
699
- desc "Copy manual resources to the output directory"
700
- task :copy_resources => targets do
701
- log "Copying manual resources"
702
- end
703
- end
704
-
705
- end # class Manual::GenTask
706
-
707
- end
708
-
709
-
710
-
711
- ### Task: manual generation
712
- if MANUALDIR.exist?
713
- MANUALOUTPUTDIR = MANUALDIR + 'output'
714
- trace "Manual will be generated in: #{MANUALOUTPUTDIR}"
715
-
716
- begin
717
- directory MANUALOUTPUTDIR.to_s
718
-
719
- Manual::GenTask.new do |manual|
720
- manual.metadata.version = PKG_VERSION
721
- manual.metadata.api_dir = API_DOCSDIR
722
- manual.output_dir = MANUALOUTPUTDIR
723
- manual.base_dir = MANUALDIR
724
- manual.source_dir = 'src'
725
- end
726
-
727
- CLOBBER.include( MANUALOUTPUTDIR.to_s )
728
-
729
- rescue LoadError => err
730
- task :no_manual do
731
- $stderr.puts "Manual-generation tasks not defined: %s" % [ err.message ]
732
- end
733
-
734
- task :manual => :no_manual
735
- task :clobber_manual => :no_manual
736
- end
737
-
738
- else
739
- TEMPLATEDIR = RAKE_TASKDIR + 'manualdir'
740
-
741
- if TEMPLATEDIR.exist?
742
-
743
- desc "Create a manual for this project from a template"
744
- task :manual do
745
- log "No manual directory (#{MANUALDIR}) currently exists."
746
- ask_for_confirmation( "Create a new manual directory tree from a template?" ) do
747
- MANUALDIR.mkpath
748
-
749
- %w[layouts lib output resources src].each do |dir|
750
- FileUtils.mkpath( MANUALDIR + dir, :mode => 0755, :verbose => true, :noop => $dryrun )
751
- end
752
-
753
- Pathname.glob( TEMPLATEDIR + '**/*.{rb,css,png,js,erb,page}' ).each do |tmplfile|
754
- trace "extname is: #{tmplfile.extname}"
755
-
756
- # Render ERB files
757
- if tmplfile.extname == '.erb'
758
- rname = tmplfile.basename( '.erb' )
759
- target = MANUALDIR + tmplfile.dirname.relative_path_from( TEMPLATEDIR ) + rname
760
- template = ERB.new( tmplfile.read, nil, '<>' )
761
-
762
- target.dirname.mkpath( :mode => 0755, :verbose => true, :noop => $dryrun ) unless
763
- target.dirname.directory?
764
- html = template.result( binding() )
765
- log "generating #{target}: html => #{html[0,20]}"
766
-
767
- target.open( File::WRONLY|File::CREAT|File::EXCL, 0644 ) do |fh|
768
- fh.print( html )
769
- end
770
-
771
- # Just copy anything else
772
- else
773
- target = MANUALDIR + tmplfile.relative_path_from( TEMPLATEDIR )
774
- FileUtils.mkpath target.dirname,
775
- :mode => 0755, :verbose => true, :noop => $dryrun unless target.dirname.directory?
776
- FileUtils.install tmplfile, target,
777
- :mode => 0644, :verbose => true, :noop => $dryrun
778
- end
779
- end
780
- end
781
-
782
- end # task :manual
783
-
784
- end
785
- end
786
-
787
-