#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
Convert text files between arbitrary encodings and line endings.

Usage:
convert_encoding.py [options] file1 file2 ...

Options are:
    -f enc,  --from=enc    the input encoding
                           (default: locale.getpreferredencoding())
                           Set this to 'guess' if the encoding is unknown.
    -t enc,  --to=enc      the output encoding
                           (default: locale.getpreferredencoding())
    -e code, --eol=code    the end-of-line mode (see below)
    -o file, --out=file    where to write the converted data. Conversion is
                           done in-place if this is omitted
    -n, --nocodec          ignore input and output encoding, just process eol
    -r, --recursive        go into directories recursively
    -l, --followlinks      follow symbolic links
    -d, --dotfiles         in combination with -r: process files that begin
                           with a dot, if they are found in a directory reached
                           by recursion. You still have to specify dotfiles
                           explicitly in the initial filelist.
    -q, --quiet            don't display warnings or status messages

For valid encoding names, see
http://docs.python.org/lib/standard-encodings.html

Accepted values for the eol code are: 'unix', 'linux', 'dos', 'win', 'mac'.
If you do not specify an end-of-line, it will be set to your system's default,
regardless of the original end-of-line.

Any '#' in the out-filename will be replaced with the full name of the input
file.

Note that guessing the input encoding is not foolproof in any way. Always
provide an explicit input encoding if you can.

The program supports full unix style globbing on all operation systems,
independently of your shell's capabilities. Also, environment variables
and '~' will be expanded following the usual conventions.

The file may also be used as a module from python.
"""

"""
   Copyright (C) 2007  Michael Goerz

   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 sys
import codecs
import getopt
import os
import os.path
import locale
import shutil

def main():
    """Command line program for converting encodings and end-of-lines """
    #
    # command line parsing / initialization
    global warn
    try:
        opts, files = getopt.getopt(sys.argv[1:], "hf:t:e:o:rlqnd",
                                                 ["help", "from=", "to=","eol=",
                                                  "recursive", "followlinks",
                                                  "quiet", "nocodec", "out=",
                                                  "dotfiles"])
    except getopt.GetoptError, details:
        warn(details)
        exit(2)
    locale.setlocale(locale.LC_ALL, '')
    from_enc = locale.getpreferredencoding()
    to_enc = locale.getpreferredencoding()
    eol = os.linesep
    recursive = False
    followlinks = False
    dotfiles = False
    guess = False
    nocodec = False
    outfilename = ""
    for o, a in opts:
        if o in ("-h", "--help"):
            usage()
            sys.exit()
        if o in ("-f", "--from"):
            if a == 'guess':
                guess = True
            else:
                from_enc = a
        if o in ("-t", "--to"):
            to_enc = a
        if o in ("-o", "--out"):
            outfilename = os.path.normcase(a)
        if o in ("-r", "--recursive"):
            recursive = True
        if o in ("-d", "--dotfiles"):
            dotfiles = True
        if o in ("-q", "--quiet"):
            warn = no_warn
        if o in ("-n", "--nocodec"):
            nocodec = True
        if o in ("-e", "--eol"):
            eolmode = a.lower()
            os_eols = {'unix':"\n",
                       'linux':"\n",
                       'dos':"\r\n",
                       'win':"\r\n",
                       'mac':"\r"}
            try:
                eol = os_eols[eolmode]
            except KeyError:
                warn("'%s' is not a valid name for a line ending." % eolmode)
                warn("Use 'unix', 'linux', 'dos', 'win', or 'mac'.")
                warn("Converting to your default line ending")
        if o in ("-l", "--followlinks"):
            recursive = True
        if o in ("-l", "--followlinks"):
            followlinks = True
    #
    # put together what we actually have to do
    if nocodec:
        rec_function = lambda infilename: convert_eol(infilename, eol, \
                                          outfilename.replace('#', infilename))
    else:
        if guess:
            warn("WARNING: guessing the input encoding is dangerous. "
                  + "Make sure to check the results.\n")
            rec_function = lambda infilename: \
                               convert_encoding(
                                   infilename,
                                   guess_file_encoding(infilename, from_enc),
                                   to_enc,
                                   eol,
                                   outfilename.replace('#', infilename))
        else:
           rec_function = lambda infilename: \
                              convert_encoding(
                                  infilename,
                                  from_enc,
                                  to_enc,
                                  eol,
                                  outfilename.replace('#', infilename))
    #
    # Run through all the files
    file_recursor(rec_function, files, recursive, followlinks, dotfiles)

def warn(msg, nl=True):
    """ print a warning message to stderr """
    sys.stderr.write(str(msg))
    if nl: sys.stderr.write("\n")

def no_warn(msg, nl=True):
    pass

def file_recursor(function=None, file_list=(), recurse=True,
                  followlinks=True, dotfiles=False):
    """ Call function(file) for each file in file_list.

        If recurse is True, go into directories recursively. If followlinks is
        True, follow symbolic links. If dotfiles is true, process all files and
        dirs beginning with a dot, if they are in a subdirectory that is
        processed recursively.

        Notice that even when dotfiles is True, you still have to explicitly
        put dotfiles in your initial filelist.

        All names in the file_list will be fully expanded as a glob and with
        environment variables
    """
    from glob import glob
    for name in file_list:
        name = os.path.expanduser(name)
        name = os.path.expandvars(name)
        name = os.path.normcase(name)
        if len(glob(name)) > 1:
            file_recursor(function, glob(name), recurse, followlinks)
            continue
        elif len(glob(name)) == 1:
            name = glob(name)[0]
        if os.path.islink(name):
            if not followlinks:
                warn("'%s' is a symlink, but following links is not activated" \
                       % name)
                continue
            else:
                name = os.readlink(name)
        if os.path.isfile(name):
            function(name)
        elif os.path.isdir(name):
            if name != '..':
                if recurse:
                    file_recursor(function, glob(os.path.join(name, '*')),
                                  recurse, followlinks)
                    if dotfiles:
                        file_recursor(function, glob(os.path.join(name, '.*')),
                                      recurse, followlinks)
                else:
                    warn("'%s' is a directory, but recursive handling is not activated" % name)
        else:
            warn("Can't process '%s'. Not found.\n" % name)
            continue

def convert_encoding(infilename, from_enc, to_enc, eol=os.linesep,
                     outfilename=""):
    """ Convert a text file from_enc to_enc

        If eol is given, it must be an appropriate string to terminate a line,
        i.e. "\\n", "\\r\\n", "\\r". It defaults to the standard line ending
        for your OS (os.linesep)

        If outfilename is given, the results will be written to that file, if
        not, the conversion is done in-place

        from_enc and to_enc are strings containing a name for any
        encoding known to Python. See
        http://docs.python.org/lib/standard-encodings.html
    """

    def error_cleanup():
        if hasattr(infile, 'close'):
            infile.close()
        if hasattr(outfile, 'close'):
            outfile.close()
        if os.path.isfile(outfilename) and os.path.isfile(infilename):
            os.remove(outfilename)

    warn("Processing %s ...  " % infilename, nl=False)
    if os.path.isfile(infilename):
        #
        # choose temp file
        tempfilename = infilename + "." + to_enc
        while os.path.isfile(tempfilename):
            tempfilename = tempfilename + "x"
        #
        # open original file (infile) and tempfile (outfile)
        infile = outfile = None
        try:
            infile = codecs.open(infilename, "rb", from_enc)
        except Exception, details:
            warn("Error opening %s: %s" % (infilename, details));
            error_cleanup()
            return None
        try:
            outfile = codecs.open(tempfilename, "wb", to_enc)
        except Exception, details:
            warn("Error opening %s: %s" % (tempfilename, details))
            error_cleanup()
            return None
        #
        # go through infile, convert, and write to outfile
        try:
            for line in infile:
                try:
                    line = line.replace("\r\n", "\n") # win
                    line = line.replace("\r", "\n") # mac
                    line = line.replace("\n", eol)
                    outfile.write(line)
                except Exception, details:
                    raise Exception, "Error writing to %s: %s" \
                                     % (tempfilename, details);
        except Exception, details:
            warn("Error in I/O: %s" % details)
            error_cleanup()
        else:
            #
            # Finish up: overwrite original file with tempfile
            try:
                infile.close()
                outfile.close()
                shutil.copystat(infilename, tempfilename)
                overwrite = False
                if outfilename == "":
                    outfilename = infilename
                    overwrite = True
                rename_file(tempfilename, outfilename, overwrite)
                warn("%s was successfully converted from %s to %s" \
                      % (infilename, from_enc, to_enc))
                warn("") # finish a block
            except Exception, details:
                warn("Renaming %s to %s FAILED. File was not converted: %s" \
                      % (tempfilename, infilename, details))
                error_cleanup()
                warn("") # finish a block

    else:
        warn("File '%s' does not exist\n" % file)

