<commit_before>const fs = require('fs');
const Archiver = require('archiver');

const StreamBuf = require('../../utils/stream-buf');

const RelType = require('../../xlsx/rel-type');
const StylesXform = require('../../xlsx/xform/style/styles-xform');
const SharedStrings = require('../../utils/shared-strings');
const DefinedNames = require('../../doc/defined-names');

const CoreXform = require('../../xlsx/xform/core/core-xform');
const RelationshipsXform = require('../../xlsx/xform/core/relationships-xform');
const ContentTypesXform = require('../../xlsx/xform/core/content-types-xform');
const AppXform = require('../../xlsx/xform/core/app-xform');
const WorkbookXform = require('../../xlsx/xform/book/workbook-xform');
const SharedStringsXform = require('../../xlsx/xform/strings/shared-strings-xform');

const WorksheetWriter = require('./worksheet-writer');

const theme1Xml = require('../../xlsx/xml/theme1.js');

class WorkbookWriter {
  constructor(options) {
    options = options || {};

    this.created = options.created || new Date();
    this.modified = options.modified || this.created;
    this.creator = options.creator || 'ExcelJS';
    this.lastModifiedBy = options.lastModifiedBy || 'ExcelJS';
    this.lastPrinted = options.lastPrinted;

    // using shared strings creates a smaller xlsx file but may use more memory
    this.useSharedStrings = options.useSharedStrings || false;
    this.sharedStrings = new SharedStrings();

    // style manager
    this.styles = options.useStyles ? new StylesXform(true) : new StylesXform.Mock(true);

    // defined names
    this._definedNames = new DefinedNames();

    this._worksheets = [];
    this.views = [];

    this.zipOptions = options.zip;

    this.media = [];
    this.commentRefs = [];

    this.zip = Archiver('zip', this.zipOptions);
    if (options.stream) {
      this.stream = options.stream;
    } else if (options.filename) {
      this.stream = fs.createWriteStream(options.filename);
    } else {
      this.stream = new StreamBuf();
    }
    this.zip.pipe(this.stream);

    // these bits can be added right now
    this.promise = Promise.all([this.addThemes(), this.addOfficeRels()]);
  }

  get definedNames() {
    return this._definedNames;
  }

  _openStream(path) {
    const stream = new StreamBuf({bufSize: 65536, batch: true});
    this.zip.append(stream, {name: path});
    stream.on('finish', () => {
      stream.emit('zipped');
    });
    return stream;
  }

  _commitWorksheets() {
    const commitWorksheet = function(worksheet) {
      if (!worksheet.committed) {
        return new Promise(resolve => {
          worksheet.stream.on('zipped', () => {
            resolve();
          });
          worksheet.commit();
        });
      }
      return Promise.resolve();
    };
    // if there are any uncommitted worksheets, commit them now and wait
    const promises = this._worksheets.map(commitWorksheet);
    if (promises.length) {
      return Promise.all(promises);
    }
    return Promise.resolve();
  }

  async commit() {
    // commit all worksheets, then add suplimentary files
    await this.promise;
    await this.addMedia();
    await this._commitWorksheets();
    await Promise.all([
      this.addContentTypes(),
      this.addApp(),
      this.addCore(),
      this.addSharedStrings(),
      this.addStyles(),
      this.addWorkbookRels(),
    ]);
    await this.addWorkbook();
    return this._finalize();
  }

  get nextId() {
    // find the next unique spot to add worksheet
    let i;
    for (i = 1; i < this._worksheets.length; i++) {
      if (!this._worksheets[i]) {
        return i;
      }
    }
    return this._worksheets.length || 1;
  }

  addImage(image) {
    const id = this.media.length;
    this.media.push(Object.assign({}, image, {type: 'image'}));
    return id;
  }

  getImage(id) {
    return this.media[id];
  }

  addWorksheet(name, options) {
    // it's possible to add a worksheet with different than default
    // shared string handling
    // in fact, it's even possible to switch it mid-sheet
    options = options || {};
    const useSharedStrings = options.useSharedStrings !== undefined ? options.useSharedStrings : this.useSharedStrings;

    if (options.tabColor) {
      // eslint-disable-next-line no-console
      console.trace('tabColor option has moved to { properties: tabColor: {...} }');
      options.properties = Object.assign(
        {
          tabColor: options.tabColor,
        },
        options.properties
      );
    }

    const id = this.nextId;
    name = name || `sheet${id}`;

    const worksheet = new WorksheetWriter({
      id,
      name,
      workbook: this,
      useSharedStrings,
      properties: options.properties,
      state: options.state,
      pageSetup: options.pageSetup,
      views: options.views,
      autoFilter: options.autoFilter,
    });

    this._worksheets[id] = worksheet;
    return worksheet;
  }

  getWorksheet(id) {
    if (id === undefined) {
      return this._worksheets.find(() => true);
    }
    if (typeof id === 'number') {
      return this._worksheets[id];
    }
    if (typeof id === 'string') {
      return this._worksheets.find(worksheet => worksheet && worksheet.name === id);
    }
    return undefined;
  }

  addStyles() {
    return new Promise(resolve => {
      this.zip.append(this.styles.xml, {name: 'xl/styles.xml'});
      resolve();
    });
  }

  addThemes() {
    return new Promise(resolve => {
      this.zip.append(theme1Xml, {name: 'xl/theme/theme1.xml'});
      resolve();
    });
  }

  addOfficeRels() {
    return new Promise(resolve => {
      const xform = new RelationshipsXform();
      const xml = xform.toXml([
        {Id: 'rId1', Type: RelType.OfficeDocument, Target: 'xl/workbook.xml'},
        {Id: 'rId2', Type: RelType.CoreProperties, Target: 'docProps/core.xml'},
        {Id: 'rId3', Type: RelType.ExtenderProperties, Target: 'docProps/app.xml'},
      ]);
      this.zip.append(xml, {name: '/_rels/.rels'});
      resolve();
    });
  }

  addContentTypes() {
    return new Promise(resolve => {
      const model = {
        worksheets: this._worksheets.filter(Boolean),
        sharedStrings: this.sharedStrings,
        commentRefs: this.commentRefs,
        media: this.media,
      };
      const xform = new ContentTypesXform();
      const xml = xform.toXml(model);
      this.zip.append(xml, {name: '[Content_Types].xml'});
      resolve();
    });
  }

  addMedia() {
    return Promise.all(
      this.media.map((medium, idx) => {
        medium.name = `image${idx+1}.${medium.extension}`;
        if (medium.type === 'image') {
          const filename = `xl/media/${medium.name}`;
          if (medium.filename) {
            return this.zip.file(medium.filename, {name: filename});
          }
          if (medium.buffer) {
            return this.zip.append(medium.buffer, {name: filename});
          }
          if (medium.base64) {
            const dataimg64 = medium.base64;
            const content = dataimg64.substring(dataimg64.indexOf(',') + 1);
            return this.zip.append(content, {name: filename, base64: true});
          }
        }
        throw new Error('Unsupported media');
      })
    );
  }

  addApp() {
    return new Promise(resolve => {
      const model = {
        worksheets: this._worksheets.filter(Boolean),
      };
      const xform = new AppXform();
      const xml = xform.toXml(model);
      this.zip.append(xml, {name: 'docProps/app.xml'});
      resolve();
    });
  }

  addCore() {
    return new Promise(resolve => {
      const coreXform = new CoreXform();
      const xml = coreXform.toXml(this);
      this.zip.append(xml, {name: 'docProps/core.xml'});
      resolve();
    });
  }

  addSharedStrings() {
    if (this.sharedStrings.count) {
      return new Promise(resolve => {
        const sharedStringsXform = new SharedStringsXform();
        const xml = sharedStringsXform.toXml(this.sharedStrings);
        this.zip.append(xml, {name: '/xl/sharedStrings.xml'});
        resolve();
      });
    }
    return Promise.resolve();
  }

  addWorkbookRels() {
    let count = 1;
    const relationships = [
      {Id: `rId${count++}`, Type: RelType.Styles, Target: 'styles.xml'},
      {Id: `rId${count++}`, Type: RelType.Theme, Target: 'theme/theme1.xml'},
    ];
    if (this.sharedStrings.count) {
      relationships.push({Id: `rId${count++}`, Type: RelType.SharedStrings, Target: 'sharedStrings.xml'});
    }
    this._worksheets.forEach(worksheet => {
      if (worksheet) {
        worksheet.rId = `rId${count++}`;
        relationships.push({Id: worksheet.rId, Type: RelType.Worksheet, Target: `worksheets/sheet${worksheet.id}.xml`});
      }
    });
    return new Promise(resolve => {
      const xform = new RelationshipsXform();
      const xml = xform.toXml(relationships);
      this.zip.append(xml, {name: '/xl/_rels/workbook.xml.rels'});
      resolve();
    });
  }

  addWorkbook() {
    const {zip} = this;
    const model = {
      worksheets: this._worksheets.filter(Boolean),
      definedNames: this._definedNames.model,
      views: this.views,
      properties: {},
      calcProperties: {},
    };

    return new Promise(resolve => {
      const xform = new WorkbookXform();
      xform.prepare(model);
      zip.append(xform.toXml(model), {name: '/xl/workbook.xml'});
      resolve();
    });
  }

  _finalize() {
    return new Promise((resolve, reject) => {
      this.stream.on('error', reject);
      this.stream.on('finish', () => {
        resolve(this);
      });
      this.zip.on('error', reject);

      this.zip.finalize();
    });
  }
}

module.exports = WorkbookWriter;
<commit_msg>Fix for issue with early commit on stream writer with background image<commit_after>const fs = require('fs');
const Archiver = require('archiver');

const StreamBuf = require('../../utils/stream-buf');

const RelType = require('../../xlsx/rel-type');
const StylesXform = require('../../xlsx/xform/style/styles-xform');
const SharedStrings = require('../../utils/shared-strings');
const DefinedNames = require('../../doc/defined-names');

const CoreXform = require('../../xlsx/xform/core/core-xform');
const RelationshipsXform = require('../../xlsx/xform/core/relationships-xform');
const ContentTypesXform = require('../../xlsx/xform/core/content-types-xform');
const AppXform = require('../../xlsx/xform/core/app-xform');
const WorkbookXform = require('../../xlsx/xform/book/workbook-xform');
const SharedStringsXform = require('../../xlsx/xform/strings/shared-strings-xform');

const WorksheetWriter = require('./worksheet-writer');

const theme1Xml = require('../../xlsx/xml/theme1.js');

class WorkbookWriter {
  constructor(options) {
    options = options || {};

    this.created = options.created || new Date();
    this.modified = options.modified || this.created;
    this.creator = options.creator || 'ExcelJS';
    this.lastModifiedBy = options.lastModifiedBy || 'ExcelJS';
    this.lastPrinted = options.lastPrinted;

    // using shared strings creates a smaller xlsx file but may use more memory
    this.useSharedStrings = options.useSharedStrings || false;
    this.sharedStrings = new SharedStrings();

    // style manager
    this.styles = options.useStyles ? new StylesXform(true) : new StylesXform.Mock(true);

    // defined names
    this._definedNames = new DefinedNames();

    this._worksheets = [];
    this.views = [];

    this.zipOptions = options.zip;

    this.media = [];
    this.commentRefs = [];

    this.zip = Archiver('zip', this.zipOptions);
    if (options.stream) {
      this.stream = options.stream;
    } else if (options.filename) {
      this.stream = fs.createWriteStream(options.filename);
    } else {
      this.stream = new StreamBuf();
    }
    this.zip.pipe(this.stream);

    // these bits can be added right now
    this.promise = Promise.all([this.addThemes(), this.addOfficeRels()]);
  }

