#!/usr/libexec/platform-python

import os
import sys
import subprocess
import tempfile
import argparse
import pipes
import re


MACRO_SETUP_TEMPLATE = """%(source_cmds)s end() { rc=$?; if [ $rc -eq 0 ]; then declare -x > %(envstore)s; fi; exit $rc; }; trap end EXIT"""


def cmd_repr(cmd):
    quoted_items = []

    for i in range(len(cmd)):
        quoted_items.append(pipes.quote(cmd[i]))

    return ' '.join(quoted_items)


def generate_source_cmds(source_list, env_store_file_name):
    source_cmds = 'source ' + env_store_file_name + '; '

    for source in source_list:
        source_cmds += 'source ' + source + '; '

    return source_cmds.strip()


def invoke_wrapper(env_store_file_name, break_on_failure, debug, source_list, env):
    source_cmds = generate_source_cmds(source_list, env_store_file_name)

    MACRO_SETUP = MACRO_SETUP_TEMPLATE % {
        'source_cmds': source_cmds,
        'envstore': env_store_file_name
    }

    def invoke(matchobj):
        retval = ''

        script = matchobj.group(1).strip()

        if not script:
            return retval

        cmd = ['/bin/bash', '-c' , '{0}; {1};'.format(MACRO_SETUP, script)]

        if debug:
            sys.stderr.write('> {0}\n'.format(script))
            sys.stderr.write('> env: {0}\n'.format(env))
            sys.stderr.write('> cmd: {0}\n'.format(cmd_repr(cmd)))
            sys.stderr.flush()

        p = subprocess.Popen(cmd, env=env, stdout=subprocess.PIPE)
        (stdout, _) = p.communicate()

        if p.returncode != 0:
            sys.stderr.write(
                '{0} failed with value {1}.\n'.format(
                script, p.returncode))

            if break_on_failure:
                sys.exit(p.returncode)

        if debug:
            sys.stderr.write('---\n')
            sys.stderr.flush()

        retval += stdout.decode('utf-8')

        return retval.strip()
    return invoke


def preprocess(break_on_failure, debug, source_list, env):
    with tempfile.NamedTemporaryFile() as env_store_file:
        invoke = invoke_wrapper(env_store_file.name, break_on_failure, debug, source_list, env)

        if sys.version_info < (3, 0):
            data = sys.stdin.read().decode('utf-8')
        else:
            data = sys.stdin.read()

        pattern = re.compile(r"""{{{(('[^']*'|"[^"]*"|(?!{{{)(?!}}})[^'"])*)}}}""", re.DOTALL)
        data = re.sub(pattern, invoke, data)

        if sys.version_info < (3, 0):
            sys.stdout.write(data.encode('utf-8'))
        else:
            sys.stdout.write(data)


def get_parser():
    parser = argparse.ArgumentParser(prog='preproc', description='Simple text preprocessor implementing '
                                     'a very basic templating language. You can use {{{ <bash_code> }}} tags '
                                     'in a text file and then pipe content of that file to preproc. '
                                     'preproc will replace each of the tags with stdout of <bash_code> '
                                     'and print the final renderred result to its own stdout. NOTE: '
                                     'You need to trust input files that you pass to preproc. Running '
                                     'preproc on an unknown file is equivalent of running an unknown script '
                                     'on your computer.')

    parser.add_argument('--no-break-on-failure', action='store_true',
                        help='Normally, preproc will immediately stop processing as soon '
                        'as an error is encountered. This switch changes the behavior.')
    parser.add_argument('--debug', action='store_true',
                        help='Print each encountered tag and its env during input processing.')
    parser.add_argument('-e', '--env', action='append', dest='env', metavar='<varname>=<value>', default=[],
                        help='set a variable into macro processing environment, e.g. -e VAR=VALUE.')
    parser.add_argument('-s', '--source', action='append', dest='source', metavar="PATH", default=[],
                        help='sources given file into macro processing environment. May be used multiple times.')
    parser.add_argument('-C', action='store', dest='cwd', metavar='path',
                        help='Directory to operate in.')

    return parser


if __name__ == '__main__':
    args = get_parser().parse_args()

    env = {}
    for env_elem in args.env:
        varname, value = env_elem.split('=')
        env[varname] = value

    if args.cwd:
        os.chdir(args.cwd)

    preprocess(
        not args.no_break_on_failure,
        args.debug, args.source, env)
