galena 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/galena.rb +324 -0
- data/lib/galena_test.rb +67 -0
- data/rakefile.rb +36 -0
- metadata +52 -0
data/lib/galena.rb
ADDED
@@ -0,0 +1,324 @@
|
|
1
|
+
# = Galena
|
2
|
+
#
|
3
|
+
# Copyright (c) 2006 Brad Phelan
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
# this software and associated documentation files (the "Software"), to deal in
|
7
|
+
# the Software without restriction, including without limitation the rights to
|
8
|
+
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
9
|
+
# of the Software, and to permit persons to whom the Software is furnished to do
|
10
|
+
# so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in all
|
13
|
+
# copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
# SOFTWARE.
|
22
|
+
#
|
23
|
+
# Galena a bright shiny obsidian black oxide of lead which I fell in love with
|
24
|
+
# whilst working at Pasminco Mines as a student many years back. Within the
|
25
|
+
# expanse of black Galena crysta there are to be found many tiny ruby crystals.
|
26
|
+
#
|
27
|
+
# More precisely, in this context, Galena is text with bit of Ruby embedded
|
28
|
+
# along similar lines to ERB. However it is different to ERB in that it uses
|
29
|
+
# metaprogramming techniques to create templated text methods on any class
|
30
|
+
# you are creating. Ruby methods are dynamically added to the class. Eval
|
31
|
+
# and binding are never used. In theorey this should make these templates
|
32
|
+
# much faster than ERB, though benchmarks need to be done.
|
33
|
+
#
|
34
|
+
#
|
35
|
+
# A simple example is
|
36
|
+
#
|
37
|
+
# class Foo
|
38
|
+
# tdef :bar, "food" <<-END
|
39
|
+
# eat %=food=%
|
40
|
+
# END
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# foo = Foo.new
|
44
|
+
# puts foo.bar "curry"
|
45
|
+
#
|
46
|
+
# eat curry
|
47
|
+
#
|
48
|
+
# See #galena_test.rb for a full example
|
49
|
+
#
|
50
|
+
# :include:galena_test.rb
|
51
|
+
#
|
52
|
+
module Galena
|
53
|
+
|
54
|
+
# Adds a method called #name to the enclosing class. The args
|
55
|
+
# to the methods are the comma seperated list defined in the
|
56
|
+
# string #args. The #text of the template is the final
|
57
|
+
# parameter.
|
58
|
+
#
|
59
|
+
# The template #text accepts the following types of markup.
|
60
|
+
#
|
61
|
+
# Given that Ruby does not require brackets and the magic of
|
62
|
+
# here documents, you can make the template look very close
|
63
|
+
# to what a method definition would look like
|
64
|
+
#
|
65
|
+
# tdef :foo, "a,b,c", <<-END
|
66
|
+
# a - %= a =%
|
67
|
+
# b - %= b =%
|
68
|
+
# c - %= b =%
|
69
|
+
# END
|
70
|
+
#
|
71
|
+
# A line beginning with any whitespace followed by <b>^</b>
|
72
|
+
# designates a line of real Ruby code to be executed.
|
73
|
+
#
|
74
|
+
# A line containing ruby code embedded between the markers
|
75
|
+
# <b>%=</b> and <b>=%</b> will inline the result of the
|
76
|
+
# evaluation of the Ruby code.
|
77
|
+
#
|
78
|
+
# A line beginning with any whitespace followed by </b>^=</b>
|
79
|
+
# designates a template sub-block which should be a function
|
80
|
+
# that processes a closure. For example the two template
|
81
|
+
# methods
|
82
|
+
#
|
83
|
+
# tdef window <<-END
|
84
|
+
# <div>
|
85
|
+
# <div>window</div>
|
86
|
+
# %= yield =%
|
87
|
+
# </div>
|
88
|
+
# END
|
89
|
+
#
|
90
|
+
# tdef foo_window <<-END
|
91
|
+
# ^= window do
|
92
|
+
# foo
|
93
|
+
# ^= end
|
94
|
+
# END
|
95
|
+
#
|
96
|
+
# result in text
|
97
|
+
#
|
98
|
+
# <div>
|
99
|
+
# <div>window</div>
|
100
|
+
# foo
|
101
|
+
# </div>
|
102
|
+
#
|
103
|
+
# Note that foo has been dedented to align with the above div.
|
104
|
+
# Text within a sublock is dedented to left align with the
|
105
|
+
# leading <b>^=</b> of the block.
|
106
|
+
#
|
107
|
+
# The generated method has access to all instance and class variables
|
108
|
+
# of the enclosing class and it's instances.
|
109
|
+
#
|
110
|
+
# You can control the indent of text placed within loops of conditionals
|
111
|
+
# by declaring the ruby code lines with the below markers
|
112
|
+
#
|
113
|
+
# ^>
|
114
|
+
#
|
115
|
+
# or
|
116
|
+
#
|
117
|
+
# ^<
|
118
|
+
#
|
119
|
+
# for example
|
120
|
+
#
|
121
|
+
# foo
|
122
|
+
# ^ for i in (1..2)
|
123
|
+
# %= i =%
|
124
|
+
# ^ end
|
125
|
+
# bar
|
126
|
+
#
|
127
|
+
# would normally output
|
128
|
+
#
|
129
|
+
# foo
|
130
|
+
# 1
|
131
|
+
# 2
|
132
|
+
# bar
|
133
|
+
#
|
134
|
+
# but if you do
|
135
|
+
#
|
136
|
+
# foo
|
137
|
+
# ^< for i in (1..2)
|
138
|
+
# %= i =%
|
139
|
+
# ^> end
|
140
|
+
# bar
|
141
|
+
#
|
142
|
+
# you get
|
143
|
+
#
|
144
|
+
# foo
|
145
|
+
# 1
|
146
|
+
# 2
|
147
|
+
# bar
|
148
|
+
#
|
149
|
+
# A more detailed example
|
150
|
+
#
|
151
|
+
# :include:galena_test.rb
|
152
|
+
def tdef(name, args, text)
|
153
|
+
text = Galena.compile(Galena.ladj(Galena.normalize(text)))
|
154
|
+
text = "def #{name}(" + ( args.split(",").collect {|a| a }.join ", " ) +
|
155
|
+
")\n" + text + "\nend\n"
|
156
|
+
text
|
157
|
+
begin
|
158
|
+
block = class_eval(text);
|
159
|
+
rescue Exception => mes
|
160
|
+
# Generate a nice error message for
|
161
|
+
# the user to find out what went wrong
|
162
|
+
msg = "\n-----------------------------------\n"
|
163
|
+
lnum = 0
|
164
|
+
text.each_line do |line|
|
165
|
+
lnum = lnum + 1
|
166
|
+
msg << lnum.to_s << ":\t" << line
|
167
|
+
end
|
168
|
+
msg << "\n\n" << mes
|
169
|
+
throw msg
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# indent text by #pad spaces
|
174
|
+
def Galena.indent text, pad
|
175
|
+
text.gsub(/^/, " " * pad)
|
176
|
+
end
|
177
|
+
|
178
|
+
# left adjust the text by #pad spaces
|
179
|
+
def Galena.ladj text, pad=0
|
180
|
+
len = 10000
|
181
|
+
text.scan(/^\s+/){|s| len = s.length if s.length < len }
|
182
|
+
rexp = "^\\s{#{len},#{len}}"
|
183
|
+
text.gsub(Regexp.new(rexp), " " * pad)
|
184
|
+
end
|
185
|
+
|
186
|
+
# balances \#= and \#=end blocks
|
187
|
+
class BalanceStack
|
188
|
+
|
189
|
+
def initialize
|
190
|
+
@stack = []
|
191
|
+
@seeking = false
|
192
|
+
@block = false
|
193
|
+
end
|
194
|
+
|
195
|
+
# process a new line
|
196
|
+
def do_line line
|
197
|
+
s_len = line.match(/^\s*/)[0].length
|
198
|
+
case line
|
199
|
+
when /^\s*\^</
|
200
|
+
@block = false
|
201
|
+
push(s_len)
|
202
|
+
@seeking = true
|
203
|
+
when /^\s*\^>/
|
204
|
+
pop
|
205
|
+
else
|
206
|
+
if @seeking
|
207
|
+
if @block == true
|
208
|
+
self.dedent = 0
|
209
|
+
else
|
210
|
+
self.dedent = dedent + s_len - indent
|
211
|
+
end
|
212
|
+
end
|
213
|
+
@seeking = false
|
214
|
+
end
|
215
|
+
# puts "#{dedent}, #{@stack.length}, #{line}"
|
216
|
+
if @stack.empty?
|
217
|
+
line
|
218
|
+
else
|
219
|
+
line = Galena.ladj(line, s_len - dedent)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# indent a line
|
224
|
+
def indent
|
225
|
+
@stack.last[0]
|
226
|
+
end
|
227
|
+
|
228
|
+
# dedent a line
|
229
|
+
def dedent=(dd)
|
230
|
+
@stack.last[1]=dd
|
231
|
+
end
|
232
|
+
|
233
|
+
# return the current dedent
|
234
|
+
def dedent(p=0)
|
235
|
+
if @stack.empty?
|
236
|
+
0
|
237
|
+
else
|
238
|
+
@stack.last[-1-p]
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
private
|
243
|
+
|
244
|
+
def push(indent)
|
245
|
+
@stack.push([indent, dedent])
|
246
|
+
end
|
247
|
+
|
248
|
+
def pop
|
249
|
+
@stack.pop
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def Galena.balance(text)
|
254
|
+
stack = BalanceStack.new
|
255
|
+
seeking = false
|
256
|
+
text.each_line do |line|
|
257
|
+
line = stack.do_line(line)
|
258
|
+
yield line
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
# normalizes the Ruby markup so that the resulting
|
263
|
+
# output will code will generate text with the expected
|
264
|
+
# indent structure. #normalize and it's workhorse
|
265
|
+
def Galena.normalize(text)
|
266
|
+
out = []
|
267
|
+
Galena.balance(text) do |line|
|
268
|
+
out << line
|
269
|
+
end
|
270
|
+
out = out.join
|
271
|
+
end
|
272
|
+
|
273
|
+
private
|
274
|
+
|
275
|
+
def Galena.tout(idx)
|
276
|
+
"_tout[#{idx}]"
|
277
|
+
end
|
278
|
+
|
279
|
+
def Galena.compile(text)
|
280
|
+
indent = 0
|
281
|
+
src = <<-END
|
282
|
+
_tout=[]
|
283
|
+
_tout[0]=""
|
284
|
+
END
|
285
|
+
scope = 0
|
286
|
+
dedent = false
|
287
|
+
|
288
|
+
text.each_line do |line|
|
289
|
+
|
290
|
+
current_indent = line.match(/^\s*/)[0].length
|
291
|
+
|
292
|
+
case line
|
293
|
+
when /^\s*\^=\s*end/
|
294
|
+
# The closing ^=end determines the indent of the
|
295
|
+
# block.
|
296
|
+
capture_indent = current_indent
|
297
|
+
scope = scope - 1
|
298
|
+
src << "end, #{capture_indent} ) \n"
|
299
|
+
when /^\s*\^=/
|
300
|
+
src << tout(scope) << " << Galena.indent( "
|
301
|
+
scope = scope + 1
|
302
|
+
src << line.gsub(/^\s*\^=/,"")
|
303
|
+
src << tout(scope) << " = \"\"\n"
|
304
|
+
when /^\s*\^[<>]?/
|
305
|
+
src << line.gsub(/^\s*\^[<>]?/,"")
|
306
|
+
else
|
307
|
+
src << tout(scope) << "<<(\""
|
308
|
+
# Replace the inline substition tokens
|
309
|
+
src << line.chomp.gsub(/(\s*)%=(.*?)=%/) do
|
310
|
+
"\" + Galena.ladj((#{$2}).to_s, " + ($1.length).to_s + ') + "'
|
311
|
+
end
|
312
|
+
|
313
|
+
src << "\\n\")\n"
|
314
|
+
end
|
315
|
+
end
|
316
|
+
if scope != 0
|
317
|
+
throw "Unbalanced ^= with ^=end : " << scope.to_s
|
318
|
+
end
|
319
|
+
src << tout(scope) << "\n"
|
320
|
+
#puts src
|
321
|
+
#src
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
data/lib/galena_test.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
#! /usr/bin/ruby
|
2
|
+
require 'galena'
|
3
|
+
require 'enumerator'
|
4
|
+
|
5
|
+
include Galena
|
6
|
+
|
7
|
+
class MyText
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@drink = "koolaid"
|
11
|
+
@food = "dogfood"
|
12
|
+
end
|
13
|
+
|
14
|
+
# Creates a method called page which
|
15
|
+
# accepts an array and prints it in
|
16
|
+
# an html table with four columns.
|
17
|
+
tdef :page, "data", <<-END
|
18
|
+
<html>
|
19
|
+
<head>
|
20
|
+
</head>
|
21
|
+
<body>
|
22
|
+
^= tableize(data, 4) do |item|
|
23
|
+
foo
|
24
|
+
^= window("Wow") do
|
25
|
+
This is my item %=item=%
|
26
|
+
boo
|
27
|
+
^= end
|
28
|
+
^= end
|
29
|
+
</body>
|
30
|
+
</html>
|
31
|
+
END
|
32
|
+
|
33
|
+
|
34
|
+
# Generates an html table with a varying
|
35
|
+
# number of columns
|
36
|
+
tdef :tableize, "data, columns", <<-END
|
37
|
+
<table>
|
38
|
+
^< data.each_slice(columns) do |slice|
|
39
|
+
<tr>
|
40
|
+
^< slice.each do |s|
|
41
|
+
<td>
|
42
|
+
%=yield s=%
|
43
|
+
</td>
|
44
|
+
^> end
|
45
|
+
</tr>
|
46
|
+
^> end
|
47
|
+
</table>
|
48
|
+
END
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
# Generates an html div with a title and some
|
53
|
+
# contents defined by the yielded block
|
54
|
+
tdef :window, "title", <<-END
|
55
|
+
<div>
|
56
|
+
<div style="width:100; color:blue;%">
|
57
|
+
%=title=%
|
58
|
+
</div>
|
59
|
+
%=yield=%
|
60
|
+
</div>
|
61
|
+
END
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
h = MyText.new
|
66
|
+
puts h.page(1..10)
|
67
|
+
|
data/rakefile.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/packagetask'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
|
5
|
+
PKG_VERSION="0.0.1"
|
6
|
+
PKG_FILES= [
|
7
|
+
"lib/galena.rb",
|
8
|
+
"lib/galena_test.rb",
|
9
|
+
"rakefile.rb" ]
|
10
|
+
|
11
|
+
spec = Gem::Specification.new do |s|
|
12
|
+
s.platform = Gem::Platform::RUBY
|
13
|
+
s.summary = "Object Oriented Template Engine"
|
14
|
+
s.name = 'galena'
|
15
|
+
s.version = PKG_VERSION
|
16
|
+
s.requirements << 'none'
|
17
|
+
s.require_path = 'lib'
|
18
|
+
s.autorequire = ''
|
19
|
+
s.files = PKG_FILES
|
20
|
+
s.has_rdoc = true
|
21
|
+
s.rdoc_options << '--title' << ' Galena -- OO Templating' <<
|
22
|
+
'--main' << 'lib/galena.rb' <<
|
23
|
+
'-S'
|
24
|
+
s.description = <<EOF
|
25
|
+
Object oriented Ruby template engine with advanced support
|
26
|
+
for blocks/closures used as arguments to layouts.
|
27
|
+
EOF
|
28
|
+
end
|
29
|
+
|
30
|
+
gem = Rake::GemPackageTask.new(spec) do |pkg|
|
31
|
+
pkg.need_zip = true
|
32
|
+
pkg.need_tar = true
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "Default Task packages this into a gem"
|
36
|
+
task :default => :package
|
metadata
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.0
|
3
|
+
specification_version: 1
|
4
|
+
name: galena
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.0.1
|
7
|
+
date: 2006-09-13 00:00:00 +02:00
|
8
|
+
summary: Object Oriented Template Engine
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email:
|
12
|
+
homepage:
|
13
|
+
rubyforge_project:
|
14
|
+
description: Object oriented Ruby template engine with advanced support for blocks/closures used as arguments to layouts.
|
15
|
+
autorequire: ""
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors: []
|
30
|
+
|
31
|
+
files:
|
32
|
+
- lib/galena.rb
|
33
|
+
- lib/galena_test.rb
|
34
|
+
- rakefile.rb
|
35
|
+
test_files: []
|
36
|
+
|
37
|
+
rdoc_options:
|
38
|
+
- --title
|
39
|
+
- " Galena -- OO Templating"
|
40
|
+
- --main
|
41
|
+
- lib/galena.rb
|
42
|
+
- -S
|
43
|
+
extra_rdoc_files: []
|
44
|
+
|
45
|
+
executables: []
|
46
|
+
|
47
|
+
extensions: []
|
48
|
+
|
49
|
+
requirements:
|
50
|
+
- none
|
51
|
+
dependencies: []
|
52
|
+
|