  get definedNames() {
    return this._definedNames;
  }

  _openStream(path) {
    const stream = new StreamBuf({bufSize: 65536, batch: true});
    this.zip.append(stream, {name: path});
    stream.on('finish', () => {
      stream.emit('zipped');
    });
    return stream;
  }

  _commitWorksheets() {
    const commitWorksheet = function(worksheet) {
      if (!worksheet.committed) {
        return new Promise(resolve => {
          worksheet.stream.on('zipped', () => {
            resolve();
          });
          worksheet.commit();
        });
      }
      return Promise.resolve();
    };
    // if there are any uncommitted worksheets, commit them now and wait
    const promises = this._worksheets.map(commitWorksheet);
    if (promises.length) {
      return Promise.all(promises);
    }
    return Promise.resolve();
  }

  async commit() {
    // commit all worksheets, then add suplimentary files
    await this.promise;
    await this.addMedia();
    await this._commitWorksheets();
    await Promise.all([
      this.addContentTypes(),
      this.addApp(),
      this.addCore(),
      this.addSharedStrings(),
      this.addStyles(),
      this.addWorkbookRels(),
    ]);
    await this.addWorkbook();
    return this._finalize();
  }

  get nextId() {
    // find the next unique spot to add worksheet
    let i;
    for (i = 1; i < this._worksheets.length; i++) {
      if (!this._worksheets[i]) {
        return i;
      }
    }
    return this._worksheets.length || 1;
  }

  addImage(image) {
    const id = this.media.length;
    const medium = Object.assign({}, image, {type: 'image', name: `image${id}.${image.extension}`});
    this.media.push(medium);
    return id;
  }

  getImage(id) {
    return this.media[id];
  }

  addWorksheet(name, options) {
    // it's possible to add a worksheet with different than default
    // shared string handling
    // in fact, it's even possible to switch it mid-sheet
    options = options || {};
    const useSharedStrings = options.useSharedStrings !== undefined ? options.useSharedStrings : this.useSharedStrings;

    if (options.tabColor) {
      // eslint-disable-next-line no-console
      console.trace('tabColor option has moved to { properties: tabColor: {...} }');
      options.properties = Object.assign(
        {
          tabColor: options.tabColor,
        },
        options.properties
      );
    }

    const id = this.nextId;
    name = name || `sheet${id}`;

    const worksheet = new WorksheetWriter({
      id,
      name,
      workbook: this,
      useSharedStrings,
      properties: options.properties,
      state: options.state,
      pageSetup: options.pageSetup,
      views: options.views,
      autoFilter: options.autoFilter,
    });

    this._worksheets[id] = worksheet;
    return worksheet;
  }

  getWorksheet(id) {
    if (id === undefined) {
      return this._worksheets.find(() => true);
    }
    if (typeof id === 'number') {
      return this._worksheets[id];
    }
    if (typeof id === 'string') {
      return this._worksheets.find(worksheet => worksheet && worksheet.name === id);
    }
    return undefined;
  }

  addStyles() {
    return new Promise(resolve => {
      this.zip.append(this.styles.xml, {name: 'xl/styles.xml'});
      resolve();
    });
  }

  addThemes() {
    return new Promise(resolve => {
      this.zip.append(theme1Xml, {name: 'xl/theme/theme1.xml'});
      resolve();
    });
  }

  addOfficeRels() {
    return new Promise(resolve => {
      const xform = new RelationshipsXform();
      const xml = xform.toXml([
        {Id: 'rId1', Type: RelType.OfficeDocument, Target: 'xl/workbook.xml'},
        {Id: 'rId2', Type: RelType.CoreProperties, Target: 'docProps/core.xml'},
        {Id: 'rId3', Type: RelType.ExtenderProperties, Target: 'docProps/app.xml'},
      ]);
      this.zip.append(xml, {name: '/_rels/.rels'});
      resolve();
    });
  }

  addContentTypes() {
    return new Promise(resolve => {
      const model = {
        worksheets: this._worksheets.filter(Boolean),
        sharedStrings: this.sharedStrings,
        commentRefs: this.commentRefs,
        media: this.media,
      };
      const xform = new ContentTypesXform();
      const xml = xform.toXml(model);
      this.zip.append(xml, {name: '[Content_Types].xml'});
      resolve();
    });
  }

  addMedia() {
    return Promise.all(
      this.media.map((medium) => {
        if (medium.type === 'image') {
          const filename = `xl/media/${medium.name}`;
          if (medium.filename) {
            return this.zip.file(medium.filename, {name: filename});
          }
          if (medium.buffer) {
            return this.zip.append(medium.buffer, {name: filename});
          }
          if (medium.base64) {
            const dataimg64 = medium.base64;
            const content = dataimg64.substring(dataimg64.indexOf(',') + 1);
            return this.zip.append(content, {name: filename, base64: true});
          }
        }
        throw new Error('Unsupported media');
      })
    );
  }

  addApp() {
    return new Promise(resolve => {
      const model = {
        worksheets: this._worksheets.filter(Boolean),
      };
      const xform = new AppXform();
      const xml = xform.toXml(model);
      this.zip.append(xml, {name: 'docProps/app.xml'});
      resolve();
    });
  }

  addCore() {
    return new Promise(resolve => {
      const coreXform = new CoreXform();
      const xml = coreXform.toXml(this);
      this.zip.append(xml, {name: 'docProps/core.xml'});
      resolve();
    });
  }

  addSharedStrings() {
    if (this.sharedStrings.count) {
      return new Promise(resolve => {
        const sharedStringsXform = new SharedStringsXform();
        const xml = sharedStringsXform.toXml(this.sharedStrings);
        this.zip.append(xml, {name: '/xl/sharedStrings.xml'});
        resolve();
      });
    }
    return Promise.resolve();
  }

  addWorkbookRels() {
    let count = 1;
    const relationships = [
      {Id: `rId${count++}`, Type: RelType.Styles, Target: 'styles.xml'},
      {Id: `rId${count++}`, Type: RelType.Theme, Target: 'theme/theme1.xml'},
    ];
    if (this.sharedStrings.count) {
      relationships.push({Id: `rId${count++}`, Type: RelType.SharedStrings, Target: 'sharedStrings.xml'});
    }
    this._worksheets.forEach(worksheet => {
      if (worksheet) {
        worksheet.rId = `rId${count++}`;
        relationships.push({Id: worksheet.rId, Type: RelType.Worksheet, Target: `worksheets/sheet${worksheet.id}.xml`});
      }
    });
    return new Promise(resolve => {
      const xform = new RelationshipsXform();
      const xml = xform.toXml(relationships);
      this.zip.append(xml, {name: '/xl/_rels/workbook.xml.rels'});
      resolve();
    });
  }

  addWorkbook() {
    const {zip} = this;
    const model = {
      worksheets: this._worksheets.filter(Boolean),
      definedNames: this._definedNames.model,
      views: this.views,
      properties: {},
      calcProperties: {},
    };

    return new Promise(resolve => {
      const xform = new WorkbookXform();
      xform.prepare(model);
      zip.append(xform.toXml(model), {name: '/xl/workbook.xml'});
      resolve();
    });
  }

  _finalize() {
    return new Promise((resolve, reject) => {
      this.stream.on('error', reject);
      this.stream.on('finish', () => {
        resolve(this);
      });
      this.zip.on('error', reject);

      this.zip.finalize();
    });
  }
}

module.exports = WorkbookWriter;
<|endoftext|>


<commit_before>!function(e,t){for(var r in t)e[r]=t[r]}(exports,function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=0)}([function(e,t){e.exports={handler:function(e,t,r){r(null,{statusCode:200,body:`Node ${process.version}`})}}}]));<commit_msg>Update<commit_after>!function(e,t){for(var r in t)e[r]=t[r]}(exports,function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=0)}([function(e,t){e.exports={handler:function(e,t,r){r(null,{statusCode:200,body:`Node.js ${process.version}`})}}}]));<|endoftext|>


<commit_before>var verbose_logging = (process.env.NODE_ENV !== "production");
var models  = require("../../models");
var express = require("express");
var router = express.Router();
var querystring = require("querystring");
var fs = require("fs");
var util = require("util");
var hash = require("../../misc/hash.js").hash;
var aws = require("../../config/aws.js").aws();

