DEV Community

Yuki Kimoto - SPVM Author
Yuki Kimoto - SPVM Author

Posted on

First release of SPVM::File::Spec - complex regular expressions, file tests, SPVM::Cwd, inheritance

First release of SPVM::File::Spec, a port of Perl's File::Spec to SPVM. The repository is SPVM::File::Spec.

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 Google RE2, a regular expression library, to SPVM as Resource::Re2, and created SPVM::Regex, 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.



Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

}




## 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.




Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

}




## 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.



Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Top comments (0)