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.
- data/.gitignore +18 -0
- data/.rspec +1 -0
- data/.yardopts +2 -0
- data/Gemfile +10 -0
- data/LICENSE +20 -0
- data/README.markdown +4 -0
- data/README.yard +149 -0
- data/Rakefile +12 -0
- data/bin/rakep +27 -0
- data/lib/rake-pipeline.rb +365 -0
- data/lib/rake-pipeline/dsl.rb +146 -0
- data/lib/rake-pipeline/error.rb +17 -0
- data/lib/rake-pipeline/file_wrapper.rb +173 -0
- data/lib/rake-pipeline/filter.rb +209 -0
- data/lib/rake-pipeline/filters.rb +1 -0
- data/lib/rake-pipeline/filters/concat.rb +63 -0
- data/lib/rake-pipeline/matcher.rb +106 -0
- data/lib/rake-pipeline/middleware.rb +70 -0
- data/lib/rake-pipeline/rails_plugin.rb +8 -0
- data/lib/rake-pipeline/railtie.rb +17 -0
- data/lib/rake-pipeline/version.rb +6 -0
- data/rails/init.rb +2 -0
- data/rake-pipeline.gemspec +22 -0
- data/spec/concat_filter_spec.rb +60 -0
- data/spec/dsl_spec.rb +86 -0
- data/spec/encoding_spec.rb +106 -0
- data/spec/file_wrapper_spec.rb +105 -0
- data/spec/filter_spec.rb +216 -0
- data/spec/matcher_spec.rb +105 -0
- data/spec/middleware_spec.rb +149 -0
- data/spec/pipeline_spec.rb +160 -0
- data/spec/rake_acceptance_spec.rb +240 -0
- data/spec/spec_helper.rb +74 -0
- metadata +123 -0
@@ -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
|
data/rails/init.rb
ADDED
@@ -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
|