Commit 321539ee authored by Rene Saarsoo's avatar Rene Saarsoo
Browse files

Extract AutoLink and LinkRenderer from Inline::Link.

Splitting up the humongous Link class into more managable pieces.

The DocFormatter constructor now takes two parameters - relations and
options, instead of passing the relations in separately.  It then
instantiates LinkRenderer and passes it to Link and AutoLink
constructors.

The LinkRenderer is still too heavily coupled to Link and AutoLink,
as these call all the methods of LinkRenderer, but the situation is
much better overall.
parent b2906849
Loading
Loading
Loading
Loading
+1 −2
Original line number Diff line number Diff line
@@ -27,8 +27,7 @@ module JsDuck
      @relations = relations
      @opts = opts

      doc_formatter = DocFormatter.new(@opts)
      doc_formatter.relations = @relations
      doc_formatter = DocFormatter.new(@relations, @opts)

      @images = Img::DirSet.new(@opts.images, "images")
      @welcome = Welcome.create(@opts.welcome, doc_formatter)
+1 −2
Original line number Diff line number Diff line
@@ -45,8 +45,7 @@ module JsDuck

    # Factory method to create new ClassFormatter instances.
    def self.create_class_formatter(relations, opts)
      doc_formatter = DocFormatter.new(opts)
      doc_formatter.relations = relations
      doc_formatter = DocFormatter.new(relations, opts)
      doc_formatter.images = Img::DirSet.new(opts.images, "images")

      class_formatter = ClassFormatter.new(relations, doc_formatter)
+10 −13
Original line number Diff line number Diff line
@@ -3,6 +3,8 @@ require 'strscan'
require 'rdiscount'
require 'jsduck/html_stack'
require 'jsduck/inline/link'
require 'jsduck/inline/auto_link'
require 'jsduck/inline/link_renderer'
require 'jsduck/inline/img'
require 'jsduck/inline/video'
require 'jsduck/inline/example'
@@ -15,9 +17,11 @@ module JsDuck
    # Creates a formatter configured with options originating from
    # command line.  For the actual effect of the options see
    # Inline::* classes.
    def initialize(opts={})
    def initialize(relations={}, opts={})
      @opts = opts
      @inline_link = Inline::Link.new(opts)
      @link_renderer = Inline::LinkRenderer.new(relations, opts)
      @inline_link = Inline::Link.new(@link_renderer)
      @auto_link = Inline::AutoLink.new(@link_renderer)
      @inline_img = Inline::Img.new(opts)
      @inline_video = Inline::Video.new(opts)
      @inline_example = Inline::Example.new(opts)
@@ -37,6 +41,7 @@ module JsDuck
    # Context#blah is meant.
    def class_context=(cls)
      @inline_link.class_context = cls
      @auto_link.class_context = cls
    end

    # Sets up instance to work in context of particular doc object.
@@ -45,6 +50,7 @@ module JsDuck
      @doc_context = doc
      @inline_video.doc_context = doc
      @inline_link.doc_context = doc
      @auto_link.doc_context = doc
      @inline_img.doc_context = doc
    end

@@ -53,15 +59,6 @@ module JsDuck
      @doc_context
    end

    # JsDuck::Relations for looking up class names.
    #
    # When auto-creating class links from CamelCased names found from
    # text, we check the relations object to see if a class with that
    # name actually exists.
    def relations=(relations)
      @inline_link.relations = relations
    end

    # Formats doc-comment for placement into HTML.
    # Renders it with Markdown-formatter and replaces @link-s.
    def format(input)