def convert_eol(infilename, eol=os.linesep, outfilename=""):
    """ Convert just the end of line of a textfile

        If eol is given, it must be an appropriate string to terminate a line,
        i.e. "\\n", "\\r\\n", "\\r". It defaults to the standard line ending
        for your os (os.linesep)

        If outfilename is given, the results will be written to that file, if
        not, the conversion is done in-place

        The encoding of the file is left untouched
    """
    def error_cleanup():
        if hasattr(infile, 'close'):
            infile.close()
        if hasattr(outfile, 'close'):
            outfile.close()
        if os.path.isfile(tempfilename) and os.path.isfile(infilename):
            os.remove(tempfilename)

    warn("Processing %s ...  " % infilename, nl=False)
    if os.path.isfile(infilename):
        #
        # choose temp file
        tempfilename = infilename + "." + "eol"
        while os.path.isfile(tempfilename):
            tempfilename = tempfilename + "x"
        #
        # open original file (infile) and tempfile (outfile)
        infile = outfile = None
        try:
            infile = open(infilename, "rb")
        except Exception, details:
            warn("Error opening %s: %s" % (infilename, details));
            error_cleanup()
            return None
        try:
            outfile = open(tempfilename, "wb")
        except Exception, details:
            warn("Error opening %s: %s" % (tempfilename, details))
            error_cleanup()
            return None
        #
        # go through infile, convert, and write to outfile
        try:
            for line in infile:
                try:
                    if "\x00\r" in line or "\x00\n" in line:
                        eol = eol.replace("\r", "\x00\r")
                        eol = eol.replace("\n", "\x00\n")
                    line = line.replace("\r\n", "\n") # win
                    line = line.replace("\x00\r\x00\n", "\n") # utf16 win
                    line = line.replace("\r", "\n") # mac
                    line = line.replace("\x00\r", "\n") # utf16 mac
                    line = line.replace("\x00\n", "\n") # utf16 unix
                    line = line.replace("\n", eol)
                    outfile.write(line)
                except Exception, details:
                    raise Exception, "Error writing to %s: %s" \
                                      % (tempfilename, details);
        except Exception, details:
            warn("Error in I/O: %s" % details)
            error_cleanup()
        else:
            #
            # Finish up: overwrite original file with tempfile
            try:
                infile.close()
                outfile.close()
                shutil.copystat(infilename, tempfilename)
                overwrite = False
                if outfilename == "":
                    outfilename = infilename
                    overwrite = True
                rename_file(tempfilename, outfilename, overwrite)
                warn("Successfully converted eol for %s" % infilename)
                warn("") # finish a block
            except Exception, details:
                warn("Renaming %s to %s FAILED. File was not converted: %s" \
                      % (tempfilename, infilename, details))
                error_cleanup()
                warn("") # finish a block

    else:
        warn("File '%s' does not exist\n" % file)

def guess_encoding(data):
    """ Given a byte string, attempt to decode it.

        Taken from:
        http://www.pyzine.com/Issue008/Section_Articles/article_Encodings.html#guessing-the-encoding

        Tries the standard 'UTF8' and 'latin-1' encodings,
        Plus several gathered from locale information.

        The calling program *must* first call
            locale.setlocale(locale.LC_ALL, '')

        If successful it returns
            (decoded_unicode, successful_encoding)
        If unsuccessful it raises a ``UnicodeError``
    """
    successful_encoding = None
    # we make 'utf-8' the first encoding
    encodings = ['utf-8']
    #
    # next we add anything we can learn from the locale
    try:
        encodings.append(locale.nl_langinfo(locale.CODESET))
    except AttributeError:
        pass
    try:
        encodings.append(locale.getlocale()[1])
    except (AttributeError, IndexError):
        pass
    try:
        encodings.append(locale.getdefaultlocale()[1])
    except (AttributeError, IndexError):
        pass
    #
    # we try try the rest now
    encodings = encodings + ['latin-1', 'mbcs', 'big5', 'euc_jp', 'euc_kr',
                             'gb2312', 'gbk', 'gb18030', 'hz', 'iso2022_jp',
                             'koi8_u', 'ptcp154', 'shift_jis' ]
    for enc in encodings:
        # some of the locale calls
        # may have returned None
        if not enc:
            continue
        try:
            decoded = unicode(data, enc)
            successful_encoding = enc

        except (UnicodeError, LookupError):
            pass
        else:
            break
    if not successful_encoding:
         raise UnicodeError(
        'Unable to decode input data.  Tried the following encodings: %s.'
        % ', '.join([repr(enc) for enc in encodings if enc]))
    else:
         return (decoded, successful_encoding)

def guess_file_encoding(filename, default):
    """ Guess the encoding of a text file

        If the function is able to guess an encoding for filename, return that
        encoding, otherwise return the default.

        Note that guessing an encoding is not fool-proof, this might return the
        wrong encoding.

        Adapted from:
        http://www.pyzine.com/Issue008/Section_Articles/article_Encodings.html#guessing-the-encoding
    """
    try:
        f = open(filename, "rb")
        the_text = f.read()
        f.close()
    except Exception, details:
        warn("Error while trying to guess the encoding of file %s: %s" \
              % (filename, details))
        return default

    bomdict = { codecs.BOM_UTF8 : 'UTF8',
                codecs.BOM_UTF16_BE : 'UTF-16BE',
                codecs.BOM_UTF16_LE : 'UTF-16LE' }

    # check if there is Unicode signature
    for bom, encoding in bomdict.items():
        if the_text.startswith(bom):
            the_text = the_text[len(bom):]
            break
        else:
            bom  = None
            encoding = None

    if encoding is None:    # there was no BOM
        try:
            unicode_text, encoding = guess_encoding(the_text)
        except UnicodeError:
            warn("Can't work out the encoding of file '%s'." % filename)
            warn("Assuming the default encoding: %s" % default)
            return default
    warn("Guessed encoding for file '%s': %s" % (filename, encoding))
    return encoding

def rename_file(file1, file2, overwrite=False):
    """ Rename file1 to file2, ask for directions if file2 already exists """
    if os.path.isfile(file1):
        if os.path.isfile(file2):
            while not overwrite:
                answer = raw_input("%s already exists. Do you want to overwrite? Yes [No] Abort: " \
                                    % file2).lower()
                if answer == 'yes':
                    overwrite = True
                elif answer == 'abort':
                    return None
                else:
                    answer = raw_input("Enter a new filename: ")
                    if answer != '': file2 = os.path.normcase(answer)
                    if not os.path.isfile(file2):
                        overwrite = True
            if file2 != file1: os.remove(file2)
        try:
            os.rename(file1, file2)
        except Exception, details:
            warn( "Failed to rename %s to %s: %s" % (file1, file2, details) )

def usage():
    """ Print usage for main program """
    print __doc__

if __name__ == "__main__":
    main()

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
Convert text files between arbitrary encodings and line endings.

Usage:
convert_encoding.py [options] file1 file2 ...

Options are:
    -f enc,  --from=enc    the input encoding
                           (default: locale.getpreferredencoding())
                           Set this to 'guess' if the encoding is unknown.
    -t enc,  --to=enc      the output encoding
                           (default: locale.getpreferredencoding())
    -e code, --eol=code    the end-of-line mode (see below)
    -o file, --out=file    where to write the converted data. Conversion is
                           done in-place if this is omitted
    -n, --nocodec          ignore input and output encoding, just process eol
    -r, --recursive        go into directories recursively
    -l, --followlinks      follow symbolic links
    -d, --dotfiles         in combination with -r: process files that begin
                           with a dot, if they are found in a directory reached
                           by recursion. You still have to specify dotfiles
                           explicitly in the initial filelist.
    -q, --quiet            don't display warnings or status messages

For valid encoding names, see
https://docs.python.org/2.4/lib/standard-encodings.html

Accepted values for the eol code are: 'unix', 'linux', 'dos', 'win', 'mac'.
If you do not specify an end-of-line, it will be set to your system's default,
regardless of the original end-of-line.

Any '#' in the out-filename will be replaced with the full name of the input
file.

Note that guessing the input encoding is not foolproof in any way. Always
provide an explicit input encoding if you can.

The program supports full unix style globbing on all operation systems,
independently of your shell's capabilities. Also, environment variables
and '~' will be expanded following the usual conventions.

The file may also be used as a module from python.
"""

"""
   Copyright (C) 2007  Michael Goerz

   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 sys
import codecs
import getopt
import os
import os.path
import locale
import shutil

def main():
    """Command line program for converting encodings and end-of-lines """
    #
    # command line parsing / initialization
    global warn
    try:
        opts, files = getopt.getopt(sys.argv[1:], "hf:t:e:o:rlqnd",
                                                 ["help", "from=", "to=","eol=",
                                                  "recursive", "followlinks",
                                                  "quiet", "nocodec", "out=",
                                                  "dotfiles"])
    except getopt.GetoptError, details:
        warn(details)
        exit(2)
    locale.setlocale(locale.LC_ALL, '')
    from_enc = locale.getpreferredencoding()
    to_enc = locale.getpreferredencoding()
    eol = os.linesep
    recursive = False
    followlinks = False
    dotfiles = False
    guess = False
    nocodec = False
    outfilename = ""
    for o, a in opts:
        if o in ("-h", "--help"):
            usage()
            sys.exit()
        if o in ("-f", "--from"):
            if a == 'guess':
                guess = True
            else:
                from_enc = a
        if o in ("-t", "--to"):
            to_enc = a
        if o in ("-o", "--out"):
            outfilename = os.path.normcase(a)
        if o in ("-r", "--recursive"):
            recursive = True
        if o in ("-d", "--dotfiles"):
            dotfiles = True
        if o in ("-q", "--quiet"):
            warn = no_warn
        if o in ("-n", "--nocodec"):
            nocodec = True
        if o in ("-e", "--eol"):
            eolmode = a.lower()
            os_eols = {'unix':"\n",
                       'linux':"\n",
                       'dos':"\r\n",
                       'win':"\r\n",
                       'mac':"\r"}
            try:
                eol = os_eols[eolmode]
            except KeyError:
                warn("'%s' is not a valid name for a line ending." % eolmode)
                warn("Use 'unix', 'linux', 'dos', 'win', or 'mac'.")
                warn("Converting to your default line ending")
        if o in ("-l", "--followlinks"):
            recursive = True
        if o in ("-l", "--followlinks"):
            followlinks = True
    #
    # put together what we actually have to do
    if nocodec:
        rec_function = lambda infilename: convert_eol(infilename, eol, \
                                          outfilename.replace('#', infilename))
    else:
        if guess:
            warn("WARNING: guessing the input encoding is dangerous. "
                  + "Make sure to check the results.\n")
            rec_function = lambda infilename: \
                               convert_encoding(
                                   infilename,
                                   guess_file_encoding(infilename, from_enc),
                                   to_enc,
                                   eol,
                                   outfilename.replace('#', infilename))
        else:
           rec_function = lambda infilename: \
                              convert_encoding(
                                  infilename,
                                  from_enc,
                                  to_enc,
                                  eol,
                                  outfilename.replace('#', infilename))
    #
    # Run through all the files
    file_recursor(rec_function, files, recursive, followlinks, dotfiles)

