pg 0.9.0.pre156-x86-mswin32

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