ruby-hdf5 0.0.2 → 0.0.3

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.
data/lib/hdf5/file.rb CHANGED
@@ -1,8 +1,11 @@
1
1
  module HDF5
2
2
  class File
3
+ include Hierarchy
4
+
3
5
  H5F_ACC_RDONLY = 0x0000
4
6
  H5F_ACC_RDWR = 0x0001
5
7
  H5F_ACC_TRUNC = 0x0002
8
+ H5F_ACC_EXCL = 0x0004
6
9
 
7
10
  class << self
8
11
  def create(filename, flags = H5F_ACC_TRUNC)
@@ -17,8 +20,9 @@ module HDF5
17
20
  end
18
21
  end
19
22
 
20
- def open(filename, mode = H5F_ACC_RDONLY)
21
- file = from_id(HDF5::FFI.H5Fopen(filename, mode, HDF5::DEFAULT_PROPERTY_LIST), filename, mode)
23
+ def open(filename, mode = 'r')
24
+ file_id, flags = open_file(filename, mode)
25
+ file = from_id(file_id, filename, flags)
22
26
  return file unless block_given?
23
27
 
24
28
  begin
@@ -30,6 +34,29 @@ module HDF5
30
34
 
31
35
  private
32
36
 
37
+ def open_file(filename, mode)
38
+ return [HDF5::FFI.H5Fopen(filename, mode, HDF5::DEFAULT_PROPERTY_LIST), mode] if mode.is_a?(Integer)
39
+
40
+ case mode
41
+ when 'r'
42
+ [HDF5::FFI.H5Fopen(filename, H5F_ACC_RDONLY, HDF5::DEFAULT_PROPERTY_LIST), H5F_ACC_RDONLY]
43
+ when 'r+'
44
+ [HDF5::FFI.H5Fopen(filename, H5F_ACC_RDWR, HDF5::DEFAULT_PROPERTY_LIST), H5F_ACC_RDWR]
45
+ when 'w'
46
+ [HDF5::FFI.H5Fcreate(filename, H5F_ACC_TRUNC, HDF5::DEFAULT_PROPERTY_LIST, HDF5::DEFAULT_PROPERTY_LIST),
47
+ H5F_ACC_TRUNC]
48
+ when 'x'
49
+ [HDF5::FFI.H5Fcreate(filename, H5F_ACC_EXCL, HDF5::DEFAULT_PROPERTY_LIST, HDF5::DEFAULT_PROPERTY_LIST),
50
+ H5F_ACC_EXCL]
51
+ when 'a'
52
+ file_id = HDF5::FFI.H5Fcreate(filename, H5F_ACC_EXCL, HDF5::DEFAULT_PROPERTY_LIST, HDF5::DEFAULT_PROPERTY_LIST)
53
+ file_id = HDF5::FFI.H5Fopen(filename, H5F_ACC_RDWR, HDF5::DEFAULT_PROPERTY_LIST) if file_id < 0
54
+ [file_id, H5F_ACC_RDWR]
55
+ else
56
+ raise ArgumentError, "Unsupported file mode: #{mode.inspect}"
57
+ end
58
+ end
59
+
33
60
  def from_id(file_id, filename, mode)
34
61
  file = allocate
35
62
  file.send(:initialize_from_id, file_id, filename, mode)
@@ -42,21 +69,36 @@ module HDF5
42
69
  end
43
70
 
44
71
  def close
45
- return if @file_id.nil?
72
+ return if @file_id.nil? || @context.closed?
46
73
 
47
- HDF5::FFI.H5Fclose(@file_id)
74
+ @context.close_file
48
75
  @file_id = nil
49
76
  end
50
77
 
78
+ def closed?
79
+ @file_id.nil? || @context.closed?
80
+ end
81
+
82
+ def flush
83
+ ensure_open!
84
+ status = HDF5::FFI.H5Fflush(@file_id, :H5F_SCOPE_GLOBAL)
85
+ raise HDF5::Error, 'Failed to flush file' if status < 0
86
+
87
+ self
88
+ end
89
+
51
90
  def create_group(name, &block)
