pluginfactory 1.0.3 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,26 @@
1
+ # 1.9.1 fixes
2
+
3
+
4
+ # Make Pathname compatible with 1.8.7 Pathname.
5
+ unless Pathname.instance_methods.include?( :=~ )
6
+ class Pathname
7
+ def self::glob( *args ) # :yield: p
8
+ args = args.collect {|p| p.to_s }
9
+ if block_given?
10
+ Dir.glob(*args) {|f| yield self.new(f) }
11
+ else
12
+ Dir.glob(*args).map {|f| self.new(f) }
13
+ end
14
+ end
15
+
16
+ def =~( other )
17
+ self.to_s =~ other
18
+ end
19
+
20
+ def to_str
21
+ self.to_s
22
+ end
23
+ end
24
+ end
25
+
26
+
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # Dependency-checking and Installation Rake Tasks
3
- # $Id: dependencies.rb 10 2008-07-18 15:52:48Z deveiant $
3
+ # $Id: dependencies.rb 43 2008-09-05 18:19:16Z deveiant $
4
4
  #
5
5
 
6
6
  require 'rubygems/dependency_installer'
@@ -9,8 +9,7 @@ require 'rubygems/requirement'
9
9
  require 'rubygems/doc_manager'
10
10
 
11
11
  ### Install the specified +gems+ if they aren't already installed.
12
- def install_gems( *gems )
13
- gems.flatten!
12
+ def install_gems( gems )
14
13
 
