The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use strict;
use warnings;
package Term::Spinner::Lite;
{
  $Term::Spinner::Lite::VERSION = '0.03';
}

# ABSTRACT: A spinner without so much Moose in it

use 5.010;
use IO::Handle;
use Time::HiRes qw(usleep);
use Carp qw( croak );



sub new {
    my ($class, %args) = @_;

    my $self = bless \%args, $class;

    $self->_setup_signal_handler;

    return $self;
}

# We need to restore the cursor if the user hits Contol-C or a TERM signal
sub _setup_signal_handler {
    my $self = shift;

    $SIG{INT} = sub { $self->done; exit 0; };
    $SIG{TERM} = sub { $self->done; exit 0; };
}


sub output_handle {
    my $self = shift;
    my $handle = shift;
    my $encoding = shift || ":utf8";

    if ( not $handle ) {
        if ( exists $self->{'output_handle'} ) {
            return $self->{'output_handle'};
        }
        $handle = \*STDERR;
    }

    binmode($handle, $encoding);
    $handle->autoflush(1);

    $self->{'output_handle'} = $handle;
}


sub spin_chars {
    my $self = shift;
    my $aref = shift;

    if ( not $aref ) {
        if ( exists $self->{'spin_chars'} ) {
            return $self->{'spin_chars'};
        }
        $aref = [ qw(- \ | /) ];
    }
        
    if ( ref($aref) ne 'ARRAY' ) {
        croak "spin_chars must be an array ref";
    }

    $self->{'spin_chars'} = $aref;
}


sub delay {
    my $self = shift;
    my $microseconds = shift;

    if ( not $microseconds ) {
        if ( exists $self->{'delay'} ) {
            return $self->{'delay'};
        }
        $microseconds = 0;
    }

    $self->{'delay'} = $microseconds;
}

sub _clear {
    my $self = shift;

    print {$self->output_handle()} "\010 \010";
}

# https://github.com/verigak/progress/blob/master/progress/helpers.py#L19
sub _show_cursor {
    my $self = shift;

    print {$self->output_handle()} "\x1b[?25h";
}

# https://github.com/verigak/progress/blob/master/progress/helpers.py#L18
sub _hide_cursor {
    my $self = shift;

    print {$self->output_handle()} "\x1b[?25l";
}

sub _spin_char_size {
    my $self = shift;

    if ( exists $self->{'spin_char_size'} ) {
        return $self->{'spin_char_size'};
    }

    $self->{'spin_char_size'} = scalar @{ $self->spin_chars() };
}


sub count {
    my $self = shift;

    return 0 if not exists $self->{'count'};

    return $self->{'count'};
}


sub next {
    my $self = shift;

    state $pos = 0;

    $self->_clear if $self->count;
    $self->_hide_cursor if not $pos;
    print {$self->output_handle()} "${$self->spin_chars()}[$pos]";
    $pos = ($self->{'count'}++) % $self->_spin_char_size();
    usleep($self->delay) if $self->delay;
}


sub done {
    my $self = shift;

    $self->_clear;
    $self->_show_cursor;
    print "\n" if $_[0];
}

1;

__END__

=pod

=head1 NAME

Term::Spinner::Lite - A spinner without so much Moose in it

=head1 VERSION

version 0.03

=head1 SYNOPSIS

  use utf8;
  use 5.010;
  use Term::Spinner::Lite;
  my $s = Term::Spinner::Lite->new(
    delay => 100_000,
    spin_chars => ['◑', '◒', '◐', '◓'],
  );

  $s->next() for 1 .. 100_000;
  $s->done();

This is a simple spinner, useful when you want to show some kind of activity 
during a long-running activity of non-determinant length.  It's loosely based
on the API from L<Term::Spinner>.  Unlike L<Term::Spinner> though, this module
doesn't have any dependencies outside of modules shipped with Perl itself.

=head1 ATTRIBUTES

=head2 output_handle

Gets or sets the handle where output will be written. By default, uses STDERR.
You may pass an optional PerlIO encoding specification. By default it will use
":utf8" in case you want to use UTF8 characters in your spinner.

=head2 spin_chars

Gets or sets the list of characters to cycle through. By default it uses the 
sequence "-" "\" "|" "/".  This attribute must be set using an array ref, e.g.,

  [ qw( . o O o ) ]

This attribute will croak by an attempt to set itself using anything else.

=head2 delay

Gets or sets the delay between state changes in microseconds. (A good value 
for smooth spinning is 100000.) Defaults to 0.

=head1 METHODS

=head2 new()

The object constructor.  You may optionally set object attributes C<spin_chars> or
C<output_handle> in the parameter list.

=head2 count()

Return the number of advances through the spin sequence.

=head2 next()

Advance the spinner state to the next character in the spin sequence and output 
it to C<output_handle>. 

=head2 done()

Finish spinning, and clear the last character printed. If a true value is passed,
output a newline.

=head1 AUTHOR

Mark Allen <mrallen1@yahoo.com>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2012 by Mark Allen.

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

=cut