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.
- checksums.yaml +4 -4
- data/README.adoc +703 -38
- data/lib/cabriolet/algorithm_factory.rb +250 -0
- data/lib/cabriolet/base_compressor.rb +206 -0
- data/lib/cabriolet/binary/bitstream.rb +167 -16
- data/lib/cabriolet/binary/bitstream_writer.rb +150 -21
- data/lib/cabriolet/binary/chm_structures.rb +2 -2
- data/lib/cabriolet/binary/hlp_structures.rb +258 -37
- data/lib/cabriolet/binary/lit_structures.rb +231 -65
- data/lib/cabriolet/binary/oab_structures.rb +17 -1
- data/lib/cabriolet/cab/command_handler.rb +226 -0
- data/lib/cabriolet/cab/compressor.rb +108 -84
- data/lib/cabriolet/cab/decompressor.rb +16 -20
- data/lib/cabriolet/cab/extractor.rb +142 -66
- data/lib/cabriolet/cab/file_compression_work.rb +52 -0
- data/lib/cabriolet/cab/file_compression_worker.rb +89 -0
- data/lib/cabriolet/checksum.rb +49 -0
- data/lib/cabriolet/chm/command_handler.rb +227 -0
- data/lib/cabriolet/chm/compressor.rb +7 -3
- data/lib/cabriolet/chm/decompressor.rb +39 -21
- data/lib/cabriolet/chm/parser.rb +5 -2
- data/lib/cabriolet/cli/base_command_handler.rb +127 -0
- data/lib/cabriolet/cli/command_dispatcher.rb +140 -0
- data/lib/cabriolet/cli/command_registry.rb +83 -0
- data/lib/cabriolet/cli.rb +356 -607
- data/lib/cabriolet/collections/file_collection.rb +175 -0
- data/lib/cabriolet/compressors/base.rb +1 -1
- data/lib/cabriolet/compressors/lzx.rb +241 -54
- data/lib/cabriolet/compressors/mszip.rb +35 -3
- data/lib/cabriolet/compressors/quantum.rb +36 -95
- data/lib/cabriolet/decompressors/base.rb +1 -1
- data/lib/cabriolet/decompressors/lzss.rb +13 -3
- data/lib/cabriolet/decompressors/lzx.rb +70 -33
- data/lib/cabriolet/decompressors/mszip.rb +126 -39
- data/lib/cabriolet/decompressors/quantum.rb +83 -53
- data/lib/cabriolet/errors.rb +3 -0
- data/lib/cabriolet/extraction/base_extractor.rb +88 -0
- data/lib/cabriolet/extraction/extractor.rb +171 -0
- data/lib/cabriolet/extraction/file_extraction_work.rb +60 -0
- data/lib/cabriolet/extraction/file_extraction_worker.rb +106 -0
- data/lib/cabriolet/file_entry.rb +156 -0
- data/lib/cabriolet/file_manager.rb +144 -0
- data/lib/cabriolet/format_base.rb +79 -0
- data/lib/cabriolet/hlp/command_handler.rb +282 -0
- data/lib/cabriolet/hlp/compressor.rb +28 -238
- data/lib/cabriolet/hlp/decompressor.rb +107 -147
- data/lib/cabriolet/hlp/parser.rb +52 -101
- data/lib/cabriolet/hlp/quickhelp/compression_stream.rb +138 -0
- data/lib/cabriolet/hlp/quickhelp/compressor.rb +151 -0
- data/lib/cabriolet/hlp/quickhelp/decompressor.rb +558 -0
- data/lib/cabriolet/hlp/quickhelp/file_writer.rb +125 -0
- data/lib/cabriolet/hlp/quickhelp/huffman_stream.rb +74 -0
- data/lib/cabriolet/hlp/quickhelp/huffman_tree.rb +167 -0
- data/lib/cabriolet/hlp/quickhelp/offset_calculator.rb +61 -0
- data/lib/cabriolet/hlp/quickhelp/parser.rb +274 -0
- data/lib/cabriolet/hlp/quickhelp/structure_builder.rb +93 -0
- data/lib/cabriolet/hlp/quickhelp/topic_builder.rb +52 -0
- data/lib/cabriolet/hlp/quickhelp/topic_compressor.rb +83 -0
- data/lib/cabriolet/hlp/winhelp/btree_builder.rb +289 -0
- data/lib/cabriolet/hlp/winhelp/compressor.rb +400 -0
- data/lib/cabriolet/hlp/winhelp/decompressor.rb +192 -0
- data/lib/cabriolet/hlp/winhelp/parser.rb +484 -0
- data/lib/cabriolet/hlp/winhelp/zeck_lz77.rb +271 -0
- data/lib/cabriolet/huffman/encoder.rb +15 -12
- data/lib/cabriolet/huffman/tree.rb +85 -1
- data/lib/cabriolet/kwaj/command_handler.rb +213 -0
- data/lib/cabriolet/kwaj/compressor.rb +7 -3
- data/lib/cabriolet/kwaj/decompressor.rb +18 -12
- data/lib/cabriolet/lit/command_handler.rb +221 -0
- data/lib/cabriolet/lit/compressor.rb +119 -168
- data/lib/cabriolet/lit/content_encoder.rb +76 -0
- data/lib/cabriolet/lit/content_type_detector.rb +50 -0
- data/lib/cabriolet/lit/decompressor.rb +518 -152
- data/lib/cabriolet/lit/directory_builder.rb +153 -0
- data/lib/cabriolet/lit/guid_generator.rb +16 -0
- data/lib/cabriolet/lit/header_writer.rb +124 -0
- data/lib/cabriolet/lit/parser.rb +670 -0
- data/lib/cabriolet/lit/piece_builder.rb +74 -0
- data/lib/cabriolet/lit/structure_builder.rb +252 -0
- data/lib/cabriolet/models/hlp_file.rb +130 -29
- data/lib/cabriolet/models/hlp_header.rb +105 -17
- data/lib/cabriolet/models/lit_header.rb +212 -25
- data/lib/cabriolet/models/szdd_header.rb +10 -2
- data/lib/cabriolet/models/winhelp_header.rb +127 -0
- data/lib/cabriolet/oab/command_handler.rb +257 -0
- data/lib/cabriolet/oab/compressor.rb +17 -8
- data/lib/cabriolet/oab/decompressor.rb +41 -10
- data/lib/cabriolet/offset_calculator.rb +81 -0
- data/lib/cabriolet/plugin.rb +233 -0
- data/lib/cabriolet/plugin_manager.rb +453 -0
- data/lib/cabriolet/plugin_validator.rb +422 -0
- data/lib/cabriolet/quantum_shared.rb +105 -0
- data/lib/cabriolet/system/io_system.rb +3 -0
- data/lib/cabriolet/system/memory_handle.rb +17 -4
- data/lib/cabriolet/szdd/command_handler.rb +217 -0
- data/lib/cabriolet/szdd/compressor.rb +15 -11
- data/lib/cabriolet/szdd/decompressor.rb +18 -9
- data/lib/cabriolet/version.rb +1 -1
- data/lib/cabriolet.rb +181 -20
- metadata +69 -4
- data/lib/cabriolet/auto.rb +0 -173
- 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
|
|
@@ -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
|
-
|
|
28
|
-
|
|
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]
|