cloud_assets 0.0.2
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/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.md +86 -0
- data/Rakefile +1 -0
- data/app/controllers/cloud_assets_controller.rb +34 -0
- data/app/views/cloud_assets/content.html.erb +0 -0
- data/cloud_assets.gemspec +37 -0
- data/lib/cloud_assets/version.rb +3 -0
- data/lib/cloud_assets.rb +223 -0
- metadata +94 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
cloud_assets enables a Rails app to make transparent use of
|
|
2
|
+
assets on a remote server in an alternative technology.
|
|
3
|
+
|
|
4
|
+
Principles and modes of operation
|
|
5
|
+
---------------------------------
|
|
6
|
+
|
|
7
|
+
You can avoid incorporating static assets in your Rails
|
|
8
|
+
app directly, if they exist somewhere else on the internet.
|
|
9
|
+
With cloud_assets you configure your Rails app as the
|
|
10
|
+
front end for assets managed in another internet-connected
|
|
11
|
+
system, e.g. WordPress, Drupal, SharePoint, etc.
|
|
12
|
+
|
|
13
|
+
Instead of returning a 404 Not Found, cloud_assets will
|
|
14
|
+
proxy a pooled request to the remote server, cache the
|
|
15
|
+
content locally, and serve it from Rails.
|
|
16
|
+
|
|
17
|
+
cloud_assets also provides tools and vocabulary for making
|
|
18
|
+
Rails views that make use of HTML originating on the
|
|
19
|
+
remote system, and rewrites URIs in such templates to
|
|
20
|
+
point either to the local Rails system or a CDN, depending
|
|
21
|
+
on configuration and utility.
|
|
22
|
+
|
|
23
|
+
Installation
|
|
24
|
+
------------
|
|
25
|
+
|
|
26
|
+
1.
|
|
27
|
+
|
|
28
|
+
Add cloud_assets to your Gemfile and bundle install, then
|
|
29
|
+
define the following in your environment.
|
|
30
|
+
|
|
31
|
+
Required:
|
|
32
|
+
CLOUD_ASSET_ORIGIN - Origin URI for remote assets
|
|
33
|
+
|
|
34
|
+
Optional:
|
|
35
|
+
CLOUD_ASSET_CDN - Content delivery network, if any. This
|
|
36
|
+
is used to rewrite URIs for images and other large static
|
|
37
|
+
assets to the CDN instead of Rails.
|
|
38
|
+
|
|
39
|
+
CLOUD_ASSET_USER, CLOUD_ASSET_PASSWORD - HTTP Basic
|
|
40
|
+
credentials which will be used when retrieving assets from
|
|
41
|
+
the cloud asset source.
|
|
42
|
+
|
|
43
|
+
2.
|
|
44
|
+
|
|
45
|
+
Put a route at the bottom of your routes.rb:
|
|
46
|
+
|
|
47
|
+
match '*url' => 'cloud_assets#content'
|
|
48
|
+
|
|
49
|
+
This will allow cloud_assets to handle anything Rails
|
|
50
|
+
doesn't recognize.
|
|
51
|
+
|
|
52
|
+
3.
|
|
53
|
+
|
|
54
|
+
In application_controller.rb:
|
|
55
|
+
|
|
56
|
+
include CloudAssets
|
|
57
|
+
|
|
58
|
+
Additional Configuration
|
|
59
|
+
------------------------
|
|
60
|
+
|
|
61
|
+
If you are serving any non-trivial amount of remotely
|
|
62
|
+
sourced assets out of your Rails system, you'll want a cache.
|
|
63
|
+
cloud_assets likes memcached via dalli.
|
|
64
|
+
|
|
65
|
+
To enable it, just define $hydra_cache somewhere in one of
|
|
66
|
+
your initializers.
|
|
67
|
+
|
|
68
|
+
Usage
|
|
69
|
+
-----
|
|
70
|
+
|
|
71
|
+
Set a remote layout for an HTML view by defining the URI to
|
|
72
|
+
the layout on the remote system, e.g.
|
|
73
|
+
|
|
74
|
+
set_remote_layout '/templates/foo.html'
|
|
75
|
+
|
|
76
|
+
You can inject additional content into the elements of that
|
|
77
|
+
template, or override the interior of its elements, using CSS
|
|
78
|
+
selectors:
|
|
79
|
+
|
|
80
|
+
inject_into_remote_layout '#notice' => flash[:notice]
|
|
81
|
+
override_remote_layout 'body' => yield
|
|
82
|
+
|
|
83
|
+
Finally, obtain and show the result (complete with rewrites
|
|
84
|
+
of CDN URLs and so on) with:
|
|
85
|
+
|
|
86
|
+
apply_remote_layout
|
data/Rakefile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
class CloudAssetsController < ApplicationController
|
|
2
|
+
|
|
3
|
+
def content
|
|
4
|
+
asset_response = cloud_asset request.fullpath
|
|
5
|
+
if asset_response.success?
|
|
6
|
+
content_type = asset_response.headers_hash['Content-type']
|
|
7
|
+
if content_type.kind_of? Array
|
|
8
|
+
content_type = content_type.pop
|
|
9
|
+
end
|
|
10
|
+
if content_type =~ /text\/html/
|
|
11
|
+
set_remote_layout asset_response
|
|
12
|
+
response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate, max-age=0'
|
|
13
|
+
render :html => ''
|
|
14
|
+
elsif content_type =~ /javascript/
|
|
15
|
+
response.headers['Cache-Control'] = "max-age=#{CloudAssets::javascript_max_age_seconds}"
|
|
16
|
+
# In externally sourced JS, mask the cloud source to point here
|
|
17
|
+
# we want to do this because of JavaScript's same-source restrictions
|
|
18
|
+
body = CloudAssets::fixup_javascript(asset_response.body.gsub CloudAssets::origin,'')
|
|
19
|
+
send_data body, :type => content_type, :disposition => 'inline'
|
|
20
|
+
elsif content_type =~ /css/
|
|
21
|
+
response.headers['Cache-Control'] = "max-age=#{CloudAssets::css_max_age_seconds}"
|
|
22
|
+
# In externally sourced CSS, mask the cloud source to point to the CDN
|
|
23
|
+
body = CloudAssets::fixup_css(asset_response.body.gsub CloudAssets::origin, CloudAssets::cdn)
|
|
24
|
+
send_data body, :type => content_type, :disposition => 'inline'
|
|
25
|
+
else
|
|
26
|
+
response.headers['Cache-Control'] = "max-age=#{CloudAssets::other_max_age_seconds}"
|
|
27
|
+
send_data asset_response.body, :type => content_type, :disposition => 'inline'
|
|
28
|
+
end
|
|
29
|
+
else
|
|
30
|
+
render :status => :not_found
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
end
|
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
|
3
|
+
require "cloud_assets/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |s|
|
|
6
|
+
s.name = "cloud_assets"
|
|
7
|
+
s.version = CloudAssets::VERSION
|
|
8
|
+
s.authors = ["Rob Heittman"]
|
|
9
|
+
s.email = ["rob.heittman@solertium.com"]
|
|
10
|
+
s.homepage = "https://github.com/rfc2616/cloud_assets"
|
|
11
|
+
s.summary = %q{
|
|
12
|
+
Enables a Rails app to make transparent use of
|
|
13
|
+
assets on a remote server in an alternative technology.
|
|
14
|
+
}
|
|
15
|
+
s.description = %q{
|
|
16
|
+
This gem is in use in some production sites to provide
|
|
17
|
+
backing for a Rails app using content from WordPress and
|
|
18
|
+
PostLaunch (a Java based CMS), and has specific
|
|
19
|
+
dependencies on Typhoeus, Nokogiri, and dalli, favorites
|
|
20
|
+
we use at Solertium. It's in the early stages of
|
|
21
|
+
being made into a general purpose tool -- cleanup,
|
|
22
|
+
generalized tests (our site-specific ones are stripped
|
|
23
|
+
from the gem) documentation, performance, and decoupling
|
|
24
|
+
from dependencies.
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
s.files = `git ls-files`.split("\n")
|
|
28
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
29
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
30
|
+
s.require_paths = ["lib"]
|
|
31
|
+
|
|
32
|
+
# specify any dependencies here; for example:
|
|
33
|
+
# s.add_development_dependency "rspec"
|
|
34
|
+
s.add_runtime_dependency "typhoeus"
|
|
35
|
+
s.add_runtime_dependency "nokogiri"
|
|
36
|
+
s.add_runtime_dependency "dalli"
|
|
37
|
+
end
|
data/lib/cloud_assets.rb
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
require "cloud_assets/version"
|
|
2
|
+
|
|
3
|
+
module CloudAssets
|
|
4
|
+
|
|
5
|
+
def self.setup
|
|
6
|
+
yield self
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Monkey-patch this method if you need to hack some fixups into
|
|
10
|
+
# html from your asset source.
|
|
11
|
+
def self.fixup_html(html)
|
|
12
|
+
html
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Monkey-patch this method if you need to hack some fixups into
|
|
16
|
+
# javascript from your asset source.
|
|
17
|
+
def self.fixup_javascript(javascript)
|
|
18
|
+
javascript
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Monkey-patch this method if you need to hack some fixups into
|
|
22
|
+
# css from your asset source.
|
|
23
|
+
def self.fixup_css(css)
|
|
24
|
+
css
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
mattr_accessor :origin
|
|
28
|
+
@@origin = ENV['CLOUD_ASSET_ORIGIN']
|
|
29
|
+
|
|
30
|
+
mattr_accessor :user
|
|
31
|
+
@@user = ENV['CLOUD_ASSET_USER']
|
|
32
|
+
|
|
33
|
+
mattr_accessor :password
|
|
34
|
+
@@password = ENV['CLOUD_ASSET_PASSWORD']
|
|
35
|
+
|
|
36
|
+
mattr_accessor :cdn
|
|
37
|
+
@@cdn = ENV['CLOUD_ASSET_CDN'] || ''
|
|
38
|
+
|
|
39
|
+
# How long objects from the source can be kept in our local cache
|
|
40
|
+
mattr_accessor :cache_timeout_seconds
|
|
41
|
+
@@cache_timeout_seconds = 604800
|
|
42
|
+
|
|
43
|
+
# How long to allow clients (and CDN) to keep javascript assets
|
|
44
|
+
mattr_accessor :javascript_max_age_seconds
|
|
45
|
+
@@javascript_max_age_seconds = 600
|
|
46
|
+
|
|
47
|
+
# How long to allow clients (and CDN) to keep css assets
|
|
48
|
+
mattr_accessor :css_max_age_seconds
|
|
49
|
+
@@css_max_age_seconds = 600
|
|
50
|
+
|
|
51
|
+
# How long to allow clients (and CDN) to keep other assets
|
|
52
|
+
mattr_accessor :other_max_age_seconds
|
|
53
|
+
@@other_max_age_seconds = 86400
|
|
54
|
+
|
|
55
|
+
mattr_accessor :verbose
|
|
56
|
+
@@verbose = false
|
|
57
|
+
|
|
58
|
+
class Engine < Rails::Engine
|
|
59
|
+
|
|
60
|
+
module ControllerMethods
|
|
61
|
+
|
|
62
|
+
require 'typhoeus'
|
|
63
|
+
require 'nokogiri'
|
|
64
|
+
|
|
65
|
+
def cloud_asset(path)
|
|
66
|
+
p = "#{CloudAssets::origin}#{path}"
|
|
67
|
+
hydra = Typhoeus::Hydra.new
|
|
68
|
+
unless $dalli_cache.nil?
|
|
69
|
+
hydra.cache_getter do |request|
|
|
70
|
+
$dalli_cache.get(request.cache_key) rescue nil
|
|
71
|
+
end
|
|
72
|
+
hydra.cache_setter do |request|
|
|
73
|
+
$dalli_cache.set(request.cache_key, request.response, request.cache_timeout)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
options = {
|
|
77
|
+
:follow_location => true,
|
|
78
|
+
:max_redirects => 3,
|
|
79
|
+
:cache_timeout => CloudAssets::cache_timeout_seconds
|
|
80
|
+
}
|
|
81
|
+
unless CloudAssets::user.nil?
|
|
82
|
+
options[:username] = CloudAssets::user
|
|
83
|
+
options[:password] = CloudAssets::password
|
|
84
|
+
options[:auth_method] = :basic
|
|
85
|
+
end
|
|
86
|
+
if CloudAssets.verbose
|
|
87
|
+
puts "Retrieving remote asset #{p}"
|
|
88
|
+
end
|
|
89
|
+
request = Typhoeus::Request.new p, options
|
|
90
|
+
asset_response = nil
|
|
91
|
+
request.on_complete do |hydra_response|
|
|
92
|
+
asset_response = hydra_response
|
|
93
|
+
end
|
|
94
|
+
hydra.queue request
|
|
95
|
+
hydra.run
|
|
96
|
+
asset_response
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def optimize_uri(src)
|
|
100
|
+
return nil if src.nil?
|
|
101
|
+
o = CloudAssets::cdn || ''
|
|
102
|
+
src.gsub!(CloudAssets::origin,'')
|
|
103
|
+
return src if src =~ /^http:/
|
|
104
|
+
"#{o}#{src}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def correct_uri(src)
|
|
108
|
+
src.gsub(CloudAssets::origin,'')
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def optimized_html_for(asset_response)
|
|
112
|
+
doc = Nokogiri::HTML(asset_response.body)
|
|
113
|
+
|
|
114
|
+
{ 'img' => 'src',
|
|
115
|
+
'link' => 'href' }.each do |tag,attribute|
|
|
116
|
+
doc.css(tag).each do |e|
|
|
117
|
+
if tag == 'link' and e['rel'] != 'stylesheet'
|
|
118
|
+
next
|
|
119
|
+
end
|
|
120
|
+
unless e[attribute].nil?
|
|
121
|
+
e[attribute] = optimize_uri(e[attribute])
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
{ 'a' => 'href',
|
|
127
|
+
'script' => 'src',
|
|
128
|
+
'link' => 'href' }.each do |tag,attribute|
|
|
129
|
+
doc.css(tag).each do |e|
|
|
130
|
+
if tag == 'link' and e['rel'] == 'stylesheet'
|
|
131
|
+
next
|
|
132
|
+
end
|
|
133
|
+
unless e[attribute].nil?
|
|
134
|
+
e[attribute] = correct_uri(e[attribute])
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
doc
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def inject_into_remote_layout(hash)
|
|
143
|
+
if @injections.nil?
|
|
144
|
+
@injections = {}
|
|
145
|
+
end
|
|
146
|
+
@injections.merge! hash
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def override_remote_layout(hash)
|
|
150
|
+
if @overrides.nil?
|
|
151
|
+
@overrides = {}
|
|
152
|
+
end
|
|
153
|
+
@overrides.merge! hash
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def set_remote_layout(layout)
|
|
157
|
+
@remote_layout = layout
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def set_default_remote_layout(layout)
|
|
161
|
+
if @remote_layout.nil?
|
|
162
|
+
@remote_layout = layout
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def apply_remote_layout
|
|
167
|
+
begin
|
|
168
|
+
if @remote_layout.nil?
|
|
169
|
+
raise <<-ERR
|
|
170
|
+
No remote layout is defined. Use set_remote_layout or
|
|
171
|
+
set_default_remote_layout in your views prior to calling
|
|
172
|
+
apply_remote_layout.
|
|
173
|
+
ERR
|
|
174
|
+
end
|
|
175
|
+
if @remote_layout.kind_of? String
|
|
176
|
+
doc = optimized_html_for cloud_asset "#{@remote_layout}"
|
|
177
|
+
else
|
|
178
|
+
doc = optimized_html_for @remote_layout
|
|
179
|
+
end
|
|
180
|
+
unless @overrides.nil?
|
|
181
|
+
@overrides.each do |key, value|
|
|
182
|
+
begin
|
|
183
|
+
doc.at_css(key).inner_html = value
|
|
184
|
+
rescue
|
|
185
|
+
puts "Failed to override template element: #{key}"
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
unless @injections.nil?
|
|
190
|
+
@injections.each do |key, value|
|
|
191
|
+
begin
|
|
192
|
+
doc.at_css(key).add_child(value)
|
|
193
|
+
rescue
|
|
194
|
+
puts "Failed to inject data into template element: #{key}"
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
# We don't know what in-doc references might be to, so we have to make
|
|
199
|
+
# them local and can't optimize them to the CDN -- at least not without
|
|
200
|
+
# some serious guessing which we are not ready to do
|
|
201
|
+
CloudAssets::fixup_html(doc.to_s.gsub CloudAssets::origin, '')
|
|
202
|
+
rescue => e
|
|
203
|
+
puts e.inspect
|
|
204
|
+
puts e.backtrace
|
|
205
|
+
raise e
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
initializer 'cloud_assets.app_controller' do |app|
|
|
212
|
+
ActiveSupport.on_load(:action_controller) do
|
|
213
|
+
include ControllerMethods
|
|
214
|
+
helper_method :inject_into_remote_layout
|
|
215
|
+
helper_method :override_remote_layout
|
|
216
|
+
helper_method :set_remote_layout
|
|
217
|
+
helper_method :set_default_remote_layout
|
|
218
|
+
helper_method :apply_remote_layout
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: cloud_assets
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.2
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Rob Heittman
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2012-03-26 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: typhoeus
|
|
16
|
+
requirement: &70144157060340 !ruby/object:Gem::Requirement
|
|
17
|
+
none: false
|
|
18
|
+
requirements:
|
|
19
|
+
- - ! '>='
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '0'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: *70144157060340
|
|
25
|
+
- !ruby/object:Gem::Dependency
|
|
26
|
+
name: nokogiri
|
|
27
|
+
requirement: &70144157059580 !ruby/object:Gem::Requirement
|
|
28
|
+
none: false
|
|
29
|
+
requirements:
|
|
30
|
+
- - ! '>='
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: *70144157059580
|
|
36
|
+
- !ruby/object:Gem::Dependency
|
|
37
|
+
name: dalli
|
|
38
|
+
requirement: &70144157058760 !ruby/object:Gem::Requirement
|
|
39
|
+
none: false
|
|
40
|
+
requirements:
|
|
41
|
+
- - ! '>='
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: '0'
|
|
44
|
+
type: :runtime
|
|
45
|
+
prerelease: false
|
|
46
|
+
version_requirements: *70144157058760
|
|
47
|
+
description: ! "\n This gem is in use in some production sites to provide\n backing
|
|
48
|
+
for a Rails app using content from WordPress and\n PostLaunch (a Java based CMS),
|
|
49
|
+
and has specific\n dependencies on Typhoeus, Nokogiri, and dalli, favorites\n
|
|
50
|
+
\ we use at Solertium. It's in the early stages of\n being made into a general
|
|
51
|
+
purpose tool -- cleanup,\n generalized tests (our site-specific ones are stripped\n
|
|
52
|
+
\ from the gem) documentation, performance, and decoupling\n from dependencies.\n
|
|
53
|
+
\ "
|
|
54
|
+
email:
|
|
55
|
+
- rob.heittman@solertium.com
|
|
56
|
+
executables: []
|
|
57
|
+
extensions: []
|
|
58
|
+
extra_rdoc_files: []
|
|
59
|
+
files:
|
|
60
|
+
- .gitignore
|
|
61
|
+
- Gemfile
|
|
62
|
+
- README.md
|
|
63
|
+
- Rakefile
|
|
64
|
+
- app/controllers/cloud_assets_controller.rb
|
|
65
|
+
- app/views/cloud_assets/content.html.erb
|
|
66
|
+
- cloud_assets.gemspec
|
|
67
|
+
- lib/cloud_assets.rb
|
|
68
|
+
- lib/cloud_assets/version.rb
|
|
69
|
+
homepage: https://github.com/rfc2616/cloud_assets
|
|
70
|
+
licenses: []
|
|
71
|
+
post_install_message:
|
|
72
|
+
rdoc_options: []
|
|
73
|
+
require_paths:
|
|
74
|
+
- lib
|
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
76
|
+
none: false
|
|
77
|
+
requirements:
|
|
78
|
+
- - ! '>='
|
|
79
|
+
- !ruby/object:Gem::Version
|
|
80
|
+
version: '0'
|
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
82
|
+
none: false
|
|
83
|
+
requirements:
|
|
84
|
+
- - ! '>='
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '0'
|
|
87
|
+
requirements: []
|
|
88
|
+
rubyforge_project:
|
|
89
|
+
rubygems_version: 1.8.17
|
|
90
|
+
signing_key:
|
|
91
|
+
specification_version: 3
|
|
92
|
+
summary: Enables a Rails app to make transparent use of assets on a remote server
|
|
93
|
+
in an alternative technology.
|
|
94
|
+
test_files: []
|