gerbil 1.0.0

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