rubyamf-ouvrages 2.0.0
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 +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
|