halorgium-actionpack 3.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (154) hide show
  1. data/CHANGELOG +5179 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +409 -0
  4. data/lib/abstract_controller.rb +16 -0
  5. data/lib/abstract_controller/base.rb +158 -0
  6. data/lib/abstract_controller/callbacks.rb +113 -0
  7. data/lib/abstract_controller/exceptions.rb +12 -0
  8. data/lib/abstract_controller/helpers.rb +151 -0
  9. data/lib/abstract_controller/layouts.rb +250 -0
  10. data/lib/abstract_controller/localized_cache.rb +49 -0
  11. data/lib/abstract_controller/logger.rb +61 -0
  12. data/lib/abstract_controller/rendering_controller.rb +188 -0
  13. data/lib/action_controller.rb +72 -0
  14. data/lib/action_controller/base.rb +168 -0
  15. data/lib/action_controller/caching.rb +80 -0
  16. data/lib/action_controller/caching/actions.rb +163 -0
  17. data/lib/action_controller/caching/fragments.rb +116 -0
  18. data/lib/action_controller/caching/pages.rb +154 -0
  19. data/lib/action_controller/caching/sweeping.rb +97 -0
  20. data/lib/action_controller/deprecated.rb +4 -0
  21. data/lib/action_controller/deprecated/integration_test.rb +2 -0
  22. data/lib/action_controller/deprecated/performance_test.rb +1 -0
  23. data/lib/action_controller/dispatch/dispatcher.rb +57 -0
  24. data/lib/action_controller/metal.rb +129 -0
  25. data/lib/action_controller/metal/benchmarking.rb +73 -0
  26. data/lib/action_controller/metal/compatibility.rb +145 -0
  27. data/lib/action_controller/metal/conditional_get.rb +86 -0
  28. data/lib/action_controller/metal/configuration.rb +28 -0
  29. data/lib/action_controller/metal/cookies.rb +105 -0
  30. data/lib/action_controller/metal/exceptions.rb +55 -0
  31. data/lib/action_controller/metal/filter_parameter_logging.rb +77 -0
  32. data/lib/action_controller/metal/flash.rb +162 -0
  33. data/lib/action_controller/metal/head.rb +27 -0
  34. data/lib/action_controller/metal/helpers.rb +115 -0
  35. data/lib/action_controller/metal/hide_actions.rb +47 -0
  36. data/lib/action_controller/metal/http_authentication.rb +312 -0
  37. data/lib/action_controller/metal/layouts.rb +171 -0
  38. data/lib/action_controller/metal/mime_responds.rb +317 -0
  39. data/lib/action_controller/metal/rack_convenience.rb +27 -0
  40. data/lib/action_controller/metal/redirector.rb +22 -0
  41. data/lib/action_controller/metal/render_options.rb +103 -0
  42. data/lib/action_controller/metal/rendering_controller.rb +57 -0
  43. data/lib/action_controller/metal/request_forgery_protection.rb +108 -0
  44. data/lib/action_controller/metal/rescuable.rb +13 -0
  45. data/lib/action_controller/metal/responder.rb +200 -0
  46. data/lib/action_controller/metal/session.rb +15 -0
  47. data/lib/action_controller/metal/session_management.rb +45 -0
  48. data/lib/action_controller/metal/streaming.rb +188 -0
  49. data/lib/action_controller/metal/testing.rb +39 -0
  50. data/lib/action_controller/metal/url_for.rb +41 -0
  51. data/lib/action_controller/metal/verification.rb +130 -0
  52. data/lib/action_controller/middleware.rb +38 -0
  53. data/lib/action_controller/notifications.rb +10 -0
  54. data/lib/action_controller/polymorphic_routes.rb +183 -0
  55. data/lib/action_controller/record_identifier.rb +91 -0
  56. data/lib/action_controller/testing/process.rb +111 -0
  57. data/lib/action_controller/testing/test_case.rb +345 -0
  58. data/lib/action_controller/translation.rb +13 -0
  59. data/lib/action_controller/url_rewriter.rb +204 -0
  60. data/lib/action_controller/vendor/html-scanner.rb +16 -0
  61. data/lib/action_controller/vendor/html-scanner/html/document.rb +68 -0
  62. data/lib/action_controller/vendor/html-scanner/html/node.rb +537 -0
  63. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +176 -0
  64. data/lib/action_controller/vendor/html-scanner/html/selector.rb +828 -0
  65. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +105 -0
  66. data/lib/action_controller/vendor/html-scanner/html/version.rb +11 -0
  67. data/lib/action_dispatch.rb +70 -0
  68. data/lib/action_dispatch/http/headers.rb +33 -0
  69. data/lib/action_dispatch/http/mime_type.rb +231 -0
  70. data/lib/action_dispatch/http/mime_types.rb +23 -0
  71. data/lib/action_dispatch/http/request.rb +539 -0
  72. data/lib/action_dispatch/http/response.rb +290 -0
  73. data/lib/action_dispatch/http/status_codes.rb +42 -0
  74. data/lib/action_dispatch/http/utils.rb +20 -0
  75. data/lib/action_dispatch/middleware/callbacks.rb +50 -0
  76. data/lib/action_dispatch/middleware/params_parser.rb +79 -0
  77. data/lib/action_dispatch/middleware/rescue.rb +26 -0
  78. data/lib/action_dispatch/middleware/session/abstract_store.rb +208 -0
  79. data/lib/action_dispatch/middleware/session/cookie_store.rb +235 -0
  80. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +47 -0
  81. data/lib/action_dispatch/middleware/show_exceptions.rb +143 -0
  82. data/lib/action_dispatch/middleware/stack.rb +116 -0
  83. data/lib/action_dispatch/middleware/static.rb +44 -0
  84. data/lib/action_dispatch/middleware/string_coercion.rb +29 -0
  85. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +24 -0
  86. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +26 -0
  87. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +10 -0
  88. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +29 -0
  89. data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +2 -0
  90. data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +10 -0
  91. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +21 -0
  92. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +2 -0
  93. data/lib/action_dispatch/routing.rb +381 -0
  94. data/lib/action_dispatch/routing/deprecated_mapper.rb +878 -0
  95. data/lib/action_dispatch/routing/mapper.rb +327 -0
  96. data/lib/action_dispatch/routing/route.rb +49 -0
  97. data/lib/action_dispatch/routing/route_set.rb +497 -0
  98. data/lib/action_dispatch/testing/assertions.rb +8 -0
  99. data/lib/action_dispatch/testing/assertions/dom.rb +35 -0
  100. data/lib/action_dispatch/testing/assertions/model.rb +19 -0
  101. data/lib/action_dispatch/testing/assertions/response.rb +145 -0
  102. data/lib/action_dispatch/testing/assertions/routing.rb +144 -0
  103. data/lib/action_dispatch/testing/assertions/selector.rb +639 -0
  104. data/lib/action_dispatch/testing/assertions/tag.rb +123 -0
  105. data/lib/action_dispatch/testing/integration.rb +504 -0
  106. data/lib/action_dispatch/testing/performance_test.rb +15 -0
  107. data/lib/action_dispatch/testing/test_request.rb +83 -0
  108. data/lib/action_dispatch/testing/test_response.rb +131 -0
  109. data/lib/action_pack.rb +24 -0
  110. data/lib/action_pack/version.rb +9 -0
  111. data/lib/action_view.rb +58 -0
  112. data/lib/action_view/base.rb +308 -0
  113. data/lib/action_view/context.rb +44 -0
  114. data/lib/action_view/erb/util.rb +48 -0
  115. data/lib/action_view/helpers.rb +62 -0
  116. data/lib/action_view/helpers/active_model_helper.rb +306 -0
  117. data/lib/action_view/helpers/ajax_helper.rb +68 -0
  118. data/lib/action_view/helpers/asset_tag_helper.rb +830 -0
  119. data/lib/action_view/helpers/atom_feed_helper.rb +198 -0
  120. data/lib/action_view/helpers/cache_helper.rb +39 -0
  121. data/lib/action_view/helpers/capture_helper.rb +168 -0
  122. data/lib/action_view/helpers/date_helper.rb +988 -0
  123. data/lib/action_view/helpers/debug_helper.rb +38 -0
  124. data/lib/action_view/helpers/form_helper.rb +1102 -0
  125. data/lib/action_view/helpers/form_options_helper.rb +600 -0
  126. data/lib/action_view/helpers/form_tag_helper.rb +495 -0
  127. data/lib/action_view/helpers/javascript_helper.rb +208 -0
  128. data/lib/action_view/helpers/number_helper.rb +311 -0
  129. data/lib/action_view/helpers/prototype_helper.rb +1309 -0
  130. data/lib/action_view/helpers/raw_output_helper.rb +9 -0
  131. data/lib/action_view/helpers/record_identification_helper.rb +20 -0
  132. data/lib/action_view/helpers/record_tag_helper.rb +58 -0
  133. data/lib/action_view/helpers/sanitize_helper.rb +259 -0
  134. data/lib/action_view/helpers/scriptaculous_helper.rb +226 -0
  135. data/lib/action_view/helpers/tag_helper.rb +151 -0
  136. data/lib/action_view/helpers/text_helper.rb +594 -0
  137. data/lib/action_view/helpers/translation_helper.rb +39 -0
  138. data/lib/action_view/helpers/url_helper.rb +639 -0
  139. data/lib/action_view/locale/en.yml +117 -0
  140. data/lib/action_view/paths.rb +80 -0
  141. data/lib/action_view/render/partials.rb +342 -0
  142. data/lib/action_view/render/rendering.rb +134 -0
  143. data/lib/action_view/safe_buffer.rb +28 -0
  144. data/lib/action_view/template/error.rb +101 -0
  145. data/lib/action_view/template/handler.rb +36 -0
  146. data/lib/action_view/template/handlers.rb +52 -0
  147. data/lib/action_view/template/handlers/builder.rb +17 -0
  148. data/lib/action_view/template/handlers/erb.rb +53 -0
  149. data/lib/action_view/template/handlers/rjs.rb +18 -0
  150. data/lib/action_view/template/resolver.rb +165 -0
  151. data/lib/action_view/template/template.rb +131 -0
  152. data/lib/action_view/template/text.rb +38 -0
  153. data/lib/action_view/test_case.rb +163 -0
  154. metadata +236 -0
