nxt_registry 0.3.0 → 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 84faa74573904a66ff0f574195ef5ab7e74508d097f04d1e7f43afcfe4867432
4
- data.tar.gz: 8f5c04f6eef2f3b7328567d2b1e49fe9e862d915b572e18fdefd32cb71c24fc2
3
+ metadata.gz: e3c1e56b8913dea9833ea5fac4b1b837389de5df7aa269f3b2f04977f2ddceb3
4
+ data.tar.gz: 782bcb6ecce2753f77b488c29806bf1b2127714fe82aeed8ab2564297a43b95b
5
5
  SHA512:
6
- metadata.gz: 293d5d1d418152c704ea04aaff4dd392b901c40a9460f1858e5cf6581a0c6fe58041bbe163b8aff3fbcdd9848638f7715e87046b14666f5111677c5241d67eea
7
- data.tar.gz: 0475b0d48a589d90ff548d2c7f414bab38e5360fb46cafee35a29e83203a72fcd8fbce4870fbd7862b182ac38558f48567417808c5d400f4a728ff7dbf353a53
6
+ metadata.gz: 1e512d832f3d40ae60ffcfad8578589138c1f00479c2d2dab797c7e9faa6a37cec77da419d671715af55629e0efeac09ef5761bf39afcafe077c0d6f6af740a0
7
+ data.tar.gz: 3f17bfcc73233107e2daa4db54905115aea50e6f926b8e7ed10aea6d098ff418a4f39ced0e688b02a0a207e0562f02274bdfd9f59d568734485454afe7e1f06d
@@ -1,3 +1,24 @@
1
+ # v0.3.5 2020-12-04
2
+
3
+ - Allow patterns as keys
4
+
5
+ # v0.3.4 2020-12-04
6
+
7
+ - Bring back Singleton
8
+ - Fix mistakes in readme
9
+
10
+ # v0.3.3 2020-11-24
11
+
12
+ - Fix: Pass key to resolver instead of value
13
+
14
+ # v0.3.2 2020-09-29
15
+
16
+ - Fix interface definition
17
+
18
+ # v0.3.1 2020-09-23
19
+
20
+ - Allow to define custom accessors for registries
21
+
1
22
  # v0.3.0 2020-09-10
2
23
 
3
24
  ### Breaking Changes
@@ -1,13 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- nxt_registry (0.3.0)
4
+ nxt_registry (0.3.5)
5
5
  activesupport
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- activesupport (6.0.3.3)
10
+ activesupport (6.0.3.4)
11
11
  concurrent-ruby (~> 1.0, >= 1.0.2)
12
12
  i18n (>= 0.7, < 2)
13
13
  minitest (~> 5.1)
@@ -24,25 +24,25 @@ GEM
24
24
  coderay (~> 1.1)
25
25
  method_source (~> 1.0)
26
26
  rake (12.3.3)
27
- rspec (3.9.0)
28
- rspec-core (~> 3.9.0)
29
- rspec-expectations (~> 3.9.0)
30
- rspec-mocks (~> 3.9.0)
31
- rspec-core (3.9.2)
32
- rspec-support (~> 3.9.3)
33
- rspec-expectations (3.9.2)
27
+ rspec (3.10.0)
28
+ rspec-core (~> 3.10.0)
29
+ rspec-expectations (~> 3.10.0)
30
+ rspec-mocks (~> 3.10.0)
31
+ rspec-core (3.10.0)
32
+ rspec-support (~> 3.10.0)
33
+ rspec-expectations (3.10.0)
34
34
  diff-lcs (>= 1.2.0, < 2.0)
35
- rspec-support (~> 3.9.0)
36
- rspec-mocks (3.9.1)
35
+ rspec-support (~> 3.10.0)
36
+ rspec-mocks (3.10.0)
37
37
  diff-lcs (>= 1.2.0, < 2.0)
38
- rspec-support (~> 3.9.0)
39
- rspec-support (3.9.3)
38
+ rspec-support (~> 3.10.0)
39
+ rspec-support (3.10.0)
40
40
  rspec_junit_formatter (0.4.1)
41
41
  rspec-core (>= 2, < 4, != 2.12.0)