router.route("/:guardian_id/checkins")
  .post(function(req, res) {

    var requestStartTime = (new Date()).valueOf();

    var json = JSON.parse(querystring.parse("all="+req.body.json).all);

    if (verbose_logging) { console.log(json); }

    models.Guardian
      .findOrCreate({ where: { guid: req.params.guardian_id } })
      .spread(function(dbGuardian, wasCreated){
      console.log("matched to guardian: "+dbGuardian.guid);

      models.GuardianSoftware
        .findOrCreate( { where: { number: json.software_version } })
        .spread(function(dSoftware, wasCreated){
          console.log("matched to software version: "+dSoftware.number);

          dbGuardian.last_check_in = new Date();
          dbGuardian.check_in_count = 1+dbGuardian.check_in_count;
          dbGuardian.version_id = dSoftware.id;
          dbGuardian.save();
          console.log("software version saved to guardian: "+dSoftware.number);

          models.GuardianCheckIn.create({
            guardian_id: dbGuardian.id,
            version_id: dSoftware.id,
            network_search_time: strArrToAvg(json.network_search_time,"|","*"),
            internal_luminosity: strArrToAvg(json.internal_luminosity,"|","*"),
            network_transmit_time: null,
            measured_at: new Date(json.measured_at.replace(/ /g,"T")+json.timezone_offset)
          }).then(function(dbCheckIn){
            console.log("check-in created: "+dbCheckIn.guid);

            // save guardian meta data

            // save guardian meta data transfer
            var metaDataTransfer = strArrToJSArr(json.data_transfer,"|","*");
            for (dtInd in metaDataTransfer) {
              models.GuardianMetaDataTransfer.create({
                  guardian_id: dbGuardian.id,
                  check_in_id: dbCheckIn.id,
                  started_at: new Date(metaDataTransfer[dtInd][0].replace(/ /g,"T")+json.timezone_offset),
                  ended_at: new Date(metaDataTransfer[dtInd][1].replace(/ /g,"T")+json.timezone_offset),
                  bytes_received: parseInt(metaDataTransfer[dtInd][2]),
                  bytes_sent: parseInt(metaDataTransfer[dtInd][3]),
                  total_bytes_received: parseInt(metaDataTransfer[dtInd][4]),
                  total_bytes_sent: parseInt(metaDataTransfer[dtInd][5])
                }).then(function(dbGuardianMetaDataTransfer){ }).catch(function(err){ });
            }

            // save guardian meta CPU
            var metaCPU = strArrToJSArr(json.cpu,"|","*");
            for (cpuInd in metaCPU) {
              models.GuardianMetaCPU.create({
                  guardian_id: dbGuardian.id,
                  check_in_id: dbCheckIn.id,
                  measured_at: new Date(metaCPU[cpuInd][0].replace(/ /g,"T")+json.timezone_offset),
                  cpu_percent: parseInt(metaCPU[cpuInd][1]),
                  cpu_clock: parseInt(metaCPU[cpuInd][2])
                }).then(function(dbGuardianMetaCPU){ }).catch(function(err){ });
            }

            // save guardian meta battery
            var metaBattery = strArrToJSArr(json.cpu,"|","*");
            for (battInd in metaBattery) {
              models.GuardianMetaBattery.create({
                  guardian_id: dbGuardian.id,
                  check_in_id: dbCheckIn.id,
                  measured_at: new Date(metaBattery[battInd][0].replace(/ /g,"T")+json.timezone_offset),
                  battery_percent: parseInt(metaBattery[battInd][1]),
                  battery_temperature: parseInt(metaBattery[battInd][2])
                }).then(function(dbGuardianMetaBattery){ }).catch(function(err){ });
            }

            var returnJson = {
              checkin_id: dbCheckIn.guid,
              audio: [],
              screenshots: [],
              messages: [],
              instructions: {
                prefs: {},
                messages: []
              }
            };

            // parse, review and save sms messages
            var messages = JSON.parse(querystring.parse("all="+req.body.messages).all);
            if (util.isArray(messages)) {
              console.log(messages.length + " messages to store...");
              var messageInfo = {};
              for (msgInd in messages) {
                var digest = messages[msgInd].digest;
                messageInfo[digest] = {
                     guardian_id: dbGuardian.guid,
                     checkin_id: dbCheckIn.guid,
                     version: dSoftware.number,
                     digest: messages[msgInd].digest,
                     number: messages[msgInd].number,
                     body: messages[msgInd].body,
                     timeStamp: new Date(parseInt(messages[msgInd].received_at)),
                     isSaved: false
                  };
              }
              for (msgInfoInd in messageInfo) {
                // ... save the messages into a database
                // if all goes well, then...
                messageInfo[msgInfoInd].isSaved = true;
                console.log("message saved: "+messageInfo[msgInfoInd].timeStamp);
              }
            }

            // if included, update previous checkIn info
            if (json.previous_checkins != null) {
              var previousCheckIns = json.previous_checkins.split("|");
              for (checkInIndex in previousCheckIns) {
                var previousCheckIn = previousCheckIns[checkInIndex].split("*");
                models.GuardianCheckIn
                  .findAll({ where: { guid: previousCheckIn[0] } })
                  .spread(function(dPreviousCheckIn){
                    dPreviousCheckIn.request_latency_guardian = previousCheckIn[1];
                    dPreviousCheckIn.save();
                  }).catch(function(err){
                    console.log("error finding/updating previous checkin id: "+previousCheckIn[0]);
                  });
              }
            }

            // save screenshot files
            if (!!req.files.screenshot) {
              if (!util.isArray(req.files.screenshot)) { req.files.screenshot = [req.files.screenshot]; }
                console.log(req.files.screenshot.length + " screenshot files to ingest...");
                var screenShotInfo = {};
                for (i in req.files.screenshot) {
                  var timeStamp = req.files.screenshot[i].originalname.substr(0,req.files.screenshot[i].originalname.lastIndexOf(".png"));
                  var dateString = (new Date(parseInt(timeStamp))).toISOString().substr(0,19).replace(/:/g,"-");
                  screenShotInfo[timeStamp] = {
                     guardian_id: dbGuardian.guid,
                     checkin_id: dbCheckIn.guid,
                     version: dSoftware.number,
                     sha1Hash: hash.fileSha1(req.files.screenshot[i].path),
                     localPath: req.files.screenshot[i].path,
                     size: fs.statSync(req.files.screenshot[i].path).size,
                     origin_id: timeStamp,
                     timeStamp: new Date(parseInt(timeStamp)),
                     isSaved: false,
                     s3Path: "/"+process.env.NODE_ENV
                              +"/"+dateString.substr(0,7)+"/"+dateString.substr(8,2)
                              +"/"+dbGuardian.guid
                              +"/"+dbGuardian.guid+"-"+dateString+".png"
                  };

                }
                for (j in screenShotInfo) {
                  aws.s3("rfcx-meta").putFile(
                    screenShotInfo[j].localPath, screenShotInfo[j].s3Path,
                    function(err, s3Res){
                      s3Res.resume();
                      if (!!err) {
                        console.log(err);
                      } else if (200 == s3Res.statusCode) {
                        for (l in screenShotInfo) {
                          if (s3Res.req.url.indexOf(screenShotInfo[l].s3Path) >= 0) {
                            screenShotInfo[l].isSaved = true;
                            console.log("screenshot saved: "+screenShotInfo[l].timeStamp);
                          }
                        }
                      }
                  });
                }
            }

            // save audio files
            if (!!req.files.audio) {
              if (!util.isArray(req.files.audio)) { req.files.audio = [req.files.audio]; }
              var audioMeta = [];
              if (json.audio != null) { audioMeta = json.audio.split("|"); }
              if (audioMeta.length == req.files.audio.length) {
                console.log(req.files.audio.length + " audio files to ingest...");
                var audioInfo = {};
                for (i in req.files.audio) {
                  audioMeta[i] = audioMeta[i].split("*");
                  var timeStamp = audioMeta[i][1];
                  audioMeta[i][1] = new Date(parseInt(audioMeta[i][1]));
                  var dateString = audioMeta[i][1].toISOString().substr(0,19).replace(/:/g,"-");
                  audioInfo[timeStamp] = {
                    guardian_id: dbGuardian.guid,
                    checkin_id: dbCheckIn.guid,
                    version: dSoftware.number,
                    battery_temperature: dbCheckIn.battery_temperature,
                    sha1Hash: hash.fileSha1(req.files.audio[i].path),
                    localPath: req.files.audio[i].path,
                    size: fs.statSync(req.files.audio[i].path).size,
                    timeStamp: timeStamp,
                    measured_at: audioMeta[i][1],
                    isSaved: { db: false, s3: false, sqs: false },
                    s3Path: "/"+process.env.NODE_ENV
                            +"/"+dateString.substr(0,7)+"/"+dateString.substr(8,2)
                            +"/"+dbGuardian.guid
                            +"/"+dbGuardian.guid+"-"+dateString
                                +req.files.audio[i].originalname.substr(req.files.audio[i].originalname.indexOf("."))
                  };
                }

                for (j in audioInfo) {

                  models.GuardianAudio.create({
                    guardian_id: dbGuardian.id,
                    check_in_id: dbCheckIn.id,
                    sha1_checksum: audioInfo[j].sha1Hash,
                    url: "s3://rfcx-ark"+audioInfo[j].s3Path,
                    size: audioInfo[j].size,
                    measured_at: audioInfo[j].measured_at
                  }).then(function(dbAudio){

                    for (k in audioInfo) {
                      if (audioInfo[k].sha1Hash === dbAudio.sha1_checksum) {

                        audioInfo[k].isSaved.db = true;
                        audioInfo[k].audio_id = dbAudio.guid;

                        console.log("uploading file to s3: "+audioInfo[k].audio_id);

                        aws.s3("rfcx-ark").putFile(
                          audioInfo[k].localPath, audioInfo[k].s3Path,
                          function(err, s3Res){
                            s3Res.resume();
                            if (!!err) {
                              console.log(err);
                            } else if (200 == s3Res.statusCode) {
                              for (l in audioInfo) {
                                if (s3Res.req.url.indexOf(audioInfo[l].s3Path) >= 0) {
                                  audioInfo[l].isSaved.s3 = true;

                                  console.log("adding job to sns/sqs ingestion queue: "+audioInfo[l].audio_id);
                                  audioInfo[l].measured_at = audioInfo[l].measured_at.toISOString();

                                  aws.sns().publish({
                                      TopicArn: aws.snsTopicArn("rfcx-analysis"),
                                      Message: JSON.stringify(audioInfo[l])
                                    }, function(err, data) {
                                      if (!!err) {
                                        console.log(err);
                                      } else {
                                        var isComplete = true;

                                        for (m in audioInfo) {
                                          if (!audioInfo[m].isSaved.sqs) { isComplete = false; }
                                          returnJson.audio.push({
                                            id: m,
                                            guid: audioInfo[m].audio_id
                                          });
                                        }
                                        for (n in screenShotInfo) {
                                          if (screenShotInfo[n].isSaved) {
                                            returnJson.screenshots.push({
                                              id: n,
                                              guid: null
                                            });
                                          }
                                        }
                                        for (o in messageInfo) {
                                          if (messageInfo[o].isSaved) {
                                            returnJson.messages.push({
                                              digest: messageInfo[o].digest,
                                              guid: null
                                            });
                                          }
                                        }
                                        if (isComplete) {

                                          dbCheckIn.request_latency_api = (new Date()).valueOf()-requestStartTime;
                                          dbCheckIn.save();

                                          if (verbose_logging) { console.log(returnJson); }
                                          res.status(200).json(returnJson);

                                          for (m in audioInfo) { audioInfo[m].isSaved.sqs = false; }
                                        }
                                      }
                                  });
                                  fs.unlink(audioInfo[l].localPath,function(e){if(e){console.log(e);}});
                                  audioInfo[l].isSaved.sqs = true;
                                }
                              }
                            }
                        });
                      }
                    }
                  }).catch(function(err){
                    console.log("error adding audio to database | "+err);
                    if (!!err) { res.status(500).json({msg:"error adding audio to database"}); }
                  });
                }
              } else {
                console.log("couldn't match audio meta to uploaded content | "+audioMeta);
                if (!!err) { res.status(500).json({msg:"couldn't match audio meta to uploaded content"}); }
              }
            } else {
              console.log("no audio files detected");
              dbCheckIn.request_latency_api = (new Date()).valueOf()-requestStartTime;
              dbCheckIn.save();
              res.status(200).json(returnJson);
            }

          }).catch(function(err){
            console.log("error adding checkin to database | "+err);
            if (!!err) { res.status(500).json({msg:"error adding checkin to database"}); }
          });
        }).catch(function(err){
          console.log("failed to update version of guardian | "+err);
          if (!!err) { res.status(500).json({msg:"failed to update version of guardian"}); }
        });
    });
  })
;

router.route("/:guardian_id/checkins/:checkin_id")
  .get(function(req, res) {

    models.Guardian
      .findAll( { where: { guid: req.params.guardian_id } })
      .spread(function(dbGuardian){

        res.status(200).json(dbGuardian);

    }).catch(function(err){
        console.log("failed to find guardian | "+err);
    });
  })
;

module.exports = router;

// Special Functions

function strArrToAvg(str,delimA,delimB) {
  if (str == null) { return null; }
  try {
    var ttl = 0, arr = str.split(delimA);
    if (arr.length > 0) {
      for (i in arr) {
        ttl = ttl + parseInt(arr[i].split(delimB)[1]);
      }
      return Math.round(ttl/arr.length);
    } else {
      return null;
    }
  } catch(e) {
    console.log(e);
    return null;
  }
}

