class Nanoc::Checking::Checks::InternalLinks

A check that verifies that all internal links point to a location that exists.

@api private

Public Instance Methods

run() click to toggle source

Starts the validator. The results will be printed to stdout.

Internal links that match a regexp pattern in `@config[:internal_links]` will be skipped.

@return [void]

# File lib/nanoc/checking/checks/internal_links.rb, line 18
def run
  # TODO: de-duplicate this (duplicated in external links check)
  filenames = output_html_filenames
  uris = ::Nanoc::Extra::LinkCollector.new(filenames, :internal).filenames_per_href

  uris.each_pair do |href, fns|
    fns.each do |filename|
      next if valid?(href, filename)

      add_issue(
        "broken reference to #{href}",
        subject: filename,
      )
    end
  end
end

Protected Instance Methods

excluded?(href, origin) click to toggle source
# File lib/nanoc/checking/checks/internal_links.rb, line 78
def excluded?(href, origin)
  config = @config.fetch(:checks, {}).fetch(:internal_links, {})
  excluded_target?(href, config) || excluded_origin?(origin, config)
end
excluded_origin?(origin, config) click to toggle source
# File lib/nanoc/checking/checks/internal_links.rb, line 88
def excluded_origin?(origin, config)
  # FIXME: do not depend on current working directory
  origin = File.absolute_path(origin)

  relative_origin = origin[@config.output_dir.size..-1]
  excludes = config.fetch(:exclude_origins, [])
  excludes.any? { |pattern| Regexp.new(pattern).match(relative_origin) }
end
excluded_target?(href, config) click to toggle source
# File lib/nanoc/checking/checks/internal_links.rb, line 83
def excluded_target?(href, config)
  excludes = config.fetch(:exclude_targets, config.fetch(:exclude, []))
  excludes.any? { |pattern| Regexp.new(pattern).match(href) }
end
valid?(href, origin) click to toggle source
# File lib/nanoc/checking/checks/internal_links.rb, line 37
def valid?(href, origin)
  # Skip hrefs that point to self
  # FIXME: this is ugly and won’t always be correct
  return true if href == '.'

  # Turn file: into output_dir-as-root relative paths

  output_dir = @config.output_dir
  output_dir += '/' unless output_dir.end_with?('/')
  base_uri = URI("file://#{output_dir}")
  path = href.sub(/#{base_uri}/, '').sub(/file:\/{1,3}/, '')

  path = "/#{path}" unless path.start_with?('/')

  # Skip hrefs that are specified in the exclude configuration
  return true if excluded?(path, origin)

  # Make an absolute path
  path = ::File.join(output_dir, path[1..path.length])

  # Remove fragment
  path = path.sub(/#.*$/, '')
  return true if path.empty?

  # Remove query string
  path = path.sub(/\?.*$/, '')
  return true if path.empty?

  # Decode URL (e.g. '%20' -> ' ')
  path = CGI.unescape(path)

  # Check whether file exists
  return true if File.file?(path)

  # Check whether directory with index file exists
  return true if File.directory?(path) && @config[:index_filenames].any? { |fn| File.file?(File.join(path, fn)) }

  # Nope :(
  false
end