nanoc2 2.2.3

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 (100) hide show
  1. data/ChangeLog +3 -0
  2. data/LICENSE +19 -0
  3. data/README +75 -0
  4. data/Rakefile +76 -0
  5. data/bin/nanoc2 +26 -0
  6. data/lib/nanoc2.rb +73 -0
  7. data/lib/nanoc2/base.rb +26 -0
  8. data/lib/nanoc2/base/asset.rb +117 -0
  9. data/lib/nanoc2/base/asset_defaults.rb +21 -0
  10. data/lib/nanoc2/base/asset_rep.rb +282 -0
  11. data/lib/nanoc2/base/binary_filter.rb +44 -0
  12. data/lib/nanoc2/base/code.rb +41 -0
  13. data/lib/nanoc2/base/compiler.rb +67 -0
  14. data/lib/nanoc2/base/core_ext.rb +2 -0
  15. data/lib/nanoc2/base/core_ext/hash.rb +78 -0
  16. data/lib/nanoc2/base/core_ext/string.rb +8 -0
  17. data/lib/nanoc2/base/data_source.rb +286 -0
  18. data/lib/nanoc2/base/defaults.rb +30 -0
  19. data/lib/nanoc2/base/filter.rb +93 -0
  20. data/lib/nanoc2/base/layout.rb +91 -0
  21. data/lib/nanoc2/base/notification_center.rb +66 -0
  22. data/lib/nanoc2/base/page.rb +132 -0
  23. data/lib/nanoc2/base/page_defaults.rb +20 -0
  24. data/lib/nanoc2/base/page_rep.rb +324 -0
  25. data/lib/nanoc2/base/plugin.rb +71 -0
  26. data/lib/nanoc2/base/proxies.rb +5 -0
  27. data/lib/nanoc2/base/proxies/asset_proxy.rb +29 -0
  28. data/lib/nanoc2/base/proxies/asset_rep_proxy.rb +26 -0
  29. data/lib/nanoc2/base/proxies/layout_proxy.rb +25 -0
  30. data/lib/nanoc2/base/proxies/page_proxy.rb +35 -0
  31. data/lib/nanoc2/base/proxies/page_rep_proxy.rb +28 -0
  32. data/lib/nanoc2/base/proxy.rb +37 -0
  33. data/lib/nanoc2/base/router.rb +72 -0
  34. data/lib/nanoc2/base/site.rb +274 -0
  35. data/lib/nanoc2/base/template.rb +64 -0
  36. data/lib/nanoc2/binary_filters.rb +1 -0
  37. data/lib/nanoc2/binary_filters/image_science_thumbnail.rb +28 -0
  38. data/lib/nanoc2/cli.rb +9 -0
  39. data/lib/nanoc2/cli/base.rb +132 -0
  40. data/lib/nanoc2/cli/commands.rb +10 -0
  41. data/lib/nanoc2/cli/commands/autocompile.rb +80 -0
  42. data/lib/nanoc2/cli/commands/compile.rb +312 -0
  43. data/lib/nanoc2/cli/commands/create_layout.rb +85 -0
  44. data/lib/nanoc2/cli/commands/create_page.rb +85 -0
  45. data/lib/nanoc2/cli/commands/create_site.rb +323 -0
  46. data/lib/nanoc2/cli/commands/create_template.rb +76 -0
  47. data/lib/nanoc2/cli/commands/help.rb +69 -0
  48. data/lib/nanoc2/cli/commands/info.rb +125 -0
  49. data/lib/nanoc2/cli/commands/switch.rb +141 -0
  50. data/lib/nanoc2/cli/commands/update.rb +91 -0
  51. data/lib/nanoc2/cli/logger.rb +72 -0
  52. data/lib/nanoc2/data_sources.rb +2 -0
  53. data/lib/nanoc2/data_sources/filesystem.rb +707 -0
  54. data/lib/nanoc2/data_sources/filesystem_combined.rb +495 -0
  55. data/lib/nanoc2/extra.rb +6 -0
  56. data/lib/nanoc2/extra/auto_compiler.rb +285 -0
  57. data/lib/nanoc2/extra/context.rb +22 -0
  58. data/lib/nanoc2/extra/core_ext.rb +2 -0
  59. data/lib/nanoc2/extra/core_ext/hash.rb +54 -0
  60. data/lib/nanoc2/extra/core_ext/time.rb +13 -0
  61. data/lib/nanoc2/extra/file_proxy.rb +29 -0
  62. data/lib/nanoc2/extra/vcs.rb +48 -0
  63. data/lib/nanoc2/extra/vcses.rb +5 -0
  64. data/lib/nanoc2/extra/vcses/bazaar.rb +21 -0
  65. data/lib/nanoc2/extra/vcses/dummy.rb +20 -0
  66. data/lib/nanoc2/extra/vcses/git.rb +21 -0
  67. data/lib/nanoc2/extra/vcses/mercurial.rb +21 -0
  68. data/lib/nanoc2/extra/vcses/subversion.rb +21 -0
  69. data/lib/nanoc2/filters.rb +16 -0
  70. data/lib/nanoc2/filters/bluecloth.rb +13 -0
  71. data/lib/nanoc2/filters/erb.rb +19 -0
  72. data/lib/nanoc2/filters/erubis.rb +14 -0
  73. data/lib/nanoc2/filters/haml.rb +21 -0
  74. data/lib/nanoc2/filters/markaby.rb +14 -0
  75. data/lib/nanoc2/filters/maruku.rb +14 -0
  76. data/lib/nanoc2/filters/old.rb +19 -0
  77. data/lib/nanoc2/filters/rainpress.rb +13 -0
  78. data/lib/nanoc2/filters/rdiscount.rb +13 -0
  79. data/lib/nanoc2/filters/rdoc.rb +23 -0
  80. data/lib/nanoc2/filters/redcloth.rb +14 -0
  81. data/lib/nanoc2/filters/relativize_paths.rb +16 -0
  82. data/lib/nanoc2/filters/relativize_paths_in_css.rb +16 -0
  83. data/lib/nanoc2/filters/relativize_paths_in_html.rb +16 -0
  84. data/lib/nanoc2/filters/rubypants.rb +14 -0
  85. data/lib/nanoc2/filters/sass.rb +18 -0
  86. data/lib/nanoc2/helpers.rb +9 -0
  87. data/lib/nanoc2/helpers/blogging.rb +217 -0
  88. data/lib/nanoc2/helpers/capturing.rb +63 -0
  89. data/lib/nanoc2/helpers/filtering.rb +54 -0
  90. data/lib/nanoc2/helpers/html_escape.rb +25 -0
  91. data/lib/nanoc2/helpers/link_to.rb +113 -0
  92. data/lib/nanoc2/helpers/render.rb +49 -0
  93. data/lib/nanoc2/helpers/tagging.rb +56 -0
  94. data/lib/nanoc2/helpers/text.rb +38 -0
  95. data/lib/nanoc2/helpers/xml_sitemap.rb +63 -0
  96. data/lib/nanoc2/routers.rb +3 -0
  97. data/lib/nanoc2/routers/default.rb +54 -0
  98. data/lib/nanoc2/routers/no_dirs.rb +66 -0
  99. data/lib/nanoc2/routers/versioned.rb +79 -0
  100. metadata +185 -0
