lib/yard/templates/helpers/html_helper.rb in yard-0.9.5 vs lib/yard/templates/helpers/html_helper.rb in yard-0.9.6

- old
+ new

@@ -1,14 +1,18 @@ +# 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 @@ -20,13 +24,27 @@ # Escapes a URL # # @param [String] text the URL # @return [String] the escaped URL def urlencode(text) - CGI.escape(text.to_s) + 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 @@ -36,11 +54,11 @@ 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) + 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) @@ -186,39 +204,45 @@ # 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(/<(\/)?(pre|code|tt)|(\\|!)?\{(?!\})(\S+?)(?:\s([^\}]*?\S))?\}(?=[\W<]|.+<\/|$)/m) do |str| - closed, tag, escape, name, title, match = $1, $2, $3, $4, $5, $& + 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] == '|' + next(match) if name[0, 1] == '|' - if name == '<a' && title =~ /href=["'](.+?)["'].*>.*<\/a>\s*(.*)\Z/ - name, title = $1, $2 + 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.*>/) + if (link == name || link == title) && (name + ' ' + link !~ /\A<a\s.*>/) match = /(.+)?(\{#{Regexp.quote name}(?:\s.*?)?\})(.+)?/.match(text) - file = (@file ? @file.filename : object.file) || '(unknown)' - line = (@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 ? ":" : ".") - log.warn((match[1] ? '...' : '') + match[2].gsub("\n","") + (match[3] ? '...' : '')) if match + 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 @@ -250,11 +274,11 @@ insert_include(obj.docstring) end # Inserts an include link while respecting inlining def insert_include(text, markup = options.markup) - htmlify(text, markup).gsub(/\A\s*<p>|<\/p>\s*\Z/, '') + 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? @@ -284,16 +308,16 @@ end # (see BaseHelper#link_url) def link_url(url, title = nil, params = {}) title ||= url - title.gsub!(/[\r\n]/, ' ') + title = title.gsub(/[\r\n]/, ' ') params = SymbolHash.new(false).update( :href => url, - :title => h(title) + :title => h(title) ).update(params) - params[:target] ||= '_parent' if url =~ /^(\w+):\/\// + params[:target] ||= '_parent' if url =~ %r{^(\w+)://} "<a #{tag_attrs(params)}>#{title}</a>".gsub(/[\r\n]/, ' ') end # @group URL Helpers @@ -325,21 +349,22 @@ 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 + 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) + !object.is_a?(CodeObjects::NamespaceObject) fromobj = owner end from = serializer.serialized_path(fromobj) link = File.relative_path(from, objpath) @@ -348,10 +373,13 @@ 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 @@ -360,15 +388,12 @@ fromobj = object if CodeObjects::Base === fromobj && !fromobj.is_a?(CodeObjects::NamespaceObject) fromobj = fromobj.namespace end from = serializer.serialized_path(fromobj) - if filename == options.readme - path = 'index.html' - else - path = serializer.serialized_path(filename) - end + path = filename == options.readme ? + 'index.html' : serializer.serialized_path(filename) link = File.relative_path(from, path) link += (anchor ? '#' + urlencode(anchor) : '') link end @@ -490,11 +515,12 @@ blk = format_block(meth) args = !full_attr_name && meth.writer? ? "" : format_args(meth) extras = [] extras_text = '' if show_extras - if rw = meth.attr_info + 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 @@ -524,20 +550,22 @@ # # @return [String] the current character set # @since 0.5.4 def charset has_encoding = defined?(::Encoding) - if @file && has_encoding + if defined?(@file) && @file && has_encoding lang = @file.contents.encoding.to_s else - return 'utf-8' unless has_encoding || lang = ENV['LANG'] - if has_encoding - lang = ::Encoding.default_external.name.downcase - else - lang = lang.downcase.split('.').last - end + 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 @@ -550,11 +578,11 @@ # 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(" ") + 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) @@ -573,11 +601,12 @@ # 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, source = $1, $' + type = $1 + source = $' end [type, source] end @@ -586,22 +615,21 @@ # # @param [String] html the html to search for code in # @return [String] highlighted html # @see #html_syntax_highlight def parse_codeblocks(html) - html.gsub(/<pre\s*(?:lang="(.+?)")?>(?:\s*<code\s*(?:class="(.+?)")?\s*>)?(.+?)(?:<\/code>\s*)?<\/pre>/m) do + 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, = 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(' ') - %Q{<pre class="#{classes}"><code class="#{language}">#{string}</code></pre>} + %(<pre class="#{classes}"><code class="#{language}">#{string}</code></pre>) end end end end end -