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,453 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "singleton"
|
|
4
|
+
require "yaml"
|
|
5
|
+
|
|
6
|
+
module Cabriolet
|
|
7
|
+
# Manages plugin lifecycle and registry for Cabriolet
|
|
8
|
+
#
|
|
9
|
+
# The PluginManager is a thread-safe singleton that handles plugin
|
|
10
|
+
# discovery, loading, activation, and deactivation. It maintains plugin
|
|
11
|
+
# states and resolves dependencies.
|
|
12
|
+
#
|
|
13
|
+
# @example Access the plugin manager
|
|
14
|
+
# manager = Cabriolet::PluginManager.instance
|
|
15
|
+
# manager.discover_plugins
|
|
16
|
+
# manager.load_plugin("my-plugin")
|
|
17
|
+
# manager.activate_plugin("my-plugin")
|
|
18
|
+
#
|
|
19
|
+
# @example Using global accessor
|
|
20
|
+
# Cabriolet.plugin_manager.discover_plugins
|
|
21
|
+
# Cabriolet.plugin_manager.list_plugins(state: :active)
|
|
22
|
+
class PluginManager
|
|
23
|
+
include Singleton
|
|
24
|
+
|
|
25
|
+
# @return [Hash] Plugin registry by name
|
|
26
|
+
attr_reader :plugins
|
|
27
|
+
|
|
28
|
+
# @return [Hash] Format registry
|
|
29
|
+
attr_reader :formats
|
|
30
|
+
|
|
31
|
+
# Initialize the plugin manager
|
|
32
|
+
def initialize
|
|
33
|
+
@plugins = {}
|
|
34
|
+
@formats = {}
|
|
35
|
+
@mutex = Mutex.new
|
|
36
|
+
@config = load_config
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Discover available plugins
|
|
40
|
+
#
|
|
41
|
+
# Searches for plugins in gem paths using the pattern
|
|
42
|
+
# 'cabriolet/plugins/**/*.rb'. Discovered plugins are added to the
|
|
43
|
+
# registry in :discovered state.
|
|
44
|
+
#
|
|
45
|
+
# @return [Array<String>] List of discovered plugin names
|
|
46
|
+
#
|
|
47
|
+
# @example Discover plugins
|
|
48
|
+
# manager.discover_plugins
|
|
49
|
+
# #=> ["plugin1", "plugin2"]
|
|
50
|
+
def discover_plugins
|
|
51
|
+
@mutex.synchronize do
|
|
52
|
+
plugin_files = Gem.find_files("cabriolet/plugins/**/*.rb")
|
|
53
|
+
|
|
54
|
+
plugin_files.each do |file|
|
|
55
|
+
load_plugin_file(file)
|
|
56
|
+
rescue StandardError => e
|
|
57
|
+
warn "Failed to load plugin from #{file}: #{e.message}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
@plugins.keys
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Register a plugin instance
|
|
65
|
+
#
|
|
66
|
+
# Adds a plugin to the registry. The plugin must be a valid Plugin
|
|
67
|
+
# instance with proper metadata.
|
|
68
|
+
#
|
|
69
|
+
# @param plugin_instance [Plugin] Plugin instance to register
|
|
70
|
+
#
|
|
71
|
+
# @return [Boolean] True if registered successfully
|
|
72
|
+
#
|
|
73
|
+
# @raise [PluginError] If plugin is invalid
|
|
74
|
+
#
|
|
75
|
+
# @example Register a plugin
|
|
76
|
+
# plugin = MyPlugin.new(manager)
|
|
77
|
+
# manager.register(plugin)
|
|
78
|
+
def register(plugin_instance)
|
|
79
|
+
@mutex.synchronize do
|
|
80
|
+
unless plugin_instance.is_a?(Plugin)
|
|
81
|
+
raise PluginError,
|
|
82
|
+
"Plugin must inherit from Cabriolet::Plugin"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Validate plugin
|
|
86
|
+
validation = PluginValidator.validate(plugin_instance.class)
|
|
87
|
+
unless validation[:valid]
|
|
88
|
+
raise PluginError,
|
|
89
|
+
"Plugin validation failed: #{validation[:errors].join(', ')}"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
meta = plugin_instance.metadata
|
|
93
|
+
name = meta[:name]
|
|
94
|
+
|
|
95
|
+
if @plugins.key?(name)
|
|
96
|
+
raise PluginError, "Plugin '#{name}' already registered"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
@plugins[name] = {
|
|
100
|
+
instance: plugin_instance,
|
|
101
|
+
metadata: meta,
|
|
102
|
+
state: :discovered,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
true
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Load a plugin
|
|
110
|
+
#
|
|
111
|
+
# Loads and validates a discovered plugin. Calls the plugin's setup
|
|
112
|
+
# method and transitions to :loaded state.
|
|
113
|
+
#
|
|
114
|
+
# @param name [String] Plugin name
|
|
115
|
+
#
|
|
116
|
+
# @return [Boolean] True if loaded successfully
|
|
117
|
+
#
|
|
118
|
+
# @raise [PluginError] If plugin not found or load fails
|
|
119
|
+
#
|
|
120
|
+
# @example Load a plugin
|
|
121
|
+
# manager.load_plugin("my-plugin")
|
|
122
|
+
def load_plugin(name)
|
|
123
|
+
@mutex.synchronize do
|
|
124
|
+
entry = @plugins[name]
|
|
125
|
+
raise PluginError, "Plugin '#{name}' not found" unless entry
|
|
126
|
+
|
|
127
|
+
if %i[loaded active].include?(entry[:state])
|
|
128
|
+
return true
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
begin
|
|
132
|
+
plugin = entry[:instance]
|
|
133
|
+
|
|
134
|
+
# Check dependencies
|
|
135
|
+
check_dependencies!(entry[:metadata])
|
|
136
|
+
|
|
137
|
+
# Call setup
|
|
138
|
+
plugin.setup
|
|
139
|
+
plugin.send(:update_state, :loaded)
|
|
140
|
+
entry[:state] = :loaded
|
|
141
|
+
|
|
142
|
+
true
|
|
143
|
+
rescue StandardError => e
|
|
144
|
+
plugin.send(:update_state, :failed)
|
|
145
|
+
entry[:state] = :failed
|
|
146
|
+
entry[:error] = e.message
|
|
147
|
+
raise PluginError, "Failed to load plugin '#{name}': #{e.message}"
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Activate a plugin
|
|
153
|
+
#
|
|
154
|
+
# Activates a loaded plugin. Calls the plugin's activate method and
|
|
155
|
+
# transitions to :active state.
|
|
156
|
+
#
|
|
157
|
+
# @param name [String] Plugin name
|
|
158
|
+
#
|
|
159
|
+
# @return [Boolean] True if activated successfully
|
|
160
|
+
#
|
|
161
|
+
# @raise [PluginError] If plugin not found or not loaded
|
|
162
|
+
#
|
|
163
|
+
# @example Activate a plugin
|
|
164
|
+
# manager.activate_plugin("my-plugin")
|
|
165
|
+
def activate_plugin(name)
|
|
166
|
+
@mutex.synchronize do
|
|
167
|
+
entry = @plugins[name]
|
|
168
|
+
raise PluginError, "Plugin '#{name}' not found" unless entry
|
|
169
|
+
|
|
170
|
+
if entry[:state] == :active
|
|
171
|
+
return true
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
unless entry[:state] == :loaded
|
|
175
|
+
raise PluginError,
|
|
176
|
+
"Plugin '#{name}' must be loaded before activation"
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
begin
|
|
180
|
+
plugin = entry[:instance]
|
|
181
|
+
plugin.activate
|
|
182
|
+
plugin.send(:update_state, :active)
|
|
183
|
+
entry[:state] = :active
|
|
184
|
+
|
|
185
|
+
true
|
|
186
|
+
rescue StandardError => e
|
|
187
|
+
plugin.send(:update_state, :failed)
|
|
188
|
+
entry[:state] = :failed
|
|
189
|
+
entry[:error] = e.message
|
|
190
|
+
raise PluginError,
|
|
191
|
+
"Failed to activate plugin '#{name}': #{e.message}"
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Deactivate a plugin
|
|
197
|
+
#
|
|
198
|
+
# Deactivates an active plugin. Calls the plugin's deactivate method
|
|
199
|
+
# and transitions back to :loaded state.
|
|
200
|
+
#
|
|
201
|
+
# @param name [String] Plugin name
|
|
202
|
+
#
|
|
203
|
+
# @return [Boolean] True if deactivated successfully
|
|
204
|
+
#
|
|
205
|
+
# @raise [PluginError] If plugin not found
|
|
206
|
+
#
|
|
207
|
+
# @example Deactivate a plugin
|
|
208
|
+
# manager.deactivate_plugin("my-plugin")
|
|
209
|
+
def deactivate_plugin(name)
|
|
210
|
+
@mutex.synchronize do
|
|
211
|
+
entry = @plugins[name]
|
|
212
|
+
raise PluginError, "Plugin '#{name}' not found" unless entry
|
|
213
|
+
|
|
214
|
+
if entry[:state] != :active
|
|
215
|
+
return true
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
begin
|
|
219
|
+
plugin = entry[:instance]
|
|
220
|
+
plugin.deactivate
|
|
221
|
+
plugin.send(:update_state, :loaded)
|
|
222
|
+
entry[:state] = :loaded
|
|
223
|
+
|
|
224
|
+
true
|
|
225
|
+
rescue StandardError => e
|
|
226
|
+
entry[:error] = e.message
|
|
227
|
+
raise PluginError,
|
|
228
|
+
"Failed to deactivate plugin '#{name}': #{e.message}"
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# List plugins
|
|
234
|
+
#
|
|
235
|
+
# Returns plugin information, optionally filtered by state.
|
|
236
|
+
#
|
|
237
|
+
# @param state [Symbol, nil] Optional state filter (:discovered,
|
|
238
|
+
# :loaded, :active, :failed, :disabled)
|
|
239
|
+
#
|
|
240
|
+
# @return [Hash] Plugin information keyed by name
|
|
241
|
+
#
|
|
242
|
+
# @example List all plugins
|
|
243
|
+
# manager.list_plugins
|
|
244
|
+
# #=> { "plugin1" => {...}, "plugin2" => {...} }
|
|
245
|
+
#
|
|
246
|
+
# @example List only active plugins
|
|
247
|
+
# manager.list_plugins(state: :active)
|
|
248
|
+
# #=> { "active-plugin" => {...} }
|
|
249
|
+
def list_plugins(state: nil)
|
|
250
|
+
@mutex.synchronize do
|
|
251
|
+
if state.nil?
|
|
252
|
+
@plugins.transform_values do |entry|
|
|
253
|
+
{
|
|
254
|
+
metadata: entry[:metadata],
|
|
255
|
+
state: entry[:state],
|
|
256
|
+
error: entry[:error],
|
|
257
|
+
}
|
|
258
|
+
end
|
|
259
|
+
else
|
|
260
|
+
@plugins.select { |_, entry| entry[:state] == state }
|
|
261
|
+
.transform_values do |entry|
|
|
262
|
+
{
|
|
263
|
+
metadata: entry[:metadata],
|
|
264
|
+
state: entry[:state],
|
|
265
|
+
error: entry[:error],
|
|
266
|
+
}
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Get a plugin by name
|
|
273
|
+
#
|
|
274
|
+
# @param name [String] Plugin name
|
|
275
|
+
#
|
|
276
|
+
# @return [Plugin, nil] Plugin instance or nil if not found
|
|
277
|
+
#
|
|
278
|
+
# @example Get a plugin
|
|
279
|
+
# plugin = manager.plugin("my-plugin")
|
|
280
|
+
def plugin(name)
|
|
281
|
+
@mutex.synchronize do
|
|
282
|
+
@plugins[name]&.dig(:instance)
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Check if a plugin is active
|
|
287
|
+
#
|
|
288
|
+
# @param name [String] Plugin name
|
|
289
|
+
#
|
|
290
|
+
# @return [Boolean] True if plugin is active
|
|
291
|
+
#
|
|
292
|
+
# @example Check plugin status
|
|
293
|
+
# manager.plugin_active?("my-plugin") #=> true
|
|
294
|
+
def plugin_active?(name)
|
|
295
|
+
@mutex.synchronize do
|
|
296
|
+
@plugins[name]&.dig(:state) == :active
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Register a format handler
|
|
301
|
+
#
|
|
302
|
+
# Called by plugins to register format handlers. This is used
|
|
303
|
+
# internally by Plugin#register_format.
|
|
304
|
+
#
|
|
305
|
+
# @param format [Symbol] Format identifier
|
|
306
|
+
# @param handler [Class] Handler class
|
|
307
|
+
#
|
|
308
|
+
# @return [void]
|
|
309
|
+
#
|
|
310
|
+
# @api private
|
|
311
|
+
def register_format(format, handler)
|
|
312
|
+
@mutex.synchronize do
|
|
313
|
+
@formats[format] = handler
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# Get format handler
|
|
318
|
+
#
|
|
319
|
+
# @param format [Symbol] Format identifier
|
|
320
|
+
#
|
|
321
|
+
# @return [Class, nil] Handler class or nil
|
|
322
|
+
#
|
|
323
|
+
# @example Get format handler
|
|
324
|
+
# handler = manager.format_handler(:myformat)
|
|
325
|
+
def format_handler(format)
|
|
326
|
+
@mutex.synchronize do
|
|
327
|
+
@formats[format]
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
private
|
|
332
|
+
|
|
333
|
+
# Load configuration from ~/.cabriolet/plugins.yml
|
|
334
|
+
#
|
|
335
|
+
# @return [Hash] Configuration hash
|
|
336
|
+
def load_config
|
|
337
|
+
config_path = File.expand_path("~/.cabriolet/plugins.yml")
|
|
338
|
+
return {} unless File.exist?(config_path)
|
|
339
|
+
|
|
340
|
+
YAML.load_file(config_path) || {}
|
|
341
|
+
rescue StandardError => e
|
|
342
|
+
warn "Failed to load plugin config: #{e.message}"
|
|
343
|
+
{}
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# Load a plugin file
|
|
347
|
+
#
|
|
348
|
+
# @param file [String] Plugin file path
|
|
349
|
+
#
|
|
350
|
+
# @return [void]
|
|
351
|
+
def load_plugin_file(file)
|
|
352
|
+
require file
|
|
353
|
+
|
|
354
|
+
# After requiring, plugin classes should auto-register
|
|
355
|
+
# This is a convention - plugins call register in their class body
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# Check plugin dependencies
|
|
359
|
+
#
|
|
360
|
+
# @param metadata [Hash] Plugin metadata
|
|
361
|
+
#
|
|
362
|
+
# @return [void]
|
|
363
|
+
#
|
|
364
|
+
# @raise [PluginError] If dependencies not met
|
|
365
|
+
def check_dependencies!(metadata)
|
|
366
|
+
dependencies = metadata[:dependencies] || []
|
|
367
|
+
|
|
368
|
+
dependencies.each do |dep|
|
|
369
|
+
dep_name, dep_version = parse_dependency(dep)
|
|
370
|
+
|
|
371
|
+
unless @plugins.key?(dep_name)
|
|
372
|
+
raise PluginError,
|
|
373
|
+
"Missing dependency: #{dep_name}"
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
dep_entry = @plugins[dep_name]
|
|
377
|
+
unless %i[loaded active].include?(dep_entry[:state])
|
|
378
|
+
raise PluginError,
|
|
379
|
+
"Dependency '#{dep_name}' not loaded"
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
if dep_version
|
|
383
|
+
actual_version = dep_entry[:metadata][:version]
|
|
384
|
+
unless version_satisfies?(actual_version, dep_version)
|
|
385
|
+
raise PluginError,
|
|
386
|
+
"Dependency '#{dep_name}' version mismatch: " \
|
|
387
|
+
"need #{dep_version}, have #{actual_version}"
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# Parse dependency string
|
|
394
|
+
#
|
|
395
|
+
# @param dep [String] Dependency string (e.g., "plugin >= 1.0")
|
|
396
|
+
#
|
|
397
|
+
# @return [Array<String, String>] [name, version_requirement]
|
|
398
|
+
def parse_dependency(dep)
|
|
399
|
+
parts = dep.split
|
|
400
|
+
name = parts[0]
|
|
401
|
+
version = parts[1..].join(" ") if parts.length > 1
|
|
402
|
+
|
|
403
|
+
[name, version]
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
# Check if version satisfies requirement
|
|
407
|
+
#
|
|
408
|
+
# @param version [String] Actual version
|
|
409
|
+
# @param requirement [String] Version requirement
|
|
410
|
+
#
|
|
411
|
+
# @return [Boolean] True if satisfied
|
|
412
|
+
def version_satisfies?(version, requirement)
|
|
413
|
+
# Simple version check - can be enhanced with gem version logic
|
|
414
|
+
return true if requirement.nil?
|
|
415
|
+
|
|
416
|
+
# Parse requirement (e.g., ">= 1.0", "~> 2.0", "= 1.5")
|
|
417
|
+
if requirement.start_with?(">=")
|
|
418
|
+
min_version = requirement.sub(">=", "").strip
|
|
419
|
+
compare_versions(version, min_version) >= 0
|
|
420
|
+
elsif requirement.start_with?("~>")
|
|
421
|
+
# Pessimistic version constraint
|
|
422
|
+
base = requirement.sub("~>", "").strip
|
|
423
|
+
compare_versions(version, base) >= 0
|
|
424
|
+
elsif requirement.start_with?("=")
|
|
425
|
+
exact = requirement.sub("=", "").strip
|
|
426
|
+
version == exact
|
|
427
|
+
else
|
|
428
|
+
true
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
# Compare two version strings
|
|
433
|
+
#
|
|
434
|
+
# @param v1 [String] Version 1
|
|
435
|
+
# @param v2 [String] Version 2
|
|
436
|
+
#
|
|
437
|
+
# @return [Integer] -1, 0, or 1
|
|
438
|
+
def compare_versions(v1, v2)
|
|
439
|
+
parts1 = v1.split(".").map(&:to_i)
|
|
440
|
+
parts2 = v2.split(".").map(&:to_i)
|
|
441
|
+
|
|
442
|
+
[parts1.length, parts2.length].max.times do |i|
|
|
443
|
+
p1 = parts1[i] || 0
|
|
444
|
+
p2 = parts2[i] || 0
|
|
445
|
+
|
|
446
|
+
return -1 if p1 < p2
|
|
447
|
+
return 1 if p1 > p2
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
0
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
end
|