fdoc 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +2 -0
- data/README.md +120 -0
- data/Rakefile +7 -0
- data/bin/fdoc_to_html +77 -0
- data/fdoc.gemspec +36 -0
- data/lib/endpoint-schema.yaml +30 -0
- data/lib/fdoc.rb +46 -0
- data/lib/fdoc/endpoint.rb +110 -0
- data/lib/fdoc/endpoint_scaffold.rb +132 -0
- data/lib/fdoc/meta_service.rb +46 -0
- data/lib/fdoc/presenters/endpoint_presenter.rb +152 -0
- data/lib/fdoc/presenters/html_presenter.rb +58 -0
- data/lib/fdoc/presenters/meta_service_presenter.rb +65 -0
- data/lib/fdoc/presenters/response_code_presenter.rb +32 -0
- data/lib/fdoc/presenters/schema_presenter.rb +138 -0
- data/lib/fdoc/presenters/service_presenter.rb +56 -0
- data/lib/fdoc/service.rb +88 -0
- data/lib/fdoc/spec_watcher.rb +48 -0
- data/lib/fdoc/templates/endpoint.html.erb +75 -0
- data/lib/fdoc/templates/meta_service.html.erb +60 -0
- data/lib/fdoc/templates/service.html.erb +54 -0
- data/lib/fdoc/templates/styles.css +63 -0
- data/spec/fdoc/endpoint_scaffold_spec.rb +242 -0
- data/spec/fdoc/endpoint_spec.rb +243 -0
- data/spec/fdoc/presenters/endpoint_presenter_spec.rb +93 -0
- data/spec/fdoc/presenters/service_presenter_spec.rb +18 -0
- data/spec/fdoc/service_spec.rb +63 -0
- data/spec/fixtures/members/add-PUT.fdoc +20 -0
- data/spec/fixtures/members/draft-POST.fdoc +5 -0
- data/spec/fixtures/members/list/GET.fdoc +50 -0
- data/spec/fixtures/members/list/complex-params-GET.fdoc +94 -0
- data/spec/fixtures/members/list/filter-GET.fdoc +60 -0
- data/spec/fixtures/members/members.fdoc.service +11 -0
- data/spec/fixtures/sample_group.fdoc.meta +9 -0
- data/spec/spec_helper.rb +2 -0
- metadata +174 -0
@@ -0,0 +1,56 @@
|
|
1
|
+
# An HtmlPresenter for Fdoc::Service
|
2
|
+
class Fdoc::ServicePresenter < Fdoc::HtmlPresenter
|
3
|
+
attr_reader :service
|
4
|
+
|
5
|
+
def initialize(service, options = {})
|
6
|
+
super(options)
|
7
|
+
@service = service
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_html
|
11
|
+
render_erb('service.html.erb')
|
12
|
+
end
|
13
|
+
|
14
|
+
def name_as_link(options = {})
|
15
|
+
path = service.meta_service ? index_path(slug_name) : index_path
|
16
|
+
'<a href="%s">%s %s</a>' % [ path, options[:prefix], service.name ]
|
17
|
+
end
|
18
|
+
|
19
|
+
def slug_name
|
20
|
+
service.name.downcase.gsub(/[ \/]/, '_')
|
21
|
+
end
|
22
|
+
|
23
|
+
def url(extension = ".html")
|
24
|
+
'%s-%s%s' % [ @endpoint.path, @endpoint.verb, extension ]
|
25
|
+
end
|
26
|
+
|
27
|
+
def endpoints
|
28
|
+
if !@endpoints
|
29
|
+
@endpoints = []
|
30
|
+
prefix = nil
|
31
|
+
|
32
|
+
service.endpoints.sort_by(&:endpoint_path).each do |endpoint|
|
33
|
+
presenter = Fdoc::EndpointPresenter.new(endpoint, options)
|
34
|
+
presenter.service_presenter = self
|
35
|
+
presenter
|
36
|
+
|
37
|
+
current_prefix = presenter.prefix
|
38
|
+
|
39
|
+
@endpoints << [] if prefix != current_prefix
|
40
|
+
@endpoints.last << presenter
|
41
|
+
|
42
|
+
prefix = current_prefix
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
@endpoints
|
47
|
+
end
|
48
|
+
|
49
|
+
def description
|
50
|
+
render_markdown(service.description)
|
51
|
+
end
|
52
|
+
|
53
|
+
def discussion
|
54
|
+
render_markdown(service.discussion)
|
55
|
+
end
|
56
|
+
end
|
data/lib/fdoc/service.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
# Services represent a group of Fdoc API endpoints in a directory
|
4
|
+
class Fdoc::Service
|
5
|
+
attr_reader :service_dir
|
6
|
+
attr_accessor :meta_service
|
7
|
+
|
8
|
+
def initialize(service_dir, scaffold_mode = Fdoc.scaffold_mode?)
|
9
|
+
@service_dir = File.expand_path(service_dir)
|
10
|
+
service_path = Dir["#{@service_dir}/*.fdoc.service"].first
|
11
|
+
@schema = if service_path
|
12
|
+
YAML.load_file(service_path)
|
13
|
+
elsif scaffold_mode
|
14
|
+
schema = {
|
15
|
+
'name' => '???',
|
16
|
+
'basePath' => '???',
|
17
|
+
'description' => '???'
|
18
|
+
}
|
19
|
+
|
20
|
+
Dir.mkdir(service_dir) unless Dir.exist?(service_dir)
|
21
|
+
service_path = "#{service_dir}/???.fdoc.service"
|
22
|
+
File.open(service_path, "w") { |file| YAML.dump(schema, file) }
|
23
|
+
|
24
|
+
schema
|
25
|
+
else
|
26
|
+
{}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
DefaultService = self.new(Fdoc::DEFAULT_SERVICE_PATH)
|
31
|
+
|
32
|
+
# Returns an Endpoint described by (verb, path)
|
33
|
+
# In scaffold_mode, it will return an EndpointScaffold an of existing file
|
34
|
+
# or create an empty EndpointScaffold
|
35
|
+
def open(verb, path, scaffold_mode = Fdoc.scaffold_mode?)
|
36
|
+
endpoint_path = path_for(verb, path)
|
37
|
+
|
38
|
+
if scaffold_mode
|
39
|
+
Fdoc::EndpointScaffold.new(endpoint_path, self)
|
40
|
+
else
|
41
|
+
Fdoc::Endpoint.new(endpoint_path, self)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def endpoint_paths
|
46
|
+
Dir["#{service_dir}/**/*.fdoc"]
|
47
|
+
end
|
48
|
+
|
49
|
+
def endpoints
|
50
|
+
endpoint_paths.map do |path|
|
51
|
+
Fdoc::Endpoint.new(path, self)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def path_for(verb, path)
|
56
|
+
flat_path = File.join(@service_dir, "#{path}-#{verb.to_s.upcase}.fdoc")
|
57
|
+
nested_path = File.join(@service_dir, "#{path}/#{verb.to_s.upcase}.fdoc")
|
58
|
+
|
59
|
+
if File.exist?(flat_path)
|
60
|
+
flat_path
|
61
|
+
elsif File.exist?(nested_path)
|
62
|
+
nested_path
|
63
|
+
else # neither exists, default to flat_path
|
64
|
+
flat_path
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def name
|
69
|
+
@schema['name']
|
70
|
+
end
|
71
|
+
|
72
|
+
def base_path
|
73
|
+
base_path = @schema['basePath']
|
74
|
+
if base_path && !base_path.end_with?('/')
|
75
|
+
base_path + '/'
|
76
|
+
else
|
77
|
+
base_path
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def description
|
82
|
+
@schema['description']
|
83
|
+
end
|
84
|
+
|
85
|
+
def discussion
|
86
|
+
@schema['discussion']
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Fdoc
|
2
|
+
module SpecWatcher
|
3
|
+
VERBS = [:get, :post, :put, :delete]
|
4
|
+
|
5
|
+
VERBS.each do |verb|
|
6
|
+
define_method(verb) do |*params|
|
7
|
+
action, request_params = params
|
8
|
+
request_params ||= {}
|
9
|
+
result = super(*params)
|
10
|
+
|
11
|
+
path = if respond_to?(:example) # Rspec 2
|
12
|
+
example.metadata[:fdoc]
|
13
|
+
else # Rspec 1.3.2
|
14
|
+
opts = {}
|
15
|
+
__send__(:example_group_hierarchy).each do |example|
|
16
|
+
opts.merge!(example.options)
|
17
|
+
end
|
18
|
+
opts.merge!(options)
|
19
|
+
opts[:fdoc]
|
20
|
+
end
|
21
|
+
|
22
|
+
if path
|
23
|
+
response_params = begin
|
24
|
+
JSON.parse(response.body)
|
25
|
+
rescue
|
26
|
+
{}
|
27
|
+
end
|
28
|
+
successful = Fdoc.decide_success(response_params, response.status)
|
29
|
+
verify!(verb, path, request_params, response_params, response.status,
|
30
|
+
successful)
|
31
|
+
end
|
32
|
+
|
33
|
+
result
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def verify!(verb, path, request_params, response_params, response_status,
|
40
|
+
successful)
|
41
|
+
service = Service.new(Fdoc.service_path)
|
42
|
+
endpoint = service.open(verb, path)
|
43
|
+
endpoint.consume_request(request_params, successful)
|
44
|
+
endpoint.consume_response(response_params, response_status, successful)
|
45
|
+
endpoint.persist! if endpoint.respond_to?(:persist!)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>
|
5
|
+
<%= title %>
|
6
|
+
</title>
|
7
|
+
|
8
|
+
<link rel="stylesheet" type="text/css" href="<%= css_path %>" />
|
9
|
+
</head>
|
10
|
+
<body>
|
11
|
+
<div id="page">
|
12
|
+
<div id="content">
|
13
|
+
<h1>
|
14
|
+
<%= name %>
|
15
|
+
</h1>
|
16
|
+
|
17
|
+
<div id="nav">
|
18
|
+
<% if @endpoint.service.meta_service %>
|
19
|
+
<a href="<%= index_path %>">
|
20
|
+
» <%= @endpoint.service.meta_service.name %>
|
21
|
+
</a>
|
22
|
+
<% end %>
|
23
|
+
<% if service_presenter %>
|
24
|
+
<%= service_presenter.name_as_link(:prefix => '»') %>
|
25
|
+
<% end %>
|
26
|
+
</div>
|
27
|
+
|
28
|
+
<%= description %>
|
29
|
+
|
30
|
+
<% if show_request? %>
|
31
|
+
<h2>Request</h2>
|
32
|
+
|
33
|
+
<h3>Example Request</h3>
|
34
|
+
<%= example_request %>
|
35
|
+
|
36
|
+
<h3>Request Parameters</h3>
|
37
|
+
<%= request_parameters %>
|
38
|
+
<% end %>
|
39
|
+
|
40
|
+
<h2>Response</h2>
|
41
|
+
<% if show_response? %>
|
42
|
+
<h3>Example Response</h3>
|
43
|
+
<%= example_response %>
|
44
|
+
|
45
|
+
<h3>Response Parameters</h3>
|
46
|
+
<%= response_parameters %>
|
47
|
+
<% end %>
|
48
|
+
|
49
|
+
<h3>Response Codes</h3>
|
50
|
+
<% if !successful_response_codes.empty? %>
|
51
|
+
<h4>Successful Response Codes</h4>
|
52
|
+
<ul>
|
53
|
+
<% successful_response_codes.each do |response_code| %>
|
54
|
+
<li>
|
55
|
+
<%= response_code.to_html %>
|
56
|
+
</li>
|
57
|
+
<% end %>
|
58
|
+
</ul>
|
59
|
+
<% end %>
|
60
|
+
|
61
|
+
<% if !successful_response_codes.empty? %>
|
62
|
+
<h4>Failure Response Codes</h4>
|
63
|
+
<ul>
|
64
|
+
<% failure_response_codes.each do |response_code| %>
|
65
|
+
<li>
|
66
|
+
<%= response_code.to_html %>
|
67
|
+
</li>
|
68
|
+
<% end %>
|
69
|
+
</ul>
|
70
|
+
<% end %>
|
71
|
+
</div>
|
72
|
+
</div>
|
73
|
+
</body>
|
74
|
+
</html>
|
75
|
+
|
@@ -0,0 +1,60 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>
|
5
|
+
<%= @meta_service.name %>
|
6
|
+
</title>
|
7
|
+
|
8
|
+
<link rel="stylesheet" type="text/css" href="<%= css_path %>" />
|
9
|
+
</head>
|
10
|
+
<body>
|
11
|
+
<div id="page">
|
12
|
+
<div id="content">
|
13
|
+
<h1>
|
14
|
+
<%= @meta_service.name %>
|
15
|
+
</h1>
|
16
|
+
|
17
|
+
<%= description %>
|
18
|
+
|
19
|
+
<h2>
|
20
|
+
Services
|
21
|
+
</h2>
|
22
|
+
|
23
|
+
<ul>
|
24
|
+
<% services.each do |serv| %>
|
25
|
+
<li>
|
26
|
+
<h3>
|
27
|
+
<%= serv.name_as_link %>
|
28
|
+
</h3>
|
29
|
+
</li>
|
30
|
+
<% end %>
|
31
|
+
</ul>
|
32
|
+
|
33
|
+
<h2>
|
34
|
+
Endpoints
|
35
|
+
</h2>
|
36
|
+
|
37
|
+
<% endpoints.each do |endpoint_ary| %>
|
38
|
+
<h3>
|
39
|
+
<%= endpoint_ary.first.prefix %>
|
40
|
+
</h3>
|
41
|
+
<ul>
|
42
|
+
<% endpoint_ary.each do |endpoint| %>
|
43
|
+
<li>
|
44
|
+
<%= endpoint.name_as_link %>
|
45
|
+
</li>
|
46
|
+
<% end %>
|
47
|
+
</ul>
|
48
|
+
<% end %>
|
49
|
+
|
50
|
+
<% if discussion %>
|
51
|
+
<h2>
|
52
|
+
Discussion
|
53
|
+
</h2>
|
54
|
+
<%= discussion %>
|
55
|
+
<% end %>
|
56
|
+
</div>
|
57
|
+
</div>
|
58
|
+
</body>
|
59
|
+
</html>
|
60
|
+
|
@@ -0,0 +1,54 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>
|
5
|
+
<%= @service.name %>
|
6
|
+
</title>
|
7
|
+
|
8
|
+
<link rel="stylesheet" type="text/css" href="<%= css_path %>" />
|
9
|
+
</head>
|
10
|
+
<body>
|
11
|
+
<div id="page">
|
12
|
+
<div id="content">
|
13
|
+
<h1>
|
14
|
+
<%= @service.name %>
|
15
|
+
</h1>
|
16
|
+
|
17
|
+
<div id="nav">
|
18
|
+
<% if @service.meta_service %>
|
19
|
+
<a href="<%= index_path %>">
|
20
|
+
» <%= @service.meta_service.name %>
|
21
|
+
</a>
|
22
|
+
<% end %>
|
23
|
+
</div>
|
24
|
+
|
25
|
+
<%= description %>
|
26
|
+
|
27
|
+
<h2>
|
28
|
+
Endpoints
|
29
|
+
</h2>
|
30
|
+
|
31
|
+
<% endpoints.each do |endpoint_ary| %>
|
32
|
+
<h3>
|
33
|
+
<%= endpoint_ary.first.prefix %>
|
34
|
+
</h3>
|
35
|
+
<ul>
|
36
|
+
<% endpoint_ary.each do |endpoint| %>
|
37
|
+
<li>
|
38
|
+
<%= endpoint.name_as_link %>
|
39
|
+
</li>
|
40
|
+
<% end %>
|
41
|
+
</ul>
|
42
|
+
<% end %>
|
43
|
+
|
44
|
+
<% if discussion %>
|
45
|
+
<h2>
|
46
|
+
Discussion
|
47
|
+
</h2>
|
48
|
+
<%= discussion %>
|
49
|
+
<% end %>
|
50
|
+
</div>
|
51
|
+
</div>
|
52
|
+
</body>
|
53
|
+
</html>
|
54
|
+
|
@@ -0,0 +1,63 @@
|
|
1
|
+
|
2
|
+
body {
|
3
|
+
font: 11pt/14pt "Helvetica Neue", Helvetica, sans-serif;
|
4
|
+
margin: 0;
|
5
|
+
padding: 0;
|
6
|
+
}
|
7
|
+
|
8
|
+
#page {
|
9
|
+
background: white;
|
10
|
+
margin: 0 auto;
|
11
|
+
width: 540pt;
|
12
|
+
}
|
13
|
+
|
14
|
+
#content {
|
15
|
+
padding: 0 12pt 2em 12pt;
|
16
|
+
}
|
17
|
+
|
18
|
+
#nav { padding-bottom: 1em; }
|
19
|
+
|
20
|
+
h1, h2 {
|
21
|
+
margin: 0 -12pt;
|
22
|
+
padding: .5em 12pt;
|
23
|
+
line-height: 1.2em;
|
24
|
+
}
|
25
|
+
|
26
|
+
h1 { font-weight: bold; }
|
27
|
+
h1, h2, h3 { font-weight: 200; }
|
28
|
+
|
29
|
+
a { text-decoration: none; color: #66f; }
|
30
|
+
a:hover, a:active { text-decoration: underline; }
|
31
|
+
|
32
|
+
ul {
|
33
|
+
list-style: square;
|
34
|
+
}
|
35
|
+
|
36
|
+
ul, ol, dl {
|
37
|
+
margin: .5em 0;
|
38
|
+
}
|
39
|
+
|
40
|
+
p {
|
41
|
+
margin: .5em 0 .5em 0;
|
42
|
+
padding: 0;
|
43
|
+
}
|
44
|
+
|
45
|
+
pre code { display: block; }
|
46
|
+
tt, kbd, code {
|
47
|
+
font-family: Monaco, monospace;
|
48
|
+
font-size: 80%;
|
49
|
+
background: #eee;
|
50
|
+
padding: .1em;
|
51
|
+
border: 1pt solid #ddd;
|
52
|
+
white-space: pre-wrap;
|
53
|
+
}
|
54
|
+
|
55
|
+
.endpoint-name {}
|
56
|
+
.endpoint-name .verb { font-weight: bold; }
|
57
|
+
.endpoint-name .root {}
|
58
|
+
.endpoint-name .path { font-weight: bold; }
|
59
|
+
|
60
|
+
a .endpoint-name .verb { display: inline-block; width: 3em; text-align: right; font-size:80% }
|
61
|
+
|
62
|
+
.endpoint-name.deprecated { color: gray; }
|
63
|
+
.schema ul span.deprecated { margin-left:.5em; font-size: 80%; color: red }
|