monad 0.0.1

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 (143) hide show
  1. data/CONTRIBUTING.md +68 -0
  2. data/Gemfile +2 -0
  3. data/LICENSE +21 -0
  4. data/README.md +88 -0
  5. data/Rakefile +136 -0
  6. data/bin/monad +102 -0
  7. data/cucumber.yml +3 -0
  8. data/features/create_sites.feature +112 -0
  9. data/features/data_sources.feature +76 -0
  10. data/features/drafts.feature +25 -0
  11. data/features/embed_filters.feature +60 -0
  12. data/features/markdown.feature +30 -0
  13. data/features/pagination.feature +54 -0
  14. data/features/permalinks.feature +65 -0
  15. data/features/post_data.feature +214 -0
  16. data/features/site_configuration.feature +206 -0
  17. data/features/site_data.feature +101 -0
  18. data/features/step_definitions/monad_steps.rb +175 -0
  19. data/features/support/env.rb +25 -0
  20. data/lib/monad.rb +90 -0
  21. data/lib/monad/command.rb +27 -0
  22. data/lib/monad/commands/build.rb +64 -0
  23. data/lib/monad/commands/doctor.rb +29 -0
  24. data/lib/monad/commands/new.rb +50 -0
  25. data/lib/monad/commands/serve.rb +33 -0
  26. data/lib/monad/configuration.rb +183 -0
  27. data/lib/monad/converter.rb +48 -0
  28. data/lib/monad/converters/identity.rb +21 -0
  29. data/lib/monad/converters/markdown.rb +43 -0
  30. data/lib/monad/converters/markdown/kramdown_parser.rb +44 -0
  31. data/lib/monad/converters/markdown/maruku_parser.rb +47 -0
  32. data/lib/monad/converters/markdown/rdiscount_parser.rb +35 -0
  33. data/lib/monad/converters/markdown/redcarpet_parser.rb +70 -0
  34. data/lib/monad/converters/textile.rb +50 -0
  35. data/lib/monad/convertible.rb +152 -0
  36. data/lib/monad/core_ext.rb +68 -0
  37. data/lib/monad/deprecator.rb +32 -0
  38. data/lib/monad/draft.rb +35 -0
  39. data/lib/monad/drivers/json_driver.rb +39 -0
  40. data/lib/monad/drivers/yaml_driver.rb +23 -0
  41. data/lib/monad/errors.rb +4 -0
  42. data/lib/monad/filters.rb +154 -0
  43. data/lib/monad/generator.rb +4 -0
  44. data/lib/monad/generators/pagination.rb +143 -0
  45. data/lib/monad/layout.rb +42 -0
  46. data/lib/monad/logger.rb +54 -0
  47. data/lib/monad/mime.types +85 -0
  48. data/lib/monad/page.rb +163 -0
  49. data/lib/monad/plugin.rb +75 -0
  50. data/lib/monad/post.rb +377 -0
  51. data/lib/monad/site.rb +455 -0
  52. data/lib/monad/static_file.rb +70 -0
  53. data/lib/monad/tags/gist.rb +30 -0
  54. data/lib/monad/tags/highlight.rb +85 -0
  55. data/lib/monad/tags/include.rb +37 -0
  56. data/lib/monad/tags/post_url.rb +61 -0
  57. data/lib/site_template/.gitignore +1 -0
  58. data/lib/site_template/_config.yml +2 -0
  59. data/lib/site_template/_layouts/default.html +46 -0
  60. data/lib/site_template/_layouts/post.html +9 -0
  61. data/lib/site_template/_posts/0000-00-00-welcome-to-monad.markdown.erb +24 -0
  62. data/lib/site_template/css/main.css +165 -0
  63. data/lib/site_template/css/syntax.css +60 -0
  64. data/lib/site_template/index.html +13 -0
  65. data/monad.gemspec +197 -0
  66. data/script/bootstrap +2 -0
  67. data/test/fixtures/broken_front_matter1.erb +5 -0
  68. data/test/fixtures/broken_front_matter2.erb +4 -0
  69. data/test/fixtures/broken_front_matter3.erb +7 -0
  70. data/test/fixtures/exploit_front_matter.erb +4 -0
  71. data/test/fixtures/front_matter.erb +4 -0
  72. data/test/fixtures/members.yaml +7 -0
  73. data/test/helper.rb +62 -0
  74. data/test/source/.htaccess +8 -0
  75. data/test/source/_includes/sig.markdown +3 -0
  76. data/test/source/_layouts/default.html +27 -0
  77. data/test/source/_layouts/simple.html +1 -0
  78. data/test/source/_plugins/dummy.rb +8 -0
  79. data/test/source/_posts/2008-02-02-not-published.textile +8 -0
  80. data/test/source/_posts/2008-02-02-published.textile +8 -0
  81. data/test/source/_posts/2008-10-18-foo-bar.textile +8 -0
  82. data/test/source/_posts/2008-11-21-complex.textile +8 -0
  83. data/test/source/_posts/2008-12-03-permalinked-post.textile +9 -0
  84. data/test/source/_posts/2008-12-13-include.markdown +8 -0
  85. data/test/source/_posts/2009-01-27-array-categories.textile +10 -0
  86. data/test/source/_posts/2009-01-27-categories.textile +7 -0
  87. data/test/source/_posts/2009-01-27-category.textile +7 -0
  88. data/test/source/_posts/2009-01-27-empty-categories.textile +7 -0
  89. data/test/source/_posts/2009-01-27-empty-category.textile +7 -0
  90. data/test/source/_posts/2009-03-12-hash-#1.markdown +6 -0
  91. data/test/source/_posts/2009-05-18-empty-tag.textile +6 -0
  92. data/test/source/_posts/2009-05-18-empty-tags.textile +6 -0
  93. data/test/source/_posts/2009-05-18-tag.textile +6 -0
  94. data/test/source/_posts/2009-05-18-tags.textile +9 -0
  95. data/test/source/_posts/2009-06-22-empty-yaml.textile +3 -0
  96. data/test/source/_posts/2009-06-22-no-yaml.textile +1 -0
  97. data/test/source/_posts/2010-01-08-triple-dash.markdown +5 -0
  98. data/test/source/_posts/2010-01-09-date-override.textile +7 -0
  99. data/test/source/_posts/2010-01-09-time-override.textile +7 -0
  100. data/test/source/_posts/2010-01-09-timezone-override.textile +7 -0
  101. data/test/source/_posts/2010-01-16-override-data.textile +4 -0
  102. data/test/source/_posts/2011-04-12-md-extension.md +7 -0
  103. data/test/source/_posts/2011-04-12-text-extension.text +0 -0
  104. data/test/source/_posts/2013-01-02-post-excerpt.markdown +14 -0
  105. data/test/source/_posts/2013-01-12-nil-layout.textile +6 -0
  106. data/test/source/_posts/2013-01-12-no-layout.textile +5 -0
  107. data/test/source/_posts/2013-03-19-not-a-post.markdown/.gitkeep +0 -0
  108. data/test/source/_posts/2013-04-11-custom-excerpt.markdown +10 -0
  109. data/test/source/_posts/2013-05-10-number-category.textile +7 -0
  110. data/test/source/_posts/es/2008-11-21-nested.textile +8 -0
  111. data/test/source/about.html +6 -0
  112. data/test/source/category/_posts/2008-9-23-categories.textile +6 -0
  113. data/test/source/contacts.html +5 -0
  114. data/test/source/contacts/bar.html +5 -0
  115. data/test/source/contacts/index.html +5 -0
  116. data/test/source/css/screen.css +76 -0
  117. data/test/source/deal.with.dots.html +7 -0
  118. data/test/source/foo/_posts/bar/2008-12-12-topical-post.textile +8 -0
  119. data/test/source/index.html +22 -0
  120. data/test/source/sitemap.xml +32 -0
  121. data/test/source/symlink-test/symlinked-file +22 -0
  122. data/test/source/win/_posts/2009-05-24-yaml-linebreak.markdown +7 -0
  123. data/test/source/z_category/_posts/2008-9-23-categories.textile +6 -0
  124. data/test/suite.rb +11 -0
  125. data/test/test_command.rb +39 -0
  126. data/test/test_configuration.rb +137 -0
  127. data/test/test_convertible.rb +51 -0
  128. data/test/test_core_ext.rb +88 -0
  129. data/test/test_filters.rb +102 -0
  130. data/test/test_generated_site.rb +83 -0
  131. data/test/test_json_driver.rb +63 -0
  132. data/test/test_kramdown.rb +35 -0
  133. data/test/test_new_command.rb +104 -0
  134. data/test/test_page.rb +193 -0
  135. data/test/test_pager.rb +115 -0
  136. data/test/test_post.rb +573 -0
  137. data/test/test_rdiscount.rb +22 -0
  138. data/test/test_redcarpet.rb +61 -0
  139. data/test/test_redcloth.rb +86 -0
  140. data/test/test_site.rb +374 -0
  141. data/test/test_tags.rb +310 -0
  142. data/test/test_yaml_driver.rb +35 -0
  143. metadata +554 -0
