fat 0.0.5 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  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