ocran 1.3.18 → 1.4.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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.txt +309 -292
  3. data/LICENSE.txt +22 -22
  4. data/README.md +549 -533
  5. data/exe/ocran +5 -5
  6. data/ext/extconf.rb +15 -0
  7. data/lib/ocran/build_constants.rb +16 -16
  8. data/lib/ocran/build_facade.rb +17 -17
  9. data/lib/ocran/build_helper.rb +110 -105
  10. data/lib/ocran/command_output.rb +22 -22
  11. data/lib/ocran/dir_builder.rb +162 -0
  12. data/lib/ocran/direction.rb +636 -458
  13. data/lib/ocran/file_path_set.rb +69 -69
  14. data/lib/ocran/gem_spec_queryable.rb +172 -172
  15. data/lib/ocran/host_config_helper.rb +57 -44
  16. data/lib/ocran/inno_setup_script_builder.rb +111 -111
  17. data/lib/ocran/launcher_batch_builder.rb +85 -85
  18. data/lib/ocran/library_detector.rb +61 -61
  19. data/lib/ocran/library_detector_posix.rb +55 -0
  20. data/lib/ocran/option.rb +323 -273
  21. data/lib/ocran/refine_pathname.rb +104 -104
  22. data/lib/ocran/runner.rb +115 -105
  23. data/lib/ocran/runtime_environment.rb +46 -46
  24. data/lib/ocran/stub_builder.rb +298 -264
  25. data/lib/ocran/version.rb +5 -5
  26. data/lib/ocran/windows_command_escaping.rb +15 -15
  27. data/lib/ocran.rb +7 -7
  28. data/share/ocran/lzma.exe +0 -0
  29. data/src/Makefile +75 -0
  30. data/src/edicon.c +161 -0
  31. data/src/error.c +100 -0
  32. data/src/error.h +66 -0
  33. data/src/inst_dir.c +334 -0
  34. data/src/inst_dir.h +157 -0
  35. data/src/lzma/7zTypes.h +529 -0
  36. data/src/lzma/Compiler.h +43 -0
  37. data/src/lzma/LzmaDec.c +1363 -0
  38. data/src/lzma/LzmaDec.h +236 -0
  39. data/src/lzma/Precomp.h +10 -0
  40. data/src/script_info.c +246 -0
  41. data/src/script_info.h +7 -0
  42. data/src/stub.c +133 -0
  43. data/src/stub.manifest +29 -0
  44. data/src/stub.rc +3 -0
  45. data/src/system_utils.c +1002 -0
  46. data/src/system_utils.h +209 -0
  47. data/src/system_utils_posix.c +500 -0
  48. data/src/unpack.c +574 -0
  49. data/src/unpack.h +85 -0
  50. data/src/vit-ruby.ico +0 -0
  51. metadata +52 -16
  52. data/share/ocran/edicon.exe +0 -0
  53. data/share/ocran/stub.exe +0 -0
  54. data/share/ocran/stubw.exe +0 -0
