ramaze 0.3.0 → 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. data/Rakefile +3 -2
  2. data/bin/ramaze +9 -3
  3. data/doc/AUTHORS +6 -2
  4. data/doc/CHANGELOG +272 -66
  5. data/doc/GPL +12 -13
  6. data/doc/README.html +729 -0
  7. data/doc/changes.txt +5757 -0
  8. data/doc/changes.xml +5759 -0
  9. data/doc/meta/announcement.txt +48 -39
  10. data/doc/tutorial/todolist.mkd +10 -12
  11. data/examples/blog/start.rb +1 -0
  12. data/examples/caching.rb +1 -0
  13. data/examples/element.rb +3 -0
  14. data/examples/hello.rb +1 -3
  15. data/examples/identity.rb +5 -8
  16. data/examples/layout.rb +1 -0
  17. data/examples/linking.rb +1 -0
  18. data/examples/memleak_detector.rb +1 -0
  19. data/examples/nitro_form.rb +1 -0
  20. data/examples/rammit/start.rb +1 -0
  21. data/examples/rapaste/Rakefile +7 -0
  22. data/examples/rapaste/{src/controller.rb → controller/paste.rb} +28 -3
  23. data/examples/rapaste/{src/model.rb → model/paste.rb} +0 -4
  24. data/examples/rapaste/public/css/display.css +17 -0
  25. data/examples/rapaste/spec/rapaste.rb +2 -2
  26. data/examples/rapaste/start.rb +9 -4
  27. data/examples/rapaste/{template → view}/copy.xhtml +0 -0
  28. data/examples/rapaste/{template → view}/index.xhtml +0 -0
  29. data/examples/rapaste/{template → view}/layout.xhtml +3 -0
  30. data/examples/rapaste/{template → view}/list.xhtml +4 -3
  31. data/examples/rapaste/view/search.xhtml +41 -0
  32. data/examples/rapaste/{template → view}/view.xhtml +0 -0
  33. data/examples/simple.rb +3 -3
  34. data/examples/templates/template/external.amrita +9 -9
  35. data/examples/templates/template/external.redcloth +19 -0
  36. data/examples/templates/template_amrita2.rb +40 -7
  37. data/examples/templates/template_erubis.rb +3 -3
  38. data/examples/templates/template_ezamar.rb +1 -3
  39. data/examples/templates/template_haml.rb +1 -3
  40. data/examples/templates/template_liquid.rb +3 -3
  41. data/examples/templates/template_markaby.rb +1 -3
  42. data/examples/templates/template_nagoro.rb +1 -3
  43. data/examples/templates/template_redcloth.rb +59 -0
  44. data/examples/templates/template_remarkably.rb +1 -3
  45. data/examples/templates/template_xslt.rb +1 -4
  46. data/examples/todolist/src/controller/main.rb +1 -1
  47. data/examples/todolist/start.rb +1 -2
  48. data/examples/upload/start.rb +19 -0
  49. data/examples/upload/view/index.xhtml +25 -0
  50. data/examples/whywiki/start.rb +1 -3
  51. data/examples/wikore/start.rb +3 -0
  52. data/examples/wiktacular/mkd/newpagename/current.mkd +1 -0
  53. data/examples/wiktacular/mkd/newpagename/current.mkd.bak +1 -0
  54. data/examples/wiktacular/start.rb +1 -0
  55. data/lib/proto/controller/main.rb +0 -3
  56. data/lib/proto/public/js/jquery.js +106 -93
  57. data/lib/proto/spec/main.rb +2 -5
  58. data/lib/proto/start.rb +0 -3
  59. data/lib/ramaze.rb +3 -2
  60. data/lib/ramaze/action.rb +6 -10
  61. data/lib/ramaze/adapter/lsws.rb +19 -0
  62. data/lib/ramaze/contrib/email.rb +84 -0
  63. data/lib/ramaze/contrib/email.rb-darcs-backup0 +81 -0
  64. data/lib/ramaze/contrib/gettext.rb +1 -0
  65. data/lib/ramaze/contrib/gettext/parser.rb +46 -0
  66. data/lib/ramaze/contrib/route.rb +3 -36
  67. data/lib/ramaze/controller.rb +4 -6
  68. data/lib/ramaze/controller/resolve.rb +28 -1
  69. data/lib/ramaze/dispatcher.rb +1 -1
  70. data/lib/ramaze/dispatcher/file.rb +17 -0
  71. data/lib/ramaze/global/globalstruct.rb +7 -4
  72. data/lib/ramaze/helper/auth.rb +1 -1
  73. data/lib/ramaze/helper/identity.rb +25 -15
  74. data/lib/ramaze/helper/link.rb +29 -8
  75. data/lib/ramaze/helper/maruku.rb +7 -0
  76. data/lib/ramaze/helper/partial.rb +25 -10
  77. data/lib/ramaze/route.rb +56 -0
  78. data/lib/ramaze/snippets/metaid.rb +17 -0
  79. data/lib/ramaze/spec/helper.rb +0 -2
  80. data/lib/ramaze/spec/helper/mock_http.rb +6 -0
  81. data/lib/ramaze/spec/helper/pretty_output.rb +5 -1
  82. data/lib/ramaze/store/default.rb +3 -1
  83. data/lib/ramaze/template.rb +1 -1
  84. data/lib/ramaze/template/amrita2.rb +21 -15
  85. data/lib/ramaze/template/bijou.rb +39 -0
  86. data/lib/ramaze/template/builder.rb +28 -0
  87. data/lib/ramaze/template/redcloth.rb +24 -0
  88. data/lib/ramaze/template/sass.rb +3 -1
  89. data/lib/ramaze/tool/create.rb +2 -1
  90. data/lib/ramaze/tool/localize.rb +6 -1
  91. data/lib/ramaze/trinity/request.rb +8 -0
  92. data/lib/ramaze/trinity/session.rb +8 -5
  93. data/lib/ramaze/version.rb +1 -1
  94. data/rake_tasks/gem.rake +0 -1
  95. data/rake_tasks/maintenance.rake +4 -1
  96. data/spec/contrib/route.rb +14 -63
  97. data/spec/examples/linking.rb +2 -2
  98. data/spec/examples/templates/template_redcloth.rb +28 -0
  99. data/spec/ramaze/controller/subclass.rb +21 -0
  100. data/spec/ramaze/dispatcher/file.rb +31 -0
  101. data/spec/ramaze/helper/link.rb +46 -0
  102. data/spec/ramaze/helper/partial.rb +22 -1
  103. data/spec/ramaze/helper/template/locals.xhtml +1 -0
  104. data/spec/ramaze/helper/template/recursive_locals.xhtml +7 -0
  105. data/spec/ramaze/params.rb +8 -0
  106. data/spec/ramaze/request.rb +14 -0
  107. data/spec/ramaze/route.rb +107 -0
  108. data/spec/ramaze/session.rb +4 -2
  109. data/spec/ramaze/template/amrita2.rb +21 -7
  110. data/spec/ramaze/template/amrita2/external.amrita +6 -0
  111. data/spec/ramaze/template/amrita2/sum.amrita +1 -1
  112. data/spec/ramaze/template/bijou.rb +25 -0
  113. data/spec/ramaze/template/builder.rb +55 -0
  114. data/spec/ramaze/template/builder/external.rxml +3 -0
  115. data/spec/ramaze/template/haml.rb +15 -0
  116. data/spec/ramaze/template/haml/locals.haml +1 -0
  117. data/spec/ramaze/template/redcloth.rb +38 -0
  118. data/spec/ramaze/template/redcloth/external.redcloth +1 -0
  119. metadata +472 -442
  120. data/examples/rapaste/rapaste.sqlite +0 -0
  121. data/spec/ramaze/template/amrita2/data.amrita +0 -6
  122. data/spec/ramaze/template/amrita2/index.amrita +0 -1
