Skip to content
This repository
branch: master
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 349 lines (309 sloc) 11.678 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348
require 'coderay'
require 'cgi'

module SitemapRenderOverride

  @@_current_version = nil
  
  def self.current_version=(v)
    @@_current_version = v
  end

  def self.current_version
    @@_current_version
  end

  def sitemap_pages
    return $sitemap_pages if $sitemap_pages
    $sitemap_pages = {}
    source = defined?(store) ? store : sitemap
    source.resources.each do |resource|
      # we only want "wiki" links, not images, etc
      next unless resource.url =~ /(html|[\/])$/
      # name = format_name(extract_name(resource.url))
      project = resource.metadata[:page]["project"]
      value = {:url => resource.url, :project => project}
      # $sitemap_pages[name] ||= value
      title = resource.metadata[:page]["title"]
      # next if title.blank?
      title = format_name(title)
      $sitemap_pages[title] = value
    end
    $sitemap_pages
  end

  def sitemap_page_key(page)
    name = format_name(page.metadata[:page]["title"]).presence
    name ||= format_name(extract_name(page.url))
    name
  end

  def extract_name(path)
    path.to_s.scan(/([^\/]+)(?:\/|\.\w+)$/u).first.first
  rescue
    path
  end

  def format_name(name)
    name.to_s.downcase.gsub(/[\s\/?]|(---)/, '-').gsub(/\-+/, '-')
  end

  # prepends X directories from the top, eg:
  # trim_dir_depth('/a', 2) => '../../a'
  def prepend_dir_depth(path, dir_depth)
    ('../' * dir_depth) + path.sub(/^[\/]/, '')
  end

  def dir_depth(path)
    # puts path
    depth = path.sub(/[^\/]+\.\w+$/, '').split('/').size - 1
    depth = 0 if path =~ /\/((?:#{projects_regex})\/[^\/]+)\/?(index\.html)?$/
    depth <= 0 ? 0 : depth
  end

  # replace [[...]] with local links, wiki-style
  def wiki_links!(data)
    data.gsub!(/\[\[([^\]]+?)(?:\|([^\]]+))?\]\]/um) do
      link_name = $2 || $1
      link_label = $1 || link_name
      anchor = nil
      link_name, anchor = link_name.split('#', 2) if link_name.include?('#')
      link_data = $sitemap_pages[format_name(link_name)] || {}
      # heuristic that an unfound url, is probably not a link
      link_url = link_data[:url]
      if link_url.blank? && link_name !~ /^([.]?\/|https?\:)/
        $stderr.puts "#{url} Unknown link [[#{link_label}]]"
        "[[#{link_label}]]"
      else
        # no html inside of the link or label
        link_label.gsub!(/\<[^\>]+\>/u, '_')
        link_url ||= link_name
        link_url += '#' + anchor unless anchor.blank?
        link_url.gsub!(/\<[^\>]+\>/u, '_')
        link_project = link_data[:project] || $default_project
        "<a href=\"#{link_url}\" class=\"#{link_project}\">#{link_label}</a>"
      end
    end
  end

  def strip_versions!(data)
    project = (metadata[:page]["project"] || $default_project).to_sym
    
    raw_version_str = SitemapRenderOverride.current_version || $versions[project]

    if raw_version_str
      # Ignore rcX if this is a pre-release
      version_str = raw_version_str.sub(/(rc\d+|pre\d+)/i, '')
      version = Versionomy.parse(version_str)

      # Create a version placeholder
      data.gsub!(/\{\{VERSION\}\}/) do
        raw_version_str
      end
      data.gsub!(/\{\{V.V.V\}\}/) do
        version_str
      end
      vv_version_str = version_str.gsub(/^(\d+\.\d+).*?$/, '\1')
      data.gsub!(/\{\{V.V\}\}/) do
        vv_version_str
      end
      v_version_str = vv_version_str.gsub(/^(\d+)\..*?$/, '\1')
      data.gsub!(/\{\{V\}\}/) do
        v_version_str
      end

      # if it's a different version, remove the entire block
      data.gsub!(/\{\{\#([^\}]+)\}\}(.*?)\{\{\/(\1)\}\}/m) do
        liversion, block = $1, $2
        liversion = liversion.sub(/\&lt\;/, '<').sub(/\&gt\;/, '>')
        if in_version_range?(liversion, version)
          # nested version block
          block.gsub(/\{\{\#([^\}]+)\}\}(.*?)\{\{\/(\1)\}\}/m) do
            liversion2, block2 = $1, $2
            liversion2 = liversion2.sub(/\&lt\;/, '<').sub(/\&gt\;/, '>')
            if in_version_range?(liversion2, version)
              block2
            else
              ''
            end
          end
        else
          ''
        end
      end

      # if it's in a list in a different version, remove the entire <li></li>
      # data.gsub!(/(\<li(?:\s[^\>]*?)?\>.*?)\{\{([^\}]+?)\}\}(.*?<\/li\>)/) do
      data.gsub!(/(\<li(?:\s[^\>]*?)?\>(?:(?!\<li).)*?)\{\{([^\}]+?)\}\}(.*?<\/li\>)/m) do
        startli, liversion, endli = $1, $2, $3
        liversion = liversion.sub(/\&lt\;/, '<').sub(/\&gt\;/, '>')
        if liversion =~ /^(?:[\<\>][\=]?)?[\d\.\-]+?(?:rc\d+|pre\d+)?[\+\-]?$/
          in_version_range?(liversion, version) ? startli + endli : ''
        else
          startli + endli
        end
      end

      data.gsub!(/(\<tr(?:\s[^\>]*?)?\>(?:(?!\<tr).)*?)\{\{([^\}]+?)\}\}(.*?<\/tr\>)/m) do
        startli, liversion, endli = $1, $2, $3
        liversion = liversion.sub(/\&lt\;/, '<').sub(/\&gt\;/, '>')
        if liversion =~ /^(?:[\<\>][\=]?)?[\d\.\-]+?(?:rc\d+|pre\d+)?[\+\-]?$/
          in_version_range?(liversion, version) ? startli + endli : ''
        else
          startli + endli
        end
      end
    end
  end

  def extract_classes(anchor)
    (anchor.scan(/class\s*\=\s*['"]([^'"]+)['"]/).first || []).first.to_s.split
  end

  # replace all absolute links with localized links
  # except in the case of cross projects
  def localize_links!(data)
    depth_to_root = dir_depth(url)
    project = (metadata[:page]["project"] || 'riak').to_sym
    version_str = $versions[project]

    # data.gsub!(/(\<a\s.*?href\s*\=\s*["'])(\/[^"'>]+)(["'][^\>]*?>)/m) do
    data.gsub!(/\<a\s+([^\>]*?)\>/mu) do
      anchor = $1
      
      href = (anchor.scan(/href\s*\=\s*['"]([^'"]+)['"]/u).first || []).first.to_s

      # XXX: This is a terrible way to get the # links in the API to work
      if url =~ /\/http\/single/ || url =~ /\/references\/dynamo/
        if href =~ /^\#/
          next "<a #{anchor}>"
        end
      end

      # if this is data, make absolute, not relative
      if href =~ /^\/data\/(.+)$/
        url = "/shared/#{version_str}/data/#{$1}"
        next "<a #{anchor.gsub(href, url)}>"
      end

      match_index = href =~ /^\/(#{projects_regex})\/[\d\.]+(?:rc\d+|pre\d+)?\/$/

      # force the root page to point to the latest projcets
      if $production && project == :root && match_index
        "<a href=\"/#{$1}/latest/\">"
      # /riak*/version/ links should be relative, unless they cross projects
      elsif match_index #href =~ /^\/(riak[^\/]*?)\/[\d\.]+(?:rc\d+|pre\d+)?\/$/
        if ($1 || $default_project).to_sym == project
          url = prepend_dir_depth('', depth_to_root)
          "<a #{anchor.gsub(href, url)}>"
        else
          "<a #{anchor}>"
        end
      # keep it the same
      elsif version_str.blank? || href.blank? || href =~ /^http[s]?\:/
        "<a #{anchor}>"
      elsif href =~ /^\/index\.html$/
        "<a #{anchor}>"
      else
        classes = extract_classes(anchor)

        link_project = ($versions.keys.find{|proj| classes.include?(proj.to_s)} || $default_project).to_sym

        # make it absolute if outside this project, otherwise relative
        if link_project != project
          # proj_str = $versions[link_project] || version_str
          # url = "/#{link_project}/#{proj_str}#{href}"
          url = "/#{link_project}/latest#{href}"
        elsif classes.include?('versioned')
          next "<a #{anchor}>"
        else
          url = prepend_dir_depth(href, depth_to_root)
        end
        "<a #{anchor.gsub(href, url)}>"
      end
    end

    # shared resources (css, js, images, etc) are put under /shared/version
    if version_str || project == :root
      version_str ||= $versions[:riak]
      data.gsub!(/(\<(?:script|link)\s.*?(?:href|src)\s*\=\s*["'])([^"'>]+)(["'][^\>]*>)/mu) do
        base, href, cap = $1, $2, $3
        href.gsub!(/\.{2}\//, '')
        href = "/" + href unless href =~ /(https?\:)|(^\/)/

        # A better way to extract this file?
        if href =~ /(https?\:)|(\/standalone)/
          "#{base}#{href}#{cap}"
        else
          # "#{base}/shared/#{version_str}#{href}#{cap}"
          if project == :root
            "#{base}/riak/#{version_str}#{href}#{cap}"
          else
            "#{base}/#{project}/#{version_str}#{href}#{cap}"
          end
        end
      end

      data.gsub!(/(\<img\s.*?src\s*\=\s*["'])([^"'>]+)(["'][^\>]*>)/mu) do
        base, href, cap = $1, $2, $3
        if href =~ /^http[s]?\:/
          "#{base}#{href}#{cap}"
        else
          href.gsub!(/\.{2}\//, '')
          href = "/" + href unless href =~ /^\//
          "#{base}/shared/#{version_str}#{href}#{cap}"
        end
      end
    end
  end

  def colorize_code!(data)
    data.gsub!(/\<pre(?:\s.*?)?\>\s*\<code(?:\s.*?)?(class\s*\=\s*["'][^"'>]+["'])?[^\>]*\>(.*?)\<\/code\>\s*<\/pre\>/mu) do
      code = $2
      given_code_type = $1.to_s.sub(/class\s*\=\s*["']([^"'>]+)["'][^\>]*/u, "\\1")
      code_type = (given_code_type.presence || :text).to_s.to_sym
      # these are unfortunate hacks to deal with an incomplete coderay
      code_type = code_type == :bash ? :php : code_type
      code_type = code_type == :erlang ? :python : code_type
      code_type = code_type == :csharp ? :java : code_type
      code = CodeRay.scan(CGI.unescapeHTML(code), code_type).div #(:css => :class)
      code
    end
  end

  def tabize_code!(data)
    block_count = 0
    data.gsub!(/(?:\<pre[^\>]*?\>\<code[^\>]*?\>.*?\<\/code\><\/pre\>\s*)+/mu) do |block|
      block_suffix = '%03d' % block_count
      tabs_html = "<ul class=\"nav nav-tabs\">"
      code_blocks = ""
      active = true
      block.scan(/(\<pre[^\>]*?\>\s*\<code(?:\s.*?)?(?:class\s*\=\s*["']([^"'>]+)["'])?[^\>]*\>.*?\<\/code\>\s*<\/pre\>)/mu).each do |code, lang|
        display_lang = case lang
        when "curl"
          "HTTP"
        when "bash"
          "Shell"
        when "appconfig"
          "app.config"
        when "vmargs"
          "vm.args"
        when "riakconf"
          "riak.conf"
        else
          lang && lang.capitalize
        end
        case lang
        when "curl"
          code = code.gsub(/(<code(?:\s.*?)?class\s*\=\s*["'])curl(["']\>)/, '\\1bash\\2')
        when "appconfig"
          code = code.gsub(/(<code(?:\s.*?)?class\s*\=\s*["'])curl(["']\>)/, '\\1erlang\\2')
        when "riakconf"
          code = code.gsub(/(<code(?:\s.*?)?class\s*\=\s*["'])curl(["']\>)/, '\\1ini\\2')
        when "vmargs"
          code = code.gsub(/(<code(?:\s.*?)?class\s*\=\s*["'])curl(["']\>)/, '\\1ini\\2')
        else
          lang
        end
        tabs_html += "<li class=\"#{active ? 'active' : ''}\"><a href=\"##{lang}#{block_suffix}\" data-code=\"#{lang}\" data-toggle=\"tab\">#{display_lang}</a></li>"
        code_blocks += "<div class=\"tab-pane#{active ? ' active' : ''}\" id=\"#{lang}#{block_suffix}\">#{code}</div>"
        active = false
      end
      block_count += 1
      tabs_html += "</ul>"
      tabs_html += "<div class=\"tab-content\">"
      tabs_html += code_blocks
      tabs_html += "</div>"
      tabs_html
    end
  end

  def process_data!(data)
    $sitemap_pages ||= sitemap_pages

    # process the generated html
    wiki_links!(data)
    strip_versions!(data)
    localize_links!(data)
    # colorize_code!(data)
    tabize_code!(data)
  rescue => e
    $stderr.puts e
  ensure
    return data
  end
end


class ::Middleman::Sitemap::Resource
  include SitemapRenderOverride

  alias_method :old_render, :render

  # accepts the rendered data, and then does some crazy shit to it
  def render(opts={}, locs={}, &block)
    data = old_render(opts, locs, &block)
    process_data!(data)
  end
end
Something went wrong with that request. Please try again.