sphere 0.1.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 +1 -0
- data/README.markdown +84 -0
- data/Rakefile +26 -0
- data/VERSION +1 -0
- data/lib/sphere.rb +32 -0
- data/lib/sphere/backends/base.rb +44 -0
- data/lib/sphere/backends/closure.rb +29 -0
- data/lib/sphere/backends/yui.rb +28 -0
- data/lib/sphere/config.rb +55 -0
- data/lib/sphere/css_minifier.rb +29 -0
- data/lib/sphere/helper.rb +38 -0
- data/lib/sphere/package/base.rb +87 -0
- data/lib/sphere/package/javascript.rb +17 -0
- data/lib/sphere/package/stylesheet.rb +17 -0
- data/lib/sphere/packager.rb +48 -0
- data/lib/sphere/tasks/commit.rake +22 -0
- data/lib/sphere/tasks/compass.rake +19 -0
- data/lib/sphere/tasks/sphere.rake +31 -0
- data/lib/sphere/tasks/upload.rake +37 -0
- data/rails/init.rb +2 -0
- data/spec/scenario/config/boot.rb +110 -0
- data/spec/scenario/config/database.yml +0 -0
- data/spec/scenario/config/environment.rb +5 -0
- data/spec/scenario/config/sphere.yml +16 -0
- data/spec/scenario/public/javascripts/custom/file_a.js +9 -0
- data/spec/scenario/public/javascripts/custom/file_b.js +8 -0
- data/spec/scenario/public/javascripts/custom/ignore.js +8 -0
- data/spec/scenario/public/javascripts/file.js +8 -0
- data/spec/scenario/public/stylesheets/compiled/custom.css +1 -0
- data/spec/scenario/public/stylesheets/compiled/print.css +40 -0
- data/spec/spec_helper.rb +50 -0
- data/spec/sphere/config_spec.rb +27 -0
- data/spec/sphere/css_minifier_spec.rb +16 -0
- data/spec/sphere/helper_spec.rb +57 -0
- data/spec/sphere/package/base_spec.rb +46 -0
- data/spec/sphere/package/javascript_spec.rb +19 -0
- data/spec/sphere/package/stylesheet_spec.rb +19 -0
- data/spec/sphere/packager_spec.rb +44 -0
- data/spec/sphere_spec.rb +16 -0
- data/sphere.gemspec +90 -0
- metadata +112 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pkg
|
data/README.markdown
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# Sphere
|
2
|
+
|
3
|
+
Sphere is a asset packer and compressor, for Rails, built (primarily) to be used with Heroku + AWS/S3.
|
4
|
+
Inspired by [Jammit](http://documentcloud.github.com/jammit) and [Compass](http://wiki.github.com/chriseppstein/compass)
|
5
|
+
|
6
|
+
## Quick Start
|
7
|
+
|
8
|
+
### Create a `config/shere.yml` file and define your packages. Here an example file:
|
9
|
+
|
10
|
+
# Use packaged assets in views [production: true, other: false].
|
11
|
+
# Hint: Don't hard-code option this in the configuration file,
|
12
|
+
# instead use your config/environments/* files.
|
13
|
+
# Example:
|
14
|
+
# # config/environments/staging.rb
|
15
|
+
# # ...
|
16
|
+
# Sphere.config.package = true
|
17
|
+
# package: true
|
18
|
+
|
19
|
+
# Where (within Rails.public_path) to store packaged assets [assets -> public/assets]
|
20
|
+
asset_path: assets
|
21
|
+
|
22
|
+
# Compression backend [closure]
|
23
|
+
backend: closure
|
24
|
+
|
25
|
+
# Use compression when packaging assets [production: true, other: false]
|
26
|
+
compress: true
|
27
|
+
|
28
|
+
# Define your javascript packages here:
|
29
|
+
javascripts:
|
30
|
+
# Example assuming default settings (Rails.public_path: public, asset_path: assets):
|
31
|
+
# Fetch content from http://ajax.googleapis.com and merge/compress it with all files
|
32
|
+
# matching public/javascripts/vendor/*.js + public/javascripts/custom/*.js into public/assets/application.js
|
33
|
+
application:
|
34
|
+
- http://ajax.googleapis.com/ajax/libs/jquery/1.4.1/jquery.js
|
35
|
+
- javascripts/vendor/*.js
|
36
|
+
- javascripts/custom/*.js
|
37
|
+
|
38
|
+
# Define your stylesheet packages here:
|
39
|
+
stylesheets:
|
40
|
+
# Example assuming default settings (Rails.public_path: public, asset_path: assets):
|
41
|
+
# Merge/compress public/stylesheets/compiled/screen.css into public/assets/screen.css
|
42
|
+
# Merge/compress public/stylesheets/compiled/print.css into public/assets/print.css
|
43
|
+
screen:
|
44
|
+
- stylesheets/compiled/screen.css
|
45
|
+
print:
|
46
|
+
- stylesheets/compiled/print.css
|
47
|
+
|
48
|
+
### In your layout/application.html.erb
|
49
|
+
|
50
|
+
<%= include_stylesheets :screen, :media => 'screen, projection' %>
|
51
|
+
<%= include_stylesheets :print, :media => 'print' %>
|
52
|
+
<%= include_javascripts :application %>
|
53
|
+
|
54
|
+
If the package_assets option is enabled (production), this will produce (given that config.action_controller.asset_host = "http://my-asset-host-bucket.s3.amazonaws.com"):
|
55
|
+
|
56
|
+
<link href="http://my-asset-host-bucket.s3.amazonaws.com/assets/screen.css" media="screen, projection" rel="stylesheet" type="text/css" />
|
57
|
+
<link href="http://my-asset-host-bucket.s3.amazonaws.com/assets/print.css" media="print" rel="stylesheet" type="text/css" />
|
58
|
+
<script src="http://my-asset-host-bucket.s3.amazonaws.com/assets/application.js" type="text/javascript"></script>
|
59
|
+
|
60
|
+
If the package_assets option is disabled (development), this will produce:
|
61
|
+
|
62
|
+
<link href="/stylesheets/compiled/screen.css" media="screen, projection" rel="stylesheet" type="text/css" />
|
63
|
+
<link href="/stylesheets/compiled/print.css" media="print" rel="stylesheet" type="text/css" />
|
64
|
+
|
65
|
+
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.1/jquery.js" type="text/javascript"></script>
|
66
|
+
<script src="/javascripts/vendor/jquery.enumerable.js" type="text/javascript"></script>
|
67
|
+
<script src="/javascripts/custom/rails.js" type="text/javascript"></script>
|
68
|
+
|
69
|
+
## Include Rake Tasks
|
70
|
+
|
71
|
+
Create a new `lib/tasks/sphere.rake` file in your Rails application. Include the following lines:
|
72
|
+
|
73
|
+
# Adds the sphere:compile task
|
74
|
+
load 'sphere/tasks/sphere.rake'
|
75
|
+
|
76
|
+
# [Optional] Extends the sphere:compile:css task; pre-compiles Compass/SASS files to CSS before packaging
|
77
|
+
load 'sphere/tasks/compass.rake'
|
78
|
+
|
79
|
+
# [Optional] Adds sphere:upload & sphere:deploy tasks for automated S3 upload
|
80
|
+
load 'sphere/tasks/upload.rake'
|
81
|
+
|
82
|
+
# [Optional] Adds sphere:commit:hook task you can run to ensure all assets are up-to-date.
|
83
|
+
# Just e.g. add "rake sphere:commit:hook -s" to your .git/hooks/pre-commit hook.
|
84
|
+
load 'sphere/tasks/commit.rake'
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spec/rake/spectask'
|
3
|
+
require 'spec/version'
|
4
|
+
|
5
|
+
desc 'Default: run specs.'
|
6
|
+
task :default => :spec
|
7
|
+
|
8
|
+
Spec::Rake::SpecTask.new do |t|
|
9
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
10
|
+
t.spec_opts = ['--colour', '--format', 'profile', '--timeout', '20']
|
11
|
+
end
|
12
|
+
|
13
|
+
begin
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gemspec|
|
16
|
+
gemspec.name = "sphere"
|
17
|
+
gemspec.summary = "Asset packer and compressor, for Rails"
|
18
|
+
gemspec.description = "Built (primarily) to be used with Heroku + AWS/S3"
|
19
|
+
gemspec.email = "dimitrij@blacksquaremedia.com"
|
20
|
+
gemspec.homepage = "http://github.com/dim/sphere"
|
21
|
+
gemspec.authors = ["Dimitrij Denissenko"]
|
22
|
+
end
|
23
|
+
Jeweler::GemcutterTasks.new
|
24
|
+
rescue LoadError
|
25
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
26
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/sphere.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
|
3
|
+
module Sphere
|
4
|
+
autoload :Config, 'sphere/config'
|
5
|
+
autoload :Packager, 'sphere/packager'
|
6
|
+
autoload :Helper, 'sphere/helper'
|
7
|
+
autoload :CSSMinifier, 'sphere/css_minifier'
|
8
|
+
|
9
|
+
module Package
|
10
|
+
autoload :Base, 'sphere/package/base'
|
11
|
+
autoload :Javascript, 'sphere/package/javascript'
|
12
|
+
autoload :Stylesheet, 'sphere/package/stylesheet'
|
13
|
+
end
|
14
|
+
|
15
|
+
module Backends
|
16
|
+
autoload :Base, 'sphere/backends/base'
|
17
|
+
autoload :Closure, 'sphere/backends/closure'
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.config
|
21
|
+
@config ||= Config.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.reset!
|
25
|
+
@config = nil
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.packager
|
30
|
+
Sphere::Packager.instance
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module Sphere
|
4
|
+
module Backends
|
5
|
+
class Base
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def default_options
|
10
|
+
{}
|
11
|
+
end
|
12
|
+
|
13
|
+
def uri
|
14
|
+
raise "Abstract Method"
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :content
|
20
|
+
|
21
|
+
def initialize(content)
|
22
|
+
@content = content
|
23
|
+
end
|
24
|
+
|
25
|
+
def compressed(options = {})
|
26
|
+
params = create_params!(self.class.default_options.merge(options))
|
27
|
+
response = Net::HTTP.post_form self.class.uri, params
|
28
|
+
unless response.code == '200'
|
29
|
+
STDERR.puts "ERROR during compilation. Response:"
|
30
|
+
STDERR.puts response.body
|
31
|
+
raise "Aborting!"
|
32
|
+
end
|
33
|
+
response.body
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def create_params!(options)
|
39
|
+
options.to_a
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module Sphere
|
4
|
+
module Backends
|
5
|
+
class Closure < Base
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def default_options
|
10
|
+
{ :compilation_level => 'SIMPLE_OPTIMIZATIONS',
|
11
|
+
:output_format => 'text',
|
12
|
+
:output_info => 'compiled_code' }
|
13
|
+
end
|
14
|
+
|
15
|
+
def uri
|
16
|
+
@uri ||= URI.parse('http://closure-compiler.appspot.com/compile')
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def create_params!(options)
|
24
|
+
options.merge(:js_code => content).to_a
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module Sphere
|
4
|
+
module Backends
|
5
|
+
class YUI < Base
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def default_options
|
10
|
+
{ :type => 'JS' }
|
11
|
+
end
|
12
|
+
|
13
|
+
def uri
|
14
|
+
@uri ||= URI.parse('http://refresh-sf.com/yui/')
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def create_params!(options)
|
22
|
+
options.merge(:compresstext => content).to_a
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Sphere
|
2
|
+
class Config
|
3
|
+
attr_accessor :env, :root
|
4
|
+
attr_writer :config_file, :asset_path, :backend, :compress, :package
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
self.env = defined?(Rails) ? Rails.env.to_s : 'development'
|
8
|
+
self.root = defined?(Rails) ? Rails.root : Pathname.new('.')
|
9
|
+
end
|
10
|
+
|
11
|
+
def update!(&block)
|
12
|
+
block.call(self)
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def config_file
|
17
|
+
@config_file || root.join('config', 'sphere.yml')
|
18
|
+
end
|
19
|
+
|
20
|
+
def asset_path
|
21
|
+
@asset_path || public_path.join(definitions['asset_path'] || 'assets')
|
22
|
+
end
|
23
|
+
|
24
|
+
def backend
|
25
|
+
@backend || "Sphere::Backends::#{(definitions['backend'] || 'closure').classify}".constantize
|
26
|
+
end
|
27
|
+
|
28
|
+
def javascripts
|
29
|
+
definitions['javascripts'] || {}
|
30
|
+
end
|
31
|
+
|
32
|
+
def stylesheets
|
33
|
+
definitions['stylesheets'] || {}
|
34
|
+
end
|
35
|
+
|
36
|
+
def compress?
|
37
|
+
defined?(@compress) ? !!@compress : (definitions.key?('compress') ? !!definitions['compress'] : (self.env == 'production'))
|
38
|
+
end
|
39
|
+
|
40
|
+
def package?
|
41
|
+
defined?(@package) ? !!@package : (definitions.key?('package') ? !!definitions['package'] : (self.env == 'production'))
|
42
|
+
end
|
43
|
+
|
44
|
+
def public_path
|
45
|
+
@public_path ||= defined?(Rails) ? Pathname.new(Rails.public_path.to_s) : root.join('public')
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def definitions
|
51
|
+
@definitions ||= YAML.load_file(config_file)
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Sphere
|
2
|
+
class CSSMinifier < String
|
3
|
+
|
4
|
+
|
5
|
+
def initialize(css)
|
6
|
+
super(css.dup)
|
7
|
+
end
|
8
|
+
|
9
|
+
def minify!
|
10
|
+
gsub!(/\/\*.+?\*\//m, '')
|
11
|
+
strip!
|
12
|
+
gsub!(/(\A|\})(.+?)(\z|\{)/m) do |m|
|
13
|
+
prefix, tags, suffix = $1, $2, $3
|
14
|
+
"#{prefix}#{tags.split(',').map(&:strip).reject(&:blank?).join(',')}#{suffix}"
|
15
|
+
end
|
16
|
+
gsub!(/\{(.+?)\}/m) do |m|
|
17
|
+
styles = $1.split(';').map do |style|
|
18
|
+
key, values = style.split(':')
|
19
|
+
next nil unless key && values
|
20
|
+
"#{key.strip}:#{values.split(',').map(&:strip).reject(&:blank?).join(',')}"
|
21
|
+
end.compact.join(';')
|
22
|
+
"{#{styles};}"
|
23
|
+
end
|
24
|
+
gsub!(/[\r\n]/m, '')
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Sphere
|
2
|
+
module Helper
|
3
|
+
|
4
|
+
def include_javascripts(*package_names)
|
5
|
+
options = package_names.extract_options!
|
6
|
+
tags = package_names.map do |name|
|
7
|
+
if Sphere.config.package?
|
8
|
+
compute_public_path(name.to_s, Sphere.config.asset_path, Sphere::Package::Javascript.extension)
|
9
|
+
else
|
10
|
+
individual_includable_urls(name, :js, options)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
javascript_include_tag(*(tags.flatten << options))
|
14
|
+
end
|
15
|
+
|
16
|
+
def include_stylesheets(*package_names)
|
17
|
+
options = package_names.extract_options!
|
18
|
+
tags = package_names.map do |name|
|
19
|
+
if Sphere.config.package?
|
20
|
+
compute_public_path(name.to_s, Sphere.config.asset_path, Sphere::Package::Stylesheet.extension)
|
21
|
+
else
|
22
|
+
individual_includable_urls(name, :css, options)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
stylesheet_link_tag(*(tags.flatten << options))
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def individual_includable_urls(package_name, extension, options = {})
|
31
|
+
package = Sphere.packager.packages(extension.to_sym)[package_name.to_s]
|
32
|
+
(package ? package.sources : []).map do |source|
|
33
|
+
compute_public_path(source.to_s, '', extension.to_s).sub(/^\/\//, '/')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Sphere
|
2
|
+
module Package
|
3
|
+
class Base
|
4
|
+
|
5
|
+
def self.extension
|
6
|
+
raise "Abstract Method"
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :name
|
10
|
+
|
11
|
+
def initialize(name, sources)
|
12
|
+
@name = name
|
13
|
+
@sources = sources
|
14
|
+
end
|
15
|
+
|
16
|
+
def file
|
17
|
+
@file ||= Sphere.config.asset_path.join("#{name}.#{self.class.extension}")
|
18
|
+
end
|
19
|
+
|
20
|
+
def public_file_name
|
21
|
+
file.to_s.sub "#{Sphere.config.public_path}/", ''
|
22
|
+
end
|
23
|
+
|
24
|
+
def out_of_date?
|
25
|
+
!file.exist? || mtime > file.mtime
|
26
|
+
end
|
27
|
+
|
28
|
+
def compile(options = {})
|
29
|
+
unless out_of_date?
|
30
|
+
STDOUT.puts "unchanged #{public_file_name}" if options[:verbose] == true
|
31
|
+
return unless options[:force] == true
|
32
|
+
end
|
33
|
+
|
34
|
+
STDOUT.puts "\e[32m compile #{public_file_name}\e[0m" if options[:verbose] == true
|
35
|
+
content = ""
|
36
|
+
expanded_sources.each do |source|
|
37
|
+
content << open(source).read
|
38
|
+
end
|
39
|
+
content = compress(content) if Sphere.config.compress?
|
40
|
+
|
41
|
+
FileUtils.mkdir_p(File.dirname(file))
|
42
|
+
file.open('w') {|f| f.write(content) }
|
43
|
+
end
|
44
|
+
|
45
|
+
def sources
|
46
|
+
@sources.map do |source|
|
47
|
+
url?(source) ? source : relative_paths(source)
|
48
|
+
end.flatten
|
49
|
+
end
|
50
|
+
|
51
|
+
def source_files
|
52
|
+
expanded_sources - source_urls
|
53
|
+
end
|
54
|
+
|
55
|
+
def source_urls
|
56
|
+
expanded_sources.select {|s| url?(s) }
|
57
|
+
end
|
58
|
+
|
59
|
+
protected
|
60
|
+
|
61
|
+
def expanded_sources
|
62
|
+
sources.map do |source|
|
63
|
+
url?(source) ? source : Sphere.config.public_path.join(source)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def relative_paths(glob)
|
68
|
+
Dir[Sphere.config.public_path.join(glob)].sort.map do |path|
|
69
|
+
path.sub "#{Sphere.config.public_path}/", ''
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def mtime
|
74
|
+
(source_files.map(&:mtime) + [Sphere.config.config_file.mtime]).max
|
75
|
+
end
|
76
|
+
|
77
|
+
def compress(content)
|
78
|
+
content
|
79
|
+
end
|
80
|
+
|
81
|
+
def url?(source)
|
82
|
+
source =~ /^https?\:/
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|