52
- Group.create(@file_id, name, &block)
91
+ ensure_open!
92
+ Group.create(@file_id, name, @context, &block)
53
93
  end
54
94
 
55
- def create_dataset(name, data, &block)
56
- Dataset.create(@file_id, name, data, &block)
95
+ def create_dataset(name, data = nil, **options, &block)
96
+ ensure_open!
97
+ Dataset.create(@file_id, name, data, context: @context, **options, &block)
57
98
  end
58
99
 
59
100
  def list_entries
101
+ ensure_open!
60
102
  list = []
61
103
  callback = ::FFI::Function.new(:int, %i[int64_t string pointer pointer]) do |_, name, _, _|
62
104
  list << name
@@ -74,27 +116,41 @@ module HDF5
74
116
  end
75
117
 
76
118
  def [](name)
119
+ ensure_open!
77
120
  if group?(name)
78
- Group.open(@file_id, name)
121
+ Group.open(@file_id, name, @context)
79
122
  elsif dataset?(name)
80
- Dataset.open(@file_id, name)
123
+ Dataset.open(@file_id, name, context: @context)
81
124
  else
82
125
  raise HDF5::Error, "Group or dataset not found: #{name}"
83
126
  end
84
127
  end
85
128
 
86
129
  def attrs
87
- @attrs ||= AttributeManager.new(@file_id)
130
+ ensure_open!
131
+ @attrs ||= AttributeManager.new(@file_id, @context)
88
132
  end
89
133
 
90
134
  private
91
135
 
136
+ def hdf5_id
137
+ ensure_open!
138
+ @file_id
139
+ end
140
+
92
141
  def initialize_from_id(file_id, filename, mode)
93
142
  raise HDF5::Error, "Failed to open file: #{filename}" if file_id < 0
94
143
 
95
144
  @filename = filename
96
145
  @mode = mode
97
146
  @file_id = file_id
147
+ @context = FileContext.new(file_id)
148
+ end
149
+
150
+ def ensure_open!
151
+ raise ClosedError, 'HDF5 file is closed' if @file_id.nil?
152
+
153
+ @context.ensure_open!(@file_id)
98
154
  end
99
155
 
100
156
  def group?(name)
@@ -122,5 +178,7 @@ module HDF5
122
178
  end
123
179
  info[:type] == :H5O_TYPE_DATASET
124
180
  end
181
+
182
+ prepend FileContext.guard(:flush, :create_group, :create_dataset, :list_entries, :[], :attrs)
125
183
  end
126
184
  end
@@ -0,0 +1,70 @@
1
+ module HDF5
2
+ class FileContext
3
+ CLOSE_FUNCTIONS = {
4
+ file: :H5Fclose,
5
+ group: :H5Gclose,
6
+ dataset: :H5Dclose
7
+ }.freeze
8
+
9
+ def initialize(file_id)
10
+ @file_id = file_id
11
+ @handles = { file_id => :file }
12
+ @closed = false
13
+ end
14
+
15
+ def register(id, type)
16
+ raise ClosedError, 'HDF5 file is closed' if closed?
17
+
18
+ @handles[id] = type
19
+ id
20
+ end
21
+
22
+ def close(id)
23
+ synchronize do
24
+ type = @handles[id]
25
+ return if type.nil?
26
+
27
+ status = HDF5::FFI.public_send(CLOSE_FUNCTIONS.fetch(type), id)
28
+ raise HDF5::Error, "Failed to close HDF5 #{type}" if status < 0
29
+
30
+ @handles.delete(id)
31
+ @closed = true if type == :file
32
+ nil
33
+ end
34
+ end
35
+
36
+ def close_file
37
+ synchronize do
38
+ return if closed?
39
+
40
+ children = @handles.keys.reject { |id| id == @file_id }
41
+ children.reverse_each { |id| close(id) }
42
+ close(@file_id)
43
+ end
44
+ end
45
+
46
+ def ensure_open!(id = nil)
47
+ raise ClosedError, 'HDF5 file is closed' if closed?
48
+ raise ClosedError, 'HDF5 object is closed' if id && !@handles.key?(id)
49
+ end
50
+
51
+ def closed?
52
+ @closed
53
+ end
54
+
55
+ def synchronize(&block)
56
+ HDF5::FFI::CALL_LOCK.synchronize(&block)
57
+ end
58
+
59
+ def self.guard(*methods)
60
+ Module.new do
61
+ methods.each do |method|
62
+ define_method(method) do |*args, **kwargs, &block|
63
+ lock = @context || HDF5::FFI::CALL_LOCK
64
+ lock.synchronize { super(*args, **kwargs, &block) }
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
data/lib/hdf5/group.rb CHANGED
@@ -1,10 +1,12 @@
1
1
  module HDF5
2
2
  class Group
3
+ include Hierarchy
4
+
3
5
  class << self
4
- def create(parent_id, name)
6
+ def create(parent_id, name, context = nil)
5
7
  group = from_id(
6
8
  HDF5::FFI.H5Gcreate2(parent_id, name, HDF5::DEFAULT_PROPERTY_LIST, HDF5::DEFAULT_PROPERTY_LIST,
7
- HDF5::DEFAULT_PROPERTY_LIST), name
9
+ HDF5::DEFAULT_PROPERTY_LIST), name, context
8
10
  )
9
11
  return group unless block_given?
10
12
 
@@ -15,8 +17,8 @@ module HDF5
15
17
  end
16
18
  end
17
19
 
18
- def open(parent_id, name)
19
- group = from_id(HDF5::FFI.H5Gopen2(parent_id, name, HDF5::DEFAULT_PROPERTY_LIST), name)
20
+ def open(parent_id, name, context = nil)
21
+ group = from_id(HDF5::FFI.H5Gopen2(parent_id, name, HDF5::DEFAULT_PROPERTY_LIST), name, context)
20
22
  return group unless block_given?
21
23
 
22
24
  begin
@@ -28,33 +30,40 @@ module HDF5
28
30
 
29
31
  private
30
32
 
31
- def from_id(group_id, name)
33
+ def from_id(group_id, name, context)
32
34
  group = allocate
33
- group.send(:initialize_from_id, group_id, name)
35
+ group.send(:initialize_from_id, group_id, name, context)
34
36
  group
35
37
  end
36
38
  end
37
39
 
38
40
  def initialize(file_id, name)
39
- initialize_from_id(HDF5::FFI.H5Gopen2(file_id, name, HDF5::DEFAULT_PROPERTY_LIST), name)
41
+ initialize_from_id(HDF5::FFI.H5Gopen2(file_id, name, HDF5::DEFAULT_PROPERTY_LIST), name, nil)
40
42
  end
41
43
 
42
44
  def close
43
45
  return if @group_id.nil?
44
46
 
45
- HDF5::FFI.H5Gclose(@group_id)
47
+ @context ? @context.close(@group_id) : HDF5::FFI.H5Gclose(@group_id)
46
48
  @group_id = nil
47
49
  end
48
50
 
51
+ def closed?
52
+ @group_id.nil? || (@context && @context.closed?)
53
+ end
54
+
49
55
  def create_group(name, &block)
50
- self.class.create(@group_id, name, &block)
56
+ ensure_open!
57
+ self.class.create(@group_id, name, @context, &block)
51
58
  end
52
59
 
53
- def create_dataset(name, data, &block)
54
- Dataset.create(@group_id, name, data, &block)
60
+ def create_dataset(name, data = nil, **options, &block)
61
+ ensure_open!
62
+ Dataset.create(@group_id, name, data, context: @context, **options, &block)
55
63
  end
56
64
 
