The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl 

use strict;
use warnings;
use Getopt::Compact::WithCmd;
use Proclet;
use YAML::XS;
use utf8;

our $VERSION = $Proclet::VERSION;

my %opt;
$opt{concurrency} = '';
$opt{procfile} = 'Procfile';
$opt{envfile} = '';
$opt{color} = 0;

my $go = Getopt::Compact::WithCmd->new(
    name => 'proclet',
    version => $Proclet::VERSION,
    args => '[service]',
    command_struct => {
        'start' => {
            options => [
                [
                    [qw/c concurrency/],
                    'the number of each process type to run. The value passed in should be in the format process=num,process=num',
                    '=s',
                    \$opt{concurrency},
                    { required => 0 },
                ],
                [
                    [qw/e env/],
                    'an alternate environment file. You can specify more than one file by using: --env file1,file2.',
                    '=s',
                    \$opt{envfile},
                    { required => 0 }
                ],
                [
                    [qw/f procfile/],
                    'an alternate Procfile to load, implies -d at the Procfile root',
                    '=s',
                    \$opt{procfile},
                    { required => 0 }
                ], 
                [
                    [qw/color/],
                    'colored log',
                    '!',
                    \$opt{color},
                    { required => 0 }
                ], 

            ],
        }
   },
);

my $cmd  = $go->command || $go->show_usage;
$go->show_usage if $go->opts->{help};
my ($args) = @{$go->args};

if ( -f '.foreman' ) {
    my $yaml_opt;
    eval { 
        $yaml_opt = YAML::XS::LoadFile('.foreman');
    };
    die 'cannot load .foreman as yaml: '. $@ if $@;
    %opt = (
        %opt,
        %$yaml_opt
    );
}

if ( $cmd eq 'start' ) {

    if ( ! length($opt{envfile}) && -f '.env') {
        load_envfile('.env');
    }
    else {
        my @envfile = split /,/, $opt{envfile};
        load_envfile($_) for @envfile;
    }

    my $services = load_procfile($opt{procfile});
    my $concurrency = parse_concurrency($opt{concurrency});

    die 'no services defined' unless keys %$services;

    my $proclet = Proclet->new( color => $opt{color} );
    for my $process ( keys %$services ) {
        next if defined $args && $process ne $args;
        my $worker = exists $concurrency->{$process} ? $concurrency->{$process} : 1;
        next if $worker == 0;
        $proclet->service(
            tag => $process,
            code => $services->{$process},
            worker => $worker, 
        );
    }
    $proclet->run();
}
else {
    $go->show_usage()
}


sub load_procfile {
    my $file = shift;
    my %services;
    open(my $fh, '<:utf8', $file) or die "cannot load procfile $file: $!";
    while (my $line = <$fh>) {
        if (my ($name, $command) = ($line =~ /^([^:]+)\s*:\s*(.+)/)) {
            $services{$name} = $command;
        }
    }
    return \%services;
}

sub load_envfile {
    my $file = shift;    
    open(my $fh, '<:utf8', $file) or die "cannot load envfile $file: $!";
    while (my $line = <$fh>) {
        if (my ($name, $val) = ($line =~ /^([^=]+)\s*=\s*(.+)/)) {
            $ENV{$name} = $val;
        }
    }
}

sub parse_concurrency {
    my $opt = shift;
    my %concurrency;
    for my $process ( split /,/, $opt ) {
        if ( $process =~ /^\s*([^=]+)\s*=\s*(\d+)\s*$/ ) {
            $concurrency{$1} = $2;
        }
        else {
            die "incorrect concurrency option, near '$process'";
        }
    }
    return \%concurrency;
}

__END__

=head1 NAME

proclet - foreman for perl

=head1 SYNOPSIS

  $ cat Procfile
  memd: memcached -v -p 11211
  plack: plackup -p 9022 -e 'sub { [200, [], ["OK"]] }'
  $ proclet start

=head1 DESCRIPTION

proclet is foreman for perl, manages Procfile-based applications.

proclet does not support B<EXPORT> yet.

=head1 RUNNING

B<proclet start> is used to run your application directly from the command line.

The following options control how the application is run:

=over 4

=item -h, --help

Display help message

=item -c, --concurrency: Str

The number of each process type to run. The value passed in should be in the format process=num,process=num

=item -e, --env: Str

An alternate environment file. You can specify more than one file by using: --env file1,file2.

=item -f, --procfile: Str

An alternate Procfile to load, implies -d at the Procfile root

=item --color

Colored log

=back

=head1 PROCFILE

A Procfile should contain both a name for the process and the command used to run it.

  web: bundle exec thin start
  job: bundle exec rake jobs:work

=head1 ENVIRONMENT

If a B<.env> file exists in the current directory, the default environment will be read from it. This file should contain key/value pairs, separated by =, with one key/value pair per line.

  FOO=bar
  BAZ=qux

=head1 DEFAULT OPTIONS

If a B<.foreman> file exists in the current directory, default options will be read from it. This file should be in YAML format with the long option name as keys. Example:

  concurrency: alpha=0,bravo=1
  color: 1

=head1 AUTHOR

Masahiro Nagano E<lt>kazeburo {at} gmail.comE<gt>

=head1 SEE ALSO

L<Proclet>, L<https://github.com/ddollar/foreman>

=head1 LICENSE

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

=cut