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