@@ -0,0 +1,39 @@
1
+ # Copyright (c) 2008 Michael Fellinger m.fellinger@gmail.com
2
+ # All files in this distribution are subject to the terms of the Ruby license.
3
+
4
+ require 'bijou'
5
+
6
+ module Ramaze
7
+ module Template
8
+
9
+ # Is responsible for compiling a template using the Bijou templating engine.
10
+ # Can be found at: http://bijou.rubyforge.org/
11
+
12
+ class Bijou < Template
13
+
14
+ ENGINES[self] = %w[ bijou rbb ]
15
+
16
+ class << self
17
+
18
+ # Transform via Haml templating engine
19
+
20
+ def transform action
21
+ compiled = wrap_compile(action)
22
+ context = ::Bijou::Context.new(::Bijou::Config.new)
23
+ # context.container_callback = method(:container_callback)
24
+ # context.component_callback = method(:component_callback)
25
+ ::Bijou::Processor.execute(context, compiled, 'GeneralView', {})
26
+ end
27
+
28
+ # Instantiates Haml::Engine with the template and haml_options trait from
29
+ # the controller.
30
+
31
+ def compile(action, template)
32
+ parser = ::Bijou::Parser.new
33
+ parser.parse('GeneralView', template)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+
@@ -0,0 +1,28 @@
1
+ require 'builder'
2
+
3
+ module Ramaze
4
+ module Template
5
+
6
+ class Builder < Template
7
+ ENGINES[self] = %w[ builder rxml ]
8
+
9
+ class << self
10
+ def transform action
11
+ if response = Response.current
12
+ response['Content-Type'] = "application/xml"
13
+ end
14
+
15
+ template = wrap_compile(action)
16
+ action.instance.instance_eval(template, action.template || __FILE__)
17
+ end
18
+
19
+ def compile action, template
20
+ "xml = ::Builder::XmlMarkup.new(:indent => 2)\n" +
21
+ template +
22
+ "\nxml.target!\n"
23
+ end
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ require 'redcloth'
2
+
3
+ module Ramaze
4
+ module Template
5
+
6
+ # Is responsible for compiling a template using the RedCloth templating engine.
7
+ # Can be found at: http://whytheluckystiff.net/ruby/redcloth/
8
+
9
+ class RedCloth < Erubis
10
+ ENGINES[self] = %w[ redcloth ]
11
+
12
+ class << self
13
+ def transform(action)
14
+ restrictions = action.controller.trait[:redcloth_options] || []
15
+ rules = action.controller.trait[:redcloth_options] || []
16
+
17
+ # Erubis -> RedCloth -> HTML
18
+ redcloth = ::RedCloth.new(super, restrictions)
19
+ redcloth.to_html(*rules)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -18,7 +18,9 @@ module Ramaze
18
18
  # Transform via Sass templating engine
