source2swagger 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/.travis.yml +6 -0
- data/Gemfile +2 -0
- data/LICENCE +22 -0
- data/README.md +189 -0
- data/Rakefile +10 -0
- data/bin/source2swagger +84 -0
- data/lib/swagger_hash.rb +70 -0
- data/lib/swagger_reader.rb +113 -0
- data/source2swagger.gemspec +20 -0
- data/test/data/sample1.json +27 -0
- data/test/data/sample1.rb +18 -0
- data/test/data/sample2.json +86 -0
- data/test/data/sample2.rb +42 -0
- data/test/data/sample3.json +221 -0
- data/test/data/sample3.rb +156 -0
- data/test/data/sample4.rb +44 -0
- data/test/data/sample5.rb +46 -0
- data/test/data/sample_bad1.rb +20 -0
- data/test/data/sample_bad2.rb +16 -0
- data/test/data/sample_bad3.rb +44 -0
- data/test/test_helper.rb +10 -0
- data/test/test_helpers/compare_hashes.rb +57 -0
- data/test/unit/swagger_hash_test.rb +177 -0
- data/test/unit/swagger_reader_test.rb +181 -0
- metadata +98 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
/*.gem
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENCE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2012 3scale networks S.L.
|
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.
|
22
|
+
|
data/README.md
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
|
2
|
+
## Description
|
3
|
+
|
4
|
+
Coming soon...
|
5
|
+
|
6
|
+
## Usage
|
7
|
+
|
8
|
+
#### Dependencies
|
9
|
+
|
10
|
+
* Ruby (1.8x or 1.9x)
|
11
|
+
* Gem
|
12
|
+
* JSON gem (gem install json)
|
13
|
+
|
14
|
+
#### Parameters
|
15
|
+
|
16
|
+
$ bin/source2swagger
|
17
|
+
|
18
|
+
Usage: source2swagger [options]
|
19
|
+
-i, --input PATH Directory of the input source code
|
20
|
+
-e, --ext ("rb"|"c"|"js"|"py") File extension of the source code
|
21
|
+
-c, --comment ("##~"|"//~") Comment tag used to write docs
|
22
|
+
-o, --output PATH Directory where the json output will be saved (optional)
|
23
|
+
|
24
|
+
#### Example
|
25
|
+
|
26
|
+
$ bin/souce2swagger -i ~/project/lib -e "rb" -c "##~"_
|
27
|
+
|
28
|
+
This will output the Swagger compatible JSON specs on the terminal.
|
29
|
+
|
30
|
+
Add *-o /tmp* and it will write the JSON file(s) to */tmp*
|
31
|
+
|
32
|
+
#### Contributions
|
33
|
+
|
34
|
+
Feel free to extend the code and issue a pull request,
|
35
|
+
|
36
|
+
The test suite can be invoked as
|
37
|
+
|
38
|
+
$ rake test
|
39
|
+
|
40
|
+
Requires *rake* and the *gem test/unit*
|
41
|
+
|
42
|
+
|
43
|
+
## How to
|
44
|
+
|
45
|
+
Check [test/data/sample3.rb](https://github.com/solso/source2swagger/blob/master/test/data/sample3.rb) for a comprehensive real example of the *source2swagger* inline docs for Ruby.
|
46
|
+
|
47
|
+
The names of the attributes can be seen on the section Grammar (partially) or better yet in the original [Swagger Specification](http://swagger.wordnik.com/spec).
|
48
|
+
|
49
|
+
#### API names declaration
|
50
|
+
|
51
|
+
First you need to declare the API
|
52
|
+
|
53
|
+
##~ a = source2swagger.namespace("your_api_spec_name")
|
54
|
+
|
55
|
+
This will generate the file your_api_spec_name.json. The name can be declared in multiple files and several times in the same file. Each time *namespace* is invoked it returns the reference to the root element of the API named "your_api_spec_name".
|
56
|
+
|
57
|
+
#### Setting attributes elements
|
58
|
+
|
59
|
+
One by one,
|
60
|
+
|
61
|
+
##~ a.basePath = "http://helloworld.3scale.net"
|
62
|
+
##~ a.swagrVersion = "0.1a"
|
63
|
+
##~ a.apiVersion = "1.0"
|
64
|
+
|
65
|
+
or all at the same time,
|
66
|
+
|
67
|
+
##~ a.set "basePath" => "http://helloworld.3scale.net", "swagrVersion" => "0.1a", "apiVersion" => "1.0"
|
68
|
+
|
69
|
+
|
70
|
+
You can always combine
|
71
|
+
|
72
|
+
##~ a.set "basePath" => "http://helloworld.3scale.net", "swagrVersion" => "0.1a"
|
73
|
+
##~ a.apiVersion = "1.0"
|
74
|
+
|
75
|
+
#### Adding and element to a list attribute
|
76
|
+
|
77
|
+
##~ op = a.operations.add
|
78
|
+
##~ op.httpMethod = "GET"
|
79
|
+
##~ op.tags = ["production"]
|
80
|
+
##~ op.nickname = "get_word"
|
81
|
+
##~ deprecated => false
|
82
|
+
##~
|
83
|
+
##~ op = a.operations.add
|
84
|
+
##~ op.set :httpMethod => "POST", :tags => ["production"], :nickname => "set_word", :deprecated => false
|
85
|
+
|
86
|
+
Two elements (*operations*) were added to *a.operations*, you can also add directly if you do not need to have a reference to the variable *op*
|
87
|
+
|
88
|
+
##~ a.operations.add :httpMethod => "GET", :tags => ["production"], :nickname => "get_word", :deprecated => false
|
89
|
+
##~ a.operations.add :httpMethod => "POST", :tags => ["production"], :nickname => "set_word", :deprecated => false
|
90
|
+
|
91
|
+
#### Using variables for common structures
|
92
|
+
|
93
|
+
The source2swagger notation also allows you to define variables that can be defined anywhere on the source code files as *@name = value*, the value is typically a hash structure in ruby notation (*{"key_1" => "value_1", ... , "key_n" => "value_n"}*)
|
94
|
+
|
95
|
+
*Note:* all variable declarations are evaluated before the non-variable statements so vars will always available no matter where they are defined. For instance,
|
96
|
+
|
97
|
+
...
|
98
|
+
## in foo.rb
|
99
|
+
##~ op.parameters.add @parameter_app_id
|
100
|
+
...
|
101
|
+
## in bar.rb
|
102
|
+
##~ @parameter_app_id = {"name" => "word", "description" => "The word whose sentiment is to be set", "dataType" => "string", "required" => true, "paramType" => "path"}
|
103
|
+
...
|
104
|
+
|
105
|
+
|
106
|
+
#### Adding comments
|
107
|
+
|
108
|
+
You can add comments on the inline docs specification, just use the normal comment tags of your language
|
109
|
+
|
110
|
+
##~ op = a.operations.add
|
111
|
+
##
|
112
|
+
## HERE IS MY COMMENT (do not use the comment tag, e.g. ##~ but the comment tag specific of your language, in ruby #)
|
113
|
+
##
|
114
|
+
##~ op.httpMethod = "GET"
|
115
|
+
|
116
|
+
|
117
|
+
|
118
|
+
Check [test/data/sample3.rb](https://github.com/solso/source2swagger/blob/master/test/data/sample3.rb) for a comprehensive real example of the *source2swagger* inline docs for Ruby.
|
119
|
+
|
120
|
+
|
121
|
+
### Grammar
|
122
|
+
|
123
|
+
(partial)
|
124
|
+
|
125
|
+
For a more comprehensive specification of the fields needes to declare your API on the Swagger format you can always go to the [source](http://swagger.wordnik.com/spec)
|
126
|
+
|
127
|
+
$ROOT
|
128
|
+
|
129
|
+
a = source2swagger.namespace(STRING)
|
130
|
+
|
131
|
+
a.basepath = STRING [required, ]
|
132
|
+
a.swagrVersion = STRING []
|
133
|
+
a.apiVersion = STRING []
|
134
|
+
a.apis = LIST[$ENDPOINTS] [required, ]
|
135
|
+
|
136
|
+
a.models = LIST[$MODELS] []
|
137
|
+
|
138
|
+
$ENDPOINTS
|
139
|
+
|
140
|
+
e = a.apis.add
|
141
|
+
|
142
|
+
e.path = STRING [required, ]
|
143
|
+
e.format = LIST[STRING] [required, ]
|
144
|
+
e.description = STRING [required, ]
|
145
|
+
e.errorResponses = LIST[$ERRORS][]
|
146
|
+
e.operations = LIST[$OPERATIONS][]
|
147
|
+
|
148
|
+
$OPERATIONS
|
149
|
+
|
150
|
+
o = e.operations.add
|
151
|
+
|
152
|
+
o.httpMethod = STRING [required, ]
|
153
|
+
o.tags = LIST[STRING] []
|
154
|
+
o.nickname = STRING []
|
155
|
+
o.deprecated = BOOLEAN []
|
156
|
+
o.summary = STRING []
|
157
|
+
o.parameters = LIST[$PARAMETERS][]
|
158
|
+
|
159
|
+
PARAMETERS
|
160
|
+
|
161
|
+
p = o.parameters.add
|
162
|
+
|
163
|
+
p.name = STRING [required]
|
164
|
+
p.description = STRING []
|
165
|
+
p.dataType = STRING [required]
|
166
|
+
p.allowMultiple = BOOLEAN []
|
167
|
+
p.required = BOOLEAN []
|
168
|
+
p.paramType = STRING
|
169
|
+
|
170
|
+
ERRORS
|
171
|
+
|
172
|
+
err = e.operations.add
|
173
|
+
|
174
|
+
err.reason = STRING []
|
175
|
+
err.code = INT []
|
176
|
+
|
177
|
+
## Extra Resources
|
178
|
+
|
179
|
+
You can edit and view the generated Swagger JSON specs online here: [JSON Editor](http://jsoneditor.appspot.com/)
|
180
|
+
|
181
|
+
It's pretty basic but it works great for a quick manual inspection and edition
|
182
|
+
of the json generated by *source2swagger*. If you know of another online editor
|
183
|
+
please let us know.
|
184
|
+
|
185
|
+
## License
|
186
|
+
|
187
|
+
MIT License
|
188
|
+
|
189
|
+
|
data/Rakefile
ADDED
data/bin/source2swagger
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
begin
|
7
|
+
require "swagger_hash"
|
8
|
+
require "swagger_reader"
|
9
|
+
rescue LoadError
|
10
|
+
lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
|
11
|
+
|
12
|
+
if $:.include?(lib)
|
13
|
+
raise
|
14
|
+
else
|
15
|
+
$:.unshift(lib)
|
16
|
+
retry
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
opt_input = nil
|
21
|
+
opt_output = nil
|
22
|
+
opt_comment = nil
|
23
|
+
opt_extension = nil
|
24
|
+
|
25
|
+
parser = OptionParser.new do |parser|
|
26
|
+
parser.on('-i','--input PATH', 'Directory of the input source code') do |value|
|
27
|
+
opt_input = value
|
28
|
+
end
|
29
|
+
|
30
|
+
parser.on('-e','--ext ("rb"|"c"|"js"|"py")', 'File extension of the source code') do |value|
|
31
|
+
opt_extension = value
|
32
|
+
end
|
33
|
+
|
34
|
+
parser.on('-c','--comment ("##~"|"//~")','Comment tag used to write docs') do |value|
|
35
|
+
opt_comment = value
|
36
|
+
end
|
37
|
+
|
38
|
+
parser.on('-o','--output PATH','Directory where the json output will be saved (optional)') do |value|
|
39
|
+
opt_output = value
|
40
|
+
end
|
41
|
+
|
42
|
+
parser.parse!
|
43
|
+
end
|
44
|
+
|
45
|
+
unless opt_extension and opt_input and opt_comment
|
46
|
+
puts parser
|
47
|
+
abort
|
48
|
+
end
|
49
|
+
|
50
|
+
def save(results, output_path)
|
51
|
+
results.each do |k,v|
|
52
|
+
puts " Saving API #{k} to #{output_path}/#{k}.json"
|
53
|
+
File.new("#{output_path}/#{k}.json","w").puts v.to_json
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def print(results)
|
58
|
+
cont = 1
|
59
|
+
results.each do |k,v|
|
60
|
+
puts "API: #{cont}, #{k}\n"
|
61
|
+
puts v.to_json
|
62
|
+
cont=cont+1
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def run(input, extension, comment, output)
|
67
|
+
reader = SwaggerReader.new
|
68
|
+
$_swaggerhash = Hash.new
|
69
|
+
|
70
|
+
code = reader.analyze_all_files(input,extension,comment)
|
71
|
+
results = reader.process_code(code) unless code.nil? || code.empty?
|
72
|
+
|
73
|
+
puts "Swagger API in #{ARGV[0]}/**/*.#{ARGV[1]}: #{results.size} API\n"
|
74
|
+
if output.nil?
|
75
|
+
print(results)
|
76
|
+
else
|
77
|
+
save(results,output)
|
78
|
+
end
|
79
|
+
puts "Done!"
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
run(opt_input, opt_extension, opt_comment, opt_output)
|
data/lib/swagger_hash.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
|
2
|
+
class SwaggerHash < Hash
|
3
|
+
|
4
|
+
KEEP_METHODS = %w{default []= each merge! debugger puts __id__ __send__ instance_eval == equal? initialize delegate caller object_id raise class [] to_json inspect to_s new nil?}
|
5
|
+
((private_instance_methods + instance_methods).map(&:to_sym) - KEEP_METHODS.map(&:to_sym)).each{|m| undef_method(m) }
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@namespaces = Hash.new
|
9
|
+
##super
|
10
|
+
end
|
11
|
+
|
12
|
+
def namespace(name)
|
13
|
+
@namespaces[name] ||= SwaggerHash.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def namespaces
|
17
|
+
@namespaces
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing(method, *args, &block)
|
21
|
+
|
22
|
+
if method.to_s.match(/=$/)
|
23
|
+
self[method.to_s.gsub("=","").to_sym] = args.first
|
24
|
+
else
|
25
|
+
if self[method].nil?
|
26
|
+
if not method == :add
|
27
|
+
self[method] = SwaggerHash.new
|
28
|
+
else
|
29
|
+
if (args.nil? || args.empty?)
|
30
|
+
self[:_array] ||= Array.new
|
31
|
+
item = SwaggerHash.new
|
32
|
+
self[:_array] << item
|
33
|
+
return item
|
34
|
+
else
|
35
|
+
self[:_array] ||= Array.new
|
36
|
+
args.each do |item|
|
37
|
+
self[:_array] << item
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
return self[method]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def set(*args)
|
47
|
+
merge!(*args)
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_hash
|
51
|
+
h2 = Hash.new
|
52
|
+
self.each do |k,v|
|
53
|
+
if v.class != SwaggerHash && v.class != Hash
|
54
|
+
h2[k] = v
|
55
|
+
else
|
56
|
+
if not (v[:_array].nil?)
|
57
|
+
v2 = []
|
58
|
+
v[:_array].each do |item|
|
59
|
+
v2 << item.to_hash
|
60
|
+
end
|
61
|
+
h2[k] = v2
|
62
|
+
else
|
63
|
+
h2[k] = v.to_hash
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
return h2
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
|
2
|
+
class SwaggerReader
|
3
|
+
|
4
|
+
def analyze_file(file, comment_str)
|
5
|
+
|
6
|
+
code = {:code => [], :line_number => [], :file =>[]}
|
7
|
+
|
8
|
+
File.open(file,"r") do |f|
|
9
|
+
line_number = 1
|
10
|
+
while (line = f.gets)
|
11
|
+
v = line.strip.split(" ")
|
12
|
+
if !v.nil? && v.size > 0 && (v[0]==comment_str)
|
13
|
+
code[:code] << v[1..v.size].join(" ")
|
14
|
+
code[:file] << file
|
15
|
+
code[:line_number] << line_number
|
16
|
+
end
|
17
|
+
line_number = line_number + 1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
return code
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
def analyze_all_files(base_path, file_extension, comment_str)
|
26
|
+
|
27
|
+
code = {:code => [], :line_number => [], :file =>[]}
|
28
|
+
|
29
|
+
files = Dir["#{base_path}/**/*.#{file_extension}"].sort
|
30
|
+
|
31
|
+
files.each do |file|
|
32
|
+
fcode = analyze_file(file,comment_str)
|
33
|
+
[:code, :line_number, :file].each do |lab|
|
34
|
+
code[lab] = code[lab] + fcode[lab]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
return code
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
def sort_for_vars_declaration(code)
|
43
|
+
|
44
|
+
tmp_vars = {:code => [], :line_number => [], :file =>[]}
|
45
|
+
tmp_not_vars = {:code => [], :line_number => [], :file =>[]}
|
46
|
+
|
47
|
+
cont = 0
|
48
|
+
code[:code].each do |code_line|
|
49
|
+
code_line.strip!
|
50
|
+
if code_line[0]=='@'
|
51
|
+
tmp_vars[:code] << code_line.gsub('@',' ')
|
52
|
+
tmp_vars[:line_number] << code[:line_number][cont]
|
53
|
+
tmp_vars[:file] << code[:file][cont]
|
54
|
+
else
|
55
|
+
tmp_not_vars[:code] << code_line.gsub('@',' ')
|
56
|
+
tmp_not_vars[:line_number] << code[:line_number][cont]
|
57
|
+
tmp_not_vars[:file] << code[:file][cont]
|
58
|
+
end
|
59
|
+
cont=cont+1
|
60
|
+
end
|
61
|
+
|
62
|
+
res = {:code => tmp_vars[:code] + tmp_not_vars[:code], :line_number => tmp_vars[:line_number] + tmp_not_vars[:line_number], :file => tmp_vars[:file] + tmp_not_vars[:file]}
|
63
|
+
|
64
|
+
return res
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
def process_code(code)
|
70
|
+
|
71
|
+
code = sort_for_vars_declaration(code)
|
72
|
+
|
73
|
+
code[:code].insert(0,"source2swagger = SwaggerHash.new")
|
74
|
+
|
75
|
+
code[:code].size.times do |cont|
|
76
|
+
|
77
|
+
begin
|
78
|
+
v = code[:code][0..cont]
|
79
|
+
v << "out = {:apis => []}"
|
80
|
+
v << "source2swagger.namespaces.each {|k,v| out[k] = v.to_hash}"
|
81
|
+
str=v.join(";")
|
82
|
+
proc do
|
83
|
+
$SAFE = 4
|
84
|
+
eval(str)
|
85
|
+
end.call
|
86
|
+
rescue Exception => e
|
87
|
+
raise SwaggerReaderException, "Error parsing source files at #{code[:file][cont-1]}:#{code[:line_number][cont-1]}\n#{e.inspect}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
code[:code] << "out = {:apis => []}"
|
92
|
+
code[:code] << "source2swagger.namespaces.each {|k,v| out[k] = v.to_hash}"
|
93
|
+
str = code[:code].join(";")
|
94
|
+
res = proc do
|
95
|
+
$SAFE = 4
|
96
|
+
eval(str)
|
97
|
+
end.call
|
98
|
+
|
99
|
+
raise SwaggerReaderException, "Error on the evaluation of the code in docs: #{res}" unless res.class==Hash
|
100
|
+
|
101
|
+
res.each do |k, v|
|
102
|
+
res[k] = v.to_hash
|
103
|
+
end
|
104
|
+
|
105
|
+
res
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
class SwaggerReaderException < Exception
|
111
|
+
|
112
|
+
end
|
113
|
+
|