function strArrToJSArr(str,delimA,delimB) {
  if (str == null) { return []; }
  try {
    var rtrnArr = [], arr = str.split(delimA);
    if (arr.length > 0) {
      for (i in arr) {
        rtrnArr.push(arr[i].split(delimB));
      }
      return rtrnArr;
    } else {
      return [];
    }
  } catch(e) {
    console.log(e);
    return [];
  }
}

<commit_msg>fixed battery/cpu mismatch<commit_after>var verbose_logging = (process.env.NODE_ENV !== "production");
var models  = require("../../models");
var express = require("express");
var router = express.Router();
var querystring = require("querystring");
var fs = require("fs");
var util = require("util");
var hash = require("../../misc/hash.js").hash;
var aws = require("../../config/aws.js").aws();

router.route("/:guardian_id/checkins")
  .post(function(req, res) {

    var requestStartTime = (new Date()).valueOf();

    var json = JSON.parse(querystring.parse("all="+req.body.json).all);

    if (verbose_logging) { console.log(json); }

    models.Guardian
      .findOrCreate({ where: { guid: req.params.guardian_id } })
      .spread(function(dbGuardian, wasCreated){
      console.log("matched to guardian: "+dbGuardian.guid);

      models.GuardianSoftware
        .findOrCreate( { where: { number: json.software_version } })
        .spread(function(dSoftware, wasCreated){
          console.log("matched to software version: "+dSoftware.number);

          dbGuardian.last_check_in = new Date();
          dbGuardian.check_in_count = 1+dbGuardian.check_in_count;
          dbGuardian.version_id = dSoftware.id;
          dbGuardian.save();
          console.log("software version saved to guardian: "+dSoftware.number);

          models.GuardianCheckIn.create({
            guardian_id: dbGuardian.id,
            version_id: dSoftware.id,
            network_search_time: strArrToAvg(json.network_search_time,"|","*"),
            internal_luminosity: strArrToAvg(json.internal_luminosity,"|","*"),
            network_transmit_time: null,
            measured_at: new Date(json.measured_at.replace(/ /g,"T")+json.timezone_offset)
          }).then(function(dbCheckIn){
            console.log("check-in created: "+dbCheckIn.guid);

            // save guardian meta data

            // save guardian meta data transfer
            var metaDataTransfer = strArrToJSArr(json.data_transfer,"|","*");
            for (dtInd in metaDataTransfer) {
              models.GuardianMetaDataTransfer.create({
                  guardian_id: dbGuardian.id,
                  check_in_id: dbCheckIn.id,
                  started_at: new Date(metaDataTransfer[dtInd][0].replace(/ /g,"T")+json.timezone_offset),
                  ended_at: new Date(metaDataTransfer[dtInd][1].replace(/ /g,"T")+json.timezone_offset),
                  bytes_received: parseInt(metaDataTransfer[dtInd][2]),
                  bytes_sent: parseInt(metaDataTransfer[dtInd][3]),
                  total_bytes_received: parseInt(metaDataTransfer[dtInd][4]),
                  total_bytes_sent: parseInt(metaDataTransfer[dtInd][5])
                }).then(function(dbGuardianMetaDataTransfer){ }).catch(function(err){ });
            }

            // save guardian meta CPU
            var metaCPU = strArrToJSArr(json.cpu,"|","*");
            for (cpuInd in metaCPU) {
              models.GuardianMetaCPU.create({
                  guardian_id: dbGuardian.id,
                  check_in_id: dbCheckIn.id,
                  measured_at: new Date(metaCPU[cpuInd][0].replace(/ /g,"T")+json.timezone_offset),
                  cpu_percent: parseInt(metaCPU[cpuInd][1]),
                  cpu_clock: parseInt(metaCPU[cpuInd][2])
                }).then(function(dbGuardianMetaCPU){ }).catch(function(err){ });
            }

            // save guardian meta battery
            var metaBattery = strArrToJSArr(json.battery,"|","*");
            for (battInd in metaBattery) {
              models.GuardianMetaBattery.create({
                  guardian_id: dbGuardian.id,
                  check_in_id: dbCheckIn.id,
                  measured_at: new Date(metaBattery[battInd][0].replace(/ /g,"T")+json.timezone_offset),
                  battery_percent: parseInt(metaBattery[battInd][1]),
                  battery_temperature: parseInt(metaBattery[battInd][2])
                }).then(function(dbGuardianMetaBattery){ }).catch(function(err){ });
            }

            var returnJson = {
              checkin_id: dbCheckIn.guid,
              audio: [],
              screenshots: [],
              messages: [],
              instructions: {
                prefs: {},
                messages: []
              }
            };

            // parse, review and save sms messages
            var messages = JSON.parse(querystring.parse("all="+req.body.messages).all);
            if (util.isArray(messages)) {
              console.log(messages.length + " messages to store...");
              var messageInfo = {};
              for (msgInd in messages) {
                var digest = messages[msgInd].digest;
                messageInfo[digest] = {
                     guardian_id: dbGuardian.guid,
                     checkin_id: dbCheckIn.guid,
                     version: dSoftware.number,
                     digest: messages[msgInd].digest,
                     number: messages[msgInd].number,
                     body: messages[msgInd].body,
                     timeStamp: new Date(parseInt(messages[msgInd].received_at)),
                     isSaved: false
                  };
              }
              for (msgInfoInd in messageInfo) {
                // ... save the messages into a database
                // if all goes well, then...
                messageInfo[msgInfoInd].isSaved = true;
                console.log("message saved: "+messageInfo[msgInfoInd].timeStamp);
              }
            }

            // if included, update previous checkIn info
            if (json.previous_checkins != null) {
              var previousCheckIns = json.previous_checkins.split("|");
              for (checkInIndex in previousCheckIns) {
                var previousCheckIn = previousCheckIns[checkInIndex].split("*");
                models.GuardianCheckIn
                  .findAll({ where: { guid: previousCheckIn[0] } })
                  .spread(function(dPreviousCheckIn){
                    dPreviousCheckIn.request_latency_guardian = previousCheckIn[1];
                    dPreviousCheckIn.save();
                  }).catch(function(err){
                    console.log("error finding/updating previous checkin id: "+previousCheckIn[0]);
                  });
              }
            }

            // save screenshot files
            if (!!req.files.screenshot) {
              if (!util.isArray(req.files.screenshot)) { req.files.screenshot = [req.files.screenshot]; }
                console.log(req.files.screenshot.length + " screenshot files to ingest...");
                var screenShotInfo = {};
                for (i in req.files.screenshot) {
                  var timeStamp = req.files.screenshot[i].originalname.substr(0,req.files.screenshot[i].originalname.lastIndexOf(".png"));
                  var dateString = (new Date(parseInt(timeStamp))).toISOString().substr(0,19).replace(/:/g,"-");
                  screenShotInfo[timeStamp] = {
                     guardian_id: dbGuardian.guid,
                     checkin_id: dbCheckIn.guid,
                     version: dSoftware.number,
                     sha1Hash: hash.fileSha1(req.files.screenshot[i].path),
                     localPath: req.files.screenshot[i].path,
                     size: fs.statSync(req.files.screenshot[i].path).size,
                     origin_id: timeStamp,
                     timeStamp: new Date(parseInt(timeStamp)),
                     isSaved: false,
                     s3Path: "/"+process.env.NODE_ENV
                              +"/"+dateString.substr(0,7)+"/"+dateString.substr(8,2)
                              +"/"+dbGuardian.guid
                              +"/"+dbGuardian.guid+"-"+dateString+".png"
                  };

                }
                for (j in screenShotInfo) {
                  aws.s3("rfcx-meta").putFile(
                    screenShotInfo[j].localPath, screenShotInfo[j].s3Path,
                    function(err, s3Res){
                      s3Res.resume();
                      if (!!err) {
                        console.log(err);
                      } else if (200 == s3Res.statusCode) {
                        for (l in screenShotInfo) {
                          if (s3Res.req.url.indexOf(screenShotInfo[l].s3Path) >= 0) {
                            screenShotInfo[l].isSaved = true;
                            console.log("screenshot saved: "+screenShotInfo[l].timeStamp);
                          }
                        }
                      }
                  });
                }
            }

            // save audio files
            if (!!req.files.audio) {
              if (!util.isArray(req.files.audio)) { req.files.audio = [req.files.audio]; }
              var audioMeta = [];
              if (json.audio != null) { audioMeta = json.audio.split("|"); }
              if (audioMeta.length == req.files.audio.length) {
                console.log(req.files.audio.length + " audio files to ingest...");
                var audioInfo = {};
                for (i in req.files.audio) {
                  audioMeta[i] = audioMeta[i].split("*");
                  var timeStamp = audioMeta[i][1];
                  audioMeta[i][1] = new Date(parseInt(audioMeta[i][1]));
                  var dateString = audioMeta[i][1].toISOString().substr(0,19).replace(/:/g,"-");
                  audioInfo[timeStamp] = {
                    guardian_id: dbGuardian.guid,
                    checkin_id: dbCheckIn.guid,
                    version: dSoftware.number,
                    battery_temperature: dbCheckIn.battery_temperature,
                    sha1Hash: hash.fileSha1(req.files.audio[i].path),
                    localPath: req.files.audio[i].path,
                    size: fs.statSync(req.files.audio[i].path).size,
                    timeStamp: timeStamp,
                    measured_at: audioMeta[i][1],
                    isSaved: { db: false, s3: false, sqs: false },
                    s3Path: "/"+process.env.NODE_ENV
                            +"/"+dateString.substr(0,7)+"/"+dateString.substr(8,2)
                            +"/"+dbGuardian.guid
                            +"/"+dbGuardian.guid+"-"+dateString
                                +req.files.audio[i].originalname.substr(req.files.audio[i].originalname.indexOf("."))
                  };
                }

                for (j in audioInfo) {

                  models.GuardianAudio.create({
                    guardian_id: dbGuardian.id,
                    check_in_id: dbCheckIn.id,
                    sha1_checksum: audioInfo[j].sha1Hash,
                    url: "s3://rfcx-ark"+audioInfo[j].s3Path,
                    size: audioInfo[j].size,
                    measured_at: audioInfo[j].measured_at
                  }).then(function(dbAudio){

                    for (k in audioInfo) {
                      if (audioInfo[k].sha1Hash === dbAudio.sha1_checksum) {

                        audioInfo[k].isSaved.db = true;
                        audioInfo[k].audio_id = dbAudio.guid;

                        console.log("uploading file to s3: "+audioInfo[k].audio_id);

                        aws.s3("rfcx-ark").putFile(
                          audioInfo[k].localPath, audioInfo[k].s3Path,
                          function(err, s3Res){
                            s3Res.resume();
                            if (!!err) {
                              console.log(err);
                            } else if (200 == s3Res.statusCode) {
                              for (l in audioInfo) {
                                if (s3Res.req.url.indexOf(audioInfo[l].s3Path) >= 0) {
                                  audioInfo[l].isSaved.s3 = true;

                                  console.log("adding job to sns/sqs ingestion queue: "+audioInfo[l].audio_id);
                                  audioInfo[l].measured_at = audioInfo[l].measured_at.toISOString();

                                  aws.sns().publish({
                                      TopicArn: aws.snsTopicArn("rfcx-analysis"),
                                      Message: JSON.stringify(audioInfo[l])
                                    }, function(err, data) {
                                      if (!!err) {
                                        console.log(err);
                                      } else {
                                        var isComplete = true;

                                        for (m in audioInfo) {
                                          if (!audioInfo[m].isSaved.sqs) { isComplete = false; }
                                          returnJson.audio.push({
                                            id: m,
                                            guid: audioInfo[m].audio_id
                                          });
                                        }
                                        for (n in screenShotInfo) {
                                          if (screenShotInfo[n].isSaved) {
                                            returnJson.screenshots.push({
                                              id: n,
                                              guid: null
                                            });
                                          }
                                        }
                                        for (o in messageInfo) {
                                          if (messageInfo[o].isSaved) {
                                            returnJson.messages.push({
                                              digest: messageInfo[o].digest,
                                              guid: null
                                            });
                                          }
                                        }
                                        if (isComplete) {

                                          dbCheckIn.request_latency_api = (new Date()).valueOf()-requestStartTime;
                                          dbCheckIn.save();

                                          if (verbose_logging) { console.log(returnJson); }
                                          res.status(200).json(returnJson);

                                          for (m in audioInfo) { audioInfo[m].isSaved.sqs = false; }
                                        }
                                      }
                                  });
                                  fs.unlink(audioInfo[l].localPath,function(e){if(e){console.log(e);}});
                                  audioInfo[l].isSaved.sqs = true;
                                }
                              }
                            }
                        });
                      }
                    }
                  }).catch(function(err){
                    console.log("error adding audio to database | "+err);
                    if (!!err) { res.status(500).json({msg:"error adding audio to database"}); }
                  });
                }
              } else {
                console.log("couldn't match audio meta to uploaded content | "+audioMeta);
                if (!!err) { res.status(500).json({msg:"couldn't match audio meta to uploaded content"}); }
              }
            } else {
              console.log("no audio files detected");
              dbCheckIn.request_latency_api = (new Date()).valueOf()-requestStartTime;
              dbCheckIn.save();
              res.status(200).json(returnJson);
            }

          }).catch(function(err){
            console.log("error adding checkin to database | "+err);
            if (!!err) { res.status(500).json({msg:"error adding checkin to database"}); }
          });
        }).catch(function(err){
          console.log("failed to update version of guardian | "+err);
          if (!!err) { res.status(500).json({msg:"failed to update version of guardian"}); }
        });
    });
  })
