rake-pipeline 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ require "rake-pipeline/filters/concat"
@@ -0,0 +1,63 @@
1
+ module Rake
2
+ class Pipeline
3
+ # A built-in filter that simply accepts a series
4
+ # of inputs and concatenates them into output files
5
+ # based on the output file name generator.
6
+ #
7
+ # @example
8
+ # !!!ruby
9
+ # Pipeline.build do
10
+ # input "app/assets", "**/*.js"
11
+ # output "public"
12
+ #
13
+ # # create a concatenated output file for each
14
+ # # directory of inputs.
15
+ # filter(Rake::Pipeline::ConcatFilter) do |input|
16
+ # # input files will look something like:
17
+ # # javascripts/admin/main.js
18
+ # # javascripts/admin/app.js
19
+ # # javascripts/users/main.js
20
+ # #
21
+ # # and the outputs will look like:
22
+ # # javascripts/admin.js
23
+ # # javascripts/users.js
24
+ # directory = File.dirname(input)
25
+ # ext = File.extname(input)
26
+ #
27
+ # "#{directory}#{ext}"
28
+ # end
29
+ # end
30
+ class ConcatFilter < Rake::Pipeline::Filter
31
+ # @param [String] string the name of the output file to
32
+ # concatenate inputs to.
33
+ # @param [Proc] block a block to use as the Filter's
34
+ # {#output_name_generator}.
35
+ def initialize(string=nil, &block)
36
+ block = proc { string } if string
37
+ super(&block)
38
+ end
39
+
40
+ # @method encoding
41
+ # @return [String] the String +"BINARY"+
42
+ processes_binary_files
43
+
44
+ # implement the {#generate_output} method required by
45
+ # the {Filter} API. In this case, simply loop through
46
+ # the inputs and write their contents to the output.
47
+ #
48
+ # Recall that this method will be called once for each
49
+ # unique output file.
50
+ #
51
+ # @param [Array<FileWrapper>] inputs an Array of
52
+ # {FileWrapper} objects representing the inputs to
53
+ # this filter.
54
+ # @param [FileWrapper] a single {FileWrapper} object
55
+ # representing the output.
56
+ def generate_output(inputs, output)
57
+ inputs.each do |input|
58
+ output.write input.read
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,106 @@
1
+ require "strscan"
2
+
3
+ module Rake
4
+ class Pipeline
5
+ # A Matcher is a type of pipeline that restricts its
6
+ # filters to a particular pattern.
7
+ #
8
+ # A Matcher's pattern is a File glob.
9
+ #
10
+ # For instance, to restrict filters to operating on
11
+ # JavaScript files in the +app+ directory, the Matcher's
12
+ # {Pipeline#input_root input_root} should be +"app"+,
13
+ # and its glob would be +"*.js"+.
14
+ #
15
+ # In general, you should not use Matcher directly. Instead use
16
+ # {DSL#match} in the block passed to {Pipeline.build}.
17
+ class Matcher < Pipeline
18
+ attr_reader :glob
19
+
20
+ # A glob matcher that a filter's input files must match
21
+ # in order to be processed by the filter.
22
+ #
23
+ # @return [String]
24
+ def glob=(pattern)
25
+ @glob = pattern
26
+ scanner = StringScanner.new(pattern)
27
+
28
+ output, pos = "", 0
29
+
30
+ # keep scanning until end of String
31
+ until scanner.eos?
32
+
33
+ # look for **/, *, {...}, or the end of the string
34
+ new_chars = scanner.scan_until %r{
35
+ \*\*/
36
+ | /\*\*/
37
+ | \*
38
+ | \{[^\}]*\}
39
+ | $
40
+ }x
41
+
42
+ # get the new part of the string up to the match
43
+ before = new_chars[0, new_chars.size - scanner.matched_size]
44
+
45
+ # get the match and new position
46
+ match = scanner.matched
47
+ pos = scanner.pos
48
+
49
+ # add any literal characters to the output
50
+ output << Regexp.escape(before) if before
51
+
52
+ output << case match
53
+ when "/**/"
54
+ # /**/ matches either a "/" followed by any number
55
+ # of characters or a single "/"
56
+ "(/.*|/)"
57
+ when "**/"
58
+ # **/ matches the beginning of the path or
59
+ # any number of characters followed by a "/"
60
+ "(^|.*/)"
61
+ when "*"
62
+ # * matches any number of non-"/" characters
63
+ "[^/]*"
64
+ when /\{.*\}/
65
+ # {...} is split over "," and glued back together
66
+ # as an or condition
67
+ "(" + match[1...-1].gsub(",", "|") + ")"
68
+ else String
69
+ # otherwise, we've grabbed until the end
70
+ match
71
+ end
72
+ end
73
+
74
+ # anchor the pattern either at the beginning of the
75
+ # path or at any "/" character
76
+ @pattern = Regexp.new("(^|/)#{output}$", "i")
77
+ end
78
+
79
+ # A list of the output files that invoking this pipeline will
80
+ # generate. This will include the outputs of files matching
81
+ # the {#glob glob} and any inputs that did not match the
82
+ # glob.
83
+ #
84
+ # This will make those inputs available to any additional
85
+ # filters or matchers.
86
+ #
87
+ # @return [Array<FileWrapper>]
88
+ def output_files
89
+ super + input_files.reject do |file|
90
+ file.path =~ @pattern
91
+ end
92
+ end
93
+
94
+ private
95
+ # Override the default {Pipeline#eligible_input_files}
96
+ # to include only files that match the {#glob glob}.
97
+ #
98
+ # @return [Array<FileWrapper>]
99
+ def eligible_input_files
100
+ input_files.select do |file|
101
+ file.path =~ @pattern
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,70 @@
1
+ require "rack"
2
+
3
+ module Rake
4
+ class Pipeline
5
+ # This middleware is used to provide a server that will continuously
6
+ # compile your files on demand.
7
+ #
8
+ # @example
9
+ # !!!ruby
10
+ # use Rake::Pipeline::Middleware, Rake::Pipeline.build {
11
+ # input "app/assets"
12
+ # output "public"
13
+ #
14
+ # ...
15
+ # }
16
+ class Middleware
17
+ attr_accessor :pipeline
18
+
19
+ # @param [#call] a Rack application
20
+ # @param [Pipeline] a Rake::Pipeline
21
+ def initialize(app, pipeline)
22
+ @app = app
23
+
24
+ if pipeline.is_a?(String)
25
+ pipeline_source = File.read(pipeline)
26
+ pipeline = Pipeline.class_eval "build do\n#{pipeline_source}\nend", pipeline, 1
27
+ end
28
+
29
+ @pipeline = pipeline
30
+ end
31
+
32
+ # Automatically compiles your assets if required and
33
+ # serves them up.
34
+ #
35
+ # @param [Hash] env a Rack environment
36
+ # @return [Array(Fixnum, Hash, #each)] A rack response
37
+ def call(env)
38
+ pipeline.invoke_clean
39
+ path = env["PATH_INFO"]
40
+
41
+ if filename = file_for(path)
42
+ if File.directory?(filename)
43
+ index = File.join(filename, "index.html")
44
+ filename = File.file?(index) ? index : nil
45
+ end
46
+
47
+ if filename
48
+ return response_for(filename)
49
+ end
50
+ end
51
+
52
+ @app.call(env)
53
+ end
54
+
55
+ private
56
+ def response_for(file)
57
+ [ 200, headers_for(file), File.open(file, "r") ]
58
+ end
59
+
60
+ def file_for(path)
61
+ Dir[File.join(pipeline.output_root, path)].first
62
+ end
63
+
64
+ def headers_for(path)
65
+ mime = Rack::Mime.mime_type(File.extname(path), "text/plain")
66
+ { "Content-Type" => mime }
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,8 @@
1
+ require 'rake-pipeline/middleware'
2
+
3
+ Rails.configuration.after_initialize do
4
+ if defined?(RAKEP_ENABLED) && RAKEP_ENABLED
5
+ assetfile = defined?(RAKEP_ASSETFILE) ? RAKEP_ASSETFILE : 'Assetfile'
6
+ Rails.configuration.middleware.use(Rake::Pipeline::Middleware, assetfile)
7
+ end
8
+ end
@@ -0,0 +1,17 @@
1
+ require "rake-pipeline/middleware"
2
+
3
+ module Rake
4
+ class Pipeline
5
+ class Railtie < ::Rails::Railtie
6
+ config.rake_pipeline_enabled = false
7
+ config.rake_pipeline_assetfile = 'Assetfile'
8
+
9
+ initializer "rake-pipeline.assetfile" do |app|
10
+ if config.rake_pipeline_enabled
11
+ assetfile = File.join(Rails.root, config.rake_pipeline_assetfile)
12
+ config.app_middleware.use(Rake::Pipeline::Middleware, assetfile)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,6 @@
1
+ module Rake
2
+ class Pipeline
3
+ # @version 0.5.0
4
+ VERSION = "0.5.0"
5
+ end
6
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ # For Rails 2
2
+ require 'rake-pipeline'
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/rake-pipeline/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Yehuda Katz", "Tom Dale"]
6
+ gem.email = ["wycats@gmail.com"]
7
+ gem.description = "Simple Asset Management"
8
+ gem.summary = "Simple Asset Management"
9
+ gem.homepage = ""
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"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Rake::Pipeline::VERSION
17
+
18
+ gem.add_dependency "rake", "~> 0.9.0"
19
+
20
+ gem.add_development_dependency "rspec"
21
+ gem.add_development_dependency "rack-test"
22
+ end
@@ -0,0 +1,60 @@
1
+ describe "ConcatFilter" do
2
+ class MemoryFileWrapper < Struct.new(:root, :path, :encoding, :body)
3
+ @@files = {}
4
+
5
+ def self.files
6
+ @@files
7
+ end
8
+
9
+ def with_encoding(new_encoding)
10
+ self.class.new(root, path, new_encoding, body)
11
+ end
12
+
13
+ def fullpath
14
+ File.join(root, path)
15
+ end
16
+
17
+ def create
18
+ @@files[fullpath] = self
19
+ self.body = ""
20
+ yield
21
+ end
22
+
23
+ alias read body
24
+
25
+ def write(contents)
26
+ self.body << contents
27
+ end
28
+ end
29
+
30
+ let(:input_files) {
31
+ [
32
+ MemoryFileWrapper.new("/path/to/input", "javascripts/jquery.js", "UTF-8", "jQuery = {};"),
33
+ MemoryFileWrapper.new("/path/to/input", "javascripts/sproutcore.js", "UTF-8", "SC = {};")
34
+ ]
35
+ }
36
+
37
+ it "generates output" do
38
+ filter = Rake::Pipeline::ConcatFilter.new { "application.js" }
39
+ filter.file_wrapper_class = MemoryFileWrapper
40
+ filter.output_root = "/path/to/output"
41
+ filter.input_files = input_files
42
+
43
+ filter.output_files.should == [MemoryFileWrapper.new("/path/to/output", "application.js", "BINARY")]
44
+
45
+ tasks = filter.generate_rake_tasks
46
+ tasks.each(&:invoke)
47
+
48
+ file = MemoryFileWrapper.files["/path/to/output/application.js"]
49
+ file.body.should == "jQuery = {};SC = {};"
50
+ file.encoding.should == "BINARY"
51
+ end
52
+
53
+ it "accepts a string to use as the output file name" do
54
+ filter = Rake::Pipeline::ConcatFilter.new("app.js")
55
+ filter.file_wrapper_class = MemoryFileWrapper
56
+ filter.output_root = "/path/to/output"
57
+ filter.input_files = input_files
58
+ filter.output_files.should == [MemoryFileWrapper.new("/path/to/output", "app.js", "BINARY")]
59
+ end
60
+ end
data/spec/dsl_spec.rb ADDED
@@ -0,0 +1,86 @@
1
+ describe "Rake::Pipeline::DSL" do
2
+ ConcatFilter = Rake::Pipeline::SpecHelpers::Filters::ConcatFilter
3
+
4
+ let(:pipeline) { Rake::Pipeline.new }
5
+ let(:dsl) { Rake::Pipeline::DSL.new(pipeline) }
6
+
7
+ before do
8
+ pipeline.input_root = "."
9
+ end
10
+
11
+ it "accepts a pipeline in its constructor" do
12
+ dsl.pipeline.should == pipeline
13
+ end
14
+
15
+ describe "#input" do
16
+ it "configures the pipeline's input_root" do
17
+ dsl.input "/app"
18
+ pipeline.input_root.should == "/app"
19
+ end
20
+
21
+ it "configures the pipeline's input_glob" do
22
+ dsl.input "/app", "*.js"
23
+ pipeline.input_glob.should == "*.js"
24
+ end
25
+
26
+ it "defaults the pipeline's input_glob to **/*" do
27
+ dsl.input "/app"
28
+ pipeline.input_glob.should == "**/*"
29
+ end
30
+ end
31
+
32
+ describe "#filter" do
33
+
34
+ it "adds a new instance of the filter class to the pipeline's filters" do
35
+ pipeline.filters.should be_empty
36
+ dsl.filter ConcatFilter
37
+ pipeline.filters.should_not be_empty
38
+ pipeline.filters.last.should be_kind_of(ConcatFilter)
39
+ end
40
+
41
+ it "takes a block to configure the filter's output file names" do
42
+ generator = proc { |input| "main.js" }
43
+ dsl.filter(ConcatFilter, &generator)
44
+ pipeline.filters.last.output_name_generator.should == generator
45
+ end
46
+
47
+ it "passes any extra arguments to the filter's constructor" do
48
+ filter_class = Class.new(Rake::Pipeline::Filter) do
49
+ attr_reader :args
50
+ def initialize(*args)
51
+ @args = args
52
+ end
53
+ end
54
+
55
+ dsl.filter filter_class, "foo", "bar"
56
+ pipeline.filters.last.args.should == %w(foo bar)
57
+ end
58
+ end
59
+
60
+ describe "#match" do
61
+ it "creates a Matcher for the given glob" do
62
+ matcher = dsl.match("*.glob") {}
63
+ matcher.should be_kind_of(Rake::Pipeline::Matcher)
64
+ matcher.glob.should == "*.glob"
65
+ end
66
+
67
+ it "adds the new matcher to the pipeline's filters" do
68
+ matcher = dsl.match("*.glob") {}
69
+ pipeline.filters.last.should == matcher
70
+ end
71
+ end
72
+
73
+ describe "#output" do
74
+ it "configures the pipeline's output_root" do
75
+ dsl.output "/path/to/output"
76
+ pipeline.output_root.should == "/path/to/output"
77
+ end
78
+ end
79
+
80
+ describe "#tmpdir" do
81
+ it "configures the pipeline's tmpdir" do
82
+ dsl.tmpdir "/temporary"
83
+ pipeline.tmpdir.should == "/temporary"
84
+ end
85
+ end
86
+ end