erbook 5.0.0 → 6.0.0

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