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,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
@@ -1,271 +1,61 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "quickhelp/compressor"
4
+ require_relative "winhelp/compressor"
5
+
3
6
  module Cabriolet
4
7
  module HLP
5
- # Compressor creates HLP (Windows Help) compressed archives
6
- #
7
- # HLP files contain an internal file system where files can be compressed
8
- # using LZSS MODE_MSHELP compression. The compressor builds the archive
9
- # structure and compresses files as needed.
8
+ # Main compressor for HLP files
10
9
  #
11
- # NOTE: This implementation is based on the knowledge that HLP files use
12
- # LZSS compression with MODE_MSHELP, but cannot be fully validated due to
13
- # lack of test fixtures and incomplete libmspack implementation.
10
+ # Creates HLP files in either QuickHelp or Windows Help format.
11
+ # By default, uses QuickHelp format for compatibility.
14
12
  class Compressor
15
13
  attr_reader :io_system
16
14
 
17
- # Default buffer size for I/O operations
18
- DEFAULT_BUFFER_SIZE = 2048
19
-
20
- # Initialize a new HLP compressor
15
+ # Initialize compressor
21
16
  #
22
- # @param io_system [System::IOSystem, nil] Custom I/O system or nil for
23
- # default
17
+ # @param io_system [System::IOSystem, nil] Custom I/O system or nil for default
24
18
  def initialize(io_system = nil)
25
19
  @io_system = io_system || System::IOSystem.new
26
- @files = []
20
+ @quickhelp = QuickHelp::Compressor.new(@io_system)
27
21
  end
28
22
 
29
- # Add a file to the HLP archive
23
+ # Add a file to the archive
30
24
  #
31
25
  # @param source_path [String] Path to source file
32
- # @param hlp_path [String] Path within HLP archive
33
- # @param compress [Boolean] Whether to compress the file
26
+ # @param hlp_path [String] Path within archive
27
+ # @param compress [Boolean] Whether to compress
34
28
  # @return [void]
35
29
  def add_file(source_path, hlp_path, compress: true)
36
- @files << {
37
- source: source_path,
38
- hlp_path: hlp_path,
39
- compress: compress,
40
- }
30
+ @quickhelp.add_file(source_path, hlp_path, compress: compress)
41
31
  end
42
32
 
43
- # Add data from memory to the HLP archive
33
+ # Add data from memory
44
34
  #
45
35
  # @param data [String] Data to add
46
- # @param hlp_path [String] Path within HLP archive
47
- # @param compress [Boolean] Whether to compress the data
36
+ # @param hlp_path [String] Path within archive
37
+ # @param compress [Boolean] Whether to compress
48
38
  # @return [void]
49
39
  def add_data(data, hlp_path, compress: true)
50
- @files << {
51
- data: data,
52
- hlp_path: hlp_path,
53
- compress: compress,
54
- }
40
+ @quickhelp.add_data(data, hlp_path, compress: compress)
55
41
  end
56
42
 
57
- # Generate HLP archive
43
+ # Generate HLP archive (QuickHelp format by default)
58
44
  #
59
- # @param output_file [String] Path to output HLP file
60
- # @param options [Hash] Compression options
61
- # @option options [Integer] :version HLP format version (default: 1)
62
- # @return [Integer] Bytes written to output file
63
- # @raise [Errors::CompressionError] if compression fails
45
+ # @param output_file [String] Output file path
46
+ # @param options [Hash] Format options
47
+ # @return [Integer] Bytes written
64
48
  def generate(output_file, **options)
