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