;

router.route("/:guardian_id/checkins/:checkin_id")
  .get(function(req, res) {

    models.Guardian
      .findAll( { where: { guid: req.params.guardian_id } })
      .spread(function(dbGuardian){

        res.status(200).json(dbGuardian);

    }).catch(function(err){
        console.log("failed to find guardian | "+err);
    });
  })
;

module.exports = router;

// Special Functions

function strArrToAvg(str,delimA,delimB) {
  if (str == null) { return null; }
  try {
    var ttl = 0, arr = str.split(delimA);
    if (arr.length > 0) {
      for (i in arr) {
        ttl = ttl + parseInt(arr[i].split(delimB)[1]);
      }
      return Math.round(ttl/arr.length);
    } else {
      return null;
    }
  } catch(e) {
    console.log(e);
    return null;
  }
}

function strArrToJSArr(str,delimA,delimB) {
  if (str == null) { return []; }
  try {
    var rtrnArr = [], arr = str.split(delimA);
    if (arr.length > 0) {
      for (i in arr) {
        rtrnArr.push(arr[i].split(delimB));
      }
      return rtrnArr;
    } else {
      return [];
    }
  } catch(e) {
    console.log(e);
    return [];
  }
}

<|endoftext|>


<commit_before>/**
 * Grid is a region which contains at most 4 cartesian systems
 *
 * TODO Default cartesian
 */
define(function(require, factory) {

    var layout = require('../../util/layout');
    var axisHelper = require('../../coord/axisHelper');

    var zrUtil = require('zrender/core/util');
    var Cartesian2D = require('./Cartesian2D');
    var Axis2D = require('./Axis2D');

    var each = zrUtil.each;

    var ifAxisCrossZero = axisHelper.ifAxisCrossZero;
    var ifAxisNeedsCrossZero = axisHelper.ifAxisNeedsCrossZero;
    var niceScaleExtent = axisHelper.niceScaleExtent;

    // 依赖 GridModel, AxisModel 做预处理
    require('./GridModel');

    /**
     * Check if the axis is used in the specified grid
     * @inner
     */
    function isAxisUsedInTheGrid(axisModel, gridModel, ecModel) {
        return ecModel.getComponent('grid', axisModel.get('gridIndex')) === gridModel;
    }

    function getLabelUnionRect(axis) {
        var axisModel = axis.model;
        var labels = axisModel.getFormattedLabels();
        var rect;
        for (var i = 0; i < labels.length; i++) {
            if (!axis.isLabelIgnored(i)) {
                var singleRect = axisModel.getTextRect(labels[i]);
                rect ? rect.union(singleRect) : (rect = singleRect);
            }
        }
        return rect;
    }

    function Grid(gridModel, ecModel, api) {
        /**
         * @type {Object.<string, module:echarts/coord/cartesian/Cartesian2D>}
         * @private
         */
        this._coordsMap = {};

        /**
         * @type {Array.<module:echarts/coord/cartesian/Cartesian>}
         * @private
         */
        this._coordsList = [];

        /**
         * @type {Object.<string, module:echarts/coord/cartesian/Axis2D>}
         * @private
         */
        this._axesMap = {};

        /**
         * @type {Array.<module:echarts/coord/cartesian/Axis2D>}
         * @private
         */
        this._axesList = [];

        this._initCartesian(gridModel, ecModel, api);
    }

    var gridProto = Grid.prototype;

    gridProto.type = 'grid';

    gridProto.getRect = function () {
        return this._rect;
    };

    /**
     * Resize the grid
     * @param {module:echarts/coord/cartesian/GridModel} gridModel
     * @param {module:echarts/ExtensionAPI} api
     */
    gridProto.resize = function (gridModel, api) {

        var gridRect = layout.parsePositionInfo({
            x: gridModel.get('x'),
            y: gridModel.get('y'),
            x2: gridModel.get('x2'),
            y2: gridModel.get('y2'),
            width: gridModel.get('width'),
            height: gridModel.get('height')
        }, {
            width: api.getWidth(),
            height: api.getHeight()
        });

        this._rect = gridRect;

        var axesList = this._axesList;

        adjustAxes();

        // Minus label size
        if (gridModel.get('containLabel')) {
            each(axesList, function (axis) {
                var labelUnionRect = getLabelUnionRect(axis);
                if (labelUnionRect) {
                    var dim = axis.isHorizontal() ? 'height' : 'width';
                    var margin = axis.model.get('axisLabel.margin');
                    gridRect[dim] -= labelUnionRect[dim] + margin;
                    if (axis.position === 'top') {
                        gridRect.y += labelUnionRect.height + margin;
                    }
                    else if (axis.position === 'left')  {
                        gridRect.x += labelUnionRect.width + margin;
                    }
                }
            });

            adjustAxes();
        }

        function adjustAxes() {
            each(axesList, function (axis) {
                var isHorizontal = axis.isHorizontal();
                var extent = isHorizontal ? [0, gridRect.width] : [0, gridRect.height];
                var idx = axis.inverse ? 1 : 0;
                axis.setExtent(extent[idx], extent[1 - idx]);
                updateAxisTransfrom(axis, isHorizontal ? gridRect.x : gridRect.y);
            });
        }
    };

    /**
     * @param {string} axisType
     * @param {number} [axisIndex]
     */
    gridProto.getAxis = function (axisType, axisIndex) {
        if (axisIndex != null) {
            var key = axisType + axisIndex;
            return this._axesMap[key];
        }
        else {
            // Find first axis with axisType
            var axesList = this._axesList;
            for (var i = 0; i < axesList.length; i++) {
                if (axesList[i].dim === axisType) {
                    return axesList[i];
                }
            }
        }
    };

    gridProto.getCartesian = function (xAxisIndex, yAxisIndex) {
        var key = 'x' + xAxisIndex + 'y' + yAxisIndex;
        return this._coordsMap[key];
    };

    /**
     * Initialize cartesian coordinate systems
     * @private
     */
    gridProto._initCartesian = function (gridModel, ecModel, api) {
        var axisPositionUsed = {
            left: false,
            right: false,
            top: false,
            bottom: false
        };

        var axesMap = {
            x: {},
            y: {}
        };
        var axesCount = {
            x: 0,
            y: 0
        };

        ecModel.eachComponent('xAxis', createAxisCreator('x'), this);

        ecModel.eachComponent('yAxis', createAxisCreator('y'), this);

        if (!axesCount.x || !axesCount.y) {
            // api.log('Grid must has at least one x axis and one y axis');
            // Roll back
            this._axesMap = {};
            this._axesList = [];
            return;
        }

        each(axesMap.x, function (xAxis, xAxisIndex) {
            each(axesMap.y, function (yAxis, yAxisIndex) {
                var key = 'x' + xAxisIndex + 'y' + yAxisIndex;
                var cartesian = new Cartesian2D(key);

                cartesian.grid = this;

                this._coordsMap[key] = cartesian;
                this._coordsList.push(cartesian);

                cartesian.addAxis(xAxis);
                cartesian.addAxis(yAxis);
            }, this);
        }, this);

        this._updateCartesianFromSeries(ecModel, gridModel);

        // Fix configuration
        each(axesMap.x, function (xAxis) {
            each(axesMap.y, function (yAxis) {
                // onZero can not be used in these two situations
                // 1. When other axis is a category axis
                // 2. When other axis not across 0 point
                if (xAxis.type === 'category'
                    || !ifAxisCrossZero(xAxis)
                ) {
                    yAxis.onZero = false;
                }
                if (yAxis.type === 'category'
                  || !ifAxisCrossZero(yAxis)
                ) {
                    xAxis.onZero = false;
                }

                if (ifAxisNeedsCrossZero(yAxis)) {
                    yAxis.scale.unionExtent([0, 0]);
                }
                if (ifAxisNeedsCrossZero(xAxis)) {
                    xAxis.scale.unionExtent([0, 0]);
                }
                niceScaleExtent(yAxis, yAxis.model);
                niceScaleExtent(xAxis, xAxis.model);

            }, this);
        }, this);

        function createAxisCreator(axisType) {
            return function (axisModel, idx) {
                if (!isAxisUsedInTheGrid(axisModel, gridModel, ecModel)) {
                    return;
                }

                var axisPosition = axisModel.get('position');
                if (axisType === 'x') {
                    // Fix position
                    if (axisPosition !== 'top' && axisPosition !== 'bottom') {
                        // Default bottom of X
                        axisPosition = 'bottom';
                    }
                    if (axisPositionUsed[axisPosition]) {
                        axisPosition = axisPosition === 'top' ? 'bottom' : 'top';
                    }
                }
                else {
                    // Fix position
                    if (axisPosition !== 'left' && axisPosition !== 'right') {
                        // Default left of Y
                        axisPosition = 'left';
                    }
                    if (axisPositionUsed[axisPosition]) {
                        axisPosition = axisPosition === 'left' ? 'right' : 'left';
                    }
                }
                axisPositionUsed[axisPosition] = true;

                var axis = new Axis2D(
                    axisType, axisHelper.createScaleByModel(axisModel),
                    [0, 0],
                    axisModel.get('type'),
                    axisPosition
                );

                var isCategory = axis.type === 'category';
                axis.onBand = isCategory && axisModel.get('boundaryGap');
                axis.inverse = axisModel.get('inverse');

                axis.onZero = axisModel.get('axisLine.onZero');

                // Inject axis into axisModel
                axisModel.axis = axis;

                // Inject axisModel into axis
                axis.model = axisModel;

                this._axesList.push(axis);
                this._axesMap[axisType + idx] = axis;

                axesMap[axisType][idx] = axis;
                axesCount[axisType]++;
            };
        }
    };

    /**
     * Update cartesian properties from series
     * @param  {module:echarts/model/Option} option
     * @private
     */
    gridProto._updateCartesianFromSeries = function (ecModel, gridModel) {
        ecModel.eachSeries(function (seriesModel) {
            if (seriesModel.get('coordinateSystem') === 'cartesian2d') {
                var xAxisIndex = seriesModel.get('xAxisIndex');
                var yAxisIndex = seriesModel.get('yAxisIndex');

                var xAxisModel = ecModel.getComponent('xAxis', xAxisIndex);
                var yAxisModel = ecModel.getComponent('yAxis', yAxisIndex);

                if (!isAxisUsedInTheGrid(xAxisModel, gridModel, ecModel)
                    || !isAxisUsedInTheGrid(yAxisModel, gridModel, ecModel)
                 ) {
                    return;
                }

                var cartesian = this.getCartesian(xAxisIndex, yAxisIndex);

                var data = seriesModel.getData();
                if (data.type === 'list') {
                    var xAxis = cartesian.getAxis('x');
                    var yAxis = cartesian.getAxis('y');
                    xAxis.scale.unionExtent(
                        data.getDataExtent('x', xAxis.scale.type !== 'ordinal')
                    );
                    yAxis.scale.unionExtent(
                        data.getDataExtent('y', yAxis.scale.type !== 'ordinal')
                    );
                }
            }
        }, this);
    };

    /**
     * @inner
     */
    function updateAxisTransfrom(axis, coordBase) {
        var axisExtent = axis.getExtent();
        var axisExtentSum = axisExtent[0] + axisExtent[1];

        // Fast transform
        axis.toGlobalCoord = axis.dim === 'x'
            ? function (coord) {
                return coord + coordBase;
            }
            : function (coord) {
                return axisExtentSum - coord + coordBase;
            };
        axis.toLocalCoord = axis.dim === 'x'
            ? function (coord) {
                return coord - coordBase;
            }
            : function (coord) {
                return axisExtentSum - coord + coordBase;
            };
    }

    Grid.create = function (ecModel, api) {
        var grids = [];
        ecModel.eachComponent('grid', function (gridModel, idx) {
            var grid = new Grid(gridModel, ecModel, api);
            grid.name = 'grid_' + idx;
            grid.resize(gridModel, api);

            gridModel.coordinateSystem = grid;

            grids.push(grid);
        });

        // Inject the coordinateSystems into seriesModel
        ecModel.eachSeries(function (seriesModel) {
            if (seriesModel.get('coordinateSystem') !== 'cartesian2d') {
                return;
            }
            var xAxisIndex = seriesModel.get('xAxisIndex');
            // TODO Validate
            var xAxisModel = ecModel.getComponent('xAxis', xAxisIndex);
            var grid = grids[xAxisModel.get('gridIndex')];
            seriesModel.coordinateSystem = grid.getCartesian(
                xAxisIndex, seriesModel.get('yAxisIndex')
            );
        });

        return grids;
    };

    require('../../CoordinateSystem').register('grid', Grid);

    return Grid;
});<commit_msg>Grid containLabel exclude inside axisLabel<commit_after>/**
 * Grid is a region which contains at most 4 cartesian systems
 *
 * TODO Default cartesian
 */
