bluecloth 2.0.0

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