fewer 0.1.0 → 0.2.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/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