zvec-ruby 0.1.0-arm64-darwin

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,170 @@
1
+ require "minitest/autorun"
2
+ require "minitest/pride"
3
+ require "fileutils"
4
+ require "tmpdir"
5
+
6
+ # The C++ extension requires zvec to be installed. For pure-Ruby layer tests,
7
+ # we stub the Ext module when the native extension is unavailable.
8
+ begin
9
+ require "zvec"
10
+ NATIVE_EXT_AVAILABLE = true
11
+ rescue LoadError
12
+ NATIVE_EXT_AVAILABLE = false
13
+
14
+ # Minimal stubs so pure-Ruby logic can be tested without the compiled extension.
15
+ module Zvec
16
+ class Error < StandardError; end
17
+
18
+ module Ext
19
+ # Stub enums as simple modules with constants
20
+ module DataType
21
+ UNDEFINED = :undefined
22
+ BINARY = :binary; STRING = :string; BOOL = :bool
23
+ INT32 = :int32; INT64 = :int64; UINT32 = :uint32; UINT64 = :uint64
24
+ FLOAT = :float; DOUBLE = :double
25
+ VECTOR_FP32 = :vector_fp32; VECTOR_FP64 = :vector_fp64
26
+ VECTOR_FP16 = :vector_fp16; VECTOR_INT8 = :vector_int8
27
+ SPARSE_VECTOR_FP32 = :sparse_vector_fp32; SPARSE_VECTOR_FP16 = :sparse_vector_fp16
28
+ ARRAY_STRING = :array_string; ARRAY_INT32 = :array_int32
29
+ ARRAY_INT64 = :array_int64; ARRAY_FLOAT = :array_float
30
+ ARRAY_DOUBLE = :array_double; ARRAY_BOOL = :array_bool
31
+ end
32
+
33
+ module MetricType
34
+ UNDEFINED = :undefined; L2 = :l2; IP = :ip; COSINE = :cosine; MIPSL2 = :mipsl2
35
+ end
36
+
37
+ module IndexType
38
+ UNDEFINED = :undefined; HNSW = :hnsw; IVF = :ivf; FLAT = :flat; INVERT = :invert
39
+ end
40
+
41
+ module QuantizeType
42
+ UNDEFINED = :undefined; FP16 = :fp16; INT8 = :int8; INT4 = :int4
43
+ end
44
+
45
+ class Doc
46
+ attr_accessor :pk, :score
47
+ def initialize; @pk = ""; @score = 0.0; @fields = {}; end
48
+ def field_names; @fields.keys; end
49
+ def has?(f); @fields.key?(f); end
50
+ def has_value?(f); @fields.key?(f) && !@fields[f].nil?; end
51
+ def is_empty; @fields.empty?; end
52
+ alias_method :empty?, :is_empty
53
+ def set_null(f); @fields[f] = nil; end
54
+ def set_string(f, v); @fields[f] = v; end
55
+ def set_bool(f, v); @fields[f] = v; end
56
+ def set_int32(f, v); @fields[f] = v; end
57
+ def set_int64(f, v); @fields[f] = v; end
58
+ def set_uint32(f, v); @fields[f] = v; end
59
+ def set_uint64(f, v); @fields[f] = v; end
60
+ def set_float(f, v); @fields[f] = v; end
61
+ def set_double(f, v); @fields[f] = v; end
62
+ def set_float_vector(f, v); @fields[f] = v; end
63
+ def set_double_vector(f, v); @fields[f] = v; end
64
+ def set_string_array(f, v); @fields[f] = v; end
65
+ def get_string(f); @fields[f].is_a?(String) ? @fields[f] : nil; end
66
+ def get_bool(f); [true, false].include?(@fields[f]) ? @fields[f] : nil; end
67
+ def get_int32(f); @fields[f].is_a?(Integer) ? @fields[f] : nil; end
68
+ def get_int64(f); @fields[f].is_a?(Integer) ? @fields[f] : nil; end
69
+ def get_float(f); @fields[f].is_a?(Float) ? @fields[f] : nil; end
70
+ def get_double(f); @fields[f].is_a?(Float) ? @fields[f] : nil; end
71
+ def get_float_vector(f); @fields[f].is_a?(Array) && @fields[f].first.is_a?(Float) ? @fields[f] : nil; end
72
+ def get_double_vector(f); get_float_vector(f); end
73
+ def get_string_array(f); @fields[f].is_a?(Array) && @fields[f].first.is_a?(String) ? @fields[f] : nil; end
74
+ def to_s; "[pk:#{@pk}, score:#{@score}, fields:#{@fields.size}]"; end
75
+ end
76
+
77
+ class FieldSchema
78
+ attr_reader :name, :data_type
79
+ attr_accessor :dimension, :nullable
80
+ def initialize(name, data_type); @name = name; @data_type = data_type; @dimension = 0; @nullable = false; end
81
+ def set_index_params(_); end
82
+ def vector_field?; [:vector_fp32, :vector_fp64, :vector_fp16, :vector_int8].include?(@data_type); end
83
+ def to_s; "FieldSchema(#{@name}, #{@data_type})"; end
84
+ end
85
+
86
+ class CollectionSchema
87
+ attr_reader :name
88
+ def initialize(name); @name = name; @fields = {}; end
89
+ def add_field(fs); @fields[fs.name] = fs; end
90
+ def has_field?(n); @fields.key?(n); end
91
+ def field_names; @fields.keys; end
92
+ def all_field_names; @fields.keys; end
93
+ alias_method :field_names, :all_field_names
94
+ def fields; @fields.values; end
95
+ def vector_fields; @fields.values.select(&:vector_field?); end
96
+ def forward_fields; @fields.values.reject(&:vector_field?); end
97
+ def to_s; "CollectionSchema(#{@name})"; end
98
+ end
99
+
100
+ class HnswIndexParams
101
+ def initialize(metric, m: 16, ef_construction: 200, quantize_type: nil); end
102
+ end
103
+
104
+ class FlatIndexParams
105
+ def initialize(metric, quantize_type: nil); end
106
+ end
107
+
108
+ class IVFIndexParams
109
+ def initialize(metric, n_list: 1024, n_iters: 10, use_soar: false, quantize_type: nil); end
110
+ end
111
+
112
+ class InvertIndexParams
113
+ def initialize(enable_range_optimization: true, enable_extended_wildcard: false); end
114
+ end
115
+
116
+ class HnswQueryParams
117
+ attr_reader :ef
118
+ def initialize(ef: 200); @ef = ef; end
119
+ end
120
+
121
+ class CollectionOptions
122
+ attr_accessor :read_only, :enable_mmap, :max_buffer_size
123
+ def initialize; @read_only = false; @enable_mmap = true; @max_buffer_size = 64 * 1024 * 1024; end
124
+ alias_method :read_only?, :read_only
125
+ alias_method :enable_mmap?, :enable_mmap
126
+ end
127
+
128
+ class VectorQuery
129
+ attr_accessor :topk, :field_name, :filter, :include_vector
130
+ def initialize; @topk = 10; @field_name = ""; @filter = ""; @include_vector = false; end
131
+ def set_query_vector(arr); @query_vector = arr; end
132
+ def set_output_fields(f); @output_fields = f; end
133
+ def set_query_params(p); @query_params = p; end
134
+ alias_method :include_vector?, :include_vector
135
+ end
136
+
137
+ class CollectionStats
138
+ attr_accessor :doc_count
139
+ def initialize; @doc_count = 0; end
140
+ def index_completeness; {}; end
141
+ def to_s; "CollectionStats(doc_count=#{@doc_count})"; end
142
+ end
143
+
144
+ class Status
145
+ def initialize(ok = true, msg = ""); @ok = ok; @msg = msg; end
146
+ def ok?; @ok; end
147
+ def message; @msg; end
148
+ def to_s; @ok ? "OK" : @msg; end
149
+ end
150
+ end
151
+
152
+ require_relative "../lib/zvec/version"
153
+ require_relative "../lib/zvec/data_types"
154
+ require_relative "../lib/zvec/schema"
155
+ require_relative "../lib/zvec/doc"
156
+ require_relative "../lib/zvec/query"
157
+
158
+ include DataTypes
159
+ end
160
+ end
161
+
162
+ # Helper to create a temporary directory that is cleaned up after the test
163
+ module TempDirHelper
164
+ def with_temp_dir(prefix = "zvec_test")
165
+ dir = Dir.mktmpdir(prefix)
166
+ yield dir
167
+ ensure
168
+ FileUtils.rm_rf(dir) if dir && Dir.exist?(dir)
169
+ end
170
+ end
@@ -0,0 +1,64 @@
1
+ require_relative "test_helper"
2
+
3
+ class TestVectorQuery < Minitest::Test
4
+ def test_initialize_basic
5
+ q = Zvec::VectorQuery.new(
6
+ field_name: "embedding",
7
+ vector: [1.0, 2.0, 3.0]
8
+ )
9
+ assert_kind_of Zvec::VectorQuery, q
10
+ assert_kind_of Zvec::Ext::VectorQuery, q.ext_query
11
+ end
12
+
13
+ def test_field_name
14
+ q = Zvec::VectorQuery.new(field_name: "vec", vector: [1.0])
15
+ assert_equal "vec", q.ext_query.field_name
16
+ end
17
+
18
+ def test_topk_default
19
+ q = Zvec::VectorQuery.new(field_name: "vec", vector: [1.0])
20
+ assert_equal 10, q.ext_query.topk
21
+ end
22
+
23
+ def test_topk_custom
24
+ q = Zvec::VectorQuery.new(field_name: "vec", vector: [1.0], topk: 5)
25
+ assert_equal 5, q.ext_query.topk
26
+ end
27
+
28
+ def test_filter
29
+ q = Zvec::VectorQuery.new(
30
+ field_name: "vec", vector: [1.0],
31
+ filter: "year > 2024"
32
+ )
33
+ assert_equal "year > 2024", q.ext_query.filter
34
+ end
35
+
36
+ def test_include_vector_default_false
37
+ q = Zvec::VectorQuery.new(field_name: "vec", vector: [1.0])
38
+ refute q.ext_query.include_vector?
39
+ end
40
+
41
+ def test_include_vector_true
42
+ q = Zvec::VectorQuery.new(
43
+ field_name: "vec", vector: [1.0],
44
+ include_vector: true
45
+ )
46
+ assert q.ext_query.include_vector?
47
+ end
48
+
49
+ def test_symbol_field_name
50
+ q = Zvec::VectorQuery.new(field_name: :embedding, vector: [1.0])
51
+ assert_equal "embedding", q.ext_query.field_name
52
+ end
53
+
54
+ def test_integer_vector_converted_to_float
55
+ q = Zvec::VectorQuery.new(field_name: "vec", vector: [1, 2, 3])
56
+ # Should not raise — integers get .to_f
57
+ assert_kind_of Zvec::VectorQuery, q
58
+ end
59
+
60
+ def test_ext_query_accessible
61
+ q = Zvec::VectorQuery.new(field_name: "vec", vector: [1.0])
62
+ refute_nil q.ext_query
63
+ end
64
+ end
@@ -0,0 +1,166 @@
1
+ require_relative "test_helper"
2
+
3
+ # Tests for the RubyLLM::Store integration.
4
+ # Requires native extension.
5
+ class TestRubyLLMStore < Minitest::Test
6
+ include TempDirHelper
7
+
8
+ def setup
9
+ skip "Native extension not available" unless NATIVE_EXT_AVAILABLE
10
+ require "zvec/ruby_llm"
11
+ end
12
+
13
+ def test_initialize_creates_collection
14
+ with_temp_dir do |dir|
15
+ path = File.join(dir, "store")
16
+ store = Zvec::RubyLLM::Store.new(path, dimension: 4)
17
+ assert_kind_of Zvec::Collection, store.collection
18
+ assert_equal 4, store.dimension
19
+ store.collection.destroy
20
+ end
21
+ end
22
+
23
+ def test_initialize_with_metric
24
+ with_temp_dir do |dir|
25
+ [:cosine, :l2, :ip].each_with_index do |metric, i|
26
+ path = File.join(dir, "store_#{i}")
27
+ store = Zvec::RubyLLM::Store.new(path, dimension: 4, metric: metric)
28
+ assert_kind_of Zvec::Collection, store.collection
29
+ store.collection.destroy
30
+ end
31
+ end
32
+ end
33
+
34
+ def test_initialize_invalid_metric
35
+ with_temp_dir do |dir|
36
+ path = File.join(dir, "store")
37
+ assert_raises(ArgumentError) do
38
+ Zvec::RubyLLM::Store.new(path, dimension: 4, metric: :invalid)
39
+ end
40
+ end
41
+ end
42
+
43
+ def test_add_and_count
44
+ with_temp_dir do |dir|
45
+ path = File.join(dir, "store")
46
+ store = Zvec::RubyLLM::Store.new(path, dimension: 4)
47
+
48
+ store.add("doc1", embedding: [0.1, 0.2, 0.3, 0.4], content: "Hello")
49
+ assert_equal 1, store.count
50
+
51
+ store.add("doc2", embedding: [0.4, 0.3, 0.2, 0.1], content: "World")
52
+ assert_equal 2, store.count
53
+
54
+ store.collection.destroy
55
+ end
56
+ end
57
+
58
+ def test_add_many
59
+ with_temp_dir do |dir|
60
+ path = File.join(dir, "store")
61
+ store = Zvec::RubyLLM::Store.new(path, dimension: 4)
62
+
63
+ docs = [
64
+ { id: "a", embedding: [0.1, 0.2, 0.3, 0.4], content: "A" },
65
+ { id: "b", embedding: [0.4, 0.3, 0.2, 0.1], content: "B" },
66
+ { id: "c", embedding: [0.2, 0.4, 0.1, 0.3], content: "C" },
67
+ ]
68
+ store.add_many(docs)
69
+ assert_equal 3, store.count
70
+
71
+ store.collection.destroy
72
+ end
73
+ end
74
+
75
+ def test_search
76
+ with_temp_dir do |dir|
77
+ path = File.join(dir, "store")
78
+ store = Zvec::RubyLLM::Store.new(path, dimension: 4)
79
+
80
+ store.add("near", embedding: [1.0, 0.0, 0.0, 0.0], content: "Near")
81
+ store.add("far", embedding: [0.0, 0.0, 0.0, 1.0], content: "Far")
82
+
83
+ results = store.search([1.0, 0.0, 0.0, 0.0], top_k: 2)
84
+ assert_kind_of Array, results
85
+ assert_equal 2, results.size
86
+
87
+ # Each result is a hash
88
+ first = results.first
89
+ assert_kind_of Hash, first
90
+ assert first.key?(:id)
91
+ assert first.key?(:score)
92
+ assert first.key?(:content)
93
+ assert first.key?(:metadata)
94
+
95
+ # Nearest should be "near"
96
+ assert_equal "near", first[:id]
97
+
98
+ store.collection.destroy
99
+ end
100
+ end
101
+
102
+ def test_search_returns_content
103
+ with_temp_dir do |dir|
104
+ path = File.join(dir, "store")
105
+ store = Zvec::RubyLLM::Store.new(path, dimension: 4)
106
+
107
+ store.add("d1", embedding: [1.0, 0.0, 0.0, 0.0], content: "Ruby rocks")
108
+ results = store.search([1.0, 0.0, 0.0, 0.0], top_k: 1)
109
+ assert_equal "Ruby rocks", results.first[:content]
110
+
111
+ store.collection.destroy
112
+ end
113
+ end
114
+
115
+ def test_delete
116
+ with_temp_dir do |dir|
117
+ path = File.join(dir, "store")
118
+ store = Zvec::RubyLLM::Store.new(path, dimension: 4)
119
+
120
+ store.add("del1", embedding: [0.1, 0.2, 0.3, 0.4])
121
+ store.add("del2", embedding: [0.4, 0.3, 0.2, 0.1])
122
+ assert_equal 2, store.count
123
+
124
+ store.delete("del1")
125
+ assert_equal 1, store.count
126
+
127
+ store.collection.destroy
128
+ end
129
+ end
130
+
131
+ def test_flush
132
+ with_temp_dir do |dir|
133
+ path = File.join(dir, "store")
134
+ store = Zvec::RubyLLM::Store.new(path, dimension: 4)
135
+ store.add("f1", embedding: [0.1, 0.2, 0.3, 0.4])
136
+ store.flush
137
+ # Should not raise
138
+ store.collection.destroy
139
+ end
140
+ end
141
+
142
+ def test_custom_vector_field
143
+ with_temp_dir do |dir|
144
+ path = File.join(dir, "store")
145
+ store = Zvec::RubyLLM::Store.new(path, dimension: 4, vector_field: "my_vec")
146
+ store.add("v1", embedding: [0.1, 0.2, 0.3, 0.4])
147
+ assert_equal 1, store.count
148
+ store.collection.destroy
149
+ end
150
+ end
151
+
152
+ def test_custom_content_field
153
+ with_temp_dir do |dir|
154
+ path = File.join(dir, "store")
155
+ store = Zvec::RubyLLM::Store.new(path, dimension: 4, content_field: "text")
156
+ store.add("c1", embedding: [0.1, 0.2, 0.3, 0.4], content: "Custom")
157
+ results = store.search([0.1, 0.2, 0.3, 0.4], top_k: 1)
158
+ assert_equal "Custom", results.first[:content]
159
+ store.collection.destroy
160
+ end
161
+ end
162
+
163
+ def test_reopen_existing_store
164
+ skip "Cannot reopen without explicit close (collection locks on open)"
165
+ end
166
+ end
@@ -0,0 +1,133 @@
1
+ require_relative "test_helper"
2
+
3
+ class TestSchema < Minitest::Test
4
+ def test_initialize_with_name
5
+ schema = Zvec::Schema.new("test_schema")
6
+ assert_equal "test_schema", schema.name
7
+ end
8
+
9
+ def test_initialize_with_block
10
+ schema = Zvec::Schema.new("test") do
11
+ string "title"
12
+ int32 "count"
13
+ end
14
+
15
+ assert schema.has_field?("title")
16
+ assert schema.has_field?("count")
17
+ end
18
+
19
+ def test_string_field
20
+ schema = Zvec::Schema.new("test") { string "name" }
21
+ assert schema.has_field?("name")
22
+ assert_equal Zvec::DataTypes::STRING, schema.field_type("name")
23
+ end
24
+
25
+ def test_int32_field
26
+ schema = Zvec::Schema.new("test") { int32 "count" }
27
+ assert schema.has_field?("count")
28
+ assert_equal Zvec::DataTypes::INT32, schema.field_type("count")
29
+ end
30
+
31
+ def test_int64_field
32
+ schema = Zvec::Schema.new("test") { int64 "big_count" }
33
+ assert schema.has_field?("big_count")
34
+ assert_equal Zvec::DataTypes::INT64, schema.field_type("big_count")
35
+ end
36
+
37
+ def test_float_field
38
+ schema = Zvec::Schema.new("test") { float "score" }
39
+ assert schema.has_field?("score")
40
+ assert_equal Zvec::DataTypes::FLOAT, schema.field_type("score")
41
+ end
42
+
43
+ def test_double_field
44
+ schema = Zvec::Schema.new("test") { double "precise" }
45
+ assert schema.has_field?("precise")
46
+ assert_equal Zvec::DataTypes::DOUBLE, schema.field_type("precise")
47
+ end
48
+
49
+ def test_bool_field
50
+ schema = Zvec::Schema.new("test") { bool "active" }
51
+ assert schema.has_field?("active")
52
+ assert_equal Zvec::DataTypes::BOOL, schema.field_type("active")
53
+ end
54
+
55
+ def test_vector_field
56
+ schema = Zvec::Schema.new("test") do
57
+ vector "embedding", dimension: 128
58
+ end
59
+ assert schema.has_field?("embedding")
60
+ assert_equal Zvec::DataTypes::VECTOR_FP32, schema.field_type("embedding")
61
+ end
62
+
63
+ def test_vector_field_custom_type
64
+ schema = Zvec::Schema.new("test") do
65
+ vector "embedding", dimension: 128, type: Zvec::DataTypes::VECTOR_FP64
66
+ end
67
+ assert_equal Zvec::DataTypes::VECTOR_FP64, schema.field_type("embedding")
68
+ end
69
+
70
+ def test_field_with_explicit_type
71
+ schema = Zvec::Schema.new("test")
72
+ schema.field("tags", Zvec::DataTypes::ARRAY_STRING)
73
+ assert schema.has_field?("tags")
74
+ assert_equal Zvec::DataTypes::ARRAY_STRING, schema.field_type("tags")
75
+ end
76
+
77
+ def test_field_names
78
+ schema = Zvec::Schema.new("test") do
79
+ string "a"
80
+ int32 "b"
81
+ vector "c", dimension: 4
82
+ end
83
+ names = schema.field_names
84
+ assert_includes names, "a"
85
+ assert_includes names, "b"
86
+ assert_includes names, "c"
87
+ assert_equal 3, names.size
88
+ end
89
+
90
+ def test_has_field_returns_false_for_missing
91
+ schema = Zvec::Schema.new("test")
92
+ refute schema.has_field?("nonexistent")
93
+ end
94
+
95
+ def test_field_type_returns_nil_for_missing
96
+ schema = Zvec::Schema.new("test")
97
+ assert_nil schema.field_type("nonexistent")
98
+ end
99
+
100
+ def test_symbol_field_name
101
+ schema = Zvec::Schema.new("test") { string "name" }
102
+ # field_type converts to string internally
103
+ assert_equal Zvec::DataTypes::STRING, schema.field_type(:name)
104
+ end
105
+
106
+ def test_field_returns_self_for_chaining
107
+ schema = Zvec::Schema.new("test")
108
+ result = schema.field("a", Zvec::DataTypes::STRING)
109
+ assert_same schema, result
110
+ end
111
+
112
+ def test_to_s_returns_string
113
+ schema = Zvec::Schema.new("test") { string "x" }
114
+ assert_kind_of String, schema.to_s
115
+ end
116
+
117
+ def test_ext_schema_accessible
118
+ schema = Zvec::Schema.new("test")
119
+ assert_kind_of Zvec::Ext::CollectionSchema, schema.ext_schema
120
+ end
121
+
122
+ def test_multiple_fields_in_block
123
+ schema = Zvec::Schema.new("docs") do
124
+ string "title"
125
+ string "body", nullable: true
126
+ int32 "year"
127
+ float "rating"
128
+ bool "published"
129
+ vector "embedding", dimension: 384
130
+ end
131
+ assert_equal 6, schema.field_names.size
132
+ end
133
+ end
@@ -0,0 +1,19 @@
1
+ require_relative "test_helper"
2
+
3
+ class TestVersion < Minitest::Test
4
+ def test_version_is_defined
5
+ refute_nil Zvec::VERSION
6
+ end
7
+
8
+ def test_version_is_a_string
9
+ assert_kind_of String, Zvec::VERSION
10
+ end
11
+
12
+ def test_version_format
13
+ assert_match(/\A\d+\.\d+\.\d+/, Zvec::VERSION)
14
+ end
15
+
16
+ def test_version_value
17
+ assert_equal "0.1.0", Zvec::VERSION
18
+ end
19
+ end
data/zvec.gemspec ADDED
@@ -0,0 +1,43 @@
1
+ require_relative "lib/zvec/version"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "zvec-ruby"
5
+ spec.version = Zvec::VERSION
6
+ spec.authors = ["Johannes Dwi Cahyo"]
7
+ spec.summary = "Ruby bindings for zvec vector database"
8
+ spec.description = "Ruby gem wrapping the zvec C++ vector database (https://github.com/alibaba/zvec) " \
9
+ "using Rice. Provides Collection, Doc, Schema, and VectorQuery classes for " \
10
+ "high-performance vector similarity search, plus integrations for ruby_llm and ActiveRecord."
11
+ spec.homepage = "https://github.com/johannesdwicahyo/zvec-ruby"
12
+ spec.license = "Apache-2.0"
13
+
14
+ spec.required_ruby_version = ">= 3.1.0"
15
+
16
+ spec.files = Dir[
17
+ "lib/**/*.rb",
18
+ "ext/**/*.{rb,cpp,h,hpp}",
19
+ "examples/**/*.rb",
20
+ "test/**/*.rb",
21
+ "README.md",
22
+ "LICENSE",
23
+ "CHANGELOG.md",
24
+ "Rakefile",
25
+ "zvec.gemspec"
26
+ ]
27
+
28
+ spec.metadata = {
29
+ "homepage_uri" => spec.homepage,
30
+ "source_code_uri" => spec.homepage,
31
+ "changelog_uri" => "#{spec.homepage}/blob/main/CHANGELOG.md",
32
+ "bug_tracker_uri" => "#{spec.homepage}/issues",
33
+ }
34
+
35
+ spec.extensions = ["ext/zvec/extconf.rb"]
36
+ spec.require_paths = ["lib"]
37
+
38
+ spec.add_dependency "rice", ">= 4.0"
39
+
40
+ spec.add_development_dependency "rake-compiler", "~> 1.2"
41
+ spec.add_development_dependency "rake", "~> 13.0"
42
+ spec.add_development_dependency "minitest", "~> 5.0"
43
+ end