57
65
  def list_entries
66
+ ensure_open!
58
67
  entries = []
59
68
  callback = ::FFI::Function.new(:int, %i[int64_t string pointer pointer]) do |_, name, _, _|
60
69
  entries << name
@@ -76,26 +85,41 @@ module HDF5
76
85
  end
77
86
 
78
87
  def [](name)
88
+ ensure_open!
79
89
  if group?(name)
80
- self.class.open(@group_id, name)
90
+ self.class.open(@group_id, name, @context)
81
91
  elsif dataset?(name)
82
- Dataset.open(@group_id, name)
92
+ Dataset.open(@group_id, name, context: @context)
83
93
  else
84
94
  raise HDF5::Error, "Group or dataset not found: #{name}"
85
95
  end
86
96
  end
87
97
 
88
98
  def attrs
89
- @attrs ||= AttributeManager.new(@group_id)
99
+ ensure_open!
100
+ @attrs ||= AttributeManager.new(@group_id, @context)
90
101
  end
91
102
 
92
103
  private
93
104
 
94
- def initialize_from_id(group_id, name)
105
+ def hdf5_id
106
+ ensure_open!
107
+ @group_id
108
+ end
109
+
110
+ def initialize_from_id(group_id, name, context)
95
111
  raise HDF5::Error, "Failed to open group: #{name}" if group_id < 0
96
112
 
97
113
  @group_id = group_id
98
114
  @name = name
115
+ @context = context
116
+ @context.register(group_id, :group) if @context
117
+ end
118
+
119
+ def ensure_open!
120
+ raise ClosedError, 'HDF5 group is closed' if @group_id.nil?
121
+
122
+ @context&.ensure_open!(@group_id)
99
123
  end
100
124
 
101
125
  def group?(name)
@@ -123,5 +147,7 @@ module HDF5
123
147
  end
124
148
  info[:type] == :H5O_TYPE_DATASET
125
149
  end
150
+
151
+ prepend FileContext.guard(:create_group, :create_dataset, :list_entries, :list_datasets, :[], :attrs)
126
152
  end
127
153
  end