@@ -1,264 +1,298 @@
1
- # frozen_string_literal: true
2
- require "tempfile"
3
- require_relative "file_path_set"
4
-
5
- module Ocran
6
- # Utility class that produces the actual executable. Opcodes
7
- # (create_file, mkdir etc) are added by invoking methods on an
8
- # instance of OcranBuilder.
9
- class StubBuilder
10
- Signature = [0x41, 0xb6, 0xba, 0x4e].freeze
11
-
12
- OP_CREATE_DIRECTORY = 1
13
- OP_CREATE_FILE = 2
14
- OP_SETENV = 3
15
- OP_SET_SCRIPT = 4
16
-
17
- DEBUG_MODE = 0x01
18
- EXTRACT_TO_EXE_DIR = 0x02
19
- AUTO_CLEAN_INST_DIR = 0x04
20
- CHDIR_BEFORE_SCRIPT = 0x08
21
- DATA_COMPRESSED = 0x10
22
-
23
- base_dir = File.expand_path("../../share/ocran", File.dirname(__FILE__))
24
- STUB_PATH = File.expand_path("stub.exe", base_dir)
25
- STUBW_PATH = File.expand_path("stubw.exe", base_dir)
26
- LZMA_PATH = File.expand_path("lzma.exe", base_dir)
27
- EDICON_PATH = File.expand_path("edicon.exe", base_dir)
28
-
29
- attr_reader :data_size
30
-
31
- # Clear invalid security directory entries from PE executables
32
- # This is necessary because some linkers may set non-zero values in the
33
- # security directory even when there is no actual digital signature
34
- def self.clear_invalid_security_entry(file_path)
35
- data = File.binread(file_path)
36
- return unless data.size > 64 # Minimum PE header size
37
-
38
- # Read DOS header to find PE header offset
39
- e_lfanew_offset = 60
40
- pe_offset = data[e_lfanew_offset, 4].unpack1("L")
41
- return if pe_offset + 160 > data.size # Not enough room for headers
42
-
43
- # Calculate security directory offset
44
- # PE signature (4) + FILE_HEADER (20) + partial OPTIONAL_HEADER to DataDirectory
45
- security_entry_offset = pe_offset + 4 + 20 + 128
46
-
47
- # Read security directory entry (VirtualAddress and Size)
48
- sec_addr = data[security_entry_offset, 4].unpack1("L")
49
- sec_size = data[security_entry_offset + 4, 4].unpack1("L")
50
-
51
- # Check if security entry is invalid (points beyond file or size is 0)
52
- if sec_size != 0 && (sec_addr == 0 || sec_addr >= data.size || sec_addr + sec_size > data.size)
53
- # Clear the invalid security entry
54
- data[security_entry_offset, 8] = "\x00" * 8
55
- File.binwrite(file_path, data)
56
- end
57
- end
58
-
59
- # chdir_before:
60
- # When set to true, the working directory is changed to the application's
61
- # deployment location at runtime.
62
- #
63
- # debug_mode:
64
- # When the debug_mode option is set to true, the stub will output debug information
65
- # when the exe file is executed. Debug mode can also be enabled within the directive
66
- # code using the enable_debug_mode method. This option is provided to transition to
67
- # debug mode from the initialization point of the stub.
68
- #
69
- # debug_extract:
70
- # When set to true, the runtime file is extracted to the directory where the executable resides,
71
- # and the extracted files remain even after the application exits.
72
- # When set to false, the runtime file is extracted to the system's temporary directory,
73
- # and the extracted files are deleted after the application exits.
74
- #
75
- # gui_mode:
76
- # When set to true, the stub does not display a console window at startup. Errors are shown in a dialog window.
77
- # When set to false, the stub reports errors through the console window.
78
- #
79
- # icon_path:
80
- # Specifies the path to the icon file to be embedded in the stub's resources.
81
- #
82
- def initialize(path, chdir_before: nil, debug_extract: nil, debug_mode: nil,
83
- enable_compression: nil, gui_mode: nil, icon_path: nil)
84
- @dirs = FilePathSet.new
85
- @files = FilePathSet.new
86
- @data_size = 0
87
-
88
- if icon_path && !File.exist?(icon_path)
89
- raise "Icon file #{icon_path} not found"
90
- end
91
-
92
- stub = Tempfile.new("", File.dirname(path))
93
- IO.copy_stream(gui_mode ? STUBW_PATH : STUB_PATH, stub)
94
- stub.close
95
-
96
- # Clear any invalid security directory entries from the stub
97
- self.class.clear_invalid_security_entry(stub.path)
98
-
99
- if icon_path
100
- system(EDICON_PATH, stub.path, icon_path.to_s, exception: true)
101
- end
102
-
103
- File.open(stub, "ab") do |of|
104
- @of = of
105
- @opcode_offset = @of.size
106
-
107
- write_header(debug_mode, debug_extract, chdir_before, enable_compression)
108
-
109
- b = proc {
110
- yield(self)
111
- }
112
-
113
- if enable_compression
114
- compress(&b)
115
- else
116
- b.yield
117
- end
118
-
119
- write_footer
120
- end
121
-
122
- File.rename(stub, path)
123
- end
124
-
125
- def mkdir(target)
126
- return unless @dirs.add?("/", target)
127
-
128
- write_opcode(OP_CREATE_DIRECTORY)
129
- write_path(target)
130
- end
131
-
132
- def cp(source, target)
133
- unless File.exist?(source)
134
- raise "The file does not exist (#{source})"
135
- end
136
-
137
- return unless @files.add?(source, target)
138
-
139
- write_opcode(OP_CREATE_FILE)
140
- write_path(target)
141
- write_file(source)
142
- end
143
-
144
- # Specifies the final application script to be launched, which can be called
145
- # from any position in the data stream. It cannot be specified more than once.
146
- #
147
- # You can omit setting OP_SET_SCRIPT without issues, in which case
148
- # the stub terminates without launching anything after performing other
149
- # runtime operations.
150
- def exec(image, script, *argv)
151
- if @script_set
152
- raise "Script is already set"
153
- end
154
- @script_set = true
155
-
156
- write_opcode(OP_SET_SCRIPT)
157
- write_string_array(convert_to_native(image), convert_to_native(script), *argv)
158
- end
159
-
160
- def export(name, value)
161
- write_opcode(OP_SETENV)
162
- write_string(name.to_s)
163
- write_string(value.to_s)
164
- end
165
-
166
- def compress
167
- IO.popen([LZMA_PATH, "e", "-si", "-so"], "r+b") do |lzma|
168
- _of, @of = @of, lzma
169
- Thread.new { yield(self); lzma.close_write }
170
- IO.copy_stream(lzma, _of)
171
- @of = _of
172
- end
173
-
174
- # Calculate the position to write the LZMA decompressed size (64-bit unsigned integer)
175
- # @opcode_offset: start position of the data section
176
- # 1: size of the header byte
177
- # 5: size of the LZMA header in bytes
178
- File.binwrite(@of.path, [@data_size].pack("Q<"), @opcode_offset + 1 + 5)
179
- end
180
- private :compress
181
-
182
- def write_header(debug_mode, debug_extract, chdir_before, compressed)
183
- next_to_exe, delete_after = debug_extract, !debug_extract
184
- @of << [0 |
185
- (debug_mode ? DEBUG_MODE : 0) |
186
- (next_to_exe ? EXTRACT_TO_EXE_DIR : 0) |
187
- (delete_after ? AUTO_CLEAN_INST_DIR : 0) |
188
- (chdir_before ? CHDIR_BEFORE_SCRIPT : 0) |
189
- (compressed ? DATA_COMPRESSED : 0)
190
- ].pack("C")
191
- end
192
- private :write_header
193
-
194
- def write_opcode(op)
195
- @of << [op].pack("C")
196
- @data_size += 1
197
- end
198
- private :write_opcode
199
-
200
- def write_size(i)
201
- if i > 0xFFFF_FFFF
202
- raise ArgumentError, "Size #{i} is too large: must be 32-bit unsigned integer (0 to 4294967295)"
203
- end
204
-
205
- @of << [i].pack("V")
206
- @data_size += 4
207
- end
208
- private :write_size
209
-
210
- def write_string(str)
211
- len = str.bytesize + 1 # +1 to account for the null terminator
212
-
213
- if len > 0xFFFF
214
- raise ArgumentError, "String length #{len} is too large: must be less than or equal to 65535 bytes including null terminator"
215
- end
216
-
217
- write_size(len)
218
- @of << [str].pack("Z*")
219
- @data_size += len
220
- end
221
- private :write_string
222
-
223
- def write_string_array(*str_array)
224
- ary = str_array.map(&:to_s)
225
-
226
- if ary.any?(&:empty?)
227
- raise ArgumentError, "Argument list must not contain empty strings"
228
- end
229
-
230
- # Append an empty string so that when joined with "\0", the final buffer
231
- # ends in two consecutive NUL bytes (double–NUL terminator) to mark end-of-list.
232
- ary << ""
233
-
234
- size = ary.sum(0) { |s| s.bytesize + 1 }
235
- write_size(size)
236
- ary.each_slice(1) { |a| @of << a.pack("Z*") }
237
- @data_size += size
238
- end
239
- private :write_string_array
240
-
241
- def write_file(src)
242
- size = File.size(src)
243
- write_size(size)
244
- IO.copy_stream(src, @of)
245
- @data_size += size
246
- end
247
- private :write_file
248
-
249
- def write_path(path)
250
- write_string(convert_to_native(path))
251
- end
252
- private :write_path
253
-
254
- def write_footer
255
- @of << ([@opcode_offset] + Signature).pack("VC*")
256
- end
257
- private :write_footer
258
-
259
- def convert_to_native(path)
260
- path.to_s.tr(File::SEPARATOR, "\\")
261
- end
262
- private :convert_to_native
263
- end
264
- end
1
+ # frozen_string_literal: true
2
+ require "tempfile"
3
+ require_relative "file_path_set"
4
+
5
+ module Ocran
6
+ # Utility class that produces the actual executable. Opcodes
7
+ # (create_file, mkdir etc) are added by invoking methods on an
8
+ # instance of OcranBuilder.
9
+ class StubBuilder
10
+ Signature = [0x41, 0xb6, 0xba, 0x4e].freeze
11
+
12
+ OP_CREATE_DIRECTORY = 1
13
+ OP_CREATE_FILE = 2
14
+ OP_SETENV = 3
15
+ OP_SET_SCRIPT = 4
16
+ OP_CREATE_SYMLINK = 5
17
+
18
+ DEBUG_MODE = 0x01
19
+ EXTRACT_TO_EXE_DIR = 0x02
20
+ AUTO_CLEAN_INST_DIR = 0x04
21
+ CHDIR_BEFORE_SCRIPT = 0x08
22
+ DATA_COMPRESSED = 0x10
23
+
24
+ WINDOWS = Gem.win_platform?
25
+
26
+ base_dir = File.expand_path("../../share/ocran", File.dirname(__FILE__))
27
+ STUB_PATH = File.expand_path(WINDOWS ? "stub.exe" : "stub", base_dir)
28
+ STUBW_PATH = WINDOWS ? File.expand_path("stubw.exe", base_dir) : nil
29
+ LZMA_PATH = WINDOWS ? File.expand_path("lzma.exe", base_dir) : nil
30
+ EDICON_PATH = WINDOWS ? File.expand_path("edicon.exe", base_dir) : nil
31
+
32
+ def self.find_posix_lzma_cmd
33
+ if system("which lzma > /dev/null 2>&1")
34
+ ["lzma", "--compress", "--stdout"]
35
+ elsif system("which xz > /dev/null 2>&1")
36
+ ["xz", "--format=lzma", "--compress", "--stdout"]
37
+ elsif File.exist?("/opt/homebrew/bin/lzma")
38
+ ["/opt/homebrew/bin/lzma", "--compress", "--stdout"]
39
+ else
40
+ nil
41
+ end
42
+ end
43
+
44
+ LZMA_CMD = WINDOWS ? [LZMA_PATH, "e", "-si", "-so"] : find_posix_lzma_cmd
45
+
46
+ attr_reader :data_size
47
+
48
+ # Clear invalid security directory entries from PE executables
49
+ # This is necessary because some linkers may set non-zero values in the
50
+ # security directory even when there is no actual digital signature
51
+ def self.clear_invalid_security_entry(file_path)
52
+ data = File.binread(file_path)
53
+ return unless data.size > 64 # Minimum PE header size
54
+
55
+ # Read DOS header to find PE header offset
56
+ e_lfanew_offset = 60
57
+ pe_offset = data[e_lfanew_offset, 4].unpack1("L")
58
+ return if pe_offset + 160 > data.size # Not enough room for headers
59
+
60
+ # Calculate security directory offset
61
+ # PE signature (4) + FILE_HEADER (20) + partial OPTIONAL_HEADER to DataDirectory
62
+ security_entry_offset = pe_offset + 4 + 20 + 128
63
+
64
+ # Read security directory entry (VirtualAddress and Size)
65
+ sec_addr = data[security_entry_offset, 4].unpack1("L")
66
+ sec_size = data[security_entry_offset + 4, 4].unpack1("L")
67
+
68
+ # Check if security entry is invalid (points beyond file or size is 0)
69
+ if sec_size != 0 && (sec_addr == 0 || sec_addr >= data.size || sec_addr + sec_size > data.size)
70
+ # Clear the invalid security entry
71
+ data[security_entry_offset, 8] = "\x00" * 8
72
+ File.binwrite(file_path, data)
73
+ end
74
+ end
75
+
76
+ # chdir_before:
77
+ # When set to true, the working directory is changed to the application's
78
+ # deployment location at runtime.
79
+ #
80
+ # debug_mode:
81
+ # When the debug_mode option is set to true, the stub will output debug information
82
+ # when the exe file is executed. Debug mode can also be enabled within the directive
83
+ # code using the enable_debug_mode method. This option is provided to transition to
84
+ # debug mode from the initialization point of the stub.
85
+ #
86
+ # debug_extract:
87
+ # When set to true, the runtime file is extracted to the directory where the executable resides,
88
+ # and the extracted files remain even after the application exits.
89
+ # When set to false, the runtime file is extracted to the system's temporary directory,
90
+ # and the extracted files are deleted after the application exits.
91
+ #
92
+ # gui_mode:
93
+ # When set to true, the stub does not display a console window at startup. Errors are shown in a dialog window.
94
+ # When set to false, the stub reports errors through the console window.
95
+ #
96
+ # icon_path:
97
+ # Specifies the path to the icon file to be embedded in the stub's resources.
98
+ #
99
+ def initialize(path, chdir_before: nil, debug_extract: nil, debug_mode: nil,
100
+ enable_compression: nil, gui_mode: nil, icon_path: nil)
101
+ @dirs = FilePathSet.new
102
+ @files = FilePathSet.new
103
+ @data_size = 0
104
+
105
+ if icon_path && !File.exist?(icon_path)
106
+ raise "Icon file #{icon_path} not found"
107
+ end
108
+
109
+ output_dir = File.dirname(path)
110
+ FileUtils.mkdir_p(output_dir) unless Dir.exist?(output_dir)
111
+ stub_tmp = File.join(output_dir, ".ocran_stub_#{$$}_#{Time.now.to_i}")
112
+ stub_src = if gui_mode && WINDOWS
113
+ STUBW_PATH
114
+ else
115
+ STUB_PATH
116
+ end
117
+ IO.copy_stream(stub_src, stub_tmp)
118
+ stub = stub_tmp
119
+
120
+ # Clear any invalid security directory entries from the stub (Windows only)
121
+ self.class.clear_invalid_security_entry(stub) if WINDOWS
122
+
123
+ # Embed icon resource (Windows only)
124
+ if icon_path && WINDOWS
125
+ system(EDICON_PATH, stub, icon_path.to_s, exception: true)
126
+ end
127
+
128
+ File.open(stub, "ab") do |of|
129
+ @of = of
130
+ @opcode_offset = @of.size
131
+
132
+ write_header(debug_mode, debug_extract, chdir_before, enable_compression)
133
+
134
+ b = proc {
135
+ yield(self)
136
+ }
137
+
138
+ if enable_compression && LZMA_CMD
139
+ compress(&b)
140
+ else
141
+ b.yield
142
+ end
143
+
144
+ write_footer
145
+ end
146
+
147
+ File.rename(stub, path)
148
+ File.chmod(0755, path) unless WINDOWS
149
+ end
150
+
151
+ def mkdir(target)
152
+ return unless @dirs.add?("/", target)
153
+
154
+ write_opcode(OP_CREATE_DIRECTORY)
155
+ write_path(target)
156
+ end
157
+
158
+ def symlink(link_path, target)
159
+ write_opcode(OP_CREATE_SYMLINK)
160
+ write_path(link_path)
161
+ write_string(target.to_s)
162
+ end
163
+
164
+ def cp(source, target)
165
+ unless File.exist?(source)
166
+ raise "The file does not exist (#{source})"
167
+ end
168
+
169
+ return unless @files.add?(source, target)
170
+
171
+ write_opcode(OP_CREATE_FILE)
172
+ write_path(target)
173
+ write_file(source)
174
+ end
175
+
176
+ # Specifies the final application script to be launched, which can be called
177
+ # from any position in the data stream. It cannot be specified more than once.
178
+ #
179
+ # You can omit setting OP_SET_SCRIPT without issues, in which case
180
+ # the stub terminates without launching anything after performing other
181
+ # runtime operations.
182
+ def exec(image, script, *argv)
183
+ if @script_set
184
+ raise "Script is already set"
185
+ end
186
+ @script_set = true
187
+
188
+ write_opcode(OP_SET_SCRIPT)
189
+ write_string_array(convert_to_native(image), convert_to_native(script), *argv)
190
+ end
191
+
192
+ def export(name, value)
193
+ write_opcode(OP_SETENV)
194
+ write_string(name.to_s)
195
+ write_string(value.to_s)
196
+ end
197
+
198
+ def compress
199
+ raise "No LZMA compressor found" unless LZMA_CMD
200
+
201
+ IO.popen(LZMA_CMD, "r+b") do |lzma|
202
+ _of, @of = @of, lzma
203
+ Thread.new { yield(self); lzma.close_write }
204
+ IO.copy_stream(lzma, _of)
205
+ @of = _of
206
+ end
207
+
208
+ # Calculate the position to write the LZMA decompressed size (64-bit unsigned integer)
209
+ # @opcode_offset: start position of the data section
210
+ # 1: size of the header byte
211
+ # 5: size of the LZMA header in bytes
212
+ File.binwrite(@of.path, [@data_size].pack("Q<"), @opcode_offset + 1 + 5)
213
+ end
214
+ private :compress
215
+
216
+ def write_header(debug_mode, debug_extract, chdir_before, compressed)
217
+ next_to_exe, delete_after = debug_extract, !debug_extract
218
+ @of << [0 |
219
+ (debug_mode ? DEBUG_MODE : 0) |
220
+ (next_to_exe ? EXTRACT_TO_EXE_DIR : 0) |
221
+ (delete_after ? AUTO_CLEAN_INST_DIR : 0) |
222
+ (chdir_before ? CHDIR_BEFORE_SCRIPT : 0) |
223
+ (compressed ? DATA_COMPRESSED : 0)
224
+ ].pack("C")
225
+ end
226
+ private :write_header
227
+
228
+ def write_opcode(op)
229
+ @of << [op].pack("C")
230
+ @data_size += 1
231
+ end
232
+ private :write_opcode
233
+
234
+ def write_size(i)
235
+ if i > 0xFFFF_FFFF
236
+ raise ArgumentError, "Size #{i} is too large: must be 32-bit unsigned integer (0 to 4294967295)"
237
+ end
238
+
239
+ @of << [i].pack("V")
240
+ @data_size += 4
241
+ end
242
+ private :write_size
243
+
244
+ def write_string(str)
245
+ len = str.bytesize + 1 # +1 to account for the null terminator
246
+
247
+ if len > 0xFFFF
248
+ raise ArgumentError, "String length #{len} is too large: must be less than or equal to 65535 bytes including null terminator"
249
+ end
250
+
251
+ write_size(len)
252
+ @of << [str].pack("Z*")
253
+ @data_size += len
254
+ end
255
+ private :write_string
256
+
257
+ def write_string_array(*str_array)
258
+ ary = str_array.map(&:to_s)
259
+
260
+ if ary.any?(&:empty?)
261
+ raise ArgumentError, "Argument list must not contain empty strings"
262
+ end
263
+
264
+ # Append an empty string so that when joined with "\0", the final buffer
265
+ # ends in two consecutive NUL bytes (double–NUL terminator) to mark end-of-list.
266
+ ary << ""
267
+
268
+ size = ary.sum(0) { |s| s.bytesize + 1 }
269
+ write_size(size)
270
+ ary.each_slice(1) { |a| @of << a.pack("Z*") }
271
+ @data_size += size
272
+ end
273
+ private :write_string_array
274
+
275
+ def write_file(src)
276
+ size = File.size(src)
277
+ write_size(size)
278
+ IO.copy_stream(src, @of)
279
+ @data_size += size
280
+ end
281
+ private :write_file
282
+
283
+ def write_path(path)
284
+ write_string(convert_to_native(path))
285
+ end
286
+ private :write_path
287
+
288
+ def write_footer
289
+ @of << ([@opcode_offset] + Signature).pack("VC*")
290
+ end
291
+ private :write_footer
292
+
293
+ def convert_to_native(path)
294
+ WINDOWS ? path.to_s.tr(File::SEPARATOR, "\\") : path.to_s
295
+ end
296
+ private :convert_to_native
297
+ end
298
+ end
data/lib/ocran/version.rb CHANGED
@@ -1,5 +1,5 @@
1
- # frozen_string_literal: true
2
-
3
- module Ocran
4
- VERSION = "1.3.18"
5
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Ocran
4
+ VERSION = "1.4.1"
5
+ end
@@ -1,15 +1,15 @@
1
- # frozen_string_literal: true
2
-
3
- module Ocran
4
- module WindowsCommandEscaping
5
- module_function
6
-
7
- def escape_double_quotes(s)
8
- s.to_s.gsub('"', '""')
9
- end
10
-
11
- def quote_and_escape(s)
12
- "\"#{escape_double_quotes(s)}\""
13
- end
14
- end
15
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Ocran
4
+ module WindowsCommandEscaping
5
+ module_function
6
+
7
+ def escape_double_quotes(s)
8
+ s.to_s.gsub('"', '""')
9
+ end
10
+
11
+ def quote_and_escape(s)
12
+ "\"#{escape_double_quotes(s)}\""
13
+ end
14
+ end
15
+ end
data/lib/ocran.rb CHANGED
@@ -1,7 +1,7 @@
1
- # frozen_string_literal: true
2
-
3
- module Ocran
4
- autoload :VERSION, "ocran/version"
5
-
6
- singleton_class.attr_accessor :option
7
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Ocran
4
+ autoload :VERSION, "ocran/version"
5
+
6
+ singleton_class.attr_accessor :option
7
+ end
data/share/ocran/lzma.exe CHANGED
File without changes