inmemory_kv 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YWFiZGI1MGRlM2I4MDg3NzFlZGEyYzBkMzAwOWU0MjFkNWY4ZGRiNQ==
5
+ data.tar.gz: !binary |-
6
+ ZjkxOTg3MTUzOWY0OTM1N2JhOGFiMmNkMDUzYjQ0ODQ3NDg3M2JhOQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ YzFlNGQ3MmU1ODE1NDI4ZGEyOWU0MTYzYzdjYTlmMTgzMzRiMTM0ZjQ1YzUy
10
+ ZjI1MjM0YTBjNTUyMjEwODFhMzVjMmFmM2FlZTUwZmNhOWZkMjdiODUyYWY5
11
+ MjUxZDEzZGI4ZjAyNDAwZjI1MGIyNzM2MzVmMTcxOTU3MzI4ZDg=
12
+ data.tar.gz: !binary |-
13
+ MGVjZWZmMDM5YTQzYmVmMjEyNmU5YjVkYTAxNTExMjllMGJhM2VhNjczMDc0
14
+ OWIzYTczMGNmYTBlMzhlMzIwNTI2NDIxN2NmODNjNGJhZTdkZWYxYWY5MzVk
15
+ MTYwZjE4MGQ1N2Q2ZDYwYmE3MDI2NGYwNDI0YTZjZWFiZDY5MmI=
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in inmemory_kv.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Sokolov Yura aka funny_falcon
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,82 @@
1
+ # InMemoryKV
2
+
3
+ This is simple off-gc in-memory hash table.
4
+
5
+ Currently, `InMemoryKV::Str2Str` is a string to string hash table
6
+ with LRU built in (a lot like builtin Hash).
7
+
8
+ It doesn't participate in GC and not encounted in. It uses `malloc` for simplicity.
9
+
10
+ If you do not clone and not mutate it in a fork, than it is as fork-frienly as your malloc is.
11
+
12
+ It is not thread-safe, so protect it by you self. (builtin hash is also not thread-safe)
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ ```ruby
19
+ gem 'inmemory_kv'
20
+ ```
21
+
22
+ And then execute:
23
+
24
+ $ bundle
25
+
26
+ Or install it yourself as:
27
+
28
+ $ gem install inmemory_kv
29
+
30
+ ## Usage
31
+
32
+ ```ruby
33
+ s2s = InMemoryKV::Str2Str.new
34
+ s2s['asdf'] = 'qwer'
35
+ s2s.keys
36
+ s2s.values
37
+ s2s.entries
38
+ # attention: iteration is not as safe as iteration of builtin Hash.
39
+ # Builtin Hash checks if you mutate hash during iteratation, this hash doesn't.
40
+ s2s.each_key{|k| }
41
+ s2s.each_value{|k| }
42
+ s2s.each{|k,v| }
43
+
44
+ s2s.up(k) # touch entry to be most recent in LRU order
45
+ s2s.down(k) # touch entry to be first to expire
46
+ s2s.first # first/oldest entry in LRU
47
+ s2s.shift # shift oldest entry
48
+ s2s.data_size # size of key+value entries
49
+ s2s.total_size # size of key+value entries + internal structures
50
+
51
+ # Str2Str is more memory efficient than storing string in a builtin hash
52
+ # also it is a bit faster.
53
+ # It tries to overwrite value inplace if it value's size not larger.
54
+ def timeit; t=Time.now; r=yield; ensure puts "Lasts: #{Time.now - t}"; r; end
55
+
56
+ timeit{ 1000000.times{|i| s2s[i.to_s] = "qwer#{i}"} }
57
+ timeit{ 1000000.times{|i| s2s[i.to_s] = "qwer#{i}"} }
58
+ hsh = {}
59
+ timeit{ 1000000.times{|i| hsh[i.to_s] = "qwer#{i}"} }
60
+ timeit{ 1000000.times{|i| hsh[i.to_s] = "qwer#{i}"} }
61
+
62
+ # cloning is made to be very fast:
63
+ # it does not copy key/value entries
64
+ # only internal structures are alloced and copied with memcpy
65
+ # key/value's reference count is incremented
66
+ timeit{ sts.dup }
67
+ timeit{ hsh.dup }
68
+ # clone is copy on write
69
+ cpy = sts.dup
70
+ sts['2'] = '!'
71
+ cpy['3'] = '!!'
72
+ sts['2'] != cpy['2']
73
+ sts['3'] != cpy['3']
74
+ ```
75
+
76
+ ## Contributing
77
+
78
+ 1. Fork it ( https://github.com/funny-falcon/inmemory_kv/fork )
79
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
80
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
81
+ 4. Push to the branch (`git push origin my-new-feature`)
82
+ 5. Create a new Pull Request
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do
5
+ end
6
+
@@ -0,0 +1,4 @@
1
+ require 'mkmf'
2
+ have_func('malloc_usable_size')
3
+ have_func('rb_memhash')
4
+ create_makefile("inmemory_kv")
@@ -0,0 +1,809 @@
1
+ #include <ruby/ruby.h>
2
+ #include <ruby/intern.h>
3
+ #include <assert.h>
4
+ #include <malloc.h>
5
+
6
+ #include <stdio.h>
7
+ #ifdef HAV_STDLIB_H
8
+ #include <stdlib.h>
9
+ #endif
10
+ #include <string.h>
11
+
12
+ typedef unsigned int u32;
13
+
14
+ typedef struct hash_item {
15
+ u32 pos;
16
+ u32 rc;
17
+ u32 key_size;
18
+ u32 val_size;
19
+ u32 val_size_max;
20
+ char key[0];
21
+ } hash_item;
22
+
23
+ static inline char*
24
+ item_val(hash_item* item) {
25
+ return item->key + item->key_size;
26
+ }
27
+
28
+ typedef struct hash_entry {
29
+ u32 hash;
30
+ u32 next;
31
+ u32 fwd;
32
+ u32 prev;
33
+ hash_item* item;
34
+ } hash_entry;
35
+
36
+ typedef struct hash_table {
37
+ hash_entry* entries;
38
+ u32* buckets;
39
+ u32 size;
40
+ u32 alloced;
41
+ u32 empty;
42
+ u32 first;
43
+ u32 last;
44
+ u32 nbuckets;
45
+ } hash_table;
46
+
47
+ static const u32 end = (u32)0 - 1;
48
+
49
+ static u32 hash_first(hash_table* tab);
50
+ static u32 hash_next(hash_table* tab, u32 pos);
51
+ static u32 hash_hash_first(hash_table* tab, u32 hash);
52
+ static u32 hash_hash_next(hash_table* tab, u32 hash, u32 pos);
53
+ static u32 hash_insert(hash_table* tab, u32 hash);
54
+ static void hash_up(hash_table* tab, u32 pos);
55
+ static void hash_delete(hash_table* tab, u32 pos);
56
+ static void hash_destroy(hash_table* tab);
57
+ static size_t hash_memsize(const hash_table* tab) {
58
+ return tab->alloced * sizeof(hash_entry) +
59
+ tab->nbuckets * sizeof(u32);
60
+ }
61
+
62
+ static u32
63
+ hash_first(hash_table* tab) {
64
+ return tab->first - 1;
65
+ }
66
+
67
+ static u32
68
+ hash_next(hash_table* tab, u32 pos) {
69
+ if (pos == end || tab->alloced < pos) {
70
+ return end;
71
+ }
72
+ return tab->entries[pos].fwd - 1;
73
+ }
74
+
75
+ static u32
76
+ hash_hash_first(hash_table* tab, u32 hash) {
77
+ u32 buc, pos;
78
+ if (tab->size == 0) return end;
79
+ buc = hash % tab->nbuckets;
80
+ pos = tab->buckets[buc] - 1;
81
+ while (pos != end && tab->entries[pos].hash != hash) {
82
+ pos = tab->entries[pos].next - 1;
83
+ }
84
+ return pos;
85
+ }
86
+
87
+ static u32
88
+ hash_hash_next(hash_table* tab, u32 hash, u32 pos) {
89
+ if (pos == end || tab->size == 0) return end;
90
+ do {
91
+ pos = tab->entries[pos].next - 1;
92
+ } while (pos != end && tab->entries[pos].hash != hash);
93
+ return pos;
94
+ }
95
+
96
+ #if 0
97
+ static void
98
+ hash_print(hash_table* tab, const char* act, u32 pos) {
99
+ u32 i;
100
+ printf("%s %d size: %d first: %d last: %d\n", act, pos, tab->size, tab->first-1, tab->last-1);
101
+ i = tab->first;
102
+ while(i-1!=end) {
103
+ hash_entry* e = tab->entries + (i-1);
104
+ printf("\tpos: %d prev: %d fwd: %d\n", i-1, e->prev-1, e->fwd-1);
105
+ i = tab->entries[i-1].fwd;
106
+ }
107
+ }
108
+ #else
109
+ #define hash_print(tab, act, pos)
110
+ #endif
111
+
112
+ static inline void
113
+ hash_enchain(hash_table* tab, u32 pos) {
114
+ tab->entries[pos].prev = tab->last;
115
+ if (tab->first == 0) {
116
+ tab->first = pos+1;
117
+ } else {
118
+ tab->entries[tab->last-1].fwd = pos+1;
119
+ }
120
+ tab->last = pos+1;
121
+ hash_print(tab, "enchain", pos);
122
+ }
123
+
124
+ static inline void
125
+ hash_enchain_first(hash_table* tab, u32 pos) {
126
+ tab->entries[pos].fwd = tab->first;
127
+ if (tab->last == 0) {
128
+ tab->last = pos+1;
129
+ } else {
130
+ tab->entries[tab->first-1].prev = pos+1;
131
+ }
132
+ tab->first = pos+1;
133
+ hash_print(tab, "enchain first", pos);
134
+ }
135
+
136
+ static inline void
137
+ hash_unchain(hash_table* tab, u32 pos) {
138
+ if (tab->first == pos+1) {
139
+ tab->first = tab->entries[pos].fwd;
140
+ } else {
141
+ tab->entries[tab->entries[pos].prev-1].fwd = tab->entries[pos].fwd;
142
+ }
143
+ if (tab->last == pos+1) {
144
+ tab->last = tab->entries[pos].prev;
145
+ } else {
146
+ tab->entries[tab->entries[pos].fwd-1].prev = tab->entries[pos].prev;
147
+ }
148
+ tab->entries[pos].fwd = 0;
149
+ tab->entries[pos].prev = 0;
150
+ hash_print(tab, "unchain", pos);
151
+ }
152
+
153
+ static void
154
+ hash_up(hash_table* tab, u32 pos) {
155
+ assert(tab->entries[pos].hash != 0);
156
+ if (tab->last == pos+1) return;
157
+ hash_unchain(tab, pos);
158
+ hash_enchain(tab, pos);
159
+ }
160
+
161
+ static void
162
+ hash_down(hash_table* tab, u32 pos) {
163
+ assert(tab->entries[pos].hash != 0);
164
+ if (tab->first == pos+1) return;
165
+ hash_unchain(tab, pos);
166
+ hash_enchain_first(tab, pos);
167
+ }
168
+
169
+ static u32
170
+ hash_insert(hash_table* tab, u32 hash) {
171
+ u32 i, pos, buc, npos;
172
+ if (tab->size == tab->alloced) {
173
+ u32 new_alloced = tab->alloced ? tab->alloced * 1.5 : 32;
174
+ tab->entries = realloc(tab->entries,
175
+ sizeof(hash_entry)*new_alloced);
176
+ assert(tab->entries);
177
+ memset(tab->entries + tab->alloced, 0,
178
+ sizeof(hash_entry)*(new_alloced - tab->alloced));
179
+ for (i=tab->alloced; i<new_alloced-1; i++) {
180
+ tab->entries[i].next = i+2;
181
+ }
182
+ tab->empty = tab->alloced+1;
183
+ tab->alloced = new_alloced;
184
+ }
185
+ if (tab->size >= tab->nbuckets * 2) {
186
+ u32 new_nbuckets = tab->nbuckets ? (tab->nbuckets+1)*2-1 : 15;
187
+ free(tab->buckets);
188
+ tab->buckets = calloc(new_nbuckets, sizeof(u32));
189
+ for (i=0; i<tab->alloced; i++) {
190
+ if (tab->entries[i].hash == 0)
191
+ continue;
192
+ buc = tab->entries[i].hash % new_nbuckets;
193
+ npos = tab->buckets[buc];
194
+ tab->entries[i].next = npos;
195
+ tab->buckets[buc] = i+1;
196
+ }
197
+ tab->nbuckets = new_nbuckets;
198
+ }
199
+ buc = hash % tab->nbuckets;
200
+ npos = tab->buckets[buc];
201
+ pos = tab->empty - 1;
202
+ assert(pos != end);
203
+ tab->buckets[buc] = pos + 1;
204
+ tab->empty = tab->entries[pos].next;
205
+ tab->entries[pos].hash = hash;
206
+ tab->entries[pos].item = NULL;
207
+ tab->entries[pos].next = npos;
208
+ tab->entries[pos].fwd = 0;
209
+ hash_enchain(tab, pos);
210
+ tab->size++;
211
+ return pos;
212
+ }
213
+
214
+ static void
215
+ hash_delete(hash_table* tab, u32 pos) {
216
+ u32 hash, buc, i, j;
217
+ hash = tab->entries[pos].hash;
218
+ buc = hash % tab->nbuckets;
219
+ i = tab->buckets[buc] - 1;
220
+ j = end;
221
+ while (i != pos && i != end) {
222
+ j = i;
223
+ i = tab->entries[i].next - 1;
224
+ }
225
+ assert(i != end && i == pos);
226
+ if (j == end) {
227
+ tab->buckets[buc] = tab->entries[i].next;
228
+ } else {
229
+ tab->entries[j].next = tab->entries[i].next;
230
+ }
231
+ tab->entries[i].next = tab->empty;
232
+ hash_unchain(tab, i);
233
+ tab->empty = i+1;
234
+ tab->entries[i].hash = 0;
235
+ tab->size--;
236
+ }
237
+
238
+ static void
239
+ hash_destroy(hash_table* tab) {
240
+ free(tab->entries);
241
+ free(tab->buckets);
242
+ }
243
+
244
+ typedef struct inmemory_kv {
245
+ hash_table tab;
246
+ size_t total_size;
247
+ } inmemory_kv;
248
+
249
+ static hash_item* kv_insert(inmemory_kv *kv, const char* key, u32 key_size, const char* val, u32 val_size);
250
+ static hash_item* kv_fetch(inmemory_kv *kv, const char* key, u32 key_size);
251
+ static void kv_up(inmemory_kv *kv, hash_item* item);
252
+ static void kv_delete(inmemory_kv *kv, hash_item* item);
253
+ static hash_item* kv_first(inmemory_kv *kv);
254
+
255
+ typedef void (*kv_each_cb)(hash_item* item, void* arg);
256
+ static void kv_each(inmemory_kv *kv, kv_each_cb cb, void* arg);
257
+
258
+ static void kv_copy_to(inmemory_kv *from, inmemory_kv *to);
259
+
260
+ #ifdef HAV_RB_MEMHASH
261
+ static inline u32
262
+ kv_hash(const char* key, u32 key_size) {
263
+ u32 hash = rb_memhash(key, key_size);
264
+ return hash ? hash : ~(u32)0;
265
+ }
266
+ #else
267
+ static inline u32
268
+ kv_hash(const char* key, u32 key_size) {
269
+ u32 a1 = 0xdeadbeef, a2 = 0x71fefeed;
270
+ u32 i;
271
+ for (i = 0; i<key_size; i++) {
272
+ unsigned char k = key[i];
273
+ a1 = (a1 + k) * 5;
274
+ a2 = (a2 ^ k) * 9;
275
+ }
276
+ a1 ^= key_size; a1 *= 5; a2 *= 9;
277
+ a1 ^= a2;
278
+ return a1 ? a1 : ~(u32)0;
279
+ }
280
+ #endif
281
+
282
+ static hash_item*
283
+ kv_insert(inmemory_kv *kv, const char* key, u32 key_size, const char* val, u32 val_size) {
284
+ u32 hash = kv_hash(key, key_size);
285
+ u32 pos;
286
+ hash_item* item;
287
+ pos = hash_hash_first(&kv->tab, hash);
288
+ while (pos != end) {
289
+ item = kv->tab.entries[pos].item;
290
+ if (item->key_size == key_size &&
291
+ memcmp(key, item->key, key_size) == 0) {
292
+ break;
293
+ }
294
+ pos = hash_hash_next(&kv->tab, hash, pos);
295
+ }
296
+ if (pos == end) {
297
+ pos = hash_insert(&kv->tab, hash);
298
+ item = NULL;
299
+ } else {
300
+ hash_up(&kv->tab, pos);
301
+ if (val_size > item->val_size_max || item->rc > 0) {
302
+ kv->total_size -= item->key_size + item->val_size_max + sizeof(*item);
303
+ if (item->rc > 0)
304
+ item->rc--;
305
+ else
306
+ free(item);
307
+ item = NULL;
308
+ }
309
+ }
310
+ if (item == NULL) {
311
+ #ifdef HAVE_MALLOC_USABLE_SIZE
312
+ item = malloc(sizeof(*item) + key_size + val_size);
313
+ assert(item);
314
+ item->rc = 0;
315
+ item->val_size_max = malloc_usable_size(item) - sizeof(*item) - key_size;
316
+ #else
317
+ u32 val_size_max = sizeof(*item) + key_size + val_size;
318
+ val_size_max = (val_size_max + 15) & 15;
319
+ item = malloc(sizeof(*item) + key_size + val_size_max);
320
+ assert(item);
321
+ item->rc = 0;
322
+ item->val_size_max = val_size_max;
323
+ #endif
324
+ kv->total_size += key_size + item->val_size_max + sizeof(*item);
325
+ item->key_size = key_size;
326
+ item->pos = pos;
327
+ memcpy(item->key, key, key_size);
328
+ }
329
+ item->val_size = val_size;
330
+ memcpy(item_val(item), val, val_size);
331
+ kv->tab.entries[pos].item = item;
332
+ return item;
333
+ }
334
+
335
+ static hash_item*
336
+ kv_fetch(inmemory_kv *kv, const char* key, u32 key_size) {
337
+ u32 hash = kv_hash(key, key_size);
338
+ u32 pos;
339
+ hash_item* item;
340
+ pos = hash_hash_first(&kv->tab, hash);
341
+ while (pos != end) {
342
+ item = kv->tab.entries[pos].item;
343
+ if (item->key_size == key_size &&
344
+ memcmp(key, item->key, key_size) == 0) {
345
+ break;
346
+ }
347
+ pos = hash_hash_next(&kv->tab, hash, pos);
348
+ }
349
+ return pos == end ? NULL : kv->tab.entries[pos].item;
350
+ }
351
+
352
+ static void
353
+ kv_up(inmemory_kv *kv, hash_item* item) {
354
+ hash_up(&kv->tab, item->pos);
355
+ }
356
+
357
+ static void
358
+ kv_down(inmemory_kv *kv, hash_item* item) {
359
+ hash_down(&kv->tab, item->pos);
360
+ }
361
+
362
+ static void
363
+ kv_delete(inmemory_kv *kv, hash_item* item) {
364
+ hash_delete(&kv->tab, item->pos);
365
+ kv->total_size -= sizeof(*item) + item->key_size + item->val_size_max;
366
+ if (item->rc > 0) {
367
+ item->rc--;
368
+ } else {
369
+ free(item);
370
+ }
371
+ }
372
+
373
+ static hash_item*
374
+ kv_first(inmemory_kv *kv) {
375
+ u32 pos = hash_first(&kv->tab);
376
+ if (pos != end) {
377
+ return kv->tab.entries[pos].item;
378
+ }
379
+ return NULL;
380
+ }
381
+
382
+ static void
383
+ kv_each(inmemory_kv *kv, kv_each_cb cb, void* arg) {
384
+ u32 pos = hash_first(&kv->tab);
385
+ while (pos != end) {
386
+ cb(kv->tab.entries[pos].item, arg);
387
+ pos = hash_next(&kv->tab, pos);
388
+ }
389
+ }
390
+
391
+ static void
392
+ kv_destroy(inmemory_kv *kv) {
393
+ u32 i;
394
+ for (i=0; i<kv->tab.alloced; i++) {
395
+ if (kv->tab.entries[i].hash != 0) {
396
+ hash_item* item = kv->tab.entries[i].item;
397
+ if (item->rc > 0) {
398
+ item->rc--;
399
+ } else {
400
+ free(item);
401
+ }
402
+ }
403
+ }
404
+ hash_destroy(&kv->tab);
405
+ }
406
+
407
+ static void
408
+ kv_copy_to(inmemory_kv *from, inmemory_kv *to) {
409
+ kv_destroy(to);
410
+ *to = *from;
411
+ if (to->tab.alloced) {
412
+ u32 i;
413
+ to->tab.entries = malloc(to->tab.alloced*sizeof(hash_entry));
414
+ memcpy(to->tab.entries, from->tab.entries,
415
+ sizeof(hash_entry)*to->tab.alloced);
416
+ to->tab.buckets = malloc(to->tab.nbuckets*sizeof(u32));
417
+ memcpy(to->tab.buckets, from->tab.buckets,
418
+ sizeof(u32)*from->tab.nbuckets);
419
+ for (i=0; i<to->tab.alloced; i++) {
420
+ if (to->tab.entries[i].hash != 0) {
421
+ to->tab.entries[i].item->rc++;
422
+ }
423
+ }
424
+ }
425
+ }
426
+
427
+ static size_t
428
+ rb_kv_memsize(const void *p) {
429
+ if (p) {
430
+ const inmemory_kv* kv = p;
431
+ return sizeof(*kv) + kv->total_size + hash_memsize(&kv->tab);
432
+ }
433
+ return 0;
434
+ }
435
+
436
+ static void
437
+ rb_kv_destroy(void *p) {
438
+ if (p) {
439
+ inmemory_kv *kv = p;
440
+ kv_destroy(kv);
441
+ free(kv);
442
+ }
443
+ }
444
+
445
+ static const rb_data_type_t InMemoryKV_data_type = {
446
+ "InMemoryKV_C",
447
+ {NULL, rb_kv_destroy, rb_kv_memsize}
448
+ };
449
+ #define GetKV(value, pointer) \
450
+ TypedData_Get_Struct((value), inmemory_kv, &InMemoryKV_data_type, (pointer))
451
+
452
+ static VALUE
453
+ rb_kv_alloc(VALUE klass) {
454
+ inmemory_kv* kv = calloc(1, sizeof(inmemory_kv));
455
+ return TypedData_Wrap_Struct(klass, &InMemoryKV_data_type, kv);
456
+ }
457
+
458
+ static VALUE
459
+ rb_kv_get(VALUE self, VALUE vkey) {
460
+ inmemory_kv* kv;
461
+ const char *key;
462
+ size_t size;
463
+ hash_item* item;
464
+
465
+ GetKV(self, kv);
466
+ StringValue(vkey);
467
+ key = RSTRING_PTR(vkey);
468
+ size = RSTRING_LEN(vkey);
469
+ item = kv_fetch(kv, key, size);
470
+ if (item == NULL) return Qnil;
471
+ return rb_str_new(item_val(item), item->val_size);
472
+ }
473
+
474
+ static VALUE
475
+ rb_kv_up(VALUE self, VALUE vkey) {
476
+ inmemory_kv* kv;
477
+ const char *key;
478
+ size_t size;
479
+ hash_item* item;
480
+
481
+ GetKV(self, kv);
482
+ StringValue(vkey);
483
+ key = RSTRING_PTR(vkey);
484
+ size = RSTRING_LEN(vkey);
485
+ item = kv_fetch(kv, key, size);
486
+ if (item == NULL) return Qnil;
487
+ kv_up(kv, item);
488
+ return rb_str_new(item_val(item), item->val_size);
489
+ }
490
+
491
+ static VALUE
492
+ rb_kv_down(VALUE self, VALUE vkey) {
493
+ inmemory_kv* kv;
494
+ const char *key;
495
+ size_t size;
496
+ hash_item* item;
497
+
498
+ GetKV(self, kv);
499
+ StringValue(vkey);
500
+ key = RSTRING_PTR(vkey);
501
+ size = RSTRING_LEN(vkey);
502
+ item = kv_fetch(kv, key, size);
503
+ if (item == NULL) return Qnil;
504
+ kv_down(kv, item);
505
+ return rb_str_new(item_val(item), item->val_size);
506
+ }
507
+
508
+ static VALUE
509
+ rb_kv_include(VALUE self, VALUE vkey) {
510
+ inmemory_kv* kv;
511
+ const char *key;
512
+ size_t size;
513
+
514
+ GetKV(self, kv);
515
+ StringValue(vkey);
516
+ key = RSTRING_PTR(vkey);
517
+ size = RSTRING_LEN(vkey);
518
+ return kv_fetch(kv, key, size) ? Qtrue : Qfalse;
519
+ }
520
+
521
+ static VALUE
522
+ rb_kv_set(VALUE self, VALUE vkey, VALUE vval) {
523
+ inmemory_kv* kv;
524
+ const char *key, *val;
525
+ size_t ksize, vsize;
526
+
527
+ GetKV(self, kv);
528
+ StringValue(vkey);
529
+ StringValue(vval);
530
+ key = RSTRING_PTR(vkey);
531
+ ksize = RSTRING_LEN(vkey);
532
+ val = RSTRING_PTR(vval);
533
+ vsize = RSTRING_LEN(vval);
534
+
535
+ kv_insert(kv, key, ksize, val, vsize);
536
+
537
+ return vval;
538
+ }
539
+
540
+ static VALUE
541
+ rb_kv_del(VALUE self, VALUE vkey) {
542
+ inmemory_kv* kv;
543
+ const char *key;
544
+ size_t size;
545
+ hash_item* item;
546
+ VALUE res;
547
+
548
+ GetKV(self, kv);
549
+ StringValue(vkey);
550
+ key = RSTRING_PTR(vkey);
551
+ size = RSTRING_LEN(vkey);
552
+ item = kv_fetch(kv, key, size);
553
+ if (item == NULL) return Qnil;
554
+ res = rb_str_new(item_val(item), item->val_size);
555
+ kv_delete(kv, item);
556
+ return res;
557
+ }
558
+
559
+ static VALUE
560
+ rb_kv_first(VALUE self) {
561
+ inmemory_kv* kv;
562
+ hash_item* item;
563
+ VALUE key, val;
564
+
565
+ GetKV(self, kv);
566
+ item = kv_first(kv);
567
+ if (item == NULL) return Qnil;
568
+ key = rb_str_new(item->key, item->key_size);
569
+ val = rb_str_new(item_val(item), item->val_size);
570
+ return rb_assoc_new(key, val);
571
+ }
572
+
573
+ static VALUE
574
+ rb_kv_shift(VALUE self) {
575
+ inmemory_kv* kv;
576
+ hash_item* item;
577
+ VALUE key, val;
578
+
579
+ GetKV(self, kv);
580
+ item = kv_first(kv);
581
+ if (item == NULL) return Qnil;
582
+ key = rb_str_new(item->key, item->key_size);
583
+ val = rb_str_new(item_val(item), item->val_size);
584
+ kv_delete(kv, item);
585
+ return rb_assoc_new(key, val);
586
+ }
587
+
588
+ static VALUE
589
+ rb_kv_unshift(VALUE self, VALUE vkey, VALUE vval) {
590
+ inmemory_kv* kv;
591
+ const char *key, *val;
592
+ size_t ksize, vsize;
593
+ hash_item* item;
594
+
595
+ GetKV(self, kv);
596
+ StringValue(vkey);
597
+ StringValue(vval);
598
+ key = RSTRING_PTR(vkey);
599
+ ksize = RSTRING_LEN(vkey);
600
+ val = RSTRING_PTR(vval);
601
+ vsize = RSTRING_LEN(vval);
602
+
603
+ item = kv_insert(kv, key, ksize, val, vsize);
604
+ kv_down(kv, item);
605
+
606
+ return vval;
607
+ }
608
+
609
+ static VALUE
610
+ rb_kv_size(VALUE self) {
611
+ inmemory_kv* kv;
612
+ GetKV(self, kv);
613
+ return UINT2NUM(kv->tab.size);
614
+ }
615
+
616
+ static VALUE
617
+ rb_kv_empty_p(VALUE self) {
618
+ inmemory_kv* kv;
619
+ GetKV(self, kv);
620
+ return kv->tab.size ? Qfalse : Qtrue;
621
+ }
622
+
623
+ static VALUE
624
+ rb_kv_data_size(VALUE self) {
625
+ inmemory_kv* kv;
626
+ GetKV(self, kv);
627
+ return SIZET2NUM(kv->total_size);
628
+ }
629
+
630
+ static VALUE
631
+ rb_kv_total_size(VALUE self) {
632
+ inmemory_kv* kv;
633
+ GetKV(self, kv);
634
+ return SIZET2NUM(rb_kv_memsize(kv));
635
+ }
636
+
637
+ static void
638
+ keys_i(hash_item* item, void* arg) {
639
+ VALUE ary = (VALUE)arg;
640
+ rb_ary_push(ary, rb_str_new(item->key, item->key_size));
641
+ }
642
+
643
+ static void
644
+ vals_i(hash_item* item, void* arg) {
645
+ VALUE ary = (VALUE)arg;
646
+ rb_ary_push(ary, rb_str_new(item_val(item), item->val_size));
647
+ }
648
+
649
+ static void
650
+ pairs_i(hash_item* item, void* arg) {
651
+ VALUE ary = (VALUE)arg;
652
+ VALUE key, val;
653
+ key = rb_str_new(item->key, item->key_size);
654
+ val = rb_str_new(item_val(item), item->val_size);
655
+ rb_ary_push(ary, rb_assoc_new(key, val));
656
+ }
657
+
658
+ static VALUE
659
+ rb_kv_keys(VALUE self) {
660
+ inmemory_kv* kv;
661
+ VALUE res;
662
+ GetKV(self, kv);
663
+ res = rb_ary_new2(kv->tab.size);
664
+ kv_each(kv, keys_i, (void*)res);
665
+ return res;
666
+ }
667
+
668
+ static VALUE
669
+ rb_kv_vals(VALUE self) {
670
+ inmemory_kv* kv;
671
+ VALUE res;
672
+ GetKV(self, kv);
673
+ res = rb_ary_new2(kv->tab.size);
674
+ kv_each(kv, vals_i, (void*)res);
675
+ return res;
676
+ }
677
+
678
+ static VALUE
679
+ rb_kv_entries(VALUE self) {
680
+ inmemory_kv* kv;
681
+ VALUE res;
682
+ GetKV(self, kv);
683
+ res = rb_ary_new2(kv->tab.size);
684
+ kv_each(kv, pairs_i, (void*)res);
685
+ return res;
686
+ }
687
+
688
+ static void
689
+ key_i(hash_item* item, void* _ __attribute__((unused))) {
690
+ rb_yield(rb_str_new(item->key, item->key_size));
691
+ }
692
+
693
+ static void
694
+ val_i(hash_item* item, void* _ __attribute__((unused))) {
695
+ rb_yield(rb_str_new(item_val(item), item->val_size));
696
+ }
697
+
698
+ static void
699
+ pair_i(hash_item* item, void* _ __attribute__((unused))) {
700
+ VALUE key, val;
701
+ key = rb_str_new(item->key, item->key_size);
702
+ val = rb_str_new(item_val(item), item->val_size);
703
+ rb_yield(rb_assoc_new(key, val));
704
+ }
705
+
706
+ static VALUE
707
+ rb_kv_each_key(VALUE self) {
708
+ inmemory_kv* kv;
709
+ GetKV(self, kv);
710
+ RETURN_ENUMERATOR(self, 0, 0);
711
+ kv_each(kv, key_i, NULL);
712
+ return self;
713
+ }
714
+
715
+ static VALUE
716
+ rb_kv_each_val(VALUE self) {
717
+ inmemory_kv* kv;
718
+ GetKV(self, kv);
719
+ RETURN_ENUMERATOR(self, 0, 0);
720
+ kv_each(kv, val_i, NULL);
721
+ return self;
722
+ }
723
+
724
+ static VALUE
725
+ rb_kv_each(VALUE self) {
726
+ inmemory_kv* kv;
727
+ GetKV(self, kv);
728
+ RETURN_ENUMERATOR(self, 0, 0);
729
+ kv_each(kv, pair_i, NULL);
730
+ return self;
731
+ }
732
+
733
+ struct inspect_arg {
734
+ VALUE str, tmp;
735
+ };
736
+ static void
737
+ inspect_i(hash_item* item, void* arg) {
738
+ struct inspect_arg* a = arg;
739
+ VALUE ins;
740
+ rb_str_cat(a->tmp, item->key, item->key_size);
741
+ ins = rb_inspect(a->tmp);
742
+ rb_str_cat(a->str, " ", 1);
743
+ rb_str_cat(a->str, RSTRING_PTR(ins), RSTRING_LEN(ins));
744
+ rb_str_cat(a->str, "=>", 2);
745
+ rb_str_resize(ins, 0);
746
+ rb_str_resize(a->tmp, 0);
747
+ rb_str_buf_cat(a->tmp, item_val(item), item->val_size);
748
+ ins = rb_inspect(a->tmp);
749
+ rb_str_append(a->str, ins);
750
+ rb_str_resize(ins, 0);
751
+ rb_str_resize(a->tmp, 0);
752
+ }
753
+
754
+ static VALUE
755
+ rb_kv_inspect(VALUE self) {
756
+ struct inspect_arg ins;
757
+ inmemory_kv* kv;
758
+ GetKV(self, kv);
759
+ ins.str = rb_str_buf_new2("<");
760
+ rb_str_append(ins.str, rb_class_name(CLASS_OF(self)));
761
+ if (kv->tab.size != 0) {
762
+ ins.tmp = rb_str_buf_new(0);
763
+ kv_each(kv, inspect_i, &ins);
764
+ }
765
+ rb_str_buf_cat2(ins.str, ">");
766
+ return ins.str;
767
+ }
768
+
769
+ static VALUE
770
+ rb_kv_init_copy(VALUE self, VALUE orig) {
771
+ inmemory_kv *origin, *new;
772
+ GetKV(self, new);
773
+ GetKV(orig, origin);
774
+ kv_copy_to(origin, new);
775
+ return self;
776
+ }
777
+
778
+ void
779
+ Init_inmemory_kv() {
780
+ VALUE mod_inmemory_kv, cls_str2str;
781
+ mod_inmemory_kv = rb_define_module("InMemoryKV");
782
+ cls_str2str = rb_define_class_under(mod_inmemory_kv, "Str2Str", rb_cObject);
783
+ rb_define_alloc_func(cls_str2str, rb_kv_alloc);
784
+ rb_define_method(cls_str2str, "[]", rb_kv_get, 1);
785
+ rb_define_method(cls_str2str, "up", rb_kv_up, 1);
786
+ rb_define_method(cls_str2str, "down", rb_kv_down, 1);
787
+ rb_define_method(cls_str2str, "[]=", rb_kv_set, 2);
788
+ rb_define_method(cls_str2str, "unshift", rb_kv_unshift, 2);
789
+ rb_define_method(cls_str2str, "delete", rb_kv_del, 1);
790
+ rb_define_method(cls_str2str, "empty?", rb_kv_empty_p, 0);
791
+ rb_define_method(cls_str2str, "size", rb_kv_size, 0);
792
+ rb_define_method(cls_str2str, "count", rb_kv_size, 0);
793
+ rb_define_method(cls_str2str, "data_size", rb_kv_data_size, 0);
794
+ rb_define_method(cls_str2str, "total_size", rb_kv_total_size, 0);
795
+ rb_define_method(cls_str2str, "include?", rb_kv_include, 1);
796
+ rb_define_method(cls_str2str, "has_key?", rb_kv_include, 1);
797
+ rb_define_method(cls_str2str, "first", rb_kv_first, 0);
798
+ rb_define_method(cls_str2str, "shift", rb_kv_shift, 0);
799
+ rb_define_method(cls_str2str, "keys", rb_kv_keys, 0);
800
+ rb_define_method(cls_str2str, "values", rb_kv_vals, 0);
801
+ rb_define_method(cls_str2str, "entries", rb_kv_entries, 0);
802
+ rb_define_method(cls_str2str, "each_key", rb_kv_each_key, 0);
803
+ rb_define_method(cls_str2str, "each_value", rb_kv_each_val, 0);
804
+ rb_define_method(cls_str2str, "each_pair", rb_kv_each, 0);
805
+ rb_define_method(cls_str2str, "each", rb_kv_each, 0);
806
+ rb_define_method(cls_str2str, "inspect", rb_kv_inspect, 0);
807
+ rb_define_method(cls_str2str, "initialize_copy", rb_kv_init_copy, 1);
808
+ rb_include_module(cls_str2str, rb_mEnumerable);
809
+ }
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'inmemory_kv/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "inmemory_kv"
8
+ spec.version = InMemoryKV::VERSION
9
+ spec.authors = ["Sokolov Yura aka funny_falcon"]
10
+ spec.email = ["funny.falcon@gmail.com"]
11
+ spec.summary = %q{Simple in memory string/string hash}
12
+ spec.description = %q{Simple in memory string/string hash}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.extensions = ["ext/extconf.rb"]
20
+ spec.require_paths = ["lib", "ext"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.7"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "minitest"
25
+ end
@@ -0,0 +1,6 @@
1
+ require "inmemory_kv.so"
2
+ require "inmemory_kv/version"
3
+
4
+ module InMemoryKV
5
+ # Your code goes here...
6
+ end
@@ -0,0 +1,3 @@
1
+ module InMemoryKV
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,198 @@
1
+ require 'inmemory_kv'
2
+ require 'minitest/spec'
3
+ require 'minitest/autorun'
4
+
5
+ describe InMemoryKV::Str2Str do
6
+ let(:s2s) { InMemoryKV::Str2Str.new }
7
+ it "should be setable" do
8
+ s2s['asdf'] = 'qwer'
9
+ end
10
+ it "should be fetchable" do
11
+ s2s['asdf'].must_be_nil
12
+ end
13
+ it "should be inspected" do
14
+ s2s.inspect.must_equal '<InMemoryKV::Str2Str>'
15
+ end
16
+ it "is empty" do
17
+ s2s.must_be_empty
18
+ end
19
+ it "has zero size" do
20
+ s2s.size.must_equal 0
21
+ end
22
+ it "has zero data_size" do
23
+ s2s.data_size.must_equal 0
24
+ end
25
+ it "should have nil first" do
26
+ s2s.first.must_be_nil
27
+ end
28
+ it "should have no keys nor values nor entries" do
29
+ s2s.keys.must_be_empty
30
+ s2s.values.must_be_empty
31
+ s2s.entries.must_be_empty
32
+ end
33
+ it "should not iterate" do
34
+ s2s.each_key{ raise }
35
+ s2s.each_value{ raise }
36
+ s2s.each{ raise }
37
+ end
38
+
39
+ describe "filled with one item" do
40
+ before do
41
+ s2s['asdf'] = 'qwer'
42
+ end
43
+ it "should be inspectable" do
44
+ s2s.inspect.must_equal '<InMemoryKV::Str2Str "asdf"=>"qwer">'
45
+ end
46
+ it "should be fetchable" do
47
+ s2s['asdf'].must_equal 'qwer'
48
+ end
49
+ it "should has size == 1" do
50
+ s2s.size.must_equal 1
51
+ end
52
+ it "should has nonzero data_size" do
53
+ s2s.data_size.wont_equal 0
54
+ end
55
+ it "first should not be nil" do
56
+ s2s.first.must_equal ["asdf", "qwer"]
57
+ end
58
+ it "should react on delete" do
59
+ s2s.delete("asdf").must_equal('qwer')
60
+ s2s.size.must_equal 0
61
+ s2s.data_size.must_equal 0
62
+ s2s.first.must_be_nil
63
+ s2s.each{ raise }
64
+ end
65
+ it "should react on shift" do
66
+ s2s.shift.must_equal(['asdf','qwer'])
67
+ s2s.size.must_equal 0
68
+ s2s.data_size.must_equal 0
69
+ s2s.first.must_be_nil
70
+ s2s.each{ raise }
71
+ end
72
+ it "should allow rewrite value" do
73
+ s2s['asdf'] = 'zxcv'
74
+ s2s['asdf'].must_equal 'zxcv'
75
+ end
76
+ it "should allow dup, and perform copy on write" do
77
+ copy = s2s.dup
78
+ copy['asdf'].must_equal 'qwer'
79
+ s2s['asdf'] = 'zxcv'
80
+ s2s['asdf'].must_equal 'zxcv'
81
+ copy['asdf'].must_equal 'qwer'
82
+ end
83
+ end
84
+
85
+ describe "filled with two items" do
86
+ before do
87
+ s2s['asdf'] = 'qwer'
88
+ s2s['qwer'] = 'zxcv'
89
+ end
90
+ it "should be inspectable" do
91
+ s2s.inspect.must_equal '<InMemoryKV::Str2Str "asdf"=>"qwer" "qwer"=>"zxcv">'
92
+ end
93
+ it "should be fetchable" do
94
+ s2s['asdf'].must_equal 'qwer'
95
+ s2s['qwer'].must_equal 'zxcv'
96
+ end
97
+ it "should has size == 2" do
98
+ s2s.size.must_equal 2
99
+ end
100
+ it "first should not be nil" do
101
+ s2s.first.must_equal ["asdf", "qwer"]
102
+ end
103
+ it "should react on delete" do
104
+ s2s.delete("asdf").must_equal('qwer')
105
+ s2s.size.must_equal 1
106
+ s2s.first.must_equal ['qwer', 'zxcv']
107
+ s2s.delete("qwer").must_equal('zxcv')
108
+ s2s.data_size.must_equal 0
109
+ s2s.each{ raise }
110
+ end
111
+ it "should react on shift" do
112
+ s2s.shift.must_equal(['asdf','qwer'])
113
+ s2s.size.must_equal 1
114
+ s2s.shift.must_equal ['qwer', 'zxcv']
115
+ s2s.size.must_equal 0
116
+ s2s.data_size.must_equal 0
117
+ s2s.first.must_be_nil
118
+ s2s.each{ raise }
119
+ end
120
+ it "should reorder on set" do
121
+ s2s['asdf'] = 'yuio'
122
+ s2s.first.must_equal ['qwer', 'zxcv']
123
+ s2s.entries.must_equal [
124
+ ['qwer', 'zxcv'],
125
+ ['asdf', 'yuio']
126
+ ]
127
+ end
128
+ it "should allow explicit reorder" do
129
+ s2s.up 'asdf'
130
+ s2s.entries.must_equal [
131
+ ['qwer', 'zxcv'],
132
+ ['asdf', 'qwer']
133
+ ]
134
+ s2s.down 'asdf'
135
+ s2s.entries.must_equal [
136
+ ['asdf', 'qwer'],
137
+ ['qwer', 'zxcv']
138
+ ]
139
+ end
140
+ it "should allow dup, and perform copy on write" do
141
+ copy = s2s.dup
142
+ copy['asdf'].must_equal 'qwer'
143
+ s2s['asdf'] = 'zxcv'
144
+ s2s['asdf'].must_equal 'zxcv'
145
+ copy['asdf'].must_equal 'qwer'
146
+
147
+ s2s['qwer'].must_equal 'zxcv'
148
+ copy['qwer'].must_equal 'zxcv'
149
+ end
150
+ end
151
+
152
+ describe "huge filled" do
153
+ let(:num) { 1000 }
154
+ before do
155
+ num.times do |i|
156
+ s2s[i.to_s] = "q#{i}"
157
+ end
158
+ end
159
+ let(:hsh) do
160
+ h = {}
161
+ num.times do |i|
162
+ h[i.to_s] = "q#{i}"
163
+ end
164
+ h
165
+ end
166
+ it "should store all values" do
167
+ num.times do |i|
168
+ s2s[i.to_s].must_equal "q#{i}"
169
+ end
170
+ end
171
+ it "should report entries" do
172
+ s2s.entries.must_equal hsh.entries
173
+ end
174
+ it "should delete entries" do
175
+ 2.step(num, 2) do |i|
176
+ s2s.delete(i.to_s).must_equal hsh.delete(i.to_s)
177
+ end
178
+ 1.step(num, 2) do |i|
179
+ s2s[i.to_s].must_equal hsh[i.to_s]
180
+ end
181
+ s2s.size.must_equal hsh.size
182
+ s2s.entries.must_equal hsh.entries
183
+ end
184
+ it "should reorder on set" do
185
+ s2s.first.must_equal ['0', 'q0']
186
+ s2s['0'] = 'ya'
187
+ s2s.first.must_equal ['1', 'q1']
188
+ s2s.entries.last.must_equal ['0', 'ya']
189
+ end
190
+ it "should allow explicit reorder" do
191
+ s2s.up '235'
192
+ s2s.entries.last.must_equal ['235', 'q235']
193
+ s2s.down '235'
194
+ s2s.first.must_equal ['235', 'q235']
195
+ s2s.entries.last.wont_equal ['235', 'q235']
196
+ end
197
+ end
198
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: inmemory_kv
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sokolov Yura aka funny_falcon
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-06-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Simple in memory string/string hash
56
+ email:
57
+ - funny.falcon@gmail.com
58
+ executables: []
59
+ extensions:
60
+ - ext/extconf.rb
61
+ extra_rdoc_files: []
62
+ files:
63
+ - .gitignore
64
+ - Gemfile
65
+ - LICENSE.txt
66
+ - README.md
67
+ - Rakefile
68
+ - ext/extconf.rb
69
+ - ext/inmemory_kv.c
70
+ - inmemory_kv.gemspec
71
+ - lib/inmemory_kv.rb
72
+ - lib/inmemory_kv/version.rb
73
+ - test/test_str2str.rb
74
+ homepage: ''
75
+ licenses:
76
+ - MIT
77
+ metadata: {}
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ - ext
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubyforge_project:
95
+ rubygems_version: 2.4.3
96
+ signing_key:
97
+ specification_version: 4
98
+ summary: Simple in memory string/string hash
99
+ test_files:
100
+ - test/test_str2str.rb