<commit_before>/*
 *  phant.js
 *
 *  David Janes
 *  IOTDB.org
 *  2014-09-05
 *  "The Great Fire of London happened this day in 1666"
 *
 *  Connect to Phant/data.sparkfun.com-type servers
 *
 *  Copyright [2013-2014] [David P. Janes]
 *
 *  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.
 */

"use strict"

var unirest = require('unirest')
var fs = require('fs')

/**
 *  Connect to a Phant server
 *
 *  @param {object} paramd
 *  @param {String|undefined} paramd.iri
 *  The server to connect to, by default "http://data.sparkfun.com"
 *
 *  @constructor
 */
var Phant = function(paramd) {
    var self = this

    paramd = defaults(paramd, {
        iri: "http://data.sparkfun.com"
    })

    self.iri = paramd.iri
}

/**
 *  Create a new stream
 *
 *  @param {object} paramd
 *  @param {String} paramd.title
 *  @param {String} paramd.description
 *  @param {String} paramd.fields
 *
 *  @param {Phant~stream_callback} callback
 */
Phant.prototype.create = function(paramd, callback) {
    var self = this

    paramd = defaults(paramd, {
        title: "",
        description: "",
        fields: ""
    })

    unirest
        .post(self.iri + "/streams")
        .headers({ 'Accept': 'application/json' })
        .type('json')
        .send({
            title: paramd.title,
            description: paramd.description,
            fields: paramd.fields
        })
        .end(function(result) {
            if (result.error) {
                if (result.body) {
                    callback(result.body.message, null)
                } else if (result.error.code) {
                    callback(result.error.code, null)
                } else {
                    callback("error creating stream", null)
                }
            } else if (!result.body.success) {
                callback(result.body.message, null)
            } else {
                console.log(result)
                callback(null, {
                    title: result.body.stream.title,
                    description: result.body.stream.description,
                    fields: result.body.stream.fields,
                    outputUrl: self.iri + '/output/' + result.body.publicKey,
                    inputUrl: self.iri + '/input/' + result.body.publicKey,
                    manageUrl: self.iri + '/streams/' + result.body.publicKey,
                    publicKey: result.body.publicKey,
                    privateKey: result.body.privateKey,
                    deleteKey: result.body.deleteKey
                })
            }

        })
    ;
}

/**
 *  Connect to a stream, via a stream
 *  record or a stream IRI
 *
 *  @param {object|iri} streamd
 *  @param {String|undefined} streamd.publicKey
 *  @param {String|undefined} streamd.privateKey
 *  @param {String|undefined} streamd.deleteKey
 *  If 'streamid' is an IRI string, it
 *  will be assumed to be an management-type URL
 *  e.g. 'https://data.sparkfun.com/streams/dZ4EVmE8yGCRGx5XRX1W'
 *
 *  @param {Phant~stream_callback} callback
 */
Phant.prototype.connect = function(streamd, callback) {
    var self = this

    if (is_iri(streamd)) {
        var parts = streamd.match(/^(.*)\/streams\/([^\/]*)/)
        if (!parts) {
            callback("unrecognized iri format: " + streamd)
            return
        }

        streamd = {
            publicKey: parts[2],
            outputUrl: parts[1] + "/output/" + parts[2],
            inputUrl: parts[1] + "/input/" + parts[2],
            manageUrl: parts[1] + "/streams/" + parts[2]
        }
    }

    unirest
        .get(streamd.manageUrl)
        .headers({
            'Accept': 'application/json',
        })
        .end(function(result) {
            if (result.error) {
                if (result.body) {
                    callback(result.body.message, null)
                } else if (result.error.code) {
                    callback(result.error.code, null)
                } else {
                    callback("error connecting to stream", null)
                }
            } else if (!result.body.success) {
                callback(result.body.message, null)
            } else {
                callback(null, {
                    title: result.body.stream.title,
                    description: result.body.stream.description,
                    fields: result.body.stream.fields,
                    outputUrl: streamd.outputUrl,   // this can be done better
                    inputUrl: streamd.inputUrl,
                    manageUrl: streamd.manageUrl,
                    publicKey: streamd.publicKey,
                    privateKey: streamd.privateKey,
                    deleteKey: streamd.deleteKey
                })
            }
        })
    ;
}

/**
 *  Return the next record from the stream. If this
 *  stream has not been read from it will return the
 *  latest record first.
 *

 *  WARNING: If the stream is being written to while
 *  this is being used, there's likely some duplicate
 *  records that will be returned when a refetch
 *  happens
 *

 *  This is likely to cache multiple results
 *
 *  @param {object} streamd
 *  As returned by
 *  {@link Phant#connect connect} or
 *  {@link Phant#create create}.
 *
 *  @param {Phant~record_callback} callback
 */
Phant.prototype.next = function(streamd, callback) {
    var self = this

    var offset = 0

    if (streamd.__rds === undefined) {
        streamd.__rds = []
        streamd.__offset = 0
        streamd.__eof = false
    } else if (streamd.__eof) {
        callback(null, null)
        return
    } else if (streamd.__offset < streamd.__rds.length) {
        callback(null, streamd.__rds[streamd.__offset++])
        return
    } else {
        /* need to go deeper into the streamd */
    }

    /*
     *  If we get here we need to fetch a new record
     */
    var iri = streamd.outputUrl + '.json?offset=' + streamd.__offset
    unirest
        .get(iri)
        .headers({
            'Accept': 'application/json',
        })
        .end(function(result) {
            if (result.error) {
                if (result.error.code) {
                    callback(result.error.code, null)
                } else if (result.body) {
                    // deceptive: an error is returned for empty streams
                    streamd.__eof = true
                    callback(null, null)
                } else {
                    callback("error reading stream", null)
                }
            } else {
                streamd.__rds.push.apply(streamd.__rds, result.body)
                if (streamd.__offset < streamd.__rds.length) {
                    callback(null, streamd.__rds[streamd.__offset++])
                } else {
                    streamd.__eof = true
                    callback(null, null)
                }
            }
        })
    ;
}

/**
 *  Reset the stream so that "next" will return
 *  the latest record again.
 *

 *  This is immediate: there is no callback.
 *
 *  @param {object} streamd
 *  As returned by
 *  {@link Phant#connect connect} or
 *  {@link Phant#create create}.
 */
Phant.prototype.reset = function(streamd) {
    var self = this

    streamd.__rds = undefined
}

/**
 *  Return the latest record from the stream.
 *  This is entirely independent of next/reset.
 *  @param {object} streamd
 *  As returned by
 *  {@link Phant#connect connect} or
 *  {@link Phant#create create}.
 *
 *  @param {Phant~record_callback} callback
 */
Phant.prototype.latest = function(streamd, callback) {
    var self = this

    unirest
        .get(streamd.outputUrl + '/latest.json')
        .headers({
            'Accept': 'application/json',
        })
        .end(function(result) {
            if (result.error) {
                if (result.error.code) {
                    callback(result.error.code, null)
                } else if (result.body) {
                    // deceptive: an error is returned for empty streams
                    callback(null, null)
                } else {
                    callback("error reading stream", null)
                }
            } else if (result.body) {
                callback(null, result.body[0])
            } else {
                callback(null, null)
            }
        })
    ;
}

/**
 *  Add a record to the stream
 *
 *  @param {object} streamd
 *  As returned by
 *  {@link Phant#connect connect} or
 *  {@link Phant#create create}.
 *
 *  @param {Phant~op_callback|undefined} callback
 */
Phant.prototype.add = function(streamd, rd, callback) {
    var self = this

    for (var fi in streamd.fields) {
        var field = streamd.fields[fi]
        if (rd[field] === undefined) {
            rd[field] = ""
        }
    }
    rd = defaults(rd, {})

    unirest
        .post(streamd.inputUrl)
        .headers({
            'Accept': 'application/json',
            'Phant-Private-Key': streamd.privateKey
        })
        .type('json')
        .send(rd)
        .end(function(result) {
            if (!callback) {
            } else if (result.error) {
                if (result.body) {
                    callback(result.body.message)
                } else if (result.error.code) {
                    callback(result.error.code)
                } else {
                    callback("error adding to stream")
                }
            } else if (!result.body.success) {
                callback(result.body.message)
            } else {
                callback(null)
            }
        })
    ;
}

/**
 *  Update the metadata of a stream.
 *

 *  WARNING: may wipe out data?
 *
 *  @param {object} streamd
 *  As returned by
 *  {@link Phant#connect connect} or
 *  {@link Phant#create create}.
 *
 *  @param {object} paramd
 *  @param {String} paramd.title
 *  @param {String} paramd.description
 *  @param {String} paramd.fields
 *  ... and more ...
 *
 *  @param {Phant~op_callback|undefined} callback
 */
