require 'redcarpet' require 'rouge' require 'rouge/plugins/redcarpet' module Jazzy module Markdown class JazzyHTML < Redcarpet::Render::HTML include Redcarpet::Render::SmartyPants include Rouge::Plugins::Redcarpet attr_accessor :default_language def header(text, header_level) text_slug = text.gsub(/[^\w]+/, '-') .downcase .sub(/^-/, '') .sub(/-$/, '') "" \ "#{text}" \ "\n" end # List from # https://github.com/apple/swift/blob/master/include/swift/Markup/SimpleFields.def UNIQUELY_HANDLED_CALLOUTS = %w[parameters parameter returns].freeze GENERAL_CALLOUTS = %w[attention author authors bug complexity copyright date experiment important invariant keyword mutatingvariant nonmutatingvariant note postcondition precondition recommended recommendedover remark remarks requires see seealso since todo throws version warning].freeze SPECIAL_LIST_TYPES = (UNIQUELY_HANDLED_CALLOUTS + GENERAL_CALLOUTS).freeze SPECIAL_LIST_TYPE_REGEX = %r{ \A\s* # optional leading spaces (

\s*)? # optional opening p tag # any one of our special list types (#{SPECIAL_LIST_TYPES.map(&Regexp.method(:escape)).join('|')}) [\s:] # followed by either a space or a colon }ix ELIDED_LI_TOKEN = '7wNVzLB0OYPL2eGlPKu8q4vITltqh0Y6DPZf659TPMAeYh49o'.freeze def list_item(text, _list_type) if text =~ SPECIAL_LIST_TYPE_REGEX type = Regexp.last_match(2) if UNIQUELY_HANDLED_CALLOUTS.include? type.downcase return ELIDED_LI_TOKEN end return render_aside(type, text.sub(/#{Regexp.escape(type)}:\s+/, '')) end str = '

  • ' str << text.strip str << "
  • \n" end def render_aside(type, text) <<-HTML

    #{type.underscore.humanize}

    #{text}
    HTML end def list(text, list_type) elided = text.gsub!(ELIDED_LI_TOKEN, '') return if text =~ /\A\s*\Z/ && elided return text if text =~ /class="aside-title"/ str = "\n" str << (list_type == :ordered ? "
      \n" : "
    \n" : "\n") end def block_code(code, language) super(code, language || default_language) end def rouge_formatter(lexer) Highlighter::Formatter.new(lexer.tag) end end REDCARPET_OPTIONS = { autolink: true, fenced_code_blocks: true, no_intra_emphasis: true, quote: true, strikethrough: true, space_after_headers: false, tables: true, lax_spacing: true, }.freeze # Spot and capture returns & param HTML for separate display. class JazzyDeclarationHTML < JazzyHTML attr_reader :returns, :parameters def reset @returns = nil @parameters = {} end INTRO_PAT = '\A(?\s*(

    \s*)?)'.freeze OUTRO_PAT = '(?.*)\z'.freeze RETURNS_REGEX = /#{INTRO_PAT}returns:#{OUTRO_PAT}/im IDENT_PAT = '(?\S+)'.freeze # Param formats: normal swift, objc via sourcekitten, and # possibly inside 'Parameters:' PARAM_PAT1 = "(parameter +#{IDENT_PAT}\\s*:)".freeze PARAM_PAT2 = "(parameter:\\s*#{IDENT_PAT}\\s+)".freeze PARAM_PAT3 = "(#{IDENT_PAT}\\s*:)".freeze PARAM_PAT = "(?:#{PARAM_PAT1}|#{PARAM_PAT2}|#{PARAM_PAT3})".freeze PARAM_REGEX = /#{INTRO_PAT}#{PARAM_PAT}#{OUTRO_PAT}/im def list_item(text, _list_type) if text =~ RETURNS_REGEX @returns = render_param_returns(Regexp.last_match) elsif text =~ PARAM_REGEX @parameters[Regexp.last_match(:param)] = render_param_returns(Regexp.last_match) end super end def render_param_returns(matches) body = matches[:intro].strip + matches[:outro].strip body = "

    #{body}

    " unless body.start_with?('

    ') # call smartypants for pretty quotes etc. postprocess(body) end end def self.renderer @renderer ||= JazzyDeclarationHTML.new end def self.markdown @markdown ||= Redcarpet::Markdown.new(renderer, REDCARPET_OPTIONS) end def self.render(markdown_text, default_language = nil) renderer.reset renderer.default_language = default_language markdown.render(markdown_text) end def self.rendered_returns renderer.returns end def self.rendered_parameters renderer.parameters end class JazzyCopyright < Redcarpet::Render::HTML def link(link, _title, content) %(#{content}) end end def self.copyright_markdown @copyright_markdown ||= Redcarpet::Markdown.new( JazzyCopyright, REDCARPET_OPTIONS, ) end def self.render_copyright(markdown_text) copyright_markdown.render(markdown_text) end end end