nitro 0.28.0 → 0.29.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/CHANGELOG +382 -0
  2. data/ProjectInfo +4 -4
  3. data/README +1 -1
  4. data/doc/AUTHORS +15 -15
  5. data/doc/MIGRATION +13 -0
  6. data/doc/RELEASES +102 -0
  7. data/lib/glue/sweeper.rb +1 -1
  8. data/lib/nitro.rb +38 -9
  9. data/lib/nitro/adapter/acgi.rb +1 -3
  10. data/lib/nitro/adapter/cgi.rb +1 -1
  11. data/lib/nitro/adapter/fastcgi.rb +1 -3
  12. data/lib/nitro/adapter/mongrel.rb +8 -6
  13. data/lib/nitro/adapter/webrick.rb +1 -2
  14. data/lib/nitro/cgi.rb +1 -1
  15. data/lib/nitro/compiler.rb +21 -40
  16. data/lib/nitro/compiler/elements.rb +72 -32
  17. data/lib/nitro/compiler/errors.rb +92 -42
  18. data/lib/nitro/compiler/include.rb +47 -17
  19. data/lib/nitro/compiler/morphing.rb +1 -3
  20. data/lib/nitro/compiler/script.rb +2 -2
  21. data/lib/nitro/context.rb +36 -0
  22. data/lib/nitro/controller.rb +140 -31
  23. data/lib/nitro/dispatcher.rb +27 -28
  24. data/lib/nitro/element.rb +52 -15
  25. data/lib/nitro/flash.rb +44 -0
  26. data/lib/nitro/helper/buffer.rb +0 -2
  27. data/lib/nitro/helper/form.rb +2 -2
  28. data/lib/nitro/helper/form/controls.rb +14 -3
  29. data/lib/nitro/helper/pager.rb +1 -1
  30. data/lib/nitro/helper/table.rb +4 -3
  31. data/lib/nitro/helper/xml.rb +1 -1
  32. data/lib/nitro/part.rb +20 -0
  33. data/lib/nitro/render.rb +44 -5
  34. data/lib/nitro/router.rb +81 -0
  35. data/lib/nitro/scaffolding.rb +24 -23
  36. data/lib/nitro/server.rb +12 -1
  37. data/lib/nitro/server/runner.rb +12 -0
  38. data/lib/nitro/session.rb +3 -12
  39. data/lib/nitro/session/drb.rb +2 -5
  40. data/lib/nitro/session/file.rb +2 -2
  41. data/lib/nitro/session/memcached.rb +14 -0
  42. data/lib/nitro/session/memory.rb +3 -26
  43. data/lib/nitro/session/og.rb +1 -1
  44. data/lib/nitro/test/assertions.rb +1 -1
  45. data/lib/nitro/test/context.rb +8 -2
  46. data/lib/nitro/test/testcase.rb +16 -7
  47. data/proto/public/error.xhtml +58 -21
  48. data/proto/public/js/controls.js +60 -15
  49. data/proto/public/js/dragdrop.js +105 -16
  50. data/proto/public/js/effects.js +19 -12
  51. data/proto/public/js/scriptaculous.js +1 -1
  52. data/proto/public/js/slider.js +2 -2
  53. data/proto/public/js/unittest.js +29 -20
  54. data/proto/public/scaffold/edit.xhtml +1 -1
  55. data/proto/public/scaffold/index.xhtml +2 -2
  56. data/proto/public/scaffold/list.xhtml +2 -2
  57. data/proto/public/scaffold/new.xhtml +1 -1
  58. data/proto/public/scaffold/search.xhtml +1 -1
  59. data/src/part/admin/controller.rb +5 -5
  60. data/src/part/admin/template/index.xhtml +2 -2
  61. data/test/nitro/compiler/tc_compiler.rb +23 -0
  62. data/test/nitro/helper/tc_table.rb +35 -0
  63. data/test/nitro/tc_cgi.rb +1 -1
  64. data/test/nitro/tc_controller.rb +3 -3
  65. data/test/nitro/tc_controller_aspect.rb +2 -0
  66. data/test/nitro/tc_dispatcher.rb +10 -1
  67. data/test/nitro/tc_flash.rb +14 -0
  68. data/test/nitro/tc_router.rb +58 -0
  69. data/test/nitro/tc_session.rb +26 -9
  70. metadata +13 -12
  71. data/lib/nitro/routing.rb +0 -41
  72. data/test/nitro/caching/tc_stores.rb +0 -17
  73. data/test/nitro/tc_table.rb +0 -66
