cabriolet 0.1.2 → 0.2.1

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.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +703 -38
  3. data/lib/cabriolet/algorithm_factory.rb +250 -0
  4. data/lib/cabriolet/base_compressor.rb +206 -0
  5. data/lib/cabriolet/binary/bitstream.rb +167 -16
  6. data/lib/cabriolet/binary/bitstream_writer.rb +150 -21
  7. data/lib/cabriolet/binary/chm_structures.rb +2 -2
  8. data/lib/cabriolet/binary/hlp_structures.rb +258 -37
  9. data/lib/cabriolet/binary/lit_structures.rb +231 -65
  10. data/lib/cabriolet/binary/oab_structures.rb +17 -1
  11. data/lib/cabriolet/cab/command_handler.rb +226 -0
  12. data/lib/cabriolet/cab/compressor.rb +108 -84
  13. data/lib/cabriolet/cab/decompressor.rb +16 -20
  14. data/lib/cabriolet/cab/extractor.rb +142 -66
  15. data/lib/cabriolet/cab/file_compression_work.rb +52 -0
  16. data/lib/cabriolet/cab/file_compression_worker.rb +89 -0
  17. data/lib/cabriolet/checksum.rb +49 -0
  18. data/lib/cabriolet/chm/command_handler.rb +227 -0
  19. data/lib/cabriolet/chm/compressor.rb +7 -3
  20. data/lib/cabriolet/chm/decompressor.rb +39 -21
  21. data/lib/cabriolet/chm/parser.rb +5 -2
  22. data/lib/cabriolet/cli/base_command_handler.rb +127 -0
  23. data/lib/cabriolet/cli/command_dispatcher.rb +140 -0
  24. data/lib/cabriolet/cli/command_registry.rb +83 -0
  25. data/lib/cabriolet/cli.rb +356 -607
  26. data/lib/cabriolet/collections/file_collection.rb +175 -0
  27. data/lib/cabriolet/compressors/base.rb +1 -1
  28. data/lib/cabriolet/compressors/lzx.rb +241 -54
  29. data/lib/cabriolet/compressors/mszip.rb +35 -3
  30. data/lib/cabriolet/compressors/quantum.rb +36 -95
  31. data/lib/cabriolet/decompressors/base.rb +1 -1
  32. data/lib/cabriolet/decompressors/lzss.rb +13 -3
  33. data/lib/cabriolet/decompressors/lzx.rb +70 -33
  34. data/lib/cabriolet/decompressors/mszip.rb +126 -39
  35. data/lib/cabriolet/decompressors/quantum.rb +83 -53
  36. data/lib/cabriolet/errors.rb +3 -0
  37. data/lib/cabriolet/extraction/base_extractor.rb +88 -0
  38. data/lib/cabriolet/extraction/extractor.rb +171 -0
  39. data/lib/cabriolet/extraction/file_extraction_work.rb +60 -0
  40. data/lib/cabriolet/extraction/file_extraction_worker.rb +106 -0
  41. data/lib/cabriolet/file_entry.rb +156 -0
  42. data/lib/cabriolet/file_manager.rb +144 -0
  43. data/lib/cabriolet/format_base.rb +79 -0
  44. data/lib/cabriolet/hlp/command_handler.rb +282 -0
  45. data/lib/cabriolet/hlp/compressor.rb +28 -238
  46. data/lib/cabriolet/hlp/decompressor.rb +107 -147
  47. data/lib/cabriolet/hlp/parser.rb +52 -101
  48. data/lib/cabriolet/hlp/quickhelp/compression_stream.rb +138 -0
  49. data/lib/cabriolet/hlp/quickhelp/compressor.rb +151 -0
  50. data/lib/cabriolet/hlp/quickhelp/decompressor.rb +558 -0
  51. data/lib/cabriolet/hlp/quickhelp/file_writer.rb +125 -0
  52. data/lib/cabriolet/hlp/quickhelp/huffman_stream.rb +74 -0
  53. data/lib/cabriolet/hlp/quickhelp/huffman_tree.rb +167 -0
  54. data/lib/cabriolet/hlp/quickhelp/offset_calculator.rb +61 -0
  55. data/lib/cabriolet/hlp/quickhelp/parser.rb +274 -0
  56. data/lib/cabriolet/hlp/quickhelp/structure_builder.rb +93 -0
  57. data/lib/cabriolet/hlp/quickhelp/topic_builder.rb +52 -0
  58. data/lib/cabriolet/hlp/quickhelp/topic_compressor.rb +83 -0
  59. data/lib/cabriolet/hlp/winhelp/btree_builder.rb +289 -0
  60. data/lib/cabriolet/hlp/winhelp/compressor.rb +400 -0
  61. data/lib/cabriolet/hlp/winhelp/decompressor.rb +192 -0
  62. data/lib/cabriolet/hlp/winhelp/parser.rb +484 -0
  63. data/lib/cabriolet/hlp/winhelp/zeck_lz77.rb +271 -0
  64. data/lib/cabriolet/huffman/encoder.rb +15 -12
  65. data/lib/cabriolet/huffman/tree.rb +85 -1
  66. data/lib/cabriolet/kwaj/command_handler.rb +213 -0
  67. data/lib/cabriolet/kwaj/compressor.rb +7 -3
  68. data/lib/cabriolet/kwaj/decompressor.rb +18 -12
  69. data/lib/cabriolet/lit/command_handler.rb +221 -0
  70. data/lib/cabriolet/lit/compressor.rb +119 -168
  71. data/lib/cabriolet/lit/content_encoder.rb +76 -0
  72. data/lib/cabriolet/lit/content_type_detector.rb +50 -0
  73. data/lib/cabriolet/lit/decompressor.rb +518 -152
  74. data/lib/cabriolet/lit/directory_builder.rb +153 -0
  75. data/lib/cabriolet/lit/guid_generator.rb +16 -0
  76. data/lib/cabriolet/lit/header_writer.rb +124 -0
  77. data/lib/cabriolet/lit/parser.rb +670 -0
  78. data/lib/cabriolet/lit/piece_builder.rb +74 -0
  79. data/lib/cabriolet/lit/structure_builder.rb +252 -0
  80. data/lib/cabriolet/models/hlp_file.rb +130 -29
  81. data/lib/cabriolet/models/hlp_header.rb +105 -17
  82. data/lib/cabriolet/models/lit_header.rb +212 -25
  83. data/lib/cabriolet/models/szdd_header.rb +10 -2
  84. data/lib/cabriolet/models/winhelp_header.rb +127 -0
  85. data/lib/cabriolet/oab/command_handler.rb +257 -0
  86. data/lib/cabriolet/oab/compressor.rb +17 -8
  87. data/lib/cabriolet/oab/decompressor.rb +41 -10
  88. data/lib/cabriolet/offset_calculator.rb +81 -0
  89. data/lib/cabriolet/plugin.rb +233 -0
  90. data/lib/cabriolet/plugin_manager.rb +453 -0
  91. data/lib/cabriolet/plugin_validator.rb +422 -0
  92. data/lib/cabriolet/quantum_shared.rb +105 -0
  93. data/lib/cabriolet/system/io_system.rb +3 -0
  94. data/lib/cabriolet/system/memory_handle.rb +17 -4
  95. data/lib/cabriolet/szdd/command_handler.rb +217 -0
  96. data/lib/cabriolet/szdd/compressor.rb +15 -11
  97. data/lib/cabriolet/szdd/decompressor.rb +18 -9
  98. data/lib/cabriolet/version.rb +1 -1
  99. data/lib/cabriolet.rb +181 -20
  100. metadata +69 -4
  101. data/lib/cabriolet/auto.rb +0 -173
  102. data/lib/cabriolet/parallel.rb +0 -333
