typhoeus 0.4.2 → 0.5.0.alpha
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +86 -28
- data/Gemfile +17 -1
- data/README.md +20 -422
- data/Rakefile +21 -12
- data/lib/typhoeus.rb +58 -41
- data/lib/typhoeus/config.rb +14 -0
- data/lib/typhoeus/errors.rb +9 -0
- data/lib/typhoeus/errors/no_stub.rb +12 -0
- data/lib/typhoeus/errors/typhoeus_error.rb +8 -0
- data/lib/typhoeus/expectation.rb +126 -0
- data/lib/typhoeus/hydra.rb +31 -236
- data/lib/typhoeus/hydras/block_connection.rb +33 -0
- data/lib/typhoeus/hydras/easy_factory.rb +67 -0
- data/lib/typhoeus/hydras/easy_pool.rb +40 -0
- data/lib/typhoeus/hydras/memoizable.rb +53 -0
- data/lib/typhoeus/hydras/queueable.rb +46 -0
- data/lib/typhoeus/hydras/runnable.rb +18 -0
- data/lib/typhoeus/hydras/stubbable.rb +27 -0
- data/lib/typhoeus/request.rb +68 -243
- data/lib/typhoeus/requests/actions.rb +101 -0
- data/lib/typhoeus/requests/block_connection.rb +31 -0
- data/lib/typhoeus/requests/callbacks.rb +82 -0
- data/lib/typhoeus/requests/marshal.rb +21 -0
- data/lib/typhoeus/requests/memoizable.rb +36 -0
- data/lib/typhoeus/requests/operations.rb +52 -0
- data/lib/typhoeus/requests/responseable.rb +29 -0
- data/lib/typhoeus/requests/stubbable.rb +29 -0
- data/lib/typhoeus/response.rb +24 -118
- data/lib/typhoeus/responses/header.rb +50 -0
- data/lib/typhoeus/responses/informations.rb +43 -0
- data/lib/typhoeus/responses/legacy.rb +27 -0
- data/lib/typhoeus/responses/status.rb +78 -0
- data/lib/typhoeus/version.rb +3 -1
- metadata +34 -141
- data/lib/typhoeus/curl.rb +0 -453
- data/lib/typhoeus/easy.rb +0 -115
- data/lib/typhoeus/easy/auth.rb +0 -14
- data/lib/typhoeus/easy/callbacks.rb +0 -33
- data/lib/typhoeus/easy/ffi_helper.rb +0 -61
- data/lib/typhoeus/easy/infos.rb +0 -90
- data/lib/typhoeus/easy/options.rb +0 -115
- data/lib/typhoeus/easy/proxy.rb +0 -20
- data/lib/typhoeus/easy/ssl.rb +0 -82
- data/lib/typhoeus/filter.rb +0 -28
- data/lib/typhoeus/form.rb +0 -61
- data/lib/typhoeus/header.rb +0 -54
- data/lib/typhoeus/hydra/callbacks.rb +0 -24
- data/lib/typhoeus/hydra/connect_options.rb +0 -61
- data/lib/typhoeus/hydra/stubbing.rb +0 -68
- data/lib/typhoeus/hydra_mock.rb +0 -131
- data/lib/typhoeus/multi.rb +0 -146
- data/lib/typhoeus/param_processor.rb +0 -43
- data/lib/typhoeus/remote.rb +0 -306
- data/lib/typhoeus/remote_method.rb +0 -108
- data/lib/typhoeus/remote_proxy_object.rb +0 -50
- data/lib/typhoeus/utils.rb +0 -50
@@ -0,0 +1,101 @@
|
|
1
|
+
module Typhoeus
|
2
|
+
module Requests # :nodoc:
|
3
|
+
|
4
|
+
# Module containing logic about shortcuts to
|
5
|
+
# http methods. Like
|
6
|
+
# Typhoeus.get("www.example.com")
|
7
|
+
module Actions
|
8
|
+
|
9
|
+
# Make a get request.
|
10
|
+
#
|
11
|
+
# @example Make get request.
|
12
|
+
# Typhoeus.get("www.example.com")
|
13
|
+
#
|
14
|
+
# @param [ String ] url The url to request.
|
15
|
+
# @param [ options ] options The options.
|
16
|
+
#
|
17
|
+
# @return [ Response ] The response.
|
18
|
+
def get(url, options = {})
|
19
|
+
Request.run(url, options.merge(:method => :get))
|
20
|
+
end
|
21
|
+
|
22
|
+
# Make a post request.
|
23
|
+
#
|
24
|
+
# @example Make post request.
|
25
|
+
# Typhoeus.post("www.example.com")
|
26
|
+
#
|
27
|
+
# @param [ String ] url The url to request.
|
28
|
+
# @param [ options ] options The options.
|
29
|
+
#
|
30
|
+
# @return [ Response ] The response.
|
31
|
+
def post(url, options = {})
|
32
|
+
Request.run(url, options.merge(:method => :post))
|
33
|
+
end
|
34
|
+
|
35
|
+
# Make a put request.
|
36
|
+
#
|
37
|
+
# @example Make put request.
|
38
|
+
# Typhoeus.put("www.example.com")
|
39
|
+
#
|
40
|
+
# @param [ String ] url The url to request.
|
41
|
+
# @param [ options ] options The options.
|
42
|
+
#
|
43
|
+
# @return [ Response ] The response.
|
44
|
+
def put(url, options = {})
|
45
|
+
Request.run(url, options.merge(:method => :put))
|
46
|
+
end
|
47
|
+
|
48
|
+
# Make a delete request.
|
49
|
+
#
|
50
|
+
# @example Make delete request.
|
51
|
+
# Typhoeus.delete("www.example.com")
|
52
|
+
#
|
53
|
+
# @param [ String ] url The url to request.
|
54
|
+
# @param [ options ] options The options.
|
55
|
+
#
|
56
|
+
# @return [ Response ] The response.
|
57
|
+
def delete(url, options = {})
|
58
|
+
Request.run(url, options.merge(:method => :delete))
|
59
|
+
end
|
60
|
+
|
61
|
+
# Make a head request.
|
62
|
+
#
|
63
|
+
# @example Make head request.
|
64
|
+
# Typhoeus.head("www.example.com")
|
65
|
+
#
|
66
|
+
# @param [ String ] url The url to request.
|
67
|
+
# @param [ options ] options The options.
|
68
|
+
#
|
69
|
+
# @return [ Response ] The response.
|
70
|
+
def head(url, options = {})
|
71
|
+
Request.run(url, options.merge(:method => :head))
|
72
|
+
end
|
73
|
+
|
74
|
+
# Make a patch request.
|
75
|
+
#
|
76
|
+
# @example Make patch request.
|
77
|
+
# Typhoeus.patch("www.example.com")
|
78
|
+
#
|
79
|
+
# @param [ String ] url The url to request.
|
80
|
+
# @param [ options ] options The options.
|
81
|
+
#
|
82
|
+
# @return [ Response ] The response.
|
83
|
+
def patch(url, options = {})
|
84
|
+
Request.run(url, options.merge(:method => :patch))
|
85
|
+
end
|
86
|
+
|
87
|
+
# Make a options request.
|
88
|
+
#
|
89
|
+
# @example Make options request.
|
90
|
+
# Typhoeus.options("www.example.com")
|
91
|
+
#
|
92
|
+
# @param [ String ] url The url to request.
|
93
|
+
# @param [ options ] options The options.
|
94
|
+
#
|
95
|
+
# @return [ Response ] The response.
|
96
|
+
def options(url, options = {})
|
97
|
+
Request.run(url, options.merge(:method => :options))
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Typhoeus
|
2
|
+
module Requests
|
3
|
+
|
4
|
+
# This module handles the blocked connection request mode on
|
5
|
+
# the request side, where only stubbed requests
|
6
|
+
# are allowed.
|
7
|
+
# Connection blocking needs to be turned on:
|
8
|
+
# Typhoeus.configure do |config|
|
9
|
+
# config.block_connection = true
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# When trying to do real requests a NoStub error
|
13
|
+
# is raised.
|
14
|
+
module BlockConnection
|
15
|
+
|
16
|
+
# Overrides run in order to check before if block connection
|
17
|
+
# is turned on. If thats the case a NoStub error is
|
18
|
+
# raised.
|
19
|
+
#
|
20
|
+
# @example Run request.
|
21
|
+
# request.run
|
22
|
+
def run
|
23
|
+
if Typhoeus::Config.block_connection
|
24
|
+
raise Typhoeus::Errors::NoStub.new(self)
|
25
|
+
else
|
26
|
+
super
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Typhoeus
|
2
|
+
module Requests
|
3
|
+
|
4
|
+
# This module contains the logic for the response callbacks.
|
5
|
+
# The on_complete callback is the only one at the moment.
|
6
|
+
#
|
7
|
+
# You can set multiple callbacks, which are then executed
|
8
|
+
# in the same order.
|
9
|
+
#
|
10
|
+
# request.on_complete { p 1 }
|
11
|
+
# request.on_complete { p 2 }
|
12
|
+
# request.execute_callbacks
|
13
|
+
# #=> 1
|
14
|
+
# #=> 2
|
15
|
+
#
|
16
|
+
# You can clear the callbacks:
|
17
|
+
#
|
18
|
+
# request.on_complete { p 1 }
|
19
|
+
# request.on_complete { p 2 }
|
20
|
+
# request.on_complete.clear
|
21
|
+
# request.execute_callbacks
|
22
|
+
# #=> []
|
23
|
+
module Callbacks
|
24
|
+
|
25
|
+
module Types
|
26
|
+
# Set on_complete callback.
|
27
|
+
#
|
28
|
+
# @example Set on_complete.
|
29
|
+
# request.on_complete { p "yay" }
|
30
|
+
#
|
31
|
+
# @param [ Block ] block The block to execute.
|
32
|
+
def on_complete(&block)
|
33
|
+
@on_complete ||= []
|
34
|
+
@on_complete << block if block_given?
|
35
|
+
@on_complete
|
36
|
+
end
|
37
|
+
|
38
|
+
# Set on_success callback.
|
39
|
+
#
|
40
|
+
# @example Set on_success.
|
41
|
+
# request.on_success { p "yay" }
|
42
|
+
#
|
43
|
+
# @param [ Block ] block The block to execute.
|
44
|
+
def on_success(&block)
|
45
|
+
@on_success ||= []
|
46
|
+
@on_success << block if block_given?
|
47
|
+
@on_success
|
48
|
+
end
|
49
|
+
|
50
|
+
# Set on_failure callback.
|
51
|
+
#
|
52
|
+
# @example Set on_failure.
|
53
|
+
# request.on_failure { p "yay" }
|
54
|
+
#
|
55
|
+
# @param [ Block ] block The block to execute.
|
56
|
+
def on_failure(&block)
|
57
|
+
@on_failure ||= []
|
58
|
+
@on_failure << block if block_given?
|
59
|
+
@on_failure
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Execute nessecary callback and yields response. This
|
64
|
+
# include in every case on_complete, on_success if
|
65
|
+
# successful and on_failure if not.
|
66
|
+
#
|
67
|
+
# @example Execute callbacks.
|
68
|
+
# request.execute_callbacks
|
69
|
+
def execute_callbacks
|
70
|
+
callbacks = Typhoeus.on_complete + on_complete
|
71
|
+
|
72
|
+
if response && response.success?
|
73
|
+
callbacks += Typhoeus.on_success + on_success
|
74
|
+
elsif response
|
75
|
+
callbacks += Typhoeus.on_failure + on_failure
|
76
|
+
end
|
77
|
+
|
78
|
+
callbacks.map{ |callback| callback.call(self.response) }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Typhoeus
|
2
|
+
module Requests
|
3
|
+
|
4
|
+
# This module contains custom serializer.
|
5
|
+
module Marshal
|
6
|
+
|
7
|
+
# Return the important data needed to serialize this Request, except the
|
8
|
+
# `on_complete` handler, since they cannot be marshalled.
|
9
|
+
def marshal_dump
|
10
|
+
(instance_variables - ['@on_complete', :@on_complete]).map do |name|
|
11
|
+
[name, instance_variable_get(name)]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Load.
|
16
|
+
def marshal_load(attributes)
|
17
|
+
attributes.each { |name, value| instance_variable_set(name, value) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Typhoeus
|
2
|
+
module Requests
|
3
|
+
|
4
|
+
# This module handles the GET request memoization
|
5
|
+
# on the request side. Memoization needs to be turned
|
6
|
+
# on:
|
7
|
+
# Typhoeus.configure do |config|
|
8
|
+
# config.memoize = true
|
9
|
+
# end
|
10
|
+
module Memoizable
|
11
|
+
|
12
|
+
# Override response setter and memoizes response
|
13
|
+
# if the request is memoizable.
|
14
|
+
#
|
15
|
+
# @param [ Response ] response The response to set.
|
16
|
+
#
|
17
|
+
# @example Set response.
|
18
|
+
# request.response = response
|
19
|
+
def response=(response)
|
20
|
+
hydra.memory[self] = response if memoizable?
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
# Return whether a request is memoizable.
|
25
|
+
#
|
26
|
+
# @example Is request memoizable?
|
27
|
+
# request.memoizable?
|
28
|
+
#
|
29
|
+
# @return [ Boolean ] Return true if memoizable, false else.
|
30
|
+
def memoizable?
|
31
|
+
Typhoeus::Config.memoize &&
|
32
|
+
(options[:method].nil? || options[:method] == :get)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Typhoeus
|
2
|
+
module Requests
|
3
|
+
|
4
|
+
# This module contains everything what is necessary
|
5
|
+
# to make a single request.
|
6
|
+
module Operations
|
7
|
+
|
8
|
+
# :nodoc:
|
9
|
+
def self.included(base)
|
10
|
+
base.extend ClassMethods
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods # :nodoc:
|
14
|
+
|
15
|
+
# Shortcut to perform a single request.
|
16
|
+
#
|
17
|
+
# @example Perform request.
|
18
|
+
# Request.run("www.example.com")
|
19
|
+
#
|
20
|
+
# @param [ String ] url The url to request.
|
21
|
+
# @param [ Hash ] options The options hash.
|
22
|
+
#
|
23
|
+
# @return [ Response ] The response.
|
24
|
+
def run(url, options = {})
|
25
|
+
new(url, options).run
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Run a request.
|
30
|
+
#
|
31
|
+
# @example Run a request.
|
32
|
+
# request.run
|
33
|
+
#
|
34
|
+
# @return [ Response ] The response.
|
35
|
+
def run
|
36
|
+
easy = Typhoeus.get_easy
|
37
|
+
easy.http_request(
|
38
|
+
url,
|
39
|
+
options.fetch(:method, :get),
|
40
|
+
options.reject{|k,_| k==:method}
|
41
|
+
)
|
42
|
+
easy.prepare
|
43
|
+
easy.perform
|
44
|
+
@response = Response.new(easy.to_hash)
|
45
|
+
@response.request = self
|
46
|
+
Typhoeus.release_easy(easy)
|
47
|
+
execute_callbacks
|
48
|
+
@response
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Typhoeus
|
2
|
+
module Requests
|
3
|
+
|
4
|
+
# This module contains logic for having a reponse
|
5
|
+
# getter and setter.
|
6
|
+
module Responseable
|
7
|
+
|
8
|
+
# Set the response.
|
9
|
+
#
|
10
|
+
# @example Set response.
|
11
|
+
# request.response = response
|
12
|
+
#
|
13
|
+
# @param [ Response ] value The response to set.
|
14
|
+
def response=(value)
|
15
|
+
@response = value
|
16
|
+
end
|
17
|
+
|
18
|
+
# Return the response.
|
19
|
+
#
|
20
|
+
# @example Return response.
|
21
|
+
# request.response
|
22
|
+
#
|
23
|
+
# @return [ Response ] The response.
|
24
|
+
def response
|
25
|
+
@response ||= nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Typhoeus
|
2
|
+
module Requests
|
3
|
+
|
4
|
+
# This module handles stubbing on the request side.
|
5
|
+
# It plays well with the block_connection configuration,
|
6
|
+
# which raises when you make a request which is not stubbed.
|
7
|
+
module Stubbable
|
8
|
+
|
9
|
+
# Override run in order to check for matching expecations.
|
10
|
+
# When an expecation is found, super is not called. Instead a
|
11
|
+
# canned response is assigned to the request.
|
12
|
+
#
|
13
|
+
# @example Run the request.
|
14
|
+
# request.run
|
15
|
+
#
|
16
|
+
# @return [ Response ] The response.
|
17
|
+
def run
|
18
|
+
if expectation = Expectation.find_by(self)
|
19
|
+
@response = expectation.response
|
20
|
+
@response.mock = true
|
21
|
+
execute_callbacks
|
22
|
+
@response
|
23
|
+
else
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/typhoeus/response.rb
CHANGED
@@ -1,123 +1,29 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
:requested_url, :requested_remote_method,
|
6
|
-
:requested_http_method, :start_time,
|
7
|
-
:effective_url, :start_transfer_time,
|
8
|
-
:app_connect_time, :pretransfer_time,
|
9
|
-
:connect_time, :name_lookup_time,
|
10
|
-
:curl_return_code, :curl_error_message,
|
11
|
-
:primary_ip, :redirect_count
|
12
|
-
|
13
|
-
attr_writer :headers_hash
|
14
|
-
|
15
|
-
def initialize(params = {})
|
16
|
-
@code = params[:code]
|
17
|
-
@curl_return_code = params[:curl_return_code]
|
18
|
-
@curl_error_message = params[:curl_error_message]
|
19
|
-
@status_message = params[:status_message]
|
20
|
-
@http_version = params[:http_version]
|
21
|
-
@headers = params[:headers]
|
22
|
-
@body = params[:body]
|
23
|
-
@time = params[:time]
|
24
|
-
@requested_url = params[:requested_url]
|
25
|
-
@requested_http_method = params[:requested_http_method]
|
26
|
-
@start_time = params[:start_time]
|
27
|
-
@start_transfer_time = params[:start_transfer_time]
|
28
|
-
@app_connect_time = params[:app_connect_time]
|
29
|
-
@pretransfer_time = params[:pretransfer_time]
|
30
|
-
@connect_time = params[:connect_time]
|
31
|
-
@name_lookup_time = params[:name_lookup_time]
|
32
|
-
@request = params[:request]
|
33
|
-
@effective_url = params[:effective_url]
|
34
|
-
@primary_ip = params[:primary_ip]
|
35
|
-
@redirect_count = params[:redirect_count]
|
36
|
-
@mock = params[:mock] || false # default
|
37
|
-
@headers_hash = Header.new(params[:headers_hash]) if params[:headers_hash]
|
38
|
-
end
|
39
|
-
|
40
|
-
# Returns true if this is a mock response.
|
41
|
-
def mock?
|
42
|
-
@mock
|
43
|
-
end
|
44
|
-
|
45
|
-
def headers
|
46
|
-
@headers ||= @headers_hash ? construct_header_string : ''
|
47
|
-
end
|
48
|
-
|
49
|
-
def headers_hash
|
50
|
-
@headers_hash ||= begin
|
51
|
-
headers.split("\n").map {|o| o.strip}.inject(Typhoeus::Header.new) do |hash, o|
|
52
|
-
if o.empty? || o =~ /^HTTP\/[\d\.]+/
|
53
|
-
hash
|
54
|
-
else
|
55
|
-
i = o.index(":") || o.size
|
56
|
-
key = o.slice(0, i)
|
57
|
-
value = o.slice(i + 1, o.size)
|
58
|
-
value = value.strip unless value.nil?
|
59
|
-
if hash.key? key
|
60
|
-
hash[key] = [hash[key], value].flatten
|
61
|
-
else
|
62
|
-
hash[key] = value
|
63
|
-
end
|
64
|
-
|
65
|
-
hash
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def status_message
|
72
|
-
return @status_message if @status_message != nil
|
73
|
-
|
74
|
-
# HTTP servers can choose not to include the explanation to HTTP codes. The RFC
|
75
|
-
# states this (http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4):
|
76
|
-
# Except when responding to a HEAD request, the server SHOULD include an entity containing
|
77
|
-
# an explanation of the error situation [...]
|
78
|
-
# This means 'HTTP/1.1 404' is as valid as 'HTTP/1.1 404 Not Found' and we have to handle it.
|
1
|
+
require 'typhoeus/responses/header'
|
2
|
+
require 'typhoeus/responses/informations'
|
3
|
+
require 'typhoeus/responses/legacy'
|
4
|
+
require 'typhoeus/responses/status'
|
79
5
|
|
80
|
-
|
81
|
-
if first_header_line != nil and first_header_line[/\d{3} (.*)$/, 1] != nil
|
82
|
-
@status_message = first_header_line[/\d{3} (.*)$/, 1].chomp
|
83
|
-
else
|
84
|
-
@status_message = nil
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def http_version
|
89
|
-
@http_version ||= first_header_line ? first_header_line[/HTTP\/(\S+)/, 1] : nil
|
90
|
-
end
|
91
|
-
|
92
|
-
def success?
|
93
|
-
@code >= 200 && @code < 300
|
94
|
-
end
|
95
|
-
|
96
|
-
def modified?
|
97
|
-
@code != 304
|
98
|
-
end
|
6
|
+
module Typhoeus
|
99
7
|
|
100
|
-
|
101
|
-
|
8
|
+
# This class respresents the response.
|
9
|
+
class Response
|
10
|
+
include Responses::Informations
|
11
|
+
include Responses::Legacy
|
12
|
+
include Responses::Status
|
13
|
+
|
14
|
+
attr_accessor :request, :options, :mock
|
15
|
+
|
16
|
+
# Create a new response.
|
17
|
+
#
|
18
|
+
# @example Create a response.
|
19
|
+
# Response.new
|
20
|
+
#
|
21
|
+
# @param [ Hash ] options The options hash.
|
22
|
+
#
|
23
|
+
# @return [ Response ] The new response.
|
24
|
+
def initialize(options = {})
|
25
|
+
@options = options
|
26
|
+
@header = options[:header]
|
102
27
|
end
|
103
|
-
|
104
|
-
private
|
105
|
-
|
106
|
-
def first_header_line
|
107
|
-
@first_header_line ||= @headers.to_s.split("\n").first
|
108
|
-
end
|
109
|
-
|
110
|
-
def construct_header_string
|
111
|
-
lines = ["HTTP/#{http_version} #{code} #{status_message}"]
|
112
|
-
|
113
|
-
@headers_hash.each do |key, values|
|
114
|
-
[values].flatten.each do |value|
|
115
|
-
lines << "#{key}: #{value}"
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
lines << '' << ''
|
120
|
-
lines.join("\r\n")
|
121
|
-
end
|
122
28
|
end
|
123
29
|
end
|