@@ -0,0 +1,107 @@
1
+ module HDF5
2
+ module Hierarchy
3
+ def keys
4
+ list_entries
5
+ end
6
+
7
+ def each_key(&block)
8
+ return enum_for(__method__) unless block
9
+
10
+ keys.each(&block)
11
+ self
12
+ end
13
+
14
+ def require_group(path)
15
+ path.split('/').reject(&:empty?).inject(self) do |parent, name|
16
+ if parent.key?(name)
17
+ child = parent[name]
18
+ raise HDF5::Error, "Existing object is not a group: #{name}" unless child.is_a?(Group)
19
+
20
+ child
21
+ else
22
+ parent.create_group(name)
23
+ end
24
+ end
25
+ end
26
+
27
+ def open_dataset(name)
28
+ object = self[name]
29
+ raise HDF5::Error, "Object is not a dataset: #{name}" unless object.is_a?(Dataset)
30
+ return object unless block_given?
31
+
32
+ begin
33
+ yield object
34
+ ensure
35
+ object.close
36
+ end
37
+ end
38
+
39
+ def key?(name)
40
+ exists = HDF5::FFI.H5Lexists(hdf5_id, name, HDF5::DEFAULT_PROPERTY_LIST)
41
+ raise HDF5::Error, "Failed to check link existence: #{name}" if exists.negative?
42
+
43
+ exists.positive?
44
+ end
45
+
46
+ def delete(name)
47
+ status = HDF5::FFI.H5Ldelete(hdf5_id, name, HDF5::DEFAULT_PROPERTY_LIST)
48
+ raise HDF5::Error, "Failed to delete link: #{name}" if status < 0
49
+
50
+ self
51
+ end
52
+
53
+ def create_hard_link(target, name)
54
+ status = HDF5::FFI.H5Lcreate_hard(hdf5_id, target, hdf5_id, name, HDF5::DEFAULT_PROPERTY_LIST,
55
+ HDF5::DEFAULT_PROPERTY_LIST)
56
+ raise HDF5::Error, "Failed to create hard link: #{name}" if status < 0
57
+
58
+ self
59
+ end
60
+
61
+ def create_soft_link(target, name)
62
+ status = HDF5::FFI.H5Lcreate_soft(target, hdf5_id, name, HDF5::DEFAULT_PROPERTY_LIST,
63
+ HDF5::DEFAULT_PROPERTY_LIST)
64
+ raise HDF5::Error, "Failed to create soft link: #{name}" if status < 0
65
+
66
+ self
67
+ end
68
+
69
+ def link_info(name)
70
+ info = HDF5::FFI::MiV == 10 ? HDF5::FFI::H5LInfoT.new : HDF5::FFI::H5LInfo2T.new
71
+ status = if HDF5::FFI::MiV == 10
72
+ HDF5::FFI.H5Lget_info(hdf5_id, name, info, HDF5::DEFAULT_PROPERTY_LIST)
73
+ else
74
+ HDF5::FFI.H5Lget_info2(hdf5_id, name, info, HDF5::DEFAULT_PROPERTY_LIST)
75
+ end
76
+ raise HDF5::Error, "Failed to get link information: #{name}" if status < 0
77
+
78
+ type = info[:type]
79
+ return { type: :hard } if type == :H5L_TYPE_HARD
80
+
81
+ size = info[:u][:val_size]
82
+ buffer = ::FFI::MemoryPointer.new(:char, size)
83
+ status = HDF5::FFI.H5Lget_val(hdf5_id, name, buffer, size, HDF5::DEFAULT_PROPERTY_LIST)
84
+ raise HDF5::Error, "Failed to get link value: #{name}" if status < 0
85
+
86
+ raw = buffer.read_bytes(size)
87
+ return { type: :soft, target: raw.delete_suffix("\0") } if type == :H5L_TYPE_SOFT
88
+
89
+ if type == :H5L_TYPE_EXTERNAL
90
+ filename, path = raw.byteslice(1..).split("\0", 2)
91
+ return { type: :external, filename:, path: path&.delete_suffix("\0") }
92
+ end
93
+
94
+ raise UnsupportedTypeError, "Unsupported link type: #{type}"
95
+ end
96
+
97
+ def move(source, destination)
98
+ status = HDF5::FFI.H5Lmove(hdf5_id, source, hdf5_id, destination, HDF5::DEFAULT_PROPERTY_LIST,
99
+ HDF5::DEFAULT_PROPERTY_LIST)
100
+ raise HDF5::Error, "Failed to move link: #{source}" if status < 0
101
+
102
+ self
103
+ end
104
+
105
+ prepend FileContext.guard(*instance_methods(false))
106
+ end
107
+ end
@@ -0,0 +1,81 @@
1
+ module HDF5
2
+ class Selection
3
+ attr_reader :start, :stride, :count, :result_shape
4
+
5
+ def self.normalize(selection, shape)
6
+ return new(Array.new(shape.length, true), shape) if selection.nil?
7
+
8
+ selectors = selection.is_a?(Array) ? selection.dup : [selection]
9
+ raise IndexError, 'Too many indices for dataset' if selectors.length > shape.length
10
+
11
+ selectors.concat(Array.new(shape.length - selectors.length, true))
12
+ new(selectors, shape)
13
+ end
14
+
15
+ def initialize(selectors, shape)
16
+ @start = []
17
+ @stride = []
18
+ @count = []
19
+ @result_shape = []
20
+
21
+ selectors.zip(shape).each do |selector, dimension|
22
+ normalize_axis(selector, dimension)
23
+ end
24
+ end
25
+
26
+ def scalar?
27
+ result_shape.empty?
28
+ end
29
+
30
+ def size
31
+ count.inject(1, :*)
32
+ end
33
+
34
+ private
35
+
36
+ def normalize_axis(selector, dimension)
37
+ case selector
38
+ when true
39
+ @start << 0
40
+ @stride << 1
41
+ @count << dimension
42
+ @result_shape << dimension
43
+ when Integer
44
+ index = selector.negative? ? dimension + selector : selector
45
+ raise IndexError, "Index #{selector} is outside dimension of size #{dimension}" unless index.between?(0,
46
+ dimension - 1)
47
+
48
+ @start << index
49
+ @stride << 1
50
+ @count << 1
51
+ when Range
52
+ first, last = normalize_range(selector, dimension)
53
+ @start << first
54
+ @stride << 1
55
+ @count << last - first
56
+ @result_shape << last - first
57
+ when HDF5::Slice
58
+ first, last = normalize_range(selector.range, dimension)
59
+ count = (last - first).fdiv(selector.step).ceil
60
+ @start << first
61
+ @stride << selector.step
62
+ @count << count
63
+ @result_shape << count
64
+ else
65
+ raise IndexError, "Unsupported index: #{selector.inspect}"
66
+ end
67
+ end
68
+
69
+ def normalize_range(range, dimension)
70
+ first = range.begin.nil? ? 0 : range.begin
71
+ last = range.end.nil? ? dimension : range.end
72
+ first += dimension if first.negative?
73
+ last += dimension if last.negative?
74
+ last += 1 unless range.exclude_end? || range.end.nil?
75
+
76
+ first = [[first, 0].max, dimension].min
77
+ last = [[last, 0].max, dimension].min
78
+ [first, [last, first].max]
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,93 @@
1
+ module HDF5
2
+ module StringCodec
3
+ module_function
4
+
5
+ UTF8 = 1
6
+ VARIABLE_SIZE = (1 << (::FFI.type_size(:size_t) * 8)) - 1
7
+
8
+ def datatype_id
9
+ type_id = HDF5::FFI.H5Tcopy(HDF5::FFI.H5T_C_S1)
10
+ raise HDF5::Error, 'Failed to create string datatype' if type_id < 0
11
+
12
+ check(HDF5::FFI.H5Tset_size(type_id, VARIABLE_SIZE), 'set variable string size')
13
+ check(HDF5::FFI.H5Tset_cset(type_id, UTF8), 'set UTF-8 string encoding')
14
+ type_id
15
+ rescue StandardError
16
+ HDF5::FFI.H5Tclose(type_id) if type_id && type_id >= 0
17
+ raise
18
+ end
19
+
20
+ def buffer_for(value)
21
+ buffer_for_values([normalize(value)])
22
+ end
23
+
24
+ def buffer_for_values(values)
25
+ pointers = values.map { |value| ::FFI::MemoryPointer.from_string(normalize(value)) }
26
+ buffer = ::FFI::MemoryPointer.new(:pointer, pointers.length)
27
+ buffer.write_array_of_pointer(pointers)
28
+ [buffer, pointers]
29
+ end
30
+
31
+ def read(buffer)
32
+ pointer = buffer.read_pointer
33
+ pointer.null? ? nil : pointer.read_string.force_encoding(Encoding::UTF_8)
34
+ end
35
+
36
+ def read_values(buffer, count, shape)
37
+ values = buffer.read_array_of_pointer(count).map do |pointer|
38
+ pointer.null? ? nil : pointer.read_string.force_encoding(Encoding::UTF_8)
39
+ end
40
+ return values.first if shape.empty?
41
+
42
+ Numo::RObject.cast(values).reshape(*shape)
43
+ end
44
+
45
+ def string_data?(value)
46
+ return true if value.is_a?(String)
47
+
48
+ values = if value.is_a?(Numo::RObject)
49
+ value.to_a.flatten
50
+ elsif value.is_a?(Array)
51
+ value.flatten
52
+ else
53
+ []
54
+ end
55
+ !values.empty? && values.all? { |item| item.is_a?(String) }
56
+ end
57
+
58
+ def normalize_data(value)
59
+ return [[normalize(value)], []] if value.is_a?(String)
60
+ return [value.to_a.flatten.map { |item| normalize(item) }, value.shape] if value.is_a?(Numo::RObject)
61
+
62
+ shape = array_shape(value)
63
+ [value.flatten.map { |item| normalize(item) }, shape]
64
+ end
65
+
66
+ def array_shape(value)
67
+ return [] unless value.is_a?(Array)
68
+ raise HDF5::Error, 'String data must not be empty' if value.empty?
69
+
70
+ child_shapes = value.map { |item| array_shape(item) }
71
+ raise HDF5::ShapeError, 'String data must be rectangular' unless child_shapes.uniq.length == 1
72
+
73
+ [value.length, *child_shapes.first]
74
+ end
75
+
76
+ def variable?(type_id)
77
+ HDF5::FFI.H5Tis_variable_str(type_id).positive?
78
+ end
79
+
80
+ def normalize(value)
81
+ raise HDF5::Error, 'String dataset data must be a String' unless value.is_a?(String)
82
+ raise HDF5::Error, 'Variable-length strings cannot contain NUL bytes' if value.include?("\0")
83
+
84
+ value.encode(Encoding::UTF_8)
85
+ rescue EncodingError
86
+ raise HDF5::Error, 'String dataset data must be valid UTF-8'
87
+ end
88
+
89
+ def check(status, operation)
90
+ raise HDF5::Error, "Failed to #{operation}" if status < 0
91
+ end
92
+ end
93
+ end
data/lib/hdf5/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HDF5
4
- VERSION = '0.0.2'
4
+ VERSION = '0.0.3'
5
5
  end
