def tour_builder(output):

    auth = current.auth
    db = current.db
    s3db = current.s3db
    request = current.request
    s3 = current.response.s3
    T = current.T

    req_vars = request.vars
    tour_id = req_vars.tour
    # Now see if the details are on the database for this user
    tour = None
    user_id = None
    if auth.is_logged_in():
        user_id = auth.s3_logged_in_person()
        # Find out if the user has done this tour before
        utable = s3db.tour_user
        uquery = (utable.person_id == user_id) & \
                (utable.tour_config_id == tour_id)
        tour = db(uquery).select(utable.id,
                                utable.completed,
                                utable.place,
                                utable.resume,
                                limitby=(0, 1)).first()
        # If the tour has just been started (from the menu) then
        # it may be necessary to redirect to a different controller
        # @todo: does place need to be changed to controller and function?
        if not req_vars.tour_running:
            if (tour and not tour.completed and tour.place != request.controller):
                redirect("%s?tour=%s" %(tour.resume, tour_id))

    # get the details from the database
    dtable = s3db.tour_details
    dquery = (dtable.tour_config_id == tour_id) &\
            (dtable.controller == request.controller) &\
            (dtable.function == request.function)
    details = db(dquery).select(dtable.args,
                               dtable.tip_title,
                               dtable.tip_details,
                               dtable.button,
                               dtable.tip_location,
                               dtable.html_id,
                               dtable.html_class,
                               dtable.datatable_id,
                               dtable.datatable_row,
                               dtable.redirect,
                               orderby=(dtable.posn)
                               )
#        tour_filename = os.path.join(request.folder,
#                                     "private",
#                                     "tour",
#                                     tour_name)
#        tour_file = open (tour_filename, "rb")
#        # now open the details of the guided_tour into a dictionary
#        import csv
#        tour_details = csv.DictReader(tour_file, skipinitialspace=True)
    # load the list of tour items in the html
    joyride_OL = OL(_id="joyrideID_1")
    pre_step_data = []
    post_step_data = []
    post_ride_data = []
    last_row = None
    last_used = None
    req_args = request.args
    cnt = -1
    for row in details:
        if row.args:
            args = row.args.split(",")
        else:
            args = []
        # if the page has a nested login form then "login" will be added to
        # the req_args list so it needs to be added to the args list as well
        if "login" in req_args:
            if "login" not in args:
                args.append("login")
        # The following will capture the actual id used for the req_arg
        # Example org/organisation/10, where 10 is the id from the database
        posn = 0
        for arg in args:
            if arg == "dt_id":
                args[posn] = req_args[posn]
            posn += 1
        # Now check that the tour url matches the current url
        if (args == req_args):
            cnt += 1 # number of records used in this part of the tour
            if row.datatable_id:
                dt_id = row.datatable_id
#                    cols = []
#                    if "DataTable_columns" in row:
#                        cols = row["DataTable_columns"].split(",")
                row_num = 0
                if row.datatable_row:
                    row_num = row.datatable_row
                # Now set this up for the pre-processor hook in joyride
                pre_step_data.append([cnt, dt_id, row_num])
            if row.redirect:
                redirect_row = row.redirect.split(",")
                if len(redirect_row) >= 3:
                    url = URL(c=redirect_row[0],
                              f=redirect_row[1],
                              args=redirect_row[2:],
                              vars={"tour_running":True,
                                    "tour":tour_id}
                              )
                    if "dt_id" in redirect_row[2]:
                        post_step_data.append([cnt, url, dt_id, row_num])
                elif len(redirect_row) == 2:
                    url = URL(c=redirect_row[0],
                              f=redirect_row[1],
                              vars={"tour_running":True,
                                    "tour":tour_id}
                              )
                    post_step_data.append([cnt, url])
                else:
                    url = URL(c=redirect_row[0],vars={"tour_running":True,
                                                  "tour":tour_id})
                    post_step_data.append([cnt, url])
            extra = {}
            if row.html_id:
                extra["_data-id"] = row.html_id
            elif row.html_class:
                extra["_data-class"] = row.html_class
            if row.button:
                extra["_data-button"] = row.button
            else:
                extra["_data-button"] = "Next"
            if row.tip_location:
                extra["_data-options"] = "tipLocation:%s" % row.tip_location.lower()
            else:
                extra["_data-options"] = "tipLocation:right"
            joyride_OL.append(LI(H2(T(row.tip_title)),
                                 P(T(row.tip_details)),
                                    **extra
                                 )
                              )
            last_used = row
        last_row = row
    # The following redirect will be triggered if the user has moved away
    # from the tour, such as by clicking on a tab. However if a tab
    # is part of the tour we are unable to determine if they have moved
    # away or just visiting as part of the tour and so it will continue.
    if len(joyride_OL) == 0:
        del request.vars.tour
        redirect(URL(args=req_args,
                     vars=request.vars))
    if (user_id != None) and (last_row == last_used):
        # set up an AJAX call to record that the tour has been completed
        post_ride_data = [cnt, tour_id]
    joyride_div = DIV(joyride_OL,
                      _class="hidden")
    # Add the javascript configuration data
    from gluon.serializers import json as jsons
    if pre_step_data:
        joyride_div.append(INPUT(_type="hidden",
                                 _id="prestep_data",
                                 _name="prestep_data",
                                 _value=jsons(pre_step_data))
                           )
    if post_step_data:
        joyride_div.append(INPUT(_type="hidden",
                                 _id="poststep_data",
                                 _name="poststep_data",
                                 _value=jsons(post_step_data))
                           )
    if post_ride_data:
        joyride_div.append(INPUT(_type="hidden",
                                 _id="postride_data",
                                 _name="postride_data",
                                 _value=jsons(post_ride_data))
                           )
    # Now add the details to the tour_user table
    if user_id != None:
        if tour == None:
            # this user has never done this tour before so create a new record
            utable.insert(person_id = user_id,
                          tour_config_id = tour_id,
                          place = request.controller,
                          resume = request.url)
        else:
            # the user has done some of this tour so update the record
            db(uquery).update(place = request.controller,
                             resume = request.url,
                             completed = False)

    output["joyride_div"] = joyride_div
    if s3.debug:
        appname = request.application
        s3.scripts.append("/%s/static/scripts/jquery.joyride.js" % appname)
        s3.scripts.append("/%s/static/scripts/S3/s3.guidedtour.js" % appname)
        s3.stylesheets.append("plugins/joyride.min.css")
    else:
        s3.scripts.append("/%s/static/scripts/S3/s3.guidedtour.min.js" % request.application)
        s3.stylesheets.append("plugins/joyride.css")
    return output

# END =========================================================================
Helper function to attach a guided tour (if required) to the output
dataset/ETHPy150Open sahana/eden/modules/s3db/tour.py/tour_builder