nitro 0.24.0 → 0.25.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/CHANGELOG +279 -0
- data/ProjectInfo +7 -7
- data/doc/AUTHORS +4 -2
- data/doc/RELEASES +96 -1
- data/lib/nitro.rb +5 -2
- data/lib/nitro/adapter/cgi.rb +1 -1
- data/lib/nitro/adapter/webrick.rb +7 -5
- data/lib/nitro/caching/output.rb +3 -2
- data/lib/nitro/cgi/utils.rb +8 -4
- data/lib/nitro/compiler.rb +9 -5
- data/lib/nitro/compiler/include.rb +42 -0
- data/lib/nitro/compiler/markup.rb +1 -1
- data/lib/nitro/compiler/morphing.rb +120 -50
- data/lib/nitro/compiler/squeeze.rb +2 -2
- data/lib/nitro/context.rb +9 -0
- data/lib/nitro/controller.rb +8 -4
- data/lib/nitro/dispatcher.rb +5 -5
- data/lib/nitro/dispatcher/nice.rb +16 -5
- data/lib/nitro/element.rb +30 -8
- data/lib/nitro/helper.rb +56 -0
- data/lib/nitro/{mixin → helper}/benchmark.rb +1 -1
- data/lib/nitro/{mixin → helper}/buffer.rb +1 -2
- data/lib/nitro/{mixin → helper}/debug.rb +1 -1
- data/lib/nitro/{mixin/helper.rb → helper/default.rb} +4 -4
- data/lib/nitro/{mixin → helper}/form.rb +3 -3
- data/lib/nitro/{mixin → helper}/javascript.rb +17 -1
- data/lib/nitro/{mixin → helper}/pager.rb +1 -1
- data/lib/nitro/{mixin → helper}/rss.rb +3 -3
- data/lib/nitro/{mixin → helper}/table.rb +1 -1
- data/lib/nitro/{mixin → helper}/xhtml.rb +2 -2
- data/lib/nitro/{mixin → helper}/xml.rb +1 -1
- data/lib/nitro/render.rb +16 -8
- data/lib/nitro/scaffold.rb +6 -6
- data/lib/nitro/server/runner.rb +12 -0
- data/lib/nitro/session/drbserver.rb +16 -1
- data/proto/public/js/builder.js +97 -0
- data/proto/public/js/controls.js +18 -5
- data/proto/public/js/dragdrop.js +8 -5
- data/proto/public/js/effects.js +185 -4
- data/proto/public/js/prototype.js +432 -178
- data/proto/public/js/scriptaculous.js +6 -2
- data/proto/public/js/slider.js +226 -0
- data/proto/public/js/unittest.js +363 -0
- data/proto/public/media/nitro.png +0 -0
- data/{script → proto/script}/scgi_ctl +0 -0
- data/{script → proto/script}/scgi_service +16 -8
- data/proto/src/skin.rb +24 -0
- data/{lib → src}/part/admin.rb +0 -0
- data/src/part/admin/controller.rb +47 -0
- data/{lib → src}/part/admin/skin.rb +0 -0
- data/src/part/admin/template/denied.xhtml +1 -0
- data/{lib → src}/part/admin/template/index.xhtml +0 -0
- data/test/nitro/{mixin → helper}/tc_pager.rb +2 -2
- data/test/nitro/{mixin → helper}/tc_rss.rb +3 -3
- data/test/nitro/{mixin → helper}/tc_table.rb +3 -3
- data/test/nitro/{mixin → helper}/tc_xhtml.rb +3 -3
- data/test/nitro/tc_caching.rb +4 -1
- data/test/nitro/tc_controller.rb +2 -2
- data/test/nitro/tc_element.rb +30 -0
- data/test/nitro/tc_helper.rb +36 -0
- data/test/nitro/tc_render.rb +1 -1
- metadata +70 -38
- data/lib/nitro/mixin/markup.rb +0 -122
- data/lib/part/admin/controller.rb +0 -28
- data/proto/README +0 -11
- data/proto/doc/README +0 -1
- data/proto/scgi.rb +0 -333
data/lib/nitro/adapter/cgi.rb
CHANGED
|
@@ -134,7 +134,8 @@ class WebrickAdapter < WEBrick::HTTPServlet::AbstractServlet
|
|
|
134
134
|
|
|
135
135
|
# gmosx: make compatible with fastcgi.
|
|
136
136
|
context.headers['REQUEST_URI'].slice!(/http:\/\/(.*?)\//)
|
|
137
|
-
context.headers['REQUEST_URI'] << '/'
|
|
137
|
+
# context.headers['REQUEST_URI'] << '/'
|
|
138
|
+
context.headers['REQUEST_URI'] = '/' + context.headers['REQUEST_URI']
|
|
138
139
|
|
|
139
140
|
Cgi.parse_params(context)
|
|
140
141
|
Cgi.parse_cookies(context)
|
|
@@ -161,9 +162,9 @@ class WebrickAdapter < WEBrick::HTTPServlet::AbstractServlet
|
|
|
161
162
|
# Try to rewrite the path to a filename.
|
|
162
163
|
|
|
163
164
|
def rewrite(req)
|
|
164
|
-
if req.
|
|
165
|
+
if req.path_info == '/'
|
|
165
166
|
req.instance_variable_set(:@path_info, '/index.html')
|
|
166
|
-
elsif req.
|
|
167
|
+
elsif req.path_info =~ /^([^.]+)$/
|
|
167
168
|
req.instance_variable_set(:@path_info, "#{$1}/index.html")
|
|
168
169
|
end
|
|
169
170
|
end
|
|
@@ -171,9 +172,9 @@ class WebrickAdapter < WEBrick::HTTPServlet::AbstractServlet
|
|
|
171
172
|
# Rewrite back to the original path.
|
|
172
173
|
|
|
173
174
|
def unrewrite(req)
|
|
174
|
-
if req.
|
|
175
|
+
if req.path_info == '/index.html'
|
|
175
176
|
req.instance_variable_set(:@path_info, '/')
|
|
176
|
-
elsif req.
|
|
177
|
+
elsif req.path_info =~ /^([^.]+)\/index.html$/
|
|
177
178
|
req.instance_variable_set(:@path_info, $1)
|
|
178
179
|
end
|
|
179
180
|
end
|
|
@@ -183,3 +184,4 @@ end
|
|
|
183
184
|
|
|
184
185
|
# * George Moschovitis <gm@navel.gr>
|
|
185
186
|
# * Guillaume Pierronnet <guillaume.pierronnet@laposte.net>
|
|
187
|
+
# * Bryan Soto <bryan.a.soto@gmail.com>
|
data/lib/nitro/caching/output.rb
CHANGED
|
@@ -44,7 +44,7 @@ module Caching
|
|
|
44
44
|
def output_cache_path(path)
|
|
45
45
|
filename = ((path.empty? || path == '/') ? 'index.html' : path.dup)
|
|
46
46
|
# filename.gsub!(/\/$/, '')
|
|
47
|
-
filename << 'index.html' unless (filename.split('/').last || filename).include? '.'
|
|
47
|
+
filename << '/index.html' unless (filename.split('/').last || filename).include? '.'
|
|
48
48
|
return output_cache_root + '/' + filename
|
|
49
49
|
end
|
|
50
50
|
|
|
@@ -66,9 +66,10 @@ module Caching
|
|
|
66
66
|
# gmosx: is this the right thing to do?
|
|
67
67
|
end
|
|
68
68
|
end
|
|
69
|
+
alias_method :delete_output, :expire_output
|
|
69
70
|
|
|
70
71
|
def caching_allowed?
|
|
71
|
-
|
|
72
|
+
not (@request.post? or @request.uri =~ /\?/)
|
|
72
73
|
end
|
|
73
74
|
|
|
74
75
|
end
|
data/lib/nitro/cgi/utils.rb
CHANGED
|
@@ -5,25 +5,29 @@ module Nitro
|
|
|
5
5
|
module Request
|
|
6
6
|
|
|
7
7
|
# Some useful mini methods for browser testing.
|
|
8
|
+
# TODO: add testing for mac IE (an other platforms)
|
|
8
9
|
|
|
10
|
+
# Different servers hold user agent in differnet
|
|
11
|
+
# strings (unify this).
|
|
12
|
+
|
|
9
13
|
def user_agent
|
|
10
14
|
headers['HTTP_USER_AGENT'] || headers['USER-AGENT']
|
|
11
15
|
end
|
|
12
16
|
|
|
13
17
|
def from_gecko?
|
|
14
|
-
|
|
18
|
+
user_agent =~ /Gecko/
|
|
15
19
|
end
|
|
16
20
|
alias_method :from_mozilla?, :from_gecko?
|
|
17
21
|
|
|
18
22
|
def from_ie?
|
|
19
|
-
|
|
23
|
+
user_agent =~ /MSIE/
|
|
20
24
|
end
|
|
21
25
|
alias_method :from_msie?, :from_ie?
|
|
22
26
|
|
|
23
27
|
def from_opera?
|
|
24
|
-
|
|
28
|
+
user_agent =~ /Opera/
|
|
25
29
|
end
|
|
26
30
|
|
|
27
31
|
def from_khtml?
|
|
28
|
-
|
|
32
|
+
user_agent =~ /KTHML/
|
|
29
33
|
end
|
|
30
34
|
alias_method :from_safari?, :from_khtml?
|
|
31
35
|
|
data/lib/nitro/compiler.rb
CHANGED
|
@@ -38,7 +38,7 @@ class Compiler
|
|
|
38
38
|
# template_root/action/index.xhtml
|
|
39
39
|
|
|
40
40
|
path = "#{template_root}/#{action.gsub(/__/, '/')}/#{Template.default}.#{ext}".squeeze('/')
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
unless File.exist?(path)
|
|
43
43
|
# No template found!
|
|
44
44
|
return nil
|
|
@@ -105,7 +105,7 @@ class Compiler
|
|
|
105
105
|
|
|
106
106
|
code = %{
|
|
107
107
|
def #{action}_action
|
|
108
|
-
@parent_action_name = @action_name
|
|
108
|
+
@parent_action_name = @action_name
|
|
109
109
|
@action_name = '#{action}'
|
|
110
110
|
}
|
|
111
111
|
|
|
@@ -131,17 +131,21 @@ class Compiler
|
|
|
131
131
|
param_count = klass.instance_method(action.intern).arity
|
|
132
132
|
|
|
133
133
|
if param_count != 0
|
|
134
|
+
if param_count > 0
|
|
135
|
+
code << "params = Array.new(#{param_count}, nil);"
|
|
136
|
+
else
|
|
137
|
+
code << "params = [];"
|
|
138
|
+
end
|
|
134
139
|
code << %{
|
|
135
|
-
params = []
|
|
136
140
|
if context.query_string
|
|
137
141
|
context.query_string.split(/[&;]/).each_with_index do |qs, i|
|
|
138
142
|
}
|
|
139
143
|
# Don't pass more parameters than the action's arity.
|
|
140
144
|
if param_count > 0
|
|
141
|
-
code << "break
|
|
145
|
+
code << "break unless i < #{param_count};"
|
|
142
146
|
end
|
|
143
147
|
code << %{
|
|
144
|
-
params
|
|
148
|
+
params[i] = CGI.unescape(qs.split(/=/).last)
|
|
145
149
|
end
|
|
146
150
|
end
|
|
147
151
|
action_return_value = #{action}(*params)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Nitro
|
|
2
|
+
|
|
3
|
+
# Performs static includes. Typically you should include this compiler
|
|
4
|
+
# as the first stage of the compile pipeline.
|
|
5
|
+
#
|
|
6
|
+
# This compiler is extremely helpful, typically you would want
|
|
7
|
+
# to use static includes in many many cases.
|
|
8
|
+
|
|
9
|
+
class StaticInclude
|
|
10
|
+
#--
|
|
11
|
+
# FIXME: use template root of the controller.
|
|
12
|
+
#++
|
|
13
|
+
|
|
14
|
+
def self.transform(text)
|
|
15
|
+
# Statically include sub-template files.
|
|
16
|
+
# The target file is included at compile time.
|
|
17
|
+
#
|
|
18
|
+
# gmosx: must be xformed before the <?r pi.
|
|
19
|
+
#
|
|
20
|
+
# Example:
|
|
21
|
+
# <?include href="root/myfile.sx" ?>
|
|
22
|
+
|
|
23
|
+
return text.gsub(/<\?include href=["|'](.*?)["|'](.*)\?>/) do |match|
|
|
24
|
+
unless File.exist?(filename = "#{Template.root}/#$1")
|
|
25
|
+
unless File.exist?(filename = "#{Template.root}/#$1.xinc")
|
|
26
|
+
unless File.exist?(filename = "#{Template.root}/#$1.xhtml")
|
|
27
|
+
raise "Cannot include '#{Template.root}/#$1'"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
itext = File.read(filename)
|
|
32
|
+
itext.gsub!(/<\?xml.*\?>/, '')
|
|
33
|
+
itext.gsub!(/<\/?root(.*?)>/m, ' ');
|
|
34
|
+
itext
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# * George Moschovitis <gm@navel.gr>
|
|
@@ -1,66 +1,136 @@
|
|
|
1
|
-
|
|
1
|
+
require 'rexml/document'
|
|
2
|
+
require 'rexml/streamlistener'
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
# according to some tag attributes. (if, unless, each)
|
|
5
|
-
#--
|
|
6
|
-
# FIXME: nested tags are not handled correctly. We should
|
|
7
|
-
# reimplement this using REXML or something.
|
|
8
|
-
#++
|
|
4
|
+
module Nitro
|
|
9
5
|
|
|
10
|
-
|
|
6
|
+
# A compiler that handles these attribute's tags:
|
|
7
|
+
#
|
|
8
|
+
# <tag if="x">..</tag>
|
|
9
|
+
# <tag unless="x">..</tag>
|
|
10
|
+
# <tag each="x">..</tag>
|
|
11
|
+
# <tag times="x">..</tag>
|
|
12
|
+
#
|
|
13
|
+
# by transforming them like that
|
|
14
|
+
#
|
|
15
|
+
# <div prop1="one" if="@mycond" prop2="two">@mycond is true</div>
|
|
16
|
+
#
|
|
17
|
+
# becomes
|
|
18
|
+
#
|
|
19
|
+
# <?r if @mycond ?>
|
|
20
|
+
# <div prop1="one" prop2="two">@mycond is true</div>
|
|
21
|
+
# <?r end ?>
|
|
22
|
+
#
|
|
23
|
+
#
|
|
24
|
+
# <li times="3">...</li>
|
|
25
|
+
#
|
|
26
|
+
# becomes
|
|
27
|
+
#
|
|
28
|
+
# <?r 3.times do ?>
|
|
29
|
+
# <li>...</li>
|
|
30
|
+
# <?r end ?>
|
|
31
|
+
#
|
|
32
|
+
#
|
|
33
|
+
# <li each="item in array">my item is #{item}</li>
|
|
34
|
+
#
|
|
35
|
+
# becomes
|
|
36
|
+
#
|
|
37
|
+
# <?r for item in array ?>
|
|
38
|
+
# <li>my item is #{item}</li>
|
|
39
|
+
# <?r end ?>
|
|
11
40
|
|
|
12
|
-
|
|
13
|
-
text = input_text.dup
|
|
14
|
-
# <tag if="x">..</tag>
|
|
15
|
-
# <tag unless="x">..</tag>
|
|
16
|
-
#
|
|
17
|
-
# example:
|
|
18
|
-
#
|
|
19
|
-
# <div prop1="one" if="@mycond" prop2="two">@mycond is true</div>
|
|
20
|
-
#
|
|
21
|
-
# becomes
|
|
22
|
-
#
|
|
23
|
-
# <?r if @mycond ?>
|
|
24
|
-
# <div prop1="one" prop2="two">@mycond is true</div>
|
|
25
|
-
# <?r end ?>
|
|
41
|
+
class Morphing
|
|
26
42
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
43
|
+
class Listener # :nodoc: all
|
|
44
|
+
include REXML::StreamListener
|
|
45
|
+
|
|
46
|
+
attr_reader :buffer
|
|
47
|
+
|
|
48
|
+
def initialize
|
|
49
|
+
super
|
|
50
|
+
@buffer = ''
|
|
51
|
+
@stack = []
|
|
52
|
+
@depth = 0
|
|
31
53
|
end
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
54
|
+
|
|
55
|
+
def tag_start(name, attributes)
|
|
56
|
+
if attr = attributes.detect { |k, v| %w{if unless for each times}.include?(k) }
|
|
57
|
+
temp_buffer =
|
|
58
|
+
if attr[0] == 'times'
|
|
59
|
+
"<?r #{attr[1]}.times do ?>"
|
|
60
|
+
elsif (attr[0] == 'for' or attr[0] == 'each') and attr[1] =~ / in /
|
|
61
|
+
"<?r for #{attr[1]} ?>"
|
|
62
|
+
elsif attr[0] == 'if' or attr[0] == 'unless'
|
|
63
|
+
"<?r #{attr[0]} #{attr[1]} ?>"
|
|
64
|
+
else
|
|
65
|
+
Logger.info "Morphing: #{attr[0]} attribute found but was not suitable for morphing" if $DBG
|
|
66
|
+
false
|
|
67
|
+
end
|
|
68
|
+
if temp_buffer
|
|
69
|
+
@buffer << temp_buffer
|
|
70
|
+
attributes.delete(attr[0])
|
|
71
|
+
@stack.push(@depth)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
attrs = attributes.collect do | k, v |
|
|
76
|
+
%|#{k}="#{v}"|
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
attrs = attrs.empty? ? "" : " " + attrs.join(' ')
|
|
80
|
+
@depth += 1
|
|
81
|
+
@buffer << "<#{name}#{attrs}>"
|
|
39
82
|
end
|
|
40
83
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
#
|
|
49
|
-
# <?r for item in array ?>
|
|
50
|
-
# <li>my item is #{item}</li>
|
|
51
|
-
# <?r end ?>
|
|
52
|
-
|
|
53
|
-
text.gsub!(/<(\w*?)([^>]*?)each=["|'](.*?)["|'](.*?)>(.*?)<\/\1>/m) do |match|
|
|
54
|
-
%{<?r for #$3 ?>
|
|
55
|
-
<#$1#$2#$4>#$5</#$1>
|
|
56
|
-
<?r end ?>}
|
|
84
|
+
def tag_end(name)
|
|
85
|
+
@depth -= 1
|
|
86
|
+
@buffer << "</#{name}>"
|
|
87
|
+
if @stack.last == @depth
|
|
88
|
+
@buffer << "\n<?r end ?>"
|
|
89
|
+
@stack.pop
|
|
90
|
+
end
|
|
57
91
|
end
|
|
58
92
|
|
|
59
|
-
text
|
|
93
|
+
def text(str)
|
|
94
|
+
@buffer << str
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def instruction(name, attributes)
|
|
98
|
+
@buffer << "<?#{name}#{attributes}?>"
|
|
99
|
+
end
|
|
60
100
|
end
|
|
101
|
+
|
|
102
|
+
def self.transform(source)
|
|
103
|
+
listener = Listener.new
|
|
104
|
+
REXML::Document.parse_stream(source, listener)
|
|
105
|
+
return listener.buffer
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
end
|
|
61
109
|
|
|
62
110
|
end
|
|
63
111
|
|
|
112
|
+
if __FILE__ == $0
|
|
113
|
+
puts Nitro::Morphing.transform(<<EOF)
|
|
114
|
+
<body>
|
|
115
|
+
<child attr1="attr1" if="test" attr2="attr2">
|
|
116
|
+
<mytag>
|
|
117
|
+
this is texxt
|
|
118
|
+
<one if="@qsdf">
|
|
119
|
+
qqqqq
|
|
120
|
+
</one>
|
|
121
|
+
</mytag>
|
|
122
|
+
</child>
|
|
123
|
+
<child times="4">
|
|
124
|
+
<mytag>
|
|
125
|
+
qqqqq
|
|
126
|
+
</mytag>
|
|
127
|
+
<one if="@qsdf">
|
|
128
|
+
text
|
|
129
|
+
</one>
|
|
130
|
+
</child>
|
|
131
|
+
</body>
|
|
132
|
+
EOF
|
|
64
133
|
end
|
|
65
134
|
|
|
66
135
|
# * George Moschovitis <gm@navel.gr>
|
|
136
|
+
# * Guillaume Pierronnet <guillaume.pierronnet@gmail.com>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
module Nitro
|
|
1
|
+
module Nitro
|
|
2
2
|
|
|
3
3
|
# Compress the inline xhtml. Does not touch the ruby code.
|
|
4
4
|
|
|
@@ -6,7 +6,7 @@ class Squeeze
|
|
|
6
6
|
|
|
7
7
|
# Compresses the inline xhtml. Does not touch the ruby code.
|
|
8
8
|
|
|
9
|
-
def transform(text)
|
|
9
|
+
def self.transform(text)
|
|
10
10
|
return text.gsub(/\@out \<\< \%\^(.*?)\^/m) do |match|
|
|
11
11
|
c = $1.gsub(/^(\s*)/m, '').squeeze(" \t").tr("\n", '').tr("\t", ' ')
|
|
12
12
|
"@out << %{#{c}}"
|
data/lib/nitro/context.rb
CHANGED
|
@@ -80,6 +80,15 @@ class Context
|
|
|
80
80
|
@session || @session = Session.lookup(self)
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
+
# Lookup the controller for this request.
|
|
84
|
+
#--
|
|
85
|
+
# FIXME: improve this! BUGGY
|
|
86
|
+
#++
|
|
87
|
+
|
|
88
|
+
def controller
|
|
89
|
+
@dispatcher.controllers[@base || '/']
|
|
90
|
+
end
|
|
91
|
+
|
|
83
92
|
# Populate an object from request parameters.
|
|
84
93
|
# This is a truly dangerous method.
|
|
85
94
|
#
|
data/lib/nitro/controller.rb
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
require 'mega/annotation'
|
|
2
2
|
|
|
3
3
|
require 'glue/aspects'
|
|
4
|
-
require 'glue/
|
|
4
|
+
require 'glue/markup'
|
|
5
5
|
|
|
6
6
|
require 'nitro'
|
|
7
7
|
require 'nitro/render'
|
|
8
8
|
require 'nitro/scaffold'
|
|
9
9
|
require 'nitro/caching'
|
|
10
10
|
require 'nitro/flash'
|
|
11
|
-
require 'nitro/
|
|
11
|
+
require 'nitro/helper'
|
|
12
12
|
|
|
13
13
|
module Nitro
|
|
14
14
|
|
|
@@ -40,7 +40,7 @@ module Publishable
|
|
|
40
40
|
include Render
|
|
41
41
|
include Glue::Aspects
|
|
42
42
|
include Flashing
|
|
43
|
-
include
|
|
43
|
+
include Helpers
|
|
44
44
|
|
|
45
45
|
# Override this method as needed. Unless overriden
|
|
46
46
|
# this method is initialized by the Dispatcher to
|
|
@@ -136,7 +136,11 @@ class Controller
|
|
|
136
136
|
include Publishable
|
|
137
137
|
include Scaffolding
|
|
138
138
|
include Caching
|
|
139
|
-
include
|
|
139
|
+
include Helpers
|
|
140
|
+
helper Markup
|
|
140
141
|
end
|
|
141
142
|
|
|
142
143
|
end
|
|
144
|
+
|
|
145
|
+
# * George Moschovitis <gm@navel.gr>
|
|
146
|
+
|