#! /usr/bin/perl
#
#######################################################################
## @OPENSOURCE_LICENSE_START@
## libfixbuf 2.0
##
## Copyright 2018-2020 Carnegie Mellon University. All Rights Reserved.
##
## NO WARRANTY. THIS CARNEGIE MELLON UNIVERSITY AND SOFTWARE
## ENGINEERING INSTITUTE MATERIAL IS FURNISHED ON AN "AS-IS"
## BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND,
## EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT
## LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY,
## EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE
## MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF
## ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR
## COPYRIGHT INFRINGEMENT.
##
## Released under a GNU-Lesser GPL 3.0-style license, please see
## LICENSE.txt or contact permission@sei.cmu.edu for full terms.
##
## [DISTRIBUTION STATEMENT A] This material has been approved for
## public release and unlimited distribution.  Please see Copyright
## notice for non-US Government use and distribution.
##
## Carnegie Mellon(R) and CERT(R) are registered in the U.S. Patent
## and Trademark Office by Carnegie Mellon University.
##
## DM18-0325
## @OPENSOURCE_LICENSE_END@
#######################################################################
# make-infomodel
#
# Generate infomodel.c and infomodel.h files
#######################################################################

use strict;
use warnings;

use Getopt::Long qw(:config gnu_compat permute no_getopt_compat no_bundling);
use Pod::Usage;
use File::Temp;

### Argument processing

my $opt_out_file = 'infomodel';
my $opt_dir_name = 'infomodel';
my $opt_static_array = 'infomodel_array_static_';
my $opt_package = '';

# base-names of *.xml and *.i files
my @names = ();

my $appname = $0;
$appname =~ s/.*\///;

parse_options();

create_header_file("$opt_out_file.h");
create_source_file("$opt_out_file.c");

exit 0;


# Helper functions

#  ##################################################################
#
#  create_header_file($destination)
#
#    Creates the .h file and saves it to $destination.  Does not
#    replace an existing file if the generated file is identical to
#    it.
#
sub create_header_file
{
    my ($header_file) = @_;

    # Create a temporary file
    my ($fh, $temp) = File::Temp::tempfile(UNLINK => 1, DIR => '.');
    select $fh;

    # CPP macro to protect from multiple inclusion
    my $guardname = '_GUARD_'.uc($opt_out_file).'_H';
    $guardname =~ s/\W/_/g;

    print <<EOF;
/* This file was automatically generated by the $appname script
 * using the "*.xml" files in the $opt_dir_name directory.
 */

#ifndef $guardname
#define $guardname

#include <fixbuf/public.h>

/**
 *    Updates the information model 'model' with the elements defined
 *    in the *.i files, created from the corresponding *.xml files.
 */
#define infomodelAddGlobalElements infomodelAddGlobalElements$opt_package
void infomodelAddGlobalElements(fbInfoModel_t *model);

/**
 *    Returns a handle to the information element array defined in the
 *    file whose name is '<name>.i', created from '<name>.xml'.
 *    Returns NULL if 'name' is NULL or does not name a known file.
 */
#define infomodelGetArrayByName infomodelGetArrayByName$opt_package
const fbInfoElement_t *infomodelGetArrayByName(const char *name);

/**
 *    Returns the number of elements (including the final FB_IE_NULL)
 *    in the information element array defined in the file whose name
 *    is '<name>.i', created from '<name>.xml'.  Returns 0 if 'name'
 *    is NULL or does not name a known file.
 */
#define infomodelGetArrayLengthByName infomodelGetArrayLengthByName$opt_package
size_t infomodelGetArrayLengthByName(const char *name);

#endif  /* $guardname */

/*
** Local Variables:
** mode:c
** indent-tabs-mode:nil
** c-basic-offset:4
** End:
*/
EOF

    #  Close the .h file and copy it into place unless it is the same
    #  as the existing file.
    select STDOUT;
    close $fh;

    if (! -f $header_file || 0 != system "cmp", "-s", $temp, $header_file) {
        system "cp", $temp, $header_file
            and die "Unable to cp $temp $header_file: $!\n";
    }
}