def warn(msg, nl=True):
    """ print a warning message to stderr """
    sys.stderr.write(str(msg))
    if nl: sys.stderr.write("\n")

def no_warn(msg, nl=True):
    pass

def file_recursor(function=None, file_list=(), recurse=True,
                  followlinks=True, dotfiles=False):
    """ Call function(file) for each file in file_list.

        If recurse is True, go into directories recursively. If followlinks is
        True, follow symbolic links. If dotfiles is true, process all files and
        dirs beginning with a dot, if they are in a subdirectory that is
        processed recursively.

        Notice that even when dotfiles is True, you still have to explicitly
        put dotfiles in your initial filelist.

        All names in the file_list will be fully expanded as a glob and with
        environment variables
    """
    from glob import glob
    for name in file_list:
        name = os.path.expanduser(name)
        name = os.path.expandvars(name)
        name = os.path.normcase(name)
        if len(glob(name)) > 1:
            file_recursor(function, glob(name), recurse, followlinks)
            continue
        elif len(glob(name)) == 1:
            name = glob(name)[0]
        if os.path.islink(name):
            if not followlinks:
                warn("'%s' is a symlink, but following links is not activated" \
                       % name)
                continue
            else:
                name = os.readlink(name)
        if os.path.isfile(name):
            function(name)
        elif os.path.isdir(name):
            if name != '..':
                if recurse:
                    file_recursor(function, glob(os.path.join(name, '*')),
                                  recurse, followlinks)
                    if dotfiles:
                        file_recursor(function, glob(os.path.join(name, '.*')),
                                      recurse, followlinks)
                else:
                    warn("'%s' is a directory, but recursive handling is not activated" % name)
        else:
            warn("Can't process '%s'. Not found.\n" % name)
            continue

def convert_encoding(infilename, from_enc, to_enc, eol=os.linesep,
                     outfilename=""):
    """ Convert a text file from_enc to_enc

        If eol is given, it must be an appropriate string to terminate a line,
        i.e. "\\n", "\\r\\n", "\\r". It defaults to the standard line ending
        for your OS (os.linesep)

        If outfilename is given, the results will be written to that file, if
        not, the conversion is done in-place

        from_enc and to_enc are strings containing a name for any
        encoding known to Python. See
        http://docs.python.org/lib/standard-encodings.html
    """

    def error_cleanup():
        if hasattr(infile, 'close'):
            infile.close()
        if hasattr(outfile, 'close'):
            outfile.close()
        if os.path.isfile(outfilename) and os.path.isfile(infilename):
            os.remove(outfilename)

    warn("Processing %s ...  " % infilename, nl=False)
    if os.path.isfile(infilename):
        #
        # choose temp file
        tempfilename = infilename + "." + to_enc
        while os.path.isfile(tempfilename):
            tempfilename = tempfilename + "x"
        #
        # open original file (infile) and tempfile (outfile)
        infile = outfile = None
        try:
            infile = codecs.open(infilename, "rb", from_enc)
        except Exception, details:
            warn("Error opening %s: %s" % (infilename, details));
            error_cleanup()
            return None
        try:
            outfile = codecs.open(tempfilename, "wb", to_enc)
        except Exception, details:
            warn("Error opening %s: %s" % (tempfilename, details))
            error_cleanup()
            return None
        #
        # go through infile, convert, and write to outfile
        try:
            for line in infile:
                try:
                    line = line.replace("\r\n", "\n") # win
                    line = line.replace("\r", "\n") # mac
                    line = line.replace("\n", eol)
                    outfile.write(line)
                except Exception, details:
                    raise Exception, "Error writing to %s: %s" \
                                     % (tempfilename, details);
        except Exception, details:
            warn("Error in I/O: %s" % details)
            error_cleanup()
        else:
            #
            # Finish up: overwrite original file with tempfile
            try:
                infile.close()
                outfile.close()
                shutil.copystat(infilename, tempfilename)
                overwrite = False
                if outfilename == "":
                    outfilename = infilename
                    overwrite = True
                rename_file(tempfilename, outfilename, overwrite)
                warn("%s was successfully converted from %s to %s" \
                      % (infilename, from_enc, to_enc))
                warn("") # finish a block
            except Exception, details:
                warn("Renaming %s to %s FAILED. File was not converted: %s" \
                      % (tempfilename, infilename, details))
                error_cleanup()
                warn("") # finish a block

    else:
        warn("File '%s' does not exist\n" % file)

def convert_eol(infilename, eol=os.linesep, outfilename=""):
    """ Convert just the end of line of a textfile

        If eol is given, it must be an appropriate string to terminate a line,
        i.e. "\\n", "\\r\\n", "\\r". It defaults to the standard line ending
        for your os (os.linesep)

        If outfilename is given, the results will be written to that file, if
        not, the conversion is done in-place

        The encoding of the file is left untouched
    """
    def error_cleanup():
        if hasattr(infile, 'close'):
            infile.close()
        if hasattr(outfile, 'close'):
            outfile.close()
        if os.path.isfile(tempfilename) and os.path.isfile(infilename):
            os.remove(tempfilename)

    warn("Processing %s ...  " % infilename, nl=False)
    if os.path.isfile(infilename):
        #
        # choose temp file
        tempfilename = infilename + "." + "eol"
        while os.path.isfile(tempfilename):
            tempfilename = tempfilename + "x"
        #
        # open original file (infile) and tempfile (outfile)
        infile = outfile = None
        try:
            infile = open(infilename, "rb")
        except Exception, details:
            warn("Error opening %s: %s" % (infilename, details));
            error_cleanup()
            return None
        try:
            outfile = open(tempfilename, "wb")
        except Exception, details:
            warn("Error opening %s: %s" % (tempfilename, details))
            error_cleanup()
            return None
        #
        # go through infile, convert, and write to outfile
        try:
            for line in infile:
                try:
                    if "\x00\r" in line or "\x00\n" in line:
                        eol = eol.replace("\r", "\x00\r")
                        eol = eol.replace("\n", "\x00\n")
                    line = line.replace("\r\n", "\n") # win
                    line = line.replace("\x00\r\x00\n", "\n") # utf16 win
                    line = line.replace("\r", "\n") # mac
                    line = line.replace("\x00\r", "\n") # utf16 mac
                    line = line.replace("\x00\n", "\n") # utf16 unix
                    line = line.replace("\n", eol)
                    outfile.write(line)
                except Exception, details:
                    raise Exception, "Error writing to %s: %s" \
                                      % (tempfilename, details);
        except Exception, details:
            warn("Error in I/O: %s" % details)
            error_cleanup()
        else:
            #
            # Finish up: overwrite original file with tempfile
            try:
                infile.close()
                outfile.close()
                shutil.copystat(infilename, tempfilename)
                overwrite = False
                if outfilename == "":
                    outfilename = infilename
                    overwrite = True
                rename_file(tempfilename, outfilename, overwrite)
                warn("Successfully converted eol for %s" % infilename)
                warn("") # finish a block
            except Exception, details:
                warn("Renaming %s to %s FAILED. File was not converted: %s" \
                      % (tempfilename, infilename, details))
                error_cleanup()
                warn("") # finish a block

    else:
        warn("File '%s' does not exist\n" % file)

def guess_encoding(data):
    """ Given a byte string, attempt to decode it.

        Taken from:
        http://www.pyzine.com/Issue008/Section_Articles/article_Encodings.html#guessing-the-encoding

        Tries the standard 'UTF8' and 'latin-1' encodings,
        Plus several gathered from locale information.

        The calling program *must* first call
            locale.setlocale(locale.LC_ALL, '')

        If successful it returns
            (decoded_unicode, successful_encoding)
        If unsuccessful it raises a ``UnicodeError``
    """
    successful_encoding = None
    # we make 'utf-8' the first encoding
    encodings = ['utf-8']
    #
    # next we add anything we can learn from the locale
    try:
        encodings.append(locale.nl_langinfo(locale.CODESET))
    except AttributeError:
        pass
    try:
        encodings.append(locale.getlocale()[1])
    except (AttributeError, IndexError):
        pass
    try:
        encodings.append(locale.getdefaultlocale()[1])
    except (AttributeError, IndexError):
        pass
    #
    # we try try the rest now
    encodings = encodings + ['latin-1', 'mbcs', 'big5', 'euc_jp', 'euc_kr',
                             'gb2312', 'gbk', 'gb18030', 'hz', 'iso2022_jp',
                             'koi8_u', 'ptcp154', 'shift_jis' ]
    for enc in encodings:
        # some of the locale calls
        # may have returned None
        if not enc:
            continue
        try:
            decoded = unicode(data, enc)
            successful_encoding = enc

        except (UnicodeError, LookupError):
            pass
        else:
            break
    if not successful_encoding:
         raise UnicodeError(
        'Unable to decode input data.  Tried the following encodings: %s.'
        % ', '.join([repr(enc) for enc in encodings if enc]))
    else:
         return (decoded, successful_encoding)

