#!/bin/bash
# vim: expandtab:ts=4:sw=4:syntax=perl

# read rc files if exist
unset PROFILEDOTD
[ -e /etc/thruk/thruk.env  ] && . /etc/thruk/thruk.env
[ -e ~/etc/thruk/thruk.env ] && . ~/etc/thruk/thruk.env
[ -e ~/.thruk              ] && . ~/.thruk
[ -e ~/.profile            ] && . ~/.profile

BASEDIR=$(dirname $0)/..

# git version
if [ -d $BASEDIR/.git -a -e $BASEDIR/lib/Thruk.pm ]; then
  export PERL5LIB="$BASEDIR/lib:$BASEDIR/plugins/plugins-available/conf/lib:$PERL5LIB";
  if [ "$OMD_ROOT" != "" -a "$THRUK_CONFIG" = "" ]; then export THRUK_CONFIG="$OMD_ROOT/etc/thruk"; fi
  if [ "$THRUK_CONFIG" = "" ]; then export THRUK_CONFIG="$BASEDIR/"; fi

# omd
elif [ "$OMD_ROOT" != "" ]; then
  export PERL5LIB=$OMD_ROOT/share/thruk/lib:$OMD_ROOT/share/thruk/plugins/plugins-available/conf/lib:$PERL5LIB
  if [ "$THRUK_CONFIG" = "" ]; then export THRUK_CONFIG="$OMD_ROOT/etc/thruk"; fi

# pkg installation
else
  export PERL5LIB=$PERL5LIB:@DATADIR@/lib:@DATADIR@/plugins/plugins-available/conf/lib:@THRUKLIBS@;
  if [ "$THRUK_CONFIG" = "" ]; then export THRUK_CONFIG='@SYSCONFDIR@'; fi
fi

eval 'exec perl -x $0 ${1+"$@"} ;'
    if 0;
# / this slash makes vscode syntax highlighting work
#! -*- perl -*-
#line 35

##############################################
use warnings;
use strict;

my $naglint = Thruk::naglint->new();
exit $naglint->run();
##############################################

=head1 NAME

naglint - beautify nagios config files

=head1 SYNOPSIS

  Usage: naglint [options] <file> [<files...>]

  Options:
  -h, --help                    Show this help message and exit
  -v, --verbose                 Print verbose output
  -V, --version                 Print version
  -c, --config                  config file. Default is ~/.naglintrc
  -i                            replace content inline instead of printing to stdout
  -r                            read directories recursivly
      --core                    core type can be nagios, icinga or shinken. Will be
                                autodetected unless specified.

=head1 DESCRIPTION

This script beautifies nagios config files.

  - clean whitespace
  - clean indention
  - remove deprecated attributes
  - validate duplicate or invalid attributes
  - break long command lines into shorter pieces
  - sort attributes naturally

=head1 OPTIONS

naglint has the following arguments:

=over 4

=item B<-h> , B<--help>

    print help and exit

=item B<-v> , B<--verbose>

    print verbose output too

=item B<-c> , B<--config>

    use different config file. Default locations are ~/.naglintrc and /etc/thruk/naglint.conf

=item B<-V> , B<--version>

    print version and exit

=item B<--core>

    use specific config type. can be 'nagios', 'icinga' or 'shinken'.
    enables core type specific attributes. Will be autodetected unless
    specified.

=item B<-i>

    edit given files inline and overwrite them with their beautified objects

=item B<-r>

    read directories recursivly

=back

=head1 RETURN VALUE

returns 0 on success or number of errors otherwise

=head1 CONFIG

see FILES section for where to store config options.

=over

=item indent_object_key

    Number of spaces before object attribute names. Default 2

=item indent_object_value

    Number of spaces before objects attribute values. Default 30

=item indent_object_comments

    Number of spaces before inline comments. Default 68

=item list_join_string

    Should list attributes be joined with space or not. Default ','

=item break_long_arguments

    Should long arguments be broken into multiple lines. Default 1

=item object_attribute_key_order

    list of attributes which should be on top of the object

=item object_cust_var_order

    list of custom variables which will be put on top of other custom variables

=back

=head1 EXAMPLES

Beautify single config file

  %> naglint objects.cfg > beauty.cfg

Beautify and replace single config file inline

  %> naglint -i objects.cfg

Process objects from STDIN

  %> cat objects.cfg | naglint > beauty.cfg

=head1 FILES

~/.naglintrc

  user config file

