#!/usr/bin/env python2.3

"""Script to make the Apache2 config snippets for a Railroad server.

This script is intended to be used by experienced admins, because
you have to know a lot about Apache2 and the environment the server
will be running in, the admin also has to place the produced config
snippets into the right place manually.
"""

# Copyright (c) 2004 Infrae. All rights reserved.
# See also LICENSE.txt


import sys
import os
import os.path
import urlparse
import cPickle as P

###

SEARCH_ENABLED = True


UJ = urlparse.urljoin

###

def _check_prefix ( p ):
    if p[0] != '/':
        return False
    if p[-1] != '/':
        return False
    return True
#

def _check_prefix_upload ( upath ):
    global ANSWERS
    rpath = ANSWERS['path']
    if upath.find(rpath) == 0:
        if upath == rpath:
            return False
        l = len(rpath)
        if upath[l] == '/':
            return False
    return True
#

def _check_auth ( auth ):
    global QUESTIONS, ANSWERS, Auth_Questions, ap_repo_templates, \
           Db_Questions, db_active, ap_service_templates
    auth = auth.lower().strip()
    if auth == 'on':
        QUESTIONS.extend(Auth_Questions)
        l = ['auth_type', 'auth_name', 'auth_require']
        ap_repo_templates['location'][2].extend(l)
        ap_service_templates['location'][2].extend(l)
        if not db_active:
            l = ['db_module', 'db_name', 'db_user', 'db_password', 'db_host']
            ap_repo_templates['location'][2].extend(l)
            ap_service_templates['location'][2].extend(l)
            QUESTIONS.extend(Db_Questions)
            db_active = True
        return True
    elif auth == 'off':
        if not db_active and not SEARCH_ENABLED:
##             QUESTIONS.extend(No_Db_Questions)
            l = ['repo_host', 'repo_path', 'repo_dir']
            ap_repo_templates['location'][2].extend(l)
            ap_service_templates['location'][2].extend(l)
        return True
    return False
#

def _check_search ( search ):
    if not SEARCH_ENABLED:
        return False
    global QUESTIONS, ANSWERS, Search_Questions, Db_Questions, \
           db_active
    search = search.lower().strip()
    if search == 'on':
        QUESTIONS.extend(Search_Questions)
        l = ['definefilter', 'setfilter']
        ap_repo_templates['location'][2].extend(l)
        if not db_active:
            l = ['db_module', 'db_name', 'db_user', 'db_password', 'db_host']
            ap_repo_templates['location'][2].extend(l)
            ap_service_templates['location'][2].extend(l)
            QUESTIONS.extend(Db_Questions)
            db_active = True
        return True
    elif search == 'off':
        if not db_active:
##             QUESTIONS.extend(No_Db_Questions)
            l = ['repo_host', 'repo_path', 'repo_dir']
            ap_repo_templates['location'][2].extend(l)
            ap_service_templates['location'][2].extend(l)
        return True
    return False
#
###

# Apache2 option templates

# entries are by config option
# values is a triple: (start tag, end tag, sub-tags as list of strings)

# The WebDAV enabled repository
ap_repo_templates = {}
ap_repo_templates['location'] = ('<Location "%(path)s" >', '</Location>',
                                 ['dav', 'options', 'addhandler', 'pythondebug',
                                  'pythonautoreload','pythonpath', 'pythoninterpreter',
                                  'pythonheaderhandler',
                                  'pythonauthenhandler', 'pythonauthzhandler',
                                  'repository',
                                  'opt_authorize', 'opt_search',
                                  'limit'])
