First release of SPVM::File::Spec - complex regular expressions, file tests, SPVM::Cwd, inheritance
Tags: perl, spvm
Published: 2023-02-03 22:40:08


First release of <a href="https://metacpan.org/pod/SPVM::File::Spec">SPVM::File::Spec</a>, a port of Perl's <a href="https://metacpan.org/pod/File::Spec">File::Spec</a> to SPVM. The repository is <a href="https://github.com/yuki-kimoto/SPVM-File-Spec/commits/master">SPVM::File::Spec</a>.

## What is File::Spec?

File::Spec is a Perl module to handle file paths in an OS-independent manner, allowing Linux, Mac, and Windows to have similar descriptions of file paths.

## File::Spec implementation difficulties.

### Numerous regular expressions

One of the difficulties of implementing File::Spec is that it uses many regular expressions to handle file paths.

Therefore, in porting File::Spec to SPVM, it was necessary to be able to use regular expressions.

I ported <a href="https://github.com/google/re2">Google RE2</a>, a regular expression library, to SPVM as <a href="https://metacpan.org/pod/SPVM::Resource::Re2::V2022_06_01">Resource::Re2</a>, and created <a href="https://metacpan.org/pod/SPVM::Regex">SPVM::Regex</a>, a wrapper for it.

This allows regular expressions to be used.

See some of the complex regular expressions in SPVM::File::Spec. The handling of file paths, especially on Windows, is complex. This is part of the implementation of a method called canonnpath in SPVM::File::Spec::Win32 to make a path a unique expression.


````
private method _canon_cat : string ($parts : string[]) {
    my $first = $parts->[0];
    
    my $volume = (string)undef;
    
    # drive letter - (C:)
    my $re_drive = Regex->new("\A([A-Za-z]:)([\\\\/]?)") ;
    $first = $re_drive->replace($first, "");
    if ($re_drive->replaced_count > 0) {
      $volume = Fn->ucfirst($re_drive->cap1);
      if (length $re_drive->cap2) {
        $volume . = "\\";
      }
    }
    else {
      my $re_unc = Regex->new("\A(? :\\\\\\\\|//)([^\\\\/]+)(? :[\\\\/]([^\\\\/]+))? [\\\\/]?" , "s");
      $first = $re_unc->replace($first, "");
      
      # UNC volume (\192.168.201.101)
      if ($re_unc->replaced_count > 0) {
        $volume = "\\\\" . $re_unc->cap1;
        if ($re_unc->cap2) {
          $volume . = "\\" . $re_unc->cap2;
        }
        $volume . = "\\" ;
      }
      else {
        my $re_root = Regex->new("\A[\\\\/]");
        $first = $re_root->replace($first, "");
        
        # root dir (\foo)
        if ($re_root->replaced_count > 0) {
          $volume = "\foo";
        }
        else {
          $volume = "";
        }
      }
    }
    
    $parts->[0] = $first;
    
    my $path = Fn->join("\", $parts);
    
    # /+ to \
    $path = Regex->new("/+")->replace_g($path, "\");
    
    # /+ to \
    $path = Regex->new("\\\\+")->replace_g($path, "\\");
    
    # xx\. \. \yy --> xx\yy.
    $path = Regex->new("(? :(? :\A|\\\\)\. (? :\\\\\.) *(? :\\\\|\z))+")->replace_g($path, "\");
    
    # xx\yy. \zz --> xx\z.
    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
    $path = Regex->new("\A\\\\")->replace_g($path, "");
    
    # xx --> xx
    $path = Regex->new("\\\\\z")->replace_g($path, "");
    
    if (Regex->new("\\\\\z")->match($volume)) {
      # <vol>\. --> <vol>\.
      $path = Regex->new("\A\. \. (? :\\\\\. \.)\.) *(? :\\\\|\z)")->replace($path, "");
      
      # \HOSTHCOSTHCARE --> \HOSTHCARE
      my $re = Regex->new("\A(\\\\\\\\. *)\\\\\z", "s");
      if (!length $path && $re->match($volume)) {
        $path = $re->cap1;
        return $path;
      }
    }
    
    if (length $path || length $volume) {
      $path = $volume . $path;
    }
    else {
      $path = "." ;
    }
    
    return $path;
  }
```

### File Test Operators

File::Spec checks whether a temporary directory is writable in order to locate it. For this reason, file tests must be implemented.

I have implemented the <a href="https://metacpan.org/pod/SPVM::Sys::FileTest">SPVM::Sys::FileTest</a> module for file testing.

````
  method _tmpdir : string ($dirlist : string[]) {
    my $interface = (File::Spec::Interface)$self;
    
    my $tmpdir = (string)undef;
    for my $dir (@$dirlist) {
      if ($dir && Sys::FileTest->d($dir) && Sys::FileTest->w($dir)){
        $tmpdir = $dir;
        last;
      }
    }
    
    unless ($tmpdir) {
      $tmpdir = $interface->curdir;
    }
    
    $tmpdir = $interface->canonpath($tmpdir);
    
    if (! $interface->file_name_is_absolute($tmpdir)) {
      $tmpdir = $interface->rel2abs($tmpdir);
      if (! $interface->file_name_is_absolute($tmpdir)) {
        die "A temporary directory must be converted to an absolute path";
      }
    }
    
    return $tmpdir;
  }
```

## Current directory

File::Spec needs to be able to handle the current directory for absolute and relative path conversion.

For this purpose I have implemented <a href="https://metacpan.org/pod/SPVM::Cwd">SPVM::Cwd</a>.

The following is an implementation of the rel2abs method that converts a relative path to an absolute path.


````
  method rel2abs : string ($path : string, $base = undef : string) {
    my $interface = (File::Spec::Interface)$self;
    
    # Clean up $path
    if (! $interface->file_name_is_absolute($path)) {
      # Figure out the effective $base and clean it up.
      If (! $base || $base eq "") {
        $base = Cwd->getcwd;
      }
      elsif (! $interface->file_name_is_absolute($base)) {
        $base = $interface->rel2abs($base) ;
      }
      else {
        $base = $interface->canonpath($base);
      }
      # Glom them together
      $path = $interface->catdir([$base, $path]);
    }
    
    my $rel2abs = $interface->canonpath($path);
    
    return $rel2abs;
  }
```

## Inheritance

Since File::Spec is implemented using inheritance, SPVM also supports inheritance. There are subtle differences between statically and dynamically typed languages in terms of expressive feasibility, and SPVM is designed to be as close as possible to Perl's inheritance.

````
class File::Spec::Unix extends File::Spec {
  
}
```

## Immediate Goals

In February 2023, our goal is to port the modules that handle file paths to SPVM.

Porting of File::Copy, FindBin, File::Path, File::Find, File::Temp, etc. to SPVM will be initiated.



Translated with www.DeepL.com/Translator (free version)