inmemory_kv 0.1.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.
@@ -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