ap_repo_templates['alias'] = ('Alias "%(path)s" "%(fsdir)s"', None, [])
ap_repo_templates['pythondebug'] = ('PythonDebug %(pythondebug)s', None, [])
ap_repo_templates['pythonautoreload'] = ('PythonAutoReload %(pythonautoreload)s', None, [])
ap_repo_templates['pythonpath'] = ('PythonPath "[\'%(instdir)s\']+sys.path"', None, [])
ap_repo_templates['dav'] = ('DAV %(dav)s', None, [])
ap_repo_templates['options'] = ('Options %(options)s', None, [])
ap_repo_templates['addhandler'] = ('AddHandler %(addhandler)s', None, [])
ap_repo_templates['pythoninterpreter'] = ('PythonInterpreter "%(pythoninter)s"', None, [])
ap_repo_templates['pythonheaderhandler'] = ('PythonHeaderParserHandler %(inithandler)s', None, [])
ap_repo_templates['pythonauthenhandler'] = ('PythonAuthenHandler %(authenhandler)s', None, [])
ap_repo_templates['pythonauthzhandler'] = ('PythonAuthzHandler %(authzhandler)s', None, [])
ap_repo_templates['repository'] = ('PythonOption repository "%(repository)s"', None, [])
ap_repo_templates['limit'] = ('<Limit GET POST OPTIONS PROPFIND PUT DELETE PATCH PROPPATCH MKCOL COPY MOVE LOCK UNLOCK >', '</Limit>', ['limit_order', 'limit_allow'])
ap_repo_templates['limit_order'] = ('Order %(limit_order)s', None, [])
ap_repo_templates['limit_allow'] = ('Allow %(limit_allow)s', None, [])
ap_repo_templates['limit_deny'] = ('Deny %(limit_deny)s', None, [])
ap_repo_templates['opt_authorize'] = ('PythonOption authorize "%(opt_authorize)s"', None, [])
ap_repo_templates['opt_search'] = ('PythonOption search "%(opt_search)s"', None, [])
ap_repo_templates['auth_type'] = ('AuthType "%(auth_type)s"', None, [])
ap_repo_templates['auth_name'] = ('AuthName "%(auth_name)s"', None, [])
ap_repo_templates['auth_require'] = ('require %(auth_require)s', None, [])
ap_repo_templates['db_module'] = ('PythonOption db_module "%(db_module)s"', None, [])
ap_repo_templates['db_name'] = ('PythonOption dbname "%(db_name)s"', None, [])
ap_repo_templates['db_user'] = ('PythonOption dbuser "%(db_user)s"', None, [])
ap_repo_templates['db_password'] = ('PythonOption dbpasswd "%(db_password)s"', None, [])
ap_repo_templates['db_host'] = ('PythonOption dbhost "%(db_host)s"', None, [])
ap_repo_templates['repo_path'] = ('PythonOption repo_path "%(path)s"', None, [])
ap_repo_templates['repo_dir'] = ('PythonOption repo_dir "%(fsdir)s"', None, [])
ap_repo_templates['repo_host'] = ('PythonOption repo_host "%(repo_host)s"', None, [])
ap_repo_templates['definefilter'] = ('PythonOutputFilter rr_main OUTFILTER', None, [])
ap_repo_templates['setfilter'] = ('SetOutputFilter OUTFILTER', None, [])


# The form based upload url
ap_service_templates = {}
ap_service_templates['location'] = ('<Location "%(prefix)sservice/" >', '</Location>',
                                 ['sethandler', 'pythondebug',
                                  'pythonautoreload','pythonpath', 'pythoninterpreter',
                                  'pythonheaderhandler',
                                  'pythonauthenhandler', 'pythonauthzhandler',
                                  'pythonhandler',
                                  'repository',
                                  'opt_authorize', 'opt_search', ])
ap_service_templates['pythondebug'] = ('PythonDebug %(pythondebug)s', None, [])
ap_service_templates['pythonautoreload'] = ('PythonAutoReload %(pythonautoreload)s', None, [])
ap_service_templates['pythonpath'] = ('PythonPath "[\'%(instdir)s\']+sys.path"', None, [])
ap_service_templates['dav'] = ('DAV %(dav)s', None, [])
ap_service_templates['options'] = ('Options %(options)s', None, [])
ap_service_templates['sethandler'] = ('SetHandler %(sethandler)s', None, [])
ap_service_templates['pythoninterpreter'] = ('PythonInterpreter "%(pythoninter)s"', None, [])
ap_service_templates['pythonheaderhandler'] = ('PythonHeaderParserHandler %(inithandler)s', None, [])
ap_service_templates['pythonauthenhandler'] = ('PythonAuthenHandler %(authenhandler)s', None, [])
ap_service_templates['pythonauthzhandler'] = ('PythonAuthzHandler %(authzhandler)s', None, [])
ap_service_templates['repository'] = ('PythonOption repository "%(repository)s"', None, [])
ap_service_templates['pythonhandler'] = ('PythonHandler %(pythonhandler)s', None, [])
ap_service_templates['limit'] = ('<Limit GET POST OPTIONS PROPFIND PUT DELETE PATCH PROPPATCH MKCOL COPY MOVE LOCK UNLOCK >', '</Limit>', ['limit_order', 'limit_allow'])
ap_service_templates['limit_order'] = ('Order %(limit_order)s', None, [])
ap_service_templates['limit_allow'] = ('Allow %(limit_allow)s', None, [])
ap_service_templates['limit_deny'] = ('Deny %(limit_deny)s', None, [])
ap_service_templates['opt_authorize'] = ('PythonOption authorize "%(opt_authorize)s"', None, [])
ap_service_templates['opt_search'] = ('PythonOption search "%(opt_search)s"', None, [])
ap_service_templates['auth_type'] = ('AuthType "%(auth_type)s"', None, [])
ap_service_templates['auth_name'] = ('AuthName "%(auth_name)s"', None, [])
## ap_service_templates['auth_require'] = ('require %(auth_require)s', None, [])
ap_service_templates['auth_require'] = ('', None, [])
ap_service_templates['db_module'] = ('PythonOption db_module "%(db_module)s"', None, [])
ap_service_templates['db_name'] = ('PythonOption dbname "%(db_name)s"', None, [])
ap_service_templates['db_user'] = ('PythonOption dbuser "%(db_user)s"', None, [])
ap_service_templates['db_password'] = ('PythonOption dbpasswd "%(db_password)s"', None, [])
ap_service_templates['db_host'] = ('PythonOption dbhost "%(db_host)s"', None, [])
ap_service_templates['repo_path'] = ('PythonOption repo_path "%(path)s"', None, [])
ap_service_templates['repo_dir'] = ('PythonOption repo_dir "%(fsdir)s"', None, [])
ap_service_templates['repo_host'] = ('PythonOption repo_host "%(repo_host)s"', None, [])


