archive_r_ruby 0.1.0 → 0.1.2

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
  SHA256:
3
- metadata.gz: 1ef63ef41cb26313bcc87243183c84d78e9b8fbc14ae05dd76b3db70b8f0fa48
4
- data.tar.gz: f4a2cb797ce90ee25cd95f033df55a71c52db54376931bdd6e531b2ba493baf1
3
+ metadata.gz: c84dab51e8d84438b6eca7d3d96baa9398ea8606307e423b2416b7f12f263ecf
4
+ data.tar.gz: 2b1df674056604436d193e8bfa871bfb823a41c2ba0edc6dd220d30c958f5ef0
5
5
  SHA512:
6
- metadata.gz: 55ab5d4f7b2d7636bdf3fb46a0bc562dc1e445eed7c14a6b217888982d38cc59d68f0791b69911c47ba20ec482e2dac31504ee5a22189acb434c972868b81973
7
- data.tar.gz: 32993a36c25247c88ff17d9b577fba22f5f75516964e2176f6dc6e25df4892eb5ea422df44d2b7da89b3c5095ee045805c67b137968838af70123b4d60364ece
6
+ metadata.gz: 15230cc63edf0704c6d7fd129b0fd5460eb42c474c34eeb5cd6ce834095819ea5202541fdada77a00f4aa63c0379fb152c8c10047b4386a02522858c6579ab71
7
+ data.tar.gz: 8ec342c7bb690af3daeeb4774db7e2ab838273bc36bbb964fbe3cb825cddb0963d1700948e03c5cb1dc7b4ece6f513d711a42e7e4cbee5e4cc061586657d9306
@@ -1,5 +1,5 @@
1
1
  archive_r License
2
- Version: 0.1.0 (2025-10-25)
2
+ Version: 0.1.2 (2025-12-02)
3
3
 
4
4
  ----------------------------------------
5
5
  Primary License
@@ -44,13 +44,34 @@ License shown above.
44
44
  - Purpose: header-only binding generator for the Python extension module.
45
45
  - License: BSD-style License (https://github.com/pybind/pybind11)
46
46
 
47
- 3. rake (development dependency for Ruby bindings)
48
- - Purpose: build and release tasks for the Ruby gem.
49
- - License: MIT License (https://github.com/ruby/rake)
47
+ The following components are redistributed only because libarchive (bundled with archive_r) depends on them at runtime:
50
48
 
51
- 4. minitest (development dependency for Ruby bindings)
52
- - Purpose: unit testing framework for the Ruby gem.
53
- - License: MIT License (https://github.com/minitest/minitest)
49
+ 3. zlib
50
+ - Purpose: libarchive dependency providing DEFLATE compression; bundled inside archive_r binaries and wheels because libarchive requires it.
51
+ - License: zlib License (https://zlib.net/zlib_license.html)
54
52
 
53
+ 4. bzip2
54
+ - Purpose: libarchive dependency providing bzip2 compression support; distributed with archive_r artifacts.
55
+ - License: BSD-style license (https://sourceware.org/bzip2/)
56
+
57
+ 5. liblzma (XZ Utils)
58
+ - Purpose: libarchive dependency providing LZMA/XZ compression; included with archive_r packages.
59
+ - License: Public Domain + GNU LGPLv2.1+ (https://tukaani.org/xz/)
60
+
61
+ 6. libxml2
62
+ - Purpose: libarchive dependency used for archive formats such as xar; distributed alongside archive_r.
63
+ - License: MIT-style License (http://xmlsoft.org/)
64
+
65
+ 7. zstd
66
+ - Purpose: libarchive dependency providing Zstandard compression; shipped within archive_r binaries.
67
+ - License: BSD License (https://github.com/facebook/zstd)
68
+
69
+ 8. OpenSSL 3
70
+ - Purpose: libarchive dependency providing cryptographic support for encrypted archives; included with archive_r packages.
71
+ - License: Apache License 2.0 with OpenSSL exception (https://www.openssl.org/source/license.html)
72
+
73
+ 9. libiconv / libcharset
74
+ - Purpose: libxml2/libarchive dependency for character set conversion; redistributed with archive_r artifacts.
75
+ - License: GNU LGPLv2.1+ (https://www.gnu.org/software/libiconv/)
55
76
  Users of archive_r should review the linked third-party licenses to ensure
56
77
  compliance with their terms when redistributing this software.
@@ -4,9 +4,11 @@
4
4
  #include "archive_r/data_stream.h"
5
5
  #include "archive_r/entry.h"
6
6
  #include "archive_r/entry_fault.h"
7
+ #include "archive_r/multi_volume_stream_base.h"
7
8
  #include "archive_r/path_hierarchy_utils.h"
8
9
  #include "archive_r/traverser.h"
9
10
  #include <cctype>
11
+ #include <cstdio>
10
12
  #include <cstring>
11
13
  #include <memory>
12
14
  #include <ruby.h>
@@ -16,6 +18,7 @@
16
18
  #include <variant>
17
19
  #include <vector>
18
20
  #include <limits>
21
+ #include <optional>
19
22
 
20
23
  using namespace archive_r;
21
24
 
@@ -24,15 +27,25 @@ static VALUE mArchive_r;
24
27
  static VALUE cTraverser;
25
28
  static VALUE cEntry;
26
29
  static ID rb_id_read_method;
27
- static ID rb_id_rewind_method;
28
30
  static ID rb_id_seek_method;
29
31
  static ID rb_id_tell_method;
30
32
  static ID rb_id_eof_method;
33
+ static ID rb_id_close_method;
34
+ static ID rb_id_size_method;
31
35
  static ID rb_id_call_method;
36
+ static ID rb_id_open_part_method;
37
+ static ID rb_id_close_part_method;
38
+ static ID rb_id_read_part_method;
39
+ static ID rb_id_seek_part_method;
40
+ static ID rb_id_part_size_method;
41
+ static ID rb_id_open_part_io_method;
42
+ static ID rb_id_close_part_io_method;
32
43
  struct RubyCallbackHolder;
33
44
  static std::shared_ptr<RubyCallbackHolder> g_stream_factory_callback;
34
45
 
35
46
  // Helper: Convert Ruby string to C++ string
47
+ static VALUE cStream;
48
+ static PathHierarchy rb_value_to_path_hierarchy(VALUE value);
36
49
  static std::string rb_string_to_cpp(VALUE rb_str) {
37
50
  Check_Type(rb_str, T_STRING);
38
51
  return std::string(RSTRING_PTR(rb_str), RSTRING_LEN(rb_str));
@@ -85,96 +98,257 @@ struct RubyCallbackHolder {
85
98
  VALUE proc_value;
86
99
  };
87
100
 
88
- class RubyIOStream : public IDataStream {
101
+ class RubyUserStream : public MultiVolumeStreamBase {
89
102
  public:
90
- RubyIOStream(VALUE io, PathHierarchy hierarchy)
91
- : _io(io)
92
- , _hierarchy(std::move(hierarchy))
93
- , _at_end(false)
94
- , _seekable(rb_respond_to(io, rb_id_seek_method))
95
- , _tellable(rb_respond_to(io, rb_id_tell_method))
96
- , _rewindable(rb_respond_to(io, rb_id_rewind_method))
97
- , _has_eof(rb_respond_to(io, rb_id_eof_method)) {
98
- // Validate before GC registration to avoid resource leak on validation failure
99
- if (!_rewindable) {
100
- rb_raise(rb_eTypeError, "stream factory IO must respond to #read and #rewind");
103
+ RubyUserStream(VALUE ruby_stream, PathHierarchy hierarchy, std::optional<bool> seekable_override)
104
+ : MultiVolumeStreamBase(std::move(hierarchy), determine_seek_support(ruby_stream, seekable_override))
105
+ , _ruby_stream(ruby_stream)
106
+ , _has_close(rb_respond_to(ruby_stream, rb_id_close_part_method))
107
+ , _has_seek(rb_respond_to(ruby_stream, rb_id_seek_part_method))
108
+ , _has_size(rb_respond_to(ruby_stream, rb_id_part_size_method))
109
+ , _has_open_part_io(rb_respond_to(ruby_stream, rb_id_open_part_io_method))
110
+ , _has_close_part_io(rb_respond_to(ruby_stream, rb_id_close_part_io_method))
111
+ , _active_io(Qnil)
112
+ , _active_io_seekable(false)
113
+ , _active_io_tellable(false)
114
+ , _active_io_has_close(false)
115
+ , _active_io_has_size(false) {
116
+ ensure_required_methods(ruby_stream);
117
+ rb_gc_register_address(&_ruby_stream);
118
+ }
119
+
120
+ ~RubyUserStream() override {
121
+ release_active_io();
122
+ rb_gc_unregister_address(&_ruby_stream);
123
+ }
124
+
125
+ protected:
126
+ void open_single_part(const PathHierarchy &single_part) override {
127
+ if (_has_open_part_io) {
128
+ VALUE arg = path_hierarchy_to_rb(single_part);
129
+ VALUE io = rb_funcall(_ruby_stream, rb_id_open_part_io_method, 1, arg);
130
+ if (NIL_P(io)) {
131
+ rb_raise(rb_eRuntimeError, "open_part_io must return an IO-like object");
132
+ }
133
+ activate_io(io);
134
+ return;
101
135
  }
102
- rb_gc_register_address(&_io);
136
+
137
+ VALUE arg = path_hierarchy_to_rb(single_part);
138
+ rb_funcall(_ruby_stream, rb_id_open_part_method, 1, arg);
103
139
  }
104
140
 
105
- ~RubyIOStream() override { rb_gc_unregister_address(&_io); }
141
+ void close_single_part() override {
142
+ if (_has_open_part_io) {
143
+ release_active_io();
144
+ if (_has_close_part_io) {
145
+ rb_funcall(_ruby_stream, rb_id_close_part_io_method, 0);
146
+ }
147
+ return;
148
+ }
149
+
150
+ if (_has_close) {
151
+ rb_funcall(_ruby_stream, rb_id_close_part_method, 0);
152
+ }
153
+ }
106
154
 
107
- ssize_t read(void *buffer, size_t size) override {
155
+ ssize_t read_from_single_part(void *buffer, size_t size) override {
108
156
  if (size == 0) {
109
157
  return 0;
110
158
  }
111
159
 
112
- VALUE result = rb_funcall(_io, rb_id_read_method, 1, SIZET2NUM(size));
160
+ if (_has_open_part_io) {
161
+ if (_active_io == Qnil) {
162
+ rb_raise(rb_eRuntimeError, "open_part_io must return an IO before reading");
163
+ }
164
+ VALUE result = rb_funcall(_active_io, rb_id_read_method, 1, SIZET2NUM(size));
165
+ if (NIL_P(result)) {
166
+ return 0;
167
+ }
168
+ Check_Type(result, T_STRING);
169
+ const ssize_t length = static_cast<ssize_t>(RSTRING_LEN(result));
170
+ if (length <= 0) {
171
+ return 0;
172
+ }
173
+ std::memcpy(buffer, RSTRING_PTR(result), static_cast<size_t>(length));
174
+ return length;
175
+ }
176
+
177
+ VALUE result = rb_funcall(_ruby_stream, rb_id_read_part_method, 1, SIZET2NUM(size));
113
178
  if (NIL_P(result)) {
114
- _at_end = true;
115
179
  return 0;
116
180
  }
117
181
  Check_Type(result, T_STRING);
118
- const ssize_t bytes_read = static_cast<ssize_t>(RSTRING_LEN(result));
119
- if (bytes_read > 0) {
120
- std::memcpy(buffer, RSTRING_PTR(result), static_cast<size_t>(bytes_read));
121
- return bytes_read;
182
+ const ssize_t length = static_cast<ssize_t>(RSTRING_LEN(result));
183
+ if (length <= 0) {
184
+ return 0;
122
185
  }
186
+ std::memcpy(buffer, RSTRING_PTR(result), static_cast<size_t>(length));
187
+ return length;
188
+ }
123
189
 
124
- if (_has_eof) {
125
- VALUE eof_val = rb_funcall(_io, rb_id_eof_method, 0);
126
- _at_end = RTEST(eof_val);
190
+ int64_t seek_within_single_part(int64_t offset, int whence) override {
191
+ if (_has_open_part_io && _active_io != Qnil && _active_io_seekable) {
192
+ VALUE result = rb_funcall(_active_io, rb_id_seek_method, 2, LL2NUM(offset), INT2NUM(whence));
193
+ return NUM2LL(result);
127
194
  }
128
- return 0;
129
- }
130
195
 
131
- void rewind() override {
132
- if (!_rewindable) {
133
- rb_raise(rb_eRuntimeError, "IO object does not respond to #rewind");
196
+ if (!_has_seek) {
197
+ return -1;
134
198
  }
135
- rb_funcall(_io, rb_id_rewind_method, 0);
136
- _at_end = false;
199
+ VALUE result = rb_funcall(_ruby_stream, rb_id_seek_part_method, 2, LL2NUM(offset), INT2NUM(whence));
200
+ return NUM2LL(result);
137
201
  }
138
202
 
139
- bool at_end() const override {
140
- if (_has_eof) {
141
- VALUE eof_val = rb_funcall(_io, rb_id_eof_method, 0);
142
- return RTEST(eof_val);
203
+ int64_t size_of_single_part(const PathHierarchy &single_part) override {
204
+ if (_has_open_part_io && _active_io != Qnil) {
205
+ if (_active_io_has_size) {
206
+ VALUE result = rb_funcall(_active_io, rb_id_size_method, 0);
207
+ return NUM2LL(result);
208
+ }
209
+ if (_active_io_seekable && _active_io_tellable) {
210
+ VALUE current = rb_funcall(_active_io, rb_id_tell_method, 0);
211
+ rb_funcall(_active_io, rb_id_seek_method, 2, LL2NUM(0), INT2NUM(SEEK_END));
212
+ VALUE end_pos = rb_funcall(_active_io, rb_id_tell_method, 0);
213
+ rb_funcall(_active_io, rb_id_seek_method, 2, current, INT2NUM(SEEK_SET));
214
+ return NUM2LL(end_pos);
215
+ }
143
216
  }
144
- return _at_end;
145
- }
146
217
 
147
- int64_t seek(int64_t offset, int whence) override {
148
- if (!_seekable) {
218
+ if (!_has_size) {
149
219
  return -1;
150
220
  }
151
- VALUE result = rb_funcall(_io, rb_id_seek_method, 2, LL2NUM(offset), INT2NUM(whence));
152
- _at_end = false;
221
+ VALUE arg = path_hierarchy_to_rb(single_part);
222
+ VALUE result = rb_funcall(_ruby_stream, rb_id_part_size_method, 1, arg);
153
223
  return NUM2LL(result);
154
224
  }
155
225
 
156
- int64_t tell() const override {
157
- if (!_tellable) {
158
- return -1;
226
+ private:
227
+ static bool determine_seek_support(VALUE ruby_stream, const std::optional<bool> &override_flag) {
228
+ if (override_flag.has_value()) {
229
+ return *override_flag;
230
+ }
231
+ if (rb_respond_to(ruby_stream, rb_id_seek_part_method)) {
232
+ return true;
233
+ }
234
+ if (rb_respond_to(ruby_stream, rb_id_open_part_io_method)) {
235
+ return true;
236
+ }
237
+ return false;
238
+ }
239
+
240
+ static void ensure_required_methods(VALUE ruby_stream) {
241
+ if (!rb_respond_to(ruby_stream, rb_id_open_part_method) && !rb_respond_to(ruby_stream, rb_id_open_part_io_method)) {
242
+ rb_raise(rb_eNotImpError, "Stream subclasses must implement #open_part_io or #open_part");
243
+ }
244
+ if (!rb_respond_to(ruby_stream, rb_id_read_part_method) && !rb_respond_to(ruby_stream, rb_id_open_part_io_method)) {
245
+ rb_raise(rb_eNotImpError, "Stream subclasses must implement #open_part_io or #read_part");
159
246
  }
160
- VALUE result = rb_funcall(_io, rb_id_tell_method, 0);
161
- return NUM2LL(result);
162
247
  }
163
248
 
164
- bool can_seek() const override { return _seekable; }
249
+ void activate_io(VALUE io) {
250
+ if (!rb_respond_to(io, rb_id_read_method)) {
251
+ rb_raise(rb_eTypeError, "open_part_io must return an object responding to #read");
252
+ }
253
+ release_active_io();
254
+ _active_io = io;
255
+ rb_gc_register_address(&_active_io);
256
+ _active_io_seekable = rb_respond_to(io, rb_id_seek_method);
257
+ _active_io_tellable = rb_respond_to(io, rb_id_tell_method);
258
+ _active_io_has_close = rb_respond_to(io, rb_id_close_method);
259
+ _active_io_has_size = rb_respond_to(io, rb_id_size_method);
260
+ }
165
261
 
166
- PathHierarchy source_hierarchy() const override { return _hierarchy; }
262
+ void release_active_io() {
263
+ if (_active_io == Qnil) {
264
+ return;
265
+ }
266
+ if (_active_io_has_close) {
267
+ rb_funcall(_active_io, rb_id_close_method, 0);
268
+ }
269
+ rb_gc_unregister_address(&_active_io);
270
+ _active_io = Qnil;
271
+ _active_io_seekable = false;
272
+ _active_io_tellable = false;
273
+ _active_io_has_close = false;
274
+ _active_io_has_size = false;
275
+ }
167
276
 
168
- private:
169
- VALUE _io;
170
- PathHierarchy _hierarchy;
171
- mutable bool _at_end;
172
- bool _seekable;
173
- bool _tellable;
174
- bool _rewindable;
175
- bool _has_eof;
277
+ VALUE _ruby_stream;
278
+ bool _has_close;
279
+ bool _has_seek;
280
+ bool _has_size;
281
+ bool _has_open_part_io;
282
+ bool _has_close_part_io;
283
+ VALUE _active_io;
284
+ bool _active_io_seekable;
285
+ bool _active_io_tellable;
286
+ bool _active_io_has_close;
287
+ bool _active_io_has_size;
288
+ };
289
+
290
+ struct RubyStreamWrapper {
291
+ std::shared_ptr<RubyUserStream> stream;
292
+ };
293
+
294
+ static void stream_wrapper_free(void *ptr) {
295
+ auto *wrapper = static_cast<RubyStreamWrapper *>(ptr);
296
+ delete wrapper;
297
+ }
298
+
299
+ static size_t stream_wrapper_memsize(const void *ptr) {
300
+ return sizeof(RubyStreamWrapper);
301
+ }
302
+
303
+ static const rb_data_type_t stream_wrapper_type = {
304
+ "ArchiveR::Stream",
305
+ {
306
+ nullptr,
307
+ stream_wrapper_free,
308
+ stream_wrapper_memsize,
309
+ },
310
+ nullptr,
311
+ nullptr,
312
+ RUBY_TYPED_FREE_IMMEDIATELY,
176
313
  };
177
314
 
315
+ static RubyStreamWrapper *get_stream_wrapper(VALUE self) {
316
+ RubyStreamWrapper *wrapper = nullptr;
317
+ TypedData_Get_Struct(self, RubyStreamWrapper, &stream_wrapper_type, wrapper);
318
+ if (!wrapper) {
319
+ rb_raise(rb_eRuntimeError, "invalid Stream wrapper");
320
+ }
321
+ return wrapper;
322
+ }
323
+
324
+ static VALUE stream_allocate(VALUE klass) {
325
+ auto *wrapper = new RubyStreamWrapper();
326
+ wrapper->stream.reset();
327
+ return TypedData_Wrap_Struct(klass, &stream_wrapper_type, wrapper);
328
+ }
329
+
330
+ static VALUE stream_initialize(int argc, VALUE *argv, VALUE self) {
331
+ RubyStreamWrapper *wrapper = get_stream_wrapper(self);
332
+ if (wrapper->stream) {
333
+ rb_raise(rb_eRuntimeError, "Stream already initialized");
334
+ }
335
+ VALUE hierarchy_value = Qnil;
336
+ VALUE opts = Qnil;
337
+ rb_scan_args(argc, argv, "11", &hierarchy_value, &opts);
338
+ PathHierarchy hierarchy = rb_value_to_path_hierarchy(hierarchy_value);
339
+ std::optional<bool> seekable_override;
340
+ if (!NIL_P(opts)) {
341
+ Check_Type(opts, T_HASH);
342
+ static ID id_seekable = rb_intern("seekable");
343
+ VALUE seekable_value = rb_hash_aref(opts, ID2SYM(id_seekable));
344
+ if (!NIL_P(seekable_value)) {
345
+ seekable_override = RTEST(seekable_value);
346
+ }
347
+ }
348
+ wrapper->stream = std::make_shared<RubyUserStream>(self, std::move(hierarchy), seekable_override);
349
+ return self;
350
+ }
351
+
178
352
  static VALUE entry_fault_to_rb(const EntryFault &fault) {
179
353
  VALUE hash = rb_hash_new();
180
354
  static ID id_message = rb_intern("message");
@@ -371,16 +545,42 @@ static std::vector<PathHierarchy> rb_paths_to_hierarchies(VALUE paths) {
371
545
  return result;
372
546
  }
373
547
 
548
+ static VALUE invoke_ruby_stream_factory(const std::shared_ptr<RubyCallbackHolder> &holder, const PathHierarchy &hierarchy) {
549
+ struct FactoryPayload {
550
+ std::shared_ptr<RubyCallbackHolder> holder;
551
+ VALUE arg;
552
+ } payload{ holder, path_hierarchy_to_rb(hierarchy) };
553
+
554
+ auto invoke = [](VALUE data) -> VALUE {
555
+ auto *info = reinterpret_cast<FactoryPayload *>(data);
556
+ return rb_funcall(info->holder->proc_value, rb_id_call_method, 1, info->arg);
557
+ };
558
+
559
+ int state = 0;
560
+ VALUE result = rb_protect(invoke, reinterpret_cast<VALUE>(&payload), &state);
561
+ if (state != 0) {
562
+ rb_jump_tag(state);
563
+ }
564
+ return result;
565
+ }
566
+
374
567
  static std::shared_ptr<IDataStream> stream_from_ruby_value(VALUE value, const PathHierarchy &requested) {
375
568
  if (NIL_P(value)) {
376
569
  return nullptr;
377
570
  }
378
571
 
379
- if (!rb_respond_to(value, rb_id_read_method)) {
380
- rb_raise(rb_eTypeError, "stream factory result must respond to #read");
572
+ if (rb_obj_is_kind_of(value, cStream)) {
573
+ RubyStreamWrapper *wrapper = get_stream_wrapper(value);
574
+ if (!wrapper->stream) {
575
+ rb_raise(rb_eRuntimeError, "Stream instance is not initialized");
576
+ }
577
+ if (!hierarchies_equal(wrapper->stream->source_hierarchy(), requested)) {
578
+ rb_raise(rb_eArgError, "Stream instance must be created with the same hierarchy passed to the factory");
579
+ }
580
+ return wrapper->stream;
381
581
  }
382
582
 
383
- return std::make_shared<RubyIOStream>(value, requested);
583
+ rb_raise(rb_eTypeError, "stream factory must return nil or an Archive_r::Stream instance");
384
584
  }
385
585
 
386
586
  // Helper: Convert EntryMetadataValue to Ruby object
@@ -495,22 +695,7 @@ static VALUE archive_r_register_stream_factory(int argc, VALUE *argv, VALUE self
495
695
  auto holder = std::make_shared<RubyCallbackHolder>(callable);
496
696
 
497
697
  RootStreamFactory factory = [holder](const PathHierarchy &hierarchy) -> std::shared_ptr<IDataStream> {
498
- struct FactoryPayload {
499
- std::shared_ptr<RubyCallbackHolder> holder;
500
- VALUE arg;
501
- } payload{ holder, path_hierarchy_to_rb(hierarchy) };
502
-
503
- auto invoke = [](VALUE data) -> VALUE {
504
- auto *info = reinterpret_cast<FactoryPayload *>(data);
505
- return rb_funcall(info->holder->proc_value, rb_id_call_method, 1, info->arg);
506
- };
507
-
508
- int state = 0;
509
- VALUE result = rb_protect(invoke, reinterpret_cast<VALUE>(&payload), &state);
510
- if (state != 0) {
511
- rb_jump_tag(state);
512
- }
513
-
698
+ VALUE result = invoke_ruby_stream_factory(holder, hierarchy);
514
699
  return stream_from_ruby_value(result, hierarchy);
515
700
  };
516
701
 
@@ -867,13 +1052,24 @@ static VALUE traverser_s_open(int argc, VALUE *argv, VALUE klass) {
867
1052
  extern "C" void Init_archive_r() {
868
1053
  // Define module Archive_r
869
1054
  mArchive_r = rb_define_module("Archive_r");
1055
+ cStream = rb_define_class_under(mArchive_r, "Stream", rb_cObject);
1056
+ rb_define_alloc_func(cStream, stream_allocate);
1057
+ rb_define_method(cStream, "initialize", RUBY_METHOD_FUNC(stream_initialize), -1);
870
1058
 
871
1059
  rb_id_read_method = rb_intern("read");
872
- rb_id_rewind_method = rb_intern("rewind");
873
1060
  rb_id_seek_method = rb_intern("seek");
874
1061
  rb_id_tell_method = rb_intern("tell");
875
1062
  rb_id_eof_method = rb_intern("eof?");
1063
+ rb_id_close_method = rb_intern("close");
1064
+ rb_id_size_method = rb_intern("size");
876
1065
  rb_id_call_method = rb_intern("call");
1066
+ rb_id_open_part_method = rb_intern("open_part");
1067
+ rb_id_close_part_method = rb_intern("close_part");
1068
+ rb_id_read_part_method = rb_intern("read_part");
1069
+ rb_id_seek_part_method = rb_intern("seek_part");
1070
+ rb_id_part_size_method = rb_intern("part_size");
1071
+ rb_id_open_part_io_method = rb_intern("open_part_io");
1072
+ rb_id_close_part_io_method = rb_intern("close_part_io");
877
1073
 
878
1074
  // Define Entry class
879
1075
  cEntry = rb_define_class_under(mArchive_r, "Entry", rb_cObject);
@@ -1,5 +1,5 @@
1
1
  archive_r License
2
- Version: 0.1.0 (2025-10-25)
2
+ Version: 0.1.2 (2025-12-02)
3
3
 
4
4
  ----------------------------------------
5
5
  Primary License
@@ -44,13 +44,34 @@ License shown above.
44
44
  - Purpose: header-only binding generator for the Python extension module.
45
45
  - License: BSD-style License (https://github.com/pybind/pybind11)
46
46
 
47
- 3. rake (development dependency for Ruby bindings)
48
- - Purpose: build and release tasks for the Ruby gem.
49
- - License: MIT License (https://github.com/ruby/rake)
47
+ The following components are redistributed only because libarchive (bundled with archive_r) depends on them at runtime:
50
48
 
51
- 4. minitest (development dependency for Ruby bindings)
52
- - Purpose: unit testing framework for the Ruby gem.
53
- - License: MIT License (https://github.com/minitest/minitest)
49
+ 3. zlib
50
+ - Purpose: libarchive dependency providing DEFLATE compression; bundled inside archive_r binaries and wheels because libarchive requires it.
51
+ - License: zlib License (https://zlib.net/zlib_license.html)
54
52
 
53
+ 4. bzip2
54
+ - Purpose: libarchive dependency providing bzip2 compression support; distributed with archive_r artifacts.
55
+ - License: BSD-style license (https://sourceware.org/bzip2/)
56
+
57
+ 5. liblzma (XZ Utils)
58
+ - Purpose: libarchive dependency providing LZMA/XZ compression; included with archive_r packages.
59
+ - License: Public Domain + GNU LGPLv2.1+ (https://tukaani.org/xz/)
60
+
61
+ 6. libxml2
62
+ - Purpose: libarchive dependency used for archive formats such as xar; distributed alongside archive_r.
63
+ - License: MIT-style License (http://xmlsoft.org/)
64
+
65
+ 7. zstd
66
+ - Purpose: libarchive dependency providing Zstandard compression; shipped within archive_r binaries.
67
+ - License: BSD License (https://github.com/facebook/zstd)
68
+
69
+ 8. OpenSSL 3
70
+ - Purpose: libarchive dependency providing cryptographic support for encrypted archives; included with archive_r packages.
71
+ - License: Apache License 2.0 with OpenSSL exception (https://www.openssl.org/source/license.html)
72
+
73
+ 9. libiconv / libcharset
74
+ - Purpose: libxml2/libarchive dependency for character set conversion; redistributed with archive_r artifacts.
75
+ - License: GNU LGPLv2.1+ (https://www.gnu.org/software/libiconv/)
55
76
  Users of archive_r should review the linked third-party licenses to ensure
56
77
  compliance with their terms when redistributing this software.
data/lib/archive_r.rb CHANGED
@@ -10,7 +10,7 @@ rescue LoadError
10
10
  end
11
11
 
12
12
  module Archive_r
13
- VERSION = "0.1.0"
13
+ VERSION = "0.1.2"
14
14
  # Common archive formats excluding libarchive's mtree/raw pseudo formats
15
15
  STANDARD_FORMATS = %w[
16
16
  7zip ar cab cpio empty iso9660 lha rar tar warc xar zip
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: archive_r_ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
- - raiso.tcs
7
+ - raizo.tcs
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-11-29 00:00:00.000000000 Z
11
+ date: 2025-12-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -38,16 +38,16 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '5.0'
41
- description: Fast archive traversal library with support for nested archives and multipart
42
- files
41
+ description: Ruby bindings for archive_r that recursively walk nested and multipart
42
+ archives directly from the source stream without creating temporary files
43
43
  email:
44
- - raiso.tcs@users.noreply.github.com
44
+ - raizo.tcs@users.noreply.github.com
45
45
  executables: []
46
46
  extensions:
47
47
  - ext/archive_r/extconf.rb
48
48
  extra_rdoc_files: []
49
49
  files:
50
- - LICENSE
50
+ - LICENSE.txt
51
51
  - README.md
52
52
  - ext/archive_r/archive_r_ext.cc
53
53
  - ext/archive_r/extconf.rb
@@ -108,5 +108,5 @@ requirements: []
108
108
  rubygems_version: 3.4.20
109
109
  signing_key:
110
110
  specification_version: 4
111
- summary: Ruby bindings for archive_r library
111
+ summary: Ruby bindings for archive_r that traverse nested archives without temp extraction
112
112
  test_files: []