@@ -127,7 +124,7 @@ module JsDuck
          # Replace class names in the following text up to next "<" or "{"
          # but only when we're not inside <a>...</a>
          text = s.scan(/[^{<]+/)
          out += tags.open?("a") ? text : @inline_link.create_magic_links(text)
          out += tags.open?("a") ? text : @auto_link.replace(text)
        end
      end

@@ -136,7 +133,7 @@ module JsDuck

    # Creates a link based on the link template.
    def link(cls, member, anchor_text, type=nil, static=nil)
      @inline_link.link(cls, member, anchor_text, type, static)
      @link_renderer.link(cls, member, anchor_text, type, static)
    end

  end
+106 −0
Original line number Diff line number Diff line
require 'jsduck/logger'

module JsDuck
  module Inline

    # Takes care of the auto-detection of links in text.
    class AutoLink
      # Sets up instance to work in context of particular class, so it
      # knows that #blah is in context of SomeClass.
      attr_accessor :class_context

      # Sets up instance to work in context of particular doc object.
      # Used for error reporting.
      attr_accessor :doc_context

      def initialize(link_renderer)
        @class_context = ""
        @doc_context = {}
        @relations = link_renderer.relations
        @renderer = link_renderer
        @magic_link_re = magic_link_re
      end

      # Looks input text for patterns like:
      #
      #  My.ClassName
      #  MyClass#method
      #  #someProperty
      #
      # and converts them to links, as if they were surrounded with
      # {@link} tag. One notable exception is that Foo is not created to
      # link, even when Foo class exists, but Foo.Bar is. This is to
      # avoid turning normal words into links. For example:
      #
      #     Math involves a lot of numbers. Ext JS is a JavaScript framework.
      #
      # In these sentences we don't want to link "Math" and "Ext" to the
      # corresponding JS classes.  And that's why we auto-link only
      # class names containing a dot "."
      #
      def replace(input)
        input.gsub(@magic_link_re) do
          cls = $1 || $3
          member = $2 || $4
          replace_magic_link(cls, member)
        end
      end

      private

      # Generates regex for auto-linking class and member names in text.
      def magic_link_re
        ident_re = "(?:[A-Za-z_$][A-Za-z0-9_$]*)"
        cls_re = "(#{ident_re}(?:\\.#{ident_re})*)"
        ns_cls_re = "(#{ident_re}(?:\\.#{ident_re})+)"
        member_re = "(?:#(#{ident_re}))"
        /#{cls_re}#{member_re}|#{ns_cls_re}|#{member_re}/m
      end

      def replace_magic_link(cls, member)
        if cls && member
          if @relations[cls] && @renderer.get_matching_member(cls, {:name => member})
            return @renderer.link(cls, member, cls+"."+member)
          else
            warn_magic_link("#{cls}##{member} links to non-existing " + (@relations[cls] ? "member" : "class"))
          end
        elsif cls
          if @relations[cls]
            return @renderer.link(cls, nil, cls)
          else
            cls2, member2 = split_to_cls_and_member(cls)
            if @relations[cls2] && @renderer.get_matching_member(cls2, {:name => member2})
              return @renderer.link(cls2, member2, cls2+"."+member2)
            elsif cls =~ /\.(js|css|html|php)\Z/
              # Ignore common filenames
            else
              warn_magic_link("#{cls} links to non-existing class")
            end
          end
        else
          if @renderer.get_matching_member(@class_context, {:name => member})
            return @renderer.link(@class_context, member, member)
          elsif member =~ /\A([A-F0-9]{3}|[A-F0-9]{6})\Z/i || member =~ /\A[0-9]/
            # Ignore HEX color codes and
            # member names beginning with number
          else
            warn_magic_link("##{member} links to non-existing member")
          end
        end

        return "#{cls}#{member ? '#' : ''}#{member}"
      end

      def split_to_cls_and_member(str)
        parts = str.split(/\./)
        return [parts.slice(0, parts.length-1).join("."), parts.last]
      end

      def warn_magic_link(msg)
        Logger.warn(:link_auto, msg, @doc_context[:filename], @doc_context[:linenr])
      end

    end

  end
end
+6 −142
Original line number Diff line number Diff line
require 'jsduck/util/html'
require 'jsduck/logger'

module JsDuck
@@ -18,31 +17,12 @@ module JsDuck
      # Used for error reporting.
      attr_accessor :doc_context

      # JsDuck::Relations for looking up class names.
      #
      # When auto-creating class links from CamelCased names found from
      # text, we check the relations object to see if a class with that
      # name actually exists.
      attr_accessor :relations

      def initialize(opts={})
      def initialize(link_renderer)
        @class_context = ""
        @doc_context = {}
        @relations = {}

        # Template HTML that replaces {@link Class#member anchor text}.
        # Can contain placeholders:
        #
        # %c - full class name (e.g. "Ext.Panel")
        # %m - class member name prefixed with member type (e.g. "method-urlEncode")
        # %# - inserts "#" if member name present
        # %- - inserts "-" if member name present
        # %a - anchor text for link
        @tpl = opts[:link_tpl] || '<a href="%c%#%m">%a</a>'

        @relations = link_renderer.relations
        @renderer = link_renderer
        @re = /\{@link\s+(\S*?)(?:\s+(.+?))?\}/m

        @magic_link_re = magic_link_re
      end

      # Takes StringScanner instance.
@@ -87,7 +67,7 @@ module JsDuck
          Logger.warn(:link, "#{full_link} links to non-existing class", file, line)
          return text
        elsif member
          ms = find_members(cls, {:name => member, :tagname => type, :static => static})
          ms = @renderer.find_members(cls, {:name => member, :tagname => type, :static => static})
          if ms.length == 0
            Logger.warn(:link, "#{full_link} links to non-existing member", file, line)
            return text
@@ -109,126 +89,10 @@ module JsDuck
            end
          end

          return link(cls, member, text, type, static)
        else
          return link(cls, false, text)
        end
      end

      # Looks input text for patterns like:
      #
      #  My.ClassName
      #  MyClass#method
      #  #someProperty
      #
      # and converts them to links, as if they were surrounded with
      # {@link} tag. One notable exception is that Foo is not created to
      # link, even when Foo class exists, but Foo.Bar is. This is to
      # avoid turning normal words into links. For example:
      #
      #     Math involves a lot of numbers. Ext JS is a JavaScript framework.
      #
      # In these sentences we don't want to link "Math" and "Ext" to the
      # corresponding JS classes.  And that's why we auto-link only
      # class names containing a dot "."
      #
      def create_magic_links(input)
        input.gsub(@magic_link_re) do
          cls = $1 || $3
          member = $2 || $4
          replace_magic_link(cls, member)
        end
      end

      # Generates regex for auto-linking class and member names in text.
      def magic_link_re
        ident_re = "(?:[A-Za-z_$][A-Za-z0-9_$]*)"
        cls_re = "(#{ident_re}(?:\\.#{ident_re})*)"
        ns_cls_re = "(#{ident_re}(?:\\.#{ident_re})+)"
        member_re = "(?:#(#{ident_re}))"
        /#{cls_re}#{member_re}|#{ns_cls_re}|#{member_re}/m
      end

      def replace_magic_link(cls, member)
        if cls && member
          if @relations[cls] && get_matching_member(cls, {:name => member})
            return link(cls, member, cls+"."+member)
          else
            warn_magic_link("#{cls}##{member} links to non-existing " + (@relations[cls] ? "member" : "class"))
          end
        elsif cls
          if @relations[cls]
            return link(cls, nil, cls)
          else
            cls2, member2 = split_to_cls_and_member(cls)
            if @relations[cls2] && get_matching_member(cls2, {:name => member2})
              return link(cls2, member2, cls2+"."+member2)
            elsif cls =~ /\.(js|css|html|php)\Z/
              # Ignore common filenames
            else
              warn_magic_link("#{cls} links to non-existing class")
            end
          end
        else
          if get_matching_member(@class_context, {:name => member})
            return link(@class_context, member, member)
          elsif member =~ /\A([A-F0-9]{3}|[A-F0-9]{6})\Z/i || member =~ /\A[0-9]/
            # Ignore HEX color codes and
            # member names beginning with number
          else
            warn_magic_link("##{member} links to non-existing member")
          end
        end

        return "#{cls}#{member ? '#' : ''}#{member}"
      end

      def split_to_cls_and_member(str)
        parts = str.split(/\./)
        return [parts.slice(0, parts.length-1).join("."), parts.last]
      end

      def warn_magic_link(msg)
        Logger.warn(:link_auto, msg, @doc_context[:filename], @doc_context[:linenr])
      end

      # applies the link template
      def link(cls, member, anchor_text, type=nil, static=nil)
        # Use the canonical class name for link (not some alternateClassName)
        cls = @relations[cls][:name]
        # prepend type name to member name
        member = member && get_matching_member(cls, {:name => member, :tagname => type, :static => static})

        @tpl.gsub(/(%[\w#-])/) do
          case $1
          when '%c'
            cls
          when '%m'
            member ? member[:id] : ""
          when '%#'
            member ? "#" : ""
          when '%-'
            member ? "-" : ""
          when '%a'
            Util::HTML.escape(anchor_text||"")
          else
            $1
          end
        end
      end

      def get_matching_member(cls, query)
        ms = find_members(cls, query)
        if ms.length > 1
          instance_ms = ms.find_all {|m| !m[:meta][:static] }
          instance_ms.length > 0 ? instance_ms[0] : ms.find_all {|m| m[:meta][:static] }[0]
          return @renderer.link(cls, member, text, type, static)
        else
          ms[0]
        end
          return @renderer.link(cls, false, text)
        end

      def find_members(cls, query)
        @relations[cls] ? @relations[cls].find_members(query) : []
      end

    end
Loading