diff --git a/lib/rake-pipeline-web-filters.rb b/lib/rake-pipeline-web-filters.rb index 277668e..bd70958 100644 --- a/lib/rake-pipeline-web-filters.rb +++ b/lib/rake-pipeline-web-filters.rb @@ -26,4 +26,5 @@ module Filters require "rake-pipeline-web-filters/chained_filter" require "rake-pipeline-web-filters/less_filter" require "rake-pipeline-web-filters/handlebars_filter" +require "rake-pipeline-web-filters/manifest_filter" require "rake-pipeline-web-filters/helpers" diff --git a/lib/rake-pipeline-web-filters/helpers.rb b/lib/rake-pipeline-web-filters/helpers.rb index 76aa7c6..8de6540 100644 --- a/lib/rake-pipeline-web-filters/helpers.rb +++ b/lib/rake-pipeline-web-filters/helpers.rb @@ -32,7 +32,7 @@ def sass(*args, &block) filter(Rake::Pipeline::Web::Filters::SassFilter, *args, &block) end alias_method :scss, :sass - + # Add a new {StylusFilter} to the pipeline. # @see StylusFilter#initialize def stylus(*args, &block) @@ -98,6 +98,22 @@ def less(*args, &block) def handlebars(*args, &block) filter(Rake::Pipeline::Web::Filters::HandlebarsFilter, *args, &block) end + # + # Add a new {ManifestFilter} to the pipeline. + # @see ManifestFilter#initialize + def manifest(*args, &block) + # Duplicate all current items in the pipeline into a new directory. + # We must work with duplicates otherwise the initial ones will + # be blown away. The manifest filter will strip out the manifest + # directory when generating the file. + match "**/*" do + copy { |name| [name, "manifest/#{name}"] } + end + + match "manifest/**/*" do + filter(Rake::Pipeline::Web::Filters::ManifestFilter, *args, &block) + end + end end module ProjectHelpers diff --git a/lib/rake-pipeline-web-filters/manifest_filter.rb b/lib/rake-pipeline-web-filters/manifest_filter.rb new file mode 100644 index 0000000..c84e982 --- /dev/null +++ b/lib/rake-pipeline-web-filters/manifest_filter.rb @@ -0,0 +1,68 @@ +module Rake::Pipeline::Web::Filters + # Take all inputs and output an HTML5 cache manifest. + # + # @example + # !!!ruby + # Rake::Pipeline.build do + # input "app/assets", "**/*.js" + # output "public" + # + # # NOTE: You must match and copy othwerise files will be deleted! + # match "**/*" do + # copy { |input| [input, "manifest/#{input}" ] + # end + # + # match "manifest/**/*" do + # filter Rake::Pipeline::Web::Filters::UglifyFilter + # end + # end + class ManifestFilter < Rake::Pipeline::Filter + include Rake::Pipeline::Web::Filters::FilterWithDependencies + + # @param [String] Generated file name + # @param [Proc] block a block to use as the Filter's + # {#output_name_generator}. + def initialize(name = 'cache.manifest', &block) + block ||= proc { |input| name } + super(&block) + end + + # Implement the {#generate_output} method required by + # the {Filter} API. Generates a proper HTML5 cache manifest + # with all inputs inside the cache section, a unique tag, + # and states that all other requests require internet access. + # + # @param [Array] inputs an Array of + # {FileWrapper} objects representing the inputs to + # this filter. + # @param [FileWrapper] output a single {FileWrapper} + # object representing the output. + def generate_output(inputs, output) + assets = inputs.map { |i| i.path.gsub('manifest/', '') }.join("\n") + + output.write ERB.new(template).result(binding) + end + + private + + def external_dependencies + [ "erb" ] + end + + def template + <<-erb +CACHE MANIFEST + +# Automatically generated by rake-pipeline-web-filters +# Tag: <%= Time.now.to_i %> (<%= Time.now %>) + +CACHE: +<%= assets %> + +NETWORK: +# All other resources require network access +* + erb + end + end +end diff --git a/spec/helpers_spec.rb b/spec/helpers_spec.rb index 3d1a8c5..34df3f5 100644 --- a/spec/helpers_spec.rb +++ b/spec/helpers_spec.rb @@ -102,6 +102,19 @@ def filter filter.should be_kind_of(Rake::Pipeline::Web::Filters::HandlebarsFilter) end end + + describe "#manifest" do + it "setups a proper manifest pipeline" do + dsl.manifest + + pipeline.filters.first.should be_kind_of(Rake::Pipeline::Matcher) + pipeline.filters.first.glob.should == "**/*" + + filter.should be_kind_of(Rake::Pipeline::Matcher) + filter.glob.should == "manifest/**/*" + filter.filters.first.should be_kind_of(Rake::Pipeline::Web::Filters::ManifestFilter) + end + end end describe "ProjectHelpers" do diff --git a/spec/manifest_filter_spec.rb b/spec/manifest_filter_spec.rb new file mode 100644 index 0000000..c91297e --- /dev/null +++ b/spec/manifest_filter_spec.rb @@ -0,0 +1,62 @@ +describe "ManifestFilter" do + ManifestFilter ||= Rake::Pipeline::Web::Filters::ManifestFilter + MemoryFileWrapper ||= Rake::Pipeline::SpecHelpers::MemoryFileWrapper + + def input_file(name, content) + MemoryFileWrapper.new("/path/to/input", name, "UTF-8", content) + end + + def output_file(name) + MemoryFileWrapper.new("/path/to/output", name, "UTF-8") + end + + def setup_filter(filter) + filter.file_wrapper_class = MemoryFileWrapper + filter.input_files = [ + input_file("manifest/index.html", ""), + input_file("manifest/application.js", ""), + input_file("manifest/application.css", "") + ] + filter.output_root = "/path/to/output" + filter.rake_application = Rake::Application.new + filter + end + + it "generates a proper cache manifest for output" do + filter = setup_filter ManifestFilter.new("cache.manifest") + + filter.output_files.should == [output_file("cache.manifest")] + + tasks = filter.generate_rake_tasks + tasks.each(&:invoke) + + file = MemoryFileWrapper.files["/path/to/output/cache.manifest"] + + file.encoding.should == "UTF-8" + + file.body.lines.first.chomp.should == 'CACHE MANIFEST' + + file.body.should match(%r{# Tag: \d+}) + + file.body.should match(/^index\.html/) + file.body.should match(/^application\.css/) + file.body.should match(/^application\.js/) + end + + describe "naming output files" do + it "should use cache.manifest by default" do + filter = setup_filter ManifestFilter.new + filter.output_files.first.path.should == "cache.manifest" + end + + it "accepts a string to set the output file name" do + filter = setup_filter(ManifestFilter.new("octopus")) + filter.output_files.first.path.should == "octopus" + end + + it "accepts a block to customize output file names" do + filter = setup_filter(ManifestFilter.new { |input| "octopus" }) + filter.output_files.first.path.should == "octopus" + end + end +end