42
42
  thread_safe (0.3.6)
43
- tzinfo (1.2.7)
43
+ tzinfo (1.2.8)
44
44
  thread_safe (~> 0.1)
45
- zeitwerk (2.4.0)
45
+ zeitwerk (2.4.2)
46
46
 
47
47
  PLATFORMS
48
48
  ruby
data/README.md CHANGED
@@ -27,24 +27,23 @@ Or install it yourself as:
27
27
 
28
28
  ## Instance Level
29
29
 
30
- To use `NxtRegistry` on an instance level simply include it and build registries like so:
30
+ If you simply need a single global instance of a registry include `NxtRegistry::Singleton`:
31
31
 
32
32
  ```ruby
33
33
  class Example
34
- include NxtRegistry
34
+ include NxtRegistry::Singleton
35
35
 
36
- registry :languages do
36
+ registry do
37
37
  register(:ruby, 'Stone')
38
38
  register(:python, 'Snake')
39
39
  register(:javascript, 'undefined')
40
40
  end
41
41
  end
42
42
 
43
- example = Example.new
44
- example.registry(:languages).resolve(:ruby) # => 'Stone'
43
+ Example.resolve(:ruby) # => 'Stone'
45
44
  ```
46
45
 
47
- Alternatively you can also create instances of `NxtRegistry::Registry`
46
+ Alternatively you can simply create instances of `NxtRegistry::Registry`:
48
47
 
49
48
  ```ruby
50
49
  registry = NxtRegistry::Registry.new do
@@ -59,7 +58,7 @@ registry.resolve(:aki) # => 'Aki'
59
58
 
60
59
  ## Class Level
61
60
 
62
- You can register registries on the class level simply by extending your class with `NxtRegistry`
61
+ You can also add registries on the class level simply by extending your class with `NxtRegistry`
63
62
 
64
63
  ```ruby
65
64
  class OtherExample
@@ -83,6 +82,30 @@ OtherExample.registry(:errors).resolve(KeyError)
83
82
  OtherExample.registry(:country_codes).resolve(:germany)
84
83
  # => :de
85
84
  ```
85
+
86
+ ## Register Patterns
87
+
88
+ You can also register values with patterns as keys. Non pattern keys are always evaluated first and then patterns
89
+ will be tried to match by definition sequence.
90
+
91
+ ```ruby
92
+ class Example
93
+ extend NxtRegistry
94
+
95
+ registry :status_codes do
96
+ register(/\A4\d{2}\z/, 'Client errors')
97
+ register(/\A5.*\z/, 'Server errors')
98
+ register('422', 'Unprocessable Entity')
99
+ register(:'503', 'Internal Server Error')
100
+ end
101
+ end
102
+
103
+ Example.registry(:status_codes).resolve('503') # => "Internal Server Error"
104
+ Example.registry(:status_codes).resolve(503) # => "Internal Server Error"
105
+ Example.registry(:status_codes).resolve(422) # => "Unprocessable Entity"
106
+ Example.registry(:status_codes).resolve(404) # => "Client Errors"
107
+ ```
108
+
86
109
  ### Readers
87
110
 
88
111
  Access your defined registries with the `registry(:country_code)` method.
@@ -130,17 +153,41 @@ class Layer
130
153
  end
131
154
  end
132
155
 
156
+ # On every upper level every resolve returns a registry
133
157
  Layer.registry(:from) # => Registry[from]
134
-
135
158
  Layer.registry(:from).resolve(:munich) # => Registry[to] -> {}
136
159
  Layer.registry(:from).resolve(:amsterdam) # => Registry[to] -> {}
137
160
  Layer.registry(:from).resolve(:any_key) # => Registry[to] -> {}
138
-
139
161
  Layer.registry(:from).resolve(:munich, :amsterdam) # => Registry[via] -> {}
162
+
163
+ # Register a value on the bottom level
140
164
  Layer.registry(:from).resolve(:munich, :amsterdam).register(:train, -> { 'train' })
165
+ # Resolve the complete path
141
166
  Layer.registry(:from).resolve(:munich, :amsterdam, :train) # => 'train'
142
167
  ```
