├── .gitignore ├── .rspec ├── .travis.yml ├── .yardopts ├── Gemfile ├── LICENSE ├── README.markdown ├── README.yard ├── Rakefile ├── lib ├── rake-pipeline-web-filters.rb └── rake-pipeline-web-filters │ ├── cache_buster_filter.rb │ ├── chained_filter.rb │ ├── coffee_script_filter.rb │ ├── es6_module_filter.rb │ ├── filter_with_dependencies.rb │ ├── gzip_filter.rb │ ├── handlebars_filter.rb │ ├── helpers.rb │ ├── iife_filter.rb │ ├── jade_filter.rb │ ├── less_filter.rb │ ├── markdown_filter.rb │ ├── minispade_filter.rb │ ├── neuter_filter.rb │ ├── sass_filter.rb │ ├── stylus_filter.rb │ ├── tilt_filter.rb │ ├── uglify_filter.rb │ ├── version.rb │ ├── yui_css_filter.rb │ └── yui_javascript_filter.rb ├── rake-pipeline-web-filters.gemspec └── spec ├── cache_buster_filter_spec.rb ├── chained_filter_spec.rb ├── coffee_script_filter_spec.rb ├── es6_module_filter_spec.rb ├── gzip_filter_spec.rb ├── handlebars_filter_spec.rb ├── helpers_spec.rb ├── iife_filter_spec.rb ├── jade_filter_spec.rb ├── less_filter_spec.rb ├── markdown_filter_spec.rb ├── minispade_filter_spec.rb ├── neuter_filter_spec.rb ├── sass_filter_spec.rb ├── spec_helper.rb ├── stylus_filter_spec.rb ├── support └── spec_helpers │ ├── file_utils.rb │ ├── filters.rb │ ├── input_helpers.rb │ ├── memory_file_wrapper.rb │ └── memory_manifest.rb ├── tilt_filter_spec.rb ├── uglify_filter_spec.rb ├── yui_css_filter_spec.rb └── yui_javascript_filter_spec.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | node_modules 19 | .project 20 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | -c -f d -r spec_helper.rb 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | rvm: 2 | - 1.9.2 3 | - 1.9.3 4 | 5 | bundler_args: --without docs 6 | 7 | before_script: npm install -g jade 8 | 9 | notifications: 10 | email: 11 | - wycats@gmail.com 12 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --readme README.yard 2 | 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | 3 | gem "rake-pipeline", :git => "git://github.com/livingsocial/rake-pipeline.git" 4 | 5 | # Specify your gem's dependencies in rake-pipeline-web-filters.gemspec 6 | gemspec 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Tilde Inc. and other contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # Rake::Pipeline::Web::Filters [![Build Status](https://secure.travis-ci.org/wycats/rake-pipeline-web-filters.png?branch=master)](http://travis-ci.org/wycats/rake-pipeline-web-filters) 2 | 3 | This project contains a set of rake-pipeline filters for building web 4 | apps. 5 | 6 | It includes these filters: 7 | 8 | * Cache Buster - Write a fingerprint into each file name 9 | * Coffescript - Convert Coffeescript to Javascript 10 | * ES6 Module Transpiler - Transpile ES6 to ES5 Javascript ([Available Options](https://github.com/square/es6-module-transpiler)) 11 | * GZip - Create gzip'd version of your files 12 | * Handlebars - Process handlebars templates 13 | * IIFE - Wrap source files in Immediately Invoked Function Expressions 14 | * Jade - Process Jade templates 15 | * LESS - Convert LESS to CSS 16 | * Markdown - Convert Markdown to HTML 17 | * Minispade - Wrap JS files in Minispade modules 18 | * Neuter - Require files in a file and generate one single combined file 19 | * SASS - Convert SASS to CSS 20 | * Stylus - Convert Stylus to CSS 21 | * Tilt - Use Tilt to process 22 | * Uglify - Minify JS 23 | * YUI CSS - Minify CSS 24 | * YUI Javascript - Minify JS 25 | 26 | Here's a quick example of a realistic project's Assetfile: 27 | 28 | ```ruby 29 | # Assetfile 30 | require 'rake-pipeline-web-filters' 31 | 32 | output "site" 33 | 34 | input "javascripts" do 35 | match "**/*.coffee" do 36 | coffee_script 37 | end 38 | 39 | match "**/*.js" do 40 | minispade 41 | concat "application.js" 42 | uglify 43 | end 44 | end 45 | 46 | input "stylesheets" do 47 | match "**/*.sass" do 48 | sass 49 | end 50 | 51 | match "**/*.css" do 52 | concat "application.css" 53 | yui_css 54 | end 55 | end 56 | ``` 57 | 58 | API documentation is hosted at 59 | rubydoc.info 60 | -------------------------------------------------------------------------------- /README.yard: -------------------------------------------------------------------------------- 1 | = Rake::Pipeline::Web::Filters 2 | 3 | Rake::Pipeline::Web::Filters is a collection of filters for Rake::Pipeline 4 | for creating pipelines to generate web content. 5 | 6 | = Usage 7 | 8 | In your +Assetfile+: 9 | 10 | !!!ruby 11 | require "rake-pipeline-web-filters" 12 | 13 | input "assets" 14 | output "public" 15 | 16 | # Take all JS inputs and wrap each of them in code to 17 | # register them with the Minispade module loader. 18 | match "*.js" do 19 | filter Rake::Pipeline::Web::Filters::MinispadeFilter 20 | end 21 | 22 | # Take all SCSS inputs and compile them with Sass 23 | match "*.scss" do 24 | filter Rake::Pipeline::Web::Filters::SassCompiler 25 | end 26 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | require "bundler/gem_tasks" 3 | require "bundler/setup" 4 | 5 | desc "run the specs" 6 | task :spec do 7 | sh "rspec -c -f d spec" 8 | end 9 | 10 | task :default => :spec 11 | -------------------------------------------------------------------------------- /lib/rake-pipeline-web-filters.rb: -------------------------------------------------------------------------------- 1 | require "rake-pipeline" 2 | 3 | module Rake 4 | class Pipeline 5 | module Web 6 | module Filters 7 | end 8 | end 9 | end 10 | end 11 | 12 | require "rake-pipeline-web-filters/version" 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" 16 | require "rake-pipeline-web-filters/minispade_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/jade_filter" 21 | require "rake-pipeline-web-filters/tilt_filter" 22 | require "rake-pipeline-web-filters/coffee_script_filter" 23 | require "rake-pipeline-web-filters/yui_javascript_filter" 24 | require "rake-pipeline-web-filters/yui_css_filter" 25 | require "rake-pipeline-web-filters/gzip_filter" 26 | require "rake-pipeline-web-filters/uglify_filter" 27 | require "rake-pipeline-web-filters/chained_filter" 28 | require "rake-pipeline-web-filters/less_filter" 29 | require "rake-pipeline-web-filters/handlebars_filter" 30 | require "rake-pipeline-web-filters/iife_filter" 31 | require "rake-pipeline-web-filters/es6_module_filter" 32 | require "rake-pipeline-web-filters/helpers" 33 | -------------------------------------------------------------------------------- /lib/rake-pipeline-web-filters/cache_buster_filter.rb: -------------------------------------------------------------------------------- 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 43 | -------------------------------------------------------------------------------- /lib/rake-pipeline-web-filters/chained_filter.rb: -------------------------------------------------------------------------------- 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 117 | -------------------------------------------------------------------------------- /lib/rake-pipeline-web-filters/coffee_script_filter.rb: -------------------------------------------------------------------------------- 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 42 | -------------------------------------------------------------------------------- /lib/rake-pipeline-web-filters/es6_module_filter.rb: -------------------------------------------------------------------------------- 1 | module Rake::Pipeline::Web::Filters 2 | # A filter that transpiles ES6 to either AMD or CommonJS JavaScript. 3 | class ES6ModuleFilter < Rake::Pipeline::Filter 4 | include Rake::Pipeline::Web::Filters::FilterWithDependencies 5 | 6 | # @return [Hash] a hash of options to pass to Transpiler 7 | attr_reader :options 8 | 9 | # Create an instance of this filter. 10 | # 11 | # Possible options: 12 | # module_id_generator: provide a Proc to convert an input to a 13 | # module identifier (AMD only) 14 | # Other options are passed along to the RubyES6ModuleTranspiler and then to 15 | # the node transpiler. See https://github.com/square/es6-module-transpiler 16 | # for more info. 17 | # 18 | # @param [Hash] options options (see above) 19 | # @param [Proc] block the output name generator block 20 | def initialize(options = {}, &block) 21 | @module_id_generator = options[:module_id_generator] 22 | super(&block) 23 | @options = options 24 | end 25 | 26 | # The body of the filter. Compile each input file into 27 | # a ES6 Module Transpiled output file. 28 | # 29 | # @param [Array] inputs an Array of FileWrapper objects. 30 | # @param [FileWrapper] output a FileWrapper object 31 | def generate_output(inputs, output) 32 | inputs.each do |input| 33 | begin 34 | body = input.read if input.respond_to?(:read) 35 | local_opts = {} 36 | if @module_id_generator 37 | local_opts[:moduleName] = @module_id_generator.call(input) 38 | end 39 | opts = @options.merge(local_opts) 40 | opts.delete(:module_id_generator) 41 | output.write RubyES6ModuleTranspiler.transpile(body, opts) 42 | rescue ExecJS::Error => error 43 | raise error, "Error compiling #{input.path}. #{error.message}" 44 | end 45 | end 46 | end 47 | 48 | def external_dependencies 49 | [ "ruby_es6_module_transpiler" ] 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/rake-pipeline-web-filters/filter_with_dependencies.rb: -------------------------------------------------------------------------------- 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 28 | -------------------------------------------------------------------------------- /lib/rake-pipeline-web-filters/gzip_filter.rb: -------------------------------------------------------------------------------- 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] 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 | 60 | -------------------------------------------------------------------------------- /lib/rake-pipeline-web-filters/handlebars_filter.rb: -------------------------------------------------------------------------------- 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 63 | -------------------------------------------------------------------------------- /lib/rake-pipeline-web-filters/helpers.rb: -------------------------------------------------------------------------------- 1 | module Rake::Pipeline::Web::Filters 2 | # Extends the Rake::Pipeline DSL to include shortcuts 3 | # for adding filters to the pipeline. 4 | # 5 | # Instead of: 6 | # !!!ruby 7 | # match("*.scss") do 8 | # filter Rake::Pipeline::Web::Filters::SassFilter, :syntax => :sass 9 | # end 10 | # 11 | # You can do: 12 | # !!!ruby 13 | # match("*.scss") do 14 | # sass :syntax => :sass 15 | # end 16 | module PipelineHelpers 17 | # Add a new {MinispadeFilter} to the pipeline. 18 | # @see MinispadeFilter#initialize 19 | def minispade(*args, &block) 20 | filter(Rake::Pipeline::Web::Filters::MinispadeFilter, *args, &block) 21 | end 22 | 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 31 | def sass(*args, &block) 32 | filter(Rake::Pipeline::Web::Filters::SassFilter, *args, &block) 33 | end 34 | alias_method :scss, :sass 35 | 36 | # Add a new {JadeFilter} to the pipeline. 37 | # @see JadeFilter#initialize 38 | def jade(*args, &block) 39 | filter(Rake::Pipeline::Web::Filters::JadeFilter, *args, &block) 40 | end 41 | 42 | # Add a new {StylusFilter} to the pipeline. 43 | # @see StylusFilter#initialize 44 | def stylus(*args, &block) 45 | filter(Rake::Pipeline::Web::Filters::StylusFilter, *args, &block) 46 | end 47 | 48 | # Add a new {TiltFilter} to the pipeline. 49 | # @see TiltFilter#initialize 50 | def tilt(*args, &block) 51 | filter(Rake::Pipeline::Web::Filters::TiltFilter, *args, &block) 52 | end 53 | 54 | # Add a new {MarkdownFilter} to the pipeline. 55 | # @see MarkdownFilter#initialize 56 | def markdown(*args, &block) 57 | filter(Rake::Pipeline::Web::Filters::MarkdownFilter, *args, &block) 58 | end 59 | 60 | # Add a new {CacheBusterFilter} to the pipeline. 61 | # @see CacheBusterFilter#initialize 62 | def cache_buster(&block) 63 | filter(Rake::Pipeline::Web::Filters::CacheBusterFilter, &block) 64 | end 65 | 66 | # Add a new {CoffeeScriptFilter} to the pipeline. 67 | # @see CoffeeScriptFilter#initialize 68 | def coffee_script(*args, &block) 69 | filter(Rake::Pipeline::Web::Filters::CoffeeScriptFilter, *args, &block) 70 | end 71 | 72 | # Add a new {ES6ModuleFilter} to the pipeline. 73 | # @see ES6ModuleFilter#initialize 74 | def es6_module(*args, &block) 75 | filter(Rake::Pipeline::Web::Filters::ES6ModuleFilter, *args, &block) 76 | end 77 | 78 | # Add a new {YUIJavaScriptFilter} to the pipeline. 79 | # @see YUIJavaScriptFilter#initialize 80 | def yui_javascript(*args, &block) 81 | filter(Rake::Pipeline::Web::Filters::YUIJavaScriptFilter, *args, &block) 82 | end 83 | 84 | # Add a new {YUICssFilter} to the pipeline. 85 | # @see YUICssFilter#initialize 86 | def yui_css(*args, &block) 87 | filter(Rake::Pipeline::Web::Filters::YUICssFilter, *args, &block) 88 | end 89 | 90 | # Add a new {GzipFilter} to the pipeline. 91 | # @see GzipFilter#initialize 92 | def gzip(&block) 93 | filter(Rake::Pipeline::Web::Filters::GzipFilter, &block) 94 | end 95 | 96 | # Add a new {UglifyFilter} to the pipeline. 97 | # @see UglifyFilter#initialize 98 | def uglify(*args, &block) 99 | filter(Rake::Pipeline::Web::Filters::UglifyFilter, *args, &block) 100 | end 101 | 102 | # Add a new {LessFilter} to the pipeline. 103 | # @see LessFilter#initialize 104 | def less(*args, &block) 105 | filter(Rake::Pipeline::Web::Filters::LessFilter, *args, &block) 106 | end 107 | 108 | # Add a new {HandlebarsFilter} to the pipeline. 109 | # @see HandlebarsFilter#initialize 110 | def handlebars(*args, &block) 111 | filter(Rake::Pipeline::Web::Filters::HandlebarsFilter, *args, &block) 112 | end 113 | # 114 | # Add a new {IifeFilter} to the pipeline. 115 | # @see IifeFilter#initialize 116 | def iife(*args, &block) 117 | filter(Rake::Pipeline::Web::Filters::IifeFilter, *args, &block) 118 | end 119 | end 120 | 121 | module ProjectHelpers 122 | # Register a filter class for a particular file extension 123 | # and add a ChainedFilter as a before filter. 124 | # 125 | # If this is the first use of +register+, it will set up 126 | # the before filter. Subsequent uses will just update the 127 | # types hash. 128 | # 129 | # @see ChainedFilter 130 | def register(extension, klass) 131 | if @types_hash 132 | @types_hash[extension] = klass 133 | else 134 | @types_hash = { extension => klass } 135 | before_filter ChainedFilter, { :types => @types_hash } 136 | end 137 | end 138 | end 139 | end 140 | 141 | require "rake-pipeline/dsl" 142 | 143 | Rake::Pipeline::DSL::PipelineDSL.send(:include, Rake::Pipeline::Web::Filters::PipelineHelpers) 144 | Rake::Pipeline::DSL::ProjectDSL.send(:include, Rake::Pipeline::Web::Filters::ProjectHelpers) 145 | -------------------------------------------------------------------------------- /lib/rake-pipeline-web-filters/iife_filter.rb: -------------------------------------------------------------------------------- 1 | module Rake::Pipeline::Web::Filters 2 | # A filter that wraps input files in an IIFE (immediately invoked functional expression) 3 | # 4 | # 5 | # @example 6 | # !!!ruby 7 | # Rake::Pipeline.build do 8 | # input "app/assets", "**/*.js" 9 | # output "public" 10 | # 11 | # # Wrap each file in: (function() { ... })();" 12 | # filter Rake::Pipeline::Web::Filters::IifeFilter 13 | # end 14 | class IifeFilter < Rake::Pipeline::Filter 15 | # @param [Proc] block a block to use as the Filter's 16 | # {#output_name_generator}. 17 | def initialize(&block) 18 | block ||= proc { |input| input } 19 | super(&block) 20 | end 21 | 22 | # Implement the {#generate_output} method required by 23 | # the {Filter} API. Wraps each input in an IIFE. 24 | # 25 | # @param [Array] inputs an Array of 26 | # {FileWrapper} objects representing the inputs to 27 | # this filter. 28 | # @param [FileWrapper] output a single {FileWrapper} 29 | # object representing the output. 30 | def generate_output(inputs, output) 31 | inputs.each do |input| 32 | output.write "(function() {\n" 33 | output.write input.read 34 | output.write "})();" 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/rake-pipeline-web-filters/jade_filter.rb: -------------------------------------------------------------------------------- 1 | require 'rake-pipeline-web-filters/filter_with_dependencies' 2 | 3 | module Rake::Pipeline::Web::Filters 4 | # A filter that compiles input files written in Jade 5 | # to HTML using the Jade compiler 6 | # 7 | # Requires node and https://npmjs.org/package/jade by 8 | # [sudo] npm install jade -g 9 | # 10 | # @example 11 | # !!!ruby 12 | # Rake::Pipeline.build do 13 | # input "app/assets", "**/*.jade" 14 | # output "public" 15 | # 16 | # # Compile each Jade file under the app/assets 17 | # # directory. 18 | # filter Rake::Pipeline::Web::Filters::JadeFilter 19 | # end 20 | # !!!ruby 21 | # Rake::Pipeline.build do 22 | # input "app/assets", "**/*.jade" 23 | # output "public" 24 | # 25 | # jade :pretty 26 | # end 27 | class JadeFilter < Rake::Pipeline::Filter 28 | include Rake::Pipeline::Web::Filters::FilterWithDependencies 29 | 30 | # @return [Hash] a hash of options to pass to Less 31 | # when compiling. 32 | # @option option :prettify output 33 | attr_reader :options 34 | 35 | def initialize(options={}, &block) 36 | block ||= proc { |input| input.sub(/\.(jade)$/, '.html') } 37 | super(&block) 38 | @options = options 39 | end 40 | 41 | def generate_output(inputs, output) 42 | 43 | inputs.each do |input| 44 | if options[:pretty] 45 | `jade < #{input.root}/#{input.path} -P --path #{input.root}/#{input.path} > #{output.root}/#{output.path}` 46 | else 47 | `jade < #{input.root}/#{input.path} --path #{input.root}/#{input.path} > #{output.root}/#{output.path}` 48 | end 49 | out = output.read 50 | output.write out 51 | end 52 | end 53 | 54 | private 55 | 56 | def external_dependencies 57 | [ ] 58 | end 59 | 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/rake-pipeline-web-filters/less_filter.rb: -------------------------------------------------------------------------------- 1 | require 'rake-pipeline-web-filters/filter_with_dependencies' 2 | 3 | module Rake::Pipeline::Web::Filters 4 | # A filter that compiles input files written in Less 5 | # to CSS using the Less compiler. 6 | # 7 | # Requires {https://rubygems.org/gems/less-js less-js} 8 | # 9 | # @example 10 | # !!!ruby 11 | # Rake::Pipeline.build do 12 | # input "app/assets", "**/*.less" 13 | # output "public" 14 | # 15 | # # Compile each less file with Less.js 16 | # filter Rake::Pipeline::Web::Filters::LessFilter 17 | # end 18 | class LessFilter < Rake::Pipeline::Filter 19 | include Rake::Pipeline::Web::Filters::FilterWithDependencies 20 | 21 | # @return [Hash] a hash of options to pass to Less 22 | # when compiling. 23 | attr_reader :options 24 | 25 | # @param [Proc] block a block to use as the Filter's 26 | # {#output_name_generator}. 27 | def initialize(options={}, context = nil, &block) 28 | block ||= proc { |input| input.sub(/\.less$/, '.css') } 29 | super(&block) 30 | @options = options 31 | end 32 | 33 | # Implement the {#generate_output} method required by 34 | # the {Filter} API. Attempts to compile each input file 35 | # with LessJs. 36 | # 37 | # @param [Array] inputs an Array of 38 | # {FileWrapper} objects representing the inputs to 39 | # this filter. 40 | # @param [FileWrapper] output a single {FileWrapper} 41 | # object representing the output. 42 | def generate_output(inputs, output) 43 | parser = Less::Parser.new options 44 | inputs.each do |input| 45 | output.write parser.parse(input.read).to_css 46 | end 47 | end 48 | 49 | def external_dependencies 50 | [ 'less' ] 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/rake-pipeline-web-filters/markdown_filter.rb: -------------------------------------------------------------------------------- 1 | module Rake::Pipeline::Web::Filters 2 | # A filter that compiles input files written in Markdown 3 | # to Markdown using the Redcarpet compiler. 4 | # 5 | # @example 6 | # !!!ruby 7 | # Rake::Pipeline.build do 8 | # input "app/assets", "**/*.md" 9 | # output "public" 10 | # 11 | # # Compile each .md file under the app/assets 12 | # # directory. 13 | # filter Rake::Pipeline::Web::Filters::MarkdownFilter 14 | # end 15 | # 16 | class MarkdownFilter < Rake::Pipeline::Filter 17 | 18 | # @param [Hash] options options to pass to the markdown 19 | # compiler 20 | # @see http://rubydoc.info/gems/redcarpet/2.0.0/frames for more information 21 | # about options 22 | # @option options [#call] :compiler If you wish to use a different 23 | # Markdown compiler, you can do so by passing anything that responds 24 | # to `:call`, which will be passed the Markdown text and any 25 | # options (other than `:compiler`). 26 | # @option options [Redcarpet::Render::Base] :renderer a 27 | # Redcarpet renderer. Used only if using the default compiler. 28 | # @param [Proc] block a block to use as the Filter's 29 | # {#output_name_generator}. 30 | def initialize(options={}, &block) 31 | block ||= proc { |input| input.sub(/\.(md|mdown|mkdown|markdown)$/, '.html') } 32 | super(&block) 33 | @compiler = options.delete(:compiler) 34 | @options = options 35 | end 36 | 37 | # Implement the {#generate_output} method required by 38 | # the {Filter} API. Compiles each input file with Sass. 39 | # 40 | # @param [Array] inputs an Array of 41 | # {FileWrapper} objects representing the inputs to 42 | # this filter. 43 | # @param [FileWrapper] output a single {FileWrapper} 44 | # object representing the output. 45 | def generate_output(inputs, output) 46 | inputs.each do |input| 47 | output.write compile(input.read) 48 | end 49 | end 50 | 51 | private 52 | 53 | def compile(markdown) 54 | if @compiler 55 | @compiler.call(markdown, @options) 56 | else 57 | default_compiler.render(markdown) 58 | end 59 | end 60 | 61 | def default_compiler 62 | @default_renderer ||= begin 63 | require 'redcarpet' 64 | renderer = @options.delete(:renderer) || Redcarpet::Render::HTML.new 65 | Redcarpet::Markdown.new(renderer, @options) 66 | end 67 | end 68 | 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/rake-pipeline-web-filters/minispade_filter.rb: -------------------------------------------------------------------------------- 1 | module Rake::Pipeline::Web::Filters 2 | # A filter that wraps JavaScript files in a minispade.register closure 3 | # for use in minispade. 4 | # 5 | # @example 6 | # !!!ruby 7 | # Rake::Pipeline.build do 8 | # input "app/assets", "**/*.js" 9 | # output "public" 10 | # 11 | # # Wrap each JS file in a minispade.register closure. 12 | # filter Rake::Pipeline::Web::Filters::MinispadeFilter 13 | # end 14 | class MinispadeFilter < Rake::Pipeline::Filter 15 | 16 | # @param [Hash] options 17 | # @option options [Boolean] :use_strict Whether to add "use strict" to 18 | # each outputted function; defaults to false. 19 | # @option options [Proc] :module_id_generator a proc to use to generate 20 | # the minispade module id. 21 | # @option options [Boolean] :rewrite_requires If true, change calls to 22 | # +require+ in the source to +minispade.require+. 23 | # @option options [Boolean] :string If true, compiles the output as 24 | # a String instead of a closure. This means that @sourceURL can be 25 | # appended for good stack traces and debugging. 26 | def initialize(options = {}) 27 | super() 28 | @use_strict = options[:use_strict] 29 | @module_id_generator = options[:module_id_generator] || 30 | proc { |input| input.fullpath.sub(Dir.pwd, '') } 31 | @rewrite_requires = options[:rewrite_requires] 32 | @string_module = options[:string] 33 | end 34 | 35 | # Implement the {#generate_output} method required by 36 | # the {Filter} API. Wraps each input file in a minispade.register 37 | # closure. 38 | # 39 | # @param [Array] inputs an Array of 40 | # {FileWrapper} objects representing the inputs to 41 | # this filter. 42 | # @param [FileWrapper] output a single {FileWrapper} 43 | # object representing the output. 44 | def generate_output(inputs, output) 45 | inputs.each do |input| 46 | code = input.read 47 | code.gsub!(%r{^\s*require\s*\(\s*}, 'minispade.require(') if @rewrite_requires 48 | code.gsub!(%r{^\s*requireAll\s*\(\s*}, 'minispade.requireAll(') if @rewrite_requires 49 | code = %["use strict";\n] + code if @use_strict 50 | 51 | module_id = @module_id_generator.call(input) 52 | 53 | if @string_module 54 | contents = %{(function() {#{code}\n})();\n//# sourceURL=#{module_id}}.to_json 55 | else 56 | contents = "function() {#{code}\n}" 57 | end 58 | ret = "minispade.register('#{module_id}', #{contents});" 59 | output.write ret 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/rake-pipeline-web-filters/neuter_filter.rb: -------------------------------------------------------------------------------- 1 | module Rake::Pipeline::Web::Filters 2 | 3 | class NeuterBatch 4 | def initialize(config, known_files) 5 | @config = config || {} 6 | @known_files = known_files 7 | @required = [] 8 | end 9 | 10 | def file_wrapper(klass, *args) 11 | file = klass.new(*args) 12 | file.extend NeuterWrapper 13 | file.batch(self) 14 | file 15 | end 16 | 17 | def required(req) 18 | unless @known_files.include?(req) 19 | warn "Included '#{req}', which is not listed in :additional_dependencies. The pipeline may not invalidate properly." 20 | end 21 | @required << req 22 | end 23 | 24 | def required?(req) 25 | @required.include?(req) 26 | end 27 | 28 | def strip_requires(source) 29 | requires = [] 30 | regexp = @config[:require_regexp] || %r{^\s*require\(['"]([^'"]*)['"]\);?\s*} 31 | # FIXME: This $1 may not be reliable with other regexps 32 | source.gsub!(regexp){ requires << $1; '' } 33 | requires 34 | end 35 | 36 | def transform_path(path, input) 37 | @config[:path_transform] ? @config[:path_transform].call(path, input) : path 38 | end 39 | 40 | def closure_wrap(source) 41 | @config[:closure_wrap] ? "(function() {\n#{source}\n})();\n\n" : source 42 | end 43 | 44 | def filename_comment(input) 45 | @config[:filename_comment] ? @config[:filename_comment].call(input) + "\n" : "" 46 | end 47 | end 48 | 49 | module NeuterWrapper 50 | def batch(batch) 51 | @batch = batch 52 | @batch.required(fullpath) 53 | end 54 | 55 | def read 56 | source = super 57 | 58 | required_files = @batch.strip_requires(source).map do |req| 59 | req_path = @batch.transform_path(req, self) 60 | if req_path && !@batch.required?(File.expand_path(req_path, root)) 61 | @batch.file_wrapper(self.class, root, req_path, encoding).read 62 | else 63 | nil 64 | end 65 | end.compact 66 | 67 | file = @batch.filename_comment(self) + @batch.closure_wrap(source) 68 | 69 | (required_files << file).join("\n\n") 70 | end 71 | end 72 | 73 | # A filter that takes files with requires and collapses them into a single 74 | # file without requires. 75 | # 76 | # @example 77 | # !!!ruby 78 | # Rake::Pipeline.build do 79 | # input "app/assets", "**/*.js" 80 | # output "public" 81 | # 82 | # filter Rake::Pipeline::Web::Filters::NeuterFilter, "neutered.js" 83 | # end 84 | class NeuterFilter < Rake::Pipeline::ConcatFilter 85 | def initialize(string=nil, config={}, &block) 86 | if string.is_a?(Hash) 87 | config = string 88 | string = nil 89 | end 90 | 91 | @config = config 92 | 93 | super(string, &block) 94 | end 95 | 96 | def generate_output(inputs, output) 97 | inputs.each do |input| 98 | known_files = [input.fullpath] + additional_dependencies(input) 99 | batch = NeuterBatch.new(@config, known_files) 100 | file = batch.file_wrapper(file_wrapper_class, input.root, input.path, input.encoding) 101 | output.write file.read 102 | end 103 | end 104 | 105 | def additional_dependencies(input) 106 | method = @config[:additional_dependencies] 107 | method ? method.call(input).map{|p| File.expand_path(p, input.root) } : [] 108 | end 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /lib/rake-pipeline-web-filters/sass_filter.rb: -------------------------------------------------------------------------------- 1 | require 'rake-pipeline-web-filters/filter_with_dependencies' 2 | 3 | module Rake::Pipeline::Web::Filters 4 | # A filter that compiles input files written in SCSS 5 | # to CSS using the Sass compiler and the Compass CSS 6 | # framework. 7 | # 8 | # Requires {http://rubygems.org/gems/sass sass} and 9 | # {http://rubygems.org/gems/compass compass} 10 | # 11 | # @example 12 | # !!!ruby 13 | # Rake::Pipeline.build do 14 | # input "app/assets", "**/*.scss" 15 | # output "public" 16 | # 17 | # # Compile each SCSS file under the app/assets 18 | # # directory. 19 | # filter Rake::Pipeline::Web::Filters::SassFilter 20 | # end 21 | class SassFilter < Rake::Pipeline::Filter 22 | include Rake::Pipeline::Web::Filters::FilterWithDependencies 23 | 24 | # @return [Hash] a hash of options to pass to Sass 25 | # when compiling. 26 | attr_reader :options, :additional_load_paths 27 | 28 | # @param [Hash] options options to pass to the Sass 29 | # compiler 30 | # @option options [Array] :additional_load_paths a 31 | # list of paths to append to Sass's :load_path. 32 | # @param [Proc] block a block to use as the Filter's 33 | # {#output_name_generator}. 34 | def initialize(options={}, &block) 35 | block ||= proc { |input| input.sub(/\.(scss|sass)$/, '.css') } 36 | super(&block) 37 | 38 | @options = compass_options 39 | @additional_load_paths = Array(options.delete(:additional_load_paths)) 40 | @options[:load_paths].concat(@additional_load_paths) 41 | @options.merge!(options) 42 | end 43 | 44 | # Implement the {#generate_output} method required by 45 | # the {Filter} API. Compiles each input file with Sass. 46 | # 47 | # @param [Array] inputs an Array of 48 | # {FileWrapper} objects representing the inputs to 49 | # this filter. 50 | # @param [FileWrapper] output a single {FileWrapper} 51 | # object representing the output. 52 | def generate_output(inputs, output) 53 | inputs.each do |input| 54 | output.write Sass.compile(input.read, sass_options_for_file(input)) 55 | end 56 | end 57 | 58 | def additional_dependencies(input) 59 | engine = Sass::Engine.new(input.read, sass_options_for_file(input)) 60 | engine.dependencies.map { |dep| dep.options[:filename] } 61 | end 62 | 63 | private 64 | 65 | def external_dependencies 66 | [ 'sass', 'compass' ] 67 | end 68 | 69 | # @return the Sass options for the current Compass 70 | # configuration. 71 | def compass_options 72 | Compass.add_project_configuration 73 | Compass.configuration.to_sass_engine_options 74 | end 75 | 76 | # @return the Sass options for the given +file+. 77 | # Adds a +:syntax+ option if the filter's options 78 | # don't already include one. 79 | def sass_options_for_file(file) 80 | added_opts = { 81 | :filename => file.fullpath, 82 | :syntax => file.path.match(/\.sass$/) ? :sass : :scss 83 | } 84 | added_opts.merge(@options) 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/rake-pipeline-web-filters/stylus_filter.rb: -------------------------------------------------------------------------------- 1 | require 'rake-pipeline-web-filters/filter_with_dependencies' 2 | 3 | module Rake::Pipeline::Web::Filters 4 | # A filter that compiles input files written in Stylus 5 | # to CSS using the Stylus compiler 6 | # 7 | # Requires http://rubygems.org/gems/stylus 8 | # 9 | # You will need the `node` command on your runtime 10 | # for this to work. See https://github.com/lucasmazza/ruby-stylus 11 | # for more information. 12 | # 13 | # @example 14 | # !!!ruby 15 | # Rake::Pipeline.build do 16 | # input "app/assets", "**/*.styl" 17 | # output "public" 18 | # 19 | # # Compile each Stylus file under the app/assets 20 | # # directory. 21 | # filter Rake::Pipeline::Web::Filters::StylusFilter 22 | # end 23 | class StylusFilter < Rake::Pipeline::Filter 24 | include Rake::Pipeline::Web::Filters::FilterWithDependencies 25 | 26 | attr_reader :options 27 | 28 | # @param [Hash] options options to pass to Stylus 29 | # @option options [Array] :use Plugins to import from Node 30 | # @option options [Boolean] :debug Output debug info 31 | # @param [Proc] block a block to use as the Filter's 32 | # {#output_name_generator}. 33 | def initialize(options={}, &block) 34 | block ||= proc { |input| input.sub(/\.(styl)$/, '.css') } 35 | super(&block) 36 | @options = options 37 | end 38 | 39 | def generate_output(inputs, output) 40 | options.each do |key, value| 41 | if key == :use 42 | Stylus.send key, *value 43 | next 44 | end 45 | Stylus.send "#{key}=", value 46 | end 47 | inputs.each do |input| 48 | output.write Stylus.compile(input.read) 49 | end 50 | end 51 | 52 | private 53 | 54 | def external_dependencies 55 | [ 'stylus' ] 56 | end 57 | 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/rake-pipeline-web-filters/tilt_filter.rb: -------------------------------------------------------------------------------- 1 | require 'rake-pipeline-web-filters/filter_with_dependencies' 2 | 3 | module Rake::Pipeline::Web::Filters 4 | # A filter that accepts a series of inputs and translates 5 | # them using the Tilt template interface, which will attempt 6 | # to guess which template language to use based on the input 7 | # file extension. 8 | # 9 | # Requires {http://rubygems.org/gems/tilt/ tilt} 10 | # 11 | # @example 12 | # !!!ruby 13 | # Rake::Pipeline.build do 14 | # input "app/assets", "**/*.scss" 15 | # output "public" 16 | # 17 | # # Compile each SCSS file using Tilt, replacing the 18 | # # scss extension with css. 19 | # filter(Rake::Pipeline::Web::Filters::TiltFilter) do |input| 20 | # input.sub(/\.scss$/, 'css') 21 | # end 22 | # end 23 | class TiltFilter < Rake::Pipeline::Filter 24 | include Rake::Pipeline::Web::Filters::FilterWithDependencies 25 | 26 | # @return [Hash] a hash of options to pass to Tilt 27 | # when rendering. 28 | attr_reader :options 29 | 30 | # @return [Object] an object to use as the rendering 31 | # context. 32 | attr_reader :context 33 | 34 | # @param [Hash] options options to pass to the Tilt 35 | # template class constructor. 36 | # @param [Proc] block a block to use as the Filter's 37 | # {#output_name_generator}. 38 | def initialize(options={}, context = nil, &block) 39 | super(&block) 40 | @options = options 41 | @context = context || Object.new 42 | end 43 | 44 | # Implement the {#generate_output} method required by 45 | # the {Filter} API. Attempts to compile each input file 46 | # with Tilt, passing the file through unchanged if Tilt 47 | # can't find a template class for the file. 48 | # 49 | # @param [Array] inputs an Array of 50 | # {FileWrapper} objects representing the inputs to 51 | # this filter. 52 | # @param [FileWrapper] output a single {FileWrapper} 53 | # object representing the output. 54 | def generate_output(inputs, output) 55 | inputs.each do |input| 56 | out = if (template_class = Tilt[input.path]) 57 | template_class.new(nil, 1, options) { |t| input.read }.render(context) 58 | else 59 | input.read 60 | end 61 | 62 | output.write out 63 | end 64 | end 65 | 66 | def external_dependencies 67 | [ 'tilt' ] 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/rake-pipeline-web-filters/uglify_filter.rb: -------------------------------------------------------------------------------- 1 | require 'rake-pipeline-web-filters/filter_with_dependencies' 2 | 3 | module Rake::Pipeline::Web::Filters 4 | # A filter that uses the Uglify JS compressor to compress 5 | # JavaScript input files. 6 | # 7 | # Requires {http://rubygems.org/gems/uglifier uglifier}. 8 | # 9 | # @example 10 | # !!!ruby 11 | # Rake::Pipeline.build do 12 | # input "app/assets", "**/*.js" 13 | # output "public" 14 | # 15 | # # Compile each JS file under the app/assets 16 | # # directory. 17 | # filter Rake::Pipeline::Web::Filters::UglifyFilter 18 | # end 19 | class UglifyFilter < Rake::Pipeline::Filter 20 | include Rake::Pipeline::Web::Filters::FilterWithDependencies 21 | 22 | # @return [Hash] a hash of options to pass to Uglify 23 | # when compiling. 24 | attr_reader :options 25 | 26 | # @param [Hash] options options to pass to Uglify 27 | # @param [Proc] block a block to use as the Filter's 28 | # {#output_name_generator}. 29 | def initialize(options={}, &block) 30 | block ||= proc { |input| 31 | if input =~ %r{.min.js$} 32 | input 33 | else 34 | input.sub(/\.js$/, '.min.js') 35 | end 36 | } 37 | 38 | @preserve_input = options.delete :preserve_input 39 | 40 | super(&block) 41 | @options = options 42 | end 43 | 44 | def should_skip_minify?(input, output) 45 | (@preserve_input && input.path == output.path) || 46 | input.path =~ %r{.min.js$} 47 | end 48 | 49 | # Implement the {#generate_output} method required by 50 | # the {Filter} API. Compiles each input file with Uglify. 51 | # 52 | # @param [Array] inputs an Array of 53 | # {FileWrapper} objects representing the inputs to 54 | # this filter. 55 | # @param [FileWrapper] output a single {FileWrapper} 56 | # object representing the output. 57 | def generate_output(inputs, output) 58 | inputs.each do |input| 59 | if should_skip_minify?(input, output) 60 | output.write input.read 61 | else 62 | output.write Uglifier.compile(input.read, options) 63 | end 64 | end 65 | end 66 | 67 | private 68 | 69 | def output_paths(input) 70 | paths = super(input) 71 | if @preserve_input 72 | raise 'cannot preserve unminified input if output path is not different' if paths.include?(input.path) 73 | paths.unshift(input.path) 74 | end 75 | paths 76 | end 77 | 78 | def external_dependencies 79 | [ 'uglifier' ] 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /lib/rake-pipeline-web-filters/version.rb: -------------------------------------------------------------------------------- 1 | module Rake 2 | class Pipeline 3 | module Web 4 | module Filters 5 | VERSION = "0.6.0" 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/rake-pipeline-web-filters/yui_css_filter.rb: -------------------------------------------------------------------------------- 1 | require 'rake-pipeline-web-filters/filter_with_dependencies' 2 | 3 | module Rake::Pipeline::Web::Filters 4 | # A filter that compresses CSS input files using 5 | # the YUI CSS compressor. 6 | # 7 | # Requires {https://rubygems.org/gems/yui-compressor yui-compressor} 8 | # 9 | # @example 10 | # !!!ruby 11 | # Rake::Pipeline.build do 12 | # input "app/assets", "**/*.js" 13 | # output "public" 14 | # 15 | # # Compress each CSS file under the app/assets 16 | # # directory. 17 | # filter Rake::Pipeline::Web::Filters::YUICssFilter 18 | # end 19 | class YUICssFilter < Rake::Pipeline::Filter 20 | include Rake::Pipeline::Web::Filters::FilterWithDependencies 21 | 22 | # @return [Hash] a hash of options to pass to the 23 | # YUI compressor when compressing. 24 | attr_reader :options 25 | 26 | # @param [Hash] options options to pass to the YUI 27 | # CSS compressor. 28 | # @param [Proc] block a block to use as the Filter's 29 | # {#output_name_generator}. 30 | def initialize(options={}, &block) 31 | block ||= proc { |input| 32 | if input =~ %r{min.css$} 33 | input 34 | else 35 | input.sub /\.css$/, '.min.css' 36 | end 37 | } 38 | 39 | super(&block) 40 | @options = options 41 | end 42 | 43 | # Implement the {#generate_output} method required by 44 | # the {Filter} API. Compresses each input file with 45 | # the YUI CSS compressor. 46 | # 47 | # @param [Array] inputs an Array of 48 | # {FileWrapper} objects representing the inputs to 49 | # this filter. 50 | # @param [FileWrapper] output a single {FileWrapper} 51 | # object representing the output. 52 | def generate_output(inputs, output) 53 | compressor = YUI::CssCompressor.new(options) 54 | inputs.each do |input| 55 | if input.path !~ /min\.css/ 56 | output.write compressor.compress(input.read) 57 | else 58 | output.write input.read 59 | end 60 | end 61 | end 62 | 63 | private 64 | 65 | def external_dependencies 66 | [ 'yui/compressor' ] 67 | end 68 | end 69 | end 70 | 71 | -------------------------------------------------------------------------------- /lib/rake-pipeline-web-filters/yui_javascript_filter.rb: -------------------------------------------------------------------------------- 1 | require 'rake-pipeline-web-filters/filter_with_dependencies' 2 | 3 | module Rake::Pipeline::Web::Filters 4 | # A filter that compresses JavaScript input files using 5 | # the YUI JavaScript compressor. 6 | # 7 | # Requires {https://rubygems.org/gems/yui-compressor yui-compressor} 8 | # 9 | # @example 10 | # !!!ruby 11 | # Rake::Pipeline.build do 12 | # input "app/assets", "**/*.js" 13 | # output "public" 14 | # 15 | # # Compress each JS file under the app/assets 16 | # # directory. 17 | # filter Rake::Pipeline::Web::Filters::YUIJavaScriptFilter 18 | # end 19 | class YUIJavaScriptFilter < Rake::Pipeline::Filter 20 | include Rake::Pipeline::Web::Filters::FilterWithDependencies 21 | 22 | # @return [Hash] a hash of options to pass to the 23 | # YUI compressor when compressing. 24 | attr_reader :options 25 | 26 | # @param [Hash] options options to pass to the YUI 27 | # JavaScript compressor. 28 | # @param [Proc] block a block to use as the Filter's 29 | # {#output_name_generator}. 30 | def initialize(options={}, &block) 31 | block ||= proc { |input| input.sub(/\.js$/, '.min.js') } 32 | super(&block) 33 | @options = options 34 | end 35 | 36 | # Implement the {#generate_output} method required by 37 | # the {Filter} API. Compresses each input file with 38 | # the YUI JavaScript compressor. 39 | # 40 | # @param [Array] inputs an Array of 41 | # {FileWrapper} objects representing the inputs to 42 | # this filter. 43 | # @param [FileWrapper] output a single {FileWrapper} 44 | # object representing the output. 45 | def generate_output(inputs, output) 46 | compressor = YUI::JavaScriptCompressor.new(options) 47 | inputs.each do |input| 48 | output.write compressor.compress(input.read) 49 | end 50 | end 51 | 52 | private 53 | 54 | def external_dependencies 55 | [ 'yui/compressor' ] 56 | end 57 | end 58 | end 59 | 60 | -------------------------------------------------------------------------------- /rake-pipeline-web-filters.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path('../lib/rake-pipeline-web-filters/version', __FILE__) 3 | 4 | Gem::Specification.new do |gem| 5 | gem.authors = ["Yehuda Katz"] 6 | gem.email = ["wycats@gmail.com"] 7 | gem.description = %q{A collection of web filters for rake-pipeline} 8 | gem.summary = %q{Contributed filters for use in rake-pipeline that are useful for web projects, like asset management} 9 | gem.homepage = "http://github.com/wycats/rake-pipeline-web-filters" 10 | 11 | gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 12 | gem.files = `git ls-files`.split("\n") 13 | gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 14 | gem.name = "rake-pipeline-web-filters" 15 | gem.require_paths = ["lib"] 16 | gem.version = Rake::Pipeline::Web::Filters::VERSION 17 | 18 | gem.add_dependency "rake-pipeline", "~> 0.6" 19 | gem.add_dependency "rack" 20 | 21 | gem.add_development_dependency "rspec" 22 | gem.add_development_dependency "tilt" 23 | gem.add_development_dependency "sass" 24 | gem.add_development_dependency "compass" 25 | gem.add_development_dependency "coffee-script" 26 | gem.add_development_dependency "redcarpet", '~> 2.0' 27 | gem.add_development_dependency "yui-compressor" 28 | gem.add_development_dependency "uglifier" 29 | gem.add_development_dependency "less" 30 | gem.add_development_dependency "json" 31 | gem.add_development_dependency "therubyracer" 32 | gem.add_development_dependency "stylus" 33 | gem.add_development_dependency "ruby_es6_module_transpiler" 34 | end 35 | -------------------------------------------------------------------------------- /spec/cache_buster_filter_spec.rb: -------------------------------------------------------------------------------- 1 | describe "CacheBusterFilter" do 2 | MemoryFileWrapper ||= Rake::Pipeline::SpecHelpers::MemoryFileWrapper 3 | MemoryManifest ||= Rake::Pipeline::SpecHelpers::MemoryManifest 4 | CacheBusterFilter ||= Rake::Pipeline::Web::Filters::CacheBusterFilter 5 | 6 | let(:content) { "it doesn't matter" } 7 | 8 | let(:input_file) { 9 | MemoryFileWrapper.new '/path/to/input', 'file.txt', 'UTF-8', content 10 | } 11 | 12 | let(:output_root) { 13 | '/path/to/output' 14 | } 15 | 16 | def setup_filter(filter) 17 | filter.file_wrapper_class = MemoryFileWrapper 18 | filter.manifest = MemoryManifest.new 19 | filter.last_manifest = MemoryManifest.new 20 | filter.input_files = [ input_file ] 21 | filter.output_root = output_root 22 | filter.rake_application = Rake::Application.new 23 | filter 24 | end 25 | 26 | describe 'DEFAULT_KEY_GENERATOR' do 27 | 28 | subject { CacheBusterFilter::DEFAULT_KEY_GENERATOR } 29 | 30 | it "returns the MD5 hash of the input's file name and contents" do 31 | expected = Digest::MD5.new << input_file.path << content 32 | subject.call(input_file).should == expected 33 | end 34 | 35 | end 36 | 37 | describe 'with the default cache key strategy' do 38 | 39 | let(:output_file) { 40 | key = CacheBusterFilter::DEFAULT_KEY_GENERATOR.call(input_file) 41 | MemoryFileWrapper.new output_root, "file-#{key}.txt", 'UTF-8' 42 | } 43 | 44 | subject { setup_filter CacheBusterFilter.new } 45 | 46 | it 'outputs to the MD5 hash of the file name and contents' do 47 | subject.output_files.should == [ output_file ] 48 | end 49 | 50 | it 'passes file contents through unchanged' do 51 | tasks = subject.generate_rake_tasks 52 | tasks.each(&:invoke) 53 | file = MemoryFileWrapper.files[ output_file.fullpath ] 54 | file.body.should == content 55 | file.encoding.should == 'UTF-8' 56 | end 57 | 58 | end 59 | 60 | describe 'with a custom key strategy' do 61 | 62 | let(:output_file) { 63 | MemoryFileWrapper.new output_root, 'file-foo.txt', 'UTF-8' 64 | } 65 | 66 | subject do 67 | setup_filter(CacheBusterFilter.new() { 'foo' }) 68 | end 69 | 70 | it 'uses the custom key strategy' do 71 | subject.output_files.should == [ output_file ] 72 | end 73 | 74 | end 75 | 76 | describe 'for an input file with multiple dots' do 77 | let(:input_file) { 78 | MemoryFileWrapper.new '/path/to/input', 'my.text.file.txt', 'UTF-8', content 79 | } 80 | 81 | let(:output_file) { 82 | MemoryFileWrapper.new output_root, "my.text.file-foo.txt", 'UTF-8' 83 | } 84 | 85 | subject { setup_filter(CacheBusterFilter.new() { 'foo' }) } 86 | 87 | it 'appends the busting key to the penultimate part' do 88 | subject.output_files.should == [ output_file ] 89 | end 90 | end 91 | 92 | describe 'for an input file with no dots' do 93 | let(:input_file) { 94 | MemoryFileWrapper.new '/path/to/input', 'my_text_file', 'UTF-8', content 95 | } 96 | 97 | let(:output_file) { 98 | MemoryFileWrapper.new output_root, "my_text_file-foo", 'UTF-8' 99 | } 100 | 101 | subject { setup_filter(CacheBusterFilter.new() { 'foo' }) } 102 | 103 | it 'appends the busting key to the end of the filename' do 104 | subject.output_files.should == [ output_file ] 105 | end 106 | end 107 | 108 | end 109 | -------------------------------------------------------------------------------- /spec/chained_filter_spec.rb: -------------------------------------------------------------------------------- 1 | describe "ChainedFilter" do 2 | MemoryFileWrapper ||= Rake::Pipeline::SpecHelpers::MemoryFileWrapper 3 | MemoryManifest ||= Rake::Pipeline::SpecHelpers::MemoryManifest 4 | ChainedFilter ||= Rake::Pipeline::Web::Filters::ChainedFilter 5 | 6 | input_file1 = MemoryFileWrapper.new("/path", "input.js.strip_asserts.erb", "UTF-8", <<-CONTENT) 7 | assert("must be true", true); 8 | Input = {}; 9 | <%= $chained_filter_title %> = {}; 10 | CONTENT 11 | 12 | input_file2 = MemoryFileWrapper.new("/path", "input.js.erb.strip_asserts", "UTF-8", <<-CONTENT) 13 | assert("must be true", true); 14 | Input = {}; 15 | <%= $chained_filter_title %> = {}; 16 | CONTENT 17 | 18 | EXPECTED_OUTPUT = <<-EXPECTED 19 | 20 | Input = {}; 21 | Chained = {}; 22 | EXPECTED 23 | 24 | let(:erb_filter) do 25 | Class.new(Rake::Pipeline::Filter) do 26 | def generate_output(inputs, output) 27 | inputs.each do |input| 28 | output.write ERB.new(input.read).result 29 | end 30 | end 31 | end 32 | end 33 | 34 | let(:strip_asserts_filter) do 35 | Class.new(Rake::Pipeline::Filter) do 36 | def generate_output(inputs, output) 37 | inputs.each do |input| 38 | output.write input.read.gsub(/^assert.*$/, '') 39 | end 40 | end 41 | end 42 | end 43 | 44 | before do 45 | $chained_filter_title = "Chained" 46 | end 47 | 48 | after do 49 | $chained_filter_title = nil 50 | end 51 | 52 | [ input_file1, input_file2 ].each do |file_wrapper| 53 | it "should run through the filters in order" do 54 | filter = ChainedFilter.new( 55 | :types => { 56 | :erb => erb_filter, 57 | :strip_asserts => strip_asserts_filter 58 | } 59 | ) 60 | 61 | filter.file_wrapper_class = MemoryFileWrapper 62 | filter.manifest = MemoryManifest.new 63 | filter.last_manifest = MemoryManifest.new 64 | filter.input_files = [ file_wrapper ] 65 | filter.output_root = "/output" 66 | filter.rake_application = Rake::Application.new 67 | 68 | filter.output_files.should == [ MemoryFileWrapper.new("/output", "input.js", "UTF-8") ] 69 | 70 | tasks = filter.generate_rake_tasks 71 | tasks.each(&:invoke) 72 | 73 | file = MemoryFileWrapper.files["/output/input.js"] 74 | file.body.should == EXPECTED_OUTPUT 75 | file.encoding.should == "UTF-8" 76 | end 77 | end 78 | 79 | end 80 | -------------------------------------------------------------------------------- /spec/coffee_script_filter_spec.rb: -------------------------------------------------------------------------------- 1 | describe "CoffeeScriptFilter" do 2 | CoffeeScriptFilter ||= Rake::Pipeline::Web::Filters::CoffeeScriptFilter 3 | MemoryFileWrapper ||= Rake::Pipeline::SpecHelpers::MemoryFileWrapper 4 | MemoryManifest ||= Rake::Pipeline::SpecHelpers::MemoryManifest 5 | 6 | let(:coffee_input) { <<-COFFEE } 7 | x = 1; 8 | 9 | y = -> 10 | x += 1 11 | COFFEE 12 | 13 | let(:expected_coffee_output) { <<-HTML } 14 | (function() { 15 | var x, y; 16 | 17 | x = 1; 18 | 19 | y = function() { 20 | return x += 1; 21 | }; 22 | 23 | }).call(this); 24 | HTML 25 | 26 | let(:expected_unwrapped_coffee_output) { <<-HTML } 27 | var x, y; 28 | 29 | x = 1; 30 | 31 | y = function() { 32 | return x += 1; 33 | }; 34 | HTML 35 | 36 | def input_file(name, content) 37 | MemoryFileWrapper.new("/path/to/input", name, "UTF-8", content) 38 | end 39 | 40 | def output_file(name) 41 | MemoryFileWrapper.new("/path/to/output", name, "UTF-8") 42 | end 43 | 44 | def should_match(expected, output) 45 | "#{expected}\n".gsub(/\n+/, "\n").should == "#{output}\n".gsub(/\n+/, "\n") 46 | end 47 | 48 | def setup_filter(filter) 49 | filter.file_wrapper_class = MemoryFileWrapper 50 | filter.manifest = MemoryManifest.new 51 | filter.last_manifest = MemoryManifest.new 52 | filter.input_files = [input_file("input.coffee", coffee_input)] 53 | filter.output_root = "/path/to/output" 54 | filter.rake_application = Rake::Application.new 55 | filter 56 | end 57 | 58 | it "generates output" do 59 | filter = setup_filter CoffeeScriptFilter.new 60 | 61 | filter.output_files.should == [output_file("input.js")] 62 | 63 | tasks = filter.generate_rake_tasks 64 | tasks.each(&:invoke) 65 | 66 | file = MemoryFileWrapper.files["/path/to/output/input.js"] 67 | should_match file.body, expected_coffee_output 68 | file.encoding.should == "UTF-8" 69 | end 70 | 71 | it "generates unwrapped output" do 72 | filter = setup_filter CoffeeScriptFilter.new(:no_wrap => true) 73 | 74 | filter.output_files.should == [output_file("input.js")] 75 | 76 | tasks = filter.generate_rake_tasks 77 | tasks.each(&:invoke) 78 | 79 | file = MemoryFileWrapper.files["/path/to/output/input.js"] 80 | should_match file.body, expected_unwrapped_coffee_output 81 | file.encoding.should == "UTF-8" 82 | end 83 | 84 | describe "naming output files" do 85 | it "translates .coffee extensions to .js by default" do 86 | filter = setup_filter CoffeeScriptFilter.new 87 | filter.output_files.first.path.should == "input.js" 88 | end 89 | 90 | it "accepts a block to customize output file names" do 91 | filter = setup_filter(CoffeeScriptFilter.new { |input| "octopus" }) 92 | filter.output_files.first.path.should == "octopus" 93 | end 94 | end 95 | 96 | describe "invalid input" do 97 | let(:coffee_input) { <<-COFFEE } 98 | y = function(){ 99 | return "whoops there javascript in here!" 100 | } 101 | COFFEE 102 | 103 | it "has a useful error message including the input file name" do 104 | filter = setup_filter CoffeeScriptFilter.new 105 | tasks = filter.generate_rake_tasks 106 | lambda { 107 | tasks.each(&:invoke) 108 | }.should raise_error(ExecJS::RuntimeError, /Error compiling input.coffee.+/i) 109 | end 110 | end 111 | 112 | end 113 | 114 | -------------------------------------------------------------------------------- /spec/es6_module_filter_spec.rb: -------------------------------------------------------------------------------- 1 | describe "ES6ModuleFilter" do 2 | ES6ModuleFilter ||= Rake::Pipeline::Web::Filters::ES6ModuleFilter 3 | MemoryFileWrapper ||= Rake::Pipeline::SpecHelpers::MemoryFileWrapper 4 | MemoryManifest ||= Rake::Pipeline::SpecHelpers::MemoryManifest 5 | 6 | let(:input) { <<-JAVASCRIPT} 7 | import { get, set } from 'ember'; 8 | JAVASCRIPT 9 | 10 | let(:expected_amd_output) { <<-JAVASCRIPT } 11 | define( 12 | ["ember"], 13 | function(__dependency1__) { 14 | "use strict"; 15 | var get = __dependency1__.get; 16 | var set = __dependency1__.set; 17 | }); 18 | JAVASCRIPT 19 | 20 | let(:expected_amd_with_module_id_output) { <<-JAVASCRIPT } 21 | define("octopus", 22 | ["ember"], 23 | function(__dependency1__) { 24 | "use strict"; 25 | var get = __dependency1__.get; 26 | var set = __dependency1__.set; 27 | }); 28 | JAVASCRIPT 29 | 30 | let(:expected_cjs_output) { <<-JAVASCRIPT } 31 | "use strict"; 32 | var get = require("ember").get; 33 | var set = require("ember").set; 34 | JAVASCRIPT 35 | 36 | let(:expected_options_output) { <<-JAVASCRIPT } 37 | (function(__dependency1__) { 38 | "use strict"; 39 | var get = __dependency1__.get; 40 | var set = __dependency1__.set; 41 | })(renamed.ember); 42 | JAVASCRIPT 43 | 44 | def input_file(name, content) 45 | MemoryFileWrapper.new("/path/to/input", name, "UTF-8", content) 46 | end 47 | 48 | def output_file(name) 49 | MemoryFileWrapper.new("/path/to/output", name, "UTF-8") 50 | end 51 | 52 | # remove all whitespaces 53 | def should_match(expected, output) 54 | "#{expected}\n".gsub(/\s+/, " ").should == "#{output}\n".gsub(/\s+/, " ") 55 | end 56 | 57 | def setup_filter(filter) 58 | filter.file_wrapper_class = MemoryFileWrapper 59 | filter.manifest = MemoryManifest.new 60 | filter.last_manifest = MemoryManifest.new 61 | filter.input_files = [input_file("input.js", input)] 62 | filter.output_root = "/path/to/output" 63 | filter.rake_application = Rake::Application.new 64 | filter 65 | end 66 | 67 | it "generates correct default amd output" do 68 | filter = setup_filter ES6ModuleFilter.new 69 | 70 | filter.output_files.should == [output_file("input.js")] 71 | 72 | tasks = filter.generate_rake_tasks 73 | tasks.each(&:invoke) 74 | 75 | file = MemoryFileWrapper.files["/path/to/output/input.js"] 76 | should_match file.body, expected_amd_output 77 | file.encoding.should == "UTF-8" 78 | end 79 | 80 | it "generates correct default amd output with a module_id_generator" do 81 | filter = setup_filter ES6ModuleFilter.new( 82 | module_id_generator: proc { |input| "octopus" } 83 | ) 84 | 85 | filter.output_files.should == [output_file("input.js")] 86 | 87 | tasks = filter.generate_rake_tasks 88 | tasks.each(&:invoke) 89 | 90 | file = MemoryFileWrapper.files["/path/to/output/input.js"] 91 | should_match file.body, expected_amd_with_module_id_output 92 | file.encoding.should == "UTF-8" 93 | end 94 | 95 | it "generates correct cjs output" do 96 | filter = setup_filter ES6ModuleFilter.new({ type: "CJS" }) 97 | 98 | filter.output_files.should == [output_file("input.js")] 99 | 100 | tasks = filter.generate_rake_tasks 101 | tasks.each(&:invoke) 102 | 103 | file = MemoryFileWrapper.files["/path/to/output/input.js"] 104 | should_match file.body, expected_cjs_output 105 | file.encoding.should == "UTF-8" 106 | end 107 | 108 | it "generates output with optional parameters" do 109 | filter = setup_filter ES6ModuleFilter.new({ type: "Globals", global: "renamed"}) 110 | 111 | filter.output_files.should == [output_file("input.js")] 112 | 113 | tasks = filter.generate_rake_tasks 114 | tasks.each(&:invoke) 115 | 116 | file = MemoryFileWrapper.files["/path/to/output/input.js"] 117 | should_match file.body, expected_options_output 118 | file.encoding.should == "UTF-8" 119 | end 120 | 121 | describe "invalid input" do 122 | # forget a curly brace 123 | let(:input) { <<-JAVASCRIPT } 124 | import { get, set from 'ember'; 125 | JAVASCRIPT 126 | 127 | it "has a useful error message including the input file name" do 128 | filter = setup_filter ES6ModuleFilter.new 129 | tasks = filter.generate_rake_tasks 130 | lambda { 131 | tasks.each(&:invoke) 132 | }.should raise_error(ExecJS::ProgramError, "Error compiling input.js. Error: Line 1: Unexpected identifier") 133 | end 134 | end 135 | 136 | describe "naming output files" do 137 | it "accepts a block to customize output file names" do 138 | filter = setup_filter(ES6ModuleFilter.new { |input| "chicken" }) 139 | filter.output_files.first.path.should == "chicken" 140 | end 141 | end 142 | end 143 | 144 | -------------------------------------------------------------------------------- /spec/gzip_filter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'base64' 2 | require 'stringio' 3 | require 'zlib' 4 | 5 | describe "GzipFilter" do 6 | GzipFilter ||= Rake::Pipeline::Web::Filters::GzipFilter 7 | MemoryFileWrapper ||= Rake::Pipeline::SpecHelpers::MemoryFileWrapper 8 | MemoryManifest ||= Rake::Pipeline::SpecHelpers::MemoryManifest 9 | 10 | let(:input) { "(function(){console.log('gzip me')})();" } 11 | 12 | def input_file(name, content) 13 | MemoryFileWrapper.new("/path/to/input", name, "UTF-8", content) 14 | end 15 | 16 | def output_file(name) 17 | MemoryFileWrapper.new("/path/to/output", name, "BINARY") 18 | end 19 | 20 | def setup_filter(filter) 21 | filter.file_wrapper_class = MemoryFileWrapper 22 | filter.manifest = MemoryManifest.new 23 | filter.last_manifest = MemoryManifest.new 24 | filter.input_files = [input_file("test.js", input)] 25 | filter.output_root = "/path/to/output" 26 | filter.rake_application = Rake::Application.new 27 | filter 28 | end 29 | 30 | it "generates output" do 31 | filter = setup_filter GzipFilter.new 32 | filter.output_files.should == [output_file("test.js.gz")] 33 | tasks = filter.generate_rake_tasks 34 | tasks.each(&:invoke) 35 | file = MemoryFileWrapper.files["/path/to/output/test.js.gz"] 36 | Zlib::GzipReader.new(StringIO.new(file.body)).read.should == input 37 | end 38 | 39 | describe "naming output files" do 40 | it "translates extensions to .*.gz by default" do 41 | filter = setup_filter GzipFilter.new 42 | filter.output_files.first.path.should == "test.js.gz" 43 | end 44 | 45 | it "accepts a block to customize output file names" do 46 | filter = setup_filter(GzipFilter.new { |input| "octopus" }) 47 | filter.output_files.first.path.should == "octopus" 48 | end 49 | end 50 | 51 | end 52 | 53 | -------------------------------------------------------------------------------- /spec/handlebars_filter_spec.rb: -------------------------------------------------------------------------------- 1 | describe "HandlebarsFilter" do 2 | HandlebarsFilter ||= Rake::Pipeline::Web::Filters::HandlebarsFilter 3 | MemoryFileWrapper ||= Rake::Pipeline::SpecHelpers::MemoryFileWrapper 4 | MemoryManifest ||= Rake::Pipeline::SpecHelpers::MemoryManifest 5 | 6 | let(:handlebars_input) { 7 | '