@@ -13,7 +13,7 @@
13
13
  </tr>
14
14
  <?r for c in @classes ?>
15
15
  <tr>
16
- <td><a href="#@base/#{c.name.plural.underscore}/list">#{c.name}</a></td>
16
+ <td><a href="#@base/#{Scaffolding.class_to_path(c).plural}/list">#{c.name}</a></td>
17
17
  <td>#{c.count}</td>
18
18
  <td><a href="delete_all/#{c.name}" onclick="return confirm('Delete all instances?')">delete</a></td>
19
19
  <td><a href="destroy/#{c.name}" onclick="return confirm('Drop the schema?')">destroy</a></td>
@@ -31,7 +31,7 @@
31
31
  <th>Type</th>
32
32
  <th>Description</th>
33
33
  </tr>
34
- <?r for s in Configuration.settings.sort ?>
34
+ <?r for s in @settings ?>
35
35
  <tr>
36
36
  <td>#{s.owner}.<strong>#{s.name}</strong></td>
37
37
  <td>#{s.value.inspect}</td>
@@ -0,0 +1,23 @@
1
+ require 'test/unit'
2
+
3
+ require 'nitro/test/testcase'
4
+
5
+ require 'nitro'
6
+ require 'nitro/test/context'
7
+
8
+ class TC_Compiler < Test::Unit::TestCase
9
+ class TestController < Nitro::Controller
10
+ def test(arg1, arg2); end
11
+ end
12
+
13
+ def setup
14
+ Server.map['/'] = TestController
15
+ reset_context()
16
+ end
17
+
18
+ def test_no_param_specified
19
+ assert_nothing_raised do
20
+ process(:uri => '/test//two')
21
+ end
22
+ end
23
+ end
@@ -28,4 +28,39 @@ class TC_TableHelper < Test::Unit::TestCase # :nodoc: all
28
28
  res = %|<table id="test"><tr><th>Name</th><th>Password</th><th>Email</th></tr><tr><td>gmosx</td><td>huh?</td><td>gm@nowhere.com</td></tr><tr><td>renos</td><td>nah</td><td>renos@nowhere.com</td></tr><tr><td>stella</td><td>hoh</td><td>stella@love.com</td></tr></table>|
29
29
  assert_equal res, table
30
30
  end
31
+
32
+ def test_table_footer
33
+ values = @users.collect { |u| [u.name, u.password, u.email] }
34
+ footers = ["#{@users.size}users", '', '']
35
+
36
+ table = build_table(:id => 'test2', :headers => @headers, :values => values, :footers => footers)
37
+
38
+ res = %|<table id="test2"><thead><tr><th>Name</th><th>Password</th><th>Email</th></tr></thead><tfoot><tr><td>3users</td><td></td><td></td></tr></tfoot><tr><td>gmosx</td><td>huh?</td><td>gm@nowhere.com</td></tr><tr><td>renos</td><td>nah</td><td>renos@nowhere.com</td></tr><tr><td>stella</td><td>hoh</td><td>stella@love.com</td></tr></table>|
39
+ assert_equal res, table
40
+ end
41
+
42
+ def test_table_tbody
43
+ values = []
44
+ values << @users[0...1].collect { |u| [u.name, u.password, u.email] }
45
+ values << @users[1..2].collect { |u| [u.name, u.password, u.email] }
46
+ table = build_table(:id => 'test', :headers => @headers, :values => values)
47
+ res = %|<table id="test"><thead><tr><th>Name</th><th>Password</th><th>Email</th></tr></thead><tbody><tr><td>gmosx</td><td>huh?</td><td>gm@nowhere.com</td></tr></tbody><tbody><tr><td>renos</td><td>nah</td><td>renos@nowhere.com</td></tr><tr><td>stella</td><td>hoh</td><td>stella@love.com</td></tr></tbody></table>|
48
+ assert_equal res, table
49
+ end
50
+
51
+ def test_table_alternating_rows
52
+ values = @users.collect { |u| [u.name, u.password, u.email] }
53
+ table = build_table(:id => 'test', :headers => @headers, :values => values, :alternating_rows => true)
54
+ res = %|<table id="test"><tr><th>Name</th><th>Password</th><th>Email</th></tr><tr class="row_odd"><td>gmosx</td><td>huh?</td><td>gm@nowhere.com</td></tr><tr class="row_even"><td>renos</td><td>nah</td><td>renos@nowhere.com</td></tr><tr class="row_odd"><td>stella</td><td>hoh</td><td>stella@love.com</td></tr></table>|
55
+ assert_equal res, table
56
+ end
57
+
58
+ def test_table_alternating_rows_tbody
59
+ values = []
60
+ values << @users[0...1].collect { |u| [u.name, u.password, u.email] }
61
+ values << @users[1..2].collect { |u| [u.name, u.password, u.email] }
62
+ table = build_table(:id => 'test', :headers => @headers, :values => values, :alternating_rows => true)
63
+ res = %|<table id="test"><thead><tr><th>Name</th><th>Password</th><th>Email</th></tr></thead><tbody><tr class="row_odd"><td>gmosx</td><td>huh?</td><td>gm@nowhere.com</td></tr></tbody><tbody><tr class="row_even"><td>renos</td><td>nah</td><td>renos@nowhere.com</td></tr><tr class="row_odd"><td>stella</td><td>hoh</td><td>stella@love.com</td></tr></tbody></table>|
64
+ assert_equal res, table
65
+ end
31
66
  end