def guess_file_encoding(filename, default):
    """ Guess the encoding of a text file

        If the function is able to guess an encoding for filename, return that
        encoding, otherwise return the default.

        Note that guessing an encoding is not fool-proof, this might return the
        wrong encoding.

        Adapted from:
        http://www.pyzine.com/Issue008/Section_Articles/article_Encodings.html#guessing-the-encoding
    """
    try:
        f = open(filename, "rb")
        the_text = f.read()
        f.close()
    except Exception, details:
        warn("Error while trying to guess the encoding of file %s: %s" \
              % (filename, details))
        return default

    bomdict = { codecs.BOM_UTF8 : 'UTF8',
                codecs.BOM_UTF16_BE : 'UTF-16BE',
                codecs.BOM_UTF16_LE : 'UTF-16LE' }

    # check if there is Unicode signature
    for bom, encoding in bomdict.items():
        if the_text.startswith(bom):
            the_text = the_text[len(bom):]
            break
        else:
            bom  = None
            encoding = None

    if encoding is None:    # there was no BOM
        try:
            unicode_text, encoding = guess_encoding(the_text)
        except UnicodeError:
            warn("Can't work out the encoding of file '%s'." % filename)
            warn("Assuming the default encoding: %s" % default)
            return default
    warn("Guessed encoding for file '%s': %s" % (filename, encoding))
    return encoding

def rename_file(file1, file2, overwrite=False):
    """ Rename file1 to file2, ask for directions if file2 already exists """
    if os.path.isfile(file1):
        if os.path.isfile(file2):
            while not overwrite:
                answer = raw_input("%s already exists. Do you want to overwrite? Yes [No] Abort: " \
                                    % file2).lower()
                if answer == 'yes':
                    overwrite = True
                elif answer == 'abort':
                    return None
                else:
                    answer = raw_input("Enter a new filename: ")
                    if answer != '': file2 = os.path.normcase(answer)
                    if not os.path.isfile(file2):
                        overwrite = True
            if file2 != file1: os.remove(file2)
        try:
            os.rename(file1, file2)
        except Exception, details:
            warn( "Failed to rename %s to %s: %s" % (file1, file2, details) )

def usage():
    """ Print usage for main program """
    print __doc__

if __name__ == "__main__":
    main()


# thanks _why
# http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html
class Object
  # The hidden singleton lurks behind everyone
  def metaclass; class << self; self; end; end
  def meta_eval &blk; metaclass.instance_eval &blk; end

  # Adds methods to a metaclass
  def meta_def name, &blk
    meta_eval { define_method name, &blk }
  end

  # Defines an instance method within a class
  def class_def name, &blk
    class_eval { define_method name, &blk }
  end
end

# thanks _why
# http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html
class Object
  # The hidden singleton lurks behind everyone
  def metaclass; class << self; self; end; end
  def meta_eval &blk; metaclass.instance_eval &blk; end

  # Adds methods to a metaclass
  def meta_def name, &blk
    meta_eval {
      define_method(name) {|*args, &block|
        blk.call(*args, &block)
      }
    }
  end

  # Defines an instance method within a class
  def class_def name, &blk
    class_eval { define_method name, &blk }
  end
end


from decimal import Decimal
from braintree.util.http import Http
import braintree
import warnings
from braintree.add_on import AddOn
from braintree.descriptor import Descriptor
from braintree.discount import Discount
from braintree.exceptions.not_found_error import NotFoundError
from braintree.resource_collection import ResourceCollection
from braintree.subscription_status_event import SubscriptionStatusEvent
from braintree.successful_result import SuccessfulResult
from braintree.error_result import ErrorResult
from braintree.transaction import Transaction
from braintree.resource import Resource
from braintree.configuration import Configuration

class Subscription(Resource):
    """
    A class representing a Subscription.

    An example of creating a subscription with all available fields::

        result = braintree.Subscription.create({
            "id": "my_subscription_id",
            "merchant_account_id": "merchant_account_one",
            "payment_method_token": "my_payment_token",
            "plan_id": "some_plan_id",
            "price": "29.95",
            "trial_duration": 1,
            "trial_duration_unit": braintree.Subscription.TrialDurationUnit.Month,
            "trial_period": True
        })

    For more information on Subscriptions, see https://developers.braintreepayments.com/reference/request/subscription/create/python

    """

    # NEXT_MAJOR_VERSION this can be an enum! they were added as of python 3.4 and we support 3.5+
    class TrialDurationUnit(object):
        """
        Constants representing trial duration units.  Available types are:

        * braintree.Subscription.TrialDurationUnit.Day
        * braintree.Subscription.TrialDurationUnit.Month
        """

        Day = "day"
        Month = "month"

    # NEXT_MAJOR_VERSION this can be an enum! they were added as of python 3.4 and we support 3.5+
    class Source(object):
        Api          = "api"
        ControlPanel = "control_panel"
        Recurring    = "recurring"

    # NEXT_MAJOR_VERSION this can be an enum! they were added as of python 3.4 and we support 3.5+
    class Status(object):
        """
        Constants representing subscription statusues.  Available statuses are:

        * braintree.Subscription.Status.Active
        * braintree.Subscription.Status.Canceled
        * braintree.Subscription.Status.Expired
        * braintree.Subscription.Status.PastDue
        * braintree.Subscription.Status.Pending
        """

        Active = "Active"
        Canceled = "Canceled"
        Expired = "Expired"
        PastDue = "Past Due"
        Pending = "Pending"

    @staticmethod
    def create(params=None):
        """
        Create a Subscription

        Token and Plan are required:::

            result = braintree.Subscription.create({
                "payment_method_token": "my_payment_token",
                "plan_id": "some_plan_id",
            })

        """
        if params is None:
            params = {}
        return Configuration.gateway().subscription.create(params)

    @staticmethod
    def create_signature():
        return [
            "billing_day_of_month",
            "first_billing_date",
            "id",
            "merchant_account_id",
            "never_expires",
            "number_of_billing_cycles",
            "payment_method_nonce",
            "payment_method_token",
            "plan_id",
            "price",
            "trial_duration",
            "trial_duration_unit",
            "trial_period",
            {
                "descriptor": ["name", "phone", "url"]
            },
            {
                "options": [
                    "do_not_inherit_add_ons_or_discounts",
                    "start_immediately",
                    {
                        "paypal": ["description"]
                    }
                ]
            }
        ] + Subscription._add_ons_discounts_signature()

    @staticmethod
    def find(subscription_id):
        """
        Find a subscription given a subscription_id.  This does not return a result
        object.  This will raise a :class:`NotFoundError <braintree.exceptions.not_found_error.NotFoundError>`
        if the provided subscription_id is not found. ::

            subscription = braintree.Subscription.find("my_subscription_id")
        """

        return Configuration.gateway().subscription.find(subscription_id)

    @staticmethod
    def retry_charge(subscription_id, amount=None, submit_for_settlement=False):
        return Configuration.gateway().subscription.retry_charge(subscription_id, amount, submit_for_settlement)

    @staticmethod
    def update(subscription_id, params=None):
        """
        Update an existing subscription

        By subscription_id. The params are similar to create::

            result = braintree.Subscription.update("my_subscription_id", {
                "price": "9.99",
            })

        """
        if params is None:
            params = {}
        return Configuration.gateway().subscription.update(subscription_id, params)

    @staticmethod
    def cancel(subscription_id):
        """
        Cancel a subscription

        By subscription_id::

            result = braintree.Subscription.cancel("my_subscription_id")

        """

        return Configuration.gateway().subscription.cancel(subscription_id)

    @staticmethod
    def search(*query):
        """
        Allows searching on subscriptions. There are two types of fields that are searchable: text and
        multiple value fields. Searchable text fields are:
        - plan_id
        - days_past_due

        Searchable multiple value fields are:
        - status

        For text fields, you can search using the following operators: ==, !=, starts_with, ends_with
        and contains. For mutiple value fields, you can search using the in_list operator. An example::

            braintree.Subscription.search([
                braintree.SubscriptionSearch.plan_id.starts_with("abc"),
                braintree.SubscriptionSearch.days_past_due == "30",
                braintree.SubscriptionSearch.status.in_list([braintree.Subscription.Status.PastDue])
            ])
        """

        return Configuration.gateway().subscription.search(*query)

    @staticmethod
    def update_signature():
        return [
            "id",
            "merchant_account_id",
            "never_expires",
            "number_of_billing_cycles",
            "payment_method_nonce",
            "payment_method_token",
            "plan_id",
            "price",
            {
                "descriptor": [ "name", "phone", "url" ]
            },
            {
                "options": [
                    "prorate_charges",
                    "replace_all_add_ons_and_discounts",
                    "revert_subscription_on_proration_failure",
                    {
                        "paypal": [ "description" ]
                    }
                ]
            }
        ] + Subscription._add_ons_discounts_signature()

    @staticmethod
    def _add_ons_discounts_signature():
        return [
            {
                "add_ons": [{
                    "add": ["amount", "inherited_from_id", "never_expires", "number_of_billing_cycles", "quantity"],
                    "remove": ["__any_key__"],
                    "update": ["amount", "existing_id", "never_expires", "number_of_billing_cycles", "quantity"]
                }],
                "discounts": [{
                    "add": ["amount", "inherited_from_id", "never_expires", "number_of_billing_cycles", "quantity"],
                    "remove": ["__any_key__"],
                    "update": ["amount", "existing_id", "never_expires", "number_of_billing_cycles", "quantity"]
                }]
            }
        ]

    def __init__(self, gateway, attributes):
        Resource.__init__(self, gateway, attributes)
        if "price" in attributes:
            self.price = Decimal(self.price)
        if "balance" in attributes:
            self.balance = Decimal(self.balance)
        if "next_billing_period_amount" in attributes:
            self.next_billing_period_amount = Decimal(self.next_billing_period_amount)
        if "add_ons" in attributes:
            self.add_ons = [AddOn(gateway, add_on) for add_on in self.add_ons]
        if "descriptor" in attributes:
            self.descriptor = Descriptor(gateway, attributes.pop("descriptor"))
        if "description" in attributes:
            self.description = attributes["description"]
        if "discounts" in attributes:
            self.discounts = [Discount(gateway, discount) for discount in self.discounts]
        if "status_history" in attributes:
            self.status_history = [SubscriptionStatusEvent(gateway, status_event) for status_event in self.status_history]
        if "transactions" in attributes:
            self.transactions = [Transaction(gateway, transaction) for transaction in self.transactions]

