rake-pipeline-web-filters 0.5.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile +1 -0
  3. data/Rakefile +8 -0
  4. data/lib/rake-pipeline-web-filters.rb +17 -3
  5. data/lib/rake-pipeline-web-filters/cache_buster_filter.rb +42 -0
  6. data/lib/rake-pipeline-web-filters/chained_filter.rb +116 -0
  7. data/lib/rake-pipeline-web-filters/coffee_script_filter.rb +41 -0
  8. data/lib/rake-pipeline-web-filters/filter_with_dependencies.rb +27 -0
  9. data/lib/rake-pipeline-web-filters/gzip_filter.rb +59 -0
  10. data/lib/rake-pipeline-web-filters/handlebars_filter.rb +62 -0
  11. data/lib/rake-pipeline-web-filters/helpers.rb +100 -18
  12. data/lib/rake-pipeline-web-filters/iife_filter.rb +38 -0
  13. data/lib/rake-pipeline-web-filters/less_filter.rb +55 -0
  14. data/lib/rake-pipeline-web-filters/markdown_filter.rb +70 -0
  15. data/lib/rake-pipeline-web-filters/minispade_filter.rb +21 -5
  16. data/lib/rake-pipeline-web-filters/neuter_filter.rb +110 -0
  17. data/lib/rake-pipeline-web-filters/sass_filter.rb +87 -0
  18. data/lib/rake-pipeline-web-filters/stylus_filter.rb +59 -0
  19. data/lib/rake-pipeline-web-filters/tilt_filter.rb +16 -3
  20. data/lib/rake-pipeline-web-filters/uglify_filter.rb +66 -0
  21. data/lib/rake-pipeline-web-filters/version.rb +1 -1
  22. data/lib/rake-pipeline-web-filters/yui_css_filter.rb +70 -0
  23. data/lib/rake-pipeline-web-filters/yui_javascript_filter.rb +59 -0
  24. data/rake-pipeline-web-filters.gemspec +10 -1
  25. data/spec/cache_buster_filter_spec.rb +105 -0
  26. data/spec/chained_filter_spec.rb +76 -0
  27. data/spec/coffee_script_filter_spec.rb +110 -0
  28. data/spec/gzip_filter_spec.rb +49 -0
  29. data/spec/handlebars_filter_spec.rb +70 -0
  30. data/spec/helpers_spec.rb +112 -18
  31. data/spec/iife_filter_spec.rb +55 -0
  32. data/spec/less_filter_spec.rb +59 -0
  33. data/spec/markdown_filter_spec.rb +86 -0
  34. data/spec/minispade_filter_spec.rb +47 -15
  35. data/spec/neuter_filter_spec.rb +204 -0
  36. data/spec/sass_filter_spec.rb +147 -0
  37. data/spec/spec_helper.rb +10 -1
  38. data/spec/stylus_filter_spec.rb +69 -0
  39. data/spec/tilt_filter_spec.rb +25 -1
  40. data/spec/uglify_filter_spec.rb +82 -0
  41. data/spec/yui_css_filter_spec.rb +88 -0
  42. data/spec/yui_javascript_filter_spec.rb +68 -0
  43. metadata +225 -19
  44. data/lib/rake-pipeline-web-filters/ordering_concat_filter.rb +0 -38
  45. data/lib/rake-pipeline-web-filters/sass_compiler.rb +0 -53
  46. data/spec/ordering_concat_filter_spec.rb +0 -39
  47. data/spec/sass_compiler_spec.rb +0 -89
data/.gitignore CHANGED
@@ -15,3 +15,5 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ node_modules
19
+ .project
data/Gemfile CHANGED
@@ -4,3 +4,4 @@ source "http://rubygems.org"
4
4
  gemspec
5
5
 
6
6
  gem "rake-pipeline", :git => "git://github.com/livingsocial/rake-pipeline.git"
7
+ gem "pry"
data/Rakefile CHANGED
@@ -1,2 +1,10 @@
1
1
  #!/usr/bin/env rake
2
2
  require "bundler/gem_tasks"