143
168
 
169
+ For registries with multiple levels the normal syntax for registering and resolving becomes quite weird and unreadable. This is why
170
+ every registry can be accessed through it's name or a custom accessor. The above example then can be simplified as follows.
171
+
172
+ ```ruby
173
+ class Layer
174
+ extend NxtRegistry
175
+
176
+ registry :path, accessor: :from do # registry named path, can be accessed with .from(...)
177
+ level :to do
178
+ level :via
179
+ end
180
+ end
181
+ end
182
+
183
+ # Register a value
184
+ Layer.registry(:path).from(:munich).to(:amsterdam).via(:train, -> { 'train' })
185
+ # Resolve the complete path
186
+ Layer.registry(:path).from(:munich).to(:amsterdam).via(:train) # => 'train'
187
+ ```
188
+
189
+ *Note that this feature is also available for registries with a single level only.*
190
+
144
191
  ### Restrict attributes to a certain set
145
192
 
146
193
  Use `attrs` to restrict which attributes can be registered on a specific level.
@@ -1,32 +1,44 @@
1
1
  require 'active_support/core_ext'
2
- require "nxt_registry/version"
3
- require "nxt_registry/blank"
4
- require "nxt_registry/attribute"
5
- require "nxt_registry/errors"
6
- require "nxt_registry/registry_builder"
7
- require "nxt_registry/registry"
8
- require "nxt_registry/recursive_registry"
2
+ require 'nxt_registry/version'
3
+ require 'nxt_registry/blank'
4
+ require 'nxt_registry/attribute'
5
+ require 'nxt_registry/errors'
6
+ require 'nxt_registry/registry_builder'
7
+ require 'nxt_registry/registry'
8
+ require 'nxt_registry/recursive_registry'
9
+ require 'nxt_registry/singleton'
9
10
 
10
11
  module NxtRegistry
11
12
  def registry(name, **options, &config)
12
- return registries.fetch(name) if registries.key?(name)
13
-
14
- registry = Registry.new(name, **options, &config)
15
- registries[name] ||= registry
16
- registry
13
+ build_registry(Registry, name, **options, &config)
17
14
  end
18
15
 
19
16
  def recursive_registry(name, **options, &config)
20
- return registries.fetch(name) if registries.key?(name)
21
-
22
- registry = RecursiveRegistry.new(name, **options, &config)
23
- registries[name] ||= registry
24
- registry
17
+ build_registry(RecursiveRegistry, name, **options, &config)
25
18
  end
26
19
 
27
20
  private
28
21
 
22
+ def build_registry(registry_class, name, **options, &config)
23
+ registry = registries.resolve(name)
24
+
25
+ if registry.present?
26
+ if registry.configured
27
+ return registry
28
+ else
29
+ raise_unconfigured_registry_accessed(name)
30
+ end
31
+ else
32
+ registry = registry_class.new(name, **options, &config)
33
+ registries.register(name, registry)
34
+ end
35
+ end
36
+
37
+ def raise_unconfigured_registry_accessed(name)
38
+ raise ArgumentError, "The registry #{name} must be configured before accessed!"
39
+ end
40
+
29
41
  def registries
30
- @registries ||= {}
42
+ @registries ||= Registry.new(:registries)
31
43
  end
32
44
  end
@@ -1,25 +1,28 @@
1
1
  module NxtRegistry
2
2
  class Registry
3
3
  def initialize(name = object_id.to_s, **options, &config)
4
+ @options = options
4
5
  @name = name
5
6
  @parent = options[:parent]
6
7
  @is_leaf = true
7
8
  @namespace = build_namespace
8
9
  @config = config
9
- @options = options
10
10
  @store = {}
11
11
  @attrs = nil
12
+ @configured = false
13
+ @patterns = []
12
14
 
13
15
  setup_defaults(options)
14
16
  configure(&config)
15
17
  end
16
18
 
17
19
  attr_reader :name
20
+ attr_accessor :configured
18
21
 
19
22
  def level(name, **options, &config)
20
23
  options = options.merge(parent: self)
21
24
 
22
- if default.is_a?(Blank)
25
+ if is_a_blank?(default)
23
26
  self.is_leaf = false