@@ -0,0 +1,68 @@
1
+ module ActionView
2
+ module Helpers
3
+ module AjaxHelper
4
+ include UrlHelper
5
+
6
+ def link_to_remote(name, url, options = {})
7
+ html = options.delete(:html) || {}
8
+
9
+ update = options.delete(:update)
10
+ if update.is_a?(Hash)
11
+ html["data-update-success"] = update[:success]
12
+ html["data-update-failure"] = update[:failure]
13
+ else
14
+ html["data-update-success"] = update
15
+ end
16
+
17
+ html["data-update-position"] = options.delete(:position)
18
+ html["data-method"] = options.delete(:method)
19
+ html["data-remote"] = "true"
20
+
21
+ html.merge!(options)
22
+
23
+ url = url_for(url) if url.is_a?(Hash)
24
+ link_to(name, url, html)
25
+ end
26
+
27
+ def button_to_remote(name, options = {}, html_options = {})
28
+ url = options.delete(:url)
29
+ url = url_for(url) if url.is_a?(Hash)
30
+
31
+ html_options.merge!(:type => "button", :value => name,
32
+ :"data-url" => url)
33
+
34
+ tag(:input, html_options)
35
+ end
36
+
37
+ module Rails2Compatibility
38
+ def set_callbacks(options, html)
39
+ [:complete, :failure, :success, :interactive, :loaded, :loading].each do |type|
40
+ html["data-#{type}-code"] = options.delete(type.to_sym)
41
+ end
42
+
43
+ options.each do |option, value|
44
+ if option.is_a?(Integer)
45
+ html["data-#{option}-code"] = options.delete(option)
46
+ end
47
+ end
48
+ end
49
+
50
+ def link_to_remote(name, url, options = nil)
51
+ if !options && url.is_a?(Hash) && url.key?(:url)
52
+ url, options = url.delete(:url), url
53
+ end
54
+
55
+ set_callbacks(options, options[:html] ||= {})
56
+
57
+ super
58
+ end
59
+
60
+ def button_to_remote(name, options = {}, html_options = {})
61
+ set_callbacks(options, html_options)
62
+ super
63
+ end
64
+ end
65
+
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,830 @@
1
+ require 'thread'
2
+ require 'cgi'
3
+ require 'action_view/helpers/url_helper'
4
+ require 'action_view/helpers/tag_helper'
5
+ require 'active_support/core_ext/file'
6
+
7
+ module ActionView
8
+ module Helpers #:nodoc:
9
+ # This module provides methods for generating HTML that links views to assets such
10
+ # as images, javascripts, stylesheets, and feeds. These methods do not verify
11
+ # the assets exist before linking to them:
12
+ #
13
+ # image_tag("rails.png")
14
+ # # => <img alt="Rails src="/images/rails.png?1230601161" />
15
+ # stylesheet_link_tag("application")
16
+ # # => <link href="/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
17
+ #
18
+ # === Using asset hosts
19
+ #
20
+ # By default, Rails links to these assets on the current host in the public
21
+ # folder, but you can direct Rails to link to assets from a dedicated asset
22
+ # server by setting ActionController::Base.asset_host in the application
23
+ # configuration, typically in <tt>config/environments/production.rb</tt>.
24
+ # For example, you'd define <tt>assets.example.com</tt> to be your asset
25
+ # host this way:
26
+ #
27
+ # ActionController::Base.asset_host = "assets.example.com"
28
+ #
29
+ # Helpers take that into account:
30
+ #
31
+ # image_tag("rails.png")
32
+ # # => <img alt="Rails" src="http://assets.example.com/images/rails.png?1230601161" />
33
+ # stylesheet_link_tag("application")
34
+ # # => <link href="http://assets.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
35
+ #
36
+ # Browsers typically open at most two simultaneous connections to a single
37
+ # host, which means your assets often have to wait for other assets to finish
38
+ # downloading. You can alleviate this by using a <tt>%d</tt> wildcard in the
39
+ # +asset_host+. For example, "assets%d.example.com". If that wildcard is
40
+ # present Rails distributes asset requests among the corresponding four hosts
41
+ # "assets0.example.com", ..., "assets3.example.com". With this trick browsers
42
+ # will open eight simultaneous connections rather than two.
43
+ #
44
+ # image_tag("rails.png")
45
+ # # => <img alt="Rails" src="http://assets0.example.com/images/rails.png?1230601161" />
46
+ # stylesheet_link_tag("application")
47
+ # # => <link href="http://assets2.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
48
+ #
49
+ # To do this, you can either setup four actual hosts, or you can use wildcard
50
+ # DNS to CNAME the wildcard to a single asset host. You can read more about
51
+ # setting up your DNS CNAME records from your ISP.
52
+ #
53
+ # Note: This is purely a browser performance optimization and is not meant
54
+ # for server load balancing. See http://www.die.net/musings/page_load_time/
55
+ # for background.
56
+ #
57
+ # Alternatively, you can exert more control over the asset host by setting
58
+ # +asset_host+ to a proc like this:
59
+ #
60
+ # ActionController::Base.asset_host = Proc.new { |source|
61
+ # "http://assets#{rand(2) + 1}.example.com"
62
+ # }
63
+ # image_tag("rails.png")
64
+ # # => <img alt="Rails" src="http://assets0.example.com/images/rails.png?1230601161" />
65
+ # stylesheet_link_tag("application")
66
+ # # => <link href="http://assets1.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
67
+ #
68
+ # The example above generates "http://assets1.example.com" and
69
+ # "http://assets2.example.com" randomly. This option is useful for example if
70
+ # you need fewer/more than four hosts, custom host names, etc.
71
+ #
72
+ # As you see the proc takes a +source+ parameter. That's a string with the
73
+ # absolute path of the asset with any extensions and timestamps in place,
74
+ # for example "/images/rails.png?1230601161".
75
+ #
76
+ # ActionController::Base.asset_host = Proc.new { |source|
77
+ # if source.starts_with?('/images')
78
+ # "http://images.example.com"
79
+ # else
80
+ # "http://assets.example.com"
81
+ # end
82
+ # }
83
+ # image_tag("rails.png")
84
+ # # => <img alt="Rails" src="http://images.example.com/images/rails.png?1230601161" />
85
+ # stylesheet_link_tag("application")
86
+ # # => <link href="http://assets.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
87
+ #
88
+ # Alternatively you may ask for a second parameter +request+. That one is
89
+ # particularly useful for serving assets from an SSL-protected page. The
90
+ # example proc below disables asset hosting for HTTPS connections, while
91
+ # still sending assets for plain HTTP requests from asset hosts. If you don't
92
+ # have SSL certificates for each of the asset hosts this technique allows you
93
+ # to avoid warnings in the client about mixed media.
94
+ #
95
+ # ActionController::Base.asset_host = Proc.new { |source, request|
96
+ # if request.ssl?
97
+ # "#{request.protocol}#{request.host_with_port}"
98
+ # else
99
+ # "#{request.protocol}assets.example.com"
100
+ # end
101
+ # }
102
+ #
103
+ # You can also implement a custom asset host object that responds to +call+
104
+ # and takes either one or two parameters just like the proc.
105
+ #
106
+ # config.action_controller.asset_host = AssetHostingWithMinimumSsl.new(
107
+ # "http://asset%d.example.com", "https://asset1.example.com"
108
+ # )
109
+ #
110
+ # === Using asset timestamps
111
+ #
112
+ # By default, Rails appends asset's timestamps to all asset paths. This allows
113
+ # you to set a cache-expiration date for the asset far into the future, but
114
+ # still be able to instantly invalidate it by simply updating the file (and
115
+ # hence updating the timestamp, which then updates the URL as the timestamp
116
+ # is part of that, which in turn busts the cache).
117
+ #
118
+ # It's the responsibility of the web server you use to set the far-future
119
+ # expiration date on cache assets that you need to take advantage of this
120
+ # feature. Here's an example for Apache:
121
+ #
122
+ # # Asset Expiration
123
+ # ExpiresActive On
124
+ # <FilesMatch "\.(ico|gif|jpe?g|png|js|css)$">
125
+ # ExpiresDefault "access plus 1 year"
126
+ # </FilesMatch>
127
+ #
128
+ # Also note that in order for this to work, all your application servers must
129
+ # return the same timestamps. This means that they must have their clocks
130
+ # synchronized. If one of them drifts out of sync, you'll see different
131
+ # timestamps at random and the cache won't work. In that case the browser
132
+ # will request the same assets over and over again even thought they didn't
133
+ # change. You can use something like Live HTTP Headers for Firefox to verify
134
+ # that the cache is indeed working.
135
+ module AssetTagHelper
136
+ assets_dir = defined?(Rails.public_path) ? Rails.public_path : "public"
137
+ ActionView::DEFAULT_CONFIG = {
138
+ :assets_dir => assets_dir,
139
+ :javascripts_dir => "#{assets_dir}/javascripts",
140
+ :stylesheets_dir => "#{assets_dir}/stylesheets",
141
+ }
142
+
143
+ JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'].freeze unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
144
+
145
+ # Returns a link tag that browsers and news readers can use to auto-detect
146
+ # an RSS or ATOM feed. The +type+ can either be <tt>:rss</tt> (default) or
147
+ # <tt>:atom</tt>. Control the link options in url_for format using the
148
+ # +url_options+. You can modify the LINK tag itself in +tag_options+.
149
+ #
150
+ # ==== Options
151
+ # * <tt>:rel</tt> - Specify the relation of this link, defaults to "alternate"
152
+ # * <tt>:type</tt> - Override the auto-generated mime type
153
+ # * <tt>:title</tt> - Specify the title of the link, defaults to the +type+
154
+ #
155
+ # ==== Examples
156
+ # auto_discovery_link_tag # =>
157
+ # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/action" />
158
+ # auto_discovery_link_tag(:atom) # =>
159
+ # <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.currenthost.com/controller/action" />
160
+ # auto_discovery_link_tag(:rss, {:action => "feed"}) # =>
161
+ # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/feed" />
162
+ # auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"}) # =>
163
+ # <link rel="alternate" type="application/rss+xml" title="My RSS" href="http://www.currenthost.com/controller/feed" />
164
+ # auto_discovery_link_tag(:rss, {:controller => "news", :action => "feed"}) # =>
165
+ # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/news/feed" />
166
+ # auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {:title => "Example RSS"}) # =>
167
+ # <link rel="alternate" type="application/rss+xml" title="Example RSS" href="http://www.example.com/feed" />
168
+ def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {})
169
+ tag(
170
+ "link",
171
+ "rel" => tag_options[:rel] || "alternate",
172
+ "type" => tag_options[:type] || Mime::Type.lookup_by_extension(type.to_s).to_s,
173
+ "title" => tag_options[:title] || type.to_s.upcase,
174
+ "href" => url_options.is_a?(Hash) ? url_for(url_options.merge(:only_path => false)) : url_options
175
+ )
176
+ end
177
+
178
+ # Computes the path to a javascript asset in the public javascripts directory.
179
+ # If the +source+ filename has no extension, .js will be appended (except for explicit URIs)
180
+ # Full paths from the document root will be passed through.
181
+ # Used internally by javascript_include_tag to build the script path.
182
+ #
183
+ # ==== Examples
184
+ # javascript_path "xmlhr" # => /javascripts/xmlhr.js
185
+ # javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js
186
+ # javascript_path "/dir/xmlhr" # => /dir/xmlhr.js
187
+ # javascript_path "http://www.railsapplication.com/js/xmlhr" # => http://www.railsapplication.com/js/xmlhr
188
+ # javascript_path "http://www.railsapplication.com/js/xmlhr.js" # => http://www.railsapplication.com/js/xmlhr.js
189
+ def javascript_path(source)
190
+ compute_public_path(source, 'javascripts', 'js')
191
+ end
192
+ alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
193
+
194
+ # Returns an html script tag for each of the +sources+ provided. You
195
+ # can pass in the filename (.js extension is optional) of javascript files
196
+ # that exist in your public/javascripts directory for inclusion into the
197
+ # current page or you can pass the full path relative to your document
198
+ # root. To include the Prototype and Scriptaculous javascript libraries in
199
+ # your application, pass <tt>:defaults</tt> as the source. When using
200
+ # <tt>:defaults</tt>, if an application.js file exists in your public
201
+ # javascripts directory, it will be included as well. You can modify the
202
+ # html attributes of the script tag by passing a hash as the last argument.
203
+ #
204
+ # ==== Examples
205
+ # javascript_include_tag "xmlhr" # =>
206
+ # <script type="text/javascript" src="/javascripts/xmlhr.js"></script>
207
+ #
208
+ # javascript_include_tag "xmlhr.js" # =>
209
+ # <script type="text/javascript" src="/javascripts/xmlhr.js"></script>
210
+ #
211
+ # javascript_include_tag "common.javascript", "/elsewhere/cools" # =>
212
+ # <script type="text/javascript" src="/javascripts/common.javascript"></script>
213
+ # <script type="text/javascript" src="/elsewhere/cools.js"></script>
214
+ #
215
+ # javascript_include_tag "http://www.railsapplication.com/xmlhr" # =>
216
+ # <script type="text/javascript" src="http://www.railsapplication.com/xmlhr.js"></script>
217
+ #
218
+ # javascript_include_tag "http://www.railsapplication.com/xmlhr.js" # =>
219
+ # <script type="text/javascript" src="http://www.railsapplication.com/xmlhr.js"></script>
220
+ #
221
+ # javascript_include_tag :defaults # =>
222
+ # <script type="text/javascript" src="/javascripts/prototype.js"></script>
223
+ # <script type="text/javascript" src="/javascripts/effects.js"></script>
224
+ # ...
225
+ # <script type="text/javascript" src="/javascripts/application.js"></script>
226
+ #
227
+ # * = The application.js file is only referenced if it exists
228
+ #
229
+ # Though it's not really recommended practice, if you need to extend the default JavaScript set for any reason
230
+ # (e.g., you're going to be using a certain .js file in every action), then take a look at the register_javascript_include_default method.
231
+ #
232
+ # You can also include all javascripts in the javascripts directory using <tt>:all</tt> as the source:
233
+ #
234
+ # javascript_include_tag :all # =>
235
+ # <script type="text/javascript" src="/javascripts/prototype.js"></script>
236
+ # <script type="text/javascript" src="/javascripts/effects.js"></script>
237
+ # ...
238
+ # <script type="text/javascript" src="/javascripts/application.js"></script>
239
+ # <script type="text/javascript" src="/javascripts/shop.js"></script>
240
+ # <script type="text/javascript" src="/javascripts/checkout.js"></script>
241
+ #
242
+ # Note that the default javascript files will be included first. So Prototype and Scriptaculous are available to
243
+ # all subsequently included files.
244
+ #
245
+ # If you want Rails to search in all the subdirectories under javascripts, you should explicitly set <tt>:recursive</tt>:
246
+ #
247
+ # javascript_include_tag :all, :recursive => true
248
+ #
249
+ # == Caching multiple javascripts into one
250
+ #
251
+ # You can also cache multiple javascripts into one file, which requires less HTTP connections to download and can better be
252
+ # compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching
253
+ # is set to <tt>true</tt> (which is the case by default for the Rails production environment, but not for the development
254
+ # environment).
255
+ #
256
+ # ==== Examples
257
+ # javascript_include_tag :all, :cache => true # when ActionController::Base.perform_caching is false =>
258
+ # <script type="text/javascript" src="/javascripts/prototype.js"></script>
259
+ # <script type="text/javascript" src="/javascripts/effects.js"></script>
260
+ # ...
261
+ # <script type="text/javascript" src="/javascripts/application.js"></script>
262
+ # <script type="text/javascript" src="/javascripts/shop.js"></script>
263
+ # <script type="text/javascript" src="/javascripts/checkout.js"></script>
264
+ #
265
+ # javascript_include_tag :all, :cache => true # when ActionController::Base.perform_caching is true =>
266
+ # <script type="text/javascript" src="/javascripts/all.js"></script>
267
+ #
268
+ # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when ActionController::Base.perform_caching is false =>
269
+ # <script type="text/javascript" src="/javascripts/prototype.js"></script>
270
+ # <script type="text/javascript" src="/javascripts/cart.js"></script>
271
+ # <script type="text/javascript" src="/javascripts/checkout.js"></script>
272
+ #
273
+ # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when ActionController::Base.perform_caching is true =>
274
+ # <script type="text/javascript" src="/javascripts/shop.js"></script>
275
+ #
276
+ # The <tt>:recursive</tt> option is also available for caching:
277
+ #
278
+ # javascript_include_tag :all, :cache => true, :recursive => true
279
+ def javascript_include_tag(*sources)
280
+ options = sources.extract_options!.stringify_keys
281
+ concat = options.delete("concat")
282
+ cache = concat || options.delete("cache")
283
+ recursive = options.delete("recursive")
284
+
285
+ if concat || (ActionController::Base.perform_caching && cache)
286
+ joined_javascript_name = (cache == true ? "all" : cache) + ".js"
287
+ joined_javascript_path = File.join(joined_javascript_name[/^#{File::SEPARATOR}/] ? config.assets_dir : config.javascripts_dir, joined_javascript_name)
288
+
289
+ unless ActionController::Base.perform_caching && File.exists?(joined_javascript_path)
290
+ write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources, recursive))
291
+ end
292
+ javascript_src_tag(joined_javascript_name, options)
293
+ else
294
+ sources = expand_javascript_sources(sources, recursive)
295
+ ensure_javascript_sources!(sources) if cache
296
+ sources.collect { |source| javascript_src_tag(source, options) }.join("\n").html_safe!
297
+ end
298
+ end
299
+
300
+ @@javascript_expansions = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup }
301
+
302
+ # Register one or more javascript files to be included when <tt>symbol</tt>
303
+ # is passed to <tt>javascript_include_tag</tt>. This method is typically intended
304
+ # to be called from plugin initialization to register javascript files
305
+ # that the plugin installed in <tt>public/javascripts</tt>.
306
+ #
307
+ # ActionView::Helpers::AssetTagHelper.register_javascript_expansion :monkey => ["head", "body", "tail"]
308
+ #
309
+ # javascript_include_tag :monkey # =>
310
+ # <script type="text/javascript" src="/javascripts/head.js"></script>
311
+ # <script type="text/javascript" src="/javascripts/body.js"></script>
312
+ # <script type="text/javascript" src="/javascripts/tail.js"></script>
313
+ def self.register_javascript_expansion(expansions)
314
+ @@javascript_expansions.merge!(expansions)
315
+ end
316
+
317
+ @@stylesheet_expansions = {}
318
+
319
+ # Register one or more stylesheet files to be included when <tt>symbol</tt>
320
+ # is passed to <tt>stylesheet_link_tag</tt>. This method is typically intended
321
+ # to be called from plugin initialization to register stylesheet files
322
+ # that the plugin installed in <tt>public/stylesheets</tt>.
323
+ #
324
+ # ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion :monkey => ["head", "body", "tail"]
325
+ #
326
+ # stylesheet_link_tag :monkey # =>
327
+ # <link href="/stylesheets/head.css" media="screen" rel="stylesheet" type="text/css" />
328
+ # <link href="/stylesheets/body.css" media="screen" rel="stylesheet" type="text/css" />
329
+ # <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" type="text/css" />
330
+ def self.register_stylesheet_expansion(expansions)
331
+ @@stylesheet_expansions.merge!(expansions)
332
+ end
333
+
334
+ # Register one or more additional JavaScript files to be included when
335
+ # <tt>javascript_include_tag :defaults</tt> is called. This method is
336
+ # typically intended to be called from plugin initialization to register additional
337
+ # .js files that the plugin installed in <tt>public/javascripts</tt>.
338
+ def self.register_javascript_include_default(*sources)
339
+ @@javascript_expansions[:defaults].concat(sources)
340
+ end
341
+
342
+ def self.reset_javascript_include_default #:nodoc:
343
+ @@javascript_expansions[:defaults] = JAVASCRIPT_DEFAULT_SOURCES.dup
344
+ end
345
+
346
+ # Computes the path to a stylesheet asset in the public stylesheets directory.
347
+ # If the +source+ filename has no extension, <tt>.css</tt> will be appended (except for explicit URIs).
348
+ # Full paths from the document root will be passed through.
349
+ # Used internally by +stylesheet_link_tag+ to build the stylesheet path.
350
+ #
351
+ # ==== Examples
352
+ # stylesheet_path "style" # => /stylesheets/style.css
353
+ # stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css
354
+ # stylesheet_path "/dir/style.css" # => /dir/style.css
355
+ # stylesheet_path "http://www.railsapplication.com/css/style" # => http://www.railsapplication.com/css/style
356
+ # stylesheet_path "http://www.railsapplication.com/css/style.css" # => http://www.railsapplication.com/css/style.css
357
+ def stylesheet_path(source)
358
+ compute_public_path(source, 'stylesheets', 'css')
359
+ end
360
+ alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
361
+
362
+ # Returns a stylesheet link tag for the sources specified as arguments. If
363
+ # you don't specify an extension, <tt>.css</tt> will be appended automatically.
364
+ # You can modify the link attributes by passing a hash as the last argument.
365
+ #
366
+ # ==== Examples
367
+ # stylesheet_link_tag "style" # =>
368
+ # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" />
369
+ #
370
+ # stylesheet_link_tag "style.css" # =>
371
+ # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" />
372
+ #
373
+ # stylesheet_link_tag "http://www.railsapplication.com/style.css" # =>
374
+ # <link href="http://www.railsapplication.com/style.css" media="screen" rel="stylesheet" type="text/css" />
375
+ #
376
+ # stylesheet_link_tag "style", :media => "all" # =>
377
+ # <link href="/stylesheets/style.css" media="all" rel="stylesheet" type="text/css" />
378
+ #
379
+ # stylesheet_link_tag "style", :media => "print" # =>
380
+ # <link href="/stylesheets/style.css" media="print" rel="stylesheet" type="text/css" />
381
+ #
382
+ # stylesheet_link_tag "random.styles", "/css/stylish" # =>
383
+ # <link href="/stylesheets/random.styles" media="screen" rel="stylesheet" type="text/css" />
384
+ # <link href="/css/stylish.css" media="screen" rel="stylesheet" type="text/css" />
385
+ #
386
+ # You can also include all styles in the stylesheets directory using <tt>:all</tt> as the source:
387
+ #
388
+ # stylesheet_link_tag :all # =>
389
+ # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" type="text/css" />
390
+ # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" type="text/css" />
391
+ # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" type="text/css" />
392
+ #
393
+ # If you want Rails to search in all the subdirectories under stylesheets, you should explicitly set <tt>:recursive</tt>:
394
+ #
395
+ # stylesheet_link_tag :all, :recursive => true
396
+ #
397
+ # == Caching multiple stylesheets into one
398
+ #
399
+ # You can also cache multiple stylesheets into one file, which requires less HTTP connections and can better be
400
+ # compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching
401
+ # is set to true (which is the case by default for the Rails production environment, but not for the development
402
+ # environment). Examples:
403
+ #
404
+ # ==== Examples
405
+ # stylesheet_link_tag :all, :cache => true # when ActionController::Base.perform_caching is false =>
406
+ # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" type="text/css" />
407
+ # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" type="text/css" />
408
+ # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" type="text/css" />
409
+ #
410
+ # stylesheet_link_tag :all, :cache => true # when ActionController::Base.perform_caching is true =>
411
+ # <link href="/stylesheets/all.css" media="screen" rel="stylesheet" type="text/css" />
412
+ #
413
+ # stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when ActionController::Base.perform_caching is false =>
414
+ # <link href="/stylesheets/shop.css" media="screen" rel="stylesheet" type="text/css" />
415
+ # <link href="/stylesheets/cart.css" media="screen" rel="stylesheet" type="text/css" />
416
+ # <link href="/stylesheets/checkout.css" media="screen" rel="stylesheet" type="text/css" />
417
+ #
418
+ # stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when ActionController::Base.perform_caching is true =>
419
+ # <link href="/stylesheets/payment.css" media="screen" rel="stylesheet" type="text/css" />
420
+ #
421
+ # The <tt>:recursive</tt> option is also available for caching:
422
+ #
423
+ # stylesheet_link_tag :all, :cache => true, :recursive => true
424
+ #
425
+ # To force concatenation (even in development mode) set <tt>:concat</tt> to true. This is useful if
426
+ # you have too many stylesheets for IE to load.
427
+ #
428
+ # stylesheet_link_tag :all, :concat => true
429
+ #
430
+ def stylesheet_link_tag(*sources)
431
+ options = sources.extract_options!.stringify_keys
432
+ concat = options.delete("concat")
433
+ cache = concat || options.delete("cache")
434
+ recursive = options.delete("recursive")
435
+
436
+ if concat || (ActionController::Base.perform_caching && cache)
437
+ joined_stylesheet_name = (cache == true ? "all" : cache) + ".css"
438
+ joined_stylesheet_path = File.join(joined_stylesheet_name[/^#{File::SEPARATOR}/] ? config.assets_dir : config.stylesheets_dir, joined_stylesheet_name)
439
+
440
+ unless ActionController::Base.perform_caching && File.exists?(joined_stylesheet_path)
441
+ write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources, recursive))
442
+ end
443
+ stylesheet_tag(joined_stylesheet_name, options)
444
+ else
445
+ sources = expand_stylesheet_sources(sources, recursive)
446
+ ensure_stylesheet_sources!(sources) if cache
447
+ sources.collect { |source| stylesheet_tag(source, options) }.join("\n").html_safe!
448
+ end
449
+ end
450
+
451
+ # Computes the path to an image asset in the public images directory.
452
+ # Full paths from the document root will be passed through.
453
+ # Used internally by +image_tag+ to build the image path.
454
+ #
455
+ # ==== Examples
456
+ # image_path("edit") # => /images/edit
457
+ # image_path("edit.png") # => /images/edit.png
458
+ # image_path("icons/edit.png") # => /images/icons/edit.png
459
+ # image_path("/icons/edit.png") # => /icons/edit.png
460
+ # image_path("http://www.railsapplication.com/img/edit.png") # => http://www.railsapplication.com/img/edit.png
461
+ def image_path(source)
462
+ compute_public_path(source, 'images')
463
+ end
464
+ alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
465
+
466
+ # Computes the path to a video asset in the public videos directory.
467
+ # Full paths from the document root will be passed through.
468
+ # Used internally by +video_tag+ to build the video path.
469
+ #
470
+ # ==== Examples
471
+ # video_path("hd") # => /videos/hd
472
+ # video_path("hd.avi") # => /videos/hd.avi
473
+ # video_path("trailers/hd.avi") # => /videos/trailers/hd.avi
474
+ # video_path("/trailers/hd.avi") # => /trailers/hd.avi
475
+ # video_path("http://www.railsapplication.com/vid/hd.avi") # => http://www.railsapplication.com/vid/hd.avi
476
+ def video_path(source)
477
+ compute_public_path(source, 'videos')
478
+ end
479
+ alias_method :path_to_video, :video_path # aliased to avoid conflicts with a video_path named route
480
+
481
+ # Computes the path to an audio asset in the public audios directory.
482
+ # Full paths from the document root will be passed through.
483
+ # Used internally by +audio_tag+ to build the audio path.
484
+ #
485
+ # ==== Examples
486
+ # audio_path("horse") # => /audios/horse
487
+ # audio_path("horse.wav") # => /audios/horse.avi
488
+ # audio_path("sounds/horse.wav") # => /audios/sounds/horse.avi
489
+ # audio_path("/sounds/horse.wav") # => /sounds/horse.avi
490
+ # audio_path("http://www.railsapplication.com/sounds/horse.wav") # => http://www.railsapplication.com/sounds/horse.wav
491
+ def audio_path(source)
492
+ compute_public_path(source, 'audios')
493
+ end
494
+ alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route
495
+
496
+ # Returns an html image tag for the +source+. The +source+ can be a full
497
+ # path or a file that exists in your public images directory.
498
+ #
499
+ # ==== Options
500
+ # You can add HTML attributes using the +options+. The +options+ supports
501
+ # three additional keys for convenience and conformance:
502
+ #
503
+ # * <tt>:alt</tt> - If no alt text is given, the file name part of the
504
+ # +source+ is used (capitalized and without the extension)
505
+ # * <tt>:size</tt> - Supplied as "{Width}x{Height}", so "30x45" becomes
506
+ # width="30" and height="45". <tt>:size</tt> will be ignored if the
507
+ # value is not in the correct format.
508
+ # * <tt>:mouseover</tt> - Set an alternate image to be used when the onmouseover
509
+ # event is fired, and sets the original image to be replaced onmouseout.
510
+ # This can be used to implement an easy image toggle that fires on onmouseover.
511
+ #
512
+ # ==== Examples
513
+ # image_tag("icon") # =>
514
+ # <img src="/images/icon" alt="Icon" />
515
+ # image_tag("icon.png") # =>
516
+ # <img src="/images/icon.png" alt="Icon" />
517
+ # image_tag("icon.png", :size => "16x10", :alt => "Edit Entry") # =>
518
+ # <img src="/images/icon.png" width="16" height="10" alt="Edit Entry" />
519
+ # image_tag("/icons/icon.gif", :size => "16x16") # =>
520
+ # <img src="/icons/icon.gif" width="16" height="16" alt="Icon" />
521
+ # image_tag("/icons/icon.gif", :height => '32', :width => '32') # =>
522
+ # <img alt="Icon" height="32" src="/icons/icon.gif" width="32" />
523
+ # image_tag("/icons/icon.gif", :class => "menu_icon") # =>
524
+ # <img alt="Icon" class="menu_icon" src="/icons/icon.gif" />
525
+ # image_tag("mouse.png", :mouseover => "/images/mouse_over.png") # =>
526
+ # <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
527
+ # image_tag("mouse.png", :mouseover => image_path("mouse_over.png")) # =>
528
+ # <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
529
+ def image_tag(source, options = {})
530
+ options.symbolize_keys!
531
+
532
+ src = options[:src] = path_to_image(source)
533
+ options[:alt] ||= File.basename(src, '.*').split('.').first.to_s.capitalize
534
+
535
+ if size = options.delete(:size)
536
+ options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
537
+ end
538
+
539
+ if mouseover = options.delete(:mouseover)
540
+ options[:onmouseover] = "this.src='#{image_path(mouseover)}'"
541
+ options[:onmouseout] = "this.src='#{src}'"
542
+ end
543
+
544
+ tag("img", options)
545
+ end
546
+
547
+ # Returns an html video tag for the +sources+. If +sources+ is a string,
548
+ # a single video tag will be returned. If +sources+ is an array, a video
549
+ # tag with nested source tags for each source will be returned. The
550
+ # +sources+ can be full paths or files that exists in your public videos
551
+ # directory.
552
+ #
553
+ # ==== Options
554
+ # You can add HTML attributes using the +options+. The +options+ supports
555
+ # two additional keys for convenience and conformance:
556
+ #
557
+ # * <tt>:poster</tt> - Set an image (like a screenshot) to be shown
558
+ # before the video loads. The path is calculated like the +src+ of +image_tag+.
559
+ # * <tt>:size</tt> - Supplied as "{Width}x{Height}", so "30x45" becomes
560
+ # width="30" and height="45". <tt>:size</tt> will be ignored if the
561
+ # value is not in the correct format.
562
+ #
563
+ # ==== Examples
564
+ # video_tag("trailer") # =>
565
+ # <video src="/videos/trailer" />
566
+ # video_tag("trailer.ogg") # =>
567
+ # <video src="/videos/trailer.ogg" />
568
+ # video_tag("trailer.ogg", :controls => true, :autobuffer => true) # =>
569
+ # <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" />
570
+ # video_tag("trailer.m4v", :size => "16x10", :poster => "screenshot.png") # =>
571
+ # <video src="/videos/trailer.m4v" width="16" height="10" poster="/images/screenshot.png" />
572
+ # video_tag("/trailers/hd.avi", :size => "16x16") # =>
573
+ # <video src="/trailers/hd.avi" width="16" height="16" />
574
+ # video_tag("/trailers/hd.avi", :height => '32', :width => '32') # =>
575
+ # <video height="32" src="/trailers/hd.avi" width="32" />
576
+ # video_tag(["trailer.ogg", "trailer.flv"]) # =>
577
+ # <video><source src="trailer.ogg" /><source src="trailer.ogg" /><source src="trailer.flv" /></video>
578
+ # video_tag(["trailer.ogg", "trailer.flv"] :size => "160x120") # =>
579
+ # <video height="120" width="160"><source src="trailer.ogg" /><source src="trailer.flv" /></video>
580
+ def video_tag(sources, options = {})
581
+ options.symbolize_keys!
582
+
583
+ options[:poster] = path_to_image(options[:poster]) if options[:poster]
584
+
585
+ if size = options.delete(:size)
586
+ options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
587
+ end
588
+
589
+ if sources.is_a?(Array)
590
+ content_tag("video", options) do
591
+ sources.map { |source| tag("source", :src => source) }.join.html_safe!
592
+ end
593
+ else
594
+ options[:src] = path_to_video(sources)
595
+ tag("video", options)
596
+ end
597
+ end
598
+
599
+ # Returns an html audio tag for the +source+.
600
+ # The +source+ can be full path or file that exists in
601
+ # your public audios directory.
602
+ #
603
+ # ==== Examples
604
+ # audio_tag("sound") # =>
605
+ # <audio src="/audios/sound" />
606
+ # audio_tag("sound.wav") # =>
607
+ # <audio src="/audios/sound.wav" />
608
+ # audio_tag("sound.wav", :autoplay => true, :controls => true) # =>
609
+ # <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav" />
610
+ def audio_tag(source, options = {})
611
+ options.symbolize_keys!
612
+ options[:src] = path_to_audio(source)
613
+ tag("audio", options)
614
+ end
615
+
616
+ def self.cache_asset_timestamps
617
+ @@cache_asset_timestamps
618
+ end
619
+
620
+ # You can enable or disable the asset tag timestamps cache.
621
+ # With the cache enabled, the asset tag helper methods will make fewer
622
+ # expense file system calls. However this prevents you from modifying
623
+ # any asset files while the server is running.
624
+ #
625
+ # ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
626
+ def self.cache_asset_timestamps=(value)
627
+ @@cache_asset_timestamps = value
628
+ end
629
+
630
+ @@cache_asset_timestamps = true
631
+
632
+ private
633
+ # Add the the extension +ext+ if not present. Return full URLs otherwise untouched.
634
+ # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
635
+ # roots. Rewrite the asset path for cache-busting asset ids. Include
636
+ # asset host, if configured, with the correct request protocol.
637
+ def compute_public_path(source, dir, ext = nil, include_host = true)
638
+ has_request = @controller.respond_to?(:request)
639
+
640
+ source_ext = File.extname(source)[1..-1]
641
+ if ext && !is_uri?(source) && (source_ext.blank? || (ext != source_ext && File.exist?(File.join(config.assets_dir, dir, "#{source}.#{ext}"))))
642
+ source += ".#{ext}"
643
+ end
644
+
645
+ unless is_uri?(source)
646
+ source = "/#{dir}/#{source}" unless source[0] == ?/
647
+
648
+ source = rewrite_asset_path(source)
649
+
650
+ if has_request && include_host
651
+ unless source =~ %r{^#{ActionController::Base.relative_url_root}/}
652
+ source = "#{ActionController::Base.relative_url_root}#{source}"
653
+ end
654
+ end
655
+ end
656
+
657
+ if include_host && !is_uri?(source)
658
+ host = compute_asset_host(source)
659
+
660
+ if has_request && !host.blank? && !is_uri?(host)
661
+ host = "#{@controller.request.protocol}#{host}"
662
+ end
663
+
664
+ "#{host}#{source}"
665
+ else
666
+ source
667
+ end
668
+ end
669
+
670
+ def is_uri?(path)
671
+ path =~ %r{^[-a-z]+://}
672
+ end
673
+
674
+ # Pick an asset host for this source. Returns +nil+ if no host is set,
675
+ # the host if no wildcard is set, the host interpolated with the
676
+ # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
677
+ # or the value returned from invoking the proc if it's a proc or the value from
678
+ # invoking call if it's an object responding to call.
679
+ def compute_asset_host(source)
680
+ if host = ActionController::Base.asset_host
681
+ if host.is_a?(Proc) || host.respond_to?(:call)
682
+ case host.is_a?(Proc) ? host.arity : host.method(:call).arity
683
+ when 2
684
+ request = @controller.respond_to?(:request) && @controller.request
685
+ host.call(source, request)
686
+ else
687
+ host.call(source)
688
+ end
689
+ else
690
+ (host =~ /%d/) ? host % (source.hash % 4) : host
691
+ end
692
+ end
693
+ end
694
+
695
+ @@asset_timestamps_cache = {}
696
+ @@asset_timestamps_cache_guard = Mutex.new
697
+
698
+ # Use the RAILS_ASSET_ID environment variable or the source's
699
+ # modification time as its cache-busting asset id.
700
+ def rails_asset_id(source)
701
+ if asset_id = ENV["RAILS_ASSET_ID"]
702
+ asset_id
703
+ else
704
+ if @@cache_asset_timestamps && (asset_id = @@asset_timestamps_cache[source])
705
+ asset_id
706
+ else
707
+ path = File.join(config.assets_dir, source)
708
+ asset_id = File.exist?(path) ? File.mtime(path).to_i.to_s : ''
709
+
710
+ if @@cache_asset_timestamps
711
+ @@asset_timestamps_cache_guard.synchronize do
712
+ @@asset_timestamps_cache[source] = asset_id
713
+ end
714
+ end
715
+
716
+ asset_id
717
+ end
718
+ end
719
+ end
720
+
721
+ # Break out the asset path rewrite in case plugins wish to put the asset id
722
+ # someplace other than the query string.
723
+ def rewrite_asset_path(source)
724
+ asset_id = rails_asset_id(source)
725
+ if asset_id.blank?
726
+ source
727
+ else
728
+ source + "?#{asset_id}"
729
+ end
730
+ end
731
+
732
+ def javascript_src_tag(source, options)
733
+ content_tag("script", "", { "type" => Mime::JS, "src" => path_to_javascript(source) }.merge(options))
734
+ end
735
+
736
+ def stylesheet_tag(source, options)
737
+ tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => html_escape(path_to_stylesheet(source)) }.merge(options), false, false)
738
+ end
739
+
740
+ def compute_javascript_paths(*args)
741
+ expand_javascript_sources(*args).collect { |source| compute_public_path(source, 'javascripts', 'js', false) }
742
+ end
743
+
744
+ def compute_stylesheet_paths(*args)
745
+ expand_stylesheet_sources(*args).collect { |source| compute_public_path(source, 'stylesheets', 'css', false) }
746
+ end
747
+
748
+ def expand_javascript_sources(sources, recursive = false)
749
+ if sources.include?(:all)
750
+ all_javascript_files = collect_asset_files(config.javascripts_dir, ('**' if recursive), '*.js')
751
+ ((determine_source(:defaults, @@javascript_expansions).dup & all_javascript_files) + all_javascript_files).uniq
752
+ else
753
+ expanded_sources = sources.collect do |source|
754
+ determine_source(source, @@javascript_expansions)
755
+ end.flatten
756
+ expanded_sources << "application" if sources.include?(:defaults) && File.exist?(File.join(config.javascripts_dir, "application.js"))
757
+ expanded_sources
758
+ end
759
+ end
760
+
761
+ def expand_stylesheet_sources(sources, recursive)
762
+ if sources.first == :all
763
+ collect_asset_files(config.stylesheets_dir, ('**' if recursive), '*.css')
764
+ else
765
+ sources.collect do |source|
766
+ determine_source(source, @@stylesheet_expansions)
767
+ end.flatten
768
+ end
769
+ end
770
+
771
+ def determine_source(source, collection)
772
+ case source
773
+ when Symbol
774
+ collection[source] || raise(ArgumentError, "No expansion found for #{source.inspect}")
775
+ else
776
+ source
777
+ end
778
+ end
779
+
780
+ def ensure_stylesheet_sources!(sources)
781
+ sources.each do |source|
782
+ asset_file_path!(path_to_stylesheet(source))
783
+ end
784
+ return sources
785
+ end
786
+
787
+ def ensure_javascript_sources!(sources)
788
+ sources.each do |source|
789
+ asset_file_path!(path_to_javascript(source))
790
+ end
791
+ return sources
792
+ end
793
+
794
+ def join_asset_file_contents(paths)
795
+ paths.collect { |path| File.read(asset_file_path!(path)) }.join("\n\n")
796
+ end
797
+
798
+ def write_asset_file_contents(joined_asset_path, asset_paths)
799
+
800
+ FileUtils.mkdir_p(File.dirname(joined_asset_path))
801
+ File.atomic_write(joined_asset_path) { |cache| cache.write(join_asset_file_contents(asset_paths)) }
802
+
803
+ # Set mtime to the latest of the combined files to allow for
804
+ # consistent ETag without a shared filesystem.
805
+ mt = asset_paths.map { |p| File.mtime(asset_file_path(p)) }.max
806
+ File.utime(mt, mt, joined_asset_path)
807
+ end
808
+
809
+ def asset_file_path(path)
810
+ File.join(config.assets_dir, path.split('?').first)
811
+ end
812
+
813
+ def asset_file_path!(path)
814
+ unless is_uri?(path)
815
+ absolute_path = asset_file_path(path)
816
+ raise(Errno::ENOENT, "Asset file not found at '#{absolute_path}'" ) unless File.exist?(absolute_path)
817
+ return absolute_path
818
+ end
819
+ end
820
+
821
+ def collect_asset_files(*path)
822
+ dir = path.first
823
+
824
+ Dir[File.join(*path.compact)].collect do |file|
825
+ file[-(file.size - dir.size - 1)..-1].sub(/\.\w+$/, '')
826
+ end.sort
827
+ end
828
+ end
829
+ end
830
+ end