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,17 @@
1
+ module Rake
2
+ class Pipeline
3
+ # The general Rake::Pipeline error class
4
+ class Error < ::StandardError
5
+ end
6
+
7
+ # The error that Rake::Pipeline uses when it detects
8
+ # that a file uses an improper encoding.
9
+ class EncodingError < Error
10
+ end
11
+
12
+ # The error that Rake::Pipeline uses if you try to
13
+ # write to a FileWrapper before creating it.
14
+ class UnopenedFile < Error
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,195 @@
1
+ module Rake
2
+ class Pipeline
3
+ # This class wraps a file for consumption inside of filters. It is
4
+ # initialized with a root and path, and filters usually use the
5
+ # {#read} and {#write} methods to work with these files.
6
+ #
7
+ # The {#root} and +path+ parameters are provided by the {Filter}
8
+ # class' internal implementation. Individual filters do not need
9
+ # to worry about them.
10
+ #
11
+ # The root of a {FileWrapper} is always an absolute path.
12
+ class FileWrapper
13
+ # @return [String] an absolute path representing this {FileWrapper}'s
14
+ # root directory.
15
+ attr_accessor :root
16
+
17
+ # @return [String] the path to the file represented by the {FileWrapper},
18
+ # relative to its {#root}.
19
+ attr_accessor :path
20
+
21
+ # @return [String] the encoding that the file represented by this
22
+ # {FileWrapper} is encoded in. Filters set the {#encoding} to
23
+ # +BINARY+ if they are declared as processing binary data.
24
+ attr_accessor :encoding
25
+
26
+ attr_accessor :original_inputs
27
+
28
+ # Create a new {FileWrapper}, passing in optional root, path, and
29
+ # encoding. Any of the parameters can be ommitted and supplied later.
30
+ #
31
+ # @return [void]
32
+ def initialize(root=nil, path=nil, encoding="UTF-8", original_inputs=nil)
33
+ @root, @path, @encoding = root, path, encoding
34
+ @created_file = nil
35
+ @original_inputs = original_inputs
36
+ end
37
+
38
+ # Create a new {FileWrapper FileWrapper} with the same root and
39
+ # path as this {FileWrapper FileWrapper}, but with a specified
40
+ # encoding.
41
+ #
42
+ # @param [String] encoding the encoding for the new object
43
+ # @return [FileWrapper]
44
+ def with_encoding(encoding)
45
+ self.class.new(@root, @path, encoding, original_inputs)
46
+ end
47
+
48
+ def original_inputs
49
+ @original_inputs ||= Set.new([self])
50
+ end
51
+
52
+ # A {FileWrapper} is equal to another {FileWrapper} for hashing purposes
53
+ # if they have the same {#root} and {#path}
54
+ #
55
+ # @param [FileWrapper] other another {FileWrapper} to compare.
56
+ # @return [true,false]
57
+ def eql?(other)
58
+ return false unless other.is_a?(self.class)
59
+ root == other.root && path == other.path
60
+ end
61
+ alias == eql?
62
+
63
+ # Similar to {#eql?}, generate a {FileWrapper}'s {#hash} from its {#root}
64
+ # and {#path}
65
+ #
66
+ # @see #eql?
67
+ # @return [Fixnum] a hash code
68
+ def hash
69
+ [root, path].hash
70
+ end
71
+
72
+ # The full path of a FileWrapper is its root joined with its path
73
+ #
74
+ # @return [String] the {FileWrapper}'s full path
75
+ def fullpath
76
+ raise "#{root}, #{path}" unless root =~ /^(\/|[a-zA-Z]:[\\\/])/
77
+ File.join(root, path)
78
+ end
79
+
80
+ # Check to see if this file is inside the given directory
81
+ #
82
+ # @return [Boolean]
83
+ def in_directory?(directory)
84
+ !!(fullpath =~ %r{^#{Regexp.escape(directory)}/})
85
+ end
86
+
87
+ # Make FileWrappers sortable
88
+ #
89
+ # @param [FileWrapper] other {FileWrapper FileWrapper}
90
+ # @return [Fixnum] -1, 0, or 1
91
+ def <=>(other)
92
+ [root, path, encoding] <=> [other.root, other.path, other.encoding]
93
+ end
94
+
95
+ # Does the file represented by the {FileWrapper} exist in the file system?
96
+ #
97
+ # @return [true,false]
98
+ def exists?
99
+ File.exists?(fullpath)
100
+ end
101
+
102
+ # Read the contents of the file represented by the {FileWrapper}.
103
+ #
104
+ # Read the file using the {FileWrapper}'s encoding, which will result in
105
+ # this method returning a +String+ tagged with the {FileWrapper}'s encoding.
106
+ #
107
+ # @return [String] the contents of the file
108
+ # @raise [EncodingError] when the contents of the file are not valid in the
109
+ # expected encoding specified in {#encoding}.
110
+ def read
111
+ contents = if "".respond_to?(:encode)
112
+ File.read(fullpath, :encoding => encoding)
113
+ else
114
+ File.read(fullpath)
115
+ end
116
+
117
+ contents.force_encoding(encoding)
118
+
119
+ # In our unit tests Rubinius returns false when the encoding is BINARY
120
+ # The encoding type check bypasses the problem and is probably acceptable, but isn't ideal
121
+ if encoding != "BINARY" && "".respond_to?(:encode) && !contents.valid_encoding?
122
+ contents.force_encoding("BINARY")
123
+ if !contents.valid_encoding?
124
+ raise EncodingError, "The file at the path #{fullpath} is not valid #{encoding}. Please save it again as #{encoding}."
125
+ end
126
+ end
127
+
128
+ contents
129
+ end
130
+
131
+ # Create a new file at the {FileWrapper}'s {#fullpath}. If the file already
132
+ # exists, it will be overwritten.
133
+ #
134
+ # @api private
135
+ # @yieldparam [File] file the newly created file
136
+ # @return [File] if a block was not given
137
+ def create
138
+ FileUtils.mkdir_p(File.dirname(fullpath))
139
+
140
+ @created_file = if "".respond_to?(:encode)
141
+ File.open(fullpath, "w:#{encoding}")
142
+ else
143
+ File.open(fullpath, "w")
144
+ end
145
+
146
+ if block_given?
147
+ yield @created_file
148
+ end
149
+
150
+ @created_file
151
+ ensure
152
+ if block_given?
153
+ @created_file.close
154
+ @created_file = nil
155
+ end
156
+ end
157
+
158
+ # Close the file represented by the {FileWrapper} if it was previously opened.
159
+ #
160
+ # @api private
161
+ # @return [void]
162
+ def close
163
+ raise IOError, "closed stream" unless @created_file
164
+ @created_file.close
165
+ @created_file = nil
166
+ end
167
+
168
+ # Check to see whether the file represented by the {FileWrapper} is open.
169
+ #
170
+ # @api private
171
+ # @return [true,false]
172
+ def closed?
173
+ @created_file.nil?
174
+ end
175
+
176
+ # Write a String to a previously opened file. This method is called repeatedly
177
+ # by a {Filter}'s +#generate_output+ method and does not create a brand new
178
+ # file for each invocation.
179
+ #
180
+ # @raise [UnopenedFile] if the file is not already opened.
181
+ def write(string)
182
+ raise UnopenedFile unless @created_file
183
+ @created_file.set_encoding string.encoding
184
+ @created_file.write(string)
185
+ end
186
+
187
+ # @return [String] A pretty representation of the {FileWrapper}.
188
+ def inspect
189
+ "#<FileWrapper root=#{root.inspect} path=#{path.inspect} encoding=#{encoding.inspect}>"
190
+ end
191
+
192
+ alias to_s inspect
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,267 @@
1
+ require "rake"
2
+
3
+ module Rake
4
+ class Pipeline
5
+
6
+ # A Filter is added to a pipeline and converts input files
7
+ # into output files.
8
+ #
9
+ # Filters operate on FileWrappers, which abstract away the
10
+ # root directory of a file, providing a relative path and
11
+ # a mechanism for reading and writing.
12
+ #
13
+ #
14
+ # For instance, a filter to wrap the contents of each file
15
+ # in a JavaScript closure would look like:
16
+ #
17
+ # !!!ruby
18
+ # require "json"
19
+ #
20
+ # class ClosureFilter < Rake::Pipeline::Filter
21
+ # def generate_output(inputs, output)
22
+ # inputs.each do |input|
23
+ # output.write "(function() { #{input.read.to_json} })()"
24
+ # end
25
+ # end
26
+ # end
27
+ #
28
+ # A filter's files come from the input directory or the directory
29
+ # owned by the previous filter, but filters are insulated from
30
+ # this concern.
31
+ #
32
+ # You can call +path+ on a FileWrapper to get the file's relative
33
+ # path, or `fullpath` to get its absolute path, but you should,
34
+ # in general, not use `fullpath` but instead use methods of
35
+ # FileWrapper like `read` and `write` that abstract the details
36
+ # from you.
37
+ #
38
+ # @see ConcatFilter Rake::Pipeline::ConcatFilter for another
39
+ # example filter implementation.
40
+ #
41
+ # @abstract
42
+ class Filter
43
+ # @return [Array<FileWrapper>] an Array of FileWrappers that
44
+ # represent the inputs of this filter. The Pipeline will
45
+ # usually set this up.
46
+ attr_accessor :input_files
47
+
48
+ # @return [Proc] a block that returns the relative output
49
+ # filename for a particular input file. If the block accepts
50
+ # just one argument, it will be passed the input's path. If
51
+ # it accepts two, it will also be passed the input itself.
52
+ attr_accessor :output_name_generator
53
+
54
+ # @return [String] the root directory to write output files
55
+ # to. For the last filter in a pipeline, the pipeline will
56
+ # set this to the pipeline's output. For all other filters,
57
+ # the pipeline will create a temporary directory that it
58
+ # also uses when creating FileWrappers for the next filter's
59
+ # inputs.
60
+ attr_accessor :output_root
61
+
62
+ # @return [Array<Rake::Task>] an Array of Rake tasks created
63
+ # for this filter. Each unique output file will get a
64
+ # single task.
65
+ attr_reader :rake_tasks
66
+
67
+ # @return [Rake::Application] the Rake::Application that the
68
+ # filter should define new rake tasks on.
69
+ attr_writer :rake_application
70
+
71
+ # @return [Rake::Pipeline] the Rake::Pipeline that contains
72
+ # this filter.
73
+ attr_accessor :pipeline
74
+
75
+ attr_writer :manifest, :last_manifest
76
+
77
+ attr_writer :file_wrapper_class
78
+
79
+ # @param [Proc] block a block to use as the Filter's
80
+ # {#output_name_generator}.
81
+ def initialize(&block)
82
+ block ||= proc { |input| input }
83
+ @output_name_generator = block
84
+ @input_files = []
85
+ end
86
+
87
+ # @return [Rake::Pipeline::Manifest] the manifest passed
88
+ # to generated rake tasks. Use the pipeline's manifest
89
+ # if this is not set
90
+ def manifest
91
+ @manifest || pipeline.manifest
92
+ end
93
+
94
+ def last_manifest
95
+ @last_manifest || pipeline.last_manifest
96
+ end
97
+
98
+ # Invoke this method in a subclass of Filter to declare that
99
+ # it expects to work with BINARY data, and that data that is
100
+ # not valid UTF-8 should be allowed.
101
+ #
102
+ # @return [void]
103
+ def self.processes_binary_files
104
+ define_method(:encoding) { "BINARY" }
105
+ end
106
+
107
+ # @return [Class] the class to use as the wrapper for output
108
+ # files.
109
+ def file_wrapper_class
110
+ @file_wrapper_class ||= FileWrapper
111
+ end
112
+
113
+ # Set the input files to a list of FileWrappers. The filter
114
+ # will map these into equivalent FileWrappers with the
115
+ # filter's encoding applied.
116
+ #
117
+ # By default, a filter's encoding is +UTF-8+, unless
118
+ # it calls #processes_binary_files, which changes it to
119
+ # +BINARY+.
120
+ #
121
+ # @param [Array<FileWrapper>] a list of FileWrapper objects
122
+ def input_files=(files)
123
+ @input_files = files.map do |file|
124
+ file.with_encoding(encoding)
125
+ end
126
+ end
127
+
128
+ # A hash of output files pointing at their associated input
129
+ # files. The output names are created by applying the
130
+ # {#output_name_generator} to each input file.
131
+ #
132
+ # For exmaple, if you had the following input files:
133
+ #
134
+ # javascripts/jquery.js
135
+ # javascripts/sproutcore.js
136
+ # stylesheets/sproutcore.css
137
+ #
138
+ # And you had the following {#output_name_generator}:
139
+ #
140
+ # !!!ruby
141
+ # filter.output_name_generator = proc do |filename|
142
+ # # javascripts/jquery.js becomes:
143
+ # # ["javascripts", "jquery", "js"]
144
+ # directory, file, ext = file.split(/[\.\/]/)
145
+ #
146
+ # "#{directory}.#{ext}"
147
+ # end
148
+ #
149
+ # You would end up with the following hash:
150
+ #
151
+ # !!!ruby
152
+ # {
153
+ # #<FileWrapper path="javascripts.js" root="#{output_root}> => [
154
+ # #<FileWrapper path="javascripts/jquery.js" root="#{previous_filter.output_root}">,
155
+ # #<FileWrapper path="javascripts/sproutcore.js" root="#{previous_filter.output_root}">
156
+ # ],
157
+ # #<FileWrapper path="stylesheets.css" root="#{output_root}"> => [
158
+ # #<FileWrapper path="stylesheets/sproutcore.css" root=#{previous_filter.output_root}">
159
+ # ]
160
+ # }
161
+ #
162
+ # Each output file becomes a Rake task, which invokes the +#generate_output+
163
+ # method defined by the subclass of {Filter} with the Array of inputs and
164
+ # the output (all as {FileWrapper}s).
165
+ #
166
+ # @return [Hash{FileWrapper => Array<FileWrapper>}]
167
+ def outputs
168
+ hash = {}
169
+
170
+ input_files.each do |file|
171
+ output_wrappers(file).each do |output|
172
+ hash[output] ||= []
173
+ hash[output] << file
174
+ end
175
+ end
176
+
177
+ hash
178
+ end
179
+
180
+ # An Array of the {FileWrapper} objects that rerepresent this filter's
181
+ # output files. It is the same as +outputs.keys+.
182
+ #
183
+ # @see #outputs
184
+ # @return [Array<FileWrapper>]
185
+ def output_files
186
+ input_files.map { |file| output_wrappers(file) }.flatten.uniq
187
+ end
188
+
189
+ # The Rake::Application that the filter should define new tasks on.
190
+ #
191
+ # @return [Rake::Application]
192
+ def rake_application
193
+ @rake_application || Rake.application
194
+ end
195
+
196
+ # @param [FileWrapper] file wrapper to get paths for
197
+ # @return [Array<String>] array of file paths within additional dependencies
198
+ def additional_dependencies(input)
199
+ []
200
+ end
201
+
202
+ # Generate the Rake tasks for the output files of this filter.
203
+ #
204
+ # @see #outputs #outputs (for information on how the output files are determined)
205
+ # @return [void]
206
+ def generate_rake_tasks
207
+ @rake_tasks = outputs.map do |output, inputs|
208
+ additional_paths = []
209
+ inputs.each do |input|
210
+
211
+ create_file_task(input.fullpath).dynamic do
212
+ additional_paths += additional_dependencies(input)
213
+ end
214
+ end
215
+ additional_paths.each { |path| create_file_task(path) }
216
+
217
+ create_file_task(output.fullpath, inputs.map(&:fullpath)) do
218
+ output.create { generate_output(inputs, output) }
219
+ end
220
+ end
221
+ end
222
+
223
+ private
224
+ # @attr_reader
225
+ def encoding
226
+ "UTF-8"
227
+ end
228
+
229
+ def create_file_task(output, deps=[], &block)
230
+ task = rake_application.define_task(Rake::Pipeline::DynamicFileTask, output => deps, &block)
231
+ task.manifest = manifest
232
+ task.last_manifest = last_manifest
233
+ task
234
+ end
235
+
236
+ def output_wrappers(input)
237
+ output_paths(input).map do |path|
238
+ if collected_wrappers.key? path
239
+ collected_wrappers[path].tap do |wrapper|
240
+ original_inputs =
241
+ if input.original_inputs.any?
242
+ input.original_inputs
243
+ else
244
+ [input]
245
+ end
246
+ wrapper.original_inputs.merge original_inputs
247
+ end
248
+ else
249
+ wrapper = file_wrapper_class.new(output_root, path, encoding,
250
+ input.original_inputs)
251
+ collected_wrappers[path] = wrapper
252
+ end
253
+ end
254
+ end
255
+
256
+ def collected_wrappers
257
+ @collected_wrappers ||= {}
258
+ end
259
+
260
+ def output_paths(input)
261
+ args = [ input.path ]
262
+ args << input if output_name_generator.arity == 2
263
+ Array(output_name_generator.call(*args))
264
+ end
265
+ end
266
+ end
267
+ end