15
14
  defaults = Gem::DependencyInstaller::DEFAULT_OPTIONS.merge({
16
15
  :generate_rdoc => true,
@@ -28,20 +27,28 @@ def install_gems( *gems )
28
27
 
29
28
  gemindex = Gem::SourceIndex.from_installed_gems
30
29
 
31
- gems.each do |gemname|
32
- if (( specs = gemindex.search(gemname) )) && ! specs.empty?
33
- log "Version %s of %s is already installed; skipping..." %
34
- [ specs.first.version, specs.first.name ]
30
+ gems.each do |gemname, reqstring|
31
+ requirement = Gem::Requirement.new( reqstring )
32
+ trace "requirement is: %p" % [ requirement ]
33
+
34
+ trace "Searching for an installed #{gemname}..."
35
+ specs = gemindex.find_name( gemname )
36
+ trace "...found %d specs: %s" %
37
+ [ specs.length, specs.collect {|s| "%s %s" % [s.name, s.version] }.join(', ') ]
38
+
39
+ if spec = specs.find {|spec| requirement.satisfied_by?(spec.version) }
40
+ log "Version %s of %s is already installed (needs %s); skipping..." %
41
+ [ spec.version, spec.name, requirement ]
35
42
  next
36
43
  end
37
44
 
38
45
  rgv = Gem::Version.new( Gem::RubyGemsVersion )
39
46
  installer = nil
40
47
 
41
- log "Trying to install #{gemname.inspect}..."
48
+ log "Trying to install #{gemname.inspect} #{requirement}..."
42
49
  if rgv >= Gem::Version.new( '1.1.1' )
43
50
  installer = Gem::DependencyInstaller.new
44
- installer.install( gemname )
51
+ installer.install( gemname, requirement )
45
52
  else
46
53
  installer = Gem::DependencyInstaller.new( gemname )
47
54
  installer.install
@@ -55,8 +62,15 @@ def install_gems( *gems )
55
62
  end
56
63
 
57
64
 
58
- ### Task: install gems for development tasks
65
+ ### Task: install runtime dependencies
66
+ desc "Install runtime dependencies as gems"
59
67
  task :install_dependencies do
60
- install_gems( DEPENDENCIES.keys )
68
+ install_gems( DEPENDENCIES )
69
+ end
70
+
71
+ ### Task: install gems for development tasks
72
+ desc "Install development dependencies as gems"
73
+ task :install_dev_dependencies do
74
+ install_gems( DEVELOPMENT_DEPENDENCIES )
61
75
  end
62
76
 
@@ -1,9 +1,21 @@
1
+ # encoding: utf-8
1
2
  #####################################################################
2
3
  ### G L O B A L H E L P E R F U N C T I O N S
3
4
  #####################################################################
4
5
 
6
+
5
7
  require 'pathname'
6
- require 'readline'
8
+
9
+ begin
10
+ require 'readline'
11
+ include Readline
12
+ rescue LoadError
13
+ # Fall back to a plain prompt
14
+ def readline( text )
15
+ $stderr.print( text.chomp )
16
+ return $stdin.gets
17
+ end
18
+ end
7
19
 
8
20
  # Set some ANSI escape code constants (Shamelessly stolen from Perl's
9
21
  # Term::ANSIColor by Russ Allbery <rra@stanford.edu> and Zenin <zenin@best.com>
@@ -42,7 +54,7 @@ CLEAR_CURRENT_LINE = "\e[2K"
42
54
  ### Output a logging message
43
55
  def log( *msg )
44
56
  output = colorize( msg.flatten.join(' '), 'cyan' )
45
- $deferr.puts( output )
57
+ $stderr.puts( output )
46
58
  end
47
59
 
48
60
 
@@ -50,7 +62,7 @@ end
50
62
  def trace( *msg )
51
63
  return unless $trace
52
64
  output = colorize( msg.flatten.join(' '), 'yellow' )
53
- $deferr.puts( output )
65
+ $stderr.puts( output )
54
66
  end
55
67
 
56
68
 
@@ -59,9 +71,14 @@ end
59
71
  def run( *cmd )
60
72
  cmd.flatten!
61
73
 
62
- log( cmd.collect {|part| part =~ /\s/ ? part.inspect : part} )
74
+ if cmd.length > 1
75
+ trace( cmd.collect {|part| part =~ /\s/ ? part.inspect : part} )
76
+ else
77
+ trace( cmd )
78
+ end
79
+
63
80
  if $dryrun
64
- $deferr.puts "(dry run mode)"
81
+ $stderr.puts "(dry run mode)"
65
82
  else
66
83
  system( *cmd )
67
84
  unless $?.success?
@@ -71,6 +88,14 @@ def run( *cmd )
71
88
  end
72
89
 
73
90
 
91
+ ### Run a subordinate Rake process with the same options and the specified +targets+.
92
+ def rake( *targets )
93
+ opts = ARGV.select {|arg| arg[0,1] == '-' }
94
+ args = opts + targets.map {|t| t.to_s }
95
+ run 'rake', '-N', *args
96
+ end
97
+
98
+
74
99
  ### Open a pipe to a process running the given +cmd+ and call the given block with it.
75
100
  def pipeto( *cmd )
76
101
  $DEBUG = true
@@ -78,7 +103,7 @@ def pipeto( *cmd )
78
103
  cmd.flatten!
79
104
  log( "Opening a pipe to: ", cmd.collect {|part| part =~ /\s/ ? part.inspect : part} )
80
105
  if $dryrun
81
- $deferr.puts "(dry run mode)"
106
+ $stderr.puts "(dry run mode)"
82
107
  else
83
108
  open( '|-', 'w+' ) do |io|
84
109
 
@@ -108,7 +133,7 @@ def download( sourceuri, targetfile=nil )
108
133
  log "Downloading %s to %s" % [sourceuri, targetfile]
109
134
  targetpath.open( File::WRONLY|File::TRUNC|File::CREAT, 0644 ) do |ofh|
110
135
 
111
- url = sourceuri.is_a?( URI ) ? sourceuri : URI.parse( sourceuri )
136
+ url = sourceuri.is_a?( URI ) ? sourceuri : URI( sourceuri )
112
137
  downloaded = false
113
138
  limit = 5
114
139
 
@@ -126,7 +151,7 @@ def download( sourceuri, targetfile=nil )
126
151
  puts "done."
127
152
 
128
153
  elsif res.is_a?( Net::HTTPRedirection )
129
- url = URI.parse( res['location'] )
154
+ url = URI( res['location'] )
130
155
  log "...following redirection to: %s" % [ url ]
131
156
  limit -= 1
132
157
  sleep 0.2
@@ -159,12 +184,12 @@ end
159
184
  def ansi_code( *attributes )
160
185
  attributes.flatten!
161
186
  attributes.collect! {|at| at.to_s }
162
- # $deferr.puts "Returning ansicode for TERM = %p: %p" %
187
+ # $stderr.puts "Returning ansicode for TERM = %p: %p" %
163
188
  # [ ENV['TERM'], attributes ]
164
189
  return '' unless /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i =~ ENV['TERM']
165
190
  attributes = ANSI_ATTRIBUTES.values_at( *attributes ).compact.join(';')
166
191
 
167
- # $deferr.puts " attr is: %p" % [attributes]
192
+ # $stderr.puts " attr is: %p" % [attributes]
168
193
  if attributes.empty?
169
194
  return ''
170
195
  else
@@ -194,7 +219,7 @@ end
194
219
  ### Output the specified <tt>msg</tt> as an ANSI-colored error message
195
220
  ### (white on red).
196
221
  def error_message( msg, details='' )
197
- $deferr.puts colorize( 'bold', 'white', 'on_red' ) { msg } + details
222
+ $stderr.puts colorize( 'bold', 'white', 'on_red' ) { msg } + details
198
223
  end
199
224
  alias :error :error_message
200
225
 
@@ -216,7 +241,7 @@ def prompt( prompt_string, failure_msg="Try again." ) # :yields: response
216
241
 
217
242
  begin
218
243
  prompt = make_prompt_string( prompt_string )
219
- response = Readline.readline( prompt ) || ''
244
+ response = readline( prompt ) || ''
220
245
  response.strip!
221
246
  if block_given? && ! yield( response )
222
247
  error_message( failure_msg + "\n\n" )
@@ -240,7 +265,7 @@ def prompt_with_default( prompt_string, default, failure_msg="Try again." )
240
265
  response = prompt( "%s [%s]" % [ prompt_string, default ] )
241
266
  response = default.to_s if !response.nil? && response.empty?
242
267
 
243
- trace "Validating reponse %p" % [ response ]
268
+ trace "Validating response %p" % [ response ]
244
269
 
245
270
  # the block is a validator. We need to make sure that the user didn't
246
271
  # enter '~', because if they did, it's nil and we should move on. If
@@ -267,7 +292,7 @@ def prompt_for_multiple_values( label, default=nil )
267
292
  result = nil
268
293
 
269
294
  begin
270
- result = Readline.readline( make_prompt_string("> ") )
295
+ result = readline( make_prompt_string("> ") )
271
296
  if result.nil? || result.empty?
272
297
  results << default if default && results.empty?
273
298
  else
@@ -279,8 +304,7 @@ def prompt_for_multiple_values( label, default=nil )
279
304
  end
280
305
 
281
306
 
282
- ### Turn echo and masking of input on/off. Based on similar code in
283
- ### Ruby-Password by Ian Macdonald <ian@caliban.org>.
307
+ ### Turn echo and masking of input on/off.
284
308
  def noecho( masked=false )
285
309
  require 'termios'
286
310
 
@@ -288,13 +312,15 @@ def noecho( masked=false )
288
312
  term = Termios.getattr( $stdin )
289
313
 
290
314
  begin
291
- term.c_lflag &= ~Termios::ECHO
292
- term.c_lflag &= ~Termios::ICANON if masked
315
+ newt = term.dup
316
+ newt.c_lflag &= ~Termios::ECHO
317
+ newt.c_lflag &= ~Termios::ICANON if masked
318
+
319
+ Termios.tcsetattr( $stdin, Termios::TCSANOW, newt )
293
320
 
294
321
  rval = yield
295
322
  ensure
296
- term.c_lflag |= ( Termios::ECHO | Termios::ICANON )
297
- Termios.setattr( $stdin, Termios::TCSANOW, term )
323
+ Termios.tcsetattr( $stdin, Termios::TCSANOW, term )
298
324
  end
299
325
 
300
326
  return rval
@@ -313,22 +339,25 @@ end
313
339
 
314
340
  ### Display a description of a potentially-dangerous task, and prompt
315
341
  ### for confirmation. If the user answers with anything that begins
316
- ### with 'y', yield to the block, else raise with an error.
317
- def ask_for_confirmation( description )
342
+ ### with 'y', yield to the block. If +abort_on_decline+ is +true+,
343
+ ### any non-'y' answer will fail with an error message.
344
+ def ask_for_confirmation( description, abort_on_decline=true )
318
345
  puts description
319
346
 
320
347
  answer = prompt_with_default( "Continue?", 'n' ) do |input|
321
348
  input =~ /^[yn]/i
322
349
  end
323
350
 
324
- case answer
325
- when /^y/i
326
- yield
327
- else
351
+ if answer =~ /^y/i
352
+ return yield
353
+ elsif abort_on_decline
328
354
  error "Aborted."
329
355
  fail
330
356
  end
357
+
358
+ return false
331
359
  end
360
+ alias :prompt_for_confirmation :ask_for_confirmation
332
361
 
333
362
 
334
363
  ### Search line-by-line in the specified +file+ for the given +regexp+, returning the
@@ -348,19 +377,6 @@ def find_pattern_in_file( regexp, file )
348
377
  end
349
378
 
350
379
 
351
- ### Get a list of the file or files to run rspec on.
352
- def rspec_files
353
- if ENV['class']
354
- classname = ENV['class']
355
- spec_file = SPECSDIR + "#{classname}_spec.rb"
356
- raise "Can't find the spec file for the class '#{classname}'" unless spec_file.exist?
357
- return [ spec_file ]
358
- else
359
- return SPEC_FILES
360
- end
361
- end
362
-
363
-
364
380
  ### Invoke the user's editor on the given +filename+ and return the exit code
365
381
  ### from doing so.
366
382
  def edit( filename )
@@ -374,9 +390,18 @@ end
374
390
 
375
391
  ### Extract all the non Rake-target arguments from ARGV and return them.
376
392
  def get_target_args
377
- args = ARGV.reject {|arg| Rake::Task.task_defined?(arg) }
393
+ args = ARGV.reject {|arg| arg =~ /^-/ || Rake::Task.task_defined?(arg) }
378
394
  return args
379
395
  end
380
396
 
381
397
 
398
+ ### Log a subdirectory change, execute a block, and exit the subdirectory
399
+ def in_subdirectory( subdir )
400
+ block = Proc.new
401
+
402
+ log "Entering #{subdir}"
403
+ Dir.chdir( subdir, &block )
404
+ log "Leaving #{subdir}"
405
+ end
406
+
382
407
 
@@ -1,8 +1,10 @@
1
1
  #
2
2
  # Manual-generation Rake tasks and classes
3
- # $Id: manual.rb 10 2008-07-18 15:52:48Z deveiant $
3
+ # $Id: manual.rb 84 2009-02-10 07:38:42Z deveiant $
4
4
  #
5
- # Author: Michael Granger <ged@FaerieMUD.org>
5
+ # Authors:
6
+ # * Michael Granger <ged@FaerieMUD.org>
7
+ # * Mahlon E. Smith <mahlon@martini.nu>
6
8
  #
7
9
  # This was born out of a frustration with other static HTML generation modules
8
10
  # and systems. I've tried webby, webgen, rote, staticweb, staticmatic, and
@@ -49,18 +51,25 @@ module Manual
49
51
  end
50
52
 
51
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
+
52
60
  ### Process the +page+'s source with the filter and return the altered content.
53
61
  def process( source, page, metadata )
54
62
  raise NotImplementedError,
55
63
  "%s does not implement the #process method" % [ self.class.name ]
56
64
  end
57
- end
65
+ end # class Filter
58
66
 
59
67
 
60
68
  ### The default page configuration if none is specified.
61
69
  DEFAULT_CONFIG = {
62
- 'filters' => [ 'textile', 'erb' ],
70
+ 'filters' => [ 'erb', 'links', 'textile' ],
63
71
  'layout' => 'default.page',
72
+ 'cleanup' => true,
64
73
  }.freeze
65
74
 
66
75
  # Pattern to match a source page with a YAML header
@@ -74,24 +83,30 @@ module Manual
74
83
  # Options to pass to libtidy
75
84
  TIDY_OPTIONS = {
76
85
  :show_warnings => true,
77
- :indent => false,
86
+ :indent => true,
78
87
  :indent_attributes => false,
79
88
  :indent_spaces => 4,
80
89
  :vertical_space => true,
81
90
  :tab_size => 4,
82
91
  :wrap_attributes => true,
83
92
  :wrap => 100,
93
+ :char_encoding => 'utf8'
84
94
  }
85
95
 
86
96
 
87
97
  ### Create a new page-generator for the given +sourcefile+, which will use
88
- ### ones of the templates in +layouts_dir+ as a wrapper.
89
- def initialize( sourcefile, layouts_dir )
90
- @sourcefile = Pathname.new( sourcefile )
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 )
91
104
  @layouts_dir = Pathname.new( layouts_dir )
105
+ @basepath = basepath
92
106
 
93
107
  rawsource = @sourcefile.read
94
108
  @config, @source = self.read_page_config( rawsource )
109
+
95
110
  # $stderr.puts "Config is: %p" % [@config],
96
111
  # "Source is: %p" % [ @source[0,100] ]
97
112
  @filters = self.load_filters( @config['filters'] )
@@ -104,6 +119,12 @@ module Manual
104
119
  public
105
120
  ######
106
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
+
107
128
  # The Pathname object that specifys the page source file
108
129
  attr_reader :sourcefile
109
130
 
@@ -124,18 +145,28 @@ module Manual
124
145
  def generate( metadata )
125
146
  content = self.generate_content( @source, metadata )
126
147
 
127
- # :TODO: Read config from page for layout, filters, etc.
128
- templatepath = @layouts_dir + 'default.page'
148
+ layout = self.config['layout'].sub( /\.page$/, '' )
149
+ templatepath = @layouts_dir + "#{layout}.page"
129
150
  template = ERB.new( templatepath.read )
130
151
  page = self
131
152
 
132
153
  html = template.result( binding() )
133
154
 
134
- # return self.cleanup( html )
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
+
135
160
  return html
136
161
  end
137
162
 
138
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
+
139
170
  ### Run the various filters on the given input and return the transformed
140
171
  ### content.
141
172
  def generate_content( input, metadata )
@@ -168,8 +199,9 @@ module Manual
168
199
  tidy.options.output_xhtml = true
169
200
 
170
201
  xml = tidy.clean( source )
171
- puts tidy.errors
172
- puts tidy.diagnostics
202
+ errors = tidy.errors
203
+ error_message( errors.join ) unless errors.empty?
204
+ trace tidy.diagnostics
173
205
  return xml
174
206
  end
175
207
  rescue LoadError => err
@@ -187,6 +219,239 @@ module Manual
187
219
  end
188
220
  end
189
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
+
190
455
  end
191
456
 
192
457
 
@@ -203,7 +468,10 @@ module Manual
203
468
  ### Process the given +source+ as Textile and return the resulting HTML
204
469
  ### fragment.
205
470
  def process( source, *ignored )
206
- return RedCloth.new( source ).to_html
471
+ formatter = RedCloth::TextileDoc.new( source )
472
+ formatter.hard_breaks = false
473
+ formatter.no_span_caps = true
474
+ return formatter.to_html
207
475
  end
208
476
 
209
477
  end
@@ -268,6 +536,70 @@ module Manual
268
536
 
269
537
  attr_reader :name
270
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
+
271
603
  ### Load the filter libraries provided in the given +libdir+
272
604
  def load_filter_libraries( libdir )
273
605
  Pathname.glob( libdir + '*.rb' ) do |filterlib|
@@ -279,12 +611,11 @@ module Manual
279
611
 
280
612
  ### Set up the main HTML-generation task that will convert files in the given +sourcedir+ to
281
613
  ### HTML in the +outputdir+
282
- def setup_page_conversion_tasks( sourcedir, outputdir, layoutsdir )
283
- source_glob = sourcedir + '**/*.page'
284
- trace "Looking for sources that match: %s" % [ source_glob ]
614
+ def setup_page_conversion_tasks( sourcedir, outputdir, catalog )
285
615
 
286
- # Find the list of source .page files
287
- manual_sources = FileList[ source_glob ]
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} ]
288
619
  trace " found %d source files" % [ manual_sources.length ]
