restapi 0.0.4 → 0.0.5
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/Gemfile +0 -1
- data/Gemfile.lock +0 -10
- data/README.rdoc +18 -12
- data/app/controllers/restapi/restapis_controller.rb +28 -1
- data/app/views/layouts/restapi/restapi.html.erb +1 -0
- data/app/views/restapi/restapis/_params.html.erb +22 -0
- data/app/views/restapi/restapis/_params_plain.html.erb +16 -0
- data/app/views/restapi/restapis/index.html.erb +5 -5
- data/app/views/restapi/restapis/method.html.erb +8 -4
- data/app/views/restapi/restapis/plain.html.erb +70 -0
- data/app/views/restapi/restapis/resource.html.erb +16 -5
- data/app/views/restapi/restapis/static.html.erb +4 -6
- data/lib/restapi.rb +2 -1
- data/lib/restapi/application.rb +72 -22
- data/lib/restapi/client/generator.rb +104 -0
- data/lib/restapi/client/template/Gemfile.tt +5 -0
- data/lib/restapi/client/template/README.tt +3 -0
- data/lib/restapi/client/template/base.rb.tt +33 -0
- data/lib/restapi/client/template/bin.rb.tt +110 -0
- data/lib/restapi/client/template/cli.rb.tt +25 -0
- data/lib/restapi/client/template/cli_command.rb.tt +129 -0
- data/lib/restapi/client/template/client.rb.tt +10 -0
- data/lib/restapi/client/template/resource.rb.tt +17 -0
- data/lib/restapi/dsl_definition.rb +20 -2
- data/lib/restapi/error_description.rb +8 -2
- data/lib/restapi/extractor.rb +143 -0
- data/lib/restapi/extractor/collector.rb +113 -0
- data/lib/restapi/extractor/recorder.rb +122 -0
- data/lib/restapi/extractor/writer.rb +356 -0
- data/lib/restapi/helpers.rb +10 -5
- data/lib/restapi/markup.rb +12 -12
- data/lib/restapi/method_description.rb +52 -8
- data/lib/restapi/param_description.rb +6 -5
- data/lib/restapi/railtie.rb +1 -1
- data/lib/restapi/resource_description.rb +1 -1
- data/lib/restapi/restapi_module.rb +43 -0
- data/lib/restapi/validator.rb +70 -3
- data/lib/restapi/version.rb +1 -1
- data/lib/tasks/restapi.rake +120 -121
- data/restapi.gemspec +0 -2
- data/spec/controllers/restapis_controller_spec.rb +41 -6
- data/spec/controllers/users_controller_spec.rb +51 -12
- data/spec/dummy/app/controllers/application_controller.rb +0 -2
- data/spec/dummy/app/controllers/twitter_example_controller.rb +4 -9
- data/spec/dummy/app/controllers/users_controller.rb +13 -6
- data/spec/dummy/config/initializers/restapi.rb +7 -0
- data/spec/dummy/doc/restapi_examples.yml +28 -0
- metadata +49 -76
- data/app/helpers/restapi/restapis_helper.rb +0 -31
@@ -1,7 +1,7 @@
|
|
1
1
|
module Restapi
|
2
2
|
|
3
3
|
# method parameter description
|
4
|
-
#
|
4
|
+
#
|
5
5
|
# name - method name (show)
|
6
6
|
# desc - description
|
7
7
|
# required - boolean if required
|
@@ -11,7 +11,7 @@ module Restapi
|
|
11
11
|
attr_reader :name, :desc, :required, :allow_nil, :validator
|
12
12
|
|
13
13
|
attr_accessor :parent
|
14
|
-
|
14
|
+
|
15
15
|
def initialize(name, *args, &block)
|
16
16
|
|
17
17
|
if args.size > 1 || !args.first.is_a?(Hash)
|
@@ -20,15 +20,16 @@ module Restapi
|
|
20
20
|
validator_type = nil
|
21
21
|
end
|
22
22
|
options = args.pop || {}
|
23
|
-
|
23
|
+
|
24
24
|
@name = name
|
25
25
|
@desc = Restapi.markup_to_html(options[:desc] || '')
|
26
26
|
@required = options[:required] || false
|
27
27
|
@allow_nil = options[:allow_nil] || false
|
28
|
-
|
28
|
+
|
29
29
|
@validator = nil
|
30
30
|
unless validator_type.nil?
|
31
|
-
@validator =
|
31
|
+
@validator =
|
32
|
+
Validator::BaseValidator.find(self, validator_type, options, block)
|
32
33
|
raise "Validator not found." unless validator
|
33
34
|
end
|
34
35
|
end
|
data/lib/restapi/railtie.rb
CHANGED
@@ -29,6 +29,8 @@ module Restapi
|
|
29
29
|
attr_accessor :app_name, :app_info, :copyright, :markup,
|
30
30
|
:validate, :api_base_url, :doc_base_url
|
31
31
|
|
32
|
+
alias_method :validate?, :validate
|
33
|
+
|
32
34
|
# matcher to be used in Dir.glob to find controllers to be reloaded e.g.
|
33
35
|
#
|
34
36
|
# "#{Rails.root}/app/controllers/api/*.rb"
|
@@ -43,6 +45,47 @@ module Restapi
|
|
43
45
|
@reload_controllers = Rails.env.development? unless defined? @reload_controllers
|
44
46
|
return @reload_controllers && @api_controllers_matcher
|
45
47
|
end
|
48
|
+
|
49
|
+
# set to true if you want to use pregenerated documentation cache and avoid
|
50
|
+
# generating the documentation on runtime (usefull for production
|
51
|
+
# environment).
|
52
|
+
# You can generate the cache by running
|
53
|
+
#
|
54
|
+
# rake restapi:cache
|
55
|
+
attr_accessor :use_cache
|
56
|
+
alias_method :use_cache?, :use_cache
|
57
|
+
|
58
|
+
attr_writer :cache_dir
|
59
|
+
def cache_dir
|
60
|
+
@cache_dir ||= File.join(Rails.root, "public", "restapi-cache")
|
61
|
+
end
|
62
|
+
|
63
|
+
# if there is not obvious reason why the DSL should be turned on (no
|
64
|
+
# validations, cache turned on etc.), it's disabled to avoid unneeded
|
65
|
+
# allocation. It you need the DSL for other reasons, you can force the
|
66
|
+
# activation.
|
67
|
+
attr_writer :force_dsl
|
68
|
+
def force_dsl?
|
69
|
+
@force_dsl
|
70
|
+
end
|
71
|
+
|
72
|
+
# array of controller names (strings) (might include actions as well)
|
73
|
+
# to be ignored # when extracting description form calls.
|
74
|
+
# e.g. %w[Api::CommentsController Api::PostsController#post]
|
75
|
+
attr_writer :ignored_by_recorder
|
76
|
+
def ignored_by_recorder
|
77
|
+
@ignored_by_recorder ||= []
|
78
|
+
@ignored_by_recorder.map(&:to_s)
|
79
|
+
end
|
80
|
+
|
81
|
+
# comment to put before docs that was generated automatically. It's used to
|
82
|
+
# determine if the description should be overwritten next recording.
|
83
|
+
# If you want to keep the documentation (prevent from overriding), remove
|
84
|
+
# the line above the docs.
|
85
|
+
attr_writer :generated_doc_disclaimer
|
86
|
+
def generated_doc_disclaimer
|
87
|
+
@generated_doc_disclaimer ||= "# DOC GENERATED AUTOMATICALLY: REMOVE THIS LINE TO PREVENT REGENARATING NEXT TIME"
|
88
|
+
end
|
46
89
|
|
47
90
|
def app_info
|
48
91
|
Restapi.markup_to_html(@app_info)
|
data/lib/restapi/validator.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
1
2
|
module Restapi
|
2
|
-
|
3
|
+
|
3
4
|
module Validator
|
4
|
-
|
5
|
+
|
5
6
|
# to create new validator, inherit from Restapi::Validator::Base
|
6
7
|
# and implement class method build and instance method validate
|
7
8
|
class BaseValidator
|
@@ -191,7 +192,7 @@ module Restapi
|
|
191
192
|
|
192
193
|
self.instance_exec(&@proc)
|
193
194
|
end
|
194
|
-
|
195
|
+
|
195
196
|
def validate(value)
|
196
197
|
if @hash_params
|
197
198
|
@hash_params.each do |k, p|
|
@@ -221,5 +222,71 @@ module Restapi
|
|
221
222
|
end
|
222
223
|
end
|
223
224
|
|
225
|
+
|
226
|
+
# special type of validator: we say that it's not specified
|
227
|
+
class UndefValidator < Restapi::Validator::BaseValidator
|
228
|
+
|
229
|
+
def validate(value)
|
230
|
+
true
|
231
|
+
end
|
232
|
+
|
233
|
+
def self.build(param_description, argument, options, block)
|
234
|
+
if argument == :undef
|
235
|
+
self.new(param_description)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def description
|
240
|
+
nil
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
class NumberValidator < Restapi::Validator::BaseValidator
|
245
|
+
|
246
|
+
def validate(value)
|
247
|
+
self.class.validate(value)
|
248
|
+
end
|
249
|
+
|
250
|
+
def self.build(param_description, argument, options, block)
|
251
|
+
if argument == :number
|
252
|
+
self.new(param_description)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def error
|
257
|
+
"Parameter #{param_name} expecting to be a number, got: #{@error_value}"
|
258
|
+
end
|
259
|
+
|
260
|
+
def description
|
261
|
+
"Has to be a number."
|
262
|
+
end
|
263
|
+
|
264
|
+
def self.validate(value)
|
265
|
+
value.to_s =~ /\A(0|[1-9]\d*)\Z$/
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
class BooleanValidator < Restapi::Validator::BaseValidator
|
270
|
+
|
271
|
+
def validate(value)
|
272
|
+
%w[true false].include?(value.to_s)
|
273
|
+
end
|
274
|
+
|
275
|
+
def self.build(param_description, argument, options, block)
|
276
|
+
if argument == :bool
|
277
|
+
self.new(param_description)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def error
|
282
|
+
"Parameter #{param_name} expecting to be a boolean value, got: #{@error_value}"
|
283
|
+
end
|
284
|
+
|
285
|
+
def description
|
286
|
+
"Has to be a boolean"
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
224
290
|
end
|
225
291
|
end
|
292
|
+
|
data/lib/restapi/version.rb
CHANGED
data/lib/tasks/restapi.rake
CHANGED
@@ -1,157 +1,156 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'fileutils'
|
3
|
+
require 'restapi/client/generator'
|
4
|
+
|
1
5
|
namespace :restapi do
|
2
6
|
|
3
7
|
desc "Generate static documentation"
|
8
|
+
# You can specify OUT=output_base_file to have the following structure:
|
9
|
+
#
|
10
|
+
# output_base_file.html
|
11
|
+
# output_base_file-onepage.html
|
12
|
+
# output_base_file
|
13
|
+
# | - resource1.html
|
14
|
+
# | - resource1
|
15
|
+
# | - | - method1.html
|
16
|
+
# | - | - method2.html
|
17
|
+
# | - resource2.html
|
18
|
+
#
|
19
|
+
# By default OUT="#{Rails.root}/doc/apidoc"
|
4
20
|
task :static => :environment do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
21
|
+
with_loaded_documentation do
|
22
|
+
out = ENV["OUT"] || File.join(::Rails.root, 'doc', 'apidoc')
|
23
|
+
subdir = File.basename(out)
|
24
|
+
|
25
|
+
copy_jscss(out)
|
26
|
+
|
27
|
+
Restapi.url_prefix = "./#{subdir}"
|
28
|
+
doc = Restapi.to_json
|
29
|
+
generate_one_page(out, doc)
|
30
|
+
generate_plain_page(out, doc)
|
31
|
+
generate_index_page(out, doc)
|
32
|
+
Restapi.url_prefix = "../#{subdir}"
|
33
|
+
generate_resource_pages(out, doc)
|
34
|
+
Restapi.url_prefix = "../../#{subdir}"
|
35
|
+
generate_method_pages(out, doc)
|
10
36
|
end
|
37
|
+
end
|
11
38
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
39
|
+
desc "Generate cache to avoid production dependencies on markup languages"
|
40
|
+
task :cache => :environment do
|
41
|
+
with_loaded_documentation do
|
42
|
+
cache_dir = Restapi.configuration.cache_dir
|
43
|
+
file_base = File.join(cache_dir, Restapi.configuration.doc_base_url)
|
44
|
+
doc = Restapi.to_json
|
45
|
+
generate_index_page(file_base, doc, true)
|
46
|
+
generate_resource_pages(file_base, doc, true)
|
47
|
+
generate_method_pages(file_base, doc, true)
|
48
|
+
end
|
49
|
+
end
|
20
50
|
|
21
|
-
|
51
|
+
def renderer
|
52
|
+
ActionView::Base.new(File.expand_path("../../../app/views/restapi/restapis", __FILE__))
|
53
|
+
end
|
22
54
|
|
23
|
-
|
24
|
-
|
55
|
+
def render_page(file_name, template, variables, layout = 'restapi')
|
56
|
+
av = renderer
|
57
|
+
File.open(file_name, "w") do |f|
|
58
|
+
variables.each do |var, val|
|
59
|
+
av.instance_variable_set("@#{var}", val)
|
60
|
+
end
|
25
61
|
f.write av.render(
|
26
|
-
:template => "
|
27
|
-
:
|
28
|
-
:layout => 'layouts/restapi/restapi')
|
29
|
-
puts File.join(dir_path,'index.html')
|
62
|
+
:template => "#{template}",
|
63
|
+
:layout => (layout && "../../layouts/restapi/#{layout}"))
|
30
64
|
end
|
31
|
-
|
32
65
|
end
|
33
66
|
|
34
|
-
def
|
35
|
-
|
36
|
-
FileUtils.cp_r "#{src}/.", dest
|
37
|
-
end
|
67
|
+
def generate_one_page(file_base, doc)
|
68
|
+
FileUtils.mkdir_p(File.dirname(file_base)) unless File.exists?(File.dirname(file_base))
|
38
69
|
|
70
|
+
render_page("#{file_base}-onepage.html", "static", {:doc => doc[:docs]})
|
71
|
+
end
|
39
72
|
|
40
|
-
|
41
|
-
|
73
|
+
def generate_plain_page(file_base, doc)
|
74
|
+
FileUtils.mkdir_p(File.dirname(file_base)) unless File.exists?(File.dirname(file_base))
|
42
75
|
|
43
|
-
|
76
|
+
render_page("#{file_base}-plain.html", "plain", {:doc => doc[:docs]}, nil)
|
77
|
+
end
|
44
78
|
|
45
|
-
|
46
|
-
|
47
|
-
end
|
79
|
+
def generate_index_page(file_base, doc, include_json = false)
|
80
|
+
FileUtils.mkdir_p(File.dirname(file_base)) unless File.exists?(File.dirname(file_base))
|
48
81
|
|
49
|
-
|
82
|
+
render_page("#{file_base}.html", "index", {:doc => doc[:docs]})
|
50
83
|
|
51
|
-
|
84
|
+
File.open("#{file_base}.json", "w") { |f| f << doc.to_json } if include_json
|
85
|
+
end
|
52
86
|
|
53
|
-
|
87
|
+
def generate_resource_pages(file_base, doc, include_json = false)
|
88
|
+
doc[:docs][:resources].each do |resource_name, _|
|
89
|
+
resource_file_base = File.join(file_base, resource_name.to_s)
|
90
|
+
FileUtils.mkdir_p(File.dirname(resource_file_base)) unless File.exists?(File.dirname(resource_file_base))
|
54
91
|
|
55
|
-
|
92
|
+
doc = Restapi.to_json(resource_name)
|
93
|
+
render_page("#{resource_file_base}.html", "resource", {:doc => doc[:docs],
|
94
|
+
:resource => doc[:docs][:resources].first})
|
95
|
+
File.open("#{resource_file_base}.json", "w") { |f| f << doc.to_json } if include_json
|
96
|
+
end
|
56
97
|
end
|
57
98
|
|
58
|
-
def
|
59
|
-
|
60
|
-
|
99
|
+
def generate_method_pages(file_base, doc, include_json = false)
|
100
|
+
doc[:docs][:resources].each do |resource_name, resource_params|
|
101
|
+
resource_params[:methods].each do |method|
|
102
|
+
method_file_base = File.join(file_base, resource_name.to_s, method[:name].to_s)
|
103
|
+
FileUtils.mkdir_p(File.dirname(method_file_base)) unless File.exists?(File.dirname(method_file_base))
|
61
104
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
ac content, "URL_APPEND = ENV['URL_APPEND'] || '#{args[:url_append]}'\n"
|
69
|
-
|
70
|
-
doc[:resources].each do |key, resource|
|
71
|
-
|
72
|
-
ac content, "class #{key.camelize}Client < Weary::Client"
|
73
|
-
ac content, "domain API_BASE_URL", 2
|
74
|
-
resource[:methods].each do |method|
|
75
|
-
method[:apis].each do |api|
|
76
|
-
ac content, "# #{api[:short_description]}", 2
|
77
|
-
ac content, "#{api[:http_method].downcase} :#{method[:name]}, \"#{api[:api_url]}\#{URL_APPEND}\" do |resource|", 2
|
78
|
-
required = []
|
79
|
-
optional = []
|
80
|
-
|
81
|
-
method[:params].each do |param|
|
82
|
-
if param[:required]
|
83
|
-
required << param[:name].to_sym
|
84
|
-
else
|
85
|
-
optional << param[:name].to_sym
|
86
|
-
end
|
87
|
-
end
|
88
|
-
ac content, "resource.optional :#{optional.join(', :')}", 4 unless optional.blank?
|
89
|
-
ac content, "resource.required :#{required.join(', :')}", 4 unless required.blank?
|
90
|
-
|
91
|
-
ac content, "end", 2
|
92
|
-
end
|
105
|
+
doc = Restapi.to_json(resource_name, method[:name])
|
106
|
+
render_page("#{method_file_base}.html", "method", {:doc => doc[:docs],
|
107
|
+
:resource => doc[:docs][:resources].first,
|
108
|
+
:method => doc[:docs][:resources].first[:methods].first})
|
109
|
+
|
110
|
+
File.open("#{method_file_base}.json", "w") { |f| f << doc.to_json } if include_json
|
93
111
|
end
|
94
|
-
ac content, "end\n"
|
95
112
|
end
|
113
|
+
end
|
96
114
|
|
97
|
-
|
115
|
+
def with_loaded_documentation
|
116
|
+
Restapi.configuration.use_cache = false # we don't want to skip DSL evaluation
|
117
|
+
Dir[File.join(Rails.root, "app", "controllers", "**","*.rb")].each {|f| load f}
|
118
|
+
yield
|
119
|
+
end
|
120
|
+
|
121
|
+
def copy_jscss(dest)
|
122
|
+
src = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'app', 'public', 'restapi'))
|
123
|
+
FileUtils.mkdir_p dest
|
124
|
+
FileUtils.cp_r "#{src}/.", dest
|
98
125
|
end
|
99
126
|
|
100
|
-
def create_cli doc
|
101
|
-
|
102
|
-
content = ""
|
103
|
-
ac content, "require 'rubygems'"
|
104
|
-
ac content, "require 'thor'"
|
105
|
-
ac content, "require './base'"
|
106
|
-
|
107
|
-
doc[:resources].each do |key, resource|
|
108
|
-
ac content, "class #{key.camelize} < Thor"
|
109
|
-
# ac content, "$klasses['#{key}'] = #{key.camelize}", 2
|
110
|
-
# ac content, "@client = RestClient::Resource.new \"\#{$API_URL}#{resource[:api_url]}\"\n", 2
|
111
|
-
|
112
|
-
ac content, "no_tasks do", 2
|
113
|
-
ac content, " def client", 2
|
114
|
-
ac content, " @client ||= #{key.camelize}Client.new", 2
|
115
|
-
ac content, " end", 2
|
116
|
-
ac content, "end", 2
|
117
|
-
|
118
|
-
resource[:methods].each do |method|
|
119
|
-
|
120
|
-
method[:apis].each do |api|
|
121
|
-
|
122
|
-
params = []
|
123
|
-
method[:params].each do |param|
|
124
|
-
ac content, "method_option :#{param[:name]},", 2
|
125
|
-
ac content, ":required => #{param[:required] ? 'true' : 'false' },", 4
|
126
|
-
ac content, ":desc => '#{plaintext(param[:description])}',", 4
|
127
|
-
ac content, ":type => :#{param[:expected_type]}", 4
|
128
|
-
# :type — :string, :hash, :array, :numeric, or :boolean
|
129
|
-
params << param[:name]
|
130
|
-
end
|
131
|
-
|
132
|
-
ac content, "desc '#{method[:name]}', '#{api[:short_description]}'", 2
|
133
|
-
ac content, "def #{method[:name]}", 2
|
134
|
-
ac content, " resp = client.#{method[:name]}(options).perform do |response|", 2
|
135
|
-
ac content, " if response.success?", 2
|
136
|
-
ac content, " puts response.body", 2
|
137
|
-
ac content, " else", 2
|
138
|
-
ac content, " puts \"status: \#{response.status}\"", 2
|
139
|
-
ac content, " puts response.body", 2
|
140
|
-
ac content, " end", 2
|
141
|
-
ac content, " end", 2
|
142
|
-
ac content, " resp.status", 2
|
143
|
-
ac content, "end\n", 2
|
144
|
-
|
145
|
-
end
|
146
127
|
|
147
|
-
|
148
|
-
|
149
|
-
|
128
|
+
desc "Generate CLI client for API documented with restapi gem."
|
129
|
+
task :client => [:environment] do |t, args|
|
130
|
+
Restapi.configuration.use_cache = false # we don't want to skip DSL evaluation
|
131
|
+
Restapi.api_controllers_paths.each { |file| require file }
|
150
132
|
|
151
|
-
|
133
|
+
Restapi::Client::Generator.start(Restapi.configuration.app_name)
|
152
134
|
end
|
153
135
|
|
154
136
|
def plaintext(text)
|
155
137
|
text.gsub(/<.*?>/, '').gsub("\n",' ').strip
|
156
138
|
end
|
139
|
+
|
140
|
+
desc "Update api description in controllers base on routes"
|
141
|
+
task :update_from_routes => [:environment] do
|
142
|
+
Restapi.configuration.force_dsl = true
|
143
|
+
ignored = Restapi.configuration.ignored_by_recorder
|
144
|
+
with_loaded_documentation do
|
145
|
+
apis_from_routes = Restapi::Extractor.apis_from_routes
|
146
|
+
apis_from_routes.each do |(controller, action), apis|
|
147
|
+
next if ignored.include?(controller)
|
148
|
+
next if ignored.include?("#{controller}##{action}")
|
149
|
+
Restapi::Extractor::Writer.update_action_description(controller.constantize, action) do |u|
|
150
|
+
u.update_apis(apis)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
157
156
|
end
|