rake-pipeline-fork 0.8.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.
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