diff --git a/.rubocop.yml b/.rubocop.yml
index 671d371d..3675ec84 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -54,4 +54,6 @@ FormatString:
CaseIndentation:
IndentWhenRelativeTo: end
TrivialAccessors:
- ExactNameMatch: true
\ No newline at end of file
+ ExactNameMatch: true
+PerceivedComplexity:
+ Enabled: false
\ No newline at end of file
diff --git a/middleman-core/features/collections.feature b/middleman-core/features/collections.feature
new file mode 100644
index 00000000..5020c147
--- /dev/null
+++ b/middleman-core/features/collections.feature
@@ -0,0 +1,145 @@
+Feature: Collections
+ Scenario: Lazy query
+ Given a fixture app "collections-app"
+ And a file named "config.rb" with:
+ """
+ articles1 = collection :articles1, resources.select { |r|
+ matcher = ::Middleman::Util::UriTemplates.uri_template('blog1/{year}-{month}-{day}-{title}.html')
+ ::Middleman::Util::UriTemplates.extract_params(matcher, ::Middleman::Util.normalize_path(r.url))
+ }
+
+ everything = resources.select do |r|
+ true
+ end
+
+ def get_tags(resource)
+ if resource.data.tags.is_a? String
+ resource.data.tags.split(',').map(&:strip)
+ else
+ resource.data.tags
+ end
+ end
+
+ def group_lookup(resource, sum)
+ results = Array(get_tags(resource)).map(&:to_s).map(&:to_sym)
+
+ results.each do |k|
+ sum[k] ||= []
+ sum[k] << resource
+ end
+ end
+
+ tags = everything
+ .select { |resource| resource.data.tags }
+ .each_with_object({}, &method(:group_lookup))
+
+ class Wrapper
+ attr_reader :stuff
+
+ def initialize
+ @stuff = Set.new
+ end
+
+ def <<((k, v))
+ @stuff << k
+ self
+ end
+ end
+
+ collection :wrapped, tags.reduce(Wrapper.new, :<<)
+
+ set :tags, tags # Expose to templates
+
+ collection :first_tag, tags.keys.sort.first
+ """
+ And a file named "source/index.html.erb" with:
+ """
+ <% collection(:articles1).each do |article| %>
+ Article1: <%= article.data.title %>
+ <% end %>
+
+ Tag Count: <%= collection(:wrapped).stuff.length %>
+
+ <% config[:tags].value.each do |k, items| %>
+ Tag: <%= k %> (<%= items.length %>)
+ <% items.each do |article| %>
+ Article (<%= k %>): <%= article.data.title %>
+ <% end %>
+ <% end %>
+
+ First Tag: <%= collection(:first_tag) %>
+ """
+ Given the Server is running at "collections-app"
+ When I go to "index.html"
+ Then I should see 'Article1: Blog1 Newer Article'
+ And I should see 'Article1: Blog1 Another Article'
+ And I should see 'Tag: foo (4)'
+ And I should see 'Article (foo): Blog1 Newer Article'
+ And I should see 'Article (foo): Blog1 Another Article'
+ And I should see 'Article (foo): Blog2 Newer Article'
+ And I should see 'Article (foo): Blog2 Another Article'
+ And I should see 'Tag: bar (2)'
+ And I should see 'Article (bar): Blog1 Newer Article'
+ And I should see 'Article (bar): Blog2 Newer Article'
+ And I should see 'Tag: 120 (1)'
+ And I should see 'Article (120): Blog1 Another Article'
+ And I should see 'First Tag: 120'
+ And I should see 'Tag Count: 3'
+
+ Scenario: Collected resources update with file changes
+ Given a fixture app "collections-app"
+ And a file named "config.rb" with:
+ """
+ collection :articles, resources.select { |r|
+ matcher = ::Middleman::Util::UriTemplates.uri_template('blog2/{year}-{month}-{day}-{title}.html')
+ ::Middleman::Util::UriTemplates.extract_params(matcher, ::Middleman::Util.normalize_path(r.url))
+ }
+ """
+ And a file named "source/index.html.erb" with:
+ """
+ <% collection(:articles).each do |article| %>
+ Article: <%= article.data.title || article.source_file[:relative_path] %>
+ <% end %>
+ """
+ Given the Server is running at "collections-app"
+ When I go to "index.html"
+ Then I should not see "Article: index.html.erb"
+ Then I should see 'Article: Blog2 Newer Article'
+ And I should see 'Article: Blog2 Another Article'
+
+ And the file "source/blog2/2011-01-02-another-article.html.markdown" has the contents
+ """
+ ---
+ title: "Blog3 Another Article"
+ date: 2011-01-02
+ tags:
+ - foo
+ ---
+
+ Another Article Content
+
+ """
+ When I go to "index.html"
+ Then I should see "Article: Blog2 Newer Article"
+ And I should not see "Article: Blog2 Another Article"
+ And I should see 'Article: Blog3 Another Article'
+
+ And the file "source/blog2/2011-01-01-new-article.html.markdown" is removed
+ When I go to "index.html"
+ Then I should not see "Article: Blog2 Newer Article"
+ And I should see 'Article: Blog3 Another Article'
+
+ And the file "source/blog2/2014-01-02-yet-another-article.html.markdown" has the contents
+ """
+ ---
+ title: "Blog2 Yet Another Article"
+ date: 2011-01-02
+ tags:
+ - foo
+ ---
+
+ Yet Another Article Content
+ """
+ When I go to "index.html"
+ And I should see 'Article: Blog3 Another Article'
+ And I should see 'Article: Blog2 Yet Another Article'
diff --git a/middleman-core/features/console.feature b/middleman-core/features/console.feature
index c3400e3b..1bd55809 100644
--- a/middleman-core/features/console.feature
+++ b/middleman-core/features/console.feature
@@ -1,8 +1,9 @@
Feature: Console
Scenario: Enter and exit the console
- Given I run `middleman console` interactively
- When I type "puts 'Hello from the console.'"
+ Given a fixture app "large-build-app"
+ When I run `middleman console` interactively
+ And I type "puts 'Hello from the console.'"
And I type "exit"
Then it should pass with:
"""
diff --git a/middleman-core/features/paginate.feature b/middleman-core/features/paginate.feature
new file mode 100644
index 00000000..9338e019
--- /dev/null
+++ b/middleman-core/features/paginate.feature
@@ -0,0 +1,204 @@
+Feature: Pagination
+ Scenario: Basic configuration
+ Given a fixture app "paginate-app"
+ And a file named "config.rb" with:
+ """
+ articles = resources.select { |r|
+ matcher = ::Middleman::Util::UriTemplates.uri_template('blog/2011-{remaining}')
+ ::Middleman::Util::UriTemplates.extract_params(matcher, ::Middleman::Util.normalize_path(r.url))
+ }
+
+ articles.sort { |a, b|
+ b.data.date <=> a.data.date
+ }.per_page(5) do |items, num, meta, is_last|
+ page_path = num == 1 ? '/2011/index.html' : "/2011/page/#{num}.html"
+
+ prev_page = case num
+ when 1
+ nil
+ when 2
+ '/2011/index.html'
+ when 3
+ "/2011/page/#{num-1}.html"
+ end
+
+ next_page = is_last ? nil : "/2011/page/#{num+1}.html"
+
+ proxy page_path, "/archive/2011/index.html", locals: {
+ items: items,
+ pagination: meta,
+ prev_page: prev_page,
+ next_page: next_page
+ }
+ end
+
+ def get_tags(resource)
+ if resource.data.tags.is_a? String
+ resource.data.tags.split(',').map(&:strip)
+ else
+ resource.data.tags
+ end
+ end
+
+ def group_lookup(resource, sum)
+ results = Array(get_tags(resource)).map(&:to_s).map(&:to_sym)
+
+ results.each do |k|
+ sum[k] ||= []
+ sum[k] << resource
+ end
+ end
+
+ tags = articles
+ .select { |resource| resource.data.tags }
+ .each_with_object({}, &method(:group_lookup))
+
+ tags.each do |k, articles_in_tag|
+ articles_in_tag.sort { |a, b|
+ b.data.date <=> a.data.date
+ }.per_page(2).each do |items, num, meta, is_last|
+ page_path = num == 1 ? "/tags/#{k}.html" : "/tags/#{k}/page/#{num}.html"
+
+ prev_page = case num
+ when 1
+ nil
+ when 2
+ "/tags/#{k}.html"
+ when 3
+ "/tags/#{k}/page/#{num-1}.html"
+ end
+
+ next_page = is_last ? nil : "/tags/#{k}/page/#{num+1}.html"
+
+ proxy page_path, "/archive/2011/index.html", locals: {
+ items: items,
+ pagination: meta,
+ prev_page: prev_page,
+ next_page: next_page
+ }
+ end
+ end
+ """
+ And the Server is running
+ When I go to "/2011/index.html"
+ Then I should see "Paginate: true"
+ Then I should see "Article Count: 5"
+ Then I should see "Page Num: 1"
+ Then I should see "Num Pages: 2"
+ Then I should see "Per Page: 5"
+ Then I should see "Page Start: 1"
+ Then I should see "Page End: 5"
+ Then I should see "Next Page: '/2011/page/2.html'"
+ Then I should see "Prev Page: ''"
+ Then I should not see "/blog/2011-01-01-test-article.html"
+ Then I should not see "/blog/2011-01-02-test-article.html"
+ Then I should see "/blog/2011-01-03-test-article.html"
+ Then I should see "/blog/2011-01-04-test-article.html"
+ Then I should see "/blog/2011-01-05-test-article.html"
+ Then I should see "/blog/2011-02-01-test-article.html"
+ Then I should see "/blog/2011-02-02-test-article.html"
+
+ When I go to "/2011/page/2.html"
+ Then I should see "Article Count: 2"
+ Then I should see "Page Num: 2"
+ Then I should see "Page Start: 6"
+ Then I should see "Page End: 7"
+ Then I should see "Next Page: ''"
+ Then I should see "Prev Page: '/2011/'"
+ Then I should see "/2011-01-01-test-article.html"
+ Then I should see "/2011-01-02-test-article.html"
+ Then I should not see "/2011-01-03-test-article.html"
+ Then I should not see "/2011-01-04-test-article.html"
+ Then I should not see "/2011-01-05-test-article.html"
+ Then I should not see "/2011-02-01-test-article.html"
+ Then I should not see "/2011-02-02-test-article.html"
+
+ When I go to "/tags/bar.html"
+ Then I should see "Paginate: true"
+ Then I should see "Article Count: 2"
+ Then I should see "Page Num: 1"
+ Then I should see "Num Pages: 3"
+ Then I should see "Per Page: 2"
+ Then I should see "Page Start: 1"
+ Then I should see "Page End: 2"
+ Then I should see "Next Page: '/tags/bar/page/2.html'"
+ Then I should see "Prev Page: ''"
+ Then I should see "/2011-02-02-test-article.html"
+ Then I should see "/2011-02-01-test-article.html"
+ Then I should not see "/2011-02-05-test-article.html"
+ Then I should not see "/2011-01-04-test-article.html"
+ Then I should not see "/2011-01-03-test-article.html"
+
+ Scenario: Custom pager method
+ Given a fixture app "paginate-app"
+ And a file named "config.rb" with:
+ """
+ def items_per_page(all_items)
+ [
+ all_items.shift(2),
+ all_items
+ ]
+ end
+
+ articles = resources.select { |r|
+ matcher = ::Middleman::Util::UriTemplates.uri_template('blog/2011-{remaining}')
+ ::Middleman::Util::UriTemplates.extract_params(matcher, ::Middleman::Util.normalize_path(r.url))
+ }
+
+ articles.sort { |a, b|
+ b.data.date <=> a.data.date
+ }.per_page(method(:items_per_page).to_proc).each do |items, num, meta, is_last|
+ page_path = num == 1 ? '/2011/index.html' : "/2011/page/#{num}.html"
+
+ prev_page = case num
+ when 1
+ nil
+ when 2
+ '/2011/index.html'
+ when 3
+ "/2011/page/#{num-1}.html"
+ end
+
+ next_page = is_last ? nil : "/2011/page/#{num+1}.html"
+
+ proxy page_path, "/archive/2011/index.html", locals: {
+ items: items,
+ pagination: meta,
+ prev_page: prev_page,
+ next_page: next_page
+ }
+ end
+ """
+ And the Server is running
+ When I go to "/2011/index.html"
+ Then I should see "Paginate: true"
+ Then I should see "Article Count: 2"
+ Then I should see "Page Num: 1"
+ Then I should see "Num Pages: 2"
+ Then I should see "Per Page: 2"
+ Then I should see "Page Start: 1"
+ Then I should see "Page End: 2"
+ Then I should see "Next Page: '/2011/page/2.html'"
+ Then I should see "Prev Page: ''"
+ Then I should not see "/blog/2011-01-01-test-article.html"
+ Then I should not see "/blog/2011-01-02-test-article.html"
+ Then I should not see "/blog/2011-01-03-test-article.html"
+ Then I should not see "/blog/2011-01-04-test-article.html"
+ Then I should not see "/blog/2011-01-05-test-article.html"
+ Then I should see "/blog/2011-02-01-test-article.html"
+ Then I should see "/blog/2011-02-02-test-article.html"
+
+ When I go to "/2011/page/2.html"
+ Then I should see "Article Count: 5"
+ Then I should see "Page Num: 2"
+ Then I should see "Page Start: 3"
+ Then I should see "Page End: 7"
+ Then I should see "Next Page: ''"
+ Then I should see "Prev Page: '/2011/'"
+ Then I should see "/2011-01-01-test-article.html"
+ Then I should see "/2011-01-02-test-article.html"
+ Then I should see "/2011-01-03-test-article.html"
+ Then I should see "/2011-01-04-test-article.html"
+ Then I should see "/2011-01-05-test-article.html"
+ Then I should not see "/2011-02-01-test-article.html"
+ Then I should not see "/2011-02-02-test-article.html"
diff --git a/middleman-core/fixtures/collections-app/config.rb b/middleman-core/fixtures/collections-app/config.rb
new file mode 100644
index 00000000..ae9cb817
--- /dev/null
+++ b/middleman-core/fixtures/collections-app/config.rb
@@ -0,0 +1,16 @@
+collection :articles,
+ where: proc { |resource|
+ uri_match resource.url, 'blog/{year}-{month}-{day}-{title}.html'
+ }
+
+collection :tags,
+ where: proc { |resource|
+ resource.data.tags
+ },
+ group_by: proc { |resource|
+ if resource.data.tags.is_a? String
+ resource.data.tags.split(',').map(&:strip)
+ else
+ resource.data.tags
+ end
+ }
\ No newline at end of file
diff --git a/middleman-core/fixtures/collections-app/source/blog1/2011-01-01-new-article.html.markdown b/middleman-core/fixtures/collections-app/source/blog1/2011-01-01-new-article.html.markdown
new file mode 100755
index 00000000..a96bcc00
--- /dev/null
+++ b/middleman-core/fixtures/collections-app/source/blog1/2011-01-01-new-article.html.markdown
@@ -0,0 +1,7 @@
+---
+title: "Blog1 Newer Article"
+date: 2011-01-01
+tags: foo, bar
+---
+
+Newer Article Content
diff --git a/middleman-core/fixtures/collections-app/source/blog1/2011-01-02-another-article.html.markdown b/middleman-core/fixtures/collections-app/source/blog1/2011-01-02-another-article.html.markdown
new file mode 100755
index 00000000..feb086af
--- /dev/null
+++ b/middleman-core/fixtures/collections-app/source/blog1/2011-01-02-another-article.html.markdown
@@ -0,0 +1,9 @@
+---
+title: "Blog1 Another Article"
+date: 2011-01-02
+tags:
+ - foo
+ - 120
+---
+
+Another Article Content
diff --git a/middleman-core/fixtures/collections-app/source/blog2/2011-01-01-new-article.html.markdown b/middleman-core/fixtures/collections-app/source/blog2/2011-01-01-new-article.html.markdown
new file mode 100755
index 00000000..125b71fa
--- /dev/null
+++ b/middleman-core/fixtures/collections-app/source/blog2/2011-01-01-new-article.html.markdown
@@ -0,0 +1,7 @@
+---
+title: "Blog2 Newer Article"
+date: 2011-01-01
+tags: foo, bar
+---
+
+Newer Article Content
diff --git a/middleman-core/fixtures/collections-app/source/blog2/2011-01-02-another-article.html.markdown b/middleman-core/fixtures/collections-app/source/blog2/2011-01-02-another-article.html.markdown
new file mode 100755
index 00000000..18656883
--- /dev/null
+++ b/middleman-core/fixtures/collections-app/source/blog2/2011-01-02-another-article.html.markdown
@@ -0,0 +1,8 @@
+---
+title: "Blog2 Another Article"
+date: 2011-01-02
+tags:
+ - foo
+---
+
+Another Article Content
diff --git a/middleman-core/fixtures/collections-app/source/index.html.erb b/middleman-core/fixtures/collections-app/source/index.html.erb
new file mode 100644
index 00000000..0d6f2ad5
--- /dev/null
+++ b/middleman-core/fixtures/collections-app/source/index.html.erb
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+<% collected.articles.each do |article| %>
+