erbook 5.0.0 → 6.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|