24
27
 
25
28
  self.default = RegistryBuilder.new do
@@ -36,13 +39,13 @@ module NxtRegistry
36
39
  end
37
40
 
38
41
  def registry(name, **options, &config)
39
- options = options.merge(parent: self)
40
- register(name, Registry.new(name, **options, &config))
42
+ opts = options.merge(parent: self)
43
+ register(name, Registry.new(name, **opts, &config))
41
44
  end
42
45
 
43
46
  def registry!(name, **options, &config)
44
- options = options.merge(parent: self)
45
- register!(name, Registry.new(name, **options, &config))
47
+ opts = options.merge(parent: self)
48
+ register!(name, Registry.new(name, **opts, &config))
46
49
  end
47
50
 
48
51
  def attr(name)
@@ -61,37 +64,37 @@ module NxtRegistry
61
64
 
62
65
  def register(key = Blank.new, value = Blank.new, **options, &block)
63
66
  if block_given?
64
- if value.is_a?(Blank)
67
+ if is_a_blank?(value)
65
68
  registry(key, **options, &block)
66
69
  else
67
70
  raise_register_argument_error
68
71
  end
69
72
  else
70
- __register(key, value, raise: true)
73
+ __register(key, value, raise_on_key_already_registered: true)
71
74
  end
72
75
  end
73
76
 
74
77
  def register!(key = Blank.new, value = Blank.new, **options, &block)
75
78
  if block_given?
76
- if value.is_a?(Blank)
79
+ if is_a_blank?(value)
77
80
  registry!(key, **options, &block)
78
81
  else
79
82
  raise_register_argument_error
80
83
  end
81
84
  else
82
- __register(key, value, raise: false)
85
+ __register(key, value, raise_on_key_already_registered: false)
83
86
  end
84
87
  end
85
88
 
86
89
  def resolve!(*keys)
87
90
  keys.inject(self) do |current_registry, key|
88
- current_registry.send(:__resolve, key, raise: true)
91
+ current_registry.send(:__resolve, key, raise_on_key_not_registered: true)
89
92
  end
90
93
  end
91
94
 
92
95
  def resolve(*keys)
93
96
  keys.inject(self) do |current_registry, key|
94
- current_registry.send(:__resolve, key, raise: false) || break
97
+ current_registry.send(:__resolve, key, raise_on_key_not_registered: false) || break
95
98
  end
96
99
  end
97
100
 
@@ -124,7 +127,8 @@ module NxtRegistry
124
127
  end
125
128
 
126
129
  def fetch(key, *args, &block)
127
- store.fetch(transformed_key(key), *args, &block)
130
+ key = matching_key(key)
131
+ store.fetch(key, *args, &block)
128
132
  end
129
133
 
130
134
  delegate :size, :values, :each, :freeze, to: :store
@@ -141,6 +145,8 @@ module NxtRegistry
141
145
  instance_exec(&block)
142
146
  end
143
147
  end
148
+
149
+ self.configured = true
144
150
  end
145
151
 
146
152
  def to_s
@@ -151,33 +157,40 @@ module NxtRegistry
151
157
 
152
158
  private
153
159
 
154
- attr_reader :namespace, :parent, :config, :store, :options
155
- attr_accessor :is_leaf
160
+ attr_reader :namespace, :parent, :config, :store, :options, :accessor, :patterns
161
+ attr_accessor :is_leaf, :interface_defined
156
162
 
157
163
  def is_leaf?
158
164
  @is_leaf
159
165
  end
160
166
 
161
- def __register(key, value, raise: true)
162
- key = transformed_key(key)
167
+ def __register(key, value, raise_on_key_already_registered: true)
168
+ key = if key.is_a?(Regexp)
169
+ patterns << key
170
+ key
171
+ else
172
+ transformed_key(key)
173
+ end
163
174
 
164
175
  raise ArgumentError, "Not allowed to register values in a registry that contains nested registries" unless is_leaf
165
176
  raise KeyError, "Keys are restricted to #{attrs.keys}" if attribute_not_allowed?(key)
166
177
 
167
- on_key_already_registered && on_key_already_registered.call(key) if store[key] && raise
178
+ on_key_already_registered && on_key_already_registered.call(key) if store[key] && raise_on_key_already_registered
168
179
 
169
180
  store[key] = value
170
181
  end
171
182
 
172
- def __resolve(key, raise: true)
183
+ def __resolve(key, raise_on_key_not_registered: true)
173
184
  key = transformed_key(key)
174
185
 
175
186
  value = if is_leaf?
176
187
  if store.key?(key)
177
188
  store.fetch(key)
189
+ elsif (pattern = matching_pattern(key))
190
+ store.fetch(pattern)
178
191
  else
179
- if default.is_a?(Blank)
180
- return unless raise
192
+ if is_a_blank?(default)
193
+ return unless raise_on_key_not_registered
181
194
 
182
195
  on_key_not_registered && on_key_not_registered.call(key)
183
196
  else
@@ -191,11 +204,7 @@ module NxtRegistry
191
204
  store[key] ||= default.call
192
205
  end
193
206
 
194
- value = if value.respond_to?(:call) && call && !value.is_a?(NxtRegistry::Registry)
195
- value.call(*[value].take(value.arity))
196
- else
197
- value
198
- end
207
+ value = call_or_value(value, key)
199
208
 
200
209
  if resolver
201
210
  resolver.call(value)
@@ -204,30 +213,59 @@ module NxtRegistry
204
213
  end
205
214
  end
206
215
 
216
+ def matching_key(key)
217
+ key = transformed_key(key)
218
+ # if key is present it always wins over patterns
219
+ return key if store.key?(key)
220
+
221
+ matching_pattern(key) || key
222
+ end
223
+
224
+ def call_or_value(value, key)
225
+ return value unless call
226
+ return value if value.is_a?(NxtRegistry::Registry)
227
+ return value unless value.respond_to?(:call)
228
+
229
+ args = [key, value]
230
+ value.call(*args.take(value.arity))
231
+ end
232
+
233
+ def matching_pattern(key)
234
+ patterns.find { |pattern| key.match?(pattern) }
235
+ end
236
+
207
237
  def define_interface
208
- define_singleton_method name do |key = Blank.new, value = Blank.new|
209
- return self if key.is_a?(Blank)
238
+ return if interface_defined
239
+
240
+ raise_invalid_accessor_name(accessor) if respond_to?(accessor.to_s)
241
+ accessor_with_bang = "#{accessor}!"
242
+ raise_invalid_accessor_name(accessor_with_bang) if respond_to?(accessor_with_bang)
243
+
244
+ define_singleton_method accessor.to_s do |key = Blank.new, value = Blank.new|
245
+ return self if is_a_blank?(key)
210
246
 
211
247
  key = transformed_key(key)
212
248
 
213
- if value.is_a?(Blank)
249
+ if is_a_blank?(value)
214
250
  resolve(key)
215
251
  else
216
252
  register(key, value)
217
253
  end
218
254
  end
219
255
 
220
- define_singleton_method "#{name}!" do |key = Blank.new, value = Blank.new|
221
- return self if key.is_a?(Blank)
256
+ define_singleton_method accessor_with_bang do |key = Blank.new, value = Blank.new|
257
+ return self if is_a_blank?(key)
222
258
 
223
259
  key = transformed_key(key)
224
260
 
225
- if value.is_a?(Blank)
261
+ if is_a_blank?(value)
226
262
  resolve!(key)
227
263
  else
228
264
  register!(key, value)
229
265
  end
230
266
  end
267
+
268
+ self.interface_defined = true
231
269
  end
232
270
 
233
271
  def setup_defaults(options)
@@ -235,7 +273,8 @@ module NxtRegistry
235
273
  @memoize = options.fetch(:memoize) { true }
236
274
  @call = options.fetch(:call) { true }
237
275
  @resolver = options.fetch(:resolver, false)
238
- @transform_keys = options.fetch(:transform_keys) { ->(key) { key.to_s } }
276
+ @transform_keys = options.fetch(:transform_keys) { ->(key) { key.is_a?(Regexp) ? key : key.to_s } }
277
+ @accessor = options.fetch(:accessor) { name }
239
278
 