{{title}}

' 8 | } 9 | 10 | let(:expected_output) { 11 | "Ember.TEMPLATES['test']=Ember.Handlebars.compile(\"

{{title}}

\");" 12 | } 13 | 14 | def input_file(name, content) 15 | MemoryFileWrapper.new("/path/to/input", name, "UTF-8", content) 16 | end 17 | 18 | def output_file(name) 19 | MemoryFileWrapper.new("/path/to/output", name, "UTF-8") 20 | end 21 | 22 | def setup_filter(filter, input_filename = "test.handlebars") 23 | filter.file_wrapper_class = MemoryFileWrapper 24 | filter.manifest = MemoryManifest.new 25 | filter.last_manifest = MemoryManifest.new 26 | filter.input_files = [input_file(input_filename, handlebars_input)] 27 | filter.output_root = "/path/to/output" 28 | filter.rake_application = Rake::Application.new 29 | filter 30 | end 31 | 32 | it "generates output" do 33 | filter = setup_filter HandlebarsFilter.new 34 | 35 | filter.output_files.should == [output_file("test.js")] 36 | 37 | tasks = filter.generate_rake_tasks 38 | tasks.each(&:invoke) 39 | 40 | file = MemoryFileWrapper.files["/path/to/output/test.js"] 41 | file.body.should == expected_output 42 | file.encoding.should == "UTF-8" 43 | end 44 | 45 | describe "naming output files" do 46 | it "translates .handlebars extensions to .js by default" do 47 | filter = setup_filter HandlebarsFilter.new, "test.handlebars" 48 | filter.output_files.first.path.should == "test.js" 49 | end 50 | 51 | it "translates .hbs extensions to .js by default" do 52 | filter = setup_filter HandlebarsFilter.new, "test.hbs" 53 | filter.output_files.first.path.should == "test.js" 54 | end 55 | 56 | it "accepts a block to customize output file names" do 57 | filter = setup_filter(HandlebarsFilter.new { |input| "squid" }) 58 | filter.output_files.first.path.should == "squid" 59 | end 60 | end 61 | 62 | describe "options" do 63 | it "should allow an option to name the key" do 64 | filter = setup_filter(HandlebarsFilter.new(:key_name_proc => proc { |input| "new_name_key" })) 65 | 66 | tasks = filter.generate_rake_tasks 67 | tasks.each(&:invoke) 68 | 69 | file = MemoryFileWrapper.files["/path/to/output/test.js"] 70 | file.body.should =~ /^Ember\.TEMPLATES\['new_name_key'\]/ 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /spec/helpers_spec.rb: -------------------------------------------------------------------------------- 1 | require "rake-pipeline-web-filters/helpers" 2 | 3 | describe "Helpers" do 4 | let(:pipeline) { Rake::Pipeline.new } 5 | let(:dsl) { Rake::Pipeline::DSL::PipelineDSL.new(pipeline) } 6 | 7 | before do 8 | pipeline.add_input '.' 9 | end 10 | 11 | def filter 12 | pipeline.filters.last 13 | end 14 | 15 | describe "#minispade" do 16 | it "creates a MinispadeFilter" do 17 | dsl.minispade 18 | filter.should be_kind_of(Rake::Pipeline::Web::Filters::MinispadeFilter) 19 | end 20 | end 21 | 22 | describe "#sass" do 23 | it "creates a SassCompiler" do 24 | dsl.sass 25 | filter.should be_kind_of(Rake::Pipeline::Web::Filters::SassFilter) 26 | end 27 | end 28 | 29 | describe "#stylus" do 30 | it "creates a StylusCompiler" do 31 | dsl.stylus 32 | filter.should be_kind_of(Rake::Pipeline::Web::Filters::StylusFilter) 33 | end 34 | end 35 | 36 | describe "#jade" do 37 | it "creates a JadeCompiler" do 38 | dsl.jade 39 | filter.should be_kind_of(Rake::Pipeline::Web::Filters::JadeFilter) 40 | end 41 | end 42 | 43 | describe "#tilt" do 44 | it "creates a TiltFilter" do 45 | dsl.tilt 46 | filter.should be_kind_of(Rake::Pipeline::Web::Filters::TiltFilter) 47 | end 48 | end 49 | 50 | describe "#markdown" do 51 | it "creates a MarkdownCompiler" do 52 | dsl.markdown 53 | filter.should be_kind_of(Rake::Pipeline::Web::Filters::MarkdownFilter) 54 | end 55 | end 56 | 57 | describe "#cache_buster" do 58 | it "creates a CacheBuster" do 59 | dsl.cache_buster 60 | filter.should be_kind_of(Rake::Pipeline::Web::Filters::CacheBusterFilter) 61 | end 62 | end 63 | 64 | describe "#coffee_script" do 65 | it "creates a CoffeeScriptCompiler" do 66 | dsl.coffee_script 67 | filter.should be_kind_of(Rake::Pipeline::Web::Filters::CoffeeScriptFilter) 68 | end 69 | end 70 | 71 | describe "#yui_javascript" do 72 | it "creates a YUIJavaScriptCompressor" do 73 | dsl.yui_javascript 74 | filter.should be_kind_of(Rake::Pipeline::Web::Filters::YUIJavaScriptFilter) 75 | end 76 | end 77 | 78 | describe "#yui_css" do 79 | it "creates a YUICssCompressor" do 80 | dsl.yui_css 81 | filter.should be_kind_of(Rake::Pipeline::Web::Filters::YUICssFilter) 82 | end 83 | end 84 | 85 | describe "#uglify" do 86 | it "creates an UglifyFilter" do 87 | dsl.uglify 88 | filter.should be_kind_of(Rake::Pipeline::Web::Filters::UglifyFilter) 89 | end 90 | end 91 | 92 | describe "#less" do 93 | it "creates a LessFilter" do 94 | dsl.less 95 | filter.should be_kind_of(Rake::Pipeline::Web::Filters::LessFilter) 96 | end 97 | end 98 | 99 | describe "#gzip" do 100 | it "creates a GzipFilter" do 101 | dsl.gzip 102 | filter.should be_kind_of(Rake::Pipeline::Web::Filters::GzipFilter) 103 | end 104 | end 105 | 106 | describe "#handlebars" do 107 | it "creates a HandlebarsFilter" do 108 | dsl.handlebars 109 | filter.should be_kind_of(Rake::Pipeline::Web::Filters::HandlebarsFilter) 110 | end 111 | end 112 | 113 | describe "#iffe" do 114 | it "creates a IifeFilter" do 115 | dsl.iife 116 | filter.should be_kind_of(Rake::Pipeline::Web::Filters::IifeFilter) 117 | end 118 | end 119 | 120 | describe "#es6_module" do 121 | it "creates a ES6ModuleFilter" do 122 | dsl.es6_module 123 | filter.should be_kind_of(Rake::Pipeline::Web::Filters::ES6ModuleFilter) 124 | end 125 | end 126 | end 127 | 128 | describe "ProjectHelpers" do 129 | let(:project) { Rake::Pipeline::Project.new } 130 | let(:dsl) { Rake::Pipeline::DSL::ProjectDSL.new(project) } 131 | 132 | describe "register" do 133 | it "registers filters per file name" do 134 | dsl.register :coffee, Rake::Pipeline::Web::Filters::CoffeeScriptFilter 135 | dsl.register :handlebars, Rake::Pipeline::Web::Filters::HandlebarsFilter 136 | 137 | dsl.input "lib" do 138 | concat "lib.js" 139 | end 140 | 141 | dsl.input "tests" do 142 | concat "tests.js" 143 | end 144 | 145 | project.pipelines.size.should == 2 146 | 147 | project.pipelines.each do |pipeline| 148 | pipeline.filters.first.should be_kind_of Rake::Pipeline::Web::Filters::ChainedFilter 149 | pipeline.filters.first.filters.keys.should == [:coffee, :handlebars] 150 | end 151 | end 152 | end 153 | end 154 | -------------------------------------------------------------------------------- /spec/iife_filter_spec.rb: -------------------------------------------------------------------------------- 1 | describe "IifeFilter" do 2 | IifeFilter ||= Rake::Pipeline::Web::Filters::IifeFilter 3 | MemoryFileWrapper ||= Rake::Pipeline::SpecHelpers::MemoryFileWrapper 4 | MemoryManifest ||= Rake::Pipeline::SpecHelpers::MemoryManifest 5 | 6 | let(:js_input) { <<-HERE } 7 | var name = "Truckasaurus Gates"; 8 | HERE 9 | 10 | let(:expected_js_output) { <<-HERE } 11 | (function() { 12 | var name = "Truckasaurus Gates"; 13 | })(); 14 | HERE 15 | 16 | def input_file(name, content) 17 | MemoryFileWrapper.new("/path/to/input", name, "UTF-8", content) 18 | end 19 | 20 | def output_file(name) 21 | MemoryFileWrapper.new("/path/to/output", name, "UTF-8") 22 | end 23 | 24 | def setup_filter(filter) 25 | filter.file_wrapper_class = MemoryFileWrapper 26 | filter.manifest = MemoryManifest.new 27 | filter.last_manifest = MemoryManifest.new 28 | filter.input_files = [input_file("name.js", js_input)] 29 | filter.output_root = "/path/to/output" 30 | filter.rake_application = Rake::Application.new 31 | filter 32 | end 33 | 34 | it "generates output" do 35 | filter = setup_filter IifeFilter.new 36 | 37 | filter.output_files.should == [output_file("name.js")] 38 | 39 | tasks = filter.generate_rake_tasks 40 | tasks.each(&:invoke) 41 | 42 | file = MemoryFileWrapper.files["/path/to/output/name.js"] 43 | file.body.should == expected_js_output.chomp 44 | file.encoding.should == "UTF-8" 45 | end 46 | 47 | describe "naming output files" do 48 | it "translates .js extensions to .min.js by default" do 49 | filter = setup_filter IifeFilter.new 50 | filter.output_files.first.path.should == "name.js" 51 | end 52 | 53 | it "accepts a block to customize output file names" do 54 | filter = setup_filter(IifeFilter.new { |input| "octopus" }) 55 | filter.output_files.first.path.should == "octopus" 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/jade_filter_spec.rb: -------------------------------------------------------------------------------- 1 | describe "JadeFilter" do 2 | JadeFilter ||= Rake::Pipeline::Web::Filters::JadeFilter 3 | MemoryManifest ||= Rake::Pipeline::SpecHelpers::MemoryManifest 4 | 5 | let(:jade_input) { 6 | """ 7 | doctype html 8 | html 9 | head 10 | title Hello 11 | body 12 | h1 Hello 13 | """ 14 | } 15 | 16 | let(:expected_html_output) { 17 | """Hello

Hello

""" 18 | } 19 | 20 | let(:expected_prettified_html_output) { 21 | """\ 22 | 23 | 24 | 25 | Hello 26 | 27 | 28 |

Hello

29 | 30 | """ 31 | } 32 | 33 | let(:input_root) { File.expand_path('./input') } 34 | let(:input_path) { 'index.jade' } 35 | 36 | let(:input_file) { 37 | mkdir_p input_root 38 | File.open(File.join(input_root, input_path), 'w+:UTF-8') {|file| file << jade_input } 39 | Rake::Pipeline::FileWrapper.new(input_root, input_path, "UTF-8") 40 | } 41 | 42 | let(:output_root) { File.expand_path('./output') } 43 | let(:output_path) { 'index.html' } 44 | 45 | let(:output_file) { 46 | Rake::Pipeline::FileWrapper.new(output_root, output_path, "UTF-8") 47 | } 48 | 49 | def setup_filter(filter, input=jade_input) 50 | filter.manifest = MemoryManifest.new 51 | filter.last_manifest = MemoryManifest.new 52 | filter.input_files = [input_file] 53 | filter.output_root = output_root 54 | filter.rake_application = Rake::Application.new 55 | filter 56 | end 57 | 58 | it "generates output" do 59 | filter = setup_filter JadeFilter.new 60 | filter.output_files.should == [output_file] 61 | 62 | tasks = filter.generate_rake_tasks 63 | tasks.each(&:invoke) 64 | 65 | output_file.read.should == expected_html_output 66 | output_file.encoding.should == "UTF-8" 67 | end 68 | 69 | it "prettifies output" do 70 | filter = setup_filter JadeFilter.new :pretty => true 71 | 72 | filter.output_files.should == [output_file] 73 | 74 | tasks = filter.generate_rake_tasks 75 | tasks.each(&:invoke) 76 | 77 | output_file.read.should == expected_prettified_html_output 78 | output_file.encoding.should == "UTF-8" 79 | end 80 | 81 | describe "naming output files" do 82 | it "translates .jade extensions to .html by default" do 83 | filter = setup_filter JadeFilter.new 84 | filter.output_files.first.path.should == "index.html" 85 | end 86 | 87 | it "accepts a block to customize output file names" do 88 | filter = setup_filter(JadeFilter.new { |input| "hbs" }) 89 | filter.output_files.first.path.should == "hbs" 90 | end 91 | end 92 | end -------------------------------------------------------------------------------- /spec/less_filter_spec.rb: -------------------------------------------------------------------------------- 1 | describe "LessFilter" do 2 | LessFilter ||= Rake::Pipeline::Web::Filters::LessFilter 3 | MemoryFileWrapper ||= Rake::Pipeline::SpecHelpers::MemoryFileWrapper 4 | MemoryManifest ||= Rake::Pipeline::SpecHelpers::MemoryManifest 5 | 6 | let(:less_input) { <<-SCSS } 7 | @blue: #3bbfce; 8 | 9 | .border { 10 | border-color: @blue; 11 | } 12 | SCSS 13 | 14 | let(:expected_css_output) { <<-CSS } 15 | .border { 16 | border-color: #3bbfce; 17 | } 18 | CSS 19 | 20 | def input_file(name, content) 21 | MemoryFileWrapper.new("/path/to/input", name, "UTF-8", content) 22 | end 23 | 24 | def output_file(name) 25 | MemoryFileWrapper.new("/path/to/output", name, "UTF-8") 26 | end 27 | 28 | def setup_filter(filter) 29 | filter.file_wrapper_class = MemoryFileWrapper 30 | filter.manifest = MemoryManifest.new 31 | filter.last_manifest = MemoryManifest.new 32 | filter.input_files = [input_file("border.less", less_input)] 33 | filter.output_root = "/path/to/output" 34 | filter.rake_application = Rake::Application.new 35 | filter 36 | end 37 | 38 | it "generates output" do 39 | filter = setup_filter LessFilter.new 40 | 41 | filter.output_files.should == [output_file("border.css")] 42 | 43 | tasks = filter.generate_rake_tasks 44 | tasks.each(&:invoke) 45 | 46 | file = MemoryFileWrapper.files["/path/to/output/border.css"] 47 | file.body.should == expected_css_output 48 | file.encoding.should == "UTF-8" 49 | end 50 | 51 | describe "naming output files" do 52 | it "translates .less extensions to .css by default" do 53 | filter = setup_filter LessFilter.new 54 | filter.output_files.first.path.should == "border.css" 55 | end 56 | 57 | it "accepts a block to customize output file names" do 58 | filter = setup_filter(LessFilter.new { |input| "octopus" }) 59 | filter.output_files.first.path.should == "octopus" 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/markdown_filter_spec.rb: -------------------------------------------------------------------------------- 1 | describe "MarkdownFilter" do 2 | MarkdownFilter ||= Rake::Pipeline::Web::Filters::MarkdownFilter 3 | MemoryFileWrapper ||= Rake::Pipeline::SpecHelpers::MemoryFileWrapper 4 | MemoryManifest ||= Rake::Pipeline::SpecHelpers::MemoryManifest 5 | 6 | let(:markdown_input) { <<-MARKDOWN } 7 | ## This is an H2 8 | 9 | Some *important* text. It might have a link: http://foo.com/ 10 | 11 | Some code 12 | 13 | That's all. 14 | MARKDOWN 15 | 16 | let(:expected_html_output) { <<-HTML } 17 |

This is an H2

18 | 19 |

Some important text. It might have a link: http://foo.com/

20 | 21 |
Some code
22 | 
23 | 24 |

That's all.

25 | HTML 26 | 27 | def input_file(name, content) 28 | MemoryFileWrapper.new("/path/to/input", name, "UTF-8", content) 29 | end 30 | 31 | def output_file(name) 32 | MemoryFileWrapper.new("/path/to/output", name, "UTF-8") 33 | end 34 | 35 | def setup_filter(filter) 36 | filter.file_wrapper_class = MemoryFileWrapper 37 | filter.manifest = MemoryManifest.new 38 | filter.last_manifest = MemoryManifest.new 39 | filter.input_files = [input_file("page.md", markdown_input)] 40 | filter.output_root = "/path/to/output" 41 | filter.rake_application = Rake::Application.new 42 | filter 43 | end 44 | 45 | it "generates output" do 46 | filter = setup_filter MarkdownFilter.new 47 | 48 | filter.output_files.should == [output_file("page.html")] 49 | 50 | tasks = filter.generate_rake_tasks 51 | tasks.each(&:invoke) 52 | 53 | file = MemoryFileWrapper.files["/path/to/output/page.html"] 54 | file.body.should == expected_html_output 55 | file.encoding.should == "UTF-8" 56 | end 57 | 58 | describe "naming output files" do 59 | it "translates .md extensions to .html by default" do 60 | filter = setup_filter MarkdownFilter.new 61 | filter.output_files.first.path.should == "page.html" 62 | end 63 | 64 | it "accepts a block to customize output file names" do 65 | filter = setup_filter(MarkdownFilter.new { |input| "octopus" }) 66 | filter.output_files.first.path.should == "octopus" 67 | end 68 | end 69 | 70 | it "passes options to the Markdown compiler" do 71 | filter = setup_filter(MarkdownFilter.new(:autolink => true)) 72 | filter.input_files = [input_file("page.md", markdown_input)] 73 | tasks = filter.generate_rake_tasks 74 | tasks.each(&:invoke) 75 | file = MemoryFileWrapper.files["/path/to/output/page.html"] 76 | file.body.should =~ %r{} 77 | end 78 | 79 | it "accepts a :compiler option" do 80 | filter = setup_filter(MarkdownFilter.new(:compiler => proc { |text, options| text })) 81 | filter.input_files = [input_file("page.md", markdown_input)] 82 | tasks = filter.generate_rake_tasks 83 | tasks.each(&:invoke) 84 | file = MemoryFileWrapper.files["/path/to/output/page.html"] 85 | file.body.should == markdown_input 86 | end 87 | 88 | end 89 | 90 | -------------------------------------------------------------------------------- /spec/minispade_filter_spec.rb: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | describe "MinispadeFilter" do 4 | MemoryFileWrapper ||= Rake::Pipeline::SpecHelpers::MemoryFileWrapper 5 | MemoryManifest ||= Rake::Pipeline::SpecHelpers::MemoryManifest 6 | 7 | def input_file(contents="var foo = 'bar'; // last-line comment", path="/path/to/input", name="foo.js") 8 | MemoryFileWrapper.new(path, name, "UTF-8", contents) 9 | end 10 | 11 | let(:output_files) { 12 | [ 13 | MemoryFileWrapper.new("/path/to/output", "foo.js", "UTF-8") 14 | ] 15 | } 16 | 17 | let(:output_file) { 18 | MemoryFileWrapper.files["/path/to/output/foo.js"] 19 | } 20 | 21 | def make_filter(input_file, *args) 22 | filter = Rake::Pipeline::Web::Filters::MinispadeFilter.new(*args) 23 | filter.file_wrapper_class = MemoryFileWrapper 24 | filter.manifest = MemoryManifest.new 25 | filter.last_manifest = MemoryManifest.new 26 | filter.input_files = [input_file] 27 | filter.output_root = "/path/to/output" 28 | filter.rake_application = Rake::Application.new 29 | filter.generate_rake_tasks.each(&:invoke) 30 | filter 31 | end 32 | 33 | it "generates output" do 34 | filter = make_filter(input_file) 35 | filter.output_files.should == output_files 36 | output_file.encoding.should == "UTF-8" 37 | output_file.body.should == 38 | "minispade.register('/path/to/input/foo.js', function() {var foo = 'bar'; // last-line comment\n});" 39 | end 40 | 41 | it "uses strict if asked" do 42 | filter = make_filter(input_file, :use_strict => true) 43 | output_file.body.should == 44 | "minispade.register('/path/to/input/foo.js', function() {\"use strict\";\nvar foo = 'bar'; // last-line comment\n});" 45 | end 46 | 47 | it "compiles a string if asked" do 48 | filter = make_filter(input_file, :string => true) 49 | output_file.body.should == 50 | %{minispade.register('/path/to/input/foo.js', "(function() {var foo = 'bar'; // last-line comment\\n})();\\n//# sourceURL=/path/to/input/foo.js");} 51 | end 52 | 53 | it "takes a proc to name the module" do 54 | filter = make_filter(input_file, :module_id_generator => proc { |input| "octopus" }) 55 | output_file.body.should == 56 | "minispade.register('octopus', function() {var foo = 'bar'; // last-line comment\n});" 57 | end 58 | 59 | it "rewrites requires if asked" do 60 | filter = make_filter(input_file("require('octopus');"), :rewrite_requires => true) 61 | output_file.body.should == 62 | "minispade.register('/path/to/input/foo.js', function() {minispade.require('octopus');\n});" 63 | end 64 | 65 | it "rewrites requires if asked even spaces wrap tokens in the require statement" do 66 | filter = make_filter(input_file("require ( 'octopus');"), :rewrite_requires => true) 67 | output_file.body.should == 68 | "minispade.register('/path/to/input/foo.js', function() {minispade.require('octopus');\n});" 69 | end 70 | 71 | it "rewrites requireAll if asked" do 72 | filter = make_filter(input_file("requireAll('octopus');"), :rewrite_requires => true) 73 | output_file.body.should == 74 | "minispade.register('/path/to/input/foo.js', function() {minispade.requireAll('octopus');\n});" 75 | end 76 | 77 | it "rewrites requireAll if asked even spaces wrap tokens in the require statement" do 78 | filter = make_filter(input_file("requireAll ( 'octopus');"), :rewrite_requires => true) 79 | output_file.body.should == 80 | "minispade.register('/path/to/input/foo.js', function() {minispade.requireAll('octopus');\n});" 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /spec/neuter_filter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'stringio' 2 | 3 | describe "NeuterFilter" do 4 | MemoryFileWrapper ||= Rake::Pipeline::SpecHelpers::MemoryFileWrapper 5 | MemoryManifest ||= Rake::Pipeline::SpecHelpers::MemoryManifest 6 | 7 | def make_input(name, data) 8 | make_data(name, data) 9 | MemoryFileWrapper.new("/path/to/input", name, "UTF-8") 10 | end 11 | 12 | def make_data(name, data) 13 | MemoryFileWrapper.data["/path/to/input/#{name}"] = data 14 | end 15 | 16 | def make_filter(input_files, *args) 17 | opts = args.last.is_a?(Hash) ? args.pop : {} 18 | opts[:additional_dependencies] ||= proc{|input| %w(b c) } 19 | args.push(opts) 20 | 21 | filter = Rake::Pipeline::Web::Filters::NeuterFilter.new(*args) 22 | filter.file_wrapper_class = MemoryFileWrapper 23 | filter.manifest = MemoryManifest.new 24 | filter.last_manifest = MemoryManifest.new 25 | filter.input_files = input_files 26 | filter.output_root = "/path/to/output" 27 | filter.rake_application = Rake::Application.new 28 | 29 | tasks = filter.generate_rake_tasks 30 | 31 | # TODO work around a bug in rakep THIS IS TEMPORARY 32 | filter.rake_application.tasks.each do |task| 33 | task.dynamic_prerequisites.each do |prereq| 34 | filter.send :create_file_task, prereq 35 | end 36 | end 37 | tasks.each(&:invoke) 38 | filter 39 | end 40 | 41 | def make_filter_with_inputs(inputs, options={}) 42 | input_file = make_input(inputs[0][0], inputs[0][1]) 43 | inputs[1..-1].each{|input| make_data(input[0], input[1]) } 44 | make_filter([input_file], "processed", options) 45 | end 46 | 47 | def capture(*streams) 48 | streams.map! { |stream| stream.to_s } 49 | begin 50 | result = StringIO.new 51 | streams.each { |stream| eval "$#{stream} = result" } 52 | yield 53 | ensure 54 | streams.each { |stream| eval("$#{stream} = #{stream.upcase}") } 55 | end 56 | result.string 57 | end 58 | 59 | after(:each) do 60 | MemoryFileWrapper.data.clear 61 | end 62 | 63 | let(:output_files) { 64 | [ 65 | MemoryFileWrapper.new("/path/to/output", "processed", "BINARY") 66 | ] 67 | } 68 | 69 | let(:output_file) { 70 | MemoryFileWrapper.files["/path/to/output/processed"] 71 | } 72 | 73 | it "generates basic output" do 74 | input_file = make_input("contents", "data") 75 | filter = make_filter([input_file], "processed") 76 | 77 | filter.output_files.should == output_files 78 | # ConcatFilter forces Binary, not sure if this is right in this case 79 | output_file.encoding.should == "BINARY" 80 | output_file.body.should == "data" 81 | end 82 | 83 | it "orders required files" do 84 | make_filter_with_inputs([ 85 | ["a", "require('b');\nA"], 86 | ["b", "require('c');\nB"], 87 | ["c", "C"] 88 | ]) 89 | 90 | output_file.body.should == "C\n\nB\n\nA" 91 | end 92 | 93 | it "works with paths" do 94 | make_filter_with_inputs([ 95 | ["lib/a", "require('lib/b');\nA"], 96 | ["lib/b", "require('lib/c');\nB"], 97 | ["lib/c", "C"] 98 | ], :additional_dependencies => proc{ %w(lib/b lib/c) }) 99 | 100 | output_file.body.should == "C\n\nB\n\nA" 101 | end 102 | 103 | it "should handle circular requires" do 104 | make_filter_with_inputs([ 105 | ["a", "require('b');\nA"], 106 | ["b", "require('c');\nB"], 107 | ["c", "require('a');\nC"] 108 | ]) 109 | 110 | output_file.body.should == "C\n\nB\n\nA" 111 | end 112 | 113 | it "should not require the same file twice" do 114 | make_filter_with_inputs([ 115 | ["a", "require('b');\nrequire('c');\nA"], 116 | ["b", "require('c');\nB"], 117 | ["c", "require('a');\nC"] 118 | ]) 119 | 120 | output_file.body.should == "C\n\nB\n\nA" 121 | end 122 | 123 | # Feature not yet supported 124 | it "does not duplicate files both matched and required" 125 | 126 | describe "config" do 127 | describe "require_regexp" do 128 | it "works with minispade format" do 129 | make_filter_with_inputs([ 130 | ["a", "minispade.require('b');\nA"], 131 | ["b", "minispade.require('c');\nB"], 132 | ["c", "C"] 133 | ], :require_regexp => %r{^\s*minispade\.require\(['"]([^'"]*)['"]\);?\s*}) 134 | 135 | output_file.body.should == "C\n\nB\n\nA" 136 | end 137 | 138 | it "works with sprockets format" do 139 | make_filter_with_inputs([ 140 | ["a", "//= require b\nA"], 141 | ["b", "//= require c\nB"], 142 | ["c", "C"] 143 | ], :require_regexp => %r{^//= require (\S+)\s*}) 144 | 145 | output_file.body.should == "C\n\nB\n\nA" 146 | end 147 | end 148 | 149 | describe "path_transform" do 150 | it "converts paths" do 151 | make_filter_with_inputs([ 152 | ["lib/a.js", "require('b');\nA"], 153 | ["lib/b.js", "require('c');\nB"], 154 | ["lib/c.js", "C"] 155 | ], :path_transform => proc{|path| "lib/#{path}.js" }, 156 | :additional_dependencies => proc{ %w(lib/b.js lib/c.js) }) 157 | 158 | output_file.body.should == "C\n\nB\n\nA" 159 | end 160 | end 161 | 162 | describe "closure_wrap" do 163 | it "wraps in a javascript closure" do 164 | make_filter_with_inputs([ 165 | ["a", "require('b');\nA"], 166 | ["b", "require('c');\nB"], 167 | ["c", "C"] 168 | ], :closure_wrap => true) 169 | 170 | output_file.body.should == "(function() {\nC\n})();\n\n\n\n(function() {\nB\n})();\n\n\n\n(function() {\nA\n})();\n\n" 171 | end 172 | 173 | # Not yet supported 174 | it "allows other wrapper types" 175 | end 176 | 177 | describe "filename_comment" do 178 | it "shows a comment with the filename" do 179 | make_filter_with_inputs([ 180 | ["a", "require('b');\nA"], 181 | ["b", "require('c');\nB"], 182 | ["c", "C"], 183 | ], :filename_comment => proc{|input| "/* #{input.fullpath} */" }) 184 | 185 | output_file.body.should == "/* /path/to/input/c */\nC\n\n/* /path/to/input/b */\nB\n\n/* /path/to/input/a */\nA" 186 | end 187 | end 188 | 189 | describe "additional_dependencies" do 190 | it "warns if required file is not contained" do 191 | output = capture(:stderr) do 192 | make_filter_with_inputs([ 193 | ["d", "require('e');\nD"], 194 | ["e", "require('f');\nE"], 195 | ["f", "F"] 196 | ]) 197 | end 198 | 199 | output.should include("Included '/path/to/input/e', which is not listed in :additional_dependencies. The pipeline may not invalidate properly.") 200 | output.should include("Included '/path/to/input/f', which is not listed in :additional_dependencies. The pipeline may not invalidate properly.") 201 | end 202 | 203 | it "does not warn if full paths are provided" do 204 | output = capture(:stderr) do 205 | make_filter_with_inputs([ 206 | ["d", "require('e');\nD"], 207 | ["e", "require('f');\nE"], 208 | ["f", "F"] 209 | ], :additional_dependencies => proc{ %w(/path/to/input/e /path/to/input/f) }) 210 | end 211 | 212 | output.should == "" 213 | end 214 | end 215 | end 216 | end 217 | -------------------------------------------------------------------------------- /spec/sass_filter_spec.rb: -------------------------------------------------------------------------------- 1 | describe "SassFilter" do 2 | SassFilter ||= Rake::Pipeline::Web::Filters::SassFilter 3 | MemoryFileWrapper ||= Rake::Pipeline::SpecHelpers::MemoryFileWrapper 4 | MemoryManifest ||= Rake::Pipeline::SpecHelpers::MemoryManifest 5 | 6 | let(:scss_input) { <<-SCSS } 7 | $blue: #3bbfce; 8 | 9 | .border { 10 | border-color: $blue; 11 | } 12 | SCSS 13 | 14 | let(:sass_input) { <<-SASS } 15 | $blue: #3bbfce 16 | 17 | .border 18 | border-color: $blue 19 | SASS 20 | 21 | def expected_css_output(filename) 22 | <<-CSS 23 | /* line 3, /path/to/input/#{filename} */ 24 | .border { 25 | border-color: #3bbfce; 26 | } 27 | CSS 28 | end 29 | 30 | def input_file(name, content) 31 | MemoryFileWrapper.new("/path/to/input", name, "UTF-8", content) 32 | end 33 | 34 | def output_file(name) 35 | MemoryFileWrapper.new("/path/to/output", name, "UTF-8") 36 | end 37 | 38 | def setup_filter(filter, input_files=nil) 39 | filter.file_wrapper_class = MemoryFileWrapper 40 | filter.manifest = MemoryManifest.new 41 | filter.last_manifest = MemoryManifest.new 42 | filter.input_files = input_files || [input_file("border.scss", scss_input)] 43 | filter.output_root = "/path/to/output" 44 | filter.rake_application = Rake::Application.new 45 | filter 46 | end 47 | 48 | it "generates output" do 49 | filter = setup_filter SassFilter.new 50 | filter.output_files.should == [output_file("border.css")] 51 | 52 | tasks = filter.generate_rake_tasks 53 | tasks.each(&:invoke) 54 | 55 | file = MemoryFileWrapper.files["/path/to/output/border.css"] 56 | file.body.should == expected_css_output("border.scss") 57 | file.encoding.should == "UTF-8" 58 | end 59 | 60 | describe "naming output files" do 61 | it "translates .scss extensions to .css by default" do 62 | filter = setup_filter SassFilter.new 63 | filter.output_files.first.path.should == "border.css" 64 | end 65 | 66 | it "accepts a block to customize output file names" do 67 | filter = setup_filter(SassFilter.new { |input| "octopus" }) 68 | filter.output_files.first.path.should == "octopus" 69 | end 70 | end 71 | 72 | it "accepts options to pass to the Sass compiler" do 73 | input_files = [input_file("border.sass_file", sass_input)] 74 | filter = setup_filter(SassFilter.new(:syntax => :sass), input_files) 75 | tasks = filter.generate_rake_tasks 76 | tasks.each(&:invoke) 77 | file = MemoryFileWrapper.files["/path/to/output/border.sass_file"] 78 | file.body.should == expected_css_output("border.sass_file") 79 | end 80 | 81 | it "compiles files with a .sass extension as sass" do 82 | input_files = [input_file("border.sass", sass_input)] 83 | filter = setup_filter(SassFilter.new, input_files) 84 | tasks = filter.generate_rake_tasks 85 | tasks.each(&:invoke) 86 | file = MemoryFileWrapper.files["/path/to/output/border.css"] 87 | file.body.should == expected_css_output("border.sass") 88 | end 89 | 90 | it "passes Compass's options to the Sass compiler" do 91 | Compass.configuration do |c| 92 | c.preferred_syntax = :sass 93 | end 94 | 95 | input_files = [input_file("border.css", scss_input)] 96 | filter = setup_filter(SassFilter.new, input_files) 97 | tasks = filter.generate_rake_tasks 98 | tasks.each(&:invoke) 99 | file = MemoryFileWrapper.files["/path/to/output/border.css"] 100 | file.body.should == expected_css_output("border.css") 101 | end 102 | 103 | describe "additional load paths" do 104 | it "is empty by default" do 105 | filter = setup_filter(SassFilter.new) 106 | filter.additional_load_paths == [] 107 | end 108 | 109 | it "transforms to array" do 110 | filter = setup_filter(SassFilter.new(:additional_load_paths => "additional")) 111 | filter.additional_load_paths == ["additional"] 112 | end 113 | 114 | it "accepts array" do 115 | filter = setup_filter(SassFilter.new(:additional_load_paths => ["additional", "extra"])) 116 | filter.additional_load_paths == ["additional", "extra"] 117 | end 118 | end 119 | 120 | describe "additional dependencies" do 121 | def write_input_file(filename, contents='', root=tmp) 122 | mkdir_p root 123 | File.open(File.join(root, filename), 'w') { |f| f.puts contents } 124 | Rake::Pipeline::FileWrapper.new(root, filename) 125 | end 126 | 127 | let(:main_scss) { '@import "blue";' } 128 | let(:blue_scss) { '$blue: #3bbfce;' } 129 | let!(:main) { write_input_file('main.scss', main_scss) } 130 | let!(:blue) { write_input_file('blue.scss', blue_scss) } 131 | 132 | before do 133 | File.open(main.fullpath, "w") { |f| f.puts main_scss } 134 | File.open(blue.fullpath, "w") { |f| f.puts blue_scss } 135 | end 136 | 137 | it "includes @imported files" do 138 | filter = SassFilter.new 139 | filter.last_manifest = MemoryManifest.new 140 | filter.manifest = MemoryManifest.new 141 | filter.input_files = [main] 142 | filter.output_root = "#{tmp}/output" 143 | filter.rake_application = Rake::Application.new 144 | 145 | filter.additional_dependencies(main).should include(blue.fullpath) 146 | 147 | tasks = filter.generate_rake_tasks 148 | tasks.each(&:invoke) 149 | end 150 | end 151 | 152 | end 153 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "rake-pipeline" 2 | require "rake-pipeline-web-filters" 3 | 4 | require "support/spec_helpers/file_utils" 5 | require "support/spec_helpers/filters" 6 | require "support/spec_helpers/input_helpers" 7 | require "support/spec_helpers/memory_file_wrapper" 8 | require "support/spec_helpers/memory_manifest" 9 | 10 | RSpec.configure do |config| 11 | original = Dir.pwd 12 | 13 | config.include Rake::Pipeline::SpecHelpers::FileUtils 14 | 15 | def tmp 16 | File.expand_path("../tmp", __FILE__) 17 | end 18 | 19 | config.before do 20 | rm_rf(tmp) 21 | mkdir_p(tmp) 22 | Dir.chdir(tmp) 23 | end 24 | 25 | config.after do 26 | Dir.chdir(original) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/stylus_filter_spec.rb: -------------------------------------------------------------------------------- 1 | describe "StylusFilter" do 2 | StylusFilter ||= Rake::Pipeline::Web::Filters::StylusFilter 3 | MemoryFileWrapper ||= Rake::Pipeline::SpecHelpers::MemoryFileWrapper 4 | MemoryManifest ||= Rake::Pipeline::SpecHelpers::MemoryManifest 5 | 6 | let(:styl_input) { <<-STYLUS } 7 | border-radius() 8 | -webkit-border-radius arguments 9 | -moz-border-radius arguments 10 | border-radius arguments 11 | 12 | body 13 | font 12px Helvetica, Arial, sans-serif 14 | 15 | a.button 16 | border-radius 5px 17 | STYLUS 18 | 19 | let(:expected_css_output) { <<-CSS } 20 | body { 21 | font: 12px Helvetica, Arial, sans-serif; 22 | } 23 | a.button { 24 | -webkit-border-radius: 5px; 25 | -moz-border-radius: 5px; 26 | border-radius: 5px; 27 | } 28 | CSS 29 | 30 | def input_file(name, content) 31 | MemoryFileWrapper.new("/path/to/input", name, "UTF-8", content) 32 | end 33 | 34 | def output_file(name) 35 | MemoryFileWrapper.new("/path/to/output", name, "UTF-8") 36 | end 37 | 38 | def setup_filter(filter, input=styl_input) 39 | filter.file_wrapper_class = MemoryFileWrapper 40 | filter.manifest = MemoryManifest.new 41 | filter.last_manifest = MemoryManifest.new 42 | filter.input_files = [input_file("border.styl", input)] 43 | filter.output_root = "/path/to/output" 44 | filter.rake_application = Rake::Application.new 45 | filter 46 | end 47 | 48 | it "generates output" do 49 | filter = setup_filter StylusFilter.new 50 | 51 | filter.output_files.should == [output_file("border.css")] 52 | 53 | tasks = filter.generate_rake_tasks 54 | tasks.each(&:invoke) 55 | 56 | file = MemoryFileWrapper.files["/path/to/output/border.css"] 57 | file.body.should == expected_css_output 58 | file.encoding.should == "UTF-8" 59 | end 60 | 61 | describe "naming output files" do 62 | it "translates .styl extensions to .css by default" do 63 | filter = setup_filter StylusFilter.new 64 | filter.output_files.first.path.should == "border.css" 65 | end 66 | 67 | it "accepts a block to customize output file names" do 68 | filter = setup_filter(StylusFilter.new { |input| "octopus" }) 69 | filter.output_files.first.path.should == "octopus" 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /spec/support/spec_helpers/file_utils.rb: -------------------------------------------------------------------------------- 1 | class Rake::Pipeline 2 | module SpecHelpers 3 | 4 | # TODO: OS agnostic modules 5 | module FileUtils 6 | def mkdir_p(dir) 7 | system "mkdir", "-p", dir 8 | end 9 | 10 | def touch(file) 11 | system "touch", file 12 | end 13 | 14 | def rm_rf(dir) 15 | system "rm", "-rf", dir 16 | end 17 | 18 | def touch_p(file) 19 | dir = File.dirname(file) 20 | mkdir_p dir 21 | touch file 22 | end 23 | 24 | def age_existing_files 25 | old_time = Time.now - 10 26 | Dir[File.join(tmp, "**/*.js")].each do |file| 27 | File.utime(old_time, old_time, file) 28 | end 29 | end 30 | end 31 | 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/support/spec_helpers/filters.rb: -------------------------------------------------------------------------------- 1 | class Rake::Pipeline 2 | module SpecHelpers 3 | 4 | module Filters 5 | ConcatFilter = Rake::Pipeline::ConcatFilter 6 | 7 | class StripAssertsFilter < Rake::Pipeline::Filter 8 | def generate_output(inputs, output) 9 | inputs.each do |input| 10 | output.write input.read.gsub(%r{^\s*assert\(.*\)\s*;?\s*$}m, '') 11 | end 12 | end 13 | end 14 | 15 | class DynamicImportFilter < Rake::Pipeline::Filter 16 | def additional_dependencies(input) 17 | includes(input) 18 | end 19 | 20 | def includes(input) 21 | input.read.scan(/^@import\(\"(.*)\"\)$/).map(&:first).map do |inc| 22 | File.join(input.root, "#{inc}.import") 23 | end 24 | end 25 | 26 | def generate_output(inputs, output) 27 | inputs.each do |input| 28 | output.write input.read 29 | includes(input).each do |inc| 30 | output.write File.read(inc) 31 | end 32 | end 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/support/spec_helpers/input_helpers.rb: -------------------------------------------------------------------------------- 1 | class Rake::Pipeline 2 | module SpecHelpers 3 | module InputHelpers 4 | def input_file(path, root=File.join(tmp, "app/assets")) 5 | Rake::Pipeline::FileWrapper.new root, path 6 | end 7 | 8 | def output_file(path, root=File.join(tmp, "public")) 9 | input_file(path, root) 10 | end 11 | 12 | def create_files(files) 13 | Array(files).each do |file| 14 | mkdir_p File.dirname(file.fullpath) 15 | 16 | File.open(file.fullpath, "w") do |file| 17 | file.write "// This is #{file.path}\n" 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/support/spec_helpers/memory_file_wrapper.rb: -------------------------------------------------------------------------------- 1 | class Rake::Pipeline 2 | module SpecHelpers 3 | class MemoryFileWrapper < Struct.new(:root, :path, :encoding, :body) 4 | @@files = {} 5 | @@data = {} 6 | 7 | def self.files 8 | @@files 9 | end 10 | 11 | def self.data 12 | @@data 13 | end 14 | 15 | def with_encoding(new_encoding) 16 | self.class.new(root, path, new_encoding, body) 17 | end 18 | 19 | def fullpath 20 | File.join(root, path) 21 | end 22 | 23 | def create 24 | @@files[fullpath] = self 25 | self.body = "" 26 | yield 27 | end 28 | 29 | def read 30 | body || @@data[fullpath] || "" 31 | end 32 | 33 | def write(contents) 34 | self.body << contents 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/support/spec_helpers/memory_manifest.rb: -------------------------------------------------------------------------------- 1 | class Rake::Pipeline 2 | module SpecHelpers 3 | class MemoryManifest 4 | def initialize 5 | @entries = {} 6 | end 7 | 8 | # Look up an entry by filename. 9 | def [](key) 10 | @entries[key] 11 | end 12 | 13 | # Set an entry 14 | def []=(key, value) 15 | @entries[key] = value 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/tilt_filter_spec.rb: -------------------------------------------------------------------------------- 1 | describe "TiltFilter" do 2 | MemoryFileWrapper ||= Rake::Pipeline::SpecHelpers::MemoryFileWrapper 3 | MemoryManifest ||= Rake::Pipeline::SpecHelpers::MemoryManifest 4 | 5 | let(:input_files) { 6 | [ 7 | MemoryFileWrapper.new("/path/to/input", "foo.erb", "UTF-8", "<%= 'foo' %>\n"), 8 | MemoryFileWrapper.new("/path/to/input", "bar.str", "UTF-8", '#{ "bar" }') 9 | ] 10 | } 11 | 12 | let(:output_files) { 13 | [ 14 | MemoryFileWrapper.new("/path/to/output", "foo.txt", "UTF-8"), 15 | MemoryFileWrapper.new("/path/to/output", "bar.txt", "UTF-8") 16 | ] 17 | } 18 | 19 | def make_filter(*args) 20 | filter = Rake::Pipeline::Web::Filters::TiltFilter.new(*args) do |input| 21 | input.sub(/\.(erb|str)$/, '.txt') 22 | end 23 | filter.file_wrapper_class = MemoryFileWrapper 24 | filter.manifest = MemoryManifest.new 25 | filter.last_manifest = MemoryManifest.new 26 | filter.input_files = input_files 27 | filter.output_root = "/path/to/output" 28 | filter.rake_application = Rake::Application.new 29 | filter 30 | end 31 | 32 | it "generates output" do 33 | filter = make_filter 34 | 35 | filter.output_files.should == output_files 36 | 37 | tasks = filter.generate_rake_tasks 38 | tasks.each(&:invoke) 39 | 40 | file = MemoryFileWrapper.files["/path/to/output/foo.txt"] 41 | file.body.should == "foo" 42 | file.encoding.should == "UTF-8" 43 | 44 | file = MemoryFileWrapper.files["/path/to/output/bar.txt"] 45 | file.body.should == "bar" 46 | file.encoding.should == "UTF-8" 47 | end 48 | 49 | it "accepts options to pass to the template class" do 50 | # :trim => '' should tell ERB not to trim newlines 51 | filter = make_filter(:trim => '') 52 | 53 | tasks = filter.generate_rake_tasks 54 | tasks.each(&:invoke) 55 | file = MemoryFileWrapper.files["/path/to/output/foo.txt"] 56 | file.body.should == "foo\n" 57 | end 58 | 59 | describe 'with a rendering context' do 60 | 61 | let(:input_files) do 62 | [ 63 | MemoryFileWrapper.new("/path/to/input", "foo.erb", "UTF-8", "<%= foo %>"), 64 | ] 65 | end 66 | 67 | let(:context) do 68 | context = Class.new do 69 | def foo; 'foo'; end 70 | end.new 71 | end 72 | 73 | it 'uses the context' do 74 | filter = make_filter({}, context) 75 | 76 | tasks = filter.generate_rake_tasks 77 | tasks.each(&:invoke) 78 | file = MemoryFileWrapper.files["/path/to/output/foo.txt"] 79 | file.body.should == "foo" 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /spec/uglify_filter_spec.rb: -------------------------------------------------------------------------------- 1 | describe "UglifyFilter" do 2 | UglifyFilter ||= Rake::Pipeline::Web::Filters::UglifyFilter 3 | MemoryFileWrapper ||= Rake::Pipeline::SpecHelpers::MemoryFileWrapper 4 | MemoryManifest ||= Rake::Pipeline::SpecHelpers::MemoryManifest 5 | 6 | let(:js_input) { <<-HERE } 7 | var name = "Truckasaurus Gates"; 8 | console.log(name); 9 | HERE 10 | 11 | let(:expected_js_output) { 12 | 'var name="Truckasaurus Gates";console.log(name);' 13 | } 14 | 15 | let(:expected_beautiful_js_output) { 16 | %[var name = "Truckasaurus Gates";\n\nconsole.log(name);] 17 | } 18 | 19 | let(:filter_args) { [] } 20 | let(:filter_block) { nil } 21 | 22 | let(:filter) { 23 | filter = UglifyFilter.new(*filter_args, &filter_block) 24 | filter.file_wrapper_class = MemoryFileWrapper 25 | filter.manifest = MemoryManifest.new 26 | filter.last_manifest = MemoryManifest.new 27 | filter.input_files = [input_file("name.js", js_input)] 28 | filter.output_root = "/path/to/output" 29 | filter.rake_application = Rake::Application.new 30 | filter 31 | } 32 | 33 | def input_file(name, content) 34 | MemoryFileWrapper.new("/path/to/input", name, "UTF-8", content) 35 | end 36 | 37 | def output_file(name) 38 | MemoryFileWrapper.new("/path/to/output", name, "UTF-8") 39 | end 40 | 41 | it "generates output" do 42 | filter.output_files.should == [output_file("name.min.js")] 43 | 44 | tasks = filter.generate_rake_tasks 45 | tasks.each(&:invoke) 46 | 47 | file = MemoryFileWrapper.files["/path/to/output/name.min.js"] 48 | file.body.should == expected_js_output 49 | file.encoding.should == "UTF-8" 50 | end 51 | 52 | describe "Skipping" do 53 | it "skips files ending in .min.js" do 54 | filter.input_files = [input_file("name.min.js", 'fake-js')] 55 | 56 | filter.output_files.should == [output_file("name.min.js")] 57 | 58 | tasks = filter.generate_rake_tasks 59 | tasks.each(&:invoke) 60 | 61 | file = MemoryFileWrapper.files["/path/to/output/name.min.js"] 62 | file.body.should == 'fake-js' 63 | file.encoding.should == "UTF-8" 64 | end 65 | 66 | it "does not count files ending in min.js as preminified" do 67 | filter.input_files = [input_file("min.js", js_input)] 68 | 69 | filter.output_files.should == [output_file("min.min.js")] 70 | 71 | tasks = filter.generate_rake_tasks 72 | tasks.each(&:invoke) 73 | 74 | file = MemoryFileWrapper.files["/path/to/output/min.min.js"] 75 | file.body.should == expected_js_output 76 | file.encoding.should == "UTF-8" 77 | end 78 | end 79 | 80 | it "translates .js extensions to .min.js by default" do 81 | filter.output_files.first.path.should == "name.min.js" 82 | end 83 | 84 | context "with preserve_input option" do 85 | let(:filter_args) do 86 | [{ :preserve_input => true }] 87 | end 88 | 89 | it "should output both the unminified and the minified files" do 90 | filter.output_files.should == [output_file("name.js"), output_file("name.min.js")] 91 | 92 | tasks = filter.generate_rake_tasks 93 | tasks.each(&:invoke) 94 | 95 | file = MemoryFileWrapper.files["/path/to/output/name.js"] 96 | file.body.should == js_input 97 | file.encoding.should == "UTF-8" 98 | 99 | file = MemoryFileWrapper.files["/path/to/output/name.min.js"] 100 | file.body.should == expected_js_output 101 | file.encoding.should == "UTF-8" 102 | end 103 | end 104 | 105 | context "with output name block" do 106 | let(:filter_block) do 107 | Proc.new { |input| "octopus" } 108 | end 109 | 110 | it "customizes output file names" do 111 | filter.output_files.first.path.should == "octopus" 112 | end 113 | end 114 | 115 | context "with Uglify options" do 116 | let(:filter_args) do 117 | [output: { :beautify => true }] 118 | end 119 | 120 | it "passes options to the Uglify compressor" do 121 | filter.input_files = [input_file("name.js", js_input)] 122 | tasks = filter.generate_rake_tasks 123 | tasks.each(&:invoke) 124 | file = MemoryFileWrapper.files["/path/to/output/name.min.js"] 125 | file.body.should == expected_beautiful_js_output 126 | end 127 | end 128 | end 129 | -------------------------------------------------------------------------------- /spec/yui_css_filter_spec.rb: -------------------------------------------------------------------------------- 1 | describe "YUICssFilter" do 2 | YUICssFilter ||= Rake::Pipeline::Web::Filters::YUICssFilter 3 | MemoryFileWrapper ||= Rake::Pipeline::SpecHelpers::MemoryFileWrapper 4 | MemoryManifest ||= Rake::Pipeline::SpecHelpers::MemoryManifest 5 | 6 | let(:css_input) { <<-HERE } 7 | div.error { 8 | color: red; 9 | } 10 | div.warning { 11 | display: none; 12 | } 13 | HERE 14 | 15 | let(:expected_css_output) { 16 | 'div.error{color:red}div.warning{display:none}' 17 | } 18 | 19 | let(:expected_linebreak_css_output) { 20 | %[div.error{color:red}\ndiv.warning{display:none}] 21 | } 22 | 23 | def input_file(name, content) 24 | MemoryFileWrapper.new("/path/to/input", name, "UTF-8", content) 25 | end 26 | 27 | def output_file(name) 28 | MemoryFileWrapper.new("/path/to/output", name, "UTF-8") 29 | end 30 | 31 | def setup_filter(filter) 32 | filter.file_wrapper_class = MemoryFileWrapper 33 | filter.manifest = MemoryManifest.new 34 | filter.last_manifest = MemoryManifest.new 35 | filter.input_files = [input_file("error.css", css_input)] 36 | filter.output_root = "/path/to/output" 37 | filter.rake_application = Rake::Application.new 38 | filter 39 | end 40 | 41 | it "generates output" do 42 | filter = setup_filter YUICssFilter.new 43 | 44 | filter.output_files.should == [output_file("error.min.css")] 45 | 46 | tasks = filter.generate_rake_tasks 47 | tasks.each(&:invoke) 48 | 49 | file = MemoryFileWrapper.files["/path/to/output/error.min.css"] 50 | file.body.should == expected_css_output 51 | file.encoding.should == "UTF-8" 52 | end 53 | 54 | it "skips files named .min" do 55 | filter = setup_filter YUICssFilter.new 56 | 57 | filter.input_files = [input_file("error.min.css", "fake-css")] 58 | 59 | filter.output_files.should == [output_file("error.min.css")] 60 | 61 | tasks = filter.generate_rake_tasks 62 | tasks.each(&:invoke) 63 | 64 | file = MemoryFileWrapper.files["/path/to/output/error.min.css"] 65 | file.body.should == "fake-css" 66 | file.encoding.should == "UTF-8" 67 | end 68 | 69 | describe "naming output files" do 70 | it "translates .css extensions to .min.css by default" do 71 | filter = setup_filter YUICssFilter.new 72 | filter.output_files.first.path.should == "error.min.css" 73 | end 74 | 75 | it "accepts a block to customize output file names" do 76 | filter = setup_filter(YUICssFilter.new { |input| "octopus" }) 77 | filter.output_files.first.path.should == "octopus" 78 | end 79 | end 80 | 81 | it "accepts options to pass to the YUI compressor" do 82 | filter = setup_filter(YUICssFilter.new(:line_break => 0)) 83 | filter.input_files = [input_file("error.css", css_input)] 84 | tasks = filter.generate_rake_tasks 85 | tasks.each(&:invoke) 86 | file = MemoryFileWrapper.files["/path/to/output/error.min.css"] 87 | file.body.should == expected_linebreak_css_output 88 | end 89 | 90 | end 91 | 92 | -------------------------------------------------------------------------------- /spec/yui_javascript_filter_spec.rb: -------------------------------------------------------------------------------- 1 | describe "YUIJavaScriptFilter" do 2 | YUIFilter ||= Rake::Pipeline::Web::Filters::YUIJavaScriptFilter 3 | MemoryFileWrapper ||= Rake::Pipeline::SpecHelpers::MemoryFileWrapper 4 | MemoryManifest ||= Rake::Pipeline::SpecHelpers::MemoryManifest 5 | 6 | let(:js_input) { <<-HERE } 7 | var name = "Truckasaurus Gates"; 8 | console.log(name); 9 | HERE 10 | 11 | let(:expected_js_output) { 12 | 'var name="Truckasaurus Gates";console.log(name);' 13 | } 14 | 15 | let(:expected_linebreak_js_output) { 16 | %[var name="Truckasaurus Gates";\nconsole.log(name);] 17 | } 18 | 19 | def input_file(name, content) 20 | MemoryFileWrapper.new("/path/to/input", name, "UTF-8", content) 21 | end 22 | 23 | def output_file(name) 24 | MemoryFileWrapper.new("/path/to/output", name, "UTF-8") 25 | end 26 | 27 | def setup_filter(filter) 28 | filter.file_wrapper_class = MemoryFileWrapper 29 | filter.manifest = MemoryManifest.new 30 | filter.last_manifest = MemoryManifest.new 31 | filter.input_files = [input_file("name.js", js_input)] 32 | filter.output_root = "/path/to/output" 33 | filter.rake_application = Rake::Application.new 34 | filter 35 | end 36 | 37 | it "generates output" do 38 | filter = setup_filter YUIFilter.new 39 | 40 | filter.output_files.should == [output_file("name.min.js")] 41 | 42 | tasks = filter.generate_rake_tasks 43 | tasks.each(&:invoke) 44 | 45 | file = MemoryFileWrapper.files["/path/to/output/name.min.js"] 46 | file.body.should == expected_js_output 47 | file.encoding.should == "UTF-8" 48 | end 49 | 50 | describe "naming output files" do 51 | it "translates .js extensions to .min.js by default" do 52 | filter = setup_filter YUIFilter.new 53 | filter.output_files.first.path.should == "name.min.js" 54 | end 55 | 56 | it "accepts a block to customize output file names" do 57 | filter = setup_filter(YUIFilter.new { |input| "octopus" }) 58 | filter.output_files.first.path.should == "octopus" 59 | end 60 | end 61 | 62 | it "accepts options to pass to the YUI compressor" do 63 | filter = setup_filter(YUIFilter.new(:line_break => 0)) 64 | filter.input_files = [input_file("name.js", js_input)] 65 | tasks = filter.generate_rake_tasks 66 | tasks.each(&:invoke) 67 | file = MemoryFileWrapper.files["/path/to/output/name.min.js"] 68 | file.body.should == expected_linebreak_js_output 69 | end 70 | 71 | end 72 | --------------------------------------------------------------------------------