3
+ require "bundler/setup"
4
+
5
+ desc "run the specs"
6
+ task :spec do
7
+ sh "rspec -cfs spec"
8
+ end
9
+
10
+ task :default => :spec
@@ -10,7 +10,21 @@ module Rake
10
10
  end
11
11
 
12
12
  require "rake-pipeline-web-filters/version"
13
- require "rake-pipeline-web-filters/tilt_filter"
14
- require "rake-pipeline-web-filters/sass_compiler"
13
+ require "rake-pipeline-web-filters/cache_buster_filter"
14
+ require "rake-pipeline-web-filters/filter_with_dependencies"
15
+ require "rake-pipeline-web-filters/markdown_filter"
15
16
  require "rake-pipeline-web-filters/minispade_filter"
16
- require "rake-pipeline-web-filters/ordering_concat_filter"
17
+ require "rake-pipeline-web-filters/neuter_filter"
18
+ require "rake-pipeline-web-filters/sass_filter"
19
+ require "rake-pipeline-web-filters/stylus_filter"
20
+ require "rake-pipeline-web-filters/tilt_filter"
21
+ require "rake-pipeline-web-filters/coffee_script_filter"
22
+ require "rake-pipeline-web-filters/yui_javascript_filter"
23
+ require "rake-pipeline-web-filters/yui_css_filter"
24
+ require "rake-pipeline-web-filters/gzip_filter"
25
+ require "rake-pipeline-web-filters/uglify_filter"
26
+ require "rake-pipeline-web-filters/chained_filter"
27
+ require "rake-pipeline-web-filters/less_filter"
28
+ require "rake-pipeline-web-filters/handlebars_filter"
29
+ require "rake-pipeline-web-filters/iife_filter"
30
+ require "rake-pipeline-web-filters/helpers"
@@ -0,0 +1,42 @@
1
+ module Rake::Pipeline::Web::Filters
2
+ # A filter that inserts a cache-busting key into the outputted file name.
3
+ #
4
+ # @example
5
+ # !!!ruby
6
+ # Rake::Pipeline.build do
7
+ # input "app/assets"
8
+ # output "public"
9
+ #
10
+ # filter Rake::Pipeline::Web::Filters::CacheBusterFilter
11
+ # end
12
+ class CacheBusterFilter < Rake::Pipeline::Filter
13
+
14
+ # @return [Proc] the default cache key generator, which
15
+ # takes the MD5 hash of the input's file name and contents.
16
+ DEFAULT_KEY_GENERATOR = proc { |input|
17
+ require 'digest/md5'
18
+ Digest::MD5.new << input.path << input.read
19
+ }
20
+
21
+ # @param [Proc] key_generator a block to use as the Filter's method of
22
+ # turning input file wrappers into cache keys; defaults to
23
+ # +DEFAULT_KEY_GENERATOR+
24
+ def initialize(&key_generator)
25
+ key_generator ||= DEFAULT_KEY_GENERATOR
26
+ output_name_generator = proc { |path, file|
27
+ parts = path.split('.')
28
+ index_to_modify = parts.length > 1 ? -2 : -1
29
+ parts[index_to_modify] << "-#{key_generator.call(file)}"
30
+ parts.join('.')
31
+ }
32
+ super(&output_name_generator)
33
+ end
34
+
35
+ def generate_output(inputs, output)
36
+ inputs.each do |input|
37
+ output.write input.read
38
+ end
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,116 @@
1
+ module Rake::Pipeline::Web::Filters
2
+ # Implement the FileWrapper API. Because filters are defined
3
+ # in terms of the FileWrapper API, we can implement an
4
+ # alternative that doesn't write to disk but still utilizes
5
+ # the same filter definitions.
6
+ class MemoryFileWrapper < Struct.new(:root, :path, :encoding, :body)
7
+ def with_encoding(new_encoding)
8
+ self.class.new(root, path, new_encoding, body)
9
+ end
10
+
11
+ def fullpath
12
+ File.join(root, path)
13
+ end
14
+
15
+ def create
16
+ self.body = ""
17
+ yield
18
+ end
19
+
20
+ alias read body
21
+
22
+ def write(contents)
23
+ self.body << contents
24
+ end
25
+ end
26
+
27
+ # The purpose of ChainedFilter is to enable filters to
28
+ # be applied to files based upon their file extensions.
29
+ #
30
+ # Filters are applied repeatedly to files for each
31
+ # extension.
32
+ #
33
+ # @example
34
+ #
35
+ # filter ChainedFilter, :types => {
36
+ # :erb => ErbFilter,
37
+ # :coffee => CoffeeFilter,
38
+ # :scss => ScssFilter
39
+ # }
40
+ #
41
+ # In this example, files with the extensions +erb+,
42
+ # +coffee+, and +scss+ will be processed using the
43
+ # specified filters. If a file has multiple extensions,
44
+ # all of the filters will be applied.
45
+ #
46
+ # For example, with the above filter specification,
47
+ # a file like +application.js.coffee.erb+ will first
48
+ # apply the +ErbFilter+, then the +CoffeeFilter+, and
49
+ # then output +application.js+.
50
+ #
51
+ # This filter is largely designed for use with the
52
+ # {ProjectHelpers#register register} helper, which
53
+ # will transparently add a ChainedFilter before each
54
+ # input block with the registered extensions.
55
+ class ChainedFilter < Rake::Pipeline::Filter
56
+ attr_reader :filters
57
+
58
+ # @param [Hash] options
59
+ # @option options [Hash] :types
60
+ # A hash of file extensions and their associated
61
+ # filters. See the class description for more
62
+ # information.
63
+ def initialize(options={}, &block)
64
+ @filters = options[:types]
65
+
66
+ keys = @filters.keys
67
+ pattern = keys.map { |key| "\\.#{key}" }.join("|")
68
+ @pattern = Regexp.new("(#{pattern})*$", "i")
69
+
70
+ block ||= proc do |input|
71
+ input.sub(@pattern, '')
72
+ end
73
+
74
+ super(&block)
75
+ end
76
+
77
+ # @private
78
+ #
79
+ # Implement +generate_output+
80
+ def generate_output(inputs, output)
81
+ inputs.each do |input|
82
+ output.write process_filters(input)
83
+ end
84
+ end
85
+
86
+ # @private
87
+ #
88
+ # Process an input file by applying the filter for each
89
+ # extension in the file.
90
+ def process_filters(input)
91
+ keys = input.path.match(@pattern)[0].scan(/(?<=\.)\w+/)
92
+
93
+ filters = keys.reverse_each.map do |key|
94
+ @filters[key.to_sym]
95
+ end
96
+
97
+ filters.each do |filter|
98
+ input = process_with_filter(input, filter)
99
+ end
100
+
101
+ input.read
102
+ end
103
+
104
+ # @private
105
+ #
106
+ # Process an individual file with a filter.
107
+ def process_with_filter(input, filter_class)
108
+ filter = filter_class.new
109
+
110
+ output = MemoryFileWrapper.new("/output", input.path, "UTF-8")
111
+ output.create { filter.generate_output([input], output) }
112
+
113
+ output
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,41 @@
1
+ module Rake::Pipeline::Web::Filters
2
+ # A filter that compiles CoffeeScript to JavaScript.
3
+ class CoffeeScriptFilter < Rake::Pipeline::Filter
4
+ include Rake::Pipeline::Web::Filters::FilterWithDependencies
5
+
6
+ # @return [Hash] a hash of options to pass to CoffeeScript when
7
+ # rendering.
8
+ attr_reader :options
9
+
10
+ # By default, the CoffeeScriptFilter converts inputs
11
+ # with the extension +.coffee+ to +.js+.
12
+ #
13
+ # @param [Hash] options options to pass to the CoffeeScript
14
+ # compiler.
15
+ # @param [Proc] block the output name generator block
16
+ def initialize(options = {}, &block)
17
+ block ||= proc { |input| input.sub(/\.coffee$/, '.js') }
18
+ super(&block)
19
+ @options = options
20
+ end
21
+
22
+ # The body of the filter. Compile each input file into
23
+ # a CoffeeScript compiled output file.
24
+ #
25
+ # @param [Array] inputs an Array of FileWrapper objects.
26
+ # @param [FileWrapper] output a FileWrapper object
27
+ def generate_output(inputs, output)
28
+ inputs.each do |input|
29
+ begin
30
+ output.write CoffeeScript.compile(input, options)
31
+ rescue ExecJS::Error => error
32
+ raise error, "Error compiling #{input.path}. #{error.message}"
33
+ end
34
+ end
35
+ end
36
+
37
+ def external_dependencies
38
+ [ "coffee-script" ]
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,27 @@
1
+ module Rake::Pipeline::Web::Filters
2
+ # A mixin for filters that have dependencies on external
3
+ # libraries. Include this module in the filter class and
4
+ # declare a private `external_dependencies` method that
5
+ # returns an array of strings. Each one will be passed
6
+ # to `Kernel#require` when an instance of the filter
7
+ # is created.
8
+ module FilterWithDependencies
9
+
10
+ def initialize(*args, &block)
11
+ require_dependencies!
12
+ super(*args, &block)
13
+ end
14
+
15
+ private
16
+
17
+ def require_dependencies!
18
+ external_dependencies.each do |d|
19
+ begin
20
+ require d
21
+ rescue LoadError => error
22
+ raise error, "#{self.class} requires #{d}, but it is not available."
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,59 @@
1
+ require 'rake-pipeline-web-filters/filter_with_dependencies'
2
+
3
+ module Rake::Pipeline::Web::Filters
4
+ # A filter that gzips input files using zlib
5
+ #
6
+ #
7
+ # @example
8
+ # !!!ruby
9
+ # Rake::Pipeline.build do
10
+ # input "app/assets", "**/*.js"
11
+ # output "public"
12
+ #
13
+ # # Compress each JS file under the app/assets
14
+ # # directory.
15
+ # filter Rake::Pipeline::Web::Filters::GzipFilter
16
+ # end
17
+ class GzipFilter < Rake::Pipeline::Filter
18
+
19
+ include Rake::Pipeline::Web::Filters::FilterWithDependencies
20
+
21
+ processes_binary_files
22
+
23
+ # @param [Proc] block a block to use as the Filter's
24
+ # {#output_name_generator}.
25
+ def initialize(&block)
26
+ block ||= proc { |input| input.sub(/\.(.*)$/, '.\1.gz') }
27
+ super(&block)
28
+ end
29
+
30
+ # Implement the {#generate_output} method required by
31
+ # the {Filter} API. Compresses each input file with
32
+ # Zlib.GzipWriter.
33
+ #
34
+ # @param [Array<FileWrapper>] inputs an Array of
35
+ # {FileWrapper} objects representing the inputs to
36
+ # this filter.
37
+ # @param [FileWrapper] output a single {FileWrapper}
38
+ # object representing the output.
39
+ def generate_output(inputs, output)
40
+ inputs.each do |input|
41
+ # gzip input file to stream
42
+ fakefile = StringIO.new
43
+ gz = Zlib::GzipWriter.new(fakefile)
44
+ gz.write(input.read)
45
+ gz.close
46
+
47
+ # send gzipped contents to output
48
+ output.write fakefile.string
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def external_dependencies
55
+ [ 'stringio', 'zlib' ]
56
+ end
57
+ end
58
+ end
59
+
@@ -0,0 +1,62 @@
1
+ module Rake::Pipeline::Web::Filters
2
+ # A filter that converts handlebars templates to javascript
3
+ # and stores them in a defined variable.
4
+ #
5
+ # @example
6
+ # !!!ruby
7
+ # Rake::Pipeline.build do
8
+ # input "**/*.handlebars"
9
+ # output "public"
10
+ #
11
+ # # Compile each handlebars file to JS
12
+ # handlebars
13
+ # end
14
+ class HandlebarsFilter < Rake::Pipeline::Filter
15
+
16
+ include Rake::Pipeline::Web::Filters::FilterWithDependencies
17
+
18
+ # @return [Hash] a hash of options for generate_output
19
+ attr_reader :options
20
+
21
+ # @param [Hash] options
22
+ # options to pass to the output generator
23
+ # @option options [Array] :target
24
+ # the variable to store templates in
25
+ # @option options [Array] :compile_open
26
+ # the js to wrap template contents in
27
+ # @option options [Array] :compile_close
28
+ # the js to wrap template contents in
29
+ # @param [Proc] block a block to use as the Filter's
30
+ # {#output_name_generator}.
31
+ def initialize(options={},&block)
32
+ # Convert .handlebars file extensions to .js
33
+ block ||= proc { |input| input.sub(/\.handlebars|\.hbs$/, '.js') }
34
+ super(&block)
35
+ @options = {
36
+ :target =>'Ember.TEMPLATES',
37
+ :wrapper_proc => proc { |source| "Ember.Handlebars.compile(#{source});" },
38
+ :key_name_proc => proc { |input| File.basename(input.path, File.extname(input.path)) }
39
+ }.merge(options)
40
+ end
41
+
42
+ def generate_output(inputs, output)
43
+
44
+ inputs.each do |input|
45
+ # The name of the template is the filename, sans extension
46
+ name = options[:key_name_proc].call(input)
47
+
48
+ # Read the file and escape it so it's a valid JS string
49
+ source = input.read.to_json
50
+
51
+ # Write out a JS file, saved to target, wrapped in compiler
52
+ output.write "#{options[:target]}['#{name}']=#{options[:wrapper_proc].call(source)}"
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def external_dependencies
59
+ [ 'json' ]
60
+ end
61
+ end
62
+ end
@@ -5,7 +5,7 @@ module Rake::Pipeline::Web::Filters
5
5
  # Instead of:
