fewer 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,6 +1,38 @@
1
- # fewer
1
+ # Fewer
2
2
 
3
- Rack middleware to bundle assets and help you make fewer HTTP requests.
3
+ Fewer is a Rack endpoint to bundle and cache assets and help you make fewer HTTP requests. Fewer extracts and combines a list of assets encoded in the URL and serves the response with far-future HTTP caching headers.
4
+
5
+ ## How to use in Rails 3
6
+
7
+ Using Fewer in your Rails app is easy, just initialize your Fewer apps and add them to your routes then include the helper methods in your `ApplicationHelper` with a one-liner.
8
+
9
+ # Gemfile
10
+ gem 'fewer'
11
+ gem 'closure-compiler', :group => :production
12
+
13
+ # config/initializers/fewer.rb
14
+ Fewer::App.new(:javascripts,
15
+ :engine => Fewer::Engines::Js,
16
+ :engine_options => { :min => Rails.env.production? }
17
+ :root => Rails.root.join('app', 'javascripts')
18
+ )
19
+ Fewer::App.new(:stylesheets,
20
+ :engine => Fewer::Engines::Css,
21
+ :root => Rails.root.join('app', 'stylesheets')
22
+ )
23
+
24
+ # config/routes.rb
25
+ match '/javascripts/:data.js', :to => Fewer::App[:javascripts]
26
+ match '/stylesheets/:data.css', :to => Fewer::App[:stylesheets]
27
+
28
+ # app/helpers/application_helper.rb
29
+ module ApplicationHelper
30
+ include Fewer::RailsHelpers
31
+ end
32
+
33
+ # app/views/layouts/application.html.erb
34
+ <%= fewer_javascripts_tag 'long', 'list', 'of/nested', 'js/files' %>
35
+ <%= fewer_stylesheets_tag 'some', 'css', 'files' %>
4
36
 
5
37
  ## How to use as a Rack app (config.ru example)
6
38
 
@@ -18,21 +50,6 @@ Rack middleware to bundle assets and help you make fewer HTTP requests.
18
50
 
19
51
  run app
20
52
 
21
- ## How to use in Rails 3 router
22
-
23
- match '/stylesheets/:data.css', :to => Fewer::App.new(
24
- :engine => Fewer::Engines::Less,
25
- :root => Rails.root.join('app', 'stylesheets')
26
- )
27
-
28
- ## How to use in Rails as middleware
29
-
30
- config.middleware.use Fewer::MiddleWare, {
31
- :engine => Fewer::Engines::Less,
32
- :mount => '/stylesheets',
33
- :root => Rails.root.join('app', 'stylesheets')
34
- }
35
-
36
53
  ## Copyright
37
54
 
38
55
  Copyright (c) 2010 Ben Pickles. See LICENSE for details.
data/Rakefile CHANGED
@@ -5,8 +5,8 @@ begin
5
5
  require 'jeweler'
6
6
  Jeweler::Tasks.new do |gem|
7
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.}
8
+ gem.summary = 'Fewer is a Rack endpoint to bundle and cache assets and help you make fewer HTTP requests.'
9
+ gem.description = 'Fewer is a Rack endpoint to bundle and cache assets and help you make fewer HTTP requests. Fewer extracts and combines a list of assets encoded in the URL and serves the response with far-future HTTP caching headers.'
10
10
  gem.email = 'spideryoung@gmail.com'
11
11
  gem.homepage = 'http://github.com/benpickles/fewer'
12
12
  gem.authors = ['Ben Pickles']
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
data/fewer.gemspec CHANGED
@@ -5,12 +5,12 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{fewer}
8
- s.version = "0.1.0"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Ben Pickles"]
12
- s.date = %q{2010-08-10}
13
- s.description = %q{Rack middleware to bundle assets and help you make fewer HTTP requests.}
12
+ s.date = %q{2010-08-17}
13
+ s.description = %q{Fewer is a Rack endpoint to bundle and cache assets and help you make fewer HTTP requests. Fewer extracts and combines a list of assets encoded in the URL and serves the response with far-future HTTP caching headers.}
14
14
  s.email = %q{spideryoung@gmail.com}
