# $Id: web.krb fe1a2365d4bf 2008-12-27 mtnyogi $
# coding=utf-8
#
# Copyright © 2008 Bruce Frederiksen
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.


#
# process($starting_tables, $template_name)
#  taking (db_connection, db_cursor, starting_keys)
#
# This is the top-level goal called by the wsgi application.
#   Input goal arguments:
#     $starting_tables      - a tuple of table_names that keys will be
#                             provided for when the plan is called (e.g.,
#                             ('movie',)).
#     $template_name        - the filename of the template file to use
#                             (e.g., 'movie1.html').
#   plan arguments:
#     db_connection         - the database connection object (to commit or
#                             rollback).
#     db_cursor             - an open cursor to the database.
#     starting_keys         - a dict mapping the table_names given in
#                             $starting_tables to their key values (e.g.,
#                             {'movie': 4}).
#   plan return:
#     The plan returns the 3-tuple that can be directly returned from the
#     wsgi function.
process_retrieval
    use process($starting_tables, $template_name)
        taking (db_connection, db_cursor, starting_keys)
    when
        !format_retrieval($starting_tables, $template_name, $needed_data) \
                step -1
            return $$(db_connection, data)
        !database.get_data($starting_tables, $needed_data)
            data = $$(db_cursor, starting_keys)

#
# format_retrieval($starting_tables, $template_name, $needed_data)
# taking (data)
#
# This creates a plan to render the indicated template.
#
#   Input goal arguments:
#     $starting_tables      - a tuple of table_names that keys will be
#                             provided for (e.g., ('movie',)).
#     $template_name        - the filename of the template file to use
#                             (e.g., 'movie1.html').
#   Output goal arguments:
#     $needed_data          - a descriptor of the data that will be needed
#                             by the plan.  This is ready for input into
#                             the sqlgen/database.get_data goal.
#   plan arguments:
#     db_connection         - the database connection object (to commit or
#                             rollback).
#     data                  - the dict returned by the
#                             sqlgen/database.get_data plan containing the
#                             values of the $needed_data.
#   plan return:
#     The plan returns the 3-tuple that can be directly returned from the
#     wsgi function.
format_retrieval
    use format_retrieval($starting_tables, $template_name, $needed_data)
        taking (db_connection, data)
    when
        $template = get_template($template_name)
        $elements = structure($template)
        !render_elements($elements, $needed_data) as $render_fn
    with
        db_connection.commit()
        return '200 OK', [('Content-Type', 'text/html')], \
               ($template.render($render_fn, data),)

#
# render_elements($elements, $needed_data) taking (template, data)
#
# This creates a plan to render the elements within an HTMLTemplate.
#   Input goal arguments:
#     $elements             - tuple of element names, which are each
#                             two-tuples of the form: (element_type, name).
#   Output goal arguments:
#     $needed_data          - a descriptor of the data that will be needed
#                             by the plan.  This is ready for input into
#                             the sqlgen/database.get_data goal.
#   plan arguments:
#     template              - the HTMLTemplate object.
#     data                  - the dict returned by the
#                             sqlgen/database.get_data plan containing the
#                             values of the $needed_data.
#   plan return:
#     The plan returns the rendered html as a string.
render_elements
    use render_elements($elements, $needed_data)
        taking (template, data)
    when
        python needed_data = []
        python render_fns = []
        forall
            $element in $elements
        require
            render_element1($element, $needed_data1) as $render_fn
            python
                needed_data1 = $needed_data1
                if needed_data1 and needed_data1 not in needed_data:
                    needed_data.append(needed_data1)
                render_fns.append($render_fn)
        $needed_data = tuple(needed_data)
        $render_fns = tuple(render_fns)
    with
        for render_fn in $render_fns: render_fn(template, data)

#
# render_element1($element, $needed_data1) taking (template, data)
#
# This creates a plan to render a single element within an HTMLTemplate.
#   Input goal arguments:
#     $element              - a single template element as a two-tuple of
#                             the form: (element_type, name).
#   Output goal arguments:
#     $needed_data1         - an individual descriptor item of the data
#                             that will be needed by the plan, or None if
#                             no data is needed.  This may describe an
#                             individual (unique) data item or a multi-row
#                             set of items.  This is ready for use in the
#                             top-level descriptor to the
#                             sqlgen/database.get_data goal.
#   plan arguments:
#     template              - the HTMLTemplate object.
#     data                  - the dict returned by the
#                             sqlgen/database.get_data plan containing the
#                             value(s) of the $needed_data1.
#   plan return:
#     None                  - the plan modifies the HTMLTemplate object in
#                             place and does not return anything.
render_element1_del
    use render_element1((del, $_), None)
        taking (template, data)
    with
        pass

render_element1_sep
    use render_element1((sep, $_), None)
        taking (template, data)
    with
        pass

render_element1_con
    use render_element1((con, $name), $name)
        taking (template, data)
    when
        check $name.find('.') == -1
        special.claim_goal()
    with
        getattr(template, $name).content = str(data[$name])

render_element1_con_dot
    use render_element1((con, $name), $real_name)
        taking (template, data)
    when
        ($real_name, $index) = $name.split('.')
        check $index.isdigit()
        special.claim_goal()
    with
        getattr(template, $name).content = str(data[$real_name])

render_element1_rep
    use render_element1((rep, $name, *$children), ($name, (), *$child_data))
        taking (template, data)
    when
        !render_elements($children, $child_data) as $detail_fun
    with
        getattr(template, $name).repeat($detail_fun, data[$name])

bc_extras
    import sys
    import StringIO
    import HTMLTemplate

    def renderFun(template, render_fn, data):
        render_fn(template, data)

    def get_template(template_name):
        f = open(template_name)
        try:
            return HTMLTemplate.Template(renderFun, f.read())
        finally:
            f.close()

    # Kludge!  HTMLTemplate needs methods to query the structure...
    def structure(template):
        try:
            # This was stdout in earlier releases of HTMLTemplate!
            stderr_save = sys.stderr
            sys.stderr = StringIO.StringIO()
            template.structure()
            lines = sys.stderr.getvalue().split('\n')
        finally:
            sys.stderr.close()
            sys.stderr = stderr_save
        return get_info(lines, '\t')[1]

    def get_info(lines, prefix, start = 0):
        '''
            Returns next_index, structure.
        '''
        ans = []
        while start < len(lines):
            line = lines[start]
            if line and not line.startswith('---') and line != 'tem:':
                if not line.startswith(prefix): break
                if len(line) > len(prefix) and line[len(prefix)] == '\t':
                    start, children = get_info(lines, prefix + '\t', start)
                    ans[-1] = tuple(ans[-1]) + children
                    continue
                else:
                    ans.append(tuple(line.strip().split(':')))
            start += 1
        #print "get_info -> %d, %s" % (start, tuple(ans))
        return start, tuple(ans)