289
620
 
290
621
  # Map .page files to their equivalent .html output
@@ -306,11 +637,12 @@ module Manual
306
637
  outputdir.to_s
307
638
  ]) do |task|
308
639
 
309
- source = Pathname.new( task.source ).relative_path_from( Pathname.getwd )
310
- target = Pathname.new( task.name ).relative_path_from( Pathname.getwd )
640
+ source = Pathname.new( task.source )
641
+ target = Pathname.new( task.name )
311
642
  log " #{ source } -> #{ target }"
312
643
 
313
- page = Manual::Page.new( source, layoutsdir )
644
+ page = catalog.path_index[ source ]
645
+ #trace " page object is: %p" % [ page ]
314
646
 
315
647
  target.dirname.mkpath
316
648
  target.open( File::WRONLY|File::CREAT|File::TRUNC ) do |io|
@@ -318,67 +650,135 @@ module Manual
318
650
  end
319
651
  end
320
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
321
656
  return manual_pages
322
657
  end
323
658
 
324
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
+
325
675
  ### Set up a rule for copying files from the resources directory to the output dir.
326
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
327
682
 
328
- task :copy_resources => [ outputdir.to_s ] do
329
- when_writing( "Copying resources" ) do
330
- verbose do
331
- files = FileList[ resourcedir + '*' ]
332
- files.exclude( /\.svn/ )
333
- unless files.empty?
334
- trace " Copying resource files: #{files}"
335
- cp_r files, outputdir, :verbose => true
336
- end
337
- end
338
- end
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"
339
697
  end