65
- version = options.fetch(:version, 1)
66
-
67
- output_handle = @io_system.open(output_file, Constants::MODE_WRITE)
68
-
69
- begin
70
- # Compress all files and collect metadata
71
- compressed_files = compress_all_files
72
-
73
- # Calculate directory size first
74
- directory_size = calculate_directory_size(compressed_files)
75
-
76
- # Calculate offsets
77
- header_size = 18 # Header structure size
78
- directory_offset = header_size
79
- data_offset = header_size + directory_size
80
-
81
- # Assign file offsets
82
- current_offset = data_offset
83
- compressed_files.each do |file_info|
84
- file_info[:offset] = current_offset
85
- current_offset += file_info[:compressed_data].bytesize
86
- end
87
-
88
- # Write header
89
- header_bytes = write_header(
90
- output_handle,
91
- version,
92
- compressed_files.size,
93
- directory_offset,
94
- )
95
-
96
- # Write directory
97
- directory_bytes = write_directory(output_handle, compressed_files)
98
-
99
- # Write file data
100
- data_bytes = write_file_data(output_handle, compressed_files)
101
-
102
- header_bytes + directory_bytes + data_bytes
103
- ensure
104
- @io_system.close(output_handle) if output_handle
105
- end
106
- end
107
-
108
- private
109
-
110
- # Compress all files and collect metadata
111
- #
112
- # @return [Array<Hash>] Array of file information hashes
113
- def compress_all_files
114
- @files.map do |file_spec|
115
- compress_file_spec(file_spec)
116
- end
117
- end
118
-
119
- # Compress a single file specification
120
- #
121
- # @param file_spec [Hash] File specification
122
- # @return [Hash] File information with compressed data
123
- def compress_file_spec(file_spec)
124
- # Get source data
125
- data = file_spec[:data] || read_file_data(file_spec[:source])
126
-
127
- # Compress if requested
128
- compressed_data = if file_spec[:compress]
129
- compress_data_lzss(data)
130
- else
131
- data
132
- end
133
-
134
- {
135
- hlp_path: file_spec[:hlp_path],
136
- uncompressed_size: data.bytesize,
137
- compressed_data: compressed_data,
138
- compressed: file_spec[:compress],
139
- }
140
- end
141
-
142
- # Read file data from disk
143
- #
144
- # @param filename [String] Path to file
145
- # @return [String] File contents
146
- def read_file_data(filename)
147
- handle = @io_system.open(filename, Constants::MODE_READ)
148
- begin
149
- data = +""
150
- loop do
151
- chunk = @io_system.read(handle, DEFAULT_BUFFER_SIZE)
152
- break if chunk.empty?
153
-
154
- data << chunk
155
- end
156
- data
157
- ensure
158
- @io_system.close(handle)
159
- end
160
- end
161
-
162
- # Compress data using LZSS MODE_MSHELP
163
- #
164
- # @param data [String] Data to compress
165
- # @return [String] Compressed data
166
- def compress_data_lzss(data)
167
- input_handle = System::MemoryHandle.new(data)
168
- output_handle = System::MemoryHandle.new("", Constants::MODE_WRITE)
169
-
170
- compressor = Compressors::LZSS.new(
171
- @io_system,
172
- input_handle,
173
- output_handle,
174
- DEFAULT_BUFFER_SIZE,
175
- Compressors::LZSS::MODE_MSHELP,
176
- )
177
-
178
- compressor.compress
179
- output_handle.data
180
- end
181
-
182
- # Calculate directory size
183
- #
184
- # @param compressed_files [Array<Hash>] Compressed file information
185
- # @return [Integer] Directory size in bytes
186
- def calculate_directory_size(compressed_files)
187
- size = 0
188
- compressed_files.each do |file_info|
189
- # 4 bytes for filename length
190
- # N bytes for filename
191
- # 4 + 4 + 4 + 1 = 13 bytes for file metadata
192
- size += 4 + file_info[:hlp_path].bytesize + 13
193
- end
194
- size
195
- end
196
-
197
- # Write HLP header
198
- #
199
- # @param output_handle [System::FileHandle] Output file handle
200
- # @param version [Integer] Format version
201
- # @param file_count [Integer] Number of files
202
- # @param directory_offset [Integer] Offset to directory
203
- # @return [Integer] Number of bytes written
204
- def write_header(output_handle, version, file_count, directory_offset)
205
- header = Binary::HLPStructures::Header.new
206
- header.signature = Binary::HLPStructures::SIGNATURE
207
- header.version = version
208
- header.file_count = file_count
209
- header.directory_offset = directory_offset
210
-
211
- header_data = header.to_binary_s
212
- written = @io_system.write(output_handle, header_data)
213
-
214
- unless written == header_data.bytesize
215
- raise Errors::CompressionError,
216
- "Failed to write HLP header"
217
- end
218
-
219
- written
220
- end
221
-
222
- # Write file directory
223
- #
224
- # @param output_handle [System::FileHandle] Output file handle
225
- # @param compressed_files [Array<Hash>] Compressed file information
226
- # @return [Integer] Number of bytes written
227
- def write_directory(output_handle, compressed_files)
228
- bytes_written = 0
229
-
230
- compressed_files.each do |file_info|
231
- # Write filename length
232
- filename = file_info[:hlp_path].b
233
- length_data = [filename.bytesize].pack("V")
234
- bytes_written += @io_system.write(output_handle, length_data)
235
-
236
- # Write filename
237
- bytes_written += @io_system.write(output_handle, filename)
238
-
239
- # Write file metadata
240
- metadata = [
241
- file_info[:offset],
242
- file_info[:uncompressed_size],
243
- file_info[:compressed_data].bytesize,
244
- file_info[:compressed] ? 1 : 0,
245
- ].pack("V3C")
246
- bytes_written += @io_system.write(output_handle, metadata)
247
- end
248
-
249
- bytes_written
49
+ @quickhelp.generate(output_file, **options)
250
50
  end
251
51
 
252
- # Write file data
52
+ # Create a Windows Help format HLP file
253
53
  #
254
- # @param output_handle [System::FileHandle] Output file handle
255
- # @param compressed_files [Array<Hash>] Compressed file information
256
- # @return [Integer] Number of bytes written
257
- def write_file_data(output_handle, compressed_files)
258
- bytes_written = 0
259
-
260
- compressed_files.each do |file_info|
261
- written = @io_system.write(
262
- output_handle,
263
- file_info[:compressed_data],
264
- )
265
- bytes_written += written
266
- end
267
-
268
- bytes_written
54
+ # @param output_file [String] Output file path
55
+ # @param options [Hash] Format options
56
+ # @return [WinHelp::Compressor] Compressor for building WinHelp file
57
+ def self.create_winhelp(io_system = nil)
58
+ WinHelp::Compressor.new(io_system)
269
59
  end
270
60
  end
271
61
  end