rake-pipeline 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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