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,34 @@
1
+ module Rake
2
+ class Pipeline
3
+ # Represents a single entry in a dynamic dependency {Manifest}.
4
+ class ManifestEntry
5
+ # Create a new entry from the given hash.
6
+ def self.from_hash(hash)
7
+ entry = new
8
+
9
+ entry.mtime = hash["mtime"]
10
+
11
+ hash["deps"].each do |dep, time_string|
12
+ entry.deps[dep] = time_string
13
+ end
14
+
15
+ entry
16
+ end
17
+
18
+ attr_accessor :deps, :mtime
19
+
20
+ def initialize(deps={}, mtime=nil)
21
+ @deps, @mtime = deps, mtime
22
+ end
23
+
24
+ def as_json
25
+ { :deps => @deps, :mtime => @mtime }
26
+ end
27
+
28
+ def ==(other)
29
+ mtime == other.mtime
30
+ deps == other.deps
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,141 @@
1
+ require "strscan"
2
+
3
+ module Rake
4
+ class Pipeline
5
+ # A Matcher is a type of pipeline that restricts its
6
+ # filters to a particular pattern.
7
+ #
8
+ # A Matcher's pattern is a File glob.
9
+ #
10
+ # For instance, to restrict filters to operating on
11
+ # JavaScript files in the +app+ directory, the Matcher's
12
+ # {Pipeline#inputs inputs} should include +"app"+,
13
+ # and its glob would be +"*.js"+.
14
+ #
15
+ # In general, you should not use Matcher directly. Instead use
16
+ # {DSL#match} in the block passed to {Pipeline.build}.
17
+ class Matcher < Pipeline
18
+ attr_reader :glob
19
+
20
+ # @return [Rake::Pipeline] the Rake::Pipeline that contains
21
+ # this matcher.
22
+ attr_accessor :pipeline
23
+
24
+ # A glob matcher that a filter's input files must match
25
+ # in order to be processed by the filter.
26
+ #
27
+ # @return [String]
28
+ def glob=(pattern)
29
+ @glob = pattern
30
+ if pattern.kind_of?(Regexp)
31
+ @pattern = pattern
32
+ else
33
+ @pattern = scan_string
34
+ end
35
+ end
36
+
37
+ # A list of the output files that invoking this pipeline will
38
+ # generate. This will include the outputs of files matching
39
+ # the {#glob glob} and any inputs that did not match the
40
+ # glob.
41
+ #
42
+ # This will make those inputs available to any additional
43
+ # filters or matchers.
44
+ #
45
+ # @return [Array<FileWrapper>]
46
+ def output_files
47
+ super + input_files.reject do |file|
48
+ file.path =~ @pattern
49
+ end
50
+ end
51
+
52
+ # Override {Pipeline#finalize} to do nothing. We want to pass
53
+ # on our unmatched inputs to the next part of the pipeline.
54
+ #
55
+ # @return [void]
56
+ # @api private
57
+ def finalize
58
+ end
59
+
60
+ protected
61
+ # Let our containing pipeline generate temp directories for us.
62
+ def generate_tmpdir
63
+ pipeline.generate_tmpdir
64
+ end
65
+
66
+ private
67
+ # Override the default {Pipeline#eligible_input_files}
68
+ # to include only files that match the {#glob glob}.
69
+ #
70
+ # @return [Array<FileWrapper>]
71
+ def eligible_input_files
72
+ input_files.select do |file|
73
+ file.path =~ @pattern
74
+ end
75
+ end
76
+
77
+ # Convert string to regexp using StringScanner
78
+ #
79
+ # @return [Regexp]
80
+ def scan_string
81
+ scanner = StringScanner.new(glob)
82
+
83
+ output, pos = "", 0
84
+
85
+ # keep scanning until end of String
86
+ until scanner.eos?
87
+
88
+ # look for **/, *, {...}, or the end of the string
89
+ new_chars = scanner.scan_until %r{
90
+ \*\*/
91
+ | /\*\*/
92
+ | \*
93
+ | \{[^\}]*\}
94
+ | $
95
+ }x
96
+
97
+ # get the new part of the string up to the match
98
+ before = new_chars[0, new_chars.size - scanner.matched_size]
99
+
100
+ # get the match and new position
101
+ match = scanner.matched
102
+ pos = scanner.pos
103
+
104
+ # add any literal characters to the output
105
+ output << Regexp.escape(before) if before
106
+
107
+ output << case match
108
+ when "/**/"
109
+ # /**/ matches either a "/" followed by any number
110
+ # of characters or a single "/"
111
+ "(/.*|/)"
112
+ when "**/"
113
+ # **/ matches the beginning of the path or
114
+ # any number of characters followed by a "/"
115
+ "(^|.*/)"
116
+ when "*"
117
+ # * matches any number of non-"/" characters
118
+ "[^/]*"
119
+ when /\{.*\}/
120
+ # {...} is split over "," and glued back together
121
+ # as an or condition
122
+ "(" + match[1...-1].gsub(",", "|") + ")"
123
+ else String
124
+ # otherwise, we've grabbed until the end
125
+ match
126
+ end
127
+ end
128
+
129
+ if glob.include?("/")
130
+ # if the pattern includes a /, it must match the
131
+ # entire input, not just the end.
132
+ Regexp.new("^#{output}$", "i")
133
+ else
134
+ # anchor the pattern either at the beginning of the
135
+ # path or at any "/" character
136
+ Regexp.new("(^|/)#{output}$", "i")
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,73 @@
1
+ require "rack"
2
+
3
+ module Rake
4
+ class Pipeline
5
+ # This middleware is used to provide a server that will continuously
6
+ # compile your files on demand.
7
+ #
8
+ # @example
9
+ # !!!ruby
10
+ # use Rake::Pipeline::Middleware, Rake::Pipeline.build {
11
+ # input "app/assets"
12
+ # output "public"
13
+ #
14
+ # ...
15
+ # }
16
+ class Middleware
17
+ attr_accessor :project
18
+
19
+ # @param [#call] app a Rack application
20
+ # @param [Rake::Pipeline::Project] an existing project
21
+ def initialize(app, project)
22
+ @app = app
23
+ @project = project
24
+ end
25
+
26
+ # Automatically compiles your assets if required and
27
+ # serves them up.
28
+ #
29
+ # @param [Hash] env a Rack environment
30
+ # @return [Array(Fixnum, Hash, #each)] A rack response
31
+ def call(env)
32
+ project.invoke
33
+ path = env["PATH_INFO"]
34
+
35
+ if project.maps.has_key?(path)
36
+ return project.maps[path].call(env)
37
+ end
38
+
39
+ if filename = file_for(path)
40
+ if File.directory?(filename)
41
+ index = File.join(filename, "index.html")
42
+ filename = File.file?(index) ? index : nil
43
+ end
44
+
45
+ if filename
46
+ return response_for(filename)
47
+ end
48
+ end
49
+
50
+ @app.call(env)
51
+ end
52
+
53
+ private
54
+ def response_for(file)
55
+ [ 200, headers_for(file), File.open(file, "r") ]
56
+ end
57
+
58
+ def file_for(path)
59
+ project.pipelines.each do |pipeline|
60
+ file = Dir[File.join(pipeline.output_root, path)].sort.first
61
+ return file unless file.nil?
62
+ end
63
+ nil
64
+ end
65
+
66
+ def headers_for(path)
67
+ mime = Rack::Mime.mime_type(File.extname(path), "text/plain")
68
+ size = File.size(path)
69
+ { "Content-Type" => mime, "Content-Length" => size }
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,8 @@
1
+ namespace :assets do
2
+ desc "Precompile assets using Rake::Pipeline"
3
+ task :precompile do
4
+ config = Rails.application.config.rake_pipeline_assetfile
5
+ Rake::Pipeline::Project.new(config).invoke
6
+ end
7
+ end
8
+
@@ -0,0 +1,338 @@
1
+ require "digest"
2
+
3
+ module Rake
4
+ class Pipeline
5
+ # A Project controls the lifecycle of a series of Pipelines,
6
+ # creating them from an Assetfile and recreating them if the
7
+ # Assetfile changes.
8
+ class Project
9
+ # @return [Pipeline] the list of pipelines in the project
10
+ attr_reader :pipelines
11
+
12
+ attr_reader :maps
13
+
14
+ # @return [String|nil] the path to the project's Assetfile
15
+ # or nil if it was created without an Assetfile.
16
+ attr_reader :assetfile_path
17
+
18
+ # @return [String|nil] the digest of the Assetfile the
19
+ # project was created with, or nil if the project
20
+ # was created without an Assetfile.
21
+ attr_reader :assetfile_digest
22
+
23
+ # @return [String] the directory path for temporary files
24
+ attr_reader :tmpdir
25
+
26
+ # @return [String] the directory path where pipelines will
27
+ # write their outputs by default
28
+ attr_reader :default_output_root
29
+
30
+ # @return [Array] a list of filters to be applied before
31
+ # the specified filters in every pipeline
32
+ attr_writer :before_filters
33
+
34
+ # @return [Array] a list of filters to be applied after
35
+ # the specified filters in every pipeline
36
+ attr_writer :after_filters
37
+
38
+ class << self
39
+ # Configure a new project by evaluating a block with the
40
+ # Rake::Pipeline::DSL::ProjectDSL class.
41
+ #
42
+ # @see Rake::Pipeline::Filter Rake::Pipeline::Filter
43
+ #
44
+ # @example
45
+ # Rake::Pipeline::Project.build do
46
+ # tmpdir "tmp"
47
+ # output "public"
48
+ #
49
+ # input "app/assets" do
50
+ # concat "app.js"
51
+ # end
52
+ # end
53
+ #
54
+ # @return [Rake::Pipeline::Project] the newly configured project
55
+ def build(&block)
56
+ project = new
57
+ project.build(&block)
58
+ end
59
+
60
+ # @return [Array[String]] an array of strings that will be
61
+ # appended to {#digested_tmpdir}.
62
+ def digest_additions
63
+ @digest_additions ||= []
64
+ end
65
+
66
+ # Set {.digest_additions} to a sorted copy of the given array.
67
+ def digest_additions=(additions)
68
+ @digest_additions = additions.sort
69
+ end
70
+
71
+ # Add a value to the list of strings to append to the digest
72
+ # temp directory. Libraries can use this to add (for example)
73
+ # their version numbers so that the pipeline will be rebuilt
74
+ # if the library version changes.
75
+ #
76
+ # @example
77
+ # Rake::Pipeline::Project.add_to_digest(Rake::Pipeline::Web::Filters::VERSION)
78
+ #
79
+ # @param [#to_s] str a value to append to {#digested_tmpdir}.
80
+ def add_to_digest(str)
81
+ self.digest_additions << str.to_s
82
+ self.digest_additions.sort!
83
+ end
84
+ end
85
+
86
+ # @param [String|Pipeline] assetfile_or_pipeline
87
+ # if this a String, create a Pipeline from the Assetfile at
88
+ # that path. If it's a Pipeline, just wrap that pipeline.
89
+ def initialize(assetfile_or_pipeline=nil, &defaults)
90
+ reset!
91
+ if assetfile_or_pipeline.kind_of?(String)
92
+ @assetfile_path = File.expand_path(assetfile_or_pipeline)
93
+ @defaults = defaults
94
+ rebuild_from_assetfile(@assetfile_path, &defaults)
95
+ elsif assetfile_or_pipeline
96
+ @pipelines << assetfile_or_pipeline
97
+ end
98
+ end
99
+
100
+ # Evaluate a block using the Rake::Pipeline::DSL::ProjectDSL
101
+ # DSL against an existing project.
102
+ def build(&block)
103
+ DSL::ProjectDSL.evaluate(self, &block) if block
104
+ self
105
+ end
106
+
107
+ # Invoke all of the project's pipelines, detecting any changes
108
+ # to the Assetfile and rebuilding the pipelines if necessary.
109
+ #
110
+ # @return [void]
111
+ # @see Rake::Pipeline#invoke
112
+ def invoke
113
+ @invoke_mutex.synchronize do
114
+ last_manifest.read_manifest
115
+
116
+ if dirty?
117
+ rebuild_from_assetfile(assetfile_path) if assetfile_dirty?
118
+
119
+ # The temporary files have to be cleaned otherwise
120
+ # there will be a "ghost" input. Here's an example
121
+ # rake task: application.js => [a.js, b.js]. Deleting a.js
122
+ # will make application.js => [b.js]. The task correctly checks
123
+ # if B has changed (which it hasn't) and says that application.js
124
+ # is correct. Cleaning tmp files ensures that this doesn't happen.
125
+ clean if files_deleted?
126
+
127
+ pipelines.each(&:invoke)
128
+
129
+ manifest.write_manifest
130
+ end
131
+ end
132
+ end
133
+
134
+ # Remove the project's temporary and output files.
135
+ def clean
136
+ files_to_clean.each { |file| FileUtils.rm_rf(file) }
137
+ end
138
+
139
+ # Clean out old tmp directories from the pipeline's
140
+ # {Rake::Pipeline#tmpdir}.
141
+ #
142
+ # @return [void]
143
+ def cleanup_tmpdir
144
+ obsolete_tmpdirs.each { |dir| FileUtils.rm_rf(dir) }
145
+ end
146
+
147
+ # Set the default output root of this project and expand its path.
148
+ #
149
+ # @param [String] root this pipeline's output root
150
+ def default_output_root=(root)
151
+ @default_output_root = File.expand_path(root)
152
+ end
153
+
154
+ # Set the temporary directory for this project and expand its path.
155
+ #
156
+ # @param [String] root this project's temporary directory
157
+ def tmpdir=(dir)
158
+ @tmpdir = File.expand_path(dir)
159
+ end
160
+
161
+ # @return [String] A subdirectory of {#tmpdir} with the digest of
162
+ # the Assetfile's contents and any {.digest_additions} in its
163
+ # name.
164
+ def digested_tmpdir
165
+ suffix = assetfile_digest
166
+ unless self.class.digest_additions.empty?
167
+ suffix += "-#{self.class.digest_additions.join('-')}"
168
+ end
169
+ File.join(tmpdir, "rake-pipeline-#{suffix}")
170
+ end
171
+
172
+ # @return Array[String] a list of the paths to temporary directories
173
+ # that don't match the pipline's Assetfile digest.
174
+ def obsolete_tmpdirs
175
+ if File.directory?(tmpdir)
176
+ Dir["#{tmpdir}/rake-pipeline-*"].sort.reject do |dir|
177
+ dir == digested_tmpdir
178
+ end
179
+ else
180
+ []
181
+ end
182
+ end
183
+
184
+ # @return Array[String] a list of files to delete to completely clean
185
+ # out a project's temporary and output files.
186
+ def files_to_clean
187
+ setup_pipelines
188
+ obsolete_tmpdirs + [digested_tmpdir] + output_files.map(&:fullpath)
189
+ end
190
+
191
+ # @return [Array[FileWrapper]] a list of the files that
192
+ # will be generated when this project is invoked.
193
+ def output_files
194
+ setup_pipelines
195
+ pipelines.map(&:output_files).flatten
196
+ end
197
+
198
+ # Build a new pipeline and add it to our list of pipelines.
199
+ def build_pipeline(input, glob=nil, &block)
200
+ pipeline = Rake::Pipeline.build({
201
+ :before_filters => @before_filters,
202
+ :after_filters => @after_filters,
203
+ :output_root => default_output_root,
204
+ :tmpdir => digested_tmpdir,
205
+ :project => self
206
+ }, &block)
207
+
208
+ if input.kind_of?(Array)
209
+ input.each { |x| pipeline.add_input(x) }
210
+ elsif input.kind_of?(Hash)
211
+ pipeline.inputs = input
212
+ else
213
+ pipeline.add_input(input, glob)
214
+ end
215
+
216
+ @pipelines << pipeline
217
+ pipeline
218
+ end
219
+
220
+ # @return [Manifest] the manifest to write dependency information
221
+ # to
222
+ def manifest
223
+ @manifest ||= Rake::Pipeline::Manifest.new(manifest_path)
224
+ end
225
+
226
+ # @return [Manifest] the manifest to write dependency information
227
+ # to
228
+ def last_manifest
229
+ @last_manifest ||= Rake::Pipeline::Manifest.new(manifest_path)
230
+ end
231
+
232
+ # @return [String] the path to the dynamic dependency manifest
233
+ def manifest_path
234
+ File.join(digested_tmpdir, "manifest.json")
235
+ end
236
+
237
+ private
238
+ # Reset this project's internal state to the default values.
239
+ #
240
+ # @return [void]
241
+ def reset!
242
+ @pipelines = []
243
+ @maps = {}
244
+ @tmpdir = "tmp"
245
+ @invoke_mutex = Mutex.new
246
+ @default_output_root = @assetfile_digest = @assetfile_path = nil
247
+ @manifest = @last_manifest = nil
248
+ end
249
+
250
+ # Reconfigure this project based on the Assetfile at path.
251
+ #
252
+ # @param [String] path the path to the Assetfile
253
+ # to use to configure the project.
254
+ # @param [String] source if given, this string is
255
+ # evaluated instead of reading the file at assetfile_path.
256
+ #
257
+ # @return [void]
258
+ def rebuild_from_assetfile(path, source=nil, &default_config)
259
+ reset!
260
+ source ||= File.read(path)
261
+ @assetfile_digest = digest(source)
262
+ @assetfile_path = path
263
+ build do
264
+ instance_eval &default_config if block_given?
265
+ instance_eval(source, path, 1)
266
+ end
267
+ end
268
+
269
+ # Setup the pipeline so its output files will be up to date.
270
+ def setup_pipelines
271
+ pipelines.map(&:setup_filters)
272
+ end
273
+
274
+ # @return [String] the SHA1 digest of the given string.
275
+ def digest(str)
276
+ Digest::SHA1.hexdigest(str)
277
+ end
278
+
279
+ def dirty?
280
+ assetfile_dirty? || files_dirty? || files_deleted?
281
+ end
282
+
283
+ def assetfile_dirty?
284
+ if assetfile_path
285
+ source = File.read(assetfile_path)
286
+ digest(source) != assetfile_digest
287
+ else
288
+ false
289
+ end
290
+ end
291
+
292
+ # Returns true if any of these conditions are met:
293
+ # * The pipeline hasn't been invoked yet
294
+ # * The input files have changed
295
+ # * There is a new input file
296
+ def files_dirty?
297
+ return true if manifest.empty?
298
+
299
+ previous_files = manifest.files
300
+
301
+ # check for modifications to new files
302
+ input_files.each do |input_file|
303
+ if !previous_files[input_file]
304
+ return true # there is a new file in the pipeline
305
+ elsif File.mtime(input_file).to_i != previous_files[input_file]
306
+ return true # existing file has been changed
307
+ end
308
+ end
309
+
310
+ false
311
+ end
312
+
313
+ def files_deleted?
314
+ manifest.files.each_key do |input_file|
315
+ return true if !File.exists?(input_file)
316
+ end
317
+
318
+ false
319
+ end
320
+
321
+ def input_files
322
+ static_input_files = pipelines.collect do |p|
323
+ p.input_files.reject { |file| file.in_directory? tmpdir }.map(&:fullpath)
324
+ end.flatten
325
+
326
+ dynamic_input_files = static_input_files.collect do |file|
327
+ if manifest[file]
328
+ manifest[file].deps.keys
329
+ else
330
+ nil
331
+ end
332
+ end.flatten.compact
333
+
334
+ static_input_files + dynamic_input_files
335
+ end
336
+ end
337
+ end
338
+ end