#!/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