# Question definitions
# list of tuples: (name, question, default value, value_type of answer, validation function)
QUESTIONS = [ ('instdir',
               'Installation directory of the RR scripts', os.getcwd(), str, None),
              ('repository',
               'Name of the repository', 'railroad_data', str, None),
              ('prefix',
               'The prefix is the part before `over` the repository.\nPrefix to path to the top of the repository', None, str, _check_prefix),
              ('fsdir',
               'The RR server needs to access the local file system to perform some of its duties.\nAbsolute path to the parent dir of the repository on the file system (must exist!)',
               None, str, lambda x: os.path.exists(x)),
##               ('uploadpath',
##                'Web path for form based uploads', None, str, _check_prefix_upload),
              ('pythoninter',
               'Name of the Python interpreter', 'rrpy', str, None),
              ('repo_host',
               'Host/port the repository is running on', 'localhost', str, None),
              ('opt_authorize',
               'Use authorization (needs the DB!)', 'on', str, _check_auth)
              ]
if SEARCH_ENABLED:
    QUESTIONS.append(('opt_search',
                      'Use property search (needs the DB!)', 'off', str, _check_search))

Auth_Questions = [ ('auth_name',
                    'Authorization domain', 'Railroad', str, None),
                 ]

Search_Questions = []

Db_Questions = [ ('db_module',
                  'DB module to use', 'db_pg', str, None),
                 ('db_name',
                  'Name of database to use', None, str, None),
                 ('db_user',
                  'User to use for db-connection', None, str, None),
                 ('db_password',
                  'Password to use', '', str, None),
                 ('db_host',
                  'Host the db is running on', '', str, None),
               ]

ANSWERS = {}

# preset some answers
ANSWERS['prefix'] = '/rrr/'
ANSWERS['pythondebug'] = 'On'
ANSWERS['pythonautoreload'] = 'On'
ANSWERS['dav'] = 'On'
ANSWERS['options'] = '-MultiViews -Indexes'
ANSWERS['addhandler'] = 'mod_python .py'
ANSWERS['sethandler'] = 'python-program'
ANSWERS['inithandler'] = 'rr_main::inithandler'
ANSWERS['authenhandler'] = 'rr_main'
ANSWERS['authzhandler'] = 'rr_main'
ANSWERS['pythonhandler'] = 'rr_service'
ANSWERS['limit_order'] = 'Deny,Allow'
ANSWERS['limit_allow'] = 'from all'
ANSWERS['auth_type'] = 'Basic'
ANSWERS['auth_require'] = 'valid-user'
ANSWERS['opt_search'] = 'on'

# some flags

# is db support activated?
db_active = False

###

def ask_question ( question, default=None, value_type=str ):
    """Ask the given question and check if it matches the given value_type.
    """
    if default is not None:
        question = question + ' [%s]: ' % str(default)
    else:
        question += ': '
    while True:
        answer = raw_input(question)
        if default is not None and not answer:
            answer = default
        elif default is None and not answer:
            print 'You have to give a value here!'
            continue
        # type check for beginners
        try:
            result = value_type(answer)
            break
        except (TypeError, ValueError):
            # bahh... wrong type, continue
            print 'Wrong type! Again...'
            pass
    return result
#

def write_option ( option, templates, values, level, out ):
    """Fill in the template for the given option and print it to out.
    """
    indent = '    '*level
    start, end, def_val = templates[option]
    if not start and not end:
        return
    try:
        line = indent + start % values
    except KeyError:
        # some value not set, ignore entry
        return
    print >>out, line
    if def_val:
        for v in def_val:
            write_option(v, templates, values, level+1, out)
    if end:
        print >>out, indent, end % values
    return
