rubyamf-ouvrages 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/Gemfile +22 -0
- data/README.rdoc +105 -0
- data/Rakefile +21 -0
- data/lib/rubyamf.rb +64 -0
- data/lib/rubyamf/class_mapping.rb +239 -0
- data/lib/rubyamf/configuration.rb +272 -0
- data/lib/rubyamf/envelope.rb +102 -0
- data/lib/rubyamf/fault.rb +31 -0
- data/lib/rubyamf/gateway.png +0 -0
- data/lib/rubyamf/intermediate_object.rb +18 -0
- data/lib/rubyamf/logger.rb +39 -0
- data/lib/rubyamf/model.rb +215 -0
- data/lib/rubyamf/rails/controller.rb +23 -0
- data/lib/rubyamf/rails/model.rb +151 -0
- data/lib/rubyamf/rails/rails2_bootstrap.rb +73 -0
- data/lib/rubyamf/rails/rails3_bootstrap.rb +53 -0
- data/lib/rubyamf/rails/request_processor.rb +90 -0
- data/lib/rubyamf/rails/routing.rb +97 -0
- data/lib/rubyamf/rails/time.rb +6 -0
- data/lib/rubyamf/request_parser.rb +99 -0
- data/lib/rubyamf/test.rb +61 -0
- data/lib/rubyamf/version.rb +3 -0
- data/rubyamf.gemspec +26 -0
- data/spec/class_mapping_spec.rb +147 -0
- data/spec/configuration_spec.rb +90 -0
- data/spec/envelope_spec.rb +106 -0
- data/spec/fixtures/remotingMessage.bin +0 -0
- data/spec/fixtures/requestWithNewCredentials.bin +0 -0
- data/spec/fixtures/requestWithOldCredentials.bin +0 -0
- data/spec/fixtures/rubyamf_config.rb +24 -0
- data/spec/model_spec.rb +281 -0
- data/spec/rails_integration_spec.rb +22 -0
- data/spec/rails_routing_spec.rb +75 -0
- data/spec/request_parser_spec.rb +52 -0
- data/spec/request_processor_spec.rb +119 -0
- data/spec/spec_helper.rb +58 -0
- metadata +103 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rubyamf/rails/controller'
|
2
|
+
require 'rubyamf/rails/model'
|
3
|
+
require 'rubyamf/rails/request_processor'
|
4
|
+
require 'rubyamf/rails/routing'
|
5
|
+
require 'rubyamf/rails/time'
|
6
|
+
require 'action_controller'
|
7
|
+
|
8
|
+
# Hook up MIME type
|
9
|
+
Mime::Type.register RubyAMF::MIME_TYPE, :amf
|
10
|
+
|
11
|
+
# Hook routing into routes
|
12
|
+
ActionDispatch::Routing::Mapper.send(:include, RubyAMF::Rails::Routing)
|
13
|
+
|
14
|
+
# Add some utility methods to ActionController
|
15
|
+
ActionController::Base.send(:include, RubyAMF::Rails::Controller)
|
16
|
+
|
17
|
+
# Hook up ActiveRecord Model extensions and make sure Relation objects work
|
18
|
+
if defined?(ActiveRecord)
|
19
|
+
ActiveRecord::Base.send(:include, RubyAMF::Rails::Model)
|
20
|
+
class ActiveRecord::Relation
|
21
|
+
def encode_amf ser
|
22
|
+
ser.serialize ser.version, self.to_a
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Hook up rendering
|
28
|
+
ActionController::Renderers.add :amf do |amf, options|
|
29
|
+
@amf_response = amf
|
30
|
+
@mapping_scope = options[:class_mapping_scope] || options[:mapping_scope] || nil
|
31
|
+
self.content_type ||= Mime::AMF
|
32
|
+
self.response_body = " "
|
33
|
+
end
|
34
|
+
|
35
|
+
# Add custom responder so respond_with works
|
36
|
+
class ActionController::Responder
|
37
|
+
def to_amf
|
38
|
+
display resource
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class RubyAMF::Railtie < Rails::Railtie #:nodoc:
|
43
|
+
config.rubyamf = RubyAMF.configuration
|
44
|
+
|
45
|
+
initializer "rubyamf.configured" do
|
46
|
+
RubyAMF.bootstrap
|
47
|
+
end
|
48
|
+
|
49
|
+
initializer "rubyamf.middleware" do
|
50
|
+
config.app_middleware.use RubyAMF::RequestParser
|
51
|
+
config.app_middleware.use RubyAMF::Rails::RequestProcessor
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module RubyAMF::Rails
|
2
|
+
# Rack middleware that handles dispatching AMF calls to the appropriate controller
|
3
|
+
# and action.
|
4
|
+
class RequestProcessor
|
5
|
+
def initialize app
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
# Processes the AMF request and forwards the method calls to the corresponding
|
10
|
+
# rails controllers. No middleware beyond the request processor will receive
|
11
|
+
# anything if the request is a handleable AMF request.
|
12
|
+
def call env
|
13
|
+
return @app.call(env) unless env['rubyamf.response']
|
14
|
+
|
15
|
+
# Handle each method call
|
16
|
+
req = env['rubyamf.request']
|
17
|
+
res = env['rubyamf.response']
|
18
|
+
res.each_method_call req do |method, args|
|
19
|
+
handle_method method, args, env
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Actually dispatch a fake request to the appropriate controller and extract
|
24
|
+
# the response for serialization
|
25
|
+
def handle_method method, args, env
|
26
|
+
# Parse method and load service
|
27
|
+
path = method.split('.')
|
28
|
+
method_name = path.pop
|
29
|
+
controller_name = path.pop
|
30
|
+
controller, method_name = get_service controller_name, method_name
|
31
|
+
|
32
|
+
# Setup request and controller
|
33
|
+
new_env = env.dup
|
34
|
+
new_env['HTTP_ACCEPT'] = RubyAMF::MIME_TYPE # Force amf response only
|
35
|
+
if defined? ActionDispatch::Request then
|
36
|
+
# rails 3.1
|
37
|
+
req = ActionDispatch::Request.new(new_env)
|
38
|
+
else
|
39
|
+
# older rails
|
40
|
+
req = ActionController::Request.new(new_env)
|
41
|
+
end
|
42
|
+
con = controller.new
|
43
|
+
|
44
|
+
# Populate with AMF data
|
45
|
+
amf_req = env['rubyamf.request']
|
46
|
+
params_hash = amf_req.params_hash(controller.name, method_name, args)
|
47
|
+
req.params.merge!(params_hash) if RubyAMF.configuration.populate_params_hash
|
48
|
+
con.instance_variable_set("@is_amf", true)
|
49
|
+
con.instance_variable_set("@rubyamf_params", params_hash)
|
50
|
+
con.instance_variable_set("@credentials", amf_req.credentials)
|
51
|
+
|
52
|
+
# Dispatch the request to the controller
|
53
|
+
rails_version = ::Rails::VERSION::MAJOR
|
54
|
+
if rails_version == 3
|
55
|
+
res = con.dispatch(method_name, req)
|
56
|
+
else # Rails 2
|
57
|
+
req.params['controller'] = controller.controller_name
|
58
|
+
req.params['action'] = method_name
|
59
|
+
con.process(req, ActionController::Response.new)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Copy mapping scope over to response so it can be used when serialized
|
63
|
+
env['rubyamf.response'].mapping_scope = con.send(:mapping_scope)
|
64
|
+
|
65
|
+
return con.send(:amf_response)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Validate the controller and method name and return the controller class
|
69
|
+
def get_service controller_name, method_name
|
70
|
+
# Check controller and validate against hacking attempts
|
71
|
+
begin
|
72
|
+
controller_name += "Controller" unless controller_name =~ /^.+Controller$/
|
73
|
+
controller = controller_name.constantize
|
74
|
+
raise "not controller" unless controller.respond_to?(:controller_name) && controller.respond_to?(:action_methods)
|
75
|
+
rescue Exception => e
|
76
|
+
raise "Service #{controller_name} does not exist"
|
77
|
+
end
|
78
|
+
|
79
|
+
# Check action both plain and underscored
|
80
|
+
actions = controller.action_methods
|
81
|
+
if actions.include?(method_name)
|
82
|
+
return [controller, method_name]
|
83
|
+
elsif actions.include?(method_name.underscore)
|
84
|
+
return [controller, method_name.underscore]
|
85
|
+
else
|
86
|
+
raise "Service #{controller_name} does not respond to #{method_name}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module RubyAMF::Rails
|
2
|
+
# The prefered method for parameter mapping in Rails is through the routing
|
3
|
+
# file. RubyAMF supports namespacing for controllers and several different
|
4
|
+
# syntax styles for better integration into Rails 2 or Rails 3. This module
|
5
|
+
# can also be used outside of Rails by including it wherever you want.
|
6
|
+
module Routing
|
7
|
+
RAILS2_NAMESPACE_OPTIONS = [:path_prefix, :name_prefix, :namespace]
|
8
|
+
|
9
|
+
# Define a parameter mapping
|
10
|
+
#
|
11
|
+
# ===Rails 2
|
12
|
+
#
|
13
|
+
# ActionController::Routing::Routes.draw do |map|
|
14
|
+
# map.amf :controller => "controller", :action => "action", :params => [:param1, :param2]
|
15
|
+
# map.namespace :admin do |admin|
|
16
|
+
# admin.amf "controller#action", [:param1, :param2]
|
17
|
+
# end
|
18
|
+
# map.amf "controller", "action1" => [:param1, :param2],
|
19
|
+
# "action2" => [:param1, :param2],
|
20
|
+
# "action3" => [:param1]
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# ===Rails 3
|
24
|
+
#
|
25
|
+
# AmfApplication::Application.routes.draw do |map|
|
26
|
+
# map_amf "controller#action", [:param1, :param2]
|
27
|
+
# namespace :admin do
|
28
|
+
# map_amf "controller#action", [:param1, :param2]
|
29
|
+
# end
|
30
|
+
# map_amf "controller", "action1" => [:param1, :param2],
|
31
|
+
# "action2" => [:param1, :param2],
|
32
|
+
# "action3" => [:param1]
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# ===Generic Framework
|
36
|
+
#
|
37
|
+
# include Routing
|
38
|
+
# # Namespace is WillBe::Camelized
|
39
|
+
# map_amf "controller#action", [:param1, :param2]
|
40
|
+
# map_amf "controller#action", [:param1, :param2], {:namespace => "will_be/camelized"}
|
41
|
+
# map_amf :controller => "controller", :action => "action", :params => [:param1, :param2], :namespace => "will_be/camelized"
|
42
|
+
def map_amf *args
|
43
|
+
# Rails 2 namespace uses ActiveSupport::OptionMerger, which adds namespace
|
44
|
+
# options to last hash, or passes it as an additional parameter
|
45
|
+
# Rails 3 stores current namespace info in @scope variable
|
46
|
+
|
47
|
+
# Process args
|
48
|
+
if args.length == 2 && args[1].is_a?(Hash) # "controller", "action" => [], "action" => []
|
49
|
+
# Extract the rails2_with_options hash and separate it from the actions
|
50
|
+
rails2_with_options = {}
|
51
|
+
actions = {}
|
52
|
+
args[1].each do |k,v|
|
53
|
+
if RAILS2_NAMESPACE_OPTIONS.include?(k)
|
54
|
+
rails2_with_options[k] = v
|
55
|
+
else
|
56
|
+
actions[k] = v
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Call map_amf for all actions
|
61
|
+
actions.each do |k, v|
|
62
|
+
map_amf({:controller => args[0].to_s, :action => k.to_s, :params => v}.merge(rails2_with_options))
|
63
|
+
end
|
64
|
+
return nil
|
65
|
+
elsif args[0].is_a?(String) # "controller#action", []
|
66
|
+
(controller, action) = args[0].split("#")
|
67
|
+
params = args[1]
|
68
|
+
rails2_with_options = args[2] || {}
|
69
|
+
else # :controller => "con", :action => "act", :params => []
|
70
|
+
controller = args[0].delete(:controller)
|
71
|
+
action = args[0].delete(:action)
|
72
|
+
params = args[0].delete(:params)
|
73
|
+
rails2_with_options = args[0]
|
74
|
+
end
|
75
|
+
|
76
|
+
# Convert controller name to actual controller class name
|
77
|
+
controller = controller.camelize(:upper)+"Controller" unless controller =~ /Controller$/
|
78
|
+
|
79
|
+
# Namespace controller name if used
|
80
|
+
modules = if @scope && @scope[:module]
|
81
|
+
# Rails 3 code path
|
82
|
+
@scope[:module].split("/").map {|n| n.camelize(:upper)}
|
83
|
+
elsif rails2_with_options[:namespace]
|
84
|
+
# Rails 2 code path
|
85
|
+
namespaces = rails2_with_options[:namespace].split("/")
|
86
|
+
namespaces.map {|n| n.camelize(:upper)}
|
87
|
+
else
|
88
|
+
[]
|
89
|
+
end
|
90
|
+
modules << controller
|
91
|
+
controller = modules.join("::")
|
92
|
+
|
93
|
+
RubyAMF.configuration.map_params controller, action, params
|
94
|
+
end
|
95
|
+
alias_method :amf, :map_amf
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module RubyAMF
|
2
|
+
# RubyAMF uses a two-stage middleware strategy. RequestParser is the first
|
3
|
+
# stage, and is responsible for parsing the stream and setting
|
4
|
+
# <tt>env ['rubyamf.request']</tt> and <tt>env ['rubyamf.response']</tt> to
|
5
|
+
# an instance of RubyAMF::Envelope. If the response envelope is marked as
|
6
|
+
# constructed, it will send back the serialized response. The second stage is
|
7
|
+
# RubyAMF::Rails::RequestProcessor.
|
8
|
+
class RequestParser
|
9
|
+
def initialize app
|
10
|
+
@app = app
|
11
|
+
end
|
12
|
+
|
13
|
+
# Determine how to handle the request and pass off to <tt>handle_amf</tt>,
|
14
|
+
# <tt>handle_image</tt>, or <tt>handle_html</tt>. If the <tt>show_html_gateway</tt>
|
15
|
+
# config is set to false, it will not serve an HTML page for non-amf requests
|
16
|
+
# to the gateway.
|
17
|
+
def call env
|
18
|
+
# Show HTML gateway page if it's enabled for non-amf requests
|
19
|
+
if RubyAMF.configuration.show_html_gateway
|
20
|
+
if env['PATH_INFO'] == gateway_image_path
|
21
|
+
return handle_image
|
22
|
+
elsif env['PATH_INFO'] == gateway_path && env['CONTENT_TYPE'] != RubyAMF::MIME_TYPE
|
23
|
+
return handle_html
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Handle if it's AMF or pass it up the chain
|
28
|
+
if env['PATH_INFO'] == gateway_path && env['CONTENT_TYPE'] == RubyAMF::MIME_TYPE
|
29
|
+
return handle_amf(env)
|
30
|
+
else
|
31
|
+
return @app.call(env)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# It parses the request, creates a response object, and forwards the call
|
36
|
+
# to the next middleware. If the amf response is constructed, then it serializes
|
37
|
+
# the response and returns it as the response.
|
38
|
+
def handle_amf env
|
39
|
+
# Wrap request and response
|
40
|
+
env['rack.input'].rewind
|
41
|
+
begin
|
42
|
+
env['rubyamf.request'] = RubyAMF::Envelope.new.populate_from_stream(env['rack.input'].read)
|
43
|
+
rescue Exception => e
|
44
|
+
RubyAMF.logger.log_error(e)
|
45
|
+
msg = "Invalid AMF request"
|
46
|
+
return [400, {"Content-Type" => "text/plain", 'Content-Length' => msg.length.to_s}, [msg]]
|
47
|
+
end
|
48
|
+
env['rubyamf.response'] = RubyAMF::Envelope.new
|
49
|
+
|
50
|
+
# Pass up the chain to the request processor, or whatever is layered in between
|
51
|
+
result = @app.call(env)
|
52
|
+
|
53
|
+
# Calculate length and return response
|
54
|
+
if env['rubyamf.response'].constructed?
|
55
|
+
RubyAMF.logger.info "Sending back AMF"
|
56
|
+
response = env['rubyamf.response'].to_s
|
57
|
+
return [200, {"Content-Type" => RubyAMF::MIME_TYPE, 'Content-Length' => response.length.to_s}, [response]]
|
58
|
+
else
|
59
|
+
return result
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# It returns a simple HTML page confirming that the gateway is properly running
|
64
|
+
def handle_html
|
65
|
+
info_url = "https://github.com/rubyamf/rubyamf"
|
66
|
+
html = <<END_OF_HTML
|
67
|
+
<html>
|
68
|
+
<head>
|
69
|
+
<title>RubyAMF Gateway</title>
|
70
|
+
<style>body{margin:0;padding:0;color:#c8c8c8;background-color:#222222;}</style>
|
71
|
+
</head>
|
72
|
+
<body>
|
73
|
+
<table width="100%" height="100%" align="center" valign="middle">
|
74
|
+
<tr><td align="center"><a href="#{info_url}"><img border="0" src="#{gateway_image_path}" /></a></td></tr>
|
75
|
+
</table>
|
76
|
+
</body>
|
77
|
+
</html>
|
78
|
+
END_OF_HTML
|
79
|
+
return [200, {'Content-Type' => 'text/html', 'Content-Length' => html.length.to_s}, [html]]
|
80
|
+
end
|
81
|
+
|
82
|
+
# Serve rubyamf logo for html page
|
83
|
+
def handle_image
|
84
|
+
path = File.join(File.dirname(__FILE__), 'gateway.png')
|
85
|
+
content = File.read(path)
|
86
|
+
size = content.respond_to?(:bytesize) ? content.bytesize : content.size
|
87
|
+
return [200, {'Content-Type' => 'image/png', 'Content-Length' => size.to_s}, [content]]
|
88
|
+
end
|
89
|
+
|
90
|
+
def gateway_path #:nodoc:
|
91
|
+
path = RubyAMF.configuration.gateway_path
|
92
|
+
path.end_with?("/") ? path[0...-1] : path
|
93
|
+
end
|
94
|
+
|
95
|
+
def gateway_image_path #:nodoc:
|
96
|
+
gateway_path + "/gateway.png"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/rubyamf/test.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'rack/mock'
|
2
|
+
|
3
|
+
module RubyAMF
|
4
|
+
# Contains helpers to make testing AMF controller testing simple. Rails users
|
5
|
+
# can simply use <tt>create_call</tt> and <tt>create_flex_call</tt> to create
|
6
|
+
# requests and run them using <tt>dispatch_rails</tt>. Users of other rack-based
|
7
|
+
# frameworks will need to handle properly dispatching the request to the right
|
8
|
+
# middleware. <tt>create_call_type</tt> creates a rack environment similar to
|
9
|
+
# what you would get from RubyAMF::RequestParser, so you can just pass it to
|
10
|
+
# whatever your processing middleware is.
|
11
|
+
#
|
12
|
+
# Rails Example:
|
13
|
+
#
|
14
|
+
# env = RubyAMF::Test.create_call 3, "TestController.get_user", 5
|
15
|
+
# res = RubyAMF::Test.dispatch_rails env
|
16
|
+
# res.mapping_scope.should == "testing"
|
17
|
+
# res.result.class.name.should == "User"
|
18
|
+
module Test
|
19
|
+
class << self
|
20
|
+
# Creates a rack environment hash that can be used to dispatch the given
|
21
|
+
# call. The environment that is created can be directly dispatched to
|
22
|
+
# RubyAMF::Rails::RequestProcessor or your own middleware. The type can either
|
23
|
+
# be <tt>:standard</tt> or <tt>:flex</tt>, with the flex type using the same
|
24
|
+
# style as <tt>RemoteObject</tt> does.
|
25
|
+
def create_call_type type, amf_version, target, *args
|
26
|
+
amf_req = RubyAMF::Envelope.new :amf_version => amf_version
|
27
|
+
if type == :standard
|
28
|
+
amf_req.call target, *args
|
29
|
+
elsif type == :flex
|
30
|
+
amf_req.call_flex target, *args
|
31
|
+
else
|
32
|
+
raise "Invalid call type: #{type}"
|
33
|
+
end
|
34
|
+
|
35
|
+
env = ::Rack::MockRequest.env_for(RubyAMF.configuration.gateway_path, :method => "post", :input => amf_req.to_s, "CONTENT_TYPE" => RubyAMF::MIME_TYPE)
|
36
|
+
env["REQUEST_URI"] = env["PATH_INFO"] # Rails 2.3.X needs this
|
37
|
+
env['rubyamf.request'] = amf_req
|
38
|
+
env['rubyamf.response'] = RubyAMF::Envelope.new
|
39
|
+
env
|
40
|
+
end
|
41
|
+
|
42
|
+
# Creates a rack environment for the standard call type
|
43
|
+
def create_call amf_version, target, *args
|
44
|
+
create_call_type :standard, amf_version, target, *args
|
45
|
+
end
|
46
|
+
|
47
|
+
# Creates a rack environment for the flex call type
|
48
|
+
def create_flex_call target, *args
|
49
|
+
create_call_type :flex, 3, target, *args
|
50
|
+
end
|
51
|
+
|
52
|
+
# Dispatches the given rack environment to RubyAMF::Rails::RequestProcessor,
|
53
|
+
# which calls the specified controllers. Returns the response RubyAMF::Envelope.
|
54
|
+
def dispatch_rails env
|
55
|
+
middleware = RubyAMF::Rails::RequestProcessor.new nil
|
56
|
+
middleware.call env
|
57
|
+
env['rubyamf.response']
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/rubyamf.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "rubyamf/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "rubyamf-ouvrages"
|
7
|
+
s.version = RubyAMF::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Stephen Augenstein"]
|
10
|
+
s.email = ["perl.programmer@gmail.com"]
|
11
|
+
s.homepage = ""
|
12
|
+
s.summary = %q{AMF remoting for Ruby and Rails}
|
13
|
+
s.description = %q{RubyAMF is an open source flash remoting gateway for Ruby on Rails and other Rack-based web frameworks.}
|
14
|
+
s.rubyforge_project = "rubyamf"
|
15
|
+
|
16
|
+
s.add_dependency('activesupport', '>= 2.3')
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
|
23
|
+
s.has_rdoc = true
|
24
|
+
s.extra_rdoc_files = ['README.rdoc']
|
25
|
+
s.rdoc_options = ['--line-numbers', '--main', 'README.rdoc']
|
26
|
+
end
|