codegen.py 10.5 KB
#! /usr/bin/env python
# ==========================================================================
# ctools code generator
#
# Copyright (C) 2018 Juergen Knoedlseder
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# ==========================================================================
import os
import sys
import glob
import commands
import fileinput
from datetime import date
import gammalib


# ================= #
# Confirm something #
# ================= #
def confirm(text):
    """
    Confirm something

    Parameters
    ----------
    text : str
        Thing to confirm

    Returns
    -------
    flag : bool
        True if something is confirmed
    """
    # Confirmation loop
    waiting = True
    while waiting:
        confirmation = str(raw_input(text+' (y/n): '))
        if confirmation == 'q':
            sys.exit()
        elif confirmation == 'y':
            flag    = True
            waiting = False
        elif confirmation == 'n':
            flag    = False
            waiting = False

    # Return confirmation flag
    return flag


# ============ #
# Get response #
# ============ #
def response(text, confirm_response=False):
    """
    Get response

    Parameters
    ----------
    text : str
        Thing we want to get a response to
    confirm_response : bool
        True if response should be confirmed

    Returns
    -------
    response : str
        Reponse
    """
    # Looping until response is accepted
    looping = True
    while looping:

        # Get response from input
        waiting = True
        while waiting:
            response = str(raw_input('%s: ' % text))
            if response == 'q':
                sys.exit()
            else:
                waiting = False

        # If the version number change is confirmed then do the change
        if confirm_response:
            if confirm('Is "%s" ok?' % response):
                looping = False
        else:
            looping = False

    # Return response
    return response


# ======== #
# Add file #
# ======== #
def add_file(infile, outfile, tokens):
    """
    Add a file, replacing all tokens

    Parameters
    ----------
    infile : str
        Input filename
    outfile : str
        Output filename
    tokens : list of dict
        Token list
    """
    # Create output file
    file = open(outfile, "w")

    # Initialise number of header lines
    n_header = 0

    # Loop over input file
    for line in open(infile, 'r'):

        # Signal if this is a header line
        is_header_ast  = False
        is_header_hash = False
        if len(line) > 75:
            is_header_ast  = line[1] == '*' and line[75] == '*'
            is_header_hash = line[0] == '#' and line[1] == ' ' and line[76] == '#'
            n_header += 1

        # Replace tokens in line
        for token in tokens:
            line = line.replace(token['pattern'], token['string'])

        # If we have a header line than format the line
        if is_header_ast:
            text = line.strip(' *\n')
            if n_header == 2:
                line = ' *'+gammalib.centre(text,73)+'*\n'
            elif n_header == 4:
                line = ' *'+gammalib.left('  '+text,73)+'*\n'
        if is_header_hash:
            text = line.strip(' #\n')
            line = '# '+gammalib.left(text,74)+'#\n'

        # Write out line
        file.write(line)

    # Close file
    file.close()

    # Return
    return


# ========== #
# Set tokens #
# ========== #
def set_tokens(classname, author, what):
    """
    Set replacement tokens for a ctool or cscript
    """
    # Get current year
    year = str(date.today().year)

    # Set tokens
    tokens = [{'pattern': 'xxx', 'string': classname},
              {'pattern': 'XXX', 'string': classname.upper()},
              {'pattern': '[WHAT]', 'string': what},
              {'pattern': '[what]', 'string': what.lower()},
              {'pattern': '[AUTHOR]', 'string': author},
              {'pattern': '[YEAR]', 'string': year}]

    # Return tokens
    return tokens


