rapper_lite 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. data/Gemfile +10 -0
  2. data/Gemfile.lock +31 -0
  3. data/LICENSE.txt +20 -0
  4. data/README.markdown +65 -0
  5. data/Rakefile +31 -0
  6. data/VERSION +1 -0
  7. data/bin/rapper_lite +19 -0
  8. data/lib/rapper_lite/compressors.rb +109 -0
  9. data/lib/rapper_lite/config.rb +61 -0
  10. data/lib/rapper_lite/utils.rb +19 -0
  11. data/lib/rapper_lite/versioning.rb +38 -0
  12. data/lib/rapper_lite.rb +50 -0
  13. data/lib/tasks.rb +37 -0
  14. data/lib/yui/css_compressor.rb +279 -0
  15. data/rapper_lite.gemspec +141 -0
  16. data/spec/fixtures/src/simple_1.css +4 -0
  17. data/spec/fixtures/src/simple_1.js +5 -0
  18. data/spec/fixtures/src/simple_2.css +4 -0
  19. data/spec/fixtures/src/subfolder/simple_2.js +5 -0
  20. data/spec/fixtures/testcases/combination/expected/base.css +1 -0
  21. data/spec/fixtures/testcases/combination/expected/base.js +1 -0
  22. data/spec/fixtures/testcases/combination/expected/base_combined.css +1 -0
  23. data/spec/fixtures/testcases/combination/expected/base_combined.js +1 -0
  24. data/spec/fixtures/testcases/combination/rapper.yml +26 -0
  25. data/spec/fixtures/testcases/concatenation/expected/base.css +8 -0
  26. data/spec/fixtures/testcases/concatenation/expected/base.js +10 -0
  27. data/spec/fixtures/testcases/concatenation/expected/base_combined.css +12 -0
  28. data/spec/fixtures/testcases/concatenation/expected/base_combined.js +15 -0
  29. data/spec/fixtures/testcases/concatenation/rapper.yml +26 -0
  30. data/spec/fixtures/yui_css/background-position.css +2 -0
  31. data/spec/fixtures/yui_css/background-position.css.min +1 -0
  32. data/spec/fixtures/yui_css/box-model-hack.css +9 -0
  33. data/spec/fixtures/yui_css/box-model-hack.css.min +1 -0
  34. data/spec/fixtures/yui_css/bug2527974.css +9 -0
  35. data/spec/fixtures/yui_css/bug2527974.css.min +1 -0
  36. data/spec/fixtures/yui_css/bug2527991.css +19 -0
  37. data/spec/fixtures/yui_css/bug2527991.css.min +1 -0
  38. data/spec/fixtures/yui_css/bug2527998.css +4 -0
  39. data/spec/fixtures/yui_css/bug2527998.css.min +1 -0
  40. data/spec/fixtures/yui_css/bug2528034.css +5 -0
  41. data/spec/fixtures/yui_css/bug2528034.css.min +1 -0
  42. data/spec/fixtures/yui_css/charset-media.css +9 -0
  43. data/spec/fixtures/yui_css/charset-media.css.min +1 -0
  44. data/spec/fixtures/yui_css/color.css +7 -0
  45. data/spec/fixtures/yui_css/color.css.min +1 -0
  46. data/spec/fixtures/yui_css/comment.css +3 -0
  47. data/spec/fixtures/yui_css/comment.css.min +1 -0
  48. data/spec/fixtures/yui_css/concat-charset.css +15 -0
  49. data/spec/fixtures/yui_css/concat-charset.css.min +1 -0
  50. data/spec/fixtures/yui_css/decimals.css +3 -0
  51. data/spec/fixtures/yui_css/decimals.css.min +1 -0
  52. data/spec/fixtures/yui_css/dollar-header.css +7 -0
  53. data/spec/fixtures/yui_css/dollar-header.css.min +3 -0
  54. data/spec/fixtures/yui_css/font-face.css +6 -0
  55. data/spec/fixtures/yui_css/font-face.css.min +1 -0
  56. data/spec/fixtures/yui_css/ie5mac.css +5 -0
  57. data/spec/fixtures/yui_css/ie5mac.css.min +1 -0
  58. data/spec/fixtures/yui_css/media-empty-class.css +16 -0
  59. data/spec/fixtures/yui_css/media-empty-class.css.min +1 -0
  60. data/spec/fixtures/yui_css/media-multi.css +5 -0
  61. data/spec/fixtures/yui_css/media-multi.css.min +1 -0
  62. data/spec/fixtures/yui_css/media-test.css +5 -0
  63. data/spec/fixtures/yui_css/media-test.css.min +1 -0
  64. data/spec/fixtures/yui_css/opacity-filter.css +14 -0
  65. data/spec/fixtures/yui_css/opacity-filter.css.min +1 -0
  66. data/spec/fixtures/yui_css/preserve-new-line.css +6 -0
  67. data/spec/fixtures/yui_css/preserve-new-line.css.min +3 -0
  68. data/spec/fixtures/yui_css/preserve-strings.css +7 -0
  69. data/spec/fixtures/yui_css/preserve-strings.css.min +1 -0
  70. data/spec/fixtures/yui_css/preserve_string.css +7 -0
  71. data/spec/fixtures/yui_css/preserve_string.css.min +1 -0
  72. data/spec/fixtures/yui_css/pseudo-first.css +16 -0
  73. data/spec/fixtures/yui_css/pseudo-first.css.min +1 -0
  74. data/spec/fixtures/yui_css/pseudo.css +4 -0
  75. data/spec/fixtures/yui_css/pseudo.css.min +1 -0
  76. data/spec/fixtures/yui_css/special-comments.css +13 -0
  77. data/spec/fixtures/yui_css/special-comments.css.min +9 -0
  78. data/spec/fixtures/yui_css/star-underscore-hacks.css +5 -0
  79. data/spec/fixtures/yui_css/star-underscore-hacks.css.min +1 -0
  80. data/spec/fixtures/yui_css/string-in-comment.css +8 -0
  81. data/spec/fixtures/yui_css/string-in-comment.css.min +1 -0
  82. data/spec/fixtures/yui_css/zeros.css +6 -0
  83. data/spec/fixtures/yui_css/zeros.css.min +1 -0
  84. data/spec/rapper_lite_spec.rb +41 -0
  85. data/spec/spec_helper.rb +23 -0
  86. data/spec/vendor_spec.rb +23 -0
  87. metadata +203 -0
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "http://rubygems.org"
2
+
3
+ group :development do
4
+ gem "rake", "~> 0.9.2"
5
+ gem "rspec", "~> 1.3.2"
6
+ gem "yard"
7
+ gem "rdiscount", "~> 1.6.8"
8
+ gem "jeweler", "~> 1.6.4"
9
+ gem "yui-compressor", "~> 0.9.6"
10
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,31 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ POpen4 (0.1.4)
5
+ Platform (>= 0.4.0)
6
+ open4
7
+ Platform (0.4.0)
8
+ git (1.2.5)
9
+ jeweler (1.6.4)
10
+ bundler (~> 1.0)
11
+ git (>= 1.2.5)
12
+ rake
13
+ open4 (1.0.1)
14
+ rake (0.9.2)
15
+ rdiscount (1.6.8)
16
+ rspec (1.3.2)
17
+ yard (0.6.4)
18
+ yui-compressor (0.9.6)
19
+ POpen4 (>= 0.1.4)
20
+
21
+ PLATFORMS
22
+ java
23
+ ruby
24
+
25
+ DEPENDENCIES
26
+ jeweler (~> 1.6.4)
27
+ rake (~> 0.9.2)
28
+ rdiscount (~> 1.6.8)
29
+ rspec (~> 1.3.2)
30
+ yard
31
+ yui-compressor (~> 0.9.6)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Tyson Tate
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,65 @@
1
+ # rapper_lite #
2
+
3
+ Bare-bones static asset packager and compressor. Currently supports CSS and JavaScript. Uses MD5 versioning to avoid re-compressing packages that don't need to be re-compressed. Uses a simple config file so that you don't have to wrangle wacky comment DSLs in your source code just to join and compress a few files.
4
+
5
+ ## Packaging assets without wanting to claw your eyes out
6
+
7
+ 1. Create a config file:
8
+
9
+ --- !omap
10
+ - root: public/javascripts/
11
+ - destination: public/assets/
12
+ - compress: true
13
+ - css: !omap
14
+ - base: !omap
15
+ - files:
16
+ - reset
17
+ - layout
18
+ - typography
19
+ - colores
20
+ - version: 683e
21
+ - js: !omap
22
+ - mootools: !omap
23
+ - files:
24
+ - mootools-core
25
+ - mootools-more
26
+ - version: f3d9
27
+ - base_combined: !omap
28
+ - files:
29
+ - preloader
30
+ - +mootools
31
+ - widgets
32
+ - version: ccfc
33
+
34
+ 2. Load `rapper` with the config path:
35
+
36
+ engine = Rapper::Engine.new( "config/rapper.yml" )
37
+
38
+ 3. Then package the assets:
39
+
40
+ engine.package
41
+
42
+ # Development
43
+
44
+ Rapper's got a Gemfile. You know what to do.
45
+
46
+ bundle package
47
+ bundle exec rake spec
48
+
49
+ ## Version history
50
+
51
+ * **0.0.1** - Initial release.
52
+
53
+ ## Contributing to rapper_lite
54
+
55
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
56
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
57
+ * Fork the project
58
+ * Start a feature/bugfix branch
59
+ * Commit and push until you are happy with your contribution
60
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
61
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
62
+
63
+ ## Copyright
64
+
65
+ Copyright (c) 2011 Tyson Tate. See LICENSE.txt for further details.
data/Rakefile ADDED
@@ -0,0 +1,31 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ begin
5
+ Bundler.setup(:default, :development)
6
+ rescue Bundler::BundlerError => e
7
+ $stderr.puts e.message
8
+ $stderr.puts "Run `bundle install` to install missing gems"
9
+ exit e.status_code
10
+ end
11
+ require 'rake'
12
+
13
+ require 'jeweler'
14
+ Jeweler::Tasks.new do |gem|
15
+ gem.name = "rapper_lite"
16
+ gem.homepage = "http://tysontate.github.com/rapper_lite/"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{Simple static asset packaging.}
19
+ gem.description = %Q{Simple static asset packaging. Compresses files only when they're updated.}
20
+ gem.email = "tyson@tysontate.com"
21
+ gem.authors = ["Tyson Tate"]
22
+ end
23
+ Jeweler::RubygemsDotOrgTasks.new
24
+
25
+ require 'spec'
26
+ require 'spec/rake/spectask'
27
+ Spec::Rake::SpecTask.new(:spec) do |spec|
28
+ spec.spec_files = FileList['spec/**/*_spec.rb']
29
+ end
30
+
31
+ task :default => :spec
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/bin/rapper_lite ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ require File.join( File.expand_path( File.dirname(__FILE__) ), '../lib/rapper_lite' )
4
+
5
+ def config_path
6
+ return ARGV[0] if ARGV[0]
7
+ ["", "config/"].each do |folder|
8
+ ["rapper.yml", "assets.yml"].each do |file|
9
+ path = File.expand_path( file, folder )
10
+ return path if File.exists?( path )
11
+ end
12
+ end
13
+ raise "No config file found."
14
+ end
15
+
16
+ config = config_path
17
+ puts "Packaging assets, using: #{config}"
18
+ RapperLite::Engine.new( config_path ).package
19
+ puts "Done!"
@@ -0,0 +1,109 @@
1
+ require File.expand_path( File.dirname( __FILE__ ) + "/../yui/css_compressor.rb" )
2
+ require 'fileutils'
3
+ begin
4
+ require "yui/compressor"
5
+ rescue LoadError; end
6
+
7
+ # Compression handlers for various types of assets. And by "various" I mean
8
+ # JavaScript and CSS.
9
+ module RapperLite::Compressors
10
+
11
+ # Compress a file in-place. Relies on the file's suffix to determine type.
12
+ def compress( file )
13
+ opts = {}
14
+ # TODO: Someday this goes away.
15
+ opts = self.yui_config if file =~ /\.js/
16
+ RapperLite::Compressors::Compressor.compress( file, opts )
17
+ end
18
+
19
+ protected
20
+
21
+ # Base class for a compression handler.
22
+ class Compressor
23
+ class << self
24
+
25
+ # Compress a file. Raises an error if it doesn't know how to compress a
26
+ # file with the given file's file extension.
27
+ def compress( file_path, opts={} )
28
+ unless compressor = @extensions[File.extname( file_path )]
29
+ raise "RapperLite doesn't know how to compress #{file_path}"
30
+ end
31
+
32
+ compressor.do_compress( file_path, opts )
33
+ end
34
+
35
+ protected
36
+
37
+ attr_accessor :extensions
38
+
39
+ # Register `self` as a file compressor for the given file extension.
40
+ def register( extension )
41
+ superclass.extensions ||= {}
42
+ superclass.extensions[extension] = self
43
+ end
44
+
45
+ def do_compress( file_path )
46
+ raise NotImplementedError
47
+ end
48
+
49
+ def read_file( file_path )
50
+ File.read( file_path )
51
+ end
52
+
53
+ # Get a writable file instance with 0644 permissions.
54
+ def writable_file( file_path )
55
+ File.new( file_path, 'w', 0644 )
56
+ end
57
+ end
58
+ end
59
+
60
+ # Use Richard Hulse's Ruby port of the YUI CSS Compressor to compress the
61
+ # contents of a CSS file.
62
+ class CSSCompressor < Compressor
63
+ register ".css"
64
+
65
+ def self.do_compress( file_path, opts={} )
66
+ return unless compressor_available?
67
+
68
+ css = read_file( file_path )
69
+ css = YUI::CSS.compress( css )
70
+ destination = writable_file( file_path )
71
+
72
+ destination.write( css )
73
+ destination.write "\n"
74
+ destination.close
75
+ end
76
+
77
+ def self.compressor_available?
78
+ YUI::CSS.is_a?( Class )
79
+ rescue NameError
80
+ false
81
+ end
82
+ end
83
+
84
+ # Uses YUI Compressor (via Sam Stephenson's yui-compressor gem) to compress
85
+ # JavaScrpt.
86
+ class JSCompressor < Compressor
87
+ register ".js"
88
+
89
+ def self.do_compress( file_path, opts={} )
90
+ return unless compressor_available?
91
+
92
+ compressor = YUI::JavaScriptCompressor.new( opts )
93
+
94
+ js = read_file( file_path )
95
+ destination = writable_file( file_path )
96
+
97
+ destination.write( compressor.compress( js ) )
98
+ destination.write "\n"
99
+ destination.close
100
+ end
101
+
102
+ def self.compressor_available?
103
+ YUI::JavaScriptCompressor.is_a?( Class )
104
+ rescue NameError
105
+ false
106
+ end
107
+ end
108
+
109
+ end
@@ -0,0 +1,61 @@
1
+ module RapperLite::Config
2
+
3
+ protected
4
+
5
+ def load_config( config_path )
6
+ @config_path = config_path
7
+ @config = YAML.load_file( config_path )
8
+ @definitions.css = @config["css"]
9
+ @definitions.js = @config["js"]
10
+ end
11
+
12
+ def save_config
13
+ File.open( @config_path, "w" ) do |file|
14
+ file.puts @config.to_yaml
15
+ end
16
+ end
17
+
18
+ def root( type )
19
+ assert_type!( type )
20
+ @definitions.send( type )["root"] || @config["root"] || "."
21
+ end
22
+
23
+ def destination( type )
24
+ assert_type!( type )
25
+ @definitions.send( type )["destination"] || @config["destination"] || "."
26
+ end
27
+
28
+ def compress?
29
+ @config.key?( "compress" ) ? @config["compress"] : false
30
+ end
31
+
32
+ def yui_config
33
+ @yui_config ||= {
34
+ "line_break" => 2000,
35
+ "munge" => false,
36
+ "optimize" => true,
37
+ "preserve_semicolons" => false
38
+ }.merge( @config["yui_compressor"] || {} )
39
+ end
40
+
41
+ # Array of source files for the given asset package.
42
+ def file_paths( type, name )
43
+ definition = @definitions[type][name]
44
+ root = self.root( type )
45
+ definition["files"].map do |file|
46
+ if file[0] == "+"
47
+ self.file_paths( type, file[1..-1] )
48
+ else
49
+ File.join( root, "#{file}.#{type}" )
50
+ end
51
+ end.flatten
52
+ end
53
+
54
+ def destination_path( type, name )
55
+ File.join( self.destination( type ), "#{name}.#{type}" )
56
+ end
57
+
58
+ def assert_type!( type )
59
+ raise "wat." unless type == :css || type == :js
60
+ end
61
+ end
@@ -0,0 +1,19 @@
1
+ # Rapper-wide utility methods for working with paths, files, etc.
2
+ module RapperLite::Utils
3
+
4
+ protected
5
+
6
+ # Concatenate one or more files. Uses <code>cat</code>.
7
+ #
8
+ # @param [Array<String>,String] source_files A path or array of paths to
9
+ # files to concatenate.
10
+ #
11
+ # @param [String] destination_file Destination for concatenated output.
12
+ def join_files( source_files, destination_file )
13
+ source_files = Array( source_files )
14
+ source_files.any? do |path|
15
+ raise "#{path} doesn't exist." unless File.exists?( path )
16
+ end
17
+ system "cat #{source_files.join( " " )} > #{destination_file}"
18
+ end
19
+ end
@@ -0,0 +1,38 @@
1
+ require 'digest/md5'
2
+ require 'tempfile'
3
+
4
+ # Asset versioning methods.
5
+ module RapperLite::Versioning
6
+
7
+ protected
8
+
9
+ def needs_packaging?( type, name )
10
+ definition = @definitions[type][name]
11
+ destination_file = self.destination_path( type, name )
12
+ return true unless File.exists?( destination_file )
13
+
14
+ current_version = definition["version"]
15
+ new_version = self.version( type, name )
16
+ new_version != current_version
17
+ end
18
+
19
+ def refresh_versions
20
+ [:css, :js].each do |type|
21
+ @definitions[type].each do |name, spec|
22
+ next if name == "root" || name == "destination"
23
+ @definitions[type][name]["version"] = self.version( type, name )
24
+ end
25
+ end
26
+ end
27
+
28
+ # MD5 version of the concatenated asset package.
29
+ def version( type, name )
30
+ definition = @definitions[type][name]
31
+ source_files = self.file_paths( type, name )
32
+ destination_file = Tempfile.new( 'rapper' )
33
+ self.join_files( source_files, destination_file.path )
34
+ version = Digest::MD5.file( destination_file.path ).to_s[0,4]
35
+ destination_file.unlink
36
+ version
37
+ end
38
+ end
@@ -0,0 +1,50 @@
1
+ require 'yaml'
2
+
3
+ module RapperLite; end
4
+ Dir[File.expand_path( "#{File.dirname( __FILE__ )}/rapper_lite/*.rb" )].each do |file|
5
+ require file
6
+ end
7
+ # TODO: Re-enable
8
+ # require File.dirname( __FILE__ ) + "/tasks.rb"
9
+
10
+ # No batteries included, and no strings attached /
11
+ # No holds barred, no time for move fakin' /
12
+ # Gots to get the loot so I can bring home the bacon
13
+ module RapperLite
14
+ class Engine
15
+ include RapperLite::Config
16
+ include RapperLite::Utils
17
+ include RapperLite::Compressors
18
+ include RapperLite::Versioning
19
+
20
+ def initialize( config_path )
21
+ @config = {}
22
+ @definitions = Struct.new( :css, :js ).new
23
+ self.load_config( config_path )
24
+ end
25
+
26
+ def package
27
+ [:js, :css].each do |type|
28
+ definition = @definitions[type]
29
+ source = File.expand_path( self.root( type ) )
30
+
31
+ definition.each do |name, spec|
32
+ next if name == "root" || name == "destination"
33
+ next unless self.needs_packaging?( type, name )
34
+
35
+ source_files = self.file_paths( type, name )
36
+ destination_file = self.destination_path( type, name )
37
+
38
+ self.join_files( source_files, destination_file )
39
+
40
+ if self.compress?
41
+ self.compress( destination_file )
42
+ end
43
+ end
44
+ end
45
+
46
+ self.refresh_versions
47
+ self.save_config
48
+ end
49
+ end
50
+ end
data/lib/tasks.rb ADDED
@@ -0,0 +1,37 @@
1
+ module Rapper
2
+
3
+ # Rake tasks for building / refreshing packages
4
+ class Tasks
5
+
6
+ # Set up rapper asset packaging Rake tasks.
7
+ #
8
+ # @param [Symbol] namespace The Rake namespace to put the generated
9
+ # tasks under.
10
+ #
11
+ # @yield [config] Configuration hash. `:path` should be the path to the
12
+ # configuration YAML file. `:env` is the optional environment. Defaults to
13
+ # `:production`.
14
+ def initialize( namespace = :rapper, &block )
15
+ @namespace = namespace
16
+ @config = {
17
+ :path => "rapper.yml"
18
+ }
19
+ yield @config
20
+ @rapper = RapperLite::Engine.new( @config[:path] )
21
+ self.define
22
+ end
23
+
24
+ private
25
+
26
+ # Creates all rapper rake tasks: package all assets, package assets for
27
+ # each type.
28
+ def define
29
+ namespace @namespace do
30
+ desc "Package static assets that need re-packaging"
31
+ task :package do
32
+ @rapper.package
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end