240
279
  @on_key_already_registered = options.fetch(:on_key_already_registered) { ->(key) { raise_key_already_registered_error(key) } }
241
280
  @on_key_not_registered = options.fetch(:on_key_not_registered) { ->(key) { raise_key_not_registered_error(key) } }
@@ -246,7 +285,7 @@ module NxtRegistry
246
285
  define_singleton_method attribute do |value = Blank.new, &block|
247
286
  value = block if block
248
287
 
249
- if value.is_a?(Blank)
288
+ if is_a_blank?(value)
250
289
  instance_variable_get("@#{attribute}")
251
290
  else
252
291
  instance_variable_set("@#{attribute}", value)
@@ -284,7 +323,7 @@ module NxtRegistry
284
323
  def transformed_key(key)
285
324
  @transformed_key ||= {}
286
325
  @transformed_key[key] ||= begin
287
- if transform_keys && !key.is_a?(Blank)
326
+ if transform_keys && !is_a_blank?(key)
288
327
  transform_keys.call(key)
289
328
  else
290
329
  key
@@ -296,6 +335,7 @@ module NxtRegistry
296
335
  super
297
336
  @store = original.send(:store).deep_dup
298
337
  @options = original.send(:options).deep_dup
338
+ @patterns = original.send(:patterns).dup
299
339
  end
300
340
 
301
341
  def build_namespace
@@ -305,5 +345,13 @@ module NxtRegistry
305
345
  def raise_register_argument_error
306
346
  raise ArgumentError, 'Either provide a key value pair or a block to register'
307
347
  end
348
+
349
+ def is_a_blank?(value)
350
+ value.is_a?(Blank)
351
+ end
352
+
353
+ def raise_invalid_accessor_name(name)
354
+ raise ArgumentError, "#{self} already implements a method named: #{name}. Please choose a different accessor name"
355
+ end
308
356
  end
309
357
  end
@@ -0,0 +1,15 @@
1
+ module NxtRegistry
2
+ module Singleton
3
+ include NxtRegistry
4
+
5
+ def self.included(base)
6
+ base.extend(self)
7
+ end
8
+
9
+ def registry(type = Registry, **options, &config)
10
+ @registry ||= build_registry(type, self.class.name, **options, &config)
11
+ end
12
+
13
+ delegate_missing_to :registry
14
+ end
15
+ end
@@ -1,3 +1,3 @@
1
1
  module NxtRegistry
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.5"
3
3
  end
@@ -6,7 +6,7 @@ Gem::Specification.new do |spec|
6
6
  spec.name = "nxt_registry"
7
7
  spec.version = NxtRegistry::VERSION
8
8
  spec.authors = ["Andreas Robecke", "Nils Sommer", "Raphael Kallensee", "Lütfi Demirci"]
9
- spec.email = ["a.robecke@getsafe.de"]
9
+ spec.email = ['a.robecke@hellogetsafe.com', 'andreas@robecke.de']
10
10
 
11
11
  spec.summary = %q{nxt_registry is a simple implementation of the container pattern}
12
12
  spec.homepage = "https://github.com/nxt-insurance"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nxt_registry
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andreas Robecke
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: exe
13
13
  cert_chain: []
14
- date: 2020-09-17 00:00:00.000000000 Z
14
+ date: 2020-12-04 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: activesupport
@@ -85,7 +85,8 @@ dependencies:
85
85
  version: '0'
86
86
  description:
87
87
  email:
88
- - a.robecke@getsafe.de
88
+ - a.robecke@hellogetsafe.com
89
+ - andreas@robecke.de
89
90
  executables: []
90
91
  extensions: []
91
92
  extra_rdoc_files: []
@@ -111,6 +112,7 @@ files:
111
112
  - lib/nxt_registry/recursive_registry.rb
112
113
  - lib/nxt_registry/registry.rb
113
114
  - lib/nxt_registry/registry_builder.rb
115
+ - lib/nxt_registry/singleton.rb
114
116
  - lib/nxt_registry/version.rb
115
117
  - nxt_registry.gemspec
116
118
  homepage: https://github.com/nxt-insurance