#!perl

our $DATE = '2017-08-05'; # DATE
our $VERSION = '0.001'; # VERSION

# IFUNBUILT
# use strict;
# use warnings;
# END IFUNBUILT

use Getopt::Long::EvenLess;

my %Opts = (
    config_path => "$ENV{HOME}/.envsetrc",
    action => 'run',
    variables => {},
);
my $Config;

sub get_options {
    GetOptions(
        'help|h|?' => sub {
            print <<'_';
envset - Run a command with sets of environment variables

Usage:
  envset [OPTS] <SET-SPEC> <CMD> [CMD_ARGS]...
  envset --list (-l)
  envset --help (-h, -?)
  envset --version (-v)

Options:
  --config-path=file, -c   Specify path for ~/.envsetrc

See documentation for more details.
_
                exit 0;
        },
        'version|v' => sub {
# IFUNBUILT
#             no warnings 'once';
# END IFUNBUILT
            print "envset version " . ($main::VERSION || "dev") . "\n";
            exit 0;
        },
        'config-path|c=s' => sub {
            $Opts{config_path} = $_[1];
        },
        'list|l' => sub {
            $Opts{action} = 'list';
        },
        'dump|d' => sub {
            $Opts{action} = 'dump';
        },
        'variable|V=s' => sub {
            $_[1] =~ /(.+?)=(.*)/ or
                die "Invalid syntax in --variable (-V), please use VAR=VALUE\n";
            $Opts{variables}{$1} = $2;
        },
    );
}

sub read_config {
    require Config::IOD::Reader;
    my $iod = Config::IOD::Reader->new(
        enable_expr => 1,
        expr_vars => $Opts{variables},
    );
    $Config = $iod->read_file($Opts{config_path});
}

sub run {
    read_config();
    if ($Opts{action} eq 'list') {
        for (sort keys %$Config) {
            next if $_ eq 'GLOBAL';
            print "$_\n";
        }
        exit 0;
    }

    die "envset: Please specify set-spec\n" unless @ARGV;
    my $spec0 = shift @ARGV;

    my %env;
    my @spec;
    while ($spec0 =~ /([+!]?)([^+!]+)/g) {
        push @spec, [$1, $2];
    }
    @spec or die "envset: Invalid set-spec '$spec0'\n";
    for my $spec (@spec) {
        my ($sym, $section) = @$spec;
        my $envs = $Config->{$section}
            or die "envset: Unknown set '$section'\n";
        for my $env (keys %$envs) {
            my $val = $envs->{$env};
            if (ref $val eq 'ARRAY') {
                $env{$env} ||= [];
                push @{ $env{$env} }, @$val;
            } else {
                $env{$env} = $val;
            }
        }
    }
    # stringify
    for my $env (keys %env) {
        if (ref $env{$env} eq 'ARRAY') {
            $env{$env} = join " ", @{ $env{$env} };
        }
    }

    if ($Opts{action} eq 'dump') {
        no warnings 'uninitialized';
        for my $env (sort keys %env) {
            print "$env=$env{$env}\n";
        }
        exit 0;
    }

    die "envset: Please specify command\n" unless @ARGV;
    for my $env (keys %env) {
        $ENV{$env} = $env{$env};
    }
    exec @ARGV;
}

### main

get_options();
run();

# ABSTRACT: Run command with sets of environment variables
# PODNAME: envset

__END__

=pod

=encoding UTF-8

=head1 NAME

envset - Run command with sets of environment variables

=head1 VERSION

This document describes version 0.001 of envset (from Perl distribution App-envset), released on 2017-08-05.

=head1 SYNOPSIS

List available environment sets defined in F<~/.envsetrc>:

 % envset

Run a command with the C<production> set:

 % envset production -- myscript.pl --script-opt blah --another-opt

Example F<~/.envsetrc>:

 [production]
 DB_HOST=myapp.example.com
 DB_NAME=myapp
 DB_USER=myapp
 DB_PASS=some-long-pazzword

 [dev]
 DB_HOST=127.0.0.1
 DB_NAME=myapp
 DB_USER=myapp
 DB_PASS=secret123

 [debug]
 TRACE=1
 PERL5OPT=["-d:Confess"] ; enable stack trace

 [lg-cs-firenze]
 PERL5OPT=["-MLog::ger::Screen::ColorScheme::Firenze"]

 [lg-cs-aspirinc]
 PERL5OPT=["-MLog::ger::Screen::ColorScheme::AspirinC"]

 [lg-cs-unlike]
 PERL5OPT=["-MLog::ger::Screen::ColorScheme::Unlike"]

Some more examples of setting environment:

Combine (array values will be joined):

 % envset production+lg-cs-unlike -- myscript.pl ...
 % envset dev+debug+lg-cs-unlike -- myscript.pl ...

=head1 DESCRIPTION

B<Early release. Some aspects might still change.>

=head1 TIPS

If you just want a shortcut for frequently used environment variables, you can
use shell aliases, e.g.:

 alias pg=PAGE_RESULT=1
 alias dbg="LOG=1 TRACE=1 PERL5OPT=-MLog::ger::App"

and then:

 % pg lcpan mods -n cpan rel
 % dbg myapp --arg1 --arg2 val --arg3

is equivalent to:

 % PAGE_RESULT=1 lcpan mods -n cpan rel
 % LOG=1 TRACE=1 PERLOPT=-MLog::ger::App dbg myapp --arg1 --arg2 val --arg3

L<envset> gives you dedicated configuration files and the ability to
add/subtract sets.

=head1 OPTIONS

=head2 --dump, -d

Instead of running the program, dump the environment variables to be set.

=head2 --list, -l

List all available environment variable sets (~ INI sections) in the
configuration file.

=head2 --variables, -V

Set variable to be used in expression. Expression is a value encoding in L<IOD>
which allows you to use some simple expressions to calculate the parameter
value, for example:

 [section]
 DEBUG=1
 PERL5OPT=!e "-MLog::ger::Screen::ColorSheme::" . $cs . " -MLog::ger::Output::$out -MSome::Module=debug," . val('DEBUG')

When you run B<envset> with:

 % envset -V cs=AspirinC -V out=Screen section --dump

you'll get:

 DEBUG=1
 PERL5OPT=-MLog::ger::Screen::ColorScheme::AspirinC -MLog::ger::Output::Screen -MSome::Module=debug,1

To refer to other parameters in the configuration file, use:

 val('DEBUG')
 val('section2.FOO')

To refer to a variable which will be supplied by C<-V>, use:

 $varname

=head1 FILES

F<~/.envsetrc>

=head1 TODO

Also look at other locations of F<.envsetrc> like F</etc/envsetrc>,
C<~/.config/.envsetrc>. Allow multiple C<-c>.

Document the syntax to subtract/undefine.

Tab completion.

=head1 HOMEPAGE

Please visit the project's homepage at L<https://metacpan.org/release/App-envset>.

=head1 SOURCE

Source repository is at L<https://github.com/perlancar/perl-App-envset>.

=head1 BUGS

Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=App-envset>

When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.

=head1 SEE ALSO

A similar npm package L<https://www.npmjs.com/package/envset>. The usage and
configuration syntax is almost identical with the following differences: 1) our
startup is a bit better :-) 2) we use L<IOD> for configuration format which is
INI with some extra features like merging, specifying array/hash, expressions &
variables; 3) we have options like C<--config-path>.

=head1 AUTHOR

perlancar <perlancar@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2017 by perlancar@cpan.org.

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
