cabriolet 0.1.2 → 0.2.0
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 +700 -38
- data/lib/cabriolet/algorithm_factory.rb +250 -0
- data/lib/cabriolet/base_compressor.rb +206 -0
- data/lib/cabriolet/binary/bitstream.rb +154 -14
- data/lib/cabriolet/binary/bitstream_writer.rb +129 -17
- 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 +35 -43
- data/lib/cabriolet/cab/decompressor.rb +14 -19
- data/lib/cabriolet/cab/extractor.rb +140 -31
- 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/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 +34 -45
- 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 +3 -2
- data/lib/cabriolet/errors.rb +3 -0
- data/lib/cabriolet/file_entry.rb +156 -0
- data/lib/cabriolet/file_manager.rb +144 -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 +626 -0
- data/lib/cabriolet/hlp/quickhelp/decompressor.rb +558 -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/parser.rb +274 -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/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 +633 -38
- data/lib/cabriolet/lit/decompressor.rb +518 -152
- data/lib/cabriolet/lit/parser.rb +670 -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/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 +67 -17
- metadata +33 -2
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cabriolet
|
|
4
|
+
# Represents a file to be added to an archive
|
|
5
|
+
#
|
|
6
|
+
# Single responsibility: Encapsulate file metadata and data access.
|
|
7
|
+
# Supports both disk files and memory data, providing unified interface
|
|
8
|
+
# for file operations across all format compressors.
|
|
9
|
+
#
|
|
10
|
+
# @example Adding a disk file
|
|
11
|
+
# entry = FileEntry.new(
|
|
12
|
+
# source: "/path/to/file.txt",
|
|
13
|
+
# archive_path: "docs/file.txt"
|
|
14
|
+
# )
|
|
15
|
+
#
|
|
16
|
+
# @example Adding memory data
|
|
17
|
+
# entry = FileEntry.new(
|
|
18
|
+
# data: "Hello, World!",
|
|
19
|
+
# archive_path: "greeting.txt"
|
|
20
|
+
# )
|
|
21
|
+
class FileEntry
|
|
22
|
+
attr_reader :source_path, :archive_path, :data, :options
|
|
23
|
+
|
|
24
|
+
# Initialize a file entry
|
|
25
|
+
#
|
|
26
|
+
# @param source [String, nil] Path to source file on disk
|
|
27
|
+
# @param data [String, nil] File data in memory
|
|
28
|
+
# @param archive_path [String] Path within the archive
|
|
29
|
+
# @param options [Hash] Format-specific options
|
|
30
|
+
# @raise [ArgumentError] if validation fails
|
|
31
|
+
def initialize(archive_path:, source: nil, data: nil, **options)
|
|
32
|
+
@source_path = source
|
|
33
|
+
@data = data
|
|
34
|
+
@archive_path = archive_path
|
|
35
|
+
@options = options
|
|
36
|
+
|
|
37
|
+
validate!
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Check if file data is from disk
|
|
41
|
+
#
|
|
42
|
+
# @return [Boolean] true if file is on disk
|
|
43
|
+
def from_disk?
|
|
44
|
+
!@source_path.nil?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Check if file data is in memory
|
|
48
|
+
#
|
|
49
|
+
# @return [Boolean] true if data is in memory
|
|
50
|
+
def from_memory?
|
|
51
|
+
!@data.nil?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Read file data (from disk or memory)
|
|
55
|
+
#
|
|
56
|
+
# @return [String] File contents
|
|
57
|
+
def read_data
|
|
58
|
+
return @data if from_memory?
|
|
59
|
+
|
|
60
|
+
File.binread(@source_path)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Get file size
|
|
64
|
+
#
|
|
65
|
+
# @return [Integer] File size in bytes
|
|
66
|
+
def size
|
|
67
|
+
return @data.bytesize if from_memory?
|
|
68
|
+
|
|
69
|
+
File.size(@source_path)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Get file stat (disk files only)
|
|
73
|
+
#
|
|
74
|
+
# @return [File::Stat, nil] File stat or nil for memory files
|
|
75
|
+
def stat
|
|
76
|
+
return nil if from_memory?
|
|
77
|
+
|
|
78
|
+
File.stat(@source_path)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Get modification time
|
|
82
|
+
#
|
|
83
|
+
# @return [Time] Modification time (current time for memory files)
|
|
84
|
+
def mtime
|
|
85
|
+
return Time.now if from_memory?
|
|
86
|
+
|
|
87
|
+
stat&.mtime || Time.now
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Get file attributes
|
|
91
|
+
#
|
|
92
|
+
# @return [Integer] File attributes flags
|
|
93
|
+
def attributes
|
|
94
|
+
return @options[:attributes] if @options[:attributes]
|
|
95
|
+
return Constants::ATTRIB_ARCH if from_memory?
|
|
96
|
+
|
|
97
|
+
calculate_disk_attributes
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Get compression flag from options
|
|
101
|
+
#
|
|
102
|
+
# @return [Boolean] Whether to compress this file
|
|
103
|
+
def compress?
|
|
104
|
+
@options.fetch(:compress, true)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
# Validate entry parameters
|
|
110
|
+
#
|
|
111
|
+
# @raise [ArgumentError] if invalid
|
|
112
|
+
def validate!
|
|
113
|
+
if @source_path.nil? && @data.nil?
|
|
114
|
+
raise ArgumentError,
|
|
115
|
+
"Must provide either source or data"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
if @source_path && @data
|
|
119
|
+
raise ArgumentError,
|
|
120
|
+
"Cannot provide both source and data"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
if @source_path
|
|
124
|
+
unless File.exist?(@source_path)
|
|
125
|
+
raise ArgumentError,
|
|
126
|
+
"File not found: #{@source_path}"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
unless File.file?(@source_path)
|
|
130
|
+
raise ArgumentError,
|
|
131
|
+
"Not a file: #{@source_path}"
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
raise ArgumentError, "Archive path required" if @archive_path.nil?
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Calculate attributes from disk file stat
|
|
139
|
+
#
|
|
140
|
+
# @return [Integer] Attribute flags
|
|
141
|
+
def calculate_disk_attributes
|
|
142
|
+
file_stat = stat
|
|
143
|
+
return Constants::ATTRIB_ARCH unless file_stat
|
|
144
|
+
|
|
145
|
+
attribs = Constants::ATTRIB_ARCH
|
|
146
|
+
|
|
147
|
+
# Read-only flag
|
|
148
|
+
attribs |= Constants::ATTRIB_READONLY unless file_stat.writable?
|
|
149
|
+
|
|
150
|
+
# Executable flag (Unix systems)
|
|
151
|
+
attribs |= Constants::ATTRIB_EXEC if file_stat.executable?
|
|
152
|
+
|
|
153
|
+
attribs
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "file_entry"
|
|
4
|
+
|
|
5
|
+
module Cabriolet
|
|
6
|
+
# Manages collection of files for archive creation
|
|
7
|
+
#
|
|
8
|
+
# Single responsibility: File list management and enumeration.
|
|
9
|
+
# Provides unified interface for adding files from disk or memory,
|
|
10
|
+
# and supports standard Ruby enumeration patterns.
|
|
11
|
+
#
|
|
12
|
+
# @example Basic usage
|
|
13
|
+
# manager = FileManager.new
|
|
14
|
+
# manager.add_file("/path/to/file.txt", "docs/file.txt")
|
|
15
|
+
# manager.add_data("Hello", "greeting.txt")
|
|
16
|
+
# manager.each { |entry| puts entry.archive_path }
|
|
17
|
+
class FileManager
|
|
18
|
+
include Enumerable
|
|
19
|
+
|
|
20
|
+
# Initialize empty file manager
|
|
21
|
+
def initialize
|
|
22
|
+
@entries = []
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Add file from disk
|
|
26
|
+
#
|
|
27
|
+
# @param source_path [String] Path to source file
|
|
28
|
+
# @param archive_path [String, nil] Path in archive (nil = use basename)
|
|
29
|
+
# @param options [Hash] Format-specific options
|
|
30
|
+
# @return [FileEntry] Added entry
|
|
31
|
+
# @raise [ArgumentError] if file doesn't exist
|
|
32
|
+
def add_file(source_path, archive_path = nil, **options)
|
|
33
|
+
archive_path ||= File.basename(source_path)
|
|
34
|
+
|
|
35
|
+
entry = FileEntry.new(
|
|
36
|
+
source: source_path,
|
|
37
|
+
archive_path: archive_path,
|
|
38
|
+
**options,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
@entries << entry
|
|
42
|
+
entry
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Add file from memory
|
|
46
|
+
#
|
|
47
|
+
# @param data [String] File data
|
|
48
|
+
# @param archive_path [String] Path in archive
|
|
49
|
+
# @param options [Hash] Format-specific options
|
|
50
|
+
# @return [FileEntry] Added entry
|
|
51
|
+
def add_data(data, archive_path, **options)
|
|
52
|
+
entry = FileEntry.new(
|
|
53
|
+
data: data,
|
|
54
|
+
archive_path: archive_path,
|
|
55
|
+
**options,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
@entries << entry
|
|
59
|
+
entry
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Enumerate entries (Enumerable interface)
|
|
63
|
+
#
|
|
64
|
+
# @yield [FileEntry] Each file entry
|
|
65
|
+
def each(&)
|
|
66
|
+
@entries.each(&)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Check if empty
|
|
70
|
+
#
|
|
71
|
+
# @return [Boolean] true if no files added
|
|
72
|
+
def empty?
|
|
73
|
+
@entries.empty?
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Get count of entries
|
|
77
|
+
#
|
|
78
|
+
# @return [Integer] Number of entries
|
|
79
|
+
def size
|
|
80
|
+
@entries.size
|
|
81
|
+
end
|
|
82
|
+
alias count size
|
|
83
|
+
|
|
84
|
+
# Get entry by index
|
|
85
|
+
#
|
|
86
|
+
# @param index [Integer] Entry index
|
|
87
|
+
# @return [FileEntry, nil] Entry or nil if out of bounds
|
|
88
|
+
def [](index)
|
|
89
|
+
@entries[index]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Get all entries
|
|
93
|
+
#
|
|
94
|
+
# @return [Array<FileEntry>] Copy of entries array
|
|
95
|
+
def all
|
|
96
|
+
@entries.dup
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Clear all entries
|
|
100
|
+
#
|
|
101
|
+
# @return [self]
|
|
102
|
+
def clear
|
|
103
|
+
@entries.clear
|
|
104
|
+
self
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Calculate total size of all files
|
|
108
|
+
#
|
|
109
|
+
# @return [Integer] Total size in bytes
|
|
110
|
+
def total_size
|
|
111
|
+
@entries.sum(&:size)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Get files from disk
|
|
115
|
+
#
|
|
116
|
+
# @return [Array<FileEntry>] Disk-based entries
|
|
117
|
+
def disk_files
|
|
118
|
+
@entries.select(&:from_disk?)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Get files from memory
|
|
122
|
+
#
|
|
123
|
+
# @return [Array<FileEntry>] Memory-based entries
|
|
124
|
+
def memory_files
|
|
125
|
+
@entries.select(&:from_memory?)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Find entry by archive path
|
|
129
|
+
#
|
|
130
|
+
# @param path [String] Archive path to find
|
|
131
|
+
# @return [FileEntry, nil] Entry or nil if not found
|
|
132
|
+
def find_by_path(path)
|
|
133
|
+
@entries.find { |entry| entry.archive_path == path }
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Check if archive path exists
|
|
137
|
+
#
|
|
138
|
+
# @param path [String] Archive path to check
|
|
139
|
+
# @return [Boolean] true if path exists
|
|
140
|
+
def path_exists?(path)
|
|
141
|
+
!find_by_path(path).nil?
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../cli/base_command_handler"
|
|
4
|
+
require_relative "decompressor"
|
|
5
|
+
require_relative "compressor"
|
|
6
|
+
|
|
7
|
+
module Cabriolet
|
|
8
|
+
module HLP
|
|
9
|
+
# Command handler for HLP (Help) format
|
|
10
|
+
#
|
|
11
|
+
# This handler implements the unified command interface for HLP files,
|
|
12
|
+
# wrapping the existing HLP::Decompressor and HLP::Compressor classes.
|
|
13
|
+
# Supports both QuickHelp and Windows Help formats.
|
|
14
|
+
#
|
|
15
|
+
class CommandHandler < Commands::BaseCommandHandler
|
|
16
|
+
# List HLP file contents
|
|
17
|
+
#
|
|
18
|
+
# Displays information about the HLP file including format type,
|
|
19
|
+
# and lists all contained files with their sizes.
|
|
20
|
+
#
|
|
21
|
+
# @param file [String] Path to the HLP file
|
|
22
|
+
# @param options [Hash] Additional options (unused)
|
|
23
|
+
# @return [void]
|
|
24
|
+
def list(file, _options = {})
|
|
25
|
+
validate_file_exists(file)
|
|
26
|
+
|
|
27
|
+
decompressor = Decompressor.new
|
|
28
|
+
header = decompressor.open(file)
|
|
29
|
+
|
|
30
|
+
display_header(header, file)
|
|
31
|
+
display_files(decompressor, header)
|
|
32
|
+
|
|
33
|
+
decompressor.close(header)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Extract files from HLP archive
|
|
37
|
+
#
|
|
38
|
+
# Extracts all files from the HLP file to the specified output directory.
|
|
39
|
+
# Supports both QuickHelp and Windows Help formats.
|
|
40
|
+
#
|
|
41
|
+
# @param file [String] Path to the HLP file
|
|
42
|
+
# @param output_dir [String] Output directory path (default: current directory)
|
|
43
|
+
# @param options [Hash] Additional options (unused)
|
|
44
|
+
# @return [void]
|
|
45
|
+
def extract(file, output_dir = nil, _options = {})
|
|
46
|
+
validate_file_exists(file)
|
|
47
|
+
|
|
48
|
+
output_dir ||= "."
|
|
49
|
+
output_dir = ensure_output_dir(output_dir)
|
|
50
|
+
|
|
51
|
+
decompressor = Decompressor.new
|
|
52
|
+
header = decompressor.open(file)
|
|
53
|
+
|
|
54
|
+
count = decompressor.extract_all(header, output_dir)
|
|
55
|
+
decompressor.close(header)
|
|
56
|
+
|
|
57
|
+
puts "Extracted #{count} file(s) to #{output_dir}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Create a new HLP archive
|
|
61
|
+
#
|
|
62
|
+
# Creates an HLP file from source files using QuickHelp format.
|
|
63
|
+
#
|
|
64
|
+
# @param output [String] Output HLP file path
|
|
65
|
+
# @param files [Array<String>] List of input files to add
|
|
66
|
+
# @param options [Hash] Additional options
|
|
67
|
+
# @option options [String] :format HLP format (:quickhelp, :winhelp)
|
|
68
|
+
# @return [void]
|
|
69
|
+
# @raise [ArgumentError] if no files specified
|
|
70
|
+
def create(output, files = [], options = {})
|
|
71
|
+
raise ArgumentError, "No files specified" if files.empty?
|
|
72
|
+
|
|
73
|
+
files.each do |f|
|
|
74
|
+
raise ArgumentError, "File does not exist: #{f}" unless File.exist?(f)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
format = parse_format_option(options[:format])
|
|
78
|
+
|
|
79
|
+
if format == :winhelp
|
|
80
|
+
create_winhelp(output, files, options)
|
|
81
|
+
else
|
|
82
|
+
create_quickhelp(output, files, options)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Display detailed HLP file information
|
|
87
|
+
#
|
|
88
|
+
# Shows comprehensive information about the HLP structure,
|
|
89
|
+
# including format type, file count, and metadata.
|
|
90
|
+
#
|
|
91
|
+
# @param file [String] Path to the HLP file
|
|
92
|
+
# @param options [Hash] Additional options (unused)
|
|
93
|
+
# @return [void]
|
|
94
|
+
def info(file, _options = {})
|
|
95
|
+
validate_file_exists(file)
|
|
96
|
+
|
|
97
|
+
decompressor = Decompressor.new
|
|
98
|
+
header = decompressor.open(file)
|
|
99
|
+
|
|
100
|
+
display_hlp_info(header, file)
|
|
101
|
+
|
|
102
|
+
decompressor.close(header)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Test HLP file integrity
|
|
106
|
+
#
|
|
107
|
+
# Verifies the HLP file structure.
|
|
108
|
+
#
|
|
109
|
+
# @param file [String] Path to the HLP file
|
|
110
|
+
# @param options [Hash] Additional options (unused)
|
|
111
|
+
# @return [void]
|
|
112
|
+
def test(file, _options = {})
|
|
113
|
+
validate_file_exists(file)
|
|
114
|
+
|
|
115
|
+
decompressor = Decompressor.new
|
|
116
|
+
header = decompressor.open(file)
|
|
117
|
+
|
|
118
|
+
puts "Testing #{file}..."
|
|
119
|
+
# TODO: Implement full integrity testing
|
|
120
|
+
format_name = if header.respond_to?(:version)
|
|
121
|
+
version_value = header.version
|
|
122
|
+
# Convert BinData objects to integer for comparison
|
|
123
|
+
version_int = version_value.to_i if version_value.respond_to?(:to_i)
|
|
124
|
+
|
|
125
|
+
if version_value.is_a?(Integer) || version_int&.positive?
|
|
126
|
+
"QUICKHELP v#{version_value}"
|
|
127
|
+
elsif version_value.is_a?(Symbol)
|
|
128
|
+
version_value.to_s.upcase.sub("WINHELP", "WinHelp ")
|
|
129
|
+
else
|
|
130
|
+
"unknown"
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
puts "OK: HLP file structure is valid (#{format_name} format)"
|
|
134
|
+
|
|
135
|
+
decompressor.close(header)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
private
|
|
139
|
+
|
|
140
|
+
# Display HLP header information
|
|
141
|
+
#
|
|
142
|
+
# @param header [Object] The HLP header object
|
|
143
|
+
# @param file [String] Original file path
|
|
144
|
+
# @return [void]
|
|
145
|
+
def display_header(header, file)
|
|
146
|
+
format_name = if header.respond_to?(:version)
|
|
147
|
+
version_value = header.version
|
|
148
|
+
# Convert BinData objects to integer for comparison
|
|
149
|
+
version_int = version_value.to_i if version_value.respond_to?(:to_i)
|
|
150
|
+
|
|
151
|
+
if version_value.is_a?(Integer) || version_int&.positive?
|
|
152
|
+
"QUICKHELP v#{version_value}"
|
|
153
|
+
elsif header.version.is_a?(Symbol)
|
|
154
|
+
header.version.to_s.upcase.sub("WINHELP", "WinHelp ")
|
|
155
|
+
else
|
|
156
|
+
header.version.to_s
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
puts "HLP File: #{file}"
|
|
160
|
+
puts "Format: #{format_name || 'unknown'}"
|
|
161
|
+
puts "\nFiles:"
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Display list of files in HLP
|
|
165
|
+
#
|
|
166
|
+
# @param decompressor [Decompressor] The decompressor instance
|
|
167
|
+
# @param header [Object] The HLP header object
|
|
168
|
+
# @return [void]
|
|
169
|
+
def display_files(_decompressor, header)
|
|
170
|
+
if header.respond_to?(:files)
|
|
171
|
+
header.files.each do |f|
|
|
172
|
+
puts " #{f.filename} (#{f.length} bytes)"
|
|
173
|
+
end
|
|
174
|
+
else
|
|
175
|
+
puts " (File listing not available for this format)"
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Display comprehensive HLP information
|
|
180
|
+
#
|
|
181
|
+
# @param header [Object] The HLP header object
|
|
182
|
+
# @param file [String] Original file path
|
|
183
|
+
# @return [void]
|
|
184
|
+
def display_hlp_info(header, file)
|
|
185
|
+
puts "HLP File Information"
|
|
186
|
+
puts "=" * 50
|
|
187
|
+
puts "Filename: #{file}"
|
|
188
|
+
|
|
189
|
+
if header.respond_to?(:version)
|
|
190
|
+
version_value = header.version
|
|
191
|
+
# Convert BinData objects to integer for comparison
|
|
192
|
+
version_int = version_value.to_i if version_value.respond_to?(:to_i)
|
|
193
|
+
|
|
194
|
+
format_name = if version_value.is_a?(Integer) || version_int&.positive?
|
|
195
|
+
"QUICKHELP v#{version_value}"
|
|
196
|
+
elsif version_value.is_a?(Symbol)
|
|
197
|
+
version_value.to_s.upcase.sub("WINHELP", "WinHelp ")
|
|
198
|
+
else
|
|
199
|
+
version_value.to_s
|
|
200
|
+
end
|
|
201
|
+
puts "Format: #{format_name}"
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
if header.respond_to?(:length)
|
|
205
|
+
puts "Size: #{header.length} bytes"
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
if header.respond_to?(:files)
|
|
209
|
+
puts "Files: #{header.files.size}"
|
|
210
|
+
puts ""
|
|
211
|
+
puts "Files:"
|
|
212
|
+
header.files.each do |f|
|
|
213
|
+
puts " #{f.filename}"
|
|
214
|
+
puts " Size: #{f.length} bytes"
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Create QuickHelp format HLP file
|
|
220
|
+
#
|
|
221
|
+
# @param output [String] Output file path
|
|
222
|
+
# @param files [Array<String>] Input files
|
|
223
|
+
# @param options [Hash] Additional options
|
|
224
|
+
# @return [void]
|
|
225
|
+
def create_quickhelp(output, files, _options)
|
|
226
|
+
compressor = Compressor.new
|
|
227
|
+
|
|
228
|
+
files.each do |f|
|
|
229
|
+
# Default: add files with compression
|
|
230
|
+
archive_name = File.basename(f)
|
|
231
|
+
compressor.add_file(f, "/#{archive_name}", compress: true)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
puts "Creating #{output} with #{files.size} file(s) (QuickHelp format)" if verbose?
|
|
235
|
+
bytes = compressor.generate(output)
|
|
236
|
+
puts "Created #{output} (#{bytes} bytes, #{files.size} files)"
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Create Windows Help format HLP file
|
|
240
|
+
#
|
|
241
|
+
# @param output [String] Output file path
|
|
242
|
+
# @param files [Array<String>] Input files
|
|
243
|
+
# @param options [Hash] Additional options
|
|
244
|
+
# @return [void]
|
|
245
|
+
def create_winhelp(output, files, _options)
|
|
246
|
+
compressor = Compressor.create_winhelp
|
|
247
|
+
|
|
248
|
+
files.each do |f|
|
|
249
|
+
archive_name = File.basename(f)
|
|
250
|
+
# WinHelp compression uses different API
|
|
251
|
+
compressor.add_file(f, "/#{archive_name}")
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
puts "Creating #{output} with #{files.size} file(s) (WinHelp format)" if verbose?
|
|
255
|
+
bytes = compressor.generate(output)
|
|
256
|
+
puts "Created #{output} (#{bytes} bytes, #{files.size} files)"
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Parse format option to symbol
|
|
260
|
+
#
|
|
261
|
+
# @param format_value [String, Symbol] The format type
|
|
262
|
+
# @return [Symbol] The format symbol
|
|
263
|
+
def parse_format_option(format_value)
|
|
264
|
+
return :quickhelp if format_value.nil?
|
|
265
|
+
|
|
266
|
+
format = format_value.to_sym
|
|
267
|
+
valid_formats = %i[quickhelp winhelp]
|
|
268
|
+
|
|
269
|
+
# Map :hlp to default :quickhelp format
|
|
270
|
+
format = :quickhelp if format == :hlp
|
|
271
|
+
|
|
272
|
+
unless valid_formats.include?(format)
|
|
273
|
+
raise ArgumentError,
|
|
274
|
+
"Invalid HLP format: #{format_value}. " \
|
|
275
|
+
"Valid options: #{valid_formats.join(', ')}"
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
format
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|