stdlib/nodejs/file.rb
# backtick_javascript: true
require 'corelib/file'
%x{
var warnings = {}, errno_codes = #{Errno.constants};
function handle_unsupported_feature(message) {
switch (Opal.config.unsupported_features_severity) {
case 'error':
#{Kernel.raise NotImplementedError, `message`}
break;
case 'warning':
warn(message)
break;
default: // ignore
// noop
}
}
function warn(string) {
if (warnings[string]) {
return;
}
warnings[string] = true;
#{warn(`string`)};
}
function is_utf8(bytes) {
var i = 0;
while (i < bytes.length) {
if ((// ASCII
bytes[i] === 0x09 ||
bytes[i] === 0x0A ||
bytes[i] === 0x0D ||
(0x20 <= bytes[i] && bytes[i] <= 0x7E)
)
) {
i += 1;
continue;
}
if ((// non-overlong 2-byte
(0xC2 <= bytes[i] && bytes[i] <= 0xDF) &&
(0x80 <= bytes[i + 1] && bytes[i + 1] <= 0xBF)
)
) {
i += 2;
continue;
}
if ((// excluding overlongs
bytes[i] === 0xE0 &&
(0xA0 <= bytes[i + 1] && bytes[i + 1] <= 0xBF) &&
(0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF)
) ||
(// straight 3-byte
((0xE1 <= bytes[i] && bytes[i] <= 0xEC) ||
bytes[i] === 0xEE ||
bytes[i] === 0xEF) &&
(0x80 <= bytes[i + 1] && bytes[i + 1] <= 0xBF) &&
(0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF)
) ||
(// excluding surrogates
bytes[i] === 0xED &&
(0x80 <= bytes[i + 1] && bytes[i + 1] <= 0x9F) &&
(0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF)
)
) {
i += 3;
continue;
}
if ((// planes 1-3
bytes[i] === 0xF0 &&
(0x90 <= bytes[i + 1] && bytes[i + 1] <= 0xBF) &&
(0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF) &&
(0x80 <= bytes[i + 3] && bytes[i + 3] <= 0xBF)
) ||
(// planes 4-15
(0xF1 <= bytes[i] && bytes[i] <= 0xF3) &&
(0x80 <= bytes[i + 1] && bytes[i + 1] <= 0xBF) &&
(0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF) &&
(0x80 <= bytes[i + 3] && bytes[i + 3] <= 0xBF)
) ||
(// plane 16
bytes[i] === 0xF4 &&
(0x80 <= bytes[i + 1] && bytes[i + 1] <= 0x8F) &&
(0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF) &&
(0x80 <= bytes[i + 3] && bytes[i + 3] <= 0xBF)
)
) {
i += 4;
continue;
}
return false;
}
return true;
}
function executeIOAction(action) {
try {
return action();
} catch (error) {
if (errno_codes.indexOf(error.code) >= 0) {
var error_class = #{Errno.const_get(`error.code`)}
#{Kernel.raise `error_class`.new(`error.message`)}
}
#{Kernel.raise `error`}
}
}
}
class File < IO
@__fs__ = `require('fs')`
@__path__ = `require('path')`
`var __fs__ = #{@__fs__}`
`var __path__ = #{@__path__}`
# Since Node.js 11+ TextEncoder and TextDecoder are now available on the global object.
%x{
var __TextEncoder__, __TextDecoder__;
if (typeof TextEncoder !== 'undefined') {
__TextEncoder__ = TextEncoder;
__TextDecoder__ = TextDecoder;
} else {
var __util__ = require('util');
__TextEncoder__ = __util__.TextEncoder;
__TextDecoder__ = __util__.TextDecoder;
}
var __utf8TextDecoder__ = new __TextDecoder__('utf8');
var __textEncoder__ = new __TextEncoder__();
}
if `__path__.sep !== #{Separator}`
ALT_SEPARATOR = `__path__.sep`
end
def self.read(path)
`return executeIOAction(function(){return __fs__.readFileSync(#{path}).toString()})`
end
def self.write(path, data)
`executeIOAction(function(){return __fs__.writeFileSync(#{path}, #{data})})`
data.size
end
def self.symlink(path, new_path)
`executeIOAction(function(){return __fs__.symlinkSync(#{path}, #{new_path})})`
0
end
def self.delete(path)
`executeIOAction(function(){return __fs__.unlinkSync(#{path})})`
end
class << self
alias unlink delete
end
def self.exist?(path)
path = path.path if path.respond_to? :path
`return executeIOAction(function(){return __fs__.existsSync(#{path})})`
end
def self.realpath(pathname, dir_string = nil, cache = nil, &block)
pathname = join(dir_string, pathname) if dir_string
if block_given?
`
__fs__.realpath(#{pathname}, #{cache}, function(error, realpath){
if (error) Opal.IOError.$new(error.message)
else #{block.call(`realpath`)}
})
`
else
`return executeIOAction(function(){return __fs__.realpathSync(#{pathname}, #{cache})})`
end
end
def self.join(*paths)
# by itself, `path.posix.join` normalizes leading // to /.
# restore the leading / on UNC paths (i.e., paths starting with //).
paths = paths.map(&:to_s)
prefix = paths.first&.start_with?('//') ? '/' : ''
`#{prefix} + __path__.posix.join.apply(__path__, #{paths})`
end
def self.directory?(path)
return false unless exist? path
result = `executeIOAction(function(){return !!__fs__.lstatSync(path).isDirectory()})`
unless result
realpath = realpath(path)
if realpath != path
result = `executeIOAction(function(){return !!__fs__.lstatSync(realpath).isDirectory()})`
end
end
result
end
def self.file?(path)
return false unless exist? path
result = `executeIOAction(function(){return !!__fs__.lstatSync(path).isFile()})`
unless result
realpath = realpath(path)
if realpath != path
result = `executeIOAction(function(){return !!__fs__.lstatSync(realpath).isFile()})`
end
end
result
end
def self.readable?(path)
return false unless exist? path
%x{
try {
__fs__.accessSync(path, __fs__.R_OK);
return true;
} catch (error) {
return false;
}
}
end
def self.size(path)
`return executeIOAction(function(){return __fs__.lstatSync(path).size})`
end
def self.open(path, mode = 'r')
file = new(path, mode)
if block_given?
begin
yield(file)
ensure
file.close
end
else
file
end
end
def self.stat(path)
path = path.path if path.respond_to? :path
File::Stat.new(path)
end
def self.mtime(path)
`return executeIOAction(function(){return __fs__.statSync(#{path}).mtime})`
end
def self.symlink?(path)
`return executeIOAction(function(){return __fs__.lstatSync(#{path}).isSymbolicLink()})`
end
def self.absolute_path(path, basedir = nil)
path = path.respond_to?(:to_path) ? path.to_path : path
basedir ||= Dir.pwd
`return __path__.normalize(__path__.resolve(#{basedir.to_str}, #{path.to_str})).split(__path__.sep).join(__path__.posix.sep)`
end
def self.absolute_path?(path)
`__path__.isAbsolute(path)`
end
# Instance Methods
def initialize(path, flags = 'r')
@binary_flag = flags.include?('b')
# Node does not recognize this flag
flags = flags.delete('b')
# encoding flag is unsupported
encoding_option_rx = /:(.*)/
if encoding_option_rx.match?(flags)
`handle_unsupported_feature("Encoding option (:encoding) is unsupported by Node.js openSync method and will be removed.")`
flags = flags.sub(encoding_option_rx, '')
end
@path = path
fd = `executeIOAction(function(){return __fs__.openSync(path, flags)})`
super(fd, flags)
end
attr_reader :path
def sysread(bytes)
if @eof
raise EOFError, 'end of file reached'
else
if @binary_flag
%x{
var buf = executeIOAction(function(){return __fs__.readFileSync(#{@path})})
var content
if (is_utf8(buf)) {
content = buf.toString('utf8')
} else {
// coerce to utf8
content = __utf8TextDecoder__.decode(__textEncoder__.encode(buf.toString('binary')))
}
}
res = `content`
else
res = `executeIOAction(function(){return __fs__.readFileSync(#{@path}).toString('utf8')})`
end
@eof = true
@lineno = res.size
res
end
end
def write(string)
`executeIOAction(function(){return __fs__.writeSync(#{@fd}, #{string})})`
end
def flush
`executeIOAction(function(){return __fs__.fsyncSync(#{@fd})})`
end
def close
`executeIOAction(function(){return __fs__.closeSync(#{@fd})})`
super
end
def mtime
`return executeIOAction(function(){return __fs__.statSync(#{@path}).mtime})`
end
end
class File::Stat
@__fs__ = `require('fs')`
`var __fs__ = #{@__fs__}`
def initialize(path)
@path = path
end
def file?
`return executeIOAction(function(){return __fs__.statSync(#{@path}).isFile()})`
end
def directory?
`return executeIOAction(function(){return __fs__.statSync(#{@path}).isDirectory()})`
end
def mtime
`return executeIOAction(function(){return __fs__.statSync(#{@path}).mtime})`
end
def readable?
`return executeIOAction(function(){return __fs__.accessSync(#{@path}, __fs__.constants.R_OK)})`
end
def writable?
`return executeIOAction(function(){return __fs__.accessSync(#{@path}, __fs__.constants.W_OK)})`
end
def executable?
`return executeIOAction(function(){return __fs__.accessSync(#{@path}, __fs__.constants.X_OK)})`
end
end