@@ -0,0 +1,5 @@
1
+ require 'nanoc2/extra/vcses/bazaar'
2
+ require 'nanoc2/extra/vcses/dummy'
3
+ require 'nanoc2/extra/vcses/git'
4
+ require 'nanoc2/extra/vcses/mercurial'
5
+ require 'nanoc2/extra/vcses/subversion'
@@ -0,0 +1,21 @@
1
+ module Nanoc2::Extra::VCSes
2
+
3
+ class Bazaar < Nanoc2::Extra::VCS
4
+
5
+ identifiers :bazaar, :bzr
6
+
7
+ def add(filename)
8
+ system('bzr', 'add', filename)
9
+ end
10
+
11
+ def remove(filename)
12
+ system('bzr', 'rm', filename)
13
+ end
14
+
15
+ def move(src, dst)
16
+ system('bzr', 'mv', src, dst)
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,20 @@
1
+ module Nanoc2::Extra::VCSes
2
+
3
+ class Dummy < Nanoc2::Extra::VCS
4
+
5
+ identifiers :dummy
6
+
7
+ def add(filename)
8
+ end
9
+
10
+ def remove(filename)
11
+ FileUtils.rm_rf(filename)
12
+ end
13
+
14
+ def move(src, dst)
15
+ FileUtils.move(src, dst)
16
+ end
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,21 @@
1
+ module Nanoc2::Extra::VCSes
2
+
3
+ class Git < Nanoc2::Extra::VCS
4
+
5
+ identifiers :git
6
+
7
+ def add(filename)
8
+ system('git', 'add', filename)
9
+ end
10
+
11
+ def remove(filename)
12
+ system('git', 'rm', filename)
13
+ end
14
+
15
+ def move(src, dst)
16
+ system('git', 'mv', src, dst)
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,21 @@
1
+ module Nanoc2::Extra::VCSes
2
+
3
+ class Mercurial < Nanoc2::Extra::VCS
4
+
5
+ identifiers :mercurial, :hg
6
+
7
+ def add(filename)
8
+ system('hg', 'add', filename)
9
+ end
10
+
11
+ def remove(filename)
12
+ system('hg', 'rm', filename)
13
+ end
14
+
15
+ def move(src, dst)
16
+ system('hg', 'mv', src, dst)
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,21 @@
1
+ module Nanoc2::Extra::VCSes
2
+
3
+ class Subversion < Nanoc2::Extra::VCS
4
+
5
+ identifiers :subversion, :svn
6
+
7
+ def add(filename)
8
+ system('svn', 'add', filename)
9
+ end
10
+
11
+ def remove(filename)
12
+ system('svn', 'rm', filename)
13
+ end
14
+
15
+ def move(src, dst)
16
+ system('svn', 'mv', src, dst)
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,16 @@
1
+ require 'nanoc2/filters/bluecloth'
2
+ require 'nanoc2/filters/erb'
3
+ require 'nanoc2/filters/erubis'
4
+ require 'nanoc2/filters/haml'
5
+ require 'nanoc2/filters/markaby'
6
+ require 'nanoc2/filters/maruku'
7
+ require 'nanoc2/filters/old'
8
+ require 'nanoc2/filters/rainpress'
9
+ require 'nanoc2/filters/rdiscount'
10
+ require 'nanoc2/filters/rdoc'
11
+ require 'nanoc2/filters/redcloth'
12
+ require 'nanoc2/filters/relativize_paths'
13
+ require 'nanoc2/filters/relativize_paths_in_css'
14
+ require 'nanoc2/filters/relativize_paths_in_html'
15
+ require 'nanoc2/filters/rubypants'
16
+ require 'nanoc2/filters/sass'
@@ -0,0 +1,13 @@
1
+ module Nanoc2::Filters
2
+ class BlueCloth < Nanoc2::Filter
3
+
4
+ identifiers :bluecloth
5
+
6
+ def run(content)
7
+ require 'bluecloth'
8
+
9
+ ::BlueCloth.new(content).to_html
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ module Nanoc2::Filters
2
+ class ERB < Nanoc2::Filter
3
+
4
+ identifiers :erb
5
+
6
+ def run(content)
7
+ require 'erb'
8
+
9
+ # Create context
10
+ context = ::Nanoc2::Extra::Context.new(assigns)
11
+
12
+ # Get result
13
+ erb = ::ERB.new(content)
14
+ erb.filename = filename
15
+ erb.result(context.get_binding)
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ module Nanoc2::Filters
2
+ class Erubis < Nanoc2::Filter
3
+
4
+ identifiers :erubis
5
+
6
+ def run(content)
7
+ require 'erubis'
8
+
9
+ # Get result
10
+ ::Erubis::Eruby.new(content).evaluate(assigns)
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,21 @@
1
+ module Nanoc2::Filters
2
+ class Haml < Nanoc2::Filter
3
+
4
+ identifiers :haml
5
+
6
+ def run(content)
7
+ require 'haml'
8
+
9
+ # Get options
10
+ options = @obj_rep.attribute_named(:haml_options) || {}
11
+ options[:filename] = filename
12
+
13
+ # Create context
14
+ context = ::Nanoc2::Extra::Context.new(assigns)
15
+
16
+ # Get result
17
+ ::Haml::Engine.new(content, options).render(context, assigns)
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ module Nanoc2::Filters
2
+ class Markaby < Nanoc2::Filter
3
+
4
+ identifiers :markaby
5
+
6
+ def run(content)
7
+ require 'markaby'
8
+
9
+ # Get result
10
+ ::Markaby::Builder.new(assigns).instance_eval(content).to_s
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module Nanoc2::Filters
2
+ class Maruku < Nanoc2::Filter
3
+
4
+ identifiers :maruku
5
+
6
+ def run(content)
7
+ require 'maruku'
8
+
9
+ # Get result
10
+ ::Maruku.new(content).to_html
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ module Nanoc2::Filters
2
+ class Old < Nanoc2::Filter
3
+
4
+ identifiers :eruby, :markdown, :smartypants, :textile
5
+
6
+ def run(content)
7
+ raise Nanoc2::Error.new(
8
+ "The 'eruby', markdown', 'smartypants' and 'textile' filters no " +
9
+ "longer exist. Instead, use the following filters:\n" +
10
+ "\n" +
11
+ "* for Markdown: bluecloth, rdiscount, redcloth\n" +
12
+ "* for Textile: redcloth\n" +
13
+ "* for embedded Ruby: erb, erubis\n" +
14
+ "* for Smartypants: rubypants"
15
+ )
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ module Nanoc2::Filters
2
+ class Rainpress < Nanoc2::Filter
3
+
4
+ identifier :rainpress
5
+
6
+ def run(content)
7
+ require 'rainpress'
8
+
9
+ ::Rainpress.compress(content)
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Nanoc2::Filters
2
+ class RDiscount < Nanoc2::Filter
3
+
4
+ identifiers :rdiscount
5
+
6
+ def run(content)
7
+ require 'rdiscount'
8
+
9
+ ::RDiscount.new(content).to_html
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,23 @@
1
+ module Nanoc2::Filters
2
+ class RDoc < Nanoc2::Filter
3
+
4
+ identifiers :rdoc
5
+
6
+ def run(content)
7
+ begin
8
+ # new RDoc
9
+ require 'rdoc/markup'
10
+ require 'rdoc/markup/to_html'
11
+
12
+ ::RDoc::Markup.new.convert(content, ::RDoc::Markup::ToHtml.new)
13
+ rescue LoadError
14
+ # old RDoc
15
+ require 'rdoc/markup/simple_markup'
16
+ require 'rdoc/markup/simple_markup/to_html'
17
+
18
+ ::SM::SimpleMarkup.new.convert(content, ::SM::ToHtml.new)
19
+ end
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,14 @@
1
+ module Nanoc2::Filters
2
+ class RedCloth < Nanoc2::Filter
3
+
4
+ identifiers :redcloth
5
+
6
+ def run(content)
7
+ require 'redcloth'
8
+
9
+ # Get result
10
+ ::RedCloth.new(content).to_html
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ module Nanoc2::Filters
2
+ class RelativizePaths < Nanoc2::Filter
3
+
4
+ identifier :relativize_paths
5
+
6
+ def run(content)
7
+ raise RuntimeError.new(
8
+ "The relativize_paths filter itself does not exist anymore. " +
9
+ "If you want to relativize paths in HTML, use the " +
10
+ "relativize_paths_in_html filter; if you want to relativize paths " +
11
+ "in CSS, use the relativize_paths_in_css filter."
12
+ )
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module Nanoc2::Filters
2
+ class RelativizePathsInCSS < Nanoc2::Filter
3
+
4
+ identifier :relativize_paths_in_css
5
+
6
+ require 'nanoc2/helpers/link_to'
7
+ include Nanoc2::Helpers::LinkTo
8
+
9
+ def run(content)
10
+ content.gsub(/url\((['"]?)(\/.+?)\1\)/) do
11
+ 'url(' + $1 + relative_path_to($2) + $1 + ')'
12
+ end
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module Nanoc2::Filters
2
+ class RelativizePathsInHTML < Nanoc2::Filter
3
+
4
+ identifier :relativize_paths_in_html
5
+
6
+ require 'nanoc2/helpers/link_to'
7
+ include Nanoc2::Helpers::LinkTo
8
+
9
+ def run(content)
10
+ content.gsub(/(src|href)=(['"]?)(\/.+?)\2([ >])/) do
11
+ $1 + '=' + $2 + relative_path_to($3) + $2 + $4
12
+ end
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,14 @@
1
+ module Nanoc2::Filters
2
+ class SmartyPants < Nanoc2::Filter
3
+
4
+ identifiers :rubypants
5
+
6
+ def run(content)
7
+ require 'rubypants'
8
+
9
+ # Get result
10
+ ::RubyPants.new(content).to_html
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ module Nanoc2::Filters
2
+ class Sass < Nanoc2::Filter
3
+
4
+ identifiers :sass
5
+
6
+ def run(content)
7
+ require 'sass'
8
+
9
+ # Get options
10
+ options = @obj_rep.attribute_named(:sass_options) || {}
11
+ options[:filename] = filename
12
+
13
+ # Get result
14
+ ::Sass::Engine.new(content, options).render
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,9 @@
1
+ require 'nanoc2/helpers/blogging'
2
+ require 'nanoc2/helpers/capturing'
3
+ require 'nanoc2/helpers/filtering'
4
+ require 'nanoc2/helpers/html_escape'
5
+ require 'nanoc2/helpers/link_to'
6
+ require 'nanoc2/helpers/render'
7
+ require 'nanoc2/helpers/tagging'
8
+ require 'nanoc2/helpers/text'
9
+ require 'nanoc2/helpers/xml_sitemap'
@@ -0,0 +1,217 @@
1
+ module Nanoc2::Helpers
2
+
3
+ # Nanoc2::Helpers::Blogging provides some functionality for building blogs,
4
+ # such as finding articles and constructing feeds.
5
+ #
6
+ # This helper has a few requirements. First, all blog articles should have
7
+ # the following attributes:
8
+ #
9
+ # * 'kind', set to 'article'.
10
+ #
11
+ # * 'created_at', set to the creation timestamp.
12
+ #
13
+ # Some functions in this blogging helper, such as the +atom_feed+ function,
14
+ # require additional attributes to be set; these attributes are described in
15
+ # the documentation for these functions.
16
+ #
17
+ # The two main functions are sorted_articles and atom_feed.
18
+ #
19
+ # To activate this helper, +include+ it, like this:
20
+ #
21
+ # include Nanoc2::Helpers::Blogging
22
+ module Blogging
23
+
24
+ # Returns an unsorted list of articles.
25
+ def articles
26
+ @pages.select { |page| page.kind == 'article' }
27
+ end
28
+
29
+ # Returns a list of articles, sorted by descending creation date (so newer
30
+ # articles appear first).
31
+ def sorted_articles
32
+ articles.sort_by { |a| a.created_at }.reverse
33
+ end
34
+
35
+ # Returns a string representing the atom feed containing recent articles,
36
+ # sorted by descending creation date. +params+ is a hash where the
37
+ # following keys can be set:
38
+ #
39
+ # +limit+:: The maximum number of articles to show. Defaults to 5.
40
+ #
41
+ # +articles+:: A list of articles to include in the feed. Defaults to the
42
+ # list of articles returned by the articles function.
43
+ #
44
+ # +content_proc+:: A proc that returns the content of the given article,
45
+ # passed as a parameter. By default, given the argument
46
+ # +article+, this proc will return +article.content+.
47
+ # This function may not return nil.
48
+ #
49
+ # +excerpt_proc+:: A proc that returns the excerpt of the given article,
50
+ # passed as a parameter. By default, given the argument
51
+ # +article+, this proc will return +article.excerpt+.
52
+ # This function may return nil.
53
+ #
54
+ # The following attributes must be set on blog articles:
55
+ #
56
+ # * 'title', containing the title of the blog post.
57
+ #
58
+ # * all other attributes mentioned above.
59
+ #
60
+ # The following attributes can optionally be set on blog articles to
61
+ # change the behaviour of the Atom feed:
62
+ #
63
+ # * 'excerpt', containing an excerpt of the article, usually only a few
64
+ # lines long.
65
+ #
66
+ # * 'custom_path_in_feed', containing the path that will be used instead
67
+ # of the normal path in the feed. This can be useful when including
68
+ # non-outputted pages in a feed; such pages could have their custom feed
69
+ # path set to the blog path instead, for example.
70
+ #
71
+ # The feed will also include dates on which the articles were updated.
72
+ # These are generated automatically; the way this happens depends on the
73
+ # used data source (the filesystem data source checks the file mtimes, for
74
+ # instance).
75
+ #
76
+ # The feed page will need to have the following attributes:
77
+ #
78
+ # * 'base_url', containing the URL to the site, without trailing slash.
79
+ # For example, if the site is at "http://example.com/", the base_url
80
+ # would be "http://example.com". It is probably a good idea to define
81
+ # this in the page defaults, i.e. the 'meta.yaml' file (at least if the
82
+ # filesystem data source is being used, which is probably the case).
83
+ #
84
+ # * 'title', containing the title of the feed, which is usually also the
85
+ # title of the blog.
86
+ #
87
+ # * 'author_name', containing the name of the page's author. This will
88
+ # likely be a global attribute, unless the site is managed by several
89
+ # people/
90
+ #
91
+ # * 'author_uri', containing the URI for the page's author, such as the
92
+ # author's web site URL. This will also likely be a global attribute.
93
+ #
94
+ # The feed page can have the following optional attributes:
95
+ #
96
+ # * 'feed_url', containing the custom URL of the feed. This can be useful
97
+ # when the private feed URL shouldn't be exposed; for example, when
98
+ # using FeedBurner this would be set to the public FeedBurner URL.
99
+ #
100
+ # To construct a feed, create a blank page with no layout, only the 'erb'
101
+ # (or 'erubis') filter, and an 'xml' extension. It may also be useful to
102
+ # set 'is_hidden' to true, so that helpers such as the sitemap helper will
103
+ # ignore the page. The content of the feed page should be:
104
+ #
105
+ # <%= atom_feed %>
106
+ def atom_feed(params={})
107
+ require 'builder'
108
+
109
+ # Extract parameters
110
+ limit = params[:limit] || 5
111
+ relevant_articles = params[:articles] || articles || []
112
+ content_proc = params[:content_proc] || lambda { |a| a.content }
113
+ excerpt_proc = params[:excerpt_proc] || lambda { |a| a.excerpt }
114
+
115
+ # Check feed page attributes
116
+ if @page.base_url.nil?
117
+ raise RuntimeError.new('Cannot build Atom feed: feed page has no base_url')
118
+ end
119
+ if @page.title.nil?
120
+ raise RuntimeError.new('Cannot build Atom feed: feed page has no title')
121
+ end
122
+ if @page.author_name.nil?
123
+ raise RuntimeError.new('Cannot build Atom feed: feed page has no author_name')
124
+ end
125
+ if @page.author_uri.nil?
126
+ raise RuntimeError.new('Cannot build Atom feed: feed page has no author_uri')
127
+ end
128
+
129
+ # Check article attributes
130
+ if relevant_articles.empty?
131
+ raise RuntimeError.new('Cannot build Atom feed: no articles')
132
+ end
133
+ if relevant_articles.any? { |a| a.created_at.nil? }
134
+ raise RuntimeError.new('Cannot build Atom feed: one or more articles lack created_at')
135
+ end
136
+
137
+ # Get sorted relevant articles
138
+ sorted_relevant_articles = relevant_articles.sort_by { |a| a.created_at }.reverse.first(limit)
139
+
140
+ # Get most recent article
141
+ last_article = sorted_relevant_articles.first
142
+
143
+ # Create builder
144
+ buffer = ''
145
+ xml = Builder::XmlMarkup.new(:target => buffer, :indent => 2)
146
+
147
+ # Build feed
148
+ xml.instruct!
149
+ xml.feed(:xmlns => 'http://www.w3.org/2005/Atom') do
150
+ # Add primary attributes
151
+ xml.id @page.base_url + '/'
152
+ xml.title @page.title
153
+
154
+ # Add date
155
+ xml.updated last_article.created_at.to_iso8601_time
156
+
157
+ # Add links
158
+ xml.link(:rel => 'alternate', :href => @page.base_url)
159
+ xml.link(:rel => 'self', :href => feed_url)
160
+
161
+ # Add author information
162
+ xml.author do
163
+ xml.name @page.author_name
164
+ xml.uri @page.author_uri
165
+ end
166
+
167
+ # Add articles
168
+ sorted_relevant_articles.each do |a|
169
+ xml.entry do
170
+ # Add primary attributes
171
+ xml.id atom_tag_for(a)
172
+ xml.title a.title, :type => 'html'
173
+
174
+ # Add dates
175
+ xml.published a.created_at.to_iso8601_time
176
+ xml.updated a.mtime.to_iso8601_time
177
+
178
+ # Add link
179
+ xml.link(:rel => 'alternate', :href => url_for(a))
180
+
181
+ # Add content
182
+ summary = excerpt_proc.call(a)
183
+ xml.content content_proc.call(a), :type => 'html'
184
+ xml.summary summary, :type => 'html' unless summary.nil?
185
+ end
186
+ end
187
+ end
188
+
189
+ buffer
190
+ end
191
+
192
+ # Returns the URL for the given page. It will return the URL containing
193
+ # the custom path in the feed if possible, otherwise the normal path.
194
+ def url_for(page)
195
+ @page.base_url + (page.custom_path_in_feed || page.path)
196
+ end
197
+
198
+ # Returns the URL of the feed. It will return the custom feed URL if set,
199
+ # or otherwise the normal feed URL.
200
+ def feed_url
201
+ @page[:feed_url] || @page.base_url + @page.path
202
+ end
203
+
204
+ # Returns an URI containing an unique ID for the given page. This will be
205
+ # used in the Atom feed to uniquely identify articles. These IDs are
206
+ # created using a procedure suggested by Mark Pilgrim in this blog post:
207
+ # http://diveintomark.org/archives/2004/05/28/howto-atom-id.
208
+ def atom_tag_for(page)
209
+ hostname = @page.base_url.sub(/.*:\/\/(.+?)\/?$/, '\1')
210
+ formatted_date = page.created_at.to_iso8601_date
211
+
212
+ 'tag:' + hostname + ',' + formatted_date + ':' + page.path
213
+ end
214
+
215
+ end
216
+
217
+ end