@@ -0,0 +1,68 @@
1
+ class Hash
2
+ # Merges self with another hash, recursively.
3
+ #
4
+ # This code was lovingly stolen from some random gem:
5
+ # http://gemjack.com/gems/tartan-0.1.1/classes/Hash.html
6
+ #
7
+ # Thanks to whoever made it.
8
+ def deep_merge(hash)
9
+ target = dup
10
+
11
+ hash.keys.each do |key|
12
+ if hash[key].is_a? Hash and self[key].is_a? Hash
13
+ target[key] = target[key].deep_merge(hash[key])
14
+ next
15
+ end
16
+
17
+ target[key] = hash[key]
18
+ end
19
+
20
+ target
21
+ end
22
+
23
+ # Read array from the supplied hash favouring the singular key
24
+ # and then the plural key, and handling any nil entries.
25
+ # +hash+ the hash to read from
26
+ # +singular_key+ the singular key
27
+ # +plural_key+ the plural key
28
+ #
29
+ # Returns an array
30
+ def pluralized_array(singular_key, plural_key)
31
+ hash = self
32
+ if hash.has_key?(singular_key)
33
+ array = [hash[singular_key]] if hash[singular_key]
34
+ elsif hash.has_key?(plural_key)
35
+ case hash[plural_key]
36
+ when String
37
+ array = hash[plural_key].split
38
+ when Array
39
+ array = hash[plural_key].compact
40
+ end
41
+ end
42
+ array || []
43
+ end
44
+ end
45
+
46
+ # Thanks, ActiveSupport!
47
+ class Date
48
+ # Converts datetime to an appropriate format for use in XML
49
+ def xmlschema
50
+ strftime("%Y-%m-%dT%H:%M:%S%Z")
51
+ end if RUBY_VERSION < '1.9'
52
+ end
53
+
54
+ module Enumerable
55
+ # Returns true if path matches against any glob pattern.
56
+ # Look for more detail about glob pattern in method File::fnmatch.
57
+ def glob_include?(e)
58
+ any? { |exp| File.fnmatch?(exp, e) }
59
+ end
60
+ end
61
+
62
+ if RUBY_VERSION < "1.9"
63
+ class String
64
+ def force_encoding(enc)
65
+ self
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,32 @@
1
+ module Monad
2
+ class Deprecator
3
+ def self.process(args)
4
+ no_subcommand(args)
5
+ deprecation_message args, "--server", "The --server command has been replaced by the \
6
+ 'serve' subcommand."
7
+ deprecation_message args, "--no-server", "To build Monad without launching a server, \
8
+ use the 'build' subcommand."
9
+ deprecation_message args, "--auto", "The switch '--auto' has been replaced with '--watch'."
10
+ deprecation_message args, "--no-auto", "To disable auto-replication, simply leave off \
11
+ the '--watch' switch."
12
+ deprecation_message args, "--pygments", "The 'pygments' setting can only be set in \
13
+ your config files."
14
+ deprecation_message args, "--paginate", "The 'paginate' setting can only be set in your \
15
+ config files."
16
+ deprecation_message args, "--url", "The 'url' setting can only be set in your config files."
17
+ end
18
+
19
+ def self.no_subcommand(args)
20
+ if args.size > 0 && args.first =~ /^--/ && !%w[--help --version].include?(args.first)
21
+ Monad::Logger.error "Deprecation:", "Monad now uses subcommands instead of just \
22
+ switches. Run `monad help' to find out more."
23
+ end
24
+ end
25
+
26
+ def self.deprecation_message(args, deprecated_argument, message)
27
+ if args.include?(deprecated_argument)
28
+ Monad::Logger.error "Deprecation:", message
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,35 @@
1
+ module Monad
2
+
3
+ class Draft < Post
4
+
5
+ # Valid post name regex (no date)
6
+ MATCHER = /^(.*)(\.[^.]+)$/
7
+
8
+ # Draft name validator. Draft filenames must be like:
9
+ # my-awesome-post.textile
10
+ #
11
+ # Returns true if valid, false if not.
12
+ def self.valid?(name)
13
+ name =~ MATCHER
14
+ end
15
+
16
+ # Get the full path to the directory containing the draft files
17
+ def containing_dir(source, dir)
18
+ File.join(source, dir, '_drafts')
19
+ end
20
+
21
+ # Extract information from the post filename.
22
+ #
23
+ # name - The String filename of the post file.
24
+ #
25
+ # Returns nothing.
26
+ def process(name)
27
+ m, slug, ext = *name.match(MATCHER)
28
+ self.date = File.mtime(File.join(@base, name))
29
+ self.slug = slug
30
+ self.ext = ext
31
+ end
32
+
33
+ end
34
+
35
+ end
@@ -0,0 +1,39 @@
1
+ require 'json'
2
+ require 'uri'
3
+ require 'net/http'
4
+ require 'net/https' # ruby 1.8.7 requires explicitly require net/https
5
+
6
+ module Monad
7
+ module Drivers
8
+ class JsonDriver
9
+ def initialize(options)
10
+ @url = options['url']
11
+
12
+ if !@url
13
+ raise FatalException.new "'url' must be specified for json data source: #{options['name']}."
14
+ end
15
+
16
+ if @url !~ URI::regexp || URI(@url).scheme !~ /^http|https$/
17
+ raise FatalException.new "incorrect json data source url: #{@url}"
18
+ end
19
+ end
20
+
21
+ def load
22
+ uri = URI(@url)
23
+
24
+ if uri.scheme == 'https'
25
+ http = Net::HTTP.new(uri.host, uri.port)
26
+ http.use_ssl = true
27
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
28
+
29
+ request = Net::HTTP::Get.new(uri.request_uri)
30
+ response = http.request(request).body
31
+ else
32
+ response = Net::HTTP.get(uri)
33
+ end
34
+
35
+ JSON.parse(response)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,23 @@
1
+ require 'safe_yaml'
2
+
3
+ module Monad
4
+ module Drivers
5
+ class YamlDriver
6
+ def initialize(options)
7
+ @path = options['path']
8
+
9
+ if !@path
10
+ raise FatalException.new "'path' must be specified for yaml data source: #{options['name']}."
11
+ end
12
+
13
+ if !File.exists?(@path)
14
+ raise FatalException.new "the file '#{@path}' doesn't exist for data source '#{options['name']}'"
15
+ end
16
+ end
17
+
18
+ def load
19
+ YAML.safe_load_file(@path)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,4 @@
1
+ module Monad
2
+ class FatalException < StandardError
3
+ end
4
+ end
@@ -0,0 +1,154 @@
1
+ require 'uri'
2
+
3
+ module Monad
4
+ module Filters
5
+ # Convert a Textile string into HTML output.
6
+ #
7
+ # input - The Textile String to convert.
8
+ #
9
+ # Returns the HTML formatted String.
10
+ def textilize(input)
11
+ site = @context.registers[:site]
12
+ converter = site.getConverterImpl(Monad::Converters::Textile)
13
+ converter.convert(input)
14
+ end
15
+
16
+ # Convert a Markdown string into HTML output.
17
+ #
18
+ # input - The Markdown String to convert.
19
+ #
20
+ # Returns the HTML formatted String.
21
+ def markdownify(input)
22
+ site = @context.registers[:site]
23
+ converter = site.getConverterImpl(Monad::Converters::Markdown)
24
+ converter.convert(input)
25
+ end
26
+
27
+ # Format a date in short format e.g. "27 Jan 2011".
28
+ #
29
+ # date - the Time to format.
30
+ #
31
+ # Returns the formatting String.
32
+ def date_to_string(date)
33
+ time(date).strftime("%d %b %Y")
34
+ end
35
+
36
+ # Format a date in long format e.g. "27 January 2011".
37
+ #
38
+ # date - The Time to format.
39
+ #
40
+ # Returns the formatted String.
41
+ def date_to_long_string(date)
42
+ time(date).strftime("%d %B %Y")
43
+ end
44
+
45
+ # Format a date for use in XML.
46
+ #
47
+ # date - The Time to format.
48
+ #
49
+ # Examples
50
+ #
51
+ # date_to_xmlschema(Time.now)
52
+ # # => "2011-04-24T20:34:46+08:00"
53
+ #
54
+ # Returns the formatted String.
55
+ def date_to_xmlschema(date)
56
+ time(date).xmlschema
57
+ end
58
+
59
+ # Format a date according to RFC-822
60
+ #
61
+ # date - The Time to format.
62
+ #
63
+ # Examples
64
+ #
65
+ # date_to_rfc822(Time.now)
66
+ # # => "Sun, 24 Apr 2011 12:34:46 +0000"
67
+ #
68
+ # Returns the formatted String.
69
+ def date_to_rfc822(date)
70
+ time(date).rfc822
71
+ end
72
+
73
+ # XML escape a string for use. Replaces any special characters with
74
+ # appropriate HTML entity replacements.
75
+ #
76
+ # input - The String to escape.
77
+ #
78
+ # Examples
79
+ #
80
+ # xml_escape('foo "bar" <baz>')
81
+ # # => "foo &quot;bar&quot; &lt;baz&gt;"
82
+ #
83
+ # Returns the escaped String.
84
+ def xml_escape(input)
85
+ CGI.escapeHTML(input)
86
+ end
87
+
88
+ # CGI escape a string for use in a URL. Replaces any special characters
89
+ # with appropriate %XX replacements.
90
+ #
91
+ # input - The String to escape.
92
+ #
93
+ # Examples
94
+ #
95
+ # cgi_escape('foo,bar;baz?')
96
+ # # => "foo%2Cbar%3Bbaz%3F"
97
+ #
98
+ # Returns the escaped String.
99
+ def cgi_escape(input)
100
+ CGI::escape(input)
101
+ end
102
+
103
+ def uri_escape(input)
104
+ URI.escape(input)
105
+ end
106
+
107
+ # Count the number of words in the input string.
108
+ #
109
+ # input - The String on which to operate.
110
+ #
111
+ # Returns the Integer word count.
112
+ def number_of_words(input)
113
+ input.split.length
114
+ end
115
+
116
+ # Join an array of things into a string by separating with commes and the
117
+ # word "and" for the last one.
118
+ #
119
+ # array - The Array of Strings to join.
120
+ #
121
+ # Examples
122
+ #
123
+ # array_to_sentence_string(["apples", "oranges", "grapes"])
124
+ # # => "apples, oranges, and grapes"
125
+ #
126
+ # Returns the formatted String.
127
+ def array_to_sentence_string(array)
128
+ connector = "and"
129
+ case array.length
130
+ when 0
131
+ ""
132
+ when 1
133
+ array[0].to_s
134
+ when 2
135
+ "#{array[0]} #{connector} #{array[1]}"
136
+ else
137
+ "#{array[0...-1].join(', ')}, #{connector} #{array[-1]}"
138
+ end
139
+ end
140
+
141
+ private
142
+ def time(input)
143
+ case input
144
+ when Time
145
+ input
146
+ when String
147
+ Time.parse(input)
148
+ else
149
+ Monad::Logger.error "Invalid Date:", "'#{input}' is not a valid datetime."
150
+ exit(1)
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,4 @@
1
+ module Monad
2
+ class Generator < Plugin
3
+ end
4
+ end
@@ -0,0 +1,143 @@
1
+ module Monad
2
+ module Generators
3
+ class Pagination < Generator
4
+ # This generator is safe from arbitrary code execution.
5
+ safe true
6
+
7
+ # Generate paginated pages if necessary.
8
+ #
9
+ # site - The Site.
10
+ #
11
+ # Returns nothing.
12
+ def generate(site)
13
+ site.pages.dup.each do |page|
14
+ paginate(site, page) if Pager.pagination_enabled?(site.config, page)
15
+ end
16
+ end
17
+
18
+ # Paginates the blog's posts. Renders the index.html file into paginated
19
+ # directories, e.g.: page2/index.html, page3/index.html, etc and adds more
20
+ # site-wide data.
21
+ #
22
+ # site - The Site.
23
+ # page - The index.html Page that requires pagination.
24
+ #
25
+ # {"paginator" => { "page" => <Number>,
26
+ # "per_page" => <Number>,
27
+ # "posts" => [<Post>],
28
+ # "total_posts" => <Number>,
29
+ # "total_pages" => <Number>,
30
+ # "previous_page" => <Number>,
31
+ # "next_page" => <Number> }}
32
+ def paginate(site, page)
33
+ all_posts = site.site_payload['site']['posts']
34
+ pages = Pager.calculate_pages(all_posts, site.config['paginate'].to_i)
35
+ (1..pages).each do |num_page|
36
+ pager = Pager.new(site.config, num_page, all_posts, pages)
37
+ if num_page > 1
38
+ newpage = Page.new(site, site.source, page.dir, page.name)
39
+ newpage.pager = pager
40
+ newpage.dir = File.join(page.dir, Pager.paginate_path(site.config, num_page))
41
+ site.pages << newpage
42
+ else
43
+ page.pager = pager
44
+ end
45
+ end
46
+ end
47
+
48
+ end
49
+ end
50
+
51
+ class Pager
52
+ attr_reader :page, :per_page, :posts, :total_posts, :total_pages,
53
+ :previous_page, :previous_page_path, :next_page, :next_page_path
54
+
55
+ # Calculate the number of pages.
56
+ #
57
+ # all_posts - The Array of all Posts.
58
+ # per_page - The Integer of entries per page.
59
+ #
60
+ # Returns the Integer number of pages.
61
+ def self.calculate_pages(all_posts, per_page)
62
+ (all_posts.size.to_f / per_page.to_i).ceil
63
+ end
64
+
65
+ # Determine if pagination is enabled for a given file.
66
+ #
67
+ # config - The configuration Hash.
68
+ # page - The Monad::Page with which to paginate
69
+ #
70
+ # Returns true if pagination is enabled, false otherwise.
71
+ def self.pagination_enabled?(config, page)
72
+ !config['paginate'].nil? &&
73
+ page.name == 'index.html' &&
74
+ subdirectories_identical(config['paginate_path'], page.dir)
75
+ end
76
+
77
+ # Determine if the subdirectories of the two paths are the same relative to source
78
+ #
79
+ # paginate_path - the paginate_path configuration setting
80
+ # page_dir - the directory of the Monad::Page
81
+ #
82
+ # Returns whether the subdirectories are the same relative to source
83
+ def self.subdirectories_identical(paginate_path, page_dir)
84
+ File.dirname(paginate_path).gsub(/\A\./, '') == page_dir.gsub(/\/\z/, '')
85
+ end
86
+
87
+ # Static: Return the pagination path of the page
88
+ #
89
+ # site_config - the site config
90
+ # num_page - the pagination page number
91
+ #
92
+ # Returns the pagination path as a string
93
+ def self.paginate_path(site_config, num_page)
94
+ return nil if num_page.nil? || num_page <= 1
95
+ format = File.basename(site_config['paginate_path'])
96
+ format.sub(':num', num_page.to_s)
97
+ end
98
+
99
+ # Initialize a new Pager.
100
+ #
101
+ # config - The Hash configuration of the site.
102
+ # page - The Integer page number.
103
+ # all_posts - The Array of all the site's Posts.
104
+ # num_pages - The Integer number of pages or nil if you'd like the number
105
+ # of pages calculated.
106
+ def initialize(config, page, all_posts, num_pages = nil)
107
+ @page = page
108
+ @per_page = config['paginate'].to_i
109
+ @total_pages = num_pages || Pager.calculate_pages(all_posts, @per_page)
110
+
111
+ if @page > @total_pages
112
+ raise RuntimeError, "page number can't be greater than total pages: #{@page} > #{@total_pages}"
113
+ end
114
+
115
+ init = (@page - 1) * @per_page
116
+ offset = (init + @per_page - 1) >= all_posts.size ? all_posts.size : (init + @per_page - 1)
117
+
118
+ @total_posts = all_posts.size
119
+ @posts = all_posts[init..offset]
120
+ @previous_page = @page != 1 ? @page - 1 : nil
121
+ @previous_page_path = Pager.paginate_path(config, @previous_page)
122
+ @next_page = @page != @total_pages ? @page + 1 : nil
123
+ @next_page_path = Pager.paginate_path(config, @next_page)
124
+ end
125
+
126
+ # Convert this Pager's data to a Hash suitable for use by Liquid.
127
+ #
128
+ # Returns the Hash representation of this Pager.
129
+ def to_liquid
130
+ {
131
+ 'page' => page,
132
+ 'per_page' => per_page,
133
+ 'posts' => posts,
134
+ 'total_posts' => total_posts,
135
+ 'total_pages' => total_pages,
136
+ 'previous_page' => previous_page,
137
+ 'previous_page_path' => previous_page_path,
138
+ 'next_page' => next_page,
139
+ 'next_page_path' => next_page_path
140
+ }
141
+ end
142
+ end
143
+ end