erbook 4.0.0
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/LICENSE +13 -0
- data/README +14 -0
- data/Rakefile +77 -0
- data/bin/erbook +309 -0
- data/doc/HelloWorld.input +37 -0
- data/doc/HelloWorld.spec +62 -0
- data/doc/README +6 -0
- data/doc/api/classes/ERBook.html +164 -0
- data/doc/api/classes/RDoc.html +112 -0
- data/doc/api/classes/RDoc/AnyMethod.html +195 -0
- data/doc/api/classes/RDoc/AnyMethod.src/M000005.html +18 -0
- data/doc/api/classes/RDoc/AnyMethod.src/M000006.html +23 -0
- data/doc/api/classes/RDoc/AnyMethod.src/M000007.html +18 -0
- data/doc/api/classes/RDoc/AnyMethod.src/M000008.html +22 -0
- data/doc/api/classes/RDoc/TopLevel.html +250 -0
- data/doc/api/classes/RDoc/TopLevel.src/M000009.html +18 -0
- data/doc/api/classes/RDoc/TopLevel.src/M000010.html +18 -0
- data/doc/api/classes/RDoc/TopLevel.src/M000011.html +18 -0
- data/doc/api/classes/RDoc/TopLevel.src/M000012.html +29 -0
- data/doc/api/classes/RDoc/TopLevel.src/M000013.html +25 -0
- data/doc/api/classes/RDoc/TopLevel.src/M000014.html +18 -0
- data/doc/api/classes/String.html +261 -0
- data/doc/api/classes/String.src/M000001.html +18 -0
- data/doc/api/classes/String.src/M000002.html +34 -0
- data/doc/api/classes/String.src/M000003.html +20 -0
- data/doc/api/classes/String.src/M000004.html +26 -0
- data/doc/api/created.rid +1 -0
- data/doc/api/files/lib/erbook/html_rb.html +125 -0
- data/doc/api/files/lib/erbook/rdoc_rb.html +116 -0
- data/doc/api/files/lib/erbook_rb.html +107 -0
- data/doc/api/fr_class_index.html +31 -0
- data/doc/api/fr_file_index.html +29 -0
- data/doc/api/fr_method_index.html +40 -0
- data/doc/api/index.html +24 -0
- data/doc/api/rdoc-style.css +208 -0
- data/doc/erbook.png +0 -0
- data/doc/erbook.svg +349 -0
- data/doc/feed-icon-28x28.png +0 -0
- data/doc/index.html +2663 -0
- data/doc/manual.erb +769 -0
- data/fmt/html.icons/LICENSE +67 -0
- data/fmt/html.icons/README +31 -0
- data/fmt/html.icons/caution.png +0 -0
- data/fmt/html.icons/important.png +0 -0
- data/fmt/html.icons/note.png +0 -0
- data/fmt/html.icons/quote.png +0 -0
- data/fmt/html.icons/tip.png +0 -0
- data/fmt/html.icons/warning.png +0 -0
- data/fmt/html.yaml +1185 -0
- data/fmt/latex.yaml +2 -0
- data/fmt/man.yaml +2 -0
- data/fmt/text.yaml +2 -0
- data/lib/erbook.rb +13 -0
- data/lib/erbook/html.rb +145 -0
- data/lib/erbook/rdoc.rb +126 -0
- metadata +141 -0
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2006 Suraj N. Kurapati <sunaku@gmail.com>
|
2
|
+
|
3
|
+
Permission to use, copy, modify, and distribute this software for any
|
4
|
+
purpose with or without fee is hereby granted, provided that the above
|
5
|
+
copyright notice and this permission notice appear in all copies.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
8
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
9
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
10
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
11
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
12
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
13
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
erbook : write books and documents in eRuby
|
2
|
+
===========================================
|
3
|
+
|
4
|
+
erbook is an extensible document processor that emits HTML
|
5
|
+
(web page), LaTeX (PDF), man (UNIX manual page), plain text,
|
6
|
+
and any document format you can imagine from eRuby templates
|
7
|
+
that allow scripting and dynamic content generation.
|
8
|
+
|
9
|
+
erbook is a light (200 source lines of code), extensible
|
10
|
+
(create your own document formats), and flexible (your content
|
11
|
+
is scriptable) alternative to DocBook, Deplate, and SiSU.
|
12
|
+
|
13
|
+
Please see the ./doc/index.html file in your web browser or
|
14
|
+
visit http://snk.tuxfamily.org/lib/erbook/ for more information.
|
data/Rakefile
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'rake/clean'
|
2
|
+
|
3
|
+
# documentation
|
4
|
+
desc "Build the documentation."
|
5
|
+
task :doc
|
6
|
+
|
7
|
+
# user manual
|
8
|
+
src = 'doc/manual.erb'
|
9
|
+
dst = 'doc/index.html'
|
10
|
+
|
11
|
+
task :manual => dst
|
12
|
+
task :doc => :manual
|
13
|
+
|
14
|
+
file dst => src do
|
15
|
+
sh "ruby bin/erbook -u html #{src} > #{dst}"
|
16
|
+
end
|
17
|
+
|
18
|
+
CLOBBER.include dst
|
19
|
+
|
20
|
+
# API reference
|
21
|
+
require 'rake/rdoctask'
|
22
|
+
|
23
|
+
Rake::RDocTask.new 'doc/api' do |t|
|
24
|
+
t.rdoc_dir = t.name
|
25
|
+
t.rdoc_files.exclude('pkg').include('**/*.rb')
|
26
|
+
end
|
27
|
+
|
28
|
+
task :doc => 'doc/api'
|
29
|
+
|
30
|
+
# packaging
|
31
|
+
require 'rake/gempackagetask'
|
32
|
+
require 'lib/erbook' # project info
|
33
|
+
|
34
|
+
spec = Gem::Specification.new do |s|
|
35
|
+
s.rubyforge_project = 'sunaku'
|
36
|
+
s.author, s.email = File.read('LICENSE').
|
37
|
+
scan(/Copyright \d+ (.*) <(.*?)>/).first
|
38
|
+
|
39
|
+
s.name = ERBook::PROJECT
|
40
|
+
s.version = ERBook::VERSION
|
41
|
+
s.summary = ERBook::SUMMARY
|
42
|
+
s.description = s.summary
|
43
|
+
s.homepage = ERBook::WEBSITE
|
44
|
+
s.files = FileList['**/*']
|
45
|
+
s.executables = s.name
|
46
|
+
s.has_rdoc = true
|
47
|
+
|
48
|
+
# gems needed by the default 'html' format
|
49
|
+
s.add_dependency 'maruku', '~> 0.5'
|
50
|
+
s.add_dependency 'coderay', '>= 0.7'
|
51
|
+
end
|
52
|
+
|
53
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
54
|
+
pkg.need_tar = true
|
55
|
+
pkg.need_zip = true
|
56
|
+
end
|
57
|
+
|
58
|
+
desc 'Build release packages.'
|
59
|
+
task :pack => [:clobber, :doc] do
|
60
|
+
sh $0, 'package'
|
61
|
+
end
|
62
|
+
|
63
|
+
# releasing
|
64
|
+
desc 'Upload to project website.'
|
65
|
+
task :website => :doc do
|
66
|
+
sh "rsync -av doc/ ~/www/lib/#{spec.name}"
|
67
|
+
sh "rsync -av doc/api/ ~/www/lib/#{spec.name}/api/ --delete"
|
68
|
+
end
|
69
|
+
|
70
|
+
desc 'Publish release packages.'
|
71
|
+
task :publish => :pack do
|
72
|
+
sh 'rubyforge', 'login'
|
73
|
+
|
74
|
+
Dir['pkg/*.[a-z]*'].each do |pkg|
|
75
|
+
sh 'rubyforge', 'add_release', '--release_date', ERBook::RELEASE, spec.rubyforge_project, spec.name, spec.version.to_s, pkg
|
76
|
+
end
|
77
|
+
end
|
data/bin/erbook
ADDED
@@ -0,0 +1,309 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# erbook is an extensible document processor based on eRuby.
|
4
|
+
#
|
5
|
+
# * STDIN will be read if no input files are specified.
|
6
|
+
#
|
7
|
+
# * If an error occurs, the input document will be printed to STDOUT
|
8
|
+
# so you can investigate line numbers in the error's stack trace.
|
9
|
+
# Otherwise, the final output document will be printed to STDOUT.
|
10
|
+
#
|
11
|
+
|
12
|
+
require 'erb'
|
13
|
+
include ERB::Util
|
14
|
+
|
15
|
+
require 'digest/sha1'
|
16
|
+
require 'yaml'
|
17
|
+
require 'ostruct'
|
18
|
+
|
19
|
+
$:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
20
|
+
require 'erbook'
|
21
|
+
|
22
|
+
# Prints the given message and raises the given error.
|
23
|
+
def error aMessage, aError = $!
|
24
|
+
STDERR.printf "%s:\n\n", aMessage
|
25
|
+
raise aError
|
26
|
+
end
|
27
|
+
|
28
|
+
class String
|
29
|
+
# Returns a digest of this string that's not altered by String#to_html.
|
30
|
+
def digest_id
|
31
|
+
# XXX: surround all digits with alphabets so
|
32
|
+
# Maruku doesn't change them into HTML
|
33
|
+
Digest::SHA2.hexdigest(self).gsub(/\d/, 'z\&z')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Template < ERB
|
38
|
+
# Returns the result of template evaluation thus far.
|
39
|
+
attr_reader :buffer
|
40
|
+
|
41
|
+
# aName:: String that replaces the ambiguous '(erb)' identifier in stack
|
42
|
+
# traces, so that the user can better determine the source of an
|
43
|
+
# error.
|
44
|
+
#
|
45
|
+
# args:: Arguments for ERB::new
|
46
|
+
def initialize aName, *args
|
47
|
+
# silence the code-only <% ... %> directive, just like PHP does
|
48
|
+
args[0].gsub!( /^[ \t]*(<%[^%=]((?!<%).)*?[^%]%>)[ \t]*\r?\n/m ) { $1 }
|
49
|
+
|
50
|
+
# use @buffer to store the result of the ERB template
|
51
|
+
args[3] = :@buffer
|
52
|
+
super(*args)
|
53
|
+
|
54
|
+
self.filename = aName
|
55
|
+
end
|
56
|
+
|
57
|
+
# Renders this template within a fresh object that
|
58
|
+
# is populated with the given instance variables.
|
59
|
+
def render_with aInstVars = {}
|
60
|
+
context = Object.new.instance_eval do
|
61
|
+
aInstVars.each_pair do |var, val|
|
62
|
+
instance_variable_set var, val
|
63
|
+
end
|
64
|
+
|
65
|
+
binding
|
66
|
+
end
|
67
|
+
|
68
|
+
result(context)
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
# Returns the content that the given block wants to append to
|
74
|
+
# the buffer. If the given block does not want to append to the
|
75
|
+
# buffer, then returns the result of invoking the given block.
|
76
|
+
def content_from_block *aBlockArgs
|
77
|
+
raise ArgumentError, 'block must be given' unless block_given?
|
78
|
+
|
79
|
+
start = @buffer.length
|
80
|
+
value = yield(*aBlockArgs) # this will do: buffer << content
|
81
|
+
finish = @buffer.length
|
82
|
+
|
83
|
+
if finish > start
|
84
|
+
@buffer.slice! start .. -1
|
85
|
+
else
|
86
|
+
value
|
87
|
+
end.to_s
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class Node < OpenStruct
|
92
|
+
undef id if respond_to? :id # deprecated in Ruby 1.8; removed in Ruby 1.9
|
93
|
+
undef type if respond_to? :type # deprecated in Ruby 1.8; removed in Ruby 1.9
|
94
|
+
end
|
95
|
+
|
96
|
+
# XXX: the basename() is for being launched by a RubyGems executable
|
97
|
+
if __FILE__ == $0 or File.basename(__FILE__) == File.basename($0)
|
98
|
+
# parse command-line options
|
99
|
+
require 'optparse'
|
100
|
+
|
101
|
+
opts = OptionParser.new('')
|
102
|
+
|
103
|
+
show_help_and_exit = lambda do
|
104
|
+
# show program description located at the top of this file
|
105
|
+
puts File.read(__FILE__).split(/^$\n/).first.gsub(/^# ?/, '').sub(/\A.*$\n/, '')
|
106
|
+
|
107
|
+
puts '', "Usage: #{File.basename $0} [Option...] {Format|SpecFile} [InputFile...]\n"
|
108
|
+
|
109
|
+
puts '', "Option: #{opts}"
|
110
|
+
|
111
|
+
puts '', "Format:"
|
112
|
+
ERBook::FORMAT_FILES.each do |file|
|
113
|
+
name = File.basename(file, '.yaml')
|
114
|
+
desc = YAML.load_file(file)['desc'] rescue nil
|
115
|
+
puts ' %-32s %s' % [name, desc]
|
116
|
+
end
|
117
|
+
|
118
|
+
exit
|
119
|
+
end
|
120
|
+
|
121
|
+
opts.on '-h', '--help', 'show usage information' do
|
122
|
+
show_help_and_exit.call
|
123
|
+
end
|
124
|
+
|
125
|
+
opts.on '-v', '--version', 'show version information' do
|
126
|
+
puts "project: #{ERBook::PROJECT}",
|
127
|
+
"version: #{ERBook::VERSION}",
|
128
|
+
"release: #{ERBook::RELEASE}",
|
129
|
+
"website: #{ERBook::WEBSITE}",
|
130
|
+
"install: #{ERBook::INSTALL_DIR}"
|
131
|
+
exit
|
132
|
+
end
|
133
|
+
|
134
|
+
unindent = false
|
135
|
+
opts.on '-u', '--unindent', 'unindent hierarchically' do unindent = true end
|
136
|
+
|
137
|
+
begin
|
138
|
+
opts.parse! ARGV
|
139
|
+
rescue
|
140
|
+
show_help_and_exit.call
|
141
|
+
end
|
142
|
+
|
143
|
+
# load format specification file
|
144
|
+
spec_file = ARGV.shift or
|
145
|
+
raise ArgumentError, "Format was not specified. Run `#{$0} -h` for help."
|
146
|
+
|
147
|
+
File.file? spec_file or
|
148
|
+
spec_file = File.join(ERBook::FORMATS_DIR, spec_file + '.yaml')
|
149
|
+
|
150
|
+
begin
|
151
|
+
spec_data = YAML.load_file(spec_file).merge! \
|
152
|
+
:file => File.expand_path(spec_file),
|
153
|
+
:name => File.basename(spec_file).sub(/\..*?$/, '')
|
154
|
+
|
155
|
+
rescue Exception
|
156
|
+
error "Error when loading the format specification file (#{spec_file.inspect})"
|
157
|
+
end
|
158
|
+
|
159
|
+
if spec_data.key? 'code'
|
160
|
+
eval spec_data['code'].to_s, binding, "#{spec_file}:code"
|
161
|
+
end
|
162
|
+
|
163
|
+
# load input document
|
164
|
+
input = ARGF.read
|
165
|
+
|
166
|
+
begin
|
167
|
+
# expand all "include" directives in the input
|
168
|
+
begin end while input.gsub! %r{<%#\s*include\s+(.+?)\s*#%>} do
|
169
|
+
"<%#begin(#{name = $1.inspect})%>#{File.read $1}<%#end(#{name})%>"
|
170
|
+
end
|
171
|
+
|
172
|
+
# unindent node content
|
173
|
+
if unindent
|
174
|
+
tags = input.scan(/<%(?:.(?!<%))*?%>/m)
|
175
|
+
margins = []
|
176
|
+
result = ''
|
177
|
+
|
178
|
+
buffer = input
|
179
|
+
tags.each do |tag|
|
180
|
+
chunk, buffer = buffer.split(tag, 2)
|
181
|
+
chunk << tag
|
182
|
+
|
183
|
+
# perform unindentation
|
184
|
+
result << chunk.gsub( /^#{margins.last}/, '' )
|
185
|
+
|
186
|
+
case tag
|
187
|
+
when /<%[^%=].*?\bdo\b.*?%>/m
|
188
|
+
margins.push buffer[/^[ \t]*(?=\S)/]
|
189
|
+
|
190
|
+
when /<%\s*end\s*%>/m
|
191
|
+
margins.pop
|
192
|
+
end
|
193
|
+
end
|
194
|
+
result << buffer
|
195
|
+
|
196
|
+
input = result
|
197
|
+
end
|
198
|
+
|
199
|
+
# create sandbox for input evaluation
|
200
|
+
template = Template.new('INPUT', input)
|
201
|
+
|
202
|
+
template_vars = {
|
203
|
+
:@spec => spec_data,
|
204
|
+
:@roots => roots = [], # root nodes of all trees
|
205
|
+
:@nodes => nodes = [], # all nodes in the forest
|
206
|
+
:@types => types = Hash.new {|h,k| h[k] = []}, # nodes by type
|
207
|
+
}.each_pair {|k,v| template.instance_variable_set(k, v) }
|
208
|
+
|
209
|
+
node_defs = spec_data['nodes'].each_pair do |name, info|
|
210
|
+
template.instance_eval %{
|
211
|
+
#
|
212
|
+
# XXX: using a string because define_method()
|
213
|
+
# does not accept a block until Ruby 1.9
|
214
|
+
#
|
215
|
+
def #{name} *aArgs, &aBlock
|
216
|
+
node = Node.new(
|
217
|
+
:type => #{name.inspect},
|
218
|
+
:args => aArgs,
|
219
|
+
:trace => caller,
|
220
|
+
:children => []
|
221
|
+
)
|
222
|
+
@nodes << node
|
223
|
+
@types[node.type] << node
|
224
|
+
|
225
|
+
# calculate occurrence number for this node
|
226
|
+
if #{info['number']}
|
227
|
+
@count ||= Hash.new {|h,k| h[k] = []}
|
228
|
+
node.number = (@count[node.type] << node).length
|
229
|
+
end
|
230
|
+
|
231
|
+
@stack ||= []
|
232
|
+
|
233
|
+
# assign node family
|
234
|
+
if parent = @stack.last
|
235
|
+
parent.children << node
|
236
|
+
node.parent = parent
|
237
|
+
node.depth = parent.depth.next
|
238
|
+
|
239
|
+
# calculate latex-style index number for this node
|
240
|
+
if #{info['index']}
|
241
|
+
branches = parent.children.select {|n| n.index}
|
242
|
+
node.index = [parent.index, branches.length.next].join('.')
|
243
|
+
end
|
244
|
+
else
|
245
|
+
@roots << node
|
246
|
+
node.parent = nil
|
247
|
+
node.depth = 0
|
248
|
+
|
249
|
+
# calculate latex-style index number for this node
|
250
|
+
if #{info['index']}
|
251
|
+
branches = @roots.select {|n| n.index}
|
252
|
+
node.index = branches.length.next.to_s
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
# assign node content
|
257
|
+
if block_given?
|
258
|
+
@stack.push node
|
259
|
+
content = content_from_block(node, &aBlock).to_s
|
260
|
+
@stack.pop
|
261
|
+
|
262
|
+
digest = content.digest_id
|
263
|
+
@buffer << digest
|
264
|
+
else
|
265
|
+
content = nil
|
266
|
+
digest = node.object_id.to_s.digest_id
|
267
|
+
end
|
268
|
+
|
269
|
+
node.content = content
|
270
|
+
node.digest = digest
|
271
|
+
|
272
|
+
digest
|
273
|
+
end
|
274
|
+
}, __FILE__, Kernel.caller.first[/\d+/].to_i.next
|
275
|
+
end
|
276
|
+
|
277
|
+
# build the document tree
|
278
|
+
document = template.instance_eval { result(binding) }
|
279
|
+
|
280
|
+
# replace nodes with output
|
281
|
+
expander = lambda do |n, buf|
|
282
|
+
# calculate node output
|
283
|
+
source = "#{spec_file}:nodes:#{n.type}:output"
|
284
|
+
n.output = Template.new(source, node_defs[n.type]['output'].to_s.chomp).
|
285
|
+
render_with(template_vars.merge :@node => n)
|
286
|
+
|
287
|
+
# replace node with output
|
288
|
+
if node_defs[n.type]['silent']
|
289
|
+
buf[n.digest] = ''
|
290
|
+
buf = n.output
|
291
|
+
else
|
292
|
+
buf[n.digest] = n.output
|
293
|
+
end
|
294
|
+
|
295
|
+
# repeat for all child nodes
|
296
|
+
n.children.each {|c| expander[c, buf] }
|
297
|
+
end
|
298
|
+
|
299
|
+
roots.each {|n| expander[n, document] }
|
300
|
+
|
301
|
+
rescue Exception
|
302
|
+
puts input # so the user can debug the line numbers in the stack trace
|
303
|
+
error 'Error when processing the input document (INPUT)'
|
304
|
+
end
|
305
|
+
|
306
|
+
# emit output document
|
307
|
+
puts Template.new("#{spec_file}:output", spec_data['output'].to_s).
|
308
|
+
render_with(template_vars.merge(:@content => document))
|
309
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
<% $style = "border-left: thick dotted LightGrey; padding-left: 1em;" %>
|
2
|
+
<% hello "Pretentious", 1, 2, 3 do %>
|
3
|
+
<big>I'm</big> the very first node, oh _yes_ I am! *sneer*
|
4
|
+
|
5
|
+
<% hello "Bashful", 4, 5, 6 do %>
|
6
|
+
Hi, I... *hide*
|
7
|
+
|
8
|
+
<% hello "Hopeful", rand do %>
|
9
|
+
*sigh*
|
10
|
+
|
11
|
+
<% hello "Confused", (rand * rand) do %>
|
12
|
+
Huh?
|
13
|
+
<% end %>
|
14
|
+
<% end %>
|
15
|
+
|
16
|
+
<% hello "Raving", __FILE__ do %>
|
17
|
+
Oh it's *on* now! You're going *down*!
|
18
|
+
<% end %>
|
19
|
+
<% end %>
|
20
|
+
|
21
|
+
<% hello "Sleepy", Time.now do %>
|
22
|
+
*yawn* Just five more minutes...
|
23
|
+
|
24
|
+
<% hello "Peaceful", Dir.pwd do %>
|
25
|
+
So _be_ happy my friend, *happy*!
|
26
|
+
|
27
|
+
<%= hello "Lonely (as you can see, I have no block)" %>
|
28
|
+
<% end %>
|
29
|
+
<% end %>
|
30
|
+
<% end %>
|
31
|
+
|
32
|
+
<% hello "Snappy" do %>
|
33
|
+
Zip! Zap! Wake up, you sap!
|
34
|
+
_Whoo I'm wild!_ ;-)
|
35
|
+
<% end %>
|
36
|
+
|
37
|
+
<%= hello "Independent (no block, no parents, I am _free_!)" %>
|