rapper 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +11 -0
  4. data/Gemfile.lock +24 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.markdown +140 -0
  7. data/Rakefile +45 -0
  8. data/VERSION +1 -0
  9. data/lib/rapper.rb +66 -0
  10. data/lib/rapper/compressors.rb +47 -0
  11. data/lib/rapper/config.rb +95 -0
  12. data/lib/rapper/errors.rb +23 -0
  13. data/lib/rapper/logging.rb +35 -0
  14. data/lib/rapper/utils.rb +106 -0
  15. data/lib/rapper/versioning.rb +37 -0
  16. data/lib/yui/css_compressor.rb +279 -0
  17. data/rapper.gemspec +167 -0
  18. data/spec/fixtures/config/assets.yml +25 -0
  19. data/spec/fixtures/config/assets/base/javascripts.yml +12 -0
  20. data/spec/fixtures/config/assets/base/stylesheets.yml +10 -0
  21. data/spec/fixtures/config/assets/base/validators.yml +9 -0
  22. data/spec/fixtures/javascripts/simple_1.js +5 -0
  23. data/spec/fixtures/javascripts/simple_2.js +5 -0
  24. data/spec/fixtures/stylesheets/simple_1.css +4 -0
  25. data/spec/fixtures/stylesheets/simple_2.css +4 -0
  26. data/spec/fixtures/test_cases/concatenation/assets.yml +5 -0
  27. data/spec/fixtures/test_cases/concatenation/definitions/css.yml +15 -0
  28. data/spec/fixtures/test_cases/concatenation/definitions/js.yml +15 -0
  29. data/spec/fixtures/test_cases/concatenation/expected/base.css +8 -0
  30. data/spec/fixtures/test_cases/concatenation/expected/base.js +10 -0
  31. data/spec/fixtures/test_cases/concatenation/expected/base_reversed.css +8 -0
  32. data/spec/fixtures/test_cases/concatenation/expected/base_reversed.js +10 -0
  33. data/spec/fixtures/yui_css/background-position.css +2 -0
  34. data/spec/fixtures/yui_css/background-position.css.min +1 -0
  35. data/spec/fixtures/yui_css/box-model-hack.css +9 -0
  36. data/spec/fixtures/yui_css/box-model-hack.css.min +1 -0
  37. data/spec/fixtures/yui_css/bug2527974.css +9 -0
  38. data/spec/fixtures/yui_css/bug2527974.css.min +1 -0
  39. data/spec/fixtures/yui_css/bug2527991.css +19 -0
  40. data/spec/fixtures/yui_css/bug2527991.css.min +1 -0
  41. data/spec/fixtures/yui_css/bug2527998.css +4 -0
  42. data/spec/fixtures/yui_css/bug2527998.css.min +1 -0
  43. data/spec/fixtures/yui_css/bug2528034.css +5 -0
  44. data/spec/fixtures/yui_css/bug2528034.css.min +1 -0
  45. data/spec/fixtures/yui_css/charset-media.css +9 -0
  46. data/spec/fixtures/yui_css/charset-media.css.min +1 -0
  47. data/spec/fixtures/yui_css/color.css +7 -0
  48. data/spec/fixtures/yui_css/color.css.min +1 -0
  49. data/spec/fixtures/yui_css/comment.css +3 -0
  50. data/spec/fixtures/yui_css/comment.css.min +1 -0
  51. data/spec/fixtures/yui_css/concat-charset.css +15 -0
  52. data/spec/fixtures/yui_css/concat-charset.css.min +1 -0
  53. data/spec/fixtures/yui_css/decimals.css +3 -0
  54. data/spec/fixtures/yui_css/decimals.css.min +1 -0
  55. data/spec/fixtures/yui_css/dollar-header.css +7 -0
  56. data/spec/fixtures/yui_css/dollar-header.css.min +3 -0
  57. data/spec/fixtures/yui_css/font-face.css +6 -0
  58. data/spec/fixtures/yui_css/font-face.css.min +1 -0
  59. data/spec/fixtures/yui_css/ie5mac.css +5 -0
  60. data/spec/fixtures/yui_css/ie5mac.css.min +1 -0
  61. data/spec/fixtures/yui_css/media-empty-class.css +16 -0
  62. data/spec/fixtures/yui_css/media-empty-class.css.min +1 -0
  63. data/spec/fixtures/yui_css/media-multi.css +5 -0
  64. data/spec/fixtures/yui_css/media-multi.css.min +1 -0
  65. data/spec/fixtures/yui_css/media-test.css +5 -0
  66. data/spec/fixtures/yui_css/media-test.css.min +1 -0
  67. data/spec/fixtures/yui_css/opacity-filter.css +14 -0
  68. data/spec/fixtures/yui_css/opacity-filter.css.min +1 -0
  69. data/spec/fixtures/yui_css/preserve-new-line.css +6 -0
  70. data/spec/fixtures/yui_css/preserve-new-line.css.min +3 -0
  71. data/spec/fixtures/yui_css/preserve-strings.css +7 -0
  72. data/spec/fixtures/yui_css/preserve-strings.css.min +1 -0
  73. data/spec/fixtures/yui_css/preserve_string.css +7 -0
  74. data/spec/fixtures/yui_css/preserve_string.css.min +1 -0
  75. data/spec/fixtures/yui_css/pseudo-first.css +16 -0
  76. data/spec/fixtures/yui_css/pseudo-first.css.min +1 -0
  77. data/spec/fixtures/yui_css/pseudo.css +4 -0
  78. data/spec/fixtures/yui_css/pseudo.css.min +1 -0
  79. data/spec/fixtures/yui_css/special-comments.css +13 -0
  80. data/spec/fixtures/yui_css/special-comments.css.min +9 -0
  81. data/spec/fixtures/yui_css/star-underscore-hacks.css +5 -0
  82. data/spec/fixtures/yui_css/star-underscore-hacks.css.min +1 -0
  83. data/spec/fixtures/yui_css/string-in-comment.css +8 -0
  84. data/spec/fixtures/yui_css/string-in-comment.css.min +1 -0
  85. data/spec/fixtures/yui_css/zeros.css +6 -0
  86. data/spec/fixtures/yui_css/zeros.css.min +1 -0
  87. data/spec/rapper_spec.rb +139 -0
  88. data/spec/spec_helper.rb +57 -0
  89. data/spec/vendor_spec.rb +36 -0
  90. metadata +349 -0
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "closure-compiler", "~> 1.0.0"
4
+
5
+ group :development do
6
+ gem "rspec", "~> 1.3.1"
7
+ gem "yard", "~> 0.6.4"
8
+ gem "bluecloth", "~> 2.0.10"
9
+ gem "bundler", "~> 1.0.0"
10
+ gem "jeweler", "~> 1.5.2"
11
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,24 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ bluecloth (2.0.10)
5
+ closure-compiler (1.0.0)
6
+ git (1.2.5)
7
+ jeweler (1.5.2)
8
+ bundler (~> 1.0.0)
9
+ git (>= 1.2.5)
10
+ rake
11
+ rake (0.8.7)
12
+ rspec (1.3.1)
13
+ yard (0.6.4)
14
+
15
+ PLATFORMS
16
+ ruby
17
+
18
+ DEPENDENCIES
19
+ bluecloth (~> 2.0.10)
20
+ bundler (~> 1.0.0)
21
+ closure-compiler (~> 1.0.0)
22
+ jeweler (~> 1.5.2)
23
+ rspec (~> 1.3.1)
24
+ yard (~> 0.6.4)
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,140 @@
1
+ # rapper
2
+
3
+ Static asset packager and compressor with versioning and built-in view helpers. Easy to configure, easy to use, and easy to ignore when you want to. No crazy JavaScript comment DSLs, either.
4
+
5
+ Merb and Sinatra view helpers are coming soon, as well as pluggable compression backends (YUI Compressor, Google Closure Compiler, etc.) `rapper` currently only includes Google Closure Compiler.
6
+
7
+ ## Packaging assets without wanting to claw your eyes out
8
+
9
+ 1. Create a config file and one or more asset type definition files.
10
+ 2. Load `rapper` with the config path and current environment:
11
+
12
+ engine = Rapper::Engine.new( "config/assets.yml", "development" )
13
+
14
+ 3. Then package the assets:
15
+
16
+ engine.package
17
+
18
+ 4. That's it. Stop fussing with ridiculous asset packagers that want you to spend an hour shuffling files around for them.
19
+
20
+ ## Rapper configuration
21
+
22
+ Rapper is configured using a YAML file that defines the settings to be used in various server environments. Example:
23
+
24
+ base: &base
25
+ definition_root: config/assets
26
+ tag_style: html # optional, ["html", "xhtml", "html5"], default: html5
27
+
28
+ development:
29
+ <<: *base
30
+ bundle: false # optional, default: true
31
+ compress: false # optional, default: true
32
+ versions: false # optional, default: true
33
+ log: stdout # optional, ["stdout", file path], default: off
34
+ log_verbose: true # optional, default: off
35
+
36
+ production:
37
+ <<: *base
38
+ bundle: true
39
+ compress: true
40
+ versions: true
41
+ # optional, passed to Google Closure Compiler
42
+ closure_compiler:
43
+ # default: SIMPLE_OPTIMIZATIONS
44
+ compilation_level: ADVANCED_OPTIMIZATIONS
45
+
46
+ The only required setting is `definition_root`. (Of course, you'll still need definition files to define the asset packages that you want build. More on that below.)
47
+
48
+ The minimum settings needed for a configuration file is:
49
+
50
+ base:
51
+ definition_root: config/assets
52
+
53
+ The following defaults are applied if not defined in your configuration:
54
+
55
+ bundle: true
56
+ compress: true
57
+ tag_style: html5
58
+ versions: true
59
+ closure_compiler:
60
+ compilation_level: SIMPLE_OPTIMIZATIONS
61
+
62
+ ## Rapper definitions
63
+
64
+ The `definition_root` setting in the rapper config is a path to a folder containing more YAML files that define the various types of bundles you want to build (eg. `stylesheets.yml`, `javascripts.yml`) Example definition file:
65
+
66
+ --- !omap
67
+ - source_root: public/javascripts
68
+ - destination_root: public/assets/javascripts
69
+ - suffix: js
70
+ - assets:
71
+ - base:
72
+ - files:
73
+ - mootools
74
+ - stats:
75
+ - files:
76
+ - protovis
77
+ - ext_js_full
78
+
79
+ The above definition will create two asset files: `public/assets/javascripts/base.js` and `public/assets/javascripts/stats.js` from the component files in `public/javascripts` (in this case: `public/javascripts/protovis.js` and `public/javascripts/ext_js_full.js`).
80
+
81
+ **Note:** Definition files are YAML ordered mapping documents. This is so that version updates (which involves rapper updating the version numbers and writing out the updated definition as YAML) don't change the order of the file. This is especially useful when using git and working with many branches because it prevents nasty merge conflicts.
82
+
83
+ ## Versioning
84
+
85
+ If versioning is turned on in your config, version strings will be used to enforce better browser caching of assets.
86
+
87
+ --- !omap
88
+ - source_root: public/javascripts
89
+ - destination_root: public/assets/javascripts
90
+ - suffix: js
91
+ - assets:
92
+ - base:
93
+ - files:
94
+ - mootools
95
+ - version: 7b06
96
+ - stats:
97
+ - files:
98
+ - protovis
99
+ - ext_js_full
100
+ - version: db62
101
+
102
+ These version strings are hashes of the final asset file. This means that they will only change when the contents of the asset file change. Version strings are used to enforce good browser caching habits, especially when you have a far-future expires header configured on your web server. For example, suppose you had the following asset:
103
+
104
+ <script type="text/javascript" src="/assets/milkshake.js?v=d3va"></script>
105
+
106
+ When the contents of the asset change, the version will change in the query string:
107
+
108
+ <script type="text/javascript" src="/assets/milkshake.js?v=ae51"></script>
109
+
110
+ Browsers will automatically re-download and cache the new asset.
111
+
112
+ ## To do soon
113
+
114
+ * Merb view helpers
115
+ * Sinatra helpers
116
+
117
+ ## To do later
118
+
119
+ * Per-asset configuration overrides
120
+ * Rails helpers
121
+ * Watch for CoffeeScript changes and automatically compile
122
+ * Watch for Sass changes and automatically compile
123
+
124
+ ## Version history
125
+
126
+ * **0.0.1** - Initial release. Functioning bundler, minus the view helpers.
127
+
128
+ ## Contributing to rapper
129
+
130
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
131
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
132
+ * Fork the project
133
+ * Start a feature/bugfix branch
134
+ * Commit and push until you are happy with your contribution
135
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
136
+ * 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.
137
+
138
+ ## Copyright
139
+
140
+ Copyright (c) 2011 Tyson Tate. See LICENSE.txt for further details.
data/Rakefile ADDED
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ gem.name = "rapper"
15
+ gem.homepage = "http://tysontate.github.com/rapper/"
16
+ gem.license = "MIT"
17
+ gem.summary = %Q{Static asset packager and compressor with versioning and built-in view helpers.}
18
+ gem.description = %Q{Static asset packager and compressor with versioning and built-in view helpers. Easy to configure, easy to use, and easy to ignore when you want to. No crazy JavaScript comment DSLs, either.}
19
+ gem.email = "tyson@tysontate.com"
20
+ gem.authors = ["Tyson Tate"]
21
+
22
+ # Runtime dependencies
23
+ gem.add_runtime_dependency "closure-compiler", "~> 1.0.0"
24
+
25
+ # Development dependencies
26
+ gem.add_development_dependency "rspec", "~> 1.3.1"
27
+ gem.add_development_dependency "yard", "~> 0.6.4"
28
+ gem.add_development_dependency "bluecloth", "~> 2.0.10"
29
+ gem.add_development_dependency "bundler", "~> 1.0.0"
30
+ gem.add_development_dependency "jeweler", "~> 1.5.2"
31
+ end
32
+ Jeweler::RubygemsDotOrgTasks.new
33
+
34
+ require 'spec'
35
+ require 'spec/rake/spectask'
36
+ Spec::Rake::SpecTask.new(:spec) do |spec|
37
+ spec.spec_files = FileList['spec/**/*_spec.rb']
38
+ end
39
+
40
+ task :default => :spec
41
+
42
+ require 'yard'
43
+ YARD::Rake::YardocTask.new do |config|
44
+ config.options = ["--private", "--protected"]
45
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/lib/rapper.rb ADDED
@@ -0,0 +1,66 @@
1
+ require 'yaml'
2
+
3
+ Dir[File.expand_path( File.dirname( __FILE__ ) + "/rapper/*.rb" )].each do |file|
4
+ require file
5
+ end
6
+
7
+ # No batteries included, and no strings attached /
8
+ # No holds barred, no time for move fakin' /
9
+ # Gots to get the loot so I can bring home the bacon
10
+ module Rapper
11
+
12
+ # The main Rapper class. Handles, well, everything.
13
+ class Engine
14
+
15
+ include Rapper::Config
16
+ include Rapper::Logging
17
+ include Rapper::Utils
18
+ include Rapper::Compressors
19
+ include Rapper::Versioning
20
+
21
+ # Load the configuration YAML file and set the current environment.
22
+ #
23
+ # @param [String] config_path Path to the configuration YAML file.
24
+ #
25
+ # @param [String,Symbol] environment The current environment. This must
26
+ # map to an environment configured in the Rapper configuration file.
27
+ def initialize( config_path, environment )
28
+ @environment = environment
29
+ @config = {}
30
+ @definitions = {}
31
+ load_config( config_path )
32
+ log :verbose, "Loaded rappper with #{environment} environment from #{config_path}"
33
+ end
34
+
35
+ # Package assets according to the loaded config and definitions. Defaults
36
+ # to packaging all asset types.
37
+ #
38
+ # @param [<String>] types Asset types to refresh versions for.
39
+ def package( *types )
40
+ types = types.empty? ? asset_types : types
41
+ log :info, "Packaging #{types.join( ', ' )}"
42
+
43
+ types.each do |type|
44
+ definition = @definitions[type]
45
+ source = File.expand_path( definition["source_root"] )
46
+ destination = definition["destination_root"]
47
+ suffix = definition["suffix"]
48
+
49
+ definition["assets"].each do |asset|
50
+ name = asset.keys.first
51
+ spec = asset.values.first.first
52
+
53
+ source_files = asset_component_paths( type, name )
54
+ destination_file = asset_path( type, name )
55
+
56
+ log :verbose, "Joining #{definition["assets"].count} files to #{name}"
57
+ join_files( source_files, destination_file )
58
+ end
59
+ end
60
+
61
+ refresh_versions( *types )
62
+ update_definitions( *types )
63
+ end
64
+
65
+ end
66
+ end
@@ -0,0 +1,47 @@
1
+ require File.expand_path( File.dirname( __FILE__ ) + "/../yui/css_compressor.rb" )
2
+ require "closure-compiler"
3
+
4
+ module Rapper
5
+ # Compression methods for various types of assets.
6
+ module Compressors
7
+
8
+ # Use Richard Hulse's port of the YUI CSS Compressor to compress the
9
+ # contents of a source file to a destination file.
10
+ #
11
+ # @param [String] source Path to source CSS file.
12
+ #
13
+ # @param [String] destination Path to destination CSS file.
14
+ # written to.
15
+ def compress_css( source, destination )
16
+ log :verbose, "Compressing #{source}"
17
+
18
+ source = readable_file( source )
19
+ destination = writable_file( source )
20
+
21
+ destination.write( YUI::CSS.compress( source.read ) )
22
+ destination.write "\n"
23
+
24
+ source.close && destination.close
25
+ end
26
+
27
+ # Use Google's Closure Compiler to compress the JavaScript from a source
28
+ # file to a destination file.
29
+ #
30
+ # @param [String] source Path to source JavaScript file.
31
+ #
32
+ # @param [String] destination Path to destination JavaScript file.
33
+ def compress_js( source, destination, opts={} )
34
+ log :verbose, "Compressing #{source}"
35
+
36
+ source = readable_file( source )
37
+ destination = writable_file( source )
38
+ closure = Closure::Compiler.new( opts )
39
+
40
+ destination.write( closure.compile( destination ) )
41
+ destination.write "\n"
42
+
43
+ source.close && destination.close
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,95 @@
1
+ require 'yaml'
2
+
3
+ module Rapper
4
+ # Rapper configuration and definition methods.
5
+ module Config
6
+
7
+ attr_accessor :environment, :config, :definitions
8
+
9
+ protected
10
+
11
+ # Load the Rapper configuration from a YAML file and all asset definition
12
+ # YAML files in the folder specified in the current environment's
13
+ # <code>definition_root</code> setting. The definition type is inferred
14
+ # from the file name. E.g. The type key for <code>javascript.yml</code>
15
+ # will be "javascript".
16
+ #
17
+ # @param [String] config_path The path to the configuration YAML file.
18
+ def load_config( config_path )
19
+ @config = YAML.load_file( config_path )
20
+ if env_config.nil?
21
+ raise Rapper::Errors::InvalidEnvironment,
22
+ "The '#{@environment}' environment is not defined in #{config_path}"
23
+ elsif env_config["definition_root"].nil?
24
+ raise Rapper::Errors::NoDefinitionRoot,
25
+ "No 'definition_root' has been defined for #{@environment}"
26
+ end
27
+ definition_path = File.join( env_config["definition_root"], "*.yml" )
28
+ Dir[definition_path].each do |definition|
29
+ type = File.basename( definition, ".yml" )
30
+ @definitions[type] = YAML.load_file( definition )
31
+ end
32
+ end
33
+
34
+ protected
35
+
36
+ # Get the config setting for the given key.
37
+ #
38
+ # @param [String] key Configuration key.
39
+ #
40
+ # @return [String,Hash] If the current environment's config defines this
41
+ # setting, return that value. If not, return the default setting. If the
42
+ # default setting is a hash, return the default merged with the
43
+ # environment's setting.
44
+ def get_config( key )
45
+ if default_config[key].is_a?( Hash )
46
+ default_config[key].merge( env_config[key] || {} )
47
+ else
48
+ env_config[key] || default_config[key]
49
+ end
50
+ end
51
+
52
+ # Update the asset definition files. (Typically done after regenerating
53
+ # versions.)
54
+ #
55
+ # @param [<String>] types Asset types to update the definition files for.
56
+ # Defaults to all types.
57
+ def update_definitions( *types )
58
+ types = types.empty? ? asset_types : types
59
+ log :info, "Updating definitions for #{types.join( ', ' )}"
60
+
61
+ types.each do |type|
62
+ log "Updating definition file for", type
63
+ File.open( definition_path( type ), "w" ) do |file|
64
+ file.puts @definitions[type].to_yaml
65
+ end
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ # @return [Hash] Default rapper configuration.
72
+ def default_config
73
+ {
74
+ "bundle" => true,
75
+ "compress" => true,
76
+ "tag_style" => "html5",
77
+ "versions" => true,
78
+ "closure_compiler" => {
79
+ "compilation_level" => "SIMPLE_OPTIMIZATIONS"
80
+ }
81
+ }
82
+ end
83
+
84
+ # @return [Hash] The configuration for the currently set environment.
85
+ def env_config
86
+ @config[@environment]
87
+ end
88
+
89
+ # @return [Array<String>] All defined asset types.
90
+ def asset_types
91
+ @definitions.keys
92
+ end
93
+
94
+ end
95
+ end