benoit 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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