#  ##################################################################
#
#  create_source_file($destination)
#
#    Creates the .c file and saves it to $destination.  Does not
#    replace an existing file if the generated file is identical to
#    it.
#
sub create_source_file
{
    my ($source_file) = @_;

    # Create a temporary file
    my ($fh, $temp) = File::Temp::tempfile(UNLINK => 1, DIR => '.');
    select $fh;

    print <<EOF;
/* This file was automatically generated by the $appname script
 * using the "*.xml" files in the $opt_dir_name directory.
 */

#include "$opt_out_file.h"


/*  Include each generated file not marked as excluded. */

/* FOREACH */

EOF

    for my $n (@names) {
        print <<EOF;
#if !defined(INFOMODEL_EXCLUDE_$n)
#include "$opt_dir_name/$n.i"
#endif  /* !defined(INFOMODEL_EXCLUDE_$n) */

EOF
    }

    print <<EOF;
/* END_FOREACH */

void infomodelAddGlobalElements(fbInfoModel_t *model)
{
    if (!model) {
        return;
    }
    /* FOREACH */

EOF

    for my $n (@names) {
        print <<EOF;
#if !defined(INFOMODEL_EXCLUDE_$n)
    fbInfoModelAddElementArray(model, $opt_static_array$n);
#endif

EOF
    }

    print <<EOF;
    /* END_FOREACH */
}

const fbInfoElement_t *infomodelGetArrayByName(const char *name)
{
    if (!name) {
        return NULL;
    }
    /* FOREACH*/

EOF

    for my $n (@names) {
        print <<EOF;
#if !defined(INFOMODEL_EXCLUDE_$n)
    if (0 == strcmp(name, "$n")) {
        return $opt_static_array$n;
    }
#endif

EOF
    }

    print <<EOF;
    /* END_FOREACH */
    return NULL;
}

size_t infomodelGetArrayLengthByName(const char *name)
{
    if (!name) {
        return 0;
    }
    /* FOREACH*/

EOF

    for my $n (@names) {
        print <<EOF;
#if !defined(INFOMODEL_EXCLUDE_$n)
    if (0 == strcmp(name, "$n")) {
        return sizeof($opt_static_array$n)/sizeof($opt_static_array$n\[0\]);
    }
#endif

EOF
    }

    print <<EOF;
    /* END_FOREACH */
    return 0;
}

/*
** Local Variables:
** mode:c
** indent-tabs-mode:nil
** c-basic-offset:4
** End:
*/
EOF

    #  Close the .c file and copy it into place unless it is the same
    #  as the existing file..  Always replace the
    #  .c file so make knows the file is up-to-date.
    select STDOUT;
    close $fh;

    if (! -f $source_file || 0 != system "cmp", "-s", $temp, $source_file) {
        system "cp", $temp, $source_file
            and die "Unable to cp $temp $source_file: $!\n";
    }
}


#  ##################################################################
#
#  parse_options()
#
#    Parse the options.
#
sub parse_options
{
    my $opt_help;
    my $opt_man;
    my $opt_version;
    my $opt_outfile;

    # process options.  see "man Getopt::Long"
    GetOptions(
        'out-file=s',       \$opt_out_file,
        'dir-name=s',       \$opt_dir_name,
        'static-array=s',   \$opt_static_array,
        'package=s',        \$opt_package,

        'help',    \$opt_help,
        'man',     \$opt_man,
        'version', \$opt_version,

        ) or pod2usage( -exitval => -1 );

    pod2usage( -exitval => 0 ) if $opt_help;
    pod2usage( -exitval => 0, -verbose => 2 ) if $opt_man;
    tool_version_exit() if $opt_version;

    # ensure $opt_package is valid C by changing each run of one or
    # more consecutive non-word characters to a single underscore
    $opt_package =~ s/\W+/_/g;

    $opt_package = '_'.$opt_package;

    @names = @ARGV;
}


# Generate output for --version: Print version and exit.
sub tool_version_exit()
{
    print <<EOF;
Copyright (C) 2018-2020 by Carnegie Mellon University
GNU General Public License (GPL) Rights pursuant to Version 2, June 1991.
Government Purpose License Rights (GPLR) pursuant to DFARS 252.227.7013.
EOF
    exit;
}

__END__

=head1 NAME

B<make-infomodel> - Creates infomodel.c and infomodel.h files.

=head1 SYNOPSIS

 make-infomodel [options] INPUT_FILE_BASENAME [INPUT_FILE_BASENAME...]

=head1 DESCRIPTION

B<make-infomodel> uses the INPUT_FILE_BASENAME values specified on the
command line to create a C source file and a corresponding header
file.  The generated files #include the input files and define global
variables that may be used to reference the arrays defined in the
input files.

=head1 OPTIONS

Option names may be abbreviated if the abbreviation is unique or is an
exact match for an option.  A parameter to an option may be specified
as B<--arg>=I<param> or B<--arg> I<param>, though the first form is
required for options that take optional parameters.

If the files already exists, they are not overwritten when the
generated files exactly match the existing files.

=over 4

=item B<--out-file>=I<OUTPUT_BASENMAE>

Specifies the base name of the generated .h and .c files.  When not
specified, the name C<infomodel> is used.

=item B<--static-array>=I<STATIC_ARRAY>

Specifies the first part of the name of the static array defined in
each *.i file.  When not specified, the name
C<infomodel_array_static_> is used.

=item B<--dir-name>=I<DIR_NAME>

Specifies the directory where each .i file is located.  Used by the
#include statements in the generated C file.

=item B<--package>=I<PACKAGE_NAME>

Specifies the name of the package or program this file is being
generated for.  This name is used to generate a unique symbol for the
exported functions in the generated C files.

=item B<--help>

Display a brief usage message and exit.

=item B<--man>

Display full documentation for B<make-infomodel> and exit.

=item B<--version>

Print the version number and exit the application.

=back

=cut

# Local Variables:
# mode:perl
# indent-tabs-mode:nil
# End:
