Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement chmod method #88

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions lib/Path/Tiny.pm
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,34 @@ sub _is_root {
return IS_WIN32() ? ( $_[0] =~ /^$WIN32_ROOT$/ ) : ( $_[0] eq '/' );
}

# mode bits encoded for chmod in symbolic mode
my %MODEBITS = ( om => 0007, gm => 0070, um => 0700 );
do { my $m = 0; $MODEBITS{$_} = ( 1 << $m++ ) for qw/ox ow or gx gw gr ux uw ur/ };

sub _symbolic_chmod {
my ( $mode, $symbolic ) = @_;
for my $clause ( split /,\s*/, $symbolic ) {
if ( $clause =~ m{\A([augo]+)([=+-])([rwx]+)\z} ) {
my ( $who, $action, $perms ) = ( $1, $2, $3 );
$who =~ s/a/ugo/g;
for my $w ( split //, $who ) {
my $p = 0;
$p |= $MODEBITS{"$w$_"} for split //, $perms;
if ( $action eq '=' ) {
$mode = ( $mode & ~$MODEBITS{"${w}m"} ) | $p;
}
else {
$mode = $action eq "+" ? ( $mode | $p ) : ( $mode & ~$p );
}
}
}
else {
Carp::croak("Invalid mode clause '$clause' for chmod()");
}
}
return $mode;
}

# flock doesn't work on NFS on BSD. Since program authors often can't control
# or detect that, we warn once instead of being fatal if we can detect it and
# people who need it strict can fatalize the 'flock' category
Expand Down Expand Up @@ -485,6 +513,40 @@ sub children {
return map { path( $self->[PATH], $_ ) } @children;
}

=method chmod

path("foo.txt")->chmod(0777);
path("foo.txt")->chmod("0755");
path("foo.txt")->chmod("go-w");
path("foo.txt")->chmod("a=r,u+wx");

Sets file or directory permissions. The argument can be a numeric mode, a
string beginning with a "0" or a subset of the symbolic mode use by
F</bin/chmod>. The symbolic mode must match C<<
qr/\A([augo]+)([=+-])([rwx]+)\z/ >>. The symbolic mode does not support
permissions "stugoX".

=cut

sub chmod {
my ( $self, $new_mode ) = @_;

my $mode;
if ( $new_mode =~ /\d/ ) {
$mode = ( $new_mode =~ /^0/ ? oct($new_mode) : $new_mode );
}
elsif ( $new_mode =~ /[=+-]/ ) {
$mode = _symbolic_chmod( $self->stat->mode & 07777, $new_mode );
}
else {
Carp::croak("Invalid mode argument '$new_mode' for chmod()");
}

CORE::chmod( $mode, $self->[PATH] ) or $self->_throw("chmod");

return 1;
}

=method copy

path("/tmp/foo.txt")->copy("/tmp/bar.txt");
Expand Down
40 changes: 40 additions & 0 deletions t/chmod.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use 5.008001;
use strict;
use warnings;
use Test::More 0.96;

use lib 't/lib';
use TestUtils qw/exception/;

use Path::Tiny;

my $fh = path("t/data/chmod.txt")->openr;

while ( my $line = <$fh> ) {
chomp $line;
my ( $chmod, $orig, $expect ) = split " ", $line;
my $got = sprintf( "%05o", Path::Tiny::_symbolic_chmod( oct($orig), $chmod ) );
is( $got, $expect, "$orig -> $chmod -> $got" );
}

my $path = Path::Tiny->tempfile;

like(
exception { $path->chmod("ldkakdfa") },
qr/Invalid mode argument/,
"Invalid mode throws exception"
);

like(
exception { $path->chmod("sdfa=kdajfkl") },
qr/Invalid mode clause/,
"Invalid mode clause throws exception"
);

ok( exception { path("adljfasldfj")->chmod(0700) },
"Nonexistent file throws exception" );

done_testing;
# COPYRIGHT

# vim: ts=4 sts=4 sw=4 et:
Loading