erbook 5.0.0 → 6.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 +1 -1
- data/Rakefile +6 -79
- data/bin/erbook +25 -319
- data/doc/HelloWorld.spec +23 -21
- data/doc/README +4 -3
- data/doc/api/ERBook.html +35 -0
- data/doc/api/ERBook/Document.html +673 -0
- data/doc/api/ERBook/Document/Node.html +102 -0
- data/doc/api/ERBook/Template.html +670 -0
- data/doc/api/RDoc.html +23 -0
- data/doc/api/RDoc/AnyMethod.html +302 -0
- data/doc/api/RDoc/DummyMarkup.html +73 -0
- data/doc/api/RDoc/DummyMixin.html +23 -0
- data/doc/api/RDoc/DummyOptions.html +140 -0
- data/doc/api/RDoc/TopLevel.html +465 -0
- data/doc/api/String.html +372 -0
- data/doc/api/all-methods.html +253 -0
- data/doc/api/all-namespaces.html +42 -0
- data/doc/api/app.js +18 -0
- data/doc/api/index.html +16 -22
- data/doc/api/jquery.js +11 -0
- data/doc/api/readme.html +35 -0
- data/doc/api/style.css +68 -0
- data/doc/api/syntax_highlight.css +21 -0
- data/doc/erbook.png +0 -0
- data/doc/erbook.svg +150 -88
- data/doc/formats.erb +387 -0
- data/doc/history.erb +62 -0
- data/doc/index.erb +8 -0
- data/doc/index.xhtml +846 -654
- data/doc/intro.erb +97 -0
- data/doc/setup.erb +62 -0
- data/doc/theory.erb +187 -0
- data/doc/usage.erb +39 -0
- data/fmt/xhtml.yaml +497 -372
- data/lib/erbook.rb +18 -10
- data/lib/erbook/document.rb +233 -0
- data/lib/erbook/template.rb +210 -0
- data/lib/erbook/to_xhtml.rb +25 -17
- metadata +39 -45
- data/README +0 -14
- data/doc/api/classes/ERBook.html +0 -164
- data/doc/api/classes/RDoc.html +0 -112
- data/doc/api/classes/RDoc/AnyMethod.html +0 -195
- data/doc/api/classes/RDoc/AnyMethod.src/M000003.html +0 -18
- data/doc/api/classes/RDoc/AnyMethod.src/M000004.html +0 -23
- data/doc/api/classes/RDoc/AnyMethod.src/M000005.html +0 -18
- data/doc/api/classes/RDoc/AnyMethod.src/M000006.html +0 -22
- data/doc/api/classes/RDoc/TopLevel.html +0 -250
- data/doc/api/classes/RDoc/TopLevel.src/M000007.html +0 -18
- data/doc/api/classes/RDoc/TopLevel.src/M000008.html +0 -18
- data/doc/api/classes/RDoc/TopLevel.src/M000009.html +0 -18
- data/doc/api/classes/RDoc/TopLevel.src/M000010.html +0 -29
- data/doc/api/classes/RDoc/TopLevel.src/M000011.html +0 -25
- data/doc/api/classes/RDoc/TopLevel.src/M000012.html +0 -18
- data/doc/api/classes/String.html +0 -196
- data/doc/api/classes/String.src/M000001.html +0 -18
- data/doc/api/classes/String.src/M000002.html +0 -31
- data/doc/api/created.rid +0 -1
- data/doc/api/files/lib/erbook/rdoc_rb.html +0 -116
- data/doc/api/files/lib/erbook/to_xhtml_rb.html +0 -125
- data/doc/api/files/lib/erbook_rb.html +0 -107
- data/doc/api/fr_class_index.html +0 -31
- data/doc/api/fr_file_index.html +0 -29
- data/doc/api/fr_method_index.html +0 -38
- data/doc/api/rdoc-style.css +0 -208
- data/doc/feed-icon-28x28.png +0 -0
- data/doc/manual.erb +0 -812
data/lib/erbook.rb
CHANGED
@@ -1,13 +1,21 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
1
|
+
require 'rubygems'
|
2
|
+
require 'inochi'
|
3
|
+
|
4
|
+
Inochi.init :ERBook,
|
5
|
+
:program => 'erbook',
|
6
|
+
:version => '6.0.0',
|
7
|
+
:release => '2009-01-19',
|
8
|
+
:website => 'http://snk.tuxfamily.org/lib/erbook/',
|
9
|
+
:tagline => 'Extensible document processor based on eRuby',
|
10
|
+
:require => {
|
11
|
+
# gems needed by the default 'xhtml' format
|
12
|
+
'maruku' => '~> 0.5',
|
13
|
+
'coderay' => '>= 0.7',
|
14
|
+
}
|
9
15
|
|
10
|
-
|
11
|
-
FORMATS_DIR = File.join
|
16
|
+
module ERBook
|
17
|
+
FORMATS_DIR = File.join(INSTALL, 'fmt')
|
12
18
|
FORMAT_FILES = Dir[File.join(FORMATS_DIR, '*.yaml')]
|
13
19
|
end
|
20
|
+
|
21
|
+
require 'erbook/document'
|
@@ -0,0 +1,233 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'erbook/template'
|
3
|
+
|
4
|
+
module ERBook
|
5
|
+
class Document
|
6
|
+
# Data from the format specification file.
|
7
|
+
attr_reader :format
|
8
|
+
|
9
|
+
# All root nodes in the document.
|
10
|
+
attr_reader :roots
|
11
|
+
|
12
|
+
# All nodes in the document.
|
13
|
+
attr_reader :nodes
|
14
|
+
|
15
|
+
# All nodes in the document arranged by node type.
|
16
|
+
attr_reader :nodes_by_type
|
17
|
+
|
18
|
+
##
|
19
|
+
# @param [String] format
|
20
|
+
# Either the short-hand name of a built-in format
|
21
|
+
# or the path to a format specification file.
|
22
|
+
#
|
23
|
+
# @param [String] input_text
|
24
|
+
# The body of the input document.
|
25
|
+
#
|
26
|
+
# @param [String] input_file
|
27
|
+
# Name of the file from which the input document originated.
|
28
|
+
#
|
29
|
+
# @param [Hash] options
|
30
|
+
# Additional method parameters:
|
31
|
+
#
|
32
|
+
# [boolean] :unindent =>
|
33
|
+
# If true, all node content is unindented hierarchically.
|
34
|
+
#
|
35
|
+
def initialize format, input_text, input_file, options = {}
|
36
|
+
# process format specification
|
37
|
+
@format_file = format.to_s
|
38
|
+
|
39
|
+
File.file? @format_file or
|
40
|
+
@format_file = File.join(ERBook::FORMATS_DIR, @format_file + '.yaml')
|
41
|
+
|
42
|
+
begin
|
43
|
+
@format = YAML.load_file(@format_file)
|
44
|
+
@format[:file] = File.expand_path(@format_file)
|
45
|
+
@format[:name] = File.basename(@format_file).sub(/\..*?$/, '')
|
46
|
+
|
47
|
+
if @format.key? 'code'
|
48
|
+
eval @format['code'].to_s, TOPLEVEL_BINDING, "#{@format_file}:code"
|
49
|
+
end
|
50
|
+
|
51
|
+
rescue Exception
|
52
|
+
error "Could not load format specification file #{@format_file.inspect}"
|
53
|
+
end
|
54
|
+
|
55
|
+
@node_defs = @format['nodes']
|
56
|
+
|
57
|
+
# process input document
|
58
|
+
begin
|
59
|
+
# create sandbox for input evaluation
|
60
|
+
template = Template.new(input_file, input_text, options[:unindent])
|
61
|
+
|
62
|
+
@template_vars = {
|
63
|
+
:@format => @format,
|
64
|
+
:@roots => @roots = [], # root nodes of all trees
|
65
|
+
:@nodes => @nodes = [], # all nodes in the forest
|
66
|
+
:@nodes_by_type => @nodes_by_type = Hash.new {|h,k| h[k] = [] },
|
67
|
+
:@node_stack => [], # stack for all nodes
|
68
|
+
:@index_stack => [], # stack for nodes having index only
|
69
|
+
}.each_pair {|k,v| template.instance_variable_set(k, v) }
|
70
|
+
|
71
|
+
class << template
|
72
|
+
private
|
73
|
+
|
74
|
+
# Handles the method call from a node
|
75
|
+
# placeholder in the input document.
|
76
|
+
def __node__ node_type, *node_args, &node_content
|
77
|
+
node = Node.new(
|
78
|
+
:type => node_type,
|
79
|
+
:defn => @format['nodes'][node_type],
|
80
|
+
:args => node_args,
|
81
|
+
:children => [],
|
82
|
+
|
83
|
+
# omit erbook internals from the stack trace
|
84
|
+
:trace => caller.reject {|t|
|
85
|
+
[$0, ERBook::INSTALL].any? {|f| t.index(f) == 0 }
|
86
|
+
}
|
87
|
+
)
|
88
|
+
@nodes << node
|
89
|
+
@nodes_by_type[node.type] << node
|
90
|
+
|
91
|
+
# calculate occurrence number for this node
|
92
|
+
if node.defn['number']
|
93
|
+
@count ||= Hash.new {|h,k| h[k] = []}
|
94
|
+
node.number = (@count[node.type] << node).length
|
95
|
+
end
|
96
|
+
|
97
|
+
# assign node family
|
98
|
+
if parent = @node_stack.last
|
99
|
+
parent.children << node
|
100
|
+
node.parent = parent
|
101
|
+
node.depth = parent.depth
|
102
|
+
node.depth += 1 if node.defn['depth']
|
103
|
+
|
104
|
+
# calculate latex-style index number for this node
|
105
|
+
if node.defn['index']
|
106
|
+
parent = @index_stack.last
|
107
|
+
branches = parent.children.select {|n| n.index }
|
108
|
+
node.index = [parent.index, branches.length + 1].join('.')
|
109
|
+
end
|
110
|
+
else
|
111
|
+
@roots << node
|
112
|
+
node.parent = nil
|
113
|
+
node.depth = 0
|
114
|
+
|
115
|
+
# calculate latex-style index number for this node
|
116
|
+
if node.defn['index']
|
117
|
+
branches = @roots.select {|n| n.index }
|
118
|
+
node.index = (branches.length + 1).to_s
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# assign node content
|
123
|
+
if block_given?
|
124
|
+
@index_stack.push node if node.defn['index']
|
125
|
+
@node_stack.push node
|
126
|
+
content = content_from_block(node, &node_content)
|
127
|
+
@node_stack.pop
|
128
|
+
@index_stack.pop if node.defn['index']
|
129
|
+
|
130
|
+
digest = Document.digest(content)
|
131
|
+
self.buffer << digest
|
132
|
+
else
|
133
|
+
content = nil
|
134
|
+
digest = Document.digest(node.object_id)
|
135
|
+
end
|
136
|
+
|
137
|
+
node.content = content
|
138
|
+
node.digest = digest
|
139
|
+
|
140
|
+
digest
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
@node_defs.each_key do |type|
|
145
|
+
# XXX: using a string because define_method()
|
146
|
+
# does not accept a block until Ruby 1.9
|
147
|
+
file, line = __FILE__, __LINE__ + 1
|
148
|
+
template.instance_eval %{
|
149
|
+
def #{type} *node_args, &node_content
|
150
|
+
__node__ #{type.inspect}, *node_args, &node_content
|
151
|
+
end
|
152
|
+
}, file, line
|
153
|
+
end
|
154
|
+
|
155
|
+
# evaluate the input & build the document tree
|
156
|
+
@processed_document = template.instance_eval { result binding }
|
157
|
+
|
158
|
+
# chain block-level nodes together for local navigation
|
159
|
+
block_nodes = @nodes.reject {|n| @node_defs[n.type]['inline'] }
|
160
|
+
|
161
|
+
require 'enumerator'
|
162
|
+
block_nodes.each_cons(2) do |a, b|
|
163
|
+
a.next_node = b
|
164
|
+
b.prev_node = a
|
165
|
+
end
|
166
|
+
|
167
|
+
# replace node placeholders with their corresponding output
|
168
|
+
expander = lambda do |n, buf|
|
169
|
+
# calculate node output
|
170
|
+
source = "#{@format_file}:nodes:#{n.type}:output"
|
171
|
+
n.output = Template.new(
|
172
|
+
source, @node_defs[n.type]['output'].to_s.chomp
|
173
|
+
).render_with(@template_vars.merge(:@node => n))
|
174
|
+
|
175
|
+
# expand all child nodes in this node's output
|
176
|
+
n.children.each {|c| expander[c, n.output] }
|
177
|
+
|
178
|
+
# replace this node's placeholder with its output in the buffer
|
179
|
+
buf[n.digest] = @node_defs[n.type]['silent'] ? '' : n.output
|
180
|
+
end
|
181
|
+
|
182
|
+
@roots.each {|n| expander[n, @processed_document] }
|
183
|
+
|
184
|
+
rescue Exception
|
185
|
+
puts input_text # so the user can debug line numbers in the stack trace
|
186
|
+
error "Could not process input document #{input_file.inspect}"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
##
|
191
|
+
# Returns the output of this document.
|
192
|
+
#
|
193
|
+
def to_s
|
194
|
+
Template.new("#{@format_file}:output", @format['output'].to_s).
|
195
|
+
render_with(@template_vars.merge(:@content => @processed_document))
|
196
|
+
end
|
197
|
+
|
198
|
+
require 'ostruct'
|
199
|
+
class Node < OpenStruct
|
200
|
+
# deprecated in Ruby 1.8; removed in Ruby 1.9
|
201
|
+
undef id if respond_to? :id
|
202
|
+
undef type if respond_to? :type
|
203
|
+
|
204
|
+
# Returns the output of this node.
|
205
|
+
def to_s
|
206
|
+
output
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
require 'digest/sha1'
|
211
|
+
##
|
212
|
+
# Returns a digest of the given string that
|
213
|
+
# will not be altered by String#to_xhtml.
|
214
|
+
#
|
215
|
+
def Document.digest input
|
216
|
+
Digest::SHA1.hexdigest(input.to_s).
|
217
|
+
|
218
|
+
# XXX: surround all digits with alphabets so
|
219
|
+
# Maruku doesn't change them into HTML
|
220
|
+
gsub(/\d/, 'z\&z')
|
221
|
+
end
|
222
|
+
|
223
|
+
private
|
224
|
+
|
225
|
+
##
|
226
|
+
# Prints the given message and raises the given error.
|
227
|
+
#
|
228
|
+
def error message, error = $!
|
229
|
+
STDERR.printf "%s:\n\n", message
|
230
|
+
raise error
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
module ERBook
|
5
|
+
##
|
6
|
+
# An eRuby template which allows access to the underlying result
|
7
|
+
# buffer (which contains the result of template evaluation thus
|
8
|
+
# far) and provides sandboxing for isolated template rendering.
|
9
|
+
#
|
10
|
+
# In addition to the standard <% eRuby %> directives, this template supports:
|
11
|
+
#
|
12
|
+
# * Lines that begin with '%' are treated as normal eRuby directives.
|
13
|
+
#
|
14
|
+
# * Include directives (<%#include YOUR_PATH #%>) are replaced by the result
|
15
|
+
# of reading and evaluating the YOUR_PATH file in the current context.
|
16
|
+
#
|
17
|
+
# * Unless YOUR_PATH is an absolute path, it is treated as being
|
18
|
+
# relative to the file which contains the include directive.
|
19
|
+
#
|
20
|
+
# * Errors originating from included files are given a proper
|
21
|
+
# stack trace which shows the chain of inclusion plus any
|
22
|
+
# further trace steps originating from the included file itself.
|
23
|
+
#
|
24
|
+
# * eRuby directives delimiting Ruby blocks (<% ... do %>
|
25
|
+
# ... <% end %>) can be heirarchically unindented by the
|
26
|
+
# crown margin of the opening (<% ... do %>) delimiter.
|
27
|
+
#
|
28
|
+
class Template < ERB
|
29
|
+
# The result of template evaluation thus far.
|
30
|
+
attr_reader :buffer
|
31
|
+
|
32
|
+
##
|
33
|
+
# @param [String] source
|
34
|
+
# Replacement for the ambiguous '(erb)' identifier in stack traces;
|
35
|
+
# so that the user can better determine the source of an error.
|
36
|
+
#
|
37
|
+
# @param [String] input
|
38
|
+
# A string containing eRuby directives.
|
39
|
+
#
|
40
|
+
# @param [boolean] unindent
|
41
|
+
# If true, then all content blocks will be unindented hierarchically,
|
42
|
+
# by the leading space of their 'do' and 'end' delimiters.
|
43
|
+
#
|
44
|
+
# @param safe_level
|
45
|
+
# See safe_level in ERB::new().
|
46
|
+
#
|
47
|
+
def initialize source, input, unindent = false, safe_level = nil
|
48
|
+
# expand all "include" directives in the input
|
49
|
+
expander = lambda do |src_file, src_text, path_stack, stack_trace|
|
50
|
+
src_path = File.expand_path(src_file)
|
51
|
+
src_line = 1 # line number of the current include directive in src_file
|
52
|
+
|
53
|
+
chunks = src_text.split(/<%#\s*include\s+(.+?)\s*#%>/)
|
54
|
+
|
55
|
+
path_stack.push src_path
|
56
|
+
chunks.each_with_index do |chunk, i|
|
57
|
+
# even number: chunk is not an include directive
|
58
|
+
if i & 1 == 0
|
59
|
+
src_line += chunk.count("\n")
|
60
|
+
|
61
|
+
# odd number: chunk is the target of the include directive
|
62
|
+
else
|
63
|
+
# resolve correct path of target file
|
64
|
+
dst_file = chunk
|
65
|
+
|
66
|
+
unless Pathname.new(dst_file).absolute?
|
67
|
+
# target is relative to the file in
|
68
|
+
# which the include directive exists
|
69
|
+
dst_file = File.join(File.dirname(src_file), dst_file)
|
70
|
+
end
|
71
|
+
|
72
|
+
dst_path = File.expand_path(dst_file)
|
73
|
+
|
74
|
+
# include the target file
|
75
|
+
if path_stack.include? dst_file
|
76
|
+
raise "Cannot include #{dst_file.inspect} at #{src_file.inspect}:#{src_line} because that would cause an infinite loop in the inclusion stack: #{path_stack.inspect}."
|
77
|
+
else
|
78
|
+
stack_trace.push "#{src_path}:#{src_line}"
|
79
|
+
dst_text = eval('File.read dst_file', binding, src_file, src_line)
|
80
|
+
|
81
|
+
# recursively expand any include directives within
|
82
|
+
# the expansion of the current include directive
|
83
|
+
dst_text = expander[dst_file, dst_text, path_stack, stack_trace]
|
84
|
+
|
85
|
+
# provide more accurate stack trace for
|
86
|
+
# errors originating from included files
|
87
|
+
line_var = "__erbook_var_#{dst_file.object_id.abs}__"
|
88
|
+
dst_text = %{<%
|
89
|
+
#{line_var} = __LINE__ + 2 # content is 2 newlines below
|
90
|
+
begin
|
91
|
+
%>#{dst_text}<%
|
92
|
+
rescue Exception => err
|
93
|
+
bak = err.backtrace
|
94
|
+
|
95
|
+
top = []
|
96
|
+
found_top = false
|
97
|
+
prev_line = nil
|
98
|
+
|
99
|
+
bak.each do |step|
|
100
|
+
if step =~ /^#{/#{source}/}:(\\d+)(.*)/
|
101
|
+
line, desc = $1, $2
|
102
|
+
line = line.to_i - #{line_var} + 1
|
103
|
+
|
104
|
+
if line > 0 and line != prev_line
|
105
|
+
top << "#{dst_path}:\#{line}\#{desc}"
|
106
|
+
found_top = true
|
107
|
+
prev_line = line
|
108
|
+
end
|
109
|
+
elsif !found_top
|
110
|
+
top << step
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
if found_top
|
115
|
+
bak.replace top
|
116
|
+
bak.concat #{stack_trace.reverse.inspect}
|
117
|
+
end
|
118
|
+
|
119
|
+
raise err
|
120
|
+
end
|
121
|
+
%>}
|
122
|
+
|
123
|
+
stack_trace.pop
|
124
|
+
end
|
125
|
+
|
126
|
+
chunks[i] = dst_text
|
127
|
+
end
|
128
|
+
end
|
129
|
+
path_stack.pop
|
130
|
+
|
131
|
+
chunks.join
|
132
|
+
end
|
133
|
+
|
134
|
+
input = expander[source, input, [], []]
|
135
|
+
|
136
|
+
# convert "% at beginning of line" usage into <% normal %> usage
|
137
|
+
input.gsub! %r{^([ \t]*)(%[=# \t].*)$}, '\1<\2 %>'
|
138
|
+
input.gsub! %r{^([ \t]*)%%}, '\1%'
|
139
|
+
|
140
|
+
# unindent node content hierarchically
|
141
|
+
if unindent
|
142
|
+
tags = input.scan(/<%(?:.(?!<%))*?%>/m)
|
143
|
+
margins = []
|
144
|
+
result = []
|
145
|
+
|
146
|
+
buffer = input
|
147
|
+
tags.each do |tag|
|
148
|
+
chunk, buffer = buffer.split(tag, 2)
|
149
|
+
chunk << tag
|
150
|
+
|
151
|
+
# perform unindentation
|
152
|
+
result << chunk.gsub(/^#{margins.last}/, '')
|
153
|
+
|
154
|
+
# prepare for next unindentation
|
155
|
+
case tag
|
156
|
+
when /<%[^%=].*?\bdo\b.*?%>/m
|
157
|
+
margins.push buffer[/^[ \t]*(?=\S)/]
|
158
|
+
|
159
|
+
when /<%\s*end\s*%>/m
|
160
|
+
margins.pop
|
161
|
+
end
|
162
|
+
end
|
163
|
+
result << buffer
|
164
|
+
|
165
|
+
input = result.join
|
166
|
+
end
|
167
|
+
|
168
|
+
# silence the code-only <% ... %> directive, just like PHP does
|
169
|
+
input.gsub! %r{^[ \t]*(<%[^%=]((?!<%).)*?[^%]%>)[ \t]*\r?\n}m, '\1'
|
170
|
+
|
171
|
+
# use @buffer to store the result of the ERB template
|
172
|
+
super input, safe_level, nil, :@buffer
|
173
|
+
|
174
|
+
self.filename = source
|
175
|
+
end
|
176
|
+
|
177
|
+
# Renders this template within a fresh object that is populated with
|
178
|
+
# the given instance variables, whose names must be prefixed with '@'.
|
179
|
+
def render_with inst_vars = {}
|
180
|
+
context = Object.new.instance_eval do
|
181
|
+
inst_vars.each_pair do |var, val|
|
182
|
+
instance_variable_set var, val
|
183
|
+
end
|
184
|
+
|
185
|
+
binding
|
186
|
+
end
|
187
|
+
|
188
|
+
result context
|
189
|
+
end
|
190
|
+
|
191
|
+
protected
|
192
|
+
|
193
|
+
# Returns the content that the given block wants to append to
|
194
|
+
# the buffer. If the given block does not want to append to the
|
195
|
+
# buffer, then returns the result of invoking the given block.
|
196
|
+
def content_from_block *block_args
|
197
|
+
raise ArgumentError, 'block must be given' unless block_given?
|
198
|
+
|
199
|
+
head = @buffer.length
|
200
|
+
body = yield(*block_args) # this appends 'content' to '@buffer'
|
201
|
+
tail = @buffer.length
|
202
|
+
|
203
|
+
if tail > head
|
204
|
+
@buffer.slice! head..tail
|
205
|
+
else
|
206
|
+
body
|
207
|
+
end.to_s
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|