#!/bin/sh

# Copyright (c) 2010 by Ladislav Lhotka, CZ.NIC <lhotka@nic.cz>
#
# Translates YANG module(s) to DSDL schemas and validates instances
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
# 
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

# Defaults for arguments
target="data"
basename=""
dir="."
instance=""
pyang_opts="--dsdl-no-documentation --dsdl-no-dublin-core"
gen_rng_parms=""
noschema=0
onlydata=0
hello=0
jing=0

xslt_dir=${PYANG_XSLT_DIR:-/usr/share/yang/xslt}
schema_dir=${PYANG_RNG_LIBDIR:-/usr/share/yang/schema}

usage() {
    cat <<EOF

Usage: 1. yang2dsdl [-t <target>] [-d <dir>] [-b <basename>] \\
                    [-j] [-v <instance>] <module> ...
       2. yang2dsdl -L [-t <target>] [-d <dir>] [-b <basename>] \\
                    [-j] [-v <instance>] <server-hello>
       3. yang2dsdl -s [-t target] [-d <dir>] [-j] -b <basename> \\
                    -v <instance>
       4. yang2dsdl -h

Generates DSDL schemas from one or more YANG modules and
validates instance documents.

Options:
  -h             Displays this help message and exits.
  -t <target>    Specifies the validation target. The argument <target>
                 must be one of: "data" (default), "config", "get-reply",
                 "get-config-reply", "edit-config", "rpc", "rpc-reply"
                 and "notification".
  -b <basename>  Specifies the basename of files in which the output
                 schemas are stored. The default is the concatenation
                 of the names of all input YANG modules connected
                 with the underscore character "_".
  -L             Use server hello message instead of YANG modules as
                 the only input file.
                 (By default, all are considered available.)
  -d <dir>       Specifies the directory for output files
                 (current directory by default).
  -v <instance>  Validates the instance document in file <instance>.
  -j             Use jing for RELAX NG validation instead of xmllint.
  -s             Performs just validation, without (re)generating
                 the schemas. Only allowed together with -v and -b.
EOF
}

gen_relaxng() {
    echo "== Generating RELAX NG schema '$btname.rng'"
    rngparms="$XSLT_OPTS $gen_rng_parms --stringparam target $target \
	--stringparam basename $basename \
        --stringparam schema-dir $schema_dir \
        $xslt_dir/gen-relaxng.xsl $hybs"
    xsltproc --output $btname.rng $rngparms
    [ $? -eq 0 ] || exit 2
    xsltproc --output $gdefsname --stringparam gdefs-only 1 $rngparms
    [ $? -eq 0 ] || exit 2
    echo "Done."
}

gen_schematron() {
    printf "\n== Generating Schematron schema '$btname.sch'\n"
    xsltproc $XSLT_OPTS --output $btname.sch \
        --stringparam target $target $xslt_dir/gen-schematron.xsl $hybs
    [ $? -eq 0 ] || exit 2
    echo "Done."
}

gen_dsrl() {
    printf "\n== Generating DSRL schema '$btname.dsrl'\n"
    xsltproc $XSLT_OPTS --output $btname.dsrl \
        --stringparam target $target $xslt_dir/gen-dsrl.xsl $hybs
    [ $? -eq 0 ] || exit 2
    echo "Done."
}

validate() {
    printf "\n== Validating grammar and datatypes ...\n"
    if [ -f $btname.rng ] ; then
	if [ "$jing" = "0" ] ; then
	    xmllint --noout --relaxng $btname.rng $instance
	    [ $? -eq 0 ] || exit 3
	else
	    jing $btname.rng $instance
	    [ $? -eq 0 ] || exit 3
	    echo $instance validates.
	fi
    else
        echo "RELAX NG schema '$btname.rng' not found."
	exit 2
    fi
    if [ "$target" = "edit-config" ] ; then
	return
    fi
    printf "\n== Adding default values... "
    if [ -f $btname.dsrl ] ; then
        xsltproc -o $dsrlxsl $xslt_dir/dsrl2xslt.xsl $btname.dsrl
	[ $? -eq 0 ] || exit 2
        xsltproc -o $instwdef $dsrlxsl $instance
	[ $? -eq 0 ] || exit 3
        inst4sch=$instwdef
        printf "done.\n"
    else
        echo "DSRL schema '$btname.dsrl' not found, no defaults added ..."
        inst4sch=$instance
    fi
    printf "\n== Validating semantic constraints ...\n"
    if [ -f $btname.sch ] ; then
        xsltproc $xslt_dir/iso_abstract_expand.xsl $btname.sch | \
            xsltproc -o $schxsl $xslt_dir/iso_svrl_for_xslt1.xsl -
        xsltproc $schxsl $inst4sch | xsltproc $xslt_dir/svrl2text.xsl -
	[ $? -eq 0 ] || exit 3
    else
        echo "Schematron schema '$btname.sch' not found, skipping ..."
    fi
}

