The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Forest::Tree::Builder::SimpleTextFile;
use Moose;

our $VERSION   = '0.09';
our $AUTHORITY = 'cpan:STEVAN';

no warnings 'recursion';

with qw(Forest::Tree::Builder::Callback); # for compatibility with overriding create_new_subtree, otherwise invisible

has fh => (
    isa => "FileHandle",
    is  => "ro",
    required => 1,
);

has 'tab_width' => (
    is      => 'rw',
    isa     => 'Int',
    default => 4
);

has 'parser' => (
    is      => 'rw',
    isa     => 'CodeRef',
    lazy    => 1,
    builder => 'build_parser',
);

sub build_parser {
    return sub {
        my ($self, $line) = @_;
        my ($indent, $node) = ($line =~ /^(\s*)(.*)$/);
        my $depth = ((length $indent) / $self->tab_width);
        return ($depth, $node);
    }
}

sub parse_line { $_[0]->parser->(@_) }

sub _build_subtrees {
    my $self = shift;

    my $cur_children = [];
    my @stack;

    my $fh = $self->fh;

    while ( defined(my $line = <$fh>) ) {

        chomp($line);

        next if !$line || $line =~ /^#/;

        my ($depth, $node, @rest) = $self->parse_line($line);

        if ( $depth > @stack ) {
            if ( $depth = @stack + 1 ) {
                push @stack, $cur_children;
                $cur_children = $cur_children->[-1]{children} = [];
            } else {
                die "Parse Error : the difference between the depth ($depth) and " .
                    "the tree depth (" . scalar(@stack)  . ") is too much (" .
                    ($depth - @stack) . ") at line:\n'$line'";
            }
        } elsif ( $depth < @stack ) {
            while ( $depth < @stack ) {
                foreach my $node ( @$cur_children ) {
                    $node = $self->create_new_subtree(%$node);
                }

                $cur_children = pop @stack;
            }
        }

        push @$cur_children, { node => $node, @rest };
    }

    while ( @stack ) {
        $_ = $self->create_new_subtree(%$_) for @$cur_children;
        $cur_children = pop @stack;
    }

    return [ map { $self->create_new_subtree(%$_) } @$cur_children ];
}


__PACKAGE__->meta->make_immutable;

no Moose; 1;

__END__

=head1 NAME

Forest::Tree::Builder::SimpleTextFile - Parse trees from indented ASCII files

=head1 SYNOPSIS

    use Path::Class;

    my $file = file($path);

    my $builder = Forest::Tree::Builder::SimpleTextFile->new(
        fh => $file->openr,
    );

    my $tree = $builder->tree;

=head1 DESCRIPTION

This module replaces L<Forest::Tree::Reader::SimpleTextFile> with a declarative
api instead of an imperative one.

=head1 ATTRIBUTES

=over 4

=item fh

The filehandle to read from.

Required.

=item parser

A coderef that parses a single line from C<fh> and returns the node depth and
its value.

Defaults to space indented text. See also L</tab_width>.

=item tab_width

The indentation level for the default parser. Defaults to 4, which means that
four spaces equate to one level of nesting.

=back

=head1 BUGS

All complex software has bugs lurking in it, and this module is no
exception. If you find a bug please either email me, or add the bug
to cpan-RT.

=head1 AUTHOR

Yuval Kogman

=head1 COPYRIGHT AND LICENSE

Copyright 2008-2010 Infinity Interactive, Inc.

L<http://www.iinteractive.com>

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=cut