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,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