Skip to content

Instantly share code, notes, and snippets.

@technoweenie
Created April 29, 2011 11:54
Show Gist options
  • Save technoweenie/948206 to your computer and use it in GitHub Desktop.
Save technoweenie/948206 to your computer and use it in GitHub Desktop.
discarded sinatra documentation extension
# Sinatra module for documenting an API. This info can be exported as JSON
# and used to generate other forms of documentation.
#
# Example documentation for a GET request:
#
# desc "List issues for this Repository"
# param :milestone, Fixnum
# param :state, String, :default => 'open', :choices => %w(open closed)
# get "/repos/:user/:repo/issues" do
# ...
# end
#
# Example documentation for a POST:
#
# desc "Create an Issue"
# input_type Hash
# input :title, String
# input :body, String
# input :assignee, String
# input :milestone, Fixnum
#
# post "/repos/:user/:repo/issues" do
# ...
# end
#
module Api::Documentation
# Dumps the given Rack app's documentation as JSON to the specified
# directory.
#
# app - A Rack app that responds to #documented_requests.
# dir - A String directory name for the file. If the app has a module
# namespace, they are added to the dir.
#
# # saves /docs/foo/bar/app.json
# dump(Foo::Bar::App, "/docs")
#
# Returns nothing.
def self.dump(app, dir)
underscored = app.name.underscore
full_dir = File.join(dir, File.dirname(underscored))
FileUtils.mkdir_p(full_dir)
File.open(File.join(dir, underscored+".json"), 'w') do |io|
io << GitHub::JSON.encode(app.documented_requests.map { |r| r.to_hash })
end
end
# This is the Sinatra extension that lets you annotate requests. To use it, just
# register `Api::Documentation::Methods` in your Sinatra apps.
#
# See also: http://www.sinatrarb.com/extensions-wild.html
module Methods
# Public: This description is used as the title for a Request section.
#
# msg - The String description.
#
# Returns nothing.
def desc(msg)
current_docs.desc = msg
end
# Public: This describes an available GET param for the current request.
#
# name - The String name of the GET param.
# type - The String type: String, Integer, etc.
# options - Optional Hash:
# :default - String default value.
# :choices - Array of possible values.
# :desc - String of more information about the param.
#
# Returns nothing.
def param(*args)
current_docs.params << Api::Documentation::Param.new(*args)
end
# Public: This describes the type of object expected in the body of a POST
# or PUT request.
#
# type - The String type.
#
# Returns nothing.
def input_type(type)
current_docs.input.type = type.to_s
end
# Public: This describes an attribute of an expected object in the body of
# a POST or PUT request.
#
# key - The String field name.
# type - The String type: String, Integer, Time, Boolean, etc.
# options - Optional Hash:
# :desc - String of more information about the param.
#
# Returns nothing.
def input(key, type, options = {})
current_docs.input.field(key, type.to_s, options)
end
def current_docs
@current_docs ||= Api::Documentation::Request.new
end
# Public: Gets all of the current documented requests.
#
# Returns an Array of Request instances.
def documented_requests
@documented_requests ||= []
end
# This applies the previous set of docs against the given request.
#
# verb - The String verb of the request: GET/PUT/POST/DELETE
# path - The String route of the request.
#
# Returns nothing.
def reset_docs(verb, path)
if doc = @current_docs
doc.verb = verb
doc.path = path
documented_requests << doc
end
@current_docs = nil
end
# This is the best way I could see to hook into Sinatra.
def route(verb, path, options={}, &block)
reset_docs(verb, path)
super(verb, path, options, &block)
end
end
# This represents the docs of a single request.
class Request
attr_accessor :verb
attr_accessor :original_path
attr_accessor :desc
attr_reader :input
attr_reader :params
attr_reader :path
def initialize
@params = []
@input = Input.new
end
def path=(s)
@original_path = s
@path = s.sub(/\*$/, ':id.json')
end
# Exports a Hash for serialization to JSON.
#
# Returns a Hash.
def to_hash
hash = {:params => @params.map { |p| p.to_hash }}
hash[:input] = @input.to_hash if @input.required?
[:verb, :path, :desc].inject(hash) do |memo, key|
memo.update key => instance_variable_get("@#{key}")
end
end
end
# This represents the accepted input of a single request.
class Input
attr_accessor :type
attr_reader :fields
# Determines if this request is expecting any input.
#
# Return true if the request needs input, or false.
def required?
@fields.present?
end
# Define a field of the expected object.
#
# key - The String field name.
# type - The String type: String, Integer, Time, Boolean, etc.
# options - Optional Hash:
# :desc - String of more information about the param.
#
# Returns nothing.
def field(key, type, options)
@fields << [key, type, options]
end
def initialize
@fields = []
end
# Exports a Hash for serialization to JSON.
#
# Returns a Hash.
def to_hash
{:type => @type,
:fields => @fields.inject([]) { |memo, (key, type)|
memo << {:key => key, :type => type}
}
}
end
end
# This represents a single accepted GET parameter for a request.
class Param
attr_reader :name
attr_reader :type
attr_reader :default
attr_reader :choices
# Initializes the instance.
#
# name - The String name of the GET param.
# type - The String type: String, Integer, etc.
# options - Optional Hash:
# :default - String default value.
# :choices - Array of possible values.
#
# Returns nothing.
def initialize(name, type, options = {})
@name = name
@type = type.to_s
@default = options[:default]
@choices = options[:choices]
end
# Exports a Hash for serialization to JSON.
#
# Returns a Hash.
def to_hash
[:name, :type, :default, :choices].inject({}) do |memo, key|
memo.update key => instance_variable_get("@#{key}")
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment