class Bundler::PubGrub::FailureWriter

Public Class Methods

new(root) click to toggle source
# File bundler/vendor/pub_grub/lib/pub_grub/failure_writer.rb, line 3
def initialize(root)
  @root = root

  # { Incompatibility => Integer }
  @derivations = {}

  # [ [ String, Integer or nil ] ]
  @lines = []

  # { Incompatibility => Integer }
  @line_numbers = {}

  count_derivations(root)
end

Public Instance Methods

write() click to toggle source
# File bundler/vendor/pub_grub/lib/pub_grub/failure_writer.rb, line 18
def write
  return @root.to_s unless @root.conflict?

  visit(@root)

  padding = @line_numbers.empty? ? 0 : "(#{@line_numbers.values.last}) ".length

  @lines.map do |message, number|
    next "" if message.empty?

    lead = number ? "(#{number}) " : ""
    lead = lead.ljust(padding)
    message = message.gsub("\n", "\n" + " " * (padding + 2))
    "#{lead}#{message}"
  end.join("\n")
end

Private Instance Methods

collapsible?(incompatibility) click to toggle source
# File bundler/vendor/pub_grub/lib/pub_grub/failure_writer.rb, line 150
def collapsible?(incompatibility)
  return false if @derivations[incompatibility] > 1

  cause = incompatibility.cause
  # If incompatibility is derived from two derived incompatibilities,
  # there are too many transitive causes to display concisely.
  return false if cause.conflict.conflict? && cause.other.conflict?

  # If incompatibility is derived from two external incompatibilities, it
  # tends to be confusing to collapse it.
  return false unless cause.conflict.conflict? || cause.other.conflict?

  # If incompatibility's internal cause is numbered, collapsing it would
  # get too noisy.
  complex = cause.conflict.conflict? ? cause.conflict : cause.other

  !@line_numbers.has_key?(complex)
end
count_derivations(incompatibility) click to toggle source
# File bundler/vendor/pub_grub/lib/pub_grub/failure_writer.rb, line 169
def count_derivations(incompatibility)
  if @derivations.has_key?(incompatibility)
    @derivations[incompatibility] += 1
  else
    @derivations[incompatibility] = 1
    if incompatibility.conflict?
      cause = incompatibility.cause
      count_derivations(cause.conflict)
      count_derivations(cause.other)
    end
  end
end
single_line?(cause) click to toggle source
# File bundler/vendor/pub_grub/lib/pub_grub/failure_writer.rb, line 146
def single_line?(cause)
  !cause.conflict.conflict? && !cause.other.conflict?
end
visit(incompatibility, conclusion: false) click to toggle source
# File bundler/vendor/pub_grub/lib/pub_grub/failure_writer.rb, line 46
def visit(incompatibility, conclusion: false)
  raise unless incompatibility.conflict?

  numbered = conclusion || @derivations[incompatibility] > 1;
  conjunction = conclusion || incompatibility == @root ? "So," : "And"

  cause = incompatibility.cause

  if cause.conflict.conflict? && cause.other.conflict?
    conflict_line = @line_numbers[cause.conflict]
    other_line = @line_numbers[cause.other]

    if conflict_line && other_line
      write_line(
        incompatibility,
        "Because #{cause.conflict} (#{conflict_line})\nand #{cause.other} (#{other_line}),\n#{incompatibility}.",
        numbered: numbered
      )
    elsif conflict_line || other_line
      with_line    = conflict_line ? cause.conflict : cause.other
      without_line = conflict_line ? cause.other : cause.conflict
      line = @line_numbers[with_line]

      visit(without_line);
      write_line(
        incompatibility,
        "#{conjunction} because #{with_line} (#{line}),\n#{incompatibility}.",
        numbered: numbered
      )
    else
      single_line_conflict = single_line?(cause.conflict.cause)
      single_line_other    = single_line?(cause.other.cause)

      if single_line_conflict || single_line_other
        first  = single_line_other ? cause.conflict : cause.other
        second = single_line_other ? cause.other : cause.conflict
        visit(first)
        visit(second)
        write_line(
          incompatibility,
          "Thus, #{incompatibility}.",
          numbered: numbered
        )
      else
        visit(cause.conflict, conclusion: true)
        @lines << ["", nil]
        visit(cause.other)

        write_line(
          incompatibility,
          "#{conjunction} because #{cause.conflict} (#{@line_numbers[cause.conflict]}),\n#{incompatibility}.",
          numbered: numbered
        )
      end
    end
  elsif cause.conflict.conflict? || cause.other.conflict?
    derived = cause.conflict.conflict? ? cause.conflict : cause.other
    ext     = cause.conflict.conflict? ? cause.other : cause.conflict

    derived_line = @line_numbers[derived]
    if derived_line
      write_line(
        incompatibility,
        "Because #{ext}\nand #{derived} (#{derived_line}),\n#{incompatibility}.",
        numbered: numbered
      )
    elsif collapsible?(derived)
      derived_cause = derived.cause
      if derived_cause.conflict.conflict?
        collapsed_derived = derived_cause.conflict
        collapsed_ext = derived_cause.other
      else
        collapsed_derived = derived_cause.other
        collapsed_ext = derived_cause.conflict
      end

      visit(collapsed_derived)

      write_line(
        incompatibility,
        "#{conjunction} because #{collapsed_ext}\nand #{ext},\n#{incompatibility}.",
        numbered: numbered
      )
    else
      visit(derived)
      write_line(
        incompatibility,
        "#{conjunction} because #{ext},\n#{incompatibility}.",
        numbered: numbered
      )
    end
  else
    write_line(
      incompatibility,
      "Because #{cause.conflict}\nand #{cause.other},\n#{incompatibility}.",
      numbered: numbered
    )
  end
end
write_line(incompatibility, message, numbered:) click to toggle source
# File bundler/vendor/pub_grub/lib/pub_grub/failure_writer.rb, line 37
def write_line(incompatibility, message, numbered:)
  if numbered
    number = @line_numbers.length + 1
    @line_numbers[incompatibility] = number
  end

  @lines << [message, number]
end