# 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, :ruby].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).convert(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) "
" + h(text) + "" 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/, '
' + html_syntax_highlight(source, :ruby) + '' end # @return [String] HTMLified text as a single line (paragraphs removed) def htmlify_line(*args) "
|
\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 "" + link + "" 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+)://} "#{title}".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| "" + linkify(o, o.name) + "" end.join(", ") end # Formats a list of types from a tag. # # @param [Array(?:\s*)?(.+?)(?:
\s*)?
}m) do
string = $3
# handle !!!LANG prefix to send to html_syntax_highlight_LANG
language, = parse_lang_for_codeblock(string)
language ||= detect_lang_in_codeblock_attributes($1, $2)
language ||= object.source_type
if options.highlight
string = html_syntax_highlight(CGI.unescapeHTML(string), language)
end
classes = ['code', language].compact.join(' ')
%(#{string}
)
end
end
# Parses code block's HTML attributes in order to detect the programming
# language of what's enclosed in that code block.
#
# @param [String, nil] pre_html_attrs HTML attribute list of +pre+ element
# @param [String, nil] code_html_attrs HTML attribute list of +code+
# element
# @return [String, nil] detected programming language
def detect_lang_in_codeblock_attributes(pre_html_attrs, code_html_attrs)
detected = nil
detected ||= (/\bdata-lang="(.+?)"/ =~ code_html_attrs && $1)
detected ||= (/\blang="(.+?)"/ =~ pre_html_attrs && $1)
detected ||= (/\bclass="(.+?)"/ =~ code_html_attrs && $1)
detected
end
end
end
end