19
19
 
20
20
  def transform action
21
- Response.current['Content-Type'] = "text/css"
21
+ if response = Response.current
22
+ response['Content-Type'] = "text/css"
23
+ end
22
24
  sass = wrap_compile(action)
23
25
  sass.to_css()
24
26
  end
@@ -43,7 +43,8 @@ module Ramaze
43
43
  directories, files =
44
44
  Dir[@basedir / '**' / '*'].partition{ |f| File.directory?(f) }
45
45
 
46
- create_dirs(*directories)
46
+ # gem packaging removes empty model directory, so add it in ourselves
47
+ create_dirs(*Array[ @basedir/'model', *directories ].uniq)
47
48
  copy_files(*files)
48
49
 
49
50
  puts "\nStart your new ramaze app: ruby #{project}/start.rb"
@@ -59,7 +59,12 @@ class Ramaze::Tool::Localize
59
59
  # `localize` for more information.
60
60
 
61
61
  def localize_body(body, options)
62
- locale = (session[:LOCALE] || set_session_locale).to_s
62
+ locale =
63
+ if trait[:languages].include?(response["Content-Language"])
64
+ response["Content-Language"]
65
+ else
66
+ (session[:LOCALE] || set_session_locale).to_s
67
+ end
63
68
 
64
69
  body.gsub!(trait[:regex]) do
65
70
  localize($1, locale) unless $1.to_s.empty?
@@ -69,6 +69,14 @@ module Ramaze
69
69
  Array[value, *params.values_at(*keys)]
70
70
  end
71
71
 
72
+ def to_ivs(*args)
73
+ instance = Action.current.instance
74
+ args.each do |arg|
75
+ next unless value = self[arg]
76
+ instance.instance_variable_set("@#{arg}", value)
77
+ end
78
+ end
79
+
72
80
  unless method_defined?(:rack_params)
73
81
  alias rack_params params
74
82
 
@@ -70,6 +70,10 @@ module Ramaze
70
70
 