from decimal import Decimal
from braintree.util.http import Http
import braintree
import warnings
from braintree.add_on import AddOn
from braintree.descriptor import Descriptor
from braintree.discount import Discount
from braintree.exceptions.not_found_error import NotFoundError
from braintree.resource_collection import ResourceCollection
from braintree.subscription_status_event import SubscriptionStatusEvent
from braintree.successful_result import SuccessfulResult
from braintree.error_result import ErrorResult
from braintree.transaction import Transaction
from braintree.resource import Resource
from braintree.configuration import Configuration

class Subscription(Resource):
    """
    A class representing a Subscription.

    An example of creating a subscription with all available fields::

        result = braintree.Subscription.create({
            "id": "my_subscription_id",
            "merchant_account_id": "merchant_account_one",
            "payment_method_token": "my_payment_token",
            "plan_id": "some_plan_id",
            "price": "29.95",
            "trial_duration": 1,
            "trial_duration_unit": braintree.Subscription.TrialDurationUnit.Month,
            "trial_period": True
        })

    For more information on Subscriptions, see https://developers.braintreepayments.com/reference/request/subscription/create/python

    """

    # NEXT_MAJOR_VERSION this can be an enum! they were added as of python 3.4 and we support 3.5+
    class TrialDurationUnit(object):
        """
        Constants representing trial duration units.  Available types are:

        * braintree.Subscription.TrialDurationUnit.Day
        * braintree.Subscription.TrialDurationUnit.Month
        """

        Day = "day"
        Month = "month"

    # NEXT_MAJOR_VERSION this can be an enum! they were added as of python 3.4 and we support 3.5+
    class Source(object):
        Api          = "api"
        ControlPanel = "control_panel"
        Recurring    = "recurring"

    # NEXT_MAJOR_VERSION this can be an enum! they were added as of python 3.4 and we support 3.5+
    class Status(object):
        """
        Constants representing subscription statuses.  Available statuses are:

        * braintree.Subscription.Status.Active
        * braintree.Subscription.Status.Canceled
        * braintree.Subscription.Status.Expired
        * braintree.Subscription.Status.PastDue
        * braintree.Subscription.Status.Pending
        """

        Active = "Active"
        Canceled = "Canceled"
        Expired = "Expired"
        PastDue = "Past Due"
        Pending = "Pending"

    @staticmethod
    def create(params=None):
        """
        Create a Subscription

        Token and Plan are required:::

            result = braintree.Subscription.create({
                "payment_method_token": "my_payment_token",
                "plan_id": "some_plan_id",
            })

        """
        if params is None:
            params = {}
        return Configuration.gateway().subscription.create(params)

    @staticmethod
    def create_signature():
        return [
            "billing_day_of_month",
            "first_billing_date",
            "id",
            "merchant_account_id",
            "never_expires",
            "number_of_billing_cycles",
            "payment_method_nonce",
            "payment_method_token",
            "plan_id",
            "price",
            "trial_duration",
            "trial_duration_unit",
            "trial_period",
            {
                "descriptor": ["name", "phone", "url"]
            },
            {
                "options": [
                    "do_not_inherit_add_ons_or_discounts",
                    "start_immediately",
                    {
                        "paypal": ["description"]
                    }
                ]
            }
        ] + Subscription._add_ons_discounts_signature()

    @staticmethod
    def find(subscription_id):
        """
        Find a subscription given a subscription_id.  This does not return a result
        object.  This will raise a :class:`NotFoundError <braintree.exceptions.not_found_error.NotFoundError>`
        if the provided subscription_id is not found. ::

            subscription = braintree.Subscription.find("my_subscription_id")
        """

        return Configuration.gateway().subscription.find(subscription_id)

    @staticmethod
    def retry_charge(subscription_id, amount=None, submit_for_settlement=False):
        return Configuration.gateway().subscription.retry_charge(subscription_id, amount, submit_for_settlement)

    @staticmethod
    def update(subscription_id, params=None):
        """
        Update an existing subscription

        By subscription_id. The params are similar to create::

            result = braintree.Subscription.update("my_subscription_id", {
                "price": "9.99",
            })

        """
        if params is None:
            params = {}
        return Configuration.gateway().subscription.update(subscription_id, params)

    @staticmethod
    def cancel(subscription_id):
        """
        Cancel a subscription

        By subscription_id::

            result = braintree.Subscription.cancel("my_subscription_id")

        """

        return Configuration.gateway().subscription.cancel(subscription_id)

    @staticmethod
    def search(*query):
        """
        Allows searching on subscriptions. There are two types of fields that are searchable: text and
        multiple value fields. Searchable text fields are:
        - plan_id
        - days_past_due

        Searchable multiple value fields are:
        - status

        For text fields, you can search using the following operators: ==, !=, starts_with, ends_with
        and contains. For multiple value fields, you can search using the in_list operator. An example::

            braintree.Subscription.search([
                braintree.SubscriptionSearch.plan_id.starts_with("abc"),
                braintree.SubscriptionSearch.days_past_due == "30",
                braintree.SubscriptionSearch.status.in_list([braintree.Subscription.Status.PastDue])
            ])
        """

        return Configuration.gateway().subscription.search(*query)

    @staticmethod
    def update_signature():
        return [
            "id",
            "merchant_account_id",
            "never_expires",
            "number_of_billing_cycles",
            "payment_method_nonce",
            "payment_method_token",
            "plan_id",
            "price",
            {
                "descriptor": [ "name", "phone", "url" ]
            },
            {
                "options": [
                    "prorate_charges",
                    "replace_all_add_ons_and_discounts",
                    "revert_subscription_on_proration_failure",
                    {
                        "paypal": [ "description" ]
                    }
                ]
            }
        ] + Subscription._add_ons_discounts_signature()

    @staticmethod
    def _add_ons_discounts_signature():
        return [
            {
                "add_ons": [{
                    "add": ["amount", "inherited_from_id", "never_expires", "number_of_billing_cycles", "quantity"],
                    "remove": ["__any_key__"],
                    "update": ["amount", "existing_id", "never_expires", "number_of_billing_cycles", "quantity"]
                }],
                "discounts": [{
                    "add": ["amount", "inherited_from_id", "never_expires", "number_of_billing_cycles", "quantity"],
                    "remove": ["__any_key__"],
                    "update": ["amount", "existing_id", "never_expires", "number_of_billing_cycles", "quantity"]
                }]
            }
        ]

    def __init__(self, gateway, attributes):
        Resource.__init__(self, gateway, attributes)
        if "price" in attributes:
            self.price = Decimal(self.price)
        if "balance" in attributes:
            self.balance = Decimal(self.balance)
        if "next_billing_period_amount" in attributes:
            self.next_billing_period_amount = Decimal(self.next_billing_period_amount)
        if "add_ons" in attributes:
            self.add_ons = [AddOn(gateway, add_on) for add_on in self.add_ons]
        if "descriptor" in attributes:
            self.descriptor = Descriptor(gateway, attributes.pop("descriptor"))
        if "description" in attributes:
            self.description = attributes["description"]
        if "discounts" in attributes:
            self.discounts = [Discount(gateway, discount) for discount in self.discounts]
        if "status_history" in attributes:
            self.status_history = [SubscriptionStatusEvent(gateway, status_event) for status_event in self.status_history]
        if "transactions" in attributes:
            self.transactions = [Transaction(gateway, transaction) for transaction in self.transactions]


/*
    Behaviour
    Class is inspired from Ben Nolan's behaviour.js script
    (http://bennolan.com/behaviour/) but uses Prototype's
    $$() and Element.getElementsBySelector() functions since
    they support more CSS syntax and are already loaded with
    prototype.
*/
var Behaviour = {
    rules : $H({}),
    register : function(new_rules) {
        Behaviour.rules = Behaviour.rules.merge(new_rules);
    },
    apply : function(el) {
        //console.log('START BEHAVIOUR APPLICATION');
        Behaviour.rules.each(function(pair) {
            var rule = pair.key;

            //var start_time = new Date().valueOf();
            //console.log('applying: ' + rule);
            var behaviour = pair.value;
            // if we have an element, use Element.select()
            // else use $$() to find the targets
            var targets;
            if( el ) {
                targets = $(el).select(rule);
            } else {
                targets = $$(rule);
            }

            // if we got anything back then apply the behaviour
            //console.log('  found ' + targets.size() + ' elements');
            if( targets.size() > 0 ) {
                targets.each(function(target) { behaviour(target) });
            }
            //var end_time = new Date().valueOf();
            //console.log('  took: ' + (end_time - start_time) + 'ms');
        });
    }
};