data/lib/hdf5.rb CHANGED
@@ -1,15 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'ffi'
4
+ require 'English'
5
+ require 'numo/narray/alt'
4
6
  require_relative 'hdf5/version'
5
7
 
6
8
  module HDF5
7
9
  class Error < StandardError; end
10
+ class ClosedError < Error; end
11
+ class UnsupportedTypeError < Error; end
12
+ class UnsupportedFeatureError < Error; end
13
+ class ShapeError < Error; end
14
+ class ConversionError < Error; end
15
+ class NativeError < Error; end
16
+
17
+ class Empty
18
+ attr_reader :dtype
19
+
20
+ def initialize(dtype)
21
+ @dtype = dtype.is_a?(DType) ? dtype : DType.for_symbol(dtype)
22
+ freeze
23
+ end
24
+ end
25
+ Slice = Struct.new(:range, :step, keyword_init: true)
8
26
  DEFAULT_PROPERTY_LIST = 0
9
27
 
10
28
  class << self
11
29
  attr_accessor :lib_path
12
30
 
31
+ def slice(range, step:)
32
+ raise ArgumentError, 'slice step must be a positive integer' unless step.is_a?(Integer) && step.positive?
33
+ raise ArgumentError, 'slice range must be a Range' unless range.is_a?(Range)
34
+
35
+ Slice.new(range:, step:)
36
+ end
37
+
13
38
  def search_hdf5lib
14
39
  name = "libhdf5.#{FFI::Platform::LIBSUFFIX}"
15
40
  env_path = ENV['HDF5_LIB_PATH']
@@ -37,6 +62,12 @@ end
37
62
 
38
63
  require_relative 'hdf5/ffi'
39
64
 
65
+ require_relative 'hdf5/file_context'
66
+ require_relative 'hdf5/dtype'
67
+ require_relative 'hdf5/data_helpers'
68
+ require_relative 'hdf5/selection'
69
+ require_relative 'hdf5/hierarchy'
70
+ require_relative 'hdf5/string_codec'
40
71
  require_relative 'hdf5/file'
41
72
  require_relative 'hdf5/group'
42
73
  require_relative 'hdf5/dataset'