71
71
  IP_COUNT_LIMIT = 1000 unless defined?(IP_COUNT_LIMIT)
72
72
 
73
+ # Holds the default cookie used for sessions
74
+
75
+ COOKIE = { :path => '/' }
76
+
73
77
  class << self
74
78
 
75
79
  # called from Ramaze::startup and adds Cache.sessions if cookies are
@@ -95,11 +99,10 @@ module Ramaze
95
99
  def initialize request
96
100
  @session_id = (request.cookies[SESSION_KEY] || random_key)
97
101
 
98
- ip = request.remote_addr
99
- IP_COUNT[ip] << @session_id
100
-
101
- if IP_COUNT[ip].size > IP_COUNT_LIMIT
102
- sessions.delete(IP_COUNT[ip].shift)
102
+ unless IP_COUNT.nil?
103
+ ip = request.remote_addr
104
+ IP_COUNT[ip] << @session_id
105
+ sessions.delete(IP_COUNT[ip].shift) if IP_COUNT[ip].size > IP_COUNT_LIMIT
103
106
  end
104
107
 
105
108
  @flash = Ramaze::SessionFlash.new
@@ -5,7 +5,7 @@ module Ramaze #:nodoc:
5
5
  module Version #:nodoc:
6
6
  MAJOR = 0
7
7
  MINOR = 3
8
- TINY = 0
8
+ TINY = 5
9
9
 
10
10
  STRING = [MAJOR, MINOR, TINY].join('.')
11
11
  end
data/rake_tasks/gem.rake CHANGED
@@ -17,7 +17,6 @@ spec =
17
17
  s.post_install_message = POST_INSTALL_MESSAGE
18
18
 
19
19
  s.add_dependency('rake', '>=0.7.3')
20
- s.add_dependency('rspec', '>=1.0.2')
21
20
  s.add_dependency('rack', '>=0.2.0')
22
21
  # s.required_ruby_version = '>= 1.8.5'
23
22
 
@@ -209,11 +209,14 @@ def authors
209
209
  'clive@crous.co.za' => 'Clive Crous',
210
210
  'comp.lang.zenix+ramaze@gmail.com' => 'zenix',
211
211
  'jesuswasramazing.10.pistos@geoshell.com' => 'Pistos',
212
+ 'jesusisramazing.10.pistos@geoshell.com' => 'Pistos',
212
213
  'manveru@weez-int.com' => 'Michael Fellinger',
213
214
  'm.fellinger@gmail.com' => 'Michael Fellinger',
214
- 'outtenr@gmail.com' => 'Carlo Zottmann',
215
+ 'outtenr@gmail.com' => 'Richard Outten',
215
216
  'rff.rff@gmail.com' => 'Gabriele Renzi',
217
+ 'skaar@waste.org' => 'skaar',
216
218
  'stephan@spaceboyz.net' => 'Stephan Maka',
219
+ 'keita.yamaguchi@gmail.com' => 'Keita Yamaguchi',
217
220
  }
218
221
 
219
222
  mapping = {}
@@ -3,20 +3,12 @@ require 'spec/helper'
3
3
  Ramaze.contrib :route
4
4
 
5
5
  class MainController < Ramaze::Controller
6
- def float(flt)
7
- "Float: #{flt}"
6
+ def text(num)
7
+ "text: #{num}"
8
8
  end
9
9
 
10
- def string(str)
11
- "String: #{str}"
12
- end
13
-
14
- def price(p)
15
- "Price: \$#{p}"
16
- end
17
-
18
- def sum(a, b)
19
- a.to_i + b.to_i
10
+ def bar
11
+ "bar"
20
12
  end
21
13
  end
22
14
 
@@ -25,61 +17,20 @@ describe 'Route' do
25
17
  ramaze
26
18
  @route = Ramaze::Contrib::Route
27
19
 