15
15
  s.extra_rdoc_files = [
16
16
  "LICENSE",
@@ -29,21 +29,23 @@ Gem::Specification.new do |s|
29
29
  "lib/fewer/engines/abstract.rb",
30
30
  "lib/fewer/engines/css.rb",
31
31
  "lib/fewer/engines/js.rb",
32
- "lib/fewer/engines/js_min.rb",
33
32
  "lib/fewer/engines/less.rb",
34
33
  "lib/fewer/errors.rb",
35
34
  "lib/fewer/middleware.rb",
35
+ "lib/fewer/rails_helpers.rb",
36
36
  "lib/fewer/serializer.rb",
37
37
  "test/app_test.rb",
38
38
  "test/engine_test.rb",
39
39
  "test/middleware_test.rb",
40
+ "test/templates/rounded.less",
41
+ "test/templates/style.less",
40
42
  "test/test_helper.rb"
41
43
  ]
42
44
  s.homepage = %q{http://github.com/benpickles/fewer}
43
45
  s.rdoc_options = ["--charset=UTF-8"]
44
46
  s.require_paths = ["lib"]
45
47
  s.rubygems_version = %q{1.3.7}
46
- s.summary = %q{Rack middleware to bundle assets and help you make fewer HTTP requests.}
48
+ s.summary = %q{Fewer is a Rack endpoint to bundle and cache assets and help you make fewer HTTP requests.}
47
49
  s.test_files = [
48
50
  "test/app_test.rb",
49
51
  "test/engine_test.rb",
data/lib/fewer.rb CHANGED
@@ -2,4 +2,5 @@ require 'fewer/app'
2
2
  require 'fewer/engines'
3
3
  require 'fewer/errors'
4
4
  require 'fewer/middleware'
5
+ require 'fewer/rails_helpers'
5
6
  require 'fewer/serializer'
data/lib/fewer/app.rb CHANGED
@@ -1,31 +1,47 @@
1
1
  module Fewer
2
2
  class App
3
- attr_reader :engine_klass, :cache, :root
3
+ class << self
4
+ def [](name)
5
+ apps[name]
6
+ end
7
+
8
+ def apps
9
+ @apps ||= {}
10
+ end
11
+ end
4
12
 
5
- def initialize(options = {})
13
+ attr_reader :cache, :engine_klass, :engine_options, :name, :root
14
+
15
+ def initialize(name, options = {})
6
16
  @engine_klass = options[:engine]
17
+ @engine_options = options[:engine_options] || {}
7
18
  @mount = options[:mount]
8
19
  @root = options[:root]
9
20
  @cache = options[:cache] || 3600 * 24 * 365
10
21
  raise 'You need to define an :engine class' unless @engine_klass
11
22
  raise 'You need to define a :root path' unless @root
23
+ self.class.apps[name] = self
12
24
  end
13
25
 
14
26
  def call(env)
15
- names = names_from_path(env['PATH_INFO'])
16
- engine = engine_klass.new(root, names)
27
+ eng = engine(names_from_path(env['PATH_INFO']))
17
28
  headers = {
18
- 'Content-Type' => engine.content_type,
19
- 'Cache-Control' => "public, max-age=#{cache}"
29
+ 'Content-Type' => eng.content_type,
30
+ 'Cache-Control' => "public, max-age=#{cache}",
31
+ 'Last-Modified' => eng.mtime.rfc2822
20
32
  }
21
33
 
22
- [200, headers, [engine.read]]
34
+ [200, headers, [eng.read]]
23
35
  rescue Fewer::MissingSourceFileError => e
24
36
  [404, { 'Content-Type' => 'text/plain' }, [e.message]]
25
37
  rescue => e
26
38
  [500, { 'Content-Type' => 'text/plain' }, ["#{e.class}: #{e.message}"]]
27
39
  end
28
40
 
41
+ def engine(names)
42
+ engine_klass.new(root, names, engine_options)
43
+ end
44
+
29
45
  private
30
46
  def names_from_path(path)
31
47
  encoded = File.basename(path, '.*')
data/lib/fewer/engines.rb CHANGED
@@ -3,7 +3,6 @@ module Fewer
3
3
  autoload :Abstract, 'fewer/engines/abstract'
4
4
  autoload :Css, 'fewer/engines/css'
5
5
  autoload :Js, 'fewer/engines/js'
6
- autoload :JsMin, 'fewer/engines/js_min'
7
6
  autoload :Less, 'fewer/engines/less'
8
7
  end
9
8
  end
@@ -1,11 +1,15 @@
1
1
  module Fewer
2
2
  module Engines
3
3
  class Abstract
4
- attr_reader :names, :root
4
+ SANITISE_REGEXP = /^#{File::Separator}|\.\.#{File::Separator}/
5
5
 
6
- def initialize(root, names)
6
+ attr_reader :names, :options, :root
7
+
8
+ def initialize(root, names, options = {})
7
9
  @root = root
8
- @names = names
10
+ @names = names.is_a?(Array) ? names : [names]
11
+ @options = options
12
+ sanitise_names!
9
13
  check_paths!
10
14
  end
11
15
 
@@ -13,18 +17,22 @@ module Fewer
13
17
  'text/plain'
14
18
  end
15
19
 
20
+ def encoded
21
+ Serializer.encode(names)
22
+ end
23
+
16
24
  def extension
17
25
  end
18
26
 
19
27
  def mtime
20
28
  paths.map { |path|
21
- File.mtime(path).to_i
22
- }.sort.last
29
+ File.mtime(path)
30
+ }.max
23
31
  end
24
32
 
25
33
  def paths
26
- names.map { |name|
27
- File.join(root, "#{File.basename(name.to_s)}#{extension}")
34
+ @paths ||= names.map { |name|
35
+ File.join(root, "#{name}#{extension}")
28
36
  }
29
37
  end
30
38
 
@@ -41,6 +49,12 @@ module Fewer
41
49
  raise Fewer::MissingSourceFileError.new("Missing source file#{'s' if missing.size > 1}:\n#{files}")
42
50
  end
43
51
  end
52
+
53
+ def sanitise_names!
54
+ names.map! { |name|
55
+ name.to_s.gsub(SANITISE_REGEXP, '')
56
+ }
57
+ end
44
58
  end
45
59
  end
46
60
  end
@@ -1,3 +1,5 @@
1
+ autoload :Closure, 'closure-compiler'
2
+
1
3
  module Fewer
2
4
  module Engines
3
5
  class Js < Abstract
@@ -8,6 +10,14 @@ module Fewer
8
10
  def extension
9
11
  '.js'
10
12
  end
13
+
14
+ def read
15
+ if options[:min]
16
+ ::Closure::Compiler.new.compress(super)
17
+ else
18
+ super
19
+ end
20
+ end
11
21
  end
12
22
  end
13
23
  end
@@ -12,7 +12,9 @@ module Fewer
12
12
  end
13
13
 
14
14
  def read
15
- ::Less::Engine.new(super).to_css
15
+ Dir.chdir root do
16
+ ::Less::Engine.new(super).to_css
17
+ end
16
18
  end
17
19
  end
18
20
  end
@@ -1,6 +1,7 @@
1
1
  module Fewer
2
2
  class MiddleWare
3
- def initialize(app, options = {})
3
+ def initialize(app, name, options = {})
4
+ @name = name
4
5
  @options = options
5
6
  @app = app
6
7
  @mount = @options[:mount]
@@ -8,7 +9,7 @@ module Fewer
8
9
 
9
10
  def call(env)
10
11
  if env['PATH_INFO'] =~ /^#{@mount}/
11
- App.new(@options).call(env)
12
+ App.new(@name, @options).call(env)
12
13
  else
13
14
  @app.call(env)
14
15
  end
@@ -0,0 +1,13 @@
1
+ module Fewer
2
+ module RailsHelpers
3
+ def fewer_javascripts_tag(*names)
4
+ engine = Fewer::App[:javascripts].engine(names)
5
+ javascript_include_tag "#{engine.encoded}.js?#{engine.mtime.to_i}"
6
+ end
7
+
8
+ def fewer_stylesheets_tag(*names)
9
+ engine = Fewer::App[:stylesheets].engine(names)
10
+ stylesheet_link_tag "#{engine.encoded}.css?#{engine.mtime.to_i}"
11
+ end
12
+ end
13
+ end
data/test/app_test.rb CHANGED
@@ -8,25 +8,31 @@ class AppTest < Test::Unit::TestCase
8
8
  @engine = stub(
9
9
  :check_request_extension => true,
10
10
  :content_type => 'text/css',
11
+ :mtime => Time.utc(2010, 8, 17, 21, 5, 24),
11
12
  :read => 'content'
12
13
  )
13
14
  @engine_klass = stub(:new => @engine)
14
- @fewer_app = Fewer::App.new({
15
+ @fewer_app = Fewer::App.new(:name,
15
16
  :engine => @engine_klass,
16
17
  :root => 'root'
17
- })
18
+ )
18
19
  @browser = Rack::Test::Session.new(Rack::MockSession.new(@fewer_app))
19
20
  end
20
21
 
22
+ def test_can_retrieve_app_by_name
23
+ assert_nil Fewer::App[:not_name]
24
+ assert_equal @fewer_app, Fewer::App[:name]
25
+ end
26
+
21
27
  def test_initialises_a_new_engine_with_a_single_file
22
28
  file = 'file'
23
- @engine_klass.expects(:new).with('root', file).returns(@engine)
29
+ @engine_klass.expects(:new).with('root', file, {}).returns(@engine)
24
30
  @browser.get "/path/#{encode(file)}.css"
25
31
  end
26
32
 
27
33
  def test_initialises_a_new_engine_with_multiple_files
28
34
  files = ['file1', 'file2']
29
- @engine_klass.expects(:new).with('root', files).returns(@engine)
35
+ @engine_klass.expects(:new).with('root', files, {}).returns(@engine)
30
36
  @browser.get "/path/#{encode(files)}.css"
31
37
  end
32
38
 
@@ -40,6 +46,11 @@ class AppTest < Test::Unit::TestCase
40
46
  assert_equal 'public, max-age=31536000', @browser.last_response.headers['Cache-Control']
41
47
  end
42
48
 
49
+ def test_responds_with_last_modified
50
+ @browser.get "/path/#{encode('file')}.css"
51
+ assert_equal 'Tue, 17 Aug 2010 21:05:24 -0000', @browser.last_response.headers['Last-Modified']
52
+ end
53
+
43
54
  def test_responds_with_bundled_content
44
55
  @engine.expects(:read).returns('content')
45
56
  @browser.get "/path/#{encode('file')}.css"
data/test/engine_test.rb CHANGED
@@ -1,10 +1,24 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class EngineTest < Test::Unit::TestCase
4
+ include TestHelper
5
+
4
6
  def test_sanitise_paths
5
- bad_files = ['./../private.txt', '/etc/passwd']
6
- engine = engine_klass_no_checking.new('./happy-place', bad_files)
7
- assert_equal ['./happy-place/private.txt', './happy-place/passwd'], engine.paths
7
+ files = [
8
+ 'dir/ectory/file.js',
9
+ 'sub/dir/ectory/file.js',
10
+ './../private.txt',
11
+ '/etc/passwd',
12
+ '/etc/../passwd'
13
+ ]
14
+ engine = engine_klass_no_checking.new('./happy-place', files)
15
+ assert_equal [
16
+ './happy-place/dir/ectory/file.js',
17
+ './happy-place/sub/dir/ectory/file.js',
18
+ './happy-place/./private.txt',
19
+ './happy-place/etc/passwd',
20
+ './happy-place/etc/passwd'
21
+ ], engine.paths
8
22
  end
9
23
 
10
24
  def test_stringify_paths
@@ -12,6 +26,31 @@ class EngineTest < Test::Unit::TestCase
12
26
  assert_equal ['./symbol', './ab'], engine.paths
13
27
  end
14
28
 
29
+ def test_converts_names_to_an_array
30
+ engine = engine_klass_no_checking.new(template_root, 'style')
31
+ assert_equal ['style'], engine.names
32
+ end
33
+
34
+ def test_raises_error_for_missing_file
35
+ assert_raises Fewer::MissingSourceFileError do
36
+ Fewer::Engines::Abstract.new(template_root, ['does-not-exist'])
37
+ end
38
+ end
39
+
40
+ def test_can_deal_with_encoding_for_you
41
+ names = ['a', 'b']
42
+ engine = engine_klass_no_checking.new(template_root, names)
43
+ Fewer::Serializer.expects(:encode).with(names)
44
+ engine.encoded
45
+ end
46
+
47
+ def test_less_import_command
48
+ engine = Fewer::Engines::Less.new(template_root, ['style'])
49
+ assert_nothing_raised do
50
+ engine.read
51
+ end
52
+ end
53
+
15
54
  private
16
55
  def engine_klass(&block)
17
56
  Class.new(Fewer::Engines::Abstract, &block)
@@ -8,13 +8,14 @@ class MiddlewareTest < Test::Unit::TestCase
8
8
  @engine = stub(
9
9
  :check_request_extension => true,
10
10
  :content_type => 'text/css',
11
+ :mtime => Time.utc(2010, 8, 17, 21, 5, 24),
11
12
  :read => 'content'
12
13
  )
13
14
  Fewer::Engines::Css.stubs(:new).returns(@engine)
14
15
 
15
16
  @app = Rack::Builder.new do
16
17
  map '/' do
17
- use Fewer::MiddleWare,
18
+ use Fewer::MiddleWare, :name,
18
19
  :root => '/some/root/path',
19
20
  :engine => Fewer::Engines::Css,
20
21
  :mount => '/css'
@@ -0,0 +1,5 @@
1
+ .rounded_corners (@radius: 5px) {
2
+ -moz-border-radius: @radius;
3
+ -webkit-border-radius: @radius;
4
+ border-radius: @radius;
5
+ }
@@ -0,0 +1,5 @@
1
+ @import "rounded";
2
+
3
+ div {
4
+ .rounded_corners(10px);
5
+ }
data/test/test_helper.rb CHANGED
@@ -11,6 +11,7 @@ rescue LoadError
11
11
  end
12
12
 
13
13
  require 'fewer'
14
+ require 'less'
14
15
 
15
16
  module TestHelper
16
17
  private
@@ -21,4 +22,8 @@ module TestHelper
21
22
  def encode(obj)
22
23
  Fewer::Serializer.encode(obj)
23
24
  end
25
+
26
+ def template_root
27
+ File.expand_path('../templates', __FILE__)
28
+ end
24
29
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fewer
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 23
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 1
8
+ - 2
9
9
  - 0
10
- version: 0.1.0
10
+ version: 0.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Ben Pickles
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-08-10 00:00:00 +01:00
18
+ date: 2010-08-17 00:00:00 +01:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -32,7 +32,7 @@ dependencies:
32
32
  version: "0"
33
33
  type: :runtime
34
34
  version_requirements: *id001
35
- description: Rack middleware to bundle assets and help you make fewer HTTP requests.
35
+ description: Fewer is a Rack endpoint to bundle and cache assets and help you make fewer HTTP requests. Fewer extracts and combines a list of assets encoded in the URL and serves the response with far-future HTTP caching headers.
36
36
  email: spideryoung@gmail.com
37
37
  executables: []
38
38
 
@@ -54,14 +54,16 @@ files:
54
54
  - lib/fewer/engines/abstract.rb
55
55
  - lib/fewer/engines/css.rb
56
56
  - lib/fewer/engines/js.rb
57
- - lib/fewer/engines/js_min.rb
58
57
  - lib/fewer/engines/less.rb
59
58
  - lib/fewer/errors.rb
60
59
  - lib/fewer/middleware.rb
60
+ - lib/fewer/rails_helpers.rb
61
61
  - lib/fewer/serializer.rb
62
62
  - test/app_test.rb
63
63
  - test/engine_test.rb
64
64
  - test/middleware_test.rb
65
+ - test/templates/rounded.less
66
+ - test/templates/style.less
65
67
  - test/test_helper.rb
66
68
  has_rdoc: true
67
69
  homepage: http://github.com/benpickles/fewer
@@ -96,7 +98,7 @@ rubyforge_project:
96
98
  rubygems_version: 1.3.7
97
99
  signing_key:
98
100
  specification_version: 3
99
- summary: Rack middleware to bundle assets and help you make fewer HTTP requests.
101
+ summary: Fewer is a Rack endpoint to bundle and cache assets and help you make fewer HTTP requests.
100
102
  test_files:
101
103
  - test/app_test.rb
102
104
  - test/engine_test.rb
@@ -1,19 +0,0 @@
1
- autoload :Closure, 'closure-compiler'
2
-
3
- module Fewer
4
- module Engines
5
- class JsMin < Abstract
6
- def content_type
7
- 'application/x-javascript'
8
- end
9
-
10
- def extension
11
- '.js'
12
- end
13
-
14
- def read
15
- ::Closure::Compiler.new.compress(super)
16
- end
17
- end
18
- end
19
- end