@@ -148,7 +148,7 @@ class TestAdapterCgi < Test::Unit::TestCase # :nodoc: all
148
148
  start = Time.now
149
149
  params = Nitro::Cgi.parse_multipart(make_context(input), boundary)
150
150
  duration = Time.now - start
151
- puts "\nparse_multipart took: #{duration} seconds\n"
151
+ # puts "\nparse_multipart took: #{duration} seconds\n"
152
152
  assert_equal(fake_file_a,params['test_file_a'].to_s)
153
153
  assert_equal(fake_file_b.read,params['test_file_b'].to_s)
154
154
  assert_equal(String.new,params['empty_field'].to_s)
@@ -15,8 +15,8 @@ class TC_Controller < Test::Unit::TestCase # :nodoc: all
15
15
  class Blog2Controller < Controller
16
16
  attr_reader :aqflag, :tflag
17
17
 
18
- def self.template_root
19
- File.expand_path(Nitro::LibPath + '../../test/public/blog')
18
+ def self.setup_template_root(path)
19
+ @template_root << File.expand_path(File.join(Nitro::LibPath, "../test/public/#{path}"))
20
20
  end
21
21
 
22
22
  def list
@@ -40,7 +40,7 @@ class TC_Controller < Test::Unit::TestCase # :nodoc: all
40
40
  ctx.instance_eval '@session = {}'
41
41
  klass, action = ctx.dispatcher.dispatch(ctx.path, ctx)
42
42
  c = klass.new(ctx)
43
-
43
+
44
44
  begin
45
45
  c.send(action)
46
46
  rescue RenderExit
@@ -10,6 +10,8 @@ class TestControllerAspect < Test::Unit::TestCase # :nodoc: all
10
10
  class TestController < Controller
11
11
  attr_reader :aflag, :tflag
12
12
 
13
+ @template_root = []
14
+
13
15
  post "@aflag = 25", :on => :list
14
16
 
15
17
  def list
@@ -7,7 +7,7 @@ require 'nitro'
7
7
  class TC_Dispatcher < Test::Unit::TestCase # :nodoc: all
8
8
  include Nitro
9
9
 
10
- Template.root = File.expand_path(File.join('..', 'public'))
10
+ Glue::Template.root = File.expand_path(File.join('..', 'public'))
11
11
 
12
12
  class MyContext
13
13
  attr_accessor :headers
@@ -21,6 +21,11 @@ class TC_Dispatcher < Test::Unit::TestCase # :nodoc: all
21
21
  end
22
22
 
23
23
  class BlogController < Controller
24
+ def list
25
+ end
26
+
27
+ def another__very_litle
28
+ end
24
29
  end
25
30
 
26
31
  def setup
