mudis 0.3.0 → 0.4.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/README.md +123 -21
- data/lib/mudis/version.rb +1 -1
- data/lib/mudis.rb +71 -2
- data/lib/mudis_config.rb +19 -0
- data/spec/mudis_spec.rb +72 -0
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a107a0fdb170a8979e0a0a8ae241c6a2bb632f50a314443124b48e82488a72f3
|
4
|
+
data.tar.gz: 438e08ddede26d761cc490d5e83e363142f264b67e67d8c2b5395ef8a7a18ff6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e261bbf2112035136d4967fc4064aed07c07918bc5322841e9c70f1761b50b2070265ae6453e5a0447b34dbfb355d5f5fd8f419c8da70808f822441ad780926
|
7
|
+
data.tar.gz: 3a622667d7000e9897ce301f8d40a202a10ba86ab0bb1178ed0b6fe250113b6b68942f1a39348572b8c9d607b852e58026e945aecbb23e3001e05d60fff34348
|
data/README.md
CHANGED
@@ -1,17 +1,55 @@
|
|
1
1
|

|
2
2
|
|
3
|
-
[](https://badge.fury.io/rb/mudis)
|
3
|
+
[](https://badge.fury.io/rb/mudis)
|
4
4
|
|
5
5
|
**Mudis** is a fast, thread-safe, in-memory, sharded LRU (Least Recently Used) cache for Ruby applications. Inspired by Redis, it provides value serialization, optional compression, per-key expiry, and metric tracking in a lightweight, dependency-free package that lives inside your Ruby process.
|
6
6
|
|
7
|
-
It’s ideal for scenarios where performance and process-local caching are critical, and where a full Redis setup is overkill or otherwise not possible.
|
7
|
+
It’s ideal for scenarios where performance and process-local caching are critical, and where a full Redis setup is overkill or otherwise not possible/desirable.
|
8
8
|
|
9
|
-
Alternatively, Mudis can be upscaled with higher sharding and resources in a dedicated
|
9
|
+
Alternatively, Mudis can be upscaled with higher sharding and resources in a dedicated Rails app to provide a Mudis server.
|
10
|
+
|
11
|
+
### Why another Caching Gem?
|
12
|
+
|
13
|
+
There are plenty out there, in various states of maintenance and in many shapes and sizes. So why on earth do we need another? I needed a drop-in replacement for Kredis, and the reason I was interested in using Kredis was for the simplified API and keyed management it gave me in extension to Redis. But what I didn't really need was Redis. I needed an observable, fast, simple, easy to use, flexible and highly configurable, thread-safe and high performant caching system which didn't require too many dependencies or standing up additional services. So, Mudis was born. In its most rudimentary state it was extremely useful in my project, which was an API gateway connecting into mutliple micro-services and a wide selection of APIs. The majority of the data was cold and produced by repeat expensive queries across several domains. Mudis allowed for me to minimize the footprint of the gateway, and improve end user experience, and increase performance. So, yeah, there's a lot of these gems out there, but none which really met all my needs. I decided to provide Mudis for anyone else. If you use it, I'd be interested to know how and whether you got any benefit.
|
14
|
+
|
15
|
+
#### Similar Gems
|
16
|
+
|
17
|
+
- [FastCache](https://github.com/swoop-inc/fast_cache)
|
18
|
+
- [EasyCache](https://github.com/malvads/easycache)
|
19
|
+
- [MiniCache](https://github.com/derrickreimer/mini_cache)
|
20
|
+
- [Zache](https://github.com/yegor256/zache)
|
21
|
+
|
22
|
+
#### Feature / Function Comparison
|
23
|
+
|
24
|
+
| **Feature** | **Mudis v0.3.0** | **MemoryStore** (`Rails.cache`) | **FastCache** | **Zache** | **EasyCache** | **MiniCache** |
|
25
|
+
| -------------------------------------- | ---------------- | ------------------------------- | -------------- | ------------- | ------------- | -------------- |
|
26
|
+
| **LRU eviction strategy** | ✅ Per-bucket | ✅ Global | ✅ Global | ❌ | ❌ | ✅ Simplistic |
|
27
|
+
| **TTL expiry support** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
28
|
+
| **Background expiry cleanup thread** | ✅ | ❌ (only on access) | ❌ | ✅ | ❌ | ❌ |
|
29
|
+
| **Thread safety** | ✅ Bucketed | ⚠️ Global lock | ✅ Fine-grained | ✅ | ⚠️ | ⚠️ |
|
30
|
+
| **Sharding (buckets)** | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
31
|
+
| **Custom serializers** | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
32
|
+
| **Compression (Zlib)** | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
33
|
+
| **Hard memory cap** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
34
|
+
| **Max value size enforcement** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
35
|
+
| **Metrics (hits, misses, evictions)** | ✅ | ⚠️ Partial | ❌ | ❌ | ❌ | ❌ |
|
36
|
+
| **Fetch/update pattern** | ✅ Full | ✅ Standard | ⚠️ Partial | ✅ Basic | ✅ Basic | ✅ Basic |
|
37
|
+
| **Namespacing** | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
38
|
+
| **Replace (if exists)** | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
39
|
+
| **Clear/delete method** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
40
|
+
| **Key inspection with metadata** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
41
|
+
| **Concurrency model** | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
42
|
+
| **Maintenance level** | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | ⚠️ |
|
43
|
+
| **Suitable for APIs or microservices** | ✅ | ⚠️ Limited | ✅ | ⚠️ Small apps | ⚠️ Small apps | ❌ |
|
10
44
|
|
11
45
|
---
|
12
46
|
|
13
47
|
## Design
|
14
48
|
|
49
|
+
#### Internal Structure and Behaviour
|
50
|
+
|
51
|
+

|
52
|
+
|
15
53
|
#### Write - Read - Eviction
|
16
54
|
|
17
55
|

|
@@ -55,19 +93,34 @@ In your Rails app, create an initializer:
|
|
55
93
|
|
56
94
|
```ruby
|
57
95
|
# config/initializers/mudis.rb
|
96
|
+
Mudis.configure do |c|
|
97
|
+
c.serializer = JSON # or Marshal | Oj
|
98
|
+
c.compress = true # Compress values using Zlib
|
99
|
+
c.max_value_bytes = 2_000_000 # Reject values > 2MB
|
100
|
+
c.hard_memory_limit = true # enforce hard memory limits
|
101
|
+
c.max_bytes = 1_073_741_824 # set maximum cache size
|
102
|
+
end
|
58
103
|
|
59
|
-
Mudis.serializer = JSON # or Marshal | Oj
|
60
|
-
Mudis.compress = true # Compress values using Zlib
|
61
|
-
Mudis.max_value_bytes = 2_000_000 # Reject values > 2MB
|
62
104
|
Mudis.start_expiry_thread(interval: 60) # Cleanup every 60s
|
63
|
-
Mudis.hard_memory_limit = true # enforce hard memory limits
|
64
105
|
|
65
106
|
at_exit do
|
66
107
|
Mudis.stop_expiry_thread
|
67
108
|
end
|
68
109
|
```
|
69
110
|
|
70
|
-
|
111
|
+
Or with direct setters:
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
Mudis.serializer = JSON # or Marshal | Oj
|
115
|
+
Mudis.compress = true # Compress values using Zlib
|
116
|
+
Mudis.max_value_bytes = 2_000_000 # Reject values > 2MB
|
117
|
+
Mudis.hard_memory_limit = true # enforce hard memory limits
|
118
|
+
Mudis.max_bytes = 1_073_741_824 # set maximum cache size
|
119
|
+
|
120
|
+
Mudis.start_expiry_thread(interval: 60) # Cleanup every 60s
|
121
|
+
|
122
|
+
## set at exit hook
|
123
|
+
```
|
71
124
|
|
72
125
|
---
|
73
126
|
|
@@ -92,6 +145,37 @@ Mudis.update('user:123') { |data| data.merge(age: 30) }
|
|
92
145
|
Mudis.delete('user:123')
|
93
146
|
```
|
94
147
|
|
148
|
+
### Developer Utilities
|
149
|
+
|
150
|
+
Mudis provides utility methods to help with test environments, console debugging, and dev tool resets.
|
151
|
+
|
152
|
+
#### `Mudis.reset!`
|
153
|
+
Clears the internal cache state. Including all keys, memory tracking, and metrics. Also stops the expiry thread.
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
Mudis.write("foo", "bar")
|
157
|
+
Mudis.reset!
|
158
|
+
Mudis.read("foo") # => nil
|
159
|
+
```
|
160
|
+
|
161
|
+
- Wipe all buckets (@stores, @lru_nodes, @current_bytes)
|
162
|
+
- Reset all metrics (:hits, :misses, :evictions, :rejected)
|
163
|
+
- Stop any running background expiry thread
|
164
|
+
|
165
|
+
#### `Mudis.reset_metrics!`
|
166
|
+
|
167
|
+
Clears only the metric counters and preserves all cached values.
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
Mudis.write("key", "value")
|
171
|
+
Mudis.read("key") # => "value"
|
172
|
+
Mudis.metrics # => { hits: 1, misses: 0, ... }
|
173
|
+
|
174
|
+
Mudis.reset_metrics!
|
175
|
+
Mudis.metrics # => { hits: 0, misses: 0, ... }
|
176
|
+
Mudis.read("key") # => "value" (still cached)
|
177
|
+
```
|
178
|
+
|
95
179
|
---
|
96
180
|
|
97
181
|
## Rails Service Integration
|
@@ -187,25 +271,29 @@ Track cache effectiveness and performance:
|
|
187
271
|
|
188
272
|
```ruby
|
189
273
|
Mudis.metrics
|
190
|
-
# => {
|
274
|
+
# => {
|
275
|
+
# hits: 15,
|
276
|
+
# misses: 5,
|
277
|
+
# evictions: 3,
|
278
|
+
# rejected: 0,
|
279
|
+
# total_memory: 45678,
|
280
|
+
# buckets: [
|
281
|
+
# { index: 0, keys: 12, memory_bytes: 12345, lru_size: 12 },
|
282
|
+
# ...
|
283
|
+
# ]
|
284
|
+
# }
|
285
|
+
|
191
286
|
```
|
192
287
|
|
193
|
-
Optionally, return these metrics from a controller for remote analysis and monitoring if using
|
288
|
+
Optionally, return these metrics from a controller for remote analysis and monitoring if using Rails.
|
194
289
|
|
195
290
|
```ruby
|
196
291
|
class MudisController < ApplicationController
|
197
|
-
|
198
292
|
def metrics
|
199
|
-
render json: {
|
200
|
-
mudis_metrics: Mudis.metrics,
|
201
|
-
memory_used_bytes: Mudis.current_memory_bytes,
|
202
|
-
memory_max_bytes: Mudis.max_memory_bytes,
|
203
|
-
keys: Mudis.all_keys.size
|
204
|
-
}
|
293
|
+
render json: { mudis: Mudis.metrics }
|
205
294
|
end
|
206
295
|
|
207
296
|
end
|
208
|
-
|
209
297
|
```
|
210
298
|
|
211
299
|
---
|
@@ -219,7 +307,8 @@ end
|
|
219
307
|
| `Mudis.max_value_bytes` | Max allowed size in bytes for a value | `nil` (no limit) |
|
220
308
|
| `Mudis.buckets` | Number of cache shards (via ENV var) | `32` |
|
221
309
|
| `start_expiry_thread` | Background TTL cleanup loop (every N sec) | Disabled by default|
|
222
|
-
| `hard_memory_limit
|
310
|
+
| `hard_memory_limit` | Enforce hard memory limits on key size and reject if exceeded | `false`|
|
311
|
+
| `max_bytes` | Maximum allowed cache size | `1GB`|
|
223
312
|
|
224
313
|
To customize the number of buckets, set the `MUDIS_BUCKETS` environment variable.
|
225
314
|
|
@@ -244,7 +333,7 @@ Based on 100000 iterations
|
|
244
333
|
| marshal + zlib | 100000 | 1.8057 | 55381 |
|
245
334
|
| json + zlib | 100000 | 2.7949 | 35780 |
|
246
335
|
|
247
|
-
> If opting for OJ, you will need to install the
|
336
|
+
> If opting for OJ, you will need to install the dependency in your project and configure as needed.
|
248
337
|
|
249
338
|
---
|
250
339
|
|
@@ -267,7 +356,18 @@ at_exit { Mudis.stop_expiry_thread }
|
|
267
356
|
|
268
357
|
## Roadmap
|
269
358
|
|
270
|
-
|
359
|
+
#### API Enhancements
|
360
|
+
|
361
|
+
- [ ] bulk_read(keys, namespace:): Batch retrieval of multiple keys with a single method call
|
362
|
+
|
363
|
+
#### Safety & Policy Controls
|
364
|
+
|
365
|
+
- [ ] max_ttl: Enforce a global upper bound on expires_in to prevent excessively long-lived keys
|
366
|
+
- [ ] default_ttl: Provide a fallback TTL when one is not specified
|
367
|
+
|
368
|
+
#### Debugging
|
369
|
+
|
370
|
+
- [ ] clear_namespace(namespace): Remove all keys in a namespace in one call
|
271
371
|
|
272
372
|
---
|
273
373
|
|
@@ -293,3 +393,5 @@ bundle install
|
|
293
393
|
## Contact
|
294
394
|
|
295
395
|
For issues, suggestions, or feedback, please open a GitHub issue
|
396
|
+
|
397
|
+
---
|
data/lib/mudis/version.rb
CHANGED
data/lib/mudis.rb
CHANGED
@@ -4,6 +4,8 @@ require "json"
|
|
4
4
|
require "thread" # rubocop:disable Lint/RedundantRequireStatement
|
5
5
|
require "zlib"
|
6
6
|
|
7
|
+
require_relative "mudis_config"
|
8
|
+
|
7
9
|
# Mudis is a thread-safe, in-memory, sharded, LRU cache with optional compression and expiry.
|
8
10
|
# It is designed for high concurrency and performance within a Ruby application.
|
9
11
|
class Mudis # rubocop:disable Metrics/ClassLength
|
@@ -18,10 +20,77 @@ class Mudis # rubocop:disable Metrics/ClassLength
|
|
18
20
|
|
19
21
|
class << self
|
20
22
|
attr_accessor :serializer, :compress, :max_value_bytes, :hard_memory_limit
|
23
|
+
attr_reader :max_bytes
|
24
|
+
|
25
|
+
# Configures Mudis with a block, allowing customization of settings
|
26
|
+
def configure
|
27
|
+
yield(config)
|
28
|
+
apply_config!
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns the current configuration object
|
32
|
+
def config
|
33
|
+
@config ||= MudisConfig.new
|
34
|
+
end
|
35
|
+
|
36
|
+
# Applies the current configuration to Mudis
|
37
|
+
def apply_config!
|
38
|
+
self.serializer = config.serializer
|
39
|
+
self.compress = config.compress
|
40
|
+
self.max_value_bytes = config.max_value_bytes
|
41
|
+
self.hard_memory_limit = config.hard_memory_limit
|
42
|
+
self.max_bytes = config.max_bytes
|
43
|
+
end
|
21
44
|
|
22
45
|
# Returns a snapshot of metrics (thread-safe)
|
23
|
-
def metrics
|
24
|
-
@metrics_mutex.synchronize
|
46
|
+
def metrics # rubocop:disable Metrics/MethodLength
|
47
|
+
@metrics_mutex.synchronize do
|
48
|
+
{
|
49
|
+
hits: @metrics[:hits],
|
50
|
+
misses: @metrics[:misses],
|
51
|
+
evictions: @metrics[:evictions],
|
52
|
+
rejected: @metrics[:rejected],
|
53
|
+
total_memory: current_memory_bytes,
|
54
|
+
buckets: buckets.times.map do |idx|
|
55
|
+
{
|
56
|
+
index: idx,
|
57
|
+
keys: @stores[idx].size,
|
58
|
+
memory_bytes: @current_bytes[idx],
|
59
|
+
lru_size: @lru_nodes[idx].size
|
60
|
+
}
|
61
|
+
end
|
62
|
+
}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Resets metric counters (thread-safe)
|
67
|
+
def reset_metrics!
|
68
|
+
@metrics_mutex.synchronize do
|
69
|
+
@metrics = { hits: 0, misses: 0, evictions: 0, rejected: 0 }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Fully resets all internal state (except config)
|
74
|
+
def reset!
|
75
|
+
stop_expiry_thread
|
76
|
+
|
77
|
+
@buckets = nil
|
78
|
+
b = buckets
|
79
|
+
|
80
|
+
@stores = Array.new(b) { {} }
|
81
|
+
@mutexes = Array.new(b) { Mutex.new }
|
82
|
+
@lru_heads = Array.new(b) { nil }
|
83
|
+
@lru_tails = Array.new(b) { nil }
|
84
|
+
@lru_nodes = Array.new(b) { {} }
|
85
|
+
@current_bytes = Array.new(b, 0)
|
86
|
+
|
87
|
+
reset_metrics!
|
88
|
+
end
|
89
|
+
|
90
|
+
# Sets the maximum size for a single value in bytes
|
91
|
+
def max_bytes=(value)
|
92
|
+
@max_bytes = value
|
93
|
+
@threshold_bytes = (@max_bytes * 0.9).to_i
|
25
94
|
end
|
26
95
|
end
|
27
96
|
|
data/lib/mudis_config.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# MudisConfig holds all configuration values for Mudis,
|
4
|
+
# and provides defaults that can be overridden via Mudis.configure.
|
5
|
+
class MudisConfig
|
6
|
+
attr_accessor :serializer,
|
7
|
+
:compress,
|
8
|
+
:max_value_bytes,
|
9
|
+
:hard_memory_limit,
|
10
|
+
:max_bytes
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@serializer = JSON # Default serialization strategy
|
14
|
+
@compress = false # Whether to compress values with Zlib
|
15
|
+
@max_value_bytes = nil # Max size per value (optional)
|
16
|
+
@hard_memory_limit = false # Enforce max_bytes as hard cap
|
17
|
+
@max_bytes = 1_073_741_824 # 1 GB default max cache size
|
18
|
+
end
|
19
|
+
end
|
data/spec/mudis_spec.rb
CHANGED
@@ -216,10 +216,82 @@ RSpec.describe Mudis do # rubocop:disable Metrics/BlockLength
|
|
216
216
|
end
|
217
217
|
end
|
218
218
|
|
219
|
+
it "respects max_bytes when updated externally" do
|
220
|
+
Mudis.max_bytes = 100
|
221
|
+
expect(Mudis.send(:max_bytes)).to eq(100)
|
222
|
+
end
|
223
|
+
|
219
224
|
describe ".current_memory_bytes" do
|
220
225
|
it "returns a non-zero byte count after writes" do
|
221
226
|
Mudis.write("size_test", "a" * 100)
|
222
227
|
expect(Mudis.current_memory_bytes).to be > 0
|
223
228
|
end
|
224
229
|
end
|
230
|
+
|
231
|
+
describe ".metrics" do
|
232
|
+
it "includes per-bucket stats" do
|
233
|
+
Mudis.write("a", "x" * 50)
|
234
|
+
metrics = Mudis.metrics
|
235
|
+
|
236
|
+
expect(metrics).to include(:buckets)
|
237
|
+
expect(metrics[:buckets]).to be_an(Array)
|
238
|
+
expect(metrics[:buckets].first).to include(:index, :keys, :memory_bytes, :lru_size)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
describe ".configure" do
|
243
|
+
it "applies configuration settings correctly" do
|
244
|
+
Mudis.configure do |c|
|
245
|
+
c.serializer = JSON
|
246
|
+
c.compress = true
|
247
|
+
c.max_value_bytes = 12_345
|
248
|
+
c.hard_memory_limit = true
|
249
|
+
c.max_bytes = 987_654
|
250
|
+
end
|
251
|
+
|
252
|
+
expect(Mudis.compress).to eq(true)
|
253
|
+
expect(Mudis.max_value_bytes).to eq(12_345)
|
254
|
+
expect(Mudis.hard_memory_limit).to eq(true)
|
255
|
+
expect(Mudis.max_bytes).to eq(987_654)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
describe ".reset!" do
|
260
|
+
it "clears all stores, memory, and metrics" do
|
261
|
+
Mudis.write("reset_key", "value")
|
262
|
+
expect(Mudis.read("reset_key")).to eq("value")
|
263
|
+
expect(Mudis.current_memory_bytes).to be > 0
|
264
|
+
expect(Mudis.metrics[:hits]).to be >= 0
|
265
|
+
|
266
|
+
Mudis.reset!
|
267
|
+
|
268
|
+
metrics = Mudis.metrics
|
269
|
+
expect(metrics[:hits]).to eq(0)
|
270
|
+
expect(metrics[:misses]).to eq(0)
|
271
|
+
expect(metrics[:evictions]).to eq(0)
|
272
|
+
expect(metrics[:rejected]).to eq(0)
|
273
|
+
expect(Mudis.current_memory_bytes).to eq(0)
|
274
|
+
expect(Mudis.all_keys).to be_empty
|
275
|
+
|
276
|
+
# Optionally confirm reset_key is now gone
|
277
|
+
expect(Mudis.read("reset_key")).to be_nil
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
describe ".reset_metrics!" do
|
282
|
+
it "resets only the metrics without clearing cache" do
|
283
|
+
Mudis.write("metrics_key", "value")
|
284
|
+
Mudis.read("metrics_key") # generates :hits
|
285
|
+
Mudis.read("missing_key") # generates :misses
|
286
|
+
|
287
|
+
expect(Mudis.metrics[:hits]).to eq(1)
|
288
|
+
expect(Mudis.metrics[:misses]).to eq(1)
|
289
|
+
|
290
|
+
Mudis.reset_metrics!
|
291
|
+
|
292
|
+
expect(Mudis.metrics[:hits]).to eq(0)
|
293
|
+
expect(Mudis.metrics[:misses]).to eq(0)
|
294
|
+
expect(Mudis.read("metrics_key")).to eq("value") # still exists
|
295
|
+
end
|
296
|
+
end
|
225
297
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mudis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- kiebor81
|
@@ -35,6 +35,7 @@ files:
|
|
35
35
|
- README.md
|
36
36
|
- lib/mudis.rb
|
37
37
|
- lib/mudis/version.rb
|
38
|
+
- lib/mudis_config.rb
|
38
39
|
- sig/mudis.rbs
|
39
40
|
- spec/mudis_spec.rb
|
40
41
|
homepage: https://github.com/kiebor81/mudis
|