#!/usr/bin/env ruby # RUN # ruby examples/sinatra/explorer.rb # navigate a browser to http://localhost:4567/explore/buzz-v1/ root_dir = File.expand_path("../../..", __FILE__) lib_dir = File.expand_path("./lib", root_dir) $LOAD_PATH.unshift(lib_dir) $LOAD_PATH.uniq! require 'rubygems' begin gem 'rack', '= 1.2.0' require 'rack' rescue LoadError STDERR.puts "Missing dependencies." STDERR.puts "sudo gem install rack -v 1.2.0" exit(1) end begin require 'sinatra' require 'liquid' require 'signet/oauth_1/client' require 'google/api_client' rescue LoadError STDERR.puts "Missing dependencies." STDERR.puts "sudo gem install sinatra liquid signet google-api-client" exit(1) end enable :sessions CSS = <<-CSS /* http://meyerweb.com/eric/tools/css/reset/ */ /* v1.0 | 20080212 */ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td { margin: 0; padding: 0; border: 0; outline: 0; font-size: 100%; vertical-align: baseline; background: transparent; } body { line-height: 1; } ol, ul { list-style: none; } blockquote, q { quotes: none; } blockquote:before, blockquote:after, q:before, q:after { content: ''; content: none; } /* remember to define focus styles! */ :focus { outline: 0; } /* remember to highlight inserts somehow! */ ins { text-decoration: none; } del { text-decoration: line-through; } /* tables still need 'cellspacing="0"' in the markup */ table { border-collapse: collapse; border-spacing: 0; } /* End Reset */ body { color: #555555; background-color: #ffffff; font-family: 'Helvetica', 'Arial', sans-serif; font-size: 18px; line-height: 27px; padding: 27px 72px; } p { margin-bottom: 27px; } h1 { font-style: normal; font-variant: normal; font-weight: normal; font-family: 'Helvetica', 'Arial', sans-serif; font-size: 36px; line-height: 54px; margin-bottom: 0px; } h2 { font-style: normal; font-variant: normal; font-weight: normal; font-family: 'Monaco', 'Andale Mono', 'Consolas', 'Inconsolata', 'Courier New', monospace; font-size: 14px; line-height: 27px; margin-top: 0px; margin-bottom: 54px; letter-spacing: 0.1em; text-transform: none; text-shadow: rgba(204, 204, 204, 0.75) 0px 1px 0px; } #output h3 { font-style: normal; font-variant: normal; font-weight: bold; font-size: 18px; line-height: 27px; margin: 27px 0px; } #output h3:first-child { margin-top: 0px; } ul, ol, dl { margin-bottom: 27px; } li { margin: 0px 0px; } form { float: left; display: block; } form label, form input, form textarea { font-family: 'Monaco', 'Andale Mono', 'Consolas', 'Inconsolata', 'Courier New', monospace; display: block; } form label { margin-bottom: 5px; } form input { width: 300px; font-size: 14px; padding: 5px; } form textarea { height: 150px; min-height: 150px; width: 300px; min-width: 300px; max-width: 300px; } #output { font-family: 'Monaco', 'Andale Mono', 'Consolas', 'Inconsolata', 'Courier New', monospace; display: inline-block; margin-left: 27px; padding: 27px; border: 1px dotted #555555; width: 1120px; max-width: 100%; min-height: 600px; } #output pre { overflow: auto; } a { color: #000000; text-decoration: none; border-bottom: 1px dotted rgba(112, 56, 56, 0.0); } a:hover { -webkit-transition: all 0.3s linear; color: #703838; border-bottom: 1px dotted rgba(112, 56, 56, 1.0); } p a { border-bottom: 1px dotted rgba(0, 0, 0, 1.0); } h1, h2 { color: #000000; } h3, h4, h5, h6 { color: #333333; } .block { display: block; } button { margin-bottom: 72px; padding: 7px 11px; font-size: 14px; } CSS JAVASCRIPT = <<-JAVASCRIPT var uriTimeout = null; $(document).ready(function () { $('#output').hide(); var rpcName = $('#rpc-name').text().trim(); var serviceId = $('#service-id').text().trim(); var getParameters = function() { var parameters = {}; var fields = $('.parameter').parents('li'); for (var i = 0; i < fields.length; i++) { var input = $(fields[i]).find('input'); var label = $(fields[i]).find('label'); if (input.val() && input.val() != "") { parameters[label.text()] = input.val(); } } return parameters; } var updateOutput = function (event) { var request = $('#request').text().trim(); var response = $('#response').text().trim(); if (request != '' || response != '') { $('#output').show(); } else { $('#output').hide(); } } var handleUri = function (event) { updateOutput(event); if (uriTimeout) { clearTimeout(uriTimeout); } uriTimeout = setTimeout(function () { $.ajax({ "url": "/template/" + serviceId + "/" + rpcName + "/", "data": getParameters(), "dataType": "text", "ifModified": true, "success": function (data, textStatus, xhr) { updateOutput(event); if (textStatus == 'success') { $('#uri-template').html(data); if (uriTimeout) { clearTimeout(uriTimeout); } } } }); }, 350); } var getResponse = function (event) { $.ajax({ "url": "/response/" + serviceId + "/" + rpcName + "/", "type": "POST", "data": getParameters(), "dataType": "html", "ifModified": true, "success": function (data, textStatus, xhr) { if (textStatus == 'success') { $('#response').text(data); } updateOutput(event); } }); } var getRequest = function (event) { $.ajax({ "url": "/request/" + serviceId + "/" + rpcName + "/", "type": "GET", "data": getParameters(), "dataType": "html", "ifModified": true, "success": function (data, textStatus, xhr) { if (textStatus == 'success') { $('#request').text(data); updateOutput(event); getResponse(event); } } }); } var transmit = function (event) { $('#request').html(''); $('#response').html(''); handleUri(event); updateOutput(event); getRequest(event); } $('form').submit(function (event) { event.preventDefault(); }); $('button').click(transmit); $('.parameter').keyup(handleUri); $('.parameter').blur(handleUri); }); JAVASCRIPT def client @client ||= Google::APIClient.new( :service => 'buzz', :authorization => Signet::OAuth1::Client.new( :temporary_credential_uri => 'https://www.google.com/accounts/OAuthGetRequestToken', :authorization_uri => 'https://www.google.com/buzz/api/auth/OAuthAuthorizeToken', :token_credential_uri => 'https://www.google.com/accounts/OAuthGetAccessToken', :client_credential_key => 'anonymous', :client_credential_secret => 'anonymous' ) ) end def service(service_name, service_version) unless service_version service_version = client.latest_service_version(service_name).version end client.discovered_api(service_name, service_version) end get '/template/:service/:method/' do service_name, service_version = params[:service].split("-", 2) method = service(service_name, service_version).to_h[params[:method].to_s] parameters = method.parameters.inject({}) do |accu, parameter| accu[parameter] = params[parameter.to_sym] if params[parameter.to_sym] accu end uri = Addressable::URI.parse( method.uri_template.partial_expand(parameters).pattern ) template_variables = method.uri_template.variables query_parameters = method.normalize_parameters(parameters).reject do |k, v| template_variables.include?(k) end if query_parameters.size > 0 uri.query_values = (uri.query_values || {}).merge(query_parameters) end # Normalization is necessary because of undesirable percent-escaping # during URI template expansion return uri.normalize.to_s.gsub('%7B', '{').gsub('%7D', '}') end get '/request/:service/:method/' do service_name, service_version = params[:service].split("-", 2) method = service(service_name, service_version).to_h[params[:method].to_s] parameters = method.parameters.inject({}) do |accu, parameter| accu[parameter] = params[parameter.to_sym] if params[parameter.to_sym] accu end body = '' request = client.generate_request( method, parameters.merge("pp" => "1"), body, [], {:signed => false} ) method, uri, headers, body = request merged_body = StringIO.new body.each do |chunk| merged_body << chunk end merged_body.rewind <<-REQUEST.strip #{method} #{uri} HTTP/1.1 #{(headers.map { |k,v| "#{k}: #{v}" }).join('\n')} #{merged_body.string} REQUEST end post '/response/:service/:method/' do require 'rack/utils' service_name, service_version = params[:service].split("-", 2) method = service(service_name, service_version).to_h[params[:method].to_s] parameters = method.parameters.inject({}) do |accu, parameter| accu[parameter] = params[parameter.to_sym] if params[parameter.to_sym] accu end body = '' response = client.execute( method, parameters.merge("pp" => "1"), body, [], {:signed => false} ) status, headers, body = response status_message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] merged_body = StringIO.new body.each do |chunk| merged_body << chunk end merged_body.rewind <<-RESPONSE.strip #{status} #{status_message} #{(headers.map { |k,v| "#{k}: #{v}" }).join("\n")} #{merged_body.string} RESPONSE end get '/explore/:service/' do service_name, service_version = params[:service].split("-", 2) service_version = service(service_name, service_version).version variables = { "css" => CSS, "service_name" => service_name, "service_version" => service_version, "methods" => service(service_name, service_version).to_h.keys.sort } Liquid::Template.parse(<<-HTML).render(variables)