nanoc 3.2.4 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (230) hide show
  1. data/.gemtest +0 -0
  2. data/ChangeLog +3 -0
  3. data/Gemfile +32 -0
  4. data/LICENSE +19 -0
  5. data/NEWS.md +470 -0
  6. data/README.md +114 -0
  7. data/Rakefile +14 -0
  8. data/bin/nanoc +7 -27
  9. data/bin/nanoc3 +3 -0
  10. data/doc/yardoc_templates/default/layout/html/footer.erb +10 -0
  11. data/lib/nanoc.rb +41 -0
  12. data/lib/nanoc/base.rb +49 -0
  13. data/lib/nanoc/base/compilation/checksum_store.rb +57 -0
  14. data/lib/nanoc/base/compilation/compiled_content_cache.rb +62 -0
  15. data/lib/nanoc/base/compilation/compiler.rb +458 -0
  16. data/lib/nanoc/base/compilation/compiler_dsl.rb +214 -0
  17. data/lib/nanoc/base/compilation/dependency_tracker.rb +200 -0
  18. data/lib/nanoc/base/compilation/filter.rb +165 -0
  19. data/lib/nanoc/base/compilation/item_rep_proxy.rb +103 -0
  20. data/lib/nanoc/base/compilation/item_rep_recorder_proxy.rb +102 -0
  21. data/lib/nanoc/base/compilation/outdatedness_checker.rb +223 -0
  22. data/lib/nanoc/base/compilation/outdatedness_reasons.rb +46 -0
  23. data/lib/nanoc/base/compilation/rule.rb +73 -0
  24. data/lib/nanoc/base/compilation/rule_context.rb +84 -0
  25. data/lib/nanoc/base/compilation/rule_memory_calculator.rb +40 -0
  26. data/lib/nanoc/base/compilation/rule_memory_store.rb +53 -0
  27. data/lib/nanoc/base/compilation/rules_collection.rb +243 -0
  28. data/lib/nanoc/base/context.rb +47 -0
  29. data/lib/nanoc/base/core_ext.rb +6 -0
  30. data/lib/nanoc/base/core_ext/array.rb +62 -0
  31. data/lib/nanoc/base/core_ext/hash.rb +63 -0
  32. data/lib/nanoc/base/core_ext/pathname.rb +26 -0
  33. data/lib/nanoc/base/core_ext/string.rb +46 -0
  34. data/lib/nanoc/base/directed_graph.rb +275 -0
  35. data/lib/nanoc/base/errors.rb +211 -0
  36. data/lib/nanoc/base/memoization.rb +67 -0
  37. data/lib/nanoc/base/notification_center.rb +84 -0
  38. data/lib/nanoc/base/ordered_hash.rb +200 -0
  39. data/lib/nanoc/base/plugin_registry.rb +181 -0
  40. data/lib/nanoc/base/result_data/item_rep.rb +492 -0
  41. data/lib/nanoc/base/source_data/code_snippet.rb +58 -0
  42. data/lib/nanoc/base/source_data/configuration.rb +24 -0
  43. data/lib/nanoc/base/source_data/data_source.rb +234 -0
  44. data/lib/nanoc/base/source_data/item.rb +301 -0
  45. data/lib/nanoc/base/source_data/layout.rb +130 -0
  46. data/lib/nanoc/base/source_data/site.rb +361 -0
  47. data/lib/nanoc/base/store.rb +135 -0
  48. data/lib/nanoc/cli.rb +137 -0
  49. data/lib/nanoc/cli/command_runner.rb +104 -0
  50. data/lib/nanoc/cli/commands/autocompile.rb +58 -0
  51. data/lib/nanoc/cli/commands/compile.rb +297 -0
  52. data/lib/nanoc/cli/commands/create_item.rb +60 -0
  53. data/lib/nanoc/cli/commands/create_layout.rb +73 -0
  54. data/lib/nanoc/cli/commands/create_site.rb +411 -0
  55. data/lib/nanoc/cli/commands/debug.rb +117 -0
  56. data/lib/nanoc/cli/commands/deploy.rb +79 -0
  57. data/lib/nanoc/cli/commands/info.rb +98 -0
  58. data/lib/nanoc/cli/commands/nanoc.rb +38 -0
  59. data/lib/nanoc/cli/commands/prune.rb +50 -0
  60. data/lib/nanoc/cli/commands/update.rb +70 -0
  61. data/lib/nanoc/cli/commands/view.rb +82 -0
  62. data/lib/nanoc/cli/commands/watch.rb +124 -0
  63. data/lib/nanoc/cli/error_handler.rb +199 -0
  64. data/lib/nanoc/cli/logger.rb +92 -0
  65. data/lib/nanoc/data_sources.rb +29 -0
  66. data/lib/nanoc/data_sources/deprecated/delicious.rb +42 -0
  67. data/lib/nanoc/data_sources/deprecated/last_fm.rb +87 -0
  68. data/lib/nanoc/data_sources/deprecated/twitter.rb +38 -0
  69. data/lib/nanoc/data_sources/filesystem.rb +299 -0
  70. data/lib/nanoc/data_sources/filesystem_unified.rb +121 -0
  71. data/lib/nanoc/data_sources/filesystem_verbose.rb +91 -0
  72. data/lib/nanoc/extra.rb +24 -0
  73. data/lib/nanoc/extra/auto_compiler.rb +103 -0
  74. data/lib/nanoc/extra/chick.rb +125 -0
  75. data/lib/nanoc/extra/core_ext.rb +6 -0
  76. data/lib/nanoc/extra/core_ext/enumerable.rb +33 -0
  77. data/lib/nanoc/extra/core_ext/pathname.rb +30 -0
  78. data/lib/nanoc/extra/core_ext/time.rb +19 -0
  79. data/lib/nanoc/extra/deployer.rb +47 -0
  80. data/lib/nanoc/extra/deployers.rb +15 -0
  81. data/lib/nanoc/extra/deployers/fog.rb +98 -0
  82. data/lib/nanoc/extra/deployers/rsync.rb +70 -0
  83. data/lib/nanoc/extra/file_proxy.rb +40 -0
  84. data/lib/nanoc/extra/pruner.rb +86 -0
  85. data/lib/nanoc/extra/validators.rb +12 -0
  86. data/lib/nanoc/extra/validators/links.rb +268 -0
  87. data/lib/nanoc/extra/validators/w3c.rb +95 -0
  88. data/lib/nanoc/extra/vcs.rb +66 -0
  89. data/lib/nanoc/extra/vcses.rb +17 -0
  90. data/lib/nanoc/extra/vcses/bazaar.rb +25 -0
  91. data/lib/nanoc/extra/vcses/dummy.rb +24 -0
  92. data/lib/nanoc/extra/vcses/git.rb +25 -0
  93. data/lib/nanoc/extra/vcses/mercurial.rb +25 -0
  94. data/lib/nanoc/extra/vcses/subversion.rb +25 -0
  95. data/lib/nanoc/filters.rb +59 -0
  96. data/lib/nanoc/filters/asciidoc.rb +38 -0
  97. data/lib/nanoc/filters/bluecloth.rb +19 -0
  98. data/lib/nanoc/filters/coderay.rb +21 -0
  99. data/lib/nanoc/filters/coffeescript.rb +20 -0
  100. data/lib/nanoc/filters/colorize_syntax.rb +298 -0
  101. data/lib/nanoc/filters/erb.rb +38 -0
  102. data/lib/nanoc/filters/erubis.rb +34 -0
  103. data/lib/nanoc/filters/haml.rb +27 -0
  104. data/lib/nanoc/filters/kramdown.rb +20 -0
  105. data/lib/nanoc/filters/less.rb +53 -0
  106. data/lib/nanoc/filters/markaby.rb +20 -0
  107. data/lib/nanoc/filters/maruku.rb +20 -0
  108. data/lib/nanoc/filters/mustache.rb +24 -0
  109. data/lib/nanoc/filters/rainpress.rb +19 -0
  110. data/lib/nanoc/filters/rdiscount.rb +22 -0
  111. data/lib/nanoc/filters/rdoc.rb +33 -0
  112. data/lib/nanoc/filters/redcarpet.rb +62 -0
  113. data/lib/nanoc/filters/redcloth.rb +47 -0
  114. data/lib/nanoc/filters/relativize_paths.rb +94 -0
  115. data/lib/nanoc/filters/rubypants.rb +20 -0
  116. data/lib/nanoc/filters/sass.rb +74 -0
  117. data/lib/nanoc/filters/slim.rb +25 -0
  118. data/lib/nanoc/filters/typogruby.rb +23 -0
  119. data/lib/nanoc/filters/uglify_js.rb +42 -0
  120. data/lib/nanoc/filters/xsl.rb +46 -0
  121. data/lib/nanoc/filters/yui_compressor.rb +23 -0
  122. data/lib/nanoc/helpers.rb +16 -0
  123. data/lib/nanoc/helpers/blogging.rb +319 -0
  124. data/lib/nanoc/helpers/breadcrumbs.rb +40 -0
  125. data/lib/nanoc/helpers/capturing.rb +138 -0
  126. data/lib/nanoc/helpers/filtering.rb +50 -0
  127. data/lib/nanoc/helpers/html_escape.rb +55 -0
  128. data/lib/nanoc/helpers/link_to.rb +151 -0
  129. data/lib/nanoc/helpers/rendering.rb +140 -0
  130. data/lib/nanoc/helpers/tagging.rb +71 -0
  131. data/lib/nanoc/helpers/text.rb +44 -0
  132. data/lib/nanoc/helpers/xml_sitemap.rb +76 -0
  133. data/lib/nanoc/tasks.rb +10 -0
  134. data/lib/nanoc/tasks/clean.rake +16 -0
  135. data/lib/nanoc/tasks/clean.rb +29 -0
  136. data/lib/nanoc/tasks/deploy/rsync.rake +16 -0
  137. data/lib/nanoc/tasks/validate.rake +92 -0
  138. data/nanoc.gemspec +49 -0
  139. data/tasks/doc.rake +16 -0
  140. data/tasks/test.rake +46 -0
  141. data/test/base/core_ext/array_spec.rb +73 -0
  142. data/test/base/core_ext/hash_spec.rb +98 -0
  143. data/test/base/core_ext/pathname_spec.rb +27 -0
  144. data/test/base/core_ext/string_spec.rb +37 -0
  145. data/test/base/test_checksum_store.rb +35 -0
  146. data/test/base/test_code_snippet.rb +31 -0
  147. data/test/base/test_compiler.rb +403 -0
  148. data/test/base/test_compiler_dsl.rb +161 -0
  149. data/test/base/test_context.rb +31 -0
  150. data/test/base/test_data_source.rb +46 -0
  151. data/test/base/test_dependency_tracker.rb +262 -0
  152. data/test/base/test_directed_graph.rb +288 -0
  153. data/test/base/test_filter.rb +83 -0
  154. data/test/base/test_item.rb +179 -0
  155. data/test/base/test_item_rep.rb +579 -0
  156. data/test/base/test_layout.rb +59 -0
  157. data/test/base/test_memoization.rb +90 -0
  158. data/test/base/test_notification_center.rb +34 -0
  159. data/test/base/test_outdatedness_checker.rb +394 -0
  160. data/test/base/test_plugin.rb +30 -0
  161. data/test/base/test_rule.rb +19 -0
  162. data/test/base/test_rule_context.rb +65 -0
  163. data/test/base/test_site.rb +190 -0
  164. data/test/cli/commands/test_compile.rb +33 -0
  165. data/test/cli/commands/test_create_item.rb +14 -0
  166. data/test/cli/commands/test_create_layout.rb +28 -0
  167. data/test/cli/commands/test_create_site.rb +24 -0
  168. data/test/cli/commands/test_deploy.rb +74 -0
  169. data/test/cli/commands/test_help.rb +12 -0
  170. data/test/cli/commands/test_info.rb +11 -0
  171. data/test/cli/commands/test_prune.rb +98 -0
  172. data/test/cli/commands/test_update.rb +10 -0
  173. data/test/cli/test_cli.rb +102 -0
  174. data/test/cli/test_error_handler.rb +29 -0
  175. data/test/cli/test_logger.rb +10 -0
  176. data/test/data_sources/test_filesystem.rb +433 -0
  177. data/test/data_sources/test_filesystem_unified.rb +536 -0
  178. data/test/data_sources/test_filesystem_verbose.rb +357 -0
  179. data/test/extra/core_ext/test_enumerable.rb +30 -0
  180. data/test/extra/core_ext/test_pathname.rb +17 -0
  181. data/test/extra/core_ext/test_time.rb +15 -0
  182. data/test/extra/deployers/test_fog.rb +67 -0
  183. data/test/extra/deployers/test_rsync.rb +100 -0
  184. data/test/extra/test_auto_compiler.rb +417 -0
  185. data/test/extra/test_file_proxy.rb +19 -0
  186. data/test/extra/test_vcs.rb +22 -0
  187. data/test/extra/validators/test_links.rb +62 -0
  188. data/test/extra/validators/test_w3c.rb +47 -0
  189. data/test/filters/test_asciidoc.rb +22 -0
  190. data/test/filters/test_bluecloth.rb +18 -0
  191. data/test/filters/test_coderay.rb +44 -0
  192. data/test/filters/test_coffeescript.rb +18 -0
  193. data/test/filters/test_colorize_syntax.rb +379 -0
  194. data/test/filters/test_erb.rb +105 -0
  195. data/test/filters/test_erubis.rb +78 -0
  196. data/test/filters/test_haml.rb +96 -0
  197. data/test/filters/test_kramdown.rb +18 -0
  198. data/test/filters/test_less.rb +113 -0
  199. data/test/filters/test_markaby.rb +24 -0
  200. data/test/filters/test_maruku.rb +18 -0
  201. data/test/filters/test_mustache.rb +25 -0
  202. data/test/filters/test_rainpress.rb +29 -0
  203. data/test/filters/test_rdiscount.rb +31 -0
  204. data/test/filters/test_rdoc.rb +18 -0
  205. data/test/filters/test_redcarpet.rb +73 -0
  206. data/test/filters/test_redcloth.rb +33 -0
  207. data/test/filters/test_relativize_paths.rb +533 -0
  208. data/test/filters/test_rubypants.rb +18 -0
  209. data/test/filters/test_sass.rb +229 -0
  210. data/test/filters/test_slim.rb +35 -0
  211. data/test/filters/test_typogruby.rb +21 -0
  212. data/test/filters/test_uglify_js.rb +30 -0
  213. data/test/filters/test_xsl.rb +105 -0
  214. data/test/filters/test_yui_compressor.rb +44 -0
  215. data/test/gem_loader.rb +11 -0
  216. data/test/helper.rb +207 -0
  217. data/test/helpers/test_blogging.rb +754 -0
  218. data/test/helpers/test_breadcrumbs.rb +81 -0
  219. data/test/helpers/test_capturing.rb +41 -0
  220. data/test/helpers/test_filtering.rb +106 -0
  221. data/test/helpers/test_html_escape.rb +32 -0
  222. data/test/helpers/test_link_to.rb +249 -0
  223. data/test/helpers/test_rendering.rb +89 -0
  224. data/test/helpers/test_tagging.rb +87 -0
  225. data/test/helpers/test_text.rb +24 -0
  226. data/test/helpers/test_xml_sitemap.rb +103 -0
  227. data/test/tasks/test_clean.rb +67 -0
  228. metadata +327 -15
  229. data/bin/nanoc-select +0 -86
  230. data/lib/nanoc-select.rb +0 -11
