hoe-manualgen 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+