nanoc 3.2.4 → 3.3.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 (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,20 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubypants'
4
+
5
+ module Nanoc::Filters
6
+ class RubyPants < Nanoc::Filter
7
+
8
+ # Runs the content through [RubyPants](http://chneukirchen.org/blog/static/projects/rubypants.html).
9
+ # This method takes no options.
10
+ #
11
+ # @param [String] content The content to filter
12
+ #
13
+ # @return [String] The filtered content
14
+ def run(content, params={})
15
+ # Get result
16
+ ::RubyPants.new(content).to_html
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,74 @@
1
+ # encoding: utf-8
2
+
3
+ require 'sass'
4
+ require 'set'
5
+
6
+ module Nanoc::Filters
7
+ class Sass < Nanoc::Filter
8
+
9
+ class << self
10
+ # The current filter. This is definitely going to bite me if I ever get
11
+ # to multithreading nanoc.
12
+ attr_accessor :current
13
+ end
14
+
15
+ # Essentially the `Sass::Importers::Filesystem` but registering each
16
+ # import file path.
17
+ class SassFilesystemImporter < ::Sass::Importers::Filesystem
18
+
19
+ private
20
+
21
+ def _find(dir, name, options)
22
+ full_filename, syntax = find_real_file(dir, name)
23
+ return unless full_filename && File.readable?(full_filename)
24
+
25
+ filter = Nanoc::Filters::Sass.current
26
+ item = filter.imported_filename_to_item(full_filename)
27
+ filter.depend_on([ item ]) unless item.nil?
28
+
29
+ options[:syntax] = syntax
30
+ options[:filename] = full_filename
31
+ options[:importer] = self
32
+ ::Sass::Engine.new(File.read(full_filename), options)
33
+ end
34
+ end
35
+
36
+ # Runs the content through [Sass](http://sass-lang.com/).
37
+ # Parameters passed to this filter will be passed on to Sass.
38
+ #
39
+ # @param [String] content The content to filter
40
+ #
41
+ # @return [String] The filtered content
42
+ def run(content, params={})
43
+ # Build options
44
+ options = params.dup
45
+ sass_filename = options[:filename] ||
46
+ (@item && @item[:content_filename])
47
+ options[:filename] ||= sass_filename
48
+ options[:filesystem_importer] ||=
49
+ Nanoc::Filters::Sass::SassFilesystemImporter
50
+
51
+ # Find items
52
+ item_dirglob = Pathname.new(sass_filename).dirname.realpath.to_s + '**'
53
+ clean_items = @items.reject { |i| i[:content_filename].nil? }
54
+ @scoped_items, @rest_items = clean_items.partition do |i|
55
+ i[:content_filename] &&
56
+ Pathname.new(i[:content_filename]).realpath.fnmatch(item_dirglob)
57
+ end
58
+
59
+ # Render
60
+ engine = ::Sass::Engine.new(content, options)
61
+ self.class.current = self
62
+ engine.render
63
+ end
64
+
65
+ def imported_filename_to_item(filename)
66
+ filematch = lambda do |i|
67
+ i[:content_filename] &&
68
+ Pathname.new(i[:content_filename]).realpath == Pathname.new(filename).realpath
69
+ end
70
+ @scoped_items.find(&filematch) || @rest_items.find(&filematch)
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+
3
+ require 'slim'
4
+
5
+ module Nanoc::Filters
6
+
7
+ # @since 3.2.0
8
+ class Slim < Nanoc::Filter
9
+
10
+ # Runs the content through [Slim](http://slim-lang.com/)
11
+ # This method takes no options.
12
+ #
13
+ # @param [String] content The content to filter
14
+ #
15
+ # @return [String] The filtered content
16
+ def run(content, params={})
17
+ # Create context
18
+ context = ::Nanoc::Context.new(assigns)
19
+
20
+ ::Slim::Template.new { content }.render(context) { assigns[:content] }
21
+ end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+
3
+ require 'typogruby'
4
+
5
+ module Nanoc::Filters
6
+
7
+ # @since 3.2.0
8
+ class Typogruby < Nanoc::Filter
9
+
10
+ # Runs the content through [Typogruby](http://avdgaag.github.com/typogruby/).
11
+ # This method takes no options.
12
+ #
13
+ # @param [String] content The content to filter
14
+ #
15
+ # @return [String] The filtered content
16
+ def run(content, params={})
17
+ # Get result
18
+ ::Typogruby.improve(content)
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+
3
+ require 'uglifier'
4
+
5
+ module Nanoc::Filters
6
+ class UglifyJS < Nanoc::Filter
7
+
8
+ # Runs the content through [UglifyJS](https://github.com/mishoo/UglifyJS/).
9
+ # This method optionally takes options to pass directly to Uglifier:
10
+ #
11
+ # {
12
+ # :mangle => true, # Mangle variables names
13
+ # :toplevel => false, # Mangle top-level variable names
14
+ # :except => [], # Variable names to be excluded from mangling
15
+ # :max_line_length => 32 * 1024, # Maximum line length
16
+ # :squeeze => true, # Squeeze code resulting in smaller, but less-readable code
17
+ # :seqs => true, # Reduce consecutive statements in blocks into single statement
18
+ # :dead_code => true, # Remove dead code (e.g. after return)
19
+ # :unsafe => false, # Optimizations known to be unsafe in some situations
20
+ # :copyright => true, # Show copyright message
21
+ # :beautify => false, # Ouput indented code
22
+ # :beautify_options => {
23
+ # :indent_level => 4,
24
+ # :indent_start => 0,
25
+ # :quote_keys => false,
26
+ # :space_colon => 0,
27
+ # :ascii_only => false
28
+ # }
29
+ # }
30
+ #
31
+ # @param [String] content The content to filter
32
+ #
33
+ # @option params [Array] :options ([]) A list of options to pass on to Uglifier
34
+ #
35
+ # @return [String] The filtered content
36
+ def run(content, params={})
37
+ # Add filename to load path
38
+ Uglifier.new(params).compile(content)
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,46 @@
1
+ # encoding: utf-8
2
+ require 'nokogiri'
3
+
4
+ module Nanoc::Filters
5
+
6
+ # @since 3.3.0
7
+ class XSL < Nanoc::Filter
8
+
9
+ # Runs the item content through an [XSLT](http://www.w3.org/TR/xslt)
10
+ # stylesheet using [Nokogiri](http://nokogiri.org/).
11
+ #
12
+ # This filter can only be run for layouts, because it will need both the
13
+ # XML to convert (= the item content) as well as the XSLT stylesheet (=
14
+ # the layout content).
15
+ #
16
+ # Additional parameters can be passed to the layout call. These parameters
17
+ # will be turned into `xsl:param` elements.
18
+ #
19
+ # @example Invoking the filter as a layout
20
+ #
21
+ # compile '/reports/*/' do
22
+ # layout 'xsl-report'
23
+ # end
24
+ #
25
+ # layout 'xsl-report', :xsl, :awesome => 'definitely'
26
+ #
27
+ # @param [String] content The XML content to transform
28
+ #
29
+ # @param [Hash] params The parameters that will be stored in corresponding
30
+ # `xsl:param` elements.
31
+ #
32
+ # @return [String] The transformed content
33
+ def run(content, params={})
34
+ if assigns[:layout].nil?
35
+ raise "The XSL filter can only be run as a layout"
36
+ end
37
+
38
+ xml = ::Nokogiri::XML(content)
39
+ xsl = ::Nokogiri::XSLT(assigns[:content])
40
+
41
+ xsl.transform(xml, ::Nokogiri::XSLT.quote_params(params)).to_s
42
+ end
43
+
44
+ end
45
+
46
+ end
@@ -0,0 +1,23 @@
1
+ require 'yuicompressor'
2
+
3
+ module Nanoc::Filters
4
+
5
+ # @since 3.3.0
6
+ class YUICompressor < Nanoc::Filter
7
+
8
+ # Compress Javascript or CSS using [YUICompressor](http://rubydoc.info/gems/yuicompressor).
9
+ # This method optionally takes options to pass directly to the
10
+ # YUICompressor gem.
11
+ #
12
+ # @param [String] content JavaScript or CSS input
13
+ #
14
+ # @param [Hash] params Options passed to YUICompressor
15
+ #
16
+ # @return [String] Compressed but equivalent JavaScript or CSS
17
+ def run(content, params = {})
18
+ ::YUICompressor.compress(content, params)
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+
3
+ module Nanoc::Helpers
4
+
5
+ autoload 'Blogging', 'nanoc/helpers/blogging'
6
+ autoload 'Breadcrumbs', 'nanoc/helpers/breadcrumbs'
7
+ autoload 'Capturing', 'nanoc/helpers/capturing'
8
+ autoload 'Filtering', 'nanoc/helpers/filtering'
9
+ autoload 'HTMLEscape', 'nanoc/helpers/html_escape'
10
+ autoload 'LinkTo', 'nanoc/helpers/link_to'
11
+ autoload 'Rendering', 'nanoc/helpers/rendering'
12
+ autoload 'Tagging', 'nanoc/helpers/tagging'
13
+ autoload 'Text', 'nanoc/helpers/text'
14
+ autoload 'XMLSitemap', 'nanoc/helpers/xml_sitemap'
15
+
16
+ end
@@ -0,0 +1,319 @@
1
+ # encoding: utf-8
2
+
3
+ module Nanoc::Helpers
4
+
5
+ # Provides functionality for building blogs, such as finding articles and
6
+ # constructing feeds.
7
+ #
8
+ # This helper has a few requirements. First, all blog articles should have
9
+ # the following attributes:
10
+ #
11
+ # * `kind` - Set to `"article"`
12
+ #
13
+ # * `created_at` - The article's publication timestamp
14
+ #
15
+ # Some functions in this blogging helper, such as the {#atom_feed} function,
16
+ # require additional attributes to be set; these attributes are described in
17
+ # the documentation for these functions.
18
+ #
19
+ # All "time" item attributes, site configuration attributes or method
20
+ # parameters can either be a `Time` instance or a string in any format
21
+ # parseable by `Time.parse`.
22
+ #
23
+ # The two main functions are {#sorted_articles} and {#atom_feed}.
24
+ module Blogging
25
+
26
+ # Returns an unsorted list of articles, i.e. items where the `kind`
27
+ # attribute is set to `"article"`.
28
+ #
29
+ # @return [Array] An array containing all articles
30
+ def articles
31
+ @items.select { |item| item[:kind] == 'article' }
32
+ end
33
+
34
+ # Returns a sorted list of articles, i.e. items where the `kind`
35
+ # attribute is set to `"article"`. Articles are sorted by descending
36
+ # creation date, so newer articles appear before older articles.
37
+ #
38
+ # @return [Array] A sorted array containing all articles
39
+ def sorted_articles
40
+ articles.sort_by do |a|
41
+ attribute_to_time(a[:created_at])
42
+ end.reverse
43
+ end
44
+
45
+ # Returns a string representing the atom feed containing recent articles,
46
+ # sorted by descending creation date.
47
+ #
48
+ # The following attributes must be set on blog articles:
49
+ #
50
+ # * `title` - The title of the blog post
51
+ #
52
+ # * `kind` and `created_at` (described above)
53
+ #
54
+ # The following attributes can optionally be set on blog articles to
55
+ # change the behaviour of the Atom feed:
56
+ #
57
+ # * `excerpt` - An excerpt of the article, which is usually only a few
58
+ # lines long.
59
+ #
60
+ # * `custom_path_in_feed` - The path that will be used instead of the
61
+ # normal path in the feed. This can be useful when including
62
+ # non-outputted items in a feed; such items could have their custom feed
63
+ # path set to the blog path instead, for example.
64
+ #
65
+ # * `custom_url_in_feed` - The url that will be used instead of the
66
+ # normal url in the feed (generated from the site's base url + the item
67
+ # rep's path). This can be useful when building a link-blog where the
68
+ # URL of article is a remote location.
69
+ #
70
+ # * `updated_at` - The time when the article was last modified. If this
71
+ # attribute is not present, the `created_at` attribute will be used as
72
+ # the time when the article was last modified.
73
+ #
74
+ # The site configuration will need to have the following attributes:
75
+ #
76
+ # * `base_url` - The URL to the site, without trailing slash. For
77
+ # example, if the site is at "http://example.com/", the `base_url`
78
+ # would be "http://example.com".
79
+ #
80
+ # The feed item will need to know about the feed title, the feed author
81
+ # name, and the URI corresponding to the author. These can be specified
82
+ # using parameters, as attributes in the feed item, or in the site
83
+ # configuration.
84
+ #
85
+ # * `title` - The title of the feed, which is usually also the title of
86
+ # the blog.
87
+ #
88
+ # * `author_name` - The name of the item's author.
89
+ #
90
+ # * `author_uri` - The URI for the item's author, such as the author's
91
+ # web site URL.
92
+ #
93
+ # The feed item can have the following optional attributes:
94
+ #
95
+ # * `feed_url` - The custom URL of the feed. This can be useful when the
96
+ # private feed URL shouldn't be exposed; for example, when using
97
+ # FeedBurner this would be set to the public FeedBurner URL.
98
+ #
99
+ # To construct a feed, create a new item and make sure that it is
100
+ # filtered with `:erb` or `:erubis`; it should not be laid out. Ensure
101
+ # that it is routed to the proper path, e.g. `/blog.xml`. It may also be
102
+ # useful to set the `is_hidden` attribute to true, so that helpers such
103
+ # as the sitemap helper will ignore the item. The content of the feed
104
+ # item should be `<%= atom_feed %>`.
105
+ #
106
+ # @example Defining compilation and routing rules for a feed item
107
+ #
108
+ # compile '/blog/feed/' do
109
+ # filter :erb
110
+ # end
111
+ #
112
+ # route '/blog/feed/' do
113
+ # '/blog.xml'
114
+ # end
115
+ #
116
+ # @example Limiting the number of items in a feed
117
+ #
118
+ # <%= atom_feed :limit => 5 %>
119
+ #
120
+ # @option params [Number] :limit (5) The maximum number of articles to
121
+ # show
122
+ #
123
+ # @option params [Array] :articles (sorted_articles) A list of articles to
124
+ # include in the feed
125
+ #
126
+ # @option params [Proc] :content_proc (->{ |article|
127
+ # article.compiled_content(:snapshot => :pre) }) A proc that returns the
128
+ # content of the given article, which is passed as a parameter. This
129
+ # function may not return nil.
130
+ #
131
+ # @option params [proc] :excerpt_proc (->{ |article| article[:excerpt] })
132
+ # A proc that returns the excerpt of the given article, passed as a
133
+ # parameter. This function should return nil if there is no excerpt.
134
+ #
135
+ # @option params [String] :title The feed's title, if it is not given in
136
+ # the item attributes.
137
+ #
138
+ # @option params [String] :author_name The name of the feed's author, if
139
+ # it is not given in the item attributes.
140
+ #
141
+ # @option params [String] :author_uri The URI of the feed's author, if it
142
+ # is not given in the item attributes.
143
+ #
144
+ # @return [String] The generated feed content
145
+ def atom_feed(params={})
146
+ require 'builder'
147
+
148
+ # Extract parameters
149
+ limit = params[:limit] || 5
150
+ relevant_articles = params[:articles] || articles || []
151
+ content_proc = params[:content_proc] || lambda { |a| a.compiled_content(:snapshot => :pre) }
152
+ excerpt_proc = params[:excerpt_proc] || lambda { |a| a[:excerpt] }
153
+
154
+ # Check config attributes
155
+ if @site.config[:base_url].nil?
156
+ raise RuntimeError.new('Cannot build Atom feed: site configuration has no base_url')
157
+ end
158
+
159
+ # Check feed item attributes
160
+ title = params[:title] || @item[:title] || @site.config[:title]
161
+ if title.nil?
162
+ raise RuntimeError.new('Cannot build Atom feed: no title in params, item or site config')
163
+ end
164
+ author_name = params[:author_name] || @item[:author_name] || @site.config[:author_name]
165
+ if author_name.nil?
166
+ raise RuntimeError.new('Cannot build Atom feed: no author_name in params, item or site config')
167
+ end
168
+ author_uri = params[:author_uri] || @item[:author_uri] || @site.config[:author_uri]
169
+ if author_uri.nil?
170
+ raise RuntimeError.new('Cannot build Atom feed: no author_uri in params, item or site config')
171
+ end
172
+
173
+ # Check article attributes
174
+ if relevant_articles.empty?
175
+ raise RuntimeError.new('Cannot build Atom feed: no articles')
176
+ end
177
+ if relevant_articles.any? { |a| a[:created_at].nil? }
178
+ raise RuntimeError.new('Cannot build Atom feed: one or more articles lack created_at')
179
+ end
180
+
181
+ # Get sorted relevant articles
182
+ sorted_relevant_articles = relevant_articles.sort_by do |a|
183
+ attribute_to_time(a[:created_at])
184
+ end.reverse.first(limit)
185
+
186
+ # Get most recent article
187
+ last_article = sorted_relevant_articles.first
188
+
189
+ # Create builder
190
+ buffer = ''
191
+ xml = Builder::XmlMarkup.new(:target => buffer, :indent => 2)
192
+
193
+ # Build feed
194
+ xml.instruct!
195
+ xml.feed(:xmlns => 'http://www.w3.org/2005/Atom') do
196
+ root_url = @site.config[:base_url] + '/'
197
+
198
+ # Add primary attributes
199
+ xml.id root_url
200
+ xml.title title
201
+
202
+ # Add date
203
+ xml.updated(attribute_to_time(last_article[:created_at]).to_iso8601_time)
204
+
205
+ # Add links
206
+ xml.link(:rel => 'alternate', :href => root_url)
207
+ xml.link(:rel => 'self', :href => feed_url)
208
+
209
+ # Add author information
210
+ xml.author do
211
+ xml.name author_name
212
+ xml.uri author_uri
213
+ end
214
+
215
+ # Add articles
216
+ sorted_relevant_articles.each do |a|
217
+ # Get URL
218
+ url = url_for(a)
219
+ next if url.nil?
220
+
221
+ xml.entry do
222
+ # Add primary attributes
223
+ xml.id atom_tag_for(a)
224
+ xml.title a[:title], :type => 'html'
225
+
226
+ # Add dates
227
+ xml.published attribute_to_time(a[:created_at]).to_iso8601_time
228
+ xml.updated attribute_to_time(a[:updated_at] || a[:created_at]).to_iso8601_time
229
+
230
+ # Add specific author information
231
+ if a[:author_name] || a[:author_uri]
232
+ xml.author do
233
+ xml.name a[:author_name] || author_name
234
+ xml.uri a[:author_uri] || author_uri
235
+ end
236
+ end
237
+
238
+ # Add link
239
+ xml.link(:rel => 'alternate', :href => url)
240
+
241
+ # Add content
242
+ summary = excerpt_proc.call(a)
243
+ xml.content content_proc.call(a), :type => 'html'
244
+ xml.summary summary, :type => 'html' unless summary.nil?
245
+ end
246
+ end
247
+ end
248
+
249
+ buffer
250
+ end
251
+
252
+ # Returns the URL for the given item. It will return the URL containing
253
+ # the custom path in the feed if possible, otherwise the normal path.
254
+ #
255
+ # @param [Nanoc::Item] item The item for which to fetch the URL.
256
+ #
257
+ # @return [String] The URL of the given item
258
+ def url_for(item)
259
+ # Check attributes
260
+ if @site.config[:base_url].nil?
261
+ raise RuntimeError.new('Cannot build Atom feed: site configuration has no base_url')
262
+ end
263
+
264
+ # Build URL
265
+ if item[:custom_url_in_feed]
266
+ item[:custom_url_in_feed]
267
+ elsif item[:custom_path_in_feed]
268
+ @site.config[:base_url] + item[:custom_path_in_feed]
269
+ elsif item.path
270
+ @site.config[:base_url] + item.path
271
+ end
272
+ end
273
+
274
+ # Returns the URL of the feed. It will return the custom feed URL if set,
275
+ # or otherwise the normal feed URL.
276
+ #
277
+ # @return [String] The URL of the feed
278
+ def feed_url
279
+ # Check attributes
280
+ if @site.config[:base_url].nil?
281
+ raise RuntimeError.new('Cannot build Atom feed: site configuration has no base_url')
282
+ end
283
+
284
+ @item[:feed_url] || @site.config[:base_url] + @item.path
285
+ end
286
+
287
+ # Returns an URI containing an unique ID for the given item. This will be
288
+ # used in the Atom feed to uniquely identify articles. These IDs are
289
+ # created using a procedure suggested by Mark Pilgrim and described in his
290
+ # ["How to make a good ID in Atom" blog post]
291
+ # (http://diveintomark.org/archives/2004/05/28/howto-atom-id).
292
+ #
293
+ # @param [Nanoc::Item] item The item for which to create an atom tag
294
+ #
295
+ # @return [String] The atom tag for the given item
296
+ def atom_tag_for(item)
297
+ hostname, base_dir = %r{^.+?://([^/]+)(.*)$}.match(@site.config[:base_url])[1..2]
298
+
299
+ formatted_date = attribute_to_time(item[:created_at]).to_iso8601_date
300
+
301
+ 'tag:' + hostname + ',' + formatted_date + ':' + base_dir + (item.path || item.identifier)
302
+ end
303
+
304
+ # Converts the given attribute (which can be a string, a Time or a Date)
305
+ # into a Time.
306
+ #
307
+ # @param [String, Time, Date] time Something that contains time
308
+ # information but is not necessarily a Time instance yet
309
+ #
310
+ # @return [Time] The Time instance corresponding to the given input
311
+ def attribute_to_time(time)
312
+ time = Time.local(time.year, time.month, time.day) if time.is_a?(Date)
313
+ time = Time.parse(time) if time.is_a?(String)
314
+ time
315
+ end
316
+
317
+ end
318
+
319
+ end