seomoz/interpol

View on GitHub
lib/interpol/documentation_app.rb

Summary

Maintainability
A
0 mins
Test Coverage
require 'interpol'
require 'interpol/documentation'
require 'sinatra/base'
require 'nokogiri'

module Interpol
  # Provides the interpol documentation app.
  module DocumentationApp
    extend self

    def build(&block)
      config = Configuration.default.customized_duplicate(&block)
      Builder.new(config).app
    end

    def render_static_page(&block)
      require 'rack/mock'
      app = build(&block)
      status, headers, body = app.call(Rack::MockRequest.env_for "/", :method => "GET")
      AssetInliner.new(body.enum_for(:each).to_a.join, app.public_folder).standalone_page
    end

    # Inlines the assets so the page can be viewed as a standalone web page.
    class AssetInliner
      def initialize(page, asset_root)
        @page, @asset_root = page, asset_root
        @doc = Nokogiri::HTML(page)
      end

      def standalone_page
        inline_stylesheets
        inline_javascript
        @doc.to_s
      end

    private

      def inline_stylesheets
        @doc.css("link[rel=stylesheet]").map do |link|
          inline_asset link, "style", link['href'], :type => "text/css"
        end
      end

      def inline_javascript
        @doc.css("script[src]").each do |script|
          inline_asset script, "script", script['src'], :type => "text/javascript"
        end
      end

      def contents_for(asset)
        File.read(File.join(@asset_root, asset))
      end

      def inline_asset(tag, tag_type, filename, attributes = {})
        inline_tag = Nokogiri::XML::Node.new(tag_type, @doc)
        attributes.each { |k, v| inline_tag[k] = v }
        inline_tag.content = contents_for(filename)
        tag.add_next_sibling(inline_tag)
        tag.remove
      end
    end

  private

    # Helper methods for the documentation app.
    module Helpers
      def interpol_config
        self.class.interpol_config
      end

      def endpoints
        interpol_config.endpoints
      end

      def current_endpoint
        endpoints.first
      end

      def title
        interpol_config.documentation_title
      end

      def url_path(*path_parts)
        [ path_prefix, path_parts ].join("/").squeeze('/')
      end
      alias_method :u, :url_path

      def path_prefix
        request.env['SCRIPT_NAME']
      end
    end

    # Private: Builds a stub sinatra app for the given interpol
    # configuration.
    class Builder
      attr_reader :app

      def initialize(config)
        @app = ::Sinatra.new do
          dir = File.dirname(File.expand_path(__FILE__))
          set :views, "#{dir}/documentation_app/views"
          set :public_folder, "#{dir}/documentation_app/public"
          set :interpol_config, config
          helpers Helpers

          get('/') do
            erb :layout, :locals => { :endpoints => endpoints,
                                      :current_endpoint => current_endpoint }
          end

          def self.name
            "Interpol::DocumentationApp (anonymous)"
          end
        end
      end
    end
  end
end