6
6
  # !!!ruby
7
7
  # match("*.scss") do
8
- # filter Rake::Pipeline::Web::Filters::SassCompiler, :syntax => :sass
8
+ # filter Rake::Pipeline::Web::Filters::SassFilter, :syntax => :sass
9
9
  # end
10
10
  #
11
11
  # You can do:
@@ -13,38 +13,120 @@ module Rake::Pipeline::Web::Filters
13
13
  # match("*.scss") do
14
14
  # sass :syntax => :sass
15
15
  # end
16
- module Helpers
17
- # If the first argument is an Array, add a new {OrderingConcatFilter}
18
- # to the pipeline. Otherwise add a new {Rake::Pipeline::ConcatFilter}.
19
- # @see OrderingConcatFilter#initialize
20
- # @see Rake::Pipeline::ConcatFilter#initialize
21
- def concat(*args, &block)
22
- if args.first.kind_of?(Array)
23
- filter(Rake::Pipeline::Web::Filters::OrderingConcatFilter, *args, &block)
24
- else
25
- filter(Rake::Pipeline::ConcatFilter, *args, &block)
26
- end
27
- end
28
-
16
+ module PipelineHelpers
29
17
  # Add a new {MinispadeFilter} to the pipeline.
30
18
  # @see MinispadeFilter#initialize