define(function(require, factory) {

    var layout = require('../../util/layout');
    var axisHelper = require('../../coord/axisHelper');

    var zrUtil = require('zrender/core/util');
    var Cartesian2D = require('./Cartesian2D');
    var Axis2D = require('./Axis2D');

    var each = zrUtil.each;

    var ifAxisCrossZero = axisHelper.ifAxisCrossZero;
    var ifAxisNeedsCrossZero = axisHelper.ifAxisNeedsCrossZero;
    var niceScaleExtent = axisHelper.niceScaleExtent;

    // 依赖 GridModel, AxisModel 做预处理
    require('./GridModel');

    /**
     * Check if the axis is used in the specified grid
     * @inner
     */
    function isAxisUsedInTheGrid(axisModel, gridModel, ecModel) {
        return ecModel.getComponent('grid', axisModel.get('gridIndex')) === gridModel;
    }

    function getLabelUnionRect(axis) {
        var axisModel = axis.model;
        var labels = axisModel.getFormattedLabels();
        var rect;
        for (var i = 0; i < labels.length; i++) {
            if (!axis.isLabelIgnored(i)) {
                var singleRect = axisModel.getTextRect(labels[i]);
                rect ? rect.union(singleRect) : (rect = singleRect);
            }
        }
        return rect;
    }

    function Grid(gridModel, ecModel, api) {
        /**
         * @type {Object.<string, module:echarts/coord/cartesian/Cartesian2D>}
         * @private
         */
        this._coordsMap = {};

        /**
         * @type {Array.<module:echarts/coord/cartesian/Cartesian>}
         * @private
         */
        this._coordsList = [];

        /**
         * @type {Object.<string, module:echarts/coord/cartesian/Axis2D>}
         * @private
         */
        this._axesMap = {};

        /**
         * @type {Array.<module:echarts/coord/cartesian/Axis2D>}
         * @private
         */
        this._axesList = [];

        this._initCartesian(gridModel, ecModel, api);
    }

    var gridProto = Grid.prototype;

    gridProto.type = 'grid';

    gridProto.getRect = function () {
        return this._rect;
    };

    /**
     * Resize the grid
     * @param {module:echarts/coord/cartesian/GridModel} gridModel
     * @param {module:echarts/ExtensionAPI} api
     */
    gridProto.resize = function (gridModel, api) {

        var gridRect = layout.parsePositionInfo({
            x: gridModel.get('x'),
            y: gridModel.get('y'),
            x2: gridModel.get('x2'),
            y2: gridModel.get('y2'),
            width: gridModel.get('width'),
            height: gridModel.get('height')
        }, {
            width: api.getWidth(),
            height: api.getHeight()
        });

        this._rect = gridRect;

        var axesList = this._axesList;

        adjustAxes();

        // Minus label size
        if (gridModel.get('containLabel')) {
            each(axesList, function (axis) {
                if (!axis.model.get('axisLabel.inside')) {
                    var labelUnionRect = getLabelUnionRect(axis);
                    if (labelUnionRect) {
                        var dim = axis.isHorizontal() ? 'height' : 'width';
                        var margin = axis.model.get('axisLabel.margin');
                        gridRect[dim] -= labelUnionRect[dim] + margin;
                        if (axis.position === 'top') {
                            gridRect.y += labelUnionRect.height + margin;
                        }
                        else if (axis.position === 'left')  {
                            gridRect.x += labelUnionRect.width + margin;
                        }
                    }
                }
            });

            adjustAxes();
        }

        function adjustAxes() {
            each(axesList, function (axis) {
                var isHorizontal = axis.isHorizontal();
                var extent = isHorizontal ? [0, gridRect.width] : [0, gridRect.height];
                var idx = axis.inverse ? 1 : 0;
                axis.setExtent(extent[idx], extent[1 - idx]);
                updateAxisTransfrom(axis, isHorizontal ? gridRect.x : gridRect.y);
            });
        }
    };

    /**
     * @param {string} axisType
     * @param {number} [axisIndex]
     */
    gridProto.getAxis = function (axisType, axisIndex) {
        if (axisIndex != null) {
            var key = axisType + axisIndex;
            return this._axesMap[key];
        }
        else {
            // Find first axis with axisType
            var axesList = this._axesList;
            for (var i = 0; i < axesList.length; i++) {
                if (axesList[i].dim === axisType) {
                    return axesList[i];
                }
            }
        }
    };

    gridProto.getCartesian = function (xAxisIndex, yAxisIndex) {
        var key = 'x' + xAxisIndex + 'y' + yAxisIndex;
        return this._coordsMap[key];
    };

    /**
     * Initialize cartesian coordinate systems
     * @private
     */
    gridProto._initCartesian = function (gridModel, ecModel, api) {
        var axisPositionUsed = {
            left: false,
            right: false,
            top: false,
            bottom: false
        };

        var axesMap = {
            x: {},
            y: {}
        };
        var axesCount = {
            x: 0,
            y: 0
        };

        ecModel.eachComponent('xAxis', createAxisCreator('x'), this);

        ecModel.eachComponent('yAxis', createAxisCreator('y'), this);

        if (!axesCount.x || !axesCount.y) {
            // api.log('Grid must has at least one x axis and one y axis');
            // Roll back
            this._axesMap = {};
            this._axesList = [];
            return;
        }

        each(axesMap.x, function (xAxis, xAxisIndex) {
            each(axesMap.y, function (yAxis, yAxisIndex) {
                var key = 'x' + xAxisIndex + 'y' + yAxisIndex;
                var cartesian = new Cartesian2D(key);

                cartesian.grid = this;

                this._coordsMap[key] = cartesian;
                this._coordsList.push(cartesian);

                cartesian.addAxis(xAxis);
                cartesian.addAxis(yAxis);
            }, this);
        }, this);

        this._updateCartesianFromSeries(ecModel, gridModel);

        // Fix configuration
        each(axesMap.x, function (xAxis) {
            each(axesMap.y, function (yAxis) {
                // onZero can not be used in these two situations
                // 1. When other axis is a category axis
                // 2. When other axis not across 0 point
                if (xAxis.type === 'category'
                    || !ifAxisCrossZero(xAxis)
                ) {
                    yAxis.onZero = false;
                }
                if (yAxis.type === 'category'
                  || !ifAxisCrossZero(yAxis)
                ) {
                    xAxis.onZero = false;
                }

                if (ifAxisNeedsCrossZero(yAxis)) {
                    yAxis.scale.unionExtent([0, 0]);
                }
                if (ifAxisNeedsCrossZero(xAxis)) {
                    xAxis.scale.unionExtent([0, 0]);
                }
                niceScaleExtent(yAxis, yAxis.model);
                niceScaleExtent(xAxis, xAxis.model);

            }, this);
        }, this);

        function createAxisCreator(axisType) {
            return function (axisModel, idx) {
                if (!isAxisUsedInTheGrid(axisModel, gridModel, ecModel)) {
                    return;
                }

                var axisPosition = axisModel.get('position');
                if (axisType === 'x') {
                    // Fix position
                    if (axisPosition !== 'top' && axisPosition !== 'bottom') {
                        // Default bottom of X
                        axisPosition = 'bottom';
                    }
                    if (axisPositionUsed[axisPosition]) {
                        axisPosition = axisPosition === 'top' ? 'bottom' : 'top';
                    }
                }
                else {
                    // Fix position
                    if (axisPosition !== 'left' && axisPosition !== 'right') {
                        // Default left of Y
                        axisPosition = 'left';
                    }
                    if (axisPositionUsed[axisPosition]) {
                        axisPosition = axisPosition === 'left' ? 'right' : 'left';
                    }
                }
                axisPositionUsed[axisPosition] = true;

                var axis = new Axis2D(
                    axisType, axisHelper.createScaleByModel(axisModel),
                    [0, 0],
                    axisModel.get('type'),
                    axisPosition
                );

                var isCategory = axis.type === 'category';
                axis.onBand = isCategory && axisModel.get('boundaryGap');
                axis.inverse = axisModel.get('inverse');

                axis.onZero = axisModel.get('axisLine.onZero');

                // Inject axis into axisModel
                axisModel.axis = axis;

                // Inject axisModel into axis
                axis.model = axisModel;

                this._axesList.push(axis);
                this._axesMap[axisType + idx] = axis;

                axesMap[axisType][idx] = axis;
                axesCount[axisType]++;
            };
        }
    };

    /**
     * Update cartesian properties from series
     * @param  {module:echarts/model/Option} option
     * @private
     */
    gridProto._updateCartesianFromSeries = function (ecModel, gridModel) {
        ecModel.eachSeries(function (seriesModel) {
            if (seriesModel.get('coordinateSystem') === 'cartesian2d') {
                var xAxisIndex = seriesModel.get('xAxisIndex');
                var yAxisIndex = seriesModel.get('yAxisIndex');

                var xAxisModel = ecModel.getComponent('xAxis', xAxisIndex);
                var yAxisModel = ecModel.getComponent('yAxis', yAxisIndex);

                if (!isAxisUsedInTheGrid(xAxisModel, gridModel, ecModel)
                    || !isAxisUsedInTheGrid(yAxisModel, gridModel, ecModel)
                 ) {
                    return;
                }

                var cartesian = this.getCartesian(xAxisIndex, yAxisIndex);

                var data = seriesModel.getData();
                if (data.type === 'list') {
                    var xAxis = cartesian.getAxis('x');
                    var yAxis = cartesian.getAxis('y');
                    xAxis.scale.unionExtent(
                        data.getDataExtent('x', xAxis.scale.type !== 'ordinal')
                    );
                    yAxis.scale.unionExtent(
                        data.getDataExtent('y', yAxis.scale.type !== 'ordinal')
                    );
                }
            }
        }, this);
    };

    /**
     * @inner
     */
    function updateAxisTransfrom(axis, coordBase) {
        var axisExtent = axis.getExtent();
        var axisExtentSum = axisExtent[0] + axisExtent[1];

        // Fast transform
        axis.toGlobalCoord = axis.dim === 'x'
            ? function (coord) {
                return coord + coordBase;
            }
            : function (coord) {
                return axisExtentSum - coord + coordBase;
            };
        axis.toLocalCoord = axis.dim === 'x'
            ? function (coord) {
                return coord - coordBase;
            }
            : function (coord) {
                return axisExtentSum - coord + coordBase;
            };
    }

    Grid.create = function (ecModel, api) {
        var grids = [];
        ecModel.eachComponent('grid', function (gridModel, idx) {
            var grid = new Grid(gridModel, ecModel, api);
            grid.name = 'grid_' + idx;
            grid.resize(gridModel, api);

            gridModel.coordinateSystem = grid;

            grids.push(grid);
        });

        // Inject the coordinateSystems into seriesModel
        ecModel.eachSeries(function (seriesModel) {
            if (seriesModel.get('coordinateSystem') !== 'cartesian2d') {
                return;
            }
            var xAxisIndex = seriesModel.get('xAxisIndex');
            // TODO Validate
            var xAxisModel = ecModel.getComponent('xAxis', xAxisIndex);
            var grid = grids[xAxisModel.get('gridIndex')];
            seriesModel.coordinateSystem = grid.getCartesian(
                xAxisIndex, seriesModel.get('yAxisIndex')
            );
        });

        return grids;
    };

    require('../../CoordinateSystem').register('grid', Grid);

    return Grid;
});<|endoftext|>


