# Copyright (c) 2023 Yuki Kimoto
# MIT License
class File::Spec::Instance::Win32 extends File::Spec::Instance::Unix {
version_from File::Spec;
use Re;
use StringList;
use Array;
use Cwd;
our $DRIVE_RX_STRING : string;
our $UNC_RX_STRING : string;
our $VOL_RX_STRING : string;
INIT {
$DRIVE_RX_STRING = "[a-zA-Z]:";
$UNC_RX_STRING = "(?:\\\\\\\\|//)[^\\\\/]+[\\\\/][^\\\\/]+";
$VOL_RX_STRING = "(?:$DRIVE_RX_STRING|$UNC_RX_STRING)";
}
# Class Methods
static method new : File::Spec::Instance::Win32 () {
my $self = new File::Spec::Instance::Win32;
return $self;
}
# Instance Methods
method devnull : string () {
return "nul";
}
method tmpdir : string () {
my $env_tmpdir = Sys->env("TMPDIR");
my $env_temp = Sys->env("TEMP");
my $env_tmp = Sys->env("TMP");
my $dirlist = [
$env_tmpdir,
$env_temp,
$env_tmp,
"SYS:/temp",
"C:\system\temp",
"C:/temp",
"/tmp",
"/",
];
my $tmpdir = $self->_tmpdir($dirlist);
return $tmpdir;
}
method file_name_is_absolute : int ($path : string) {
my $is_abs = 0;
if (my $match = Re->m($path, "^($VOL_RX_STRING)")) {
my $vol = $match->cap1;
if (Re->m($vol, "^$UNC_RX_STRING")) {
$is_abs = 2;
}
else {
if (Re->m($path, "^$DRIVE_RX_STRING[\\\\/]")) {
$is_abs = 2;
}
}
}
else {
if (Re->m($path, "^[\\\\/]")) {
$is_abs = 1;
}
}
return $is_abs;
}
method catfile : string ($parts : string[]) {
unless ($parts) {
die "The \$parts must be defined";
}
my $parts_length = @$parts;
unless ($parts_length > 0) {
die "The parts \$parts must have elements.";
}
my $file_base_name = $parts->[$parts_length - 1];
my $dir_parts = Array->copy_string_address($parts, 0, $parts_length - 1);
if (@$dir_parts) {
unless (length $dir_parts->[0]) {
$dir_parts->[0] = "/";
}
if (Re->m($dir_parts->[0], "^$DRIVE_RX_STRING\z")) {
$dir_parts->[0] .= "\\";
}
}
my $new_parts_list = StringList->new($dir_parts);
$new_parts_list->push($file_base_name);
my $new_parts = $new_parts_list->to_array;
my $file = $self->_canon_cat($new_parts);
return $file;
}
method canonpath : string ($path : string) {
unless ($path) {
die "The \$path must be defined";
}
unless (length $path) {
return $path;
}
my $canonpath = $self->_canon_cat([$path]);
return $canonpath;
}
method splitpath : string[] ($path : string, $nofile : int = 0) {
my $volume = "";
my $dir = "";
my $file = "";
if (my $match = Re->m($path, ["^($VOL_RX_STRING?)(.*)", "s"])) {
$volume = $match->cap1;
my $rest = $match->cap2;
if ($nofile) {
$dir = $rest;
}
else {
if (my $match = Re->m($rest, ["^((?:.*[\\\\/](?:\.\.?\z)?)?)([^\\\\/]*)", "s"])) {
$dir = $match->cap1;
$file = $match->cap2;
}
}
}
return [$volume, $dir, $file];
}
method splitdir : string[] ($path : string) {
my $parts = Re->split("[\\\\/]", $path, -1);
return $parts;
}
method catpath : string ($volume : string, $dir : string, $file : string) {
unless ($volume) {
$volume = "";
}
unless ($dir) {
$dir = "";
}
unless ($file) {
$file = "";
}
my $path = $volume;
# If it's UNC, make sure the glue separator is there, reusing
# whatever separator is first in the $volume
my $v = (string)undef;
if (my $match = Re->m($path, ["^([\\\\/])[\\\\/][^\\\\/]+[\\\\/][^\\\\/]+\z", "s"])) {
my $v = $match->cap1;
if (Re->m($dir, "^[^\\\\/]")) {
$path .= $v;
}
}
$path .= $dir;
# If the volume is not just A:, make sure the glue separator is
# there, reusing whatever separator is first in the $volume if possible.
if (!Re->m($path, ["^[a-zA-Z]:\z", "s"]) && Re->m($path, "[^\\\\/]\z")
&& Re->m($file, "[^\\\\/]"))
{
my $sep = "\\";
my $match = Re->m($path, "([\\\\/])");
if ($match) {
$sep = $match->cap1;
}
$path .= $sep;
}
$path .= $file;
return $path;
}
method rootdir : string () {
return "\\";
}
method path : string[] () {
my $env_path = Sys->env("PATH");
unless ($env_path) {
return new string[0];
}
my $pathes = Fn->split(";", $env_path);
my $pathes_list = StringList->new;
for (my $i = 0; $i < @$pathes; $i++) {
my $path = $pathes->[$i];
Re->s((mutable string)$path, "\"", "");
if (length $path) {
$pathes_list->push($path);
}
}
$pathes_list->unshift(".");
$pathes = $pathes_list->to_array;
return $pathes;
}
method catdir : string ($parts : string[]) {
unless ($parts) {
die "The \$parts must be defined";
}
unless (@$parts) {
return "";
}
unless (length $parts->[0]) {
$parts->[0] = "/";
}
if (Re->m($parts->[0], "^$DRIVE_RX_STRING\z")) {
$parts->[0] .= "\\";
}
my $dir = $self->_canon_cat($parts);
return $dir;
}
method rel2abs : string ($path : string, $base : string = undef) {
my $is_abs = $self->file_name_is_absolute($path);
my $abs_path = (string)undef;
# Check for volume (should probably document the '2' thing...)
if ($is_abs == 2) {
$abs_path = $self->canonpath($path) ;
}
elsif ($is_abs) {
# It's missing a volume, add one
my $vol_parts = $self->splitpath(Cwd->getcwd);
my $vol = $vol_parts->[0];
$abs_path = $self->canonpath($vol . $path);
}
else {
if (!$base || $base eq "") {
my $drive = $self->splitpath($path)->[0];
if (length $drive) {
eval { $base = Cwd->getdcwd($drive); };
if ($@) {
unless (eval_error_id == basic_type_id Error::System) {
die $@;
}
}
}
unless ($base) {
$base = Cwd->getcwd;
}
}
elsif (!$self->file_name_is_absolute($base)) {
$base = $self->rel2abs($base) ;
}
else {
$base = $self->canonpath($base) ;
}
my $path_parts = $self->splitpath($path, 1);
my $path_dir = $path_parts->[1];
my $path_base_file = $path_parts->[2];
my $base_parts = $self->splitpath($base, 1);
my $base_volume = $base_parts->[0];
my $base_dir = $base_parts->[1];
$path = $self->catpath(
$base_volume,
$self->catdir([$base_dir, $path_dir]),
$path_base_file
);
$abs_path = $self->canonpath($path);
}
return $abs_path;
}
private method _canon_cat : string ($parts : string[]) {
my $first = copy $parts->[0];
my $volume = (string)undef;
# drive letter - (C:)
my $match = Re->m($first, "\A([A-Za-z]:)([\\\\/]?)");
Re->s((mutable string)$first, "\A([A-Za-z]:)([\\\\/]?)", "");
if ($match) {
$volume = Fn->ucfirst($match->cap1);
if (length $match->cap2) {
$volume .= "\\";
}
}
else {
my $replace_info = Re->s((mutable string)$first, ["\A(?:\\\\\\\\|//)([^\\\\/]+)(?:[\\\\/]([^\\\\/]+))?[\\\\/]?", "s"], "");
# UNC volume (\\192.168.201.101)
if ($replace_info) {
my $match = $replace_info->match;
$volume = "\\\\" . $match->cap1;
if ($match->cap2) {
$volume .= "\\" . $match->cap2;
}
$volume .= "\\";
}
else {
my $replace_info = Re->s((mutable string)$first, "\A[\\\\/]", "");
# root dir (\foo)
if ($replace_info) {
$volume = "\\";
}
else {
$volume = "";
}
}
}
$parts->[0] = $first;
my $path = Fn->join("\\", $parts);
# /+ to \
Re->s((mutable string)$path, ["/+", "g"], "\\");
# \+ to \
Re->s((mutable string)$path, ["\\\\+", "g"], "\\");
# xx\.\.\yy --> xx\yy
Re->s((mutable string)$path, ["(?:(?:\A|\\\\)\.(?:\\\\\.)*(?:\\\\|\z))+", "g"], "\\");
# xx\yy\..\zz --> xx\zz
my $parts_list = StringList->new;
$parts = Fn->split("\\", $path);
for (my $i = 0; $i < @$parts; $i++) {
my $part = $parts->[$i];
if ($part eq "..") {
if ($parts_list->length > 0) {
my $before_part = $parts_list->get($parts_list->length - 1);
unless ($before_part eq "..") {
$parts_list->pop;
next;
}
}
}
$parts_list->push($part);
}
$parts = $parts_list->to_array;
$path = Fn->join("\\", $parts);
# \xx --> xx
Re->s((mutable string)$path, ["\A\\\\", "g"], "");
# xx\ --> xx
Re->s((mutable string)$path, ["\\\\\z", "g"], "");
if (Re->m($volume, "\\\\\z")) {
# <vol>\.. --> <vol>\
Re->s((mutable string)$path, "\A\.\.(?:\\\\\.\.)*(?:\\\\|\z)", "");
# \\HOST\SHARE\ --> \\HOST\SHARE
my $match = Re->m($volume, ["\A(\\\\\\\\.*)\\\\\z", "s"]);
if (!length $path && $match) {
$path = $match->cap1;
return $path;
}
}
if (length $path || length $volume) {
$path = $volume . $path;
}
else {
$path = ".";
}
return $path;
}
}