#

def make_answers_sane ():
    global ANSWERS
    a = ANSWERS['prefix'].strip()
    if a[-1] != '/':
        a += '/'
        ANSWERS['prefix'] = a
    a = a + 'repo/'
    ANSWERS['path'] = a
    a = ANSWERS['fsdir'].strip()
    if a[-1] != '/':
        a += '/'
        ANSWERS['fsdir'] = a
    delkeys = []
    for key, value in ANSWERS.items():
        if not value:
            delkeys.append(key)
    for key in delkeys:
        del ANSWERS[key]
    return
#

def make_repo_snippet ( out=sys.stdout ):
    """Create the snippet for the main (WebDAV) repository.
    """
    # first Alias the fs directory to the path
    print >>out, '    # WebDAV repository'
    write_option('alias', ap_repo_templates, ANSWERS, 1, out)
    # second the Location directive
    write_option('location', ap_repo_templates, ANSWERS, 1, out)
    print >>out
    return
#

def make_service_snippet ( out=sys.stdout ):
    """Create the snippet for the upload URL.
    """
    # The Location directive
    print >>out, '    # services url'
    write_option('location', ap_service_templates, ANSWERS, 1, out)
    return
#

def make_sql_snippet ( out=sys.stdout ):
    """Create the snippet for the SQL database.
    """
    print >>out
    print >>out, '-'*60

    print >>out, '-- FILL IN THE MASTER USER AND PASSWORD!'
    print >>out, """INSERT INTO repositories(name, fs_top_dir, host, path, upload_path, maser_user, master_pw) VALUES ('%(repository)s', '%(fsdir)s', '%(repo_host)s', '%(path)s', '%(uploadpath)s', '', '');""" % ANSWERS
    pass

def make_admconf ():
    """Create admconf.py file from configured options.
    """
    fout = file('./admconf.py', 'wb')
    # build dbopts dict
    dbopts = {}
    dbopts['dbname'] = ANSWERS['db_name']
    dbopts['dbuser'] = ANSWERS['db_user']
    if ANSWERS.has_key('db_password'):
        dbopts['dbpasswd'] = ANSWERS['db_password']
    if ANSWERS.has_key('db_host'):
        dbopts['dbhost'] = ANSWERS['db_host']
    dbstr = str(dbopts)
##     print dbstr
    dbm = ANSWERS['db_module']
    fout.write('# Copyright (c) 2004 by Infrae.\n# See LICENSE.txt\n\n')
    fout.write('import %s as DB\n\n' % dbm)
    fout.write('dbopts = %s\n' % dbstr)
    fout.flush()
    fout.close()
    return

def main ( argv ):
    """Main entry point if used from the comamnd line.
    """
    global ANSWERS
    #
    # process command line
    # only one parameter is known:
    # -o <output file>
    if argv:
        if argv[0] == '-o':
            outfile_name = argv[1]
            outfile = file(outfile_name, 'wb')
    else:
        outfile = sys.stdout
    recerate = False
    pwd = os.path.abspath(os.getcwd())
    answer_fn = os.path.join(pwd, '.apconf')
    if os.path.exists(answer_fn):
        f = file(answer_fn, 'rb')
        d = P.load(f)
        f.close()
        del f
        ANSWERS.update(d)
        _check_auth(ANSWERS['opt_authorize'])
        if SEARCH_ENABLED:
            _check_search(ANSWERS['opt_search'])
        recerate = True
    # go through the questions
    qnumber = 0
    for name, question, def_value, typ, validate in QUESTIONS:
        if name in ANSWERS:
            if recerate:
                continue
            # if there is an answer, make the value the default
            def_value = ANSWERS[name]
        while True:
            print '<%02d>' % qnumber,
            answer = ask_question(question, default=def_value, value_type=typ)
            if callable(validate) and not validate(answer):
                print 'Ups... that seems to be invalid!'
            else:
                break
        ANSWERS[name] = answer
        qnumber += 1
    # XXX DEBUG
    f = file(answer_fn, 'wb')
    P.dump(ANSWERS, f, -1)
    f.flush()
    f.close()
    # XXX END DEBUG
    # all answers collected, make/print config snippets
    make_answers_sane()
    make_repo_snippet(out=outfile)
    make_service_snippet(out=outfile)
    if db_active:
##         make_sql_snippet()
        make_admconf()
    return
#
###

if __name__ == '__main__':
    main(sys.argv[1:])
#