cleanup () {
    rm -f $hybs $schxsl $dsrlxsl $instwdef
}

rtfm () {
    echo 'Please check the manual page or run "yang2dsdl -h"' >&2
    exit 1
}

while getopts ":ht:d:b:v:jLs" opt ; do
    case $opt in
        h)
            usage
            exit 0
            ;;
        t)
            target=$OPTARG
            if [ "$target" != "data" ] && [ "$target" != "config" ] && \
	       [ "$target" != "get-reply" ] && [ "$target" != "get-config-reply" ] \
               && [ "$target" != "rpc" ] && [ "$target" != "rpc-reply" ] && \
               [ "$target" != "edit-config" ] && [ "$target" != "notification" ]
            then
                echo "Invalid argument for -t: $target."
                rtfm
            fi
            ;;
        d)
            dir=$OPTARG
            if [ ! -d $dir ] ; then
                echo "Directory '$dir' doesn't exist."
                exit 1
            fi
            ;;
        b)
            basename=$OPTARG
            ;;
        v)
            instance=$OPTARG
            if [ ! -f $instance ] ; then
                echo "File '$instance' not found."
                exit 1
            fi
            ;;
	j)
	    jing=1
	    ;;
	L)
	    hello=1
	    ;;
        s)
            noschema=1
            ;;
        \?)
            echo "Invalid option: -$OPTARG." >&2
            rtfm
            ;;
        :)
            echo "Option -$OPTARG requires an argument."
	    rtfm
            ;;
    esac
done

if [ "$noschema" = "1" ] && [ "$instance" = "" ] ; then
    echo "Option -s may only be used together with -v."
    exit 1
fi

shift $(($OPTIND-1))
infiles=$*
randext=$(mktemp -u .XXXXXXXX)
hybs=$dir/dsdl$randext
schxsl=$dir/sch$randext
dsrlxsl=$dir/dsrl$randext
instwdef=$dir/inst$randext

trap "cleanup" 0 1 2 3 15

if [ "$noschema" = "0" ] ; then
    if [ "$infiles" = "" ] ; then
	echo "No input file(s) given." >&2
	rtfm
    elif [ ! -w $dir ] ; then
        echo "Directory '$dir' not writable."
        exit 1
    fi
    if [ "$hello" = "1" ] ; then
	printf "\n== Validating hello file ...\n"
	hellorng=$schema_dir/hello-server.rng
	if [ "$jing" = "0" ] ; then
	    xmllint --noout --relaxng $hellorng $infiles
	    [ $? -eq 0 ] || exit 3
	else
	    jing $hellorng $infiles
	    [ $? -eq 0 ] || exit 3
	    echo $infiles validates.
	fi
	pyang_opts="${pyang_opts} --hello"
	printf "\n"
    fi
    pyang -f dsdl -o $hybs $pyang_opts $infiles
    [ $? -eq 0 ] || exit 2
    if [ "$basename" = "" ] ; then
        basename=$(xsltproc $xslt_dir/basename.xsl $hybs)
    fi
elif [ "$basename" = "" ] ; then
    echo "-b <basename> is required with -s option." >&2
    rtfm
fi

btname=$dir/$basename-$target
if [ "$target" = "get-config-reply" ] || [ "$target" = "config" ]
then
    gdefsname=$dir/$basename-gdefs-config.rng
elif [ "$target" = "edit-config" ]
then
    gdefsname=$dir/$basename-gdefs-edit.rng
else
    gdefsname=$dir/$basename-gdefs.rng
fi

if [ "$noschema" = "0" ] ; then
    gen_relaxng
    if [ "$target" != "edit-config" ] ; then
        gen_schematron
        gen_dsrl
    fi
else
    echo "== Using pre-generated schemas"
fi

if [ "$instance" != "" ] ; then
    validate
fi

exit 0
