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.
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