@@ -56,6 +61,10 @@ class TC_Dispatcher < Test::Unit::TestCase # :nodoc: all
56
61
  klass, action = @d.dispatch('/blog/another/very_litle', @ctx)
57
62
  assert_equal BlogController, klass
58
63
  assert_equal 'another__very_litle_action', action
64
+
65
+ assert_raises(NoActionError) do
66
+ @d.dispatch('a/nonexistent/action')
67
+ end
59
68
  =begin
60
69
  klass, action = @d.dispatch('/another/litle/list', @ctx)
61
70
  assert_equal MainController, klass
@@ -42,4 +42,18 @@ class TC_Flash < Test::Unit::TestCase # :nodoc: all
42
42
  c.action2
43
43
  assert_equal 'Hello world!', c.flag
44
44
  end
45
+
46
+ def test_push
47
+ f = Flashing::Flash.new
48
+ f.push :errors, 'Error 1'
49
+ f.push :errors, 'Error 2'
50
+
51
+ assert_equal 2, f[:errors].size
52
+
53
+ f.push :errors, [1, 2, 3]
54
+
55
+ assert_equal 5, f[:errors].size
56
+
57
+ assert_equal 3, f[:errors].pop
58
+ end
45
59
  end
@@ -0,0 +1,58 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), '..', '..', 'lib')
2
+
3
+ require 'test/unit'
4
+ require 'ostruct'
5
+
6
+ require 'glue/configuration'
7
+ require 'nitro/router'
8
+
9
+ class IdController; end
10
+ class AdminController; end
11
+
12
+ class AbstractRouter
13
+ include Nitro::Router
14
+ end
15
+
16
+ class TC_Router < Test::Unit::TestCase # :nodoc: all
17
+ include Nitro
18
+
19
+ def setup
20
+ @r = AbstractRouter.new
21
+
22
+ @r.add_route(%r{rewritten/url/(.*)}, :controller => IdController, :action => :register, :param => :name)
23
+ @r.add_route(%r{another/zelo/(.*)/(.*)}, :controller => AdminController, :action => :kick, :params => [:name, :age])
24
+ @r.add_route(%r{cool/(.*)_(.*).html}, :controller => AdminController, :action => :long, :params => [:name, :age])
25
+ end
26
+
27
+ def teardown
28
+ @r = nil
29
+ end
30
+
31
+ def test_decode
32
+ c, a, params = @r.decode_route('rewritten/url/gmosx')
33
+ assert_equal IdController, c
34
+ assert_equal :register, a
35
+ assert_equal 'gmosx', params['name']
36
+
37
+ c, a, params = @r.decode_route('another/zelo/gmosx/32')
38
+ assert_equal AdminController, c
39
+ assert_equal :kick, a
40
+ assert_equal 'gmosx', params['name']
41
+ assert_equal '32', params['age']
42
+
43
+ c, a, params = @r.decode_route('cool/gmosx_32.html')
44
+ assert_equal AdminController, c
45
+ assert_equal :long, a
46
+ assert_equal 'gmosx', params['name']
47
+ assert_equal '32', params['age']
48
+
49
+ assert_equal false, @r.decode_route('this/doesnt/decode')
50
+ end
51
+
52
+ def test_encode
53
+ assert_equal 'rewritten/url/gmosx', @r.encode_route(IdController, :register, :name, 'gmosx')
54
+ assert_equal 'cool/gmosx_32.html', @r.encode_route(AdminController, :long, :name, 'gmosx', :age, 32)
55
+ assert_equal false, @r.encode_route(AdminController, :invalid, :gender, 'male')
56
+ end
57
+
58
+ end
@@ -6,13 +6,21 @@ require 'ostruct'
6
6
  require 'test/unit'
7
7
 
8
8
  require 'glue'
9
+ require 'glue/cache/file'
9
10
  require 'nitro/session'
10
11
 
11
12
  class TC_Session < Test::Unit::TestCase # :nodoc: all
12
13
  include Nitro
13
14
 
14
- # CACHES = [:memory, :file, :og]
15
- CACHES = [:memory, :file]
15
+ CACHES = [:memory, :file, :og]
16
+ begin
17
+ require 'glue/cache/memcached'
18
+ Glue::MemCached.new
19
+ CACHES << :memcached
20
+ rescue Object # Errno::EBADF => e
21
+ Logger.warn "skipping memcached test: server not running"
22
+ end
23
+
16
24
 
