gerbil 1.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 ADDED
@@ -0,0 +1,23 @@
1
+ Copyright 2006 Suraj N. Kurapati <SNK at GNA dot ORG>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a
4
+ copy of this software and associated documentation files (the "Software"),
5
+ to deal in the Software without restriction, including without limitation
6
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
7
+ and/or sell copies of the Software, and to permit persons to whom the
8
+ Software is furnished to do so, subject to the following conditions:
9
+
10
+ * All copies and substantial portions of the Software (the "Derivatives")
11
+ and their corresponding machine-readable source code (the "Code") must
12
+ include the above copyright notice and this permission notice.
13
+
14
+ * Upon distribution, the Derivatives must be accompanied either by the Code
15
+ or--provided that the Code is obtainable for no more than the cost of
16
+ distribution plus a nominal fee--by information on how to obtain the Code.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
20
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
21
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
22
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1 @@
1
+ Please see doc/guide.html in your web browser.
data/Rakefile ADDED
@@ -0,0 +1,48 @@
1
+ # Copyright 2007 Suraj N. Kurapati
2
+ # See the file named LICENSE for details.
3
+
4
+ require 'rake/clean'
5
+ require 'rake/gempackagetask'
6
+
7
+ # documentation
8
+ desc "Generate documentation."
9
+ task :doc => 'doc/guide.html'
10
+
11
+ file 'doc/guide.html' => 'doc/guide.erb' do |t|
12
+ sh "ruby bin/gerbil html #{t.prerequisites} > #{t.name}"
13
+ end
14
+
15
+ CLOBBER.include 'doc/guide.html'
16
+
17
+ # packaging
18
+ require 'lib/gerbil' # project info
19
+
20
+ spec = Gem::Specification.new do |s|
21
+ s.name = 'gerbil'
22
+ s.version = Gerbil[:version]
23
+ s.summary = 'Extensible document generator based on eRuby.'
24
+ s.description = s.summary
25
+ s.homepage = Gerbil[:website]
26
+ s.files = FileList['**/*'].exclude('_darcs')
27
+ s.executables = s.name
28
+
29
+ s.add_dependency 'RedCloth' # needed by the default 'html' format
30
+ s.add_dependency 'coderay' # needed by the default 'html' format
31
+ end
32
+
33
+ Rake::GemPackageTask.new(spec) do |pkg|
34
+ pkg.need_tar = true
35
+ pkg.need_zip = true
36
+ end
37
+
38
+ # releasing
39
+ desc 'Build packages for distribution.'
40
+ task :release => [:clobber, :doc] do
41
+ system 'rake package'
42
+ end
43
+
44
+ # utility
45
+ desc 'Upload to project website.'
46
+ task :upload => :doc do
47
+ sh 'rsync -av doc/ ~/www/lib/gerbil'
48
+ end
data/bin/gerbil ADDED
@@ -0,0 +1,275 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Gerbil is an extensible document generator based on eRuby.
4
+ #
5
+ # * STDIN will be read if an input file is not 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
+ #
10
+ # Otherwise, the final output document will be printed to STDOUT.
11
+
12
+ # Copyright 2007 Suraj N. Kurapati
13
+ # See the file named LICENSE for details.
14
+
15
+ require 'erb'
16
+ include ERB::Util
17
+
18
+ require 'digest/sha1'
19
+ require 'yaml'
20
+ require 'ostruct'
21
+
22
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
23
+ require 'gerbil'
24
+
25
+ # Prints the given message and raises the given error.
26
+ def hoist aMessage, aError = $!
27
+ STDERR.printf "%s:\n\n", aMessage
28
+ raise aError
29
+ end
30
+
31
+ class String
32
+ # Returns a SHA1 digest (in hex format) of this string.
33
+ def digest
34
+ Digest::SHA1.hexdigest(self)
35
+ end
36
+ end
37
+
38
+ class Template < ERB
39
+ # Returns the result of template evaluation thus far.
40
+ attr_reader :buffer
41
+
42
+ # aName:: String that replaces the ambiguous '(erb)' identifier in stack
43
+ # traces, so that the user can better determine the source of an
44
+ # error.
45
+ #
46
+ # args:: Arguments for ERB::new
47
+ def initialize aName, *args
48
+ # silence the code-only <% ... %> directive, just like PHP does
49
+ args[0].gsub!(/^[ \t]*<%[^%=]((?!<%).)*?[^%]%>[ \t]*\r?\n/m) {|s| s.strip}
50
+
51
+ args[3] = '@buffer'
52
+ super(*args)
53
+
54
+ @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, type # these are deprecated in Ruby 1.8 anyway
93
+ end
94
+
95
+ # the basename() is for being launched by a RubyGems executable
96
+ if __FILE__ == $0 or File.basename(__FILE__) == File.basename($0)
97
+ # parse command-line options
98
+ require 'optparse'
99
+
100
+ opts = OptionParser.new('')
101
+
102
+ opts.on '-h', '--help', 'show usage information' do
103
+ # show program description located at the top of this file
104
+ puts File.read(__FILE__).split(/^$\n/)[1].gsub(/^# ?/, '')
105
+
106
+ puts "\nUsage: #{File.basename $0} [Option...] Format|SpecFile [InputFile]\n"
107
+
108
+ puts "\nOption: #{opts}"
109
+
110
+ puts "\nFormat:"
111
+ Gerbil[:format_files].each do |file|
112
+ name = File.basename(file, '.yaml')
113
+ desc = YAML.load_file(file)['desc'] rescue nil
114
+ puts ' %-32s %s' % [name, desc]
115
+ end
116
+
117
+ exit
118
+ end
119
+
120
+ opts.on '-v', '--version', 'show version information' do
121
+ puts "%s %s (%s) %s %s" %
122
+ [:name, :version, :release, :website, :home].map { |m| Gerbil[m] }
123
+ exit
124
+ end
125
+
126
+ opts.parse! ARGV
127
+
128
+ # load format specification file
129
+ specFile = ARGV.shift or
130
+ raise ArgumentError, "Format was not specified. Try `{$0} -h` for help."
131
+
132
+ File.file? specFile or
133
+ specFile = File.join(Gerbil[:format_home], specFile + '.yaml')
134
+
135
+ begin
136
+ specData = YAML.load_file(specFile).merge! \
137
+ :file => File.expand_path(specFile),
138
+ :name => File.basename(specFile).sub(/\..*?$/, '')
139
+
140
+ rescue Exception
141
+ hoist "Error when loading the format specification file (#{specFile.inspect})"
142
+ end
143
+
144
+ if specData.key? 'code'
145
+ eval specData['code'].to_s, binding, "#{specFile}:code"
146
+ end
147
+
148
+ # load input document
149
+ input = ARGF.read
150
+
151
+ # expand all "include" directives in the input
152
+ begin end while input.gsub! %r/<%#\s*include\s+(.+?)\s*%>/ do
153
+ "<%#begin(#{name = $1.inspect})%>#{File.read $1}<%#end(#{name})%>"
154
+ end
155
+
156
+ # process input document
157
+ template = Template.new('INPUT', input)
158
+
159
+ templateVars = {
160
+ :@spec => specData,
161
+ :@roots => roots = [], # root nodes of all trees
162
+ :@nodes => nodes = [], # all nodes in the forest
163
+ :@types => types = Hash.new {|h,k| h[k] = []}, # nodes by type
164
+ }.each_pair { |k,v| template.instance_variable_set(k, v) }
165
+
166
+ nodeDefs = specData['nodes'].each_pair do |name, info|
167
+ template.instance_eval %{
168
+ #
169
+ # XXX: using a string because define_method()
170
+ # does not accept a block until Ruby 1.9
171
+ #
172
+ def #{name} *aArgs, &aBlock
173
+ node = Node.new(
174
+ :type => #{name.inspect},
175
+ :args => aArgs,
176
+ :trace => caller,
177
+ :children => []
178
+ )
179
+ @nodes << node
180
+ @types[node.type] << node
181
+
182
+ # calculate occurrence number for this node
183
+ if #{info['number']}
184
+ @count ||= Hash.new {|h,k| h[k] = []}
185
+ node.number = (@count[node.type] << node).length
186
+ end
187
+
188
+ @stack ||= []
189
+
190
+ # assign node family
191
+ if parent = @stack.last
192
+ parent.children << node
193
+ node.parent = parent
194
+ node.depth = parent.depth.next
195
+
196
+ # calculate latex-style index number for this node
197
+ if #{info['index']}
198
+ branches = parent.children.select {|n| n.index}
199
+ node.index = [parent.index, branches.length.next].join('.')
200
+ end
201
+ else
202
+ @roots << node
203
+ node.parent = nil
204
+ node.depth = 0
205
+
206
+ # calculate latex-style index number for this node
207
+ if #{info['index']}
208
+ branches = @roots.select {|n| n.index}
209
+ node.index = branches.length.next.to_s
210
+ end
211
+ end
212
+
213
+ # assign node content
214
+ if block_given?
215
+ @stack << node
216
+ content = content_from_block(node, &aBlock)
217
+ @stack.pop
218
+
219
+ digest = content.digest
220
+
221
+ node.content = content
222
+ node.digest = digest
223
+
224
+ @buffer << digest
225
+ else
226
+ digest = node.object_id.to_s.digest
227
+ node.digest = digest
228
+ end
229
+
230
+ digest
231
+ end
232
+ }, __FILE__, Kernel.caller.first[/\d+/].to_i.next
233
+ end
234
+
235
+ begin
236
+ # build the document tree
237
+ document = template.instance_eval { result(binding) }
238
+
239
+ # replace nodes with output
240
+ expander = lambda do |n, buf|
241
+ # unindent content
242
+ if defined? $unindent and n.content
243
+ n.content.gsub! %r/^(#{Regexp.escape $unindent}){0,#{n.depth.next}}/, ''
244
+ end
245
+
246
+ # calculate node output
247
+ source = "#{specFile}:nodes:#{n.type}:output"
248
+ n.output = Template.new(source, nodeDefs[n.type]['output'].to_s.chomp).
249
+ render_with(templateVars.merge(:@node => n))
250
+
251
+ # replace node with output
252
+ if nodeDefs[n.type]['silent']
253
+ buf.sub! n.digest, ''
254
+ buf = n.output
255
+ else
256
+ buf.sub! n.digest, n.output
257
+ end
258
+
259
+ # repeat for all child nodes
260
+ n.children.each {|c| expander[c, buf]}
261
+ end
262
+
263
+ roots.each {|n| expander[n, document]}
264
+
265
+ rescue Exception
266
+ puts input # so the user can debug the line numbers in the stack trace
267
+ hoist 'Error when processing the input document (INPUT)'
268
+ end
269
+
270
+ # produce output document
271
+ output = Template.new("#{specFile}:output", specData['output'].to_s).
272
+ render_with(templateVars.merge(:@content => document))
273
+
274
+ puts output
275
+ end
@@ -0,0 +1,37 @@
1
+ <% $style = "border-left: thick solid salmon; padding-left: 1em;" %>
2
+ <% hello "Pretentious" do %>
3
+ <big>I'm</big> the very first node, oh _yes_ I am! *sneer*
4
+
5
+ <% hello "Bashful" do %>
6
+ Hi, I... *hide*
7
+
8
+ <% hello "Hopeful" do %>
9
+ *sigh*
10
+
11
+ <% hello "Confused" do %>
12
+ Huh?
13
+ <% end %>
14
+ <% end %>
15
+
16
+ <% hello "Raving" do %>
17
+ Oh it's *on* now! You're going *down*!
18
+ <% end %>
19
+ <% end %>
20
+
21
+ <% hello "Sleepy" do %>
22
+ *yawn* Just five more minutes...
23
+
24
+ <% hello "Peaceful" 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_!)" %>
@@ -0,0 +1,66 @@
1
+ desc: A illustrative format.
2
+
3
+ code: |
4
+ # Returns a random, but pronounceable, name.
5
+ def generate_name
6
+ letters = ('a'..'z').to_a - %w[ c q w x ] # redundant sounds
7
+ vowels = %w[a e i o u]
8
+ consonants = letters - vowels
9
+ sets = [consonants, vowels]
10
+
11
+ length = 3 + rand(5)
12
+
13
+ name = ''
14
+ length.times do |i|
15
+ set = sets[i % sets.length]
16
+ name << set[rand(set.length)]
17
+ end
18
+
19
+ name
20
+ end
21
+
22
+ class Node
23
+ def name
24
+ @name ||= generate_name
25
+ end
26
+ end
27
+
28
+ nodes:
29
+ hello:
30
+ index: true
31
+ number: true
32
+ silent: false
33
+ output: |
34
+ <h1><%= @node.type %> #<%= @node.index %>: <%= @node.name.inspect %></h1>
35
+ My name is <%= @node.name.inspect %> and these are my properties:
36
+ <table>
37
+ <tr>
38
+ <th>Property</th>
39
+ <th>Value</th>
40
+ </tr>
41
+ <tr>
42
+ <td>args</td>
43
+ <td><%= @node.args.inspect %></td>
44
+ </tr>
45
+ <tr>
46
+ <td>index</td>
47
+ <td><%= @node.index.inspect %></td>
48
+ </tr>
49
+ <tr>
50
+ <td>number</td>
51
+ <td><%= @node.number.inspect %></td>
52
+ </tr>
53
+ <tr>
54
+ <td>trace</td>
55
+ <td><ul><%= @node.trace.map {|s| "<li>#{s}</li>"} %></ul></td>
56
+ </tr>
57
+ <tr>
58
+ <td>content</td>
59
+ <td><%= @node.content %></td>
60
+ </tr>
61
+ </table>
62
+
63
+ output: |
64
+ Welcome to the "<%= @spec[:name] %>" format.
65
+ <div style="<%= $style %>"><%= @content %></div>
66
+ That's all folks!