/etc/thruk/naglint.conf

  global config file

=head1 AUTHOR

2012, Sven Nierlein, <sven@nierlein.de>

=cut

##############################################

package Thruk::naglint;

use warnings;
use strict;
use Getopt::Long;
use Pod::Usage;

use Monitoring::Config ();
use Monitoring::Config::File ();
use Thruk::Config 'noautoload';
use Thruk::Utils::Scripts ();

##############################################
sub new {
    my($class) = @_;
    my $self = {};
    bless $self, $class;
    return $self;
}

##############################################
sub run {
    my($self) = @_;

    $self->{'opt'} = {
        'verbose'  => 0,
        'files'    => [],
        'inline'   => 0,
        'coretype' => 'any',
        'config'   => '',
    };
    Getopt::Long::Configure('no_ignore_case');
    Getopt::Long::Configure('bundling');
    GetOptions (
       "h|help"             => \$self->{'opt'}->{'help'},
       "v|verbose"          => \$self->{'opt'}->{'verbose'},
       "V|version"          => \$self->{'opt'}->{'version'},
       "config=s"           => \$self->{'opt'}->{'config'},
       "c|core=s"           => \$self->{'opt'}->{'coretype'},
       "i"                  => \$self->{'opt'}->{'inline'},
       "r"                  => \$self->{'opt'}->{'recursive'},
       "<>"                 => sub { push @{$self->{'opt'}->{'files'}}, $_[0] },
    ) or _usage('error in options');

    if($self->{'opt'}->{'version'}) { print "naglint v",Thruk::Config::get_thruk_version(),"\n"; exit 0; }
    pod2usage( { -verbose => 2, -exit => 3 } ) if $self->{'opt'}->{'help'};

    # do we have a rc file?
    Monitoring::Config->read_rc_file($self->{'opt'}->{'config'});

    ##############################################
    # make sure we don't touch owner and permissions
    local $ENV{'THRUK_NO_TOUCH_PERM'} = 1;

    my $errors = 0;

    # attached to a terminal?
    if (!-t STDIN and scalar @{$self->{'opt'}->{'files'}} == 0) {
        my $file = Monitoring::Config::File->new('virt.cfg', [], $self->{'opt'}->{'coretype'});
        my $text = "";
        while(<STDIN>) {
            $text .= $_;
            if($_ =~ m/^\s*}\s*$/mx) {
                $file->update_objects_from_text($text);
                $errors += Thruk::Utils::Scripts::print_errors($file);
                print $file->get_new_file_content();
                $text = '';
            }
        }
        $file->update_objects_from_text($text);
        $errors += Thruk::Utils::Scripts::print_errors($file);
        print $file->get_new_file_content();
        exit $errors;
    }

    _usage('no input files!') if scalar @{$self->{'opt'}->{'files'}} == 0;

    # iterate all given files
    for my $filename (@{$self->{'opt'}->{'files'}}) {
        $errors += $self->_process_file($filename);
    }

    return $errors;
}


##############################################
# SUBS
##############################################
sub _usage {
    my($msg) = @_;
    print $msg."\n" if $msg;
    print "Usage: naglint [options] <file> [<files...>]\n";
    print "Detailed help available with --help\n";
    exit;
}

##############################################
sub _process_file {
    my($self, $filename) = @_;
    if(!-e $filename) {
        print STDERR "ERROR: ", $filename, " ", $!, "\n";
        return 1;
    }
    if(-d $filename) {
        if(!$self->{'opt'}->{'recursive'}) {
            print STDERR "ERROR: won't process directory unless -r specified\n";
            return 1;
        } else {
            my $errors = 0;
            my $mc     = Monitoring::Config->new();
            my $files  = $mc->_get_files_for_folder($filename);
            for my $f (@{$files}) {
                $errors += $self->_process_file($f);
            }
            return $errors;
        }
    }

    print STDERR "parsing ", $filename, "\n" if $self->{'opt'}->{'verbose'};
    my $file = Monitoring::Config::File->new($filename, [], $self->{'opt'}->{'coretype'});
    die("could not create file object") unless defined $file;
    $file->update_objects();
    $file->{'changed'} = 1; # new content will only be returned for changed files
    return 1 if Thruk::Utils::Scripts::print_errors($file);
    if($self->{'opt'}->{'inline'}) {
        $file->save();
        print STDERR "wrote ", $filename, "\n";
    } else {
        print $file->get_new_file_content();
    }
    return 0;
}

##############################################

1;
