<title>FretFind2D</title>
<meta name="keywords" lang="en" content="FretFind, stringed instrument, luthier, fret, guitar, fret placement calculator" />
<meta name="description" lang="en" content="FretFind2d models fretboards as a system of line segments on a two dimensional plane to design freboards with multiple scale lenghts and microtonal scales." />
<link rel="stylesheet" type="text/css" href="fretfind.css" />

FretFind2D

FretFind2D is a two dimensional fretboard design tool.
FretFind2D doesn't just calculate fret spacing.
It models the entire fretboard, strings and frets,
as a system of line segments on a two dimensional plane.
Because of this approach, it can design fretboards for instruments with
multiple scale lengths and non-parallel frets
as well as fretboards for instruments that play just or meantone scales.

units

            <input type="radio" name="units" value="in" checked="checked"/>inches
            <input type="radio" name="units" value="cm" />centimeters
            <input type="radio" name="units" value="mm" />millimeters

scale length

single

fundamental scale length

<input type="text" id="len" value="25" />

                            The scale length is the playing/speaking length of the
                            string measured from the nut to the bridge.
                            It is perhaps more properly twice the distance from the nut to the octave fret.
                            The fundamental scale length is the length of a line drawn from
                            the middle of the nut to the middle of the bridge.
                            For single scale length instruments that line is the perpendicular
                            bisector of both the nut and the bridge.
                            I call this length "fundamental" because on a standard instrument
                            with a narrow nut and a wide bridge the outer strings
                            actually have a slightly longer scale length.

multiple

first string scale length

<input type="text" id="lenF" value="25" />

                            The scale length is the playing/speaking length of the
                            string measured from the nut to the bridge.
                            It is perhaps more properly twice the distance from the nut to the octave fret.
                            Enter the actual scale length of the first (traditional high E) string.

last string scale length

<input type="text" id="lenL" value="28" />

                            The scale length is the playing/speaking length of the
                            string measured from the nut to the bridge.
                            It is perhaps more properly twice the distance from the nut to the octave fret.
                            Enter the actual scale length of the last (traditional low E) string.

perpendicular fret distance

<input type="text" id="pDist" value="0.5" />

                            The perpendicular fret distance
                            is the ratio of distances along the first and last
                            string that fall on a line perpendicular to the midline of the neck.
                            This is used to control the angle of the nut, frets and bridge.

                            Traditionally this property of non-parallel-ly fretted
                            fretboards is measured by assigning a "perpendicular fret".
                            "Perpendicular distance" avoids two problems with the "perpendicular fret" method.
                            First, it is possible that no fret falls into this perpendicular position.
                            With "perpendicular distance" we avoid fractional frets.
                            Second, it is possible and even likely with non-equal temperament fretboards that
                            as a fret crosses the fretboard it will fall at different ratios along the strings.
                            With "perpendicular distance" we avoid complex calculations
                            and have more predictable results.

                            A value of 0 results in a perpendicular nut.
                            A value of 1 results in a perpendicular bridge.
                            The default 0.5 results in a perpendicular octave fret.
                            To calculate an appropriate value for any fret,
                            simply divide the distance of the fret from the nut by the total length of the string.
                            In twelve tone equal temperament the values look like this:

Fret  P.D.    Fret  P.D.
1  0.05613    13  0.52806
2  0.10910    14  0.55455
3  0.15910    15  0.57955
4  0.20630    16  0.60315
5  0.25085    17  0.62542
6  0.29289    18  0.64645
7  0.33258    19  0.66629
8  0.37004    20  0.68502
9  0.40540    21  0.70270
10  0.43877    22  0.71938
11  0.47027    23  0.73513
12  0.50000    24  0.75000

individual

Danger: Experimental!!!

string scale lengths:

                            The scale length is the playing/speaking length of the
                            string measured from the nut to the bridge.
                            It is perhaps more properly twice the distance from the nut to the octave fret.
                            Enter the actual scale length of the each string.

perpendicular fret distance

<input type="text" id="ipDist" value="0.5" />

                            The perpendicular fret distance
                            is the ratio of distances along the first and last
                            string that fall on a line perpendicular to the midline of the neck.
                            This is used to control the angle of the nut, frets and bridge.

                            Traditionally this property of non-parallel-ly fretted
                            fretboards is measured by assigning a "perpendicular fret".
                            "Perpendicular distance" avoids two problems with the "perpendicular fret" method.
                            First, it is possible that no fret falls into this perpendicular position.
                            With "perpendicular distance" we avoid fractional frets.
                            Second, it is possible and even likely with non-equal temperament fretboards that
                            as a fret crosses the fretboard it will fall at different ratios along the strings.
                            With "perpendicular distance" we avoid complex calculations
                            and have more predictable results.

                            A value of 0 results in a perpendicular nut.
                            A value of 1 results in a perpendicular bridge.
                            The default 0.5 results in a perpendicular octave fret.
                            To calculate an appropriate value for any fret,
                            simply divide the distance of the fret from the nut by the total length of the string.
                            In twelve tone equal temperament the values look like this:

Fret  P.D.    Fret  P.D.
1  0.05613    13  0.52806
2  0.10910    14  0.55455
3  0.15910    15  0.57955
4  0.20630    16  0.60315
5  0.25085    17  0.62542
6  0.29289    18  0.64645
7  0.33258    19  0.66629
8  0.37004    20  0.68502
9  0.40540    21  0.70270
10  0.43877    22  0.71938
11  0.47027    23  0.73513
12  0.50000    24  0.75000

string width at the nut

<input type="text" id="nutWidth" value="1.375" />

            The string width at the nut is the distance along the nut from the center
            of the first string to the center of the last string.
            I'm using delta x distance
            (distance measured along a line drawn perpendicular to the neck's midline)
            because I think that is what
            you would feel as the width if you were playing an instrument with multiple scale lengths.
            It also makes the calculation easier.

string width at the bridge

<input type="text" id="bridgeWidth" value="2.125" />

            The string width at the bridge is the distance along the bridge from the center
            of the first string to the center of the last string.
            I'm using delta x distance
            (distance measured along a line drawn perpendicular to the neck's midline)
            because I think that is what
            you would feel as the width if you were playing an instrument with multiple scale lengths.
            It also makes the calculation easier.

string spacing

equal

proportional

string gauges:

Equal:

Space out the strings evenly from center to center without regard for the thickness of the strings.

Proportional:

The spacing accounts for the diameter of the strings so that the empty space between each pair of strings is the same.

              Note that the outer two strings are still assumed to be centered on their coordinates,
              i.e. if you enter a nut width of 2" then the outer edges of your outer two strings will
              be wider than that by half of the sum of their gauges.

               Enter the thickness of each string, with String 1 being the thinnest/highest.
               For example, a standard set of electric guitar strings, in inches, would be 0.010, 0.013, 0.017, 0.026, 0.036, 0.046.
               If you are using metric, please convert your string gauges to metric as well.

fretboard overhang

equal

<input type="text" id="oE" value="0.09375" />

nut & bridge

nut
                    <input type="text" id="oN" value="0.09375" />

bridge
                    <input type="text" id="oB" value="0.09375" />

first & last

last	first

                    <input type="text" id="oL" value="0.09375" />

                    <input type="text" id="oF" value="0.09375" />

all

 	last	first

nut
                    <input type="text" id="oNL" value="0.09375" />

                    <input type="text" id="oNF" value="0.09375" />

bridge
                    <input type="text" id="oBL" value="0.09375" />

                    <input type="text" id="oBF" value="0.09375" />

            The fretboard overhang is the distance from the center of outer strings to edge of nut or bridge.
            For fretboards with multiple scale lengths this is calculated as delta x distance,
            distance measured along a line drawn perpendicular to the neck's midline.
            There are four input modes for overhang.

Equal:

you enter a single value and the overhang will be constant.

Nut & Bridge:

allows you to specify one overhang at the nut and another overhang at the bridge.

First & Last:

allows you to specify one overhang for the first string and another for the last string.

All:

you specify an overhang for all four locations separately.

            (Please note, in FretFind the first string is shown on the far right
            where the high E string would be on a typical right-handed guitar.
            The last string is on the far left, where the low E would be found.)

calculation method

equal (root 2)

<input type="text" id="root" value="12" />

just (scala)

<textarea rows="17" id="scl">
! 12tet.scl
!
12 tone equal temperament
12
!
100.0
200.
300.
400.
500.
600.
700.
800.
900.
1000.
1100.
2/1
</textarea>

            The calculation method determines how FretFind calculates fret placement.
            There are two input modes.

Equal:

uses the Xth root of two, a standard
            method for calculating equal temperaments. You enter the number of tones per octave.

Scala:

uses a Scala SCL file which allows you to specify
            each scale step exactly in either ratios or cents.
            If you are interested in creating your own scale, please read this description of the
            Scala scale file format.
            Otherwise try a scale from the Scala scale archive, found at the very bottom of the
            Scala download page.
            You can learn more about Scala at the
            Scala home page.

number of frets

<input type="text" id="numFrets" value="24" />

            This is the number of frets you would like FretFind to calculate.
            The number of frets must be an integer.

number of strings

<input type="text" id="numStrings" value="6" />

            The number of strings must be an integer.
            If you change the number of strings be sure to update the tuning section below (only useful with non-equal temperament scales).

tuning

            Enter the scale step (of the scale defined above) to which each string will be tuned.
            For example a standard guitar in the key of E would be tuned 0, 7, 3, 10, 5, 0.
            The first string is the string to the far right on the fretboard.
            This step is not important for the Equal calculation method.
            Entering a tuning for the Scala calculation method will very likely result in partial frets.

Link

Link to this design

The latest version in development is available on GitHub.

<title>FretFind2D</title>
<meta name="keywords" lang="en" content="FretFind, stringed instrument, luthier, fret, guitar, fret placement calculator" />
<meta name="description" lang="en" content="FretFind2d models fretboards as a system of line segments on a two dimensional plane to design freboards with multiple scale lenghts and microtonal scales." />
<link rel="stylesheet" type="text/css" href="fretfind.css" />

FretFind2D

FretFind2D is a two dimensional fretboard design tool.
FretFind2D doesn't just calculate fret spacing.
It models the entire fretboard, strings and frets,
as a system of line segments on a two dimensional plane.
Because of this approach, it can design fretboards for instruments with
multiple scale lengths and non-parallel frets
as well as fretboards for instruments that play just or meantone scales.

units

            <input type="radio" name="units" value="in" checked="checked"/>inches

            <input type="radio" name="units" value="cm" />centimeters

            <input type="radio" name="units" value="mm" />millimeters

display

            <input type="checkbox" name="showFretboardEdges" id="showFretboardEdges" checked="checked" />show fretboard edges

            <input type="checkbox" name="showStrings" id="showStrings" checked="checked" />show strings

            <input type="checkbox" name="extendFrets" id="extendFrets" />extend frets to edge

            <input type="checkbox" name="showBoundingBox" id="showBoundingBox" />bounding box

            <input type="checkbox" name="showMetas" id="showMetas" checked="checked" />show metas

            Selecting or deselecting any of these options will only affect the display and output files.
            No fret calculations will be affected.

scale length

single

fundamental scale length

<input type="text" id="len" value="25" />

                            The scale length is the playing/speaking length of the
                            string measured from the nut to the bridge.
                            It is perhaps more properly twice the distance from the nut to the octave fret.
                            The fundamental scale length is the length of a line drawn from
                            the middle of the nut to the middle of the bridge.
                            For single scale length instruments that line is the perpendicular
                            bisector of both the nut and the bridge.
                            I call this length "fundamental" because on a standard instrument
                            with a narrow nut and a wide bridge the outer strings
                            actually have a slightly longer scale length.

multiple

first string scale length

<input type="text" id="lenF" value="25" />

                            The scale length is the playing/speaking length of the
                            string measured from the nut to the bridge.
                            It is perhaps more properly twice the distance from the nut to the octave fret.
                            Enter the actual scale length of the first (traditional high E) string.

last string scale length

<input type="text" id="lenL" value="28" />

                            The scale length is the playing/speaking length of the
                            string measured from the nut to the bridge.
                            It is perhaps more properly twice the distance from the nut to the octave fret.
                            Enter the actual scale length of the last (traditional low E) string.

perpendicular fret distance

<input type="text" id="pDist" value="0.5" />

                            The perpendicular fret distance
                            is the ratio of distances along the first and last
                            string that fall on a line perpendicular to the midline of the neck.
                            This is used to control the angle of the nut, frets and bridge.

                            Traditionally this property of non-parallel-ly fretted
                            fretboards is measured by assigning a "perpendicular fret".
                            "Perpendicular distance" avoids two problems with the "perpendicular fret" method.
                            First, it is possible that no fret falls into this perpendicular position.
                            With "perpendicular distance" we avoid fractional frets.
                            Second, it is possible and even likely with non-equal temperament fretboards that
                            as a fret crosses the fretboard it will fall at different ratios along the strings.
                            With "perpendicular distance" we avoid complex calculations
                            and have more predictable results.

                            A value of 0 results in a perpendicular nut.
                            A value of 1 results in a perpendicular bridge.
                            The default 0.5 results in a perpendicular octave fret.
                            To calculate an appropriate value for any fret,
                            simply divide the distance of the fret from the nut by the total length of the string.
                            In twelve tone equal temperament the values look like this:

Fret  P.D.    Fret  P.D.
1  0.05613    13  0.52806
2  0.10910    14  0.55455
3  0.15910    15  0.57955
4  0.20630    16  0.60315
5  0.25085    17  0.62542
6  0.29289    18  0.64645
7  0.33258    19  0.66629
8  0.37004    20  0.68502
9  0.40540    21  0.70270
10  0.43877    22  0.71938
11  0.47027    23  0.73513
12  0.50000    24  0.75000

individual

Danger: Experimental!!!

string scale lengths:

                            The scale length is the playing/speaking length of the
                            string measured from the nut to the bridge.
                            It is perhaps more properly twice the distance from the nut to the octave fret.
                            Enter the actual scale length of the each string.

perpendicular fret distance

<input type="text" id="ipDist" value="0.5" />

                            The perpendicular fret distance
                            is the ratio of distances along the first and last
                            string that fall on a line perpendicular to the midline of the neck.
                            This is used to control the angle of the nut, frets and bridge.

                            Traditionally this property of non-parallel-ly fretted
                            fretboards is measured by assigning a "perpendicular fret".
                            "Perpendicular distance" avoids two problems with the "perpendicular fret" method.
                            First, it is possible that no fret falls into this perpendicular position.
                            With "perpendicular distance" we avoid fractional frets.
                            Second, it is possible and even likely with non-equal temperament fretboards that
                            as a fret crosses the fretboard it will fall at different ratios along the strings.
                            With "perpendicular distance" we avoid complex calculations
                            and have more predictable results.

                            A value of 0 results in a perpendicular nut.
                            A value of 1 results in a perpendicular bridge.
                            The default 0.5 results in a perpendicular octave fret.
                            To calculate an appropriate value for any fret,
                            simply divide the distance of the fret from the nut by the total length of the string.
                            In twelve tone equal temperament the values look like this:

Fret  P.D.    Fret  P.D.
1  0.05613    13  0.52806
2  0.10910    14  0.55455
3  0.15910    15  0.57955
4  0.20630    16  0.60315
5  0.25085    17  0.62542
6  0.29289    18  0.64645
7  0.33258    19  0.66629
8  0.37004    20  0.68502
9  0.40540    21  0.70270
10  0.43877    22  0.71938
11  0.47027    23  0.73513
12  0.50000    24  0.75000

string width at the nut

<input type="text" id="nutWidth" value="1.375" />

            The string width at the nut is the distance along the nut from the center
            of the first string to the center of the last string.
            I'm using delta x distance
            (distance measured along a line drawn perpendicular to the neck's midline)
            because I think that is what
            you would feel as the width if you were playing an instrument with multiple scale lengths.
            It also makes the calculation easier.

string width at the bridge

<input type="text" id="bridgeWidth" value="2.125" />

            The string width at the bridge is the distance along the bridge from the center
            of the first string to the center of the last string.
            I'm using delta x distance
            (distance measured along a line drawn perpendicular to the neck's midline)
            because I think that is what
            you would feel as the width if you were playing an instrument with multiple scale lengths.
            It also makes the calculation easier.

string spacing

equal

proportional

string gauges:

Equal:

Space out the strings evenly from center to center without regard for the thickness of the strings.

Proportional:

The spacing accounts for the diameter of the strings so that the empty space between each pair of strings is the same.

              Note that the outer two strings are still assumed to be centered on their coordinates,
              i.e. if you enter a nut width of 2" then the outer edges of your outer two strings will
              be wider than that by half of the sum of their gauges.

               Enter the thickness of each string, with String 1 being the thinnest/highest.
               For example, a standard set of electric guitar strings, in inches, would be 0.010, 0.013, 0.017, 0.026, 0.036, 0.046.
               If you are using metric, please convert your string gauges to metric as well.

fretboard overhang

equal

<input type="text" id="oE" value="0.09375" />

nut & bridge

nut
                    <input type="text" id="oN" value="0.09375" />

bridge
                    <input type="text" id="oB" value="0.09375" />

first & last

last	first

                    <input type="text" id="oL" value="0.09375" />

                    <input type="text" id="oF" value="0.09375" />

all

 	last	first

nut
                    <input type="text" id="oNL" value="0.09375" />

                    <input type="text" id="oNF" value="0.09375" />

bridge
                    <input type="text" id="oBL" value="0.09375" />

                    <input type="text" id="oBF" value="0.09375" />

            The fretboard overhang is the distance from the center of outer strings to edge of nut or bridge.
            For fretboards with multiple scale lengths this is calculated as delta x distance,
            distance measured along a line drawn perpendicular to the neck's midline.
            There are four input modes for overhang.

Equal:

you enter a single value and the overhang will be constant.

Nut & Bridge:

allows you to specify one overhang at the nut and another overhang at the bridge.

First & Last:

allows you to specify one overhang for the first string and another for the last string.

All:

you specify an overhang for all four locations separately.

            (Please note, in FretFind the first string is shown on the far right
            where the high E string would be on a typical right-handed guitar.
            The last string is on the far left, where the low E would be found.)

calculation method

equal (root 2)

<input type="text" id="root" value="12" />

just (scala)

<textarea rows="17" id="scl">
! 12tet.scl
!
12 tone equal temperament
12
!
100.0
200.
300.
400.
500.
600.
700.
800.
900.
1000.
1100.
2/1
</textarea>

            The calculation method determines how FretFind calculates fret placement.
            There are two input modes.

Equal:

uses the Xth root of two, a standard
            method for calculating equal temperaments. You enter the number of tones per octave.

Scala:

uses a Scala SCL file which allows you to specify
            each scale step exactly in either ratios or cents.
            If you are interested in creating your own scale, please read this description of the
            Scala scale file format.
            Otherwise try a scale from the Scala scale archive, found at the very bottom of the
            Scala download page.
            You can learn more about Scala at the
            Scala home page.

number of frets

<input type="text" id="numFrets" value="24" />

            This is the number of frets you would like FretFind to calculate.
            The number of frets must be an integer.

number of strings

<input type="text" id="numStrings" value="6" />

            The number of strings must be an integer.
            If you change the number of strings be sure to update the tuning section below (only useful with non-equal temperament scales).

tuning

            Enter the scale step (of the scale defined above) to which each string will be tuned.
            For example a standard guitar in the key of E would be tuned 0, 7, 3, 10, 5, 0.
            The first string is the string to the far right on the fretboard.
            This step is not important for the Equal calculation method.
            Entering a tuning for the Scala calculation method will very likely result in partial frets.

Link

Link to this design

The latest version in development is available on GitHub.


(function() {

    var shortcutSettings;

    function initialize() {
        document.addEventListener("keydown", onWindowKeyDown, false);
        chrome.extension.onRequest.addListener(onExtensionRequest);
        chrome.extension.sendRequest({'action' : 'getSettings'}, onGetSettings);
    }

    function reload() {
        var elements = document.querySelectorAll('link[rel=stylesheet][href]');
        for (var i = 0, element; element = elements[i]; i++) {
            var href = element.href.replace(/[?&]cssReloader=([^&$]*)/,'');
            element.href = href + (href.indexOf('?')>=0?'&':'?') + 'cssReloader=' + (new Date().valueOf());
        }
    }

    function onGetSettings(settings) {
        shortcutSettings = settings;
    }

    function onWindowKeyDown(e) {
        if(e.keyIdentifier == shortcutSettings["keyIdentifier"] &&
        e.shiftKey ===  shortcutSettings["shiftKeySelected"] &&
        e.altKey === shortcutSettings["altKeySelected"] &&
        e.ctrlKey === shortcutSettings["controlKeySelected"])
        {
            reload();
        }
    }

    function onExtensionRequest(request, sender) {
        if (request.action == "reload") {
            reload();
        }
    }

    CSSreloader = {
        reload : reload,
        initialize: initialize
    };

    CSSreloader.initialize();

})();

(function() {

    var shortcutSettings;

    var blacklist = [
        new Regexp('^https?://use\.typekit\.net\/'),
        new Regexp('^https?://fonts\.googleapis\.com\/')
    ];

    function initialize() {
        document.addEventListener("keydown", onWindowKeyDown, false);
        chrome.extension.onRequest.addListener(onExtensionRequest);
        chrome.extension.sendRequest({'action' : 'getSettings'}, onGetSettings);
    }

    function reload() {
        var elements = document.querySelectorAll('link[rel=stylesheet][href]');
        for (var i = 0, element; element = elements[i]; i++) {
            if (isBlacklisted(element.href)) continue;
            var href = element.href.replace(/[?&]cssReloader=([^&$]*)/,'');
            element.href = href + (href.indexOf('?')>=0?'&':'?') + 'cssReloader=' + (new Date().valueOf());
        }
    }

    function isBlacklisted(href) {
        for (var i = 0, len = blacklist.length; i < len; i++) {
            if (blacklist[i].test(href)) return true;
        }
    }

    function onGetSettings(settings) {
        shortcutSettings = settings;
    }

    function onWindowKeyDown(e) {
        if(e.keyIdentifier == shortcutSettings["keyIdentifier"] &&
        e.shiftKey ===  shortcutSettings["shiftKeySelected"] &&
        e.altKey === shortcutSettings["altKeySelected"] &&
        e.ctrlKey === shortcutSettings["controlKeySelected"])
        {
            reload();
        }
    }

    function onExtensionRequest(request, sender) {
        if (request.action == "reload") {
            reload();
        }
    }

    CSSreloader = {
        reload : reload,
        initialize: initialize
    };

    CSSreloader.initialize();

})();


YUI().use('node', function(Y) {
    var code = Y.all('.prettyprint.linenums');
    if (code.size()) {
        code.each(function(c) {
            var lis = c.all('ol li'),
                l = 1;
            lis.each(function(n) {
                n.prepend('');
                l++;
            });
        });
        var h = location.hash;
        location.hash = '';
        h = h.replace('LINE_', 'LINENUM_');
        location.hash = h;
    }
});


#!/bin/sh
#
# sample bash script for running RecordLoader
#
# Copyright (c)2005-2012 Mark Logic Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# The use of the Apache License does not indicate that this project is
# affiliated with the Apache Software Foundation.
#

function readlink() {
  DIR=$(echo "${1%/*}")
  (cd "$DIR" && echo "$(pwd -P)")
}

# look for GNU readlink first (OS X, BSD, Solaris)
READLINK=`type -P greadlink`
if [ -z "$READLINK" ]; then
    READLINK=`type -P readlink`
fi
# if readlink is not GNU-style, setting BASE will fail
BASE=`$READLINK -f $0 2>/dev/null`
if [ -z "$BASE" ]; then
    # try the bash function, which does not need dirname afterwards
    BASE=$(readlink $0)
else
    BASE=`dirname $BASE`
fi
BASE=`dirname $BASE`
if [ -z "$BASE" ]; then
    echo Error initializing environment from $READLINK
    $READLINK --help
    exit 1
fi

CP=$BASE/../lib/recordloader.jar
CP=$CP:$HOME/lib/java/svnkit.jar
CP=$CP:$HOME/lib/java/xcc.jar
CP=$CP:$HOME/lib/java/xpp3.jar

FILES=
VMARGS=-Xincgc
# OS X defaults to MacRoman
VMARGS=$VMARGS" -Dfile.encoding=UTF-8"

for a in $*; do
    if [ -e $a ]; then
        FILES="$FILES $a"
    else
        VMARGS="$VMARGS $a"
    fi
done

if [ -d "$JAVA_HOME" ]; then
  JAVA=$JAVA_HOME/bin/java
else
  JAVA=java
fi

$JAVA -cp $CP $VMARGS com.marklogic.ps.RecordLoader $FILES

# end recordloader.sh

#!/bin/sh
#
# sample bash script for running RecordLoader
#
# Copyright (c)2005-2012 Mark Logic Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# The use of the Apache License does not indicate that this project is
# affiliated with the Apache Software Foundation.
#

function readlink() {
  DIR=$(echo "${1%/*}")
  (cd "$DIR" && echo "$(pwd -P)")
}

# look for GNU readlink first (OS X, BSD, Solaris)
READLINK=`type -P greadlink`
if [ -z "$READLINK" ]; then
    READLINK=`type -P readlink`
fi
# if readlink is not GNU-style, setting BASE will fail
BASE=`$READLINK -f $0 2>/dev/null`
if [ -z "$BASE" ]; then
    # try the bash function, which does not need dirname afterwards
    BASE=$(readlink $0)
else
    BASE=`dirname $BASE`
fi
BASE=`dirname $BASE`
if [ -z "$BASE" ]; then
    echo Error initializing environment from $READLINK
    $READLINK --help
    exit 1
fi

CP=$BASE/../lib/recordloader.jar
CP=$CP:$HOME/lib/java/xcc.jar
CP=$CP:$HOME/lib/java/xpp3.jar

FILES=
VMARGS=-Xincgc
# OS X defaults to MacRoman
VMARGS=$VMARGS" -Dfile.encoding=UTF-8"

for a in $*; do
    if [ -e $a ]; then
        FILES="$FILES $a"
    else
        VMARGS="$VMARGS $a"
    fi
done

if [ -d "$JAVA_HOME" ]; then
  JAVA=$JAVA_HOME/bin/java
else
  JAVA=java
fi

$JAVA -cp $CP $VMARGS com.marklogic.ps.RecordLoader $FILES

# end recordloader.sh


/*
html5slider - a JS implementation of <input type=range> for Firefox 16 and up
https://github.com/fryn/html5slider

Copyright (c) 2010-2012 Frank Yan, <http://frankyan.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

(function() {

// test for native support
var test = document.createElement('input');
try {
  test.type = 'range';
  if (test.type == 'range')
    return;
} catch (e) {
  return;
}

// test for required property support
test.style.background = 'linear-gradient(red, red)';
if (!test.style.backgroundImage || !('MozAppearance' in test.style) ||
    !document.mozSetImageElement || !this.MutationObserver)
  return;

var scale;
var isMac = navigator.platform == 'MacIntel';
var thumb = {
  radius: isMac ? 9 : 6,
  width: isMac ? 22 : 12,
  height: isMac ? 16 : 20
};
var track = 'linear-gradient(transparent ' + (isMac ?
  '6px, #999 6px, #999 7px, #ccc 8px, #bbb 9px, #bbb 10px, transparent 10px' :
  '9px, #999 9px, #bbb 10px, #fff 11px, transparent 11px') +
  ', transparent)';
var styles = {
  'min-width': thumb.width + 'px',
  'min-height': thumb.height + 'px',
  'max-height': thumb.height + 'px',
  padding: '0 0 ' + (isMac ? '2px' : '1px'),
  border: 0,
  'border-radius': 0,
  cursor: 'default',
  'text-indent': '-999999px' // -moz-user-select: none; breaks mouse capture
};
var options = {
  attributes: true,
  attributeFilter: ['min', 'max', 'step', 'value']
};
var forEach = Array.prototype.forEach;
var onChange = document.createEvent('HTMLEvents');
onChange.initEvent('change', true, false);

if (document.readyState == 'loading')
  document.addEventListener('DOMContentLoaded', initialize, true);
else
  initialize();

function initialize() {
  // create initial sliders
  forEach.call(document.querySelectorAll('input[type=range]'), transform);
  // create sliders on-the-fly
  new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
      if (mutation.addedNodes)
        forEach.call(mutation.addedNodes, function(node) {
          check(node);
          if (node.childElementCount)
            forEach.call(node.querySelectorAll('input'), check);
        });
    });
  }).observe(document, { childList: true, subtree: true });
}

function check(input) {
  if (input.localName == 'input' && input.type != 'range' &&
      input.getAttribute('type') == 'range')
    transform(input);
}

function transform(slider) {

  var isValueSet, areAttrsSet, isChanged, isClick, prevValue, rawValue, prevX;
  var min, max, step, range, value = slider.value;

  // lazily create shared slider affordance
  if (!scale) {
    scale = document.body.appendChild(document.createElement('hr'));
    style(scale, {
      '-moz-appearance': isMac ? 'scale-horizontal' : 'scalethumb-horizontal',
      display: 'block',
      visibility: 'visible',
      opacity: 1,
      position: 'fixed',
      top: '-999999px'
    });
    document.mozSetImageElement('__sliderthumb__', scale);
  }

  // reimplement value and type properties
  var getValue = function() { return '' + value; };
  var setValue = function setValue(val) {
    value = '' + val;
    isValueSet = true;
    draw();
    delete slider.value;
    slider.value = value;
    slider.__defineGetter__('value', getValue);
    slider.__defineSetter__('value', setValue);
  };
  slider.__defineGetter__('value', getValue);
  slider.__defineSetter__('value', setValue);
  slider.__defineGetter__('type', function() { return 'range'; });

  // sync properties with attributes
  ['min', 'max', 'step'].forEach(function(prop) {
    if (slider.hasAttribute(prop))
      areAttrsSet = true;
    slider.__defineGetter__(prop, function() {
      return this.hasAttribute(prop) ? this.getAttribute(prop) : '';
    });
    slider.__defineSetter__(prop, function(val) {
      val === null ? this.removeAttribute(prop) : this.setAttribute(prop, val);
    });
  });

  // initialize slider
  slider.readOnly = true;
  style(slider, styles);
  update();

  new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
      if (mutation.attributeName != 'value') {
        update();
        areAttrsSet = true;
      }
      // note that value attribute only sets initial value
      else if (!isValueSet) {
        value = slider.getAttribute('value');
        draw();
      }
    });
  }).observe(slider, options);

  slider.addEventListener('mousedown', onDragStart, true);
  slider.addEventListener('keydown', onKeyDown, true);
  slider.addEventListener('focus', onFocus, true);
  slider.addEventListener('blur', onBlur, true);

  function onDragStart(e) {
    isClick = true;
    setTimeout(function() { isClick = false; }, 0);
    if (e.button || !range)
      return;
    var width = parseFloat(getComputedStyle(this, 0).width);
    var multiplier = (width - thumb.width) / range;
    if (!multiplier)
      return;
    // distance between click and center of thumb
    var dev = e.clientX - this.getBoundingClientRect().left - thumb.width / 2 -
              (value - min) * multiplier;
    // if click was not on thumb, move thumb to click location
    if (Math.abs(dev) > thumb.radius) {
      isChanged = true;
      this.value -= -dev / multiplier;
    }
    rawValue = value;
    prevX = e.clientX;
    this.addEventListener('mousemove', onDrag, true);
    this.addEventListener('mouseup', onDragEnd, true);
  }

  function onDrag(e) {
    var width = parseFloat(getComputedStyle(this, 0).width);
    var multiplier = (width - thumb.width) / range;
    if (!multiplier)
      return;
    rawValue += (e.clientX - prevX) / multiplier;
    prevX = e.clientX;
    isChanged = true;
    this.value = rawValue;
  }

  function onDragEnd() {
    this.removeEventListener('mousemove', onDrag, true);
    this.removeEventListener('mouseup', onDragEnd, true);
  }

  function onKeyDown(e) {
    if (e.keyCode > 36 && e.keyCode < 41) { // 37-40: left, up, right, down
      onFocus.call(this);
      isChanged = true;
      this.value = value + (e.keyCode == 38 || e.keyCode == 39 ? step : -step);
    }
  }

  function onFocus() {
    if (!isClick)
      this.style.boxShadow = !isMac ? '0 0 0 2px #fb0' :
        'inset 0 0 20px rgba(0,127,255,.1), 0 0 1px rgba(0,127,255,.4)';
  }

  function onBlur() {
    this.style.boxShadow = '';
  }

  // determines whether value is valid number in attribute form
  function isAttrNum(value) {
    return !isNaN(value) && +value == parseFloat(value);
  }

  // validates min, max, and step attributes and redraws
  function update() {
    min = isAttrNum(slider.min) ? +slider.min : 0;
    max = isAttrNum(slider.max) ? +slider.max : 100;
    if (max < min)
      max = min > 100 ? min : 100;
    step = isAttrNum(slider.step) && slider.step > 0 ? +slider.step : 1;
    range = max - min;
    draw(true);
  }

  // recalculates value property
  function calc() {
    if (!isValueSet && !areAttrsSet)
      value = slider.getAttribute('value');
    if (!isAttrNum(value))
      value = (min + max) / 2;;
    // snap to step intervals (WebKit sometimes does not - bug?)
    value = Math.round((value - min) / step) * step + min;
    if (value < min)
      value = min;
    else if (value > max)
      value = min + ~~(range / step) * step;
  }

  // renders slider using CSS background ;)
  function draw(attrsModified) {
    calc();
    if (isChanged && value != prevValue)
      slider.dispatchEvent(onChange);
    isChanged = false;
    if (!attrsModified && value == prevValue)
      return;
    prevValue = value;
    var position = range ? (value - min) / range * 100 : 0;
    var bg = '-moz-element(#__sliderthumb__) ' + position + '% no-repeat, ';
    style(slider, { background: bg + track });
  }

}

function style(element, styles) {
  for (var prop in styles)
    element.style.setProperty(prop, styles[prop], 'important');
}

})();

/*
html5slider - a JS implementation of <input type=range> for Firefox 16 and up
https://github.com/fryn/html5slider

Copyright (c) 2010-2012 Frank Yan, <http://frankyan.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

(function() {

// test for native support
var test = document.createElement('input');
try {
  test.type = 'range';
  if (test.type == 'range')
    return;
} catch (e) {
  return;
}

// test for required property support
test.style.background = 'linear-gradient(red, red)';
if (!test.style.backgroundImage || !('MozAppearance' in test.style) ||
    !document['mozSetImageElement'] || !this.MutationObserver)
  return;

var scale;
var isMac = navigator.platform == 'MacIntel';
var thumb = {
  "radius": isMac ? 9 : 6,
  "width": isMac ? 22 : 12,
  "height": isMac ? 16 : 20
};
var track = 'linear-gradient(transparent ' + (isMac ?
  '6px, #999 6px, #999 7px, #ccc 8px, #bbb 9px, #bbb 10px, transparent 10px' :
  '9px, #999 9px, #bbb 10px, #fff 11px, transparent 11px') +
  ', transparent)';
var styles = {
  "min-width": thumb.width + 'px',
  "min-height": thumb.height + 'px',
  "max-height": thumb.height + 'px',
  "padding": '0 0 ' + (isMac ? '2px' : '1px'),
  "border": 0,
  "border-radius": 0,
  "cursor": 'default',
  "text-indent": '-999999px' // -moz-user-select: none; breaks mouse capture
};
var options = {
  "attributes": true,
  "attributeFilter": ['min', 'max', 'step', 'value']
};
var forEach = Array.prototype.forEach;
var onChange = document.createEvent('HTMLEvents');
onChange.initEvent('change', true, false);

if (document.readyState == 'loading')
  document.addEventListener('DOMContentLoaded', initialize, true);
else
  initialize();

function initialize() {
  // create initial sliders
  forEach.call(document.querySelectorAll('input[type=range]'), transform);
  // create sliders on-the-fly
  new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
      if (mutation.addedNodes)
        forEach.call(mutation.addedNodes, function(node) {
          check(node);
          if (node.childElementCount)
            forEach.call(node.querySelectorAll('input'), check);
        });
    });
  }).observe(document, {"childList": true, "subtree": true});
}

function check(input) {
  if (input.localName == 'input' && input.type != 'range' &&
      input.getAttribute('type') == 'range')
    transform(input);
}

function transform(slider) {

  var isValueSet, areAttrsSet, isChanged, isClick, prevValue, rawValue, prevX;
  var min, max, step, range, value = slider.value;

  // lazily create shared slider affordance
  if (!scale) {
    scale = document.body.appendChild(document.createElement('hr'));
    style(scale, {
      "-moz-appearance": isMac ? 'scale-horizontal' : 'scalethumb-horizontal',
      "display": 'block',
      "visibility": 'visible',
      "opacity": 1,
      "position": 'fixed',
      "top": '-999999px'
    });
    document['mozSetImageElement']('__sliderthumb__', scale);
  }

  // reimplement value and type properties
  var getValue = function() { return '' + value; };
  var setValue = function setValue(val) {
    value = '' + val;
    isValueSet = true;
    draw();
    delete slider.value;
    slider.value = value;
    slider.__defineGetter__('value', getValue);
    slider.__defineSetter__('value', setValue);
  };
  slider.__defineGetter__('value', getValue);
  slider.__defineSetter__('value', setValue);
  slider.__defineGetter__('type', function() { return 'range'; });

  // sync properties with attributes
  ['min', 'max', 'step'].forEach(function(prop) {
    if (slider.hasAttribute(prop))
      areAttrsSet = true;
    slider.__defineGetter__(prop, function() {
      return this.hasAttribute(prop) ? this.getAttribute(prop) : '';
    });
    slider.__defineSetter__(prop, function(val) {
      if (val === null) {
        this.removeAttribute(prop);
      }
      else {
        this.setAttribute(prop, val);
      }
    });
  });

  // initialize slider
  slider['readOnly'] = true;
  style(slider, styles);
  update();

  new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
      if (mutation.attributeName != 'value') {
        update();
        areAttrsSet = true;
      }
      // note that value attribute only sets initial value
      else if (!isValueSet) {
        value = slider.getAttribute('value');
        draw();
      }
    });
  }).observe(slider, options);

  slider.addEventListener('mousedown', onDragStart, true);
  slider.addEventListener('keydown', onKeyDown, true);
  slider.addEventListener('focus', onFocus, true);
  slider.addEventListener('blur', onBlur, true);

  function onDragStart(e) {
    isClick = true;
    setTimeout(function() { isClick = false; }, 0);
    if (e.button || !range)
      return;
    var width = parseFloat(window['getComputedStyle'](this, 0).width);
    var multiplier = (width - thumb.width) / range;
    if (!multiplier)
      return;
    // distance between click and center of thumb
    var dev = e.clientX - this.getBoundingClientRect().left - thumb.width / 2 -
              (value - min) * multiplier;
    // if click was not on thumb, move thumb to click location
    if (Math.abs(dev) > thumb.radius) {
      isChanged = true;
      this.value -= -dev / multiplier;
    }
    rawValue = value;
    prevX = e.clientX;
    this.addEventListener('mousemove', onDrag, true);
    this.addEventListener('mouseup', onDragEnd, true);
  }

  function onDrag(e) {
    var width = parseFloat(window['getComputedStyle'](this, 0).width);
    var multiplier = (width - thumb.width) / range;
    if (!multiplier)
      return;
    rawValue += (e.clientX - prevX) / multiplier;
    prevX = e.clientX;
    isChanged = true;
    this.value = rawValue;
  }

  function onDragEnd() {
    this.removeEventListener('mousemove', onDrag, true);
    this.removeEventListener('mouseup', onDragEnd, true);
  }

  function onKeyDown(e) {
    if (e.keyCode > 36 && e.keyCode < 41) { // 37-40: left, up, right, down
      onFocus.call(this);
      isChanged = true;
      this.value = value + (e.keyCode == 38 || e.keyCode == 39 ? step : -step);
    }
  }

  function onFocus() {
    if (!isClick)
      this.style.boxShadow = !isMac ? '0 0 0 2px #fb0' :
        'inset 0 0 20px rgba(0,127,255,.1), 0 0 1px rgba(0,127,255,.4)';
  }

  function onBlur() {
    this.style.boxShadow = '';
  }

  // determines whether value is valid number in attribute form
  function isAttrNum(value) {
    return !isNaN(value) && +value == parseFloat(value);
  }

  // validates min, max, and step attributes and redraws
  function update() {
    min = isAttrNum(slider.min) ? +slider.min : 0;
    max = isAttrNum(slider.max) ? +slider.max : 100;
    if (max < min)
      max = min > 100 ? min : 100;
    step = isAttrNum(slider.step) && slider.step > 0 ? +slider.step : 1;
    range = max - min;
    draw(true);
  }

  // recalculates value property
  function calc() {
    if (!isValueSet && !areAttrsSet)
      value = slider.getAttribute('value');
    if (!isAttrNum(value))
      value = (min + max) / 2;;
    // snap to step intervals (WebKit sometimes does not - bug?)
    value = Math.round((value - min) / step) * step + min;
    if (value < min)
      value = min;
    else if (value > max)
      value = min + ~~(range / step) * step;
  }

  /**
  * @param {boolean=} attrsModified
  */
  function draw(attrsModified) {
    // renders slider using CSS background ;)
    calc();
    if (isChanged && value != prevValue)
      slider.dispatchEvent(onChange);
    isChanged = false;
    if (!attrsModified && value == prevValue)
      return;
    prevValue = value;
    var position = range ? (value - min) / range * 100 : 0;
    var bg = '-moz-element(#__sliderthumb__) ' + position + '% no-repeat, ';
    style(slider, {"background": bg + track});
  }

}

function style(element, styles) {
  for (var prop in styles)
    element.style.setProperty(prop, styles[prop], 'important');
}

})();


##
# .versionstring
##
"""
PostgreSQL version string parsing.

>>> postgresql.versionstring.split('8.0.1')
(8, 0, 1, None, None)
"""

def split(vstr: str) -> tuple:
	"""
	Split a PostgreSQL version string into a tuple.
	(major, minor, patch, ..., state_class, state_level)
	"""
	v = vstr.strip().split('.')

	# Get rid of the numbers around the state_class (beta,a,dev,alpha, etc)
	state_class = v[-1].strip('0123456789')
	if state_class:
		last_version, state_level = v[-1].split(state_class)
		if not state_level:
			state_level = None
		else:
			state_level = int(state_level)
		vlist = [int(x or '0') for x in v[:-1]]
		if last_version:
			vlist.append(int(last_version))
		vlist += [None] * (3 - len(vlist))
		vlist += [state_class, state_level]
	else:
		state_level = None
		state_class = None
		vlist = [int(x or '0') for x in v]
		# pad the difference with `None` objects, and +2 for the state_*.
		vlist += [None] * ((3 - len(vlist)) + 2)
	return tuple(vlist)

def unsplit(vtup: tuple) -> str:
	"""
	Join a version tuple back into the original version string.
	"""
	svtup = [str(x) for x in vtup[:-2] if x is not None]
	state_class, state_level = vtup[-2:]
	return '.'.join(svtup) + ('' if state_class is None else state_class + str(state_level))

def normalize(split_version: tuple) -> tuple:
	"""
	Given a tuple produced by `split`, normalize the `None` objects into int(0)
	or 'final' if it's the ``state_class``.
	"""
	(*head, state_class, state_level) = split_version
	mmp = [x if x is not None else 0 for x in head]
	return tuple(mmp + [state_class or 'final', state_level or 0])

default_state_class_priority = [
	'dev',
	'a',
	'alpha',
	'b',
	'beta',
	'rc',
	'final',
	None,
]

python = repr

def xml(self):
	return '<version type="one">\n' + \
		' <major>' + str(self[0]) + '</major>\n' + \
		' <minor>' + str(self[1]) + '</minor>\n' + \
		' <patch>' + str(self[2]) + '</patch>\n' + \
		' <state>' + str(self[-2]) + '</state>\n' + \
		' <level>' + str(self[-1]) + '</level>\n' + \
		'</version>'

def sh(self):
	return """PG_VERSION_MAJOR=%s
PG_VERSION_MINOR=%s
PG_VERSION_PATCH=%s
PG_VERSION_STATE=%s
PG_VERSION_LEVEL=%s""" %(
		str(self[0]),
		str(self[1]),
		str(self[2]),
		str(self[-2]),
		str(self[-1]),
	)

if __name__ == '__main__':
	import sys
	import os
	from optparse import OptionParser
	op = OptionParser()
	op.add_option('-f', '--format',
		type='choice',
		dest='format',
		help='format of output information',
		choices=('sh', 'xml', 'python'),
		default='sh',
	)
	op.add_option('-n', '--normalize',
		action='store_true',
		dest='normalize',
		help='replace missing values with defaults',
		default=False,
	)
	op.set_usage(op.get_usage().strip() + ' "version to parse"')
	co, ca = op.parse_args()
	if len(ca) != 1:
		op.error('requires exactly one argument, the version')
	else:
		v = split(ca[0])
	if co.normalize:
		v = normalize(v)
	sys.stdout.write(getattr(sys.modules[__name__], co.format)(v))
	sys.stdout.write(os.linesep)

##
# .versionstring
##
"""
PostgreSQL version string parsing.

>>> postgresql.versionstring.split('8.0.1')
(8, 0, 1, None, None)
"""

def split(vstr: str) -> tuple:
	"""
	Split a PostgreSQL version string into a tuple.
	(major, minor, patch, ..., state_class, state_level)
	"""
	v = vstr.strip().split(' ')[0].split('.')

	# Get rid of the numbers around the state_class (beta,a,dev,alpha, etc)
	state_class = v[-1].strip('0123456789')
	if state_class:
		last_version, state_level = v[-1].split(state_class)
		if not state_level:
			state_level = None
		else:
			state_level = int(state_level)
		vlist = [int(x or '0') for x in v[:-1]]
		if last_version:
			vlist.append(int(last_version))
		vlist += [None] * (3 - len(vlist))
		vlist += [state_class, state_level]
	else:
		state_level = None
		state_class = None
		vlist = [int(x or '0') for x in v]
		# pad the difference with `None` objects, and +2 for the state_*.
		vlist += [None] * ((3 - len(vlist)) + 2)
	return tuple(vlist)

def unsplit(vtup: tuple) -> str:
	"""
	Join a version tuple back into the original version string.
	"""
	svtup = [str(x) for x in vtup[:-2] if x is not None]
	state_class, state_level = vtup[-2:]
	return '.'.join(svtup) + ('' if state_class is None else state_class + str(state_level))

def normalize(split_version: tuple) -> tuple:
	"""
	Given a tuple produced by `split`, normalize the `None` objects into int(0)
	or 'final' if it's the ``state_class``.
	"""
	(*head, state_class, state_level) = split_version
	mmp = [x if x is not None else 0 for x in head]
	return tuple(mmp + [state_class or 'final', state_level or 0])

default_state_class_priority = [
	'dev',
	'a',
	'alpha',
	'b',
	'beta',
	'rc',
	'final',
	None,
]

python = repr

def xml(self):
	return '<version type="one">\n' + \
		' <major>' + str(self[0]) + '</major>\n' + \
		' <minor>' + str(self[1]) + '</minor>\n' + \
		' <patch>' + str(self[2]) + '</patch>\n' + \
		' <state>' + str(self[-2]) + '</state>\n' + \
		' <level>' + str(self[-1]) + '</level>\n' + \
		'</version>'

def sh(self):
	return """PG_VERSION_MAJOR=%s
PG_VERSION_MINOR=%s
PG_VERSION_PATCH=%s
PG_VERSION_STATE=%s
PG_VERSION_LEVEL=%s""" %(
		str(self[0]),
		str(self[1]),
		str(self[2]),
		str(self[-2]),
		str(self[-1]),
	)

if __name__ == '__main__':
	import sys
	import os
	from optparse import OptionParser
	op = OptionParser()
	op.add_option('-f', '--format',
		type='choice',
		dest='format',
		help='format of output information',
		choices=('sh', 'xml', 'python'),
		default='sh',
	)
	op.add_option('-n', '--normalize',
		action='store_true',
		dest='normalize',
		help='replace missing values with defaults',
		default=False,
	)
	op.set_usage(op.get_usage().strip() + ' "version to parse"')
	co, ca = op.parse_args()
	if len(ca) != 1:
		op.error('requires exactly one argument, the version')
	else:
		v = split(ca[0])
	if co.normalize:
		v = normalize(v)
	sys.stdout.write(getattr(sys.modules[__name__], co.format)(v))
	sys.stdout.write(os.linesep)


/*
Copyright [2014] [Graham Abbott <graham.abbott@gmail.com>]

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

var genie = ( function() {
    var UNIQUE_TIME = "" + new Date().getTime();
    var GENIE_VERSION = "0.3";
    var genie_context_begin;
    var genie_context_end;

    var GENIE_CONTEXT_begin = eval("genie_context_begin") || "[";
    var GENIE_CONTEXT_end =   eval("genie_context_end")   || "]";

    var GENIE_CONTEXT_lookup = {
        "#":"comment",
        "%":"condition",
        "!":"exec",
        "*":"exec-coffee",
        "&":"bindable",
        "^":"notes",
        "~":"compiler",
    };

    GENIE_CONTEXT_lookup[GENIE_CONTEXT_begin] = "variable";
    GENIE_CONTEXT_lookup[GENIE_CONTEXT_end] = "variable";

    var genie_environ_count = 0;

    // I'm not really proud of this sort of monkey patching, but it's somewhat required here.
    var str_trim = function(s) { return s.replace(/^\s+|\s+$/g, "").replace(/^[\n|\r]+|[\n|\r]+$/g, ""); };

    var str_trimr = function(s) { return s.replace(/\s+$/g, "").replace(/[\n|\r]+$/g, ""); };
    var str_trimr_spaces = function(s) { return s.replace(/[ |\t]+$/g, ""); };
    var str_trimr_one = function(s)    { return s.replace(/\n[ |\t]*/g, ""); };

    var str_triml = function(s) { return s.replace(/^\s+/g, "").replace(/^[\n|\r]+/g, ""); };
    var str_triml_spaces = function(s) { return s.replace(/^[ |\t]+/g, ""); };
    var str_triml_one = function(s)    { return s.replace(/^[ |\t]*\n/g, ""); };
    var safe_str = function(s)         { return JSON.stringify(s); };

    var str_count = function(s, c, accum) {
        if (accum == undefined) {
            accum = 0;
        }
        var i = s.indexOf(c);
        if (i == -1) {
            return accum;
        } else {
            return str_count(s.slice(i+1), c, accum+1);
        }
    };

    var str_starts_with = function(key, st) {
        if (key.slice(0, st.length) == st) {
            return true;
        } else {
            return false;
        }
    }

    // Makes the code printouts very pretty ( can't help but keep it )
    var pad = function(count) {
        var index = 0;
        var pad = "";
        while(index < count) {
            pad += "    ";
            index += 1;
        }
        return pad;
    };

    var Template = function(sss) {
        this.orig_string = sss;
        this.string = sss;
        this.environment = null;
        this.blocks = [];
        this.final_func = null;
        this.notes = [];
        this.cur_template_line = 0;
        this.value_only = undefined;
        this.exposed_values = [];
    };

    Template.prototype.find_next_block = function() {
        var begin_char;
        var end_char;
        var cmd_lookup;

        var blocks = [];

        if (this.environment) {
            begin_char = this.environment.begin;
            end_char = this.environment.end;
            cmd_lookup = this.environment.lookup;
        } else {
            begin_char = GENIE_CONTEXT_begin;
            end_char = GENIE_CONTEXT_end;
            cmd_lookup = GENIE_CONTEXT_lookup;
        }

        if (this.value_only != undefined) {
            begin_char = this.value_only;
            end_char = this.value_only;
            cmd_lookup = {};
            cmd_lookup[this.value_only] = "variable";
        }

        var start = this.string.indexOf(begin_char);
        var next_char = start+1;

        if (start == -1) {
            var s = this.string;
            this.string = '';
            if (s == '') {
                return [];
            } else {
                //*
                this.cur_template_line += str_count(s, '\n');
                //*
                blocks.push( ['text', s, this.cur_template_line]);
                return blocks;
            }
        }

        var before_block = this.string.substring(0, start);
        var after_block = this.string.substring(start+1);

        this.cur_template_line += str_count(before_block, '\n');
        blocks.push( ['text', before_block, this.cur_template_line] );

        var start_char = after_block[0];
        var type = cmd_lookup[start_char];
        var end = null;

        if (start_char == begin_char) {
            end = after_block.indexOf(end_char + end_char);
        } else {
            if (start_char in cmd_lookup) {
                end = after_block.indexOf(start_char + end_char);
            } else {
                this.cur_template_line += str_count(begin_char, '\n');
                blocks.push( ['text', begin_char, this.cur_template_line] );
                this.string = after_block.substring(0);
                return blocks;
            }
        }

        end += 1;

        var block = after_block.substring(1, end-1);
        after_block = after_block.substring(end+1);

        // Pre-inner-operator.
        if (block[0] == '-') {
            block = block.substring(1);
            if (blocks[blocks.length-1]) {
                blocks[blocks.length-1][1] = str_trimr_spaces(blocks[blocks.length-1][1]);
            }
        } else if (block[0] == '=' || type == "notes") {
            block = block.substring(1);
            if (blocks[blocks.length-1]) {
                blocks[blocks.length-1][1] = str_trimr(blocks[blocks.length-1][1]);
            }
        } else if (block[0] == '|') {
            block = block.substring(1);
        }

        //post inner operator.
        if (block[block.length-1] == '|') {
            block = block.substring(0, block.length-1);
            after_block = str_triml_one(after_block);
        } else if (block[block.length-1] == '-') {
            block = block.substring(0, block.length-1);
            after_block = str_triml_spaces(after_block);
        } else if (block[block.length-1] == '=') {
            block = block.substring(0, block.length-1);
            after_block = str_triml(after_block);
        }

        this.cur_template_line += str_count(block, '\n');
        blocks.push( [type, block, this.cur_template_line] );

        this.string = after_block;
        return blocks;
    };

    Template.prototype.bailout = function() {
        /* throw an exception and stop rendering a template */
        throw { type: "bailout", message: "bailout of current template render" };
    };

    Template.prototype.compile = function() {
        var counter_count = 0;
        var depth = 0;
        var f_code = [];
        var in_func = [];
        var i = 0;
        var blocks = this.find_next_block();
        var tempvar_counter = 0;

        while(blocks.length > 0) {
            for( i = 0; i < blocks.length; i++ ) {
                var obj = blocks[i];
                var type = obj[0];
                var data = obj[1];
                var line = obj[2];

                if (type == 'text') {
                    f_code.push( "/* " + line + " */ " + pad(depth) );
                    f_code.push("write(" + JSON.stringify(data) + ");\n" );
                } else if ( type == 'condition') {
                    data = str_trim(data);
                    if (data.substring(0,2) == 'if') {
                        var d = str_trim(data.substring(2));
                        var bulk = d;
                        if (d[0] == '(') {
                            bulk = d.substring(1, d.length-1);
                        }
                        f_code.push( "\n" + "/* " + line + " */ " + pad(depth) );
                        f_code.push("if (" + bulk + ")" + " {\n");
                        depth += 1;
                        in_func.push('}');
                    } else if (data.substring(0, 5) == 'while') {
                        var d = str_trim(data.substring(5));
                        var bulk = d;
                        if (d[0] == '(') {
                            bulk = d.substring(1, d.length-2);
                        }
                        f_code.push( "\n/* " + line + " */ " + pad(depth) );
                        f_code.push("while (" + bulk + ")" + " {\n");
                        depth += 1;
                        in_func.push('}');
                    } else if (data.substring(0, 4) == 'ford') {
                        var d = str_trim(data.substring(4));
                        var bulk = d;
                        if (d[0] == '(') {
                            bulk = d.substring(1, d.length-2);
                        }

                        var value_name = bulk.substring(0, bulk.indexOf(' in '));
                        var rest = bulk.substring(bulk.indexOf(' in ') + 4);

                        var cvar = '_count_' + counter_count;
                        counter_count += 1;
                        f_code.push( "\n/* " + line + " */ for( var " + value_name + " in " + rest + " ) {" );
                        f_code.push( "\n/* " + line + " */ " + pad(depth) );
                        in_func.push('}');
                        depth += 1;
                    } else if (data.substring(0, 3) == 'for') {
                        var d = str_trim(data.substring(3));
                        var bulk = d;
                        if (d[0] == '(') {
                            bulk = d.substring(1, d.length-2);
                        }

                        var value_name = bulk.substring(0, bulk.indexOf(' in '));
                        var rest = bulk.substring(bulk.indexOf(' in ') + 4);

                        var cvar = '_count_' + counter_count;
                        counter_count += 1;
                        f_code.push( "\n/* " + line + " */ for( var " + cvar + " = 0; " + cvar + " < " + rest + ".length; " + cvar + "++ ) {" );
                        f_code.push( "\n/* " + line + " */   var " + value_name + " = " + rest + "[" + cvar + "]; var index=" + cvar + ";");
                        f_code.push( "\n/* " + line + " */   var rindex = (" + rest + ".length" + " - index) - 1");
                        f_code.push( "\n/* " + line + " */ " + pad(depth) );
                        in_func.push('}');
                        depth += 1;
                    } else if (data == 'end') {
                        depth -= 1;
                        f_code.push( "/* " + line + " */ " + pad(depth) );
                        f_code.push(in_func.pop() + ';\n');
                    } else if (data.substring(0, 7) == 'else if') {
                        var command = data.substring(0, 7);
                        var rest = str_trim(data.substring(7));
                        if (rest[0] == '(') {
                            bulk = d.substring(1, d.length-1);
                        }
                        if (rest[rest.length-1] == ')') {
                            bulk = d.substring(0, d.length-2);
                        }

                        f_code.push( "/* " + line + " */ " + pad(depth-1) );
                        f_code.push( "} " + command + " ( " + rest + " ) {\n");
                    } else if (data.substring(0, 4) == 'else') {
                        var command = data.substring(0, 4);
                        f_code.push( "/* " + line + " */ " + pad(depth-1) );
                        f_code.push( "} " + command + " {\n");
                    }
                } else if (type == 'variable') {
                    f_code.push( pad(depth) );
                    var vardata = data;
                    var vartype = undefined;

                    // :: means of type. (obj :: type)
                    if (data.indexOf('::') != -1) {
                        var temp = data.split('::');
                        vardata = str_trim(temp[0]);
                        vartype = str_trim(temp[1]);
                    }

		            if (data.indexOf(GENIE_CONTEXT_begin) == 0) {
			            f_code.push( "/* " + line + " */ write( " + vardata.substring(1) + " );\n");
                    } else {
                        var tempvar_name = "__tempvar_" + tempvar_counter;
                        tempvar_counter++;
                        f_code.push( "/* " + line + " */ var " + tempvar_name + " = " + vardata + ";\n");
                        f_code.push( "/* " + line + " */ if (typeof(" + tempvar_name + ") == \"function\") { write(" + tempvar_name + "());}\n");
			            f_code.push( "/* " + line + " */ else { write( (typeof(" + tempvar_name + ") != 'undefined') ? escape_variable(" + tempvar_name + ", '" + vartype + "') : undefined_variable(" + JSON.stringify(vardata) + ") ); } \n");
		            }
                } else if (type == 'bindable') {
                    var value = this.environment.bindable_dict[str_trim(data)];
                    if (value === undefined) {
                        value = '';
                    }

                    f_code.push( "/* " + line + " */ write( \"\" + " + data + " + \"\" );\n" );
                } else if (type == 'exec') {
                    f_code.push( "/* " + line + " */ " + data);
                } else if (type == 'exec-coffee') {
                    f_code.push( "/* " + line + " */ " + CoffeeScript.compile(data));
                } else if (type == 'notes') {
                    this.notes.push(str_trim(data));
                } else if (type == 'compiler') {
                    // this should have been compiled out, ignore in this case.
                    // pass
                }
            }
            blocks = this.find_next_block();
        }

        var preamble = [];
        if (this.notes) {
            preamble = this.preamble_notes();
        }

        preamble = preamble.join(' ');

        var header = "var write = locals.write; var escape_variable = locals.escape_variable;";
        header += "var partial = locals.partial; var bailout = locals.bailout;";
        header += "var _env = locals._env; var _template = locals._template;";
        this.f_code_render = preamble + header + f_code.join('');

        //console.log(this.f_code_render);
        this.f_code = null;
    };

    Template.prototype.preamble_notes = function() {
        var newnotes = [];
        var preamble = [];

        for(var i = 0; i < this.notes.length; i++) {
            var obj = str_trim(this.notes[i]);
            var result = null;
            if (obj.slice(0, 6) == "expose") {
                try {
                    result = JSON.parse(obj.slice(6, obj.length));
                } catch (err) {
                    result = null;
                }
                if (result) {
                    if (typeof(result) == "string") {
                        preamble.push("var " + result + " = v." + result + ";");
                    } else if (typeof(result) == "object") {
                        for(var __i = 0; __i < result.length; __i++) {
                            var result_final = result[__i].replace(' ', '_');
                            preamble.push("var " + result_final + " = v." + result_final + ";");
                        }
                    }
                }
            } else {
                newnotes.push(obj);
            }
        }
        this.notes = newnotes;
        return preamble;
    };

    Template.prototype.pre_render = function(undefined_variable) {
        this.compile();

        var locals = {};
        locals['_env'] = this.environment;
        locals['____output'] = [];

        locals['partial'] = function(name, d) {
            var ptemp = locals['_env'].get_template(name);
            if (ptemp == undefined) {
                console.log("ERROR: Template " + name + " not found.");
                return "TEMPLATE_NOT_FOUND: " + name;
            } else {
                return locals['_env'].get_template(name).render(d);
            }
        };

        locals['write'] = function(ddd) { locals['____output'].push(ddd); };
        locals['_template'] = this;
        locals['bailout'] = this.bailout;

        locals['escape_variable'] = function(data, type) { return data; };

        try {
            var compiled_code = new Function('parent', 'v', 'defaults', 'undefined_variable', 'locals', this.f_code_render);
        } catch (e) {
            this.stack_trace(e);
        }

        var encased_template = function(tvars, uv) {
            locals['____output'] = [];
            var template_vars = tvars;

            var undef_var = function(name) {
                if (!uv) {
                    console.log("Variable '" + name + "' is not defined, state: " + tvars);
                    return "** " + name + " not defined **";
                } else if (uv.indexOf('%s') == -1) {
                    return str_trim(uv);
                } else {
                    return str_trim(uv.replace('%s', str_trim(name)));
                }
            };

            var defaults;
            if (this.environment) {
                defaults = this.environment.default_dict;
            } else {
                defaults = {};
            }

            compiled_code(locals['_template'], template_vars, defaults, undef_var, locals);
            return locals['____output'].join('');
        }
        this.final_func = encased_template;
        this.f_code_render = null;
    };

    Template.prototype.render = function(variables, undefined_variable) {
        if (this.final_func == null) {
            this.pre_render(undefined_variable);
        }

        try {
            var result = this.final_func(variables, undefined_variable);
            return result;
        } catch (e) {
            if (e.type == 'bailout') {
                return null;
            } else {
                this.stack_trace(e);
            }
        }
    };

    Template.prototype.stack_trace = function(e) {
        throw e;
        var line = null;
        if (e.line) {
            line = this.f_code.join('').split('\n')[e.line-3];
        } else if (e.lineNumber) {
            line = this.f_code.join('').split('\n')[e.lineNumber-3];
        } else {
            throw new Error('Your browser sucks: ' + e.message);
        }
        if (line.slice(0, 2) == '/*') {
            var os_by_line = this.orig_string.split('\n');
            var line_number = parseInt(str_trim(line.slice(2, line.indexOf('*/'))));
            var error_lines = [];

            if (line_number > 0) {
                error_lines.push(" line " + (line_number) + ": " + os_by_line[line_number-1]);
            }

            error_lines.push(" line " + (line_number+1) + ": " + os_by_line[line_number]);

            if (line_number < os_by_line.length-1) {
                error_lines.push(" line " + (line_number+2) + ": " + os_by_line[line_number+1]);
            }

            var message = "Javascript Error => " + e.message + "\nOn template line => " + (line_number+1) + "\n--------------------\n" + error_lines.join('\n') + "\n--------------------";
            console.log(message);
            throw new Error(message);
        } else {
            throw e;
        }
    };

    Template.prototype.async_render = function(variables, options) {
        var do_nothing = function() {};
        var undefined_variable = options['undefined_variable'] || function(varname) { return '**' +  varname + ' not defined **'; };
        var on_success = options['on_success'] || do_nothing;
        var on_error = options['on_error'] || do_nothing;
        var on_bailout = options['on_bailout'] || do_nothing;

        try {
            var result = this.render(variables, undefined_variable);
            if (result == null && on_bailout) {
                on_bailout(this);
                return;
            } else {
                if (on_success) {
                    on_success(result, this);
                }
            }
        } catch (e) {
            on_error(e, this);
        }
    };

    var Environment = function() {
        this.id = genie_environ_count + 1;
        genie_environ_count += 1;
        this.default_data = {};
        this.object_dict = {};
        this.template_dict = {};
        this.bindable_dict = {};
        this.escape_dict = {};

        this.begin = GENIE_CONTEXT_begin;
        this.end = GENIE_CONTEXT_end;
        this.lookup = GENIE_CONTEXT_lookup;
    };

    Environment.prototype.escape_variable = function(vardata, vartype) {
        return vardata;
    };

    Environment.prototype.template_list = function() {
        l = [];
        for( var i in this.template_dict ) {
            l.push(i);
        }
        return l;
    };

    Environment.prototype.set_bindable = function(key, value) {
        this.bindable_dict[key] = value;

        var targets = document.getElementsByClassName('genie_' + this.id + '_value_update_' + key);
        for( var i = 0; i < targets.length; i++ ) {
            var obj = targets[i];
            obj.innerHTML = value;
        }
    };

    Environment.prototype.get_template = function(name) {
        return this.template_dict[name];
    };

    Environment.prototype.create_template = function(name, data) {
        var t = new Template(data);
        t.key = name;
        t.environment = this;
        this.template_dict[name] = t;
        return t;
    };

    Environment.prototype.render_quick = function(template_text, vars, undef_var) {
        if (vars === undefined) {
            vars = {};
        }
        var t = new Template(template_text);
        t.key = 'anon';
        t.environment = this;
        return t.render(vars, undef_var);
    };

  Environment.prototype.render = function(name, vars, undef_var) {
    if (vars === undefined) {
      vars = {};
    }
    var t = this.template_dict[name];
    if (t === undefined) {
      console.log("Template " + name + " not found.");
      return '';
    } else {
      return t.render(vars, undef_var);
    }
  };

    Environment.prototype.set_obj = function(name, obj) {
        this.object_dict[name] = obj;
    };

    Environment.prototype.get_obj = function(name) {
        return this.object_dict[name];
    };

    Environment.prototype.load_template = function(url, name, cb) {
        var env = this;
        $.get(url + "?_ts=" + UNIQUE_TIME,
              function(data) {
                  env.create_template(name, data);
                  console.log('created template: ' + name + ' (' + data.length + ' bytes)');
                  if (cb) {
                      cb.finish();
                  }
              });
    };

    Environment.prototype.load_templates = function(orig_paths, final_callback) {
        var env = this;
        function load_next(paths, callback) {
            if (paths.length == 0) {
                callback(env);
            } else {
                var template_name = paths.pop();
                $.get(template_name + "?_ts=" + UNIQUE_TIME,
                      function(data) {
                          env.create_template(template_name, data);
                          console.log('created template: ' + name + ' (' + data.length + ' bytes)');
                          load_next(paths, callback);
                      }).fail( function() {load_next(paths, callback)} );
            }

        };
        load_next(orig_paths, final_callback);
    };

    Environment.prototype.auto_load = function(callback) {
        // this sucks because it requires jquery, should figure out how to
        // make that not a dependency.

        var env = this;
        var template_names = [];
        $('.genie-template').each( function(index, item) {
                template_names.push($(item).attr('data-genie-template'));
        });

        env.load_templates(template_names, function() {
                $('.genie-template').each( function(index, item) {
                        var template_name = $(item).attr('data-genie-template');
                        var result = env.render(template_name, {});
                        $(item).html(result);
                    });
                if (callback) {
                    callback();
                }
            });
    };

    Environment.prototype.load_template_dir = function(url, cb) {
        // this sucks because it requires jquery, should figure out how to
        // make that not a dependency.

        var env = this;
        $.get(url, function(data) {
                data = JSON.parse(data);
                var items = [];
                for(var name in data) {
                    var obj = data[name];
                    var load = function(o) {
                        return function(t) {
                            env.load_template(url + o, o.split('.')[0], t);
                        }
                    }
                    items.push( load(obj) );
                }
                ut.serial(function() { return; }, items, cb);
            });
    };

    var main_environment = new Environment();

    var fs = function( s, args, value_only ) {
        var t = new Template(s);
        t.value_only = value_only;
        return t.render(args);
    };

    var ts = function() {
        var d = new Date();
        return d.getTime() / 1000.0;
    };

    var genie_render_dom_element = function(d, o) {
        var content = d.value;
        var t = new Template(content);
        d.value = t.render(o);
    }

    var loadr = function(url) {
        var d = document.createElement('script');
        d.src = url;
        d.type = 'text/javascript';
        document.body.appendChild(d);
    };

    var monkey_patch = function() {
        String.prototype.render = function(args, undef_var) {
        var t = new Template(this);
        t.key = 'anon';
        return t.render(args, undef_var);
        };
    };

    var dig_get = function(obj, key, settings) {
        var split_key = "/";
        if (settings['di']) {
            split_key = settings['di'];
        }

        if (key.indexOf(split_key) == -1) {
            return obj[key];
        } else {
            var cur = key.split(split_key, 1);
            var rest = key.split(split_key).slice(1).join(split_key);
            obj = obj[cur];
            return this.dig_get(obj, rest);
        }
    };

    var dig_set = function(obj, key, value, settings) {
        var split_key = "/";
        var def = function() { return new Object(); };
        if (settings == undefined) {
            settings = {};
        }
        if (settings['di']) {
            split_key = settings['di'];
        }
        if (settings['def']) {
            def = settings['def'];
        }

        if (key[key.length-1] == split_key) {
            key = key.slice(0, key.length-1);
        }

        if (key.indexOf(split_key) == -1) {
            obj[key] = value;
            return [obj, key];
        } else {
            var cur = key.split(split_key, 1);
            var rest = key.split(split_key).slice(1).join(split_key);
            var newb = obj[cur];
            if (newb == undefined) {
                obj[cur] = def();
                newb = obj[cur];
            }

            return this.dig_set(newb, rest, value);
        }
    };

    var unpack_packed_hash = function(data) {
    };

    var render_body_as_template = function(d, undef_var) {
        var v = main_environment.render_quick(document.body.innerHTML, d, undef_var);
        document.body.innerHTML = v;
        return v;
    };

    var exports;

    exports = {
        'Template':Template,
        'Environment':Environment,
        'monkey_patch':monkey_patch,
        'main_environment':main_environment,
        'fs':fs,
        'str_count':str_count,
        'version':GENIE_VERSION,
        'dig_set':dig_set,
        'dig_get':dig_get,
        'render_body_as_template':render_body_as_template,
        'rbt':render_body_as_template,
        'str_starts_with':str_starts_with
    };

    return exports;
})();

