aibika 1.3.12

Sign up to get free protection for your applications and to get access to all the features.
@@ -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