optionsful 0.1.2 → 0.1.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/lib/baurets/optionsful/version.rb +16 -0
- data/lib/introspections.rb +94 -0
- data/lib/optionsful.rb +42 -75
- data/lib/optionsful_docs.rb +129 -0
- data/rails/init.rb +14 -1
- data/spec/optionsful_docs_spec.rb +38 -0
- data/spec/optionsful_spec.rb +2 -2
- data/spec/spec_helper.rb +15 -4
- metadata +8 -4
@@ -0,0 +1,94 @@
|
|
1
|
+
|
2
|
+
module Introspections
|
3
|
+
|
4
|
+
|
5
|
+
|
6
|
+
def self.guess_route(routes, path)
|
7
|
+
parts = prepare_request_path(path)
|
8
|
+
guess = []
|
9
|
+
index = 0
|
10
|
+
parts.each do |part|
|
11
|
+
if is_part_static?(routes, index, part)
|
12
|
+
guess << part
|
13
|
+
else
|
14
|
+
guess << :dynamic
|
15
|
+
end
|
16
|
+
index += 1
|
17
|
+
end
|
18
|
+
guess
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.is_part_static?(routes, index, value)
|
22
|
+
routes.each do |route|
|
23
|
+
if route[0][index] == value
|
24
|
+
return true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
return false
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.do_routing_introspection
|
31
|
+
routes = []
|
32
|
+
route_requirements = nil
|
33
|
+
ActionController::Routing::Routes.routes.each do |route|
|
34
|
+
static_path = []
|
35
|
+
route.segments.each do |segment|
|
36
|
+
route_requirements = route.requirements #TODO validate
|
37
|
+
if segment.kind_of?(ActionController::Routing::StaticSegment)
|
38
|
+
static_path << segment.value if (segment.respond_to?(:value) && segment.value != "/")
|
39
|
+
elsif segment.kind_of?(ActionController::Routing::DynamicSegment)
|
40
|
+
static_path << :dynamic unless (segment.respond_to?(:key) && segment.key == :format)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
routes << [static_path, [route.conditions[:method].to_s.upcase, route_requirements[:action]], route_requirements] unless route.conditions[:method].nil?
|
44
|
+
end
|
45
|
+
routes
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.guess_route(routes, path)
|
49
|
+
parts = prepare_request_path(path)
|
50
|
+
guess = []
|
51
|
+
index = 0
|
52
|
+
parts.each do |part|
|
53
|
+
if is_part_static?(routes, index, part)
|
54
|
+
guess << part
|
55
|
+
else
|
56
|
+
guess << :dynamic
|
57
|
+
end
|
58
|
+
index += 1
|
59
|
+
end
|
60
|
+
guess
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.discover_controller_name(path)
|
64
|
+
routes = do_routing_introspection
|
65
|
+
guess = guess_route(routes, path)
|
66
|
+
routes.each do |route|
|
67
|
+
if route[0] == guess
|
68
|
+
return route[2][:controller]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.do_the_matches(routes, route_guess)
|
75
|
+
allow = ""
|
76
|
+
routes.each do |route|
|
77
|
+
if route.first == route_guess
|
78
|
+
allow += route[1][0].to_s.upcase + "|"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
allow = allow.split("|").join(", ")
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.prepare_request_path(path)
|
85
|
+
unless path.kind_of? Array
|
86
|
+
path = path[0..(path.rindex('.')-1)] if path.include?('.')
|
87
|
+
path_parts = path.split("/")
|
88
|
+
else
|
89
|
+
path_parts = path
|
90
|
+
end
|
91
|
+
path_parts.delete("")
|
92
|
+
path_parts
|
93
|
+
end
|
94
|
+
end
|
data/lib/optionsful.rb
CHANGED
@@ -1,90 +1,57 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'introspections.rb')
|
1
2
|
|
2
|
-
class Optionsful
|
3
3
|
|
4
|
-
|
5
|
-
@app = app
|
6
|
-
end
|
4
|
+
class Optionsful
|
7
5
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
private
|
6
|
+
|
7
|
+
def initialize(app)
|
8
|
+
@app = app
|
9
|
+
@doc = {:controller => nil, :actions => [] } # { :action => "index", :method => "GET" }, { :action => "create", :method => "POST" }
|
10
|
+
end
|
17
11
|
|
18
|
-
|
19
|
-
|
12
|
+
def call(env)
|
13
|
+
unless env["REQUEST_METHOD"] == "OPTIONS"
|
14
|
+
@app.call(env)
|
15
|
+
else
|
16
|
+
extract_options_information(env)
|
20
17
|
end
|
18
|
+
end
|
21
19
|
|
22
|
-
|
23
|
-
# do routing introspection:
|
24
|
-
routes = do_routing_introspection
|
25
|
-
# do request path investigation
|
26
|
-
route_guess = guess_route(routes, env["PATH_INFO"])
|
27
|
-
allows = ""
|
28
|
-
# do the matches:
|
29
|
-
routes.each do |route|
|
30
|
-
if route.first == route_guess
|
31
|
-
allows += route[1].to_s.upcase! + "|"
|
32
|
-
end
|
33
|
-
end
|
34
|
-
allows = allows.split("|").join(", ")
|
35
|
-
end
|
20
|
+
private
|
36
21
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
22
|
+
def extract_options_information(env)
|
23
|
+
[204, {"Allow" => extract_allowed_methods(env), "Link" => build_help_link(env)}, ""]
|
24
|
+
end
|
41
25
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
guess << :dynamic
|
51
|
-
end
|
52
|
-
index += 1
|
53
|
-
end
|
54
|
-
guess
|
55
|
-
end
|
26
|
+
def extract_allowed_methods(env)
|
27
|
+
# do routing introspection:
|
28
|
+
routes = Introspections.do_routing_introspection
|
29
|
+
# do request path investigation
|
30
|
+
route_guess = Introspections.guess_route(routes, env["PATH_INFO"])
|
31
|
+
# do the matches:
|
32
|
+
allow = do_the_matches(routes, route_guess)
|
33
|
+
end
|
56
34
|
|
57
|
-
|
58
|
-
routes.each do |route|
|
59
|
-
if route[0][index] == value
|
60
|
-
return true
|
61
|
-
end
|
62
|
-
end
|
63
|
-
return false
|
64
|
-
end
|
35
|
+
private
|
65
36
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
route.
|
71
|
-
|
72
|
-
|
73
|
-
elsif segment.kind_of?(ActionController::Routing::DynamicSegment)
|
74
|
-
static_path << :dynamic unless (segment.respond_to?(:key) && segment.key == :format)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
routes << [static_path, route.conditions[:method]] unless route.conditions[:method].nil?
|
37
|
+
def do_the_matches(routes, route_guess)
|
38
|
+
allow = ""
|
39
|
+
routes.each do |route|
|
40
|
+
if route.first == route_guess
|
41
|
+
allow += route[1][0].to_s.upcase + "|"
|
42
|
+
@doc[:controller] = route[2][:controller]
|
43
|
+
@doc[:actions] << { :action => (route[2][:action]), :method => (route[1].to_s.upcase!) }
|
78
44
|
end
|
79
|
-
routes
|
80
|
-
end
|
81
|
-
|
82
|
-
def prepare_request_path(path)
|
83
|
-
path = path[0..(path.rindex('.')-1)] if path.include?('.')
|
84
|
-
path_parts = path.split("/")
|
85
|
-
path_parts.delete("")
|
86
|
-
path_parts
|
87
45
|
end
|
46
|
+
allow = allow.split("|").join(", ")
|
47
|
+
end
|
88
48
|
|
49
|
+
def build_help_link(env)
|
50
|
+
server_name = env["SERVER_NAME"]
|
51
|
+
server_port = env["SERVER_PORT"]
|
52
|
+
"<http://#{server_name}:#{server_port}/opts#{env["PATH_INFO"]}>; type=text/html; rel=help"
|
89
53
|
end
|
90
54
|
|
55
|
+
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'yard'
|
3
|
+
require 'rdoc/rdoc'
|
4
|
+
require 'fileutils'
|
5
|
+
require File.join(File.dirname(__FILE__), 'introspections.rb')
|
6
|
+
|
7
|
+
class OptionsfulDocs
|
8
|
+
|
9
|
+
|
10
|
+
def initialize(app)
|
11
|
+
@app = app
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(env)
|
15
|
+
unless env["PATH_INFO"].index("/opts/") == 0
|
16
|
+
@app.call(env)
|
17
|
+
else
|
18
|
+
extract_documentation(env)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def extract_documentation(env)
|
23
|
+
|
24
|
+
parts = env["PATH_INFO"].split("/")
|
25
|
+
parts.delete("")
|
26
|
+
parts.delete("opts")
|
27
|
+
# do routing introspection:
|
28
|
+
routes = Introspections.do_routing_introspection
|
29
|
+
# do request path investigation
|
30
|
+
path = parts.join("/")
|
31
|
+
puts "\n #{path} \n"
|
32
|
+
route_guess = Introspections.guess_route(routes,path)
|
33
|
+
# do the matches:
|
34
|
+
allow = Introspections.do_the_matches(routes, route_guess)
|
35
|
+
http_methods = allow.split(", ")
|
36
|
+
|
37
|
+
controller_actions = []
|
38
|
+
http_methods.each do |verb|
|
39
|
+
controller_actions << [verb, relate_action_to_method(parts, verb)]
|
40
|
+
end
|
41
|
+
|
42
|
+
controller_actions.delete_if {|pair| pair[1].empty? }
|
43
|
+
puts "controller_actions: #{controller_actions.inspect}"
|
44
|
+
controller_name = Introspections.discover_controller_name(parts) + "_controller"
|
45
|
+
file = File.join(RAILS_ROOT, "app", "controllers", controller_name + ".rb")
|
46
|
+
controller_class = controller_name.camelize
|
47
|
+
|
48
|
+
service_doc = extract_comments_above(file, find_line_for(file, controller_class, :class)).join("\n")
|
49
|
+
|
50
|
+
methods_docs = []
|
51
|
+
controller_actions.each do |info|
|
52
|
+
methods_docs << [info, extract_comments_above(file, find_line_for(file, info[1], :method)).join("\n")]
|
53
|
+
end
|
54
|
+
|
55
|
+
body = "\n\nService: \n" + service_doc + "\n"
|
56
|
+
methods_docs.each do |md|
|
57
|
+
body += "\n\nMethod: #{md[0][0]} \n Action: #{md[0][1]} \n Metadata: \n #{md[1]}\n\n-- end ---\n"
|
58
|
+
end
|
59
|
+
|
60
|
+
[200, {}, "Under development!!! \n #{body}"]
|
61
|
+
end
|
62
|
+
|
63
|
+
def relate_action_to_method(path, verb)
|
64
|
+
action = ""
|
65
|
+
routes = Introspections.do_routing_introspection
|
66
|
+
route_guess = Introspections.guess_route(routes, path)
|
67
|
+
routes.each do |route|
|
68
|
+
if ((route.first == route_guess) && (route[1][0] == verb))
|
69
|
+
action = route[1][1] unless route[1][1].empty?
|
70
|
+
end
|
71
|
+
end
|
72
|
+
action
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
def file_lines(file_name)
|
78
|
+
lines = []
|
79
|
+
begin
|
80
|
+
file = File.new(file_name, "r")
|
81
|
+
while (line = file.gets)
|
82
|
+
line = line.strip
|
83
|
+
lines << line unless line.empty?
|
84
|
+
end
|
85
|
+
file.close
|
86
|
+
rescue => err
|
87
|
+
puts "Exception: #{err}"
|
88
|
+
err
|
89
|
+
end
|
90
|
+
lines.delete(nil)
|
91
|
+
lines
|
92
|
+
end
|
93
|
+
|
94
|
+
def extract_comments_above(file_name, line_number)
|
95
|
+
lines = file_lines(file_name)
|
96
|
+
doc = []
|
97
|
+
line_number = line_number -1
|
98
|
+
while ((line_number = line_number -1) && (line_number >= 0) && (!lines.nil?) && (!lines[line_number].empty?))
|
99
|
+
line = lines[line_number].lstrip
|
100
|
+
if line[0] == 35
|
101
|
+
doc << line
|
102
|
+
|
103
|
+
else
|
104
|
+
line_number = 0
|
105
|
+
end
|
106
|
+
end
|
107
|
+
doc.reverse
|
108
|
+
puts doc.inspect
|
109
|
+
doc
|
110
|
+
end
|
111
|
+
|
112
|
+
def find_line_for(file, name, type)
|
113
|
+
lines = file_lines(file)
|
114
|
+
signature = ""
|
115
|
+
if type == :class
|
116
|
+
signature = "class " + name
|
117
|
+
elsif type == :method
|
118
|
+
signature = "def " + name
|
119
|
+
end
|
120
|
+
counter = 1;
|
121
|
+
lines.each do |line|
|
122
|
+
if line.include? signature
|
123
|
+
return counter
|
124
|
+
end
|
125
|
+
counter += 1
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
data/rails/init.rb
CHANGED
@@ -1,2 +1,15 @@
|
|
1
1
|
# Adding Optionsful to the Rack middleware stack:
|
2
|
-
ActionController::Dispatcher.middleware.use Optionsful
|
2
|
+
ActionController::Dispatcher.middleware.use Optionsful
|
3
|
+
ActionController::Dispatcher.middleware.use OptionsfulDocs
|
4
|
+
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'yard'
|
8
|
+
YARD::Templates::Engine.register_template_path File.dirname(__FILE__) + '/../templates'
|
9
|
+
|
10
|
+
# Define custom tags
|
11
|
+
YARD::Tags::Library.define_tag("URL for Service", :url)
|
12
|
+
YARD::Tags::Library.define_tag("Topic for Service", :topic)
|
13
|
+
YARD::Tags::Library.define_tag("Arguments", :argument, :with_types_and_name)
|
14
|
+
YARD::Tags::Library.define_tag("Example Responses", :example_response)
|
15
|
+
YARD::Tags::Library.define_tag("Response Fields", :response_field, :with_name)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
|
4
|
+
describe "OptionsfulDocs" do
|
5
|
+
|
6
|
+
include Rack::Test::Methods
|
7
|
+
|
8
|
+
|
9
|
+
describe "as a Rack middleware" do
|
10
|
+
|
11
|
+
it "is a Ruby object that responds to call;" do
|
12
|
+
assert OptionsfulDocs.new(app).respond_to? :call
|
13
|
+
end
|
14
|
+
|
15
|
+
it "takes exactly one argument, (the environment) and returns an Array;" do
|
16
|
+
response = OptionsfulDocs.new(app).call(mock_env({"REQUEST_METHOD" => "GET", "PATH_INFO" => "/opts/"}))
|
17
|
+
assert response.kind_of?(Array)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "the returned Array must have exactly three values: the status, the headers and the body;" do
|
21
|
+
response = OptionsfulDocs.new(app).call(mock_env({"REQUEST_METHOD" => "GET", "PATH_INFO" => "/opts/"}))
|
22
|
+
assert response.size.should == 3
|
23
|
+
assert response[0].kind_of? Fixnum
|
24
|
+
assert response[1].kind_of? Hash
|
25
|
+
assert response[2].kind_of? String
|
26
|
+
end
|
27
|
+
|
28
|
+
it "must be nice, acting somewhere on a Rack middleware stack." do
|
29
|
+
response = fake_docs_app.call(mock_env({"REQUEST_METHOD" => "GET", "PATH_INFO" => "/opts/"}))
|
30
|
+
assert response.size.should == 3
|
31
|
+
assert response[0].kind_of? Fixnum
|
32
|
+
assert response[0].should == 200
|
33
|
+
assert response[1].kind_of? Hash
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
data/spec/optionsful_spec.rb
CHANGED
@@ -32,7 +32,7 @@ describe "Optionsful," do
|
|
32
32
|
end
|
33
33
|
|
34
34
|
it "must be nice, acting somewhere on a Rack middleware stack." do
|
35
|
-
response =
|
35
|
+
response = fake_opts_app.call(mock_env({"REQUEST_METHOD" => "OPTIONS", "PATH_INFO" => "/posts"}))
|
36
36
|
assert response.size.should == 3
|
37
37
|
assert response[0].kind_of? Fixnum
|
38
38
|
assert response[0].should == 204
|
@@ -101,7 +101,7 @@ describe "Optionsful," do
|
|
101
101
|
|
102
102
|
it " method GET example" do
|
103
103
|
complex_env = mock_env({"REQUEST_METHOD" => "GET", "PATH_INFO" => "/lobster" })
|
104
|
-
response =
|
104
|
+
response = fake_opts_app.call(complex_env)
|
105
105
|
assert response.kind_of?(Array)
|
106
106
|
assert response.size.should == 3
|
107
107
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), '..', 'lib','optionsful.rb')
|
2
|
+
require File.join(File.dirname(__FILE__), '..', 'lib','optionsful_docs.rb')
|
2
3
|
|
3
4
|
require 'rubygems'
|
4
5
|
require 'sinatra'
|
@@ -9,8 +10,7 @@ require 'spec'
|
|
9
10
|
require 'spec/autorun'
|
10
11
|
require 'spec/interop/test'
|
11
12
|
require 'action_controller'
|
12
|
-
|
13
|
-
# require 'baurets/optionsful'
|
13
|
+
|
14
14
|
|
15
15
|
# set test environment set :environment, :test set :run, false set :raise_errors, true set :logging, false
|
16
16
|
|
@@ -37,7 +37,7 @@ DEFAULT_ENV = { "rack.version" => Rack::VERSION, "rack.input" => StringIO.new, "
|
|
37
37
|
end
|
38
38
|
|
39
39
|
|
40
|
-
def
|
40
|
+
def fake_opts_app
|
41
41
|
app = Rack::Builder.new {
|
42
42
|
use Rack::CommonLogger
|
43
43
|
use Rack::ShowExceptions
|
@@ -48,6 +48,18 @@ DEFAULT_ENV = { "rack.version" => Rack::VERSION, "rack.input" => StringIO.new, "
|
|
48
48
|
end
|
49
49
|
}
|
50
50
|
end
|
51
|
+
|
52
|
+
def fake_docs_app
|
53
|
+
app = Rack::Builder.new {
|
54
|
+
use Rack::CommonLogger
|
55
|
+
use Rack::ShowExceptions
|
56
|
+
use OptionsfulDocs
|
57
|
+
map "/lobster" do
|
58
|
+
use Rack::Lint
|
59
|
+
run Rack::Lobster.new
|
60
|
+
end
|
61
|
+
}
|
62
|
+
end
|
51
63
|
|
52
64
|
def mock_env(options = {})
|
53
65
|
FAKE_ENV.merge!(options)
|
@@ -56,7 +68,6 @@ DEFAULT_ENV = { "rack.version" => Rack::VERSION, "rack.input" => StringIO.new, "
|
|
56
68
|
def http_options_request(path)
|
57
69
|
complex_env = mock_env({"REQUEST_METHOD" => "OPTIONS", "PATH_INFO" => path })
|
58
70
|
response = Optionsful.new(app).call(complex_env)
|
59
|
-
response
|
60
71
|
end
|
61
72
|
|
62
73
|
def allows?(headers, method)
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: optionsful
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 17
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 5
|
10
|
+
version: 0.1.5
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Marco Antonio Gonzalez Junior
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-07-
|
18
|
+
date: 2010-07-22 00:00:00 -03:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|
@@ -37,6 +37,10 @@ files:
|
|
37
37
|
- spec/optionsful_spec.rb
|
38
38
|
- spec/spec.opts
|
39
39
|
- spec/spec_helper.rb
|
40
|
+
- lib/optionsful_docs.rb
|
41
|
+
- lib/introspections.rb
|
42
|
+
- lib/baurets/optionsful/version.rb
|
43
|
+
- spec/optionsful_docs_spec.rb
|
40
44
|
has_rdoc: true
|
41
45
|
homepage: http://optionsful.rubyforge.org
|
42
46
|
licenses: []
|