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.
Files changed (68) hide show
  1. data/LICENSE +1 -1
  2. data/Rakefile +6 -79
  3. data/bin/erbook +25 -319
  4. data/doc/HelloWorld.spec +23 -21
  5. data/doc/README +4 -3
  6. data/doc/api/ERBook.html +35 -0
  7. data/doc/api/ERBook/Document.html +673 -0
  8. data/doc/api/ERBook/Document/Node.html +102 -0
  9. data/doc/api/ERBook/Template.html +670 -0
  10. data/doc/api/RDoc.html +23 -0
  11. data/doc/api/RDoc/AnyMethod.html +302 -0
  12. data/doc/api/RDoc/DummyMarkup.html +73 -0
  13. data/doc/api/RDoc/DummyMixin.html +23 -0
  14. data/doc/api/RDoc/DummyOptions.html +140 -0
  15. data/doc/api/RDoc/TopLevel.html +465 -0
  16. data/doc/api/String.html +372 -0
  17. data/doc/api/all-methods.html +253 -0
  18. data/doc/api/all-namespaces.html +42 -0
  19. data/doc/api/app.js +18 -0
  20. data/doc/api/index.html +16 -22
  21. data/doc/api/jquery.js +11 -0
  22. data/doc/api/readme.html +35 -0
  23. data/doc/api/style.css +68 -0
  24. data/doc/api/syntax_highlight.css +21 -0
  25. data/doc/erbook.png +0 -0
  26. data/doc/erbook.svg +150 -88
  27. data/doc/formats.erb +387 -0
  28. data/doc/history.erb +62 -0
  29. data/doc/index.erb +8 -0
  30. data/doc/index.xhtml +846 -654
  31. data/doc/intro.erb +97 -0
  32. data/doc/setup.erb +62 -0
  33. data/doc/theory.erb +187 -0
  34. data/doc/usage.erb +39 -0
  35. data/fmt/xhtml.yaml +497 -372
  36. data/lib/erbook.rb +18 -10
  37. data/lib/erbook/document.rb +233 -0
  38. data/lib/erbook/template.rb +210 -0
  39. data/lib/erbook/to_xhtml.rb +25 -17
  40. metadata +39 -45
  41. data/README +0 -14
  42. data/doc/api/classes/ERBook.html +0 -164
  43. data/doc/api/classes/RDoc.html +0 -112
  44. data/doc/api/classes/RDoc/AnyMethod.html +0 -195
  45. data/doc/api/classes/RDoc/AnyMethod.src/M000003.html +0 -18
  46. data/doc/api/classes/RDoc/AnyMethod.src/M000004.html +0 -23
  47. data/doc/api/classes/RDoc/AnyMethod.src/M000005.html +0 -18
  48. data/doc/api/classes/RDoc/AnyMethod.src/M000006.html +0 -22
  49. data/doc/api/classes/RDoc/TopLevel.html +0 -250
  50. data/doc/api/classes/RDoc/TopLevel.src/M000007.html +0 -18
  51. data/doc/api/classes/RDoc/TopLevel.src/M000008.html +0 -18
  52. data/doc/api/classes/RDoc/TopLevel.src/M000009.html +0 -18
  53. data/doc/api/classes/RDoc/TopLevel.src/M000010.html +0 -29
  54. data/doc/api/classes/RDoc/TopLevel.src/M000011.html +0 -25
  55. data/doc/api/classes/RDoc/TopLevel.src/M000012.html +0 -18
  56. data/doc/api/classes/String.html +0 -196
  57. data/doc/api/classes/String.src/M000001.html +0 -18
  58. data/doc/api/classes/String.src/M000002.html +0 -31
  59. data/doc/api/created.rid +0 -1
  60. data/doc/api/files/lib/erbook/rdoc_rb.html +0 -116
  61. data/doc/api/files/lib/erbook/to_xhtml_rb.html +0 -125
  62. data/doc/api/files/lib/erbook_rb.html +0 -107
  63. data/doc/api/fr_class_index.html +0 -31
  64. data/doc/api/fr_file_index.html +0 -29
  65. data/doc/api/fr_method_index.html +0 -38
  66. data/doc/api/rdoc-style.css +0 -208
  67. data/doc/feed-icon-28x28.png +0 -0
  68. data/doc/manual.erb +0 -812
data/lib/erbook.rb CHANGED
@@ -1,13 +1,21 @@
1
- # Project and release information.
2
- module ERBook
3
- PROJECT = 'erbook'
4
- VERSION = '5.0.0'
5
- RELEASE = '2008-11-22'
6
- WEBSITE = 'http://snk.tuxfamily.org/lib/erbook'
7
- SUMMARY = 'Extensible document processor based on eRuby.'
8
- DISPLAY = PROJECT + ' ' + VERSION
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
- INSTALL_DIR = File.expand_path File.join(File.dirname(__FILE__), '..')
11
- FORMATS_DIR = File.join INSTALL_DIR, 'fmt'
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