<commit_before>'use strict';

var gulp = require('gulp');
var runSequence = require('run-sequence');
var browserSync = require('browser-sync');

var eyeglass = require('eyeglass');
var sass = require('gulp-sass');
var sourcemaps = require('gulp-sourcemaps');
var prefix   = require('gulp-autoprefixer');
var plumber  = require('gulp-plumber');
var notify   = require('gulp-notify');
var rename   = require('gulp-rename');
var del      = require('del');
var clone    = require('gulp-clone');
var merge    = require('merge-stream');
var size     = require('gulp-size');

var postcss = require('gulp-postcss');
var cssnano = require('cssnano');
var flexibility = require('postcss-flexibility');
var uglify  = require('gulp-uglify');

var jade = require('gulp-jade');
var md = require('jstransformer')(require('jstransformer-markdown-it'));
var shorthash = require('shorthash');
var _ = require('lodash');

var affected = require('gulp-jade-find-affected');
var jadeInheritance = require('gulp-jade-inheritance');
var changed = require('gulp-changed');
var cached = require('gulp-cached');
var gulpif = require('gulp-if');

var vfs = require('vinyl-fs');
var symlink = require('gulp-symlink');
var path = require('path');
var exec = require('child_process').exec;

var sitemap = require('gulp-sitemap');
var argv = require('minimist')(process.argv.slice(2));
// var buildPath = './docs';
var buildPath = './dist';
var buildCss = buildPath + '/css';
var rbDir = './rocketbelt';
var siteDir = './site';

var nav = [];

gulp.task('default', function (done) {
  runSequence('build', ['server', 'watch'], done);
});

gulp.task('server', function () {
  return browserSync.init({
    server: {
      injectChanges: true,
      baseDir: buildPath
    }
  });
});

gulp.task('watch', function(){
  global.isWatching = true;

  gulp.watch(['./rocketbelt/**/*.scss', './templates/scss/**/*.scss'], ['styles']);
  gulp.watch('./templates/**/*', ['views']);
  gulp.watch(['./rocketbelt/**/*.js'], ['uglify']);
  gulp.watch(buildPath + '/**/*.html').on('change', debounce(browserSync.reload, 500));
  gulp.watch(buildPath + '/**/*.js').on('change', debounce(browserSync.reload, 500));
});

var sizeOptions = {
  showFiles: true,
  gzip: true
};

gulp.task('uglify', function () {
  return gulp.src(['./rocketbelt/**/*.js', '!./rocketbelt/**/*.min.js'])
    .pipe(changed(buildPath))
    .pipe(rename({ suffix: '.min' }))
    .pipe(sourcemaps.init())
    .pipe(uglify())
    .pipe(size(sizeOptions))
    .pipe(sourcemaps.write('.', { sourceRoot: '.' }))
    .pipe(gulp.dest(buildPath))
  ;
});

gulp.task('styles', function () {
  var source = gulp.src(['./rocketbelt/**/*.scss', './templates/scss/**/*.scss'])
    .pipe(plumber({ errorHandler: notify.onError('Error: <%= error.message %>') }))
    .pipe(sourcemaps.init())
    .pipe(sass(eyeglass()))
    .pipe(prefix({
      browsers: ['last 5 Chrome versions',
                 'last 5 Firefox versions',
                 'Safari >= 9',
                 'ie >= 9',
                 'Edge >= 1',
                 'iOS >= 8',
                 'Android >= 4.4']
    }))
    .pipe(postcss([flexibility()]))
  ;

  var max = source.pipe(clone())
    .pipe(sourcemaps.write('.', { sourceRoot: null }))
    .pipe(gulp.dest(buildCss))
  ;

  var min = source.pipe(clone())
    .pipe(sourcemaps.init())
    .pipe(rename({ suffix: '.min' }))
    .pipe(postcss([cssnano()]))
    .pipe(size(sizeOptions))
    .pipe(sourcemaps.write('.', { sourceRoot: null }))
    .pipe(gulp.dest(buildCss))
    .pipe(browserSync.stream({ match: '**/*.css' }))
  ;

  return merge(max, min);
});

gulp.task('clean', ['link:clean'], function () {
  del(['**/*/.DS_Store']);
  del([buildPath]);
});

gulp.task('build', function (done) {
  if (!argv.release) {
    runSequence('uglify', 'link', ['styles', 'views'], done);
  }
  else {
    runSequence('uglify', 'link', ['styles', 'views'], 'del-assets', done);
  }
});

gulp.task('del-assets', function () {
  if (argv.release) {
    del(['rocketbelt/', 'templates/', 'docs/']);
  }
});

gulp.task('js:site:copy', function () {
  vfs.src(['./templates/**/*.js'])
    .pipe(changed(buildPath))
    .pipe(vfs.dest(buildPath, { overwrite: true }));
});

gulp.task('img:site:copy', function () {
  vfs.src(['./templates/**/img/*.*'])
    .pipe(changed(buildPath))
    .pipe(vfs.dest(buildPath, { overwrite: true }));
});

gulp.task('link', ['link:partials', 'link:js']);

gulp.task('link:partials', function () {
  return gulp.src('./rocketbelt/**/_*.jade')
    .pipe(symlink(function (file) {
      return path.join('./templates', file.relative);
    }, { force: true, log: false }));
  // TODO: Using gulp-symlink because relative symlinks are broken in vfs.
  // Should be fixed in vfs 3.0 and the above should be replaced with the following:
  // return vfs.src('./rocketbelt/**/_*.jade')
  //   .pipe(vfs.symlink('./templates', { relative: true }));
});

gulp.task('link:js', function () {
  return gulp.src(['./rocketbelt/**/*.js', './rocketbelt/**/*.json', '!./**/slipsum-cache.json'])
    .pipe(symlink(function (file) {
      return path.join('./templates', file.relative);
    }, { force: true, log: false }));
  // TODO: Using gulp-symlink because relative symlinks are broken in vfs.
  // Should be fixed in vfs 3.0 and the above should be replaced with the following:
  // return vfs.src('./rocketbelt/**/*.js', './rocketbelt/**/*.json')
  //   .pipe(vfs.symlink('./templates', { relative: true }));
});

gulp.task('link:clean', function () {
  exec('find ./templates -type l -delete', function (err, stdout, stderr) { });
});

gulp.task('views', ['js:site:copy', 'img:site:copy'], function () {
  var dir = './templates';
  directoryTreeToObj(dir, function (err, res) {
    if (err)
      console.error(err);

    var colorFamilies = require('./templates/base/color/_color-families.json');

    return gulp.src(['./templates/**/*.jade', '!./templates/**/_*.jade'])
      .pipe(plumber({ errorHandler: notify.onError('Error: <%= error.message %>') }))
      .pipe(affected())
      .pipe(jadeInheritance({ basedir: dir }))
      .pipe(jade({
        basedir: __dirname + '/templates',
        pretty: true,
        md: md,
        locals: {
          buildPath: argv.release ? '' : '',
          nav: res,
          colorFamilies: colorFamilies,
          shorthash: shorthash,
          _: _
        }
      }))
      .pipe(sitemap({
            siteUrl: 'http://rocketbelt.io'
        }))
      .pipe(gulp.dest(buildPath))
    ;
  });
});