@@ -0,0 +1,422 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cabriolet
4
+ # Validates plugin classes and configurations
5
+ #
6
+ # The PluginValidator provides comprehensive validation for plugins
7
+ # including inheritance checks, metadata validation, version
8
+ # compatibility, and safety scanning.
9
+ #
10
+ # @example Validate a plugin class
11
+ # result = PluginValidator.validate(MyPlugin)
12
+ # if result[:valid]
13
+ # puts "Plugin is valid"
14
+ # else
15
+ # puts "Errors: #{result[:errors].join(', ')}"
16
+ # end
17
+ class PluginValidator
18
+ # Required metadata fields
19
+ REQUIRED_METADATA = %i[name version author description
20
+ cabriolet_version].freeze
21
+
22
+ # Dangerous method names to check for
23
+ DANGEROUS_METHODS = %w[
24
+ system exec spawn ` fork eval instance_eval class_eval
25
+ module_eval binding const_set remove_const send __send__
26
+ method_missing respond_to_missing?
27
+ ].freeze
28
+
29
+ class << self
30
+ # Validate a plugin class
31
+ #
32
+ # Performs comprehensive validation including inheritance, metadata,
33
+ # version compatibility, and safety checks.
34
+ #
35
+ # @param plugin_class [Class] Plugin class to validate
36
+ #
37
+ # @return [Hash] Validation result with:
38
+ # - :valid [Boolean] True if all checks pass
39
+ # - :errors [Array<String>] List of validation errors (empty if
40
+ # valid)
41
+ # - :warnings [Array<String>] List of warnings (non-fatal issues)
42
+ #
43
+ # @example Validate a plugin
44
+ # result = PluginValidator.validate(MyPlugin)
45
+ # result[:valid] #=> true
46
+ # result[:errors] #=> []
47
+ # result[:warnings] #=> ["Uses eval in setup method"]
48
+ def validate(plugin_class)
49
+ errors = []
50
+ warnings = []
51
+
52
+ # Check inheritance
53
+ inherit_errors = validate_inheritance(plugin_class)
54
+ errors.concat(inherit_errors)
55
+
56
+ # If inheritance fails, stop here
57
+ return { valid: false, errors: errors, warnings: warnings } unless
58
+ inherit_errors.empty?
59
+
60
+ # Create instance to check metadata
61
+ begin
62
+ instance = plugin_class.new(nil)
63
+ metadata = instance.metadata
64
+
65
+ # Validate metadata
66
+ meta_errors = validate_metadata(metadata)
67
+ errors.concat(meta_errors)
68
+
69
+ # Check version compatibility
70
+ if metadata[:cabriolet_version]
71
+ version_errors = validate_version_compatibility(
72
+ metadata[:cabriolet_version],
73
+ Cabriolet::VERSION,
74
+ )
75
+ errors.concat(version_errors)
76
+ end
77
+
78
+ # Validate dependencies
79
+ if metadata[:dependencies]
80
+ dep_warnings = validate_dependencies(metadata[:dependencies])
81
+ warnings.concat(dep_warnings)
82
+ end
83
+ rescue NotImplementedError => e
84
+ errors << "Plugin does not implement required method: " \
85
+ "#{e.message}"
86
+ rescue StandardError => e
87
+ errors << "Failed to instantiate plugin: #{e.message}"
88
+ end
89
+
90
+ # Safety checks
91
+ safety_warnings = check_safety(plugin_class)
92
+ warnings.concat(safety_warnings)
93
+
94
+ {
95
+ valid: errors.empty?,
96
+ errors: errors,
97
+ warnings: warnings,
98
+ }
99
+ end
100
+
101
+ # Validate plugin inheritance
102
+ #
103
+ # Checks that the plugin class properly inherits from
104
+ # Cabriolet::Plugin.
105
+ #
106
+ # @param plugin_class [Class] Plugin class to validate
107
+ #
108
+ # @return [Array<String>] List of inheritance errors (empty if valid)
109
+ #
110
+ # @example Valid inheritance
111
+ # PluginValidator.validate_inheritance(MyPlugin)
112
+ # #=> []
113
+ #
114
+ # @example Invalid inheritance
115
+ # PluginValidator.validate_inheritance(Object)
116
+ # #=> ["Plugin must inherit from Cabriolet::Plugin"]
117
+ def validate_inheritance(plugin_class)
118
+ errors = []
119
+
120
+ unless plugin_class.is_a?(Class)
121
+ errors << "Plugin must be a class, got #{plugin_class.class}"
122
+ return errors
123
+ end
124
+
125
+ unless plugin_class < Plugin
126
+ errors << "Plugin must inherit from Cabriolet::Plugin"
127
+ end
128
+
129
+ errors
130
+ end
131
+
132
+ # Validate plugin metadata
133
+ #
134
+ # Checks that all required metadata fields are present and valid.
135
+ #
136
+ # @param metadata [Hash] Plugin metadata to validate
137
+ #
138
+ # @return [Array<String>] List of metadata errors (empty if valid)
139
+ #
140
+ # @example Valid metadata
141
+ # meta = { name: "test", version: "1.0", ... }
142
+ # PluginValidator.validate_metadata(meta)
143
+ # #=> []
144
+ #
145
+ # @example Missing fields
146
+ # PluginValidator.validate_metadata({})
147
+ # #=> ["Missing required metadata: name, version, ..."]
148
+ def validate_metadata(metadata)
149
+ errors = []
150
+
151
+ unless metadata.is_a?(Hash)
152
+ errors << "Metadata must be a Hash"
153
+ return errors
154
+ end
155
+
156
+ # Check required fields
157
+ missing = REQUIRED_METADATA - metadata.keys
158
+ unless missing.empty?
159
+ errors << "Missing required metadata: #{missing.join(', ')}"
160
+ end
161
+
162
+ # Validate field types and formats
163
+ if metadata[:name]
164
+ unless metadata[:name].is_a?(String) &&
165
+ !metadata[:name].empty?
166
+ errors << "Plugin name must be a non-empty string"
167
+ end
168
+
169
+ if metadata[:name].is_a?(String) && metadata[:name] =~ /^[a-z0-9_-]+$/
170
+ # Valid format - do nothing
171
+ elsif metadata[:name].is_a?(String)
172
+ errors << "Plugin name must contain only lowercase letters, " \
173
+ "numbers, hyphens, and underscores"
174
+ end
175
+ end
176
+
177
+ if metadata[:version] && !valid_version?(metadata[:version])
178
+ errors << "Plugin version must be a valid semantic version " \
179
+ "(e.g., '1.0.0')"
180
+ end
181
+
182
+ if metadata[:author] && !(metadata[:author].is_a?(String) &&
183
+ !metadata[:author].empty?)
184
+ errors << "Plugin author must be a non-empty string"
185
+ end
186
+
187
+ if metadata[:description] && !(metadata[:description].is_a?(String) &&
188
+ !metadata[:description].empty?)
189
+ errors << "Plugin description must be a non-empty string"
190
+ end
191
+
192
+ # Optional fields validation
193
+ if metadata[:homepage] && !metadata[:homepage].empty? && !valid_url?(metadata[:homepage])
194
+ errors << "Plugin homepage must be a valid URL"
195
+ end
196
+
197
+ if metadata[:dependencies] && !metadata[:dependencies].is_a?(Array)
198
+ errors << "Plugin dependencies must be an array"
199
+ end
200
+
201
+ if metadata[:tags] && !metadata[:tags].is_a?(Array)
202
+ errors << "Plugin tags must be an array"
203
+ end
204
+
205
+ errors
206
+ end
207
+
208
+ # Validate version compatibility
209
+ #
210
+ # Checks if the plugin's required Cabriolet version matches the
211
+ # current version.
212
+ #
213
+ # @param plugin_version [String] Required Cabriolet version
214
+ # @param cabriolet_version [String] Current Cabriolet version
215
+ #
216
+ # @return [Array<String>] List of version errors (empty if
217
+ # compatible)
218
+ #
219
+ # @example Compatible version
220
+ # PluginValidator.validate_version_compatibility("~> 0.1", "0.1.0")
221
+ # #=> []
222
+ #
223
+ # @example Incompatible version
224
+ # PluginValidator.validate_version_compatibility(">= 2.0", "0.1.0")
225
+ # #=> ["Plugin requires Cabriolet version >= 2.0, ..."]
226
+ def validate_version_compatibility(plugin_version, cabriolet_version)
227
+ errors = []
228
+
229
+ # Parse version requirement
230
+ if plugin_version.start_with?("~>")
231
+ # Pessimistic version constraint
232
+ required = plugin_version.sub("~>", "").strip
233
+ unless version_compatible?(cabriolet_version, required, :pessimistic)
234
+ errors << "Plugin requires Cabriolet version ~> #{required}, " \
235
+ "but #{cabriolet_version} is installed"
236
+ end
237
+ elsif plugin_version.start_with?(">=")
238
+ # Minimum version
239
+ required = plugin_version.sub(">=", "").strip
240
+ unless version_compatible?(cabriolet_version, required, :gte)
241
+ errors << "Plugin requires Cabriolet version >= #{required}, " \
242
+ "but #{cabriolet_version} is installed"
243
+ end
244
+ elsif plugin_version.start_with?("=")
245
+ # Exact version
246
+ required = plugin_version.sub("=", "").strip
247
+ unless cabriolet_version == required
248
+ errors << "Plugin requires exact Cabriolet version #{required}, " \
249
+ "but #{cabriolet_version} is installed"
250
+ end
251
+ end
252
+
253
+ errors
254
+ end
255
+
256
+ # Validate plugin dependencies
257
+ #
258
+ # Checks if dependency specifications are valid. This performs
259
+ # format validation only; actual dependency resolution happens at
260
+ # load time.
261
+ #
262
+ # @param dependencies [Array<String>] Dependency specifications
263
+ #
264
+ # @return [Array<String>] List of validation warnings
265
+ #
266
+ # @example Valid dependencies
267
+ # deps = ["other-plugin >= 1.0"]
268
+ # PluginValidator.validate_dependencies(deps)
269
+ # #=> []
270
+ def validate_dependencies(dependencies)
271
+ warnings = []
272
+
273
+ unless dependencies.is_a?(Array)
274
+ warnings << "Dependencies must be an array"
275
+ return warnings
276
+ end
277
+
278
+ dependencies.each do |dep|
279
+ unless dep.is_a?(String)
280
+ warnings << "Each dependency must be a string"
281
+ next
282
+ end
283
+
284
+ parts = dep.split
285
+ if parts.empty?
286
+ warnings << "Empty dependency specification"
287
+ elsif !/^[a-z0-9_-]+$/.match?(parts[0])
288
+ warnings << "Invalid dependency name: #{parts[0]}"
289
+ end
290
+ end
291
+
292
+ warnings
293
+ end
294
+
295
+ # Check plugin for potentially dangerous code
296
+ #
297
+ # Scans the plugin's source code for dangerous method calls that
298
+ # might pose security risks.
299
+ #
300
+ # @param plugin_class [Class] Plugin class to check
301
+ #
302
+ # @return [Array<String>] List of safety warnings
303
+ #
304
+ # @example Safe plugin
305
+ # PluginValidator.check_safety(MySafePlugin)
306
+ # #=> []
307
+ #
308
+ # @example Potentially dangerous plugin
309
+ # PluginValidator.check_safety(MyDangerousPlugin)
310
+ # #=> ["Uses system call in setup method"]
311
+ def check_safety(plugin_class)
312
+ warnings = []
313
+
314
+ # Get source location
315
+ begin
316
+ methods_to_check = %i[setup activate metadata]
317
+
318
+ methods_to_check.each do |method_name|
319
+ next unless plugin_class.method_defined?(method_name, false)
320
+
321
+ method_obj = plugin_class.instance_method(method_name)
322
+ source_location = method_obj.source_location
323
+
324
+ if source_location && File.exist?(source_location[0])
325
+ source = File.read(source_location[0])
326
+
327
+ DANGEROUS_METHODS.each do |dangerous|
328
+ pattern = /\b#{Regexp.escape(dangerous)}\b/
329
+ if source&.match?(pattern)
330
+ warnings << "Plugin uses potentially dangerous method " \
331
+ "'#{dangerous}' " \
332
+ "in #{source_location[0]}"
333
+ end
334
+ end
335
+ end
336
+ end
337
+ rescue StandardError => e
338
+ warnings << "Could not perform safety check: #{e.message}"
339
+ end
340
+
341
+ warnings
342
+ end
343
+
344
+ private
345
+
346
+ # Check if a version string is valid
347
+ #
348
+ # @param version [String] Version string to check
349
+ #
350
+ # @return [Boolean] True if valid
351
+ def valid_version?(version)
352
+ version.is_a?(String) && version =~ /^\d+\.\d+(\.\d+)?$/
353
+ end
354
+
355
+ # Check if a URL is valid
356
+ #
357
+ # @param url [String] URL string to check
358
+ #
359
+ # @return [Boolean] True if valid
360
+ def valid_url?(url)
361
+ url.is_a?(String) && url =~ %r{^https?://}
362
+ end
363
+
364
+ # Check version compatibility
365
+ #
366
+ # @param actual [String] Actual version
367
+ # @param required [String] Required version
368
+ # @param constraint [Symbol] Constraint type (:gte, :pessimistic)
369
+ #
370
+ # @return [Boolean] True if compatible
371
+ def version_compatible?(actual, required, constraint)
372
+ actual_parts = actual.split(".").map(&:to_i)
373
+ required_parts = required.split(".").map(&:to_i)
374
+
375
+ case constraint
376
+ when :gte
377
+ compare_versions(actual_parts, required_parts) >= 0
378
+ when :pessimistic
379
+ # ~> 1.2 means >= 1.2 and < 2.0
380
+ # ~> 1.2.3 means >= 1.2.3 and < 1.3
381
+ return false if compare_versions(actual_parts,
382
+ required_parts).negative?
383
+
384
+ upper = required_parts.dup
385
+ if required_parts.length >= 3
386
+ # Patch-level constraint
387
+ upper[1] += 1
388
+ upper[2] = 0
389
+ else
390
+ # Minor-level constraint
391
+ upper[0] += 1
392
+ upper[1] = 0
393
+ end
394
+
395
+ compare_versions(actual_parts, upper).negative?
396
+ else
397
+ false
398
+ end
399
+ end
400
+
401
+ # Compare version part arrays
402
+ #
403
+ # @param v1 [Array<Integer>] Version 1 parts
404
+ # @param v2 [Array<Integer>] Version 2 parts
405
+ #
406
+ # @return [Integer] -1, 0, or 1
407
+ def compare_versions(v1, v2)
408
+ max_length = [v1.length, v2.length].max
409
+
410
+ max_length.times do |i|
411
+ p1 = v1[i] || 0
412
+ p2 = v2[i] || 0
413
+
414
+ return -1 if p1 < p2
415
+ return 1 if p1 > p2
416
+ end
417
+
418
+ 0
419
+ end
420
+ end
421
+ end
422
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cabriolet
4
+ # Shared Quantum compression constants and models
5
+ # Used by both Compressors::Quantum and Decompressors::Quantum
6
+ module QuantumShared
7
+ # Frame size (32KB per frame)
8
+ FRAME_SIZE = 32_768
9
+
10
+ # Match constants
11
+ MIN_MATCH = 3
12
+ MAX_MATCH = 259
13
+
14
+ # Position slot tables (same as in qtmd.c)
15
+ POSITION_BASE = [
16
+ 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 384,
17
+ 512, 768, 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12_288, 16_384,
18
+ 24_576, 32_768, 49_152, 65_536, 98_304, 131_072, 196_608, 262_144,
19
+ 393_216, 524_288, 786_432, 1_048_576, 1_572_864
20
+ ].freeze
21
+
22
+ EXTRA_BITS = [
23
+ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8,
24
+ 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16,
25
+ 17, 17, 18, 18, 19, 19
26
+ ].freeze
27
+
28
+ LENGTH_BASE = [
29
+ 0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 14, 18, 22, 26,
30
+ 30, 38, 46, 54, 62, 78, 94, 110, 126, 158, 190, 222, 254
31
+ ].freeze
32
+
33
+ LENGTH_EXTRA = [
34
+ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
35
+ 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0
36
+ ].freeze
37
+
38
+ # Represents a symbol in an arithmetic coding model
39
+ class ModelSymbol
40
+ attr_accessor :sym, :cumfreq
41
+
42
+ def initialize(sym, cumfreq)
43
+ @sym = sym
44
+ @cumfreq = cumfreq
45
+ end
46
+ end
47
+
48
+ # Represents an arithmetic coding model
49
+ class Model
50
+ attr_accessor :shiftsleft, :entries, :syms
51
+
52
+ def initialize(syms, entries)
53
+ @syms = syms
54
+ @entries = entries
55
+ @shiftsleft = 4
56
+ end
57
+ end
58
+
59
+ # Find position slot for a given offset
60
+ #
61
+ # @param offset [Integer] Position offset
62
+ # @return [Integer] Position slot index
63
+ def self.find_position_slot(offset)
64
+ return 0 if offset < 4
65
+
66
+ # Binary search through POSITION_BASE
67
+ low = 1
68
+ high = POSITION_BASE.size - 1
69
+
70
+ while low < high
71
+ mid = (low + high + 1) / 2
72
+ if POSITION_BASE[mid] <= offset
73
+ low = mid
74
+ else
75
+ high = mid - 1
76
+ end
77
+ end
78
+
79
+ low
80
+ end
81
+
82
+ # Find length slot for a given length
83
+ #
84
+ # @param length [Integer] Match length
85
+ # @return [Integer] Length slot index
86
+ def self.find_length_slot(length)
87
+ return 0 if length < 4
88
+
89
+ # Binary search through LENGTH_BASE
90
+ low = 1
91
+ high = LENGTH_BASE.size - 1
92
+
93
+ while low < high
94
+ mid = (low + high + 1) / 2
95
+ if LENGTH_BASE[mid] <= length
96
+ low = mid
97
+ else
98
+ high = mid - 1
99
+ end
100
+ end
101
+
102
+ low
103
+ end
104
+ end
105
+ end
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "file_handle"
4
+ require_relative "memory_handle"
5
+
3
6
  module Cabriolet
4
7
  module System
5
8
  # IOSystem provides an abstraction layer for file I/O operations,
@@ -19,13 +19,19 @@ module Cabriolet
19
19
 
20
20
  # Read bytes from memory
21
21
  #
22
- # @param bytes [Integer] Number of bytes to read
22
+ # @param bytes [Integer, nil] Number of bytes to read (nil = read all remaining)
23
23
  # @return [String] Bytes read (binary encoding)
24
- def read(bytes)
24
+ def read(bytes = nil)
25
25
  return "" if @pos >= @data.bytesize
26
26
 
27
- result = @data.byteslice(@pos, bytes) || ""
28
- @pos += result.bytesize
27
+ if bytes.nil?
28
+ # Read all remaining data
29
+ result = @data.byteslice(@pos..-1) || ""
30
+ @pos = @data.bytesize
31
+ else
32
+ result = @data.byteslice(@pos, bytes) || ""
33
+ @pos += result.bytesize
34
+ end
29
35
  result
30
36
  end
31
37
 
@@ -77,6 +83,13 @@ module Cabriolet
77
83
  @pos
78
84
  end
79
85
 
86
+ # Rewind to the beginning of the handle
87
+ #
88
+ # @return [Integer] New position (0)
89
+ def rewind
90
+ @pos = 0
91
+ end
92
+
80
93
  # Close the handle
81
94
  #
82
95
  # @return [void]