| Filename | /Users/ether/.perlbrew/libs/36.0@std/lib/perl5/Path/Tiny.pm |
| Statements | Executed 961 statements in 17.3ms |
| Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
|---|---|---|---|---|---|
| 14 | 1 | 1 | 4.43ms | 4.43ms | Path::Tiny::CORE:read (opcode) |
| 14 | 1 | 1 | 1.76ms | 1.76ms | Path::Tiny::CORE:open (opcode) |
| 14 | 1 | 1 | 1.41ms | 8.48ms | Path::Tiny::slurp |
| 14 | 1 | 1 | 528µs | 2.48ms | Path::Tiny::filehandle |
| 14 | 1 | 1 | 262µs | 325µs | Path::Tiny::_pathify |
| 14 | 1 | 1 | 165µs | 165µs | Path::Tiny::CORE:flock (opcode) |
| 28 | 2 | 1 | 148µs | 148µs | Path::Tiny::_get_args |
| 14 | 2 | 2 | 128µs | 142µs | Path::Tiny::path |
| 2 | 2 | 1 | 111µs | 111µs | Path::Tiny::CORE:regcomp (opcode) |
| 14 | 2 | 2 | 55µs | 55µs | Path::Tiny::slurp_raw |
| 28 | 2 | 1 | 38µs | 38µs | Path::Tiny::_is_root |
| 1 | 1 | 1 | 37µs | 89µs | Path::Tiny::BEGIN@29 |
| 14 | 1 | 1 | 32µs | 32µs | Path::Tiny::CORE:ftsize (opcode) |
| 1 | 1 | 1 | 26µs | 26µs | JSON::Schema::Modern::BEGIN@1 |
| 1 | 1 | 1 | 17µs | 28µs | Path::Tiny::BEGIN@1613 |
| 1 | 1 | 1 | 15µs | 22µs | Path::Tiny::BEGIN@13 |
| 1 | 1 | 1 | 15µs | 202µs | Path::Tiny::BEGIN@19 |
| 1 | 1 | 1 | 15µs | 53µs | flock::BEGIN@131 |
| 1 | 1 | 1 | 14µs | 27µs | Path::Tiny::BEGIN@11 |
| 1 | 1 | 1 | 14µs | 30µs | Path::Tiny::BEGIN@12 |
| 14 | 1 | 1 | 14µs | 14µs | Path::Tiny::CORE:match (opcode) |
| 1 | 1 | 1 | 11µs | 11µs | Path::Tiny::BEGIN@93 |
| 1 | 1 | 1 | 11µs | 43µs | Path::Tiny::Error::BEGIN@2415 |
| 14 | 1 | 1 | 10µs | 10µs | Path::Tiny::CORE:subst (opcode) |
| 1 | 1 | 1 | 7µs | 35µs | Path::Tiny::BEGIN@37 |
| 1 | 1 | 1 | 6µs | 9µs | JSON::Schema::Modern::BEGIN@2 |
| 6 | 6 | 1 | 6µs | 6µs | Path::Tiny::CORE:qr (opcode) |
| 1 | 1 | 1 | 4µs | 66µs | JSON::Schema::Modern::BEGIN@3 |
| 1 | 1 | 1 | 4µs | 4µs | Path::Tiny::BEGIN@14 |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::Error::__ANON__[:2415] |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::Error::throw |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::THAW |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::__ANON__[:1348] |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::__ANON__[:1623] |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::__ANON__[:1629] |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::__ANON__[:1642] |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::__ANON__[:1737] |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::__ANON__[:94] |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::_ceil |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::_check_PU |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::_check_UU |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::_formats |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::_human_size |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::_just_filepath |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::_non_empty |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::_parse_file_temp_args |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::_path |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::_replacment_path |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::_resolve_between |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::_resolve_symlinks |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::_splitpath |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::_symbolic_chmod |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::_throw |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::_win32_vol |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::absolute |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::append |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::append_raw |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::append_utf8 |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::assert |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::basename |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::cached_temp |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::canonpath |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::child |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::children |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::chmod |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::copy |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::cwd |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::digest |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::dirname |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::edit |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::edit_lines |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::edit_lines_raw |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::edit_lines_utf8 |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::edit_raw |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::edit_utf8 |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::exists |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::has_same_bytes |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::is_absolute |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::is_dir |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::is_file |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::is_relative |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::is_rootdir |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::iterator |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::lines |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::lines_raw |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::lines_utf8 |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::lstat |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::mkdir |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::mkpath |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::move |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::new |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::parent |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::realpath |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::relative |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::remove |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::remove_tree |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::rootdir |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::sibling |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::size |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::size_human |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::slurp_utf8 |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::spew |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::spew_raw |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::spew_utf8 |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::stat |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::stringify |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::subsumes |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::tempdir |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::tempfile |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::touch |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::touchpath |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::visit |
| 0 | 0 | 0 | 0s | 0s | Path::Tiny::volume |
| Line | State ments |
Time on line |
Calls | Time in subs |
Code |
|---|---|---|---|---|---|
| 1 | 2 | 37µs | 1 | 26µs | # spent 26µs within JSON::Schema::Modern::BEGIN@1 which was called:
# once (26µs+0s) by JSON::Schema::Modern::BEGIN@25 at line 1 # spent 26µs making 1 call to JSON::Schema::Modern::BEGIN@1 |
| 2 | 2 | 22µs | 2 | 12µs | # spent 9µs (6+3) within JSON::Schema::Modern::BEGIN@2 which was called:
# once (6µs+3µs) by JSON::Schema::Modern::BEGIN@25 at line 2 # spent 9µs making 1 call to JSON::Schema::Modern::BEGIN@2
# spent 3µs making 1 call to strict::import |
| 3 | 2 | 51µs | 2 | 128µs | # spent 66µs (4+62) within JSON::Schema::Modern::BEGIN@3 which was called:
# once (4µs+62µs) by JSON::Schema::Modern::BEGIN@25 at line 3 # spent 66µs making 1 call to JSON::Schema::Modern::BEGIN@3
# spent 62µs making 1 call to warnings::import |
| 4 | |||||
| 5 | package Path::Tiny; | ||||
| 6 | # ABSTRACT: File path utility | ||||
| 7 | |||||
| 8 | 1 | 2µs | our $VERSION = '0.144'; | ||
| 9 | |||||
| 10 | # Dependencies | ||||
| 11 | 2 | 55µs | 2 | 40µs | # spent 27µs (14+13) within Path::Tiny::BEGIN@11 which was called:
# once (14µs+13µs) by JSON::Schema::Modern::BEGIN@25 at line 11 # spent 27µs making 1 call to Path::Tiny::BEGIN@11
# spent 13µs making 1 call to Config::import |
| 12 | 3 | 61µs | 3 | 46µs | # spent 30µs (14+16) within Path::Tiny::BEGIN@12 which was called:
# once (14µs+16µs) by JSON::Schema::Modern::BEGIN@25 at line 12 # spent 30µs making 1 call to Path::Tiny::BEGIN@12
# spent 9µs making 1 call to UNIVERSAL::VERSION
# spent 7µs making 1 call to Exporter::import |
| 13 | 3 | 41µs | 2 | 29µs | # spent 22µs (15+7) within Path::Tiny::BEGIN@13 which was called:
# once (15µs+7µs) by JSON::Schema::Modern::BEGIN@25 at line 13 # spent 22µs making 1 call to Path::Tiny::BEGIN@13
# spent 7µs making 1 call to UNIVERSAL::VERSION |
| 14 | 2 | 123µs | 1 | 4µs | # spent 4µs within Path::Tiny::BEGIN@14 which was called:
# once (4µs+0s) by JSON::Schema::Modern::BEGIN@25 at line 14 # spent 4µs making 1 call to Path::Tiny::BEGIN@14 |
| 15 | |||||
| 16 | 1 | 1µs | our @EXPORT = qw/path/; | ||
| 17 | 1 | 1µs | our @EXPORT_OK = qw/cwd rootdir tempfile tempdir/; | ||
| 18 | |||||
| 19 | # spent 202µs (15+187) within Path::Tiny::BEGIN@19 which was called:
# once (15µs+187µs) by JSON::Schema::Modern::BEGIN@25 at line 27 | ||||
| 20 | 1 | 1µs | PATH => 0, | ||
| 21 | CANON => 1, | ||||
| 22 | VOL => 2, | ||||
| 23 | DIR => 3, | ||||
| 24 | FILE => 4, | ||||
| 25 | TEMP => 5, | ||||
| 26 | IS_WIN32 => ( $^O eq 'MSWin32' ), | ||||
| 27 | 1 | 97µs | 2 | 389µs | }; # spent 202µs making 1 call to Path::Tiny::BEGIN@19
# spent 187µs making 1 call to constant::import |
| 28 | |||||
| 29 | # spent 89µs (37+52) within Path::Tiny::BEGIN@29 which was called:
# once (37µs+52µs) by JSON::Schema::Modern::BEGIN@25 at line 33 | ||||
| 30 | q{""} => 'stringify', | ||||
| 31 | bool => sub () { 1 }, | ||||
| 32 | 1 | 1µs | fallback => 1, | ||
| 33 | 1 | 102µs | 2 | 141µs | ); # spent 89µs making 1 call to Path::Tiny::BEGIN@29
# spent 52µs making 1 call to overload::import |
| 34 | |||||
| 35 | # FREEZE/THAW per Sereal/CBOR/Types::Serialiser protocol | ||||
| 36 | sub THAW { return path( $_[2] ) } | ||||
| 37 | 4 | 432µs | 2 | 63µs | # spent 35µs (7+28) within Path::Tiny::BEGIN@37 which was called:
# once (7µs+28µs) by JSON::Schema::Modern::BEGIN@25 at line 37 # spent 35µs making 1 call to Path::Tiny::BEGIN@37
# spent 28µs making 1 call to warnings::unimport |
| 38 | |||||
| 39 | 1 | 0s | my $HAS_UU; # has Unicode::UTF8; lazily populated | ||
| 40 | |||||
| 41 | sub _check_UU { | ||||
| 42 | local $SIG{__DIE__}; # prevent outer handler from being called | ||||
| 43 | !!eval { | ||||
| 44 | require Unicode::UTF8; | ||||
| 45 | Unicode::UTF8->VERSION(0.58); | ||||
| 46 | 1; | ||||
| 47 | }; | ||||
| 48 | } | ||||
| 49 | |||||
| 50 | my $HAS_PU; # has PerlIO::utf8_strict; lazily populated | ||||
| 51 | |||||
| 52 | sub _check_PU { | ||||
| 53 | local $SIG{__DIE__}; # prevent outer handler from being called | ||||
| 54 | !!eval { | ||||
| 55 | # MUST preload Encode or $SIG{__DIE__} localization fails | ||||
| 56 | # on some Perl 5.8.8 (maybe other 5.8.*) compiled with -O2. | ||||
| 57 | require Encode; | ||||
| 58 | require PerlIO::utf8_strict; | ||||
| 59 | PerlIO::utf8_strict->VERSION(0.003); | ||||
| 60 | 1; | ||||
| 61 | }; | ||||
| 62 | } | ||||
| 63 | |||||
| 64 | 1 | 8µs | 1 | 2.32ms | my $HAS_FLOCK = $Config{d_flock} || $Config{d_fcntl_can_lock} || $Config{d_lockf}; # spent 2.32ms making 1 call to Config::FETCH |
| 65 | |||||
| 66 | # notions of "root" directories differ on Win32: \\server\dir\ or C:\ or \ | ||||
| 67 | 1 | 6µs | 1 | 2µs | my $SLASH = qr{[\\/]}; # spent 2µs making 1 call to Path::Tiny::CORE:qr |
| 68 | 1 | 2µs | 1 | 1µs | my $NOTSLASH = qr{[^\\/]}; # spent 1µs making 1 call to Path::Tiny::CORE:qr |
| 69 | 1 | 2µs | 1 | 0s | my $DRV_VOL = qr{[a-z]:}i; # spent 0s making 1 call to Path::Tiny::CORE:qr |
| 70 | 1 | 48µs | 2 | 43µs | my $UNC_VOL = qr{$SLASH $SLASH $NOTSLASH+ $SLASH $NOTSLASH+}x; # spent 42µs making 1 call to Path::Tiny::CORE:regcomp
# spent 1µs making 1 call to Path::Tiny::CORE:qr |
| 71 | 1 | 75µs | 2 | 70µs | my $WIN32_ROOT = qr{(?: $UNC_VOL $SLASH | $DRV_VOL $SLASH | $SLASH )}x; # spent 69µs making 1 call to Path::Tiny::CORE:regcomp
# spent 1µs making 1 call to Path::Tiny::CORE:qr |
| 72 | |||||
| 73 | sub _win32_vol { | ||||
| 74 | my ( $path, $drv ) = @_; | ||||
| 75 | require Cwd; | ||||
| 76 | my $dcwd = eval { Cwd::getdcwd($drv) }; # C: -> C:\some\cwd | ||||
| 77 | # getdcwd on non-existent drive returns empty string | ||||
| 78 | # so just use the original drive Z: -> Z: | ||||
| 79 | $dcwd = "$drv" unless defined $dcwd && length $dcwd; | ||||
| 80 | # normalize dwcd to end with a slash: might be C:\some\cwd or D:\ or Z: | ||||
| 81 | $dcwd =~ s{$SLASH?\z}{/}; | ||||
| 82 | # make the path absolute with dcwd | ||||
| 83 | $path =~ s{^$DRV_VOL}{$dcwd}; | ||||
| 84 | return $path; | ||||
| 85 | } | ||||
| 86 | |||||
| 87 | # This is a string test for before we have the object; see is_rootdir for well-formed | ||||
| 88 | # object test | ||||
| 89 | sub _is_root { | ||||
| 90 | 28 | 76µs | return IS_WIN32() ? ( $_[0] =~ /^$WIN32_ROOT\z/ ) : ( $_[0] eq '/' ); | ||
| 91 | } | ||||
| 92 | |||||
| 93 | # spent 11µs within Path::Tiny::BEGIN@93 which was called:
# once (11µs+0s) by JSON::Schema::Modern::BEGIN@25 at line 95 | ||||
| 94 | 1 | 8µs | *_same = IS_WIN32() ? sub { lc( $_[0] ) eq lc( $_[1] ) } : sub { $_[0] eq $_[1] }; | ||
| 95 | 1 | 364µs | 1 | 11µs | } # spent 11µs making 1 call to Path::Tiny::BEGIN@93 |
| 96 | |||||
| 97 | # mode bits encoded for chmod in symbolic mode | ||||
| 98 | 1 | 3µs | my %MODEBITS = ( om => 0007, gm => 0070, um => 0700 ); ## no critic | ||
| 99 | 3 | 8µs | { my $m = 0; $MODEBITS{$_} = ( 1 << $m++ ) for qw/ox ow or gx gw gr ux uw ur/ }; | ||
| 100 | |||||
| 101 | sub _symbolic_chmod { | ||||
| 102 | my ( $mode, $symbolic ) = @_; | ||||
| 103 | for my $clause ( split /,\s*/, $symbolic ) { | ||||
| 104 | if ( $clause =~ m{\A([augo]+)([=+-])([rwx]+)\z} ) { | ||||
| 105 | my ( $who, $action, $perms ) = ( $1, $2, $3 ); | ||||
| 106 | $who =~ s/a/ugo/g; | ||||
| 107 | for my $w ( split //, $who ) { | ||||
| 108 | my $p = 0; | ||||
| 109 | $p |= $MODEBITS{"$w$_"} for split //, $perms; | ||||
| 110 | if ( $action eq '=' ) { | ||||
| 111 | $mode = ( $mode & ~$MODEBITS{"${w}m"} ) | $p; | ||||
| 112 | } | ||||
| 113 | else { | ||||
| 114 | $mode = $action eq "+" ? ( $mode | $p ) : ( $mode & ~$p ); | ||||
| 115 | } | ||||
| 116 | } | ||||
| 117 | } | ||||
| 118 | else { | ||||
| 119 | Carp::croak("Invalid mode clause '$clause' for chmod()"); | ||||
| 120 | } | ||||
| 121 | } | ||||
| 122 | return $mode; | ||||
| 123 | } | ||||
| 124 | |||||
| 125 | # flock doesn't work on NFS on BSD or on some filesystems like lustre. | ||||
| 126 | # Since program authors often can't control or detect that, we warn once | ||||
| 127 | # instead of being fatal if we can detect it and people who need it strict | ||||
| 128 | # can fatalize the 'flock' category | ||||
| 129 | |||||
| 130 | #<<< No perltidy | ||||
| 131 | 2 | 4.48ms | 2 | 91µs | # spent 53µs (15+38) within flock::BEGIN@131 which was called:
# once (15µs+38µs) by JSON::Schema::Modern::BEGIN@25 at line 131 # spent 53µs making 1 call to flock::BEGIN@131
# spent 38µs making 1 call to warnings::register::import |
| 132 | #>>> | ||||
| 133 | |||||
| 134 | 2 | 1µs | my $WARNED_NO_FLOCK = 0; | ||
| 135 | |||||
| 136 | sub _throw { | ||||
| 137 | my ( $self, $function, $file, $msg ) = @_; | ||||
| 138 | if ( $function =~ /^flock/ | ||||
| 139 | && $! =~ /operation not supported|function not implemented/i | ||||
| 140 | && !warnings::fatal_enabled('flock') ) | ||||
| 141 | { | ||||
| 142 | if ( !$WARNED_NO_FLOCK ) { | ||||
| 143 | warnings::warn( flock => "Flock not available: '$!': continuing in unsafe mode" ); | ||||
| 144 | $WARNED_NO_FLOCK++; | ||||
| 145 | } | ||||
| 146 | } | ||||
| 147 | else { | ||||
| 148 | $msg = $! unless defined $msg; | ||||
| 149 | Path::Tiny::Error->throw( $function, ( defined $file ? $file : $self->[PATH] ), | ||||
| 150 | $msg ); | ||||
| 151 | } | ||||
| 152 | return; | ||||
| 153 | } | ||||
| 154 | |||||
| 155 | # cheapo option validation | ||||
| 156 | sub _get_args { | ||||
| 157 | 28 | 19µs | my ( $raw, @valid ) = @_; | ||
| 158 | 28 | 16µs | if ( defined($raw) && ref($raw) ne 'HASH' ) { | ||
| 159 | my ( undef, undef, undef, $called_as ) = caller(1); | ||||
| 160 | $called_as =~ s{^.*::}{}; | ||||
| 161 | Carp::croak("Options for $called_as must be a hash reference"); | ||||
| 162 | } | ||||
| 163 | 28 | 10µs | my $cooked = {}; | ||
| 164 | 28 | 24µs | for my $k (@valid) { | ||
| 165 | 42 | 39µs | $cooked->{$k} = delete $raw->{$k} if exists $raw->{$k}; | ||
| 166 | } | ||||
| 167 | 28 | 9µs | if ( keys %$raw ) { | ||
| 168 | my ( undef, undef, undef, $called_as ) = caller(1); | ||||
| 169 | $called_as =~ s{^.*::}{}; | ||||
| 170 | Carp::croak( "Invalid option(s) for $called_as: " . join( ", ", keys %$raw ) ); | ||||
| 171 | } | ||||
| 172 | 28 | 52µs | return $cooked; | ||
| 173 | } | ||||
| 174 | |||||
| 175 | #--------------------------------------------------------------------------# | ||||
| 176 | # Constructors | ||||
| 177 | #--------------------------------------------------------------------------# | ||||
| 178 | |||||
| 179 | #pod =construct path | ||||
| 180 | #pod | ||||
| 181 | #pod $path = path("foo/bar"); | ||||
| 182 | #pod $path = path("/tmp", "file.txt"); # list | ||||
| 183 | #pod $path = path("."); # cwd | ||||
| 184 | #pod | ||||
| 185 | #pod Constructs a C<Path::Tiny> object. It doesn't matter if you give a file or | ||||
| 186 | #pod directory path. It's still up to you to call directory-like methods only on | ||||
| 187 | #pod directories and file-like methods only on files. This function is exported | ||||
| 188 | #pod automatically by default. | ||||
| 189 | #pod | ||||
| 190 | #pod The first argument must be defined and have non-zero length or an exception | ||||
| 191 | #pod will be thrown. This prevents subtle, dangerous errors with code like | ||||
| 192 | #pod C<< path( maybe_undef() )->remove_tree >>. | ||||
| 193 | #pod | ||||
| 194 | #pod B<DEPRECATED>: If and only if the B<first> character of the B<first> argument | ||||
| 195 | #pod to C<path> is a tilde ('~'), then tilde replacement will be applied to the | ||||
| 196 | #pod first path segment. A single tilde will be replaced with C<glob('~')> and a | ||||
| 197 | #pod tilde followed by a username will be replaced with output of | ||||
| 198 | #pod C<glob('~username')>. B<No other method does tilde expansion on its arguments>. | ||||
| 199 | #pod See L</Tilde expansion (deprecated)> for more. | ||||
| 200 | #pod | ||||
| 201 | #pod On Windows, if the path consists of a drive identifier without a path component | ||||
| 202 | #pod (C<C:> or C<D:>), it will be expanded to the absolute path of the current | ||||
| 203 | #pod directory on that volume using C<Cwd::getdcwd()>. | ||||
| 204 | #pod | ||||
| 205 | #pod If called with a single C<Path::Tiny> argument, the original is returned unless | ||||
| 206 | #pod the original is holding a temporary file or directory reference in which case a | ||||
| 207 | #pod stringified copy is made. | ||||
| 208 | #pod | ||||
| 209 | #pod $path = path("foo/bar"); | ||||
| 210 | #pod $temp = Path::Tiny->tempfile; | ||||
| 211 | #pod | ||||
| 212 | #pod $p2 = path($path); # like $p2 = $path | ||||
| 213 | #pod $t2 = path($temp); # like $t2 = path( "$temp" ) | ||||
| 214 | #pod | ||||
| 215 | #pod This optimizes copies without proliferating references unexpectedly if a copy is | ||||
| 216 | #pod made by code outside your control. | ||||
| 217 | #pod | ||||
| 218 | #pod Current API available since 0.017. | ||||
| 219 | #pod | ||||
| 220 | #pod =cut | ||||
| 221 | |||||
| 222 | # spent 142µs (128+14) within Path::Tiny::path which was called 14 times, avg 10µs/call:
# 8 times (75µs+8µs) by JSON::Schema::Modern::_get_or_load_resource at line 811 of JSON/Schema/Modern.pm, avg 10µs/call
# 6 times (53µs+6µs) by JSON::Schema::Modern::Document::OpenAPI::_add_vocab_and_default_schemas at line 204 of JSON/Schema/Modern/Document/OpenAPI.pm, avg 10µs/call | ||||
| 223 | 14 | 4µs | my $path = shift; | ||
| 224 | Carp::croak("Path::Tiny paths require defined, positive-length parts") | ||||
| 225 | 14 | 33µs | unless 1 + @_ == grep { defined && length } $path, @_; | ||
| 226 | |||||
| 227 | # non-temp Path::Tiny objects are effectively immutable and can be reused | ||||
| 228 | 14 | 1µs | if ( !@_ && ref($path) eq __PACKAGE__ && !$path->[TEMP] ) { | ||
| 229 | return $path; | ||||
| 230 | } | ||||
| 231 | |||||
| 232 | # stringify objects | ||||
| 233 | 14 | 2µs | $path = "$path"; | ||
| 234 | |||||
| 235 | # do any tilde expansions | ||||
| 236 | 14 | 45µs | 14 | 14µs | my ($tilde) = $path =~ m{^(~[^/]*)}; # spent 14µs making 14 calls to Path::Tiny::CORE:match, avg 1µs/call |
| 237 | 14 | 1µs | if ( defined $tilde ) { | ||
| 238 | # Escape File::Glob metacharacters | ||||
| 239 | (my $escaped = $tilde) =~ s/([\[\{\*\?\\])/\\$1/g; | ||||
| 240 | require File::Glob; | ||||
| 241 | my ($homedir) = File::Glob::bsd_glob($escaped); | ||||
| 242 | if (defined $homedir && ! $File::Glob::ERROR) { | ||||
| 243 | $homedir =~ tr[\\][/] if IS_WIN32(); | ||||
| 244 | $path =~ s{^\Q$tilde\E}{$homedir}; | ||||
| 245 | } | ||||
| 246 | } | ||||
| 247 | |||||
| 248 | 14 | 13µs | unshift @_, $path; | ||
| 249 | 14 | 64µs | 14 | 325µs | goto &_pathify; # spent 325µs making 14 calls to Path::Tiny::_pathify, avg 23µs/call |
| 250 | } | ||||
| 251 | |||||
| 252 | # _path is like path but without tilde expansion | ||||
| 253 | sub _path { | ||||
| 254 | my $path = shift; | ||||
| 255 | Carp::croak("Path::Tiny paths require defined, positive-length parts") | ||||
| 256 | unless 1 + @_ == grep { defined && length } $path, @_; | ||||
| 257 | |||||
| 258 | # non-temp Path::Tiny objects are effectively immutable and can be reused | ||||
| 259 | if ( !@_ && ref($path) eq __PACKAGE__ && !$path->[TEMP] ) { | ||||
| 260 | return $path; | ||||
| 261 | } | ||||
| 262 | |||||
| 263 | # stringify objects | ||||
| 264 | $path = "$path"; | ||||
| 265 | |||||
| 266 | unshift @_, $path; | ||||
| 267 | goto &_pathify; | ||||
| 268 | } | ||||
| 269 | |||||
| 270 | # _pathify expects one or more string arguments, then joins and canonicalizes | ||||
| 271 | # them into an object. | ||||
| 272 | # spent 325µs (262+63) within Path::Tiny::_pathify which was called 14 times, avg 23µs/call:
# 14 times (262µs+63µs) by JSON::Schema::Modern::Document::OpenAPI::_add_vocab_and_default_schemas or JSON::Schema::Modern::_get_or_load_resource at line 249, avg 23µs/call | ||||
| 273 | 14 | 7µs | my $path = shift; | ||
| 274 | |||||
| 275 | # expand relative volume paths on windows; put trailing slash on UNC root | ||||
| 276 | if ( IS_WIN32() ) { | ||||
| 277 | $path = _win32_vol( $path, $1 ) if $path =~ m{^($DRV_VOL)(?:$NOTSLASH|\z)}; | ||||
| 278 | $path .= "/" if $path =~ m{^$UNC_VOL\z}; | ||||
| 279 | } | ||||
| 280 | |||||
| 281 | # concatenations stringifies objects, too | ||||
| 282 | 14 | 50µs | 14 | 34µs | if (@_) { # spent 34µs making 14 calls to Path::Tiny::_is_root, avg 2µs/call |
| 283 | $path .= ( _is_root($path) ? "" : "/" ) . join( "/", @_ ); | ||||
| 284 | } | ||||
| 285 | |||||
| 286 | |||||
| 287 | # canonicalize, but with unix slashes and put back trailing volume slash | ||||
| 288 | 14 | 47µs | 14 | 15µs | my $cpath = $path = File::Spec->canonpath($path); # spent 15µs making 14 calls to File::Spec::Unix::canonpath, avg 1µs/call |
| 289 | $path =~ tr[\\][/] if IS_WIN32(); | ||||
| 290 | 14 | 6µs | $path = "/" if $path eq '/..'; # for old File::Spec | ||
| 291 | $path .= "/" if IS_WIN32() && $path =~ m{^$UNC_VOL\z}; | ||||
| 292 | |||||
| 293 | # root paths must always have a trailing slash, but other paths must not | ||||
| 294 | 14 | 18µs | 14 | 4µs | if ( _is_root($path) ) { # spent 4µs making 14 calls to Path::Tiny::_is_root, avg 286ns/call |
| 295 | $path =~ s{/?\z}{/}; | ||||
| 296 | } | ||||
| 297 | else { | ||||
| 298 | 14 | 29µs | 14 | 10µs | $path =~ s{/\z}{}; # spent 10µs making 14 calls to Path::Tiny::CORE:subst, avg 714ns/call |
| 299 | } | ||||
| 300 | |||||
| 301 | 14 | 104µs | bless [ $path, $cpath ], __PACKAGE__; | ||
| 302 | } | ||||
| 303 | |||||
| 304 | #pod =construct new | ||||
| 305 | #pod | ||||
| 306 | #pod $path = Path::Tiny->new("foo/bar"); | ||||
| 307 | #pod | ||||
| 308 | #pod This is just like C<path>, but with method call overhead. (Why would you | ||||
| 309 | #pod do that?) | ||||
| 310 | #pod | ||||
| 311 | #pod Current API available since 0.001. | ||||
| 312 | #pod | ||||
| 313 | #pod =cut | ||||
| 314 | |||||
| 315 | sub new { shift; path(@_) } | ||||
| 316 | |||||
| 317 | #pod =construct cwd | ||||
| 318 | #pod | ||||
| 319 | #pod $path = Path::Tiny->cwd; # path( Cwd::getcwd ) | ||||
| 320 | #pod $path = cwd; # optional export | ||||
| 321 | #pod | ||||
| 322 | #pod Gives you the absolute path to the current directory as a C<Path::Tiny> object. | ||||
| 323 | #pod This is slightly faster than C<< path(".")->absolute >>. | ||||
| 324 | #pod | ||||
| 325 | #pod C<cwd> may be exported on request and used as a function instead of as a | ||||
| 326 | #pod method. | ||||
| 327 | #pod | ||||
| 328 | #pod Current API available since 0.018. | ||||
| 329 | #pod | ||||
| 330 | #pod =cut | ||||
| 331 | |||||
| 332 | sub cwd { | ||||
| 333 | require Cwd; | ||||
| 334 | return _path( Cwd::getcwd() ); | ||||
| 335 | } | ||||
| 336 | |||||
| 337 | #pod =construct rootdir | ||||
| 338 | #pod | ||||
| 339 | #pod $path = Path::Tiny->rootdir; # / | ||||
| 340 | #pod $path = rootdir; # optional export | ||||
| 341 | #pod | ||||
| 342 | #pod Gives you C<< File::Spec->rootdir >> as a C<Path::Tiny> object if you're too | ||||
| 343 | #pod picky for C<path("/")>. | ||||
| 344 | #pod | ||||
| 345 | #pod C<rootdir> may be exported on request and used as a function instead of as a | ||||
| 346 | #pod method. | ||||
| 347 | #pod | ||||
| 348 | #pod Current API available since 0.018. | ||||
| 349 | #pod | ||||
| 350 | #pod =cut | ||||
| 351 | |||||
| 352 | sub rootdir { _path( File::Spec->rootdir ) } | ||||
| 353 | |||||
| 354 | #pod =construct tempfile, tempdir | ||||
| 355 | #pod | ||||
| 356 | #pod $temp = Path::Tiny->tempfile( @options ); | ||||
| 357 | #pod $temp = Path::Tiny->tempdir( @options ); | ||||
| 358 | #pod $temp = $dirpath->tempfile( @options ); | ||||
| 359 | #pod $temp = $dirpath->tempdir( @options ); | ||||
| 360 | #pod $temp = tempfile( @options ); # optional export | ||||
| 361 | #pod $temp = tempdir( @options ); # optional export | ||||
| 362 | #pod | ||||
| 363 | #pod C<tempfile> passes the options to C<< File::Temp->new >> and returns a | ||||
| 364 | #pod C<Path::Tiny> object with the file name. The C<TMPDIR> option will be enabled | ||||
| 365 | #pod by default, but you can override that by passing C<< TMPDIR => 0 >> along with | ||||
| 366 | #pod the options. (If you use an absolute C<TEMPLATE> option, you will want to | ||||
| 367 | #pod disable C<TMPDIR>.) | ||||
| 368 | #pod | ||||
| 369 | #pod The resulting C<File::Temp> object is cached. When the C<Path::Tiny> object is | ||||
| 370 | #pod destroyed, the C<File::Temp> object will be as well. | ||||
| 371 | #pod | ||||
| 372 | #pod C<File::Temp> annoyingly requires you to specify a custom template in slightly | ||||
| 373 | #pod different ways depending on which function or method you call, but | ||||
| 374 | #pod C<Path::Tiny> lets you ignore that and can take either a leading template or a | ||||
| 375 | #pod C<TEMPLATE> option and does the right thing. | ||||
| 376 | #pod | ||||
| 377 | #pod $temp = Path::Tiny->tempfile( "customXXXXXXXX" ); # ok | ||||
| 378 | #pod $temp = Path::Tiny->tempfile( TEMPLATE => "customXXXXXXXX" ); # ok | ||||
| 379 | #pod | ||||
| 380 | #pod The tempfile path object will be normalized to have an absolute path, even if | ||||
| 381 | #pod created in a relative directory using C<DIR>. If you want it to have | ||||
| 382 | #pod the C<realpath> instead, pass a leading options hash like this: | ||||
| 383 | #pod | ||||
| 384 | #pod $real_temp = tempfile({realpath => 1}, @options); | ||||
| 385 | #pod | ||||
| 386 | #pod C<tempdir> is just like C<tempfile>, except it calls | ||||
| 387 | #pod C<< File::Temp->newdir >> instead. | ||||
| 388 | #pod | ||||
| 389 | #pod Both C<tempfile> and C<tempdir> may be exported on request and used as | ||||
| 390 | #pod functions instead of as methods. | ||||
| 391 | #pod | ||||
| 392 | #pod The methods can be called on an instances representing a | ||||
| 393 | #pod directory. In this case, the directory is used as the base to create the | ||||
| 394 | #pod temporary file/directory, setting the C<DIR> option in File::Temp. | ||||
| 395 | #pod | ||||
| 396 | #pod my $target_dir = path('/to/destination'); | ||||
| 397 | #pod my $tempfile = $target_dir->tempfile('foobarXXXXXX'); | ||||
| 398 | #pod $tempfile->spew('A lot of data...'); # not atomic | ||||
| 399 | #pod $tempfile->move($target_dir->child('foobar')); # hopefully atomic | ||||
| 400 | #pod | ||||
| 401 | #pod In this case, any value set for option C<DIR> is ignored. | ||||
| 402 | #pod | ||||
| 403 | #pod B<Note>: for tempfiles, the filehandles from File::Temp are closed and not | ||||
| 404 | #pod reused. This is not as secure as using File::Temp handles directly, but is | ||||
| 405 | #pod less prone to deadlocks or access problems on some platforms. Think of what | ||||
| 406 | #pod C<Path::Tiny> gives you to be just a temporary file B<name> that gets cleaned | ||||
| 407 | #pod up. | ||||
| 408 | #pod | ||||
| 409 | #pod B<Note 2>: if you don't want these cleaned up automatically when the object | ||||
| 410 | #pod is destroyed, File::Temp requires different options for directories and | ||||
| 411 | #pod files. Use C<< CLEANUP => 0 >> for directories and C<< UNLINK => 0 >> for | ||||
| 412 | #pod files. | ||||
| 413 | #pod | ||||
| 414 | #pod B<Note 3>: Don't lose the temporary object by chaining a method call instead | ||||
| 415 | #pod of storing it: | ||||
| 416 | #pod | ||||
| 417 | #pod my $lost = tempdir()->child("foo"); # tempdir cleaned up right away | ||||
| 418 | #pod | ||||
| 419 | #pod B<Note 4>: The cached object may be accessed with the L</cached_temp> method. | ||||
| 420 | #pod Keeping a reference to, or modifying the cached object may break the | ||||
| 421 | #pod behavior documented above and is not supported. Use at your own risk. | ||||
| 422 | #pod | ||||
| 423 | #pod Current API available since 0.119. | ||||
| 424 | #pod | ||||
| 425 | #pod =cut | ||||
| 426 | |||||
| 427 | sub tempfile { | ||||
| 428 | my ( $opts, $maybe_template, $args ) | ||||
| 429 | = _parse_file_temp_args(tempfile => @_); | ||||
| 430 | |||||
| 431 | # File::Temp->new demands TEMPLATE | ||||
| 432 | $args->{TEMPLATE} = $maybe_template->[0] if @$maybe_template; | ||||
| 433 | |||||
| 434 | require File::Temp; | ||||
| 435 | my $temp = File::Temp->new( TMPDIR => 1, %$args ); | ||||
| 436 | close $temp; | ||||
| 437 | my $self = $opts->{realpath} ? _path($temp)->realpath : _path($temp)->absolute; | ||||
| 438 | $self->[TEMP] = $temp; # keep object alive while we are | ||||
| 439 | return $self; | ||||
| 440 | } | ||||
| 441 | |||||
| 442 | sub tempdir { | ||||
| 443 | my ( $opts, $maybe_template, $args ) | ||||
| 444 | = _parse_file_temp_args(tempdir => @_); | ||||
| 445 | |||||
| 446 | require File::Temp; | ||||
| 447 | my $temp = File::Temp->newdir( @$maybe_template, TMPDIR => 1, %$args ); | ||||
| 448 | my $self = $opts->{realpath} ? _path($temp)->realpath : _path($temp)->absolute; | ||||
| 449 | $self->[TEMP] = $temp; # keep object alive while we are | ||||
| 450 | # Some ActiveState Perls for Windows break Cwd in ways that lead | ||||
| 451 | # File::Temp to get confused about what path to remove; this | ||||
| 452 | # monkey-patches the object with our own view of the absolute path | ||||
| 453 | $temp->{REALNAME} = $self->[CANON] if IS_WIN32; | ||||
| 454 | return $self; | ||||
| 455 | } | ||||
| 456 | |||||
| 457 | # normalize the various ways File::Temp does templates | ||||
| 458 | sub _parse_file_temp_args { | ||||
| 459 | my $called_as = shift; | ||||
| 460 | if ( @_ && $_[0] eq 'Path::Tiny' ) { shift } # class method | ||||
| 461 | elsif ( @_ && eval{$_[0]->isa('Path::Tiny')} ) { | ||||
| 462 | my $dir = shift; | ||||
| 463 | if (! $dir->is_dir) { | ||||
| 464 | $dir->_throw( $called_as, $dir, "is not a directory object" ); | ||||
| 465 | } | ||||
| 466 | push @_, DIR => $dir->stringify; # no overriding | ||||
| 467 | } | ||||
| 468 | my $opts = ( @_ && ref $_[0] eq 'HASH' ) ? shift @_ : {}; | ||||
| 469 | $opts = _get_args( $opts, qw/realpath/ ); | ||||
| 470 | |||||
| 471 | my $leading_template = ( scalar(@_) % 2 == 1 ? shift(@_) : '' ); | ||||
| 472 | my %args = @_; | ||||
| 473 | %args = map { uc($_), $args{$_} } keys %args; | ||||
| 474 | my @template = ( | ||||
| 475 | exists $args{TEMPLATE} ? delete $args{TEMPLATE} | ||||
| 476 | : $leading_template ? $leading_template | ||||
| 477 | : () | ||||
| 478 | ); | ||||
| 479 | |||||
| 480 | return ( $opts, \@template, \%args ); | ||||
| 481 | } | ||||
| 482 | |||||
| 483 | #--------------------------------------------------------------------------# | ||||
| 484 | # Private methods | ||||
| 485 | #--------------------------------------------------------------------------# | ||||
| 486 | |||||
| 487 | sub _splitpath { | ||||
| 488 | my ($self) = @_; | ||||
| 489 | @{$self}[ VOL, DIR, FILE ] = File::Spec->splitpath( $self->[PATH] ); | ||||
| 490 | } | ||||
| 491 | |||||
| 492 | sub _resolve_symlinks { | ||||
| 493 | my ($self) = @_; | ||||
| 494 | my $new = $self; | ||||
| 495 | my ( $count, %seen ) = 0; | ||||
| 496 | while ( -l $new->[PATH] ) { | ||||
| 497 | if ( $seen{ $new->[PATH] }++ ) { | ||||
| 498 | $self->_throw( 'readlink', $self->[PATH], "symlink loop detected" ); | ||||
| 499 | } | ||||
| 500 | if ( ++$count > 100 ) { | ||||
| 501 | $self->_throw( 'readlink', $self->[PATH], "maximum symlink depth exceeded" ); | ||||
| 502 | } | ||||
| 503 | my $resolved = readlink $new->[PATH]; | ||||
| 504 | $new->_throw( 'readlink', $new->[PATH] ) unless defined $resolved; | ||||
| 505 | $resolved = _path($resolved); | ||||
| 506 | $new = $resolved->is_absolute ? $resolved : $new->sibling($resolved); | ||||
| 507 | } | ||||
| 508 | return $new; | ||||
| 509 | } | ||||
| 510 | |||||
| 511 | sub _replacment_path { | ||||
| 512 | my ($self) = @_; | ||||
| 513 | |||||
| 514 | my $unique_suffix = $$ . int( rand( 2**31 ) ); | ||||
| 515 | my $temp = _path( $self . $unique_suffix ); | ||||
| 516 | |||||
| 517 | # If filename with process+random suffix is too long, use a shorter | ||||
| 518 | # version that doesn't preserve the basename. | ||||
| 519 | if ( length $temp->basename > 255 ) { | ||||
| 520 | $temp = $self->sibling( "temp" . $unique_suffix ); | ||||
| 521 | } | ||||
| 522 | |||||
| 523 | return $temp; | ||||
| 524 | } | ||||
| 525 | |||||
| 526 | #--------------------------------------------------------------------------# | ||||
| 527 | # Public methods | ||||
| 528 | #--------------------------------------------------------------------------# | ||||
| 529 | |||||
| 530 | #pod =method absolute | ||||
| 531 | #pod | ||||
| 532 | #pod $abs = path("foo/bar")->absolute; | ||||
| 533 | #pod $abs = path("foo/bar")->absolute("/tmp"); | ||||
| 534 | #pod | ||||
| 535 | #pod Returns a new C<Path::Tiny> object with an absolute path (or itself if already | ||||
| 536 | #pod absolute). If no argument is given, the current directory is used as the | ||||
| 537 | #pod absolute base path. If an argument is given, it will be converted to an | ||||
| 538 | #pod absolute path (if it is not already) and used as the absolute base path. | ||||
| 539 | #pod | ||||
| 540 | #pod This will not resolve upward directories ("foo/../bar") unless C<canonpath> | ||||
| 541 | #pod in L<File::Spec> would normally do so on your platform. If you need them | ||||
| 542 | #pod resolved, you must call the more expensive C<realpath> method instead. | ||||
| 543 | #pod | ||||
| 544 | #pod On Windows, an absolute path without a volume component will have it added | ||||
| 545 | #pod based on the current drive. | ||||
| 546 | #pod | ||||
| 547 | #pod Current API available since 0.101. | ||||
| 548 | #pod | ||||
| 549 | #pod =cut | ||||
| 550 | |||||
| 551 | sub absolute { | ||||
| 552 | my ( $self, $base ) = @_; | ||||
| 553 | |||||
| 554 | # absolute paths handled differently by OS | ||||
| 555 | if (IS_WIN32) { | ||||
| 556 | return $self if length $self->volume; | ||||
| 557 | # add missing volume | ||||
| 558 | if ( $self->is_absolute ) { | ||||
| 559 | require Cwd; | ||||
| 560 | # use Win32::GetCwd not Cwd::getdcwd because we're sure | ||||
| 561 | # to have the former but not necessarily the latter | ||||
| 562 | my ($drv) = Win32::GetCwd() =~ /^($DRV_VOL | $UNC_VOL)/x; | ||||
| 563 | return _path( $drv . $self->[PATH] ); | ||||
| 564 | } | ||||
| 565 | } | ||||
| 566 | else { | ||||
| 567 | return $self if $self->is_absolute; | ||||
| 568 | } | ||||
| 569 | |||||
| 570 | # no base means use current directory as base | ||||
| 571 | require Cwd; | ||||
| 572 | return _path( Cwd::getcwd(), $_[0]->[PATH] ) unless defined $base; | ||||
| 573 | |||||
| 574 | # relative base should be made absolute; we check is_absolute rather | ||||
| 575 | # than unconditionally make base absolute so that "/foo" doesn't become | ||||
| 576 | # "C:/foo" on Windows. | ||||
| 577 | $base = _path($base); | ||||
| 578 | return _path( ( $base->is_absolute ? $base : $base->absolute ), $_[0]->[PATH] ); | ||||
| 579 | } | ||||
| 580 | |||||
| 581 | #pod =method append, append_raw, append_utf8 | ||||
| 582 | #pod | ||||
| 583 | #pod path("foo.txt")->append(@data); | ||||
| 584 | #pod path("foo.txt")->append(\@data); | ||||
| 585 | #pod path("foo.txt")->append({binmode => ":raw"}, @data); | ||||
| 586 | #pod path("foo.txt")->append_raw(@data); | ||||
| 587 | #pod path("foo.txt")->append_utf8(@data); | ||||
| 588 | #pod | ||||
| 589 | #pod Appends data to a file. The file is locked with C<flock> prior to writing | ||||
| 590 | #pod and closed afterwards. An optional hash reference may be used to pass | ||||
| 591 | #pod options. Valid options are: | ||||
| 592 | #pod | ||||
| 593 | #pod =for :list | ||||
| 594 | #pod * C<binmode>: passed to C<binmode()> on the handle used for writing. | ||||
| 595 | #pod * C<truncate>: truncates the file after locking and before appending | ||||
| 596 | #pod | ||||
| 597 | #pod The C<truncate> option is a way to replace the contents of a file | ||||
| 598 | #pod B<in place>, unlike L</spew> which writes to a temporary file and then | ||||
| 599 | #pod replaces the original (if it exists). | ||||
| 600 | #pod | ||||
| 601 | #pod C<append_raw> is like C<append> with a C<binmode> of C<:unix> for a fast, | ||||
| 602 | #pod unbuffered, raw write. | ||||
| 603 | #pod | ||||
| 604 | #pod C<append_utf8> is like C<append> with an unbuffered C<binmode> | ||||
| 605 | #pod C<:unix:encoding(UTF-8)> (or C<:unix:utf8_strict> with | ||||
| 606 | #pod L<PerlIO::utf8_strict>). If L<Unicode::UTF8> 0.58+ is installed, an | ||||
| 607 | #pod unbuffered, raw append will be done instead on the data encoded with | ||||
| 608 | #pod C<Unicode::UTF8>. | ||||
| 609 | #pod | ||||
| 610 | #pod Current API available since 0.060. | ||||
| 611 | #pod | ||||
| 612 | #pod =cut | ||||
| 613 | |||||
| 614 | sub append { | ||||
| 615 | my ( $self, @data ) = @_; | ||||
| 616 | my $args = ( @data && ref $data[0] eq 'HASH' ) ? shift @data : {}; | ||||
| 617 | $args = _get_args( $args, qw/binmode truncate/ ); | ||||
| 618 | my $binmode = $args->{binmode}; | ||||
| 619 | $binmode = ( ( caller(0) )[10] || {} )->{'open>'} unless defined $binmode; | ||||
| 620 | my $mode = $args->{truncate} ? ">" : ">>"; | ||||
| 621 | my $fh = $self->filehandle( { locked => 1 }, $mode, $binmode ); | ||||
| 622 | print( {$fh} map { ref eq 'ARRAY' ? @$_ : $_ } @data ) or self->_throw('print'); | ||||
| 623 | close $fh or $self->_throw('close'); | ||||
| 624 | } | ||||
| 625 | |||||
| 626 | sub append_raw { | ||||
| 627 | my ( $self, @data ) = @_; | ||||
| 628 | my $args = ( @data && ref $data[0] eq 'HASH' ) ? shift @data : {}; | ||||
| 629 | $args = _get_args( $args, qw/binmode truncate/ ); | ||||
| 630 | $args->{binmode} = ':unix'; | ||||
| 631 | append( $self, $args, @data ); | ||||
| 632 | } | ||||
| 633 | |||||
| 634 | sub append_utf8 { | ||||
| 635 | my ( $self, @data ) = @_; | ||||
| 636 | my $args = ( @data && ref $data[0] eq 'HASH' ) ? shift @data : {}; | ||||
| 637 | $args = _get_args( $args, qw/binmode truncate/ ); | ||||
| 638 | if ( defined($HAS_UU) ? $HAS_UU : ( $HAS_UU = _check_UU() ) ) { | ||||
| 639 | $args->{binmode} = ":unix"; | ||||
| 640 | append( $self, $args, map { Unicode::UTF8::encode_utf8($_) } @data ); | ||||
| 641 | } | ||||
| 642 | elsif ( defined($HAS_PU) ? $HAS_PU : ( $HAS_PU = _check_PU() ) ) { | ||||
| 643 | $args->{binmode} = ":unix:utf8_strict"; | ||||
| 644 | append( $self, $args, @data ); | ||||
| 645 | } | ||||
| 646 | else { | ||||
| 647 | $args->{binmode} = ":unix:encoding(UTF-8)"; | ||||
| 648 | append( $self, $args, @data ); | ||||
| 649 | } | ||||
| 650 | } | ||||
| 651 | |||||
| 652 | #pod =method assert | ||||
| 653 | #pod | ||||
| 654 | #pod $path = path("foo.txt")->assert( sub { $_->exists } ); | ||||
| 655 | #pod | ||||
| 656 | #pod Returns the invocant after asserting that a code reference argument returns | ||||
| 657 | #pod true. When the assertion code reference runs, it will have the invocant | ||||
| 658 | #pod object in the C<$_> variable. If it returns false, an exception will be | ||||
| 659 | #pod thrown. The assertion code reference may also throw its own exception. | ||||
| 660 | #pod | ||||
| 661 | #pod If no assertion is provided, the invocant is returned without error. | ||||
| 662 | #pod | ||||
| 663 | #pod Current API available since 0.062. | ||||
| 664 | #pod | ||||
| 665 | #pod =cut | ||||
| 666 | |||||
| 667 | sub assert { | ||||
| 668 | my ( $self, $assertion ) = @_; | ||||
| 669 | return $self unless $assertion; | ||||
| 670 | if ( ref $assertion eq 'CODE' ) { | ||||
| 671 | local $_ = $self; | ||||
| 672 | $assertion->() | ||||
| 673 | or Path::Tiny::Error->throw( "assert", $self->[PATH], "failed assertion" ); | ||||
| 674 | } | ||||
| 675 | else { | ||||
| 676 | Carp::croak("argument to assert must be a code reference argument"); | ||||
| 677 | } | ||||
| 678 | return $self; | ||||
| 679 | } | ||||
| 680 | |||||
| 681 | #pod =method basename | ||||
| 682 | #pod | ||||
| 683 | #pod $name = path("foo/bar.txt")->basename; # bar.txt | ||||
| 684 | #pod $name = path("foo.txt")->basename('.txt'); # foo | ||||
| 685 | #pod $name = path("foo.txt")->basename(qr/.txt/); # foo | ||||
| 686 | #pod $name = path("foo.txt")->basename(@suffixes); | ||||
| 687 | #pod | ||||
| 688 | #pod Returns the file portion or last directory portion of a path. | ||||
| 689 | #pod | ||||
| 690 | #pod Given a list of suffixes as strings or regular expressions, any that match at | ||||
| 691 | #pod the end of the file portion or last directory portion will be removed before | ||||
| 692 | #pod the result is returned. | ||||
| 693 | #pod | ||||
| 694 | #pod Current API available since 0.054. | ||||
| 695 | #pod | ||||
| 696 | #pod =cut | ||||
| 697 | |||||
| 698 | sub basename { | ||||
| 699 | my ( $self, @suffixes ) = @_; | ||||
| 700 | $self->_splitpath unless defined $self->[FILE]; | ||||
| 701 | my $file = $self->[FILE]; | ||||
| 702 | for my $s (@suffixes) { | ||||
| 703 | my $re = ref($s) eq 'Regexp' ? qr/$s\z/ : qr/\Q$s\E\z/; | ||||
| 704 | last if $file =~ s/$re//; | ||||
| 705 | } | ||||
| 706 | return $file; | ||||
| 707 | } | ||||
| 708 | |||||
| 709 | #pod =method canonpath | ||||
| 710 | #pod | ||||
| 711 | #pod $canonical = path("foo/bar")->canonpath; # foo\bar on Windows | ||||
| 712 | #pod | ||||
| 713 | #pod Returns a string with the canonical format of the path name for | ||||
| 714 | #pod the platform. In particular, this means directory separators | ||||
| 715 | #pod will be C<\> on Windows. | ||||
| 716 | #pod | ||||
| 717 | #pod Current API available since 0.001. | ||||
| 718 | #pod | ||||
| 719 | #pod =cut | ||||
| 720 | |||||
| 721 | sub canonpath { $_[0]->[CANON] } | ||||
| 722 | |||||
| 723 | #pod =method cached_temp | ||||
| 724 | #pod | ||||
| 725 | #pod Returns the cached C<File::Temp> or C<File::Temp::Dir> object if the | ||||
| 726 | #pod C<Path::Tiny> object was created with C</tempfile> or C</tempdir>. | ||||
| 727 | #pod If there is no such object, this method throws. | ||||
| 728 | #pod | ||||
| 729 | #pod B<WARNING>: Keeping a reference to, or modifying the cached object may | ||||
| 730 | #pod break the behavior documented for temporary files and directories created | ||||
| 731 | #pod with C<Path::Tiny> and is not supported. Use at your own risk. | ||||
| 732 | #pod | ||||
| 733 | #pod Current API available since 0.101. | ||||
| 734 | #pod | ||||
| 735 | #pod =cut | ||||
| 736 | |||||
| 737 | sub cached_temp { | ||||
| 738 | my $self = shift; | ||||
| 739 | $self->_throw( "cached_temp", $self, "has no cached File::Temp object" ) | ||||
| 740 | unless defined $self->[TEMP]; | ||||
| 741 | return $self->[TEMP]; | ||||
| 742 | } | ||||
| 743 | |||||
| 744 | #pod =method child | ||||
| 745 | #pod | ||||
| 746 | #pod $file = path("/tmp")->child("foo.txt"); # "/tmp/foo.txt" | ||||
| 747 | #pod $file = path("/tmp")->child(@parts); | ||||
| 748 | #pod | ||||
| 749 | #pod Returns a new C<Path::Tiny> object relative to the original. Works | ||||
| 750 | #pod like C<catfile> or C<catdir> from File::Spec, but without caring about | ||||
| 751 | #pod file or directories. | ||||
| 752 | #pod | ||||
| 753 | #pod B<WARNING>: because the argument could contain C<..> or refer to symlinks, | ||||
| 754 | #pod there is no guarantee that the new path refers to an actual descendent of | ||||
| 755 | #pod the original. If this is important to you, transform parent and child with | ||||
| 756 | #pod L</realpath> and check them with L</subsumes>. | ||||
| 757 | #pod | ||||
| 758 | #pod Current API available since 0.001. | ||||
| 759 | #pod | ||||
| 760 | #pod =cut | ||||
| 761 | |||||
| 762 | sub child { | ||||
| 763 | my ( $self, @parts ) = @_; | ||||
| 764 | return _path( $self->[PATH], @parts ); | ||||
| 765 | } | ||||
| 766 | |||||
| 767 | #pod =method children | ||||
| 768 | #pod | ||||
| 769 | #pod @paths = path("/tmp")->children; | ||||
| 770 | #pod @paths = path("/tmp")->children( qr/\.txt\z/ ); | ||||
| 771 | #pod | ||||
| 772 | #pod Returns a list of C<Path::Tiny> objects for all files and directories | ||||
| 773 | #pod within a directory. Excludes "." and ".." automatically. | ||||
| 774 | #pod | ||||
| 775 | #pod If an optional C<qr//> argument is provided, it only returns objects for child | ||||
| 776 | #pod names that match the given regular expression. Only the base name is used | ||||
| 777 | #pod for matching: | ||||
| 778 | #pod | ||||
| 779 | #pod @paths = path("/tmp")->children( qr/^foo/ ); | ||||
| 780 | #pod # matches children like the glob foo* | ||||
| 781 | #pod | ||||
| 782 | #pod Current API available since 0.028. | ||||
| 783 | #pod | ||||
| 784 | #pod =cut | ||||
| 785 | |||||
| 786 | sub children { | ||||
| 787 | my ( $self, $filter ) = @_; | ||||
| 788 | my $dh; | ||||
| 789 | opendir $dh, $self->[PATH] or $self->_throw('opendir'); | ||||
| 790 | my @children = readdir $dh; | ||||
| 791 | closedir $dh or $self->_throw('closedir'); | ||||
| 792 | |||||
| 793 | if ( not defined $filter ) { | ||||
| 794 | @children = grep { $_ ne '.' && $_ ne '..' } @children; | ||||
| 795 | } | ||||
| 796 | elsif ( $filter && ref($filter) eq 'Regexp' ) { | ||||
| 797 | @children = grep { $_ ne '.' && $_ ne '..' && $_ =~ $filter } @children; | ||||
| 798 | } | ||||
| 799 | else { | ||||
| 800 | Carp::croak("Invalid argument '$filter' for children()"); | ||||
| 801 | } | ||||
| 802 | |||||
| 803 | return map { _path( $self->[PATH], $_ ) } @children; | ||||
| 804 | } | ||||
| 805 | |||||
| 806 | #pod =method chmod | ||||
| 807 | #pod | ||||
| 808 | #pod path("foo.txt")->chmod(0777); | ||||
| 809 | #pod path("foo.txt")->chmod("0755"); | ||||
| 810 | #pod path("foo.txt")->chmod("go-w"); | ||||
| 811 | #pod path("foo.txt")->chmod("a=r,u+wx"); | ||||
| 812 | #pod | ||||
| 813 | #pod Sets file or directory permissions. The argument can be a numeric mode, a | ||||
| 814 | #pod octal string beginning with a "0" or a limited subset of the symbolic mode use | ||||
| 815 | #pod by F</bin/chmod>. | ||||
| 816 | #pod | ||||
| 817 | #pod The symbolic mode must be a comma-delimited list of mode clauses. Clauses must | ||||
| 818 | #pod match C<< qr/\A([augo]+)([=+-])([rwx]+)\z/ >>, which defines "who", "op" and | ||||
| 819 | #pod "perms" parameters for each clause. Unlike F</bin/chmod>, all three parameters | ||||
| 820 | #pod are required for each clause, multiple ops are not allowed and permissions | ||||
| 821 | #pod C<stugoX> are not supported. (See L<File::chmod> for more complex needs.) | ||||
| 822 | #pod | ||||
| 823 | #pod Current API available since 0.053. | ||||
| 824 | #pod | ||||
| 825 | #pod =cut | ||||
| 826 | |||||
| 827 | sub chmod { | ||||
| 828 | my ( $self, $new_mode ) = @_; | ||||
| 829 | |||||
| 830 | my $mode; | ||||
| 831 | if ( $new_mode =~ /\d/ ) { | ||||
| 832 | $mode = ( $new_mode =~ /^0/ ? oct($new_mode) : $new_mode ); | ||||
| 833 | } | ||||
| 834 | elsif ( $new_mode =~ /[=+-]/ ) { | ||||
| 835 | $mode = _symbolic_chmod( $self->stat->mode & 07777, $new_mode ); ## no critic | ||||
| 836 | } | ||||
| 837 | else { | ||||
| 838 | Carp::croak("Invalid mode argument '$new_mode' for chmod()"); | ||||
| 839 | } | ||||
| 840 | |||||
| 841 | CORE::chmod( $mode, $self->[PATH] ) or $self->_throw("chmod"); | ||||
| 842 | |||||
| 843 | return 1; | ||||
| 844 | } | ||||
| 845 | |||||
| 846 | #pod =method copy | ||||
| 847 | #pod | ||||
| 848 | #pod path("/tmp/foo.txt")->copy("/tmp/bar.txt"); | ||||
| 849 | #pod | ||||
| 850 | #pod Copies the current path to the given destination using L<File::Copy>'s | ||||
| 851 | #pod C<copy> function. Upon success, returns the C<Path::Tiny> object for the | ||||
| 852 | #pod newly copied file. | ||||
| 853 | #pod | ||||
| 854 | #pod Current API available since 0.070. | ||||
| 855 | #pod | ||||
| 856 | #pod =cut | ||||
| 857 | |||||
| 858 | # XXX do recursively for directories? | ||||
| 859 | sub copy { | ||||
| 860 | my ( $self, $dest ) = @_; | ||||
| 861 | require File::Copy; | ||||
| 862 | File::Copy::copy( $self->[PATH], $dest ) | ||||
| 863 | or Carp::croak("copy failed for $self to $dest: $!"); | ||||
| 864 | |||||
| 865 | return -d $dest ? _path( $dest, $self->basename ) : _path($dest); | ||||
| 866 | } | ||||
| 867 | |||||
| 868 | #pod =method digest | ||||
| 869 | #pod | ||||
| 870 | #pod $obj = path("/tmp/foo.txt")->digest; # SHA-256 | ||||
| 871 | #pod $obj = path("/tmp/foo.txt")->digest("MD5"); # user-selected | ||||
| 872 | #pod $obj = path("/tmp/foo.txt")->digest( { chunk_size => 1e6 }, "MD5" ); | ||||
| 873 | #pod | ||||
| 874 | #pod Returns a hexadecimal digest for a file. An optional hash reference of options may | ||||
| 875 | #pod be given. The only option is C<chunk_size>. If C<chunk_size> is given, that many | ||||
| 876 | #pod bytes will be read at a time. If not provided, the entire file will be slurped | ||||
| 877 | #pod into memory to compute the digest. | ||||
| 878 | #pod | ||||
| 879 | #pod Any subsequent arguments are passed to the constructor for L<Digest> to select | ||||
| 880 | #pod an algorithm. If no arguments are given, the default is SHA-256. | ||||
| 881 | #pod | ||||
| 882 | #pod Current API available since 0.056. | ||||
| 883 | #pod | ||||
| 884 | #pod =cut | ||||
| 885 | |||||
| 886 | sub digest { | ||||
| 887 | my ( $self, @opts ) = @_; | ||||
| 888 | my $args = ( @opts && ref $opts[0] eq 'HASH' ) ? shift @opts : {}; | ||||
| 889 | $args = _get_args( $args, qw/chunk_size/ ); | ||||
| 890 | unshift @opts, 'SHA-256' unless @opts; | ||||
| 891 | require Digest; | ||||
| 892 | my $digest = Digest->new(@opts); | ||||
| 893 | if ( $args->{chunk_size} ) { | ||||
| 894 | my $fh = $self->filehandle( { locked => 1 }, "<", ":unix" ); | ||||
| 895 | my $buf; | ||||
| 896 | while (!eof($fh)) { | ||||
| 897 | my $rc = read $fh, $buf, $args->{chunk_size}; | ||||
| 898 | $self->_throw('read') unless defined $rc; | ||||
| 899 | $digest->add($buf); | ||||
| 900 | } | ||||
| 901 | } | ||||
| 902 | else { | ||||
| 903 | $digest->add( $self->slurp_raw ); | ||||
| 904 | } | ||||
| 905 | return $digest->hexdigest; | ||||
| 906 | } | ||||
| 907 | |||||
| 908 | #pod =method dirname (deprecated) | ||||
| 909 | #pod | ||||
| 910 | #pod $name = path("/tmp/foo.txt")->dirname; # "/tmp/" | ||||
| 911 | #pod | ||||
| 912 | #pod Returns the directory portion you would get from calling | ||||
| 913 | #pod C<< File::Spec->splitpath( $path->stringify ) >> or C<"."> for a path without a | ||||
| 914 | #pod parent directory portion. Because L<File::Spec> is inconsistent, the result | ||||
| 915 | #pod might or might not have a trailing slash. Because of this, this method is | ||||
| 916 | #pod B<deprecated>. | ||||
| 917 | #pod | ||||
| 918 | #pod A better, more consistently approach is likely C<< $path->parent->stringify >>, | ||||
| 919 | #pod which will not have a trailing slash except for a root directory. | ||||
| 920 | #pod | ||||
| 921 | #pod Deprecated in 0.056. | ||||
| 922 | #pod | ||||
| 923 | #pod =cut | ||||
| 924 | |||||
| 925 | sub dirname { | ||||
| 926 | my ($self) = @_; | ||||
| 927 | $self->_splitpath unless defined $self->[DIR]; | ||||
| 928 | return length $self->[DIR] ? $self->[DIR] : "."; | ||||
| 929 | } | ||||
| 930 | |||||
| 931 | #pod =method edit, edit_raw, edit_utf8 | ||||
| 932 | #pod | ||||
| 933 | #pod path("foo.txt")->edit( \&callback, $options ); | ||||
| 934 | #pod path("foo.txt")->edit_utf8( \&callback ); | ||||
| 935 | #pod path("foo.txt")->edit_raw( \&callback ); | ||||
| 936 | #pod | ||||
| 937 | #pod These are convenience methods that allow "editing" a file using a single | ||||
| 938 | #pod callback argument. They slurp the file using C<slurp>, place the contents | ||||
| 939 | #pod inside a localized C<$_> variable, call the callback function (without | ||||
| 940 | #pod arguments), and then write C<$_> (presumably mutated) back to the | ||||
| 941 | #pod file with C<spew>. | ||||
| 942 | #pod | ||||
| 943 | #pod An optional hash reference may be used to pass options. The only option is | ||||
| 944 | #pod C<binmode>, which is passed to C<slurp> and C<spew>. | ||||
| 945 | #pod | ||||
| 946 | #pod C<edit_utf8> and C<edit_raw> act like their respective C<slurp_*> and | ||||
| 947 | #pod C<spew_*> methods. | ||||
| 948 | #pod | ||||
| 949 | #pod Current API available since 0.077. | ||||
| 950 | #pod | ||||
| 951 | #pod =cut | ||||
| 952 | |||||
| 953 | sub edit { | ||||
| 954 | my $self = shift; | ||||
| 955 | my $cb = shift; | ||||
| 956 | my $args = _get_args( shift, qw/binmode/ ); | ||||
| 957 | Carp::croak("Callback for edit() must be a code reference") | ||||
| 958 | unless defined($cb) && ref($cb) eq 'CODE'; | ||||
| 959 | |||||
| 960 | local $_ = | ||||
| 961 | $self->slurp( exists( $args->{binmode} ) ? { binmode => $args->{binmode} } : () ); | ||||
| 962 | $cb->(); | ||||
| 963 | $self->spew( $args, $_ ); | ||||
| 964 | |||||
| 965 | return; | ||||
| 966 | } | ||||
| 967 | |||||
| 968 | # this is done long-hand to benefit from slurp_utf8 optimizations | ||||
| 969 | sub edit_utf8 { | ||||
| 970 | my ( $self, $cb ) = @_; | ||||
| 971 | Carp::croak("Callback for edit_utf8() must be a code reference") | ||||
| 972 | unless defined($cb) && ref($cb) eq 'CODE'; | ||||
| 973 | |||||
| 974 | local $_ = $self->slurp_utf8; | ||||
| 975 | $cb->(); | ||||
| 976 | $self->spew_utf8($_); | ||||
| 977 | |||||
| 978 | return; | ||||
| 979 | } | ||||
| 980 | |||||
| 981 | sub edit_raw { $_[2] = { binmode => ":unix" }; goto &edit } | ||||
| 982 | |||||
| 983 | #pod =method edit_lines, edit_lines_utf8, edit_lines_raw | ||||
| 984 | #pod | ||||
| 985 | #pod path("foo.txt")->edit_lines( \&callback, $options ); | ||||
| 986 | #pod path("foo.txt")->edit_lines_utf8( \&callback ); | ||||
| 987 | #pod path("foo.txt")->edit_lines_raw( \&callback ); | ||||
| 988 | #pod | ||||
| 989 | #pod These are convenience methods that allow "editing" a file's lines using a | ||||
| 990 | #pod single callback argument. They iterate over the file: for each line, the | ||||
| 991 | #pod line is put into a localized C<$_> variable, the callback function is | ||||
| 992 | #pod executed (without arguments) and then C<$_> is written to a temporary file. | ||||
| 993 | #pod When iteration is finished, the temporary file is atomically renamed over | ||||
| 994 | #pod the original. | ||||
| 995 | #pod | ||||
| 996 | #pod An optional hash reference may be used to pass options. The only option is | ||||
| 997 | #pod C<binmode>, which is passed to the method that open handles for reading and | ||||
| 998 | #pod writing. | ||||
| 999 | #pod | ||||
| 1000 | #pod C<edit_lines_raw> is like C<edit_lines> with a buffered C<binmode> of | ||||
| 1001 | #pod C<:raw>. | ||||
| 1002 | #pod | ||||
| 1003 | #pod C<edit_lines_utf8> is like C<edit_lines> with a buffered C<binmode> | ||||
| 1004 | #pod C<:raw:encoding(UTF-8)> (or C<:raw:utf8_strict> with | ||||
| 1005 | #pod L<PerlIO::utf8_strict>). | ||||
| 1006 | #pod | ||||
| 1007 | #pod Current API available since 0.077. | ||||
| 1008 | #pod | ||||
| 1009 | #pod =cut | ||||
| 1010 | |||||
| 1011 | sub edit_lines { | ||||
| 1012 | my $self = shift; | ||||
| 1013 | my $cb = shift; | ||||
| 1014 | my $args = _get_args( shift, qw/binmode/ ); | ||||
| 1015 | Carp::croak("Callback for edit_lines() must be a code reference") | ||||
| 1016 | unless defined($cb) && ref($cb) eq 'CODE'; | ||||
| 1017 | |||||
| 1018 | my $binmode = $args->{binmode}; | ||||
| 1019 | # get default binmode from caller's lexical scope (see "perldoc open") | ||||
| 1020 | $binmode = ( ( caller(0) )[10] || {} )->{'open>'} unless defined $binmode; | ||||
| 1021 | |||||
| 1022 | # writing needs to follow the link and create the tempfile in the same | ||||
| 1023 | # dir for later atomic rename | ||||
| 1024 | my $resolved_path = $self->_resolve_symlinks; | ||||
| 1025 | my $temp = $resolved_path->_replacment_path; | ||||
| 1026 | |||||
| 1027 | my $temp_fh = $temp->filehandle( { exclusive => 1, locked => 1 }, ">", $binmode ); | ||||
| 1028 | my $in_fh = $self->filehandle( { locked => 1 }, '<', $binmode ); | ||||
| 1029 | |||||
| 1030 | local $_; | ||||
| 1031 | while (! eof($in_fh) ) { | ||||
| 1032 | defined( $_ = readline($in_fh) ) or $self->_throw('readline'); | ||||
| 1033 | $cb->(); | ||||
| 1034 | $temp_fh->print($_) or self->_throw('print', $temp); | ||||
| 1035 | } | ||||
| 1036 | |||||
| 1037 | close $temp_fh or $self->_throw( 'close', $temp ); | ||||
| 1038 | close $in_fh or $self->_throw('close'); | ||||
| 1039 | |||||
| 1040 | return $temp->move($resolved_path); | ||||
| 1041 | } | ||||
| 1042 | |||||
| 1043 | sub edit_lines_raw { $_[2] = { binmode => ":raw" }; goto &edit_lines } | ||||
| 1044 | |||||
| 1045 | sub edit_lines_utf8 { | ||||
| 1046 | if ( defined($HAS_PU) ? $HAS_PU : ( $HAS_PU = _check_PU() ) ) { | ||||
| 1047 | $_[2] = { binmode => ":raw:utf8_strict" }; | ||||
| 1048 | } | ||||
| 1049 | else { | ||||
| 1050 | $_[2] = { binmode => ":raw:encoding(UTF-8)" }; | ||||
| 1051 | } | ||||
| 1052 | goto &edit_lines; | ||||
| 1053 | } | ||||
| 1054 | |||||
| 1055 | #pod =method exists, is_file, is_dir | ||||
| 1056 | #pod | ||||
| 1057 | #pod if ( path("/tmp")->exists ) { ... } # -e | ||||
| 1058 | #pod if ( path("/tmp")->is_dir ) { ... } # -d | ||||
| 1059 | #pod if ( path("/tmp")->is_file ) { ... } # -e && ! -d | ||||
| 1060 | #pod | ||||
| 1061 | #pod Implements file test operations, this means the file or directory actually has | ||||
| 1062 | #pod to exist on the filesystem. Until then, it's just a path. | ||||
| 1063 | #pod | ||||
| 1064 | #pod B<Note>: C<is_file> is not C<-f> because C<-f> is not the opposite of C<-d>. | ||||
| 1065 | #pod C<-f> means "plain file", excluding symlinks, devices, etc. that often can be | ||||
| 1066 | #pod read just like files. | ||||
| 1067 | #pod | ||||
| 1068 | #pod Use C<-f> instead if you really mean to check for a plain file. | ||||
| 1069 | #pod | ||||
| 1070 | #pod Current API available since 0.053. | ||||
| 1071 | #pod | ||||
| 1072 | #pod =cut | ||||
| 1073 | |||||
| 1074 | sub exists { -e $_[0]->[PATH] } | ||||
| 1075 | |||||
| 1076 | sub is_file { -e $_[0]->[PATH] && !-d _ } | ||||
| 1077 | |||||
| 1078 | sub is_dir { -d $_[0]->[PATH] } | ||||
| 1079 | |||||
| 1080 | #pod =method filehandle | ||||
| 1081 | #pod | ||||
| 1082 | #pod $fh = path("/tmp/foo.txt")->filehandle($mode, $binmode); | ||||
| 1083 | #pod $fh = path("/tmp/foo.txt")->filehandle({ locked => 1 }, $mode, $binmode); | ||||
| 1084 | #pod $fh = path("/tmp/foo.txt")->filehandle({ exclusive => 1 }, $mode, $binmode); | ||||
| 1085 | #pod | ||||
| 1086 | #pod Returns an open file handle. The C<$mode> argument must be a Perl-style | ||||
| 1087 | #pod read/write mode string ("<" ,">", ">>", etc.). If a C<$binmode> | ||||
| 1088 | #pod is given, it is set during the C<open> call. | ||||
| 1089 | #pod | ||||
| 1090 | #pod An optional hash reference may be used to pass options. | ||||
| 1091 | #pod | ||||
| 1092 | #pod The C<locked> option governs file locking; if true, handles opened for writing, | ||||
| 1093 | #pod appending or read-write are locked with C<LOCK_EX>; otherwise, they are | ||||
| 1094 | #pod locked with C<LOCK_SH>. When using C<locked>, ">" or "+>" modes will delay | ||||
| 1095 | #pod truncation until after the lock is acquired. | ||||
| 1096 | #pod | ||||
| 1097 | #pod The C<exclusive> option causes the open() call to fail if the file already | ||||
| 1098 | #pod exists. This corresponds to the O_EXCL flag to sysopen / open(2). | ||||
| 1099 | #pod C<exclusive> implies C<locked> and will set it for you if you forget it. | ||||
| 1100 | #pod | ||||
| 1101 | #pod See C<openr>, C<openw>, C<openrw>, and C<opena> for sugar. | ||||
| 1102 | #pod | ||||
| 1103 | #pod Current API available since 0.066. | ||||
| 1104 | #pod | ||||
| 1105 | #pod =cut | ||||
| 1106 | |||||
| 1107 | # Note: must put binmode on open line, not subsequent binmode() call, so things | ||||
| 1108 | # like ":unix" actually stop perlio/crlf from being added | ||||
| 1109 | |||||
| 1110 | # spent 2.48ms (528µs+1.96) within Path::Tiny::filehandle which was called 14 times, avg 177µs/call:
# 14 times (528µs+1.96ms) by Path::Tiny::slurp at line 2066, avg 177µs/call | ||||
| 1111 | 14 | 12µs | my ( $self, @args ) = @_; | ||
| 1112 | 14 | 12µs | my $args = ( @args && ref $args[0] eq 'HASH' ) ? shift @args : {}; | ||
| 1113 | 14 | 15µs | 14 | 26µs | $args = _get_args( $args, qw/locked exclusive/ ); # spent 26µs making 14 calls to Path::Tiny::_get_args, avg 2µs/call |
| 1114 | 14 | 4µs | $args->{locked} = 1 if $args->{exclusive}; | ||
| 1115 | 14 | 12µs | my ( $opentype, $binmode ) = @args; | ||
| 1116 | |||||
| 1117 | 14 | 5µs | $opentype = "<" unless defined $opentype; | ||
| 1118 | Carp::croak("Invalid file mode '$opentype'") | ||||
| 1119 | 14 | 22µs | unless grep { $opentype eq $_ } qw/< +< > +> >> +>>/; | ||
| 1120 | |||||
| 1121 | 14 | 6µs | $binmode = ( ( caller(0) )[10] || {} )->{ 'open' . substr( $opentype, -1, 1 ) } | ||
| 1122 | unless defined $binmode; | ||||
| 1123 | 14 | 5µs | $binmode = "" unless defined $binmode; | ||
| 1124 | |||||
| 1125 | 14 | 5µs | my ( $fh, $lock, $trunc ); | ||
| 1126 | 14 | 44µs | if ( $HAS_FLOCK && $args->{locked} && !$ENV{PERL_PATH_TINY_NO_FLOCK} ) { | ||
| 1127 | 14 | 17µs | require Fcntl; | ||
| 1128 | # truncating file modes shouldn't truncate until lock acquired | ||||
| 1129 | 14 | 34µs | if ( grep { $opentype eq $_ } qw( > +> ) ) { | ||
| 1130 | # sysopen in write mode without truncation | ||||
| 1131 | my $flags = $opentype eq ">" ? Fcntl::O_WRONLY() : Fcntl::O_RDWR(); | ||||
| 1132 | $flags |= Fcntl::O_CREAT(); | ||||
| 1133 | $flags |= Fcntl::O_EXCL() if $args->{exclusive}; | ||||
| 1134 | sysopen( $fh, $self->[PATH], $flags ) or $self->_throw("sysopen"); | ||||
| 1135 | |||||
| 1136 | # fix up the binmode since sysopen() can't specify layers like | ||||
| 1137 | # open() and binmode() can't start with just :unix like open() | ||||
| 1138 | if ( $binmode =~ s/^:unix// ) { | ||||
| 1139 | # eliminate pseudo-layers | ||||
| 1140 | binmode( $fh, ":raw" ) or $self->_throw("binmode (:raw)"); | ||||
| 1141 | # strip off real layers until only :unix is left | ||||
| 1142 | while ( 1 < ( my $layers =()= PerlIO::get_layers( $fh, output => 1 ) ) ) { | ||||
| 1143 | binmode( $fh, ":pop" ) or $self->_throw("binmode (:pop)"); | ||||
| 1144 | } | ||||
| 1145 | } | ||||
| 1146 | |||||
| 1147 | # apply any remaining binmode layers | ||||
| 1148 | if ( length $binmode ) { | ||||
| 1149 | binmode( $fh, $binmode ) or $self->_throw("binmode ($binmode)"); | ||||
| 1150 | } | ||||
| 1151 | |||||
| 1152 | # ask for lock and truncation | ||||
| 1153 | $lock = Fcntl::LOCK_EX(); | ||||
| 1154 | $trunc = 1; | ||||
| 1155 | } | ||||
| 1156 | elsif ( $^O eq 'aix' && $opentype eq "<" ) { | ||||
| 1157 | # AIX can only lock write handles, so upgrade to RW and LOCK_EX if | ||||
| 1158 | # the file is writable; otherwise give up on locking. N.B. | ||||
| 1159 | # checking -w before open to determine the open mode is an | ||||
| 1160 | # unavoidable race condition | ||||
| 1161 | if ( -w $self->[PATH] ) { | ||||
| 1162 | $opentype = "+<"; | ||||
| 1163 | $lock = Fcntl::LOCK_EX(); | ||||
| 1164 | } | ||||
| 1165 | } | ||||
| 1166 | else { | ||||
| 1167 | 14 | 8µs | $lock = $opentype eq "<" ? Fcntl::LOCK_SH() : Fcntl::LOCK_EX(); | ||
| 1168 | } | ||||
| 1169 | } | ||||
| 1170 | |||||
| 1171 | 14 | 10µs | unless ($fh) { | ||
| 1172 | 14 | 6µs | my $mode = $opentype . $binmode; | ||
| 1173 | 14 | 1.92ms | 14 | 1.76ms | open $fh, $mode, $self->[PATH] or $self->_throw("open ($mode)"); # spent 1.76ms making 14 calls to Path::Tiny::CORE:open, avg 126µs/call |
| 1174 | } | ||||
| 1175 | |||||
| 1176 | 14 | 224µs | 14 | 165µs | do { flock( $fh, $lock ) or $self->_throw("flock ($lock)") } if $lock; # spent 165µs making 14 calls to Path::Tiny::CORE:flock, avg 12µs/call |
| 1177 | 14 | 7µs | do { truncate( $fh, 0 ) or $self->_throw("truncate") } if $trunc; | ||
| 1178 | |||||
| 1179 | 14 | 81µs | return $fh; | ||
| 1180 | } | ||||
| 1181 | |||||
| 1182 | #pod =method has_same_bytes | ||||
| 1183 | #pod | ||||
| 1184 | #pod if ( path("foo.txt")->has_same_bytes("bar.txt") ) { | ||||
| 1185 | #pod # ... | ||||
| 1186 | #pod } | ||||
| 1187 | #pod | ||||
| 1188 | #pod This method returns true if both the invocant and the argument can be opened as | ||||
| 1189 | #pod file handles and the handles contain the same bytes. It returns false if their | ||||
| 1190 | #pod contents differ. If either can't be opened as a file (e.g. a directory or | ||||
| 1191 | #pod non-existent file), the method throws an exception. If both can be opened and | ||||
| 1192 | #pod both have the same C<realpath>, the method returns true without scanning any | ||||
| 1193 | #pod data. | ||||
| 1194 | #pod | ||||
| 1195 | #pod Current API available since 0.125. | ||||
| 1196 | #pod | ||||
| 1197 | #pod =cut | ||||
| 1198 | |||||
| 1199 | sub has_same_bytes { | ||||
| 1200 | my ($self, $other_path) = @_; | ||||
| 1201 | my $other = _path($other_path); | ||||
| 1202 | |||||
| 1203 | my $fh1 = $self->openr_raw({ locked => 1 }); | ||||
| 1204 | my $fh2 = $other->openr_raw({ locked => 1 }); | ||||
| 1205 | |||||
| 1206 | # check for directories | ||||
| 1207 | if (-d $fh1) { | ||||
| 1208 | $self->_throw('has_same_bytes', $self->[PATH], "directory not allowed"); | ||||
| 1209 | } | ||||
| 1210 | if (-d $fh2) { | ||||
| 1211 | $self->_throw('has_same_bytes', $other->[PATH], "directory not allowed"); | ||||
| 1212 | } | ||||
| 1213 | |||||
| 1214 | # Now that handles are open, we know the inputs are readable files that | ||||
| 1215 | # exist, so it's safe to compare via realpath | ||||
| 1216 | if ($self->realpath eq $other->realpath) { | ||||
| 1217 | return 1 | ||||
| 1218 | } | ||||
| 1219 | |||||
| 1220 | # result is 0 for equal, 1 for unequal, -1 for error | ||||
| 1221 | require File::Compare; | ||||
| 1222 | my $res = File::Compare::compare($fh1, $fh2, 65536); | ||||
| 1223 | if ($res < 0) { | ||||
| 1224 | $self->_throw('has_same_bytes') | ||||
| 1225 | } | ||||
| 1226 | |||||
| 1227 | return $res == 0; | ||||
| 1228 | } | ||||
| 1229 | |||||
| 1230 | #pod =method is_absolute, is_relative | ||||
| 1231 | #pod | ||||
| 1232 | #pod if ( path("/tmp")->is_absolute ) { ... } | ||||
| 1233 | #pod if ( path("/tmp")->is_relative ) { ... } | ||||
| 1234 | #pod | ||||
| 1235 | #pod Booleans for whether the path appears absolute or relative. | ||||
| 1236 | #pod | ||||
| 1237 | #pod Current API available since 0.001. | ||||
| 1238 | #pod | ||||
| 1239 | #pod =cut | ||||
| 1240 | |||||
| 1241 | sub is_absolute { substr( $_[0]->dirname, 0, 1 ) eq '/' } | ||||
| 1242 | |||||
| 1243 | sub is_relative { substr( $_[0]->dirname, 0, 1 ) ne '/' } | ||||
| 1244 | |||||
| 1245 | #pod =method is_rootdir | ||||
| 1246 | #pod | ||||
| 1247 | #pod while ( ! $path->is_rootdir ) { | ||||
| 1248 | #pod $path = $path->parent; | ||||
| 1249 | #pod ... | ||||
| 1250 | #pod } | ||||
| 1251 | #pod | ||||
| 1252 | #pod Boolean for whether the path is the root directory of the volume. I.e. the | ||||
| 1253 | #pod C<dirname> is C<q[/]> and the C<basename> is C<q[]>. | ||||
| 1254 | #pod | ||||
| 1255 | #pod This works even on C<MSWin32> with drives and UNC volumes: | ||||
| 1256 | #pod | ||||
| 1257 | #pod path("C:/")->is_rootdir; # true | ||||
| 1258 | #pod path("//server/share/")->is_rootdir; #true | ||||
| 1259 | #pod | ||||
| 1260 | #pod Current API available since 0.038. | ||||
| 1261 | #pod | ||||
| 1262 | #pod =cut | ||||
| 1263 | |||||
| 1264 | sub is_rootdir { | ||||
| 1265 | my ($self) = @_; | ||||
| 1266 | $self->_splitpath unless defined $self->[DIR]; | ||||
| 1267 | return $self->[DIR] eq '/' && $self->[FILE] eq ''; | ||||
| 1268 | } | ||||
| 1269 | |||||
| 1270 | #pod =method iterator | ||||
| 1271 | #pod | ||||
| 1272 | #pod $iter = path("/tmp")->iterator( \%options ); | ||||
| 1273 | #pod | ||||
| 1274 | #pod Returns a code reference that walks a directory lazily. Each invocation | ||||
| 1275 | #pod returns a C<Path::Tiny> object or undef when the iterator is exhausted. | ||||
| 1276 | #pod | ||||
| 1277 | #pod $iter = path("/tmp")->iterator; | ||||
| 1278 | #pod while ( $path = $iter->() ) { | ||||
| 1279 | #pod ... | ||||
| 1280 | #pod } | ||||
| 1281 | #pod | ||||
| 1282 | #pod The current and parent directory entries ("." and "..") will not | ||||
| 1283 | #pod be included. | ||||
| 1284 | #pod | ||||
| 1285 | #pod If the C<recurse> option is true, the iterator will walk the directory | ||||
| 1286 | #pod recursively, breadth-first. If the C<follow_symlinks> option is also true, | ||||
| 1287 | #pod directory links will be followed recursively. There is no protection against | ||||
| 1288 | #pod loops when following links. If a directory is not readable, it will not be | ||||
| 1289 | #pod followed. | ||||
| 1290 | #pod | ||||
| 1291 | #pod The default is the same as: | ||||
| 1292 | #pod | ||||
| 1293 | #pod $iter = path("/tmp")->iterator( { | ||||
| 1294 | #pod recurse => 0, | ||||
| 1295 | #pod follow_symlinks => 0, | ||||
| 1296 | #pod } ); | ||||
| 1297 | #pod | ||||
| 1298 | #pod For a more powerful, recursive iterator with built-in loop avoidance, see | ||||
| 1299 | #pod L<Path::Iterator::Rule>. | ||||
| 1300 | #pod | ||||
| 1301 | #pod See also L</visit>. | ||||
| 1302 | #pod | ||||
| 1303 | #pod Current API available since 0.016. | ||||
| 1304 | #pod | ||||
| 1305 | #pod =cut | ||||
| 1306 | |||||
| 1307 | sub iterator { | ||||
| 1308 | my $self = shift; | ||||
| 1309 | my $args = _get_args( shift, qw/recurse follow_symlinks/ ); | ||||
| 1310 | my @dirs = $self; | ||||
| 1311 | my $current; | ||||
| 1312 | return sub { | ||||
| 1313 | my $next; | ||||
| 1314 | while (@dirs) { | ||||
| 1315 | if ( ref $dirs[0] eq 'Path::Tiny' ) { | ||||
| 1316 | if ( !-r $dirs[0] ) { | ||||
| 1317 | # Directory is missing or not readable, so skip it. There | ||||
| 1318 | # is still a race condition possible between the check and | ||||
| 1319 | # the opendir, but we can't easily differentiate between | ||||
| 1320 | # error cases that are OK to skip and those that we want | ||||
| 1321 | # to be exceptions, so we live with the race and let opendir | ||||
| 1322 | # be fatal. | ||||
| 1323 | shift @dirs and next; | ||||
| 1324 | } | ||||
| 1325 | $current = $dirs[0]; | ||||
| 1326 | my $dh; | ||||
| 1327 | opendir( $dh, $current->[PATH] ) | ||||
| 1328 | or $self->_throw( 'opendir', $current->[PATH] ); | ||||
| 1329 | $dirs[0] = $dh; | ||||
| 1330 | if ( -l $current->[PATH] && !$args->{follow_symlinks} ) { | ||||
| 1331 | # Symlink attack! It was a real dir, but is now a symlink! | ||||
| 1332 | # N.B. we check *after* opendir so the attacker has to win | ||||
| 1333 | # two races: replace dir with symlink before opendir and | ||||
| 1334 | # replace symlink with dir before -l check above | ||||
| 1335 | shift @dirs and next; | ||||
| 1336 | } | ||||
| 1337 | } | ||||
| 1338 | while ( defined( $next = readdir $dirs[0] ) ) { | ||||
| 1339 | next if $next eq '.' || $next eq '..'; | ||||
| 1340 | my $path = $current->child($next); | ||||
| 1341 | push @dirs, $path | ||||
| 1342 | if $args->{recurse} && -d $path && !( !$args->{follow_symlinks} && -l $path ); | ||||
| 1343 | return $path; | ||||
| 1344 | } | ||||
| 1345 | shift @dirs; | ||||
| 1346 | } | ||||
| 1347 | return; | ||||
| 1348 | }; | ||||
| 1349 | } | ||||
| 1350 | |||||
| 1351 | #pod =method lines, lines_raw, lines_utf8 | ||||
| 1352 | #pod | ||||
| 1353 | #pod @contents = path("/tmp/foo.txt")->lines; | ||||
| 1354 | #pod @contents = path("/tmp/foo.txt")->lines(\%options); | ||||
| 1355 | #pod @contents = path("/tmp/foo.txt")->lines_raw; | ||||
| 1356 | #pod @contents = path("/tmp/foo.txt")->lines_utf8; | ||||
| 1357 | #pod | ||||
| 1358 | #pod @contents = path("/tmp/foo.txt")->lines( { chomp => 1, count => 4 } ); | ||||
| 1359 | #pod | ||||
| 1360 | #pod Returns a list of lines from a file. Optionally takes a hash-reference of | ||||
| 1361 | #pod options. Valid options are C<binmode>, C<count> and C<chomp>. | ||||
| 1362 | #pod | ||||
| 1363 | #pod If C<binmode> is provided, it will be set on the handle prior to reading. | ||||
| 1364 | #pod | ||||
| 1365 | #pod If a positive C<count> is provided, that many lines will be returned from the | ||||
| 1366 | #pod start of the file. If a negative C<count> is provided, the entire file will be | ||||
| 1367 | #pod read, but only C<abs(count)> will be kept and returned. If C<abs(count)> | ||||
| 1368 | #pod exceeds the number of lines in the file, all lines will be returned. | ||||
| 1369 | #pod | ||||
| 1370 | #pod If C<chomp> is set, any end-of-line character sequences (C<CR>, C<CRLF>, or | ||||
| 1371 | #pod C<LF>) will be removed from the lines returned. | ||||
| 1372 | #pod | ||||
| 1373 | #pod Because the return is a list, C<lines> in scalar context will return the number | ||||
| 1374 | #pod of lines (and throw away the data). | ||||
| 1375 | #pod | ||||
| 1376 | #pod $number_of_lines = path("/tmp/foo.txt")->lines; | ||||
| 1377 | #pod | ||||
| 1378 | #pod C<lines_raw> is like C<lines> with a C<binmode> of C<:raw>. We use C<:raw> | ||||
| 1379 | #pod instead of C<:unix> so PerlIO buffering can manage reading by line. | ||||
| 1380 | #pod | ||||
| 1381 | #pod C<lines_utf8> is like C<lines> with a C<binmode> of C<:raw:encoding(UTF-8)> | ||||
| 1382 | #pod (or C<:raw:utf8_strict> with L<PerlIO::utf8_strict>). If L<Unicode::UTF8> | ||||
| 1383 | #pod 0.58+ is installed, a raw, unbuffered UTF-8 slurp will be done and then the | ||||
| 1384 | #pod lines will be split. This is actually faster than relying on | ||||
| 1385 | #pod IO layers, though a bit memory intensive. If memory use is a | ||||
| 1386 | #pod concern, consider C<openr_utf8> and iterating directly on the handle. | ||||
| 1387 | #pod | ||||
| 1388 | #pod Current API available since 0.065. | ||||
| 1389 | #pod | ||||
| 1390 | #pod =cut | ||||
| 1391 | |||||
| 1392 | sub lines { | ||||
| 1393 | my $self = shift; | ||||
| 1394 | my $args = _get_args( shift, qw/binmode chomp count/ ); | ||||
| 1395 | my $binmode = $args->{binmode}; | ||||
| 1396 | $binmode = ( ( caller(0) )[10] || {} )->{'open<'} unless defined $binmode; | ||||
| 1397 | my $fh = $self->filehandle( { locked => 1 }, "<", $binmode ); | ||||
| 1398 | my $chomp = $args->{chomp}; | ||||
| 1399 | # XXX more efficient to read @lines then chomp(@lines) vs map? | ||||
| 1400 | if ( $args->{count} ) { | ||||
| 1401 | my ( $counter, $mod, @result ) = ( 0, abs( $args->{count} ) ); | ||||
| 1402 | my $line; | ||||
| 1403 | while ( !eof($fh) ) { | ||||
| 1404 | defined( $line = readline($fh) ) or $self->_throw('readline'); | ||||
| 1405 | |||||
| 1406 | $line =~ s/(?:\x{0d}?\x{0a}|\x{0d})\z// if $chomp; | ||||
| 1407 | $result[ $counter++ ] = $line; | ||||
| 1408 | # for positive count, terminate after right number of lines | ||||
| 1409 | last if $counter == $args->{count}; | ||||
| 1410 | # for negative count, eventually wrap around in the result array | ||||
| 1411 | $counter %= $mod; | ||||
| 1412 | } | ||||
| 1413 | # reorder results if full and wrapped somewhere in the middle | ||||
| 1414 | splice( @result, 0, 0, splice( @result, $counter ) ) | ||||
| 1415 | if @result == $mod && $counter % $mod; | ||||
| 1416 | return @result; | ||||
| 1417 | } | ||||
| 1418 | elsif ($chomp) { | ||||
| 1419 | local $!; | ||||
| 1420 | my @lines = map { s/(?:\x{0d}?\x{0a}|\x{0d})\z//; $_ } <$fh>; ## no critic | ||||
| 1421 | $self->_throw('readline') if $!; | ||||
| 1422 | return @lines; | ||||
| 1423 | } | ||||
| 1424 | else { | ||||
| 1425 | if ( wantarray ) { | ||||
| 1426 | local $!; | ||||
| 1427 | my @lines = <$fh>; | ||||
| 1428 | $self->_throw('readline') if $!; | ||||
| 1429 | return @lines; | ||||
| 1430 | } else { | ||||
| 1431 | local $!; | ||||
| 1432 | my $count =()= <$fh>; | ||||
| 1433 | $self->_throw('readline') if $!; | ||||
| 1434 | return $count; | ||||
| 1435 | } | ||||
| 1436 | } | ||||
| 1437 | } | ||||
| 1438 | |||||
| 1439 | sub lines_raw { | ||||
| 1440 | my $self = shift; | ||||
| 1441 | my $args = _get_args( shift, qw/binmode chomp count/ ); | ||||
| 1442 | if ( $args->{chomp} && !$args->{count} ) { | ||||
| 1443 | return split /\n/, slurp_raw($self); ## no critic | ||||
| 1444 | } | ||||
| 1445 | else { | ||||
| 1446 | $args->{binmode} = ":raw"; | ||||
| 1447 | return lines( $self, $args ); | ||||
| 1448 | } | ||||
| 1449 | } | ||||
| 1450 | |||||
| 1451 | 1 | 2µs | 1 | 1µs | my $CRLF = qr/(?:\x{0d}?\x{0a}|\x{0d})/; # spent 1µs making 1 call to Path::Tiny::CORE:qr |
| 1452 | |||||
| 1453 | sub lines_utf8 { | ||||
| 1454 | my $self = shift; | ||||
| 1455 | my $args = _get_args( shift, qw/binmode chomp count/ ); | ||||
| 1456 | if ( ( defined($HAS_UU) ? $HAS_UU : ( $HAS_UU = _check_UU() ) ) | ||||
| 1457 | && $args->{chomp} | ||||
| 1458 | && !$args->{count} ) | ||||
| 1459 | { | ||||
| 1460 | my $slurp = slurp_utf8($self); | ||||
| 1461 | $slurp =~ s/$CRLF\z//; # like chomp, but full CR?LF|CR | ||||
| 1462 | return split $CRLF, $slurp, -1; ## no critic | ||||
| 1463 | } | ||||
| 1464 | elsif ( defined($HAS_PU) ? $HAS_PU : ( $HAS_PU = _check_PU() ) ) { | ||||
| 1465 | $args->{binmode} = ":raw:utf8_strict"; | ||||
| 1466 | return lines( $self, $args ); | ||||
| 1467 | } | ||||
| 1468 | else { | ||||
| 1469 | $args->{binmode} = ":raw:encoding(UTF-8)"; | ||||
| 1470 | return lines( $self, $args ); | ||||
| 1471 | } | ||||
| 1472 | } | ||||
| 1473 | |||||
| 1474 | #pod =method mkdir | ||||
| 1475 | #pod | ||||
| 1476 | #pod path("foo/bar/baz")->mkdir; | ||||
| 1477 | #pod path("foo/bar/baz")->mkdir( \%options ); | ||||
| 1478 | #pod | ||||
| 1479 | #pod Like calling C<make_path> from L<File::Path>. An optional hash reference | ||||
| 1480 | #pod is passed through to C<make_path>. Errors will be trapped and an exception | ||||
| 1481 | #pod thrown. Returns the the path object to facilitate chaining. | ||||
| 1482 | #pod | ||||
| 1483 | #pod B<NOTE>: unlike Perl's builtin C<mkdir>, this will create intermediate paths | ||||
| 1484 | #pod similar to the Unix C<mkdir -p> command. It will not error if applied to an | ||||
| 1485 | #pod existing directory. | ||||
| 1486 | #pod | ||||
| 1487 | #pod Current API available since 0.125. | ||||
| 1488 | #pod | ||||
| 1489 | #pod =cut | ||||
| 1490 | |||||
| 1491 | sub mkdir { | ||||
| 1492 | my ( $self, $args ) = @_; | ||||
| 1493 | $args = {} unless ref $args eq 'HASH'; | ||||
| 1494 | my $err; | ||||
| 1495 | $args->{error} = \$err unless defined $args->{error}; | ||||
| 1496 | require File::Path; | ||||
| 1497 | my @dirs; | ||||
| 1498 | my $ok = eval { | ||||
| 1499 | File::Path::make_path( $self->[PATH], $args ); | ||||
| 1500 | 1; | ||||
| 1501 | }; | ||||
| 1502 | if (!$ok) { | ||||
| 1503 | $self->_throw('mkdir', $self->[PATH], "error creating path: $@"); | ||||
| 1504 | } | ||||
| 1505 | if ( $err && @$err ) { | ||||
| 1506 | my ( $file, $message ) = %{ $err->[0] }; | ||||
| 1507 | $self->_throw('mkdir', $file, $message); | ||||
| 1508 | } | ||||
| 1509 | return $self; | ||||
| 1510 | } | ||||
| 1511 | |||||
| 1512 | #pod =method mkpath (deprecated) | ||||
| 1513 | #pod | ||||
| 1514 | #pod Like calling C<mkdir>, but returns the list of directories created or an empty list if | ||||
| 1515 | #pod the directories already exist, just like C<make_path>. | ||||
| 1516 | #pod | ||||
| 1517 | #pod Deprecated in 0.125. | ||||
| 1518 | #pod | ||||
| 1519 | #pod =cut | ||||
| 1520 | |||||
| 1521 | sub mkpath { | ||||
| 1522 | my ( $self, $args ) = @_; | ||||
| 1523 | $args = {} unless ref $args eq 'HASH'; | ||||
| 1524 | my $err; | ||||
| 1525 | $args->{error} = \$err unless defined $args->{error}; | ||||
| 1526 | require File::Path; | ||||
| 1527 | my @dirs = File::Path::make_path( $self->[PATH], $args ); | ||||
| 1528 | if ( $err && @$err ) { | ||||
| 1529 | my ( $file, $message ) = %{ $err->[0] }; | ||||
| 1530 | Carp::croak("mkpath failed for $file: $message"); | ||||
| 1531 | } | ||||
| 1532 | return @dirs; | ||||
| 1533 | } | ||||
| 1534 | |||||
| 1535 | #pod =method move | ||||
| 1536 | #pod | ||||
| 1537 | #pod path("foo.txt")->move("bar.txt"); | ||||
| 1538 | #pod | ||||
| 1539 | #pod Moves the current path to the given destination using L<File::Copy>'s | ||||
| 1540 | #pod C<move> function. Upon success, returns the C<Path::Tiny> object for the | ||||
| 1541 | #pod newly moved file. | ||||
| 1542 | #pod | ||||
| 1543 | #pod If the destination already exists and is a directory, and the source is not a | ||||
| 1544 | #pod directory, then the source file will be renamed into the directory | ||||
| 1545 | #pod specified by the destination. | ||||
| 1546 | #pod | ||||
| 1547 | #pod If possible, move() will simply rename the file. Otherwise, it | ||||
| 1548 | #pod copies the file to the new location and deletes the original. If an | ||||
| 1549 | #pod error occurs during this copy-and-delete process, you may be left | ||||
| 1550 | #pod with a (possibly partial) copy of the file under the destination | ||||
| 1551 | #pod name. | ||||
| 1552 | #pod | ||||
| 1553 | #pod Current API available since 0.124. Prior versions used Perl's | ||||
| 1554 | #pod -built-in (and less robust) L<rename|perlfunc/rename> function | ||||
| 1555 | #pod and did not return an object. | ||||
| 1556 | #pod | ||||
| 1557 | #pod =cut | ||||
| 1558 | |||||
| 1559 | sub move { | ||||
| 1560 | my ( $self, $dest ) = @_; | ||||
| 1561 | require File::Copy; | ||||
| 1562 | File::Copy::move( $self->[PATH], $dest ) | ||||
| 1563 | or $self->_throw( 'move', $self->[PATH] . "' -> '$dest" ); | ||||
| 1564 | |||||
| 1565 | return -d $dest ? _path( $dest, $self->basename ) : _path($dest); | ||||
| 1566 | } | ||||
| 1567 | |||||
| 1568 | #pod =method openr, openw, openrw, opena | ||||
| 1569 | #pod | ||||
| 1570 | #pod $fh = path("foo.txt")->openr($binmode); # read | ||||
| 1571 | #pod $fh = path("foo.txt")->openr_raw; | ||||
| 1572 | #pod $fh = path("foo.txt")->openr_utf8; | ||||
| 1573 | #pod | ||||
| 1574 | #pod $fh = path("foo.txt")->openw($binmode); # write | ||||
| 1575 | #pod $fh = path("foo.txt")->openw_raw; | ||||
| 1576 | #pod $fh = path("foo.txt")->openw_utf8; | ||||
| 1577 | #pod | ||||
| 1578 | #pod $fh = path("foo.txt")->opena($binmode); # append | ||||
| 1579 | #pod $fh = path("foo.txt")->opena_raw; | ||||
| 1580 | #pod $fh = path("foo.txt")->opena_utf8; | ||||
| 1581 | #pod | ||||
| 1582 | #pod $fh = path("foo.txt")->openrw($binmode); # read/write | ||||
| 1583 | #pod $fh = path("foo.txt")->openrw_raw; | ||||
| 1584 | #pod $fh = path("foo.txt")->openrw_utf8; | ||||
| 1585 | #pod | ||||
| 1586 | #pod Returns a file handle opened in the specified mode. The C<openr> style methods | ||||
| 1587 | #pod take a single C<binmode> argument. All of the C<open*> methods have | ||||
| 1588 | #pod C<open*_raw> and C<open*_utf8> equivalents that use buffered I/O layers C<:raw> | ||||
| 1589 | #pod and C<:raw:encoding(UTF-8)> (or C<:raw:utf8_strict> with | ||||
| 1590 | #pod L<PerlIO::utf8_strict>). | ||||
| 1591 | #pod | ||||
| 1592 | #pod An optional hash reference may be used to pass options. The only option is | ||||
| 1593 | #pod C<locked>. If true, handles opened for writing, appending or read-write are | ||||
| 1594 | #pod locked with C<LOCK_EX>; otherwise, they are locked for C<LOCK_SH>. | ||||
| 1595 | #pod | ||||
| 1596 | #pod $fh = path("foo.txt")->openrw_utf8( { locked => 1 } ); | ||||
| 1597 | #pod | ||||
| 1598 | #pod See L</filehandle> for more on locking. | ||||
| 1599 | #pod | ||||
| 1600 | #pod Current API available since 0.011. | ||||
| 1601 | #pod | ||||
| 1602 | #pod =cut | ||||
| 1603 | |||||
| 1604 | # map method names to corresponding open mode | ||||
| 1605 | 1 | 2µs | my %opens = ( | ||
| 1606 | opena => ">>", | ||||
| 1607 | openr => "<", | ||||
| 1608 | openw => ">", | ||||
| 1609 | openrw => "+<" | ||||
| 1610 | ); | ||||
| 1611 | |||||
| 1612 | 1 | 5µs | while ( my ( $k, $v ) = each %opens ) { | ||
| 1613 | 2 | 1.90ms | 2 | 39µs | # spent 28µs (17+11) within Path::Tiny::BEGIN@1613 which was called:
# once (17µs+11µs) by JSON::Schema::Modern::BEGIN@25 at line 1613 # spent 28µs making 1 call to Path::Tiny::BEGIN@1613
# spent 11µs making 1 call to strict::unimport |
| 1614 | # must check for lexical IO mode hint | ||||
| 1615 | *{$k} = sub { | ||||
| 1616 | my ( $self, @args ) = @_; | ||||
| 1617 | my $args = ( @args && ref $args[0] eq 'HASH' ) ? shift @args : {}; | ||||
| 1618 | $args = _get_args( $args, qw/locked/ ); | ||||
| 1619 | my ($binmode) = @args; | ||||
| 1620 | $binmode = ( ( caller(0) )[10] || {} )->{ 'open' . substr( $v, -1, 1 ) } | ||||
| 1621 | unless defined $binmode; | ||||
| 1622 | $self->filehandle( $args, $v, $binmode ); | ||||
| 1623 | 4 | 7µs | }; | ||
| 1624 | *{ $k . "_raw" } = sub { | ||||
| 1625 | my ( $self, @args ) = @_; | ||||
| 1626 | my $args = ( @args && ref $args[0] eq 'HASH' ) ? shift @args : {}; | ||||
| 1627 | $args = _get_args( $args, qw/locked/ ); | ||||
| 1628 | $self->filehandle( $args, $v, ":raw" ); | ||||
| 1629 | 4 | 6µs | }; | ||
| 1630 | *{ $k . "_utf8" } = sub { | ||||
| 1631 | my ( $self, @args ) = @_; | ||||
| 1632 | my $args = ( @args && ref $args[0] eq 'HASH' ) ? shift @args : {}; | ||||
| 1633 | $args = _get_args( $args, qw/locked/ ); | ||||
| 1634 | my $layer; | ||||
| 1635 | if ( defined($HAS_PU) ? $HAS_PU : ( $HAS_PU = _check_PU() ) ) { | ||||
| 1636 | $layer = ":raw:utf8_strict"; | ||||
| 1637 | } | ||||
| 1638 | else { | ||||
| 1639 | $layer = ":raw:encoding(UTF-8)"; | ||||
| 1640 | } | ||||
| 1641 | $self->filehandle( $args, $v, $layer ); | ||||
| 1642 | 4 | 6µs | }; | ||
| 1643 | } | ||||
| 1644 | |||||
| 1645 | #pod =method parent | ||||
| 1646 | #pod | ||||
| 1647 | #pod $parent = path("foo/bar/baz")->parent; # foo/bar | ||||
| 1648 | #pod $parent = path("foo/wibble.txt")->parent; # foo | ||||
| 1649 | #pod | ||||
| 1650 | #pod $parent = path("foo/bar/baz")->parent(2); # foo | ||||
| 1651 | #pod | ||||
| 1652 | #pod Returns a C<Path::Tiny> object corresponding to the parent directory of the | ||||
| 1653 | #pod original directory or file. An optional positive integer argument is the number | ||||
| 1654 | #pod of parent directories upwards to return. C<parent> by itself is equivalent to | ||||
| 1655 | #pod C<parent(1)>. | ||||
| 1656 | #pod | ||||
| 1657 | #pod Current API available since 0.014. | ||||
| 1658 | #pod | ||||
| 1659 | #pod =cut | ||||
| 1660 | |||||
| 1661 | # XXX this is ugly and coverage is incomplete. I think it's there for windows | ||||
| 1662 | # so need to check coverage there and compare | ||||
| 1663 | sub parent { | ||||
| 1664 | my ( $self, $level ) = @_; | ||||
| 1665 | $level = 1 unless defined $level && $level > 0; | ||||
| 1666 | $self->_splitpath unless defined $self->[FILE]; | ||||
| 1667 | my $parent; | ||||
| 1668 | if ( length $self->[FILE] ) { | ||||
| 1669 | if ( $self->[FILE] eq '.' || $self->[FILE] eq ".." ) { | ||||
| 1670 | $parent = _path( $self->[PATH] . "/.." ); | ||||
| 1671 | } | ||||
| 1672 | else { | ||||
| 1673 | $parent = _path( _non_empty( $self->[VOL] . $self->[DIR] ) ); | ||||
| 1674 | } | ||||
| 1675 | } | ||||
| 1676 | elsif ( length $self->[DIR] ) { | ||||
| 1677 | # because of symlinks, any internal updir requires us to | ||||
| 1678 | # just add more updirs at the end | ||||
| 1679 | if ( $self->[DIR] =~ m{(?:^\.\./|/\.\./|/\.\.\z)} ) { | ||||
| 1680 | $parent = _path( $self->[VOL] . $self->[DIR] . "/.." ); | ||||
| 1681 | } | ||||
| 1682 | else { | ||||
| 1683 | ( my $dir = $self->[DIR] ) =~ s{/[^\/]+/\z}{/}; | ||||
| 1684 | $parent = _path( $self->[VOL] . $dir ); | ||||
| 1685 | } | ||||
| 1686 | } | ||||
| 1687 | else { | ||||
| 1688 | $parent = _path( _non_empty( $self->[VOL] ) ); | ||||
| 1689 | } | ||||
| 1690 | return $level == 1 ? $parent : $parent->parent( $level - 1 ); | ||||
| 1691 | } | ||||
| 1692 | |||||
| 1693 | sub _non_empty { | ||||
| 1694 | my ($string) = shift; | ||||
| 1695 | return ( ( defined($string) && length($string) ) ? $string : "." ); | ||||
| 1696 | } | ||||
| 1697 | |||||
| 1698 | #pod =method realpath | ||||
| 1699 | #pod | ||||
| 1700 | #pod $real = path("/baz/foo/../bar")->realpath; | ||||
| 1701 | #pod $real = path("foo/../bar")->realpath; | ||||
| 1702 | #pod | ||||
| 1703 | #pod Returns a new C<Path::Tiny> object with all symbolic links and upward directory | ||||
| 1704 | #pod parts resolved using L<Cwd>'s C<realpath>. Compared to C<absolute>, this is | ||||
| 1705 | #pod more expensive as it must actually consult the filesystem. | ||||
| 1706 | #pod | ||||
| 1707 | #pod If the parent path can't be resolved (e.g. if it includes directories that | ||||
| 1708 | #pod don't exist), an exception will be thrown: | ||||
| 1709 | #pod | ||||
| 1710 | #pod $real = path("doesnt_exist/foo")->realpath; # dies | ||||
| 1711 | #pod | ||||
| 1712 | #pod However, if the parent path exists and only the last component (e.g. filename) | ||||
| 1713 | #pod doesn't exist, the realpath will be the realpath of the parent plus the | ||||
| 1714 | #pod non-existent last component: | ||||
| 1715 | #pod | ||||
| 1716 | #pod $real = path("./aasdlfasdlf")->realpath; # works | ||||
| 1717 | #pod | ||||
| 1718 | #pod The underlying L<Cwd> module usually worked this way on Unix, but died on | ||||
| 1719 | #pod Windows (and some Unixes) if the full path didn't exist. As of version 0.064, | ||||
| 1720 | #pod it's safe to use anywhere. | ||||
| 1721 | #pod | ||||
| 1722 | #pod Current API available since 0.001. | ||||
| 1723 | #pod | ||||
| 1724 | #pod =cut | ||||
| 1725 | |||||
| 1726 | # Win32 and some Unixes need parent path resolved separately so realpath | ||||
| 1727 | # doesn't throw an error resolving non-existent basename | ||||
| 1728 | sub realpath { | ||||
| 1729 | my $self = shift; | ||||
| 1730 | $self = $self->_resolve_symlinks; | ||||
| 1731 | require Cwd; | ||||
| 1732 | $self->_splitpath if !defined $self->[FILE]; | ||||
| 1733 | my $check_parent = | ||||
| 1734 | length $self->[FILE] && $self->[FILE] ne '.' && $self->[FILE] ne '..'; | ||||
| 1735 | my $realpath = eval { | ||||
| 1736 | # pure-perl Cwd can carp | ||||
| 1737 | local $SIG{__WARN__} = sub { }; | ||||
| 1738 | Cwd::realpath( $check_parent ? $self->parent->[PATH] : $self->[PATH] ); | ||||
| 1739 | }; | ||||
| 1740 | # parent realpath must exist; not all Cwd::realpath will error if it doesn't | ||||
| 1741 | $self->_throw("resolving realpath") | ||||
| 1742 | unless defined $realpath && length $realpath && -e $realpath; | ||||
| 1743 | return ( $check_parent ? _path( $realpath, $self->[FILE] ) : _path($realpath) ); | ||||
| 1744 | } | ||||
| 1745 | |||||
| 1746 | #pod =method relative | ||||
| 1747 | #pod | ||||
| 1748 | #pod $rel = path("/tmp/foo/bar")->relative("/tmp"); # foo/bar | ||||
| 1749 | #pod | ||||
| 1750 | #pod Returns a C<Path::Tiny> object with a path relative to a new base path | ||||
| 1751 | #pod given as an argument. If no argument is given, the current directory will | ||||
| 1752 | #pod be used as the new base path. | ||||
| 1753 | #pod | ||||
| 1754 | #pod If either path is already relative, it will be made absolute based on the | ||||
| 1755 | #pod current directly before determining the new relative path. | ||||
| 1756 | #pod | ||||
| 1757 | #pod The algorithm is roughly as follows: | ||||
| 1758 | #pod | ||||
| 1759 | #pod =for :list | ||||
| 1760 | #pod * If the original and new base path are on different volumes, an exception | ||||
| 1761 | #pod will be thrown. | ||||
| 1762 | #pod * If the original and new base are identical, the relative path is C<".">. | ||||
| 1763 | #pod * If the new base subsumes the original, the relative path is the original | ||||
| 1764 | #pod path with the new base chopped off the front | ||||
| 1765 | #pod * If the new base does not subsume the original, a common prefix path is | ||||
| 1766 | #pod determined (possibly the root directory) and the relative path will | ||||
| 1767 | #pod consist of updirs (C<"..">) to reach the common prefix, followed by the | ||||
| 1768 | #pod original path less the common prefix. | ||||
| 1769 | #pod | ||||
| 1770 | #pod Unlike C<File::Spec::abs2rel>, in the last case above, the calculation based | ||||
| 1771 | #pod on a common prefix takes into account symlinks that could affect the updir | ||||
| 1772 | #pod process. Given an original path "/A/B" and a new base "/A/C", | ||||
| 1773 | #pod (where "A", "B" and "C" could each have multiple path components): | ||||
| 1774 | #pod | ||||
| 1775 | #pod =for :list | ||||
| 1776 | #pod * Symlinks in "A" don't change the result unless the last component of A is | ||||
| 1777 | #pod a symlink and the first component of "C" is an updir. | ||||
| 1778 | #pod * Symlinks in "B" don't change the result and will exist in the result as | ||||
| 1779 | #pod given. | ||||
| 1780 | #pod * Symlinks and updirs in "C" must be resolved to actual paths, taking into | ||||
| 1781 | #pod account the possibility that not all path components might exist on the | ||||
| 1782 | #pod filesystem. | ||||
| 1783 | #pod | ||||
| 1784 | #pod Current API available since 0.001. New algorithm (that accounts for | ||||
| 1785 | #pod symlinks) available since 0.079. | ||||
| 1786 | #pod | ||||
| 1787 | #pod =cut | ||||
| 1788 | |||||
| 1789 | sub relative { | ||||
| 1790 | my ( $self, $base ) = @_; | ||||
| 1791 | $base = _path( defined $base && length $base ? $base : '.' ); | ||||
| 1792 | |||||
| 1793 | # relative paths must be converted to absolute first | ||||
| 1794 | $self = $self->absolute if $self->is_relative; | ||||
| 1795 | $base = $base->absolute if $base->is_relative; | ||||
| 1796 | |||||
| 1797 | # normalize volumes if they exist | ||||
| 1798 | $self = $self->absolute if !length $self->volume && length $base->volume; | ||||
| 1799 | $base = $base->absolute if length $self->volume && !length $base->volume; | ||||
| 1800 | |||||
| 1801 | # can't make paths relative across volumes | ||||
| 1802 | if ( !_same( $self->volume, $base->volume ) ) { | ||||
| 1803 | Carp::croak("relative() can't cross volumes: '$self' vs '$base'"); | ||||
| 1804 | } | ||||
| 1805 | |||||
| 1806 | # if same absolute path, relative is current directory | ||||
| 1807 | return _path(".") if _same( $self->[PATH], $base->[PATH] ); | ||||
| 1808 | |||||
| 1809 | # if base is a prefix of self, chop prefix off self | ||||
| 1810 | if ( $base->subsumes($self) ) { | ||||
| 1811 | $base = "" if $base->is_rootdir; | ||||
| 1812 | my $relative = "$self"; | ||||
| 1813 | $relative =~ s{\A\Q$base/}{}; | ||||
| 1814 | return _path(".", $relative); | ||||
| 1815 | } | ||||
| 1816 | |||||
| 1817 | # base is not a prefix, so must find a common prefix (even if root) | ||||
| 1818 | my ( @common, @self_parts, @base_parts ); | ||||
| 1819 | @base_parts = split /\//, $base->_just_filepath; | ||||
| 1820 | |||||
| 1821 | # if self is rootdir, then common directory is root (shown as empty | ||||
| 1822 | # string for later joins); otherwise, must be computed from path parts. | ||||
| 1823 | if ( $self->is_rootdir ) { | ||||
| 1824 | @common = (""); | ||||
| 1825 | shift @base_parts; | ||||
| 1826 | } | ||||
| 1827 | else { | ||||
| 1828 | @self_parts = split /\//, $self->_just_filepath; | ||||
| 1829 | |||||
| 1830 | while ( @self_parts && @base_parts && _same( $self_parts[0], $base_parts[0] ) ) { | ||||
| 1831 | push @common, shift @base_parts; | ||||
| 1832 | shift @self_parts; | ||||
| 1833 | } | ||||
| 1834 | } | ||||
| 1835 | |||||
| 1836 | # if there are any symlinks from common to base, we have a problem, as | ||||
| 1837 | # you can't guarantee that updir from base reaches the common prefix; | ||||
| 1838 | # we must resolve symlinks and try again; likewise, any updirs are | ||||
| 1839 | # a problem as it throws off calculation of updirs needed to get from | ||||
| 1840 | # self's path to the common prefix. | ||||
| 1841 | if ( my $new_base = $self->_resolve_between( \@common, \@base_parts ) ) { | ||||
| 1842 | return $self->relative($new_base); | ||||
| 1843 | } | ||||
| 1844 | |||||
| 1845 | # otherwise, symlinks in common or from common to A don't matter as | ||||
| 1846 | # those don't involve updirs | ||||
| 1847 | my @new_path = ( ("..") x ( 0+ @base_parts ), @self_parts ); | ||||
| 1848 | return _path(@new_path); | ||||
| 1849 | } | ||||
| 1850 | |||||
| 1851 | sub _just_filepath { | ||||
| 1852 | my $self = shift; | ||||
| 1853 | my $self_vol = $self->volume; | ||||
| 1854 | return "$self" if !length $self_vol; | ||||
| 1855 | |||||
| 1856 | ( my $self_path = "$self" ) =~ s{\A\Q$self_vol}{}; | ||||
| 1857 | |||||
| 1858 | return $self_path; | ||||
| 1859 | } | ||||
| 1860 | |||||
| 1861 | sub _resolve_between { | ||||
| 1862 | my ( $self, $common, $base ) = @_; | ||||
| 1863 | my $path = $self->volume . join( "/", @$common ); | ||||
| 1864 | my $changed = 0; | ||||
| 1865 | for my $p (@$base) { | ||||
| 1866 | $path .= "/$p"; | ||||
| 1867 | if ( $p eq '..' ) { | ||||
| 1868 | $changed = 1; | ||||
| 1869 | if ( -e $path ) { | ||||
| 1870 | $path = _path($path)->realpath->[PATH]; | ||||
| 1871 | } | ||||
| 1872 | else { | ||||
| 1873 | $path =~ s{/[^/]+/..\z}{/}; | ||||
| 1874 | } | ||||
| 1875 | } | ||||
| 1876 | if ( -l $path ) { | ||||
| 1877 | $changed = 1; | ||||
| 1878 | $path = _path($path)->realpath->[PATH]; | ||||
| 1879 | } | ||||
| 1880 | } | ||||
| 1881 | return $changed ? _path($path) : undef; | ||||
| 1882 | } | ||||
| 1883 | |||||
| 1884 | #pod =method remove | ||||
| 1885 | #pod | ||||
| 1886 | #pod path("foo.txt")->remove; | ||||
| 1887 | #pod | ||||
| 1888 | #pod This is just like C<unlink>, except for its error handling: if the path does | ||||
| 1889 | #pod not exist, it returns false; if deleting the file fails, it throws an | ||||
| 1890 | #pod exception. | ||||
| 1891 | #pod | ||||
| 1892 | #pod Current API available since 0.012. | ||||
| 1893 | #pod | ||||
| 1894 | #pod =cut | ||||
| 1895 | |||||
| 1896 | sub remove { | ||||
| 1897 | my $self = shift; | ||||
| 1898 | |||||
| 1899 | return 0 if !-e $self->[PATH] && !-l $self->[PATH]; | ||||
| 1900 | |||||
| 1901 | return unlink( $self->[PATH] ) || $self->_throw('unlink'); | ||||
| 1902 | } | ||||
| 1903 | |||||
| 1904 | #pod =method remove_tree | ||||
| 1905 | #pod | ||||
| 1906 | #pod # directory | ||||
| 1907 | #pod path("foo/bar/baz")->remove_tree; | ||||
| 1908 | #pod path("foo/bar/baz")->remove_tree( \%options ); | ||||
| 1909 | #pod path("foo/bar/baz")->remove_tree( { safe => 0 } ); # force remove | ||||
| 1910 | #pod | ||||
| 1911 | #pod Like calling C<remove_tree> from L<File::Path>, but defaults to C<safe> mode. | ||||
| 1912 | #pod An optional hash reference is passed through to C<remove_tree>. Errors will be | ||||
| 1913 | #pod trapped and an exception thrown. Returns the number of directories deleted, | ||||
| 1914 | #pod just like C<remove_tree>. | ||||
| 1915 | #pod | ||||
| 1916 | #pod If you want to remove a directory only if it is empty, use the built-in | ||||
| 1917 | #pod C<rmdir> function instead. | ||||
| 1918 | #pod | ||||
| 1919 | #pod rmdir path("foo/bar/baz/"); | ||||
| 1920 | #pod | ||||
| 1921 | #pod Current API available since 0.013. | ||||
| 1922 | #pod | ||||
| 1923 | #pod =cut | ||||
| 1924 | |||||
| 1925 | sub remove_tree { | ||||
| 1926 | my ( $self, $args ) = @_; | ||||
| 1927 | return 0 if !-e $self->[PATH] && !-l $self->[PATH]; | ||||
| 1928 | $args = {} unless ref $args eq 'HASH'; | ||||
| 1929 | my $err; | ||||
| 1930 | $args->{error} = \$err unless defined $args->{error}; | ||||
| 1931 | $args->{safe} = 1 unless defined $args->{safe}; | ||||
| 1932 | require File::Path; | ||||
| 1933 | my $count = File::Path::remove_tree( $self->[PATH], $args ); | ||||
| 1934 | |||||
| 1935 | if ( $err && @$err ) { | ||||
| 1936 | my ( $file, $message ) = %{ $err->[0] }; | ||||
| 1937 | Carp::croak("remove_tree failed for $file: $message"); | ||||
| 1938 | } | ||||
| 1939 | return $count; | ||||
| 1940 | } | ||||
| 1941 | |||||
| 1942 | #pod =method sibling | ||||
| 1943 | #pod | ||||
| 1944 | #pod $foo = path("/tmp/foo.txt"); | ||||
| 1945 | #pod $sib = $foo->sibling("bar.txt"); # /tmp/bar.txt | ||||
| 1946 | #pod $sib = $foo->sibling("baz", "bam.txt"); # /tmp/baz/bam.txt | ||||
| 1947 | #pod | ||||
| 1948 | #pod Returns a new C<Path::Tiny> object relative to the parent of the original. | ||||
| 1949 | #pod This is slightly more efficient than C<< $path->parent->child(...) >>. | ||||
| 1950 | #pod | ||||
| 1951 | #pod Current API available since 0.058. | ||||
| 1952 | #pod | ||||
| 1953 | #pod =cut | ||||
| 1954 | |||||
| 1955 | sub sibling { | ||||
| 1956 | my $self = shift; | ||||
| 1957 | return _path( $self->parent->[PATH], @_ ); | ||||
| 1958 | } | ||||
| 1959 | |||||
| 1960 | #pod =method size, size_human | ||||
| 1961 | #pod | ||||
| 1962 | #pod my $p = path("foo"); # with size 1025 bytes | ||||
| 1963 | #pod | ||||
| 1964 | #pod $p->size; # "1025" | ||||
| 1965 | #pod $p->size_human; # "1.1 K" | ||||
| 1966 | #pod $p->size_human( {format => "iec"} ); # "1.1 KiB" | ||||
| 1967 | #pod | ||||
| 1968 | #pod Returns the size of a file. The C<size> method is just a wrapper around C<-s>. | ||||
| 1969 | #pod | ||||
| 1970 | #pod The C<size_human> method provides a human-readable string similar to | ||||
| 1971 | #pod C<ls -lh>. Like C<ls>, it rounds upwards and provides one decimal place for | ||||
| 1972 | #pod single-digit sizes and no decimal places for larger sizes. The only available | ||||
| 1973 | #pod option is C<format>, which has three valid values: | ||||
| 1974 | #pod | ||||
| 1975 | #pod =for :list | ||||
| 1976 | #pod * 'ls' (the default): base-2 sizes, with C<ls> style single-letter suffixes (K, M, etc.) | ||||
| 1977 | #pod * 'iec': base-2 sizes, with IEC binary suffixes (KiB, MiB, etc.) | ||||
| 1978 | #pod * 'si': base-10 sizes, with SI decimal suffixes (kB, MB, etc.) | ||||
| 1979 | #pod | ||||
| 1980 | #pod If C<-s> would return C<undef>, C<size_human> returns the empty string. | ||||
| 1981 | #pod | ||||
| 1982 | #pod Current API available since 0.122. | ||||
| 1983 | #pod | ||||
| 1984 | #pod =cut | ||||
| 1985 | |||||
| 1986 | sub size { -s $_[0]->[PATH] } | ||||
| 1987 | |||||
| 1988 | my %formats = ( | ||||
| 1989 | 'ls' => [ 1024, log(1024), [ "", map { " $_" } qw/K M G T/ ] ], | ||||
| 1990 | 'iec' => [ 1024, log(1024), [ "", map { " $_" } qw/KiB MiB GiB TiB/ ] ], | ||||
| 1991 | 1 | 13µs | 'si' => [ 1000, log(1000), [ "", map { " $_" } qw/kB MB GB TB/ ] ], | ||
| 1992 | ); | ||||
| 1993 | |||||
| 1994 | sub _formats { return $formats{$_[0]} } | ||||
| 1995 | |||||
| 1996 | sub size_human { | ||||
| 1997 | my $self = shift; | ||||
| 1998 | my $args = _get_args( shift, qw/format/ ); | ||||
| 1999 | my $format = defined $args->{format} ? $args->{format} : "ls"; | ||||
| 2000 | my $fmt_opts = $formats{$format} | ||||
| 2001 | or Carp::croak("Invalid format '$format' for size_human()"); | ||||
| 2002 | my $size = -s $self->[PATH]; | ||||
| 2003 | return defined $size ? _human_size( $size, @$fmt_opts ) : ""; | ||||
| 2004 | } | ||||
| 2005 | |||||
| 2006 | sub _ceil { | ||||
| 2007 | return $_[0] == int($_[0]) ? $_[0] : int($_[0]+1); | ||||
| 2008 | } | ||||
| 2009 | |||||
| 2010 | sub _human_size { | ||||
| 2011 | my ( $size, $base, $log_base, $suffixes ) = @_; | ||||
| 2012 | return "0" if $size == 0; | ||||
| 2013 | |||||
| 2014 | my $mag = int( log($size) / $log_base ); | ||||
| 2015 | $size /= $base**$mag; | ||||
| 2016 | $size = | ||||
| 2017 | $mag == 0 ? $size | ||||
| 2018 | : length( int($size) ) == 1 ? _ceil( $size * 10 ) / 10 | ||||
| 2019 | : _ceil($size); | ||||
| 2020 | if ( $size >= $base ) { | ||||
| 2021 | $size /= $base; | ||||
| 2022 | $mag++; | ||||
| 2023 | } | ||||
| 2024 | |||||
| 2025 | my $fmt = ( $mag == 0 || length( int($size) ) > 1 ) ? "%.0f%s" : "%.1f%s"; | ||||
| 2026 | return sprintf( $fmt, $size, $suffixes->[$mag] ); | ||||
| 2027 | } | ||||
| 2028 | |||||
| 2029 | #pod =method slurp, slurp_raw, slurp_utf8 | ||||
| 2030 | #pod | ||||
| 2031 | #pod $data = path("foo.txt")->slurp; | ||||
| 2032 | #pod $data = path("foo.txt")->slurp( {binmode => ":raw"} ); | ||||
| 2033 | #pod $data = path("foo.txt")->slurp_raw; | ||||
| 2034 | #pod $data = path("foo.txt")->slurp_utf8; | ||||
| 2035 | #pod | ||||
| 2036 | #pod Reads file contents into a scalar. Takes an optional hash reference which may | ||||
| 2037 | #pod be used to pass options. The only available option is C<binmode>, which is | ||||
| 2038 | #pod passed to C<binmode()> on the handle used for reading. | ||||
| 2039 | #pod | ||||
| 2040 | #pod C<slurp_raw> is like C<slurp> with a C<binmode> of C<:unix> for | ||||
| 2041 | #pod a fast, unbuffered, raw read. | ||||
| 2042 | #pod | ||||
| 2043 | #pod C<slurp_utf8> is like C<slurp> with a C<binmode> of | ||||
| 2044 | #pod C<:unix:encoding(UTF-8)> (or C<:unix:utf8_strict> with | ||||
| 2045 | #pod L<PerlIO::utf8_strict>). If L<Unicode::UTF8> 0.58+ is installed, a | ||||
| 2046 | #pod unbuffered, raw slurp will be done instead and the result decoded with | ||||
| 2047 | #pod C<Unicode::UTF8>. This is just as strict and is roughly an order of | ||||
| 2048 | #pod magnitude faster than using C<:encoding(UTF-8)>. | ||||
| 2049 | #pod | ||||
| 2050 | #pod B<Note>: C<slurp> and friends lock the filehandle before slurping. If | ||||
| 2051 | #pod you plan to slurp from a file created with L<File::Temp>, be sure to | ||||
| 2052 | #pod close other handles or open without locking to avoid a deadlock: | ||||
| 2053 | #pod | ||||
| 2054 | #pod my $tempfile = File::Temp->new(EXLOCK => 0); | ||||
| 2055 | #pod my $guts = path($tempfile)->slurp; | ||||
| 2056 | #pod | ||||
| 2057 | #pod Current API available since 0.004. | ||||
| 2058 | #pod | ||||
| 2059 | #pod =cut | ||||
| 2060 | |||||
| 2061 | # spent 8.48ms (1.41+7.07) within Path::Tiny::slurp which was called 14 times, avg 606µs/call:
# 14 times (1.41ms+7.07ms) by JSON::Schema::Modern::Document::OpenAPI::_add_vocab_and_default_schemas or JSON::Schema::Modern::_get_or_load_resource at line 2083, avg 606µs/call | ||||
| 2062 | 14 | 11µs | my $self = shift; | ||
| 2063 | 14 | 35µs | 14 | 122µs | my $args = _get_args( shift, qw/binmode/ ); # spent 122µs making 14 calls to Path::Tiny::_get_args, avg 9µs/call |
| 2064 | 14 | 3µs | my $binmode = $args->{binmode}; | ||
| 2065 | 14 | 3µs | $binmode = ( ( caller(0) )[10] || {} )->{'open<'} unless defined $binmode; | ||
| 2066 | 14 | 69µs | 14 | 2.48ms | my $fh = $self->filehandle( { locked => 1 }, "<", $binmode ); # spent 2.48ms making 14 calls to Path::Tiny::filehandle, avg 177µs/call |
| 2067 | 14 | 92µs | 14 | 32µs | if ( ( defined($binmode) ? $binmode : "" ) eq ":unix" # spent 32µs making 14 calls to Path::Tiny::CORE:ftsize, avg 2µs/call |
| 2068 | and my $size = -s $fh ) | ||||
| 2069 | { | ||||
| 2070 | 14 | 3µs | my $buf; | ||
| 2071 | 14 | 4.58ms | 14 | 4.43ms | my $rc = read $fh, $buf, $size; # File::Slurp in a nutshell # spent 4.43ms making 14 calls to Path::Tiny::CORE:read, avg 316µs/call |
| 2072 | 14 | 8µs | $self->_throw('read') unless defined $rc; | ||
| 2073 | 14 | 1.12ms | return $buf; | ||
| 2074 | } | ||||
| 2075 | else { | ||||
| 2076 | local $/; | ||||
| 2077 | my $buf = scalar <$fh>; | ||||
| 2078 | $self->_throw('read') unless defined $buf; | ||||
| 2079 | return $buf; | ||||
| 2080 | } | ||||
| 2081 | } | ||||
| 2082 | |||||
| 2083 | 28 | 91µs | 14 | 8.48ms | # spent 55µs within Path::Tiny::slurp_raw which was called 14 times, avg 4µs/call:
# 8 times (29µs+0s) by JSON::Schema::Modern::_get_or_load_resource at line 812 of JSON/Schema/Modern.pm, avg 4µs/call
# 6 times (26µs+0s) by JSON::Schema::Modern::Document::OpenAPI::_add_vocab_and_default_schemas at line 204 of JSON/Schema/Modern/Document/OpenAPI.pm, avg 4µs/call # spent 8.48ms making 14 calls to Path::Tiny::slurp, avg 606µs/call |
| 2084 | |||||
| 2085 | sub slurp_utf8 { | ||||
| 2086 | if ( defined($HAS_UU) ? $HAS_UU : ( $HAS_UU = _check_UU() ) ) { | ||||
| 2087 | return Unicode::UTF8::decode_utf8( slurp( $_[0], { binmode => ":unix" } ) ); | ||||
| 2088 | } | ||||
| 2089 | elsif ( defined($HAS_PU) ? $HAS_PU : ( $HAS_PU = _check_PU() ) ) { | ||||
| 2090 | $_[1] = { binmode => ":unix:utf8_strict" }; | ||||
| 2091 | goto &slurp; | ||||
| 2092 | } | ||||
| 2093 | else { | ||||
| 2094 | $_[1] = { binmode => ":unix:encoding(UTF-8)" }; | ||||
| 2095 | goto &slurp; | ||||
| 2096 | } | ||||
| 2097 | } | ||||
| 2098 | |||||
| 2099 | #pod =method spew, spew_raw, spew_utf8 | ||||
| 2100 | #pod | ||||
| 2101 | #pod path("foo.txt")->spew(@data); | ||||
| 2102 | #pod path("foo.txt")->spew(\@data); | ||||
| 2103 | #pod path("foo.txt")->spew({binmode => ":raw"}, @data); | ||||
| 2104 | #pod path("foo.txt")->spew_raw(@data); | ||||
| 2105 | #pod path("foo.txt")->spew_utf8(@data); | ||||
| 2106 | #pod | ||||
| 2107 | #pod Writes data to a file atomically. The file is written to a temporary file in | ||||
| 2108 | #pod the same directory, then renamed over the original. An optional hash reference | ||||
| 2109 | #pod may be used to pass options. The only option is C<binmode>, which is passed to | ||||
| 2110 | #pod C<binmode()> on the handle used for writing. | ||||
| 2111 | #pod | ||||
| 2112 | #pod C<spew_raw> is like C<spew> with a C<binmode> of C<:unix> for a fast, | ||||
| 2113 | #pod unbuffered, raw write. | ||||
| 2114 | #pod | ||||
| 2115 | #pod C<spew_utf8> is like C<spew> with a C<binmode> of C<:unix:encoding(UTF-8)> | ||||
| 2116 | #pod (or C<:unix:utf8_strict> with L<PerlIO::utf8_strict>). If L<Unicode::UTF8> | ||||
| 2117 | #pod 0.58+ is installed, a raw, unbuffered spew will be done instead on the data | ||||
| 2118 | #pod encoded with C<Unicode::UTF8>. | ||||
| 2119 | #pod | ||||
| 2120 | #pod B<NOTE>: because the file is written to a temporary file and then renamed, the | ||||
| 2121 | #pod new file will wind up with permissions based on your current umask. This is a | ||||
| 2122 | #pod feature to protect you from a race condition that would otherwise give | ||||
| 2123 | #pod different permissions than you might expect. If you really want to keep the | ||||
| 2124 | #pod original mode flags, use L</append> with the C<truncate> option. | ||||
| 2125 | #pod | ||||
| 2126 | #pod Current API available since 0.011. | ||||
| 2127 | #pod | ||||
| 2128 | #pod =cut | ||||
| 2129 | |||||
| 2130 | sub spew { | ||||
| 2131 | my ( $self, @data ) = @_; | ||||
| 2132 | my $args = ( @data && ref $data[0] eq 'HASH' ) ? shift @data : {}; | ||||
| 2133 | $args = _get_args( $args, qw/binmode/ ); | ||||
| 2134 | my $binmode = $args->{binmode}; | ||||
| 2135 | # get default binmode from caller's lexical scope (see "perldoc open") | ||||
| 2136 | $binmode = ( ( caller(0) )[10] || {} )->{'open>'} unless defined $binmode; | ||||
| 2137 | |||||
| 2138 | # writing needs to follow the link and create the tempfile in the same | ||||
| 2139 | # dir for later atomic rename | ||||
| 2140 | my $resolved_path = $self->_resolve_symlinks; | ||||
| 2141 | my $temp = $resolved_path->_replacment_path; | ||||
| 2142 | |||||
| 2143 | my $fh = $temp->filehandle( { exclusive => 1, locked => 1 }, ">", $binmode ); | ||||
| 2144 | print( {$fh} map { ref eq 'ARRAY' ? @$_ : $_ } @data) or self->_throw('print', $temp->[PATH]); | ||||
| 2145 | close $fh or $self->_throw( 'close', $temp->[PATH] ); | ||||
| 2146 | |||||
| 2147 | return $temp->move($resolved_path); | ||||
| 2148 | } | ||||
| 2149 | |||||
| 2150 | sub spew_raw { splice @_, 1, 0, { binmode => ":unix" }; goto &spew } | ||||
| 2151 | |||||
| 2152 | sub spew_utf8 { | ||||
| 2153 | if ( defined($HAS_UU) ? $HAS_UU : ( $HAS_UU = _check_UU() ) ) { | ||||
| 2154 | my $self = shift; | ||||
| 2155 | spew( | ||||
| 2156 | $self, | ||||
| 2157 | { binmode => ":unix" }, | ||||
| 2158 | map { Unicode::UTF8::encode_utf8($_) } map { ref eq 'ARRAY' ? @$_ : $_ } @_ | ||||
| 2159 | ); | ||||
| 2160 | } | ||||
| 2161 | elsif ( defined($HAS_PU) ? $HAS_PU : ( $HAS_PU = _check_PU() ) ) { | ||||
| 2162 | splice @_, 1, 0, { binmode => ":unix:utf8_strict" }; | ||||
| 2163 | goto &spew; | ||||
| 2164 | } | ||||
| 2165 | else { | ||||
| 2166 | splice @_, 1, 0, { binmode => ":unix:encoding(UTF-8)" }; | ||||
| 2167 | goto &spew; | ||||
| 2168 | } | ||||
| 2169 | } | ||||
| 2170 | |||||
| 2171 | #pod =method stat, lstat | ||||
| 2172 | #pod | ||||
| 2173 | #pod $stat = path("foo.txt")->stat; | ||||
| 2174 | #pod $stat = path("/some/symlink")->lstat; | ||||
| 2175 | #pod | ||||
| 2176 | #pod Like calling C<stat> or C<lstat> from L<File::stat>. | ||||
| 2177 | #pod | ||||
| 2178 | #pod Current API available since 0.001. | ||||
| 2179 | #pod | ||||
| 2180 | #pod =cut | ||||
| 2181 | |||||
| 2182 | # XXX break out individual stat() components as subs? | ||||
| 2183 | sub stat { | ||||
| 2184 | my $self = shift; | ||||
| 2185 | require File::stat; | ||||
| 2186 | return File::stat::stat( $self->[PATH] ) || $self->_throw('stat'); | ||||
| 2187 | } | ||||
| 2188 | |||||
| 2189 | sub lstat { | ||||
| 2190 | my $self = shift; | ||||
| 2191 | require File::stat; | ||||
| 2192 | return File::stat::lstat( $self->[PATH] ) || $self->_throw('lstat'); | ||||
| 2193 | } | ||||
| 2194 | |||||
| 2195 | #pod =method stringify | ||||
| 2196 | #pod | ||||
| 2197 | #pod $path = path("foo.txt"); | ||||
| 2198 | #pod say $path->stringify; # same as "$path" | ||||
| 2199 | #pod | ||||
| 2200 | #pod Returns a string representation of the path. Unlike C<canonpath>, this method | ||||
| 2201 | #pod returns the path standardized with Unix-style C</> directory separators. | ||||
| 2202 | #pod | ||||
| 2203 | #pod Current API available since 0.001. | ||||
| 2204 | #pod | ||||
| 2205 | #pod =cut | ||||
| 2206 | |||||
| 2207 | sub stringify { $_[0]->[PATH] =~ /^~/ ? './' . $_[0]->[PATH] : $_[0]->[PATH] } | ||||
| 2208 | |||||
| 2209 | #pod =method subsumes | ||||
| 2210 | #pod | ||||
| 2211 | #pod path("foo/bar")->subsumes("foo/bar/baz"); # true | ||||
| 2212 | #pod path("/foo/bar")->subsumes("/foo/baz"); # false | ||||
| 2213 | #pod | ||||
| 2214 | #pod Returns true if the first path is a prefix of the second path at a directory | ||||
| 2215 | #pod boundary. | ||||
| 2216 | #pod | ||||
| 2217 | #pod This B<does not> resolve parent directory entries (C<..>) or symlinks: | ||||
| 2218 | #pod | ||||
| 2219 | #pod path("foo/bar")->subsumes("foo/bar/../baz"); # true | ||||
| 2220 | #pod | ||||
| 2221 | #pod If such things are important to you, ensure that both paths are resolved to | ||||
| 2222 | #pod the filesystem with C<realpath>: | ||||
| 2223 | #pod | ||||
| 2224 | #pod my $p1 = path("foo/bar")->realpath; | ||||
| 2225 | #pod my $p2 = path("foo/bar/../baz")->realpath; | ||||
| 2226 | #pod if ( $p1->subsumes($p2) ) { ... } | ||||
| 2227 | #pod | ||||
| 2228 | #pod Current API available since 0.048. | ||||
| 2229 | #pod | ||||
| 2230 | #pod =cut | ||||
| 2231 | |||||
| 2232 | sub subsumes { | ||||
| 2233 | my $self = shift; | ||||
| 2234 | Carp::croak("subsumes() requires a defined, positive-length argument") | ||||
| 2235 | unless defined $_[0]; | ||||
| 2236 | my $other = _path(shift); | ||||
| 2237 | |||||
| 2238 | # normalize absolute vs relative | ||||
| 2239 | if ( $self->is_absolute && !$other->is_absolute ) { | ||||
| 2240 | $other = $other->absolute; | ||||
| 2241 | } | ||||
| 2242 | elsif ( $other->is_absolute && !$self->is_absolute ) { | ||||
| 2243 | $self = $self->absolute; | ||||
| 2244 | } | ||||
| 2245 | |||||
| 2246 | # normalize volume vs non-volume; do this after absolute path | ||||
| 2247 | # adjustments above since that might add volumes already | ||||
| 2248 | if ( length $self->volume && !length $other->volume ) { | ||||
| 2249 | $other = $other->absolute; | ||||
| 2250 | } | ||||
| 2251 | elsif ( length $other->volume && !length $self->volume ) { | ||||
| 2252 | $self = $self->absolute; | ||||
| 2253 | } | ||||
| 2254 | |||||
| 2255 | if ( $self->[PATH] eq '.' ) { | ||||
| 2256 | return !!1; # cwd subsumes everything relative | ||||
| 2257 | } | ||||
| 2258 | elsif ( $self->is_rootdir ) { | ||||
| 2259 | # a root directory ("/", "c:/") already ends with a separator | ||||
| 2260 | return $other->[PATH] =~ m{^\Q$self->[PATH]\E}; | ||||
| 2261 | } | ||||
| 2262 | else { | ||||
| 2263 | # exact match or prefix breaking at a separator | ||||
| 2264 | return $other->[PATH] =~ m{^\Q$self->[PATH]\E(?:/|\z)}; | ||||
| 2265 | } | ||||
| 2266 | } | ||||
| 2267 | |||||
| 2268 | #pod =method touch | ||||
| 2269 | #pod | ||||
| 2270 | #pod path("foo.txt")->touch; | ||||
| 2271 | #pod path("foo.txt")->touch($epoch_secs); | ||||
| 2272 | #pod | ||||
| 2273 | #pod Like the Unix C<touch> utility. Creates the file if it doesn't exist, or else | ||||
| 2274 | #pod changes the modification and access times to the current time. If the first | ||||
| 2275 | #pod argument is the epoch seconds then it will be used. | ||||
| 2276 | #pod | ||||
| 2277 | #pod Returns the path object so it can be easily chained with other methods: | ||||
| 2278 | #pod | ||||
| 2279 | #pod # won't die if foo.txt doesn't exist | ||||
| 2280 | #pod $content = path("foo.txt")->touch->slurp; | ||||
| 2281 | #pod | ||||
| 2282 | #pod Current API available since 0.015. | ||||
| 2283 | #pod | ||||
| 2284 | #pod =cut | ||||
| 2285 | |||||
| 2286 | sub touch { | ||||
| 2287 | my ( $self, $epoch ) = @_; | ||||
| 2288 | if ( !-e $self->[PATH] ) { | ||||
| 2289 | my $fh = $self->openw; | ||||
| 2290 | close $fh or $self->_throw('close'); | ||||
| 2291 | } | ||||
| 2292 | if ( defined $epoch ) { | ||||
| 2293 | utime $epoch, $epoch, $self->[PATH] | ||||
| 2294 | or $self->_throw("utime ($epoch)"); | ||||
| 2295 | } | ||||
| 2296 | else { | ||||
| 2297 | # literal undef prevents warnings :-( | ||||
| 2298 | utime undef, undef, $self->[PATH] | ||||
| 2299 | or $self->_throw("utime ()"); | ||||
| 2300 | } | ||||
| 2301 | return $self; | ||||
| 2302 | } | ||||
| 2303 | |||||
| 2304 | #pod =method touchpath | ||||
| 2305 | #pod | ||||
| 2306 | #pod path("bar/baz/foo.txt")->touchpath; | ||||
| 2307 | #pod | ||||
| 2308 | #pod Combines C<mkdir> and C<touch>. Creates the parent directory if it doesn't exist, | ||||
| 2309 | #pod before touching the file. Returns the path object like C<touch> does. | ||||
| 2310 | #pod | ||||
| 2311 | #pod If you need to pass options, use C<mkdir> and C<touch> separately: | ||||
| 2312 | #pod | ||||
| 2313 | #pod path("bar/baz")->mkdir( \%options )->child("foo.txt")->touch($epoch_secs); | ||||
| 2314 | #pod | ||||
| 2315 | #pod Current API available since 0.022. | ||||
| 2316 | #pod | ||||
| 2317 | #pod =cut | ||||
| 2318 | |||||
| 2319 | sub touchpath { | ||||
| 2320 | my ($self) = @_; | ||||
| 2321 | my $parent = $self->parent; | ||||
| 2322 | $parent->mkdir unless $parent->exists; | ||||
| 2323 | $self->touch; | ||||
| 2324 | } | ||||
| 2325 | |||||
| 2326 | #pod =method visit | ||||
| 2327 | #pod | ||||
| 2328 | #pod path("/tmp")->visit( \&callback, \%options ); | ||||
| 2329 | #pod | ||||
| 2330 | #pod Executes a callback for each child of a directory. It returns a hash | ||||
| 2331 | #pod reference with any state accumulated during iteration. | ||||
| 2332 | #pod | ||||
| 2333 | #pod The options are the same as for L</iterator> (which it uses internally): | ||||
| 2334 | #pod C<recurse> and C<follow_symlinks>. Both default to false. | ||||
| 2335 | #pod | ||||
| 2336 | #pod The callback function will receive a C<Path::Tiny> object as the first argument | ||||
| 2337 | #pod and a hash reference to accumulate state as the second argument. For example: | ||||
| 2338 | #pod | ||||
| 2339 | #pod # collect files sizes | ||||
| 2340 | #pod my $sizes = path("/tmp")->visit( | ||||
| 2341 | #pod sub { | ||||
| 2342 | #pod my ($path, $state) = @_; | ||||
| 2343 | #pod return if $path->is_dir; | ||||
| 2344 | #pod $state->{$path} = -s $path; | ||||
| 2345 | #pod }, | ||||
| 2346 | #pod { recurse => 1 } | ||||
| 2347 | #pod ); | ||||
| 2348 | #pod | ||||
| 2349 | #pod For convenience, the C<Path::Tiny> object will also be locally aliased as the | ||||
| 2350 | #pod C<$_> global variable: | ||||
| 2351 | #pod | ||||
| 2352 | #pod # print paths matching /foo/ | ||||
| 2353 | #pod path("/tmp")->visit( sub { say if /foo/ }, { recurse => 1} ); | ||||
| 2354 | #pod | ||||
| 2355 | #pod If the callback returns a B<reference> to a false scalar value, iteration will | ||||
| 2356 | #pod terminate. This is not the same as "pruning" a directory search; this just | ||||
| 2357 | #pod stops all iteration and returns the state hash reference. | ||||
| 2358 | #pod | ||||
| 2359 | #pod # find up to 10 files larger than 100K | ||||
| 2360 | #pod my $files = path("/tmp")->visit( | ||||
| 2361 | #pod sub { | ||||
| 2362 | #pod my ($path, $state) = @_; | ||||
| 2363 | #pod $state->{$path}++ if -s $path > 102400 | ||||
| 2364 | #pod return \0 if keys %$state == 10; | ||||
| 2365 | #pod }, | ||||
| 2366 | #pod { recurse => 1 } | ||||
| 2367 | #pod ); | ||||
| 2368 | #pod | ||||
| 2369 | #pod If you want more flexible iteration, use a module like L<Path::Iterator::Rule>. | ||||
| 2370 | #pod | ||||
| 2371 | #pod Current API available since 0.062. | ||||
| 2372 | #pod | ||||
| 2373 | #pod =cut | ||||
| 2374 | |||||
| 2375 | sub visit { | ||||
| 2376 | my $self = shift; | ||||
| 2377 | my $cb = shift; | ||||
| 2378 | my $args = _get_args( shift, qw/recurse follow_symlinks/ ); | ||||
| 2379 | Carp::croak("Callback for visit() must be a code reference") | ||||
| 2380 | unless defined($cb) && ref($cb) eq 'CODE'; | ||||
| 2381 | my $next = $self->iterator($args); | ||||
| 2382 | my $state = {}; | ||||
| 2383 | while ( my $file = $next->() ) { | ||||
| 2384 | local $_ = $file; | ||||
| 2385 | my $r = $cb->( $file, $state ); | ||||
| 2386 | last if ref($r) eq 'SCALAR' && !$$r; | ||||
| 2387 | } | ||||
| 2388 | return $state; | ||||
| 2389 | } | ||||
| 2390 | |||||
| 2391 | #pod =method volume | ||||
| 2392 | #pod | ||||
| 2393 | #pod $vol = path("/tmp/foo.txt")->volume; # "" | ||||
| 2394 | #pod $vol = path("C:/tmp/foo.txt")->volume; # "C:" | ||||
| 2395 | #pod | ||||
| 2396 | #pod Returns the volume portion of the path. This is equivalent | ||||
| 2397 | #pod to what L<File::Spec> would give from C<splitpath> and thus | ||||
| 2398 | #pod usually is the empty string on Unix-like operating systems or the | ||||
| 2399 | #pod drive letter for an absolute path on C<MSWin32>. | ||||
| 2400 | #pod | ||||
| 2401 | #pod Current API available since 0.001. | ||||
| 2402 | #pod | ||||
| 2403 | #pod =cut | ||||
| 2404 | |||||
| 2405 | sub volume { | ||||
| 2406 | my ($self) = @_; | ||||
| 2407 | $self->_splitpath unless defined $self->[VOL]; | ||||
| 2408 | return $self->[VOL]; | ||||
| 2409 | } | ||||
| 2410 | |||||
| 2411 | package Path::Tiny::Error; | ||||
| 2412 | |||||
| 2413 | 1 | 1µs | our @CARP_NOT = qw/Path::Tiny/; | ||
| 2414 | |||||
| 2415 | 2 | 209µs | 2 | 75µs | # spent 43µs (11+32) within Path::Tiny::Error::BEGIN@2415 which was called:
# once (11µs+32µs) by JSON::Schema::Modern::BEGIN@25 at line 2415 # spent 43µs making 1 call to Path::Tiny::Error::BEGIN@2415
# spent 32µs making 1 call to overload::import |
| 2416 | |||||
| 2417 | sub throw { | ||||
| 2418 | my ( $class, $op, $file, $err ) = @_; | ||||
| 2419 | chomp( my $trace = Carp::shortmess ); | ||||
| 2420 | my $msg = "Error $op on '$file': $err$trace\n"; | ||||
| 2421 | die bless { op => $op, file => $file, err => $err, msg => $msg }, $class; | ||||
| 2422 | } | ||||
| 2423 | |||||
| 2424 | 1 | 21µs | 1; | ||
| 2425 | |||||
| 2426 | |||||
| 2427 | # vim: ts=4 sts=4 sw=4 et: | ||||
| 2428 | |||||
| 2429 | __END__ | ||||
# spent 165µs within Path::Tiny::CORE:flock which was called 14 times, avg 12µs/call:
# 14 times (165µs+0s) by Path::Tiny::filehandle at line 1176, avg 12µs/call | |||||
# spent 32µs within Path::Tiny::CORE:ftsize which was called 14 times, avg 2µs/call:
# 14 times (32µs+0s) by Path::Tiny::slurp at line 2067, avg 2µs/call | |||||
# spent 14µs within Path::Tiny::CORE:match which was called 14 times, avg 1µs/call:
# 14 times (14µs+0s) by Path::Tiny::path at line 236, avg 1µs/call | |||||
# spent 1.76ms within Path::Tiny::CORE:open which was called 14 times, avg 126µs/call:
# 14 times (1.76ms+0s) by Path::Tiny::filehandle at line 1173, avg 126µs/call | |||||
# spent 6µs within Path::Tiny::CORE:qr which was called 6 times, avg 1µs/call:
# once (2µs+0s) by JSON::Schema::Modern::BEGIN@25 at line 67
# once (1µs+0s) by JSON::Schema::Modern::BEGIN@25 at line 70
# once (1µs+0s) by JSON::Schema::Modern::BEGIN@25 at line 71
# once (1µs+0s) by JSON::Schema::Modern::BEGIN@25 at line 68
# once (1µs+0s) by JSON::Schema::Modern::BEGIN@25 at line 1451
# once (0s+0s) by JSON::Schema::Modern::BEGIN@25 at line 69 | |||||
# spent 4.43ms within Path::Tiny::CORE:read which was called 14 times, avg 316µs/call:
# 14 times (4.43ms+0s) by Path::Tiny::slurp at line 2071, avg 316µs/call | |||||
sub Path::Tiny::CORE:regcomp; # opcode | |||||
# spent 10µs within Path::Tiny::CORE:subst which was called 14 times, avg 714ns/call:
# 14 times (10µs+0s) by Path::Tiny::_pathify at line 298, avg 714ns/call |