Skip to content

Instantly share code, notes, and snippets.

@fjolnir
Last active December 23, 2015 12:29
Show Gist options
  • Save fjolnir/6635385 to your computer and use it in GitHub Desktop.
Save fjolnir/6635385 to your computer and use it in GitHub Desktop.
A little API documentation formatter & mock server
require "polyglot"
require "treetop"
require "sinatra"
module Nodes
class Text < Treetop::Runtime::SyntaxNode
end
class APIMethod < Treetop::Runtime::SyntaxNode
end
end
Treetop.load "appi"
if ARGV.size < 1
puts "No input provided"
return 1
end
parser = AppiParser.new
src = File.read(ARGV[0])
methods = []
parser = AppiParser.new
if (ast = parser.parse(src))
ast.elements.each do |e|
case e
when Nodes::Text
print e.text_value
when Nodes::APIMethod
methods << e
anchor = e.http_method.to_s + e.url_path.gsub("/", "_").gsub(":", "").chomp("_")
puts "## [#{e.http_method} #{e.url_path}](id:#{anchor})"
puts e.description if e.description.length > 0
if e.request
puts "### Request"
puts e.request.body.gsub(/^ /, "")
end
e.responses.each do |resp|
puts "### Response #{resp.status_code}"
puts resp.body.gsub(/^ /, "")
end
else
puts "Unhandled node type: #{e.class}"
end
end
else
parser.failure_reason =~ /^(Expected .+) after/m
puts "#{$1.gsub("\n", '$NEWLINE')}:"
puts src.lines.to_a[parser.failure_line - 1]
puts "#{'~' * (parser.failure_column - 1)}^"
end
if ARGV[1] == "-server"
methods.each do |method|
# `route` is private for some reason
Sinatra::Base.send(:route, method.http_method.to_s, method.url_path) do
response = method.responses.first
unless response.nil?
content_type response.content_type unless response.content_type.nil?
response.body.gsub(/^ /, "") unless response.nil?
end
end
end
Sinatra::Base.run!
end
grammar Appi
rule root
(method / text)*
end
rule text
line+ <Nodes::Text>
end
rule line
(spaces* word)* "\n"
end
rule word
(!http_method [\S]+)
end
rule method
http_method spaces path eol
method_description
request
emptyline*
responses
<Nodes::APIMethod>
{
def http_method
super.text_value.strip.to_sym
end
def url_path
path.text_value
end
def responses
super.elements
end
def description
method_description.text_value if method_description
end
}
end
rule method_description
(((!("Request" / "Response") [\S]+) spaces?)+ "\n"+)*
end
rule http_method
"GET" / "POST" / "PUT" / "DELETE"
end
rule path
[/ \w:]+
end
rule request
indent "Request" spaces? content_type eol
http_body
{
def content_type
super.text_value
end
def body
http_body.text_value
end
}
end
rule responses
response*
end
rule response
indent "Response" spaces status_code spaces? content_type eol
http_body
{
def status_code
super.text_value.to_i
end
def content_type
super.text_value.strip[1...-1]
end
def body
http_body.text_value
end
}
end
rule content_type
("(" (!")" .)+ ")")?
end
rule indent
" " / "\t"
end
rule spaces
" "+
end
rule newlines
"\n"+
end
rule emptyline
spaces* "\n"
end
rule whitespace
[\s]+
end
rule eol
spaces* newlines
end
rule status_code
[\d] 3..3
end
rule http_body
(indent indent line)+
end
end
# Analytics API
POST /session
Initiates a new session
Request
{
"device": @"iPad1,1",
"os": "iOS 7.0",
}
Response 200 (application/json)
{
"session_id": ident
}
POST /session/:ident
Appends nodes to the session graph
Request
[
{
"location": "signin", // Location name must be unique
}
]
 
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment