benoit 0.1.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 (164) hide show
  1. data/.gitignore +4 -0
  2. data/.gitmodules +3 -0
  3. data/.rspec +1 -0
  4. data/.rspec-turnip +1 -0
  5. data/.ruby-version +1 -0
  6. data/Assetfile +44 -0
  7. data/Gemfile +5 -0
  8. data/Gemfile.lock +110 -0
  9. data/Rakefile +9 -0
  10. data/benoit.gemspec +50 -0
  11. data/bin/benoit +121 -0
  12. data/bin/bundle-development +1 -0
  13. data/bin/bundle-sandbox +1 -0
  14. data/lib/benoit.rb +36 -0
  15. data/lib/benoit/cadenza.rb +11 -0
  16. data/lib/benoit/cadenza/output_filters.rb +21 -0
  17. data/lib/benoit/cleaner.rb +10 -0
  18. data/lib/benoit/compiler_error.rb +49 -0
  19. data/lib/benoit/configuration.rb +24 -0
  20. data/lib/benoit/current_site.rb +12 -0
  21. data/lib/benoit/file_wrapper_extensions.rb +13 -0
  22. data/lib/benoit/filters.rb +15 -0
  23. data/lib/benoit/filters/base_filter.rb +44 -0
  24. data/lib/benoit/filters/blacklist_filter.rb +18 -0
  25. data/lib/benoit/filters/cadenza_filter.rb +75 -0
  26. data/lib/benoit/filters/content_page_filter.rb +45 -0
  27. data/lib/benoit/filters/markdown_filter.rb +19 -0
  28. data/lib/benoit/filters/metadata_cleaner.rb +30 -0
  29. data/lib/benoit/filters/move_to_root_filter.rb +22 -0
  30. data/lib/benoit/filters/pagination_filter.rb +23 -0
  31. data/lib/benoit/filters/pass_thru_filter.rb +22 -0
  32. data/lib/benoit/filters/sass_filter.rb +74 -0
  33. data/lib/benoit/filters/set_metadata_filter.rb +15 -0
  34. data/lib/benoit/logger.rb +69 -0
  35. data/lib/benoit/page.rb +88 -0
  36. data/lib/benoit/page_metadata.rb +8 -0
  37. data/lib/benoit/page_metadata/container.rb +74 -0
  38. data/lib/benoit/page_metadata/json_converter.rb +28 -0
  39. data/lib/benoit/page_metadata/parser.rb +38 -0
  40. data/lib/benoit/page_metadata/store.rb +75 -0
  41. data/lib/benoit/pipeline.rb +1 -0
  42. data/lib/benoit/pipeline/dsl_extensions.rb +8 -0
  43. data/lib/benoit/pipeline/pagination_matcher.rb +53 -0
  44. data/lib/benoit/pipeline_project.rb +81 -0
  45. data/lib/benoit/site_context.rb +123 -0
  46. data/lib/benoit/utils/finds_layouts_for_template.rb +77 -0
  47. data/lib/benoit/utils/normalizes_path_to_template.rb +43 -0
  48. data/lib/benoit/utils/paginated_list.rb +102 -0
  49. data/lib/benoit/version.rb +3 -0
  50. data/lib/build_notifiers/file_built_notifier.rb +20 -0
  51. data/lib/build_notifiers/progress_notifier.rb +57 -0
  52. data/lib/cli.rb +39 -0
  53. data/spec/.rbenv-gemsets +1 -0
  54. data/spec/Gemfile +9 -0
  55. data/spec/Gemfile.lock +57 -0
  56. data/spec/bin/autospec +16 -0
  57. data/spec/bin/cucumber +16 -0
  58. data/spec/bin/htmldiff +16 -0
  59. data/spec/bin/ldiff +16 -0
  60. data/spec/bin/rspec +16 -0
  61. data/spec/features/build_command.feature +46 -0
  62. data/spec/features/frontmatter_metadata.feature +99 -0
  63. data/spec/features/javascript_files.feature +35 -0
  64. data/spec/features/output_filters.feature +20 -0
  65. data/spec/features/page_layouts.feature +72 -0
  66. data/spec/features/pagination.feature +58 -0
  67. data/spec/features/sass_files.feature +30 -0
  68. data/spec/features/version.feature +5 -0
  69. data/spec/lib/filters/base_filter_spec.rb +141 -0
  70. data/spec/lib/filters/markdown_filter_spec.rb +65 -0
  71. data/spec/lib/filters/sass_filter_spec.rb +73 -0
  72. data/spec/lib/metadata_json_converter_spec.rb +65 -0
  73. data/spec/lib/metadata_store_spec.rb +148 -0
  74. data/spec/lib/page_spec.rb +19 -0
  75. data/spec/lib/site_context_spec.rb +106 -0
  76. data/spec/spec_helper.rb +16 -0
  77. data/spec/steps/output_file_steps.rb +45 -0
  78. data/spec/steps/run_steps.rb +71 -0
  79. data/spec/steps/staticly_steps.rb +194 -0
  80. data/spec/support/aruba/rspec.rb +66 -0
  81. data/spec/support/files/img.png +0 -0
  82. data/spec/support/files/input.css.scss +7 -0
  83. data/spec/support/files/input.scss +7 -0
  84. data/spec/support/spec_helpers/file_helpers.rb +8 -0
  85. data/spec/support/spec_helpers/memory_file_wrapper.rb +43 -0
  86. data/spec/support/spec_helpers/memory_manifest.rb +19 -0
  87. data/spec/turnip_helper.rb +19 -0
  88. data/vendor/frontmatter/frontmatter.gemspec +24 -0
  89. data/vendor/frontmatter/lib/frontmatter.rb +92 -0
  90. data/vendor/frontmatter/lib/frontmatter/version.rb +3 -0
  91. data/vendor/rake-pipeline/.gitignore +18 -0
  92. data/vendor/rake-pipeline/.rspec +1 -0
  93. data/vendor/rake-pipeline/.travis.yml +12 -0
  94. data/vendor/rake-pipeline/.yardopts +2 -0
  95. data/vendor/rake-pipeline/GETTING_STARTED.md +268 -0
  96. data/vendor/rake-pipeline/Gemfile +14 -0
  97. data/vendor/rake-pipeline/LICENSE +20 -0
  98. data/vendor/rake-pipeline/README.markdown +11 -0
  99. data/vendor/rake-pipeline/README.yard +178 -0
  100. data/vendor/rake-pipeline/Rakefile +21 -0
  101. data/vendor/rake-pipeline/bin/rakep +4 -0
  102. data/vendor/rake-pipeline/examples/copying_files.md +12 -0
  103. data/vendor/rake-pipeline/examples/minifying_files.md +37 -0
  104. data/vendor/rake-pipeline/examples/modifying_pipelines.md +67 -0
  105. data/vendor/rake-pipeline/examples/multiple_pipelines.md +77 -0
  106. data/vendor/rake-pipeline/lib/generators/rake/pipeline/install/install_generator.rb +70 -0
  107. data/vendor/rake-pipeline/lib/rake-pipeline.rb +509 -0
  108. data/vendor/rake-pipeline/lib/rake-pipeline/cli.rb +57 -0
  109. data/vendor/rake-pipeline/lib/rake-pipeline/dsl.rb +9 -0
  110. data/vendor/rake-pipeline/lib/rake-pipeline/dsl/pipeline_dsl.rb +246 -0
  111. data/vendor/rake-pipeline/lib/rake-pipeline/dsl/project_dsl.rb +108 -0
  112. data/vendor/rake-pipeline/lib/rake-pipeline/dynamic_file_task.rb +194 -0
  113. data/vendor/rake-pipeline/lib/rake-pipeline/error.rb +17 -0
  114. data/vendor/rake-pipeline/lib/rake-pipeline/file_wrapper.rb +195 -0
  115. data/vendor/rake-pipeline/lib/rake-pipeline/filter.rb +267 -0
  116. data/vendor/rake-pipeline/lib/rake-pipeline/filters.rb +4 -0
  117. data/vendor/rake-pipeline/lib/rake-pipeline/filters/concat_filter.rb +63 -0
  118. data/vendor/rake-pipeline/lib/rake-pipeline/filters/gsub_filter.rb +56 -0
  119. data/vendor/rake-pipeline/lib/rake-pipeline/filters/ordering_concat_filter.rb +38 -0
  120. data/vendor/rake-pipeline/lib/rake-pipeline/filters/pipeline_finalizing_filter.rb +21 -0
  121. data/vendor/rake-pipeline/lib/rake-pipeline/graph.rb +178 -0
  122. data/vendor/rake-pipeline/lib/rake-pipeline/manifest.rb +82 -0
  123. data/vendor/rake-pipeline/lib/rake-pipeline/manifest_entry.rb +34 -0
  124. data/vendor/rake-pipeline/lib/rake-pipeline/matcher.rb +141 -0
  125. data/vendor/rake-pipeline/lib/rake-pipeline/middleware.rb +73 -0
  126. data/vendor/rake-pipeline/lib/rake-pipeline/precompile.rake +8 -0
  127. data/vendor/rake-pipeline/lib/rake-pipeline/project.rb +338 -0
  128. data/vendor/rake-pipeline/lib/rake-pipeline/rails_plugin.rb +10 -0
  129. data/vendor/rake-pipeline/lib/rake-pipeline/railtie.rb +34 -0
  130. data/vendor/rake-pipeline/lib/rake-pipeline/reject_matcher.rb +29 -0
  131. data/vendor/rake-pipeline/lib/rake-pipeline/server.rb +15 -0
  132. data/vendor/rake-pipeline/lib/rake-pipeline/sorted_pipeline.rb +19 -0
  133. data/vendor/rake-pipeline/lib/rake-pipeline/version.rb +6 -0
  134. data/vendor/rake-pipeline/rails/init.rb +2 -0
  135. data/vendor/rake-pipeline/rake-pipeline.gemspec +24 -0
  136. data/vendor/rake-pipeline/spec/cli_spec.rb +73 -0
  137. data/vendor/rake-pipeline/spec/concat_filter_spec.rb +37 -0
  138. data/vendor/rake-pipeline/spec/dsl/pipeline_dsl_spec.rb +165 -0
  139. data/vendor/rake-pipeline/spec/dsl/project_dsl_spec.rb +41 -0
  140. data/vendor/rake-pipeline/spec/dynamic_file_task_spec.rb +119 -0
  141. data/vendor/rake-pipeline/spec/encoding_spec.rb +106 -0
  142. data/vendor/rake-pipeline/spec/file_wrapper_spec.rb +132 -0
  143. data/vendor/rake-pipeline/spec/filter_spec.rb +367 -0
  144. data/vendor/rake-pipeline/spec/graph_spec.rb +56 -0
  145. data/vendor/rake-pipeline/spec/gsub_filter_spec.rb +87 -0
  146. data/vendor/rake-pipeline/spec/manifest_entry_spec.rb +46 -0
  147. data/vendor/rake-pipeline/spec/manifest_spec.rb +67 -0
  148. data/vendor/rake-pipeline/spec/matcher_spec.rb +141 -0
  149. data/vendor/rake-pipeline/spec/middleware_spec.rb +199 -0
  150. data/vendor/rake-pipeline/spec/ordering_concat_filter_spec.rb +42 -0
  151. data/vendor/rake-pipeline/spec/pipeline_spec.rb +232 -0
  152. data/vendor/rake-pipeline/spec/project_spec.rb +295 -0
  153. data/vendor/rake-pipeline/spec/rake_acceptance_spec.rb +720 -0
  154. data/vendor/rake-pipeline/spec/rake_tasks_spec.rb +21 -0
  155. data/vendor/rake-pipeline/spec/reject_matcher_spec.rb +31 -0
  156. data/vendor/rake-pipeline/spec/sorted_pipeline_spec.rb +27 -0
  157. data/vendor/rake-pipeline/spec/spec_helper.rb +38 -0
  158. data/vendor/rake-pipeline/spec/support/spec_helpers/file_utils.rb +35 -0
  159. data/vendor/rake-pipeline/spec/support/spec_helpers/filters.rb +37 -0
  160. data/vendor/rake-pipeline/spec/support/spec_helpers/input_helpers.rb +23 -0
  161. data/vendor/rake-pipeline/spec/support/spec_helpers/memory_file_wrapper.rb +35 -0
  162. data/vendor/rake-pipeline/spec/support/spec_helpers/memory_manifest.rb +19 -0
  163. data/vendor/rake-pipeline/tools/perfs +101 -0
  164. metadata +497 -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/cli"
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,509 @@
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/graph"
14
+
15
+ if defined?(Rails::Railtie)
16
+ require "rake-pipeline/railtie"
17
+ elsif defined?(Rails)
18
+ require "rake-pipeline/rails_plugin"
19
+ end
20
+
21
+ require "thread"
22
+
23
+ # Use the Rake namespace
24
+ module Rake
25
+ # Override Rake::Task to support recursively re-enabling
26
+ # a task and its dependencies.
27
+ class Task
28
+
29
+ # @param [Rake::Application] app a Rake Application
30
+ # @return [void]
31
+ def recursively_reenable(app)
32
+ reenable
33
+
34
+ prerequisites.each do |dep|
35
+ app[dep].recursively_reenable(app)
36
+ end
37
+ end
38
+ end
39
+
40
+ # Override Rake::FileTask to make it sortable
41
+ class FileTask
42
+ # implement Ruby protocol for sorting
43
+ #
44
+ # @return [Fixnum]
45
+ def <=>(other)
46
+ [name, prerequisites] <=> [other.name, other.prerequisites]
47
+ end
48
+ end
49
+
50
+ # A Pipeline is responsible for taking a directory of input
51
+ # files, applying a number of filters to the inputs, and
52
+ # outputting them into an output directory.
53
+ #
54
+ # The normal way to build and configure a pipeline is by
55
+ # using {.build}. Inside the block passed to {.build}, all
56
+ # methods of {DSL} are available.
57
+ #
58
+ # @see DSL Rake::Pipeline::DSL for information on the methods
59
+ # available inside the block.
60
+ #
61
+ # @example
62
+ # !!!ruby
63
+ # Rake::Pipeline.build do
64
+ # # process all js, css and html files in app/assets
65
+ # input "app/assets", "**/*.{js,coffee,css,scss,html}"
66
+ #
67
+ # # processed files should be outputted to public
68
+ # output "public"
69
+ #
70
+ # # process all coffee files
71
+ # match "*.coffee" do
72
+ # # compile all CoffeeScript files. the output file
73
+ # # for the compilation should be the input name
74
+ # # with the .coffee extension replaced with .js
75
+ # filter(CoffeeCompiler) do |input|
76
+ # input.sub(/\.coffee$/, '.js')
77
+ # end
78
+ # end
79
+ #
80
+ # # specify filters for js files. this includes the
81
+ # # output of the previous step, which converted
82
+ # # coffee files to js files
83
+ # match "*.js" do
84
+ # # first, wrap all JS files in a custom filter
85
+ # filter ClosureFilter
86
+ # # then, concatenate all JS files into a single file
87
+ # concat "application.js"
88
+ # end
89
+ #
90
+ # # specify filters for css and scss files
91
+ # match "*.{css,scss}" do
92
+ # # compile CSS and SCSS files using the SCSS
93
+ # # compiler. if an input file has the extension
94
+ # # scss, replace it with css
95
+ # filter(ScssCompiler) do |input|
96
+ # input.sub(/\.scss$/, 'css')
97
+ # end
98
+ # # then, concatenate all CSS files into a single file
99
+ # concat "application.css"
100
+ # end
101
+ #
102
+ # # the remaining files not specified by a matcher (the
103
+ # # HTML files) are simply copied over.
104
+ #
105
+ # # you can also specify filters here that will apply to
106
+ # # all processed files (application.js and application.css)
107
+ # # up until this point, as well as the HTML files.
108
+ # end
109
+ class Pipeline
110
+ class Error < StandardError ; end
111
+ class TmpInputError < Error
112
+ def initialize(file)
113
+ @file = file
114
+ end
115
+
116
+ def to_s
117
+ "Temporary files cannot be input! #{@file} is inside a pipeline's tmp directory"
118
+ end
119
+ end
120
+
121
+ ALLOWED_HOOKS = [:before_pipeline, :before_filter, :before_task, :after_task, :after_filter, :after_pipeline, :filters_ready, :rake_tasks_ready]
122
+
123
+ # @return [Hash[String, String]] the directory paths for the input files
124
+ # and their matching globs.
125
+ attr_accessor :inputs
126
+
127
+ # @return [String] the directory path for the output files.
128
+ attr_reader :output_root
129
+
130
+ # @return [String] the directory path for temporary files
131
+ attr_accessor :tmpdir
132
+
133
+ # @return [Array] an Array of Rake::Task objects. This
134
+ # property is populated by the #generate_rake_tasks
135
+ # method.
136
+ attr_reader :rake_tasks
137
+
138
+ # @return [String] a list of files that will be outputted
139
+ # to the output directory when the pipeline is invoked
140
+ attr_reader :output_files
141
+
142
+ # @return [Array] this pipeline's filters.
143
+ attr_reader :filters
144
+
145
+ attr_writer :input_files
146
+
147
+ # @return [Project] the Project that created this pipeline
148
+ attr_accessor :project
149
+
150
+ attr_accessor :before_invoke
151
+ attr_accessor :after_invoke
152
+
153
+ # @param [Hash] options
154
+ # @option options [Hash] :inputs
155
+ # set the pipeline's {#inputs}.
156
+ # @option options [String] :tmpdir
157
+ # set the pipeline's {#tmpdir}.
158
+ # @option options [String] :output_root
159
+ # set the pipeline's {#output_root}.
160
+ # @option options [Rake::Application] :rake_application
161
+ # set the pipeline's {#rake_application}.
162
+ def initialize(options={})
163
+ @filters = []
164
+ @invoke_mutex = Mutex.new
165
+ @clean_mutex = Mutex.new
166
+ @tmp_id = 0
167
+ @inputs = options[:inputs] || {}
168
+ @tmpdir = options[:tmpdir] || File.expand_path("tmp")
169
+ @project = options[:project]
170
+
171
+ if options[:output_root]
172
+ self.output_root = options[:output_root]
173
+ end
174
+
175
+ if options[:rake_application]
176
+ self.rake_application = options[:rake_application]
177
+ end
178
+ end
179
+
180
+ # Build a new pipeline taking a block. The block will
181
+ # be evaluated by the Rake::Pipeline::DSL class.
182
+ #
183
+ # @see Rake::Pipeline::Filter Rake::Pipeline::Filter
184
+ #
185
+ # @example
186
+ # Rake::Pipeline.build do
187
+ # input "app/assets"
188
+ # output "public"
189
+ #
190
+ # concat "app.js"
191
+ # end
192
+ #
193
+ # @see DSL the Rake::Pipeline::DSL documentation.
194
+ # All instance methods of DSL are available inside
195
+ # the build block.
196
+ #
197
+ # @return [Rake::Pipeline] the newly configured pipeline
198
+ def self.build(options={}, &block)
199
+ pipeline = new(options)
200
+ pipeline.build(options, &block)
201
+ end
202
+
203
+ # Evaluate a block using the Rake::Pipeline DSL against an
204
+ # existing pipeline.
205
+ #
206
+ # @see Rake::Pipeline.build
207
+ #
208
+ # @return [Rake::Pipeline] this pipeline with any modifications
209
+ # made by the given block.
210
+ def build(options={}, &block)
211
+ DSL::PipelineDSL.evaluate(self, options, &block) if block
212
+ self
213
+ end
214
+
215
+ def call_hook(hook, *args)
216
+ if self.hooks[hook].respond_to? :call
217
+ args = [self].concat(args)
218
+ self.hooks[hook].call(*args)
219
+ end
220
+ end
221
+
222
+ def register_invocation_hook(name, hook=nil, &block)
223
+ return unless ALLOWED_HOOKS.include? name
224
+ self.hooks ||= {}
225
+ self.hooks[name] = hook || block
226
+ end
227
+
228
+ def hooks
229
+ @hooks ||= {}
230
+ end
231
+
232
+ # Copy the current pipeline's attributes over.
233
+ #
234
+ # @param [Class] target_class the class to create a new
235
+ # instance of. Defaults to the class of the current
236
+ # pipeline. Is overridden in {Matcher}
237
+ # @param [Proc] block a block to pass to the {DSL DSL}
238
+ # @return [Pipeline] the new pipeline
239
+ # @api private
240
+ def copy(target_class=self.class, &block)
241
+ pipeline = target_class.new
242
+ pipeline.inputs = inputs
243
+ pipeline.tmpdir = tmpdir
244
+ pipeline.rake_application = rake_application
245
+ pipeline.project = project
246
+ pipeline.build &block
247
+ pipeline
248
+ end
249
+
250
+ # Set the output root of this pipeline and expand its path.
251
+ #
252
+ # @param [String] root this pipeline's output root
253
+ def output_root=(root)
254
+ @output_root = File.expand_path(root)
255
+ end
256
+
257
+ # Set the temporary directory for this pipeline and expand its path.
258
+ #
259
+ # @param [String] root this pipeline's temporary directory
260
+ def tmpdir=(dir)
261
+ @tmpdir = File.expand_path(dir)
262
+ end
263
+
264
+ # Add an input directory, optionally filtering which files within
265
+ # the input directory are included.
266
+ #
267
+ # @param [String] root the input root directory; required
268
+ # @param [String] pattern a pattern to match within +root+;
269
+ # optional; defaults to "**/*"
270
+ def add_input(root, pattern = nil)
271
+ pattern ||= "**/*"
272
+ @inputs[root] = pattern
273
+ end
274
+
275
+ # If you specify #inputs, this method will
276
+ # calculate the input files for the directory. If you supply
277
+ # input_files directly, this method will simply return the
278
+ # input_files you supplied.
279
+ #
280
+ # @return [Array<FileWrapper>] An Array of file wrappers
281
+ # that represent the inputs for the current pipeline.
282
+ def input_files
283
+ return @input_files if @input_files
284
+
285
+ assert_input_provided
286
+
287
+ result = []
288
+
289
+ @inputs.each do |root, glob|
290
+ expanded_root = File.expand_path(root)
291
+ files = Dir[File.join(expanded_root, glob)].sort.select { |f| File.file?(f) }
292
+
293
+ files.each do |file|
294
+ relative_path = file.sub(%r{^#{Regexp.escape(expanded_root)}/}, '')
295
+ wrapped_file = FileWrapper.new(expanded_root, relative_path)
296
+
297
+ raise TmpInputError, file if wrapped_file.in_directory?(tmpdir)
298
+
299
+ result << wrapped_file
300
+ end
301
+ end
302
+
303
+ result.sort
304
+ end
305
+
306
+ # for Pipelines, this is every file, but it may be overridden
307
+ # by subclasses
308
+ alias eligible_input_files input_files
309
+
310
+ # @return [Rake::Application] The Rake::Application to install
311
+ # rake tasks onto. Defaults to Rake.application
312
+ def rake_application
313
+ @rake_application || Rake.application
314
+ end
315
+
316
+ # Set the rake_application on the pipeline and apply it to filters.
317
+ #
318
+ # @return [void]
319
+ def rake_application=(rake_application)
320
+ @rake_application = rake_application
321
+ @filters.each { |filter| filter.rake_application = rake_application }
322
+ @rake_tasks = nil
323
+ end
324
+
325
+ # Add one or more filters to the current pipeline.
326
+ #
327
+ # @param [Array<Filter>] filters a list of filters
328
+ # @return [void]
329
+ def add_filters(*filters)
330
+ filters.each do |filter|
331
+ filter.rake_application = rake_application
332
+ filter.pipeline = self
333
+ end
334
+ @filters.concat(filters)
335
+ end
336
+ alias add_filter add_filters
337
+
338
+ # Invoke the pipeline, processing the inputs into the output.
339
+ #
340
+ # @return [void]
341
+ def invoke
342
+ @invoke_mutex.synchronize do
343
+ @tmp_id = 0
344
+
345
+ self.rake_application = Rake::Application.new
346
+
347
+ setup
348
+
349
+ self.call_hook(:before_pipeline)
350
+ filters.each do |filter|
351
+ invoke_filter_tasks(filter)
352
+ end
353
+ self.call_hook(:after_pipeline)
354
+ end
355
+ end
356
+
357
+ def invoke_filter_tasks(filter)
358
+ if filter.respond_to? :filters
359
+ filter.filters.each do |filter|
360
+ invoke_tasks(filter)
361
+ end
362
+ else
363
+ invoke_tasks(filter)
364
+ end
365
+ end
366
+
367
+ def invoke_tasks(filter)
368
+ self.call_hook :before_filter, filter
369
+ filter.rake_tasks.each do |task|
370
+ self.call_hook(:before_task, task)
371
+ task.invoke
372
+ self.call_hook(:after_task, task)
373
+ end
374
+ self.call_hook :after_filter, filter
375
+ end
376
+
377
+ # Set up the filters and generate rake tasks. In general, this method
378
+ # is called by invoke.
379
+ #
380
+ # @return [void]
381
+ # @api private
382
+ def setup
383
+ setup_filters
384
+ call_hook :filters_ready
385
+ generate_rake_tasks
386
+ call_hook :rake_tasks_ready
387
+ record_input_files
388
+ end
389
+
390
+ # Set up the filters. This will loop through all of the filters for
391
+ # the current pipeline and wire up their input_files and output_files.
392
+ #
393
+ # Because matchers implement the filter API, matchers will also be
394
+ # set up as part of this process.
395
+ #
396
+ # @return [void]
397
+ # @api private
398
+ def setup_filters
399
+ last = @filters.last
400
+
401
+ @filters.inject(eligible_input_files) do |current_inputs, filter|
402
+ filter.input_files = current_inputs
403
+
404
+ # if filters are being reinvoked, they should keep their roots but
405
+ # get updated with new files.
406
+ filter.output_root ||= begin
407
+ output = if filter == last
408
+ output_root
409
+ else
410
+ generate_tmpdir
411
+ end
412
+
413
+ File.expand_path(output)
414
+ end
415
+
416
+ filter.setup_filters if filter.respond_to?(:setup_filters)
417
+
418
+ filter.output_files
419
+ end
420
+ end
421
+
422
+ # A list of the output files that invoking this pipeline will
423
+ # generate.
424
+ #
425
+ # @return [Array<FileWrapper>]
426
+ def output_files
427
+ @filters.last.output_files unless @filters.empty?
428
+ end
429
+
430
+ # Add a final filter to the pipeline that will copy the
431
+ # pipeline's generated files to the output.
432
+ #
433
+ # @return [void]
434
+ # @api private
435
+ def finalize
436
+ add_filter(Rake::Pipeline::PipelineFinalizingFilter.new)
437
+ end
438
+
439
+ # A unique fingerprint. It's used to generate unique temporary
440
+ # directory names. It must be unique to the pipeline. It must be
441
+ # the same across processes.
442
+ #
443
+ # @return [String]
444
+ # @api private
445
+ def fingerprint
446
+ if project
447
+ project.pipelines.index self
448
+ else
449
+ 1
450
+ end
451
+ end
452
+
453
+ # the Manifest used in this pipeline
454
+ def manifest
455
+ project.manifest
456
+ end
457
+
458
+ # the Manifest used in this pipeline
459
+ def last_manifest
460
+ project.last_manifest
461
+ end
462
+
463
+ protected
464
+ # Generate a new temporary directory name.
465
+ #
466
+ # @return [String] a unique temporary directory name
467
+ def generate_tmpname
468
+ "rake-pipeline-#{fingerprint}-tmp-#{@tmp_id += 1}"
469
+ end
470
+
471
+ # Generate a new temporary directory name under the main tmpdir.
472
+ #
473
+ # @return [void]
474
+ def generate_tmpdir
475
+ File.join(tmpdir, self.generate_tmpname)
476
+ end
477
+
478
+ # Generate all of the rake tasks for this pipeline.
479
+ #
480
+ # @return [void]
481
+ def generate_rake_tasks
482
+ @rake_tasks = filters.collect { |f| f.generate_rake_tasks }.flatten
483
+ end
484
+
485
+ # Assert that an input root and glob were both provided.
486
+ #
487
+ # @raise Rake::Pipeline::Error if input root or glob were missing.
488
+ # @return [void]
489
+ def assert_input_provided
490
+ if inputs.empty?
491
+ raise Rake::Pipeline::Error, "You cannot get input files without " \
492
+ "first providing input files and an input root"
493
+ end
494
+ end
495
+
496
+ # This is needed to ensure that every file procesed in the pipeline
497
+ # has an entry in the manifest. This is used to compare input files
498
+ # for the global dirty check
499
+ def record_input_files
500
+ input_files.each do |file|
501
+ full_path = file.fullpath
502
+
503
+ if File.exists?(full_path) && !manifest[full_path]
504
+ manifest[full_path] ||= ManifestEntry.new({}, File.mtime(full_path).to_i)
505
+ end
506
+ end
507
+ end
508
+ end
509
+ end