if (typeof genie_module !== 'undefined') {
    genie_module.exports.genie = genie;
    genie_module.exports.Template = genie.Template;
    genie_module.exports.Environment = genie.Environment;
}


require File.dirname(__FILE__) + '/../spec_helper'

# stub the Rails module functionality
RAILS_CACHE = ActiveSupport::Cache.lookup_store(:memory_store)
module Rails
  def self.cache
    RAILS_CACHE
  end
end

describe ActionController::Base do
  it "should have render_with_cache private method" do
    ActionController::Base.new.private_methods.should include('render_with_cache')
  end
end

describe RenderCaching::ControllerAdditions do
  include RenderCaching::ControllerAdditions

  before(:each) do
    Rails.cache.clear
    @request = stub
    @response = stub(:body => '')
    stubs(:request).returns(@request)
    stubs(:response).returns(@response)
    stubs(:performed?)
    stubs(:render)
  end

  it "should read from the cache with request uri as key and render that text" do
    @request.stubs(:request_uri).returns('/foo/bar')
    Rails.cache.write('/foo/bar', 'page content')
    expects(:render).with(:text => 'page content')
    render_with_cache
  end

  it "should read from the cache with custom passed key and render that text" do
    Rails.cache.write('my_key', 'page content')
    expects(:render).with(:text => 'page content')
    render_with_cache 'my_key'
  end

  it "should save response.body to cache as key when not specified" do
    @response.stubs(:body).returns('content')
    render_with_cache 'some_key'
    Rails.cache.read('some_key').should == 'content'
  end

  it "should call render when not cached or rendered yet" do
    stubs(:performed?).returns(false)
    expects(:render).with()
    render_with_cache 'some_key'
  end

  it "should not call render if already rendered" do
    stubs(:performed?).returns(true)
    stubs(:render).raises('should not be called')
    lambda { render_with_cache 'some_key' }.should_not raise_error
  end

  it "should not call render :text if cache doesn't exist" do
    stubs(:render).with(:text => @response.body).raises('should not be called')
    lambda { render_with_cache 'some_key' }.should_not raise_error
  end

  it "should yield to block when not cached" do
    pass = false
    render_with_cache('some_key') { pass = true }
    pass.should be_true
  end

  it "should not yield to block when cached" do
    Rails.cache.write('some_key', 'page content')
    render_with_cache('some_key') { violated('block was executed') }
  end

  it "should pass options to cache write call" do
    Rails.cache.expects(:write).with('some_key', @response.body, :expires_in => 5)
    render_with_cache('some_key', :expires_in => 5)
  end
