package Mud::FS;
use strict;
use Mud::CoreTools;

use File::Spec ();
use File::Path ();
use IO::File ();

use constant FILE_PERMISSIONS => 0660;
use constant DIR_PERMISSIONS  => 0770;

my $OK_CHARS = "-_.~`(),<>A-Za-z0-9 /";

=head1 Description

Mud::FS contains methods for accessing the file system platform-independently.

Pathnames are always specified using slashes as separators, relative to the
mud's root directory.

=head1 Methods

=over 4

=cut

###############################################################################

sub _clean_path {
  my ($p) = @_;
  local $Carp::CarpLevel = $Carp::CarpLevel + 1;
  $p =~ m#([^$OK_CHARS])# and croak "Unsafe character '$1' in path '$p'";
  return grep {
    m#^\.# and croak "Path element started with '.' in path '$p'";
    length;
  } split m#/#, $p;
}

=item CM rp_file(PATH)

=item CM rp_dir(PATH)

Returns platform-specific paths for the specified file or directory.

=cut

sub rp_file {return File::Spec->catfile(Mud::CoreTools->Root, _clean_path($_[1]))}
sub rp_dir  {return File::Spec->catdir( Mud::CoreTools->Root, _clean_path($_[1]))}

=item CM exists(PATH)

Like the -e file test.

=cut

sub exists {-e $_[0]->rp_file($_[1])}

=item CM open(PATH, MODE)

Returns an IO::File or equivalent object opened to access the specified file.
Unlike IO::File, MODE is not optional.

=cut

sub open {
  my ($class, $path, $mode) = @_;
  $mode or croak __PACKAGE__.": You must specify a mode!";
  #warn $class->rp_file($path);
  return IO::File->new($class->rp_file($path), $mode);
}

=item CM mkpath(PATH)

Creates directories specified in PATH, if they do not already exist.

=cut

sub mkpath {
  my ($class, $path) = @_;
  File::Path::mkpath($class->rp_dir($path), 0, DIR_PERMISSIONS);
}

=item CM mkpath_to(PATH)

Creates directories specified in PATH (minus the last component), if they do
not already exist. Use this before creating a file using the PATH.

=cut

sub mkpath_to {
  my ($class, $path) = @_;
  $path =~ s#[^/]*$##;
  $class->mkpath($path);
}

=back

=cut

1;
__END__