Phant.prototype.update = function(streamd, paramd, callback) {
    var self = this

    paramd = defaults(paramd, {
        title: streamd.title,
        description: streamd.description
    })

    // API oddness
    if (paramd.fields)  {
        paramd.field_check = ''
    } else {
        paramd.fields = streamd.fields.join(",")
        paramd.field_check = streamd.fields.join(",")
    }

    unirest
        .post(streamd.manageUrl + '/update/' + streamd.privateKey + '.json')
        .headers({
            'Accept': 'application/json',
            'Phant-Private-Key': streamd.privateKey
        })
        .type('json')
        .send(paramd)
        .end(function(result) {
            if (!callback) {
            } else if (result.error) {
                if (result.body) {
                    callback(result.body.message)
                } else if (result.error.code) {
                    callback(result.error.code)
                } else {
                    callback("error updating metadata")
                }
            } else if (!result.body.success) {
                callback(result.body.message)
            } else {
                callback(null)
            }
        })
    ;
}

/**
 *  Return a scrubbed version of the streamd.
 *  These can be used for serialization to disk
 *  or for starting an independent input stream.
 *

 *  A scrubbed streamd can be operated on
 *  if it's the result of a
 *  {@link Phant#connect connect} or
 *  {@link Phant#create create} callback.
 *

 *  This is immediate: there is no callback.
 *
 *  @param {object} streamd
 *  As returned by
 *  {@link Phant#connect connect} or
 *  {@link Phant#create create}.
 *
 *  @return {object}
 *  A new streamd
 */
Phant.prototype.scrub = function(streamd) {
    var nd = {}

    for (var key in streamd) {
        if (!key.match(/^__/)) {
            nd[key] = streamd[key]
        }
    }

    return nd
}

/**
 *  Helper function to load a stream record
 *  from a file.
 *
 *  @param {String} filename
 *  File to load from
 *
 *  @param {object} streamd
 *  Stream record. You must still should send this to
 *  {@link Phant#connect connect} to use.
 */
Phant.prototype.load = function(filename) {
    return JSON.parse(fs.readFileSync(filename, { encoding: 'utf8' }))
}

/**
 *  Helper function to save a stream record
 *  to a file. The stream record
 *
 *  @param {String} filename
 *  File to save to
 *
 *  @param {object} streamd
 *  As returned by
 *  {@link Phant#connect connect} or
 *  {@link Phant#create create}.
 */
Phant.prototype.save = function(filename, streamd) {
    fs.writeFileSync(filename, JSON.stringify(this.scrub(streamd), null, 2) + "\n")
}

/**
 *  Make sure a 'paramd' is properly set up. That is,
 *  that it's a dictionary and if any values in defaultd
 *  are undefined in paramd, they're copied over
 */
var defaults = function(paramd, defaultd) {
    if (paramd === undefined) {
        return defaultd
    }

    for (var key in defaultd) {
        var pvalue = paramd[key]
        if (pvalue === undefined) {
            paramd[key] = defaultd[key]
        }
    }

    return paramd;
}

var is_string = function(value) {
    return typeof value === "string"
}

