sphere 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/.gitignore +1 -0
  2. data/README.markdown +84 -0
  3. data/Rakefile +26 -0
  4. data/VERSION +1 -0
  5. data/lib/sphere.rb +32 -0
  6. data/lib/sphere/backends/base.rb +44 -0
  7. data/lib/sphere/backends/closure.rb +29 -0
  8. data/lib/sphere/backends/yui.rb +28 -0
  9. data/lib/sphere/config.rb +55 -0
  10. data/lib/sphere/css_minifier.rb +29 -0
  11. data/lib/sphere/helper.rb +38 -0
  12. data/lib/sphere/package/base.rb +87 -0
  13. data/lib/sphere/package/javascript.rb +17 -0
  14. data/lib/sphere/package/stylesheet.rb +17 -0
  15. data/lib/sphere/packager.rb +48 -0
  16. data/lib/sphere/tasks/commit.rake +22 -0
  17. data/lib/sphere/tasks/compass.rake +19 -0
  18. data/lib/sphere/tasks/sphere.rake +31 -0
  19. data/lib/sphere/tasks/upload.rake +37 -0
  20. data/rails/init.rb +2 -0
  21. data/spec/scenario/config/boot.rb +110 -0
  22. data/spec/scenario/config/database.yml +0 -0
  23. data/spec/scenario/config/environment.rb +5 -0
  24. data/spec/scenario/config/sphere.yml +16 -0
  25. data/spec/scenario/public/javascripts/custom/file_a.js +9 -0
  26. data/spec/scenario/public/javascripts/custom/file_b.js +8 -0
  27. data/spec/scenario/public/javascripts/custom/ignore.js +8 -0
  28. data/spec/scenario/public/javascripts/file.js +8 -0
  29. data/spec/scenario/public/stylesheets/compiled/custom.css +1 -0
  30. data/spec/scenario/public/stylesheets/compiled/print.css +40 -0
  31. data/spec/spec_helper.rb +50 -0
  32. data/spec/sphere/config_spec.rb +27 -0
  33. data/spec/sphere/css_minifier_spec.rb +16 -0
  34. data/spec/sphere/helper_spec.rb +57 -0
  35. data/spec/sphere/package/base_spec.rb +46 -0
  36. data/spec/sphere/package/javascript_spec.rb +19 -0
  37. data/spec/sphere/package/stylesheet_spec.rb +19 -0
  38. data/spec/sphere/packager_spec.rb +44 -0
  39. data/spec/sphere_spec.rb +16 -0
  40. data/sphere.gemspec +90 -0
  41. 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