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.
- data/ChangeLog +26 -0
- data/README +1 -1
- data/Rakefile +82 -22
- data/lib/pluginfactory.rb +77 -39
- data/rake/191_compat.rb +26 -0
- data/rake/dependencies.rb +25 -11
- data/rake/helpers.rb +65 -40
- data/rake/manual.rb +464 -64
- data/rake/publishing.rb +30 -16
- data/rake/rdoc.rb +36 -21
- data/rake/svn.rb +166 -25
- data/rake/testing.rb +46 -35
- data/rake/win32.rb +94 -0
- data/spec/lib/helpers.rb +247 -0
- data/spec/pluginfactory_spec.rb +43 -69
- metadata +138 -9
data/rake/191_compat.rb
ADDED
@@ -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
|
+
|
data/rake/dependencies.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#
|
2
2
|
# Dependency-checking and Installation Rake Tasks
|
3
|
-
# $Id: dependencies.rb
|
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(
|
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
|
-
|
33
|
-
|
34
|
-
|
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
|
65
|
+
### Task: install runtime dependencies
|
66
|
+
desc "Install runtime dependencies as gems"
|
59
67
|
task :install_dependencies do
|
60
|
-
install_gems( DEPENDENCIES
|
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
|
|
data/rake/helpers.rb
CHANGED
@@ -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
|
-
|
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
|
-
$
|
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
|
-
$
|
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
|
-
|
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
|
-
$
|
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
|
-
$
|
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
|
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
|
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
|
-
# $
|
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
|
-
# $
|
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
|
-
$
|
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 =
|
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
|
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 =
|
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.
|
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.
|
292
|
-
|
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
|
-
|
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
|
317
|
-
|
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
|
-
|
325
|
-
|
326
|
-
|
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
|
|
data/rake/manual.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
#
|
2
2
|
# Manual-generation Rake tasks and classes
|
3
|
-
# $Id: manual.rb
|
3
|
+
# $Id: manual.rb 84 2009-02-10 07:38:42Z deveiant $
|
4
4
|
#
|
5
|
-
#
|
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' => [ '
|
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 =>
|
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
|
-
|
90
|
-
|
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
|
-
|
128
|
-
templatepath = @layouts_dir +
|
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
|
-
#
|
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
|
-
|
172
|
-
|
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
|
-
|
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,
|
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
|
-
#
|
287
|
-
|
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 )
|
310
|
-
target = Pathname.new( task.name )
|
640
|
+
source = Pathname.new( task.source )
|
641
|
+
target = Pathname.new( task.name )
|
311
642
|
log " #{ source } -> #{ target }"
|
312
643
|
|
313
|
-
page =
|
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
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
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
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
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
|
-
|
722
|
+
task :clobber_manual do
|
723
|
+
rmtree( MANUALOUTPUTDIR, :verbose => true )
|
724
|
+
end
|
360
725
|
|
361
|
-
|
362
|
-
|
363
|
-
|
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
|
-
|
366
|
-
|
367
|
-
|
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
|
-
|
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
|
-
|
372
|
-
|
373
|
-
|
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
|
-
|
378
|
-
end
|
777
|
+
end
|
379
778
|
|
380
|
-
end #
|
381
|
-
|
382
|
-
end
|
383
|
-
|
779
|
+
end # task :manual
|
780
|
+
|
781
|
+
end
|
384
782
|
end
|
783
|
+
|
784
|
+
|