rake-pipeline-fork 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +18 -0
- data/.rspec +1 -0
- data/.travis.yml +12 -0
- data/.yardopts +2 -0
- data/GETTING_STARTED.md +268 -0
- data/Gemfile +14 -0
- data/LICENSE +20 -0
- data/README.markdown +11 -0
- data/README.yard +178 -0
- data/Rakefile +21 -0
- data/bin/rakep +4 -0
- data/examples/copying_files.md +12 -0
- data/examples/minifying_files.md +37 -0
- data/examples/modifying_pipelines.md +67 -0
- data/examples/multiple_pipelines.md +77 -0
- data/lib/generators/rake/pipeline/install/install_generator.rb +70 -0
- data/lib/rake-pipeline.rb +462 -0
- data/lib/rake-pipeline/cli.rb +56 -0
- data/lib/rake-pipeline/dsl.rb +9 -0
- data/lib/rake-pipeline/dsl/pipeline_dsl.rb +246 -0
- data/lib/rake-pipeline/dsl/project_dsl.rb +108 -0
- data/lib/rake-pipeline/dynamic_file_task.rb +194 -0
- data/lib/rake-pipeline/error.rb +17 -0
- data/lib/rake-pipeline/file_wrapper.rb +182 -0
- data/lib/rake-pipeline/filter.rb +249 -0
- data/lib/rake-pipeline/filters.rb +4 -0
- data/lib/rake-pipeline/filters/concat_filter.rb +63 -0
- data/lib/rake-pipeline/filters/gsub_filter.rb +56 -0
- data/lib/rake-pipeline/filters/ordering_concat_filter.rb +38 -0
- data/lib/rake-pipeline/filters/pipeline_finalizing_filter.rb +21 -0
- data/lib/rake-pipeline/graph.rb +178 -0
- data/lib/rake-pipeline/manifest.rb +86 -0
- data/lib/rake-pipeline/manifest_entry.rb +34 -0
- data/lib/rake-pipeline/matcher.rb +141 -0
- data/lib/rake-pipeline/middleware.rb +72 -0
- data/lib/rake-pipeline/precompile.rake +8 -0
- data/lib/rake-pipeline/project.rb +335 -0
- data/lib/rake-pipeline/rails_plugin.rb +10 -0
- data/lib/rake-pipeline/railtie.rb +34 -0
- data/lib/rake-pipeline/reject_matcher.rb +29 -0
- data/lib/rake-pipeline/server.rb +15 -0
- data/lib/rake-pipeline/sorted_pipeline.rb +19 -0
- data/lib/rake-pipeline/version.rb +6 -0
- data/rails/init.rb +2 -0
- data/rake-pipeline.gemspec +24 -0
- data/spec/cli_spec.rb +71 -0
- data/spec/concat_filter_spec.rb +37 -0
- data/spec/dsl/pipeline_dsl_spec.rb +165 -0
- data/spec/dsl/project_dsl_spec.rb +41 -0
- data/spec/dynamic_file_task_spec.rb +119 -0
- data/spec/encoding_spec.rb +106 -0
- data/spec/file_wrapper_spec.rb +132 -0
- data/spec/filter_spec.rb +332 -0
- data/spec/graph_spec.rb +56 -0
- data/spec/gsub_filter_spec.rb +87 -0
- data/spec/manifest_entry_spec.rb +46 -0
- data/spec/manifest_spec.rb +67 -0
- data/spec/matcher_spec.rb +141 -0
- data/spec/middleware_spec.rb +199 -0
- data/spec/ordering_concat_filter_spec.rb +42 -0
- data/spec/pipeline_spec.rb +232 -0
- data/spec/project_spec.rb +295 -0
- data/spec/rake_acceptance_spec.rb +738 -0
- data/spec/rake_tasks_spec.rb +21 -0
- data/spec/reject_matcher_spec.rb +31 -0
- data/spec/sorted_pipeline_spec.rb +27 -0
- data/spec/spec_helper.rb +38 -0
- data/spec/support/spec_helpers/file_utils.rb +35 -0
- data/spec/support/spec_helpers/filters.rb +37 -0
- data/spec/support/spec_helpers/input_helpers.rb +23 -0
- data/spec/support/spec_helpers/memory_file_wrapper.rb +31 -0
- data/spec/support/spec_helpers/memory_manifest.rb +19 -0
- data/tools/perfs +101 -0
- metadata +215 -0
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
|
4
|
+
directory "doc"
|
5
|
+
|
6
|
+
desc "generate documentation"
|
7
|
+
task :docs => Dir["lib/**"] do
|
8
|
+
sh "devbin/yard doc --readme README.yard --hide-void-return"
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "generate a dependency graph from the documentation"
|
12
|
+
task :graph => ["doc", :docs] do
|
13
|
+
sh "devbin/yard graph --dependencies | dot -Tpng -o doc/arch.png"
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "run the specs"
|
17
|
+
task :spec do
|
18
|
+
sh "rspec"
|
19
|
+
end
|
20
|
+
|
21
|
+
task :default => :spec
|
data/bin/rakep
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
The `copy` method also accepts an array. You can use this to copy the
|
2
|
+
file into multiple locations. Here's an example `Assetfile`:
|
3
|
+
|
4
|
+
```
|
5
|
+
input "source" do
|
6
|
+
match "*.js" do
|
7
|
+
copy do |input|
|
8
|
+
["/staging/#{input.path}", "/production/#{input.path}"]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
```
|
@@ -0,0 +1,37 @@
|
|
1
|
+
Minifying files is a very common task for web developers. There are two
|
2
|
+
common uses cases:
|
3
|
+
|
4
|
+
1. Create an unminified and minified file
|
5
|
+
2. Generate a minified file from an unminifed file.
|
6
|
+
|
7
|
+
Doing #2 is very easy with rake pipeline. Doing #1 is slightly more
|
8
|
+
complicated. For these examples assume there is a `MinifyFilter` that
|
9
|
+
actually does the work.
|
10
|
+
|
11
|
+
Doing the first use case creates a problem. Filters are destructive.
|
12
|
+
That means if you change one file (move it, rewrite) it will not be
|
13
|
+
available for the next filters. For example, taking `app.js` and
|
14
|
+
minifying it will remove the unminfied source from the pipeline.
|
15
|
+
|
16
|
+
You must first duplicate the unminified file then minify it. Here's an
|
17
|
+
`Assetfile`
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
input "source" do
|
21
|
+
match "application.js" do
|
22
|
+
# Duplicate the source file for future filters (application.js)
|
23
|
+
# and provide duplicate in "application.min.js" for minifcation
|
24
|
+
copy ["application.js", "application.min.js"]
|
25
|
+
end
|
26
|
+
|
27
|
+
match "application.min.js" do
|
28
|
+
filter MinifyFilter
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
output "compiled"
|
33
|
+
```
|
34
|
+
|
35
|
+
Now you have two files: `application.js` and `application.min.js`. You
|
36
|
+
can use this same technique everytime you need to transform a file and
|
37
|
+
keep the source around for future filters.
|
@@ -0,0 +1,67 @@
|
|
1
|
+
You may want to do some trickery with your input files. Here is a use
|
2
|
+
case: you need to sort your files in a custom way. The easiest way to do
|
3
|
+
this is to insert a new pipeline into your pipeline. This pipeline must
|
4
|
+
act like a filter because it will be used as such. Let's start out by
|
5
|
+
describing the most basic pipeline:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
class PassThroughPipeline < Rake::Pipeline
|
9
|
+
# this need to act like a filter
|
10
|
+
attr_accessor :pipeline
|
11
|
+
|
12
|
+
# simply return the original input_files
|
13
|
+
def output_files
|
14
|
+
input_files
|
15
|
+
end
|
16
|
+
|
17
|
+
# this is very imporant! define this method
|
18
|
+
# to do nothing and files will not be copied
|
19
|
+
# to the output directory
|
20
|
+
def finalize
|
21
|
+
end
|
22
|
+
end
|
23
|
+
```
|
24
|
+
|
25
|
+
At this point you can insert it into your pipeline:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
input "**/*.js" do
|
29
|
+
# stick our pass through
|
30
|
+
pass_through = pipeline.copy PassThroughPipeline
|
31
|
+
pipeline.add_filter pass_through
|
32
|
+
|
33
|
+
# now continue on with your life
|
34
|
+
concat "application.js"
|
35
|
+
end
|
36
|
+
```
|
37
|
+
|
38
|
+
Now we can begin to do all sorts of crazyness in this pass through
|
39
|
+
pipeline. You could expand directories to groups of files or you could
|
40
|
+
collapse then. You could even skip files if you wanted to. Hell, you can
|
41
|
+
even sort them--and that's what we're going to do. So let's get going
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
class SortedPipeline < PassThroughPipeline
|
45
|
+
def output_files
|
46
|
+
super.sort do |f1, f2|
|
47
|
+
# just an easy example of reverse sorting
|
48
|
+
f2.fullpath <=> f1.fullpath
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
Now add it to the pipeline:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
input "**/*.js" do
|
58
|
+
# stick our pass through
|
59
|
+
pass_through = pipeline.copy SortedPipeline
|
60
|
+
pipeline.add_filter pass_through
|
61
|
+
|
62
|
+
# now continue on with your life
|
63
|
+
concat "application.js"
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
Voila! You can sort stuff. Let your mind run wild with possibilities!
|
@@ -0,0 +1,77 @@
|
|
1
|
+
Your build process may become to complex to express in terms of a single
|
2
|
+
`input` block. You can break your `Assetfile` into multiple input
|
3
|
+
blocks. You can also use an `input`'s output as the input for a new
|
4
|
+
`input` block.
|
5
|
+
|
6
|
+
Say you have different pipelines for different types of files. There is
|
7
|
+
one for JS, CSS, and other assets. The output of these 3 different build
|
8
|
+
steps needs be the input for the next build step. You can express that
|
9
|
+
rather easily with `Rake::Pipeline`. Here are the pipelines for the
|
10
|
+
different types. The internals are left out because they are not
|
11
|
+
relavant to this example.
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
# Stage 1
|
15
|
+
input "js" do
|
16
|
+
# do JS stuff
|
17
|
+
end
|
18
|
+
|
19
|
+
input "css" do
|
20
|
+
# do css stuff
|
21
|
+
end
|
22
|
+
|
23
|
+
input "assets" do
|
24
|
+
# do asset stuff
|
25
|
+
end
|
26
|
+
```
|
27
|
+
|
28
|
+
Now let's add a line at the top of the `Assetfile`. Let's stay that all
|
29
|
+
the pipelines should output into one directory.
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
# All `input` blocks will output to `tmp/stage1` unless output is
|
33
|
+
# set again
|
34
|
+
output "tmp/stage1"
|
35
|
+
|
36
|
+
input "js" do
|
37
|
+
# do JS stuff
|
38
|
+
end
|
39
|
+
|
40
|
+
input "css" do
|
41
|
+
# do css stuff
|
42
|
+
end
|
43
|
+
|
44
|
+
input "assets" do
|
45
|
+
# do asset stuff
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
Now let's hookup stage 2.
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
# Stage 1
|
53
|
+
output "tmp/stage1"
|
54
|
+
input "js" do
|
55
|
+
# do JS stuff
|
56
|
+
end
|
57
|
+
|
58
|
+
input "css" do
|
59
|
+
# do css stuff
|
60
|
+
end
|
61
|
+
|
62
|
+
input "assets" do
|
63
|
+
# do asset stuff
|
64
|
+
end
|
65
|
+
|
66
|
+
# Stage 2
|
67
|
+
# output of next input block should go to the real output directory
|
68
|
+
output "compiled"
|
69
|
+
input "tmp/stage1" do
|
70
|
+
# do stage 2 stuff
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
You can repeat this process over and over again for as many stages as
|
75
|
+
you like. Just remember that the final input should output to where you
|
76
|
+
want the final files. Also, keep the intermediate build steps inside
|
77
|
+
temp directories so they are ignored by source control.
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Rake
|
2
|
+
class Pipeline
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
4
|
+
|
5
|
+
desc "Install Rake::Pipeline in this Rails app"
|
6
|
+
|
7
|
+
def disable_asset_pipeline_railtie
|
8
|
+
say_status :config, "Updating configuration to remove asset pipeline"
|
9
|
+
gsub_file app, "require 'rails/all'", <<-RUBY.strip_heredoc
|
10
|
+
# Pick the frameworks you want:
|
11
|
+
require "active_record/railtie"
|
12
|
+
require "action_controller/railtie"
|
13
|
+
require "action_mailer/railtie"
|
14
|
+
require "active_resource/railtie"
|
15
|
+
require "rails/test_unit/railtie"
|
16
|
+
RUBY
|
17
|
+
end
|
18
|
+
|
19
|
+
# TODO: Support sprockets API
|
20
|
+
def disable_asset_pipeline_config
|
21
|
+
regex = /^\n?\s*#.*\n\s*(#\s*)?config\.assets.*\n/
|
22
|
+
gsub_file app, regex, ''
|
23
|
+
gsub_file Rails.root.join("config/environments/development.rb"), regex, ''
|
24
|
+
gsub_file Rails.root.join("config/environments/production.rb"), regex, ''
|
25
|
+
end
|
26
|
+
|
27
|
+
def remove_assets_group
|
28
|
+
regex = /^\n(#.*\n)+group :assets.*\n(.*\n)*?end\n/
|
29
|
+
|
30
|
+
gsub_file "Gemfile", regex, ''
|
31
|
+
end
|
32
|
+
|
33
|
+
def enable_assets_in_development
|
34
|
+
gsub_file "config/environments/development.rb", /^end/, "\n config.rake_pipeline_enabled = true\nend"
|
35
|
+
end
|
36
|
+
|
37
|
+
# TODO: Support asset-pipeline like API
|
38
|
+
def add_assetfile
|
39
|
+
create_file "Assetfile", <<-RUBY.strip_heredoc
|
40
|
+
# NOTE: The Assetfile will eventually be replaced with an asset-pipeline
|
41
|
+
# compatible API. This is mostly important so that plugins can easily
|
42
|
+
# inject into the pipeline.
|
43
|
+
#
|
44
|
+
# Depending on demand and how the API shakes out, we may retain the
|
45
|
+
# Assetfile API but pull in the information from the Rails API.
|
46
|
+
|
47
|
+
input "app/assets"
|
48
|
+
output "public"
|
49
|
+
|
50
|
+
match "*.js" do
|
51
|
+
concat "application.js"
|
52
|
+
end
|
53
|
+
|
54
|
+
match "*.css" do
|
55
|
+
concat "application.css"
|
56
|
+
end
|
57
|
+
|
58
|
+
# copy any remaining files
|
59
|
+
concat
|
60
|
+
RUBY
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
def app
|
65
|
+
@app ||= Rails.root.join("config/application.rb")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
@@ -0,0 +1,462 @@
|
|
1
|
+
require "rake-pipeline/file_wrapper"
|
2
|
+
require "rake-pipeline/filter"
|
3
|
+
require "rake-pipeline/manifest_entry"
|
4
|
+
require "rake-pipeline/manifest"
|
5
|
+
require "rake-pipeline/dynamic_file_task"
|
6
|
+
require "rake-pipeline/filters"
|
7
|
+
require "rake-pipeline/dsl"
|
8
|
+
require "rake-pipeline/matcher"
|
9
|
+
require "rake-pipeline/reject_matcher"
|
10
|
+
require "rake-pipeline/sorted_pipeline"
|
11
|
+
require "rake-pipeline/error"
|
12
|
+
require "rake-pipeline/project"
|
13
|
+
require "rake-pipeline/cli"
|
14
|
+
require "rake-pipeline/graph"
|
15
|
+
|
16
|
+
if defined?(Rails::Railtie)
|
17
|
+
require "rake-pipeline/railtie"
|
18
|
+
elsif defined?(Rails)
|
19
|
+
require "rake-pipeline/rails_plugin"
|
20
|
+
end
|
21
|
+
|
22
|
+
require "thread"
|
23
|
+
|
24
|
+
# Use the Rake namespace
|
25
|
+
module Rake
|
26
|
+
# Override Rake::Task to support recursively re-enabling
|
27
|
+
# a task and its dependencies.
|
28
|
+
class Task
|
29
|
+
|
30
|
+
# @param [Rake::Application] app a Rake Application
|
31
|
+
# @return [void]
|
32
|
+
def recursively_reenable(app)
|
33
|
+
reenable
|
34
|
+
|
35
|
+
prerequisites.each do |dep|
|
36
|
+
app[dep].recursively_reenable(app)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Override Rake::FileTask to make it sortable
|
42
|
+
class FileTask
|
43
|
+
# implement Ruby protocol for sorting
|
44
|
+
#
|
45
|
+
# @return [Fixnum]
|
46
|
+
def <=>(other)
|
47
|
+
[name, prerequisites] <=> [other.name, other.prerequisites]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# A Pipeline is responsible for taking a directory of input
|
52
|
+
# files, applying a number of filters to the inputs, and
|
53
|
+
# outputting them into an output directory.
|
54
|
+
#
|
55
|
+
# The normal way to build and configure a pipeline is by
|
56
|
+
# using {.build}. Inside the block passed to {.build}, all
|
57
|
+
# methods of {DSL} are available.
|
58
|
+
#
|
59
|
+
# @see DSL Rake::Pipeline::DSL for information on the methods
|
60
|
+
# available inside the block.
|
61
|
+
#
|
62
|
+
# @example
|
63
|
+
# !!!ruby
|
64
|
+
# Rake::Pipeline.build do
|
65
|
+
# # process all js, css and html files in app/assets
|
66
|
+
# input "app/assets", "**/*.{js,coffee,css,scss,html}"
|
67
|
+
#
|
68
|
+
# # processed files should be outputted to public
|
69
|
+
# output "public"
|
70
|
+
#
|
71
|
+
# # process all coffee files
|
72
|
+
# match "*.coffee" do
|
73
|
+
# # compile all CoffeeScript files. the output file
|
74
|
+
# # for the compilation should be the input name
|
75
|
+
# # with the .coffee extension replaced with .js
|
76
|
+
# filter(CoffeeCompiler) do |input|
|
77
|
+
# input.sub(/\.coffee$/, '.js')
|
78
|
+
# end
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# # specify filters for js files. this includes the
|
82
|
+
# # output of the previous step, which converted
|
83
|
+
# # coffee files to js files
|
84
|
+
# match "*.js" do
|
85
|
+
# # first, wrap all JS files in a custom filter
|
86
|
+
# filter ClosureFilter
|
87
|
+
# # then, concatenate all JS files into a single file
|
88
|
+
# concat "application.js"
|
89
|
+
# end
|
90
|
+
#
|
91
|
+
# # specify filters for css and scss files
|
92
|
+
# match "*.{css,scss}" do
|
93
|
+
# # compile CSS and SCSS files using the SCSS
|
94
|
+
# # compiler. if an input file has the extension
|
95
|
+
# # scss, replace it with css
|
96
|
+
# filter(ScssCompiler) do |input|
|
97
|
+
# input.sub(/\.scss$/, 'css')
|
98
|
+
# end
|
99
|
+
# # then, concatenate all CSS files into a single file
|
100
|
+
# concat "application.css"
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# # the remaining files not specified by a matcher (the
|
104
|
+
# # HTML files) are simply copied over.
|
105
|
+
#
|
106
|
+
# # you can also specify filters here that will apply to
|
107
|
+
# # all processed files (application.js and application.css)
|
108
|
+
# # up until this point, as well as the HTML files.
|
109
|
+
# end
|
110
|
+
class Pipeline
|
111
|
+
class Error < StandardError ; end
|
112
|
+
class TmpInputError < Error
|
113
|
+
def initialize(file)
|
114
|
+
@file = file
|
115
|
+
end
|
116
|
+
|
117
|
+
def to_s
|
118
|
+
"Temporary files cannot be input! #{@file} is inside a pipeline's tmp directory"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# @return [Hash[String, String]] the directory paths for the input files
|
123
|
+
# and their matching globs.
|
124
|
+
attr_accessor :inputs
|
125
|
+
|
126
|
+
# @return [String] the directory path for the output files.
|
127
|
+
attr_reader :output_root
|
128
|
+
|
129
|
+
# @return [String] the directory path for temporary files
|
130
|
+
attr_accessor :tmpdir
|
131
|
+
|
132
|
+
# @return [Array] an Array of Rake::Task objects. This
|
133
|
+
# property is populated by the #generate_rake_tasks
|
134
|
+
# method.
|
135
|
+
attr_reader :rake_tasks
|
136
|
+
|
137
|
+
# @return [String] a list of files that will be outputted
|
138
|
+
# to the output directory when the pipeline is invoked
|
139
|
+
attr_reader :output_files
|
140
|
+
|
141
|
+
# @return [Array] this pipeline's filters.
|
142
|
+
attr_reader :filters
|
143
|
+
|
144
|
+
attr_writer :input_files
|
145
|
+
|
146
|
+
# @return [Project] the Project that created this pipeline
|
147
|
+
attr_accessor :project
|
148
|
+
|
149
|
+
# @param [Hash] options
|
150
|
+
# @option options [Hash] :inputs
|
151
|
+
# set the pipeline's {#inputs}.
|
152
|
+
# @option options [String] :tmpdir
|
153
|
+
# set the pipeline's {#tmpdir}.
|
154
|
+
# @option options [String] :output_root
|
155
|
+
# set the pipeline's {#output_root}.
|
156
|
+
# @option options [Rake::Application] :rake_application
|
157
|
+
# set the pipeline's {#rake_application}.
|
158
|
+
def initialize(options={})
|
159
|
+
@filters = []
|
160
|
+
@invoke_mutex = Mutex.new
|
161
|
+
@clean_mutex = Mutex.new
|
162
|
+
@tmp_id = 0
|
163
|
+
@inputs = options[:inputs] || {}
|
164
|
+
@tmpdir = options[:tmpdir] || File.expand_path("tmp")
|
165
|
+
@project = options[:project]
|
166
|
+
|
167
|
+
if options[:output_root]
|
168
|
+
self.output_root = options[:output_root]
|
169
|
+
end
|
170
|
+
|
171
|
+
if options[:rake_application]
|
172
|
+
self.rake_application = options[:rake_application]
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Build a new pipeline taking a block. The block will
|
177
|
+
# be evaluated by the Rake::Pipeline::DSL class.
|
178
|
+
#
|
179
|
+
# @see Rake::Pipeline::Filter Rake::Pipeline::Filter
|
180
|
+
#
|
181
|
+
# @example
|
182
|
+
# Rake::Pipeline.build do
|
183
|
+
# input "app/assets"
|
184
|
+
# output "public"
|
185
|
+
#
|
186
|
+
# concat "app.js"
|
187
|
+
# end
|
188
|
+
#
|
189
|
+
# @see DSL the Rake::Pipeline::DSL documentation.
|
190
|
+
# All instance methods of DSL are available inside
|
191
|
+
# the build block.
|
192
|
+
#
|
193
|
+
# @return [Rake::Pipeline] the newly configured pipeline
|
194
|
+
def self.build(options={}, &block)
|
195
|
+
pipeline = new(options)
|
196
|
+
pipeline.build(options, &block)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Evaluate a block using the Rake::Pipeline DSL against an
|
200
|
+
# existing pipeline.
|
201
|
+
#
|
202
|
+
# @see Rake::Pipeline.build
|
203
|
+
#
|
204
|
+
# @return [Rake::Pipeline] this pipeline with any modifications
|
205
|
+
# made by the given block.
|
206
|
+
def build(options={}, &block)
|
207
|
+
DSL::PipelineDSL.evaluate(self, options, &block) if block
|
208
|
+
self
|
209
|
+
end
|
210
|
+
|
211
|
+
# Copy the current pipeline's attributes over.
|
212
|
+
#
|
213
|
+
# @param [Class] target_class the class to create a new
|
214
|
+
# instance of. Defaults to the class of the current
|
215
|
+
# pipeline. Is overridden in {Matcher}
|
216
|
+
# @param [Proc] block a block to pass to the {DSL DSL}
|
217
|
+
# @return [Pipeline] the new pipeline
|
218
|
+
# @api private
|
219
|
+
def copy(target_class=self.class, &block)
|
220
|
+
pipeline = target_class.new
|
221
|
+
pipeline.inputs = inputs
|
222
|
+
pipeline.tmpdir = tmpdir
|
223
|
+
pipeline.rake_application = rake_application
|
224
|
+
pipeline.project = project
|
225
|
+
pipeline.build &block
|
226
|
+
pipeline
|
227
|
+
end
|
228
|
+
|
229
|
+
# Set the output root of this pipeline and expand its path.
|
230
|
+
#
|
231
|
+
# @param [String] root this pipeline's output root
|
232
|
+
def output_root=(root)
|
233
|
+
@output_root = File.expand_path(root)
|
234
|
+
end
|
235
|
+
|
236
|
+
# Set the temporary directory for this pipeline and expand its path.
|
237
|
+
#
|
238
|
+
# @param [String] root this pipeline's temporary directory
|
239
|
+
def tmpdir=(dir)
|
240
|
+
@tmpdir = File.expand_path(dir)
|
241
|
+
end
|
242
|
+
|
243
|
+
# Add an input directory, optionally filtering which files within
|
244
|
+
# the input directory are included.
|
245
|
+
#
|
246
|
+
# @param [String] root the input root directory; required
|
247
|
+
# @param [String] pattern a pattern to match within +root+;
|
248
|
+
# optional; defaults to "**/*"
|
249
|
+
def add_input(root, pattern = nil)
|
250
|
+
pattern ||= "**/*"
|
251
|
+
@inputs[root] = pattern
|
252
|
+
end
|
253
|
+
|
254
|
+
# If you specify #inputs, this method will
|
255
|
+
# calculate the input files for the directory. If you supply
|
256
|
+
# input_files directly, this method will simply return the
|
257
|
+
# input_files you supplied.
|
258
|
+
#
|
259
|
+
# @return [Array<FileWrapper>] An Array of file wrappers
|
260
|
+
# that represent the inputs for the current pipeline.
|
261
|
+
def input_files
|
262
|
+
return @input_files if @input_files
|
263
|
+
|
264
|
+
assert_input_provided
|
265
|
+
|
266
|
+
result = []
|
267
|
+
|
268
|
+
@inputs.each do |root, glob|
|
269
|
+
expanded_root = File.expand_path(root)
|
270
|
+
files = Dir[File.join(expanded_root, glob)].sort.select { |f| File.file?(f) }
|
271
|
+
|
272
|
+
files.each do |file|
|
273
|
+
relative_path = file.sub(%r{^#{Regexp.escape(expanded_root)}/}, '')
|
274
|
+
wrapped_file = FileWrapper.new(expanded_root, relative_path)
|
275
|
+
|
276
|
+
raise TmpInputError, file if wrapped_file.in_directory?(tmpdir)
|
277
|
+
|
278
|
+
result << wrapped_file
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
result.sort
|
283
|
+
end
|
284
|
+
|
285
|
+
# for Pipelines, this is every file, but it may be overridden
|
286
|
+
# by subclasses
|
287
|
+
alias eligible_input_files input_files
|
288
|
+
|
289
|
+
# @return [Rake::Application] The Rake::Application to install
|
290
|
+
# rake tasks onto. Defaults to Rake.application
|
291
|
+
def rake_application
|
292
|
+
@rake_application || Rake.application
|
293
|
+
end
|
294
|
+
|
295
|
+
# Set the rake_application on the pipeline and apply it to filters.
|
296
|
+
#
|
297
|
+
# @return [void]
|
298
|
+
def rake_application=(rake_application)
|
299
|
+
@rake_application = rake_application
|
300
|
+
@filters.each { |filter| filter.rake_application = rake_application }
|
301
|
+
@rake_tasks = nil
|
302
|
+
end
|
303
|
+
|
304
|
+
# Add one or more filters to the current pipeline.
|
305
|
+
#
|
306
|
+
# @param [Array<Filter>] filters a list of filters
|
307
|
+
# @return [void]
|
308
|
+
def add_filters(*filters)
|
309
|
+
filters.each do |filter|
|
310
|
+
filter.rake_application = rake_application
|
311
|
+
filter.pipeline = self
|
312
|
+
end
|
313
|
+
@filters.concat(filters)
|
314
|
+
end
|
315
|
+
alias add_filter add_filters
|
316
|
+
|
317
|
+
# Invoke the pipeline, processing the inputs into the output.
|
318
|
+
#
|
319
|
+
# @return [void]
|
320
|
+
def invoke
|
321
|
+
@invoke_mutex.synchronize do
|
322
|
+
@tmp_id = 0
|
323
|
+
|
324
|
+
self.rake_application = Rake::Application.new
|
325
|
+
|
326
|
+
setup
|
327
|
+
|
328
|
+
@rake_tasks.each { |task| task.invoke }
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
# Set up the filters and generate rake tasks. In general, this method
|
333
|
+
# is called by invoke.
|
334
|
+
#
|
335
|
+
# @return [void]
|
336
|
+
# @api private
|
337
|
+
def setup
|
338
|
+
setup_filters
|
339
|
+
generate_rake_tasks
|
340
|
+
record_input_files
|
341
|
+
end
|
342
|
+
|
343
|
+
# Set up the filters. This will loop through all of the filters for
|
344
|
+
# the current pipeline and wire up their input_files and output_files.
|
345
|
+
#
|
346
|
+
# Because matchers implement the filter API, matchers will also be
|
347
|
+
# set up as part of this process.
|
348
|
+
#
|
349
|
+
# @return [void]
|
350
|
+
# @api private
|
351
|
+
def setup_filters
|
352
|
+
last = @filters.last
|
353
|
+
|
354
|
+
@filters.inject(eligible_input_files) do |current_inputs, filter|
|
355
|
+
filter.input_files = current_inputs
|
356
|
+
|
357
|
+
# if filters are being reinvoked, they should keep their roots but
|
358
|
+
# get updated with new files.
|
359
|
+
filter.output_root ||= begin
|
360
|
+
output = if filter == last
|
361
|
+
output_root
|
362
|
+
else
|
363
|
+
generate_tmpdir
|
364
|
+
end
|
365
|
+
|
366
|
+
File.expand_path(output)
|
367
|
+
end
|
368
|
+
|
369
|
+
filter.setup_filters if filter.respond_to?(:setup_filters)
|
370
|
+
|
371
|
+
filter.output_files
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
# A list of the output files that invoking this pipeline will
|
376
|
+
# generate.
|
377
|
+
#
|
378
|
+
# @return [Array<FileWrapper>]
|
379
|
+
def output_files
|
380
|
+
@filters.last.output_files unless @filters.empty?
|
381
|
+
end
|
382
|
+
|
383
|
+
# Add a final filter to the pipeline that will copy the
|
384
|
+
# pipeline's generated files to the output.
|
385
|
+
#
|
386
|
+
# @return [void]
|
387
|
+
# @api private
|
388
|
+
def finalize
|
389
|
+
add_filter(Rake::Pipeline::PipelineFinalizingFilter.new)
|
390
|
+
end
|
391
|
+
|
392
|
+
# A unique fingerprint. It's used to generate unique temporary
|
393
|
+
# directory names. It must be unique to the pipeline. It must be
|
394
|
+
# the same across processes.
|
395
|
+
#
|
396
|
+
# @return [String]
|
397
|
+
# @api private
|
398
|
+
def fingerprint
|
399
|
+
if project
|
400
|
+
project.pipelines.index self
|
401
|
+
else
|
402
|
+
1
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
# the Manifest used in this pipeline
|
407
|
+
def manifest
|
408
|
+
project.manifest
|
409
|
+
end
|
410
|
+
|
411
|
+
# the Manifest used in this pipeline
|
412
|
+
def last_manifest
|
413
|
+
project.last_manifest
|
414
|
+
end
|
415
|
+
|
416
|
+
protected
|
417
|
+
# Generate a new temporary directory name.
|
418
|
+
#
|
419
|
+
# @return [String] a unique temporary directory name
|
420
|
+
def generate_tmpname
|
421
|
+
"rake-pipeline-#{fingerprint}-tmp-#{@tmp_id += 1}"
|
422
|
+
end
|
423
|
+
|
424
|
+
# Generate a new temporary directory name under the main tmpdir.
|
425
|
+
#
|
426
|
+
# @return [void]
|
427
|
+
def generate_tmpdir
|
428
|
+
File.join(tmpdir, self.generate_tmpname)
|
429
|
+
end
|
430
|
+
|
431
|
+
# Generate all of the rake tasks for this pipeline.
|
432
|
+
#
|
433
|
+
# @return [void]
|
434
|
+
def generate_rake_tasks
|
435
|
+
@rake_tasks = filters.collect { |f| f.generate_rake_tasks }.flatten
|
436
|
+
end
|
437
|
+
|
438
|
+
# Assert that an input root and glob were both provided.
|
439
|
+
#
|
440
|
+
# @raise Rake::Pipeline::Error if input root or glob were missing.
|
441
|
+
# @return [void]
|
442
|
+
def assert_input_provided
|
443
|
+
if inputs.empty?
|
444
|
+
raise Rake::Pipeline::Error, "You cannot get input files without " \
|
445
|
+
"first providing input files and an input root"
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
# This is needed to ensure that every file procesed in the pipeline
|
450
|
+
# has an entry in the manifest. This is used to compare input files
|
451
|
+
# for the global dirty check
|
452
|
+
def record_input_files
|
453
|
+
input_files.each do |file|
|
454
|
+
full_path = file.fullpath
|
455
|
+
|
456
|
+
if File.exists?(full_path) && !manifest[full_path]
|
457
|
+
manifest[full_path] ||= ManifestEntry.new({}, File.mtime(full_path).to_i)
|
458
|
+
end
|
459
|
+
end
|
460
|
+
end
|
461
|
+
end
|
462
|
+
end
|