aibika 1.3.12

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.
@@ -0,0 +1,191 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aibika
4
+ # Utility class that produces the actual executable. Opcodes
5
+ # (createfile, mkdir etc) are added by invoking methods on an
6
+ # instance of AibikaBuilder.
7
+ class AibikaBuilder
8
+ Signature = [0x41, 0xb6, 0xba, 0x4e].freeze
9
+ OP_END = 0
10
+ OP_CREATE_DIRECTORY = 1
11
+ OP_CREATE_FILE = 2
12
+ OP_CREATE_PROCESS = 3
13
+ OP_DECOMPRESS_LZMA = 4
14
+ OP_SETENV = 5
15
+ OP_POST_CREATE_PROCESS = 6
16
+ OP_ENABLE_DEBUG_MODE = 7
17
+ OP_CREATE_INST_DIRECTORY = 8
18
+
19
+ def initialize(path, windowed)
20
+ @paths = {}
21
+ @files = {}
22
+ File.open(path, 'wb') do |aibikafile|
23
+ image = if windowed
24
+ Aibika.stubwimage
25
+ else
26
+ Aibika.stubimage
27
+ end
28
+
29
+ Aibika.fatal_error 'Stub image not available' unless image
30
+ aibikafile.write(image)
31
+ end
32
+
33
+ system Aibika.ediconpath, path, Aibika.icon_filename if Aibika.icon_filename
34
+
35
+ opcode_offset = File.size(path)
36
+
37
+ File.open(path, 'ab') do |aibikafile|
38
+ tmpinpath = 'tmpin'
39
+
40
+ @of = if Aibika.lzma_mode
41
+ File.open(tmpinpath, 'wb')
42
+ else
43
+ aibikafile
44
+ end
45
+
46
+ if Aibika.debug
47
+ Aibika.msg('Enabling debug mode in executable')
48
+ aibikafile.write([OP_ENABLE_DEBUG_MODE].pack('V'))
49
+ end
50
+
51
+ createinstdir Aibika.debug_extract, !Aibika.debug_extract, Aibika.chdir_first
52
+
53
+ yield(self)
54
+
55
+ @of.close if Aibika.lzma_mode
56
+
57
+ if Aibika.lzma_mode && !Aibika.inno_script
58
+ tmpoutpath = 'tmpout'
59
+ begin
60
+ data_size = File.size(tmpinpath)
61
+ Aibika.msg "Compressing #{data_size} bytes"
62
+ system(Aibika.lzmapath, 'e', tmpinpath, tmpoutpath) or raise
63
+ compressed_data_size = File.size?(tmpoutpath)
64
+ aibikafile.write([OP_DECOMPRESS_LZMA, compressed_data_size].pack('VV'))
65
+ IO.copy_stream(tmpoutpath, aibikafile)
66
+ ensure
67
+ File.unlink(@of.path) if File.exist?(@of.path)
68
+ File.unlink(tmpoutpath) if File.exist?(tmpoutpath)
69
+ end
70
+ end
71
+
72
+ aibikafile.write([OP_END].pack('V'))
73
+ aibikafile.write([opcode_offset].pack('V')) # Pointer to start of opcodes
74
+ aibikafile.write(Signature.pack('C*'))
75
+ end
76
+
77
+ return unless Aibika.inno_script
78
+
79
+ begin
80
+ iss = "#{File.read(Aibika.inno_script)}\n\n"
81
+
82
+ iss << "[Dirs]\n"
83
+ @paths.each_key do |p|
84
+ iss << "Name: \"{app}/#{p}\"\n"
85
+ end
86
+ iss << "\n"
87
+
88
+ iss << "[Files]\n"
89
+ path_escaped = path.to_s.gsub('"', '""')
90
+ iss << "Source: \"#{path_escaped}\"; DestDir: \"{app}\"\n"
91
+ @files.each do |tgt, src|
92
+ src_escaped = src.to_s.gsub('"', '""')
93
+ target_dir_escaped = Pathname.new(tgt).dirname.to_s.gsub('"', '""')
94
+ unless src_escaped =~ IGNORE_MODULE_NAMES
95
+ iss << "Source: \"#{src_escaped}\"; DestDir: \"{app}/#{target_dir_escaped}\"\n"
96
+ end
97
+ end
98
+ iss << "\n"
99
+
100
+ Aibika.verbose_msg "### INNOSETUP SCRIPT ###\n\n#{iss}\n\n"
101
+
102
+ f = File.open('aibikatemp.iss', 'w')
103
+ f.write(iss)
104
+ f.close
105
+
106
+ iscc_cmd = ['iscc']
107
+ iscc_cmd << '/Q' unless Aibika.verbose
108
+ iscc_cmd << 'aibikatemp.iss'
109
+ Aibika.msg 'Running InnoSetup compiler ISCC'
110
+ result = system(*iscc_cmd)
111
+ unless result
112
+ case $CHILD_STATUS
113
+ when 0 then raise 'ISCC reported success, but system reported error?'
114
+ when 1 then raise 'ISCC reports invalid command line parameters'
115
+ when 2 then raise 'ISCC reports that compilation failed'
116
+ else raise 'ISCC failed to run. Is the InnoSetup directory in your PATH?'
117
+ end
118
+ end
119
+ rescue Exception => e
120
+ Aibika.fatal_error("InnoSetup installer creation failed: #{e.message}")
121
+ ensure
122
+ File.unlink('aibikatemp.iss') if File.exist?('aibikatemp.iss')
123
+ File.unlink(path) if File.exist?(path)
124
+ end
125
+ end
126
+
127
+ def mkdir(path)
128
+ return if @paths[path.path.downcase]
129
+
130
+ @paths[path.path.downcase] = true
131
+ Aibika.verbose_msg "m #{showtempdir path}"
132
+ return if Aibika.inno_script # The directory will be created by InnoSetup with a [Dirs] statement
133
+
134
+ @of << [OP_CREATE_DIRECTORY, path.to_native].pack('VZ*')
135
+ end
136
+
137
+ def ensuremkdir(tgt)
138
+ tgt = Aibika.Pathname(tgt)
139
+ return if tgt.path == '.'
140
+
141
+ return if @paths[tgt.to_posix.downcase]
142
+
143
+ ensuremkdir(tgt.dirname)
144
+ mkdir(tgt)
145
+ end
146
+
147
+ def createinstdir(next_to_exe = false, delete_after = false, chdir_before = false)
148
+ return if Aibika.inno_script # Creation of installation directory will be handled by InnoSetup
149
+
150
+ @of << [OP_CREATE_INST_DIRECTORY, next_to_exe ? 1 : 0, delete_after ? 1 : 0, chdir_before ? 1 : 0].pack('VVVV')
151
+ end
152
+
153
+ def createfile(src, tgt)
154
+ return if @files[tgt]
155
+
156
+ @files[tgt] = src
157
+ src = Aibika.Pathname(src)
158
+ tgt = Aibika.Pathname(tgt)
159
+ ensuremkdir(tgt.dirname)
160
+ str = File.open(src, 'rb', &:read)
161
+ Aibika.verbose_msg "a #{showtempdir tgt}"
162
+ return if Aibika.inno_script # InnoSetup will install the file with a [Files] statement
163
+
164
+ @of << [OP_CREATE_FILE, tgt.to_native, str.size].pack('VZ*V')
165
+ @of << str
166
+ end
167
+
168
+ def createprocess(image, cmdline)
169
+ Aibika.verbose_msg "l #{showtempdir image} #{showtempdir cmdline}"
170
+ @of << [OP_CREATE_PROCESS, image.to_native, cmdline].pack('VZ*Z*')
171
+ end
172
+
173
+ def postcreateprocess(image, cmdline)
174
+ Aibika.verbose_msg "p #{showtempdir image} #{showtempdir cmdline}"
175
+ @of << [OP_POST_CREATE_PROCESS, image.to_native, cmdline].pack('VZ*Z*')
176
+ end
177
+
178
+ def setenv(name, value)
179
+ Aibika.verbose_msg "e #{name} #{showtempdir value}"
180
+ @of << [OP_SETENV, name, value].pack('VZ*Z*')
181
+ end
182
+
183
+ def close
184
+ @of.close
185
+ end
186
+
187
+ def showtempdir(tdir)
188
+ tdir.to_s.gsub(TEMPDIR_ROOT, '<tempdir>')
189
+ end
190
+ end
191
+ end
data/lib/aibika/cli.rb ADDED
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Aibika front-end
4
+ module Aibika
5
+ def self.parseargs(argv)
6
+ usage = <<~USG
7
+ aibika [options] script.rb
8
+
9
+ Aibika options:
10
+
11
+ --help Display this information.
12
+ --quiet Suppress output while building executable.
13
+ --verbose Show extra output while building executable.
14
+ --version Display version number and exit.
15
+
16
+ Packaging options:
17
+
18
+ --dll dllname Include additional DLLs from the Ruby bindir.
19
+ --add-all-core Add all core ruby libraries to the executable.
20
+ --gemfile <file> Add all gems and dependencies listed in a Bundler Gemfile.
21
+ --no-enc Exclude encoding support files
22
+ --allow-self Include self (aibika gem) if detected or specified
23
+ This option is required only if aibika gem is deployed as a part
24
+ of broader bundled your solution
25
+
26
+ Gem content detection modes:
27
+
28
+ --gem-minimal[=gem1,..] Include only loaded scripts
29
+ --gem-guess=[gem1,...] Include loaded scripts & best guess (DEFAULT)
30
+ --gem-all[=gem1,..] Include all scripts & files
31
+ --gem-full[=gem1,..] Include EVERYTHING
32
+ --gem-spec[=gem1,..] Include files in gemspec (Does not work with Rubygems 1.7+)
33
+
34
+ minimal: loaded scripts
35
+ guess: loaded scripts and other files
36
+ all: loaded scripts, other scripts, other files (except extras)
37
+ full: Everything found in the gem directory
38
+
39
+ --[no-]gem-scripts[=..] Other script files than those loaded
40
+ --[no-]gem-files[=..] Other files (e.g. data files)
41
+ --[no-]gem-extras[=..] Extra files (README, etc.)
42
+
43
+ scripts: .rb/.rbw files
44
+ extras: C/C++ sources, object files, test, spec, README
45
+ files: all other files
46
+
47
+ Auto-detection options:
48
+
49
+ --no-dep-run Don't run script.rb to check for dependencies.
50
+ --no-autoload Don't load/include script.rb's autoloads.
51
+ --no-autodll Disable detection of runtime DLL dependencies.
52
+
53
+ Output options:
54
+
55
+ --output <file> Name the exe to generate. Defaults to ./<scriptname>.exe.
56
+ --no-lzma Disable LZMA compression of the executable.
57
+ --innosetup <file> Use given Inno Setup script (.iss) to create an installer.
58
+
59
+ Executable options:
60
+
61
+ --windows Force Windows application (rubyw.exe)
62
+ --console Force console application (ruby.exe)
63
+ --chdir-first When exe starts, change working directory to app dir.
64
+ --icon <ico> Replace icon with a custom one.
65
+ --debug Executable will be verbose.
66
+ --debug-extract Executable will unpack to local dir and not delete after.
67
+ USG
68
+
69
+ while (arg = argv.shift)
70
+ case arg
71
+ when /\A--(no-)?lzma\z/
72
+ @options[:lzma_mode] = !::Regexp.last_match(1)
73
+ when /\A--no-dep-run\z/
74
+ @options[:run_script] = false
75
+ when /\A--add-all-core\z/
76
+ @options[:add_all_core] = true
77
+ when /\A--output\z/
78
+ @options[:output_override] = Pathname(argv.shift)
79
+ when /\A--dll\z/
80
+ @options[:extra_dlls] << argv.shift
81
+ when /\A--quiet\z/
82
+ @options[:quiet] = true
83
+ when /\A--verbose\z/
84
+ @options[:verbose] = true
85
+ when /\A--windows\z/
86
+ @options[:force_windows] = true
87
+ when /\A--console\z/
88
+ @options[:force_console] = true
89
+ when /\A--no-autoload\z/
90
+ @options[:load_autoload] = false
91
+ when /\A--chdir-first\z/
92
+ @options[:chdir_first] = true
93
+ when /\A--icon\z/
94
+ @options[:icon_filename] = Pathname(argv.shift)
95
+ Aibika.fatal_error "Icon file #{icon_filename} not found.\n" unless icon_filename.exist?
96
+ when /\A--gemfile\z/
97
+ @options[:gemfile] = Pathname(argv.shift)
98
+ Aibika.fatal_error "Gemfile #{gemfile} not found.\n" unless gemfile.exist?
99
+ when /\A--innosetup\z/
100
+ @options[:inno_script] = Pathname(argv.shift)
101
+ Aibika.fatal_error "Inno Script #{inno_script} not found.\n" unless inno_script.exist?
102
+ when /\A--no-autodll\z/
103
+ @options[:autodll] = false
104
+ when /\A--version\z/
105
+ puts "Aibika #{VERSION}"
106
+ exit 0
107
+ when /\A--no-warnings\z/
108
+ @options[:show_warnings] = false
109
+ when /\A--debug\z/
110
+ @options[:debug] = true
111
+ when /\A--debug-extract\z/
112
+ @options[:debug_extract] = true
113
+ when /\A--\z/
114
+ @options[:arg] = ARGV.dup
115
+ ARGV.clear
116
+ when /\A--(no-)?enc\z/
117
+ @options[:enc] = !::Regexp.last_match(1)
118
+ when /\A--(no-)?gem-(\w+)(?:=(.*))?$/
119
+ negate = ::Regexp.last_match(1)
120
+ group = ::Regexp.last_match(2)
121
+ list = ::Regexp.last_match(3)
122
+ @options[:gem] ||= []
123
+ @options[:gem] << [negate, group.to_sym, list && list.split(',')]
124
+ when /\A--help\z/, /\A--./
125
+ puts usage
126
+ exit 0
127
+ else
128
+ @options[:files] << if __FILE__.respond_to?(:encoding)
129
+ arg.dup.force_encoding(__FILE__.encoding)
130
+ else
131
+ arg
132
+ end
133
+ end
134
+ end
135
+
136
+ if Aibika.debug_extract && Aibika.inno_script
137
+ Aibika.fatal_error 'The --debug-extract option conflicts with use of Inno Setup'
138
+ end
139
+
140
+ if Aibika.lzma_mode && Aibika.inno_script
141
+ Aibika.fatal_error 'LZMA compression must be disabled (--no-lzma) when using Inno Setup'
142
+ end
143
+
144
+ if !Aibika.chdir_first && Aibika.inno_script
145
+ Aibika.fatal_error 'Chdir-first mode must be enabled (--chdir-first) when using Inno Setup'
146
+ end
147
+
148
+ if files.empty?
149
+ puts usage
150
+ exit 1
151
+ end
152
+
153
+ @options[:files].map! do |path|
154
+ path = path.encode('UTF-8').tr('\\', '/')
155
+ if File.directory?(path)
156
+ # If a directory is passed, we want all files under that directory
157
+ path = "#{path}/**/*"
158
+ end
159
+ files = Dir[path]
160
+ Aibika.fatal_error "#{path} not found!" if files.empty?
161
+ files.map { |pth| Pathname(pth).expand }
162
+ end.flatten!
163
+ end
164
+
165
+ def self.msg(msg)
166
+ puts "=== #{msg}" unless Aibika.quiet
167
+ end
168
+
169
+ def self.verbose_msg(msg)
170
+ puts msg if Aibika.verbose && !Aibika.quiet
171
+ end
172
+
173
+ def self.warn(msg)
174
+ msg "WARNING: #{msg}" if Aibika.show_warnings
175
+ end
176
+
177
+ def self.fatal_error(msg)
178
+ puts "ERROR: #{msg}"
179
+ exit 1
180
+ end
181
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aibika
4
+ # Variables describing the host's build environment.
5
+ module Host
6
+ class << self
7
+ def exec_prefix
8
+ @exec_prefix ||= Aibika.Pathname(RbConfig::CONFIG['exec_prefix'])
9
+ end
10
+
11
+ def sitelibdir
12
+ @sitelibdir ||= Aibika.Pathname(RbConfig::CONFIG['sitelibdir'])
13
+ end
14
+
15
+ def bindir
16
+ @bindir ||= Aibika.Pathname(RbConfig::CONFIG['bindir'])
17
+ end
18
+
19
+ def libruby_so
20
+ @libruby_so ||= Aibika.Pathname(RbConfig::CONFIG['LIBRUBY_SO'])
21
+ end
22
+
23
+ def exeext
24
+ RbConfig::CONFIG['EXEEXT'] || '.exe'
25
+ end
26
+
27
+ def rubyw_exe
28
+ @rubyw_exe ||= (RbConfig::CONFIG['rubyw_install_name'] || 'rubyw') + exeext
29
+ end
30
+
31
+ def ruby_exe
32
+ @ruby_exe ||= (RbConfig::CONFIG['ruby_install_name'] || 'ruby') + exeext
33
+ end
34
+
35
+ def tempdir
36
+ @tempdir ||= Aibika.Pathname(ENV['TEMP'])
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aibika
4
+ module LibraryDetector
5
+ def self.init_fiddle
6
+ require 'fiddle'
7
+ require 'fiddle/types'
8
+ module_eval do
9
+ extend Fiddle::Importer
10
+ dlload 'psapi.dll'
11
+ include Fiddle::Win32Types
12
+ extern 'BOOL EnumProcessModules(HANDLE, HMODULE*, DWORD, DWORD*)'
13
+ extend Fiddle::Importer
14
+ dlload 'kernel32.dll'
15
+ include Fiddle::Win32Types
16
+
17
+ # https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types
18
+ # typedef PVOID HANDLE
19
+ # typedef HINSTANCE HMODULE;
20
+
21
+ typealias 'HMODULE', 'voidp'
22
+ typealias 'HANDLE', 'voidp'
23
+ typealias 'LPWSTR', 'char*'
24
+
25
+ extern 'DWORD GetModuleFileNameW(HMODULE, LPWSTR, DWORD)'
26
+ extern 'HANDLE GetCurrentProcess(void)'
27
+ extern 'DWORD GetLastError(void)'
28
+ end
29
+ end
30
+
31
+ def self.loaded_dlls
32
+ require 'fiddle'
33
+ psapi = Fiddle.dlopen('psapi')
34
+ enumprocessmodules = Fiddle::Function.new(
35
+ psapi['EnumProcessModules'],
36
+ [Fiddle::TYPE_UINTPTR_T, Fiddle::TYPE_VOIDP, Fiddle::TYPE_LONG, Fiddle::TYPE_VOIDP], Fiddle::TYPE_LONG
37
+ )
38
+ kernel32 = Fiddle.dlopen('kernel32')
39
+ getcurrentprocess = Fiddle::Function.new(kernel32['GetCurrentProcess'], [], Fiddle::TYPE_LONG)
40
+ getmodulefilename = Fiddle::Function.new(
41
+ kernel32['GetModuleFileNameW'],
42
+ [Fiddle::TYPE_UINTPTR_T, Fiddle::TYPE_VOIDP, Fiddle::TYPE_LONG], Fiddle::TYPE_LONG
43
+ )
44
+ getlasterror = Fiddle::Function.new(kernel32['GetLastError'], [], Fiddle::TYPE_LONG)
45
+
46
+ # Different packing/unpacking for 64/32 bits systems
47
+ if Fiddle::SIZEOF_VOIDP == 8
48
+ f_single = 'Q'
49
+ f_array = 'Q*'
50
+ else
51
+ f_single = 'I'
52
+ f_array = 'I*'
53
+ end
54
+
55
+ bytes_needed = Fiddle::SIZEOF_VOIDP * 32
56
+ module_handle_buffer = nil
57
+ process_handle = getcurrentprocess.call
58
+ loop do
59
+ module_handle_buffer = "\x00" * bytes_needed
60
+ bytes_needed_buffer = [0].pack(f_single)
61
+ enumprocessmodules.call(process_handle, module_handle_buffer, module_handle_buffer.size,
62
+ bytes_needed_buffer)
63
+ bytes_needed = bytes_needed_buffer.unpack1(f_single)
64
+ break if bytes_needed <= module_handle_buffer.size
65
+ end
66
+
67
+ handles = module_handle_buffer.unpack(f_array)
68
+ handles.select(&:positive?).map do |handle|
69
+ str = "\x00\x00" * 256
70
+ modulefilename_length = getmodulefilename.call(handle, str, str.size)
71
+ unless modulefilename_length.positive?
72
+ errorcode = getlasterror.call
73
+ Aibika.fatal_error "LibraryDetector: GetModuleFileNameW failed with error code 0x#{errorcode.to_s(16)}"
74
+ end
75
+ modulefilename = str[0, modulefilename_length * 2].force_encoding('UTF-16LE').encode('UTF-8')
76
+ Aibika.Pathname(modulefilename)
77
+ end
78
+ end
79
+
80
+ def self.detect_dlls
81
+ loaded = loaded_dlls
82
+ exec_prefix = Host.exec_prefix
83
+ loaded.select do |path|
84
+ path.subpath?(exec_prefix) && path.basename.ext?('.dll') && path.basename != Host.libruby_so
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aibika
4
+ # Path handling class. Ruby's Pathname class is not used because it
5
+ # is case sensitive and doesn't handle paths with mixed path
6
+ # separators.
7
+ class Pathname
8
+ def self.pwd
9
+ Pathname.new(Dir.pwd.encode('UTF-8'))
10
+ end
11
+
12
+ def self.pathequal(path_a, path_b)
13
+ path_a.downcase == path_b.downcase
14
+ end
15
+
16
+ attr_reader :path
17
+
18
+ File::ALT_SEPARATOR = '\\' if File::ALT_SEPARATOR.nil?
19
+ SEPARATOR_PAT = /[#{Regexp.quote File::ALT_SEPARATOR}#{Regexp.quote File::SEPARATOR}]/.freeze # }
20
+ ABSOLUTE_PAT = /\A([A-Z]:)?#{SEPARATOR_PAT}/i.freeze
21
+
22
+ def initialize(path)
23
+ @path = path&.encode('UTF-8')
24
+ end
25
+
26
+ def to_native
27
+ @path.tr File::SEPARATOR, File::ALT_SEPARATOR
28
+ end
29
+
30
+ def to_posix
31
+ @path.tr File::ALT_SEPARATOR, File::SEPARATOR
32
+ end
33
+
34
+ # Compute the relative path from the 'src' path (directory) to 'tgt'
35
+ # (directory or file). Return the absolute path to 'tgt' if it can't
36
+ # be reached from 'src'.
37
+ def relative_path_from(other)
38
+ a = @path.split(SEPARATOR_PAT)
39
+ b = other.path.split(SEPARATOR_PAT)
40
+ while a.first && b.first && Pathname.pathequal(a.first, b.first)
41
+ a.shift
42
+ b.shift
43
+ end
44
+ return other if Pathname.new(b.first).absolute?
45
+
46
+ b.size.times { a.unshift '..' }
47
+ Pathname.new(a.join('/'))
48
+ end
49
+
50
+ # Determines if 'src' is contained in 'tgt' (i.e. it is a subpath of
51
+ # 'tgt'). Both must be absolute paths and not contain '..'
52
+ def subpath?(other)
53
+ other = Aibika.Pathname(other)
54
+ src_normalized = to_posix.downcase
55
+ tgt_normalized = other.to_posix.downcase
56
+ src_normalized =~ /^#{Regexp.escape tgt_normalized}#{SEPARATOR_PAT}/i
57
+ end
58
+
59
+ # Join two pathnames together. Returns the right-hand side if it
60
+ # is an absolute path. Otherwise, returns the full path of the
61
+ # left + right.
62
+ def /(other)
63
+ other = Aibika.Pathname(other)
64
+ if other.absolute?
65
+ other
66
+ else
67
+ Aibika.Pathname("#{@path}/#{other.path}")
68
+ end
69
+ end
70
+
71
+ def append_to_filename!(append)
72
+ @path.sub!(/(\.[^.]*?|)$/) { append.to_s + ::Regexp.last_match(1) }
73
+ end
74
+
75
+ def ext(new_ext = nil)
76
+ if new_ext
77
+ Pathname.new(@path.sub(/(\.[^.]*?)?$/) { new_ext })
78
+ else
79
+ File.extname(@path)
80
+ end
81
+ end
82
+
83
+ def ext?(expected_ext)
84
+ Pathname.pathequal(ext, expected_ext)
85
+ end
86
+
87
+ def entries
88
+ Dir.entries(@path).map { |e| self / e.encode('UTF-8') }
89
+ end
90
+
91
+ # Recursively find all files which match a specified regular
92
+ # expression.
93
+ def find_all_files(reg)
94
+ entries.map do |pn|
95
+ if pn.directory?
96
+ if pn.basename =~ /^\.\.?$/
97
+ []
98
+ else
99
+ pn.find_all_files(reg)
100
+ end
101
+ elsif pn.file?
102
+ if pn.basename =~ reg
103
+ pn
104
+ else
105
+ []
106
+ end
107
+ end
108
+ end.flatten
109
+ end
110
+
111
+ def ==(other)
112
+ to_posix.downcase == other.to_posix.downcase
113
+ end
114
+
115
+ def =~(other)
116
+ @path =~ other
117
+ end
118
+
119
+ def <=>(other)
120
+ @path.casecmp(other.path)
121
+ end
122
+
123
+ def exist?
124
+ File.exist?(@path)
125
+ end
126
+
127
+ def file?
128
+ File.file?(@path)
129
+ end
130
+
131
+ def directory?
132
+ File.directory?(@path)
133
+ end
134
+
135
+ def absolute?
136
+ @path =~ ABSOLUTE_PAT
137
+ end
138
+
139
+ def dirname
140
+ Pathname.new(File.dirname(@path))
141
+ end
142
+
143
+ def basename
144
+ Pathname.new(File.basename(@path))
145
+ end
146
+
147
+ def expand(dir = nil)
148
+ Pathname.new(File.expand_path(@path, dir && Aibika.Pathname(dir)))
149
+ end
150
+
151
+ def size
152
+ File.size(@path)
153
+ end
154
+
155
+ alias to_s to_posix
156
+ alias to_str to_posix
157
+ end
158
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aibika
4
+ VERSION = '1.3.12'
5
+ end