31
19
  def minispade(*args, &block)
32
20
  filter(Rake::Pipeline::Web::Filters::MinispadeFilter, *args, &block)
33
21
  end
34
22
 
35
- # Add a new {SassCompiler} to the pipeline.
36
- # @see SassCompiler#initialize
23
+ # Add a new {NeuterFilter} to the pipeline.
24
+ # @see NeuterFilter#initialize
25
+ def neuter(*args, &block)
26
+ filter(Rake::Pipeline::Web::Filters::NeuterFilter, *args, &block)
27
+ end
28
+
29
+ # Add a new {SassFilter} to the pipeline.
30
+ # @see SassFilter#initialize
37
31
  def sass(*args, &block)
38
- filter(Rake::Pipeline::Web::Filters::SassCompiler, *args, &block)
32
+ filter(Rake::Pipeline::Web::Filters::SassFilter, *args, &block)
39
33
  end
40
34
  alias_method :scss, :sass
41
35
 
36
+ # Add a new {StylusFilter} to the pipeline.
37
+ # @see StylusFilter#initialize
38
+ def stylus(*args, &block)
39
+ filter(Rake::Pipeline::Web::Filters::StylusFilter, *args, &block)
40
+ end
41
+
42
42
  # Add a new {TiltFilter} to the pipeline.
