pg 0.11.0 → 0.12.0pre258

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.
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
-
@@ -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
-