end

require File.dirname(__FILE__) + '/../spec_helper'

# stub the Rails module functionality
RAILS_CACHE = ActiveSupport::Cache.lookup_store(:memory_store)
module Rails
  def self.cache
    RAILS_CACHE
  end
end

describe ActionController::Base do
  it "should have render_with_cache private method" do
    ActionController::Base.new.private_methods.should include('render_with_cache')
  end
end

describe RenderCaching::ControllerAdditions do
  include RenderCaching::ControllerAdditions

  before(:each) do
    Rails.cache.clear
    @request = stub
    @response = stub(:body => '')
    stubs(:request).returns(@request)
    stubs(:response).returns(@response)
    stubs(:performed?)
    stubs(:render)
  end

  it "should read from the cache with request uri as key and render that text" do
    @request.stubs(:fullpath).returns('/foo/bar')
    Rails.cache.write('/foo/bar', 'page content')
    expects(:render).with(:text => 'page content')
    render_with_cache
  end

  it "should read from the cache with custom passed key and render that text" do
    Rails.cache.write('my_key', 'page content')
    expects(:render).with(:text => 'page content')
    render_with_cache 'my_key'
  end

  it "should save response.body to cache as key when not specified" do
    @response.stubs(:body).returns('content')
    render_with_cache 'some_key'
    Rails.cache.read('some_key').should == 'content'
  end

  it "should call render when not cached or rendered yet" do
    stubs(:performed?).returns(false)
    expects(:render).with()
    render_with_cache 'some_key'
  end

  it "should not call render if already rendered" do
    stubs(:performed?).returns(true)
    stubs(:render).raises('should not be called')
    lambda { render_with_cache 'some_key' }.should_not raise_error
  end

  it "should not call render :text if cache doesn't exist" do
    stubs(:render).with(:text => @response.body).raises('should not be called')
    lambda { render_with_cache 'some_key' }.should_not raise_error
  end

  it "should yield to block when not cached" do
    pass = false
    render_with_cache('some_key') { pass = true }
    pass.should be_true
  end

  it "should not yield to block when cached" do
    Rails.cache.write('some_key', 'page content')
    render_with_cache('some_key') { violated('block was executed') }
  end

  it "should pass options to cache write call" do
    Rails.cache.expects(:write).with('some_key', @response.body, :expires_in => 5)
    render_with_cache('some_key', :expires_in => 5)
  end
