The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Mojo::Home;
use Mojo::Base -base;
use overload bool => sub {1}, '""' => sub { shift->to_string }, fallback => 1;

use Cwd qw(abs_path getcwd);
use File::Basename 'dirname';
use File::Spec::Functions qw(abs2rel catdir catfile splitdir);
use Mojo::Util qw(class_to_path files);

has parts => sub { [] };

sub detect {
  my ($self, $class) = @_;

  # Environment variable
  return $self->parts([splitdir abs_path $ENV{MOJO_HOME}]) if $ENV{MOJO_HOME};

  # Location of the application class
  if ($class && (my $path = $INC{my $file = class_to_path $class})) {
    $path =~ s/\Q$file\E$//;
    my @home = splitdir $path;

    # Remove "lib" and "blib"
    pop @home while @home && ($home[-1] =~ /^b?lib$/ || !length $home[-1]);

    # Turn into absolute path
    return $self->parts([splitdir abs_path catdir(@home) || '.']);
  }

  # Current working directory
  return $self->parts([splitdir getcwd]);
}

sub lib_dir {
  my $path = catdir @{shift->parts}, 'lib';
  return -d $path ? $path : undef;
}

sub list_files {
  my ($self, $dir, $options) = (shift, shift // '', shift);
  $dir = catdir @{$self->parts}, split('/', $dir);
  return [map { join '/', splitdir abs2rel($_, $dir) } files $dir, $options];
}

sub mojo_lib_dir { catdir dirname(__FILE__), '..' }

sub new { @_ > 1 ? shift->SUPER::new->parse(@_) : shift->SUPER::new }

sub parse { shift->parts([splitdir shift]) }

sub rel_dir  { catdir @{shift->parts},  split('/', shift) }
sub rel_file { catfile @{shift->parts}, split('/', shift) }

sub to_string { catdir @{shift->parts} }

1;

=encoding utf8

=head1 NAME

Mojo::Home - Home sweet home

=head1 SYNOPSIS

  use Mojo::Home;

  # Find and manage the project root directory
  my $home = Mojo::Home->new;
  $home->detect;
  say $home->lib_dir;
  say $home->rel_file('templates/layouts/default.html.ep');
  say "$home";

=head1 DESCRIPTION

L<Mojo::Home> is a container for home directories.

=head1 ATTRIBUTES

L<Mojo::Home> implements the following attributes.

=head2 parts

  my $parts = $home->parts;
  $home     = $home->parts(['home', 'sri', 'myapp']);

Home directory parts.

=head1 METHODS

L<Mojo::Home> inherits all methods from L<Mojo::Base> and implements the
following new ones.

=head2 detect

  $home = $home->detect;
  $home = $home->detect('My::App');

Detect home directory from the value of the C<MOJO_HOME> environment variable,
location of the application class, or the current working directory.

=head2 lib_dir

  my $path = $home->lib_dir;

Path to C<lib> directory of application.

=head2 list_files

  my $files = $home->list_files;
  my $files = $home->list_files('foo/bar');
  my $files = $home->list_files('foo/bar', {hidden => 1});

Portably list all files recursively in directory relative to the home directory.

  # List layouts
  say $home->rel_file($_) for @{$home->list_files('templates/layouts')};

These options are currently available:

=over 2

=item hidden

  hidden => 1

Include hidden files and directories.

=back

=head2 mojo_lib_dir

  my $path = $home->mojo_lib_dir;

Path to C<lib> directory in which L<Mojolicious> is installed.

=head2 new

  my $home = Mojo::Home->new;
  my $home = Mojo::Home->new('/home/sri/my_app');

Construct a new L<Mojo::Home> object and L</"parse"> home directory if
necessary.

=head2 parse

  $home = $home->parse('/home/sri/my_app');

Parse home directory.

=head2 rel_dir

  my $path = $home->rel_dir('foo/bar');

Portably generate an absolute path for a directory relative to the home
directory.

=head2 rel_file

  my $path = $home->rel_file('foo/bar.html');

Portably generate an absolute path for a file relative to the home directory.

=head2 to_string

  my $str = $home->to_string;

Home directory.

=head1 OPERATORS

L<Mojo::Home> overloads the following operators.

=head2 bool

  my $bool = !!$home;

Always true.

=head2 stringify

  my $str = "$home";

Alias for L</"to_string">.

=head1 SEE ALSO

L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicious.org>.

=cut