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