fat 0.0.5 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fffbd3df88760a30ab94ec780231688a17944655
4
- data.tar.gz: 9bdbe4cff365157a89f42cde46e5de3b94112abc
3
+ metadata.gz: 7cb164e13d70c6ba83c7a6388dee09b09f1c16ad
4
+ data.tar.gz: c83ade83a11961ed0e878f4be1f983864d02f185
5
5
  SHA512:
6
- metadata.gz: 39307004150f2606963b21e8f8adae44d269b2cb7f01142ff333fd6740a098f881672259c738f5282523330d2cfab6de452f8c330923f2569c42591212409144
7
- data.tar.gz: 11adbb07becc3a42b6c480aa7fc23d5598142932f66e12b20b98604e8307b65f8fdcf62ee590ed46868c26f4c9f57a72dd926abb23d7034f286384f46bba18f4
6
+ metadata.gz: dcbb1ecdb3c05e52ce5575fc5c4f5f9c46c25ab591e63237fcbe5b7efb1ec78385b7e8e4037c8bf614095486de2bd48de102d1817c24e4699859e47fdf5831b8
7
+ data.tar.gz: 1c4e40eafe1e97a2eee88b5f2042d3189575d3fadd223ec0c30570c49546395a4d9b1f238bdd2f30e4af202bbdde40a05fc0af05f836cad0266f439eebe48b80
data/.gitignore CHANGED
@@ -1,2 +1,3 @@
1
1
  tmp/
2
2
  *.bundle
