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,181 @@
1
+ # encoding: utf-8
2
+
3
+ module Nanoc
4
+
5
+ # The class responsible for keeping track of all loaded plugins, such as
6
+ # filters ({Nanoc::Filter}), data sources ({Nanoc::DataSource}) and VCSes
7
+ # ({Nanoc::Extra::VCS}).
8
+ class PluginRegistry
9
+
10
+ # A module that contains class methods for plugins. It provides functions
11
+ # for setting identifiers, registering plugins and finding plugins. Plugin
12
+ # classes should extend this module.
13
+ module PluginMethods
14
+
15
+ # Sets the identifiers for this plugin.
16
+ #
17
+ # @param [Array<Symbol>] identifier A list of identifiers to assign to
18
+ # this plugin.
19
+ #
20
+ # @return [void]
21
+ def identifiers(*identifiers)
22
+ register(self, *identifiers)
23
+ end
24
+
25
+ # Sets the identifier for this plugin.
26
+ #
27
+ # @param [Symbol] identifier An identifier to assign to this plugin.
28
+ #
29
+ # @return [void]
30
+ def identifier(identifier)
31
+ register(self, identifier)
32
+ end
33
+
34
+ # Registers the given class as a plugin with the given identifier.
35
+ #
36
+ # @param [Class, String] class_or_name The class to register, or a
37
+ # string containing the class name to register.
38
+ #
39
+ # @param [Array<Symbol>] identifiers A list of identifiers to assign to
40
+ # this plugin.
41
+ #
42
+ # @return [void]
43
+ def register(class_or_name, *identifiers)
44
+ # Find plugin class
45
+ klass = self
46
+ klass = klass.superclass while klass.superclass.respond_to?(:register)
47
+
48
+ # Register
49
+ registry = Nanoc::PluginRegistry.instance
50
+ registry.register(klass, class_or_name, *identifiers)
51
+ end
52
+
53
+ # Returns the plugin with the given name (identifier)
54
+ #
55
+ # @param [String] name The name of the plugin class to find
56
+ #
57
+ # @return [Class] The plugin class with the given name
58
+ def named(name)
59
+ Nanoc::Plugin.find(self, name)
60
+ end
61
+
62
+ end
63
+
64
+ # Returns the shared {PluginRegistry} instance, creating it if none exists
65
+ # yet.
66
+ #
67
+ # @return [Nanoc::PluginRegistry] The shared plugin registry
68
+ def self.instance
69
+ @instance ||= self.new
70
+ end
71
+
72
+ # Creates a new plugin registry. This should usually not be necessary; it
73
+ # is recommended to use the shared instance (obtained from
74
+ # {Nanoc::PluginRegistry.instance}).
75
+ def initialize
76
+ @map = {}
77
+ end
78
+
79
+ # Registers the given class as a plugin.
80
+ #
81
+ # @param [Class] superclass The superclass of the plugin. For example:
82
+ # {Nanoc::Filter}, {Nanoc::Extra::VCS}.
83
+ #
84
+ # @param [Class, String] class_or_name The class to register. This can be
85
+ # a string, in which case it will be automatically converted to a proper
86
+ # class at lookup. For example: `Nanoc::Filters::ERB`,
87
+ # `"Nanoc::Filters::Haml"`.
88
+ #
89
+ # @param [Symbol] identifiers One or more symbols identifying the class.
90
+ # For example: `:haml`, :`erb`.
91
+ #
92
+ # @return [void]
93
+ def register(superclass, class_or_name, *identifiers)
94
+ @map[superclass] ||= {}
95
+
96
+ identifiers.each do |identifier|
97
+ @map[superclass][identifier.to_sym] = class_or_name
98
+ end
99
+ end
100
+
101
+ # Finds the plugin that is a subclass of the given class and has the given
102
+ # name.
103
+ #
104
+ # @param [Class] klass The class of the plugin to return
105
+ #
106
+ # @param [Symbol] name The name of the plugin to return
107
+ #
108
+ # @return [Class, nil] The plugin with the given name
109
+ def find(klass, name)
110
+ @map[klass] ||= {}
111
+ resolve(@map[klass][name.to_sym], klass)
112
+ end
113
+
114
+ # Returns all plugins of the given class.
115
+ #
116
+ # @param [Class] klass The class of the plugin to return
117
+ #
118
+ # @return [Enumerable<Class>] A collection of class plugins
119
+ def find_all(klass)
120
+ @map[klass] ||= {}
121
+ res = {}
122
+ @map[klass].each_pair { |k,v| res[k] = resolve(v, k) }
123
+ res
124
+ end
125
+
126
+ # Returns a list of all plugins. The returned list of plugins is an array
127
+ # with array elements in the following format:
128
+ #
129
+ # { :class => ..., :superclass => ..., :identifiers => ... }
130
+ #
131
+ # @return [Array<Hash>] A list of all plugins in the format described
132
+ def all
133
+ plugins = []
134
+ @map.each_pair do |superclass, submap|
135
+ submap.each_pair do |identifier, klass|
136
+ # Find existing plugin
137
+ existing_plugin = plugins.find do |p|
138
+ p[:class] == klass && p[:superclass] == superclass
139
+ end
140
+
141
+ if existing_plugin
142
+ # Add identifier to existing plugin
143
+ existing_plugin[:identifiers] << identifier
144
+ existing_plugin[:identifiers] = existing_plugin[:identifiers].sort_by { |s| s.to_s }
145
+ else
146
+ # Create new plugin
147
+ plugins << {
148
+ :class => klass,
149
+ :superclass => superclass,
150
+ :identifiers => [ identifier ]
151
+ }
152
+ end
153
+ end
154
+ end
155
+
156
+ plugins
157
+ end
158
+
159
+ # @deprecated Use {Nanoc::PluginRegistry#find} instead
160
+ def named(name)
161
+ find(self, name)
162
+ end
163
+
164
+ protected
165
+
166
+ def resolve(class_or_name, klass)
167
+ if class_or_name.is_a?(String)
168
+ class_or_name.scan(/\w+/).inject(Kernel) do |memo, part|
169
+ memo.const_get(part)
170
+ end
171
+ else
172
+ class_or_name
173
+ end
174
+ end
175
+
176
+ end
177
+
178
+ # @deprecated Use {Nanoc::PluginRegistry.instance} instead
179
+ Plugin = PluginRegistry.instance
180
+
181
+ end
@@ -0,0 +1,492 @@
1
+ # encoding: utf-8
2
+
3
+ module Nanoc
4
+
5
+ # A single representation (rep) of an item ({Nanoc::Item}). An item can
6
+ # have multiple representations. A representation has its own output file.
7
+ # A single item can therefore have multiple output files, each run through
8
+ # a different set of filters with a different layout.
9
+ class ItemRep
10
+
11
+ # Contains all deprecated methods. Mixed into {Nanoc::ItemRep}.
12
+ module Deprecated
13
+
14
+ # @deprecated Modify the {#raw_paths} attribute instead
15
+ def raw_path=(raw_path)
16
+ raw_paths[:last] = raw_path
17
+ end
18
+
19
+ # @deprecated Modify the {#paths} attribute instead
20
+ def path=(path)
21
+ paths[:last] = path
22
+ end
23
+
24
+ # @deprecated Use {Nanoc::ItemRep#compiled_content} instead.
25
+ def content_at_snapshot(snapshot=:pre)
26
+ compiled_content(:snapshot => snapshot)
27
+ end
28
+
29
+ # @deprecated
30
+ def created
31
+ raise NotImplementedError, "Nanoc::ItemRep#created is no longer implemented"
32
+ end
33
+
34
+ # @deprecated
35
+ def created?
36
+ raise NotImplementedError, "Nanoc::ItemRep#created? is no longer implemented"
37
+ end
38
+
39
+ # @deprecated
40
+ def modified
41
+ raise NotImplementedError, "Nanoc::ItemRep#modified is no longer implemented"
42
+ end
43
+
44
+ # @deprecated
45
+ def modified?
46
+ raise NotImplementedError, "Nanoc::ItemRep#modified? is no longer implemented"
47
+ end
48
+
49
+ # @deprecated
50
+ def written
51
+ raise NotImplementedError, "Nanoc::ItemRep#written is no longer implemented"
52
+ end
53
+
54
+ # @deprecated
55
+ def written?
56
+ raise NotImplementedError, "Nanoc::ItemRep#written? is no longer implemented"
57
+ end
58
+
59
+ end
60
+
61
+ # Contains all private methods. Mixed into {Nanoc::ItemRep}.
62
+ module Private
63
+
64
+ # @return [Hash] A hash containing the assigns that will be used in the
65
+ # next filter or layout operation. The keys (symbols) will be made
66
+ # available during the next operation.
67
+ attr_accessor :assigns
68
+
69
+ # @return [Boolean] true if this representation has already been
70
+ # compiled during the current or last compilation session; false
71
+ # otherwise
72
+ #
73
+ # @api private
74
+ attr_accessor :compiled
75
+ alias_method :compiled?, :compiled
76
+
77
+ # @return [Hash<Symbol,String>] A hash containing the raw paths (paths
78
+ # including the path to the output directory and the filename) for all
79
+ # snapshots. The keys correspond with the snapshot names, and the
80
+ # values with the path.
81
+ #
82
+ # @api private
83
+ attr_accessor :raw_paths
84
+
85
+ # @return [Hash<Symbol,String>] A hash containing the paths for all
86
+ # snapshots. The keys correspond with the snapshot names, and the
87
+ # values with the path.
88
+ #
89
+ # @api private
90
+ attr_accessor :paths
91
+
92
+ # @return [Hash<Symbol,String>] A hash containing the paths to the
93
+ # temporary _files_ that filters write binary content to. This is only
94
+ # used when the item representation is binary. The keys correspond
95
+ # with the snapshot names, and the values with the filename. When
96
+ # writing the item representation, the file corresponding with the
97
+ # requested snapshot (usually `:last`) will be copied from
98
+ # `filenames[snapshot]` to `raw_paths[snapshot]`.
99
+ #
100
+ # @api private
101
+ attr_reader :temporary_filenames
102
+
103
+ # @return [Hash<Symbol,String>] A hash containing the content at all
104
+ # snapshots. The keys correspond with the snapshot names, and the
105
+ # values with the content.
106
+ #
107
+ # @api private
108
+ attr_accessor :content
109
+
110
+ # Writes the item rep's compiled content to the rep's output file.
111
+ #
112
+ # This method will send two notifications: one before writing the item
113
+ # representation, and one after. These notifications can be used for
114
+ # generating diffs, for example.
115
+ #
116
+ # @api private
117
+ #
118
+ # @param [String, nil] raw_path The raw path to write the compiled rep
119
+ # to. If nil, the default raw path will be used.
120
+ #
121
+ # @return [void]
122
+ def write(snapshot=:last)
123
+ # Get raw path
124
+ raw_path = self.raw_path(:snapshot => snapshot)
125
+ return if raw_path.nil?
126
+
127
+ # Create parent directory
128
+ FileUtils.mkdir_p(File.dirname(raw_path))
129
+
130
+ # Check if file will be created
131
+ is_created = !File.file?(raw_path)
132
+
133
+ # Calculate characteristics of old content
134
+ if File.file?(raw_path)
135
+ hash_old = Pathname.new(raw_path).checksum
136
+ size_old = File.size(raw_path)
137
+ end
138
+
139
+ # Notify
140
+ Nanoc::NotificationCenter.post(:will_write_rep, self, snapshot)
141
+
142
+ if self.binary?
143
+ # Calculate characteristics of new content
144
+ size_new = File.size(temporary_filenames[:last])
145
+ hash_new = Pathname.new(temporary_filenames[:last]).checksum if size_old == size_new
146
+
147
+ # Check whether content was modified
148
+ is_modified = (size_old != size_new || hash_old != hash_new)
149
+
150
+ # Copy
151
+ if is_modified
152
+ FileUtils.cp(temporary_filenames[:last], raw_path)
153
+ end
154
+ else
155
+ # Check whether content was modified
156
+ is_modified = (!File.file?(raw_path) || File.read(raw_path) != @content[:last])
157
+
158
+ # Write
159
+ if is_modified
160
+ File.open(raw_path, 'w') { |io| io.write(@content[:last]) }
161
+ end
162
+ end
163
+
164
+ # Notify
165
+ Nanoc::NotificationCenter.post(:rep_written, self, raw_path, is_created, is_modified)
166
+ end
167
+
168
+ # Resets the compilation progress for this item representation. This is
169
+ # necessary when an unmet dependency is detected during compilation.
170
+ #
171
+ # @api private
172
+ #
173
+ # @return [void]
174
+ def forget_progress
175
+ initialize_content
176
+ end
177
+
178
+ # Returns the type of this object. Will always return `:item_rep`,
179
+ # because this is an item rep. For layouts, this method returns
180
+ # `:layout`.
181
+ #
182
+ # @api private
183
+ #
184
+ # @return [Symbol] :item_rep
185
+ def type
186
+ :item_rep
187
+ end
188
+
189
+ end
190
+
191
+ include Deprecated
192
+ include Private
193
+
194
+ # @return [Nanoc::Item] The item to which this rep belongs
195
+ attr_reader :item
196
+
197
+ # @return [Symbol] The representation's unique name
198
+ attr_reader :name
199
+
200
+ # @return [Boolean] true if this rep is currently binary; false otherwise
201
+ attr_reader :binary
202
+ alias_method :binary?, :binary
203
+
204
+ # @return [Array] A list of snapshots, represented as arrays where the
205
+ # first element is the snapshot name (a Symbol) and the last element is
206
+ # a Boolean indicating whether the snapshot is final or not
207
+ attr_accessor :snapshots
208
+
209
+ # Creates a new item representation for the given item.
210
+ #
211
+ # @param [Nanoc::Item] item The item to which the new representation will
212
+ # belong.
213
+ #
214
+ # @param [Symbol] name The unique name for the new item representation.
215
+ def initialize(item, name)
216
+ # Set primary attributes
217
+ @item = item
218
+ @name = name
219
+
220
+ # Set binary
221
+ @binary = @item.binary?
222
+
223
+ # Set default attributes
224
+ @raw_paths = {}
225
+ @paths = {}
226
+ @assigns = {}
227
+ @snapshots = []
228
+ initialize_content
229
+
230
+ # Reset flags
231
+ @compiled = false
232
+ end
233
+
234
+ # Returns the compiled content from a given snapshot.
235
+ #
236
+ # @option params [String] :snapshot The name of the snapshot from which to
237
+ # fetch the compiled content. By default, the returned compiled content
238
+ # will be the content compiled right before the first layout call (if
239
+ # any).
240
+ #
241
+ # @return [String] The compiled content at the given snapshot (or the
242
+ # default snapshot if no snapshot is specified)
243
+ def compiled_content(params={})
244
+ # Notify
245
+ Nanoc::NotificationCenter.post(:visit_started, self.item)
246
+ Nanoc::NotificationCenter.post(:visit_ended, self.item)
247
+
248
+ # Get name of last pre-layout snapshot
249
+ snapshot = params.fetch(:snapshot) { @content[:pre] ? :pre : :last }
250
+ is_moving = [ :pre, :post, :last ].include?(snapshot)
251
+
252
+ # Check existance of snapshot
253
+ if !is_moving && snapshots.find { |s| s.first == snapshot && s.last == true }.nil?
254
+ raise Nanoc::Errors::NoSuchSnapshot.new(self, snapshot)
255
+ end
256
+
257
+ # Require compilation
258
+ if @content[snapshot].nil? || (!self.compiled? && is_moving)
259
+ raise Nanoc::Errors::UnmetDependency.new(self)
260
+ else
261
+ @content[snapshot]
262
+ end
263
+ end
264
+
265
+ # Checks whether content exists at a given snapshot.
266
+ #
267
+ # @return [Boolean] True if content exists for the snapshot with the
268
+ # given name, false otherwise
269
+ #
270
+ # @since 3.2.0
271
+ def has_snapshot?(snapshot_name)
272
+ !@content[snapshot_name].nil?
273
+ end
274
+
275
+ # Returns the item rep’s raw path. It includes the path to the output
276
+ # directory and the full filename.
277
+ #
278
+ # @option params [Symbol] :snapshot (:last) The snapshot for which the
279
+ # path should be returned
280
+ #
281
+ # @return [String] The item rep’s path
282
+ def raw_path(params={})
283
+ Nanoc3::NotificationCenter.post(:visit_started, self.item)
284
+ Nanoc3::NotificationCenter.post(:visit_ended, self.item)
285
+
286
+ snapshot_name = params[:snapshot] || :last
287
+ @raw_paths[snapshot_name]
288
+ end
289
+
290
+ # Returns the item rep’s path, as used when being linked to. It starts
291
+ # with a slash and it is relative to the output directory. It does not
292
+ # include the path to the output directory. It will not include the
293
+ # filename if the filename is an index filename.
294
+ #
295
+ # @option params [Symbol] :snapshot (:last) The snapshot for which the
296
+ # path should be returned
297
+ #
298
+ # @return [String] The item rep’s path
299
+ def path(params={})
300
+ Nanoc3::NotificationCenter.post(:visit_started, self.item)
301
+ Nanoc3::NotificationCenter.post(:visit_ended, self.item)
302
+
303
+ snapshot_name = params[:snapshot] || :last
304
+ @paths[snapshot_name]
305
+ end
306
+
307
+ # Runs the item content through the given filter with the given arguments.
308
+ # This method will replace the content of the `:last` snapshot with the
309
+ # filtered content of the last snapshot.
310
+ #
311
+ # This method is supposed to be called only in a compilation rule block
312
+ # (see {Nanoc::CompilerDSL#compile}).
313
+ #
314
+ # @see Nanoc::ItemRepProxy#filter
315
+ #
316
+ # @param [Symbol] filter_name The name of the filter to run the item
317
+ # representations' content through
318
+ #
319
+ # @param [Hash] filter_args The filter arguments that should be passed to
320
+ # the filter's #run method
321
+ #
322
+ # @return [void]
323
+ def filter(filter_name, filter_args={})
324
+ # Get filter class
325
+ klass = filter_named(filter_name)
326
+ raise Nanoc::Errors::UnknownFilter.new(filter_name) if klass.nil?
327
+
328
+ # Check whether filter can be applied
329
+ if klass.from_binary? && !self.binary?
330
+ raise Nanoc::Errors::CannotUseBinaryFilter.new(self, klass)
331
+ elsif !klass.from_binary? && self.binary?
332
+ raise Nanoc::Errors::CannotUseTextualFilter.new(self, klass)
333
+ end
334
+
335
+ begin
336
+ # Notify start
337
+ Nanoc::NotificationCenter.post(:filtering_started, self, filter_name)
338
+
339
+ # Create filter
340
+ filter = klass.new(assigns)
341
+
342
+ # Run filter
343
+ source = self.binary? ? temporary_filenames[:last] : @content[:last]
344
+ result = filter.run(source, filter_args)
345
+ if klass.to_binary?
346
+ temporary_filenames[:last] = filter.output_filename
347
+ else
348
+ @content[:last] = result
349
+ @content[:last].freeze
350
+ end
351
+ @binary = klass.to_binary?
352
+
353
+ # Check whether file was written
354
+ if self.binary? && !File.file?(filter.output_filename)
355
+ raise RuntimeError,
356
+ "The #{filter_name.inspect} filter did not write anything to the required output file, #{filter.output_filename}."
357
+ end
358
+
359
+ # Create snapshot
360
+ snapshot(@content[:post] ? :post : :pre, :final => false) unless self.binary?
361
+ ensure
362
+ # Notify end
363
+ Nanoc::NotificationCenter.post(:filtering_ended, self, filter_name)
364
+ end
365
+ end
366
+
367
+ # Lays out the item using the given layout. This method will replace the
368
+ # content of the `:last` snapshot with the laid out content of the last
369
+ # snapshot.
370
+ #
371
+ # This method is supposed to be called only in a compilation rule block
372
+ # (see {Nanoc::CompilerDSL#compile}).
373
+ #
374
+ # @see Nanoc::ItemRepProxy#layout
375
+ #
376
+ # @param [Nanoc::Layout] layout The layout to use
377
+ #
378
+ # @param [Symbol] filter_name The name of the filter to layout the item
379
+ # representations' content with
380
+ #
381
+ # @param [Hash] filter_args The filter arguments that should be passed to
382
+ # the filter's #run method
383
+ #
384
+ # @return [void]
385
+ def layout(layout, filter_name, filter_args)
386
+ # Check whether item can be laid out
387
+ raise Nanoc::Errors::CannotLayoutBinaryItem.new(self) if self.binary?
388
+
389
+ # Create "pre" snapshot
390
+ if @content[:post].nil?
391
+ snapshot(:pre, :final => true)
392
+ end
393
+
394
+ # Create filter
395
+ klass = filter_named(filter_name)
396
+ raise Nanoc::Errors::UnknownFilter.new(filter_name) if klass.nil?
397
+ filter = klass.new(assigns.merge({ :layout => layout }))
398
+
399
+ # Visit
400
+ Nanoc::NotificationCenter.post(:visit_started, layout)
401
+ Nanoc::NotificationCenter.post(:visit_ended, layout)
402
+
403
+ begin
404
+ # Notify start
405
+ Nanoc::NotificationCenter.post(:processing_started, layout)
406
+ Nanoc::NotificationCenter.post(:filtering_started, self, filter_name)
407
+
408
+ # Layout
409
+ @content[:last] = filter.run(layout.raw_content, filter_args)
410
+
411
+ # Create "post" snapshot
412
+ snapshot(:post, :final => false)
413
+ ensure
414
+ # Notify end
415
+ Nanoc::NotificationCenter.post(:filtering_ended, self, filter_name)
416
+ Nanoc::NotificationCenter.post(:processing_ended, layout)
417
+ end
418
+ end
419
+
420
+ # Creates a snapshot of the current compiled item content.
421
+ #
422
+ # @param [Symbol] snapshot_name The name of the snapshot to create
423
+ #
424
+ # @option params [Boolean] :final (true) True if this is the final time
425
+ # the snapshot will be updated; false if it is a non-final moving
426
+ # snapshot (such as `:pre`, `:post` or `:last`)
427
+ #
428
+ # @return [void]
429
+ def snapshot(snapshot_name, params={})
430
+ is_final = params.fetch(:final) { true }
431
+ @content[snapshot_name] = @content[:last] unless self.binary?
432
+ self.write(snapshot_name) if is_final
433
+ end
434
+
435
+ # Returns a recording proxy that is used for determining whether the
436
+ # compilation has changed, and thus whether the item rep needs to be
437
+ # recompiled.
438
+ #
439
+ # @api private
440
+ #
441
+ # @return [Nanoc::ItemRepRecorderProxy] The recording proxy
442
+ def to_recording_proxy
443
+ Nanoc::ItemRepRecorderProxy.new(self)
444
+ end
445
+
446
+ # Returns false because this item is not yet a proxy, and therefore does
447
+ # need to be wrapped in a proxy during compilation.
448
+ #
449
+ # @api private
450
+ #
451
+ # @return [false]
452
+ #
453
+ # @see Nanoc::ItemRepRecorderProxy#is_proxy?
454
+ # @see Nanoc::ItemRepProxy#is_proxy?
455
+ def is_proxy?
456
+ false
457
+ end
458
+
459
+ # Returns an object that can be used for uniquely identifying objects.
460
+ #
461
+ # @api private
462
+ #
463
+ # @return [Object] An unique reference to this object
464
+ def reference
465
+ [ type, self.item.identifier, self.name ]
466
+ end
467
+
468
+ def inspect
469
+ "<#{self.class} name=#{self.name} binary=#{self.binary?} raw_path=#{self.raw_path} item.identifier=#{self.item.identifier}>"
470
+ end
471
+
472
+ private
473
+
474
+ def initialize_content
475
+ # Initialize content and filenames
476
+ if self.binary?
477
+ @temporary_filenames = { :last => @item.raw_filename }
478
+ @content = {}
479
+ else
480
+ @content = { :last => @item.raw_content }
481
+ @content[:last].freeze
482
+ @temporary_filenames = {}
483
+ end
484
+ end
485
+
486
+ def filter_named(name)
487
+ Nanoc::Filter.named(name)
488
+ end
489
+
490
+ end
491
+
492
+ end