mailosaur 7.5.0 → 7.8.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.
- checksums.yaml +4 -4
- data/lib/Mailosaur/analysis.rb +1 -1
- data/lib/Mailosaur/devices.rb +83 -0
- data/lib/Mailosaur/messages.rb +6 -6
- data/lib/Mailosaur/models/code.rb +12 -0
- data/lib/Mailosaur/models/device.rb +16 -0
- data/lib/Mailosaur/models/device_create_options.rb +16 -0
- data/lib/Mailosaur/models/device_list_result.rb +13 -0
- data/lib/Mailosaur/models/message_content.rb +5 -0
- data/lib/Mailosaur/models/message_summary.rb +0 -5
- data/lib/Mailosaur/models/metadata.rb +23 -0
- data/lib/Mailosaur/models/otp_result.rb +16 -0
- data/lib/Mailosaur/servers.rb +6 -6
- data/lib/Mailosaur/usage.rb +2 -2
- data/lib/Mailosaur/version.rb +1 -1
- data/lib/mailosaur.rb +11 -0
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eeaf9bb2c4d001d8f0ec173e4fb0fcbe02515cbb2ac1eae04c2f9961811dad2c
|
4
|
+
data.tar.gz: cdcf3c7bdd32cde468875fac15003169d65a4283b38de7c8970c1f3870a08d8f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 534cdf190200dcf2b24ef400f598dec13de6466fa347d46c90a967e3c7c45c9ba1ed024da2fd67a9ce0afa3c41e886b6cffe2e3af8c711cb2fe8f2185805efab
|
7
|
+
data.tar.gz: 16ab4b387df53be279ad409e9d04d479c17db62f43db17b5ed60df256ee28c617b2fa6ed4d22e9df64b8a7b3f26e05e5b8ea02a7f1c97927d745932522296684
|
data/lib/Mailosaur/analysis.rb
CHANGED
@@ -24,7 +24,7 @@ module Mailosaur
|
|
24
24
|
def spam(email)
|
25
25
|
response = conn.get "api/analysis/spam/#{email}"
|
26
26
|
@handle_http_error.call(response) unless response.status == 200
|
27
|
-
model = JSON.
|
27
|
+
model = JSON.parse(response.body)
|
28
28
|
Mailosaur::Models::SpamAnalysisResult.new(model)
|
29
29
|
end
|
30
30
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Mailosaur
|
2
|
+
class Devices
|
3
|
+
#
|
4
|
+
# Creates and initializes a new instance of the Devices class.
|
5
|
+
# @param client connection.
|
6
|
+
#
|
7
|
+
def initialize(conn, handle_http_error)
|
8
|
+
@conn = conn
|
9
|
+
@handle_http_error = handle_http_error
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [Connection] the client connection.
|
13
|
+
attr_reader :conn
|
14
|
+
|
15
|
+
#
|
16
|
+
# List all devices
|
17
|
+
#
|
18
|
+
# Returns a list of your virtual security devices.
|
19
|
+
#
|
20
|
+
# @return [DeviceListResult] operation results.
|
21
|
+
#
|
22
|
+
def list
|
23
|
+
response = conn.get 'api/devices'
|
24
|
+
@handle_http_error.call(response) unless response.status == 200
|
25
|
+
model = JSON.parse(response.body)
|
26
|
+
Mailosaur::Models::DeviceListResult.new(model)
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# Create a device
|
31
|
+
#
|
32
|
+
# Creates a new virtual security device and returns it.
|
33
|
+
#
|
34
|
+
# @param device_create_options [DeviceCreateOptions]
|
35
|
+
#
|
36
|
+
# @return [Device] operation results.
|
37
|
+
#
|
38
|
+
def create(device_create_options)
|
39
|
+
response = conn.post 'api/devices', device_create_options.to_json
|
40
|
+
@handle_http_error.call(response) unless response.status == 200
|
41
|
+
model = JSON.parse(response.body)
|
42
|
+
Mailosaur::Models::Device.new(model)
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# Retrieve OTP
|
47
|
+
#
|
48
|
+
# Retrieves the current one-time password for a saved device, or given base32-encoded shared secret.
|
49
|
+
#
|
50
|
+
# @param query [String] Either the unique identifier of the device, or a base32-encoded shared secret.
|
51
|
+
#
|
52
|
+
# @return [OtpResult] operation results.
|
53
|
+
#
|
54
|
+
def otp(query)
|
55
|
+
if query.include? '-'
|
56
|
+
response = conn.get "api/devices/#{query}/otp"
|
57
|
+
@handle_http_error.call(response) unless response.status == 200
|
58
|
+
model = JSON.parse(response.body)
|
59
|
+
return Mailosaur::Models::OtpResult.new(model)
|
60
|
+
end
|
61
|
+
|
62
|
+
options = Mailosaur::Models::DeviceCreateOptions.new
|
63
|
+
options.shared_secret = query
|
64
|
+
response = conn.post 'api/devices/otp', options.to_json
|
65
|
+
@handle_http_error.call(response) unless response.status == 200
|
66
|
+
model = JSON.parse(response.body)
|
67
|
+
Mailosaur::Models::OtpResult.new(model)
|
68
|
+
end
|
69
|
+
|
70
|
+
#
|
71
|
+
# Delete a device
|
72
|
+
#
|
73
|
+
# Permanently deletes a device. This operation cannot be undone.
|
74
|
+
#
|
75
|
+
# @param id [String] The identifier of the device to be deleted.
|
76
|
+
#
|
77
|
+
def delete(id)
|
78
|
+
response = conn.delete "api/devices/#{id}"
|
79
|
+
@handle_http_error.call(response) unless response.status == 204
|
80
|
+
nil
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/Mailosaur/messages.rb
CHANGED
@@ -51,7 +51,7 @@ module Mailosaur
|
|
51
51
|
def get_by_id(id)
|
52
52
|
response = conn.get "api/messages/#{id}"
|
53
53
|
@handle_http_error.call(response) unless response.status == 200
|
54
|
-
model = JSON.
|
54
|
+
model = JSON.parse(response.body)
|
55
55
|
Mailosaur::Models::Message.new(model)
|
56
56
|
end
|
57
57
|
|
@@ -96,7 +96,7 @@ module Mailosaur
|
|
96
96
|
|
97
97
|
@handle_http_error.call(response) unless response.status == 200
|
98
98
|
|
99
|
-
model = JSON.
|
99
|
+
model = JSON.parse(response.body)
|
100
100
|
Mailosaur::Models::MessageListResult.new(model)
|
101
101
|
end
|
102
102
|
|
@@ -151,7 +151,7 @@ module Mailosaur
|
|
151
151
|
|
152
152
|
@handle_http_error.call(response) unless response.status == 200
|
153
153
|
|
154
|
-
model = JSON.
|
154
|
+
model = JSON.parse(response.body)
|
155
155
|
return Mailosaur::Models::MessageListResult.new(model) if timeout.to_i.zero? || !model['items'].empty?
|
156
156
|
|
157
157
|
delay_pattern = (response.headers['x-ms-delay'] || '1000').split(',').map(&:to_i)
|
@@ -186,7 +186,7 @@ module Mailosaur
|
|
186
186
|
def create(server, message_create_options)
|
187
187
|
response = conn.post "api/messages?server=#{server}", message_create_options.to_json
|
188
188
|
@handle_http_error.call(response) unless response.status == 200
|
189
|
-
model = JSON.
|
189
|
+
model = JSON.parse(response.body)
|
190
190
|
Mailosaur::Models::Message.new(model)
|
191
191
|
end
|
192
192
|
|
@@ -204,7 +204,7 @@ module Mailosaur
|
|
204
204
|
def forward(id, message_forward_options)
|
205
205
|
response = conn.post "api/messages/#{id}/forward", message_forward_options.to_json
|
206
206
|
@handle_http_error.call(response) unless response.status == 200
|
207
|
-
model = JSON.
|
207
|
+
model = JSON.parse(response.body)
|
208
208
|
Mailosaur::Models::Message.new(model)
|
209
209
|
end
|
210
210
|
|
@@ -223,7 +223,7 @@ module Mailosaur
|
|
223
223
|
def reply(id, message_reply_options)
|
224
224
|
response = conn.post "api/messages/#{id}/reply", message_reply_options.to_json
|
225
225
|
@handle_http_error.call(response) unless response.status == 200
|
226
|
-
model = JSON.
|
226
|
+
model = JSON.parse(response.body)
|
227
227
|
Mailosaur::Models::Message.new(model)
|
228
228
|
end
|
229
229
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Mailosaur
|
2
|
+
module Models
|
3
|
+
class Device < BaseModel
|
4
|
+
def initialize(data = {})
|
5
|
+
@id = data['id']
|
6
|
+
@name = data['name']
|
7
|
+
end
|
8
|
+
|
9
|
+
# @return [String] Unique identifier for the device.
|
10
|
+
attr_accessor :id
|
11
|
+
|
12
|
+
# @return [String] The name of the device.
|
13
|
+
attr_accessor :name
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Mailosaur
|
2
|
+
module Models
|
3
|
+
class DeviceCreateOptions < BaseModel
|
4
|
+
def initialize(data = {})
|
5
|
+
@name = data['name']
|
6
|
+
@shared_secret = data['shared_secret']
|
7
|
+
end
|
8
|
+
|
9
|
+
# @return [String] A name used to identify the device.
|
10
|
+
attr_accessor :name
|
11
|
+
|
12
|
+
# @return [String] The base32-encoded shared secret for this device.
|
13
|
+
attr_accessor :shared_secret
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Mailosaur
|
2
|
+
module Models
|
3
|
+
class DeviceListResult < BaseModel
|
4
|
+
def initialize(data = {})
|
5
|
+
@items = []
|
6
|
+
(data['items'] || []).each { |i| @items << Mailosaur::Models::Device.new(i) }
|
7
|
+
end
|
8
|
+
|
9
|
+
# @return [Array<Device>] The individual devices forming the result.
|
10
|
+
attr_accessor :items
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -4,6 +4,8 @@ module Mailosaur
|
|
4
4
|
def initialize(data = {})
|
5
5
|
@links = []
|
6
6
|
(data['links'] || []).each do |i| @links << Mailosaur::Models::Link.new(i) end
|
7
|
+
@codes = []
|
8
|
+
(data['codes'] || []).each do |i| @codes << Mailosaur::Models::Code.new(i) end
|
7
9
|
@images = []
|
8
10
|
(data['images'] || []).each do |i| @images << Mailosaur::Models::Image.new(i) end
|
9
11
|
@body = data['body']
|
@@ -12,6 +14,9 @@ module Mailosaur
|
|
12
14
|
# @return [Array<Link>]
|
13
15
|
attr_accessor :links
|
14
16
|
|
17
|
+
# @return [Array<Code>]
|
18
|
+
attr_accessor :codes
|
19
|
+
|
15
20
|
# @return [Array<Image>]
|
16
21
|
attr_accessor :images
|
17
22
|
|
@@ -4,8 +4,6 @@ module Mailosaur
|
|
4
4
|
def initialize(data = {})
|
5
5
|
@id = data['id']
|
6
6
|
@server = data['server']
|
7
|
-
@rcpt = []
|
8
|
-
(data['rcpt'] || []).each do |i| @rcpt << Mailosaur::Models::MessageAddress.new(i) end
|
9
7
|
@from = []
|
10
8
|
(data['from'] || []).each do |i| @from << Mailosaur::Models::MessageAddress.new(i) end
|
11
9
|
@to = []
|
@@ -26,9 +24,6 @@ module Mailosaur
|
|
26
24
|
# @return [String]
|
27
25
|
attr_accessor :server
|
28
26
|
|
29
|
-
# @return [Array<MessageAddress>]
|
30
|
-
attr_accessor :rcpt
|
31
|
-
|
32
27
|
# @return [Array<MessageAddress>]
|
33
28
|
attr_accessor :from
|
34
29
|
|
@@ -4,10 +4,33 @@ module Mailosaur
|
|
4
4
|
def initialize(data = {})
|
5
5
|
@headers = []
|
6
6
|
(data['headers'] || []).each do |i| @headers << Mailosaur::Models::MessageHeader.new(i) end
|
7
|
+
|
8
|
+
@ehlo = data['ehlo']
|
9
|
+
|
10
|
+
@mail_from = data['mailFrom']
|
11
|
+
|
12
|
+
@rcpt_to = []
|
13
|
+
(data['rcptTo'] || []).each do |i| @rcpt_to << Mailosaur::Models::MessageAddress.new(i) end
|
7
14
|
end
|
8
15
|
|
9
16
|
# @return [Array<MessageHeader>] Email headers.
|
10
17
|
attr_accessor :headers
|
18
|
+
|
19
|
+
# @return [String] The fully-qualified domain name or IP address that was provided with the
|
20
|
+
# Extended HELLO (EHLO) or HELLO (HELO) command. This value is generally
|
21
|
+
# used to identify the SMTP client.
|
22
|
+
# https://datatracker.ietf.org/doc/html/rfc5321#section-4.1.1.1
|
23
|
+
attr_accessor :ehlo
|
24
|
+
|
25
|
+
# @return [String] The source mailbox/email address, referred to as the 'reverse-path',
|
26
|
+
# provided via the MAIL command during the SMTP transaction.
|
27
|
+
# https://datatracker.ietf.org/doc/html/rfc5321#section-4.1.1.2
|
28
|
+
attr_accessor :mail_from
|
29
|
+
|
30
|
+
# @return [Array<MessageAddress>] The recipient email addresses, each referred to as a 'forward-path',
|
31
|
+
# provided via the RCPT command during the SMTP transaction.
|
32
|
+
# https://datatracker.ietf.org/doc/html/rfc5321#section-4.1.1.3
|
33
|
+
attr_accessor :rcpt_to
|
11
34
|
end
|
12
35
|
end
|
13
36
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Mailosaur
|
2
|
+
module Models
|
3
|
+
class OtpResult < BaseModel
|
4
|
+
def initialize(data = {})
|
5
|
+
@code = data['code']
|
6
|
+
@expires = data['expires']
|
7
|
+
end
|
8
|
+
|
9
|
+
# @return [String] The current one-time password.
|
10
|
+
attr_accessor :code
|
11
|
+
|
12
|
+
# @return [String] The expiry date/time of the current one-time password.
|
13
|
+
attr_accessor :expires
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/Mailosaur/servers.rb
CHANGED
@@ -23,7 +23,7 @@ module Mailosaur
|
|
23
23
|
def list
|
24
24
|
response = conn.get 'api/servers'
|
25
25
|
@handle_http_error.call(response) unless response.status == 200
|
26
|
-
model = JSON.
|
26
|
+
model = JSON.parse(response.body)
|
27
27
|
Mailosaur::Models::ServerListResult.new(model)
|
28
28
|
end
|
29
29
|
|
@@ -39,7 +39,7 @@ module Mailosaur
|
|
39
39
|
def create(server_create_options)
|
40
40
|
response = conn.post 'api/servers', server_create_options.to_json
|
41
41
|
@handle_http_error.call(response) unless response.status == 200
|
42
|
-
model = JSON.
|
42
|
+
model = JSON.parse(response.body)
|
43
43
|
Mailosaur::Models::Server.new(model)
|
44
44
|
end
|
45
45
|
|
@@ -56,7 +56,7 @@ module Mailosaur
|
|
56
56
|
def get(id)
|
57
57
|
response = conn.get "api/servers/#{id}"
|
58
58
|
@handle_http_error.call(response) unless response.status == 200
|
59
|
-
model = JSON.
|
59
|
+
model = JSON.parse(response.body)
|
60
60
|
Mailosaur::Models::Server.new(model)
|
61
61
|
end
|
62
62
|
|
@@ -73,7 +73,7 @@ module Mailosaur
|
|
73
73
|
def get_password(id)
|
74
74
|
response = conn.get "api/servers/#{id}/password"
|
75
75
|
@handle_http_error.call(response) unless response.status == 200
|
76
|
-
model = JSON.
|
76
|
+
model = JSON.parse(response.body)
|
77
77
|
model['value']
|
78
78
|
end
|
79
79
|
|
@@ -90,7 +90,7 @@ module Mailosaur
|
|
90
90
|
def update(id, server)
|
91
91
|
response = conn.put "api/servers/#{id}", server.to_json
|
92
92
|
@handle_http_error.call(response) unless response.status == 200
|
93
|
-
model = JSON.
|
93
|
+
model = JSON.parse(response.body)
|
94
94
|
Mailosaur::Models::Server.new(model)
|
95
95
|
end
|
96
96
|
|
@@ -110,7 +110,7 @@ module Mailosaur
|
|
110
110
|
|
111
111
|
def generate_email_address(server)
|
112
112
|
host = ENV['MAILOSAUR_SMTP_HOST'] || 'mailosaur.net'
|
113
|
-
'%s@%s.%s'
|
113
|
+
format('%s@%s.%s', SecureRandom.hex(3), server, host)
|
114
114
|
end
|
115
115
|
end
|
116
116
|
end
|
data/lib/Mailosaur/usage.rb
CHANGED
@@ -22,7 +22,7 @@ module Mailosaur
|
|
22
22
|
def limits
|
23
23
|
response = conn.get 'api/usage/limits'
|
24
24
|
@handle_http_error.call(response) unless response.status == 200
|
25
|
-
model = JSON.
|
25
|
+
model = JSON.parse(response.body)
|
26
26
|
Mailosaur::Models::UsageAccountLimits.new(model)
|
27
27
|
end
|
28
28
|
|
@@ -34,7 +34,7 @@ module Mailosaur
|
|
34
34
|
def transactions
|
35
35
|
response = conn.get 'api/usage/transactions'
|
36
36
|
@handle_http_error.call(response) unless response.status == 200
|
37
|
-
model = JSON.
|
37
|
+
model = JSON.parse(response.body)
|
38
38
|
Mailosaur::Models::UsageTransactionListResult.new(model)
|
39
39
|
end
|
40
40
|
end
|
data/lib/Mailosaur/version.rb
CHANGED
data/lib/mailosaur.rb
CHANGED
@@ -14,6 +14,7 @@ module Mailosaur
|
|
14
14
|
autoload :Messages, 'Mailosaur/messages.rb'
|
15
15
|
autoload :Servers, 'Mailosaur/servers.rb'
|
16
16
|
autoload :Usage, 'Mailosaur/usage.rb'
|
17
|
+
autoload :Devices, 'Mailosaur/devices.rb'
|
17
18
|
autoload :MailosaurError, 'Mailosaur/mailosaur_error.rb'
|
18
19
|
|
19
20
|
module Models
|
@@ -34,6 +35,7 @@ module Mailosaur
|
|
34
35
|
autoload :MessageContent, 'Mailosaur/models/message_content.rb'
|
35
36
|
autoload :Server, 'Mailosaur/models/server.rb'
|
36
37
|
autoload :Link, 'Mailosaur/models/link.rb'
|
38
|
+
autoload :Code, 'Mailosaur/models/code.rb'
|
37
39
|
autoload :ServerListResult, 'Mailosaur/models/server_list_result.rb'
|
38
40
|
autoload :SpamFilterResults, 'Mailosaur/models/spam_filter_results.rb'
|
39
41
|
autoload :ServerCreateOptions, 'Mailosaur/models/server_create_options.rb'
|
@@ -41,6 +43,10 @@ module Mailosaur
|
|
41
43
|
autoload :UsageAccountLimit, 'Mailosaur/models/usage_account_limit.rb'
|
42
44
|
autoload :UsageTransactionListResult, 'Mailosaur/models/usage_transaction_list_result.rb'
|
43
45
|
autoload :UsageTransaction, 'Mailosaur/models/usage_transaction.rb'
|
46
|
+
autoload :Device, 'Mailosaur/models/device.rb'
|
47
|
+
autoload :DeviceListResult, 'Mailosaur/models/device_list_result.rb'
|
48
|
+
autoload :DeviceCreateOptions, 'Mailosaur/models/device_create_options.rb'
|
49
|
+
autoload :OtpResult, 'Mailosaur/models/otp_result.rb'
|
44
50
|
autoload :BaseModel, 'Mailosaur/models/base_model.rb'
|
45
51
|
end
|
46
52
|
|
@@ -80,6 +86,11 @@ module Mailosaur
|
|
80
86
|
@usage ||= Usage.new(connection, method(:handle_http_error))
|
81
87
|
end
|
82
88
|
|
89
|
+
# @return [Devices] usage
|
90
|
+
def devices
|
91
|
+
@devices ||= Devices.new(connection, method(:handle_http_error))
|
92
|
+
end
|
93
|
+
|
83
94
|
private
|
84
95
|
|
85
96
|
def connection
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mailosaur
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 7.
|
4
|
+
version: 7.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mailosaur
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-05-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -148,11 +148,16 @@ files:
|
|
148
148
|
- LICENSE
|
149
149
|
- README.md
|
150
150
|
- lib/Mailosaur/analysis.rb
|
151
|
+
- lib/Mailosaur/devices.rb
|
151
152
|
- lib/Mailosaur/files.rb
|
152
153
|
- lib/Mailosaur/mailosaur_error.rb
|
153
154
|
- lib/Mailosaur/messages.rb
|
154
155
|
- lib/Mailosaur/models/attachment.rb
|
155
156
|
- lib/Mailosaur/models/base_model.rb
|
157
|
+
- lib/Mailosaur/models/code.rb
|
158
|
+
- lib/Mailosaur/models/device.rb
|
159
|
+
- lib/Mailosaur/models/device_create_options.rb
|
160
|
+
- lib/Mailosaur/models/device_list_result.rb
|
156
161
|
- lib/Mailosaur/models/image.rb
|
157
162
|
- lib/Mailosaur/models/link.rb
|
158
163
|
- lib/Mailosaur/models/message.rb
|
@@ -165,6 +170,7 @@ files:
|
|
165
170
|
- lib/Mailosaur/models/message_reply_options.rb
|
166
171
|
- lib/Mailosaur/models/message_summary.rb
|
167
172
|
- lib/Mailosaur/models/metadata.rb
|
173
|
+
- lib/Mailosaur/models/otp_result.rb
|
168
174
|
- lib/Mailosaur/models/search_criteria.rb
|
169
175
|
- lib/Mailosaur/models/server.rb
|
170
176
|
- lib/Mailosaur/models/server_create_options.rb
|
@@ -205,7 +211,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
205
211
|
- !ruby/object:Gem::Version
|
206
212
|
version: '0'
|
207
213
|
requirements: []
|
208
|
-
rubygems_version: 3.
|
214
|
+
rubygems_version: 3.3.7
|
209
215
|
signing_key:
|
210
216
|
specification_version: 4
|
211
217
|
summary: The Mailosaur Ruby library
|