end


/**
 * loupe - an image magnifier for jQuery
 * (C) 2010 jdbartlett, MIT license
 * http://github.com/jdbartlett/loupe
 */
(function ($) {
	$.fn.loupe = function (arg) {
		var options = $.extend({
			loupe: 'loupe',
			width: 200,
			height: 150
		}, arg || {});

		return this.length ? this.each(function () {
			var $this = $(this), $big, $loupe,
				$small = $this.is('img') ? $this : $this.find('img:first'),
				move, hide = function () { $loupe.hide(); },
				time;

			if ($this.data('loupe') != null) {
				return $this.data('loupe', arg);
			}

			move = function (e) {
				var os = $small.offset(),
					sW = $small.outerWidth(),
					sH = $small.outerHeight(),
					oW = options.width / 2,
					oH = options.height / 2;

				if (!$this.data('loupe') ||
					e.pageX > sW + os.left + 10 || e.pageX < os.left - 10 ||
					e.pageY > sH + os.top + 10 || e.pageY < os.top - 10) {
					return hide();
				}

				time = time ? clearTimeout(time) : 0;

				$loupe.show().css({
					left: e.pageX - oW,
					top: e.pageY - oH
				});
				$big.css({
					left: -(((e.pageX - os.left) / sW) * $big.width() - oW)|0,
					top: -(((e.pageY - os.top) / sH) * $big.height() - oH)|0
				});
			};

			$loupe = $('<div />')
				.addClass(options.loupe)
				.css({
					width: options.width,
					height: options.height,
					position: 'absolute',
					overflow: 'hidden'
				})
				.append($big = $('<img />').attr('src', $this.attr($this.is('img') ? 'src' : 'href')).css('position', 'absolute'))
				.mousemove(move)
				.hide()
				.appendTo('body');

			$this.data('loupe', true)
				.mouseenter(move)
				.mouseout(function () {
					time = setTimeout(hide, 10);
				});
		}) : this;
	};
}(jQuery));