17
25
  def test_create_id
18
26
  sid = Session.new.session_id
@@ -27,22 +35,31 @@ class TC_Session < Test::Unit::TestCase # :nodoc: all
27
35
  CACHES.each do |cache_type|
28
36
  Session.keepalive = 2
29
37
 
38
+ if :file == cache_type
39
+ path = File.join(File.dirname(__FILE__), '..', 'cache')
40
+ Glue::FileCache.basedir = path
41
+ FileUtils.rm_r path if File.exists? path
42
+ end
43
+
30
44
  Session.cache_type = cache_type
31
45
 
32
46
  if cache_type == :og
33
- Og.setup(:store => :sqlite, :name => "/tmp/test", :destroy => true)
47
+ Og.setup(:store => :sqlite, :destroy => true)
48
+ end
49
+
50
+ sessions = (1..2).collect do
51
+ s = Session.new
52
+ s.sync
53
+ s
34
54
  end
35
-
36
- Session.cache[1] = Session.new
37
- Session.cache[2] = Session.new
38
55
 
39
56
  Session.cache.gc!
40
- assert_equal 2, Session.cache.all.size
57
+ sessions.each { |s| assert_not_nil(Session.cache[s.session_id]) }
41
58
  Session.cache.gc!
42
- assert_equal 2, Session.cache.all.size
59
+ sessions.each { |s| assert_not_nil(Session.cache[s.session_id]) }
43
60
  sleep(3)
44
61
  Session.cache.gc!
45
- assert_equal 0, Session.cache.all.size
62
+ sessions.each { |s| assert_nil(Session.cache[s.session_id]) }
46
63
  end
47
64
  end
48
65
 
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: nitro
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.28.0
7
- date: 2006-02-06 00:00:00 +02:00
6
+ version: 0.29.0
7
+ date: 2006-03-07 00:00:00 +02:00
8
8
  summary: Everything you need to create Web 2.0 applications with Ruby and Javascript
9
9
  require_paths:
10
10
  - lib
@@ -15,7 +15,7 @@ description:
15
15
  autorequire:
16
16
  default_executable:
17
17
  bindir: bin
18
- has_rdoc: false
18
+ has_rdoc: true
19
19
  required_ruby_version: !ruby/object:Gem::Version::Requirement
20
20
  requirements:
21
21
  - - ">"
@@ -61,6 +61,7 @@ files:
61
61
  - lib/nitro/caching
62
62
  - lib/nitro/cgi
63
63
  - lib/nitro/compiler
64
+ - lib/nitro/part.rb
64
65
  - lib/nitro/element
65
66
  - lib/nitro/server
66
67
  - lib/nitro/service
@@ -71,7 +72,6 @@ files:
71
72
  - lib/nitro/service.rb
72
73
  - lib/nitro/server.rb
73
74
  - lib/nitro/compiler.rb
74
- - lib/nitro/routing.rb
75
75
  - lib/nitro/render.rb
76
76
  - lib/nitro/flash.rb
77
77
  - lib/nitro/element.rb
@@ -80,15 +80,16 @@ files:
80
80
  - lib/nitro/controller.rb
81
81
  - lib/nitro/helper
82
82
  - lib/nitro/cgi.rb
83
+ - lib/nitro/router.rb
83
84
  - lib/nitro/caching.rb
84
85
  - lib/nitro/helper.rb
85
86
  - lib/nitro/scaffolding.rb
86
87
  - lib/nitro/adapter/scgi.rb
87
- - lib/nitro/adapter/mongrel.rb
88
88
  - lib/nitro/adapter/acgi.rb
89
89
  - lib/nitro/adapter/webrick.rb
90
90
  - lib/nitro/adapter/fastcgi.rb
91
91
  - lib/nitro/adapter/cgi.rb
92
+ - lib/nitro/adapter/mongrel.rb
92
93
  - lib/nitro/caching/fragments.rb
93
94
  - lib/nitro/caching/output.rb
94
95
  - lib/nitro/caching/actions.rb
@@ -114,6 +115,7 @@ files:
114
115
  - lib/nitro/server/runner.rb
115
116
  - lib/nitro/server/drb.rb
