rake-pipeline-fork 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +12 -0
  5. data/.yardopts +2 -0
  6. data/GETTING_STARTED.md +268 -0
  7. data/Gemfile +14 -0
  8. data/LICENSE +20 -0
  9. data/README.markdown +11 -0
  10. data/README.yard +178 -0
  11. data/Rakefile +21 -0
  12. data/bin/rakep +4 -0
  13. data/examples/copying_files.md +12 -0
  14. data/examples/minifying_files.md +37 -0
  15. data/examples/modifying_pipelines.md +67 -0
  16. data/examples/multiple_pipelines.md +77 -0
  17. data/lib/generators/rake/pipeline/install/install_generator.rb +70 -0
  18. data/lib/rake-pipeline.rb +462 -0
  19. data/lib/rake-pipeline/cli.rb +56 -0
  20. data/lib/rake-pipeline/dsl.rb +9 -0
  21. data/lib/rake-pipeline/dsl/pipeline_dsl.rb +246 -0
  22. data/lib/rake-pipeline/dsl/project_dsl.rb +108 -0
  23. data/lib/rake-pipeline/dynamic_file_task.rb +194 -0
  24. data/lib/rake-pipeline/error.rb +17 -0
  25. data/lib/rake-pipeline/file_wrapper.rb +182 -0
  26. data/lib/rake-pipeline/filter.rb +249 -0
  27. data/lib/rake-pipeline/filters.rb +4 -0
  28. data/lib/rake-pipeline/filters/concat_filter.rb +63 -0
  29. data/lib/rake-pipeline/filters/gsub_filter.rb +56 -0
  30. data/lib/rake-pipeline/filters/ordering_concat_filter.rb +38 -0
  31. data/lib/rake-pipeline/filters/pipeline_finalizing_filter.rb +21 -0
  32. data/lib/rake-pipeline/graph.rb +178 -0
  33. data/lib/rake-pipeline/manifest.rb +86 -0
  34. data/lib/rake-pipeline/manifest_entry.rb +34 -0
  35. data/lib/rake-pipeline/matcher.rb +141 -0
  36. data/lib/rake-pipeline/middleware.rb +72 -0
  37. data/lib/rake-pipeline/precompile.rake +8 -0
  38. data/lib/rake-pipeline/project.rb +335 -0
  39. data/lib/rake-pipeline/rails_plugin.rb +10 -0
  40. data/lib/rake-pipeline/railtie.rb +34 -0
  41. data/lib/rake-pipeline/reject_matcher.rb +29 -0
  42. data/lib/rake-pipeline/server.rb +15 -0
  43. data/lib/rake-pipeline/sorted_pipeline.rb +19 -0
  44. data/lib/rake-pipeline/version.rb +6 -0
  45. data/rails/init.rb +2 -0
  46. data/rake-pipeline.gemspec +24 -0
  47. data/spec/cli_spec.rb +71 -0
  48. data/spec/concat_filter_spec.rb +37 -0
  49. data/spec/dsl/pipeline_dsl_spec.rb +165 -0
  50. data/spec/dsl/project_dsl_spec.rb +41 -0
  51. data/spec/dynamic_file_task_spec.rb +119 -0
  52. data/spec/encoding_spec.rb +106 -0
  53. data/spec/file_wrapper_spec.rb +132 -0
  54. data/spec/filter_spec.rb +332 -0
  55. data/spec/graph_spec.rb +56 -0
  56. data/spec/gsub_filter_spec.rb +87 -0
  57. data/spec/manifest_entry_spec.rb +46 -0
  58. data/spec/manifest_spec.rb +67 -0
  59. data/spec/matcher_spec.rb +141 -0
  60. data/spec/middleware_spec.rb +199 -0
  61. data/spec/ordering_concat_filter_spec.rb +42 -0
  62. data/spec/pipeline_spec.rb +232 -0
  63. data/spec/project_spec.rb +295 -0
  64. data/spec/rake_acceptance_spec.rb +738 -0
  65. data/spec/rake_tasks_spec.rb +21 -0
  66. data/spec/reject_matcher_spec.rb +31 -0
  67. data/spec/sorted_pipeline_spec.rb +27 -0
  68. data/spec/spec_helper.rb +38 -0
  69. data/spec/support/spec_helpers/file_utils.rb +35 -0
  70. data/spec/support/spec_helpers/filters.rb +37 -0
  71. data/spec/support/spec_helpers/input_helpers.rb +23 -0
  72. data/spec/support/spec_helpers/memory_file_wrapper.rb +31 -0
  73. data/spec/support/spec_helpers/memory_manifest.rb +19 -0
  74. data/tools/perfs +101 -0
  75. metadata +215 -0
@@ -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
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rake-pipeline"
4
+ Rake::Pipeline::CLI.start
@@ -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