28
- it 'should take custom lambda routers' do
29
- @route['string'] = lambda {|path, req| path if path =~ %r!^/string! }
30
- @route['string'].class.should == Proc
31
-
32
- @route['calc sum'] = lambda do |path, req|
33
- if req[:do_calc]
34
- lval, rval = req[:a, :b]
35
- rval = rval.to_i * 10
36
- "/sum/#{lval}/#{rval}"
37
- end
38
- end
39
- end
40
-
41
- it 'should be possible to define routes' do
42
- @route[%r!^/(\d+\.\d{2})$!] = "/price/%.2f"
43
- @route[%r!^/(\d+\.\d{2})$!].should == "/price/%.2f"
44
-
45
- @route[%r!^/(\d+\.\d+)!] = "/float/%.3f"
46
- @route[%r!^/(\d+\.\d+)!].should == "/float/%.3f"
47
-
48
- @route[%r!^/(\w+)!] = "/string/%s"
49
- @route[%r!^/(\w+)!].should == "/string/%s"
50
- end
51
-
52
- it 'should be used - /float' do
53
- r = get('/123.123')
54
- r.status.should == 200
55
- r.body.should == 'Float: 123.123'
56
- end
57
-
58
- it 'should be used - /string' do
59
- r = get('/foo')
60
- r.status.should == 200
61
- r.body.should == 'String: foo'
62
- end
63
-
64
- it 'should use %.3f' do
65
- r = get('/123.123456')
66
- r.status.should == 200
67
- r.body.should == 'Float: 123.123'
68
- end
69
-
70
- it 'should resolve in the order added' do
71
- r = get('/12.84')
72
- r.status.should == 200
73
- r.body.should == 'Price: $12.84'
20
+ it 'should provide backwards compat wrapper to Ramaze::Route' do
21
+ @route[ %r!^/(\d+)\.te?xt$! ] = "/text/%d"
22
+ @route[ 'foobar' ] = lambda{ |path, request|
23
+ '/bar' if path == '/foo' and request[:bar] == '1'
24
+ }
74
25
  end
75
26
 
76
- it 'should use lambda routers' do
77
- r = get('/string/abc')
27
+ it 'should work' do
28
+ r = get('/123.txt')
78
29
  r.status.should == 200
79
- r.body.should == 'String: abc'
30
+ r.body.should == 'text: 123'
80
31
 
81
- r = get('/', 'do_calc=1&a=2&b=6')
32
+ r = get('/foo', 'bar=1')
82
33
  r.status.should == 200
83
- r.body.should == '62'
34
+ r.body.should == 'bar'
84
35
  end
85
36
  end
@@ -9,10 +9,10 @@ describe 'Linking' do
9
9
  r = get('/').body
10
10
  r.should.include('<a href="/help">Help?</a>')
11
11
  end
12
-
12
+
13
13
  it 'should provide a link to another controller' do
14
14
  r = get('/help').body
15
15
  r.should.include('<a href="/link_to/another">A Different Controller</a>')
16
16
  end
17
17
 