/**
 * loupe - an image magnifier for jQuery
 * (C) 2010 jdbartlett, MIT license
 * http://github.com/jdbartlett/loupe
 */
(function ($) {
	$.fn.loupe = function (arg) {
		var options = $.extend({
			loupe: 'loupe',
			width: 200,
			height: 150
		}, arg || {});

		return this.length ? this.each(function () {
			var $this = $(this), $big, $loupe,
				$small = $this.is('img') ? $this : $this.find('img:first'),
				tmove, move, hide = function () { $loupe.hide(); },
				time;

			if ($this.data('loupe') != null) {
				return $this.data('loupe', arg);
			}

			tmove = function(e) {
				var eMouse = null;

				if(e !== null && typeof(e.originalEvent.targetTouches)!='undefined' &&
					e.originalEvent.targetTouches.length>0)
				{
					var objTouch = e.originalEvent.targetTouches[e.originalEvent.targetTouches.length-1];

					if(objTouch!==null)
					{
						if(typeof(objTouch.pageX)!='undefined' && !isNaN(objTouch.pageX))
						{
							eMouse =
								{
									pageX: objTouch.pageX,
									pageY: objTouch.pageY
								};
						}
						else if(typeof(objTouch.clientX)!='undefined' && !isNaN(objTouch.clientX))
						{
							eMouse =
								{
									pageX: objTouch.clientX,
									pageY: objTouch.clientY
								};
						}
						else if(typeof(objTouch.screenX)!='undefined' && !isNaN(objTouch.screenX))
						{
							eMouse =
								{
									pageX: objTouch.screenX,
									pageY: objTouch.screenY
								};
						}
					}
				}

				if(eMouse!==null)
				{
					e.preventDefault();
					return move(eMouse);
				}
				else
					return hide();
			};

			move = function (e) {
				var os = $small.offset(),
					sW = $small.outerWidth(),
					sH = $small.outerHeight(),
					oW = options.width / 2,
					oH = options.height / 2;

				if (!$this.data('loupe') ||
					e.pageX > sW + os.left + 10 || e.pageX < os.left - 10 ||
					e.pageY > sH + os.top + 10 || e.pageY < os.top - 10) {
					return hide();
				}

				time = time ? clearTimeout(time) : 0;

				$loupe.show().css({
					left: e.pageX - oW,
					top: e.pageY - oH
				});
				$big.css({
					left: -(((e.pageX - os.left) / sW) * $big.width() - oW)|0,
					top: -(((e.pageY - os.top) / sH) * $big.height() - oH)|0
				});
			};

			$loupe = $('<div />')
				.addClass(options.loupe)
				.css({
					width: options.width,
					height: options.height,
					position: 'absolute',
					overflow: 'hidden'
				})
				.append($big = $('<img />').attr('src', $this.attr($this.is('img') ? 'src' : 'href')).css('position', 'absolute'))
				.mousemove(move)
				.on('touchstart touchmove', tmove)
				.hide()
				.appendTo('body');

			$this.data('loupe', true)
				.mouseenter(move)
				.on('touchstart touchenter touchmove', tmove)
				.on('mouseout touchend touchleave touchcancel', function () {
					time = setTimeout(hide, 10);
				});
		}) : this;
	};
}(jQuery));