var Smolder = {};

Smolder.load = function(target, json) {
    if(! json) json = {};
    // update the navigation if we need to
    if( json.update_nav ) Smolder.update_nav();

    // apply our registered behaviours
    Behaviour.apply(target);

    // run any code from Smolder.onload()
    var size = Smolder.onload_code.length;
    for(var i=0; i< size; i++) {
        var code = Smolder.onload_code.pop();
        if( code ) code();
    }

    // show any messages we got
    Smolder.show_messages(json);
};

/*
    Smolder.onload()
    Add some code that will get executed after the DOM is loaded
    (but without having to wait on images, etc to load).
    Multiple calls will not overwrite previous calls and all code
    given will be executed in the order give.
*/
Smolder.onload_code = [];
Smolder.onload = function(code) {
    Smolder.onload_code.push(code);
};

/*
    Smolder.Cookie.get(name)
    Returns the value of a specific cookie.
*/
Smolder.Cookie = {};
Smolder.Cookie.get = function(name) {
    var value  = null;
    var cookie = document.cookie;
    var start, end;

    if ( cookie.length > 0 ) {
        start = cookie.indexOf( name + '=' );

        // if the cookie exists
        if ( start != -1 )  {
          start += name.length + 1; // need to account for the '='

          // set index of beginning of value
          end = cookie.indexOf( ';', start );

          if ( end == -1 ) end = cookie.length;

          value = unescape( cookie.substring( start, end ) );
        }
    }
    return value;
};

/*
    Smolder.Cookie.set(name, value)
    Sets a cookie to a particular value.
*/
Smolder.Cookie.set = function(name, value) {
    document.cookie = name + '=' + value;
};