18
- end
18
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec/helper'
2
+
3
+ spec_require 'redcloth'
4
+ require 'examples/templates/template_redcloth'
5
+
6
+ describe 'Template RedCloth' do
7
+ behaves_like 'http'
8
+ ramaze
9
+
10
+ it '/' do
11
+ html = get('/').body.strip
12
+ html.should =~ %r(<a href=\"/\">Home</a>)
13
+ html.should =~ %r(<a href=\"/internal\">internal</a>)
14
+ html.should =~ %r(<a href=\"/external\">external</a>)
15
+ end
16
+
17
+ %w[/internal /external].each do |url|
18
+ it url do
19
+ name = url.gsub(/^\//, '')
20
+ response = get(url)
21
+ response.status.should == 200
22
+ html = response.body
23
+ html.should.not == nil
24
+ html.should =~ %r(<title>Template::RedCloth #{name}</title>)
25
+ html.should =~ %r(<h1>The #{name} Template for RedCloth</h1>)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,21 @@
1
+ # Copyright (c) 2006 Michael Fellinger m.fellinger@gmail.com
2
+ # All files in this distribution are subject to the terms of the Ruby license.
3
+
4
+ require 'spec/helper'
5
+
6
+ class BaseController < Ramaze::Controller
7
+ def test() 'test' end
8
+ end
9
+
10
+ class MainController < BaseController
11
+ engine :None
12
+ end
13
+
14
+ describe 'Controller' do
15
+ behaves_like 'http'
16
+ ramaze
17
+
18
+ it 'should allow sub-classing MainController' do
19
+ get('/test').body.should == 'test'
20
+ end
21
+ end
@@ -21,4 +21,35 @@ describe 'Dispatcher::File' do
21
21
  end
22
22
  get('/favicon.ico').body.should == original
23
23
  end
24
+
25
+ it 'should send ETag' do
26
+ res = get '/test_download.css'
27
+ res.headers['ETag'].should.not.be == nil
28
+ res.headers['ETag'].length.should == 34 # "32 hash"
29
+ end
30
+
31
+ it 'should send Last-Modified' do
32
+ res = get '/test_download.css'
33
+ res.headers['Last-Modified'].should.not.be == nil
34
+ res.headers['Last-Modified'].should == File.stat(@public_root/'test_download.css').mtime.httpdate
35
+ end
36
+
37
+ it 'should respect ETag with IF_NONE_MATCHES' do
38
+ res = get '/test_download.css'
39
+ etag = res.headers['ETag']
40
+ etag.should.not.be == nil
41
+ res = get '/test_download.css', :if_none_match=>etag
42
+ res.status.should == 304
43
+ res.body.should == ''
44
+ end
45
+
46
+ it 'should respect If-Modified' do
47
+ res = get '/test_download.css'
48
+ mtime = res.headers['Last-Modified']
49
+ mtime.should.not.be == nil
50
+ res = get '/test_download.css', :if_modified_since=>mtime
51
+ res.status.should == 304
52
+ res.body.should == ''
53
+ end
54
+
24
55
  end
@@ -6,11 +6,24 @@ require 'ramaze/helper/link'
6
6
 
7
7
  class TCLink < Ramaze::Controller
8
8
  map '/'
9
+ def index; end
9
10
  end
10
11
 
12
+ class TCLink2 < Ramaze::Controller
13
+ map '/2'
14
+ def index; end
15
+ end
16
+
17
+ ramaze
18
+
11
19
  describe "A" do
12
20
  extend Ramaze::LinkHelper
13
21
 
22
+ before do
23
+ # initialize Ramaze::Controller.current for Rs()
24
+ Ramaze::Controller.handle('/')
25
+ end
26
+
14
27
  it 'should build links' do
15
28
  A('title', :href => '/').should == %(<a href="/">title</a>)
16
29
  A('title', :href => '/foo').should == %(<a href="/foo">title</a>)
@@ -21,6 +34,10 @@ describe "A" do
21
34
  a.should =~ /class="none"/
22
35
  a.should =~ /href="\/foo"/
23
36
  end
37
+
38
+ it 'should build position independend links' do
39
+ A(TCLink, :foo).should == %(<a href="/foo">foo</a>)
40
+ end
24
41
  end
25
42
 
26
43
  describe 'R' do
@@ -34,6 +51,24 @@ describe 'R' do
34
51
  end
35
52
  end
36
53
 
54
+ describe 'Rs' do
55
+ extend Ramaze::LinkHelper
56
+
57
+ it 'should build links for current controller' do
58
+ Ramaze::Controller.handle('/2')
59
+ Rs(:index).should == '/2/index'
60
+
61
+ Ramaze::Controller.handle('/')
62
+ Rs(:index).should == '/index'
63
+ end
64
+
65
+ it 'should treat Rs() like R() when Controller given' do
66
+ Ramaze::Controller.handle('/2')
67
+ Rs(TCLink, :index).should == '/2/index'
68
+ end
69
+
70
+ end
71
+
37
72
  describe 'breadcrumbs' do
38
73
  extend Ramaze::LinkHelper
39
74
 
@@ -47,4 +82,15 @@ describe 'breadcrumbs' do
47
82
  "<a href=\"/file/dir/listing/is/cool\">cool</a>"
48
83
  ].join('/')
49
84
  end
85
+
86
+ it 'should lay out breadcrumbs with href prefix' do
87
+ breadcrumbs('/file/dir/listing/is/cool', '/', '/', '/prefix/path').
88
+ should == [
89
+ "<a href=\"/prefix/path/file\">file</a>",
90
+ "<a href=\"/prefix/path/file/dir\">dir</a>",
91
+ "<a href=\"/prefix/path/file/dir/listing\">listing</a>",
92
+ "<a href=\"/prefix/path/file/dir/listing/is\">is</a>",
93
+ "<a href=\"/prefix/path/file/dir/listing/is/cool\">cool</a>"
94
+ ].join('/')
95
+ end
50
96
  end