-#!/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)
-<!DOCTYPE html>
-<html>
- <head>
- <title>{{service_name}}</title>
- <style type="text/css">
- {{css}}
- </style>
- </head>
- <body>
- <h1>{{service_name}}</h1>
- <ul>
- {% for method in methods %}
- <li>
- <a href="/explore/{{service_name}}-{{service_version}}/{{method}}/">
- {{method}}
- </a>
- </li>
- {% endfor %}
- </ul>
- </body>
-</html>
- HTML
-end
-
-get '/explore/:service/:method/' do
- service_name, service_version = params[:service].split("-", 2)
- service_version = service(service_name, service_version).version
- method = service(service_name, service_version).to_h[params[:method].to_s]
- variables = {
- "css" => CSS,
- "javascript" => JAVASCRIPT,
- "http_method" => (method.description['httpMethod'] || 'GET'),
- "service_name" => service_name,
- "service_version" => service_version,
- "method" => params[:method].to_s,
- "required_parameters" =>
- method.required_parameters,
- "optional_parameters" =>
- method.optional_parameters.sort,
- "template" => method.uri_template.pattern
- }
- Liquid::Template.parse(<<-HTML).render(variables)
-<!DOCTYPE html>
-<html>
- <head>
- <title>{{service_name}} - {{method}}</title>
- <style type="text/css">
- {{css}}
- </style>
- <script
- src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"
- type="text/javascript">
- </script>
- <script type="text/javascript">
- {{javascript}}
- </script>
- </head>
- <body>
- <h3 id="service-id">
- <a href="/explore/{{service_name}}-{{service_version}}/">
- {{service_name}}-{{service_version}}
- </a>
- </h3>
- <h1 id="rpc-name">{{method}}</h1>
- <h2>{{http_method}} <span id="uri-template">{{template}}</span></h2>
- <form>
- <ul>
- {% for parameter in required_parameters %}
- <li>
- <label for="param-{{parameter}}">{{parameter}}</label>
- <input id="param-{{parameter}}" name="param-{{parameter}}"
- class="parameter" type="text" />
- </li>
- {% endfor %}
- {% for parameter in optional_parameters %}
- <li>
- <label for="param-{{parameter}}">{{parameter}}</label>
- <input id="param-{{parameter}}" name="param-{{parameter}}"
- class="parameter" type="text" />
- </li>
- {% endfor %}
- {% if http_method != 'GET' %}
- <li>
- <label for="http-body">body</label>
- <textarea id="http-body" name="http-body"></textarea>
- </li>
- {% endif %}
- </ul>
- <button>Transmit</button>
- </form>
- <div id="output">
- <h3>Request</h3>
- <pre id="request"></pre>
- <h3>Response</h3>
- <pre id="response"></pre>
- </div>
- </body>
-</html>
- HTML
-end
-
-get '/favicon.ico' do
- require 'httpadapter'
- HTTPAdapter.transmit(
- ['GET', 'http://www.google.com/favicon.ico', [], ['']],
- HTTPAdapter::NetHTTPRequestAdapter
- )
-end
-
-get '/' do
- redirect '/explore/buzz/'
-end