rake-pipeline-web-filters 0.5.0 → 0.7.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 (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)