io-reactor 0.05 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,62 @@
1
+ #
2
+ # Dependency-checking and Installation Rake Tasks
3
+ # $Id: dependencies.rb 10 2008-07-18 15:52:48Z deveiant $
4
+ #
5
+
6
+ require 'rubygems/dependency_installer'
7
+ require 'rubygems/source_index'
8
+ require 'rubygems/requirement'
9
+ require 'rubygems/doc_manager'
10
+
11
+ ### Install the specified +gems+ if they aren't already installed.
12
+ def install_gems( *gems )
13
+ gems.flatten!
14
+
15
+ defaults = Gem::DependencyInstaller::DEFAULT_OPTIONS.merge({
16
+ :generate_rdoc => true,
17
+ :generate_ri => true,
18
+ :install_dir => Gem.dir,
19
+ :format_executable => false,
20
+ :test => false,
21
+ :version => Gem::Requirement.default,
22
+ })
23
+
24
+ # Check for root
25
+ if Process.euid != 0
26
+ $stderr.puts "This probably won't work, as you aren't root, but I'll try anyway"
27
+ end
28
+
29
+ gemindex = Gem::SourceIndex.from_installed_gems
30
+
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 ]
35
+ next
36
+ end
37
+
38
+ rgv = Gem::Version.new( Gem::RubyGemsVersion )
39
+ installer = nil
40
+
41
+ log "Trying to install #{gemname.inspect}..."
42
+ if rgv >= Gem::Version.new( '1.1.1' )
43
+ installer = Gem::DependencyInstaller.new
44
+ installer.install( gemname )
45
+ else
46
+ installer = Gem::DependencyInstaller.new( gemname )
47
+ installer.install
48
+ end
49
+
50
+ installer.installed_gems.each do |spec|
51
+ log "Installed: %s" % [ spec.full_name ]
52
+ end
53
+
54
+ end
55
+ end
56
+
57
+
58
+ ### Task: install gems for development tasks
59
+ task :install_dependencies do
60
+ install_gems( DEPENDENCIES.keys )
61
+ end
62
+
@@ -0,0 +1,384 @@
1
+ #####################################################################
2
+ ### G L O B A L H E L P E R F U N C T I O N S
3
+ #####################################################################
4
+
5
+ require 'pathname'
6
+ require 'readline'
7
+
8
+ # Set some ANSI escape code constants (Shamelessly stolen from Perl's
9
+ # Term::ANSIColor by Russ Allbery <rra@stanford.edu> and Zenin <zenin@best.com>
10
+ ANSI_ATTRIBUTES = {
11
+ 'clear' => 0,
12
+ 'reset' => 0,
13
+ 'bold' => 1,
14
+ 'dark' => 2,
15
+ 'underline' => 4,
16
+ 'underscore' => 4,
17
+ 'blink' => 5,
18
+ 'reverse' => 7,
19
+ 'concealed' => 8,
20
+
21
+ 'black' => 30, 'on_black' => 40,
22
+ 'red' => 31, 'on_red' => 41,
23
+ 'green' => 32, 'on_green' => 42,
24
+ 'yellow' => 33, 'on_yellow' => 43,
25
+ 'blue' => 34, 'on_blue' => 44,
26
+ 'magenta' => 35, 'on_magenta' => 45,
27
+ 'cyan' => 36, 'on_cyan' => 46,
28
+ 'white' => 37, 'on_white' => 47
29
+ }
30
+
31
+
32
+ MULTILINE_PROMPT = <<-'EOF'
33
+ Enter one or more values for '%s'.
34
+ A blank line finishes input.
35
+ EOF
36
+
37
+
38
+ CLEAR_TO_EOL = "\e[K"
39
+ CLEAR_CURRENT_LINE = "\e[2K"
40
+
41
+
42
+ ### Output a logging message
43
+ def log( *msg )
44
+ output = colorize( msg.flatten.join(' '), 'cyan' )
45
+ $deferr.puts( output )
46
+ end
47
+
48
+
49
+ ### Output a logging message if tracing is on
50
+ def trace( *msg )
51
+ return unless $trace
52
+ output = colorize( msg.flatten.join(' '), 'yellow' )
53
+ $deferr.puts( output )
54
+ end
55
+
56
+
57
+ ### Run the specified command +cmd+ with system(), failing if the execution
58
+ ### fails.
59
+ def run( *cmd )
60
+ cmd.flatten!
61
+
62
+ log( cmd.collect {|part| part =~ /\s/ ? part.inspect : part} )
63
+ if $dryrun
64
+ $deferr.puts "(dry run mode)"
65
+ else
66
+ system( *cmd )
67
+ unless $?.success?
68
+ fail "Command failed: [%s]" % [cmd.join(' ')]
69
+ end
70
+ end
71
+ end
72
+
73
+
74
+ ### Open a pipe to a process running the given +cmd+ and call the given block with it.
75
+ def pipeto( *cmd )
76
+ $DEBUG = true
77
+
78
+ cmd.flatten!
79
+ log( "Opening a pipe to: ", cmd.collect {|part| part =~ /\s/ ? part.inspect : part} )
80
+ if $dryrun
81
+ $deferr.puts "(dry run mode)"
82
+ else
83
+ open( '|-', 'w+' ) do |io|
84
+
85
+ # Parent
86
+ if io
87
+ yield( io )
88
+
89
+ # Child
90
+ else
91
+ exec( *cmd )
92
+ fail "Command failed: [%s]" % [cmd.join(' ')]
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+
99
+ ### Download the file at +sourceuri+ via HTTP and write it to +targetfile+.
100
+ def download( sourceuri, targetfile=nil )
101
+ oldsync = $defout.sync
102
+ $defout.sync = true
103
+ require 'net/http'
104
+ require 'uri'
105
+
106
+ targetpath = Pathname.new( targetfile )
107
+
108
+ log "Downloading %s to %s" % [sourceuri, targetfile]
109
+ targetpath.open( File::WRONLY|File::TRUNC|File::CREAT, 0644 ) do |ofh|
110
+
111
+ url = sourceuri.is_a?( URI ) ? sourceuri : URI.parse( sourceuri )
112
+ downloaded = false
113
+ limit = 5
114
+
115
+ until downloaded or limit.zero?
116
+ Net::HTTP.start( url.host, url.port ) do |http|
117
+ req = Net::HTTP::Get.new( url.path )
118
+
119
+ http.request( req ) do |res|
120
+ if res.is_a?( Net::HTTPSuccess )
121
+ log "Downloading..."
122
+ res.read_body do |buf|
123
+ ofh.print( buf )
124
+ end
125
+ downloaded = true
126
+ puts "done."
127
+
128
+ elsif res.is_a?( Net::HTTPRedirection )
129
+ url = URI.parse( res['location'] )
130
+ log "...following redirection to: %s" % [ url ]
131
+ limit -= 1
132
+ sleep 0.2
133
+ next
134
+
135
+ else
136
+ res.error!
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ end
143
+
144
+ return targetpath
145
+ ensure
146
+ $defout.sync = oldsync
147
+ end
148
+
149
+
150
+ ### Return the fully-qualified path to the specified +program+ in the PATH.
151
+ def which( program )
152
+ ENV['PATH'].split(/:/).
153
+ collect {|dir| Pathname.new(dir) + program }.
154
+ find {|path| path.exist? && path.executable? }
155
+ end
156
+
157
+
158
+ ### Create a string that contains the ANSI codes specified and return it
159
+ def ansi_code( *attributes )
160
+ attributes.flatten!
161
+ attributes.collect! {|at| at.to_s }
162
+ # $deferr.puts "Returning ansicode for TERM = %p: %p" %
163
+ # [ ENV['TERM'], attributes ]
164
+ return '' unless /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i =~ ENV['TERM']
165
+ attributes = ANSI_ATTRIBUTES.values_at( *attributes ).compact.join(';')
166
+
167
+ # $deferr.puts " attr is: %p" % [attributes]
168
+ if attributes.empty?
169
+ return ''
170
+ else
171
+ return "\e[%sm" % attributes
172
+ end
173
+ end
174
+
175
+
176
+ ### Colorize the given +string+ with the specified +attributes+ and return it, handling
177
+ ### line-endings, color reset, etc.
178
+ def colorize( *args )
179
+ string = ''
180
+
181
+ if block_given?
182
+ string = yield
183
+ else
184
+ string = args.shift
185
+ end
186
+
187
+ ending = string[/(\s)$/] || ''
188
+ string = string.rstrip
189
+
190
+ return ansi_code( args.flatten ) + string + ansi_code( 'reset' ) + ending
191
+ end
192
+
193
+
194
+ ### Output the specified <tt>msg</tt> as an ANSI-colored error message
195
+ ### (white on red).
196
+ def error_message( msg, details='' )
197
+ $deferr.puts colorize( 'bold', 'white', 'on_red' ) { msg } + details
198
+ end
199
+ alias :error :error_message
200
+
201
+
202
+ ### Highlight and embed a prompt control character in the given +string+ and return it.
203
+ def make_prompt_string( string )
204
+ return CLEAR_CURRENT_LINE + colorize( 'bold', 'green' ) { string + ' ' }
205
+ end
206
+
207
+
208
+ ### Output the specified <tt>prompt_string</tt> as a prompt (in green) and
209
+ ### return the user's input with leading and trailing spaces removed. If a
210
+ ### test is provided, the prompt will repeat until the test returns true.
211
+ ### An optional failure message can also be passed in.
212
+ def prompt( prompt_string, failure_msg="Try again." ) # :yields: response
213
+ prompt_string.chomp!
214
+ prompt_string << ":" unless /\W$/.match( prompt_string )
215
+ response = nil
216
+
217
+ begin
218
+ prompt = make_prompt_string( prompt_string )
219
+ response = Readline.readline( prompt ) || ''
220
+ response.strip!
221
+ if block_given? && ! yield( response )
222
+ error_message( failure_msg + "\n\n" )
223
+ response = nil
224
+ end
225
+ end while response.nil?
226
+
227
+ return response
228
+ end
229
+
230
+
231
+ ### Prompt the user with the given <tt>prompt_string</tt> via #prompt,
232
+ ### substituting the given <tt>default</tt> if the user doesn't input
233
+ ### anything. If a test is provided, the prompt will repeat until the test
234
+ ### returns true. An optional failure message can also be passed in.
235
+ def prompt_with_default( prompt_string, default, failure_msg="Try again." )
236
+ response = nil
237
+
238
+ begin
239
+ default ||= '~'
240
+ response = prompt( "%s [%s]" % [ prompt_string, default ] )
241
+ response = default.to_s if !response.nil? && response.empty?
242
+
243
+ trace "Validating reponse %p" % [ response ]
244
+
245
+ # the block is a validator. We need to make sure that the user didn't
246
+ # enter '~', because if they did, it's nil and we should move on. If
247
+ # they didn't, then call the block.
248
+ if block_given? && response != '~' && ! yield( response )
249
+ error_message( failure_msg + "\n\n" )
250
+ response = nil
251
+ end
252
+ end while response.nil?
253
+
254
+ return nil if response == '~'
255
+ return response
256
+ end
257
+
258
+
259
+ ### Prompt for an array of values
260
+ def prompt_for_multiple_values( label, default=nil )
261
+ $stderr.puts( MULTILINE_PROMPT % [label] )
262
+ if default
263
+ $stderr.puts "Enter a single blank line to keep the default:\n %p" % [ default ]
264
+ end
265
+
266
+ results = []
267
+ result = nil
268
+
269
+ begin
270
+ result = Readline.readline( make_prompt_string("> ") )
271
+ if result.nil? || result.empty?
272
+ results << default if default && results.empty?
273
+ else
274
+ results << result
275
+ end
276
+ end until result.nil? || result.empty?
277
+
278
+ return results.flatten
279
+ end
280
+
281
+
282
+ ### Turn echo and masking of input on/off. Based on similar code in
283
+ ### Ruby-Password by Ian Macdonald <ian@caliban.org>.
284
+ def noecho( masked=false )
285
+ require 'termios'
286
+
287
+ rval = nil
288
+ term = Termios.getattr( $stdin )
289
+
290
+ begin
291
+ newt = term.dup
292
+ newt.c_lflag &= ~Termios::ECHO
293
+ newt.c_lflag &= ~Termios::ICANON if masked
294
+
295
+ Termios.txsetattr( $stdin, Termios::TCSANOW, newt )
296
+
297
+ rval = yield
298
+ ensure
299
+ Termios.setattr( $stdin, Termios::TCSANOW, term )
300
+ end
301
+
302
+ return rval
303
+ end
304
+
305
+
306
+ ### Prompt the user for her password, turning off echo if the 'termios' module is
307
+ ### available.
308
+ def prompt_for_password( prompt="Password: " )
309
+ return noecho( true ) do
310
+ $stderr.print( prompt )
311
+ ($stdin.gets || '').chomp
312
+ end
313
+ end
314
+
315
+
316
+ ### Display a description of a potentially-dangerous task, and prompt
317
+ ### for confirmation. If the user answers with anything that begins
318
+ ### with 'y', yield to the block, else raise with an error.
319
+ def ask_for_confirmation( description )
320
+ puts description
321
+
322
+ answer = prompt_with_default( "Continue?", 'n' ) do |input|
323
+ input =~ /^[yn]/i
324
+ end
325
+
326
+ case answer
327
+ when /^y/i
328
+ yield
329
+ else
330
+ error "Aborted."
331
+ fail
332
+ end
333
+ end
334
+
335
+
336
+ ### Search line-by-line in the specified +file+ for the given +regexp+, returning the
337
+ ### first match, or nil if no match was found. If the +regexp+ has any capture groups,
338
+ ### those will be returned in an Array, else the whole matching line is returned.
339
+ def find_pattern_in_file( regexp, file )
340
+ rval = nil
341
+
342
+ File.open( file, 'r' ).each do |line|
343
+ if (( match = regexp.match(line) ))
344
+ rval = match.captures.empty? ? match[0] : match.captures
345
+ break
346
+ end
347
+ end
348
+
349
+ return rval
350
+ end
351
+
352
+
353
+ ### Get a list of the file or files to run rspec on.
354
+ def rspec_files
355
+ if ENV['class']
356
+ classname = ENV['class']
357
+ spec_file = SPECSDIR + "#{classname}_spec.rb"
358
+ raise "Can't find the spec file for the class '#{classname}'" unless spec_file.exist?
359
+ return [ spec_file ]
360
+ else
361
+ return SPEC_FILES
362
+ end
363
+ end
364
+
365
+
366
+ ### Invoke the user's editor on the given +filename+ and return the exit code
367
+ ### from doing so.
368
+ def edit( filename )
369
+ editor = ENV['EDITOR'] || ENV['VISUAL'] || DEFAULT_EDITOR
370
+ system editor, filename
371
+ unless $?.success?
372
+ fail "Editor exited uncleanly."
373
+ end
374
+ end
375
+
376
+
377
+ ### Extract all the non Rake-target arguments from ARGV and return them.
378
+ def get_target_args
379
+ args = ARGV.reject {|arg| Rake::Task.task_defined?(arg) }
380
+ return args
381
+ end
382
+
383
+
384
+
@@ -0,0 +1,384 @@
1
+ #
2
+ # Manual-generation Rake tasks and classes
3
+ # $Id: manual.rb 10 2008-07-18 15:52:48Z deveiant $
4
+ #
5
+ # Author: Michael Granger <ged@FaerieMUD.org>
6
+ #
7
+ # This was born out of a frustration with other static HTML generation modules
8
+ # and systems. I've tried webby, webgen, rote, staticweb, staticmatic, and
9
+ # nanoc, but I didn't find any of them really suitable (except rote, which was
10
+ # excellent but apparently isn't maintained and has a fundamental
11
+ # incompatibilty with Rake because of some questionable monkeypatching.)
12
+ #
13
+ # So, since nothing seemed to scratch my itch, I'm going to scratch it myself.
14
+ #
15
+
16
+ require 'pathname'
17
+ require 'singleton'
18
+ require 'rake/tasklib'
19
+ require 'erb'
20
+
21
+
22
+ ### Namespace for Manual-generation classes
23
+ module Manual
24
+
25
+ ### Manual page-generation class
26
+ class Page
27
+
28
+ ### An abstract filter class for manual content transformation.
29
+ class Filter
30
+ include Singleton
31
+
32
+ # A list of inheriting classes, keyed by normalized name
33
+ @derivatives = {}
34
+ class << self; attr_reader :derivatives; end
35
+
36
+ ### Inheritance callback -- keep track of all inheriting classes for
37
+ ### later.
38
+ def self::inherited( subclass )
39
+ key = subclass.name.
40
+ sub( /^.*::/, '' ).
41
+ gsub( /[^[:alpha:]]+/, '_' ).
42
+ downcase.
43
+ sub( /filter$/, '' )
44
+
45
+ self.derivatives[ key ] = subclass
46
+ self.derivatives[ key.to_sym ] = subclass
47
+
48
+ super
49
+ end
50
+
51
+
52
+ ### Process the +page+'s source with the filter and return the altered content.
53
+ def process( source, page, metadata )
54
+ raise NotImplementedError,
55
+ "%s does not implement the #process method" % [ self.class.name ]
56
+ end
57
+ end
58
+
59
+
60
+ ### The default page configuration if none is specified.
61
+ DEFAULT_CONFIG = {
62
+ 'filters' => [ 'textile', 'erb' ],
63
+ 'layout' => 'default.page',
64
+ }.freeze
65
+
66
+ # Pattern to match a source page with a YAML header
67
+ PAGE_WITH_YAML_HEADER = /
68
+ \A---\s*$ # It should should start with three hyphens
69
+ (.*?) # ...have some YAML stuff
70
+ ^---\s*$ # then have another three-hyphen line,
71
+ (.*)\Z # then the rest of the document
72
+ /xm
73
+
74
+ # Options to pass to libtidy
75
+ TIDY_OPTIONS = {
76
+ :show_warnings => true,
77
+ :indent => false,
78
+ :indent_attributes => false,
79
+ :indent_spaces => 4,
80
+ :vertical_space => true,
81
+ :tab_size => 4,
82
+ :wrap_attributes => true,
83
+ :wrap => 100,
84
+ }
85
+
86
+
87
+ ### 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 )
91
+ @layouts_dir = Pathname.new( layouts_dir )
92
+
93
+ rawsource = @sourcefile.read
94
+ @config, @source = self.read_page_config( rawsource )
95
+ # $stderr.puts "Config is: %p" % [@config],
96
+ # "Source is: %p" % [ @source[0,100] ]
97
+ @filters = self.load_filters( @config['filters'] )
98
+
99
+ super()
100
+ end
101
+
102
+
103
+ ######
104
+ public
105
+ ######
106
+
107
+ # The Pathname object that specifys the page source file
108
+ attr_reader :sourcefile
109
+
110
+ # The configured layouts directory as a Pathname object.
111
+ attr_reader :layouts_dir
112
+
113
+ # The page configuration, as read from its YAML header
114
+ attr_reader :config
115
+
116
+ # The raw source of the page
117
+ attr_reader :source
118
+
119
+ # The filters the page will use to render itself
120
+ attr_reader :filters
121
+
122
+
123
+ ### Generate HTML output from the page and return it.
124
+ def generate( metadata )
125
+ content = self.generate_content( @source, metadata )
126
+
127
+ # :TODO: Read config from page for layout, filters, etc.
128
+ templatepath = @layouts_dir + 'default.page'
129
+ template = ERB.new( templatepath.read )
130
+ page = self
131
+
132
+ html = template.result( binding() )
133
+
134
+ # return self.cleanup( html )
135
+ return html
136
+ end
137
+
138
+
139
+ ### Run the various filters on the given input and return the transformed
140
+ ### content.
141
+ def generate_content( input, metadata )
142
+ return @filters.inject( input ) do |source, filter|
143
+ filter.process( source, self, metadata )
144
+ end
145
+ end
146
+
147
+
148
+ ### Trim the YAML header from the provided page +source+, convert it to
149
+ ### a Ruby object, and return it.
150
+ def read_page_config( source )
151
+ unless source =~ PAGE_WITH_YAML_HEADER
152
+ return DEFAULT_CONFIG.dup, source
153
+ end
154
+
155
+ pageconfig = YAML.load( $1 )
156
+ source = $2
157
+
158
+ return DEFAULT_CONFIG.merge( pageconfig ), source
159
+ end
160
+
161
+
162
+ ### Clean up and return the given HTML +source+.
163
+ def cleanup( source )
164
+ require 'tidy'
165
+
166
+ Tidy.path = '/usr/lib/libtidy.dylib'
167
+ Tidy.open( TIDY_OPTIONS ) do |tidy|
168
+ tidy.options.output_xhtml = true
169
+
170
+ xml = tidy.clean( source )
171
+ puts tidy.errors
172
+ puts tidy.diagnostics
173
+ return xml
174
+ end
175
+ rescue LoadError => err
176
+ trace "No cleanup: " + err.message
177
+ return source
178
+ end
179
+
180
+
181
+ ### Get (singleton) instances of the filters named in +filterlist+ and return them.
182
+ def load_filters( filterlist )
183
+ filterlist.flatten.collect do |key|
184
+ raise ArgumentError, "filter '#{key}' is not loaded" unless
185
+ Manual::Page::Filter.derivatives.key?( key )
186
+ Manual::Page::Filter.derivatives[ key ].instance
187
+ end
188
+ end
189
+
190
+ end
191
+
192
+
193
+ ### A Textile filter for the manual generation tasklib, implemented using RedCloth.
194
+ class TextileFilter < Manual::Page::Filter
195
+
196
+ ### Load RedCloth when the filter is first created
197
+ def initialize( *args )
198
+ require 'redcloth'
199
+ super
200
+ end
201
+
202
+
203
+ ### Process the given +source+ as Textile and return the resulting HTML
204
+ ### fragment.
205
+ def process( source, *ignored )
206
+ return RedCloth.new( source ).to_html
207
+ end
208
+
209
+ end
210
+
211
+
212
+ ### An ERB filter for the manual generation tasklib, implemented using Erubis.
213
+ class ErbFilter < Manual::Page::Filter
214
+
215
+ ### Process the given +source+ as ERB and return the resulting HTML
216
+ ### fragment.
217
+ def process( source, page, metadata )
218
+ template_name = page.sourcefile.basename
219
+ template = ERB.new( source )
220
+ return template.result( binding() )
221
+ end
222
+
223
+ end
224
+
225
+
226
+ ### Manual generation task library
227
+ class GenTask < Rake::TaskLib
228
+
229
+ # Default values for task config variables
230
+ DEFAULT_NAME = :manual
231
+ DEFAULT_BASE_DIR = Pathname.new( 'docs/manual' )
232
+ DEFAULT_SOURCE_DIR = 'source'
233
+ DEFAULT_LAYOUTS_DIR = 'layouts'
234
+ DEFAULT_OUTPUT_DIR = 'output'
235
+ DEFAULT_RESOURCE_DIR = 'resources'
236
+ DEFAULT_LIB_DIR = 'lib'
237
+ DEFAULT_METADATA = OpenStruct.new
238
+
239
+
240
+ ### Define a new manual-generation task with the given +name+.
241
+ def initialize( name=:manual )
242
+ @name = name
243
+
244
+ @source_dir = DEFAULT_SOURCE_DIR
245
+ @layouts_dir = DEFAULT_LAYOUTS_DIR
246
+ @output_dir = DEFAULT_OUTPUT_DIR
247
+ @resource_dir = DEFAULT_RESOURCE_DIR
248
+ @lib_dir = DEFAULT_LIB_DIR
249
+ @metadata = DEFAULT_METADATA
250
+
251
+ yield( self ) if block_given?
252
+
253
+ self.define
254
+ end
255
+
256
+
257
+ ######
258
+ public
259
+ ######
260
+
261
+ attr_accessor :base_dir,
262
+ :source_dir,
263
+ :layouts_dir,
264
+ :output_dir,
265
+ :resource_dir,
266
+ :lib_dir,
267
+ :metadata
268
+
269
+ attr_reader :name
270
+
271
+ ### Load the filter libraries provided in the given +libdir+
272
+ def load_filter_libraries( libdir )
273
+ Pathname.glob( libdir + '*.rb' ) do |filterlib|
274
+ trace " loading filter library #{filterlib}"
275
+ require( filterlib )
276
+ end
277
+ end
278
+
279
+
280
+ ### Set up the main HTML-generation task that will convert files in the given +sourcedir+ to
281
+ ### 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 ]
285
+
286
+ # Find the list of source .page files
287
+ manual_sources = FileList[ source_glob ]
288
+ trace " found %d source files" % [ manual_sources.length ]
289
+
290
+ # Map .page files to their equivalent .html output
291
+ html_pathmap = "%%{%s,%s}X.html" % [ sourcedir, outputdir ]
292
+ manual_pages = manual_sources.pathmap( html_pathmap )
293
+ trace "Mapping sources like so: \n %p -> %p" %
294
+ [ manual_sources.first, manual_pages.first ]
295
+
296
+ # Output directory task
297
+ directory( outputdir.to_s )
298
+ file outputdir.to_s do
299
+ touch outputdir + '.buildtime'
300
+ end
301
+
302
+ # Rule to generate .html files from .page files
303
+ rule(
304
+ %r{#{outputdir}/.*\.html$} => [
305
+ proc {|name| name.sub(/\.[^.]+$/, '.page').sub( outputdir, sourcedir) },
306
+ outputdir.to_s
307
+ ]) do |task|
308
+
309
+ source = Pathname.new( task.source ).relative_path_from( Pathname.getwd )
310
+ target = Pathname.new( task.name ).relative_path_from( Pathname.getwd )
311
+ log " #{ source } -> #{ target }"
312
+
313
+ page = Manual::Page.new( source, layoutsdir )
314
+
315
+ target.dirname.mkpath
316
+ target.open( File::WRONLY|File::CREAT|File::TRUNC ) do |io|
317
+ io.write( page.generate(metadata) )
318
+ end
319
+ end
320
+
321
+ return manual_pages
322
+ end
323
+
324
+
325
+ ### Set up a rule for copying files from the resources directory to the output dir.
326
+ def setup_resource_copy_tasks( resourcedir, outputdir )
327
+
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
339
+ end
340
+ end
341
+
342
+
343
+ ### Set up the tasks for building the manual
344
+ def define
345
+
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
+
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
358
+
359
+ load_filter_libraries( libdir )
360
+
361
+ # Declare the tasks outside the namespace that point in
362
+ task @name => "#@name:build"
363
+ task "clobber_#@name" => "#@name:clobber"
364
+
365
+ namespace( self.name ) do
366
+ setup_resource_copy_tasks( resourcedir, outputdir )
367
+ manual_pages = setup_page_conversion_tasks( sourcedir, outputdir, layoutsdir )
368
+
369
+ task :build => [ :copy_resources, *manual_pages ]
370
+
371
+ task :clobber do
372
+ RakeFileUtils.verbose( $verbose ) do
373
+ rm_f manual_pages.to_a
374
+ end
375
+ remove_dir( outputdir ) if ( outputdir + '.buildtime' ).exist?
376
+ end
377
+ task :rebuild => [ :clobber, :build ]
378
+ end
379
+
380
+ end # def define
381
+
382
+ end # class Manual::GenTask
383
+
384
+ end