43
43
  # @see TiltFilter#initialize
44
44
  def tilt(*args, &block)
45
45
  filter(Rake::Pipeline::Web::Filters::TiltFilter, *args, &block)
46
46
  end
47
+
48
+ # Add a new {MarkdownFilter} to the pipeline.
49
+ # @see MarkdownFilter#initialize
50
+ def markdown(*args, &block)
51
+ filter(Rake::Pipeline::Web::Filters::MarkdownFilter, *args, &block)
52
+ end
53
+
54
+ # Add a new {CacheBusterFilter} to the pipeline.
55
+ # @see CacheBusterFilter#initialize
56
+ def cache_buster(&block)
57
+ filter(Rake::Pipeline::Web::Filters::CacheBusterFilter, &block)
58
+ end
59
+
60
+ # Add a new {CoffeeScriptFilter} to the pipeline.
61
+ # @see CoffeeScriptFilter#initialize
62
+ def coffee_script(*args, &block)
63
+ filter(Rake::Pipeline::Web::Filters::CoffeeScriptFilter, *args, &block)
64
+ end
65
+
66
+ # Add a new {YUIJavaScriptFilter} to the pipeline.
67
+ # @see YUIJavaScriptFilter#initialize
68
+ def yui_javascript(*args, &block)
69
+ filter(Rake::Pipeline::Web::Filters::YUIJavaScriptFilter, *args, &block)
70
+ end
71
+
72
+ # Add a new {YUICssFilter} to the pipeline.
73
+ # @see YUICssFilter#initialize
74
+ def yui_css(*args, &block)
75
+ filter(Rake::Pipeline::Web::Filters::YUICssFilter, *args, &block)
76
+ end
77
+
78
+ # Add a new {GzipFilter} to the pipeline.
79
+ # @see GzipFilter#initialize
80
+ def gzip(&block)
81
+ filter(Rake::Pipeline::Web::Filters::GzipFilter, &block)
82
+ end
83
+
84
+ # Add a new {UglifyFilter} to the pipeline.
85
+ # @see UglifyFilter#initialize
86
+ def uglify(*args, &block)
87
+ filter(Rake::Pipeline::Web::Filters::UglifyFilter, *args, &block)
88
+ end
89
+
90
+ # Add a new {LessFilter} to the pipeline.
91
+ # @see LessFilter#initialize
92
+ def less(*args, &block)
93
+ filter(Rake::Pipeline::Web::Filters::LessFilter, *args, &block)
94
+ end
95
+
96
+ # Add a new {HandlebarsFilter} to the pipeline.
97
+ # @see HandlebarsFilter#initialize
98
+ def handlebars(*args, &block)
99
+ filter(Rake::Pipeline::Web::Filters::HandlebarsFilter, *args, &block)
100
+ end
101
+ #
102
+ # Add a new {IifeFilter} to the pipeline.
103
+ # @see IifeFilter#initialize
104
+ def iife(*args, &block)
105
+ filter(Rake::Pipeline::Web::Filters::IifeFilter, *args, &block)
106
+ end
107
+ end
108
+
109
+ module ProjectHelpers
110
+ # Register a filter class for a particular file extension
111
+ # and add a ChainedFilter as a before filter.
112
+ #
113
+ # If this is the first use of +register+, it will set up
114
+ # the before filter. Subsequent uses will just update the
115
+ # types hash.
116
+ #
117
+ # @see ChainedFilter
118
+ def register(extension, klass)
119
+ if @types_hash
120
+ @types_hash[extension] = klass
121
+ else
122
+ @types_hash = { extension => klass }
123
+ before_filter ChainedFilter, { :types => @types_hash }
124
+ end
125
+ end
47
126
  end
48
127
  end
49
128
 
50
- Rake::Pipeline::DSL.send(:include, Rake::Pipeline::Web::Filters::Helpers)
129
+ require "rake-pipeline/dsl"
130
+
131
+ Rake::Pipeline::DSL::PipelineDSL.send(:include, Rake::Pipeline::Web::Filters::PipelineHelpers)
132
+ Rake::Pipeline::DSL::ProjectDSL.send(:include, Rake::Pipeline::Web::Filters::ProjectHelpers)