quicknode_sdk 0.1.0-aarch64-linux → 0.1.0.pre.alpha.14-aarch64-linux
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/README.md +1537 -0
- data/lib/quicknode_sdk/clients/admin.rb +4 -0
- data/lib/quicknode_sdk/clients/kvstore.rb +4 -0
- data/lib/quicknode_sdk/clients/streams.rb +4 -0
- data/lib/quicknode_sdk/clients/webhooks.rb +4 -0
- data/lib/quicknode_sdk/native_delegator.rb +43 -0
- data/lib/{quicknode_sdk.so → quicknode_sdk/quicknode_sdk.so} +0 -0
- data/lib/quicknode_sdk/sdk.rb +27 -0
- data/lib/quicknode_sdk/wrap.rb +16 -0
- data/lib/quicknode_sdk.rb +16 -1
- data/sig/quicknode_sdk.rbs +162 -0
- metadata +29 -6
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module QuicknodeSdk
|
|
2
|
+
# Shared base class for the user-facing client wrappers (Admin, Streams,
|
|
3
|
+
# Webhooks, KvStore). Each Magnus-bound native client is held in @native and
|
|
4
|
+
# all method calls are forwarded through method_missing.
|
|
5
|
+
#
|
|
6
|
+
# The native side exposes two kinds of methods: arity-0 (e.g. list_chains)
|
|
7
|
+
# and arity-1 taking a single positional Hash of options (e.g.
|
|
8
|
+
# get_endpoints). To support all three Ruby call styles documented in the
|
|
9
|
+
# README and examples — bare (qn.admin.get_endpoints), kwargs
|
|
10
|
+
# (qn.admin.get_endpoints(limit: 5)), and positional hash
|
|
11
|
+
# (qn.streams.list_streams({})) — we coerce whatever the caller passed into
|
|
12
|
+
# a single options hash, then dispatch on the native arity. Arity-0 methods
|
|
13
|
+
# reject any argument (Magnus enforces this), so we must call them with no
|
|
14
|
+
# args; arity-1 methods always need a Hash passed POSITIONALLY (Magnus's
|
|
15
|
+
# RHash is a positional arg, and Ruby 3 treats `**{}` as zero arguments —
|
|
16
|
+
# so we must not splat the options).
|
|
17
|
+
class NativeDelegator
|
|
18
|
+
def initialize(native)
|
|
19
|
+
@native = native
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def method_missing(name, *args, **kwargs)
|
|
23
|
+
return super unless @native.respond_to?(name)
|
|
24
|
+
opts = if !kwargs.empty?
|
|
25
|
+
kwargs
|
|
26
|
+
elsif args.length == 1 && args[0].is_a?(Hash)
|
|
27
|
+
args[0]
|
|
28
|
+
else
|
|
29
|
+
{}
|
|
30
|
+
end
|
|
31
|
+
result = if @native.method(name).arity == 0
|
|
32
|
+
@native.public_send(name)
|
|
33
|
+
else
|
|
34
|
+
@native.public_send(name, opts)
|
|
35
|
+
end
|
|
36
|
+
QuicknodeSdk.wrap(result)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def respond_to_missing?(name, include_private = false)
|
|
40
|
+
@native.respond_to?(name, include_private) || super
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
Binary file
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module QuicknodeSdk
|
|
2
|
+
class SDK
|
|
3
|
+
def self.from_env
|
|
4
|
+
new(Native::SDK.from_env)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def initialize(native)
|
|
8
|
+
@native = native
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def admin
|
|
12
|
+
Admin.new(@native.admin)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def streams
|
|
16
|
+
Streams.new(@native.streams)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def webhooks
|
|
20
|
+
Webhooks.new(@native.webhooks)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def kvstore
|
|
24
|
+
KvStore.new(@native.kvstore)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require "hashie"
|
|
2
|
+
|
|
3
|
+
module QuicknodeSdk
|
|
4
|
+
class IndifferentHash < Hash
|
|
5
|
+
include Hashie::Extensions::MergeInitializer
|
|
6
|
+
include Hashie::Extensions::IndifferentAccess
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.wrap(v)
|
|
10
|
+
case v
|
|
11
|
+
when Hash then IndifferentHash.new(v).tap { |h| h.each { |k, val| h[k] = wrap(val) } }
|
|
12
|
+
when Array then v.map { |x| wrap(x) }
|
|
13
|
+
else v
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/quicknode_sdk.rb
CHANGED
|
@@ -1 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
begin
|
|
2
|
+
require_relative "quicknode_sdk/quicknode_sdk"
|
|
3
|
+
rescue LoadError => e
|
|
4
|
+
raise LoadError, <<~MSG
|
|
5
|
+
Could not load the quicknode_sdk native extension for this platform (#{RUBY_PLATFORM}).
|
|
6
|
+
Precompiled binaries are published for: x86_64-linux, aarch64-linux, arm64-darwin.
|
|
7
|
+
Original error: #{e.message}
|
|
8
|
+
MSG
|
|
9
|
+
end
|
|
10
|
+
require_relative "quicknode_sdk/wrap"
|
|
11
|
+
require_relative "quicknode_sdk/native_delegator"
|
|
12
|
+
require_relative "quicknode_sdk/clients/admin"
|
|
13
|
+
require_relative "quicknode_sdk/clients/streams"
|
|
14
|
+
require_relative "quicknode_sdk/clients/webhooks"
|
|
15
|
+
require_relative "quicknode_sdk/clients/kvstore"
|
|
16
|
+
require_relative "quicknode_sdk/sdk"
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
module QuicknodeSdk
|
|
2
|
+
def self.wrap: (untyped v) -> untyped
|
|
3
|
+
|
|
4
|
+
class Error < StandardError
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
class ConfigError < Error
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class HttpError < Error
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class TimeoutError < HttpError
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class ConnectionError < HttpError
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class ApiError < Error
|
|
20
|
+
attr_reader status: Integer
|
|
21
|
+
attr_reader body: String
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class DecodeError < Error
|
|
25
|
+
attr_reader body: String
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class SDK
|
|
29
|
+
def self.from_env: () -> SDK
|
|
30
|
+
def initialize: (untyped native) -> void
|
|
31
|
+
def admin: () -> Admin
|
|
32
|
+
def streams: () -> Streams
|
|
33
|
+
def webhooks: () -> Webhooks
|
|
34
|
+
def kvstore: () -> KvStore
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class DestinationAttributes
|
|
38
|
+
def self.webhook: (url: String, ?max_retry: Integer, ?retry_interval_sec: Integer, ?post_timeout_sec: Integer, ?security_token: String, compression: String) -> DestinationAttributes
|
|
39
|
+
def self.s3: (endpoint: String, access_key: String, secret_key: String, bucket: String, object_prefix: String, compression: String, file_type: String, ?max_retry: Integer, ?retry_interval_sec: Integer, ?use_ssl: bool) -> DestinationAttributes
|
|
40
|
+
def self.azure: (storage_account: String, sas_token: String, container: String, compression: String, file_type: String, ?max_retry: Integer, ?retry_interval_sec: Integer, ?blob_prefix: String) -> DestinationAttributes
|
|
41
|
+
def self.postgres: (host: String, ?port: Integer, database: String, username: String, password: String, table_name: String, sslmode: String, ?max_retry: Integer, ?retry_interval_sec: Integer) -> DestinationAttributes
|
|
42
|
+
def self.mysql: (host: String, ?port: Integer, database: String, username: String, password: String, table_name: String, ?max_retry: Integer, ?retry_interval_sec: Integer) -> DestinationAttributes
|
|
43
|
+
def self.mongo: (host: String, database: String, username: String, password: String, collection_name: String, ?max_retry: Integer, ?retry_interval_sec: Integer) -> DestinationAttributes
|
|
44
|
+
def self.clickhouse: (hosts: String, database: String, username: String, password: String, table_name: String, default_table_engine_opts: String, ?default_granularity: Integer, default_compression: String, default_index_type: String, ?max_retry: Integer, ?retry_interval_sec: Integer, ?disable_datetime_precision: bool, ?dont_support_rename_column: bool, ?dont_support_empty_default_value: bool, ?skip_initialize_with_version: bool) -> DestinationAttributes
|
|
45
|
+
def self.snowflake: (account: String, host: String, ?port: Integer, protocol: String, database: String, schema: String, warehouse: String, username: String, password: String, ?max_retry: Integer, ?retry_interval_sec: Integer, ?table_name: String) -> DestinationAttributes
|
|
46
|
+
def self.kafka: (bootstrap_servers: String, topic_name: String, compression_type: String, ?batch_size: Integer, ?linger_ms: Integer, ?max_request_size: Integer, ?timeout_sec: Integer, ?max_retry: Integer, ?retry_interval_sec: Integer, ?username: String, ?password: String, ?protocol: String, ?mechanisms: String) -> DestinationAttributes
|
|
47
|
+
def self.redis: (host: String, ?port: Integer, ?database: Integer, username: String, password: String, key_name: String, ?max_retry: Integer, ?retry_interval_sec: Integer, ?tls: bool) -> DestinationAttributes
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class Admin
|
|
51
|
+
def initialize: (untyped native) -> void
|
|
52
|
+
|
|
53
|
+
def get_endpoints: (?limit: Integer, ?offset: Integer, ?search: String, ?sort_by: String, ?sort_direction: String, ?networks: Array[String], ?statuses: Array[String], ?labels: Array[String], ?dedicated: bool, ?is_flat_rate: bool, ?tag_ids: Array[Integer], ?tag_labels: Array[String]) -> untyped
|
|
54
|
+
def create_endpoint: (?chain: String, ?network: String) -> untyped
|
|
55
|
+
def show_endpoint: (id: String) -> untyped
|
|
56
|
+
def update_endpoint: (id: String, ?label: String) -> void
|
|
57
|
+
def archive_endpoint: (id: String) -> void
|
|
58
|
+
def update_endpoint_status: (id: String, status: String) -> untyped
|
|
59
|
+
def create_tag: (id: String, ?label: String) -> void
|
|
60
|
+
def delete_tag: (id: String, tag_id: String) -> void
|
|
61
|
+
def get_usage: (?start_time: Integer, ?end_time: Integer) -> untyped
|
|
62
|
+
def get_usage_by_endpoint: (?start_time: Integer, ?end_time: Integer) -> untyped
|
|
63
|
+
def get_usage_by_method: (?start_time: Integer, ?end_time: Integer) -> untyped
|
|
64
|
+
def get_usage_by_chain: (?start_time: Integer, ?end_time: Integer) -> untyped
|
|
65
|
+
def get_endpoint_logs: (id: String, from_time: String, to_time: String, ?include_details: bool, ?limit: Integer, ?next_at: String) -> untyped
|
|
66
|
+
def get_log_details: (id: String, request_id: String) -> untyped
|
|
67
|
+
def get_security_options: (id: String) -> untyped
|
|
68
|
+
def update_security_options: (id: String, ?tokens: String, ?referrers: String, ?jwts: String, ?ips: String, ?domain_masks: String, ?hsts: String, ?cors: String, ?request_filters: String, ?ip_custom_header: String) -> untyped
|
|
69
|
+
def create_token: (id: String) -> void
|
|
70
|
+
def delete_token: (id: String, token_id: String) -> untyped
|
|
71
|
+
def create_referrer: (id: String, ?referrer: String) -> void
|
|
72
|
+
def delete_referrer: (id: String, referrer_id: String) -> untyped
|
|
73
|
+
def create_ip: (id: String, ?ip: String) -> void
|
|
74
|
+
def delete_ip: (id: String, ip_id: String) -> untyped
|
|
75
|
+
def create_domain_mask: (id: String, ?domain_mask: String) -> void
|
|
76
|
+
def delete_domain_mask: (id: String, domain_mask_id: String) -> untyped
|
|
77
|
+
def create_jwt: (id: String, ?public_key: String, ?kid: String, ?name: String) -> void
|
|
78
|
+
def delete_jwt: (id: String, jwt_id: String) -> void
|
|
79
|
+
def create_request_filter: (id: String, ?methods: Array[String]) -> untyped
|
|
80
|
+
def update_request_filter: (id: String, request_filter_id: String, ?methods: Array[String]) -> void
|
|
81
|
+
def delete_request_filter: (id: String, request_filter_id: String) -> void
|
|
82
|
+
def enable_multichain: (id: String) -> void
|
|
83
|
+
def disable_multichain: (id: String) -> void
|
|
84
|
+
def create_or_update_ip_custom_header: (id: String, header_name: String) -> untyped
|
|
85
|
+
def delete_ip_custom_header: (id: String) -> untyped
|
|
86
|
+
def get_method_rate_limits: (id: String) -> untyped
|
|
87
|
+
def create_method_rate_limit: (id: String, interval: String, methods: Array[String], rate: Integer) -> untyped
|
|
88
|
+
def update_method_rate_limit: (id: String, method_rate_limit_id: String, ?methods: Array[String], ?status: String, ?rate: Integer) -> untyped
|
|
89
|
+
def delete_method_rate_limit: (id: String, method_rate_limit_id: String) -> void
|
|
90
|
+
def update_rate_limits: (id: String, ?rps: Integer, ?rpm: Integer, ?rpd: Integer) -> void
|
|
91
|
+
def get_endpoint_metrics: (id: String, period: String, metric: String) -> untyped
|
|
92
|
+
def get_account_metrics: (period: String, metric: String, ?percentile: String) -> untyped
|
|
93
|
+
def list_chains: () -> untyped
|
|
94
|
+
def list_invoices: () -> untyped
|
|
95
|
+
def list_payments: () -> untyped
|
|
96
|
+
def list_teams: () -> untyped
|
|
97
|
+
def create_team: (name: String) -> untyped
|
|
98
|
+
def get_team: (id: Integer) -> untyped
|
|
99
|
+
def delete_team: (id: Integer) -> untyped
|
|
100
|
+
def list_team_endpoints: (id: Integer) -> untyped
|
|
101
|
+
def update_team_endpoints: (id: Integer, endpoint_ids: Array[String]) -> untyped
|
|
102
|
+
def invite_team_member: (id: Integer, email: String, ?full_name: String, ?role: String) -> untyped
|
|
103
|
+
def remove_team_member: (id: Integer, user_id: Integer, ?destroy_user: bool) -> untyped
|
|
104
|
+
def resend_team_invite: (id: Integer, user_id: Integer) -> untyped
|
|
105
|
+
def bulk_update_endpoint_status: (ids: Array[String], status: String) -> untyped
|
|
106
|
+
def bulk_add_tag: (ids: Array[String], label: String) -> untyped
|
|
107
|
+
def bulk_remove_tag: (ids: Array[String], tag_id: Integer) -> untyped
|
|
108
|
+
def list_tags: () -> untyped
|
|
109
|
+
def rename_tag: (id: Integer, label: String) -> untyped
|
|
110
|
+
def delete_account_tag: (id: Integer) -> untyped
|
|
111
|
+
def get_usage_by_tag: (?start_time: Integer, ?end_time: Integer) -> untyped
|
|
112
|
+
def get_endpoint_security: (id: String) -> untyped
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
class Streams
|
|
116
|
+
def initialize: (untyped native) -> void
|
|
117
|
+
|
|
118
|
+
def create_stream: (name: String, network: String, dataset: String, region: String, start_range: Integer, end_range: Integer, destination_attributes: DestinationAttributes, plan: String, threshold_fetch_buffer: Integer, ?dataset_batch_size: Integer, ?max_batch_size: Integer, ?max_buffer_range_size: Integer, ?max_buffer_processing_workers: Integer, ?keep_distance_from_tip: Integer, ?filter_function: String, ?filter_language: String, ?include_stream_metadata: String, ?product_type: String, ?status: String, ?notification_email: String, ?charge_min_cap: Integer, ?fix_block_reorgs: Integer, ?elastic_batch_enabled: bool, ?extra_destinations: Array[DestinationAttributes]) -> untyped
|
|
119
|
+
def list_streams: (?stream_type: String, ?offset: Integer, ?limit: Integer, ?order_by: String, ?order_direction: String) -> untyped
|
|
120
|
+
def delete_all_streams: () -> void
|
|
121
|
+
def get_stream: (id: String) -> untyped
|
|
122
|
+
def update_stream: (id: String, ?name: String, ?network: String, ?dataset: String, ?region: String, ?start_range: Integer, ?end_range: Integer, ?destination_attributes: DestinationAttributes, ?plan: String, ?threshold_fetch_buffer: Integer, ?dataset_batch_size: Integer, ?max_batch_size: Integer, ?max_buffer_range_size: Integer, ?max_buffer_processing_workers: Integer, ?keep_distance_from_tip: Integer, ?filter_function: String, ?filter_language: String, ?include_stream_metadata: String, ?notification_email: String, ?charge_min_cap: Integer, ?fix_block_reorgs: Integer, ?elastic_batch_enabled: bool, ?status: String, ?memo: String, ?extra_destinations: Array[DestinationAttributes]) -> untyped
|
|
123
|
+
def delete_stream: (id: String) -> void
|
|
124
|
+
def activate_stream: (id: String) -> void
|
|
125
|
+
def pause_stream: (id: String) -> void
|
|
126
|
+
def test_filter: (network: String, dataset: String, block: String, ?filter_function: String, ?filter_language: String) -> untyped
|
|
127
|
+
def get_enabled_count: (?stream_type: String) -> untyped
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
class Webhooks
|
|
131
|
+
def initialize: (untyped native) -> void
|
|
132
|
+
|
|
133
|
+
def list_webhooks: (?limit: Integer, ?offset: Integer) -> untyped
|
|
134
|
+
def delete_all_webhooks: () -> void
|
|
135
|
+
def get_webhook: (id: String) -> untyped
|
|
136
|
+
def update_webhook: (id: String, ?name: String, ?notification_email: String, ?destination_attributes_json: String) -> untyped
|
|
137
|
+
def delete_webhook: (id: String) -> void
|
|
138
|
+
def pause_webhook: (id: String) -> void
|
|
139
|
+
def activate_webhook: (id: String, start_from: String) -> void
|
|
140
|
+
def get_enabled_count: () -> untyped
|
|
141
|
+
def create_webhook_from_template: (name: String, network: String, destination_attributes_json: String, template_args_json: String, ?notification_email: String) -> untyped
|
|
142
|
+
def update_webhook_template: (webhook_id: String, template_args_json: String, ?name: String, ?notification_email: String) -> untyped
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
class KvStore
|
|
146
|
+
def initialize: (untyped native) -> void
|
|
147
|
+
|
|
148
|
+
def create_set: (key: String, value: String) -> void
|
|
149
|
+
def get_sets: (?limit: Integer, ?cursor: String) -> untyped
|
|
150
|
+
def get_set: (key: String) -> untyped
|
|
151
|
+
def bulk_sets: (?add_sets: Hash[String, String], ?delete_sets: Array[String]) -> void
|
|
152
|
+
def delete_set: (key: String) -> void
|
|
153
|
+
def create_list: (key: String, items: Array[String]) -> void
|
|
154
|
+
def get_lists: (?limit: Integer, ?cursor: String) -> untyped
|
|
155
|
+
def get_list: (key: String, ?limit: Integer, ?cursor: String) -> untyped
|
|
156
|
+
def update_list: (key: String, ?add_items: Array[String], ?remove_items: Array[String]) -> void
|
|
157
|
+
def add_list_item: (key: String, item: String) -> void
|
|
158
|
+
def list_contains_item: (key: String, item: String) -> untyped
|
|
159
|
+
def delete_list_item: (key: String, item: String) -> void
|
|
160
|
+
def delete_list: (key: String) -> void
|
|
161
|
+
end
|
|
162
|
+
end
|
metadata
CHANGED
|
@@ -1,23 +1,46 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: quicknode_sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.0
|
|
4
|
+
version: 0.1.0.pre.alpha.14
|
|
5
5
|
platform: aarch64-linux
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
7
|
+
- Quicknode
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-04-
|
|
12
|
-
dependencies:
|
|
11
|
+
date: 2026-04-27 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: hashie
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '5.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '5.0'
|
|
13
27
|
description:
|
|
14
28
|
email:
|
|
15
29
|
executables: []
|
|
16
30
|
extensions: []
|
|
17
31
|
extra_rdoc_files: []
|
|
18
32
|
files:
|
|
33
|
+
- README.md
|
|
19
34
|
- lib/quicknode_sdk.rb
|
|
20
|
-
- lib/quicknode_sdk.
|
|
35
|
+
- lib/quicknode_sdk/clients/admin.rb
|
|
36
|
+
- lib/quicknode_sdk/clients/kvstore.rb
|
|
37
|
+
- lib/quicknode_sdk/clients/streams.rb
|
|
38
|
+
- lib/quicknode_sdk/clients/webhooks.rb
|
|
39
|
+
- lib/quicknode_sdk/native_delegator.rb
|
|
40
|
+
- lib/quicknode_sdk/quicknode_sdk.so
|
|
41
|
+
- lib/quicknode_sdk/sdk.rb
|
|
42
|
+
- lib/quicknode_sdk/wrap.rb
|
|
43
|
+
- sig/quicknode_sdk.rbs
|
|
21
44
|
homepage:
|
|
22
45
|
licenses:
|
|
23
46
|
- MIT
|
|
@@ -40,5 +63,5 @@ requirements: []
|
|
|
40
63
|
rubygems_version: 3.5.22
|
|
41
64
|
signing_key:
|
|
42
65
|
specification_version: 4
|
|
43
|
-
summary:
|
|
66
|
+
summary: Quicknode SDK for Ruby
|
|
44
67
|
test_files: []
|