340
698
  end
341
699
 
700
+ end # class Manual::GenTask
701
+
702
+ end
342
703
 
343
- ### Set up the tasks for building the manual
344
- def define
345
704
 
346
- # Set up a description if the caller hasn't already defined one
347
- unless Rake.application.last_comment
348
- desc "Generate the manual"
349
- end
350
705
 
351
- # Make Pathnames of the directories relative to the base_dir
352
- basedir = Pathname.new( @base_dir )
353
- sourcedir = basedir + @source_dir
354
- layoutsdir = basedir + @layouts_dir
355
- outputdir = basedir + @output_dir
356
- resourcedir = basedir + @resource_dir
357
- libdir = basedir + @lib_dir
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
358
721
 
359
- load_filter_libraries( libdir )
722
+ task :clobber_manual do
723
+ rmtree( MANUALOUTPUTDIR, :verbose => true )
724
+ end
360
725
 
361
- # Declare the tasks outside the namespace that point in
362
- task @name => "#@name:build"
363
- task "clobber_#@name" => "#@name:clobber"
726
+ rescue LoadError => err
727
+ task :no_manual do
728
+ $stderr.puts "Manual-generation tasks not defined: %s" % [ err.message ]
729
+ end
364
730
 
365
- namespace( self.name ) do
366
- setup_resource_copy_tasks( resourcedir, outputdir )
367
- manual_pages = setup_page_conversion_tasks( sourcedir, outputdir, layoutsdir )
731
+ task :manual => :no_manual
732
+ task :clobber_manual => :no_manual
733
+ end
734
+
735
+ else
736
+ TEMPLATEDIR = RAKE_TASKDIR + 'manualdir'
737
+
738
+ if TEMPLATEDIR.exist?
739
+
740
+ desc "Create a manual for this project from a template"
741
+ task :manual do
742
+ log "No manual directory (#{MANUALDIR}) currently exists."
743
+ ask_for_confirmation( "Create a new manual directory tree from a template?" ) do
744
+ MANUALDIR.mkpath
368
745
 
