lib/yard/templates/helpers/html_helper.rb in yard-0.9.16 vs lib/yard/templates/helpers/html_helper.rb in yard-0.9.17
- old
+ new
@@ -1,642 +1,646 @@
-# frozen_string_literal: true
-require 'cgi'
-
-module YARD
- module Templates::Helpers
- # The helper module for HTML templates.
- module HtmlHelper
- include MarkupHelper
- include HtmlSyntaxHighlightHelper
-
- # @private
- URLMATCH = /[^\w\s~!\*'\(\):;@&=\$,\[\]<>-]/
-
- # @group Escaping Template Data
-
- # Escapes HTML entities
- #
- # @param [String] text the text to escape
- # @return [String] the HTML with escaped entities
- def h(text)
- CGI.escapeHTML(text.to_s)
- end
-
- # Escapes a URL
- #
- # @param [String] text the URL
- # @return [String] the escaped URL
- def urlencode(text)
- text = text.dup
- enc = nil
- if text.respond_to?(:force_encoding)
- enc = text.encoding
- text = text.force_encoding('binary')
- end
-
- text = text.gsub(/%[a-z0-9]{2}|#{URLMATCH}/i) do
- $&.size > 1 ? $& : "%" + $&.ord.to_s(16).upcase
- end.tr(' ', '+')
-
- text = text.force_encoding(enc) if enc
- text
- end
-
- module_function :urlencode
-
- # @group Converting Markup to HTML
-
- # Turns text into HTML using +markup+ style formatting.
- #
- # @param [String] text the text to format
- # @param [Symbol] markup examples are +:markdown+, +:textile+, +:rdoc+.
- # To add a custom markup type, see {MarkupHelper}
- # @return [String] the HTML
- def htmlify(text, markup = options.markup)
- markup_meth = "html_markup_#{markup}"
- return text unless respond_to?(markup_meth)
- return "" unless text
- return text unless markup
- html = send(markup_meth, text).dup
- if html.respond_to?(:encode)
- html = html.force_encoding(text.encoding) # for libs that mess with encoding
- html = html.encode(:invalid => :replace, :replace => '?')
- end
- html = resolve_links(html)
- unless [:text, :none, :pre].include?(markup)
- html = parse_codeblocks(html)
- end
- html
- end
-
- # Converts Markdown to HTML
- # @param [String] text input Markdown text
- # @return [String] output HTML
- # @since 0.6.0
- def html_markup_markdown(text)
- # TODO: other libraries might be more complex
- provider = markup_class(:markdown)
- if provider.to_s == 'RDiscount'
- provider.new(text, :autolink).to_html
- elsif provider.to_s == 'RedcarpetCompat'
- provider.new(text, :no_intraemphasis, :gh_blockcode,
- :fenced_code, :autolink, :tables,
- :lax_spacing).to_html
- else
- provider.new(text).to_html
- end
- end
-
- # Converts org-mode to HTML
- # @param [String] text input org-mode text
- # @return [String] output HTML
- def html_markup_org(text)
- markup_class(:org).new(text).to_html
- end
-
- # Converts Asciidoc to HTML
- # @param [String] text input Asciidoc text
- # @return [String] output HTML
- def html_markup_asciidoc(text)
- markup_class(:asciidoc).render(text)
- end
-
- # Converts Textile to HTML
- # @param [String] text the input Textile text
- # @return [String] output HTML
- # @since 0.6.0
- def html_markup_textile(text)
- doc = markup_class(:textile).new(text)
- doc.hard_breaks = false if doc.respond_to?(:hard_breaks=)
- doc.to_html
- end
-
- # Converts plaintext to strict Textile (hard breaks)
- # @param [String] text the input textile data
- # @return [String] the output HTML
- # @since 0.6.0
- def html_markup_textile_strict(text)
- markup_class(:textile).new(text).to_html
- end
-
- # Converts RDoc formatting (SimpleMarkup) to HTML
- # @param [String] text the input RDoc formatted text
- # @return [String] output HTML
- # @since 0.6.0
- def html_markup_rdoc(text)
- doc = markup_class(:rdoc).new(text)
- doc.from_path = url_for(object) if doc.respond_to?(:from_path=)
- doc.to_html
- end
-
- # Converts plaintext to pre-formatted HTML
- # @param [String] text the input text
- # @return [String] the output HTML
- # @since 0.6.0
- def html_markup_pre(text)
- "<pre>" + h(text) + "</pre>"
- end
-
- # Converts plaintext to regular HTML
- # @param [String] text the input text
- # @return [String] the output HTML
- # @since 0.6.0
- def html_markup_text(text)
- h(text).gsub(/\r?\n/, '<br/>')
- end
-
- # @return [String] the same text with no markup
- # @since 0.6.6
- def html_markup_none(text)
- h(text)
- end
-
- # Converts HTML to HTML
- # @param [String] text input html
- # @return [String] output HTML
- # @since 0.6.0
- def html_markup_html(text)
- text
- end
-
- # Highlights Ruby source. Similar to {#html_syntax_highlight}, but
- # this method is meant to be called from {#htmlify} when markup is
- # set to "ruby".
- #
- # @param [String] source the Ruby source
- # @return [String] the highlighted HTML
- # @since 0.7.0
- def html_markup_ruby(source)
- '<pre class="code ruby">' + html_syntax_highlight(source, :ruby) + '</pre>'
- end
-
- # @return [String] HTMLified text as a single line (paragraphs removed)
- def htmlify_line(*args)
- "<div class='inline'>" + htmlify(*args) + "</div>"
- end
-
- # @group Syntax Highlighting Source Code
-
- # Syntax highlights +source+ in language +type+.
- #
- # @note To support a specific language +type+, implement the method
- # +html_syntax_highlight_TYPE+ in this class.
- #
- # @param [String] source the source code to highlight
- # @param [Symbol, String] type the language type (:ruby, :plain, etc). Use
- # :plain for no syntax highlighting.
- # @return [String] the highlighted source
- def html_syntax_highlight(source, type = nil)
- return "" unless source
- return h(source) unless options.highlight
-
- new_type, source = parse_lang_for_codeblock(source)
- type ||= new_type || :ruby
- meth = "html_syntax_highlight_#{type}"
- respond_to?(meth) ? send(meth, source) : h(source)
- end
-
- # @return [String] unhighlighted source
- def html_syntax_highlight_plain(source)
- h(source)
- end
-
- # @group Linking Objects and URLs
-
- # Resolves any text in the form of +{Name}+ to the object specified by
- # Name. Also supports link titles in the form +{Name title}+.
- #
- # @example Linking to an instance method
- # resolve_links("{MyClass#method}") # => "<a href='...'>MyClass#method</a>"
- # @example Linking to a class with a title
- # resolve_links("{A::B::C the C class}") # => "<a href='...'>the c class</a>"
- # @param [String] text the text to resolve links in
- # @return [String] HTML with linkified references
- def resolve_links(text)
- code_tags = 0
- text.gsub(%r{<(/)?(pre|code|tt)|(\\|!)?\{(?!\})(\S+?)(?:\s([^\}]*?\S))?\}(?=[\W<]|.+</|$)}m) do |str|
- closed = $1
- tag = $2
- escape = $3
- name = $4
- title = $5
- match = $&
- if tag
- code_tags += (closed ? -1 : 1)
- next str
- end
- next str unless code_tags == 0
-
- next(match[1..-1]) if escape
-
- next(match) if name[0, 1] == '|'
-
- if name == '<a' && title =~ %r{href=["'](.+?)["'].*>.*</a>\s*(.*)\Z}
- name = $1
- title = $2
- title = nil if title.empty?
- end
-
- name = CGI.unescapeHTML(name)
-
- if object.is_a?(String)
- object
- else
- link = linkify(name, title)
- if (link == name || link == title) && (name + ' ' + link !~ /\A<a\s.*>/)
- match = /(.+)?(\{#{Regexp.quote name}(?:\s.*?)?\})(.+)?/.match(text)
- file = (defined?(@file) && @file ? @file.filename : object.file) || '(unknown)'
- line = (defined?(@file) && @file ? 1 : (object.docstring.line_range ? object.docstring.line_range.first : 1)) + (match ? $`.count("\n") : 0)
- log.warn "In file `#{file}':#{line}: Cannot resolve link to #{name} from text" + (match ? ":" : ".") +
- "\n\t" + (match[1] ? '...' : '') + match[2].delete("\n") + (match[3] ? '...' : '') if match
- end
-
- link
- end
- end
- end
-
- # (see BaseHelper#link_file)
- def link_file(filename, title = nil, anchor = nil)
- if CodeObjects::ExtraFileObject === filename
- file = filename
- else
- contents = File.file?(filename) ? nil : ''
- file = CodeObjects::ExtraFileObject.new(filename, contents)
- end
- return title || file.title unless serializer
- link_url(url_for_file(file, anchor), title || file.title)
- end
-
- # (see BaseHelper#link_include_file)
- def link_include_file(file)
- unless file.is_a?(CodeObjects::ExtraFileObject)
- file = CodeObjects::ExtraFileObject.new(file)
- end
- file.attributes[:markup] ||= markup_for_file('', file.filename)
- insert_include(file.contents, file.attributes[:markup] || options.markup)
- end
-
- # (see BaseHelper#link_include_object)
- def link_include_object(obj)
- insert_include(obj.docstring)
- end
-
- # Inserts an include link while respecting inlining
- def insert_include(text, markup = options.markup)
- htmlify(text, markup).gsub(%r{\A\s*<p>|</p>\s*\Z}, '')
- end
-
- # (see BaseHelper#link_object)
- def link_object(obj, title = nil, anchor = nil, relative = true)
- return title if obj.nil?
- obj = Registry.resolve(object, obj, true, true) if obj.is_a?(String)
- if title
- title = title.to_s
- elsif object.is_a?(CodeObjects::Base)
- # Check if we're linking to a class method in the current
- # object. If we are, create a title in the format of
- # "CurrentClass.method_name"
- if obj.is_a?(CodeObjects::MethodObject) && obj.scope == :class && obj.parent == object
- title = h([object.name, obj.sep, obj.name].join)
- elsif obj.title != obj.path
- title = h(obj.title)
- else
- title = h(object.relative_path(obj))
- end
- else
- title = h(obj.title)
- end
- return title unless serializer
- return title if obj.is_a?(CodeObjects::Proxy)
-
- link = url_for(obj, anchor, relative)
- link = link ? link_url(link, title, :title => h("#{obj.title} (#{obj.type})")) : title
- "<span class='object_link'>" + link + "</span>"
- end
-
- # (see BaseHelper#link_url)
- def link_url(url, title = nil, params = {})
- title ||= url
- title = title.gsub(/[\r\n]/, ' ')
- params = SymbolHash.new(false).update(
- :href => url,
- :title => h(title)
- ).update(params)
- params[:target] ||= '_parent' if url =~ %r{^(\w+)://}
- "<a #{tag_attrs(params)}>#{title}</a>".gsub(/[\r\n]/, ' ')
- end
-
- # @group URL Helpers
-
- # @param [CodeObjects::Base] object the object to get an anchor for
- # @return [String] the anchor for a specific object
- def anchor_for(object)
- case object
- when CodeObjects::MethodObject
- "#{object.name}-#{object.scope}_#{object.type}"
- when CodeObjects::ClassVariableObject
- "#{object.name.to_s.gsub('@@', '')}-#{object.type}"
- when CodeObjects::Base
- "#{object.name}-#{object.type}"
- when CodeObjects::Proxy
- object.path
- else
- object.to_s
- end
- end
-
- # Returns the URL for an object.
- #
- # @param [String, CodeObjects::Base] obj the object (or object path) to link to
- # @param [String] anchor the anchor to link to
- # @param [Boolean] relative use a relative or absolute link
- # @return [String] the URL location of the object
- def url_for(obj, anchor = nil, relative = true)
- link = nil
- return link unless serializer
- return link if obj.is_a?(CodeObjects::Base) && run_verifier([obj]).empty?
-
- if obj.is_a?(CodeObjects::Base) && !obj.is_a?(CodeObjects::NamespaceObject)
- # If the obj is not a namespace obj make it the anchor.
- anchor = obj
- obj = obj.namespace
- end
-
- objpath = serializer.serialized_path(obj)
- return link unless objpath
-
- relative = false if object == Registry.root
- if relative
- fromobj = object
- if object.is_a?(CodeObjects::Base) &&
- !object.is_a?(CodeObjects::NamespaceObject)
- fromobj = owner
- end
-
- from = serializer.serialized_path(fromobj)
- link = File.relative_path(from, objpath)
- else
- link = objpath
- end
-
- link + (anchor ? '#' + urlencode(anchor_for(anchor)) : '')
- end
-
- alias mtime_url url_for
- def mtime(_file) nil end
-
- # Returns the URL for a specific file
- #
- # @param [String, CodeObjects::ExtraFileObject] filename the filename to link to
- # @param [String] anchor optional anchor
- # @return [String] the URL pointing to the file
- def url_for_file(filename, anchor = nil)
- return '' unless serializer
- fromobj = object
- if CodeObjects::Base === fromobj && !fromobj.is_a?(CodeObjects::NamespaceObject)
- fromobj = fromobj.namespace
- end
- from = serializer.serialized_path(fromobj)
- path = filename == options.readme ?
- 'index.html' : serializer.serialized_path(filename)
- link = File.relative_path(from, path)
- link += (anchor ? '#' + urlencode(anchor) : '')
- link
- end
-
- # Returns the URL for a list type
- #
- # @param [String, Symbol] type the list type to generate a URL for
- # @return [String] the URL pointing to the list
- # @since 0.8.0
- def url_for_list(type)
- url_for_file("#{type}_list.html")
- end
-
- # Returns the URL for the frameset page
- #
- # @return [String] the URL pointing to the frames page
- # @since 0.8.0
- def url_for_frameset
- url_for_file("frames.html")
- end
-
- # Returns the URL for the main page (README or alphabetic index)
- #
- # @return [String] the URL pointing to the first main page the
- # user should see.
- def url_for_main
- url_for_file("index.html")
- end
-
- # Returns the URL for the alphabetic index page
- #
- # @return [String] the URL pointing to the first main page the
- # user should see.
- def url_for_index
- url_for_file("_index.html")
- end
-
- # @group Formatting Objects and Attributes
-
- # Formats a list of objects and links them
- # @return [String] a formatted list of objects
- def format_object_name_list(objects)
- objects.sort_by {|o| o.name.to_s.downcase }.map do |o|
- "<span class='name'>" + linkify(o, o.name) + "</span>"
- end.join(", ")
- end
-
- # Formats a list of types from a tag.
- #
- # @param [Array<String>, FalseClass] typelist
- # the list of types to be formatted.
- #
- # @param [Boolean] brackets omits the surrounding
- # brackets if +brackets+ is set to +false+.
- #
- # @return [String] the list of types formatted
- # as [Type1, Type2, ...] with the types linked
- # to their respective descriptions.
- #
- def format_types(typelist, brackets = true)
- return unless typelist.is_a?(Array)
- list = typelist.map do |type|
- type = type.gsub(/([<>])/) { h($1) }
- type = type.gsub(/([\w:]+)/) { $1 == "lt" || $1 == "gt" ? $1 : linkify($1, $1) }
- "<tt>" + type + "</tt>"
- end
- list.empty? ? "" : (brackets ? "(#{list.join(", ")})" : list.join(", "))
- end
-
- # Get the return types for a method signature.
- #
- # @param [CodeObjects::MethodObject] meth the method object
- # @param [Boolean] link whether to link the types
- # @return [String] the signature types
- # @since 0.5.3
- def signature_types(meth, link = true)
- meth = convert_method_to_overload(meth)
- if meth.respond_to?(:object) && !meth.has_tag?(:return)
- meth = meth.object
- end
-
- type = options.default_return || ""
- if meth.tag(:return) && meth.tag(:return).types
- types = meth.tags(:return).map {|t| t.types ? t.types : [] }.flatten.uniq
- first = link ? h(types.first) : format_types([types.first], false)
- if types.size == 2 && types.last == 'nil'
- type = first + '<sup>?</sup>'
- elsif types.size == 2 && types.last =~ /^(Array)?<#{Regexp.quote types.first}>$/
- type = first + '<sup>+</sup>'
- elsif types.size > 2
- type = [first, '...'].join(', ')
- elsif types == ['void'] && options.hide_void_return
- type = ""
- else
- type = link ? h(types.join(", ")) : format_types(types, false)
- end
- elsif !type.empty?
- type = link ? h(type) : format_types([type], false)
- end
- type = "#{type} " unless type.empty?
- type
- end
-
- # Formats the signature of method +meth+.
- #
- # @param [CodeObjects::MethodObject] meth the method object to list
- # the signature of
- # @param [Boolean] link whether to link the method signature to the details view
- # @param [Boolean] show_extras whether to show extra meta-data (visibility, attribute info)
- # @param [Boolean] full_attr_name whether to show the full attribute name
- # ("name=" instead of "name")
- # @return [String] the formatted method signature
- def signature(meth, link = true, show_extras = true, full_attr_name = true)
- meth = convert_method_to_overload(meth)
-
- type = signature_types(meth, link)
- type = "⇒ #{type}" if type && !type.empty?
- scope = meth.scope == :class ? "." : "#"
- name = full_attr_name ? meth.name : meth.name.to_s.gsub(/^(\w+)=$/, '\1')
- blk = format_block(meth)
- args = !full_attr_name && meth.writer? ? "" : format_args(meth)
- extras = []
- extras_text = ''
- if show_extras
- rw = meth.attr_info
- if rw
- attname = [rw[:read] ? 'read' : nil, rw[:write] ? 'write' : nil].compact
- attname = attname.size == 1 ? attname.join('') + 'only' : nil
- extras << attname if attname
- end
- extras << meth.visibility if meth.visibility != :public
- extras_text = ' <span class="extras">(' + extras.join(", ") + ')</span>' unless extras.empty?
- end
- title = "%s<strong>%s</strong>%s %s %s" % [scope, h(name), args, blk, type]
- if link
- if meth.is_a?(YARD::CodeObjects::MethodObject)
- link_title = "#{h meth.name(true)} (#{meth.scope} #{meth.type})"
- else
- link_title = "#{h name} (#{meth.type})"
- end
- obj = meth.respond_to?(:object) ? meth.object : meth
- url = url_for(object, obj)
- link_url(url, title, :title => link_title) + extras_text
- else
- title + extras_text
- end
- end
-
- # @group Getting the Character Encoding
-
- # Returns the current character set. The default value can be overridden
- # by setting the +LANG+ environment variable or by overriding this
- # method. In Ruby 1.9 you can also modify this value by setting
- # +Encoding.default_external+.
- #
- # @return [String] the current character set
- # @since 0.5.4
- def charset
- has_encoding = defined?(::Encoding)
- if defined?(@file) && @file && has_encoding
- lang = @file.contents.encoding.to_s
- else
- return 'utf-8' unless has_encoding || ENV['LANG']
- lang =
- if has_encoding
- ::Encoding.default_external.name.downcase
- else
- ENV['LANG'].downcase.split('.').last
- end
- end
-
- case lang
- when "ascii-8bit", "us-ascii", "ascii-7bit"; 'iso-8859-1'
- when "utf8"; 'utf-8'
- else; lang
- end
- end
-
- # @endgroup
-
- private
-
- # Converts a set of hash options into HTML attributes for a tag
- #
- # @param [Hash{String => String}] opts the tag options
- # @return [String] the tag attributes of an HTML tag
- def tag_attrs(opts = {})
- opts.sort_by {|k, _v| k.to_s }.map {|k, v| "#{k}=#{v.to_s.inspect}" if v }.join(" ")
- end
-
- # Converts a {CodeObjects::MethodObject} into an overload object
- # @since 0.5.3
- def convert_method_to_overload(meth)
- # use first overload tag if it has a return type and method itself does not
- if !meth.tag(:return) && meth.tags(:overload).size == 1 && meth.tag(:overload).tag(:return)
- return meth.tag(:overload)
- end
- meth
- end
-
- # Parses !!!lang out of codeblock, returning the codeblock language
- # followed by the source code.
- #
- # @param [String] source the source code whose language to determine
- # @return [Array(String, String)] the language, if any, and the
- # remaining source
- # @since 0.7.5
- def parse_lang_for_codeblock(source)
- type = nil
- if source =~ /\A(?:[ \t]*\r?\n)?[ \t]*!!!([\w.+-]+)[ \t]*\r?\n/
- type = $1
- source = $'
- end
-
- [type, source]
- end
-
- # Parses code blocks out of html and performs syntax highlighting
- # on code inside of the blocks.
- #
- # @param [String] html the html to search for code in
- # @return [String] highlighted html
- # @see #html_syntax_highlight
- def parse_codeblocks(html)
- html.gsub(%r{<pre\s*(?:lang="(.+?)")?>(?:\s*<code\s*(?:class="(.+?)")?\s*>)?(.+?)(?:</code>\s*)?</pre>}m) do
- string = $3
- # handle !!!LANG prefix to send to html_syntax_highlight_LANG
- language, = parse_lang_for_codeblock(string)
- language ||= $1 || $2 || object.source_type
-
- if options.highlight
- string = html_syntax_highlight(CGI.unescapeHTML(string), language)
- end
- classes = ['code', language].compact.join(' ')
- %(<pre class="#{classes}"><code class="#{language}">#{string}</code></pre>)
- end
- end
- end
- end
-end
+# frozen_string_literal: true
+require 'cgi'
+
+module YARD
+ module Templates::Helpers
+ # The helper module for HTML templates.
+ module HtmlHelper
+ include MarkupHelper
+ include HtmlSyntaxHighlightHelper
+
+ # @private
+ URLMATCH = /[^\w\s~!\*'\(\):;@&=\$,\[\]<>-]/
+
+ # @private
+ ASCIIDOC_ATTRIBUTES = {"env" => "yard", "env-yard" => ""}.freeze
+
+ # @group Escaping Template Data
+
+ # Escapes HTML entities
+ #
+ # @param [String] text the text to escape
+ # @return [String] the HTML with escaped entities
+ def h(text)
+ CGI.escapeHTML(text.to_s)
+ end
+
+ # Escapes a URL
+ #
+ # @param [String] text the URL
+ # @return [String] the escaped URL
+ def urlencode(text)
+ text = text.dup
+ enc = nil
+ if text.respond_to?(:force_encoding)
+ enc = text.encoding
+ text = text.force_encoding('binary')
+ end
+
+ text = text.gsub(/%[a-z0-9]{2}|#{URLMATCH}/i) do
+ $&.size > 1 ? $& : "%" + $&.ord.to_s(16).upcase
+ end.tr(' ', '+')
+
+ text = text.force_encoding(enc) if enc
+ text
+ end
+
+ module_function :urlencode
+
+ # @group Converting Markup to HTML
+
+ # Turns text into HTML using +markup+ style formatting.
+ #
+ # @param [String] text the text to format
+ # @param [Symbol] markup examples are +:markdown+, +:textile+, +:rdoc+.
+ # To add a custom markup type, see {MarkupHelper}
+ # @return [String] the HTML
+ def htmlify(text, markup = options.markup)
+ markup_meth = "html_markup_#{markup}"
+ return text unless respond_to?(markup_meth)
+ return "" unless text
+ return text unless markup
+ html = send(markup_meth, text).dup
+ if html.respond_to?(:encode)
+ html = html.force_encoding(text.encoding) # for libs that mess with encoding
+ html = html.encode(:invalid => :replace, :replace => '?')
+ end
+ html = resolve_links(html)
+ unless [:text, :none, :pre].include?(markup)
+ html = parse_codeblocks(html)
+ end
+ html
+ end
+
+ # Converts Markdown to HTML
+ # @param [String] text input Markdown text
+ # @return [String] output HTML
+ # @since 0.6.0
+ def html_markup_markdown(text)
+ # TODO: other libraries might be more complex
+ provider = markup_class(:markdown)
+ if provider.to_s == 'RDiscount'
+ provider.new(text, :autolink).to_html
+ elsif provider.to_s == 'RedcarpetCompat'
+ provider.new(text, :no_intraemphasis, :gh_blockcode,
+ :fenced_code, :autolink, :tables,
+ :lax_spacing).to_html
+ else
+ provider.new(text).to_html
+ end
+ end
+
+ # Converts org-mode to HTML
+ # @param [String] text input org-mode text
+ # @return [String] output HTML
+ def html_markup_org(text)
+ markup_class(:org).new(text).to_html
+ end
+
+ # Converts Asciidoc to HTML
+ # @param [String] text input Asciidoc text
+ # @return [String] output HTML
+ def html_markup_asciidoc(text)
+ options = {:attributes => ASCIIDOC_ATTRIBUTES}
+ markup_class(:asciidoc).render(text, options)
+ end
+
+ # Converts Textile to HTML
+ # @param [String] text the input Textile text
+ # @return [String] output HTML
+ # @since 0.6.0
+ def html_markup_textile(text)
+ doc = markup_class(:textile).new(text)
+ doc.hard_breaks = false if doc.respond_to?(:hard_breaks=)
+ doc.to_html
+ end
+
+ # Converts plaintext to strict Textile (hard breaks)
+ # @param [String] text the input textile data
+ # @return [String] the output HTML
+ # @since 0.6.0
+ def html_markup_textile_strict(text)
+ markup_class(:textile).new(text).to_html
+ end
+
+ # Converts RDoc formatting (SimpleMarkup) to HTML
+ # @param [String] text the input RDoc formatted text
+ # @return [String] output HTML
+ # @since 0.6.0
+ def html_markup_rdoc(text)
+ doc = markup_class(:rdoc).new(text)
+ doc.from_path = url_for(object) if doc.respond_to?(:from_path=)
+ doc.to_html
+ end
+
+ # Converts plaintext to pre-formatted HTML
+ # @param [String] text the input text
+ # @return [String] the output HTML
+ # @since 0.6.0
+ def html_markup_pre(text)
+ "<pre>" + h(text) + "</pre>"
+ end
+
+ # Converts plaintext to regular HTML
+ # @param [String] text the input text
+ # @return [String] the output HTML
+ # @since 0.6.0
+ def html_markup_text(text)
+ h(text).gsub(/\r?\n/, '<br/>')
+ end
+
+ # @return [String] the same text with no markup
+ # @since 0.6.6
+ def html_markup_none(text)
+ h(text)
+ end
+
+ # Converts HTML to HTML
+ # @param [String] text input html
+ # @return [String] output HTML
+ # @since 0.6.0
+ def html_markup_html(text)
+ text
+ end
+
+ # Highlights Ruby source. Similar to {#html_syntax_highlight}, but
+ # this method is meant to be called from {#htmlify} when markup is
+ # set to "ruby".
+ #
+ # @param [String] source the Ruby source
+ # @return [String] the highlighted HTML
+ # @since 0.7.0
+ def html_markup_ruby(source)
+ '<pre class="code ruby">' + html_syntax_highlight(source, :ruby) + '</pre>'
+ end
+
+ # @return [String] HTMLified text as a single line (paragraphs removed)
+ def htmlify_line(*args)
+ "<div class='inline'>" + htmlify(*args) + "</div>"
+ end
+
+ # @group Syntax Highlighting Source Code
+
+ # Syntax highlights +source+ in language +type+.
+ #
+ # @note To support a specific language +type+, implement the method
+ # +html_syntax_highlight_TYPE+ in this class.
+ #
+ # @param [String] source the source code to highlight
+ # @param [Symbol, String] type the language type (:ruby, :plain, etc). Use
+ # :plain for no syntax highlighting.
+ # @return [String] the highlighted source
+ def html_syntax_highlight(source, type = nil)
+ return "" unless source
+ return h(source) unless options.highlight
+
+ new_type, source = parse_lang_for_codeblock(source)
+ type ||= new_type || :ruby
+ meth = "html_syntax_highlight_#{type}"
+ respond_to?(meth) ? send(meth, source) : h(source)
+ end
+
+ # @return [String] unhighlighted source
+ def html_syntax_highlight_plain(source)
+ h(source)
+ end
+
+ # @group Linking Objects and URLs
+
+ # Resolves any text in the form of +{Name}+ to the object specified by
+ # Name. Also supports link titles in the form +{Name title}+.
+ #
+ # @example Linking to an instance method
+ # resolve_links("{MyClass#method}") # => "<a href='...'>MyClass#method</a>"
+ # @example Linking to a class with a title
+ # resolve_links("{A::B::C the C class}") # => "<a href='...'>the c class</a>"
+ # @param [String] text the text to resolve links in
+ # @return [String] HTML with linkified references
+ def resolve_links(text)
+ code_tags = 0
+ text.gsub(%r{<(/)?(pre|code|tt)|(\\|!)?\{(?!\})(\S+?)(?:\s([^\}]*?\S))?\}(?=[\W<]|.+</|$)}m) do |str|
+ closed = $1
+ tag = $2
+ escape = $3
+ name = $4
+ title = $5
+ match = $&
+ if tag
+ code_tags += (closed ? -1 : 1)
+ next str
+ end
+ next str unless code_tags == 0
+
+ next(match[1..-1]) if escape
+
+ next(match) if name[0, 1] == '|'
+
+ if name == '<a' && title =~ %r{href=["'](.+?)["'].*>.*</a>\s*(.*)\Z}
+ name = $1
+ title = $2
+ title = nil if title.empty?
+ end
+
+ name = CGI.unescapeHTML(name)
+
+ if object.is_a?(String)
+ object
+ else
+ link = linkify(name, title)
+ if (link == name || link == title) && (name + ' ' + link !~ /\A<a\s.*>/)
+ match = /(.+)?(\{#{Regexp.quote name}(?:\s.*?)?\})(.+)?/.match(text)
+ file = (defined?(@file) && @file ? @file.filename : object.file) || '(unknown)'
+ line = (defined?(@file) && @file ? 1 : (object.docstring.line_range ? object.docstring.line_range.first : 1)) + (match ? $`.count("\n") : 0)
+ log.warn "In file `#{file}':#{line}: Cannot resolve link to #{name} from text" + (match ? ":" : ".") +
+ "\n\t" + (match[1] ? '...' : '') + match[2].delete("\n") + (match[3] ? '...' : '') if match
+ end
+
+ link
+ end
+ end
+ end
+
+ # (see BaseHelper#link_file)
+ def link_file(filename, title = nil, anchor = nil)
+ if CodeObjects::ExtraFileObject === filename
+ file = filename
+ else
+ contents = File.file?(filename) ? nil : ''
+ file = CodeObjects::ExtraFileObject.new(filename, contents)
+ end
+ return title || file.title unless serializer
+ link_url(url_for_file(file, anchor), title || file.title)
+ end
+
+ # (see BaseHelper#link_include_file)
+ def link_include_file(file)
+ unless file.is_a?(CodeObjects::ExtraFileObject)
+ file = CodeObjects::ExtraFileObject.new(file)
+ end
+ file.attributes[:markup] ||= markup_for_file('', file.filename)
+ insert_include(file.contents, file.attributes[:markup] || options.markup)
+ end
+
+ # (see BaseHelper#link_include_object)
+ def link_include_object(obj)
+ insert_include(obj.docstring)
+ end
+
+ # Inserts an include link while respecting inlining
+ def insert_include(text, markup = options.markup)
+ htmlify(text, markup).gsub(%r{\A\s*<p>|</p>\s*\Z}, '')
+ end
+
+ # (see BaseHelper#link_object)
+ def link_object(obj, title = nil, anchor = nil, relative = true)
+ return title if obj.nil?
+ obj = Registry.resolve(object, obj, true, true) if obj.is_a?(String)
+ if title
+ title = title.to_s
+ elsif object.is_a?(CodeObjects::Base)
+ # Check if we're linking to a class method in the current
+ # object. If we are, create a title in the format of
+ # "CurrentClass.method_name"
+ if obj.is_a?(CodeObjects::MethodObject) && obj.scope == :class && obj.parent == object
+ title = h([object.name, obj.sep, obj.name].join)
+ elsif obj.title != obj.path
+ title = h(obj.title)
+ else
+ title = h(object.relative_path(obj))
+ end
+ else
+ title = h(obj.title)
+ end
+ return title unless serializer
+ return title if obj.is_a?(CodeObjects::Proxy)
+
+ link = url_for(obj, anchor, relative)
+ link = link ? link_url(link, title, :title => h("#{obj.title} (#{obj.type})")) : title
+ "<span class='object_link'>" + link + "</span>"
+ end
+
+ # (see BaseHelper#link_url)
+ def link_url(url, title = nil, params = {})
+ title ||= url
+ title = title.gsub(/[\r\n]/, ' ')
+ params = SymbolHash.new(false).update(
+ :href => url,
+ :title => h(title)
+ ).update(params)
+ params[:target] ||= '_parent' if url =~ %r{^(\w+)://}
+ "<a #{tag_attrs(params)}>#{title}</a>".gsub(/[\r\n]/, ' ')
+ end
+
+ # @group URL Helpers
+
+ # @param [CodeObjects::Base] object the object to get an anchor for
+ # @return [String] the anchor for a specific object
+ def anchor_for(object)
+ case object
+ when CodeObjects::MethodObject
+ "#{object.name}-#{object.scope}_#{object.type}"
+ when CodeObjects::ClassVariableObject
+ "#{object.name.to_s.gsub('@@', '')}-#{object.type}"
+ when CodeObjects::Base
+ "#{object.name}-#{object.type}"
+ when CodeObjects::Proxy
+ object.path
+ else
+ object.to_s
+ end
+ end
+
+ # Returns the URL for an object.
+ #
+ # @param [String, CodeObjects::Base] obj the object (or object path) to link to
+ # @param [String] anchor the anchor to link to
+ # @param [Boolean] relative use a relative or absolute link
+ # @return [String] the URL location of the object
+ def url_for(obj, anchor = nil, relative = true)
+ link = nil
+ return link unless serializer
+ return link if obj.is_a?(CodeObjects::Base) && run_verifier([obj]).empty?
+
+ if obj.is_a?(CodeObjects::Base) && !obj.is_a?(CodeObjects::NamespaceObject)
+ # If the obj is not a namespace obj make it the anchor.
+ anchor = obj
+ obj = obj.namespace
+ end
+
+ objpath = serializer.serialized_path(obj)
+ return link unless objpath
+
+ relative = false if object == Registry.root
+ if relative
+ fromobj = object
+ if object.is_a?(CodeObjects::Base) &&
+ !object.is_a?(CodeObjects::NamespaceObject)
+ fromobj = owner
+ end
+
+ from = serializer.serialized_path(fromobj)
+ link = File.relative_path(from, objpath)
+ else
+ link = objpath
+ end
+
+ link + (anchor ? '#' + urlencode(anchor_for(anchor)) : '')
+ end
+
+ alias mtime_url url_for
+ def mtime(_file) nil end
+
+ # Returns the URL for a specific file
+ #
+ # @param [String, CodeObjects::ExtraFileObject] filename the filename to link to
+ # @param [String] anchor optional anchor
+ # @return [String] the URL pointing to the file
+ def url_for_file(filename, anchor = nil)
+ return '' unless serializer
+ fromobj = object
+ if CodeObjects::Base === fromobj && !fromobj.is_a?(CodeObjects::NamespaceObject)
+ fromobj = fromobj.namespace
+ end
+ from = serializer.serialized_path(fromobj)
+ path = filename == options.readme ?
+ 'index.html' : serializer.serialized_path(filename)
+ link = File.relative_path(from, path)
+ link += (anchor ? '#' + urlencode(anchor) : '')
+ link
+ end
+
+ # Returns the URL for a list type
+ #
+ # @param [String, Symbol] type the list type to generate a URL for
+ # @return [String] the URL pointing to the list
+ # @since 0.8.0
+ def url_for_list(type)
+ url_for_file("#{type}_list.html")
+ end
+
+ # Returns the URL for the frameset page
+ #
+ # @return [String] the URL pointing to the frames page
+ # @since 0.8.0
+ def url_for_frameset
+ url_for_file("frames.html")
+ end
+
+ # Returns the URL for the main page (README or alphabetic index)
+ #
+ # @return [String] the URL pointing to the first main page the
+ # user should see.
+ def url_for_main
+ url_for_file("index.html")
+ end
+
+ # Returns the URL for the alphabetic index page
+ #
+ # @return [String] the URL pointing to the first main page the
+ # user should see.
+ def url_for_index
+ url_for_file("_index.html")
+ end
+
+ # @group Formatting Objects and Attributes
+
+ # Formats a list of objects and links them
+ # @return [String] a formatted list of objects
+ def format_object_name_list(objects)
+ objects.sort_by {|o| o.name.to_s.downcase }.map do |o|
+ "<span class='name'>" + linkify(o, o.name) + "</span>"
+ end.join(", ")
+ end
+
+ # Formats a list of types from a tag.
+ #
+ # @param [Array<String>, FalseClass] typelist
+ # the list of types to be formatted.
+ #
+ # @param [Boolean] brackets omits the surrounding
+ # brackets if +brackets+ is set to +false+.
+ #
+ # @return [String] the list of types formatted
+ # as [Type1, Type2, ...] with the types linked
+ # to their respective descriptions.
+ #
+ def format_types(typelist, brackets = true)
+ return unless typelist.is_a?(Array)
+ list = typelist.map do |type|
+ type = type.gsub(/([<>])/) { h($1) }
+ type = type.gsub(/([\w:]+)/) { $1 == "lt" || $1 == "gt" ? $1 : linkify($1, $1) }
+ "<tt>" + type + "</tt>"
+ end
+ list.empty? ? "" : (brackets ? "(#{list.join(", ")})" : list.join(", "))
+ end
+
+ # Get the return types for a method signature.
+ #
+ # @param [CodeObjects::MethodObject] meth the method object
+ # @param [Boolean] link whether to link the types
+ # @return [String] the signature types
+ # @since 0.5.3
+ def signature_types(meth, link = true)
+ meth = convert_method_to_overload(meth)
+ if meth.respond_to?(:object) && !meth.has_tag?(:return)
+ meth = meth.object
+ end
+
+ type = options.default_return || ""
+ if meth.tag(:return) && meth.tag(:return).types
+ types = meth.tags(:return).map {|t| t.types ? t.types : [] }.flatten.uniq
+ first = link ? h(types.first) : format_types([types.first], false)
+ if types.size == 2 && types.last == 'nil'
+ type = first + '<sup>?</sup>'
+ elsif types.size == 2 && types.last =~ /^(Array)?<#{Regexp.quote types.first}>$/
+ type = first + '<sup>+</sup>'
+ elsif types.size > 2
+ type = [first, '...'].join(', ')
+ elsif types == ['void'] && options.hide_void_return
+ type = ""
+ else
+ type = link ? h(types.join(", ")) : format_types(types, false)
+ end
+ elsif !type.empty?
+ type = link ? h(type) : format_types([type], false)
+ end
+ type = "#{type} " unless type.empty?
+ type
+ end
+
+ # Formats the signature of method +meth+.
+ #
+ # @param [CodeObjects::MethodObject] meth the method object to list
+ # the signature of
+ # @param [Boolean] link whether to link the method signature to the details view
+ # @param [Boolean] show_extras whether to show extra meta-data (visibility, attribute info)
+ # @param [Boolean] full_attr_name whether to show the full attribute name
+ # ("name=" instead of "name")
+ # @return [String] the formatted method signature
+ def signature(meth, link = true, show_extras = true, full_attr_name = true)
+ meth = convert_method_to_overload(meth)
+
+ type = signature_types(meth, link)
+ type = "⇒ #{type}" if type && !type.empty?
+ scope = meth.scope == :class ? "." : "#"
+ name = full_attr_name ? meth.name : meth.name.to_s.gsub(/^(\w+)=$/, '\1')
+ blk = format_block(meth)
+ args = !full_attr_name && meth.writer? ? "" : format_args(meth)
+ extras = []
+ extras_text = ''
+ if show_extras
+ rw = meth.attr_info
+ if rw
+ attname = [rw[:read] ? 'read' : nil, rw[:write] ? 'write' : nil].compact
+ attname = attname.size == 1 ? attname.join('') + 'only' : nil
+ extras << attname if attname
+ end
+ extras << meth.visibility if meth.visibility != :public
+ extras_text = ' <span class="extras">(' + extras.join(", ") + ')</span>' unless extras.empty?
+ end
+ title = "%s<strong>%s</strong>%s %s %s" % [scope, h(name), args, blk, type]
+ if link
+ if meth.is_a?(YARD::CodeObjects::MethodObject)
+ link_title = "#{h meth.name(true)} (#{meth.scope} #{meth.type})"
+ else
+ link_title = "#{h name} (#{meth.type})"
+ end
+ obj = meth.respond_to?(:object) ? meth.object : meth
+ url = url_for(object, obj)
+ link_url(url, title, :title => link_title) + extras_text
+ else
+ title + extras_text
+ end
+ end
+
+ # @group Getting the Character Encoding
+
+ # Returns the current character set. The default value can be overridden
+ # by setting the +LANG+ environment variable or by overriding this
+ # method. In Ruby 1.9 you can also modify this value by setting
+ # +Encoding.default_external+.
+ #
+ # @return [String] the current character set
+ # @since 0.5.4
+ def charset
+ has_encoding = defined?(::Encoding)
+ if defined?(@file) && @file && has_encoding
+ lang = @file.contents.encoding.to_s
+ else
+ return 'utf-8' unless has_encoding || ENV['LANG']
+ lang =
+ if has_encoding
+ ::Encoding.default_external.name.downcase
+ else
+ ENV['LANG'].downcase.split('.').last
+ end
+ end
+
+ case lang
+ when "ascii-8bit", "us-ascii", "ascii-7bit"; 'iso-8859-1'
+ when "utf8"; 'utf-8'
+ else; lang
+ end
+ end
+
+ # @endgroup
+
+ private
+
+ # Converts a set of hash options into HTML attributes for a tag
+ #
+ # @param [Hash{String => String}] opts the tag options
+ # @return [String] the tag attributes of an HTML tag
+ def tag_attrs(opts = {})
+ opts.sort_by {|k, _v| k.to_s }.map {|k, v| "#{k}=#{v.to_s.inspect}" if v }.join(" ")
+ end
+
+ # Converts a {CodeObjects::MethodObject} into an overload object
+ # @since 0.5.3
+ def convert_method_to_overload(meth)
+ # use first overload tag if it has a return type and method itself does not
+ if !meth.tag(:return) && meth.tags(:overload).size == 1 && meth.tag(:overload).tag(:return)
+ return meth.tag(:overload)
+ end
+ meth
+ end
+
+ # Parses !!!lang out of codeblock, returning the codeblock language
+ # followed by the source code.
+ #
+ # @param [String] source the source code whose language to determine
+ # @return [Array(String, String)] the language, if any, and the
+ # remaining source
+ # @since 0.7.5
+ def parse_lang_for_codeblock(source)
+ type = nil
+ if source =~ /\A(?:[ \t]*\r?\n)?[ \t]*!!!([\w.+-]+)[ \t]*\r?\n/
+ type = $1
+ source = $'
+ end
+
+ [type, source]
+ end
+
+ # Parses code blocks out of html and performs syntax highlighting
+ # on code inside of the blocks.
+ #
+ # @param [String] html the html to search for code in
+ # @return [String] highlighted html
+ # @see #html_syntax_highlight
+ def parse_codeblocks(html)
+ html.gsub(%r{<pre\s*(?:lang="(.+?)")?>(?:\s*<code\s*(?:class="(.+?)")?\s*>)?(.+?)(?:</code>\s*)?</pre>}m) do
+ string = $3
+ # handle !!!LANG prefix to send to html_syntax_highlight_LANG
+ language, = parse_lang_for_codeblock(string)
+ language ||= $1 || $2 || object.source_type
+
+ if options.highlight
+ string = html_syntax_highlight(CGI.unescapeHTML(string), language)
+ end
+ classes = ['code', language].compact.join(' ')
+ %(<pre class="#{classes}"><code class="#{language}">#{string}</code></pre>)
+ end
+ end
+ end
+ end
+end