@@ -0,0 +1,214 @@
1
+ # encoding: utf-8
2
+
3
+ module Nanoc
4
+
5
+ # Contains methods that will be executed by the site’s `Rules` file.
6
+ class CompilerDSL
7
+
8
+ # Creates a new compiler DSL for the given collection of rules.
9
+ #
10
+ # @api private
11
+ #
12
+ # @param [Nanoc::RulesCollection] rules_collection The collection of
13
+ # rules to modify when loading this DSL
14
+ #
15
+ # @param [Hash] config The site configuration
16
+ def initialize(rules_collection, config)
17
+ @rules_collection = rules_collection
18
+ @config = config
19
+ end
20
+
21
+ # Creates a preprocessor block that will be executed after all data is
22
+ # loaded, but before the site is compiled.
23
+ #
24
+ # @yield The block that will be executed before site compilation starts
25
+ #
26
+ # @return [void]
27
+ def preprocess(&block)
28
+ @rules_collection.preprocessor = block
29
+ end
30
+
31
+ # Creates a compilation rule for all items whose identifier match the
32
+ # given identifier, which may either be a string containing the *
33
+ # wildcard, or a regular expression.
34
+ #
35
+ # This rule will be applicable to reps with a name equal to `:default`;
36
+ # this can be changed by giving an explicit `:rep` parameter.
37
+ #
38
+ # An item rep will be compiled by calling the given block and passing the
39
+ # rep as a block argument.
40
+ #
41
+ # @param [String] identifier A pattern matching identifiers of items that
42
+ # should be compiled using this rule
43
+ #
44
+ # @option params [Symbol] :rep (:default) The name of the representation
45
+ # that should be compiled using this rule
46
+ #
47
+ # @yield The block that will be executed when an item matching this
48
+ # compilation rule needs to be compiled
49
+ #
50
+ # @return [void]
51
+ #
52
+ # @example Compiling the default rep of the `/foo/` item
53
+ #
54
+ # compile '/foo/' do
55
+ # rep.filter :erb
56
+ # end
57
+ #
58
+ # @example Compiling the `:raw` rep of the `/bar/` item
59
+ #
60
+ # compile '/bar/', :rep => :raw do
61
+ # # do nothing
62
+ # end
63
+ def compile(identifier, params={}, &block)
64
+ # Require block
65
+ raise ArgumentError.new("#compile requires a block") unless block_given?
66
+
67
+ # Get rep name
68
+ rep_name = params[:rep] || :default
69
+
70
+ # Create rule
71
+ rule = Rule.new(identifier_to_regex(identifier), rep_name, block)
72
+ @rules_collection.add_item_compilation_rule(rule)
73
+ end
74
+
75
+ # Creates a routing rule for all items whose identifier match the
76
+ # given identifier, which may either be a string containing the `*`
77
+ # wildcard, or a regular expression.
78
+ #
79
+ # This rule will be applicable to reps with a name equal to `:default`;
80
+ # this can be changed by giving an explicit `:rep` parameter.
81
+ #
82
+ # The path of an item rep will be determined by calling the given block
83
+ # and passing the rep as a block argument.
84
+ #
85
+ # @param [String] identifier A pattern matching identifiers of items that
86
+ # should be routed using this rule
87
+ #
88
+ # @option params [Symbol] :rep (:default) The name of the representation
89
+ # that should be routed using this rule
90
+ #
91
+ # @yield The block that will be executed when an item matching this
92
+ # compilation rule needs to be routed
93
+ #
94
+ # @return [void]
95
+ #
96
+ # @example Routing the default rep of the `/foo/` item
97
+ #
98
+ # route '/foo/' do
99
+ # item.identifier + 'index.html'
100
+ # end
101
+ #
102
+ # @example Routing the `:raw` rep of the `/bar/` item
103
+ #
104
+ # route '/bar/', :rep => :raw do
105
+ # '/raw' + item.identifier + 'index.txt'
106
+ # end
107
+ def route(identifier, params={}, &block)
108
+ # Require block
109
+ raise ArgumentError.new("#route requires a block") unless block_given?
110
+
111
+ # Get rep name
112
+ rep_name = params[:rep] || :default
113
+ snapshot_name = params[:snapshot] || :last
114
+
115
+ # Create rule
116
+ rule = Rule.new(identifier_to_regex(identifier), rep_name, block, :snapshot_name => snapshot_name)
117
+ @rules_collection.add_item_routing_rule(rule)
118
+ end
119
+
120
+ # Creates a layout rule for all layouts whose identifier match the given
121
+ # identifier, which may either be a string containing the * wildcard, or a
122
+ # regular expression. The layouts matching the identifier will be filtered
123
+ # using the filter specified in the second argument. The params hash
124
+ # contains filter arguments that will be passed to the filter.
125
+ #
126
+ # @param [String] identifier A pattern matching identifiers of layouts
127
+ # that should be filtered using this rule
128
+ #
129
+ # @param [Symbol] filter_name The name of the filter that should be run
130
+ # when processing the layout
131
+ #
132
+ # @param [Hash] params Extra filter arguments that should be passed to the
133
+ # filter when processing the layout (see {Nanoc::Filter#run})
134
+ #
135
+ # @return [void]
136
+ #
137
+ # @example Specifying the filter to use for a layout
138
+ #
139
+ # layout '/default/', :erb
140
+ #
141
+ # @example Using custom filter arguments for a layout
142
+ #
143
+ # layout '/custom/', :haml, :format => :html5
144
+ def layout(identifier, filter_name, params={})
145
+ @rules_collection.layout_filter_mapping[identifier_to_regex(identifier)] = [ filter_name, params ]
146
+ end
147
+
148
+ # Creates a pair of compilation and routing rules that indicate that the
149
+ # specified item(s) should be copied to the output folder as-is. The items
150
+ # are selected using an identifier, which may either be a string
151
+ # containing the `*` wildcard, or a regular expression.
152
+ #
153
+ # This meta-rule will be applicable to reps with a name equal to
154
+ # `:default`; this can be changed by giving an explicit `:rep` parameter.
155
+ #
156
+ # @param [String] identifier A pattern matching identifiers of items that
157
+ # should be processed using this meta-rule
158
+ #
159
+ # @option params [Symbol] :rep (:default) The name of the representation
160
+ # that should be routed using this rule
161
+ #
162
+ # @return [void]
163
+ #
164
+ # @since 3.2.0
165
+ #
166
+ # @example Copying the `/foo/` item as-is
167
+ #
168
+ # passthrough '/foo/'
169
+ #
170
+ # @example Copying the `:raw` rep of the `/bar/` item as-is
171
+ #
172
+ # passthrough '/bar/', :rep => :raw
173
+ def passthrough(identifier, params={})
174
+ # Require no block
175
+ raise ArgumentError.new("#passthrough does not require a block") if block_given?
176
+
177
+ # Get rep name
178
+ rep_name = params[:rep] || :default
179
+
180
+ # Create compilation rule
181
+ compilation_block = proc { }
182
+ compilation_rule = Rule.new(identifier_to_regex(identifier), rep_name, compilation_block)
183
+ @rules_collection.add_item_compilation_rule(compilation_rule, :before)
184
+
185
+ # Create routing rule
186
+ routing_block = proc do
187
+ item.identifier.chop + '.' + item[:extension]
188
+ end
189
+ routing_rule = Rule.new(identifier_to_regex(identifier), rep_name, routing_block)
190
+ @rules_collection.add_item_routing_rule(routing_rule, :before)
191
+ end
192
+
193
+ private
194
+
195
+ # Converts the given identifier, which can contain the '*' or '+'
196
+ # wildcard characters, matching zero or more resp. one or more
197
+ # characters, to a regex. For example, 'foo/*/bar' is transformed
198
+ # into /^foo\/(.*?)\/bar$/ and 'foo+' is transformed into /^foo(.+?)/.
199
+ def identifier_to_regex(identifier)
200
+ if identifier.is_a? String
201
+ # Add leading/trailing slashes if necessary
202
+ new_identifier = identifier.dup
203
+ new_identifier[/^/] = '/' if identifier[0,1] != '/'
204
+ new_identifier[/$/] = '/' unless [ '*', '/' ].include?(identifier[-1,1])
205
+
206
+ /^#{new_identifier.gsub('*', '(.*?)').gsub('+', '(.+?)')}$/
207
+ else
208
+ identifier
209
+ end
210
+ end
211
+
212
+ end
213
+
214
+ end
@@ -0,0 +1,200 @@
1
+ # encoding: utf-8
2
+
3
+ module Nanoc
4
+
5
+ # Responsible for remembering dependencies between items and layouts. It is
6
+ # used to speed up compilation by only letting an item be recompiled when it
7
+ # is outdated or any of its dependencies (or dependencies’ dependencies,
8
+ # etc) is outdated.
9
+ #
10
+ # The dependencies tracked by the dependency tracker are not dependencies
11
+ # based on an item’s or a layout’s content. When one object uses an
12
+ # attribute of another object, then this is also treated as a dependency.
13
+ # While dependencies based on an item’s or layout’s content (handled in
14
+ # {Nanoc::Compiler}) cannot be mutually recursive, the more general
15
+ # dependencies in Nanoc::DependencyTracker can (e.g. item A can use an
16
+ # attribute of item B and vice versa without problems).
17
+ #
18
+ # The dependency tracker remembers the dependency information between runs.
19
+ # Dependency information is stored in the `tmp/dependencies` file.
20
+ #
21
+ # @api private
22
+ class DependencyTracker < ::Nanoc::Store
23
+
24
+ # @return [Array<Nanoc::Item, Nanoc::Layout>] The list of items and
25
+ # layouts that are being tracked by the dependency tracker
26
+ attr_reader :objects
27
+
28
+ # @return [Nanoc::Compiler] The compiler that corresponds to this
29
+ # dependency tracker
30
+ attr_accessor :compiler
31
+
32
+ # Creates a new dependency tracker for the given items and layouts.
33
+ #
34
+ # @param [Array<Nanoc::Item, Nanoc::Layout>] objects The list of items
35
+ # and layouts whose dependencies should be managed
36
+ def initialize(objects)
37
+ super('tmp/dependencies', 4)
38
+
39
+ @objects = objects
40
+
41
+ @graph = Nanoc::DirectedGraph.new([ nil ] + @objects)
42
+ end
43
+
44
+ # Starts listening for dependency messages (`:visit_started` and
45
+ # `:visit_ended`) and start recording dependencies.
46
+ #
47
+ # @return [void]
48
+ def start
49
+ # Initialize dependency stack. An object will be pushed onto this stack
50
+ # when it is visited. Therefore, an object on the stack always depends
51
+ # on all objects pushed above it.
52
+ @stack = []
53
+
54
+ # Register start of visits
55
+ Nanoc::NotificationCenter.on(:visit_started, self) do |obj|
56
+ if !@stack.empty?
57
+ Nanoc::NotificationCenter.post(:dependency_created, @stack.last, obj)
58
+ self.record_dependency(@stack.last, obj)
59
+ end
60
+ @stack.push(obj)
61
+ end
62
+
63
+ # Register end of visits
64
+ Nanoc::NotificationCenter.on(:visit_ended, self) do |obj|
65
+ @stack.pop
66
+ end
67
+ end
68
+
69
+ # Stop listening for dependency messages and stop recording dependencies.
70
+ #
71
+ # @return [void]
72
+ def stop
73
+ # Unregister
74
+ Nanoc::NotificationCenter.remove(:visit_started, self)
75
+ Nanoc::NotificationCenter.remove(:visit_ended, self)
76
+ end
77
+
78
+ # Returns the direct dependencies for the given object.
79
+ #
80
+ # The direct dependencies of the given object include the items and
81
+ # layouts that, when outdated will cause the given object to be marked as
82
+ # outdated. Indirect dependencies will not be returned (e.g. if A depends
83
+ # on B which depends on C, then the direct dependencies of A do not
84
+ # include C).
85
+ #
86
+ # The direct predecessors can include nil, which indicates an item that is
87
+ # no longer present in the site.
88
+ #
89
+ # @param [Nanoc::Item, Nanoc::Layout] object The object for
90
+ # which to fetch the direct predecessors
91
+ #
92
+ # @return [Array<Nanoc::Item, Nanoc::Layout, nil>] The direct
93
+ # predecessors of
94
+ # the given object
95
+ def objects_causing_outdatedness_of(object)
96
+ @graph.direct_predecessors_of(object)
97
+ end
98
+
99
+ # Returns the direct inverse dependencies for the given object.
100
+ #
101
+ # The direct inverse dependencies of the given object include the objects
102
+ # that will be marked as outdated when the given object is outdated.
103
+ # Indirect dependencies will not be returned (e.g. if A depends on B which
104
+ # depends on C, then the direct inverse dependencies of C do not include
105
+ # A).
106
+ #
107
+ # @param [Nanoc::Item, Nanoc::Layout] object The object for which to
108
+ # fetch the direct successors
109
+ #
110
+ # @return [Array<Nanoc::Item, Nanoc::Layout>] The direct successors of
111
+ # the given object
112
+ def objects_outdated_due_to(object)
113
+ @graph.direct_successors_of(object).compact
114
+ end
115
+
116
+ # Records a dependency from `src` to `dst` in the dependency graph. When
117
+ # `dst` is oudated, `src` will also become outdated.
118
+ #
119
+ # @param [Nanoc::Item, Nanoc::Layout] src The source of the dependency,
120
+ # i.e. the object that will become outdated if dst is outdated
121
+ #
122
+ # @param [Nanoc::Item, Nanoc::Layout] dst The destination of the
123
+ # dependency, i.e. the object that will cause the source to become
124
+ # outdated if the destination is outdated
125
+ #
126
+ # @return [void]
127
+ def record_dependency(src, dst)
128
+ # Warning! dst and src are *reversed* here!
129
+ @graph.add_edge(dst, src) unless src == dst
130
+ end
131
+
132
+ # Empties the list of dependencies for the given object. This is necessary
133
+ # before recompiling the given object, because otherwise old dependencies
134
+ # will stick around and new dependencies will appear twice. This function
135
+ # removes all incoming edges for the given vertex.
136
+ #
137
+ # @api private
138
+ #
139
+ # @param [Nanoc::Item, Nanoc::Layout] object The object for which to
140
+ # forget all dependencies
141
+ #
142
+ # @return [void]
143
+ def forget_dependencies_for(object)
144
+ @graph.delete_edges_to(object)
145
+ end
146
+
147
+ # @deprecated Use {#store} instead
148
+ def store_graph
149
+ self.store
150
+ end
151
+
152
+ # @deprecated Use {#load} instead
153
+ def load_graph
154
+ self.load
155
+ end
156
+
157
+ # @see Nanoc::Store#unload
158
+ def unload
159
+ @graph = Nanoc::DirectedGraph.new([ nil ] + @objects)
160
+ end
161
+
162
+ protected
163
+
164
+ def data
165
+ {
166
+ :edges => @graph.edges,
167
+ :vertices => @graph.vertices.map { |obj| obj && obj.reference }
168
+ }
169
+ end
170
+
171
+ def data=(new_data)
172
+ # Create new graph
173
+ @graph = Nanoc::DirectedGraph.new([ nil ] + @objects)
174
+
175
+ # Load vertices
176
+ previous_objects = new_data[:vertices].map do |reference|
177
+ @objects.find { |obj| reference == obj.reference }
178
+ end
179
+
180
+ # Load edges
181
+ new_data[:edges].each do |edge|
182
+ from_index, to_index = *edge
183
+ from = from_index && previous_objects[from_index]
184
+ to = to_index && previous_objects[to_index]
185
+ @graph.add_edge(from, to)
186
+ end
187
+
188
+ # Record dependency from all items on new items
189
+ new_objects = (@objects - previous_objects)
190
+ new_objects.each do |new_obj|
191
+ @objects.each do |obj|
192
+ next unless obj.is_a?(Nanoc::Item)
193
+ @graph.add_edge(new_obj, obj)
194
+ end
195
+ end
196
+ end
197
+
198
+ end
199
+
200
+ end
@@ -0,0 +1,165 @@
1
+ # encoding: utf-8
2
+
3
+ module Nanoc
4
+
5
+ # Nanoc::Filter is responsible for filtering items. It is the superclass
6
+ # for all textual filters.
7
+ #
8
+ # A filter instance should only be used once. Filters should not be reused
9
+ # since they store state.
10
+ #
11
+ # When creating a filter with a hash containing assigned variables, those
12
+ # variables will be made available in the `@assigns` instance variable and
13
+ # the {#assigns} method. The assigns itself will also be available as
14
+ # instance variables and instance methods.
15
+ #
16
+ # @example Accessing assigns in different ways
17
+ #
18
+ # filter = SomeFilter.new({ :foo => 'bar' })
19
+ # filter.instance_eval { @assigns[:foo] }
20
+ # # => 'bar'
21
+ # filter.instance_eval { assigns[:foo] }
22
+ # # => 'bar'
23
+ # filter.instance_eval { @foo }
24
+ # # => 'bar'
25
+ # filter.instance_eval { foo }
26
+ # # => 'bar'
27
+ #
28
+ # @abstract Subclass and override {#run} to implement a custom filter.
29
+ class Filter < Context
30
+
31
+ # The path to the directory where temporary binary items are stored
32
+ TMP_BINARY_ITEMS_DIR = 'tmp/binary_items'
33
+
34
+ # A hash containing variables that will be made available during
35
+ # filtering.
36
+ #
37
+ # @return [Hash]
38
+ attr_reader :assigns
39
+
40
+ extend Nanoc::PluginRegistry::PluginMethods
41
+
42
+ class << self
43
+
44
+ # Sets the new type for the filter. The type can be `:binary` (default)
45
+ # or `:text`. The given argument can either be a symbol indicating both
46
+ # “from” and “to” types, or a hash where the only key is the “from” type
47
+ # and the only value is the “to” type.
48
+ #
49
+ # @example Specifying a text-to-text filter
50
+ #
51
+ # type :text
52
+ #
53
+ # @example Specifying a text-to-binary filter
54
+ #
55
+ # type :text => :binary
56
+ #
57
+ # @param [Symbol, Hash] arg The new type of this filter
58
+ #
59
+ # @return [void]
60
+ def type(arg)
61
+ if arg.is_a?(Hash)
62
+ @from, @to = arg.keys[0], arg.values[0]
63
+ else
64
+ @from, @to = arg, arg
65
+ end
66
+ end
67
+
68
+ # @return [Boolean] True if this filter can be applied to binary item
69
+ # representations, false otherwise
70
+ def from_binary?
71
+ (@from || :text) == :binary
72
+ end
73
+
74
+ # @return [Boolean] True if this filter results in a binary item
75
+ # representation, false otherwise
76
+ def to_binary?
77
+ (@to || :text) == :binary
78
+ end
79
+
80
+ end
81
+
82
+ # Creates a new filter that has access to the given assigns.
83
+ #
84
+ # @param [Hash] hash A hash containing variables that should be made
85
+ # available during filtering.
86
+ def initialize(hash={})
87
+ @assigns = hash
88
+ super
89
+ end
90
+
91
+ # Runs the filter on the given content or filename.
92
+ #
93
+ # @abstract
94
+ #
95
+ # @param [String] content_or_filename The unprocessed content that should
96
+ # be filtered (if the item is a textual item) or the path to the file
97
+ # that should be filtered (if the item is a binary item)
98
+ #
99
+ # @param [Hash] params A hash containing parameters. Filter subclasses can
100
+ # use these parameters to allow modifying the filter's behaviour.
101
+ #
102
+ # @return [String, void] If the filter output binary content, the return
103
+ # value is undefined; if the filter outputs textual content, the return
104
+ # value will be the filtered content.
105
+ def run(content_or_filename, params={})
106
+ raise NotImplementedError.new("Nanoc::Filter subclasses must implement #run")
107
+ end
108
+
109
+ # Returns a filename that is used to write data to. This method is only
110
+ # used on binary items. When running a binary filter on a file, the
111
+ # resulting file must end up in the location returned by this method.
112
+ #
113
+ # The returned filename will be absolute, so it is safe to change to
114
+ # another directory inside the filter.
115
+ #
116
+ # @return [String] The output filename
117
+ def output_filename
118
+ @output_filename ||= begin
119
+ FileUtils.mkdir_p(TMP_BINARY_ITEMS_DIR)
120
+ tempfile = Tempfile.new(filename.gsub(/[^a-z]/, '-'), TMP_BINARY_ITEMS_DIR)
121
+ new_filename = tempfile.path
122
+ tempfile.close!
123
+
124
+ File.expand_path(new_filename)
125
+ end
126
+ end
127
+
128
+ # Returns the filename associated with the item that is being filtered.
129
+ # It is in the format `item <identifier> (rep <name>)`.
130
+ #
131
+ # @return [String] The filename
132
+ def filename
133
+ if assigns[:layout]
134
+ "layout #{assigns[:layout].identifier}"
135
+ elsif assigns[:item]
136
+ "item #{assigns[:item].identifier} (rep #{assigns[:item_rep].name})"
137
+ else
138
+ '?'
139
+ end
140
+ end
141
+
142
+ # Creates a dependency from the item that is currently being filtered onto
143
+ # the given collection of items. In other words, require the given items
144
+ # to be compiled first before this items is processed.
145
+ #
146
+ # @param [Array<Nanoc::Item>] items The items that are depended on.
147
+ #
148
+ # @return [void]
149
+ def depend_on(items)
150
+ # Notify
151
+ items.each do |item|
152
+ Nanoc::NotificationCenter.post(:visit_started, item)
153
+ Nanoc::NotificationCenter.post(:visit_ended, item)
154
+ end
155
+
156
+ # Raise unmet dependency error if necessary
157
+ items.each do |item|
158
+ rep = item.reps.find { |r| !r.compiled? }
159
+ raise Nanoc::Errors::UnmetDependency.new(rep) if rep
160
+ end
161
+ end
162
+
163
+ end
164
+
165
+ end