optionsful 0.1.5 → 0.1.6
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/README.textile +31 -10
- data/Rakefile +1 -1
- data/lib/baurets/optionsful/config.rb +35 -0
- data/lib/baurets/optionsful/documentator.rb +127 -0
- data/lib/baurets/optionsful/introspections.rb +79 -0
- data/lib/baurets/optionsful/server.rb +47 -0
- data/lib/baurets/optionsful/version.rb +1 -1
- data/lib/optionsful.rb +7 -53
- data/rails/init.rb +2 -14
- data/spec/{optionsful_docs_spec.rb → optionsful_documentator_spec.rb} +7 -5
- data/spec/{optionsful_spec.rb → optionsful_server_spec.rb} +14 -4
- data/spec/spec.opts +1 -1
- data/spec/spec_helper.rb +8 -5
- metadata +10 -8
- data/lib/introspections.rb +0 -94
- data/lib/optionsful_docs.rb +0 -129
data/README.textile
CHANGED
@@ -8,29 +8,50 @@ Provide dynamic information about resources via the OPTIONS' HTTP method on a RE
|
|
8
8
|
|
9
9
|
h2. "-No! No! No! *-Show me the code!*"
|
10
10
|
|
11
|
+
h3. Retrieving an HTTP OPTIONS request via telnet:
|
12
|
+
|
11
13
|
<pre>
|
12
14
|
|
13
|
-
$ telnet
|
14
|
-
OPTIONS /
|
15
|
-
Host:
|
15
|
+
$ telnet localhost 3000
|
16
|
+
OPTIONS /posts HTTP/1.1
|
17
|
+
Host: http://localhost:3000
|
16
18
|
|
17
19
|
HTTP/1.1 204 No Content
|
18
20
|
Allow: GET, POST
|
19
|
-
|
20
|
-
|
21
|
+
Connection: close
|
22
|
+
Date: Thu, 22 Jul 2010 17:20:27 GMT
|
23
|
+
Link: <http://localhost:3000/opts/posts>; type=text/html; rel=help
|
21
24
|
|
22
|
-
OPTIONS /
|
23
|
-
Host:
|
25
|
+
OPTIONS /posts/1 HTTP/1.1
|
26
|
+
Host: http://localhost:3000
|
24
27
|
|
25
28
|
HTTP/1.1 204 No Content
|
26
29
|
Allow: GET, PUT, DELETE
|
27
|
-
|
28
|
-
|
30
|
+
Connection: close
|
31
|
+
Date: Thu, 22 Jul 2010 18:14:24 GMT
|
32
|
+
Link: <http://localhost:3000/opts/posts/1/>; type=text/html; rel=help
|
29
33
|
|
30
|
-
|
34
|
+
OPTIONS /posts/1/comments HTTP/1.1
|
35
|
+
Host: http://localhost:3000
|
36
|
+
|
37
|
+
HTTP/1.1 204 No Content
|
38
|
+
Allow: GET, POST
|
39
|
+
Connection: close
|
40
|
+
Date: Thu, 22 Jul 2010 18:12:43 GMT
|
41
|
+
Link: <http://localhost:3000/opts/posts/1/comments>; type=text/html; rel=help
|
31
42
|
|
43
|
+
</pre>
|
32
44
|
~Note the empty line which is part of the HTTP protocol.~
|
33
45
|
|
46
|
+
h3. Retrieving the resource/service documentation right from your browser:
|
47
|
+
|
48
|
+
* Access the given Link header through your preferable browser. Yeah, via an HTTP GET request.
|
49
|
+
* @http://localhost:3000/opts/posts/1/comments@ for instance.
|
50
|
+
* Try up adding some RDoc-like comments above your actions definitions on the appropriate controllers.
|
51
|
+
* Refresh the page and your documentation and have fun!
|
52
|
+
* There's a lot of helpful things we can do with this extracted 'on-the-fly' information (metadata).
|
53
|
+
|
54
|
+
|
34
55
|
h2. INSTALLATION:
|
35
56
|
|
36
57
|
# Change directory to your Ruby on Rails web application,
|
data/Rakefile
CHANGED
@@ -3,7 +3,7 @@ require 'rubygems'
|
|
3
3
|
require 'spec/rake/spectask'
|
4
4
|
|
5
5
|
Spec::Rake::SpecTask.new(:spec) do |t|
|
6
|
-
t.spec_files = Dir.glob( File.dirname(__FILE__) + '/spec/**/*_spec.rb')
|
6
|
+
t.spec_files = Dir.glob( File.dirname(__FILE__) + '/spec/**/*_spec.rb' )
|
7
7
|
t.spec_opts << '--format specdoc'
|
8
8
|
t.rcov = true
|
9
9
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Baurets
|
2
|
+
module Optionsful
|
3
|
+
|
4
|
+
class Config
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
# Initialize default settings- May be overriden if RAILS_ROOT/config/optionsful.yml exists.
|
8
|
+
@config = { :http => {:base_path => "/optionsful"} }
|
9
|
+
setup
|
10
|
+
end
|
11
|
+
|
12
|
+
def setup
|
13
|
+
require "yaml"
|
14
|
+
begin
|
15
|
+
yaml_file = File.join(RAILS_ROOT, "config", "optionsful.yml")
|
16
|
+
if File.exist? yaml_file
|
17
|
+
conf = YAML::load_file(yaml_file)[RAILS_ENV].symbolize_keys
|
18
|
+
configure(conf) if conf
|
19
|
+
end
|
20
|
+
rescue
|
21
|
+
end
|
22
|
+
end
|
23
|
+
def configure(conf)
|
24
|
+
@config[:http][:base_path] = conf[:http][:base_path] if (conf[:http] && conf[:http][:base_path])
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
def base_path
|
29
|
+
@config[:http][:base_path]
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module Baurets
|
2
|
+
module Optionsful
|
3
|
+
class Documentator
|
4
|
+
|
5
|
+
def initialize(app)
|
6
|
+
@app = app
|
7
|
+
@config = ::Baurets::Optionsful::Config.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
unless env["PATH_INFO"].index(@config.base_path) == 0
|
12
|
+
@app.call(env)
|
13
|
+
else
|
14
|
+
begin
|
15
|
+
return extract_documentation(env)
|
16
|
+
rescue => e
|
17
|
+
[500, {}, e.backtrace.join("\n")]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def extract_documentation(env)
|
25
|
+
path = env["PATH_INFO"]
|
26
|
+
path = path.gsub(@config.base_path, '')
|
27
|
+
# do routing introspection:
|
28
|
+
routes = ::Baurets::Optionsful::Introspections.do_routing_introspection
|
29
|
+
# do request path investigation
|
30
|
+
route_guess = ::Baurets::Optionsful::Introspections.guess_route(routes,path)
|
31
|
+
# do the matches:
|
32
|
+
allow = ::Baurets::Optionsful::Introspections.do_the_matches(routes, route_guess)
|
33
|
+
http_methods = allow.split(", ")
|
34
|
+
|
35
|
+
path_parts = ::Baurets::Optionsful::Introspections.prepare_request_path(path)
|
36
|
+
|
37
|
+
controller_actions = []
|
38
|
+
http_methods.each do |verb|
|
39
|
+
controller_actions << [verb, relate_action_to_method(path_parts, verb)]
|
40
|
+
end
|
41
|
+
controller_actions.delete_if {|pair| pair[1].empty? }
|
42
|
+
|
43
|
+
controller_name = ::Baurets::Optionsful::Introspections.discover_controller_name(path_parts) + "_controller"
|
44
|
+
file = File.join(RAILS_ROOT, "app", "controllers", controller_name + ".rb")
|
45
|
+
controller_class = controller_name.camelize
|
46
|
+
|
47
|
+
service_doc = extract_comments_above(file, find_line_for(file, controller_class, :class)).join("\n")
|
48
|
+
|
49
|
+
methods_docs = []
|
50
|
+
controller_actions.each do |info|
|
51
|
+
methods_docs << [info, extract_comments_above(file, find_line_for(file, info[1], :method)).join("\n")]
|
52
|
+
end
|
53
|
+
|
54
|
+
body = "\n\nService: \n" + service_doc + "\n"
|
55
|
+
methods_docs.each do |md|
|
56
|
+
body += "\n\nMethod: #{md[0][0]} \n Action: #{md[0][1]} \n Metadata: \n #{md[1]}\n\n-- end ---\n"
|
57
|
+
end
|
58
|
+
|
59
|
+
[200, {}, "Under development!!! \n #{body}"]
|
60
|
+
end
|
61
|
+
|
62
|
+
def relate_action_to_method(path, verb)
|
63
|
+
action = ""
|
64
|
+
routes = ::Baurets::Optionsful::Introspections.do_routing_introspection
|
65
|
+
route_guess = ::Baurets::Optionsful::Introspections.guess_route(routes, path)
|
66
|
+
routes.each do |route|
|
67
|
+
if ((route.first == route_guess) && (route[1][0] == verb))
|
68
|
+
action = route[1][1] unless route[1][1].empty?
|
69
|
+
end
|
70
|
+
end
|
71
|
+
action
|
72
|
+
end
|
73
|
+
|
74
|
+
def file_lines(file_name)
|
75
|
+
lines = []
|
76
|
+
begin
|
77
|
+
file = File.new(file_name, "r")
|
78
|
+
while (line = file.gets)
|
79
|
+
line = line.strip
|
80
|
+
lines << line unless line.empty?
|
81
|
+
end
|
82
|
+
file.close
|
83
|
+
rescue => err
|
84
|
+
puts "Exception: #{err}"
|
85
|
+
err
|
86
|
+
end
|
87
|
+
lines.delete(nil)
|
88
|
+
lines
|
89
|
+
end
|
90
|
+
|
91
|
+
def extract_comments_above(file_name, line_number)
|
92
|
+
lines = file_lines(file_name)
|
93
|
+
doc = []
|
94
|
+
line_number = line_number -1
|
95
|
+
while ((line_number = line_number -1) && (line_number >= 0) && (!lines.nil?) && (!lines[line_number].empty?))
|
96
|
+
line = lines[line_number].lstrip
|
97
|
+
if line[0] == 35
|
98
|
+
doc << line
|
99
|
+
else
|
100
|
+
line_number = 0
|
101
|
+
end
|
102
|
+
end
|
103
|
+
doc.reverse
|
104
|
+
doc
|
105
|
+
end
|
106
|
+
|
107
|
+
def find_line_for(file, name, type)
|
108
|
+
lines = file_lines(file)
|
109
|
+
signature = ""
|
110
|
+
if type == :class
|
111
|
+
signature = "class " + name
|
112
|
+
elsif type == :method
|
113
|
+
signature = "def " + name
|
114
|
+
end
|
115
|
+
counter = 1;
|
116
|
+
lines.each do |line|
|
117
|
+
if line.include? signature
|
118
|
+
return counter
|
119
|
+
end
|
120
|
+
counter += 1
|
121
|
+
end
|
122
|
+
counter = 0
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Baurets
|
2
|
+
module Optionsful
|
3
|
+
module Introspections
|
4
|
+
|
5
|
+
def self.is_part_static?(routes, index, value)
|
6
|
+
routes.each do |route|
|
7
|
+
return true if route[0][index] == value
|
8
|
+
end
|
9
|
+
return false
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.do_routing_introspection
|
13
|
+
returning Array.new do |routes|
|
14
|
+
route_requirements = nil
|
15
|
+
ActionController::Routing::Routes.routes.each do |route|
|
16
|
+
static_path = []
|
17
|
+
route.segments.each do |segment|
|
18
|
+
route_requirements = route.requirements #TODO validate
|
19
|
+
if segment.kind_of?(ActionController::Routing::StaticSegment)
|
20
|
+
static_path << segment.value if (segment.respond_to?(:value) && segment.value != "/")
|
21
|
+
elsif segment.kind_of?(ActionController::Routing::DynamicSegment)
|
22
|
+
static_path << :dynamic unless (segment.respond_to?(:key) && segment.key == :format)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
routes << [static_path, [route.conditions[:method].to_s.upcase, route_requirements[:action]], route_requirements] unless route.conditions[:method].nil?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.guess_route(routes, path)
|
31
|
+
guess = []
|
32
|
+
parts = prepare_request_path(path)
|
33
|
+
index = 0
|
34
|
+
parts.each do |part|
|
35
|
+
if is_part_static?(routes, index, part)
|
36
|
+
guess << part
|
37
|
+
else
|
38
|
+
guess << :dynamic
|
39
|
+
end
|
40
|
+
index += 1
|
41
|
+
end
|
42
|
+
guess
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.discover_controller_name(path)
|
46
|
+
routes = do_routing_introspection
|
47
|
+
guess = guess_route(routes, path)
|
48
|
+
routes.each do |route|
|
49
|
+
if route[0] == guess
|
50
|
+
return route[2][:controller]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.do_the_matches(routes, route_guess)
|
56
|
+
allow = ""
|
57
|
+
routes.each do |route|
|
58
|
+
if route.first == route_guess
|
59
|
+
allow += route[1][0].to_s.upcase + "|"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
allow = allow.split("|").join(", ")
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.prepare_request_path(path)
|
66
|
+
path_parts = []
|
67
|
+
unless path.kind_of? Array
|
68
|
+
path = path[0..(path.rindex('.')-1)] if path.include?('.')
|
69
|
+
path_parts = path.split("/")
|
70
|
+
else
|
71
|
+
path_parts = path
|
72
|
+
end
|
73
|
+
path_parts.delete("")
|
74
|
+
path_parts
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Baurets
|
2
|
+
module Optionsful
|
3
|
+
class Server
|
4
|
+
|
5
|
+
def initialize(app)
|
6
|
+
@app = app
|
7
|
+
@config = ::Baurets::Optionsful::Config.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
unless env["REQUEST_METHOD"] == "OPTIONS"
|
12
|
+
@app.call(env)
|
13
|
+
else
|
14
|
+
extract_options_information(env)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def extract_options_information(env)
|
21
|
+
allows = extract_allowed_methods(env)
|
22
|
+
if allows.empty?
|
23
|
+
[404, {}, "Not found."]
|
24
|
+
else
|
25
|
+
[204, {"Allow" => allows, "Link" => build_help_link(env)}, ""]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def extract_allowed_methods(env)
|
30
|
+
# do routing introspection:
|
31
|
+
routes = ::Baurets::Optionsful::Introspections.do_routing_introspection
|
32
|
+
# do request path investigation
|
33
|
+
path = env["PATH_INFO"]
|
34
|
+
route_guess = ::Baurets::Optionsful::Introspections.guess_route(routes, path)
|
35
|
+
# do the matches:
|
36
|
+
allow = ::Baurets::Optionsful::Introspections.do_the_matches(routes, route_guess)
|
37
|
+
end
|
38
|
+
|
39
|
+
def build_help_link(env)
|
40
|
+
server_name = env["SERVER_NAME"]
|
41
|
+
server_port = env["SERVER_PORT"]
|
42
|
+
"<http://#{server_name}:#{server_port}" + @config.base_path + "#{env["PATH_INFO"]}>; type=text/html; rel=help"
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/optionsful.rb
CHANGED
@@ -1,57 +1,11 @@
|
|
1
|
-
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__))
|
2
2
|
|
3
|
+
require 'baurets/optionsful/config'
|
4
|
+
require 'baurets/optionsful/introspections'
|
5
|
+
require 'baurets/optionsful/server'
|
6
|
+
require 'baurets/optionsful/documentator'
|
3
7
|
|
4
|
-
|
5
|
-
|
8
|
+
module Optionsful
|
9
|
+
|
6
10
|
|
7
|
-
def initialize(app)
|
8
|
-
@app = app
|
9
|
-
@doc = {:controller => nil, :actions => [] } # { :action => "index", :method => "GET" }, { :action => "create", :method => "POST" }
|
10
|
-
end
|
11
|
-
|
12
|
-
def call(env)
|
13
|
-
unless env["REQUEST_METHOD"] == "OPTIONS"
|
14
|
-
@app.call(env)
|
15
|
-
else
|
16
|
-
extract_options_information(env)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
private
|
21
|
-
|
22
|
-
def extract_options_information(env)
|
23
|
-
[204, {"Allow" => extract_allowed_methods(env), "Link" => build_help_link(env)}, ""]
|
24
|
-
end
|
25
|
-
|
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
|
34
|
-
|
35
|
-
private
|
36
|
-
|
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!) }
|
44
|
-
end
|
45
|
-
end
|
46
|
-
allow = allow.split("|").join(", ")
|
47
|
-
end
|
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"
|
53
|
-
end
|
54
|
-
|
55
|
-
|
56
|
-
|
57
11
|
end
|
data/rails/init.rb
CHANGED
@@ -1,15 +1,3 @@
|
|
1
1
|
# Adding Optionsful to the Rack middleware stack:
|
2
|
-
ActionController::Dispatcher.middleware.use Optionsful
|
3
|
-
ActionController::Dispatcher.middleware.use
|
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)
|
2
|
+
ActionController::Dispatcher.middleware.use Baurets::Optionsful::Server
|
3
|
+
ActionController::Dispatcher.middleware.use Baurets::Optionsful::Documentator
|
@@ -1,24 +1,26 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/spec_helper'
|
2
2
|
|
3
3
|
|
4
|
-
describe
|
4
|
+
describe Baurets::Optionsful::Documentator do
|
5
5
|
|
6
6
|
include Rack::Test::Methods
|
7
7
|
|
8
|
+
config = ::Baurets::Optionsful::Config.new
|
9
|
+
path = config.base_path
|
8
10
|
|
9
11
|
describe "as a Rack middleware" do
|
10
12
|
|
11
13
|
it "is a Ruby object that responds to call;" do
|
12
|
-
assert
|
14
|
+
assert ::Baurets::Optionsful::Documentator.new(app).respond_to? :call
|
13
15
|
end
|
14
16
|
|
15
17
|
it "takes exactly one argument, (the environment) and returns an Array;" do
|
16
|
-
response =
|
18
|
+
response = ::Baurets::Optionsful::Documentator.new(app).call(mock_env({"REQUEST_METHOD" => "GET", "PATH_INFO" => path}))
|
17
19
|
assert response.kind_of?(Array)
|
18
20
|
end
|
19
21
|
|
20
22
|
it "the returned Array must have exactly three values: the status, the headers and the body;" do
|
21
|
-
response =
|
23
|
+
response = ::Baurets::Optionsful::Documentator.new(app).call(mock_env({"REQUEST_METHOD" => "GET", "PATH_INFO" => path}))
|
22
24
|
assert response.size.should == 3
|
23
25
|
assert response[0].kind_of? Fixnum
|
24
26
|
assert response[1].kind_of? Hash
|
@@ -26,7 +28,7 @@ describe "OptionsfulDocs" do
|
|
26
28
|
end
|
27
29
|
|
28
30
|
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" => "/
|
31
|
+
response = fake_docs_app.call(mock_env({"REQUEST_METHOD" => "GET", "PATH_INFO" => "/lobster"}))
|
30
32
|
assert response.size.should == 3
|
31
33
|
assert response[0].kind_of? Fixnum
|
32
34
|
assert response[0].should == 200
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/spec_helper'
|
2
2
|
|
3
3
|
|
4
|
-
describe
|
4
|
+
describe Baurets::Optionsful::Server do
|
5
5
|
|
6
6
|
include Rack::Test::Methods
|
7
7
|
|
@@ -15,16 +15,16 @@ describe "Optionsful," do
|
|
15
15
|
describe "as a Rack middleware" do
|
16
16
|
|
17
17
|
it "is a Ruby object that responds to call;" do
|
18
|
-
assert Optionsful.new(app).respond_to? :call
|
18
|
+
assert ::Baurets::Optionsful::Server.new(app).respond_to? :call
|
19
19
|
end
|
20
20
|
|
21
21
|
it "takes exactly one argument, (the environment) and returns an Array;" do
|
22
|
-
response = Optionsful.new(app).call(mock_env({"REQUEST_METHOD" => "OPTIONS", "PATH_INFO" => "/posts"}))
|
22
|
+
response = ::Baurets::Optionsful::Server.new(app).call(mock_env({"REQUEST_METHOD" => "OPTIONS", "PATH_INFO" => "/posts"}))
|
23
23
|
assert response.kind_of?(Array)
|
24
24
|
end
|
25
25
|
|
26
26
|
it "the returned Array must have exactly three values: the status, the headers and the body;" do
|
27
|
-
response = Optionsful.new(app).call(mock_env({"REQUEST_METHOD" => "OPTIONS", "PATH_INFO" => "/posts"}))
|
27
|
+
response = ::Baurets::Optionsful::Server.new(app).call(mock_env({"REQUEST_METHOD" => "OPTIONS", "PATH_INFO" => "/posts"}))
|
28
28
|
assert response.size.should == 3
|
29
29
|
assert response[0].kind_of? Fixnum
|
30
30
|
assert response[1].kind_of? Hash
|
@@ -90,6 +90,16 @@ describe "Optionsful," do
|
|
90
90
|
assert response.kind_of?(Array)
|
91
91
|
assert allows?(response[1], "DELETE")
|
92
92
|
end
|
93
|
+
|
94
|
+
it "not found a path" do
|
95
|
+
response = http_options_request("/sblingers/sblongers")
|
96
|
+
assert response.kind_of?(Array)
|
97
|
+
assert response[0].should == 404
|
98
|
+
end
|
99
|
+
|
100
|
+
it "not finding a path, pretend you're dead and.. let it goes through" do
|
101
|
+
pending
|
102
|
+
end
|
93
103
|
|
94
104
|
end
|
95
105
|
|
data/spec/spec.opts
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), '..', 'lib','optionsful.rb
|
2
|
-
require File.join(File.dirname(__FILE__), '..', 'lib','
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', "baurets", 'optionsful', "config.rb")
|
2
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', "baurets", 'optionsful', "server.rb")
|
3
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', "baurets", 'optionsful', "documentator.rb")
|
4
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', "baurets", 'optionsful', "introspections.rb")
|
5
|
+
# TODO ^ this was ugly?! :-S
|
3
6
|
|
4
7
|
require 'rubygems'
|
5
8
|
require 'sinatra'
|
@@ -41,7 +44,7 @@ DEFAULT_ENV = { "rack.version" => Rack::VERSION, "rack.input" => StringIO.new, "
|
|
41
44
|
app = Rack::Builder.new {
|
42
45
|
use Rack::CommonLogger
|
43
46
|
use Rack::ShowExceptions
|
44
|
-
use Optionsful
|
47
|
+
use Baurets::Optionsful::Server
|
45
48
|
map "/lobster" do
|
46
49
|
use Rack::Lint
|
47
50
|
run Rack::Lobster.new
|
@@ -53,7 +56,7 @@ DEFAULT_ENV = { "rack.version" => Rack::VERSION, "rack.input" => StringIO.new, "
|
|
53
56
|
app = Rack::Builder.new {
|
54
57
|
use Rack::CommonLogger
|
55
58
|
use Rack::ShowExceptions
|
56
|
-
use
|
59
|
+
use Baurets::Optionsful::Documentator
|
57
60
|
map "/lobster" do
|
58
61
|
use Rack::Lint
|
59
62
|
run Rack::Lobster.new
|
@@ -67,7 +70,7 @@ DEFAULT_ENV = { "rack.version" => Rack::VERSION, "rack.input" => StringIO.new, "
|
|
67
70
|
|
68
71
|
def http_options_request(path)
|
69
72
|
complex_env = mock_env({"REQUEST_METHOD" => "OPTIONS", "PATH_INFO" => path })
|
70
|
-
response = Optionsful.new(app).call(complex_env)
|
73
|
+
response = Baurets::Optionsful::Server.new(app).call(complex_env)
|
71
74
|
end
|
72
75
|
|
73
76
|
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: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 6
|
10
|
+
version: 0.1.6
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Marco Antonio Gonzalez Junior
|
@@ -30,17 +30,19 @@ extra_rdoc_files: []
|
|
30
30
|
files:
|
31
31
|
- README.textile
|
32
32
|
- MIT-LICENSE
|
33
|
-
- lib/optionsful.rb
|
33
|
+
- lib/baurets/optionsful/server.rb
|
34
34
|
- rails/init.rb
|
35
35
|
- lib/tasks/optionsful.rake
|
36
36
|
- Rakefile
|
37
|
-
- spec/
|
37
|
+
- spec/optionsful_server_spec.rb
|
38
38
|
- spec/spec.opts
|
39
39
|
- spec/spec_helper.rb
|
40
|
-
- lib/
|
41
|
-
- lib/introspections.rb
|
40
|
+
- lib/baurets/optionsful/documentator.rb
|
41
|
+
- lib/baurets/optionsful/introspections.rb
|
42
42
|
- lib/baurets/optionsful/version.rb
|
43
|
-
-
|
43
|
+
- lib/baurets/optionsful/config.rb
|
44
|
+
- lib/optionsful.rb
|
45
|
+
- spec/optionsful_documentator_spec.rb
|
44
46
|
has_rdoc: true
|
45
47
|
homepage: http://optionsful.rubyforge.org
|
46
48
|
licenses: []
|
data/lib/introspections.rb
DELETED
@@ -1,94 +0,0 @@
|
|
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_docs.rb
DELETED
@@ -1,129 +0,0 @@
|
|
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
|