369
- task :build => [ :copy_resources, *manual_pages ]
746
+ %w[layouts lib output resources src].each do |dir|
747
+ FileUtils.mkpath( MANUALDIR + dir, :mode => 0755, :verbose => true, :noop => $dryrun )
748
+ end
370
749
 
371
- task :clobber do
372
- RakeFileUtils.verbose( $verbose ) do
373
- rm_f manual_pages.to_a
750
+ Pathname.glob( TEMPLATEDIR + '**/*.{rb,css,png,js,erb,page}' ).each do |tmplfile|
751
+ trace "extname is: #{tmplfile.extname}"
752
+
753
+ # Render ERB files
754
+ if tmplfile.extname == '.erb'
755
+ rname = tmplfile.basename( '.erb' )
756
+ target = MANUALDIR + tmplfile.dirname.relative_path_from( TEMPLATEDIR ) + rname
757
+ template = ERB.new( tmplfile.read, nil, '<>' )
758
+
759
+ target.dirname.mkpath( :mode => 0755, :verbose => true, :noop => $dryrun ) unless
760
+ target.dirname.directory?
761
+ html = template.result( binding() )
762
+ log "generating #{target}: html => #{html[0,20]}"
763
+
764
+ target.open( File::WRONLY|File::CREAT|File::EXCL, 0644 ) do |fh|
765
+ fh.print( html )
766
+ end
767
+
768
+ # Just copy anything else
769
+ else
770
+ target = MANUALDIR + tmplfile.relative_path_from( TEMPLATEDIR )
771
+ FileUtils.mkpath target.dirname,
772
+ :mode => 0755, :verbose => true, :noop => $dryrun unless target.dirname.directory?
773
+ FileUtils.install tmplfile, target,
774
+ :mode => 0644, :verbose => true, :noop => $dryrun
374
775
  end
375
- remove_dir( outputdir ) if ( outputdir + '.buildtime' ).exist?
376
776
  end
377
- task :rebuild => [ :clobber, :build ]
378
- end
777
+ end
379
778
 
380
- end # def define
381
-
382
- end # class Manual::GenTask
383
-
779
+ end # task :manual
780
+
781
+ end
384
782
  end
783
+
784
+