<commit_before>my class IO::Path is Cool does IO::FileTestable {
    has IO::Spec $.SPEC;
    has Str $.CWD;
    has Str $.path;
    has Bool $.is-absolute;
    has Str $!abspath;  # should be native for faster file tests, but segfaults
    has %!parts;

    multi method ACCEPTS(IO::Path:D: IO::Path:D \other) {
        nqp::p6bool(nqp::iseq_s($!path, nqp::unbox_s(other.path)));
    }

    multi method ACCEPTS(IO::Path:D: Mu \that) {
        nqp::p6bool(nqp::iseq_s($!path,nqp::unbox_s(IO::Path.new(|that).path)));
    }

    submethod BUILD(:$!path! as Str, :$!SPEC!, :$!CWD! as Str) { }

    multi method new(IO::Path: $path, :$SPEC = $*SPEC, :$CWD = $*CWD) {
        self.bless(:$path, :$SPEC, :$CWD);
    }
    multi method new(IO::Path:
      :$basename!,
      :$dirname = '.',
      :$volume  = '',
      :$SPEC    = $*SPEC,
      :$CWD     = $*CWD,
    ) {
        self.bless(:path($SPEC.join($volume,$dirname,$basename)),:$SPEC,:$CWD);
    }
    multi method new(IO::Path:
      :$basename,
      :$directory!,
      :$volume = '',
      :$SPEC   = $*SPEC,
      :$CWD    = $*CWD,
    ) {
#        DEPRECATED(':dirname', :what<IO::Path.new with :directory>); # after 2014.10
        self.bless(
          :path($SPEC.join($volume,$directory,$basename)), :$SPEC, :$CWD);
    }

    method abspath() {
        $!abspath //= $!path.substr(0,1) eq '-'
          ?? ''
          !! $!SPEC.rel2abs($!path,$!CWD);
    }
    method is-absolute() {
        $!is-absolute //= $!SPEC.is-absolute($!path);
    }
    method is-relative() {
        !( $!is-absolute //= $!SPEC.is-absolute($!path) );
    }

    method parts                  {
        %!parts ||= $!SPEC.split($!path);
    }
    method volume(IO::Path:D:)   { %.parts<volume>   }
    method dirname(IO::Path:D:)  { %.parts<dirname>  }
    method basename(IO::Path:D:) { %.parts<basename> }

    # core can't do 'basename handles <Numeric Bridge Int>'
    method Numeric(IO::Path:D:) { self.basename.Numeric }
    method Bridge (IO::Path:D:) { self.basename.Bridge  }
    method Int    (IO::Path:D:) { self.basename.Int     }

    multi method Str (IO::Path:D:) { $!path }
    multi method gist(IO::Path:D:) {
        "q|$.abspath|.IO";
    }
    multi method perl(IO::Path:D:) {
        ($.is-absolute
          ?? "q|$.abspath|.IO(:SPEC({$!SPEC.^name}))"
          !! "q|$.path|.IO(:SPEC({$!SPEC.^name}),:CWD<$!CWD>)"
        ).subst(:global, '\\', '\\\\');
    }

    method succ(IO::Path:D:) {
        self.bless(
          :path($!SPEC.join($.volume,$.dirname,$.basename.succ)),
          :$!SPEC,
          :$!CWD,
        );
    }
    method pred(IO::Path:D:) {
        self.bless(
          :path($!SPEC.join($.volume,$.dirname,$.basename.pred)),
          :$!SPEC,
          :$!CWD,
        );
    }

    method IO(IO::Path:D: |c) {
        $?CLASS.new($!path, :$!SPEC, :$!CWD, |c);
    }

    method open(IO::Path:D: |c) {
        my $handle = IO::Handle.new(:path($.abspath));
        $handle && $handle.open(|c);
    }

#?if moar
    method watch(IO::Path:D:) {
        IO::Notification.watch_path($.abspath);
    }
#?endif

    proto method absolute(|) { * }
    multi method absolute (IO::Path:D:) { $.abspath }
    multi method absolute (IO::Path:D: $CWD) {
        self.is-absolute
          ?? $.abspath
          !! $!SPEC.rel2abs($!path, $CWD);
    }

    method relative (IO::Path:D: $CWD = $*CWD) {
        $!SPEC.abs2rel($.abspath, $CWD);
    }

    method cleanup (IO::Path:D:) {
        self.bless(:path($!SPEC.canonpath($!path)), :$!SPEC, :$!CWD);
    }
    method resolve (IO::Path:D:) {
        # NYI: requires readlink()
        X::NYI.new(feature=>'IO::Path.resolve').fail;
    }

    method parent(IO::Path:D:) {    # XXX needs work
        my $curdir := $!SPEC.curdir;
        my $updir  := $!SPEC.updir;

        if self.is-absolute {
            return self.bless(
              :path($!SPEC.join($.volume, $.dirname, '')),
              :$!SPEC,
              :$!CWD,
            );
        }
        elsif $.dirname eq $curdir and $.basename eq $curdir {
            return self.bless(
              :path($!SPEC.join($.volume,$curdir,$updir)),
              :$!SPEC,
              :$!CWD,
            );
        }
        elsif $.dirname eq $curdir && $.basename eq $updir
           or !grep({$_ ne $updir}, $!SPEC.splitdir($.dirname)) {
            return self.bless(    # If all updirs, then add one more
              :path($!SPEC.join($.volume,$!SPEC.catdir($.dirname,$updir),$.basename)),
              :$!SPEC,
              :$!CWD,
            );
        }
        else {
            return self.bless(
              :path($!SPEC.join($.volume, $.dirname, '')),
              :$!SPEC,
              :$!CWD,
            );
        }
    }

    method child (IO::Path:D: $child) {
        self.bless(:path($!SPEC.catfile($!path,$child)), :$!SPEC, :$!CWD);
    }

    proto method rename(|) { * }
    multi method rename(IO::Path:D: IO::Path:D $to, :$createonly) {
        if $createonly and $to.e {
            fail X::IO::Rename.new(
              :from(nqp::box_s($!path,Str)),
              :$to,
              :os-error(':createonly specified and destination exists'),
            );
        }
        nqp::rename($!path, nqp::unbox_s($to.path));
        CATCH { default {
            fail X::IO::Rename.new(
              :from(nqp::box_s($!path,Str)), :$to, :os-error(.Str) );
        } }
        True;
    }
    multi method rename(IO::Path:D: $to, :$CWD = $*CWD, |c) {
        self.rename($to.IO(:$!SPEC,:$CWD),|c);
    }

    proto method copy(|) { * }
    multi method copy(IO::Path:D: IO::Path:D $to, :$createonly) {
        if $createonly and $to.e {
            fail X::IO::Copy.new(
              :from(nqp::box_s($!path,Str)),
              :$to,
              :os-error(':createonly specified and destination exists'),
            );
        }
        nqp::copy($!path, nqp::unbox_s($to.path));
        CATCH { default {
            fail X::IO::Copy.new(
              :from(nqp::box_s($!path,Str)), :$to, :os-error(.Str) );<commit_msg>Fix copy() breakage when specifying relative paths<commit_after>my class IO::Path is Cool does IO::FileTestable {
    has IO::Spec $.SPEC;
    has Str $.CWD;
    has Str $.path;
    has Bool $.is-absolute;
    has Str $!abspath;  # should be native for faster file tests, but segfaults
    has %!parts;

    multi method ACCEPTS(IO::Path:D: IO::Path:D \other) {
        nqp::p6bool(nqp::iseq_s($!path, nqp::unbox_s(other.path)));
    }

    multi method ACCEPTS(IO::Path:D: Mu \that) {
        nqp::p6bool(nqp::iseq_s($!path,nqp::unbox_s(IO::Path.new(|that).path)));
    }

    submethod BUILD(:$!path! as Str, :$!SPEC!, :$!CWD! as Str) { }

    multi method new(IO::Path: $path, :$SPEC = $*SPEC, :$CWD = $*CWD) {
        self.bless(:$path, :$SPEC, :$CWD);
    }
    multi method new(IO::Path:
      :$basename!,
      :$dirname = '.',
      :$volume  = '',
      :$SPEC    = $*SPEC,
      :$CWD     = $*CWD,
    ) {
        self.bless(:path($SPEC.join($volume,$dirname,$basename)),:$SPEC,:$CWD);
    }
    multi method new(IO::Path:
      :$basename,
      :$directory!,
      :$volume = '',
      :$SPEC   = $*SPEC,
      :$CWD    = $*CWD,
    ) {
#        DEPRECATED(':dirname', :what<IO::Path.new with :directory>); # after 2014.10
        self.bless(
          :path($SPEC.join($volume,$directory,$basename)), :$SPEC, :$CWD);
    }

    method abspath() {
        $!abspath //= $!path.substr(0,1) eq '-'
          ?? ''
          !! $!SPEC.rel2abs($!path,$!CWD);
    }
    method is-absolute() {
        $!is-absolute //= $!SPEC.is-absolute($!path);
    }
    method is-relative() {
        !( $!is-absolute //= $!SPEC.is-absolute($!path) );
    }

    method parts                  {
        %!parts ||= $!SPEC.split($!path);
    }
    method volume(IO::Path:D:)   { %.parts<volume>   }
    method dirname(IO::Path:D:)  { %.parts<dirname>  }
    method basename(IO::Path:D:) { %.parts<basename> }

    # core can't do 'basename handles <Numeric Bridge Int>'
    method Numeric(IO::Path:D:) { self.basename.Numeric }
    method Bridge (IO::Path:D:) { self.basename.Bridge  }
    method Int    (IO::Path:D:) { self.basename.Int     }

    multi method Str (IO::Path:D:) { $!path }
    multi method gist(IO::Path:D:) {
        "q|$.abspath|.IO";
    }
    multi method perl(IO::Path:D:) {
        ($.is-absolute
          ?? "q|$.abspath|.IO(:SPEC({$!SPEC.^name}))"
          !! "q|$.path|.IO(:SPEC({$!SPEC.^name}),:CWD<$!CWD>)"
        ).subst(:global, '\\', '\\\\');
    }

    method succ(IO::Path:D:) {
        self.bless(
          :path($!SPEC.join($.volume,$.dirname,$.basename.succ)),
          :$!SPEC,
          :$!CWD,
        );
    }
    method pred(IO::Path:D:) {
        self.bless(
          :path($!SPEC.join($.volume,$.dirname,$.basename.pred)),
          :$!SPEC,
          :$!CWD,
        );
    }

    method IO(IO::Path:D: |c) {
        $?CLASS.new($!path, :$!SPEC, :$!CWD, |c);
    }

    method open(IO::Path:D: |c) {
        my $handle = IO::Handle.new(:path($.abspath));
        $handle && $handle.open(|c);
    }

#?if moar
    method watch(IO::Path:D:) {
        IO::Notification.watch_path($.abspath);
    }
#?endif

    proto method absolute(|) { * }
    multi method absolute (IO::Path:D:) { $.abspath }
    multi method absolute (IO::Path:D: $CWD) {
        self.is-absolute
          ?? $.abspath
          !! $!SPEC.rel2abs($!path, $CWD);
    }

    method relative (IO::Path:D: $CWD = $*CWD) {
        $!SPEC.abs2rel($.abspath, $CWD);
    }

    method cleanup (IO::Path:D:) {
        self.bless(:path($!SPEC.canonpath($!path)), :$!SPEC, :$!CWD);
    }
    method resolve (IO::Path:D:) {
        # NYI: requires readlink()
        X::NYI.new(feature=>'IO::Path.resolve').fail;
    }

    method parent(IO::Path:D:) {    # XXX needs work
        my $curdir := $!SPEC.curdir;
        my $updir  := $!SPEC.updir;

        if self.is-absolute {
            return self.bless(
              :path($!SPEC.join($.volume, $.dirname, '')),
              :$!SPEC,
              :$!CWD,
            );
        }
        elsif $.dirname eq $curdir and $.basename eq $curdir {
            return self.bless(
              :path($!SPEC.join($.volume,$curdir,$updir)),
              :$!SPEC,
              :$!CWD,
            );
        }
        elsif $.dirname eq $curdir && $.basename eq $updir
           or !grep({$_ ne $updir}, $!SPEC.splitdir($.dirname)) {
            return self.bless(    # If all updirs, then add one more
              :path($!SPEC.join($.volume,$!SPEC.catdir($.dirname,$updir),$.basename)),
              :$!SPEC,
              :$!CWD,
            );
        }
        else {
            return self.bless(
              :path($!SPEC.join($.volume, $.dirname, '')),
              :$!SPEC,
              :$!CWD,
            );
        }
    }

    method child (IO::Path:D: $child) {
        self.bless(:path($!SPEC.catfile($!path,$child)), :$!SPEC, :$!CWD);
    }

    proto method rename(|) { * }
    multi method rename(IO::Path:D: IO::Path:D $to, :$createonly) {
        if $createonly and $to.e {
            fail X::IO::Rename.new(
              :from(nqp::box_s($!path,Str)),
              :$to,
              :os-error(':createonly specified and destination exists'),
            );
        }
        nqp::rename($!path, nqp::unbox_s($to.path));
        CATCH { default {
            fail X::IO::Rename.new(
              :from(nqp::box_s($!path,Str)), :$to, :os-error(.Str) );
        } }
        True;
    }
    multi method rename(IO::Path:D: $to, :$CWD = $*CWD, |c) {
        self.rename($to.IO(:$!SPEC,:$CWD),|c);
    }

    proto method copy(|) { * }
    multi method copy(IO::Path:D: IO::Path:D $to, :$createonly) {
        if $createonly and $to.e {
            fail X::IO::Copy.new(
              :from(self),
              :$to,
              :os-error(':createonly specified and destination exists'),
            );
        }
        nqp::copy($.abspath, nqp::unbox_s($to.abspath));
        CATCH { default {
            fail X::IO::Copy.new(
              :from(self), :$to, :os-error(.Str) );
{'fraction_non_alphanumeric': 0.20757363253856942, 'fraction_numerical': 0.0021450375381569177, 'mean_word_length': 1.9652641878669277, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4184635', 'n_tokens_mistral': 4578, 'n_tokens_neox': 4322, 'n_words': 744}