# ========= #
# Add ctool #
# ========= #
def add_ctool(name, tokens, baseclass):
    """
    Add an instrument class

    Parameters
    ----------
    name : str
        ctool or script name
    tokens : str
        Tokens for replacement
    baseclass : str
        Baseclass
    """
    # Create directory structure
    if not os.path.isdir('src/%s' % name):
        os.mkdir('src/%s' % name)

    # Set template file names
    inctemp  = 'src/template/ctool_%s.hpp' % (baseclass)
    srctemp  = 'src/template/ctool_%s.cpp' % (baseclass)
    pytemp   = 'src/template/ctool_%s.i'   % (baseclass)
    partemp  = 'src/template/ctool.par'
    maintemp = 'src/template/main.cpp'
    maketemp = 'src/template/ctool_Makefile.am'
    testtemp = 'src/template/ctool_test.py'

    # Set destination file names
    incfile  = 'src/%s/%s.hpp'      % (name, name)
    srcfile  = 'src/%s/%s.cpp'      % (name, name)
    parfile  = 'src/%s/%s.par'      % (name, name)
    mainfile = 'src/%s/main.cpp'    % (name)
    makefile = 'src/%s/Makefile.am' % (name)
    pyfile   = 'pyext/%s.i'         % (name)
    testfile = 'test/test_%s.py'    % (name)
    
    # Add files
    add_file(inctemp,  incfile,  tokens)
    add_file(srctemp,  srcfile,  tokens)
    add_file(partemp,  parfile,  tokens)
    add_file(maintemp, mainfile, tokens)
    add_file(maketemp, makefile, tokens)
    add_file(pytemp,   pyfile,   tokens)
    add_file(testtemp, testfile, tokens)

    # Update src/Makefile.am
    filename    = 'src/Makefile.am'
    insertline1 = '          %s ' % (name)
    insertline2 = '                      %s/lib%s.la ' % (name, name)
    for line in fileinput.FileInput(filename,inplace=1):
        if 'SUBDIRS' in line.replace(' ', ''):
            last = line[-2]
            line = line.replace(line, line+insertline1+last+'\n')
        if 'libctools_la_LIBADD' in line.replace(' ', ''):
            last = line[-2]
            line = line.replace(line, line+insertline2+last+'\n')
        print line,

    # Update Python module
    for line in fileinput.FileInput('pyext/ctools/tools.i',inplace=1):
        if '%include "ctbin.i"' in line:
            print('%%include "%s.i"' % (name))
        print line,

    # Update unit test
    for line in fileinput.FileInput('test/test_python_ctools.py',inplace=1):
        if 'import test_ctbin' in line:
            print('import test_%s' % (name))
        elif 'test_ctbin.Test()' in line:
            print('             test_%s.Test(),' % (name))
        print line,

    # Update export of unit test
    for line in fileinput.FileInput('pyext/Makefile.am',inplace=1):
        if '$(top_srcdir)/test/test_ctbin.py' in line:
            print('              $(top_srcdir)/test/test_%s.py \\' % (name))
        print line,

    # Update configure.ac
    for line in fileinput.FileInput('configure.ac',inplace=1):
        if 'src/ctbin/Makefile' in line:
            print('                 src/%s/Makefile' % (name))
        print line,

    # Return
    return


# ============ #
# Manage ctool #
# ============ #
def ctool_menu():
    """
    Manage ctool
    """
    # Annonce actions
    print("")
    print("Add ctool")
    print("---------")

    # Stay in loop until there is a final confirmation
    while True:

        # Enter information
        ctoolname = response('Please enter a ctool name (e.g. "ctpntsim")')
        what      = response('Please say what the tool is for (e.g. "Pointing simulation")')
        author    = response('Please enter your name (e.g. "Joe Public")')

        # Enter baseclass
        print('From which baseclass should the ctool derive?')
        print('[1] ctool')
        print('[2] ctobservation')
        print('[3] ctlikelihood')
        waiting = True
        choice  = 1
        while waiting:
            choice = str(raw_input('Enter your choice: '))
            if choice == '1' or choice == '2' or choice == '3':
                waiting = False
        if choice == '1':
            basename  = 'base'
            baseclass = 'ctool'
        elif choice == '2':
            basename  = 'obs'
            baseclass = 'ctobservation'
        elif choice == '3':
            basename  = 'like'
            baseclass = 'ctlikelihood'

        # Ask to confirm module summary
        print('\nAll right. Have now:')
        print('ctool name ......: "%s"' % ctoolname)
        print('Tools descriptor : "%s"' % what)
        print('Your name .......: "%s"' % author)
        print('Base class ......: "%s"' % baseclass)
        if confirm('Is this correct?'):
            break

    # Set tokens
    tokens = set_tokens(ctoolname, author, what)

    # Add ctool
    add_ctool(ctoolname, tokens, basename)

    # Return
    return


# ================ #
# Manage main menu #
# ================ #
def main_menu():
    """
    Manage main menu
    """
    # Print main menu
    print('[1] Add ctool')
    print('[2] Add cscript')
    print('[q] Quit')

    # Wait for the input
    waiting = True
    while waiting:
        choice = str(raw_input('Enter your choice: '))
        if choice == '1' or choice == '2' or choice == 'q':
            waiting = False

    # Return choice
    return choice


# ======================== #
# Main routine entry point #
# ======================== #
if __name__ == '__main__':

    # Check if script has been started from ctools root directory
    if not os.path.isfile('ctools.pc.in'):
        print('"codgen.py" script needs to be started from ctools source code '
              'root directory, you are somewhere else. Quit now.')
        sys.exit()

    # Clear console
    os.system('clear')

    # Print header
    print('ctools code generator')
    print('=====================')
    print('')

    # Enter endless loop
    while True:

        # Show main menu
        choice = main_menu()

        # Dispatch according to choice
        if choice == '1':
            ctool_menu()
            print('')
        elif choice == '2':
            cscript_menu()
            print('')
        elif choice == 'q':
            break