///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
Smolder.update_nav = function(){
    Smolder.Ajax.update({
        url    : '/app/public/nav',
        target : 'nav'
    });
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// FUNCTION: Smolder.Ajax.request
// takes the following named args
// url       : the full url of the request (required)
// parmas    : an object of query params to send along
// indicator : the id of the image to use as an indicator (optional defaults to 'indicator')
// onComplete: a call back function to be executed after the normal processing (optional)
//              Receives as arguments, the same args passed into Smolder.Ajax.request
//
//  Smolder.Ajax.request({
//      url        : '/app/some_mod/something',
//      params     : { foo: 1, bar: 2 },
//      indicator  : 'add_indicator',
//      onComplete : function(args) {
//          // do something
//      }
//  });
///////////////////////////////////////////////////////////////////////////////////////////////////
Smolder.Ajax = {};
Smolder.Ajax.request = function(args) {
    var url       = args.url;
    var params    = args.params || {};
    var indicator = args.indicator;
    var on_complete  = args.onComplete || Prototype.emptyFunction;;
    var on_failure  = args.onFailure || Prototype.emptyFunction;;
    var on_exception  = args.onException || Prototype.emptyFunction;;

    // tell the user that we're doing something
    Smolder.show_indicator(indicator);

    // add the ajax=1 flag to the existing query params
    params.ajax = 1;

    new Ajax.Request(
        url,
        {
            parameters  : params,
            asynchronous: true,
            evalScripts : true,
            onComplete : function(request, json) {
                if(! json ) json = {};
                Smolder.show_messages(json);

                // hide the indicator
                Smolder.hide_indicator(indicator);

                // do whatever else the caller wants
                args.request = request;
                args.json    = json || {};
                on_complete(args);
            },
            onException: function(request, exception) {
                on_exception();
                alert("ERROR FROM AJAX REQUEST:\n" + exception);
            },
            onFailure: function(request) {
                on_failure();
                Smolder.show_error();
            }
        }
    );
};

///////////////////////////////////////////////////////////////////////////////////////////////////
// FUNCTION: Smolder.Ajax.update
// takes the following named args
// url       : the full url of the request (required)
// parmas    : an object of query params to send along
// target    : the id of the element receiving the contents (optional defaults to 'content')
// indicator : the id of the image to use as an indicator (optional defaults to 'indicator')
// onComplete: a call back function to be executed after the normal processing (optional)
//              Receives as arguments, the same args passed into Smolder.Ajax.update
//
//  Smolder.Ajax.update({
//      url        : '/app/some_mod/something',
//      params     : { foo: 1, bar: 2 },
//      target     : 'div_id',
//      indicator  : 'add_indicator',
//      onComplete : function(args) {
//          // do something
//      }
//  });
///////////////////////////////////////////////////////////////////////////////////////////////////
Smolder.Ajax.update = function(args) {
    var url       = args.url;
    var params    = args.params || {};
    var target    = args.target;
    var indicator = args.indicator;
    var on_complete  = args.onComplete || Prototype.emptyFunction;
    var on_failure   = args.onFailure || Prototype.emptyFunction;
    var on_exception = args.onFailure || Prototype.emptyFunction;

    // tell the user that we're doing something
    Smolder.show_indicator(indicator);

    // add the ajax=1 flag to the existing query params
    params.ajax = 1;

    // the default target
    if( target == null || target == '' )
        target = 'content';

    new Ajax.Updater(
        { success : target },
        url,
        {
            parameters  : params,
            asynchronous: true,
            evalScripts : true,
            onComplete : function(request, json) {
                Smolder.load(target, json);

                // hide the indicator
                Smolder.hide_indicator(indicator);

                // do whatever else the caller wants
                args.request = request;
                args.json    = json || {};
                on_complete(args);
            },
            onException: function(request, exception) {
                on_exception();
                alert("ERROR FROM AJAX REQUEST:\n" + exception);
            },
            onFailure: function(request) {
                on_failure();
                Smolder.show_error();
            }
        }
    );
};

///////////////////////////////////////////////////////////////////////////////////////////////////
// FUNCTION: Smolder.Ajax.form_update
// takes the following named args
// form      : the form object (required)
//
// All other arguments are passed to the underlying Smolder.Ajax.update() call
//
//  Smolder.Ajax.form_update({
//      form   : formObj,
//      target : 'div_id'
//  });
///////////////////////////////////////////////////////////////////////////////////////////////////
Smolder.Ajax.form_update = function(args) {
    var form    = args.form;
    args.url    = form.action;
    args.params = Form.serialize(form, true);
    if(! args.onComplete ) args.onComplete = Prototype.emptyFunction;;

    // disable all of the inputs of this form that
    // aren't already and remember which ones we disabled
    var form_disabled_inputs = Smolder.disable_form(form);
    var oldOnComplete = args.onComplete;
    var reset_things = function() {
        // reset which forms are open
        Smolder.PopupForm.shown_popup_id = '';
        // if we have a form, enable all of the inputs  that we disabled
        Smolder.reenable_form(form, form_disabled_inputs);
    };
    args.onComplete = function(request, json) {
        oldOnComplete(request, json);
        reset_things();
    };
    args.onFailure = reset_things;
    args.onException = reset_things;

    // now submit this normally
    Smolder.Ajax.update(args);
};

Smolder.disable_form = function(form) {
    // disable all of the inputs of this form that
    // aren't already and remember which ones we disabled
    var disabled = $H();
    $A(form.elements).each(
        function(input, i) {
            if( !input.disabled ) {
                disabled.set(input.name, true);
                input.disabled = true;
            }
        }
    );
    return disabled.keys();
};

Smolder.reenable_form = function(form, inputs) {
    // if we have a form, enable all of the inputs
    // that we disabled
    if( form && inputs.length > 0 ) {
        $A(inputs).each(
            function(name, i) {
                if( name && form.elements[name] ) {
                    $A(form.elements[name]).each(function(input) {
                        input.disabled = false;
                    });
                }
            }
        );
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
Smolder.show_error = function() {
    new Notify.Alert(
        $('ajax_error_container').innerHTML,
        {
            messagecolor : '#FFFFFF',
            autoHide     : 'false'
        }
    );
}

///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
Smolder.PopupForm = {
    shown_popup_id: '',
    toggle: function(popup_id) {
        // first turn off any other forms showing of this type
        var old_popup_id = Smolder.PopupForm.shown_popup_id;
        if( old_popup_id != '' && $(old_popup_id) != null ) {
            Smolder.PopupForm.hide();
        }

        if( old_popup_id == popup_id ) {
            Smolder.PopupForm.shown_popup_id = '';
        } else {
            new Effect.SlideDown(popup_id, { duration: .1 });
            Smolder.PopupForm.shown_popup_id = popup_id;
        }
        return false;
    },
    show: function(popup_id) {
        if( Smolder.PopupForm.shown_popup_id != popup_id ) {
            Smolder.PopupForm.toggle(popup_id);
        }
    },
    hide: function() {
        new Effect.SlideUp( Smolder.PopupForm.shown_popup_id, { duration: .1 } );
        Smolder.PopupForm.shown_popup_id = '';
    }
};

///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
Smolder.changeSmokeGraph = function(form) {
    var type      = form.elements['type'].value;
    var url       = form.action + "/" + escape(type) + "?change=1&";

    // add each field that we want to see to the URL
    var fields = new Array('total', 'pass', 'fail', 'skip', 'todo', 'duration');
    fields.each(
        function(value, index) {
            if( form.elements[value].checked ) {
                url = url + escape(value) + '=1&';
            }
        }
    );

    // add any extra args that we might want to search by
    var extraArgs = ['start', 'stop', 'tag', 'platform', 'architecture'];
    for(var i = 0; i < extraArgs.length; i++) {
        var arg = extraArgs[i];
        if( form.elements[arg] != null && form.elements[arg].value != '') {
            url = url + arg + '=' + escape(form.elements[arg].value) + '&';
        }
    }

    $('graph_image').src = url;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
Smolder.toggleSmokeValid = function(form) {
    // TODO - replace with one regex
    var trigger = form.id.replace(/_trigger$/, '');
    var smokeId = trigger.replace(/^(in)?valid_form_/, '');
    var divId = "smoke_test_" + smokeId;

    // we are currently not showing any other forms
    Smolder.PopupForm.shown_form_id = '';

    Smolder.Ajax.form_update({
        form      : form,
        target    : divId,
        indicator : trigger + "_indicator"
    });
}

///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
Smolder.newSmokeReportWindow = function(url) {
    window.open(
        url,
        'report_details',
        'resizeable=yes,width=850,height=600,scrollbars=yes'
    );
}

///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
Smolder.makeFormAjaxable = function(element) {
    // find which element it targets
    var target;
    var matches = element.className.match(/(^|\s)for_([^\s]+)($|\s)/);
    if( matches != null )
        target = matches[2];

    // find which indicator it uses
    var indicatorId;
    matches = element.className.match(/(^|\s)show_([^\s]+)($|\s)/);
    if( matches != null )
        indicatorId = matches[2];

    element.onsubmit = function(event) {
        Smolder.Ajax.form_update({
            form      : element,
            target    : target,
            indicator : indicatorId
        });
        return false;
    };
}

///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
Smolder.makeLinkAjaxable = function(element) {
    var target;

    // find which target it targets
    var matches = element.className.match(/(^|\s)for_([^\s]+)($|\s)/);
    if( matches != null )
        target = matches[2];

    // find which indicator it uses
    var indicatorId;
    matches = element.className.match(/(^|\s)show_([^\s]+)($|\s)/);
    if( matches != null )
        indicatorId = matches[2];

    element.onclick = function(event) {
        Smolder.Ajax.update({
            url       : element.href,
            target    : target,
            indicator : indicatorId
        });
        return false;
    };
}

///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
Smolder.show_indicator = function(indicator) {
    indicator = $(indicator);
    if( indicator ) Element.show(indicator);
}

///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
Smolder.hide_indicator = function(indicator) {
    indicator = $(indicator);
    if( indicator ) Element.hide(indicator);
}

///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
Smolder.highlight = function(el) {
    new Effect.Highlight(
        el,
        {
            'startcolor'  : '#ffffff',
            'endcolor'    : '#ffff99',
            'restorecolor': '#ffff99'
        }
    );
}

///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
Smolder.unHighlight = function(el) {
    new Effect.Highlight(
        el,
        {
            'startcolor'  : '#ffff99',
            'endcolor'    : '#ffffff',
            'restorecolor': '#ffffff'
        }
    );
}

///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
Smolder.flash = function(el) {
    new Effect.Highlight(
        el,
        {
            'startcolor'  : '#ffff99',
            'endcolor'    : '#ffffff',
            'restorecolor': '#ffffff'
        }
    );
}

///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
Smolder.show_messages = function(json) {
    if( json ) {
        var msgs = json.messages || [];
        msgs.each( function(msg) { Smolder.show_message(msg.type, msg.msg) } );
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
Smolder.__message_count = 0;
Smolder.message_template = new Template('
#{text}
');
Smolder.show_message = function(type, text) {
    Smolder.__message_count++;
    // insert it at the top of the messages
    $('message_container').insert({
        top: Smolder.message_template.evaluate({
            type  : type,
            count : Smolder.__message_count,
            text  : text
        })
    });

    // fade it out after 10 secs, or onclick
    var el = $('message_' + Smolder.__message_count);
    var fade = function() { new Effect.Fade(el, { duration: .4 } ); };
    el.onclick = fade;
    setTimeout(fade, 7000);
}

///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
Smolder.setup_tooltip = function(trigger, target) {
    trigger.onclick = function() {
       new Effect.toggle(target, 'appear', { duration: .4 });
    };
}

// CRUD abstraction for Smolder
Smolder.__known_CRUDS = { };
Smolder.CRUD = Class.create();

/* Class methods */
Smolder.CRUD.find     = function(id)   { return Smolder.__known_CRUDS[id] };
Smolder.CRUD.remember = function(crud) { Smolder.__known_CRUDS[crud.div.id] = crud; };
Smolder.CRUD.forget   = function(crud) { Smolder.__known_CRUDS[crud.div.id] = false; };

/* Object methods */
Object.extend(Smolder.CRUD.prototype, {
    initialize: function(id, url) {
        this.div      = $(id);
        this.url      = url;
        this.list_url = url + '/list';

        // initialize these if we don't already have a crud
        this.add_shown = false;
        // find the containers, triggers and indicator that won't change
        this.list_container = this.div.select('.list_container')[0];
        this.add_container  = this.div.select('.add_container')[0];
        this.indicator      = this.div.select('.indicator')[0].id;
        this.add_trigger    = this.div.select('.add_trigger')[0];
        // add the handlers for the triggers
        this.add_trigger.onclick = function() {
            this.toggle_add();
            // prevent submission of the link
            return false;
        }.bindAsEventListener(this);

        // find our triggers that might change (edit and delete)
        this.refresh();

        // the fact that we've created this CRUD
        Smolder.CRUD.remember(this);
    },
    refresh: function() {
        this.edit_triggers   = $(this.list_container).select('.edit_trigger');
        this.delete_triggers = $(this.list_container).select('.delete_trigger');

        this.edit_triggers.each(
            function(trigger) {
                trigger.onclick = function() {
                    this.show_edit(trigger);
                    // prevent submission of the link
                    return false;
                }.bindAsEventListener(this);
            }.bindAsEventListener(this)
        );

        this.delete_triggers.each(
            function(trigger) {
                trigger.onclick = function() {
                    this.show_delete(trigger);
                    // prevent submission of the link
                    return false;
                }.bindAsEventListener(this);
            }.bindAsEventListener(this)
        );
    },
    toggle_add: function() {
        if( this.add_shown ) {
            this.hide_add();
        } else {
            this.show_add();
        }
    },
    hide_add: function() {
        new Effect.SlideUp(this.add_container);
        this.add_shown  = false;
    },
    show_add: function() {
        Smolder.Ajax.update({
            url        : this.add_trigger.href,
            target     : this.add_container.id,
            indicator  : this.indicator,
            onComplete : function(args) {
                if( !this.add_shown ) {
                    new Effect.SlideDown(this.add_container)
                }
                this.add_shown  = true;

                // make sure we submit the add changes correctly
                this._handle_form_submit('add_form');

            }.bindAsEventListener(this)
        });
    },
    _handle_form_submit: function(name) {
        var form = $(this.add_container).select('.' + name)[0];
        if( form ) {
            form.onsubmit = function() {
                this.submit_change(form);
                return false;
            }.bindAsEventListener(this);
        }
    },
    show_edit: function(trigger) {
        var matches = trigger.className.match(/(^|\s)for_item_(\d+)($|\s)/);
        var itemId  = matches[2];
        if( itemId == null )
            return;

        Smolder.Ajax.update({
            url        : trigger.href,
            target     : this.add_container.id,
            indicator  : this.indicator,
            onComplete : function() {
                if( !this.add_shown ) {
                    Effect.SlideDown(this.add_container);
                }
                this.add_shown = true;

                // setup the 'cancel' button
                var cancel = $(this.add_container).select('.edit_cancel')[0];
                cancel.onclick = function() { this.hide_add(); }.bindAsEventListener(this);

                // make sure we submit the add changes correctly
                this._handle_form_submit('edit_form');

            }.bindAsEventListener(this)
        });
    },
    show_delete: function(trigger) {
        var matches = trigger.className.match(/(^|\s)for_item_(\d+)($|\s)/);
        var itemId  = matches[2];

        // set the onsubmit handler for the form in this popup
        var form = $('delete_form_' + itemId);
        form.onsubmit = function() {
            Smolder.Ajax.update({
                url        : form.action,
                target     : this.list_container_id,
                indicator  : 'delete_indicator_' + itemId
            });
            return false;
        }.bindAsEventListener(this);

        // show the popup form
        var popup = 'delete_' + itemId;
        Smolder.PopupForm.toggle(popup);
    },
    submit_change: function(form) {

        // find the add_inidicator
        var indicator = $(this.add_container).select('.add_indicator')[0].id;
        Smolder.Ajax.form_update({
            form       : form,
            target     : this.add_container.id,
            indicator  : indicator,
            onComplete : function(args) {
                if(! args.json ) args.json = {};
                // if the submission changed the list
                if( args.json.list_changed ) {
                    this.add_shown = false;
                    Element.hide(this.add_container);
                    this.update_list();
                }

                // since the add/edit forms may exist still
                // (ie, their submission was incorrect so it reappears with error msgs)
                // we need to make sure they're submitted correctly the 2nd time
                this._handle_form_submit('add_form');
                this._handle_form_submit('edit_form');

            }.bindAsEventListener(this)
        });
    },
    update_list: function () {
        Smolder.Ajax.update({
            url       : this.list_url,
            indicator : this.indicator,
            target    : this.list_container_id
        });
    }
});

///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// These are our JS behaviors that are applied externally to HTML elements just like CSS
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
var myrules = {
    'a.calendar_trigger'  : function(element) {
        // now find the id's of the calendar div and input based on the id of the trigger
        // triggers are named $inputId_calendar_trigger, and calendar divs are named
        // $inputId_calendar
        var inputId = element.id.replace(/_calendar_trigger/, '');
        var input = $(inputId);

        Calendar.setup({
            inputField  : input.name,
            ifFormat    : "%m/%d/%Y",
            button      : element.id,
            weekNumbers : false,
            showOthers  : true,
            align       : 'CL',
            cache       : true
        });
    },

    'a.smoke_reports_nav' : function(element) {
        // set the limit and then do an ajax request for the form
        element.onclick = function() {
            var offset = 0;
            var matches = element.className.match(/(^|\s)offset_([^\s]+)($|\s)/);
            if( matches != null )
                offset = matches[2];
            $('smoke_reports').elements['offset'].value = offset;
            Smolder.Ajax.form_update({
                form      : $('smoke_reports'),
                indicator : 'paging_indicator'
            });
            return false;
        };
    },

    'form.change_smoke_graph'  : function(element) {
        element.onsubmit = function() {
            Smolder.changeSmokeGraph(element);
            return false;
        }
    },

    'a.popup_form' : function(element) {
        var popupId = element.id.replace(/_trigger$/, '');
        element.onclick = function() {
            Smolder.PopupForm.toggle(popupId);
            return false;
        };
    },

    'input.cancel_popup' : function(element) {
        element.onclick = function() {
            Smolder.PopupForm.hide();
        };
    },

    'form.toggle_smoke_valid' : function(element) {
        element.onsubmit = function() {
            Smolder.toggleSmokeValid(element);
            return false;
        }
    },

    '#platform_auto_complete' : function(element) {
        new Ajax.Autocompleter(
            'platform',
            'platform_auto_complete',
            '/app/projects/platform_options'
        );
    },

    '#architecture_auto_complete' : function(element) {
        new Ajax.Autocompleter(
            'architecture',
            'architecture_auto_complete',
            '/app/projects/architecture_options'
        );
    },

    'input.auto_submit' : function(element) {
        element.onchange = function(event) {
            element.form.onsubmit();
            return false;
        }
    },

    'select.auto_submit' : function(element) {
        element.onchange = function(event) {
            element.form.onsubmit();
            return false;
        }
    },

    // for ajaxable forms and anchors the taget div for updating is determined
    // as follows:
    // Specify a the target div's id by adding a 'for_$id' class to the elements
    // class list:
    //
    // This will make the target a div named "some_div"
    // If no target is specified, then it will default to "content"
    'a.ajaxable' : function(element) {
        Smolder.makeLinkAjaxable(element);
    },

    'form.ajaxable' : function(element) {
        Smolder.makeFormAjaxable(element);
    },

    'div.crud' : function(element) {
        var matches = element.className.match(/(^|\s)for_(\w+)($|\s)/);
        var url     = "/app/" + matches[2];
        new Smolder.CRUD(element.id, url);
    },
    'form.resetpw_form': function(element) {
        Smolder.makeFormAjaxable(element);
        // extend the onsubmit handler to turn off the popup
        var popupId = element.id.replace(/_form$/, '');
        var oldOnSubmit = element.onsubmit;
        element.onsubmit = function() {
            oldOnSubmit();
            Smolder.PopupForm.toggle(popupId);
            return false;
        };
    },
    // on the preferences form, the project selector should update
    // the preferences form with the selected projects preferences
    // from the server
    '#project_preference_selector': function(element) {
        var form  = element.form;

        // if we start off looking at the default options
        if( element.value == form.elements['default_pref_id'].value ) {
            Element.show('dev_prefs_sync_button');
            // if we want to show some info - Element.show('default_pref_info');
        }

        element.onchange = function() {

            // if we are the default preference, then show the stuff
            // that needs to be shown
            if( element.value == form.elements['default_pref_id'].value ) {
                Element.show('dev_prefs_sync_button');
                // if we want to show some info - Element.show('default_pref_info');
            } else {
                Element.hide('dev_prefs_sync_button');
                // if we want to show some info - Element.hide('default_pref_info');
            }

            // get the preference details from the server
            Smolder.show_indicator('pref_indicator');
            new Ajax.Request(
                '/app/developer_prefs/get_pref_details',
                {
                    parameters: Form.serialize(form),
                    asynchronous: true,
                    onComplete: function(response, json) {
                        // for every value in our JSON response, set that
                        // same element in the form
                        $A(['email_type', 'email_freq', 'email_limit']).each(
                            function(name) {
                                var elm = form.elements[name];
                                elm.value = json[name];
                                Smolder.flash(elm);
                            }
                        );
                        Smolder.hide_indicator('pref_indicator');
                    },
                    onFailure: function() { Smolder.show_error() }
                }
            );
        };
    },
    // submit the preference form to sync the other preferences
    '#dev_prefs_sync_button': function(element) {
        element.onclick = function() {
            var form = $('update_pref');
            form.elements['sync'].value = 1;
            Smolder.Ajax.form_update({
                form   : form,
                target : 'developer_prefs'
            });
        };
    },
    // hightlight selected text, textarea and select inputs
    'input.hl': function(element) {
        element.onfocus = function() { Smolder.highlight(element);   };
        element.onblur  = function() { Smolder.unHighlight(element); };
    },
    'textarea.hl': function(element) {
        element.onfocus = function() { Smolder.highlight(element);   };
        element.onblur  = function() { Smolder.unHighlight(element); };
    },
    'select.hl': function(element) {
        element.onfocus = function() { Smolder.highlight(element);   };
        element.onblur  = function() { Smolder.unHighlight(element); };
    },

    // setup tooltips
    '.tooltip_trigger': function(element) {
        var matches = element.className.match(/(^|\s)for_([^\s]+)($|\s)/);
        if( matches ) {
            var target  = matches[2];
            if( target ) {
                Smolder.setup_tooltip(element, $(target));
                Smolder.setup_tooltip($(target), $(target));
            }
        }
    },
    // TAP Matrix triggers for more test file details
    '.tap a.details_trigger' : function(el) {
        // get the id of the target div
        var matches = el.id.match(/^for_(.*)$/);
        var target = matches[1];
        // get the id of the indicator image
        matches = el.className.match(/(^|\s)show_(\S*)($|\s)/);
        var indicator = matches[2];

        el.onclick = function() {
            if( Element.visible(target) ) {
                $(target + '_tap_stream').hide();
                Effect.BlindUp(target, { duration: .1 });
            } else {
                $(indicator).style.visibility = 'visible';
                Smolder.Ajax.update({
                    url        : el.href,
                    target     : target,
                    indicator  : 'none',
                    onComplete : function() {
                        window.setTimeout(function() { $(indicator).style.visibility = 'hidden'}, 200);
                        Effect.BlindDown(
                            target,
                            // reapply any dynamic bits
                            {
                                afterFinish : function() {
                                    $(target + '_tap_stream').show();
                                    $$('.tooltip_trigger').each(function(el) {
                                        var diag = $(el).select('.tooltip')[0];
                                        Smolder.setup_tooltip(el, diag);
                                    });
                                },
                                duration    : .1
                            }
                        );
                    }
                });
            }
            return false;
        };
    },
    '.tap div.diag': function(el) {
        Smolder.setup_tooltip(el, el);
    },
    '#toggle_tests_trigger' : function(el) {
    	el.onchange = function() {
            var count = 0;
            $$('.tap tbody').each(function(row) {
                if( el.checked ) {
                    if( row.hasClassName('passed') ) {
                        row.hide();
                    } else {
                        if( count % 2 == 1 ) {
                            if(row.hasClassName('even')) {
                                row.removeClassName('even');
                                row.addClassName('odd');
                            }
                        } else {
                            if(row.hasClassName('odd')) {
                                row.removeClassName('odd');
                                row.addClassName('even');
                            }
                        }
                        count++;
                    }
                } else {
                    if( row.hasClassName('passed') ) {
                        row.show();
                    }
                    if( count % 2 == 1 ) {
                        if(row.hasClassName('even')) {
                            row.removeClassName('even');
                            row.addClassName('odd');
                        }
                    } else {
                        if(row.hasClassName('odd')) {
                            row.removeClassName('odd');
                            row.addClassName('even');
                        }
                    }
                    count++;
                }
            });
        };
    },
    '.tap a.show_all' : function(el) {
        Event.observe(el, 'click', function() {
            // an anonymous function that we use as a callback after each
            // panel is opened so that it opens the next one
            var show_details = function(index) {
                var el = $('testfile_details_' + index);
                // apply the behaviours if we're the last one
                if( ! el ) {
                    Behaviour.apply();
                    return;
                }

                // we only need to fetch it if we're not already visible
                if( Element.visible(el) ) {
                    show_details(++index);
                } else {
                    matches = el.id.match(/^testfile_details_(\d+)$/);
                    var index = matches[1];
                    var indicator = $('indicator_' + index);
                    var div = $('testfile_details_' + index);
                    indicator.style.visibility = 'visible';

                    Smolder.Ajax.update({
                        url        : $('for_testfile_details_' + index).href,
                        target     : div,
                        indicator  : 'none',
                        onComplete : function() {
                            Element.show(div);
                            indicator.style.visibility = 'hidden';
                            show_details(++index);
                        }
                    });
                }
            };
            show_details(0);
        });
    }
};

Behaviour.register(myrules);