3
+ lib/*.dSYM
data/README.md CHANGED
@@ -21,7 +21,7 @@ hash = {
21
21
 
22
22
  To get your `:value` you usually do `hash["foo"]["bar"]["baz"]`. But what happens if `"bar"` doesn't exist? Yeap, BOOM!
23
23
 
24
- I find more comfortable to ask if I can walk to `:value` using the keys `"foo"`, `"bar"`, `"baz"`. If I can't, give me some `nil`.
24
+ I find more comfortable to ask if I can walk to `:value` using the keys `"foo"`, `"bar"`, `"baz"`. If I can't, tell me where the path ends.
25
25
 
26
26
  ```ruby
27
27
  require "fat"
@@ -30,9 +30,11 @@ Fat.at(hash, "foo", "bar", "baz")
30
30
  # => :value
31
31
 
32
32
  Fat.at(hash, "foo", "not", "here")
33
- # => nil
33
+ # => Fat::FatError: No hash found at foo.not
34
34
  ```
35
35
 
36
+ The `Fat::FatError` let's you know that a `Hash` was expected as a value for the key `"not"`. Way more explicit than guessing from a `undefined method [] for nil`.
37
+
36
38
  It's the same with Symbols
37
39
 
38
40
  ```ruby
@@ -57,7 +59,7 @@ hash.at("foo", "bar", "baz")
57
59
  # => :value
58
60
  ```
59
61
 
60
- And the last one! If all your keys are Strings you can *namespace* them with dots.
62
+ If all your keys are Strings you can *namespace* them with dots.
61
63
 
62
64
  ```ruby
63
65
  Hash.include(Fat)
@@ -8,10 +8,9 @@ class Hash
8
8
  fields = args.length == 1 ? args[0].split(".") : args
9
9
  value = self
10
10
 
11
- fields.each do |field|
12
- value = self[field]
13
- return unless value
14
- return value unless value.kind_of?(Hash)
11
+ fields[0..-2].each_with_index do |field, index|
12
+ value = value[field]
13
+ raise Fat::FatError, "No hash found at #{fields[0..index].join(".")}" unless value.kind_of?(Hash)
15
14
  end
16
15
 
17
16
  value
@@ -30,21 +29,23 @@ hash = {
30
29
 
31
30
  puts "### String chain as a single argument."
32
31
  Benchmark.ips do |bench|
33
- bench.report("ruby") { hash.ruby_at("foo.bar.baz") }
34
- bench.report("c") { hash.at("foo.bar.baz") }
32
+ bench.report("ruby") { hash.ruby_at("foo.bar.baz.key") }
33
+ bench.report("c") { hash.at("foo.bar.baz.key") }
35
34
  bench.compare!
36
35
  end
37
36
 
38
- # ### String chain as a single argument.
37
+ ### String chain as a single argument.
39
38
  # Calculating -------------------------------------
40
- # ruby 47304 i/100ms
41
- # c 55888 i/100ms
39
+ # ruby 37856 i/100ms
40
+ # c 47169 i/100ms
42
41
  # -------------------------------------------------
43
- # ruby 741487.110.6%) i/s - 3642408 in 5.002662s
44
- # c 977630.911.3%) i/s - 4806368 in 5.001422s
42
+ # ruby 538803.87.3%) i/s - 2687776 in 5.026742s
43
+ # c 722313.87.2%) i/s - 3584844 in 5.006989s
44
+ #
45
45
  # Comparison:
46
- # c: 977630.9 i/s
47
- # ruby: 741487.1 i/s - 1.32x slower
46
+ # c: 722313.8 i/s
47
+ # ruby: 538803.8 i/s - 1.34x slower
48
+
48
49
 
49
50
  puts "### Each key as an argument."
50
51
  Benchmark.ips do |bench|
@@ -53,84 +54,108 @@ Benchmark.ips do |bench|
53
54
  bench.compare!
54
55
  end
55
56
 
56
- # ### Each key as an argument.
57
+ ### Each key as an argument.
57
58
  # Calculating -------------------------------------
58
- # ruby 57145 i/100ms
59
- # c 73161 i/100ms
59
+ # ruby 59783 i/100ms
60
+ # c 74832 i/100ms
60
61
  # -------------------------------------------------
61
- # ruby 1035908.29.7%) i/s - 5143050 in 5.036346s
62
- # c 1513478.511.1%) i/s - 7462422 in 5.017305s
62
+ # ruby 1074906.73.1%) i/s - 5380470 in 5.010624s
63
+ # c 1590070.04.9%) i/s - 7932192 in 5.004163s
64
+ #
63
65
  # Comparison:
64
- # c: 1513478.5 i/s
65
- # ruby: 1035908.2 i/s - 1.46x slower
66
+ # c: 1590070.0 i/s
67
+ # ruby: 1074906.7 i/s - 1.48x slower
66
68
 
67
69
  puts "### No value found."
68
70
  Benchmark.ips do |bench|
69
- bench.report("ruby") { hash.ruby_at("foo.one.key") }
70
- bench.report("c") { hash.at("foo.one.key") }
71
+ bench.report("ruby") { hash.ruby_at("foo.one.key") rescue Fat::FatError }
72
+ bench.report("c") { hash.at("foo.one.key") rescue Fat::FatError }
71
73
  bench.compare!
72
74
  end
73
75
 
74
- # ### No value found.
76
+ ### No value found.
75
77
  # Calculating -------------------------------------
76
- # ruby 46842 i/100ms
77
- # c 62515 i/100ms
78
+ # ruby 17493 i/100ms
79
+ # c 18993 i/100ms
78
80
  # -------------------------------------------------
79
- # ruby 733070.010.0%) i/s - 3653676 in 5.052594s
80
- # c 1103249.911.2%) i/s - 5438805 in 5.026985s
81
+ # ruby 208018.14.9%) i/s - 1049580 in 5.060661s
82
+ # c 225204.05.1%) i/s - 1139580 in 5.074972s
83
+ #
81
84
  # Comparison:
82
- # c: 1103249.9 i/s
83
- # ruby: 733070.0 i/s - 1.50x slower
85
+ # c: 225204.0 i/s
86
+ # ruby: 208018.1 i/s - 1.08x slower
87
+
88
+ deep_hash = {}
89
+ 1.upto(100) do |n|
90
+ key = (1...n).to_a.join(".")
91
+ current_hash = deep_hash.at(key)
92
+ current_hash[n.to_s] = {}
93
+ end
84
94
 
85
- deep_hash = {
86
- "foo" => {
87
- "bar" => {
88
- "baz" => {
89
- "and" => {
90
- "one" => {
91
- "more" => {
92
- "key" => :value
93
- }
94
- }
95
- }
96
- }
97
- }
98
- }
99
- }
95
+ path_to_100 = 1.upto(100).to_a.map(&:to_s)
96
+
97
+ deep_hash.at(path_to_100.join("."))["foo"] = :bar
98
+
99
+ path_to_foo = path_to_100 << "foo"
100
100
 
101
101
  puts "### Deep hash - String chain argument."
102
102
  Benchmark.ips do |bench|
103
- bench.report("ruby") { deep_hash.ruby_at("foo.bar.baz.and.one.more.key") }
104
- bench.report("c") { deep_hash.at("foo.bar.baz.and.one.more.key") }
103
+ bench.report("ruby") { deep_hash.ruby_at(path_to_foo.join(".")) }
104
+ bench.report("c") { deep_hash.at(path_to_foo.join(".")) }
105
105
  bench.compare!
106
106
  end
107
107
 
108
- # ### Deep hash - String chain argument.
108
+ ### Deep hash - String chain argument.
109
109
  # Calculating -------------------------------------
110
- # ruby 35209 i/100ms
111
- # c 37201 i/100ms
110
+ # ruby 2549 i/100ms
111
+ # c 3633 i/100ms
112
112
  # -------------------------------------------------
113
- # ruby 490384.610.9%) i/s - 2429421 in 5.040102s
114
- # c 530276.410.0%) i/s - 2641271 in 5.057748s
113
+ # ruby 27061.44.1%) i/s - 135097 in 5.002624s
114
+ # c 37773.53.0%) i/s - 188916 in 5.006074s
115
+ #
115
116
  # Comparison:
116
- # c: 530276.4 i/s
117
- # ruby: 490384.6 i/s - 1.08x slower
117
+ # c: 37773.5 i/s
118
+ # ruby: 27061.4 i/s - 1.40x slower
118
119
 
119
120
  puts "### Deep hash - Each key as an argument."
120
121
  Benchmark.ips do |bench|
121
- bench.report("ruby") { deep_hash.ruby_at("foo", "bar", "baz", "and", "one", "more", "key") }
122
- bench.report("c") { deep_hash.at("foo", "bar", "baz", "and", "one", "more", "key") }
122
+ bench.report("ruby") { deep_hash.ruby_at(*path_to_foo) }
123
+ bench.report("c") { deep_hash.at(*path_to_foo) }
124
+ bench.compare!
125
+ end
126
+
127
+ ### Deep hash - Each key as an argument.
128
+ # Calculating -------------------------------------
129
+ # ruby 4812 i/100ms
130
+ # c 14712 i/100ms
131
+ # -------------------------------------------------
132
+ # ruby 49779.4 (±5.1%) i/s - 250224 in 5.042241s
133
+ # c 168563.4 (±3.4%) i/s - 853296 in 5.068311s
134
+ #
135
+ # Comparison:
136
+ # c: 168563.4 i/s
137
+ # ruby: 49779.4 i/s - 3.39x slower
138
+
139
+ path_to_not = 1.upto(99).to_a.map(&:to_s)
140
+ path_to_not << "not"
141
+ path_to_not << "100"
142
+
143
+ puts "### Deep hash - No value found."
144
+ Benchmark.ips do |bench|
145
+ bench.report("ruby") { deep_hash.ruby_at(*path_to_not) rescue Fat::FatError }
146
+ bench.report("c") { deep_hash.at(*path_to_not) rescue Fat::FatError }
123
147
  bench.compare!
124
148
  end
125
149
 
126
- # ### Deep hash - Each key as an argument.
150
+ ### Deep hash - No value found.
127
151
  # Calculating -------------------------------------
128
- # ruby 44009 i/100ms
129
- # c 46413 i/100ms
152
+ # ruby 2787 i/100ms
153
+ # c 8277 i/100ms
130
154
  # -------------------------------------------------
131
- # ruby 674854.410.2%) i/s - 3344684 in 5.040045s
132
- # c 734889.38.5%) i/s - 3666627 in 5.039101s
155
+ # ruby 30667.81.3%) i/s - 156072 in 5.090009s
156
+ # c 86884.15.1%) i/s - 438681 in 5.064367s
157
+ #
133
158
  # Comparison:
134
- # c: 734889.3 i/s
135
- # ruby: 674854.4 i/s - 1.09x slower
159
+ # c: 86884.1 i/s
160
+ # ruby: 30667.8 i/s - 2.83x slower
136
161
 
@@ -1,30 +1,30 @@
1
1
  #include<ruby.h>
2
2
 
3
3
  VALUE Fat = Qnil;
4
+ VALUE rb_eFatError = Qnil;
4
5
 
5
6
  void Init_fat();
6
7
 
7
8
  // Interface methods
8
9
  static VALUE singleton_method_at(int argc, VALUE *argv, VALUE self);
9
- static VALUE singleton_method_fetch_at(int argc, VALUE *argv, VALUE self);
10
10
  static VALUE method_at(int argc, VALUE *argv, VALUE hash);
11
- static VALUE method_fetch_at(int argc, VALUE *argv, VALUE hash);
12
11
 
13
- static VALUE fat(VALUE hash, VALUE fields, int raise_on_nil);
12
+ static VALUE fat(VALUE hash, VALUE fields);
14
13
 
15
14
  // Helpers
16
15
  static inline void parse_fields(VALUE args, VALUE *fields);
17
16
  static inline VALUE fields_upto_index(VALUE fields, long index);
18
17
  static inline void parse_singleton_args(int argc, VALUE *argv, VALUE *hash, VALUE *fields);
19
18
  static inline void parse_method_args(int argc, VALUE *argv, VALUE *fields);
19
+ static inline long compute_error_message_length(VALUE fields, long index);
20
+ static inline void copy_error_message(VALUE fields, long index, char* error_message_pointer);
20
21
 
21
22
  void Init_fat(void) {
22
23
  Fat = rb_define_module("Fat");
24
+ rb_eFatError = rb_define_class_under(Fat, "FatError", rb_eStandardError);
23
25
 
24
26
  rb_define_module_function(Fat, "at", singleton_method_at, -1);
25
- rb_define_module_function(Fat, "fetch_at", singleton_method_fetch_at, -1);
26
27
  rb_define_method(Fat, "at", method_at, -1);
27
- rb_define_method(Fat, "fetch_at", method_fetch_at, -1);
28
28
  }
29
29
 
30
30
  static VALUE singleton_method_at(int argc, VALUE *argv, VALUE self) {
@@ -33,16 +33,7 @@ static VALUE singleton_method_at(int argc, VALUE *argv, VALUE self) {
33
33
 
34
34
  parse_singleton_args(argc, argv, &hash, &fields);
35
35
 
36
- return fat(hash, fields, 0);
37
- }
38
-
39
- static VALUE singleton_method_fetch_at(int argc, VALUE *argv, VALUE self) {
40
- VALUE hash;
41
- VALUE fields;
42
-
43
- parse_singleton_args(argc, argv, &hash, &fields);
44
-
45
- return fat(hash, fields, 1);
36
+ return fat(hash, fields);
46
37
  }
47
38
 
48
39
  static VALUE method_at(int argc, VALUE *argv, VALUE hash) {
@@ -50,33 +41,17 @@ static VALUE method_at(int argc, VALUE *argv, VALUE hash) {
50
41
 
51
42
  parse_method_args(argc, argv, &fields);
52
43
 
53
- return fat(hash, fields, 0);
54
- }
55
-
56
- static VALUE method_fetch_at(int argc, VALUE *argv, VALUE hash) {
57
- VALUE fields;
58
-
59
- parse_method_args(argc, argv, &fields);
60
-
61
- return fat(hash, fields, 1);
44
+ return fat(hash, fields);
62
45
  }
63
46
 
64
- static VALUE fat(VALUE hash, VALUE fields, int raise_on_nil) {
47
+ static VALUE fat(VALUE hash, VALUE fields) {
65
48
  VALUE value = hash;
66
49
 
67
50
  for (long i = 0; i < RARRAY_LEN(fields); i++) {
68
51
  value = rb_hash_aref(value, RARRAY_AREF(fields, i));
69
52
 
70
- if (value == Qnil) {
71
- if (raise_on_nil) {
72
- rb_raise(rb_eKeyError, "No value found at %s", RSTRING_PTR(fields_upto_index(fields, i)));
73
- } else {
74
- return Qnil;
75
- }
76
- }
77
-
78
- if (TYPE(value) != T_HASH) {
79
- return value;
53
+ if (i < RARRAY_LEN(fields) - 1 && TYPE(value) != T_HASH) {
54
+ rb_raise(rb_eFatError, "No hash found at %s", RSTRING_PTR(fields_upto_index(fields, i)));
80
55
  }
81
56
  }
82
57
 
@@ -92,9 +67,11 @@ static inline void parse_fields(VALUE args, VALUE *fields) {
92
67
  }
93
68
 
94
69
  static inline VALUE fields_upto_index(VALUE fields, long index) {
95
- VALUE range = rb_range_new(INT2FIX(0), INT2FIX(index), 0);
96
- VALUE slice = rb_funcall(fields, rb_intern("slice"), 1, range);
97
- return rb_ary_join(slice, rb_str_new2("."));
70
+ char error_message_pointer[compute_error_message_length(fields, index)];
71
+
72
+ copy_error_message(fields, index, error_message_pointer);
73
+
74
+ return rb_str_new2(error_message_pointer);
98
75
  }
99
76
 
100
77
  static inline void parse_singleton_args(int argc, VALUE *argv, VALUE *hash, VALUE *fields) {
@@ -108,3 +85,48 @@ static inline void parse_method_args(int argc, VALUE *argv, VALUE *fields) {
108
85
  rb_scan_args(argc, argv, "*", &args);
109
86
  parse_fields(args, fields);
110
87
  }
88
+
89
+ static inline long compute_error_message_length(VALUE fields, long index) {
90
+ long error_length = 0;
91
+
92
+ for (long j = 0; j <= index; j++) {
93
+ VALUE field = RARRAY_AREF(fields, j);
94
+
95
+ if (TYPE(field) == T_SYMBOL) {
96
+ error_length += rb_str_length(rb_id2str(SYM2ID(field)));
97
+ } else {
98
+ error_length += RSTRING_LEN(field);
99
+ }
100
+
101
+ if (j != index) {
102
+ error_length++;
103
+ }
104
+ }
105
+
106
+ return error_length;
107
+ }
108
+
109
+ static inline void copy_error_message(VALUE fields, long index, char* error_message_pointer) {
110
+ char* current_char_pointer = error_message_pointer;
111
+
112
+ for (long j = 0; j <= index; j++) {
113
+ VALUE field = RARRAY_AREF(fields, j);
114
+
115
+ long size;
116
+ if (TYPE(field) == T_SYMBOL) {
117
+ size = rb_str_length(rb_id2str(SYM2ID(field)));
118
+ memcpy(current_char_pointer, RSTRING_PTR(rb_id2str(SYM2ID(field))), size);
119
+ } else {
120
+ size = RSTRING_LEN(field);
121
+ memcpy(current_char_pointer, RSTRING_PTR(field), size);
122
+ }
123
+
124
+ current_char_pointer += size;
125
+
126
+ if (j != index) {
127
+ memcpy(current_char_pointer, ".", 1);
128
+ current_char_pointer++;
129
+ }
130
+ }
131
+ }
132
+
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "fat"
3
- s.version = "0.0.5"
3
+ s.version = "0.1.0"
4
4
  s.summary = "C extension to find values in nested hashes without pain"
5
5
  s.description = s.summary
6
6
  s.authors = ["Lucas Tolchinsky"]
@@ -13,11 +13,9 @@ scope do
13
13
  end
14
14
 
15
15
  test "honor key type" do |hash|
16
- assert_equal nil, Fat.at(hash, "foo", :not, :found)
17
- assert_raise { Fat.fetch_at(hash, "foo", :not, :found) }
16
+ assert_raise(Fat::FatError) { Fat.at(hash, :foo, :not, :found) }
18
17
 
19
18
  assert_equal :found, Fat.at(hash, :foo, "bar", :baz)
20
- assert_equal :found, Fat.fetch_at(hash, :foo, "bar", :baz)
21
19
  end
22
20
  end
23
21
 
@@ -33,11 +31,9 @@ scope do
33
31
  end
34
32
 
35
33
  test "namespaced string keys" do |hash|
36
- assert_equal nil, Fat.at(hash, "foo.not.found")
37
- assert_raise { Fat.fetch_at(hash, "foo.not.found") }
34
+ assert_raise(Fat::FatError) { Fat.at(hash, "foo", :not, :found) }
38
35
 
39
36
  assert_equal :found, Fat.at(hash, "foo.bar.baz")
40
- assert_equal :found, Fat.fetch_at(hash, "foo.bar.baz")
41
37
  end
42
38
  end
43
39
 
@@ -56,15 +52,11 @@ scope do
56
52
 
57
53
  test "include the module" do |hash|
58
54
  assert hash.respond_to?(:at)
59
- assert hash.respond_to?(:fetch_at)
60
55
  end
61
56
 
62
57
  test "honor Fat interface" do |hash|
63
58
  assert_equal :found, hash.at("foo", "bar", "baz")
64
59
  assert_equal :found, hash.at("foo.bar.baz")
65
-
66
- assert_equal :found, hash.fetch_at("foo", "bar", "baz")
67
- assert_equal :found, hash.fetch_at("foo.bar.baz")
68
60
  end
69
61
  end
70
62
 
@@ -80,9 +72,19 @@ scope do
80
72
  }
81
73
  end
82
74
 
83
- test "break and return if a value is not a hash" do |hash|
84
- assert_equal :wat, Fat.at(hash, "foo.not_a_hash.baz")
85
- assert_equal :wat, Fat.fetch_at(hash, "foo.not_a_hash.baz")
75
+ test "raise error if a value is not a hash" do |hash|
76
+ assert_raise(Fat::FatError) { Fat.at(hash, "foo.not_a_hash.baz") }
77
+ end
78
+ end
79
+
80
+ scope do
81
+ setup do
82
+ Hash.include(Fat)
83
+ end
84
+
85
+ test "corner case" do
86
+ assert_raise(Fat::FatError) { {}.at(:foo, :bar) }
87
+ assert_raise(Fat::FatError) { Fat.at({}, :foo, :bar) }
86
88
  end
87
89
  end
88
90
 
@@ -0,0 +1,11 @@
1
+ require "cutest"
2
+ require_relative "../lib/fat"
3
+
4
+ # The point of this test is to check for memory leaks.
5
+ # The idea is to run the test suite forever and monitor the process with `htop`
6
+ # to see if the memory consumption increases.
7
+
8
+ loop do
9
+ Cutest.run_file(File.absolute_path("test/fat_test.rb"))
10
+ end
11
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lucas Tolchinsky
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-10-09 00:00:00.000000000 Z
11
+ date: 2014-10-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cutest
@@ -58,6 +58,7 @@ files:
58
58
  - fat.gemspec
59
59
  - lib/.gitkeep
60
60
  - test/fat_test.rb
61
+ - test/memleak.rb
61
62
  homepage: https://github.com/tonchis/fat
62
63
  licenses:
63
64
  - MIT