fewer 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +3 -0
- data/LICENSE +20 -0
- data/README.md +15 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/lib/fewer.rb +4 -0
- data/lib/fewer/app.rb +36 -0
- data/lib/fewer/engines.rb +7 -0
- data/lib/fewer/engines/abstract.rb +46 -0
- data/lib/fewer/engines/css.rb +13 -0
- data/lib/fewer/engines/less.rb +19 -0
- data/lib/fewer/errors.rb +3 -0
- data/lib/fewer/serializer.rb +30 -0
- data/test/abstract_bundler_test.rb +11 -0
- data/test/app_test.rb +73 -0
- data/test/less_bundler_test.rb +7 -0
- data/test/test_helper.rb +12 -0
- metadata +100 -0
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Ben Pickles
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# fewer
|
2
|
+
|
3
|
+
Rack middleware to bundle assets and help you make fewer HTTP requests.
|
4
|
+
|
5
|
+
## How to use in Rails (3)
|
6
|
+
|
7
|
+
config.middleware.insert 0, Fewer::App, {
|
8
|
+
:engine => Fewer::Engines::Css,
|
9
|
+
:mount => '/stylesheets',
|
10
|
+
:root => Rails.root.join('public', 'stylesheets')
|
11
|
+
}
|
12
|
+
|
13
|
+
## Copyright
|
14
|
+
|
15
|
+
Copyright (c) 2010 Ben Pickles. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "fewer"
|
8
|
+
gem.summary = %q{Rack middleware to bundle assets and help you make fewer HTTP requests.}
|
9
|
+
gem.description = %q{Rack middleware to bundle assets and help you make fewer HTTP requests.}
|
10
|
+
gem.email = 'spideryoung@gmail.com'
|
11
|
+
gem.homepage = 'http://github.com/benpickles/fewer'
|
12
|
+
gem.authors = ['Ben Pickles']
|
13
|
+
gem.add_dependency('rack')
|
14
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts 'Jeweler (or a dependency) not available. Install it with: gem install jeweler'
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'rake/testtask'
|
22
|
+
Rake::TestTask.new(:test) do |test|
|
23
|
+
test.libs << 'test'
|
24
|
+
test.pattern = 'test/**/*_test.rb'
|
25
|
+
test.verbose = true
|
26
|
+
end
|
27
|
+
|
28
|
+
begin
|
29
|
+
require 'rcov/rcovtask'
|
30
|
+
Rcov::RcovTask.new do |test|
|
31
|
+
test.libs << 'test'
|
32
|
+
test.pattern = 'test/**/*_test.rb'
|
33
|
+
test.verbose = true
|
34
|
+
end
|
35
|
+
rescue LoadError
|
36
|
+
task :rcov do
|
37
|
+
abort 'RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
task :test => :check_dependencies
|
42
|
+
|
43
|
+
task :default => :test
|
44
|
+
|
45
|
+
require 'rake/rdoctask'
|
46
|
+
Rake::RDocTask.new do |rdoc|
|
47
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
|
+
|
49
|
+
rdoc.rdoc_dir = 'rdoc'
|
50
|
+
rdoc.title = "fewer #{version}"
|
51
|
+
rdoc.rdoc_files.include('README*')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.0
|
data/lib/fewer.rb
ADDED
data/lib/fewer/app.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
module Fewer
|
2
|
+
class App
|
3
|
+
attr_reader :app, :engine_klass, :cache, :mount, :root
|
4
|
+
|
5
|
+
def initialize(app, options = {})
|
6
|
+
@app = app
|
7
|
+
@engine_klass = options[:engine]
|
8
|
+
@mount = options[:mount]
|
9
|
+
@root = options[:root]
|
10
|
+
@cache = options[:cache] || 3600 * 24 * 365
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
return app.call(env) unless env['PATH_INFO'] =~ /^#{mount}/
|
15
|
+
|
16
|
+
names = names_from_path(env['PATH_INFO'])
|
17
|
+
engine = engine_klass.new(root, names)
|
18
|
+
headers = {
|
19
|
+
'Content-Type' => engine.content_type,
|
20
|
+
'Cache-Control' => "public, max-age=#{cache}"
|
21
|
+
}
|
22
|
+
|
23
|
+
[200, headers, [engine.read]]
|
24
|
+
rescue Fewer::MissingSourceFileError => e
|
25
|
+
[404, { 'Content-Type' => 'text/plain' }, [e.message]]
|
26
|
+
rescue => e
|
27
|
+
[500, { 'Content-Type' => 'text/plain' }, ["#{e.class}: #{e.message}"]]
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def names_from_path(path)
|
32
|
+
encoded = File.basename(path.sub(/^#{mount}\/?/, ''), '.*')
|
33
|
+
Serializer.decode(encoded)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Fewer
|
2
|
+
module Engines
|
3
|
+
class Abstract
|
4
|
+
attr_reader :names, :root
|
5
|
+
|
6
|
+
def initialize(root, names)
|
7
|
+
@root = root
|
8
|
+
@names = names
|
9
|
+
check_paths!
|
10
|
+
end
|
11
|
+
|
12
|
+
def check_paths!
|
13
|
+
if (missing = paths.reject { |path| File.exist?(path) }).any?
|
14
|
+
files = missing.map { |path| path.to_s }.join("\n")
|
15
|
+
raise Fewer::MissingSourceFileError.new("Missing source file#{'s' if missing.size > 1}:\n#{files}")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def content_type
|
20
|
+
'text/plain'
|
21
|
+
end
|
22
|
+
|
23
|
+
def extension
|
24
|
+
end
|
25
|
+
|
26
|
+
def mtime
|
27
|
+
paths.map { |path|
|
28
|
+
File.mtime(path).to_i
|
29
|
+
}.sort.last
|
30
|
+
end
|
31
|
+
|
32
|
+
def paths
|
33
|
+
names.map { |name|
|
34
|
+
# TODO: Fix rather large security hole...
|
35
|
+
File.join(root, "#{name}#{extension}")
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
def read
|
40
|
+
paths.map { |path|
|
41
|
+
File.read(path)
|
42
|
+
}.join("\n")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/fewer/errors.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Fewer
|
4
|
+
module Serializer
|
5
|
+
class BadString < RuntimeError; end
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def b64_decode(string)
|
9
|
+
padding_length = string.length % 4
|
10
|
+
Base64.decode64(string + '=' * padding_length)
|
11
|
+
end
|
12
|
+
|
13
|
+
def b64_encode(string)
|
14
|
+
Base64.encode64(string).tr("\n=",'')
|
15
|
+
end
|
16
|
+
|
17
|
+
def marshal_decode(string)
|
18
|
+
Marshal.load(b64_decode(string))
|
19
|
+
rescue TypeError, ArgumentError => e
|
20
|
+
raise BadString, "couldn't decode #{string} - got #{e}"
|
21
|
+
end
|
22
|
+
alias_method :decode, :marshal_decode
|
23
|
+
|
24
|
+
def marshal_encode(object)
|
25
|
+
b64_encode(Marshal.dump(object))
|
26
|
+
end
|
27
|
+
alias_method :encode, :marshal_encode
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# require 'test_helper'
|
2
|
+
#
|
3
|
+
# class AbstractBundlerTest < Test::Unit::TestCase
|
4
|
+
# def setup
|
5
|
+
# @bundler = Fewer::Engines::Abstract.new('.', ['a.txt', 'b.txt'])
|
6
|
+
# end
|
7
|
+
#
|
8
|
+
# def test_root_to_a_pathname
|
9
|
+
# assert_kind_of Pathname, @bundler.root
|
10
|
+
# end
|
11
|
+
# end
|
data/test/app_test.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class AppTest < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@app = stub
|
6
|
+
@engine = stub(
|
7
|
+
:check_request_extension => true,
|
8
|
+
:content_type => 'text/css',
|
9
|
+
:read => 'content'
|
10
|
+
)
|
11
|
+
@engine_klass = stub(:new => @engine)
|
12
|
+
@fewer_app = Fewer::App.new(@app, {
|
13
|
+
:engine => @engine_klass,
|
14
|
+
:mount => '/path',
|
15
|
+
:root => 'root'
|
16
|
+
})
|
17
|
+
@browser = Rack::Test::Session.new(Rack::MockSession.new(@fewer_app))
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_initialises_a_new_engine_with_a_single_file
|
21
|
+
file = 'file'
|
22
|
+
@engine_klass.expects(:new).with('root', file).returns(@engine)
|
23
|
+
@browser.get "/path/#{encode(file)}.css"
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_initialises_a_new_engine_with_multiple_files
|
27
|
+
files = ['file1', 'file2']
|
28
|
+
@engine_klass.expects(:new).with('root', files).returns(@engine)
|
29
|
+
@browser.get "/path/#{encode(files)}.css"
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_responds_with_engine_content_type
|
33
|
+
@browser.get "/path/#{encode('file')}.css"
|
34
|
+
assert_equal 'text/css', @browser.last_response.content_type
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_responds_with_cache_control
|
38
|
+
@browser.get "/path/#{encode('file')}.css"
|
39
|
+
assert_equal 'public, max-age=31536000', @browser.last_response.headers['Cache-Control']
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_responds_with_bundled_content
|
43
|
+
@engine.expects(:read).returns('content')
|
44
|
+
@browser.get "/path/#{encode('file')}.css"
|
45
|
+
assert_equal 'content', @browser.last_response.body
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_responds_with_200
|
49
|
+
@browser.get "/path/#{encode('file')}.css"
|
50
|
+
assert_equal 200, @browser.last_response.status
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_responds_with_404_for_missing_source
|
54
|
+
@engine.stubs(:read).raises(Fewer::MissingSourceFileError)
|
55
|
+
@browser.get "/path/#{encode('file')}.css"
|
56
|
+
assert_equal 404, @browser.last_response.status
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_responds_with_500_for_other_errors
|
60
|
+
@engine.stubs(:read).raises(RuntimeError)
|
61
|
+
@browser.get "/path/#{encode('file')}.css"
|
62
|
+
assert_equal 500, @browser.last_response.status
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
def decode(string)
|
67
|
+
Fewer::Serializer.decode(string)
|
68
|
+
end
|
69
|
+
|
70
|
+
def encode(obj)
|
71
|
+
Fewer::Serializer.encode(obj)
|
72
|
+
end
|
73
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fewer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 31
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 0.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Ben Pickles
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-07-23 00:00:00 +01:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rack
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
description: Rack middleware to bundle assets and help you make fewer HTTP requests.
|
36
|
+
email: spideryoung@gmail.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- LICENSE
|
43
|
+
- README.md
|
44
|
+
files:
|
45
|
+
- .gitignore
|
46
|
+
- LICENSE
|
47
|
+
- README.md
|
48
|
+
- Rakefile
|
49
|
+
- VERSION
|
50
|
+
- lib/fewer.rb
|
51
|
+
- lib/fewer/app.rb
|
52
|
+
- lib/fewer/engines.rb
|
53
|
+
- lib/fewer/engines/abstract.rb
|
54
|
+
- lib/fewer/engines/css.rb
|
55
|
+
- lib/fewer/engines/less.rb
|
56
|
+
- lib/fewer/errors.rb
|
57
|
+
- lib/fewer/serializer.rb
|
58
|
+
- test/app_test.rb
|
59
|
+
- test/test_helper.rb
|
60
|
+
- test/abstract_bundler_test.rb
|
61
|
+
- test/less_bundler_test.rb
|
62
|
+
has_rdoc: true
|
63
|
+
homepage: http://github.com/benpickles/fewer
|
64
|
+
licenses: []
|
65
|
+
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options:
|
68
|
+
- --charset=UTF-8
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 3
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
version: "0"
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
hash: 3
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
version: "0"
|
89
|
+
requirements: []
|
90
|
+
|
91
|
+
rubyforge_project:
|
92
|
+
rubygems_version: 1.3.7
|
93
|
+
signing_key:
|
94
|
+
specification_version: 3
|
95
|
+
summary: Rack middleware to bundle assets and help you make fewer HTTP requests.
|
96
|
+
test_files:
|
97
|
+
- test/abstract_bundler_test.rb
|
98
|
+
- test/app_test.rb
|
99
|
+
- test/less_bundler_test.rb
|
100
|
+
- test/test_helper.rb
|