var is_iri = function(value) {
    if (!is_string(value)) {
        return false
    }

    return value.match(/^https?:\/\//) ? true : false
}

/**
 *  Callback that returns a stream
 *
 *  @param {string} error
 *  If not null, the error.
 *
 *  @param {object|undefined}
 *  An open stream
 *  Ignore if error is present
 *
 *  @callback Phant~stream_callback
 */

/**
 *  Callback that returns a record from a stream
 *
 *  @param {string} error
 *  If not null, the error
 *
 *  @param {object|null|undefined}
 *  If null, this means EOF - no more
 *  records are available. Otherwise
 *  it's a record.
 *  Ignore if error is present
 *
 *  @callback Phant~record_callback
 */

/**
 *  Callback from a operations that have no result
 *
 *  @param {string} error
 *  If null, the operation succeeed. Otherwise
 *  it's the operation.
 *
 *  @callback Phant~op_callback
 */

exports.Phant = Phant
<commit_msg>slightly improved error handling<commit_after>/*
 *  phant.js
 *
 *  David Janes
 *  IOTDB.org
 *  2014-09-05
 *  "The Great Fire of London happened this day in 1666"
 *
 *  Connect to Phant/data.sparkfun.com-type servers
 *
 *  Copyright [2013-2014] [David P. Janes]
 *
 *  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.
 */

"use strict"

var unirest = require('unirest')
var fs = require('fs')

/**
 *  Connect to a Phant server
 *
 *  @param {object} paramd
 *  @param {String|undefined} paramd.iri
 *  The server to connect to, by default "http://data.sparkfun.com"
 *
 *  @constructor
 */
var Phant = function(paramd) {
    var self = this

    paramd = defaults(paramd, {
        iri: "http://data.sparkfun.com"
    })

    self.iri = paramd.iri
}

/**
 *  Create a new stream
 *
 *  @param {object} paramd
 *  @param {String} paramd.title
 *  @param {String} paramd.description
 *  @param {String} paramd.fields
 *
 *  @param {Phant~stream_callback} callback
 */
Phant.prototype.create = function(paramd, callback) {
    var self = this

    paramd = defaults(paramd, {
        title: "",
        description: "",
        fields: ""
    })

    unirest
        .post(self.iri + "/streams")
        .headers({ 'Accept': 'application/json' })
        .type('json')
        .send({
            title: paramd.title,
            description: paramd.description,
            fields: paramd.fields
        })
        .end(function(result) {
            if (result.error || !result.body.success) {
                if (result.body && result.body.message) {
                    callback(result.body.message, null)
                } else if (result.error.code) {
                    callback(result.error.code, null)
                } else {
                    console.error("# Phant.create", "unknown error text follows as-is", "\n" + result.body)
                    callback("error creating stream [unknown error]", null)
                }
            } else {
                callback(null, {
                    title: result.body.stream.title,
                    description: result.body.stream.description,
                    fields: result.body.stream.fields,
                    outputUrl: self.iri + '/output/' + result.body.publicKey,
                    inputUrl: self.iri + '/input/' + result.body.publicKey,
                    manageUrl: self.iri + '/streams/' + result.body.publicKey,
                    publicKey: result.body.publicKey,
                    privateKey: result.body.privateKey,
                    deleteKey: result.body.deleteKey
                })
            }

        })
    ;
}

/**
 *  Connect to a stream, via a stream
 *  record or a stream IRI
 *
 *  @param {object|iri} streamd
 *  @param {String|undefined} streamd.publicKey
 *  @param {String|undefined} streamd.privateKey
 *  @param {String|undefined} streamd.deleteKey
 *  If 'streamid' is an IRI string, it
 *  will be assumed to be an management-type URL
 *  e.g. 'https://data.sparkfun.com/streams/dZ4EVmE8yGCRGx5XRX1W'
 *
 *  @param {Phant~stream_callback} callback
 */
Phant.prototype.connect = function(streamd, callback) {
    var self = this

    if (is_iri(streamd)) {
        var parts = streamd.match(/^(.*)\/streams\/([^\/]*)/)
        if (!parts) {
            callback("unrecognized iri format: " + streamd)
            return
        }

        streamd = {
            publicKey: parts[2],
            outputUrl: parts[1] + "/output/" + parts[2],
            inputUrl: parts[1] + "/input/" + parts[2],
            manageUrl: parts[1] + "/streams/" + parts[2]
        }
    }

    unirest
        .get(streamd.manageUrl)
        .headers({
            'Accept': 'application/json',
        })
        .end(function(result) {
            if (result.error || !result.body.success) {
                if (result.body) {
                    callback(result.body.message, null)
                } else if (result.error.code) {
                    callback(result.error.code, null)
                } else {
                    console.error("# Phant.create", "unknown error text follows as-is", "\n" + result.body)
                    callback("error connecting to stream [unknown error]", null)
                }
            } else {
                callback(null, {
                    title: result.body.stream.title,
                    description: result.body.stream.description,
                    fields: result.body.stream.fields,
                    outputUrl: streamd.outputUrl,   // this can be done better
                    inputUrl: streamd.inputUrl,
                    manageUrl: streamd.manageUrl,
                    publicKey: streamd.publicKey,
                    privateKey: streamd.privateKey,
                    deleteKey: streamd.deleteKey
                })
            }
        })
    ;
}

/**
 *  Return the next record from the stream. If this
 *  stream has not been read from it will return the
 *  latest record first.
 *

 *  WARNING: If the stream is being written to while
 *  this is being used, there's likely some duplicate
 *  records that will be returned when a refetch
 *  happens
 *

 *  This is likely to cache multiple results
 *
 *  @param {object} streamd
 *  As returned by
 *  {@link Phant#connect connect} or
 *  {@link Phant#create create}.
 *
 *  @param {Phant~record_callback} callback
 */
Phant.prototype.next = function(streamd, callback) {
    var self = this

    var offset = 0

    if (streamd.__rds === undefined) {
        streamd.__rds = []
        streamd.__offset = 0
        streamd.__eof = false
    } else if (streamd.__eof) {
        callback(null, null)
        return
    } else if (streamd.__offset < streamd.__rds.length) {
        callback(null, streamd.__rds[streamd.__offset++])
        return
    } else {
        /* need to go deeper into the streamd */
    }

    /*
     *  If we get here we need to fetch a new record
     */
    var iri = streamd.outputUrl + '.json?offset=' + streamd.__offset
    unirest
        .get(iri)
        .headers({
            'Accept': 'application/json',
        })
        .end(function(result) {
            if (result.error) {
                if (result.error.code) {
                    callback(result.error.code, null)
                } else if (result.body) {
                    // deceptive: an error is returned for empty streams
                    streamd.__eof = true
                    callback(null, null)
                } else {
                    callback("error reading stream", null)
                }
            } else {
                streamd.__rds.push.apply(streamd.__rds, result.body)
                if (streamd.__offset < streamd.__rds.length) {
                    callback(null, streamd.__rds[streamd.__offset++])
                } else {
                    streamd.__eof = true
                    callback(null, null)
                }
            }
        })
    ;
}

/**
 *  Reset the stream so that "next" will return
 *  the latest record again.
 *

 *  This is immediate: there is no callback.
 *
 *  @param {object} streamd
 *  As returned by
 *  {@link Phant#connect connect} or
 *  {@link Phant#create create}.
 */
Phant.prototype.reset = function(streamd) {
    var self = this

    streamd.__rds = undefined
}

/**
 *  Return the latest record from the stream.
 *  This is entirely independent of next/reset.
 *  @param {object} streamd
 *  As returned by
 *  {@link Phant#connect connect} or
 *  {@link Phant#create create}.
 *
 *  @param {Phant~record_callback} callback
 */
Phant.prototype.latest = function(streamd, callback) {
    var self = this

    unirest
        .get(streamd.outputUrl + '/latest.json')
        .headers({
            'Accept': 'application/json',
        })
        .end(function(result) {
            if (result.error) {
                if (result.error.code) {
                    callback(result.error.code, null)
                } else if (result.body) {
                    // deceptive: an error is returned for empty streams
                    callback(null, null)
                } else {
                    callback("error reading stream", null)
                }
            } else if (result.body) {
                callback(null, result.body[0])
            } else {
                callback(null, null)
            }
        })
    ;
}

/**
 *  Add a record to the stream
 *
 *  @param {object} streamd
 *  As returned by
 *  {@link Phant#connect connect} or
 *  {@link Phant#create create}.
 *
 *  @param {Phant~op_callback|undefined} callback
 */
Phant.prototype.add = function(streamd, rd, callback) {
    var self = this

    for (var fi in streamd.fields) {
        var field = streamd.fields[fi]
        if (rd[field] === undefined) {
            rd[field] = ""
        }
    }
    rd = defaults(rd, {})

    unirest
        .post(streamd.inputUrl)
        .headers({
            'Accept': 'application/json',
            'Phant-Private-Key': streamd.privateKey
        })
        .type('json')
        .send(rd)
        .end(function(result) {
            if (!callback) {
            } else if (result.error || !result.body.success) {
                if (result.body) {
                    callback(result.body.message)
                } else if (result.error.code) {
                    callback(result.error.code)
                } else {
                    console.error("# Phant.create", "unknown error text follows as-is", "\n" + result.body)
                    callback("error adding to stream [unknown error]")
                }
            } else {
                callback(null)
            }
        })
    ;
}

/**
 *  Update the metadata of a stream.
 *

 *  WARNING: may wipe out data?
 *
 *  @param {object} streamd
 *  As returned by
 *  {@link Phant#connect connect} or
 *  {@link Phant#create create}.
 *
 *  @param {object} paramd
 *  @param {String} paramd.title
 *  @param {String} paramd.description
 *  @param {String} paramd.fields
 *  ... and more ...
 *
 *  @param {Phant~op_callback|undefined} callback
 */
Phant.prototype.update = function(streamd, paramd, callback) {
    var self = this

    paramd = defaults(paramd, {
        title: streamd.title,
        description: streamd.description
    })

    // API oddness
    if (paramd.fields)  {
        paramd.field_check = ''
    } else {
        paramd.fields = streamd.fields.join(",")
        paramd.field_check = streamd.fields.join(",")
    }

    unirest
        .post(streamd.manageUrl + '/update/' + streamd.privateKey + '.json')
        .headers({
            'Accept': 'application/json',
            'Phant-Private-Key': streamd.privateKey
        })
        .type('json')
        .send(paramd)
        .end(function(result) {
            if (!callback) {
            } else if (result.error || !result.body.success) {
                if (result.body) {
                    callback(result.body.message)
                } else if (result.error.code) {
                    callback(result.error.code)
                } else {
                    console.error("# Phant.create", "unknown error text follows as-is", "\n" + result.body)
                    callback("error updating metadata [unknown error]")
                }
            } else {
                callback(null)
            }
        })
    ;
}

/**
 *  Return a scrubbed version of the streamd.
 *  These can be used for serialization to disk
 *  or for starting an independent input stream.
 *

 *  A scrubbed streamd can be operated on
 *  if it's the result of a
 *  {@link Phant#connect connect} or
 *  {@link Phant#create create} callback.
 *

 *  This is immediate: there is no callback.
 *
 *  @param {object} streamd
 *  As returned by
 *  {@link Phant#connect connect} or
 *  {@link Phant#create create}.
 *
 *  @return {object}
 *  A new streamd
 */
Phant.prototype.scrub = function(streamd) {
    var nd = {}

    for (var key in streamd) {
        if (!key.match(/^__/)) {
            nd[key] = streamd[key]
        }
    }

    return nd
}

/**
 *  Helper function to load a stream record
 *  from a file.
 *
 *  @param {String} filename
 *  File to load from
 *
 *  @param {object} streamd
 *  Stream record. You must still should send this to
 *  {@link Phant#connect connect} to use.
 */
Phant.prototype.load = function(filename) {
    return JSON.parse(fs.readFileSync(filename, { encoding: 'utf8' }))
}

/**
 *  Helper function to save a stream record
 *  to a file. The stream record
 *
 *  @param {String} filename
 *  File to save to
 *
 *  @param {object} streamd
 *  As returned by
 *  {@link Phant#connect connect} or
 *  {@link Phant#create create}.
 */
Phant.prototype.save = function(filename, streamd) {
    fs.writeFileSync(filename, JSON.stringify(this.scrub(streamd), null, 2) + "\n")
}

/**
 *  Make sure a 'paramd' is properly set up. That is,
 *  that it's a dictionary and if any values in defaultd
 *  are undefined in paramd, they're copied over
 */
var defaults = function(paramd, defaultd) {
    if (paramd === undefined) {
        return defaultd
    }

    for (var key in defaultd) {
        var pvalue = paramd[key]
        if (pvalue === undefined) {
            paramd[key] = defaultd[key]
        }
    }

    return paramd;
}

var is_string = function(value) {
    return typeof value === "string"
}

var is_iri = function(value) {
    if (!is_string(value)) {
        return false
    }

    return value.match(/^https?:\/\//) ? true : false
}

/**
 *  Callback that returns a stream
 *
 *  @param {string} error
 *  If not null, the error.
 *
 *  @param {object|undefined}
 *  An open stream
 *  Ignore if error is present
 *
 *  @callback Phant~stream_callback
 */

/**
 *  Callback that returns a record from a stream
 *
 *  @param {string} error
 *  If not null, the error
 *
 *  @param {object|null|undefined}
 *  If null, this means EOF - no more
 *  records are available. Otherwise
 *  it's a record.
 *  Ignore if error is present
 *
 *  @callback Phant~record_callback
 */

/**
 *  Callback from a operations that have no result
 *
 *  @param {string} error
 *  If null, the operation succeeed. Otherwise
 *  it's the operation.
 *
 *  @callback Phant~op_callback
 */

exports.Phant = Phant
<|endoftext|>


<commit_before>const superagent = require("superagent");
const fs         = require("fs");

exports.run = function (client, msg, args) {
	if (msg.author.id !== "180093157554388993") return msg.channel.createMessage({ embed: {
		color: config.options.embedColour,
		title: ":warning: Restricted Command",
		description: "This command is locked to the developer only."
	}});

	try {
        let code = eval(args.join(" "));
        if (typeof code !== 'string')
        	code = require('util').inspect(code, {depth:0});
        code = code.replace(new RegExp(client.token.slice(4), "gi"), "*");
        msg.channel.createMessage(`\`\`\`js\n${code}\n\`\`\``);
    } catch(e) {
        msg.channel.createMessage(`\`\`\`js\n${e}\n\`\`\``);
    };
};

function setAvatar(client, url, local) {
	if (!local) {
		superagent.get(url).end((err, res) => {
			client.editSelf({ avatar: `data:${res.header["content-type"]};base64,${res.body.toString("base64")}` });
		});
	} else {
		try {
			let data = fs.readFileSync(url);

			client.editSelf({ avatar: `data:image/png;base64,${data.toString("base64")}` });
		} catch(err) {
			console.log(err.message);
		}
	}
};

exports.usage = {
	main: "{prefix}{command}",
	args: "",
	description: "Developer command"
};
<commit_msg>make eval great again<commit_after>const sf = require("snekfetch");
const fs = require("fs");

exports.run = function (client, msg, args) {
	if (msg.author.id !== "180093157554388993") return msg.channel.createMessage({ embed: {
		color: config.options.embedColour,
		title: ":warning: Restricted Command",
		description: "This command is locked to the developer only."
	}});

	try {
        let code = eval(args.join(" "));
        if (typeof code !== 'string')
        	code = require('util').inspect(code, {depth:0});
        code = code.replace(new RegExp(client.token.slice(4), "gi"), "*");
        msg.channel.createMessage(`\`\`\`js\n${code}\n\`\`\``);
    } catch(e) {
        msg.channel.createMessage(`\`\`\`js\n${e}\n\`\`\``);
    };
};

function setAvatar(client, url, local) {
	if (!local) {
		sf.get(url).end((err, res) => {
			client.editSelf({ avatar: `data:${res.header["content-type"]};base64,${res.body.toString("base64")}` });
		});
	} else {
		try {
			let data = fs.readFileSync(url);

			client.editSelf({ avatar: `data:image/png;base64,${data.toString("base64")}` });
		} catch(err) {
			console.log(err.message);
		}
	}
};

exports.usage = {
	main: "{prefix}{command}",
	args: "",
	description: "Developer command"
};
<|endoftext|>


<commit_before>export default class ConvertCodeCell {

  /*
    Collect  and turn it into cell elements in
    TextureJATS

    TODO:
      - Properly handle CDATA content
      - Implement export method
  */
  import(dom, converter) {
    let cells = dom.findAll('code[specific-use=cell]')

    cells.forEach((oldCell) => {
      let cell = dom.createElement('cell')
      let miniSource = oldCell.find('code[language=mini]')
      let nativeSource = oldCell.find('code:not([language=mini])')
      let output = oldCell.find('code[specific-use=output]')
      let miniSourceText

      if (miniSource) {
        miniSourceText = miniSource.textContent
      } else if (nativeSource) {
        // We make up mini source string if not present
        miniSourceText = nativeSource.attr('language')+'()'
      } else {
        converter.error({
          msg: 'Either code[lanuage=mini] or code:not([language=mini]) must be provided',
          el: oldCell
        })
      }
      cell.append(
        dom.createElement('source-code').attr('language', 'mini').append(
          miniSourceText
        )
      )

      if (nativeSource) {
        dom.createElement('source-code').attr('language', nativeSource.attr('language')).append(
          nativeSource.textContent
        )
      }
      cell.append(
        dom.createElement('output').append(output.textContent)
      )
      oldCell.getParent().replaceChild(oldCell, cell)
    })
  }

  export(dom) {
    console.error('TODO: implement ConvertCodeCell.export')
  }
}
<commit_msg>Fix linter.<commit_after>export default class ConvertCodeCell {

  /*
    Collect  and turn it into cell elements in
    TextureJATS

    TODO:
      - Properly handle CDATA content
      - Implement export method
  */
  import(dom, converter) {
    let cells = dom.findAll('code[specific-use=cell]')

    cells.forEach((oldCell) => {
      let cell = dom.createElement('cell')
      let miniSource = oldCell.find('code[language=mini]')
      let nativeSource = oldCell.find('code:not([language=mini])')
      let output = oldCell.find('code[specific-use=output]')
      let miniSourceText

      if (miniSource) {
        miniSourceText = miniSource.textContent
      } else if (nativeSource) {
        // We make up mini source string if not present
        miniSourceText = nativeSource.attr('language')+'()'
      } else {
        converter.error({
          msg: 'Either code[lanuage=mini] or code:not([language=mini]) must be provided',
          el: oldCell
        })
      }
      cell.append(
        dom.createElement('source-code').attr('language', 'mini').append(
          miniSourceText
        )
      )

      if (nativeSource) {
        dom.createElement('source-code').attr('language', nativeSource.attr('language')).append(
          nativeSource.textContent
        )
      }
      cell.append(
        dom.createElement('output').append(output.textContent)
      )
      oldCell.getParent().replaceChild(oldCell, cell)
    })
  }

  export(/*dom*/) {
    console.error('TODO: implement ConvertCodeCell.export')
  }
}
<|endoftext|>


<commit_before>// Load in child_process and chai
var cp = require('child_process'),
    exec = cp.exec,
    chai = require('chai'),
    expect = chai.expect;

// Outline
var outline = {
  'doubleshot': {
    'reads the `test` directory implicitly': true,
    'allows for usage of `mocha` options': true,
    'allows for explicit directory specification': true,
    'allows for explicity file (and pattern) matching': true
  }
};

// Content
var doubleshot = __dirname + '/../bin/doubleshot';
describe('doubleshot', function () {
  it('reads the `test` directory implicitly', function (done) {
    // Run doubleshot implicitly
    exec(doubleshot, function handleDblImplicit (err, stdout, stderr) {
      // If there is an error or stderr, callback with it
      err = err || stderr;
      if (err) {
        done(err);
      } else {
      // Otherwise, assert the test suite ran successfully
        expect(stdout).to.contain('complete');
        expect(stdout).to.not.contain('pending');
        expect(stdout).to.not.contain('failed');

        // and callback
        done();
      }
    });
  });

  it('allows for usage of `mocha` options', function (done) {
    '( ^ .^)'; // Success face
    // '( - .-)'; // Pending face
    // '( o .o)'; // Fail face
    done();
  });

  it('b', '');
  // it('c', function () { throw new Error('=('); });
});

// ./bin/doubleshot --reporter nyan
// ./bin/doubleshot test
// ./bin/doubleshot --outline test/doubleshot_outline.json --content test/doubleshot_content.js --reporter nyan<commit_msg>Finished up assert for mocha options<commit_after>// Load in child_process and chai
var cp = require('child_process'),
    exec = cp.exec,
    chai = require('chai'),
    expect = chai.expect;

// Outline
var outline = {
  'doubleshot': {
    'reads the `test` directory implicitly': true,
    'allows for usage of `mocha` options': true,
    'allows for explicit directory specification': true,
    'allows for explicity file (and pattern) matching': true
  }
};

// Content
var doubleshot = __dirname + '/../bin/doubleshot';
describe('doubleshot', function () {
  it('reads the `test` directory implicitly', function (done) {
    // Run doubleshot implicitly
    exec(doubleshot, function handleDblImplicit (err, stdout, stderr) {
      // If there is an error or stderr, callback with it
      err = err || stderr;
      if (err) {
        done(err);
      } else {
      // Otherwise, assert the test suite ran successfully
        expect(stdout).to.contain('complete');
        expect(stdout).to.not.contain('pending');
        expect(stdout).to.not.contain('failed');

        // and callback
        done();
      }
    });
  });

  it('allows for usage of `mocha` options', function (done) {
    // Run doubleshot
    var cmd = doubleshot + ' --reporter nyan';
    exec(cmd, function handleDblImplicit (err, stdout, stderr) {
      // If there is an error or stderr, callback with it
      err = err || stderr;
      if (err) {
        done(err);
      } else {
      // Otherwise, assert the test suite ran successfully
        expect(stdout).to.contain('( ^ .^)');
        // '( ^ .^)'; // Success face
        // '( - .-)'; // Pending face
        // '( o .o)'; // Fail face
        done();
      }
    });
    done();
  });

  // it('b', '');
  // it('c', function () { throw new Error('=('); });
});

// ./bin/doubleshot --reporter nyan
// ./bin/doubleshot test
// ./bin/doubleshot --outline test/doubleshot_outline.json --content test/doubleshot_content.js --reporter nyan<|endoftext|>


<commit_before>/*##################################################|*/
/* #CMS# */
(function($) {
// CMS.$ will be passed for $
$(document).ready(function () {
	/*!
	 * Clipboard
	 * Handles copy & paste
	 */
	CMS.Clipboard = new CMS.Class({

		implement: [CMS.API.Helpers],

		options: {
			'position': 220, // offset to top
			'speed': 100,
			'id': null,
			'url': ''
		},

		initialize: function (options) {
			this.clipboard = $('.cms_clipboard');
			this.options = $.extend(true, {}, this.options, options);
			this.config = CMS.config;
			this.settings = this.getSettings();

			// elements
			this.containers = this.clipboard.find('.cms_clipboard-containers > .cms_draggable');
			this.triggers = this.clipboard.find('.cms_clipboard-triggers a');
			this.triggerRemove = this.clipboard.find('.cms_clipboard-empty a');

			// states
			this.click = (document.ontouchstart !== null) ? 'click.cms' : 'touchend.cms';
			this.timer = function () {};

			// setup initial stuff
			this._setup();

			// setup events
			this._events();
		},

		// initial methods
		_setup: function () {
			var that = this;

			// attach visual events
			this.triggers.bind('mouseenter mouseleave', function (e) {
				e.preventDefault();
				// clear timeout
				clearTimeout(that.timer);

				if(e.type === 'mouseleave') hide();

				var index = that.clipboard.find('.cms_clipboard-triggers a').index(this);
				var el = that.containers.eq(index);
				// cancel if element is already open
				if(el.data('open') === true) return false;

				// show element
				that.containers.stop().css({ 'margin-left': -that.options.position }).data('open', false);
				el.stop().animate({ 'margin-left': 0 }, that.options.speed);
				el.data('open', true);
			});
			that.containers.bind('mouseover mouseleave', function (e) {
				// clear timeout
				clearTimeout(that.timer);

				// cancel if we trigger mouseover
				if(e.type === 'mouseover') return false;

				// we need a little timer to detect if we should hide the menu
				hide();
			});

			function hide() {
				that.timer = setTimeout(function () {
					that.containers.stop().css({ 'margin-left': -that.options.position }).data('open', false);
				}, that.options.speed);
			}
		},

		_events: function () {
			var that = this;

			// add remove event
			this.triggerRemove.bind(this.click, function (e) {
				e.preventDefault();
				that.clear();
			});
		},

		// public methods
		clear: function () {
			// post needs to be a string, it will be converted using JSON.parse
			var post = '{ "csrfmiddlewaretoken": "' + this.config.csrf + '" }';
			// redirect to ajax
			CMS.API.Toolbar.openAjax(this.config.clipboard.url, post);
		}

	});

});
})(CMS.$);<commit_msg>add callback to ajax request<commit_after>/*##################################################|*/
/* #CMS# */
(function($) {
// CMS.$ will be passed for $
$(document).ready(function () {
	/*!
	 * Clipboard
	 * Handles copy & paste
	 */
	CMS.Clipboard = new CMS.Class({

		implement: [CMS.API.Helpers],

		options: {
			'position': 220, // offset to top
			'speed': 100,
			'id': null,
			'url': ''
		},

		initialize: function (options) {
			this.clipboard = $('.cms_clipboard');
			this.options = $.extend(true, {}, this.options, options);
			this.config = CMS.config;
			this.settings = this.getSettings();

			// elements
			this.containers = this.clipboard.find('.cms_clipboard-containers > .cms_draggable');
			this.triggers = this.clipboard.find('.cms_clipboard-triggers a');
			this.triggerRemove = this.clipboard.find('.cms_clipboard-empty a');

			// states
			this.click = (document.ontouchstart !== null) ? 'click.cms' : 'touchend.cms';
			this.timer = function () {};

			// setup initial stuff
			this._setup();

			// setup events
			this._events();
		},

		// initial methods
		_setup: function () {
			var that = this;

			// attach visual events
			this.triggers.bind('mouseenter mouseleave', function (e) {
				e.preventDefault();
				// clear timeout
				clearTimeout(that.timer);

				if(e.type === 'mouseleave') hide();

				var index = that.clipboard.find('.cms_clipboard-triggers a').index(this);
				var el = that.containers.eq(index);
				// cancel if element is already open
				if(el.data('open') === true) return false;

				// show element
				that.containers.stop().css({ 'margin-left': -that.options.position }).data('open', false);
				el.stop().animate({ 'margin-left': 0 }, that.options.speed);
				el.data('open', true);
			});
			that.containers.bind('mouseover mouseleave', function (e) {
				// clear timeout
				clearTimeout(that.timer);

				// cancel if we trigger mouseover
				if(e.type === 'mouseover') return false;

				// we need a little timer to detect if we should hide the menu
				hide();
			});

			function hide() {
				that.timer = setTimeout(function () {
					that.containers.stop().css({ 'margin-left': -that.options.position }).data('open', false);
				}, that.options.speed);
			}
		},

		_events: function () {
			var that = this;

			// add remove event
			this.triggerRemove.bind(this.click, function (e) {
				e.preventDefault();
				that.clear();
			});
		},

		// public methods
		clear: function (callback) {
			// post needs to be a string, it will be converted using JSON.parse
			var post = '{ "csrfmiddlewaretoken": "' + this.config.csrf + '" }';
			// redirect to ajax
			CMS.API.Toolbar.openAjax(this.config.clipboard.url, post, '', callback);
		}

	});

});
})(CMS.$);<|endoftext|>


<commit_before>/**
 * @author: Dennis Hernández
 * @webSite: http://djhvscf.github.io/Blog
 * @version: v1.0.0
 */

!function ($) {

    'use strict';

    var sprintf = function (str) {
        var args = arguments,
            flag = true,
            i = 1;

        str = str.replace(/%s/g, function () {
            var arg = args[i++];

            if (typeof arg === 'undefined') {
                flag = false;
                return '';
            }
            return arg;
        });
        return flag ? str : '';
    };

    var getFieldIndex = function (columns, field) {
        var index = -1;

        $.each(columns, function (i, column) {
            if (column.field === field) {
                index = i;
                return false;
            }
            return true;
        });
        return index;
    };

    var calculateObjectValue = function (self, name, args, defaultValue) {
        if (typeof name === 'string') {
            // support obj.func1.func2
            var names = name.split('.');

            if (names.length > 1) {
                name = window;
                $.each(names, function (i, f) {
                    name = name[f];
                });
            } else {
                name = window[name];
            }
        }
        if (typeof name === 'object') {
            return name;
        }
        if (typeof name === 'function') {
            return name.apply(self, args);
        }
        return defaultValue;
    };

    var addValueToSelectControl = function (selectControl, value, text) {
        if (existsValueInSelectControl(selectControl, value)) {
            selectControl.append($("<option></option>")
                .attr("value", value)
                .text(text));
        }
    };

    var existsValueInSelectControl = function (selectControl, value) {
        var options = selectControl.get(0).options,
            iOpt = 0;

        for (; iOpt < options.length; iOpt++) {
            if (options[iOpt].value === value) {
                //The value is nor valid to add
                return false;
            }
        }

        //If we get here, the value is valid to add
        return true;
    };

    var fixHeaderCSS = function (that) {
        that.$tableHeader.css('height', '77px');
    };

    var copyValues = function (that) {
        that.options.values = [];
        that.$tableHeader.find('table select, table input').each(function () {
            that.options.values.push($(this).val());
        });
    };

    var setValues = function(that) {
        that.$tableHeader.find('table select, table input').each(function (index, ele) {
            $(this).val(that.options.values[index]);
        });
    };

    var createControls = function (that, header) {
        var addedFilterControl = false,
            isVisible,
            html,
            timeoutId = 0;

        $.each(that.options.columns, function (i, column) {
            isVisible = 'hidden';
            html = [];

            if (!column.visible) {
                return;
            }

            if (!column.filterControl) {
                html.push('

');
            } else {
                html.push('
');

                if (column.filterControl && column.searchable) {
                    addedFilterControl = true;
                    isVisible = 'visible'
                }
                switch (column.filterControl.toLowerCase()) {
                    case 'input' :
                        html.push(sprintf('<input type="text" class="form-control" style="width: 100%; visibility: %s">', isVisible));
                        break;
                    case 'select':
                        html.push(sprintf('<select class="%s form-control" style="width: 100%; visibility: %s"></select>',
                            column.field, isVisible))
                        break;
                    case 'datepicker':
                        html.push(sprintf('<input type="text" class="date-filter-control %s form-control" style="width: 100%; visibility: %s">',
                            column.field, isVisible));
                        break;
                }
            }

            $.each(header.children().children(), function (i, tr) {
                tr = $(tr);
                if (tr.data('field') === column.field) {
                    tr.find('.fht-cell').append(html.join(''));
                    return false;
                }
            });
            if (column.filterData !== undefined && column.filterData.toLowerCase() !== 'column') {
                var filterDataType = column.filterData.substring(0, 3);
                var filterDataSource = column.filterData.substring(4, column.filterData.length);
                var selectControl = $('.' + column.field);
                addValueToSelectControl(selectControl, '', '');

                switch (filterDataType) {
                    case 'url':
                        $.ajax({
                            url: filterDataSource,
                            dataType: 'json',
                            success: function (data) {
                                $.each(data, function (key, value) {
                                    addValueToSelectControl(selectControl, key, value);
                                });
                            }
                        });
                        break;
                    case 'var':
                        var variableValues = window[filterDataSource];
                        for (var key in variableValues) {
                            addValueToSelectControl(selectControl, key, variableValues[key]);
                        }
                        break;
                }
            }
        });

        if (addedFilterControl) {
            header.off('keyup', 'input').on('keyup', 'input', function (event) {
                clearTimeout(timeoutId);
                timeoutId = setTimeout(function () {
                    that.onColumnSearch(event);
                }, that.options.searchTimeOut);
            });

            header.off('change', 'select').on('change', 'select', function (event) {
                clearTimeout(timeoutId);
                timeoutId = setTimeout(function () {
                    that.onColumnSearch(event);
                }, that.options.searchTimeOut);
            });

            var datepickers = that.$header.find('.date-filter-control');
            if (datepickers.length > 0) {
                $.each(that.options.columns, function (i, column) {
                    if (column.filterControl !== undefined && column.filterControl.toLowerCase() === 'datepicker') {
                        column.filterDatepickerOptions = $.extend(column.filterDatepickerOptions, {
                            calendarWeeks: true
                        });

                        header.find('.date-filter-control.' + column.field).datepicker(column.filterDatepickerOptions)
                            .on('changeDate', function (e) {
                                //Fired the keyup event
                                $(e.currentTarget).keyup();
                            });
                    }
                });
            }
        } else {
            header.find('.filterControl').hide();
        }
    };

    $.extend($.fn.bootstrapTable.defaults, {
        filterControl: false,
        onColumnSearch: function (field, text) {
            return false;
        },
        values: [] //internal variables
    });

    $.extend($.fn.bootstrapTable.COLUMN_DEFAULTS, {
        filterControl: undefined,
        filterData: undefined,
        filterDatepickerOptions: undefined
    });

    $.extend($.fn.bootstrapTable.Constructor.EVENTS, {
        'column-search.bs.table': 'onColumnSearch'
    });

    var BootstrapTable = $.fn.bootstrapTable.Constructor,
        _init = BootstrapTable.prototype.init,
        _initHeader = BootstrapTable.prototype.initHeader,
        _initBody = BootstrapTable.prototype.initBody,
        _initSearch = BootstrapTable.prototype.initSearch;

    BootstrapTable.prototype.init = function () {
        //Make sure that the filtercontrol option is set
        if (this.options.filterControl) {
            var that = this;
            this.$el.on('reset-view.bs.table', function () {

                //Create controls on $tableHeader if the height is set
                if (!that.options.height) {
                    return;
                }

                //Avoid recreate the controls
                if (that.$tableHeader.find('select').length > 0 || that.$tableHeader.find('input').length > 0) {
                    return;
                }

                createControls(that, that.$tableHeader);
            }).on('post-header.bs.table', function () {
                setValues(that);
            });

            this.$el.on('post-body.bs.table', function () {
                if (that.options.height) {
                    fixHeaderCSS(that);
                }
            });
        }
        _init.apply(this, Array.prototype.slice.apply(arguments));
    };

    BootstrapTable.prototype.initHeader = function () {
        _initHeader.apply(this, Array.prototype.slice.apply(arguments));

        if (!this.options.filterControl) {
            return;
        }
        createControls(this, this.$header);
    };

    BootstrapTable.prototype.initBody = function () {
        _initBody.apply(this, Array.prototype.slice.apply(arguments));

        var that = this,
            data = this.getData();

        for (var i = this.pageFrom - 1; i < this.pageTo; i++) {
            var key,
                item = data[i];

            $.each(this.header.fields, function (j, field) {
                var value = item[field],
                    column = that.options.columns[getFieldIndex(that.options.columns, field)];

                value = calculateObjectValue(that.header,
                    that.header.formatters[j], [value, item, i], value);

                if ((!column.checkbox) || (!column.radio)) {
                    if (column.filterControl !== undefined && column.filterControl.toLowerCase() === 'select'
                            && column.searchable) {

                        if (column.filterData === undefined || column.filterData.toLowerCase() === 'column') {
                            var selectControl = $('.' + column.field);
                            if (selectControl !== undefined && selectControl.length > 0) {
                                if (selectControl.get(0).options.length === 0) {
                                    //Added the default option
                                    addValueToSelectControl(selectControl, '', '');
                                }

                                //Added a new value
                                addValueToSelectControl(selectControl, value, value);
                            }
                        }
                    }
                }
            });
        }
    };

    BootstrapTable.prototype.initSearch = function () {
        _initSearch.apply(this, Array.prototype.slice.apply(arguments));

        var that = this;
        var fp = $.isEmptyObject(this.filterColumnsPartial) ? null : this.filterColumnsPartial;

        //Check partial column filter
        this.data = fp ? $.grep(this.data, function (item, i) {
            for (var key in fp) {
                var fval = fp[key].toLowerCase();
                var value = item[key];
                value = calculateObjectValue(that.header,
                    that.header.formatters[$.inArray(key, that.header.fields)],
                    [value, item, i], value);

                if (!($.inArray(key, that.header.fields) !== -1 &&
                    (typeof value === 'string' || typeof value === 'number') &&
                    (value + '').toLowerCase().indexOf(fval) !== -1)) {
                    return false;
                }
            }
            return true;
        }) : this.data;
    };

    BootstrapTable.prototype.onColumnSearch = function (event) {
        copyValues(this);
        var text = $.trim($(event.currentTarget).val());
        var $field = $(event.currentTarget).parent().parent().parent().data('field')

        if ($.isEmptyObject(this.filterColumnsPartial)) {
            this.filterColumnsPartial = {};
        }
        if (text) {
            this.filterColumnsPartial[$field] = text;
        } else {
            delete this.filterColumnsPartial[$field];
        }

        this.options.pageNumber = 1;
        this.onSearch(event);
        this.updatePagination();
        this.trigger('column-search', $field, text);
    };
}(jQuery);
<commit_msg>Improve the filter control extension<commit_after>/**
 * @author: Dennis Hernández
 * @webSite: http://djhvscf.github.io/Blog
 * @version: v1.0.0
 */

!function ($) {

    'use strict';

    var sprintf = function (str) {
        var args = arguments,
            flag = true,
            i = 1;

        str = str.replace(/%s/g, function () {
            var arg = args[i++];

            if (typeof arg === 'undefined') {
                flag = false;
                return '';
            }
            return arg;
        });
        return flag ? str : '';
    };

    var getFieldIndex = function (columns, field) {
        var index = -1;

        $.each(columns, function (i, column) {
            if (column.field === field) {
                index = i;
                return false;
            }
            return true;
        });
        return index;
    };

    var calculateObjectValue = function (self, name, args, defaultValue) {
        if (typeof name === 'string') {
            // support obj.func1.func2
            var names = name.split('.');

            if (names.length > 1) {
                name = window;
                $.each(names, function (i, f) {
                    name = name[f];
                });
            } else {
                name = window[name];
            }
        }
        if (typeof name === 'object') {
            return name;
        }
        if (typeof name === 'function') {
            return name.apply(self, args);
        }
        return defaultValue;
    };

    var addOptionToSelectControl = function (selectControl, value, text) {
        //selectControl = $(selectControl.get(0));
        if (existsOptionInSelectControl(selectControl, value)) {
            selectControl.append($("<option></option>")
                .attr("value", value)
                .text(text));
        }
    };

    var existsOptionInSelectControl = function (selectControl, value) {
        var options = selectControl.get(0).options,
            iOpt = 0;

        for (; iOpt < options.length; iOpt++) {
            if (options[iOpt].value === value) {
                //The value is nor valid to add
                return false;
            }
        }

        //If we get here, the value is valid to add
        return true;
    };

    var fixHeaderCSS = function (that) {
        that.$tableHeader.css('height', '77px');
    };

    var copyValues = function (that) {
        that.options.values = [];
        that.$tableHeader.find('table select, table input').each(function () {
            that.options.values.push(
                {
                    field: $(this).parent().parent().parent().data('field'),
                    value: $(this).val()
                });
        });
    };

    var setValues = function(that) {
        var field = null;
        that.$tableHeader.find('table select, table input').each(function (index, ele) {
            if (that.options.values.length > 0) {
                field = $(this).parent().parent().parent().data('field');
                for (var i = 0; i < that.options.values.length; i++) {
                    if (field === that.options.values[i].field) {
                        $(this).val(that.options.values[i].value);
                        break;
                    }
                }
            }
        });
    };

    var createControls = function (that, header) {
        var addedFilterControl = false,
            isVisible,
            html,
            timeoutId = 0;

        $.each(that.options.columns, function (i, column) {
            isVisible = 'hidden';
            html = [];

            if (!column.visible) {
                return;
            }

            if (!column.filterControl) {
                html.push('

');
            } else {
                html.push('
');

                if (column.filterControl && column.searchable) {
                    addedFilterControl = true;
                    isVisible = 'visible'
                }
                switch (column.filterControl.toLowerCase()) {
                    case 'input' :
                        html.push(sprintf('<input type="text" class="form-control" style="width: 100%; visibility: %s">', isVisible));
                        break;
                    case 'select':
                        html.push(sprintf('<select class="%s form-control" style="width: 100%; visibility: %s"></select>',
                            column.field, isVisible))
                        break;
                    case 'datepicker':
                        html.push(sprintf('<input type="text" class="date-filter-control %s form-control" style="width: 100%; visibility: %s">',
                            column.field, isVisible));
                        break;
                }
            }

            $.each(header.children().children(), function (i, tr) {
                tr = $(tr);
                if (tr.data('field') === column.field) {
                    tr.find('.fht-cell').append(html.join(''));
                    return false;
                }
            });
            if (column.filterData !== undefined && column.filterData.toLowerCase() !== 'column') {
                var filterDataType = column.filterData.substring(0, 3);
                var filterDataSource = column.filterData.substring(4, column.filterData.length);
                var selectControl = $('.' + column.field);
                addOptionToSelectControl(selectControl, '', '');

                switch (filterDataType) {
                    case 'url':
                        $.ajax({
                            url: filterDataSource,
                            dataType: 'json',
                            success: function (data) {
                                $.each(data, function (key, value) {
                                    addOptionToSelectControl(selectControl, key, value);
                                });
                            }
                        });
                        break;
                    case 'var':
                        var variableValues = window[filterDataSource];
                        for (var key in variableValues) {
                            addOptionToSelectControl(selectControl, key, variableValues[key]);
                        }
                        break;
                }
            }
        });

        if (addedFilterControl) {
            header.off('keyup', 'input').on('keyup', 'input', function (event) {
                clearTimeout(timeoutId);
                timeoutId = setTimeout(function () {
                    that.onColumnSearch(event);
                }, that.options.searchTimeOut);
            });

            header.off('change', 'select').on('change', 'select', function (event) {
                clearTimeout(timeoutId);
                timeoutId = setTimeout(function () {
                    that.onColumnSearch(event);
                }, that.options.searchTimeOut);
            });

            var datepickers = header.find('.date-filter-control');
            if (datepickers.length > 0) {
                $.each(that.options.columns, function (i, column) {
                    if (column.filterControl !== undefined && column.filterControl.toLowerCase() === 'datepicker') {
                        header.find('.date-filter-control.' + column.field).datepicker(column.filterDatepickerOptions)
                            .on('changeDate', function (e) {
                                //Fired the keyup event
                                $(e.currentTarget).keyup();
                            });
                    }
                });
            }
        } else {
            header.find('.filterControl').hide();
        }
    };

    $.extend($.fn.bootstrapTable.defaults, {
        filterControl: false,
        onColumnSearch: function (field, text) {
            return false;
        },
        //internal variables
        values: []
    });

    $.extend($.fn.bootstrapTable.COLUMN_DEFAULTS, {
        filterControl: undefined,
        filterData: undefined,
        filterDatepickerOptions: undefined
    });

    $.extend($.fn.bootstrapTable.Constructor.EVENTS, {
        'column-search.bs.table': 'onColumnSearch'
    });

    var BootstrapTable = $.fn.bootstrapTable.Constructor,
        _init = BootstrapTable.prototype.init,
        _initHeader = BootstrapTable.prototype.initHeader,
        _initBody = BootstrapTable.prototype.initBody,
        _initSearch = BootstrapTable.prototype.initSearch;

    BootstrapTable.prototype.init = function () {
        //Make sure that the filtercontrol option is set
        if (this.options.filterControl) {
            var that = this;
            //Make sure that the internal variables are set correctly
            this.options.values = [];

            this.$el.on('reset-view.bs.table', function () {
                //Create controls on $tableHeader if the height is set
                if (!that.options.height) {
                    return;
                }

                //Avoid recreate the controls
                if (that.$tableHeader.find('select').length > 0 || that.$tableHeader.find('input').length > 0) {
                    return;
                }

                createControls(that, that.$tableHeader);
            }).on('post-header.bs.table', function () {
                setValues(that);
            }).on('post-body.bs.table', function () {
                if (that.options.height) {
                    fixHeaderCSS(that);
                }
            });
        }
        _init.apply(this, Array.prototype.slice.apply(arguments));
    };

    BootstrapTable.prototype.initHeader = function () {
        _initHeader.apply(this, Array.prototype.slice.apply(arguments));

        if (!this.options.filterControl) {
            return;
        }
        createControls(this, this.$header);
    };

    BootstrapTable.prototype.initBody = function () {
        _initBody.apply(this, Array.prototype.slice.apply(arguments));

        var that = this,
            data = this.options.data,
            pageTo = this.pageTo < this.options.data.length ? this.options.data.length : this.pageTo;

        for (var i = this.pageFrom - 1; i < pageTo; i++) {
            var key,
                item = data[i];

            $.each(this.header.fields, function (j, field) {
                var value = item[field],
                    column = that.options.columns[getFieldIndex(that.options.columns, field)];

                value = calculateObjectValue(that.header,
                    that.header.formatters[j], [value, item, i], value);

                if ((!column.checkbox) || (!column.radio)) {
                    if (column.filterControl !== undefined && column.filterControl.toLowerCase() === 'select'
                            && column.searchable) {

                        if (column.filterData === undefined || column.filterData.toLowerCase() === 'column') {
                            var selectControl = $('.' + column.field);
                            if (selectControl !== undefined && selectControl.length > 0) {
                                if (selectControl.get(0).options.length === 0) {
                                    //Added the default option
                                    addOptionToSelectControl(selectControl, '', '');
                                }

                                //Added a new value
                                addOptionToSelectControl(selectControl, value, value);
                            }
                        }
                    }
                }
            });
        }
    };

    BootstrapTable.prototype.initSearch = function () {
        _initSearch.apply(this, Array.prototype.slice.apply(arguments));

        var that = this;
        var fp = $.isEmptyObject(this.filterColumnsPartial) ? null : this.filterColumnsPartial;

        //Check partial column filter
        this.data = fp ? $.grep(this.data, function (item, i) {
            for (var key in fp) {
                var fval = fp[key].toLowerCase();
                var value = item[key];
                value = calculateObjectValue(that.header,
                    that.header.formatters[$.inArray(key, that.header.fields)],
                    [value, item, i], value);

                if (!($.inArray(key, that.header.fields) !== -1 &&
                    (typeof value === 'string' || typeof value === 'number') &&
                    (value + '').toLowerCase().indexOf(fval) !== -1)) {
                    return false;
                }
            }
            return true;
        }) : this.data;
    };

    BootstrapTable.prototype.onColumnSearch = function (event) {
        copyValues(this);
        var text = $.trim($(event.currentTarget).val());
        var $field = $(event.currentTarget).parent().parent().parent().data('field')

        if ($.isEmptyObject(this.filterColumnsPartial)) {
            this.filterColumnsPartial = {};
        }
        if (text) {
            this.filterColumnsPartial[$field] = text;
        } else {
            delete this.filterColumnsPartial[$field];
        }

        this.options.pageNumber = 1;
        this.onSearch(event);
        this.updatePagination();
        this.trigger('column-search', $field, text);
    };
}(jQuery);
<|endoftext|>


<commit_before>'use strict';

const EventEmitter = require('events');
const fetch = require('node-fetch');
const crypto = require('crypto');
const http = require('http');
const bodyParser = require('body-parser');

class LineBot extends EventEmitter {

	constructor(options) {
		super();
		this.options = options || {};
		this.options.channelId = options.channelId || '1541948237';
		this.options.ChannelSecret = options.ChannelSecret || '9a8c7887c8ed7ceb3f963ce8f73d64a9';
		this.options.ChannelAccessToken = options.ChannelAccessToken || '3cL4zroJybMhWDUTZPUnLbxzPjfsoNu5FO0EqmZ/fhOka8bgzPZBCCp2EkmVw4l5lRUhDRML3vSuT9wV+VUd/xzw02X6FL6nFi8LaaT8NDuJcNJaaIIyNDzB/C5dfeU21BS73yzS3jQ88fmlwQS1VAdB04t89/1O/w1cDnyilFU=';
		if (this.options.verify === undefined) {
			this.options.verify = true;
		}
		this.headers = {
			Accept: 'application/json',
			'Content-Type': 'application/json',
			Authorization: 'Bearer ' + this.options.ChannelAccessToken
		};
		this.endpoint = 'https://api.line.me/v2/bot';
	}

	verify(rawBody, signature) {
		const hash = crypto.createHmac('sha256', this.options.ChannelSecret)
			.update(rawBody, 'utf8')
			.digest('base64');
		return hash === signature;
	}

	parse(body) {
		const that = this;
		if (!body || !body.events) {
			return;
		}
		body.events.forEach(function (event) {
			event.reply = function (message) {
				return that.reply(event.replyToken, message);
			};
			if (event.source) {
				event.source.profile = function () {
					return that.getUserProfile(event.source.userId);
				};
			}
			if (event.message) {
				event.message.content = function () {
					return that.getMessageContent(event.message.id);
				};
			}
			process.nextTick(function () {
				that.emit(event.type, event);
			});
		});
	}

	static createMessages(message) {
		if (typeof message === 'string') {
			return [{ type: 'text', text: message }];
		}
		if (Array.isArray(message)) {
			return message.map(function (m) {
				if (typeof m === 'string') {
					return { type: 'text', text: m };
				}
				return m;
			});
		}
		return [message];
	}

	reply(replyToken, message) {
		const body = {
			replyToken: replyToken,
			messages: LineBot.createMessages(message)
		};
		return this.post('/message/reply', body).then(function (res) {
			return res.json();
		});
	}

	push(to, message) {
		if (Array.isArray(to)) {
			return Promise.all(to.map(recipient => this.push(recipient, message)));
		}
		const body = {
			to: to,
			messages: LineBot.createMessages(message)
		};
		return this.post('/message/push', body).then(function (res) {
			return res.json();
		});
	}

	multicast(to, message) {
		const body = {
			to: to,
			messages: LineBot.createMessages(message)
		};
		return this.post('/message/multicast', body).then(function (res) {
			return res.json();
		});
	}

	getUserProfile(userId) {
		return this.get('/profile/' + userId).then(function (res) {
			return res.json();
		});
	}

	getMessageContent(messageId) {
		return this.get('/message/' + messageId + '/content/').then(function (res) {
			return res.buffer();
		});
	}

	leaveGroup(groupId) {
		return this.post('/group/' + groupId + '/leave/').then(function (res) {
			return res.json();
		});
	}

	leaveRoom(roomId) {
		return this.post('/room/' + roomId + '/leave/').then(function (res) {
			return res.json();
		});
	}

	get(path) {
		return fetch(this.endpoint + path, { method: 'GET', headers: this.headers });
	}

	post(path, body) {
		return fetch(this.endpoint + path, { method: 'POST', headers: this.headers, body: JSON.stringify(body) });
	}

	// Optional Express.js middleware
	parser() {
		const parser = bodyParser.json({
			verify: function (req, res, buf, encoding) {
				req.rawBody = buf.toString(encoding);
			}
		});
		return (req, res) => {
			parser(req, res, () => {
				if (this.options.verify && !this.verify(req.rawBody, req.get('X-Line-Signature'))) {
					return res.sendStatus(400);
				}
				this.parse(req.body);
				return res.json({});
			});
		};
	}

	// Optional built-in http server
	listen(path, port, callback) {
		const parser = bodyParser.json({
			verify: function (req, res, buf, encoding) {
				req.rawBody = buf.toString(encoding);
			}
		});
		const server = http.createServer((req, res) => {
			const signature = req.headers['x-line-signature']; // Must be lowercase
			res.setHeader('X-Powered-By', 'LineBot');
			if (req.method === 'POST' && req.url === path) {
				parser(req, res, () => {
					if (this.options.verify && !this.verify(req.rawBody, signature)) {
						res.statusCode = 400;
						res.setHeader('Content-Type', 'text/html; charset=utf-8');
						return res.end('Bad request');
					}
					this.parse(req.body);
					res.statusCode = 200;
					res.setHeader('Content-Type', 'application/json');
					return res.end('{}');
				});
			} else {
				res.statusCode = 404;
				res.setHeader('Content-Type', 'text/html; charset=utf-8');
				return res.end('Not found');
			}
		});
		return server.listen(port, callback);
	}

} // class LineBot

function createBot(options) {
	return new LineBot(options);
}

module.exports = createBot;
module.exports.LineFoodBot = LineBot;
<commit_msg>Update linebot.js<commit_after>'use strict';

const EventEmitter = require('events');
const fetch = require('node-fetch');
const crypto = require('crypto');
const http = require('http');
const bodyParser = require('body-parser');

class LineBot extends EventEmitter {

	constructor(options) {
		super();
		this.options = options || {};
		this.options.channelId = options.channelId || '';
		this.options.ChannelSecret = options.ChannelSecret || '';
		this.options.ChannelAccessToken = options.ChannelAccessToken || '';
		if (this.options.verify === undefined) {
			this.options.verify = true;
		}
		this.headers = {
			Accept: 'application/json',
			'Content-Type': 'application/json',
			Authorization: 'Bearer ' + this.options.ChannelAccessToken
		};
		this.endpoint = 'https://api.line.me/v2/bot';
	}

	verify(rawBody, signature) {
		const hash = crypto.createHmac('sha256', this.options.ChannelSecret)
			.update(rawBody, 'utf8')
			.digest('base64');
		return hash === signature;
	}

	parse(body) {
		const that = this;
		if (!body || !body.events) {
			return;
		}
		body.events.forEach(function (event) {
			event.reply = function (message) {
				return that.reply(event.replyToken, message);
			};
			if (event.source) {
				event.source.profile = function () {
					return that.getUserProfile(event.source.userId);
				};
			}
			if (event.message) {
				event.message.content = function () {
					return that.getMessageContent(event.message.id);
				};
			}
			process.nextTick(function () {
				that.emit(event.type, event);
			});
		});
	}

	static createMessages(message) {
		if (typeof message === 'string') {
			return [{ type: 'text', text: message }];
		}
		if (Array.isArray(message)) {
			return message.map(function (m) {
				if (typeof m === 'string') {
					return { type: 'text', text: m };
				}
				return m;
			});
		}
		return [message];
	}

	reply(replyToken, message) {
		const body = {
			replyToken: replyToken,
			messages: LineBot.createMessages(message)
		};
		return this.post('/message/reply', body).then(function (res) {
			return res.json();
		});
	}

	push(to, message) {
		if (Array.isArray(to)) {
			return Promise.all(to.map(recipient => this.push(recipient, message)));
		}
		const body = {
			to: to,
			messages: LineBot.createMessages(message)
		};
		return this.post('/message/push', body).then(function (res) {
			return res.json();
		});
	}

	multicast(to, message) {
		const body = {
			to: to,
			messages: LineBot.createMessages(message)
		};
		return this.post('/message/multicast', body).then(function (res) {
			return res.json();
		});
	}

	getUserProfile(userId) {
		return this.get('/profile/' + userId).then(function (res) {
			return res.json();
		});
	}

	getMessageContent(messageId) {
		return this.get('/message/' + messageId + '/content/').then(function (res) {
			return res.buffer();
		});
	}

	leaveGroup(groupId) {
		return this.post('/group/' + groupId + '/leave/').then(function (res) {
			return res.json();
		});
	}

	leaveRoom(roomId) {
		return this.post('/room/' + roomId + '/leave/').then(function (res) {
			return res.json();
		});
	}

	get(path) {
		return fetch(this.endpoint + path, { method: 'GET', headers: this.headers });
	}

	post(path, body) {
		return fetch(this.endpoint + path, { method: 'POST', headers: this.headers, body: JSON.stringify(body) });
	}

	// Optional Express.js middleware
	parser() {
		const parser = bodyParser.json({
			verify: function (req, res, buf, encoding) {
				req.rawBody = buf.toString(encoding);
			}
		});
		return (req, res) => {
			parser(req, res, () => {
				if (this.options.verify && !this.verify(req.rawBody, req.get('X-Line-Signature'))) {
					return res.sendStatus(400);
				}
				this.parse(req.body);
				return res.json({});
			});
		};
	}

	// Optional built-in http server
	listen(path, port, callback) {
		const parser = bodyParser.json({
			verify: function (req, res, buf, encoding) {
				req.rawBody = buf.toString(encoding);
			}
		});
		const server = http.createServer((req, res) => {
			const signature = req.headers['x-line-signature']; // Must be lowercase
			res.setHeader('X-Powered-By', 'LineBot');
			if (req.method === 'POST' && req.url === path) {
				parser(req, res, () => {
					if (this.options.verify && !this.verify(req.rawBody, signature)) {
						res.statusCode = 400;
						res.setHeader('Content-Type', 'text/html; charset=utf-8');
						return res.end('Bad request');
					}
					this.parse(req.body);
					res.statusCode = 200;
					res.setHeader('Content-Type', 'application/json');
					return res.end('{}');
				});
			} else {
				res.statusCode = 404;
				res.setHeader('Content-Type', 'text/html; charset=utf-8');
				return res.end('Not found');
			}
		});
		return server.listen(port, callback);
	}

} // class LineBot

function createBot(options) {
	return new LineBot(options);
}

module.exports = createBot;
module.exports.LineFoodBot = LineBot;
<|endoftext|>