apispec 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +21 -0
- data/README.md +1 -0
- data/Rakefile +9 -0
- data/apispec-0.0.2.gem +0 -0
- data/apispec.gemspec +37 -0
- data/bin/apispec +112 -0
- data/example/datagram/user.json +7 -0
- data/example/example.rb +59 -0
- data/example/user.rb +34 -0
- data/lib/apispec/example.rb +10 -0
- data/lib/apispec/field.rb +37 -0
- data/lib/apispec/generator.rb +104 -0
- data/lib/apispec/http.rb +53 -0
- data/lib/apispec/interface.rb +29 -0
- data/lib/apispec/message.rb +56 -0
- data/lib/apispec/namespace.rb +113 -0
- data/lib/apispec/node.rb +52 -0
- data/lib/apispec/object.rb +34 -0
- data/lib/apispec/resource.rb +43 -0
- data/lib/apispec/version.rb +6 -0
- data/lib/apispec.rb +15 -0
- data/resources/jquery.min.js +154 -0
- data/resources/links.css +42 -0
- data/resources/stripe.png +0 -0
- data/resources/style.css +260 -0
- data/spec/dsl/example_spec.rb +5 -0
- data/spec/dsl/field_spec.rb +5 -0
- data/spec/dsl/interface_spec.rb +5 -0
- data/spec/dsl/message_spec.rb +5 -0
- data/spec/dsl/object_spec.rb +5 -0
- data/spec/dsl/resource_spec.rb +5 -0
- data/spec/generator_spec.rb +28 -0
- data/spec/spec_helper.rb +1 -0
- data/templates/field.html.erb +24 -0
- data/templates/index.html.erb +11 -0
- data/templates/interface.html.erb +19 -0
- data/templates/links.html.erb +26 -0
- data/templates/message.html.erb +38 -0
- data/templates/object.html.erb +27 -0
- data/templates/resource.html.erb +26 -0
- metadata +160 -0
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2011 Vincent Landgraf
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
TODO
|
data/Rakefile
ADDED
data/apispec-0.0.2.gem
ADDED
Binary file
|
data/apispec.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
$:.push('lib')
|
2
|
+
require "apispec/version"
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'apispec'
|
6
|
+
s.version = APISpec::VERSION.dup
|
7
|
+
s.date = Time.now
|
8
|
+
s.summary = "A ruby based http/rest documentation generator"
|
9
|
+
s.email = "vilandgr+github@googlemail.com"
|
10
|
+
s.homepage = "http://github.com/threez/apispec/"
|
11
|
+
s.authors = ['Vincent Landgraf']
|
12
|
+
s.description = "A documentation generator for http/rest"
|
13
|
+
|
14
|
+
dependencies = [
|
15
|
+
[:runtime, "RedCloth", "~> 4.2.7"],
|
16
|
+
[:runtime, "coderay", "~> 0.9.7"],
|
17
|
+
[:development, "rspec", "~> 2.1"],
|
18
|
+
]
|
19
|
+
|
20
|
+
s.files = Dir['**/*']
|
21
|
+
s.test_files = Dir['test/**/*'] + Dir['spec/**/*']
|
22
|
+
s.executables = Dir['bin/*'].map { |f| File.basename(f) }
|
23
|
+
s.require_paths = ["lib"]
|
24
|
+
|
25
|
+
## Make sure you can build the gem on older versions of RubyGems too:
|
26
|
+
s.rubygems_version = APISpec::VERSION.dup
|
27
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
28
|
+
s.specification_version = 3 if s.respond_to? :specification_version
|
29
|
+
|
30
|
+
dependencies.each do |type, name, version|
|
31
|
+
if s.respond_to?("add_#{type}_dependency")
|
32
|
+
s.send("add_#{type}_dependency", name, version)
|
33
|
+
else
|
34
|
+
s.add_dependency(name, version)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/bin/apispec
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
# grab the project root for tempaltes and the lib
|
5
|
+
project_root = File.join(File.dirname(__FILE__), "..")
|
6
|
+
|
7
|
+
require File.expand_path(File.join(project_root, "lib", "apispec"))
|
8
|
+
|
9
|
+
# the default tempate dir
|
10
|
+
template_dir = File.join(project_root, "templates")
|
11
|
+
resource_dir = File.join(project_root, "resources")
|
12
|
+
|
13
|
+
class DirectoryError < StandardError; end
|
14
|
+
def require_directory!(dir)
|
15
|
+
dir = File.expand_path(dir)
|
16
|
+
raise DirectoryError.new("directory #{dir} doesn't exist!") unless File.exist? dir
|
17
|
+
dir
|
18
|
+
end
|
19
|
+
|
20
|
+
options = {
|
21
|
+
:workspace => File.expand_path("."),
|
22
|
+
:template => File.expand_path(template_dir),
|
23
|
+
:output => File.expand_path("apidoc"),
|
24
|
+
:resource => File.expand_path(resource_dir)
|
25
|
+
}
|
26
|
+
parser = nil
|
27
|
+
|
28
|
+
begin
|
29
|
+
parser = OptionParser.new do |opts|
|
30
|
+
opts.banner = "Usage: #{$0} [options]"
|
31
|
+
opts.version = APISpec::VERSION
|
32
|
+
|
33
|
+
opts.separator ""
|
34
|
+
opts.separator "No existing project options:"
|
35
|
+
|
36
|
+
opts.on("-c", "--create <name>", "create a new project") do |name|
|
37
|
+
options[:create] = name
|
38
|
+
end
|
39
|
+
|
40
|
+
opts.separator ""
|
41
|
+
opts.separator "Project options:"
|
42
|
+
|
43
|
+
opts.on("-w", "--workspace <dir>",
|
44
|
+
"The directory containing the source files (default: .)") do |dir|
|
45
|
+
options[:workspace] = require_directory!(dir)
|
46
|
+
end
|
47
|
+
|
48
|
+
opts.on("-t", "--template <dir>",
|
49
|
+
"The template directory (default: bundled with apispec)") do |dir|
|
50
|
+
options[:template] = require_directory!(dir)
|
51
|
+
end
|
52
|
+
|
53
|
+
opts.on("-r", "--rescoure <dir>",
|
54
|
+
"The resource directory (default: bundled with apispec)") do |dir|
|
55
|
+
options[:template] = require_directory!(dir)
|
56
|
+
end
|
57
|
+
|
58
|
+
opts.on("-p", "--print",
|
59
|
+
"The resource directory (default: bundled with apispec)") do
|
60
|
+
options[:print] = true
|
61
|
+
end
|
62
|
+
|
63
|
+
opts.on("-o", "--output <dir>",
|
64
|
+
"The output directory (default: apidoc)") do |dir|
|
65
|
+
options[:output] = File.expand_path(dir)
|
66
|
+
end
|
67
|
+
|
68
|
+
# opts.on("-s", "--server <port>", "Don't create a output folder start a server on given port") do |dir|
|
69
|
+
# options[:output] = dir
|
70
|
+
# end
|
71
|
+
|
72
|
+
opts.separator ""
|
73
|
+
opts.separator "Common options:"
|
74
|
+
|
75
|
+
opts.on("-v", "--verbose", "Run verbosely") do
|
76
|
+
options[:verbose] = true
|
77
|
+
end
|
78
|
+
end
|
79
|
+
parser.parse!
|
80
|
+
rescue DirectoryError => ex
|
81
|
+
STDERR.puts "Invalid Argument: #{ex.message}"
|
82
|
+
STDERR.puts parser.help
|
83
|
+
exit 3
|
84
|
+
rescue OptionParser::ParseError => ex
|
85
|
+
STDERR.puts ex.message
|
86
|
+
STDERR.puts parser.help
|
87
|
+
exit 2
|
88
|
+
end
|
89
|
+
|
90
|
+
if options[:print]
|
91
|
+
# just print the structure
|
92
|
+
generator = APISpec::Generator.new(options)
|
93
|
+
generator.parse_files!
|
94
|
+
puts generator.namespace.print_tree()
|
95
|
+
elsif dir = options[:create]
|
96
|
+
# create a new project based on the example project
|
97
|
+
new_dir = File.expand_path(dir)
|
98
|
+
FileUtils.mkdir_p(new_dir)
|
99
|
+
FileUtils.cp_r(Dir[File.expand_path(File.join(project_root, "example", "*"))],
|
100
|
+
new_dir)
|
101
|
+
elsif Dir[File.join(options[:workspace], "**", "*.rb")].any?
|
102
|
+
begin
|
103
|
+
APISpec::Generator.new(options).start!
|
104
|
+
rescue APISpec::Namespace::ReferenceError => ex
|
105
|
+
STDERR.puts ex.message
|
106
|
+
exit 4
|
107
|
+
end
|
108
|
+
else
|
109
|
+
STDERR.puts "There are no files to generate a documentation from!"
|
110
|
+
STDERR.puts parser.help
|
111
|
+
exit 5
|
112
|
+
end
|
data/example/example.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
field "X-User", "UserID" do
|
2
|
+
desc "The users email address"
|
3
|
+
example "test@example.com"
|
4
|
+
end
|
5
|
+
|
6
|
+
field "folder_id", "FolderID" do
|
7
|
+
desc "unique id of folder for user"
|
8
|
+
end
|
9
|
+
|
10
|
+
object "Folder" do
|
11
|
+
fields "FolderID"
|
12
|
+
example :text, %q{
|
13
|
+
asd
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
interface "Folders" do
|
18
|
+
base_uri "http://example.com/mail-rest"
|
19
|
+
|
20
|
+
get 'folders' do
|
21
|
+
desc "returns the list of folders"
|
22
|
+
|
23
|
+
request do
|
24
|
+
headers "UserID"
|
25
|
+
end
|
26
|
+
|
27
|
+
response do
|
28
|
+
example :json, %q|
|
29
|
+
{
|
30
|
+
"Kreditkarte" : "Xema",
|
31
|
+
"Nummer" : "1234-5678-9012-3456",
|
32
|
+
"Inhaber" : {
|
33
|
+
"Name" : "Reich",
|
34
|
+
"Vorname" : "Rainer",
|
35
|
+
"Geschlecht" : "männlich",
|
36
|
+
"Vorlieben" : [ "Reiten", "Schwimmen", "Lesen" ],
|
37
|
+
"Alter" : null
|
38
|
+
},
|
39
|
+
"Deckung" : 2e+6,
|
40
|
+
"Währung" : "EURO"
|
41
|
+
}
|
42
|
+
|
|
43
|
+
array "Folder"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
get 'folder/:folder_id' do
|
48
|
+
desc "returns the mail header objects of the requested Folder"
|
49
|
+
|
50
|
+
request do
|
51
|
+
headers "UserID"
|
52
|
+
params "FolderID"
|
53
|
+
end
|
54
|
+
|
55
|
+
response do
|
56
|
+
object "Folder"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/example/user.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
namespace "Account" do
|
2
|
+
field("id") { type :integer }
|
3
|
+
field("firstname") { example "John" }
|
4
|
+
field("lastname") { example "Doe" }
|
5
|
+
field("login") { example "john.doe" }
|
6
|
+
field("password") { example "secretxxx" }
|
7
|
+
|
8
|
+
object "User" do
|
9
|
+
fields "Account.id", "Account.firstname", "Account.lastname",
|
10
|
+
"Account.login", "Account.password"
|
11
|
+
example_file :json, "datagram/user.json"
|
12
|
+
end
|
13
|
+
|
14
|
+
interface "UserRest" do
|
15
|
+
base_uri "/account"
|
16
|
+
|
17
|
+
get "users/" do
|
18
|
+
response do
|
19
|
+
array "Account.User"
|
20
|
+
example_file :json, "datagram/user.json"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
interface "SystemCheck" do
|
27
|
+
get "check" do
|
28
|
+
desc "returns true if the system is ok otherwise false"
|
29
|
+
|
30
|
+
response do
|
31
|
+
example :text, "OK"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class APISpec::Field < APISpec::Node
|
2
|
+
# default type is String and field is not optional
|
3
|
+
def initialize(name, &block)
|
4
|
+
@optional = false
|
5
|
+
@nullable = false
|
6
|
+
@type = :string
|
7
|
+
super(name, &block)
|
8
|
+
end
|
9
|
+
|
10
|
+
def type(value)
|
11
|
+
@type = value
|
12
|
+
end
|
13
|
+
|
14
|
+
def desc(value)
|
15
|
+
@desc = value
|
16
|
+
end
|
17
|
+
|
18
|
+
def default(value)
|
19
|
+
@default = value
|
20
|
+
end
|
21
|
+
|
22
|
+
def optional(value)
|
23
|
+
@optional = value
|
24
|
+
end
|
25
|
+
|
26
|
+
def nullable(value)
|
27
|
+
@nullable = value
|
28
|
+
end
|
29
|
+
|
30
|
+
def example(value)
|
31
|
+
@example = value
|
32
|
+
end
|
33
|
+
|
34
|
+
def resolve_references!(root_namespace)
|
35
|
+
@type = root_namespace.find_object(@type) if @type.is_a? String
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require "logger"
|
2
|
+
require "fileutils"
|
3
|
+
|
4
|
+
class APISpec::Generator
|
5
|
+
attr_reader :namespace, :logger
|
6
|
+
|
7
|
+
def initialize(options)
|
8
|
+
@options = options
|
9
|
+
# logging
|
10
|
+
@logger = Logger.new(STDOUT)
|
11
|
+
@logger.level = Logger::WARN unless options[:verbose]
|
12
|
+
@logger.formatter = proc do |severity, datetime, progname, msg|
|
13
|
+
"#{datetime.strftime("%Y-%m-%d %H:%M:%S")} [#{severity}]: #{msg}\n"
|
14
|
+
end
|
15
|
+
@namespace = APISpec::Namespace.new(nil)
|
16
|
+
end
|
17
|
+
|
18
|
+
# returns the dir with the root based in the working directory
|
19
|
+
def path(dir)
|
20
|
+
File.join(@options[:workspace], dir)
|
21
|
+
end
|
22
|
+
|
23
|
+
# read all ruby files that can be found in the working directory
|
24
|
+
def parse_files!
|
25
|
+
@logger.info "parse all files"
|
26
|
+
Dir[File.join(@options[:workspace], "**", "*.rb")].each do |path|
|
27
|
+
logger.info "read file #{path}..."
|
28
|
+
@namespace.read_file(path)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# creates the output folder
|
33
|
+
def create_output_folder!
|
34
|
+
@logger.info "remove and create the output folder"
|
35
|
+
FileUtils.rm_rf "#{@options[:output]}"
|
36
|
+
FileUtils.mkdir_p @options[:output]
|
37
|
+
end
|
38
|
+
|
39
|
+
# create the index frame
|
40
|
+
def create_index!
|
41
|
+
@logger.info "create index frame"
|
42
|
+
File.open(File.join("#{@options[:output]}", "index.html"), "w") do |file|
|
43
|
+
file.write(template(binding, :index))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# create the links page (left part of frame)
|
48
|
+
def create_links!
|
49
|
+
@logger.info "create links page"
|
50
|
+
File.open(File.join("#{@options[:output]}", "links.html"), "w") do |file|
|
51
|
+
file.write(template(binding, :links))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# create the documentation files itself
|
56
|
+
def create_objects_and_interfaces!
|
57
|
+
@namespace.objects.each do |object|
|
58
|
+
create(object)
|
59
|
+
end
|
60
|
+
@namespace.interfaces.each do |interface|
|
61
|
+
create(interface)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# create the resources for all subfolders and main folder
|
66
|
+
def create_resources!
|
67
|
+
Dir[File.join(@options[:output], "**/*")].map do |path|
|
68
|
+
File.dirname(File.expand_path(path))
|
69
|
+
end.uniq.each do |path|
|
70
|
+
@logger.info "copy template resources to #{path}"
|
71
|
+
FileUtils.cp Dir[File.join(@options[:resource], "*")], path
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# start the generator
|
76
|
+
def start!
|
77
|
+
parse_files!
|
78
|
+
create_output_folder!
|
79
|
+
create_index!
|
80
|
+
create_links!
|
81
|
+
create_objects_and_interfaces!
|
82
|
+
create_resources!
|
83
|
+
end
|
84
|
+
|
85
|
+
# render a template with the passed object as binding
|
86
|
+
def template(object, template_name)
|
87
|
+
path = File.join(@options[:template], "#{template_name}.html.erb")
|
88
|
+
erb = ERB.new(File.read(path))
|
89
|
+
erb.filename = path
|
90
|
+
erb.result(object)
|
91
|
+
end
|
92
|
+
|
93
|
+
# create a doc file for the passed node
|
94
|
+
def create(node)
|
95
|
+
dir = File.dirname(node.to_path)
|
96
|
+
file_name = File.basename(node.to_path)
|
97
|
+
path = File.join("#{@options[:output]}", dir)
|
98
|
+
FileUtils.mkdir_p path
|
99
|
+
File.open(File.join(path, file_name), "w") do |file|
|
100
|
+
@logger.info "create page for #{node}"
|
101
|
+
file.write(node.to_html(self))
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/lib/apispec/http.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
module APISpec
|
2
|
+
HTTP_STATUS_CODES = {
|
3
|
+
100 => "Continue",
|
4
|
+
101 => "Switching Protocols",
|
5
|
+
102 => "Processing",
|
6
|
+
200 => "OK",
|
7
|
+
201 => "Created",
|
8
|
+
202 => "Accepted",
|
9
|
+
203 => "Non-Authoritative Information",
|
10
|
+
204 => "No Content",
|
11
|
+
205 => "Reset Content",
|
12
|
+
206 => "Partial Content",
|
13
|
+
207 => "Multi-Status",
|
14
|
+
226 => "IM Used",
|
15
|
+
300 => "Multiple Choices",
|
16
|
+
301 => "Moved Permanently",
|
17
|
+
302 => "Found",
|
18
|
+
303 => "See Other",
|
19
|
+
304 => "Not Modified",
|
20
|
+
305 => "Use Proxy",
|
21
|
+
307 => "Temporary Redirect",
|
22
|
+
400 => "Bad Request",
|
23
|
+
401 => "Unauthorized",
|
24
|
+
402 => "Payment Required",
|
25
|
+
403 => "Forbidden",
|
26
|
+
404 => "Not Found",
|
27
|
+
405 => "Method Not Allowed",
|
28
|
+
406 => "Not Acceptable",
|
29
|
+
407 => "Proxy Authentication Required",
|
30
|
+
408 => "Request Timeout",
|
31
|
+
409 => "Conflict",
|
32
|
+
410 => "Gone",
|
33
|
+
411 => "Length Required",
|
34
|
+
412 => "Precondition Failed",
|
35
|
+
413 => "Request Entity Too Large",
|
36
|
+
414 => "Request-URI Too Long",
|
37
|
+
415 => "Unsupported Media Type",
|
38
|
+
416 => "Requested Range Not Satisfiable",
|
39
|
+
417 => "Expectation Failed",
|
40
|
+
422 => "Unprocessable Entity",
|
41
|
+
423 => "Locked",
|
42
|
+
424 => "Failed Dependency",
|
43
|
+
426 => "Upgrade Required",
|
44
|
+
500 => "Internal Server Error",
|
45
|
+
501 => "Not Implemented",
|
46
|
+
502 => "Bad Gateway",
|
47
|
+
503 => "Service Unavailable",
|
48
|
+
504 => "Gateway Timeout",
|
49
|
+
505 => "HTTP Version Not Supported",
|
50
|
+
507 => "Insufficient Storage",
|
51
|
+
510 => "Not Extended"
|
52
|
+
}
|
53
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class APISpec::Interface < APISpec::Node
|
2
|
+
def initialize(name, &block)
|
3
|
+
@resources = []
|
4
|
+
super(name, &block)
|
5
|
+
end
|
6
|
+
|
7
|
+
def base_uri(value = nil)
|
8
|
+
@base_uri ||= value
|
9
|
+
end
|
10
|
+
|
11
|
+
def get(path, &block); http(:get, path, &block); end
|
12
|
+
def put(path, &block); http(:put, path, &block); end
|
13
|
+
def post(path, &block); http(:post, path, &block); end
|
14
|
+
def delete(path, &block); http(:delete, path, &block); end
|
15
|
+
def options(path, &block); http(:options, path, &block); end
|
16
|
+
def head(path, &block); http(:head, path, &block); end
|
17
|
+
def trace(path, &block); http(:trace, path, &block); end
|
18
|
+
def connect(path, &block); http(:connect, path, &block); end
|
19
|
+
|
20
|
+
def http(method, path, &block)
|
21
|
+
@resources << APISpec::Resource.new(method, path, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def resolve_references!(root_namespace)
|
25
|
+
@resources.each do |resource|
|
26
|
+
resource.resolve_references!(root_namespace)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
class APISpec::Message
|
2
|
+
def initialize(&block)
|
3
|
+
@headers = []
|
4
|
+
@parameters = []
|
5
|
+
instance_eval(&block)
|
6
|
+
end
|
7
|
+
|
8
|
+
def headers(*value)
|
9
|
+
@headers = value
|
10
|
+
end
|
11
|
+
|
12
|
+
def params(*value)
|
13
|
+
@parameters = value
|
14
|
+
end
|
15
|
+
|
16
|
+
def desc(value)
|
17
|
+
@desc = value
|
18
|
+
end
|
19
|
+
|
20
|
+
def object(value)
|
21
|
+
@object = value
|
22
|
+
end
|
23
|
+
|
24
|
+
def array(value)
|
25
|
+
@array = value
|
26
|
+
end
|
27
|
+
|
28
|
+
def content_desc(value)
|
29
|
+
@content_desc = value
|
30
|
+
end
|
31
|
+
|
32
|
+
def example(format, value)
|
33
|
+
@example = APISpec::Example.new(format, value)
|
34
|
+
end
|
35
|
+
|
36
|
+
def example_file(format, path)
|
37
|
+
@example = [format, path]
|
38
|
+
end
|
39
|
+
|
40
|
+
def resolve_references!(root_namespace)
|
41
|
+
@headers = @headers.map { |name| root_namespace.find_field(name) }.flatten
|
42
|
+
@parameters = @parameters.map { |name| root_namespace.find_field(name) }.flatten
|
43
|
+
@object = root_namespace.find_object(@object) if @object
|
44
|
+
@array = root_namespace.find_object(@array) if @array
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_html(generator, resource)
|
48
|
+
@generator = generator
|
49
|
+
@resource = resource
|
50
|
+
if @example.is_a? Array
|
51
|
+
format, path = @example
|
52
|
+
@example = APISpec::Example.new(format, File.read(@generator.path(path)))
|
53
|
+
end
|
54
|
+
generator.template(binding, :message)
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
class APISpec::Namespace < APISpec::Node
|
2
|
+
class AlreadyDefinedError < StandardError; end
|
3
|
+
class ReferenceError < StandardError; end
|
4
|
+
|
5
|
+
def initialize(name, &block)
|
6
|
+
@nodes = {}
|
7
|
+
super(name, &block)
|
8
|
+
end
|
9
|
+
|
10
|
+
# reads the passed file
|
11
|
+
def read_file(path)
|
12
|
+
eval(File.read(path), binding, path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def find_field(path)
|
16
|
+
result = find(path)
|
17
|
+
if result.is_a? Array
|
18
|
+
result.map { |path| find(path) }
|
19
|
+
else
|
20
|
+
[result]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def find_object(path)
|
25
|
+
find(path)
|
26
|
+
end
|
27
|
+
|
28
|
+
def find(path)
|
29
|
+
path_parts = path.split(".")
|
30
|
+
node = self
|
31
|
+
while path_parts.any?
|
32
|
+
node = node.find_node(path_parts.shift)
|
33
|
+
raise ReferenceError.new("#{path} not found in #{self.to_s}") unless node
|
34
|
+
end
|
35
|
+
node
|
36
|
+
end
|
37
|
+
|
38
|
+
def all(method, type)
|
39
|
+
nodes = []
|
40
|
+
@nodes.keys.sort.each do |name|
|
41
|
+
node = @nodes[name]
|
42
|
+
if node.is_a? APISpec::Namespace
|
43
|
+
nodes << node.send(method)
|
44
|
+
elsif node.is_a? type
|
45
|
+
nodes << node
|
46
|
+
end
|
47
|
+
end
|
48
|
+
nodes.flatten
|
49
|
+
end
|
50
|
+
|
51
|
+
def interfaces
|
52
|
+
all(:interfaces, APISpec::Interface)
|
53
|
+
end
|
54
|
+
|
55
|
+
def objects
|
56
|
+
all(:objects, APISpec::Object)
|
57
|
+
end
|
58
|
+
|
59
|
+
def find_node(name)
|
60
|
+
@nodes[name]
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_s
|
64
|
+
"#{super} (Nodes: #{@nodes.size})"
|
65
|
+
end
|
66
|
+
|
67
|
+
# print the structure of all namespaces
|
68
|
+
def print_tree(indent = 0, lines = [])
|
69
|
+
lines << (" " * indent) + self.to_s
|
70
|
+
@nodes.keys.sort.each do |reference|
|
71
|
+
node = @nodes[reference]
|
72
|
+
if node.is_a? APISpec::Namespace
|
73
|
+
node.print_tree(indent + 1, lines)
|
74
|
+
else
|
75
|
+
lines << (" " * (indent + 1)) + "#{reference} => #{node.to_s}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
lines.join("\n")
|
79
|
+
end
|
80
|
+
|
81
|
+
def namespace(name, &block)
|
82
|
+
if node = @nodes[name]
|
83
|
+
node.instance_eval &block
|
84
|
+
else
|
85
|
+
define_node name, APISpec::Namespace.new(name, &block)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def define_node(name, node)
|
90
|
+
raise AlreadyDefinedError.new("#{name} is already defined!") if @nodes[name]
|
91
|
+
@nodes[name] = node
|
92
|
+
node.parent = self if node.respond_to? :parent
|
93
|
+
end
|
94
|
+
|
95
|
+
def interface(name, reference = nil, &block)
|
96
|
+
reference = reference || name
|
97
|
+
define_node name, APISpec::Interface.new(name, &block)
|
98
|
+
end
|
99
|
+
|
100
|
+
def object(name, reference = nil, &block)
|
101
|
+
reference = reference || name
|
102
|
+
define_node reference, APISpec::Object.new(name, &block)
|
103
|
+
end
|
104
|
+
|
105
|
+
def field(name, reference = nil, &block)
|
106
|
+
reference = reference || name
|
107
|
+
define_node reference, APISpec::Field.new(name, &block)
|
108
|
+
end
|
109
|
+
|
110
|
+
def field_set(reference = nil, *fields)
|
111
|
+
define_node reference, fields
|
112
|
+
end
|
113
|
+
end
|