hoe-manualgen 0.0.1

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.
Files changed (41) hide show
  1. data/.autotest +23 -0
  2. data/History.md +4 -0
  3. data/README.md +72 -0
  4. data/Rakefile +85 -0
  5. data/data/hoe-manualgen/layouts/default.erb.erb +87 -0
  6. data/data/hoe-manualgen/lib/api-filter.rb +76 -0
  7. data/data/hoe-manualgen/lib/editorial-filter.rb +59 -0
  8. data/data/hoe-manualgen/lib/examples-filter.rb +238 -0
  9. data/data/hoe-manualgen/lib/links-filter.rb +111 -0
  10. data/data/hoe-manualgen/resources/css/manual.css.erb +755 -0
  11. data/data/hoe-manualgen/resources/fonts/GraublauWeb.otf +0 -0
  12. data/data/hoe-manualgen/resources/fonts/GraublauWebBold.otf +0 -0
  13. data/data/hoe-manualgen/resources/fonts/Inconsolata.otf +0 -0
  14. data/data/hoe-manualgen/resources/images/arrow_225_small.png +0 -0
  15. data/data/hoe-manualgen/resources/images/arrow_315_small.png +0 -0
  16. data/data/hoe-manualgen/resources/images/arrow_skip.png +0 -0
  17. data/data/hoe-manualgen/resources/images/cc-by.png +0 -0
  18. data/data/hoe-manualgen/resources/images/dialog-error.png +0 -0
  19. data/data/hoe-manualgen/resources/images/dialog-information.png +0 -0
  20. data/data/hoe-manualgen/resources/images/dialog-warning.png +0 -0
  21. data/data/hoe-manualgen/resources/images/emblem-important.png +0 -0
  22. data/data/hoe-manualgen/resources/images/help.png +0 -0
  23. data/data/hoe-manualgen/resources/images/information.png +0 -0
  24. data/data/hoe-manualgen/resources/images/magnifier.png +0 -0
  25. data/data/hoe-manualgen/resources/images/magnifier_left.png +0 -0
  26. data/data/hoe-manualgen/resources/images/page_white_code.png +0 -0
  27. data/data/hoe-manualgen/resources/images/page_white_copy.png +0 -0
  28. data/data/hoe-manualgen/resources/images/printer.png +0 -0
  29. data/data/hoe-manualgen/resources/images/question.png +0 -0
  30. data/data/hoe-manualgen/resources/images/scripts_code.png +0 -0
  31. data/data/hoe-manualgen/resources/images/wrap.png +0 -0
  32. data/data/hoe-manualgen/resources/images/wrapping.png +0 -0
  33. data/data/hoe-manualgen/resources/js/jquery-1.4.4.min.js +167 -0
  34. data/data/hoe-manualgen/resources/js/manual.js +30 -0
  35. data/data/hoe-manualgen/resources/js/sh.js +580 -0
  36. data/data/hoe-manualgen/resources/swf/clipboard.swf +0 -0
  37. data/data/hoe-manualgen/src/index.page.erb +59 -0
  38. data/lib/hoe/manualgen.rb +804 -0
  39. data.tar.gz.sig +0 -0
  40. metadata +230 -0
  41. metadata.gz.sig +4 -0
@@ -0,0 +1,804 @@
1
+ #!/usr/bin/env ruby
2
+ #encoding: utf-8
3
+
4
+ require 'hoe'
5
+
6
+ require 'pathname'
7
+ require 'singleton'
8
+ require 'erb'
9
+ require 'fileutils'
10
+
11
+ require 'rake/clean'
12
+
13
+ # Rake tasks for generating a project manual or tutorial.
14
+ #
15
+ # This was born out of a frustration with other static HTML generation modules
16
+ # and systems. I've tried webby, webgen, rote, staticweb, staticmatic, and
17
+ # nanoc, but I didn't find any of them really suitable (except rote, which was
18
+ # excellent but apparently isn't maintained and has a fundamental
19
+ # incompatibilty with Rake because of some questionable monkeypatching.)
20
+ #
21
+ # So, since nothing seemed to scratch my itch, I'm going to scratch it myself.
22
+ #
23
+ # @author Michael Granger <ged@FaerieMUD.org>
24
+ # @author Mahlon E. Smith <mahlon@martini.nu>
25
+ #
26
+ module Hoe::ManualGen
27
+ require 'hoe/manualgen' # Hook up Gem.datadir( 'hoe-manualgen' )
28
+
29
+ include FileUtils
30
+ include FileUtils::Verbose if Rake.application.options.trace
31
+ include FileUtils::DryRun if Rake.application.options.dryrun
32
+
33
+ # Library version constant
34
+ VERSION = '0.0.1'
35
+
36
+ # Version-control revision constant
37
+ REVISION = %q$Revision: 63a03e1c872c $
38
+
39
+ # Configuration defaults
40
+ DEFAULT_BASE_DIR = Pathname( 'manual' )
41
+ DEFAULT_SOURCE_DIR = 'src'
42
+ DEFAULT_LAYOUTS_DIR = 'layouts'
43
+ DEFAULT_OUTPUT_DIR = 'output'
44
+ DEFAULT_RESOURCE_DIR = 'resources'
45
+ DEFAULT_LIB_DIR = 'lib'
46
+ DEFAULT_METADATA = OpenStruct.new
47
+
48
+ DEFAULT_MANUAL_TEMPLATE_DIR = Pathname( Gem.datadir('hoe-manualgen') || 'data/hoe-manualgen' )
49
+
50
+ # A glob pattern for matching resource files when copying them around
51
+ RESOURCE_EXTNAMES = %w[ css erb gif html jpg js otf page png rb svg svgz swf ]
52
+ RESOURCE_GLOB_PATTERN = "**/*.{%s}" % [ RESOURCE_EXTNAMES.join(',') ]
53
+
54
+ # The subdirectories to create under the manual dir
55
+ DEFAULT_MANUAL_SUBDIRS = [
56
+ DEFAULT_SOURCE_DIR,
57
+ DEFAULT_LAYOUTS_DIR,
58
+ DEFAULT_OUTPUT_DIR,
59
+ DEFAULT_RESOURCE_DIR,
60
+ DEFAULT_LIB_DIR,
61
+ ]
62
+
63
+ ### Manual page-generation class
64
+ class Page
65
+
66
+ ### An abstract filter class for manual content transformation.
67
+ class Filter
68
+ include Singleton
69
+
70
+ # A list of inheriting classes, keyed by normalized name
71
+ @derivatives = {}
72
+ class << self; attr_reader :derivatives; end
73
+
74
+ ### Inheritance callback -- keep track of all inheriting classes for
75
+ ### later.
76
+ def self::inherited( subclass )
77
+ key = subclass.name.
78
+ sub( /^.*::/, '' ).
79
+ gsub( /[^[:alpha:]]+/, '_' ).
80
+ downcase.
81
+ sub( /filter$/, '' )
82
+
83
+ self.derivatives[ key ] = subclass
84
+ self.derivatives[ key.to_sym ] = subclass
85
+
86
+ super
87
+ end
88
+
89
+
90
+ ### Export any static resources required by this filter to the given +output_dir+.
91
+ def export_resources( output_dir )
92
+ # No-op by default
93
+ end
94
+
95
+
96
+ ### Process the +page+'s source with the filter and return the altered content.
97
+ def process( source, page, metadata )
98
+ raise NotImplementedError,
99
+ "%s does not implement the #process method" % [ self.class.name ]
100
+ end
101
+ end # class Filter
102
+
103
+
104
+ ### The default page configuration if none is specified.
105
+ DEFAULT_CONFIG = {
106
+ 'filters' => [ 'erb', 'links', 'textile' ],
107
+ 'layout' => 'default.erb',
108
+ 'cleanup' => false,
109
+ }.freeze
110
+
111
+ # Pattern to match a source page with a YAML header
112
+ PAGE_WITH_YAML_HEADER = /
113
+ \A---\s*$ # It should should start with three hyphens
114
+ (.*?) # ...have some YAML stuff
115
+ ^---\s*$ # then have another three-hyphen line,
116
+ (.*)\Z # then the rest of the document
117
+ /xm
118
+
119
+ # Options to pass to libtidy
120
+ TIDY_OPTIONS = {
121
+ :show_warnings => true,
122
+ :indent => true,
123
+ :indent_attributes => false,
124
+ :indent_spaces => 4,
125
+ :vertical_space => true,
126
+ :tab_size => 4,
127
+ :wrap_attributes => true,
128
+ :wrap => 100,
129
+ :char_encoding => 'utf8'
130
+ }
131
+
132
+
133
+ ### Create a new page-generator for the given +sourcefile+, which will use
134
+ ### ones of the templates in +layouts_dir+ as a wrapper. The +basepath+
135
+ ### is the path to the base output directory, and the +catalog+ is the
136
+ ### PageCatalog to which the page belongs.
137
+ def initialize( catalog, sourcefile, layouts_dir, basepath='.' )
138
+ @catalog = catalog
139
+ @sourcefile = Pathname.new( sourcefile )
140
+ @layouts_dir = Pathname.new( layouts_dir )
141
+ @basepath = basepath
142
+
143
+ rawsource = nil
144
+ if Object.const_defined?( :Encoding )
145
+ rawsource = @sourcefile.read( :encoding => 'UTF-8' )
146
+ else
147
+ rawsource = @sourcefile.read
148
+ end
149
+ @config, @source = self.read_page_config( rawsource )
150
+
151
+ # $stderr.puts "Config is: %p" % [@config],
152
+ # "Source is: %p" % [ @source[0,100] ]
153
+ @filters = self.load_filters( @config['filters'] )
154
+
155
+ super()
156
+ end
157
+
158
+
159
+ ######
160
+ public
161
+ ######
162
+
163
+ # The PageCatalog to which the page belongs
164
+ attr_reader :catalog
165
+
166
+ # The relative path to the base directory, for prepending to page paths
167
+ attr_reader :basepath
168
+
169
+ # The Pathname object that specifys the page source file
170
+ attr_reader :sourcefile
171
+
172
+ # The configured layouts directory as a Pathname object.
173
+ attr_reader :layouts_dir
174
+
175
+ # The page configuration, as read from its YAML header
176
+ attr_reader :config
177
+
178
+ # The raw source of the page
179
+ attr_reader :source
180
+
181
+ # The filters the page will use to render itself
182
+ attr_reader :filters
183
+
184
+
185
+ ### Generate HTML output from the page and return it.
186
+ def generate( metadata )
187
+ content = self.generate_content( @source, metadata )
188
+
189
+ layout = self.config['layout'].sub( /\.erb$/, '' )
190
+ templatepath = @layouts_dir + "#{layout}.erb"
191
+ template = nil
192
+ if Object.const_defined?( :Encoding )
193
+ template = ERB.new( templatepath.read(:encoding => 'UTF-8') )
194
+ else
195
+ template = ERB.new( templatepath.read )
196
+ end
197
+
198
+ page = self
199
+ html = template.result( binding() )
200
+
201
+ # Use Tidy to clean up the html if 'cleanup' is turned on, but remove the Tidy
202
+ # meta-generator propaganda/advertising.
203
+ html = self.cleanup( html ).sub( %r:<meta name="generator"[^>]*tidy[^>]*/>:im, '' ) if
204
+ self.config['cleanup']
205
+
206
+ return html
207
+ end
208
+
209
+
210
+ ### Return the page title as specified in the YAML options
211
+ def title
212
+ return self.config['title'] || self.sourcefile.basename
213
+ end
214
+
215
+
216
+ ### Run the various filters on the given input and return the transformed
217
+ ### content.
218
+ def generate_content( input, metadata )
219
+ return @filters.inject( input ) do |source, filter|
220
+ filter.process( source, self, metadata )
221
+ end
222
+ end
223
+
224
+
225
+ ### Trim the YAML header from the provided page +source+, convert it to
226
+ ### a Ruby object, and return it.
227
+ def read_page_config( source )
228
+ unless source =~ PAGE_WITH_YAML_HEADER
229
+ return DEFAULT_CONFIG.dup, source
230
+ end
231
+
232
+ pageconfig = YAML.load( $1 )
233
+ source = $2
234
+
235
+ return DEFAULT_CONFIG.merge( pageconfig ), source
236
+ end
237
+
238
+
239
+ ### Clean up and return the given HTML +source+.
240
+ def cleanup( source )
241
+ require 'tidy'
242
+
243
+ Tidy.path = '/usr/lib/libtidy.dylib'
244
+ Tidy.open( TIDY_OPTIONS ) do |tidy|
245
+ tidy.options.output_xhtml = true
246
+
247
+ xml = tidy.clean( source )
248
+ errors = tidy.errors
249
+ error_message( errors.join ) unless errors.empty?
250
+ warn tidy.diagnostics if $DEBUG
251
+ return xml
252
+ end
253
+ rescue LoadError => err
254
+ $stderr.puts "No cleanup: " + err.message
255
+ return source
256
+ end
257
+
258
+
259
+ ### Get (singleton) instances of the filters named in +filterlist+ and return them.
260
+ def load_filters( filterlist )
261
+ filterlist.flatten.collect do |key|
262
+ raise ArgumentError, "filter '#{key}' could not be loaded" unless
263
+ Page::Filter.derivatives.key?( key )
264
+ Page::Filter.derivatives[ key ].instance
265
+ end
266
+ end
267
+
268
+
269
+ ### Build the index relative to the receiving page and return it as a String
270
+ def make_index_html
271
+ items = [ '<div class="index">' ]
272
+
273
+ @catalog.traverse_page_hierarchy( self ) do |type, title, path|
274
+ case type
275
+ when :section
276
+ items << %Q{<div class="section">}
277
+ items << %Q{<h3><a href="#{self.basepath + path}/">#{title}</a></h3>}
278
+ items << '<ul class="index-section">'
279
+
280
+ when :current_section
281
+ items << %Q{<div class="section current-section">}
282
+ items << %Q{<h3><a href="#{self.basepath + path}/">#{title}</a></h3>}
283
+ items << '<ul class="index-section current-index-section">'
284
+
285
+ when :section_end, :current_section_end
286
+ items << '</ul></div>'
287
+
288
+ when :entry
289
+ items << %Q{<li><a href="#{self.basepath + path}.html">#{title}</a></li>}
290
+
291
+ when :current_entry
292
+ items << %Q{<li class="current-entry">#{title}</li>}
293
+
294
+ else
295
+ raise "Unknown index entry type %p" % [ type ]
296
+ end
297
+
298
+ end
299
+
300
+ items << '</div>'
301
+
302
+ return items.join("\n")
303
+ end
304
+
305
+ end
306
+
307
+
308
+ ### A catalog of Page objects that can be referenced by filters.
309
+ class PageCatalog
310
+
311
+ ### Create a new PageCatalog that will load Page objects for .page files
312
+ ### in the specified +sourcedir+.
313
+ def initialize( sourcedir, layoutsdir )
314
+ @sourcedir = sourcedir
315
+ @layoutsdir = layoutsdir
316
+
317
+ @pages = []
318
+ @path_index = {}
319
+ @uri_index = {}
320
+ @title_index = {}
321
+ @hierarchy = {}
322
+
323
+ self.find_and_load_pages
324
+ end
325
+
326
+
327
+ ######
328
+ public
329
+ ######
330
+
331
+ # An index of the pages in the catalog by Pathname
332
+ attr_reader :path_index
333
+
334
+ # An index of the pages in the catalog by title
335
+ attr_reader :title_index
336
+
337
+ # An index of the pages in the catalog by the URI of their source relative to the source
338
+ # directory
339
+ attr_reader :uri_index
340
+
341
+ # The hierarchy of pages in the catalog, suitable for generating an on-page index
342
+ attr_reader :hierarchy
343
+
344
+ # An Array of all Page objects found
345
+ attr_reader :pages
346
+
347
+ # The Pathname location of the .page files.
348
+ attr_reader :sourcedir
349
+
350
+ # The Pathname location of look and feel templates.
351
+ attr_reader :layoutsdir
352
+
353
+
354
+ ### Traverse the catalog's #hierarchy, yielding to the given +builder+
355
+ ### block for each entry, as well as each time a sub-hash is entered or
356
+ ### exited, setting the +type+ appropriately. Valid values for +type+ are:
357
+ ###
358
+ ### :entry, :section, :section_end
359
+ ###
360
+ ### If the optional +from+ value is given, it should be the Page object
361
+ ### which is considered "current"; if the +from+ object is the same as the
362
+ ### hierarchy entry being yielded, it will be yielded with the +type+ set to
363
+ ### one of:
364
+ ###
365
+ ### :current_entry, :current_section, :current_section_end
366
+ ###
367
+ ### each of which correspond to the like-named type from above.
368
+ def traverse_page_hierarchy( from=nil, &builder ) # :yields: type, title, path
369
+ raise LocalJumpError, "no block given" unless builder
370
+ self.traverse_hierarchy( Pathname.new(''), self.hierarchy, from, &builder )
371
+ end
372
+
373
+
374
+ #########
375
+ protected
376
+ #########
377
+
378
+ ### Sort and traverse the specified +hash+ recursively, yielding for each entry.
379
+ def traverse_hierarchy( path, hash, from=nil, &builder )
380
+ # Now generate the index in the sorted order
381
+ sort_hierarchy( hash ).each do |subpath, page_or_section|
382
+ if page_or_section.is_a?( Hash )
383
+ self.handle_section_callback( path + subpath, page_or_section, from, &builder )
384
+ else
385
+ next if subpath == INDEX_PATH
386
+ self.handle_page_callback( path + subpath, page_or_section, from, &builder )
387
+ end
388
+ end
389
+ end
390
+
391
+
392
+ ### Return the specified hierarchy of pages as a sorted Array of tuples.
393
+ ### Sort the hierarchy using the 'index' config value of either the
394
+ ### page, or the directory's index page if it's a directory.
395
+ def sort_hierarchy( hierarchy )
396
+ hierarchy.sort_by do |subpath, page_or_section|
397
+
398
+ # Directory
399
+ if page_or_section.is_a?( Hash )
400
+
401
+ # Use the index of the index page if it exists
402
+ if page_or_section[INDEX_PATH]
403
+ idx = page_or_section[INDEX_PATH].config['index']
404
+ trace "Index page's index for directory '%s' is: %p" % [ subpath, idx ]
405
+ "%08d:%s" % [ idx || 0, subpath.to_s ]
406
+ else
407
+ trace "Using the path for the sort of directory %p" % [ subpath ]
408
+ subpath.to_s
409
+ end
410
+
411
+ # Page
412
+ else
413
+ if subpath == INDEX_PATH
414
+ trace "Sort index for index page %p is 0" % [ subpath ]
415
+ '0'
416
+ else
417
+ idx = page_or_section.config['index']
418
+ trace "Sort index for page %p is: %p" % [ subpath, idx ]
419
+ "%08d:%s" % [ idx || 0, subpath.to_s ]
420
+ end
421
+ end
422
+
423
+ end # sort_by
424
+ end
425
+
426
+
427
+ INDEX_PATH = Pathname.new('index')
428
+
429
+ ### Build up the data structures necessary for calling the +builder+ callback
430
+ ### for an index section and call it, then recurse into the section contents.
431
+ def handle_section_callback( path, section, from=nil, &builder )
432
+ from_current = false
433
+ trace "Section handler: path=%p, section keys=%p, from=%s" %
434
+ [ path, section.keys, from.sourcefile ]
435
+
436
+ # Call the callback with :section -- determine the section title from
437
+ # the 'index.page' file underneath it, or the directory name if no
438
+ # index.page exists.
439
+ if section.key?( INDEX_PATH )
440
+ if section[INDEX_PATH].sourcefile.dirname == from.sourcefile.dirname
441
+ from_current = true
442
+ builder.call( :current_section, section[INDEX_PATH].title, path )
443
+ else
444
+ builder.call( :section, section[INDEX_PATH].title, path )
445
+ end
446
+ else
447
+ title = File.dirname( path ).gsub( /_/, ' ' )
448
+ builder.call( :section, title, path )
449
+ end
450
+
451
+ # Recurse
452
+ self.traverse_hierarchy( path, section, from, &builder )
453
+
454
+ # Call the callback with :section_end
455
+ if from_current
456
+ builder.call( :current_section_end, '', path )
457
+ else
458
+ builder.call( :section_end, '', path )
459
+ end
460
+ end
461
+
462
+
463
+ ### Yield the specified +page+ to the builder
464
+ def handle_page_callback( path, page, from=nil )
465
+ if from == page
466
+ yield( :current_entry, page.title, path )
467
+ else
468
+ yield( :entry, page.title, path )
469
+ end
470
+ end
471
+
472
+
473
+ ### Find all .page files under the configured +sourcedir+ and create a new
474
+ ### Page object for each one.
475
+ def find_and_load_pages
476
+ Pathname.glob( @sourcedir + '**/*.page' ).each do |pagefile|
477
+ path_to_base = @sourcedir.relative_path_from( pagefile.dirname )
478
+
479
+ page = Page.new( self, pagefile, @layoutsdir, path_to_base )
480
+ hierpath = pagefile.relative_path_from( @sourcedir )
481
+
482
+ @pages << page
483
+ @path_index[ pagefile ] = page
484
+ @title_index[ page.title ] = page
485
+ @uri_index[ hierpath.to_s ] = page
486
+
487
+ # Place the page in the page hierarchy by using inject to find and/or create the
488
+ # necessary subhashes. The last run of inject will return the leaf hash in which
489
+ # the page will live
490
+ section = hierpath.dirname.split[1..-1].inject( @hierarchy ) do |hier, component|
491
+ hier[ component ] ||= {}
492
+ hier[ component ]
493
+ end
494
+
495
+ section[ pagefile.basename('.page') ] = page
496
+ end
497
+ end
498
+
499
+ end
500
+
501
+
502
+ ### A Textile filter for the manual generation tasklib.
503
+ class TextileFilter < Page::Filter
504
+
505
+ ### Load RedCloth when the filter is first created
506
+ def initialize( *args )
507
+ require 'redcloth'
508
+ super
509
+ end
510
+
511
+
512
+ ### Process the given +source+ as Textile and return the resulting HTML
513
+ ### fragment.
514
+ def process( source, *ignored )
515
+ formatter = RedCloth::TextileDoc.new( source )
516
+ formatter.hard_breaks = false
517
+ formatter.no_span_caps = true
518
+ return formatter.to_html
519
+ end
520
+
521
+ end
522
+
523
+
524
+ ### An ERB filter.
525
+ class ErbFilter < Page::Filter
526
+
527
+ ### Process the given +source+ as ERB and return the resulting HTML
528
+ ### fragment.
529
+ def process( source, page, metadata )
530
+ template_name = page.sourcefile.basename
531
+ template = ERB.new( source )
532
+ return template.result( binding() )
533
+ end
534
+
535
+ end
536
+
537
+
538
+ attr_accessor :manual_template_dir,
539
+ :manual_base_dir,
540
+ :manual_source_dir,
541
+ :manual_layouts_dir,
542
+ :manual_output_dir,
543
+ :manual_resource_dir,
544
+ :manual_lib_dir,
545
+ :manual_metadata,
546
+ :manual_paths
547
+
548
+
549
+ ### Hoe callback -- set up defaults
550
+ def initialize_manualgen
551
+ @manual_template_dir = DEFAULT_MANUAL_TEMPLATE_DIR
552
+ @manual_base_dir = DEFAULT_BASE_DIR
553
+ @manual_source_dir = DEFAULT_SOURCE_DIR
554
+ @manual_layouts_dir = DEFAULT_LAYOUTS_DIR
555
+ @manual_output_dir = DEFAULT_OUTPUT_DIR
556
+ @manual_resource_dir = DEFAULT_RESOURCE_DIR
557
+ @manual_lib_dir = DEFAULT_LIB_DIR
558
+ @manual_metadata = DEFAULT_METADATA
559
+ @manual_paths = {}
560
+
561
+ self.extra_dev_deps << ['hoe-manualgen', "~> #{VERSION}"] unless
562
+ self.name == 'hoe-manualgen'
563
+ end
564
+
565
+
566
+ ### Set up the tasks for building the manual
567
+ def define_manualgen_tasks
568
+
569
+ # Make Pathnames of the directories relative to the base_dir
570
+ basedir = Pathname( self.manual_base_dir )
571
+ @manual_paths = {
572
+ :templatedir => Pathname( self.manual_template_dir ),
573
+ :basedir => basedir,
574
+ :sourcedir => basedir + self.manual_source_dir,
575
+ :layoutsdir => basedir + self.manual_layouts_dir,
576
+ :resourcedir => basedir + self.manual_resource_dir,
577
+ :libdir => basedir + self.manual_lib_dir,
578
+ :outputdir => basedir + self.manual_output_dir,
579
+ }
580
+
581
+ if basedir.directory?
582
+ trace "Basedir %s exists, so defining tasks for building the manual" % [ basedir ]
583
+ define_existing_manual_tasks( @manual_paths )
584
+ else
585
+ trace "Basedir %s doesn't exist, so defining tasks for creating a new manual" % [ basedir ]
586
+ define_manual_setup_tasks( @manual_paths )
587
+ end
588
+
589
+ end
590
+
591
+
592
+ ### Define tasks for creating a skeleton manual
593
+ def define_manual_setup_tasks( paths )
594
+ templatedir = paths[:templatedir]
595
+ trace "Templatedir is: %s" % [ templatedir ]
596
+ manualdir = paths[:basedir]
597
+
598
+ desc "Create a manual for this project from a template"
599
+ task :manual do
600
+ log "No manual directory (#{manualdir}) currently exists."
601
+ ask_for_confirmation( "Create a new manual directory tree from a template?" ) do
602
+ log "Generating manual skeleton"
603
+ install_manual_directory( manualdir, templatedir )
604
+ end
605
+
606
+ end # task :manual
607
+
608
+ end
609
+
610
+
611
+ ### Generate (or refresh) a manual directory from the specified +templatedir+.
612
+ def install_manual_directory( manualdir, templatedir, include_srcdir=true )
613
+
614
+ self.manual_paths.each do |key, dir|
615
+ mkpath( dir, :mode => 0755 )
616
+ end
617
+
618
+ Pathname.glob( templatedir + RESOURCE_GLOB_PATTERN ).each do |tmplfile|
619
+ if tmplfile.to_s =~ %r{/src/}
620
+ trace "Skipping %s" % [ tmplfile ]
621
+ next unless include_srcdir
622
+ end
623
+
624
+ # Render ERB files
625
+ if tmplfile.extname == '.erb'
626
+ rname = tmplfile.basename( '.erb' )
627
+ target = manualdir + tmplfile.dirname.relative_path_from( templatedir ) + rname
628
+ template = ERB.new( tmplfile.read, nil, '<>' )
629
+
630
+ target.dirname.mkpath unless target.dirname.directory?
631
+ html = template.result( binding() )
632
+ log "generating #{target}"
633
+
634
+ target.open( File::WRONLY|File::CREAT|File::TRUNC, 0644 ) do |fh|
635
+ fh.print( html )
636
+ end
637
+
638
+ # Just copy anything else
639
+ else
640
+ target = manualdir + tmplfile.relative_path_from( templatedir )
641
+ mkpath target.dirname,
642
+ :mode => 0755, :noop => $dryrun unless target.dirname.directory?
643
+ install tmplfile, target,
644
+ :mode => 0644, :noop => $dryrun
645
+ end
646
+ end
647
+ end
648
+
649
+
650
+ ### Define tasks for generating output for an existing manual.
651
+ def define_existing_manual_tasks( paths )
652
+
653
+ # Read all of the filters, pages, and layouts
654
+ load_filter_libraries( paths[:libdir] )
655
+ trace "Creating the manual page catalog with source at %p, layouts in %p" %
656
+ paths.values_at( :sourcedir, :layoutsdir )
657
+ catalog = PageCatalog.new( paths[:sourcedir], paths[:layoutsdir] )
658
+
659
+ # Declare the tasks outside the namespace that point in
660
+ desc "Generate the manual"
661
+ task :manual => "manual:build"
662
+
663
+ CLEAN.include( paths[:outputdir].to_s )
664
+
665
+ # Namespace all our tasks
666
+ namespace :manual do
667
+
668
+ # Set up a file task for each resource, then a conversion task for
669
+ # each page in the sourcedir so pages re-generate if they're modified
670
+ setup_resource_copy_tasks( paths[:resourcedir], paths[:outputdir] )
671
+ manual_pages = setup_page_conversion_tasks( paths[:sourcedir], paths[:outputdir], catalog )
672
+
673
+ # The main task
674
+ desc "Build the manual"
675
+ task :build => [ :copy_resources, :copy_apidocs, :generate_pages ]
676
+
677
+ task :clean do
678
+ RakeFileUtils.verbose( $verbose ) do
679
+ rm_f manual_pages.to_a
680
+ end
681
+ remove_dir( paths[:outputdir] ) if ( paths[:outputdir] + '.buildtime' ).exist?
682
+ end
683
+
684
+ desc "Force a rebuild of the manual"
685
+ task :rebuild => [ :clean, :build ]
686
+
687
+ desc "Update the resources templates for the manual to the latest versions"
688
+ task :update do
689
+ ask_for_confirmation( "Update the resources/templates in the manual directory?" ) do
690
+ log "Updating..."
691
+ install_manual_directory( paths[:basedir], paths[:templatedir], false )
692
+ end
693
+
694
+ end # task :manual
695
+
696
+ end
697
+ end
698
+
699
+
700
+ ### Load the filter libraries provided in the given +libdir+
701
+ def load_filter_libraries( libdir )
702
+ Pathname.glob( libdir.expand_path + '*.rb' ) do |filterlib|
703
+ trace " loading filter library #{filterlib}"
704
+ require( filterlib )
705
+ end
706
+ end
707
+
708
+
709
+ ### Set up the main HTML-generation task that will convert files in the given +sourcedir+ to
710
+ ### HTML in the +outputdir+
711
+ def setup_page_conversion_tasks( sourcedir, outputdir, catalog )
712
+
713
+ # we need to figure out what HTML pages need to be generated so we can set up the
714
+ # dependency that causes the rule to be fired for each one when the task is invoked.
715
+ manual_sources = Rake::FileList[ catalog.path_index.keys.map(&:to_s) ]
716
+ trace " found %d source files" % [ manual_sources.length ]
717
+
718
+ # Map .page files to their equivalent .html output
719
+ html_pathmap = "%%{%s,%s}X.html" % [ sourcedir, outputdir ]
720
+ manual_pages = manual_sources.pathmap( html_pathmap )
721
+ trace "Mapping sources like so: \n %p -> %p" %
722
+ [ manual_sources.first, manual_pages.first ]
723
+
724
+ # Output directory task
725
+ directory( outputdir.to_s )
726
+ file outputdir.to_s do
727
+ touch outputdir + '.buildtime'
728
+ end
729
+
730
+ # Rule to generate .html files from .page files
731
+ rule(
732
+ %r{#{outputdir}/.*\.html$} => [
733
+ proc {|name| name.sub(/\.[^.]+$/, '.page').sub(outputdir.to_s, sourcedir.to_s) },
734
+ outputdir.to_s
735
+ ]) do |task|
736
+
737
+ source = Pathname.new( task.source )
738
+ target = Pathname.new( task.name )
739
+ log " #{ source } -> #{ target }"
740
+
741
+ page = catalog.path_index[ source ]
742
+ html = page.generate( self.manual_metadata )
743
+ #trace " page object is: %p" % [ page ]
744
+
745
+ target.dirname.mkpath
746
+ target.open( File::WRONLY|File::CREAT|File::TRUNC ) do |io|
747
+ io.write( html )
748
+ end
749
+ end
750
+
751
+ # Group all the manual page output files targets into a containing task
752
+ desc "Generate any pages of the manual that have changed"
753
+ task :generate_pages => manual_pages
754
+ return manual_pages
755
+ end
756
+
757
+
758
+ ### Copy method for resources -- passed as a block to the various file tasks that copy
759
+ ### resources to the output directory.
760
+ def copy_resource( task )
761
+ source = task.prerequisites[ 1 ]
762
+ target = task.name
763
+
764
+ when_writing do
765
+ trace " #{source} -> #{target}"
766
+ mkpath File.dirname( target ), :verbose => $trace unless
767
+ File.directory?( File.dirname(target) )
768
+ install source, target, :mode => 0644, :verbose => $trace
769
+ end
770
+ end
771
+
772
+
773
+ ### Set up a rule for copying files from the resources directory to the output dir.
774
+ def setup_resource_copy_tasks( resourcedir, outputdir )
775
+ glob = resourcedir + RESOURCE_GLOB_PATTERN
776
+ resources = FileList[ glob.to_s ]
777
+ resources.exclude( /\.svn/ )
778
+ target_pathmap = "%%{%s,%s}p" % [ resourcedir, outputdir ]
779
+ targets = resources.pathmap( target_pathmap )
780
+ copier = self.method( :copy_resource ).to_proc
781
+
782
+ # Create a file task to copy each file to the output directory
783
+ resources.each_with_index do |resource, i|
784
+ file( targets[i] => [ outputdir.to_s, resource ], &copier )
785
+ end
786
+
787
+ desc "Copy API documentation to the manual output directory"
788
+ task :copy_apidocs => [ outputdir.to_s, :docs ] do
789
+ # Since Hoe hard-codes the 'docs' output dir, it's hard-coded
790
+ # here too.
791
+ apidir = outputdir + 'api'
792
+ self.manual_metadata.api_dir = apidir
793
+ cp_r( 'doc', apidir )
794
+ end
795
+
796
+ # Now group all the resource file tasks into a containing task
797
+ desc "Copy manual resources to the output directory"
798
+ task :copy_resources => targets do
799
+ log "Copying manual resources"
800
+ end
801
+ end
802
+
803
+ end unless defined?( Hoe::ManualGen )
804
+