pluginfactory 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+