116
117
  - lib/nitro/service/xmlrpc.rb
118
+ - lib/nitro/session/memcached.rb
117
119
  - lib/nitro/session/memory.rb
118
120
  - lib/nitro/session/file.rb
119
121
  - lib/nitro/session/drb.rb
@@ -172,17 +174,16 @@ files:
172
174
  - test/nitro/tc_context.rb
173
175
  - test/nitro/tc_cgi.rb
174
176
  - test/nitro/tc_caching.rb
175
- - test/nitro/caching
176
- - test/nitro/tc_table.rb
177
+ - test/nitro/tc_router.rb
177
178
  - test/nitro/adapter/tc_webrick.rb
178
179
  - test/nitro/adapter/raw_post1.bin
179
180
  - test/nitro/cgi/tc_request.rb
180
181
  - test/nitro/cgi/tc_cookie.rb
181
- - test/nitro/helper/tc_xhtml.rb
182
+ - test/nitro/compiler/tc_compiler.rb
182
183
  - test/nitro/helper/tc_table.rb
184
+ - test/nitro/helper/tc_xhtml.rb
183
185
  - test/nitro/helper/tc_rss.rb
184
186
  - test/nitro/helper/tc_pager.rb
185
- - test/nitro/caching/tc_stores.rb
186
187
  - test/public/blog
187
188
  - test/public/dummy_mailer
188
189
  - test/public/blog/another
@@ -252,7 +253,7 @@ dependencies:
252
253
  requirements:
253
254
  - - "="
254
255
  - !ruby/object:Gem::Version
255
- version: 0.28.0
256
+ version: 0.29.0
256
257
  version:
257
258
  - !ruby/object:Gem::Dependency
258
259
  name: gen
@@ -261,7 +262,7 @@ dependencies:
261
262
  requirements:
262
263
  - - "="
263
264
  - !ruby/object:Gem::Version
264
- version: 0.28.0
265
+ version: 0.29.0
265
266
  version:
266
267
  - !ruby/object:Gem::Dependency
267
268
  name: glue
@@ -270,7 +271,7 @@ dependencies:
270
271
  requirements:
271
272
  - - "="
272
273
  - !ruby/object:Gem::Version
273
- version: 0.28.0
274
+ version: 0.29.0
274
275
  version:
275
276
  - !ruby/object:Gem::Dependency
276
277
  name: RedCloth
@@ -1,41 +0,0 @@
1
- module Nitro
2
-
3
- # Router mixin. Typically used to generate 'nice' urls.
4
- # Nice urls are considered (?) more Search Engine
5
- # friendly.
6
- #
7
- # However, due to the power of Nitro'w intelligent dispatching
8
- # mechanism, routing is almost never used!
9
-
10
- module Router
11
-
12
- # The route table maps 'nice URLs' to real URLs that
13
- # can be handled by the Dispatcher.
14
-
15
- attr_accessor :routes
16
-
17
- # Strip the beginning of the path, used by cgi adapter.
18
-
19
- setting :strip_path, :default => nil, :doc => 'Strip the beginning of the path, used by cgi adapter'
20
-
21
- # Apply routing rules to the path.
22
-
23
- def route(path, context)
24
- for rule, real_path, *params in @routes
25
- if md = path.match(rule)
26
- params.each_with_index do |p, idx|
27
- context[p] = md.captures[idx]
28
- end
29
- return real_path
30
- end
31
- end
32
-
33
- path.sub!(Router.strip_path, '') if Router.strip_path
34
- return path
35
- end
36
-
37
- end
38
-
39
- end
40
-
41
- # * George Moschovitis <gm@navel.gr>
@@ -1,17 +0,0 @@
1
- $:.unshift File.join(File.dirname(__FILE__), '..', '..', '..', '..', 'lib')
2
-
3
- require 'test/unit'
4
-
5
- require 'nitro/caching/stores'
6
-
7
- class TC_CachingStores < Test::Unit::TestCase # :nodoc: all
8
-
9
- def test_memory
10
- s = Nitro::Caching::MemoryStore.new
11
- s.write('test', 'hello', { :none => 1})
12
- s.read('test')
13
- end
14
-
15
- end
16
-
17
- # * George Moschovitis <gm@navel.gr>