lib/nanoc/helpers/breadcrumbs.rb in nanoc-4.10.4 vs lib/nanoc/helpers/breadcrumbs.rb in nanoc-4.11.0
- old
+ new
@@ -1,12 +1,38 @@
# frozen_string_literal: true
module Nanoc::Helpers
# @see http://nanoc.ws/doc/reference/helpers/#breadcrumbs
module Breadcrumbs
+ class AmbiguousAncestorError < Nanoc::Int::Errors::Generic
+ def initialize(pattern, items)
+ @pattern = pattern
+ @items = items
+ end
+
+ def message
+ "expected only one item to match #{@pattern}, but found #{@items.size}"
+ end
+ end
+
# @api private
module Int
+ DEFAULT_TIEBREAKER =
+ lambda do |items, pattern|
+ identifiers = items.map(&:identifier).sort
+ $stderr.puts <<~WARNING
+ Warning: The breadcrumbs trail (generated by #breadcrumbs_trail) found more than one potential parent item at #{pattern} (found #{identifiers.join(', ')}). Nanoc will pick the first item as the parent. Consider eliminating the ambiguity by making only one item match #{pattern}, or by passing a `:tiebreaker` option to `#breadcrumbs_trail`. (This situation will be an error in the next major version of Nanoc.)
+ WARNING
+
+ items.min_by(&:identifier)
+ end
+
+ ERROR_TIEBREAKER =
+ lambda do |items, pattern|
+ raise AmbiguousAncestorError.new(pattern, items)
+ end
+
# e.g. unfold(10.class, &:superclass)
# => [Integer, Numeric, Object, BasicObject]
def self.unfold(obj, &blk)
acc = [obj]
@@ -27,14 +53,30 @@
new_prefix == old_prefix ? nil : new_prefix
end
prefixes.map { |pr| pr + '.*' }
end
+
+ def self.find_one(items, pat, tiebreaker)
+ res = items.find_all(pat)
+ case res.size
+ when 0
+ nil
+ when 1
+ res.first
+ else
+ if tiebreaker.arity == 1
+ tiebreaker.call(res)
+ else
+ tiebreaker.call(res, pat)
+ end
+ end
+ end
end
# @return [Array]
- def breadcrumbs_trail
+ def breadcrumbs_trail(tiebreaker: Int::DEFAULT_TIEBREAKER)
# The design of this function is a little complicated.
#
# We can’t use #parent_of from the ChildParent helper, because the
# breadcrumb trail can have gaps. For example, the breadcrumbs trail for
# /software/oink.md might be /index.md -> nil -> /software/oink.md if
@@ -63,20 +105,22 @@
# e.g. ['', '/foo', '/foo/bar']
components = item.identifier.components
prefixes = components.inject(['']) { |acc, elem| acc + [acc.last + '/' + elem] }
+ tiebreaker = Int::ERROR_TIEBREAKER if tiebreaker == :error
+
if @item.identifier.legacy?
prefixes.map { |pr| @items[Nanoc::Identifier.new('/' + pr, type: :legacy)] }
else
ancestral_prefixes = prefixes.reject { |pr| pr =~ /^\/index\./ }[0..-2]
ancestral_items =
ancestral_prefixes.map do |pr|
if pr == ''
@items['/index.*']
else
prefix_patterns = Int.patterns_for_prefix(pr)
- prefix_patterns.lazy.map { |pat| @items[pat] }.find(&:itself)
+ prefix_patterns.lazy.map { |pat| Int.find_one(@items, pat, tiebreaker) }.find(&:itself)
end
end
ancestral_items + [item]
end
end