var directoryTreeToObj = function(dir, done) {
  var fs = require('fs');
  var path = require('path');
  var results = [];

  fs.readdir(dir, function(err, files) {
    if (err)
      return done(err);

    files = files.filter(function (file) {
      if (fs.lstatSync(dir + '/' + file).isDirectory()) {
        if (file === 'js' || file === 'scss') return false;
      }
      return (file.indexOf('_') !== 0) && (file.indexOf('.js') == -1);
    });

    var pending = files.length;

    if (!pending)
      return done(null, { name: path.basename(dir), type: 'folder', children: results });

    files.forEach(function(file) {
      file = path.resolve(dir, file);
      fs.stat(file, function(err, stat) {
        if ( true /* file.indexOf('/img') === -1  */) {
          if (stat && stat.isDirectory() ) {
            directoryTreeToObj(file, function(err, res) {
              results.push({
                name: path.basename(file),
                type: 'folder',
                children: res
              });
              if (!--pending)
                done(null, results);
            });
          }
          else {
            results.push({
              type: 'file',
              name: path.basename(file)
            });
            if (!--pending)
              done(null, results);
          }
        }
      });
    });
  });
};

function throttle (callback, limit) {
    var wait = false;
    return function () {
        if (!wait) {
            callback.call();
            wait = true;
            setTimeout(function () {
                wait = false;
            }, limit);
        }
    }
}

function debounce(func, wait, immediate) {
	var timeout;
	return function() {
		var context = this, args = arguments;
		var later = function() {
			timeout = null;
			if (!immediate) func.apply(context, args);
		};
		var callNow = immediate && !timeout;
		clearTimeout(timeout);
		timeout = setTimeout(later, wait);
		if (callNow) func.apply(context, args);
	};
}
<commit_msg>Add sitemap build task.<commit_after>'use strict';

var gulp = require('gulp');
var runSequence = require('run-sequence');
var browserSync = require('browser-sync');

var eyeglass = require('eyeglass');
var sass = require('gulp-sass');
var sourcemaps = require('gulp-sourcemaps');
var prefix   = require('gulp-autoprefixer');
var plumber  = require('gulp-plumber');
var notify   = require('gulp-notify');
var rename   = require('gulp-rename');
var del      = require('del');
var clone    = require('gulp-clone');
var merge    = require('merge-stream');
var size     = require('gulp-size');

var postcss = require('gulp-postcss');
var cssnano = require('cssnano');
var flexibility = require('postcss-flexibility');
var uglify  = require('gulp-uglify');

var jade = require('gulp-jade');
var md = require('jstransformer')(require('jstransformer-markdown-it'));
var shorthash = require('shorthash');
var _ = require('lodash');

var affected = require('gulp-jade-find-affected');
var jadeInheritance = require('gulp-jade-inheritance');
var changed = require('gulp-changed');
var cached = require('gulp-cached');
var gulpif = require('gulp-if');

var vfs = require('vinyl-fs');
var symlink = require('gulp-symlink');
var path = require('path');
var exec = require('child_process').exec;

var sitemap = require('gulp-sitemap');
var argv = require('minimist')(process.argv.slice(2));
// var buildPath = './docs';
var buildPath = './dist';
var buildCss = buildPath + '/css';
var rbDir = './rocketbelt';
var siteDir = './site';

var nav = [];

gulp.task('default', function (done) {
  runSequence('build', ['server', 'watch'], done);
});

gulp.task('server', function () {
  return browserSync.init({
    server: {
      injectChanges: true,
      baseDir: buildPath
    }
  });
});

gulp.task('watch', function(){
  global.isWatching = true;

  gulp.watch(['./rocketbelt/**/*.scss', './templates/scss/**/*.scss'], ['styles']);
  gulp.watch('./templates/**/*', ['views']);
  gulp.watch(['./rocketbelt/**/*.js'], ['uglify']);
  gulp.watch(buildPath + '/**/*.html').on('change', debounce(browserSync.reload, 500));
  gulp.watch(buildPath + '/**/*.js').on('change', debounce(browserSync.reload, 500));
});

var sizeOptions = {
  showFiles: true,
  gzip: true
};

gulp.task('uglify', function () {
  return gulp.src(['./rocketbelt/**/*.js', '!./rocketbelt/**/*.min.js'])
    .pipe(changed(buildPath))
    .pipe(rename({ suffix: '.min' }))
    .pipe(sourcemaps.init())
    .pipe(uglify())
    .pipe(size(sizeOptions))
    .pipe(sourcemaps.write('.', { sourceRoot: '.' }))
    .pipe(gulp.dest(buildPath))
  ;
});

gulp.task('styles', function () {
  var source = gulp.src(['./rocketbelt/**/*.scss', './templates/scss/**/*.scss'])
    .pipe(plumber({ errorHandler: notify.onError('Error: <%= error.message %>') }))
    .pipe(sourcemaps.init())
    .pipe(sass(eyeglass()))
    .pipe(prefix({
      browsers: ['last 5 Chrome versions',
                 'last 5 Firefox versions',
                 'Safari >= 9',
                 'ie >= 9',
                 'Edge >= 1',
                 'iOS >= 8',
                 'Android >= 4.4']
    }))
    .pipe(postcss([flexibility()]))
  ;

  var max = source.pipe(clone())
    .pipe(sourcemaps.write('.', { sourceRoot: null }))
    .pipe(gulp.dest(buildCss))
  ;

  var min = source.pipe(clone())
    .pipe(sourcemaps.init())
    .pipe(rename({ suffix: '.min' }))
    .pipe(postcss([cssnano()]))
    .pipe(size(sizeOptions))
    .pipe(sourcemaps.write('.', { sourceRoot: null }))
    .pipe(gulp.dest(buildCss))
    .pipe(browserSync.stream({ match: '**/*.css' }))
  ;

  return merge(max, min);
});

gulp.task('clean', ['link:clean'], function () {
  del(['**/*/.DS_Store']);
  del([buildPath]);
});

gulp.task('build', function (done) {
  if (!argv.release) {
    runSequence('uglify', 'link', ['styles', 'views'], 'sitemap', done);
  }
  else {
    runSequence('uglify', 'link', ['styles', 'views'], 'sitemap', 'del-assets', done);
  }
});

gulp.task('del-assets', function () {
  if (argv.release) {
    del(['rocketbelt/', 'templates/', 'docs/']);
  }
});

gulp.task('js:site:copy', function () {
  vfs.src(['./templates/**/*.js'])
    .pipe(changed(buildPath))
    .pipe(vfs.dest(buildPath, { overwrite: true }));
});

gulp.task('img:site:copy', function () {
  vfs.src(['./templates/**/img/*.*'])
    .pipe(changed(buildPath))
    .pipe(vfs.dest(buildPath, { overwrite: true }));
});

gulp.task('link', ['link:partials', 'link:js']);

gulp.task('link:partials', function () {
  return gulp.src('./rocketbelt/**/_*.jade')
    .pipe(symlink(function (file) {
      return path.join('./templates', file.relative);
    }, { force: true, log: false }));
  // TODO: Using gulp-symlink because relative symlinks are broken in vfs.
  // Should be fixed in vfs 3.0 and the above should be replaced with the following:
  // return vfs.src('./rocketbelt/**/_*.jade')
  //   .pipe(vfs.symlink('./templates', { relative: true }));
});

gulp.task('link:js', function () {
  return gulp.src(['./rocketbelt/**/*.js', './rocketbelt/**/*.json', '!./**/slipsum-cache.json'])
    .pipe(symlink(function (file) {
      return path.join('./templates', file.relative);
    }, { force: true, log: false }));
  // TODO: Using gulp-symlink because relative symlinks are broken in vfs.
  // Should be fixed in vfs 3.0 and the above should be replaced with the following:
  // return vfs.src('./rocketbelt/**/*.js', './rocketbelt/**/*.json')
  //   .pipe(vfs.symlink('./templates', { relative: true }));
});

gulp.task('link:clean', function () {
  exec('find ./templates -type l -delete', function (err, stdout, stderr) { });
});

gulp.task('views', ['js:site:copy', 'img:site:copy'], function () {
  var dir = './templates';
  directoryTreeToObj(dir, function (err, res) {
    if (err)
      console.error(err);

    var colorFamilies = require('./templates/base/color/_color-families.json');

    return gulp.src(['./templates/**/*.jade', '!./templates/**/_*.jade'])
      .pipe(plumber({ errorHandler: notify.onError('Error: <%= error.message %>') }))
      .pipe(affected())
      .pipe(jadeInheritance({ basedir: dir }))
      .pipe(jade({
        basedir: __dirname + '/templates',
        pretty: true,
        md: md,
        locals: {
          buildPath: argv.release ? '' : '',
          nav: res,
          colorFamilies: colorFamilies,
          shorthash: shorthash,
          _: _
        }
      }))
      .pipe(gulp.dest(buildPath))
    ;
  });
});

gulp.task('sitemap', function () {
  return gulp.src(buildPath + '**/*.html', { read: false })
    .pipe(sitemap({ siteUrl: 'http://rocketbelt.io' }))
    .pipe(gulp.dest(buildPath))
  ;
});

var directoryTreeToObj = function(dir, done) {
  var fs = require('fs');
  var path = require('path');
  var results = [];

  fs.readdir(dir, function(err, files) {
    if (err)
      return done(err);

    files = files.filter(function (file) {
      if (fs.lstatSync(dir + '/' + file).isDirectory()) {
        if (file === 'js' || file === 'scss') return false;
      }
      return (file.indexOf('_') !== 0) && (file.indexOf('.js') == -1);
    });

    var pending = files.length;

    if (!pending)
      return done(null, { name: path.basename(dir), type: 'folder', children: results });

    files.forEach(function(file) {
      file = path.resolve(dir, file);
      fs.stat(file, function(err, stat) {
        if ( true /* file.indexOf('/img') === -1  */) {
          if (stat && stat.isDirectory() ) {
            directoryTreeToObj(file, function(err, res) {
              results.push({
                name: path.basename(file),
                type: 'folder',
                children: res
              });
              if (!--pending)
                done(null, results);
            });
          }
          else {
            results.push({
              type: 'file',
              name: path.basename(file)
            });
            if (!--pending)
              done(null, results);
          }
        }
      });
    });
  });
};

function throttle (callback, limit) {
    var wait = false;
    return function () {
        if (!wait) {
            callback.call();
            wait = true;
            setTimeout(function () {
                wait = false;
            }, limit);
        }
    }
}

function debounce(func, wait, immediate) {
	var timeout;
	return function() {
		var context = this, args = arguments;
		var later = function() {
			timeout = null;
			if (!immediate) func.apply(context, args);
		};
		var callNow = immediate && !timeout;
		clearTimeout(timeout);
		timeout = setTimeout(later, wait);
		if (callNow) func.apply(context, args);
	};
}
<|endoftext|>