#!/usr/bin/perl
use strict; use warnings;
use JSON ();
use JSON::PP ();
use Getopt::Long qw(:config no_ignore_case bundling);
use File::Slurp;
use FindBin;

my %opts = (
    list => 0
    );
my $usage = <<EOT;
usage: $0 [OPTIONS..]
  --list            print JSON inventory
EOT

GetOptions(
    "list" => \$opts{list},
    "help|h" => sub {print $usage; exit 0}
    ) or die $usage;


my @inventory = read_file("$FindBin::Bin/../inventory");
my %groups;
my %hostvars;
my %blockvars;
my @blockgroups;
for (my $i = 0; $i <= $#inventory; $i++) {
    my $line = $inventory[$i];
    chomp $line;
    next if $line =~ /^\s*#/;
    $line =~ s/\s*$//;
    if ($line =~ /^\s+/) {
        print STDERR "Syntax error (leading whitespace) in line ". ($i + 1) .
            ": $line\n";
        next;
    }
    next if $line !~ /\S/;
    # continuation lines (leading whitespace):
    while ($i + 1 <= $#inventory) {
        if ($inventory[$i + 1] =~ /^\s+#/) {
            $i++;
            next;
        }
        last if $inventory[$i + 1] !~ /^\s+(\S.*)/;
        $i++;
        my $cont = $1;
        $cont =~ s/\s*$//;
        $line .= " ". $cont;
    }
    # group definition: [group < parent + group < parent...] default_vars...
    if ($line =~ /^\[([a-z0-9_< +]+)\](\s*.*)/i) {
        my ($grouptext, $vars) = ($1, $2);
        @blockgroups = ();
        for my $x (split /\+/, $grouptext) {
            if ($x !~ /^\s*([a-z0-9_]+)(\s*<\s*([a-z0-9_]+))?\s*$/) {
                print STDERR "Invalid group definition in line ". ($i + 1) .
                    ": $line\n";
                next;
            }
            my ($group, $parent) = ($1, $3);
            push @blockgroups, $group;
            $groups{$group} = {hosts => []} if !exists $groups{$group};
            if ($parent) {
                $groups{$parent} = {hosts => []} if !exists $groups{$parent};
                $groups{$parent}->{children} = []
                    if !exists $groups{$parent}->{children};
                push @{$groups{$parent}->{children}}, $group;
            }
        }
        %blockvars = ();
        if ($vars and $vars =~ s/\s+(\{.*\})//) {
            my $jsonstring = $1;
            my $jo = JSON::PP->new->relaxed->allow_singlequote->allow_barekey;
            my $data;
            eval {
                $data = $jo->decode($jsonstring);
            };
            if ($@) {
                print STDERR "Invalid JSON in line ". ($i + 1) .": $jsonstring\n";
            }
            for my $k (keys %$data) {
                $blockvars{$k} = $data->{$k};
            }

        }
        if ($vars) {
            while ($vars =~ /\s+([a-z0-9_]+)=(\S*)/gci) {
                my ($a, $v) = ($1, $2);
                $blockvars{$a} = $v;
            }
            if (length $vars != pos $vars) {
                print STDERR "Invalid parameters in line ". ($i + 1) .
                    ": $line\n";
            }
        }
        next;
    }
    if ($line !~ /^(\S+)\s+(\S+)(\s+\[(\S+)\])?(\s+.*)?/) {
        print STDERR "Syntax error in line ". ($i + 1) .": $line\n";
        next;
    }
    my ($name, $addr, $gr, $rest) = ($1, $2, $4, $5);

    my @gr;
    @gr = split /,/, $gr if $gr;
    push @gr, @blockgroups;
    for my $g (@gr) {
        push @{$groups{$g}->{hosts}}, $name;
    }
    if (exists $hostvars{$name}) {
        print STDERR "Duplicate host definition in line ". ($i + 1) .": $line\n";
    }
    $hostvars{$name} = { %blockvars };
    $hostvars{$name}->{ansible_host} = $addr;
    if ($rest and $rest =~ s/\s+(\{.*\})//) {
        my $jsonstring = $1;
        my $jo = JSON::PP->new->relaxed->allow_singlequote->allow_barekey;
        my $data;
        eval {
            $data = $jo->decode($jsonstring);
        };
        if ($@) {
            print STDERR "Invalid JSON in line ". ($i + 1) .": $jsonstring\n";
        }
        for my $k (keys %$data) {
            $hostvars{$name}->{$k} = $data->{$k};
        }

    }
    if ($rest) {
        while ($rest =~ /\s+([a-z0-9_]+)=(\S*)/gci) {
            my ($a, $v) = ($1, $2);
            $hostvars{$name}->{$a} = $v;
        }
        if (length $rest != pos $rest) {
            print STDERR "Invalid parameters in line ". ($i + 1) .": $line\n";
        }
    }
}

$groups{_meta}->{hostvars} = \%hostvars;
    

if ($opts{list}) {
    my $json = JSON->new->canonical->pretty;
    print $json->encode(\%groups);
} else {
    printf "%i hosts in %i groups.\n",
        scalar keys %hostvars, (scalar keys %groups) - 1;
}

__END__
# vim: set tabstop=4 shiftwidth=4 expandtab smarttab:
