<commit_before>/**
 * CategoricalAbstract is the base class for all categorical or timeseries
 */

pvc.CategoricalAbstract = pvc.TimeseriesAbstract.extend({

  yAxisPanel : null,
  xAxisPanel : null,

  yScale: null,
  xScale: null,

  prevMax: null,
  prevMin: null,

  constructor: function(o){

    this.base();

    var _defaults = {
      showAllTimeseries: false, // meaningless here
      showXScale: true,
      showYScale: true,
      yAxisPosition: "left",
      xAxisPosition: "bottom",
      yAxisSize: 50,
      xAxisSize: 50,
      xAxisFullGrid: false,
      yAxisFullGrid: false
    };

    // Apply options
    $.extend(this.options,_defaults, o);

    // Sanitize some options:
    if (this.options.showYScale == false){
      this.options.yAxisSize = 0
    }
    if (this.options.showXScale == false){
      this.options.xAxisSize = 0
    }

  },

  preRender: function(){

    this.base();

    pvc.log("Prerendering in CategoricalAbstract");

    this.xScale = this.getXScale();
    this.yScale = this.getYScale();

    // Generate axis

    this.generateXAxis();
    this.generateYAxis();

  },

  /*
   * Generates the X axis. It's in a separate function to allow overriding this value
   */

  generateXAxis: function(){

    if (this.options.showXScale){
      this.xAxisPanel = new pvc.XAxisPanel(this, {
        ordinal: this.isXAxisOrdinal(),
        showAllTimeseries: false,
        anchor: this.options.xAxisPosition,
        axisSize: this.options.xAxisSize,
        oppositeAxisSize: this.options.yAxisSize,
        fullGrid:  this.options.xAxisFullGrid,
        elements: this.getAxisOrdinalElements()
      });

      this.xAxisPanel.setScale(this.xScale);
      this.xAxisPanel.appendTo(this.basePanel); // Add it

    }

  },

  /*
   * Generates the Y axis. It's in a separate function to allow overriding this value
   */

  generateYAxis: function(){

    if (this.options.showYScale){
      this.yAxisPanel = new pvc.YAxisPanel(this, {
        ordinal: this.isYAxisOrdinal(),
        showAllTimeseries: false,
        anchor: this.options.yAxisPosition,
        axisSize: this.options.yAxisSize,
        oppositeAxisSize: this.options.xAxisSize,
        fullGrid:  this.options.yAxisFullGrid,
        elements: this.getAxisOrdinalElements()
      });

      this.yAxisPanel.setScale(this.yScale);
      this.yAxisPanel.appendTo(this.basePanel); // Add it

    }

  },

  /*
   * Indicates if xx is an ordinal scale
   */

  isXAxisOrdinal: function(){
    return this.options.orientation == "vertical" && !this.options.timeSeries;
  },

  /*
   * Indicates if yy is an ordinal scale
   */

  isYAxisOrdinal: function(){
    return this.options.orientation == "horizontal" && !this.options.timeSeries;
  },

  /*
   *  List of elements to use in the axis ordinal
   *
   */
  getAxisOrdinalElements: function(){
    return this.dataEngine.getCategories();
  },

  /*
   * xx scale for categorical charts
   */

  getXScale: function(){

    return this.options.orientation == "vertical"?
    (this.options.timeSeries?this.getTimeseriesScale():this.getOrdinalScale()):
    this.getLinearScale();

  },

  /*
   * yy scale for categorical charts
   */

  getYScale: function(){

    return this.options.orientation == "vertical"?
    this.getLinearScale():
    (this.options.timeSeries?this.getTimeseriesScale():this.getOrdinalScale());
  },

  /*
   * Scale for the ordinal axis. xx if orientation is vertical, yy otherwise
   *
   */
  getOrdinalScale: function(bypassAxis){

    var yAxisSize = bypassAxis?0:this.options.yAxisSize;
    var xAxisSize = bypassAxis?0:this.options.xAxisSize;

    var scale = new pv.Scale.ordinal(this.dataEngine.getVisibleCategories());

    var size = this.options.orientation=="vertical"?this.basePanel.width:this.basePanel.height;

    if(this.options.orientation=="vertical" && this.options.yAxisPosition == "left"){
      scale.splitBanded( yAxisSize , size, this.options.panelSizeRatio);
    }
    else if(this.options.orientation=="vertical" && this.options.yAxisPosition == "right"){
      scale.splitBanded(0, size - yAxisSize, this.options.panelSizeRatio);
    }
    else{
      scale.splitBanded(0, size - xAxisSize, this.options.panelSizeRatio);
    }

    return scale;

  },

  /*
   * Scale for the linear axis. yy if orientation is vertical, xx otherwise
   *
   */
  getLinearScale: function(bypassAxis){

    var yAxisSize = bypassAxis?0:this.options.yAxisSize;
    var xAxisSize = bypassAxis?0:this.options.xAxisSize;

    var isVertical = this.options.orientation=="vertical"
    var size = isVertical?this.basePanel.height:this.basePanel.width;

    var max, min;

    if(this.options.stacked){
      max = this.dataEngine.getCategoriesMaxSumOfVisibleSeries();
      min = 0;
    }
    else{
      max = this.dataEngine.getVisibleSeriesAbsoluteMax();
      min = this.dataEngine.getVisibleSeriesAbsoluteMin();

    }
    if(min > 0 && this.options.originIsZero){
      min = 0
    }

    // Adding a small offset to the scale:
    var offset = (max - min) * this.options.axisOffset;
    var scale = new pv.Scale.linear(min - offset,max + offset)

    if( !isVertical && this.options.yAxisPosition == "left"){
      scale.range( yAxisSize , size);
    }
    else if( !isVertical && this.options.yAxisPosition == "right"){
      scale.range(0, size - yAxisSize);
    }
    else{
      scale.range(0, size - xAxisSize);
    }

    return scale

  },

  /*
   * Scale for the timeseries axis. xx if orientation is vertical, yy otherwise
   *
   */
  getTimeseriesScale: function(bypassAxis){

    var yAxisSize = bypassAxis?0:this.options.yAxisSize;
    var xAxisSize = bypassAxis?0:this.options.xAxisSize;

    var size = this.options.orientation=="vertical"?
    this.basePanel.width:
    this.basePanel.height - xAxisSize;

    var parser = pv.Format.date(this.options.timeSeriesFormat);
    var categories =  this.dataEngine.getVisibleCategories().sort(function(a,b){
      return parser.parse(a) - parser.parse(b)
    });

    // Adding a small offset to the scale:
    var max = parser.parse(categories[categories.length -1]);
    var min = parser.parse(categories[0]);
    var offset = (max.getTime() - min.getTime()) * this.options.axisOffset;

    var scale = new pv.Scale.linear(new Date(min.getTime() - offset),new Date(max.getTime() + offset));

    if(this.options.orientation=="vertical" && this.options.yAxisPosition == "left"){
      scale.range( yAxisSize , size);
    }
    else if(this.options.orientation=="vertical" && this.options.yAxisPosition == "right"){
      scale.range(0, size - yAxisSize);
    }
    else{
      scale.range(0, size - xAxisSize);
    }

    return scale;

  }

}
)

/*
 * AxisPanel panel.
 *
 *
 */
pvc.AxisPanel = pvc.BasePanel.extend({

  _parent: null,
  pvRule: null,
  pvLabel: null,
  pvRuleGrid: null,

  ordinal: false,
  anchor: "bottom",
  axisSize: 30,
  tickLength: 6,
  oppositeAxisSize: 30,
  panelName: "axis", // override
  scale: null,
  fullGrid: false,
  elements: [], // To be used in ordinal scales

  constructor: function(chart, options){

    this.base(chart,options);

  },

  create: function(){

    // Size will depend only on the existence of the labels

    if (this.anchor == "top" || this.anchor == "bottom"){
      this.width = this._parent.width;
      this.height = this.axisSize;
    }
    else{
      this.height = this._parent.height;
      this.width = this.axisSize;
    }

    this.pvPanel = this._parent.getPvPanel().add(this.type)
    .width(this.width)
    .height(this.height)

    this.renderAxis();

    // Extend panel
    this.extend(this.pvPanel, this.panelName + "_");
    this.extend(this.pvRule, this.panelName + "Rule_");
    this.extend(this.pvLabel, this.panelName + "Label_");
    this.extend(this.pvRuleGrid, this.panelName + "Grid_");

  },

  setScale: function(scale){
    this.scale = scale;
  },

  renderAxis: function(){

    var scaleRange = this.scale.range();
    this.pvRule = this.pvPanel
    .add(pv.Rule)
    .strokeStyle("#aaa")
    [pvc.BasePanel.oppositeAnchor[this.anchor]](0)
    [pvc.BasePanel.relativeAnchor[this.anchor]](scaleRange[0])
    [pvc.BasePanel.paralelLength[this.anchor]](scaleRange[scaleRange.length - 1] + (this.ordinal?scaleRange.band:0))

    if (this.ordinal == true){
      this.renderOrdinalAxis();
    }
    else{
      this.renderLinearAxis();

    }

  },

  renderOrdinalAxis: function(){

    var myself = this;

    this.pvLabel = this.pvRule.add(pv.Label)
    .data(this.elements)
    [pvc.BasePanel.paralelLength[this.anchor]](null)
    [pvc.BasePanel.oppositeAnchor[this.anchor]](10)
    [pvc.BasePanel.relativeAnchor[this.anchor]](function(d){
      return myself.scale(d) + myself.scale.range().band/2;
    })
    .textAlign("center")
    .textBaseline("middle")
    .text(pv.identity)
    .font("9px sans-serif")
  },

  renderLinearAxis: function(){

    var myself = this;

    var scale = this.scale;

    this.pvLabel = this.pvRule.add(pv.Rule)
    .data(this.scale.ticks())
    [pvc.BasePanel.paralelLength[this.anchor]](null)
    [pvc.BasePanel.oppositeAnchor[this.anchor]](0)
    [pvc.BasePanel.relativeAnchor[this.anchor]](this.scale)
    [pvc.BasePanel.orthogonalLength[this.anchor]](function(d){
      return myself.tickLength/(this.index%2 + 1)
    })
    .anchor(this.anchor)
    .add(pv.Label)
    .text(scale.tickFormat)
    .font("9px sans-serif")
    .visible(function(d){
      // mini grids
      if (this.index % 2){
        return false;
      }
      // also, hide the first and last ones
      if( scale(d) == 0  || scale(d) == scale.range()[1] ){
        return false;
      }
      return true;
    })

    // Now do the full grids
    if(this.fullGrid){

      this.pvRuleGrid = this.pvRule.add(pv.Rule)
      .data(scale.ticks())
      .strokeStyle("#f0f0f0")
      [pvc.BasePanel.paralelLength[this.anchor]](null)
      [pvc.BasePanel.oppositeAnchor[this.anchor]](- this._parent[pvc.BasePanel.orthogonalLength[this.anchor]] +
        this[pvc.BasePanel.orthogonalLength[this.anchor]])
      [pvc.BasePanel.relativeAnchor[this.anchor]](scale)
      [pvc.BasePanel.orthogonalLength[this.anchor]](this._parent[pvc.BasePanel.orthogonalLength[this.anchor]] -
        this[pvc.BasePanel.orthogonalLength[this.anchor]])
      .visible(function(d){
        // mini grids
        if (this.index % 2){
          return false;
        }
        // also, hide the first and last ones
        if( scale(d) == 0  || scale(d) == scale.range()[1] ){
          return false;
        }
        return true;
      })
    }

  }

});

/*
 * XAxisPanel panel.
 *
 *
 */
pvc.XAxisPanel = pvc.AxisPanel.extend({

  anchor: "bottom",
  panelName: "xAxis",

  constructor: function(chart, options){

    this.base(chart,options);

  }

});

/*
 * YAxisPanel panel.
 *
 *
 */
pvc.YAxisPanel = pvc.AxisPanel.extend({

  anchor: "left",
  panelName: "yAxis",
  pvRule: null,

  constructor: function(chart, options){

    this.base(chart,options);

  }

});<commit_msg>fixed a few issues with the way axes are drawn<commit_after>/**
 * CategoricalAbstract is the base class for all categorical or timeseries
 */

pvc.CategoricalAbstract = pvc.TimeseriesAbstract.extend({

    yAxisPanel : null,
    xAxisPanel : null,

    yScale: null,
    xScale: null,

    prevMax: null,
    prevMin: null,

    constructor: function(o){

        this.base();

        var _defaults = {
            showAllTimeseries: false, // meaningless here
            showXScale: true,
            showYScale: true,
            yAxisPosition: "left",
            xAxisPosition: "bottom",
            yAxisSize: 50,
            xAxisSize: 50,
            xAxisFullGrid: false,
            yAxisFullGrid: false
        };

        // Apply options
        $.extend(this.options,_defaults, o);

        // Sanitize some options:
        if (this.options.showYScale == false){
            this.options.yAxisSize = 0
        }
        if (this.options.showXScale == false){
            this.options.xAxisSize = 0
        }

    },

    preRender: function(){

        this.base();

        pvc.log("Prerendering in CategoricalAbstract");

        this.xScale = this.getXScale();
        this.yScale = this.getYScale();

        // Generate axis

        this.generateXAxis();
        this.generateYAxis();

    },

    /*
   * Generates the X axis. It's in a separate function to allow overriding this value
   */

    generateXAxis: function(){

        if (this.options.showXScale){
            this.xAxisPanel = new pvc.XAxisPanel(this, {
                ordinal: this.isXAxisOrdinal(),
                showAllTimeseries: false,
                anchor: this.options.xAxisPosition,
                axisSize: this.options.xAxisSize,
                oppositeAxisSize: this.options.yAxisSize,
                fullGrid:  this.options.xAxisFullGrid,
                elements: this.getAxisOrdinalElements()
            });

            this.xAxisPanel.setScale(this.xScale);
            this.xAxisPanel.appendTo(this.basePanel); // Add it

        }

    },

    /*
   * Generates the Y axis. It's in a separate function to allow overriding this value
   */

    generateYAxis: function(){

        if (this.options.showYScale){
            this.yAxisPanel = new pvc.YAxisPanel(this, {
                ordinal: this.isYAxisOrdinal(),
                showAllTimeseries: false,
                anchor: this.options.yAxisPosition,
                axisSize: this.options.yAxisSize,
                oppositeAxisSize: this.options.xAxisSize,
                fullGrid:  this.options.yAxisFullGrid,
                elements: this.getAxisOrdinalElements()
            });

            this.yAxisPanel.setScale(this.yScale);
            this.yAxisPanel.appendTo(this.basePanel); // Add it

        }

    },

    /*
   * Indicates if xx is an ordinal scale
   */

    isXAxisOrdinal: function(){
        return this.options.orientation == "vertical" && !this.options.timeSeries;
    },

    /*
   * Indicates if yy is an ordinal scale
   */

    isYAxisOrdinal: function(){
        return this.options.orientation == "horizontal" && !this.options.timeSeries;
    },

    /*
   *  List of elements to use in the axis ordinal
   *
   */
    getAxisOrdinalElements: function(){
        return this.dataEngine.getCategories();
    },

    /*
   * xx scale for categorical charts
   */

    getXScale: function(){

        return this.options.orientation == "vertical"?
        (this.options.timeSeries?this.getTimeseriesScale():this.getOrdinalScale()):
        this.getLinearScale();

    },

    /*
   * yy scale for categorical charts
   */

    getYScale: function(){

        return this.options.orientation == "vertical"?
        this.getLinearScale():
        (this.options.timeSeries?this.getTimeseriesScale():this.getOrdinalScale());
    },

    /*
   * Scale for the ordinal axis. xx if orientation is vertical, yy otherwise
   *
   */
    getOrdinalScale: function(bypassAxis){

        var yAxisSize = bypassAxis?0:this.options.yAxisSize;
        var xAxisSize = bypassAxis?0:this.options.xAxisSize;

        var scale = new pv.Scale.ordinal(this.dataEngine.getVisibleCategories());

        var size = this.options.orientation=="vertical"?this.basePanel.width:this.basePanel.height;

        if(this.options.orientation=="vertical" && this.options.yAxisPosition == "left"){
            scale.min = yAxisSize;
            scale.max = size;
        }
        else if(this.options.orientation=="vertical" && this.options.yAxisPosition == "right"){
            scale.min = 0;
            scale.max = size-yAxisSize;
        }
        else{
            scale.min = 0;
            scale.max = size-xAxisSize;
        }
        scale.splitBanded( scale.min, scale.max, this.options.panelSizeRatio);
        return scale;

    },

    /*
   * Scale for the linear axis. yy if orientation is vertical, xx otherwise
   *
   */
    getLinearScale: function(bypassAxis){

        var yAxisSize = bypassAxis?0:this.options.yAxisSize;
        var xAxisSize = bypassAxis?0:this.options.xAxisSize;

        var isVertical = this.options.orientation=="vertical"
        var size = isVertical?this.basePanel.height:this.basePanel.width;

        var max, min;

        if(this.options.stacked){
            max = this.dataEngine.getCategoriesMaxSumOfVisibleSeries();
            min = 0;
        }
        else{
            max = this.dataEngine.getVisibleSeriesAbsoluteMax();
            min = this.dataEngine.getVisibleSeriesAbsoluteMin();

        }
        if(min > 0 && this.options.originIsZero){
            min = 0
        }

        // Adding a small offset to the scale:
        var offset = (max - min) * this.options.axisOffset;
        var scale = new pv.Scale.linear(min - offset,max + offset)

        if( !isVertical && this.options.yAxisPosition == "left"){
            scale.min = yAxisSize;
            scale.max = size;

        }
        else if( !isVertical && this.options.yAxisPosition == "right"){
            scale.min = 0;
            scale.max = size - yAxisSize;
        }
        else{
            scale.min = 0;
            scale.max = size - xAxisSize;
        }

        scale.range(scale.min, scale.max);
        return scale;

    },

    /*
   * Scale for the timeseries axis. xx if orientation is vertical, yy otherwise
   *
   */
    getTimeseriesScale: function(bypassAxis){

        var yAxisSize = bypassAxis?0:this.options.yAxisSize;
        var xAxisSize = bypassAxis?0:this.options.xAxisSize;

        var size = this.options.orientation=="vertical"?
        this.basePanel.width:
        this.basePanel.height;

        var parser = pv.Format.date(this.options.timeSeriesFormat);
        var categories =  this.dataEngine.getVisibleCategories().sort(function(a,b){
            return parser.parse(a) - parser.parse(b)
        });

        // Adding a small offset to the scale:
        var max = parser.parse(categories[categories.length -1]);
        var min = parser.parse(categories[0]);
        var offset = (max.getTime() - min.getTime()) * this.options.axisOffset;

        var scale = new pv.Scale.linear(new Date(min.getTime() - offset),new Date(max.getTime() + offset));

        if(this.options.orientation=="vertical" && this.options.yAxisPosition == "left"){
            scale.min = yAxisSize;
            scale.max = size;

        }
        else if(this.options.orientation=="vertical" && this.options.yAxisPosition == "right"){
            scale.min = 0;
            scale.max = size - yAxisSize;
        }
        else{
            scale.min = 0;
            scale.max = size - xAxisSize;
        }

        scale.range( scale.min , scale.max);
        return scale;

    }

}
)

/*
 * AxisPanel panel.
 *
 *
 */
pvc.AxisPanel = pvc.BasePanel.extend({

    _parent: null,
    pvRule: null,
    pvLabel: null,
    pvRuleGrid: null,

    ordinal: false,
    anchor: "bottom",
    axisSize: 30,
    tickLength: 6,
    oppositeAxisSize: 30,
    panelName: "axis", // override
    scale: null,
    fullGrid: false,
    elements: [], // To be used in ordinal scales

    constructor: function(chart, options){

        this.base(chart,options);

    },

    create: function(){

        // Size will depend only on the existence of the labels

        if (this.anchor == "top" || this.anchor == "bottom"){
            this.width = this._parent.width;
            this.height = this.axisSize;
        }
        else{
            this.height = this._parent.height;
            this.width = this.axisSize;
        }

        this.pvPanel = this._parent.getPvPanel().add(this.type)
        .width(this.width)
        .height(this.height)

        this.renderAxis();

        // Extend panel
        this.extend(this.pvPanel, this.panelName + "_");
        this.extend(this.pvRule, this.panelName + "Rule_");
        this.extend(this.pvLabel, this.panelName + "Label_");
        this.extend(this.pvRuleGrid, this.panelName + "Grid_");

    },

    setScale: function(scale){
        this.scale = scale;
    },

    renderAxis: function(){

        var scaleRange = this.scale.range();
        this.pvRule = this.pvPanel
        .add(pv.Rule)
        .strokeStyle("#aaa")
        [pvc.BasePanel.oppositeAnchor[this.anchor]](0)
        [pvc.BasePanel.relativeAnchor[this.anchor]](this.scale.min)
        [pvc.BasePanel.paralelLength[this.anchor]](this.scale.max - this.scale.min)

        if (this.ordinal == true){
            this.renderOrdinalAxis();
        }
        else{
            this.renderLinearAxis();

        }

    },

    renderOrdinalAxis: function(){

        var myself = this;

        this.pvLabel = this.pvRule.add(pv.Label)
        .data(this.elements)
        [pvc.BasePanel.paralelLength[this.anchor]](null)
        [pvc.BasePanel.oppositeAnchor[this.anchor]](10)
        [pvc.BasePanel.relativeAnchor[this.anchor]](function(d){
            return myself.scale(d) + myself.scale.range().band/2;
        })
        .textAlign("center")
        .textBaseline("middle")
        .text(pv.identity)
        .font("9px sans-serif")
    },

    renderLinearAxis: function(){

        var myself = this;

        var scale = this.scale;

        this.pvLabel = this.pvRule.add(pv.Rule)
        .data(this.scale.ticks())
        [pvc.BasePanel.paralelLength[this.anchor]](null)
        [pvc.BasePanel.oppositeAnchor[this.anchor]](0)
        [pvc.BasePanel.relativeAnchor[this.anchor]](this.scale)
        [pvc.BasePanel.orthogonalLength[this.anchor]](function(d){
            return myself.tickLength/(this.index%2 + 1)
        })
        .anchor(this.anchor)
        .add(pv.Label)
        .text(scale.tickFormat)
        .font("9px sans-serif")
        .visible(function(d){
            // mini grids
            if (this.index % 2){
                return false;
            }
            // also, hide the first and last ones
            if( scale(d) == 0  || scale(d) == scale.range()[1] ){
                return false;
            }
            return true;
        })

        // Now do the full grids
        if(this.fullGrid){

            this.pvRuleGrid = this.pvRule.add(pv.Rule)
            .data(scale.ticks())
            .strokeStyle("#f0f0f0")
            [pvc.BasePanel.paralelLength[this.anchor]](null)
            [pvc.BasePanel.oppositeAnchor[this.anchor]](- this._parent[pvc.BasePanel.orthogonalLength[this.anchor]] +
                this[pvc.BasePanel.orthogonalLength[this.anchor]])
            [pvc.BasePanel.relativeAnchor[this.anchor]](scale)
            [pvc.BasePanel.orthogonalLength[this.anchor]](this._parent[pvc.BasePanel.orthogonalLength[this.anchor]] -
                this[pvc.BasePanel.orthogonalLength[this.anchor]])
            .visible(function(d){
                // mini grids
                if (this.index % 2){
                    return false;
                }
                // also, hide the first and last ones
                if( scale(d) == 0  || scale(d) == scale.range()[1] ){
                    return false;
                }
                return true;
            })
        }

    }

});

/*
 * XAxisPanel panel.
 *
 *
 */
pvc.XAxisPanel = pvc.AxisPanel.extend({

    anchor: "bottom",
    panelName: "xAxis",

    constructor: function(chart, options){

        this.base(chart,options);

    }

});

/*
 * YAxisPanel panel.
 *
 *
 */
pvc.YAxisPanel = pvc.AxisPanel.extend({

    anchor: "left",
    panelName: "yAxis",
    pvRule: null,

    constructor: function(chart, options){

        this.base(chart,options);

    }

});<|endoftext|>


<commit_before>module.exports = Locket

// In case you forgot, Alan. You've finished a rewrite. You no longer have many
// different staging trees, you only have one staging tree and it is not a
// tree, it is simply a log, a b-tree with only one leaf. When it is time to
// merge, it is renamed and then it is merged. Thus there is the primary tree,
// the staging log and possibly a merging log. There are, therefore, only two
// extra cursors in addition to the primary tree, and they can always be read
// pretty much directly using an Advance iterator, instead of having to traverse
// them as if they where actual trees.
//
// We have no way of vacuuming the primary tree at this point.
//
// *Note:*
//
// Please curb your compulsion to refactor the upstream libraries any further.
//
// You thought long and hard about this. You are not getting smarter.

// Node.js API.
const util              = require('util')
const assert            = require('assert')

// Return the first value that is not `null` nor `undefined`.
const { coalesce }      = require('extant')

// Modules for storage and concurrency.
const Strata            = require('b-tree')

// A fiber-constrained `async`/`await` work queue.
const Turnstile         = require('turnstile')

// Write-ahead log.
const WriteAhead        = require('writeahead')

// LevelUp adaptors.
const AbstractLevelDOWN = require('abstract-leveldown').AbstractLevelDOWN
const AbstractIterator  = require('abstract-leveldown').AbstractIterator

// An `async`/`await` trampoline.
const Trampoline        = require('reciprocate')

// Structured concurrency.
const Destructible      = require('destructible')

// A Swiss Army asynchronous control-flow function generator for JavaScript.
const cadence           = require('cadence')

// A LRU cache for memory paging and content caching.
const Magazine          = require('magazine')

// Handle-based `async`/`await` file operations.
const Operation         = require('operation')

// WAL merge tree.
const Amalgamator       = require('amalgamate')
const Rotator           = require('amalgamate/rotator')

const Fracture          = require('fracture')

// Modules for file operations. We use `strftime` to create date stamped file
// names.
const fs                = require('fs').promises
const path              = require('path')

const constrain         = require('constrain')

// Conditionally catch JavaScript exceptions based on type and properties.
const rescue            = require('rescue')

// A comparator function builder.
const ascension         = require('ascension')

const mvcc = {
    satiate: require('satiate'),
    constrain: require('constrain/iterator')
}

// TODO Let's see if we can get throught his without having to worry about
// encodings.
function encode (buffer) {
    return Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer)
}

class Paginator {
    constructor (iterator, constraints, options) {
        const constrained = constraints == null
            ? iterator
            : mvcc.constrain(iterator, constraints)
        this._iterator = mvcc.satiate(constrained, 1)
        this._constraints = constraints
        this._keyAsBuffer = options.keyAsBuffer
        this._valueAsBuffer = options.valueAsBuffer
        this._keys = options.keys
        this._values = options.values
        this._items = []
        this._index = 0
    }

    next = cadence(function (step) {
        if (this._items.length != this._index) {
            const item = this._items[this._index++], result = new Array(2)
            if (this._keys) {
                result[0] = this._keyAsBuffer ? item.parts[1] : item.parts[1].toString()
            }
            if (this._values) {
                result[1] = this._valueAsBuffer ? item.parts[2] : item.parts[2].toString()
            }
            return result
        }
        let items = null
        step(function () {
            const trampoline = new Trampoline
            this._iterator.next(trampoline, $items => items = $items)
            trampoline.callback(step())
        }, function () {
            if (this._iterator.done) {
                return []
            } else {
                this._items = items
                this._index = 0
                this.next(step())
            }
        })
    })
}

// An implementation of the LevelDOWN `Iterator` object.
//
// The LevelUP interface allows you to specify encodings at both when you create
// the database and when you invoke one of it's functions, so we need to pass
// two different sets of options to all the functions that consider the
// encoding.

// TODO No call to super!
function Iterator (db, options) {
    AbstractIterator.call(this, db)
    this._constraint = createConstraint(options)
    this._options = options
    this._db = db
    this._transaction = this._db._rotator.locker.snapshot()
    this._paginator = this._db._paginator(this._constraint, this._transaction, this._options)
}
util.inherits(Iterator, AbstractIterator)

// Our LevelUP `Iterator` implementation is a wrapper around the internal
// iterator that merges our Strata b-tree with one or two logs where we are
// gathering new batch operations. See the `Locket._internalIterator` method for
// a description of how we compose an amalgamated MVCC iterator from the MVCC
// iterator modules.

//
Iterator.prototype._next = function (callback) {
    this._paginator.next(callback)
}

Iterator.prototype._seek = function (target) {
    const paginator = this._paginator
    this._paginator = this.db._paginator(target, this._options)
    paginator.release()
}

Iterator.prototype._end = function (callback) {
    this._db._rotator.locker.release(this._transaction)
    callback()
}

function Locket (destructible, location, options = {}) {
    if (!(this instanceof Locket)) return new Locket(destructible, location, options)
    AbstractLevelDOWN.call(this)
    this.location = location
    this._cache = coalesce(options.cache, new Magazine),
    // TODO Allow common operation handle cache.
    this._versions = {}
    this._options = options
    this._version = 1n
    this._amalgamator = null
}
util.inherits(Locket, AbstractLevelDOWN)

Locket.prototype._serializeKey = encode

Locket.prototype._serializeValue = encode

Locket.prototype._open = cadence(function (step, options) {
    const destructible = new Destructible('locket')
    // TODO Only use the old callback `fs`.
    const fs = require('fs')
    // TODO What is the behavior if you close while opening, or open while closing?
    step(function () {
        step(function () {
            fs.readdir(this.location, step())
        }, function (files) {
            const exists = ([ 'wal', 'tree' ]).filter(file => ~files.indexOf(file))
            if (exists.length == 0) {
                return false
            }
            if (exists.length == 2) {
                return true
            }
            // TODO Interrupt or LevelUp specific error.
            throw new Error('partial extant database')
        }, function (exists) {
            if (! exists) {
                step(function () {
                    fs.mkdir(path.resolve(this.location, 'wal'), { recursive: true }, step())
                }, function () {
                    fs.mkdir(path.resolve(this.location, 'tree'), { recursive: true }, step())
                }, function () {
                    return false
                })
            } else {
                return exists
            }
        })
    }, async function (exists) {
        const turnstile = new Turnstile(destructible.durable($ => $(), { isolated: true }, 'turnstile'))
        const writeahead = new WriteAhead(destructible.durable($ => $(), 'writeahead'), turnstile, await WriteAhead.open({
            directory: path.resolve(this.location, 'wal')
        }))
        this._rotator = new Rotator(destructible.durable($ => $(), 'rotator'), await Rotator.open(writeahead), { size: 1024 * 1024 / 4 })
        // TODO Make strands user optional.
        return this._rotator.open(Fracture.stack(), 'locket', {
            handles: new Operation.Cache(new Magazine),
            directory: path.resolve(this.location, 'tree'),
            create: ! exists,
            cache: this._cache,
            key: 'tree',
            // TODO Use CRC32 or FNV.
            checksum: () => '0',
            extractor: parts => parts[0],
            serializer: {
                key: {
                    serialize: key => [ key ],
                    deserialize: parts => parts[0]
                },
                parts: {
                    serialize: parts => parts,
                    deserialize: parts => parts
                }
            },
        }, {
            pages: new Magazine,
            turnstile: turnstile,
            comparator: Buffer.compare,
            transformer: function (operation) {
                if (operation.type == 'put') {
                    return {
                        method: 'insert',
                        key: operation.key,
                        parts: [ operation.key, operation.value ]
                    }
                }
                return {
                    method: 'remove',
                    key: operation.key
                }
            },
            primary: options.primary || {
                leaf: { split: 256, merge: 32 },
                branch: { split: 256, merge: 32 },
            },
            stage: options.stage || {
                leaf: { split: 256, merge: 32 },
                branch: { split: 256, merge: 32 },
            }
        })
    }, function (amalgamator) {
        this._amalgamator = amalgamator
    }, function () {
        return []
    })
})

// Iteration of the database requires merging the results from the deep storage
// b-tree and the one or two staging logs.
//
// We do this by creating a merged Homogonize iterator across the b-tree and the
// logs. This is an iteartor that takes one or more iterators and advances
// through. It will advance each iterator and then when it is advanced, it
// returns the least value of each of the three iterators (or greatest value if
// iteration is reversed.)
//
// We then use the versioned iterator from the Designate module which will
// select the key/value pair for a key that has the greatest committed version.
//
// Finally we need to respect the properties given a user when creating an
// external iterator; start, stop, greater than, less than, greater than or
// equal to, less than or equal to. We use a Dilute iterator to select out only
// records that have not been deleted and that match the user's range critera.

//
Locket.prototype._paginator = function (constraint, transaction, options) {
    const [{ key, inclusive, direction }, constraints ] = constraint
    const iterator = this._amalgamator.iterator(transaction, direction, key, inclusive)
    return new Paginator(iterator, constraints, options)
}

Locket.prototype._iterator = function (options) {
    return new Iterator(this, options)
}

const duplicated = ascension([ Buffer.compare, [ Number, -1 ], [ Number, -1 ] ])

function createConstraint (options) {
    const start = coalesce(options.gt, options.start, options.gte, null)
    const end = coalesce(options.lt, options.end, options.lte, null)
    const limit = coalesce(options.limit, -1)
    const reverse = coalesce(options.reverse, false)
    const direction = reverse ? 'reverse' : 'forward'
    const keys = [{
        comparator: duplicated,
        key: [ start, Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER ],
        inclusive: options.lt == null,
        limit: limit,
        direction: direction,
        reverse: reverse
    }, {
        comparator: duplicated,
        key: [ end, 0, 0 ],
        inclusive: options.gt == null,
        limit: limit,
        direction: direction,
        reverse: reverse
    }]
    if (reverse) {
        keys.reverse()
    }
    if (keys[1].limit == -1 && keys[1].key[0] == null) {
        keys[1] = null
    }
    keys[0].key = keys[0].key[0]
    return keys
}

// TODO Maybe just leave this?
Locket.prototype._get = cadence(function (step, key, options) {
    const constraint = createConstraint({ gte: key, lte: key })
    const snapshot = this._rotator.locker.snapshot()
    const paginator = this._paginator(constraint, snapshot, {
        keys: true, values: true, keyAsBuffer: true, valueAsBuffer: true
    })
    step(function () {
        paginator.next(step())
    }, [], function (next) {
        this._rotator.locker.release(snapshot)
        if (next.length != 0 && Buffer.compare(next[0], key) == 0) {
            return [ options.asBuffer ? next[1] : next[1].toString() ]
        }
        throw new Error('NotFoundError: not found')
    })
})

Locket.prototype._put = function (key, value, options, callback) {
    this._batch([{ type: 'put', key: key, value: value }], options, callback)
}

Locket.prototype._del = function (key, options, callback) {
    this._batch([{ type: 'del', key: key }], options, callback)
}

// Could use a header record. It would sort out to be less than all the user
// records, so it wouldn't get in the way of a search, and we wouldn't have to
// filter it. It does however mean at least two writes for every `put` or `del`
// and I suspect that common usage is ingle `put` or `del`, so going to include
// the count in ever record, it is only 32-bits.
Locket.prototype._batch = cadence(function (step, batch, options) {
    const mutator = this._rotator.locker.mutator()
    step(function () {
        return this._amalgamator.merge(Fracture.stack(), mutator, batch)
    }, function () {
        return this._rotator.commit(Fracture.stack(), mutator.mutation.version)
    }, function () {
        this._rotator.locker.commit(mutator)
        return []
    })
})

Locket.prototype._approximateSize = cadence(function (from, to) {
    const constraint = constrain(Buffer.compare, encode, { gte: from, lte: to })
    let approximateSize = 0
    for (const items of this._whatever()) {
        for (const item of items) {
            approximateSize += item.heft
        }
    }
    return approximateSize
})

// TODO Countdown through the write queue.
Locket.prototype._close = cadence(function (step) {
    step(function () {
        return this._amalgamator.destructible.destroy().promise
    }, function () {
        return []
    })
})
<commit_msg>Convert Iterator class to ES6.<commit_after>module.exports = Locket

// In case you forgot, Alan. You've finished a rewrite. You no longer have many
// different staging trees, you only have one staging tree and it is not a
// tree, it is simply a log, a b-tree with only one leaf. When it is time to
// merge, it is renamed and then it is merged. Thus there is the primary tree,
// the staging log and possibly a merging log. There are, therefore, only two
// extra cursors in addition to the primary tree, and they can always be read
// pretty much directly using an Advance iterator, instead of having to traverse
// them as if they where actual trees.
//
// We have no way of vacuuming the primary tree at this point.
//
// *Note:*
//
// Please curb your compulsion to refactor the upstream libraries any further.
//
// You thought long and hard about this. You are not getting smarter.

// Node.js API.
const util              = require('util')
const assert            = require('assert')

// Return the first value that is not `null` nor `undefined`.
const { coalesce }      = require('extant')

// Modules for storage and concurrency.
const Strata            = require('b-tree')

// A fiber-constrained `async`/`await` work queue.
const Turnstile         = require('turnstile')

// Write-ahead log.
const WriteAhead        = require('writeahead')

// LevelUp adaptors.
const AbstractLevelDOWN = require('abstract-leveldown').AbstractLevelDOWN
const AbstractIterator  = require('abstract-leveldown').AbstractIterator

// An `async`/`await` trampoline.
const Trampoline        = require('reciprocate')

// Structured concurrency.
const Destructible      = require('destructible')

// A Swiss Army asynchronous control-flow function generator for JavaScript.
const cadence           = require('cadence')

// A LRU cache for memory paging and content caching.
const Magazine          = require('magazine')

// Handle-based `async`/`await` file operations.
const Operation         = require('operation')

// WAL merge tree.
const Amalgamator       = require('amalgamate')
const Rotator           = require('amalgamate/rotator')

const Fracture          = require('fracture')

// Modules for file operations. We use `strftime` to create date stamped file
// names.
const fs                = require('fs').promises
const path              = require('path')

const constrain         = require('constrain')

// Conditionally catch JavaScript exceptions based on type and properties.
const rescue            = require('rescue')

// A comparator function builder.
const ascension         = require('ascension')

const mvcc = {
    satiate: require('satiate'),
    constrain: require('constrain/iterator')
}

// TODO Let's see if we can get throught his without having to worry about
// encodings.
function encode (buffer) {
    return Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer)
}

class Paginator {
    constructor (iterator, constraints, options) {
        const constrained = constraints == null
            ? iterator
            : mvcc.constrain(iterator, constraints)
        this._iterator = mvcc.satiate(constrained, 1)
        this._constraints = constraints
        this._keyAsBuffer = options.keyAsBuffer
        this._valueAsBuffer = options.valueAsBuffer
        this._keys = options.keys
        this._values = options.values
        this._items = []
        this._index = 0
    }

    next = cadence(function (step) {
        if (this._items.length != this._index) {
            const item = this._items[this._index++], result = new Array(2)
            if (this._keys) {
                result[0] = this._keyAsBuffer ? item.parts[1] : item.parts[1].toString()
            }
            if (this._values) {
                result[1] = this._valueAsBuffer ? item.parts[2] : item.parts[2].toString()
            }
            return result
        }
        let items = null
        step(function () {
            const trampoline = new Trampoline
            this._iterator.next(trampoline, $items => items = $items)
            trampoline.callback(step())
        }, function () {
            if (this._iterator.done) {
                return []
            } else {
                this._items = items
                this._index = 0
                this.next(step())
            }
        })
    })
}

// An implementation of the LevelDOWN `Iterator` object.
//
// The LevelUP interface allows you to specify encodings at both when you create
// the database and when you invoke one of it's functions, so we need to pass
// two different sets of options to all the functions that consider the
// encoding.

// TODO No call to super!
class Iterator extends AbstractIterator {
    constructor (db, options) {
        super(db)
        this._constraint = createConstraint(options)
        this._options = options
        this._db = db
        this._transaction = this._db._rotator.locker.snapshot()
        this._paginator = this._db._paginator(this._constraint, this._transaction, this._options)
    }

    // Our LevelUP `Iterator` implementation is a wrapper around the internal
    // iterator that merges our Strata b-tree with one or two logs where we are
    // gathering new batch operations. See the `Locket._internalIterator` method
    // for a description of how we compose an amalgamated MVCC iterator from the
    // MVCC iterator modules.

    //
    _next (callback) {
        this._paginator.next(callback)
    }

    _seek (target) {
        const paginator = this._paginator
        this._paginator = this.db._paginator(target, this._options)
        paginator.release()
    }

    _end (callback) {
        this._db._rotator.locker.release(this._transaction)
        callback()
    }
}

function Locket (destructible, location, options = {}) {
    if (!(this instanceof Locket)) return new Locket(destructible, location, options)
    AbstractLevelDOWN.call(this)
    this.location = location
    this._cache = coalesce(options.cache, new Magazine),
    // TODO Allow common operation handle cache.
    this._versions = {}
    this._options = options
    this._version = 1n
    this._amalgamator = null
}
util.inherits(Locket, AbstractLevelDOWN)

Locket.prototype._serializeKey = encode

Locket.prototype._serializeValue = encode

Locket.prototype._open = cadence(function (step, options) {
    const destructible = new Destructible('locket')
    // TODO Only use the old callback `fs`.
    const fs = require('fs')
    // TODO What is the behavior if you close while opening, or open while closing?
    step(function () {
        step(function () {
            fs.readdir(this.location, step())
        }, function (files) {
            const exists = ([ 'wal', 'tree' ]).filter(file => ~files.indexOf(file))
            if (exists.length == 0) {
                return false
            }
            if (exists.length == 2) {
                return true
            }
            // TODO Interrupt or LevelUp specific error.
            throw new Error('partial extant database')
        }, function (exists) {
            if (! exists) {
                step(function () {
                    fs.mkdir(path.resolve(this.location, 'wal'), { recursive: true }, step())
                }, function () {
                    fs.mkdir(path.resolve(this.location, 'tree'), { recursive: true }, step())
                }, function () {
                    return false
                })
            } else {
                return exists
            }
        })
    }, async function (exists) {
        const turnstile = new Turnstile(destructible.durable($ => $(), { isolated: true }, 'turnstile'))
        const writeahead = new WriteAhead(destructible.durable($ => $(), 'writeahead'), turnstile, await WriteAhead.open({
            directory: path.resolve(this.location, 'wal')
        }))
        this._rotator = new Rotator(destructible.durable($ => $(), 'rotator'), await Rotator.open(writeahead), { size: 1024 * 1024 / 4 })
        // TODO Make strands user optional.
        return this._rotator.open(Fracture.stack(), 'locket', {
            handles: new Operation.Cache(new Magazine),
            directory: path.resolve(this.location, 'tree'),
            create: ! exists,
            cache: this._cache,
            key: 'tree',
            // TODO Use CRC32 or FNV.
            checksum: () => '0',
            extractor: parts => parts[0],
            serializer: {
                key: {
                    serialize: key => [ key ],
                    deserialize: parts => parts[0]
                },
                parts: {
                    serialize: parts => parts,
                    deserialize: parts => parts
                }
            },
        }, {
            pages: new Magazine,
            turnstile: turnstile,
            comparator: Buffer.compare,
            transformer: function (operation) {
                if (operation.type == 'put') {
                    return {
                        method: 'insert',
                        key: operation.key,
                        parts: [ operation.key, operation.value ]
                    }
                }
                return {
                    method: 'remove',
                    key: operation.key
                }
            },
            primary: options.primary || {
                leaf: { split: 256, merge: 32 },
                branch: { split: 256, merge: 32 },
            },
            stage: options.stage || {
                leaf: { split: 256, merge: 32 },
                branch: { split: 256, merge: 32 },
            }
        })
    }, function (amalgamator) {
        this._amalgamator = amalgamator
    }, function () {
        return []
    })
})

// Iteration of the database requires merging the results from the deep storage
// b-tree and the one or two staging logs.
//
// We do this by creating a merged Homogonize iterator across the b-tree and the
// logs. This is an iteartor that takes one or more iterators and advances
// through. It will advance each iterator and then when it is advanced, it
// returns the least value of each of the three iterators (or greatest value if
// iteration is reversed.)
//
// We then use the versioned iterator from the Designate module which will
// select the key/value pair for a key that has the greatest committed version.
//
// Finally we need to respect the properties given a user when creating an
// external iterator; start, stop, greater than, less than, greater than or
// equal to, less than or equal to. We use a Dilute iterator to select out only
// records that have not been deleted and that match the user's range critera.

//
Locket.prototype._paginator = function (constraint, transaction, options) {
    const [{ key, inclusive, direction }, constraints ] = constraint
    const iterator = this._amalgamator.iterator(transaction, direction, key, inclusive)
    return new Paginator(iterator, constraints, options)
}

Locket.prototype._iterator = function (options) {
    return new Iterator(this, options)
}

const duplicated = ascension([ Buffer.compare, [ Number, -1 ], [ Number, -1 ] ])

function createConstraint (options) {
    const start = coalesce(options.gt, options.start, options.gte, null)
    const end = coalesce(options.lt, options.end, options.lte, null)
    const limit = coalesce(options.limit, -1)
    const reverse = coalesce(options.reverse, false)
    const direction = reverse ? 'reverse' : 'forward'
    const keys = [{
        comparator: duplicated,
        key: [ start, Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER ],
        inclusive: options.lt == null,
        limit: limit,
        direction: direction,
        reverse: reverse
    }, {
        comparator: duplicated,
        key: [ end, 0, 0 ],
        inclusive: options.gt == null,
        limit: limit,
        direction: direction,
        reverse: reverse
    }]
    if (reverse) {
        keys.reverse()
    }
    if (keys[1].limit == -1 && keys[1].key[0] == null) {
        keys[1] = null
    }
    keys[0].key = keys[0].key[0]
    return keys
}

// TODO Maybe just leave this?
Locket.prototype._get = cadence(function (step, key, options) {
    const constraint = createConstraint({ gte: key, lte: key })
    const snapshot = this._rotator.locker.snapshot()
    const paginator = this._paginator(constraint, snapshot, {
        keys: true, values: true, keyAsBuffer: true, valueAsBuffer: true
    })
    step(function () {
        paginator.next(step())
    }, [], function (next) {
        this._rotator.locker.release(snapshot)
        if (next.length != 0 && Buffer.compare(next[0], key) == 0) {
            return [ options.asBuffer ? next[1] : next[1].toString() ]
        }
        throw new Error('NotFoundError: not found')
    })
})

Locket.prototype._put = function (key, value, options, callback) {
    this._batch([{ type: 'put', key: key, value: value }], options, callback)
}

Locket.prototype._del = function (key, options, callback) {
    this._batch([{ type: 'del', key: key }], options, callback)
}

// Could use a header record. It would sort out to be less than all the user
// records, so it wouldn't get in the way of a search, and we wouldn't have to
// filter it. It does however mean at least two writes for every `put` or `del`
// and I suspect that common usage is ingle `put` or `del`, so going to include
// the count in ever record, it is only 32-bits.
Locket.prototype._batch = cadence(function (step, batch, options) {
    const mutator = this._rotator.locker.mutator()
    step(function () {
        return this._amalgamator.merge(Fracture.stack(), mutator, batch)
    }, function () {
        return this._rotator.commit(Fracture.stack(), mutator.mutation.version)
    }, function () {
        this._rotator.locker.commit(mutator)
        return []
    })
})

Locket.prototype._approximateSize = cadence(function (from, to) {
    const constraint = constrain(Buffer.compare, encode, { gte: from, lte: to })
    let approximateSize = 0
    for (const items of this._whatever()) {
        for (const item of items) {
            approximateSize += item.heft
        }
    }
    return approximateSize
})

// TODO Countdown through the write queue.
Locket.prototype._close = cadence(function (step) {
    step(function () {
        return this._amalgamator.destructible.destroy().promise
    }, function () {
        return []
    })
})
<|endoftext|>


<commit_before>// Makery.js v0.2.0
// (c) 2013 Leonardo Andres Garcia Crespo
// Distributed Under MIT License
(function() {
  "use strict";

  var root = this,
      Makery;

  if (typeof exports !== "undefined") {
    Makery = exports;
  } else {
    Makery = root.Makery = {};
  }

  // Require Underscore, if we're on the server, and it's not already present.
  var _ = root._;
  if (!_ && (typeof require !== 'undefined')) _ = require('underscore');

  var helpers = {
        unique: function(value) {
          return _.uniqueId(value);
        }
      };

  function extractHooks(options) {
    // No-op default hooks
    var hooks = {
      afterCreation: function() {}
    };

    _.each(hooks, function(hook, hookKey) {
      if (options[hookKey]) {
        hooks[hookKey] = options[hookKey];
        delete options[hookKey];
      }
    });

    return hooks;
  }

  function addPublicMakeTo(ctor) {
    if (!_.isFunction(ctor.make)) {
      ctor.make = function() {
        var params = arguments,
            name = "default",
            firstArg = _.first(arguments);

        if (_.isString(firstArg)) {
          name = firstArg;
          params = _.tail(arguments);
        }
        return ctor.make[name].apply(ctor.make, params);
      }
    }
  }

  function getSingleObjParam(ctor, name, option, attr) {
    var param = {};

    _.each(option, function(value, key) {
      param[key] = _.isFunction(value) ?
        value :
        function() { return value; };
    });

    _.each(attr, function(value, key) {
      param[key] = function() { return value; };
    });

    // include helpers so they can be called in the function properties
    var paramWithHelpers = _.extend({}, param, helpers);

    // iterate through param as we don't want to exec the helpers, but
    // exec every property using the context of the object with helpers.
    _.each(param, function(value, prop) {
      param[prop] = paramWithHelpers[prop]();
    });

    return param;
  }

  // We need a custom creation of an instance because we cannot
  // pass a dynamic amount of args into a constructor when calling them
  // with "new", so we must first call it with a fake constructor
  // with the same prototype and then apply the real one with the correct args.
  function createInstanceOf(ctor, args) {
    var tempCtor = function(){},
       inst, ret;

    tempCtor.prototype = ctor.prototype;
    inst = new tempCtor;

    ret = ctor.apply(inst, args);
    return Object(ret) === ret ? ret : inst;
  }

  Makery.blueprint = function(ctor, name, options) {
    options = arguments.length === 3 ? options : name;
    name = arguments.length === 3 ? name : "default";

    addPublicMakeTo(ctor);

    var usingFunction = _.isFunction(options),
        getOptions = usingFunction ?
          _.bind(options, helpers) :
          function() { return _.clone(options); };

    ctor.make[name] = function() {
      var attrs = arguments,
          defaultOptions = getDefaultOptions(),
          options = getOptions(),
          hooks = extractHooks(options),
          ctorParams = [];

      // Multiple parameters in constructor
      if (usingFunction && _.isArray(options)) {
        var attrs = attrs || [];
        _.each(options, function(option, index) {
          ctorParams.push(attrs[index] || option || defaultOptions[index]);
        });
      } else { // Single object parameter
        options = _.extend(defaultOptions[0], options);
        ctorParams.push(getSingleObjParam(ctor, name, options, attrs[0]));
      }

      var obj = createInstanceOf(ctor, ctorParams);

      hooks.afterCreation(obj);
      return obj;
    };

    // Make default options accesible, in case a named blueprint is defined.
    if (name === "default") {
      ctor.make[name].getOptions = _.isFunction(options) ?
        _.bind(options, helpers) :
        function() {return _.clone(options); };
    }

    function getDefaultOptions() {
      // If the blueprint is not the default one, and a default one is defined,
      // use that blueprint's options as options for this one.
      var defaultOptions =
        name !== "default" && _.isFunction(ctor.make["default"]) ?
          ctor.make["default"].getOptions() :
          {};
      // Always wrap default options in array, for consistent use
      if (!_.isArray(defaultOptions)) defaultOptions = [defaultOptions];
      return defaultOptions;
    }
  };

}).call(this);<commit_msg>Small refactor, code reorganization<commit_after>// Makery.js v0.2.0
// (c) 2013 Leonardo Andres Garcia Crespo
// Distributed Under MIT License
(function() {
  "use strict";

  var root = this,
      Makery;

  if (typeof exports !== "undefined") {
    Makery = exports;
  } else {
    Makery = root.Makery = {};
  }

  // Require Underscore, if we're on the server, and it's not already present.
  var _ = root._;
  if (!_ && (typeof require !== 'undefined')) _ = require('underscore');

  var helpers = {
        unique: function(value) {
          return _.uniqueId(value);
        }
      };

  function extractHooks(options) {
    // No-op default hooks
    var hooks = {
      afterCreation: function() {}
    };

    _.each(hooks, function(hook, hookKey) {
      if (options[hookKey]) {
        hooks[hookKey] = options[hookKey];
        delete options[hookKey];
      }
    });

    return hooks;
  }

  function addPublicMakeTo(ctor) {
    if (!_.isFunction(ctor.make)) {
      ctor.make = function() {
        var params = arguments,
            name = "default",
            firstArg = _.first(arguments);

        if (_.isString(firstArg)) {
          name = firstArg;
          params = _.tail(arguments);
        }
        return ctor.make[name].apply(ctor.make, params);
      };
    }
  }

  function getSingleObjParam(ctor, name, option, attr) {
    var param = {};

    _.each(option, function(value, key) {
      param[key] = _.isFunction(value) ?
        value :
        function() { return value; };
    });

    _.each(attr, function(value, key) {
      param[key] = function() { return value; };
    });

    // include helpers so they can be called in the function properties
    var paramWithHelpers = _.extend({}, param, helpers);

    // iterate through param as we don't want to exec the helpers, but
    // exec every property using the context of the object with helpers.
    _.each(param, function(value, prop) {
      param[prop] = paramWithHelpers[prop]();
    });

    return param;
  }

  // We need a custom creation of an instance because we cannot
  // pass a dynamic amount of args into a constructor when calling them
  // with "new", so we must first call it with a fake constructor
  // with the same prototype and then apply the real one with the correct args.
  function createInstanceOf(ctor, args) {
    var tempCtor = function() {
      // This will return the instance or the explicit return of the ctor
      // accordingly.
      // Remember that if you return a primitive type, the constructor will
      // still return the object instance.
      return ctor.apply(this, args);
    };

    tempCtor.prototype = ctor.prototype;
    return new tempCtor();
  }

  function getDefaultOptions(ctor, name) {
    // If the blueprint is not the default one, and a default one is defined,
    // use that blueprint's options as options for this one.
    var defaultOptions =
      name !== "default" && _.isFunction(ctor.make["default"]) ?
        ctor.make["default"].getOptions() :
        {};
    // Always wrap default options in array, for consistent use
    if (!_.isArray(defaultOptions)) defaultOptions = [defaultOptions];
    return defaultOptions;
  }

  Makery.blueprint = function(ctor, name, options) {
    options = arguments.length === 3 ? options : name;
    name = arguments.length === 3 ? name : "default";

    addPublicMakeTo(ctor);

    var usingFunction = _.isFunction(options),
        getOptions = usingFunction ?
          _.bind(options, helpers) :
          function() { return _.clone(options); };

    ctor.make[name] = function() {
      var attrs = arguments,
          defaultOptions = getDefaultOptions(ctor, name),
          options = getOptions(),
          hooks = extractHooks(options),
          ctorParams = [];

      // Multiple parameters in constructor
      if (usingFunction && _.isArray(options)) {
        _.each(options, function(option, index) {
          ctorParams.push(attrs[index] || option || defaultOptions[index]);
        });
      } else { // Single object parameter
        options = _.extend(defaultOptions[0], options);
        ctorParams.push(getSingleObjParam(ctor, name, options, attrs[0]));
      }

      var obj = createInstanceOf(ctor, ctorParams);

      hooks.afterCreation(obj);
      return obj;
    };

    // Make default options accesible, in case a named blueprint is defined.
    if (name === "default") {
      ctor.make[name].getOptions = _.isFunction(options) ?
        _.bind(options, helpers) :
        function() {return _.clone(options); };
    }
  };

}).call(this);<|endoftext|>


<commit_before>(function() {
  var Logger, logLevel, _,
    __slice = Array.prototype.slice;

  _ = require('underscore');

  exports.defaultConfig = {
    showMillis: false,
    showTimestamp: true,
    printObjFunc: require('util').inspect,
    prefix: ""
  };

  logLevel = 4;

  module.exports = Logger = (function() {

    Logger.levels = {
      error: 1,
      warning: 2,
      warn: 2,
      info: 3,
      debug: 4,
      trace: 5
    };

    Logger.setLevel = function(level, silent) {
      var levelName, levelValue, log, name, val, _ref;
      if (silent == null) silent = false;
      levelName = null;
      levelValue = null;
      _ref = Logger.levels;
      for (name in _ref) {
        val = _ref[name];
        if (level === name || level === val) {
          levelName = name;
          levelValue = val;
          break;
        }
      }
      log = new this({
        prefix: 'basic-logger'
      });
      if ((levelName != null) && (levelValue != null)) {
        if (!silent) log.info("Setting log level to '" + levelName + "'");
        return logLevel = levelValue;
      } else {
        return log.warn("Can't set log level to '" + level + "'. This level does not exist.");
      }
    };

    function Logger(config) {
      if (config == null) config = {};
      this.config = _.defaults(config, exports.defaultConfig);
    }

    Logger.prototype.padZeros = function(num, digits) {
      var zerosToAdd;
      num = String(num);
      zerosToAdd = digits - num.length;
      while (zerosToAdd > 0) {
        num = '0' + num;
        zerosToAdd = zerosToAdd - 1;
      }
      return num;
    };

    Logger.prototype.log = function() {
      var args, date, level, levelName, msg, output, timestamp;
      msg = arguments[0], levelName = arguments[1], args = 3 <= arguments.length ? __slice.call(arguments, 2) : [];
      level = Logger.levels[levelName];
      if (level <= logLevel) {
        date = new Date;
        timestamp = date.getFullYear() + "-" + this.padZeros(date.getMonth() + 1, 2) + "-" + this.padZeros(date.getDate(), 2) + " " + this.padZeros(date.getHours(), 2) + ":" + this.padZeros(date.getMinutes(), 2) + ":" + this.padZeros(date.getSeconds(), 2);
        if (this.config.showMillis) {
          timestamp += "." + this.padZeros(date.getMilliseconds(), 3);
        }
        if (typeof msg === "object") msg = this.config.printObjFunc(msg);
        output = '';
        if (this.config.showTimestamp) output += '[' + timestamp + ']';
        if (this.config.prefix !== "") output += ' ' + this.config.prefix;
        output += ' (' + levelName + ') ';
        output += msg;
        args.unshift(output);
        return console.log.apply(this, args);
      } else {
        return -1;
      }
    };

    Logger.prototype.error = function() {
      var args, msg;
      msg = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
      args.unshift(msg, 'error');
      return this.log.apply(this, args);
    };

    Logger.prototype.info = function() {
      var args, msg;
      msg = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
      args.unshift(msg, 'info');
      return this.log.apply(this, args);
    };

    Logger.prototype.warn = function() {
      var args, msg;
      msg = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
      args.unshift(msg, 'warning');
      return this.log.apply(this, args);
    };

    Logger.prototype.warning = function() {
      var args, msg;
      msg = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
      args.unshift(msg, 'warning');
      return this.log.apply(this, args);
    };

    Logger.prototype.debug = function() {
      var args, msg;
      msg = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
      args.unshift(msg, 'debug');
      return this.log.apply(this, args);
    };

    Logger.prototype.trace = function() {
      var args, msg;
      msg = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
      args.unshift(msg, 'trace');
      return this.log.apply(this, args);
    };

    return Logger;

  })();

}).call(this);
<commit_msg>Delete logger.js<commit_after><|endoftext|>


<commit_before>var MutantMap = require('./map')
var computed = require('./computed')
var resolve = require('./resolve')
var KeyValue = require('./lib/key-value')
var isObservable = require('./is-observable')

module.exports = Lookup

function Lookup (obs, lambdaOrKey, opts) {
  var mapped = MutantMap(obs, function (item, invalidateOn) {
    if (typeof lambdaOrKey === 'function') {
      var value = lambdaOrKey(item, invalidateOn)
      if (isObservable(value) && value._type === 'MutantKeyValue') {
        return value // passthru
      } else if (Array.isArray(value)) {
        return KeyValue(value[0], value[1])
      } else {
        return KeyValue(value, item)
      }
    } else if (typeof lambdaOrKey === 'string') {
      return KeyValue(item[lambdaOrKey], item)
    }
  })

  var raw = {}
  var value = {}
  var keys = new Set()

  var instance = computed.extended(mapped, function update () {
    var currentKeys = []

    for (var i = 0; i < mapped.getLength(); i++) {
      var item = mapped.get(i)
      if (item) {
        var key = resolve(item.key)
        if (key !== undefined) {
          var rawValue = item.value
          currentKeys.push(key)
          keys.add(key)
          raw[key] = rawValue
          value[key] = resolve(rawValue)
        }
      }
    }

    // remove deleted keys
    Array.from(keys.values()).filter(function (k) {
      return !currentKeys.includes(k)
    }).forEach(function (key) {
      keys.delete(key)
      delete raw[key]
      delete value[key]
    })

    return value
  })

  var result = function MutantLookup (listener) {
    return instance(listener)
  }

  result.keys = function () {
    instance.checkUpdated()
    return Array.from(keys.values())
  }

  result.get = function (key) {
    instance.checkUpdated()
    return raw[key]
  }

  return result
}
<commit_msg>lookup: fix handling of null keys<commit_after>var MutantMap = require('./map')
var computed = require('./computed')
var resolve = require('./resolve')
var KeyValue = require('./lib/key-value')
var isObservable = require('./is-observable')

module.exports = Lookup

function Lookup (obs, lambdaOrKey, opts) {
  var mapped = MutantMap(obs, function (item, invalidateOn) {
    if (typeof lambdaOrKey === 'function') {
      var value = lambdaOrKey(item, invalidateOn)
      if (isObservable(value) && value._type === 'MutantKeyValue') {
        return value // passthru
      } else if (Array.isArray(value)) {
        return KeyValue(value[0], value[1])
      } else {
        return KeyValue(value, item)
      }
    } else if (typeof lambdaOrKey === 'string') {
      return KeyValue(item[lambdaOrKey], item)
    }
  })

  var raw = {}
  var value = {}
  var keys = new Set()

  var instance = computed.extended(mapped, function update () {
    var currentKeys = []

    for (var i = 0; i < mapped.getLength(); i++) {
      var item = mapped.get(i)
      if (item) {
        var key = resolve(item.key)
        if (key != null) {
          var rawValue = item.value
          currentKeys.push(key)
          keys.add(key)
          raw[key] = rawValue
          value[key] = resolve(rawValue)
        }
      }
    }

    // remove deleted keys
    Array.from(keys.values()).filter(function (k) {
      return !currentKeys.includes(k)
    }).forEach(function (key) {
      keys.delete(key)
      delete raw[key]
      delete value[key]
    })

    return value
  })

  var result = function MutantLookup (listener) {
    return instance(listener)
  }

  result.keys = function () {
    instance.checkUpdated()
    return Array.from(keys.values())
  }

  result.get = function (key) {
    instance.checkUpdated()
    return raw[key]
  }

  return result
}
<|endoftext|>


<commit_before>var itunes = require('playback');
var fs = require('fs');
var gui = require('nw.gui');
var win = gui.Window.get();
var app = gui.App;
var np = { };
var lyricsProviders = (function() {
    var obj = require('./lyricsProviders.js');
    var arr = new Array();
    $.each(obj, function(name, item) {
        arr.push(item);
    });
    return arr;
})();
var lyrics_dir = process.env['HOME']+'/.lyrics';

$(document).ready(function() {
   NRdiv = $('#iTunesNotRunning');
   lyricsDiv = $('#lyrics');
   headerDiv = $('#header');
   loaderDiv = $('#loader');
   noLyricsDiv = $('#NoLyricsFound');
   itunes.currentTrack();
});

win.on('close', function() {this.hide()});
app.on('reopen', function() {win.show(); win.focus()})
itunes.on('playing', function(data) {
   if(!data) {
        NRdiv.show();
        lyricsDiv.hide();
        headerDiv.hide();
        loaderDiv.hide();
        return;
   }
   checkIfNewSong(data.artist, data.name, function (artist, title) {
       setCurrentTrack(artist, title);
   });
})

$(document).keydown(function(evt) {
    if ((evt.which == '115' || evt.which == '83' ) && (evt.ctrlKey || evt.metaKey)) // Cmd+S
        {
            evt.preventDefault();
            lyricsDiv.removeAttr('class');
            lyricsDiv.removeAttr('contenteditable');
            var lyrics = lyricsDiv.text();
            var artist = np.artist.replace(/ /g, "_"); var title = np.title.replace(/ /g, "_")
            saveLyrics(artist, title, lyrics);
            return false;
        }
    if (evt.keyCode == 27) { // Escape
            evt.preventDefault();
            lyricsDiv.removeAttr('class');
            lyricsDiv.removeAttr('contenteditable');
            var artist = np.artist.replace(/ /g, "_"); var title = np.title.replace(/ /g, "_")
            readLyrics(artist, title);
    }
    var tag = evt.target.tagName.toLowerCase();
    if (tag != 'input' && tag != 'textarea' && evt.target.getAttribute('contenteditable') != 'true') {
        if (String.fromCharCode(evt.keyCode) == "R") {
            itunes.currentTrack();
        }
        if (String.fromCharCode(evt.keyCode) == "D") {
            require('nw.gui').Window.get().showDevTools()
        }
        if (String.fromCharCode(evt.keyCode) == "S") {
            toggleSearch();
        }
        if (String.fromCharCode(evt.keyCode) == "E") {
            editMode();
        }
    };
});

function autoSizeText(el) {
    el.style.fontSize = null;
    function resizeText() {
        var style = window.getComputedStyle(el);
        var elNewFontSize = (parseInt(style.getPropertyValue('font-size').slice(0,-2)) -1) + 'px';
        el.style.fontSize = elNewFontSize;
    }
    while (el.offsetWidth+50 > window.innerWidth) { resizeText(); }
}

function editMode(focus) {
    noLyricsDiv.hide();
    lyricsDiv.show();
    lyricsDiv.attr('class', "editmode");
    lyricsDiv.attr('contenteditable', "true");
    if(focus) { lyricsDiv.focus(); }
}

function markAsInstrumental(artist, title) {
    var artist = artist || np.artist;
    var title = title || np.title;
    saveLyrics(artist, title, "Instrumental");
    setLyrics('Instrumental');
    noLyricsDiv.hide();
    lyricsDiv.show();
}

function webSearch(artist, title) {
    var artist = artist || np.artist;
    var title = title || np.title;
    var query = artist + ' ' + title + ' lyrics';
    gui.Shell.openExternal('http://google.com/search?q='+encodeURI(query));
}

function onSearch(e) {
    if (!e) e = window.event;
    if (e.keyCode == '13') {
        var aBox = $('#searchArtist');
        var tBox = $('#searchTitle');
        var artist = aBox.val();
        var title = tBox.val();
        toggleSearch();
        setCurrentTrack(artist, title);
    };
};

$('#searchArtist').keypress(onSearch);
$('#searchTitle').keypress(onSearch);

$('#header').dblclick(function() {
    var aBox = $('#searchArtist');
    var tBox = $('#searchTitle');
    aBox.val(np.artist);
    tBox.val(np.title);
    toggleSearch();
});

function toggleSearch() {
    var sBox = $('#search');
    var lBox = lyricsDiv;
    if(typeof sBox == "undefined") {return;}
    sBox.toggle();
    lBox.toggle();
}

function setHeader(artist, title) {
    headerDiv.show(); //Make header visible
    var divArtist = $('#artist');
    divArtist.text(artist);
    var divTitle = $('#title');
    divTitle.text(title);
    autoSizeText(divArtist[0]); autoSizeText(divTitle[0]);
}

function checkIfNewSong(artist, title, callback) {
    if(!( (np.title == title) && (np.artist == artist) )) {
        callback(artist, title);
    }
}

function setCurrentTrack(artist, title) {
    lyricsDiv.hide();
    loaderDiv.show();
    NRdiv.hide();
    np.artist = artist; np.title = title;
    setHeader(artist, title);
    getLyrics(artist, title, function(succ, lyrics) {
        if(succ) {
            //Saving lyrics
            saveLyrics(artist, title, lyrics.toString());
            setLyrics(lyrics);
        }
        else {
            setLyrics('');
            lyricsDiv.hide();
            noLyricsDiv.show();
        }
    });
    window.scrollTo(0,0);
}

function setLyrics(lyrics) {
    lyricsDiv.show();
    lyricsDiv[0].innerText = lyrics; //jQuery would ignore the newlines
    noLyricsDiv.hide();
    loaderDiv.hide();
}

function saveLyrics(artist, title, lyrics) {
    if(!fs.existsSync(lyrics_dir)) {fs.mkdirSync(lyrics_dir)}
    fs.writeFile(lyrics_dir+'/'+artist+':'+title+'.txt', lyrics.toString()+'\n');
}

function readLyrics(artist, title, callback) {
    artist = artist.replace(/ /g, "_");title = title.replace(/ /g, "_");
    if(fs.existsSync(lyrics_dir+'/'+artist+':'+title+'.txt')) {
        fs.readFile(lyrics_dir+'/'+artist+':'+title+'.txt', 'utf8', function (error, data) {
            if(error) throw error
            if(callback == null) {
                setLyrics(data);
            }
            else { callback(true, data) }
        })
        return true;
    }
    else {return false}
}

function getLyrics(artist, title, callback) {
    if(!artist || !title) {return false}
    if(readLyrics(artist, title, callback)) {return;}
    var asyncLoop = function (arr, index) {
        var item = arr[index];
        if(item) {
            item.func(artist, title, function (succ, lyrics) {
                if(succ) {
                    callback(true, lyrics);
                }
                else {
                    asyncLoop(arr, index+1);
                }
            });
        }
        else {
            callback(false);
        }
    }
    asyncLoop(lyricsProviders, 0);
}

function addMenu() {
    var alignLyrics = function (pos) { lyricsDiv.css('text-align', pos); }
    var zoom = function (n) {if(n==0) {win.zoomLevel=0} else {win.zoomLevel = win.zoomLevel + n}}
    var menubar = new gui.Menu({type: 'menubar'})
    var alignMenu = new gui.Menu(), zoomMenu = new gui.Menu();
    win.menu = menubar;
    var leftButton = new gui.MenuItem({ label: 'Left', click: function() { alignLyrics('left'); } });
    var centerButton = new gui.MenuItem({ label: 'Center', click: function() { alignLyrics('center'); } });
    var rightButton = new gui.MenuItem({ label: 'Right', click: function() { alignLyrics('right'); } });
    var zoomIn = new gui.MenuItem({ label: 'Zoom In', click: function() { zoom(1); } });
    var zoomOut = new gui.MenuItem({ label: 'Zoom out', click: function() { zoom(-1); } });
    var zoomReset = new gui.MenuItem({ label: 'Reset Zoom', click: function() { zoom(0); } });
    alignMenu.append(leftButton);
    alignMenu.append(centerButton);
    alignMenu.append(rightButton);
    zoomMenu.append(zoomIn);
    zoomMenu.append(zoomOut);
    zoomMenu.append(zoomReset);
    win.menu.insert(new gui.MenuItem({ label: 'Align', submenu: alignMenu }), 2)
    win.menu.insert(new gui.MenuItem({ label: 'Zoom', submenu: zoomMenu }), 3)
}

addMenu();
<commit_msg>Fixed pesky bug caused by jQuery .text() apparently ignores newlines, so I had to use native .innerText instead Ugly but necessary<commit_after>var itunes = require('playback');
var fs = require('fs');
var gui = require('nw.gui');
var win = gui.Window.get();
var app = gui.App;
var np = { };
var lyricsProviders = (function() {
    var obj = require('./lyricsProviders.js');
    var arr = new Array();
    $.each(obj, function(name, item) {
        arr.push(item);
    });
    return arr;
})();
var lyrics_dir = process.env['HOME']+'/.lyrics';

$(document).ready(function() {
   NRdiv = $('#iTunesNotRunning');
   lyricsDiv = $('#lyrics');
   headerDiv = $('#header');
   loaderDiv = $('#loader');
   noLyricsDiv = $('#NoLyricsFound');
   itunes.currentTrack();
});

win.on('close', function() {this.hide()});
app.on('reopen', function() {win.show(); win.focus()})
itunes.on('playing', function(data) {
   if(!data) {
        NRdiv.show();
        lyricsDiv.hide();
        headerDiv.hide();
        loaderDiv.hide();
        return;
   }
   checkIfNewSong(data.artist, data.name, function (artist, title) {
       setCurrentTrack(artist, title);
   });
})

$(document).keydown(function(evt) {
    if ((evt.which == '115' || evt.which == '83' ) && (evt.ctrlKey || evt.metaKey)) // Cmd+S
        {
            evt.preventDefault();
            lyricsDiv.removeAttr('class');
            lyricsDiv.removeAttr('contenteditable');
            var lyrics = lyricsDiv[0].innerText;
            var artist = np.artist.replace(/ /g, "_"); var title = np.title.replace(/ /g, "_")
            saveLyrics(artist, title, lyrics);
            return false;
        }
    if (evt.keyCode == 27) { // Escape
            evt.preventDefault();
            lyricsDiv.removeAttr('class');
            lyricsDiv.removeAttr('contenteditable');
            var artist = np.artist.replace(/ /g, "_"); var title = np.title.replace(/ /g, "_")
            readLyrics(artist, title);
    }
    var tag = evt.target.tagName.toLowerCase();
    if (tag != 'input' && tag != 'textarea' && evt.target.getAttribute('contenteditable') != 'true') {
        if (String.fromCharCode(evt.keyCode) == "R") {
            itunes.currentTrack();
        }
        if (String.fromCharCode(evt.keyCode) == "D") {
            require('nw.gui').Window.get().showDevTools()
        }
        if (String.fromCharCode(evt.keyCode) == "S") {
            toggleSearch();
        }
        if (String.fromCharCode(evt.keyCode) == "E") {
            editMode();
        }
    };
});

function autoSizeText(el) {
    el.style.fontSize = null;
    function resizeText() {
        var style = window.getComputedStyle(el);
        var elNewFontSize = (parseInt(style.getPropertyValue('font-size').slice(0,-2)) -1) + 'px';
        el.style.fontSize = elNewFontSize;
    }
    while (el.offsetWidth+50 > window.innerWidth) { resizeText(); }
}

function editMode(focus) {
    noLyricsDiv.hide();
    lyricsDiv.show();
    lyricsDiv.attr('class', "editmode");
    lyricsDiv.attr('contenteditable', "true");
    if(focus) { lyricsDiv.focus(); }
}

function markAsInstrumental(artist, title) {
    var artist = artist || np.artist;
    var title = title || np.title;
    saveLyrics(artist, title, "Instrumental");
    setLyrics('Instrumental');
    noLyricsDiv.hide();
    lyricsDiv.show();
}

function webSearch(artist, title) {
    var artist = artist || np.artist;
    var title = title || np.title;
    var query = artist + ' ' + title + ' lyrics';
    gui.Shell.openExternal('http://google.com/search?q='+encodeURI(query));
}

function onSearch(e) {
    if (!e) e = window.event;
    if (e.keyCode == '13') {
        var aBox = $('#searchArtist');
        var tBox = $('#searchTitle');
        var artist = aBox.val();
        var title = tBox.val();
        toggleSearch();
        setCurrentTrack(artist, title);
    };
};

$('#searchArtist').keypress(onSearch);
$('#searchTitle').keypress(onSearch);

$('#header').dblclick(function() {
    var aBox = $('#searchArtist');
    var tBox = $('#searchTitle');
    aBox.val(np.artist);
    tBox.val(np.title);
    toggleSearch();
});

function toggleSearch() {
    var sBox = $('#search');
    var lBox = lyricsDiv;
    if(typeof sBox == "undefined") {return;}
    sBox.toggle();
    lBox.toggle();
}

function setHeader(artist, title) {
    headerDiv.show(); //Make header visible
    var divArtist = $('#artist');
    divArtist.text(artist);
    var divTitle = $('#title');
    divTitle.text(title);
    autoSizeText(divArtist[0]); autoSizeText(divTitle[0]);
}

function checkIfNewSong(artist, title, callback) {
    if(!( (np.title == title) && (np.artist == artist) )) {
        callback(artist, title);
    }
}

function setCurrentTrack(artist, title) {
    lyricsDiv.hide();
    loaderDiv.show();
    NRdiv.hide();
    np.artist = artist; np.title = title;
    setHeader(artist, title);
    getLyrics(artist, title, function(succ, lyrics) {
        if(succ) {
            //Saving lyrics
            saveLyrics(artist, title, lyrics.toString());
            setLyrics(lyrics);
        }
        else {
            setLyrics('');
            lyricsDiv.hide();
            noLyricsDiv.show();
        }
    });
    window.scrollTo(0,0);
}

function setLyrics(lyrics) {
    lyricsDiv.show();
    lyricsDiv[0].innerText = lyrics; //jQuery would ignore the newlines
    noLyricsDiv.hide();
    loaderDiv.hide();
}

function saveLyrics(artist, title, lyrics) {
    if(!fs.existsSync(lyrics_dir)) {fs.mkdirSync(lyrics_dir)}
    fs.writeFile(lyrics_dir+'/'+artist+':'+title+'.txt', lyrics.toString()+'\n');
}

function readLyrics(artist, title, callback) {
    artist = artist.replace(/ /g, "_");title = title.replace(/ /g, "_");
    if(fs.existsSync(lyrics_dir+'/'+artist+':'+title+'.txt')) {
        fs.readFile(lyrics_dir+'/'+artist+':'+title+'.txt', 'utf8', function (error, data) {
            if(error) throw error
            if(callback == null) {
                setLyrics(data);
            }
            else { callback(true, data) }
        })
        return true;
    }
    else {return false}
}

function getLyrics(artist, title, callback) {
    if(!artist || !title) {return false}
    if(readLyrics(artist, title, callback)) {return;}
    var asyncLoop = function (arr, index) {
        var item = arr[index];
        if(item) {
            item.func(artist, title, function (succ, lyrics) {
                if(succ) {
                    callback(true, lyrics);
                }
                else {
                    asyncLoop(arr, index+1);
                }
            });
        }
        else {
            callback(false);
        }
    }
    asyncLoop(lyricsProviders, 0);
}

function addMenu() {
    var alignLyrics = function (pos) { lyricsDiv.css('text-align', pos); }
    var zoom = function (n) {if(n==0) {win.zoomLevel=0} else {win.zoomLevel = win.zoomLevel + n}}
    var menubar = new gui.Menu({type: 'menubar'})
    var alignMenu = new gui.Menu(), zoomMenu = new gui.Menu();
    win.menu = menubar;
    var leftButton = new gui.MenuItem({ label: 'Left', click: function() { alignLyrics('left'); } });
    var centerButton = new gui.MenuItem({ label: 'Center', click: function() { alignLyrics('center'); } });
    var rightButton = new gui.MenuItem({ label: 'Right', click: function() { alignLyrics('right'); } });
    var zoomIn = new gui.MenuItem({ label: 'Zoom In', click: function() { zoom(1); } });
    var zoomOut = new gui.MenuItem({ label: 'Zoom out', click: function() { zoom(-1); } });
    var zoomReset = new gui.MenuItem({ label: 'Reset Zoom', click: function() { zoom(0); } });
    alignMenu.append(leftButton);
    alignMenu.append(centerButton);
    alignMenu.append(rightButton);
    zoomMenu.append(zoomIn);
    zoomMenu.append(zoomOut);
    zoomMenu.append(zoomReset);
    win.menu.insert(new gui.MenuItem({ label: 'Align', submenu: alignMenu }), 2)
    win.menu.insert(new gui.MenuItem({ label: 'Zoom', submenu: zoomMenu }), 3)
}

addMenu();
<|endoftext|>


<commit_before>var fs = require('fs');
var arg = process.argv[2];

var stream = fs.createReadStream(arg);

var bs = [];
var table = {};
stream.on('data', function(chunk) {
    bs.push(chunk);
});

stream.on('end', function() {
    var res = Buffer.concat(bs).toString();
    var array = res.split(/\r\n|\n|\r| /);

    array = array.filter(function(el) {
        // remove empty strings
        return el.length > 0;
    }).filter(function(el) {
        // remove Roman numbers
        return el.replace(/[LMCXIV]/g, '').length > 0;
    }).filter(function(el) {
        // remove all latin numbers
        return el.replace(/[0-9:]/g,'').length > 0;
    });

    var key, value;
    for (var i = 0 ; i < array.length; i++) {
        key = '' + array[i];
        value = array[i + 1] || array[0];
        if (typeof table[key] === 'undefined') {
            table[key] = [value];
        } else {
            if (typeof table[key].push !== 'undefined' && table[key].indexOf(value) === -1) {
               table[key].push(value);
           }
        }
    }

    var start = 0;
    var end = 5000;
    var s = '';
    var current = array[0];
    s += current;
    for (start; start < end; start++) {
        if (table[current]) {
            current = table[current][Math.floor(Math.random() * table[current].length)];
            s += ' ';
            s += current;
        }
    }
    s += '.';
    var s = s.replace(/^([a-z]{1})|([\.\?\!]{1} [a-z]{1})/g, function(match) { return match.toUpperCase(); });
    console.log(s);
});

<commit_msg>made a partly stream based version<commit_after>var fs = require('fs');
var arg = process.argv[2];

var util = require('util');
var Transform = require('stream').Transform;

util.inherits(CleanUp, Transform);
util.inherits(ProcessToTable, Transform);

var cleanUp = new CleanUp({ decodeStrings: false, objectMode: true });
var processToTable = new ProcessToTable({ objectMode: true });

var readable = fs.createReadStream(arg);
readable.setEncoding('utf8');

readable.pipe(cleanUp)
.pipe(processToTable);
.pipe(process.stdout);

function CleanUp(options) {
    if (!(this instanceof CleanUp)) {
        return new CleanUp(options);
    }

    Transform.call(this, options);
}

CleanUp.prototype._transform = function(data, encoding, done) {
    var array = data.split(/\r\n|\n|\r| /);
    array = array.filter(function(el) {
        // remove empty strings
        return el.length > 0;
    }).filter(function(el) {
        // remove Roman numbers
        return el.replace(/[LMCXIV]/g, '').length > 0;
    }).filter(function(el) {
        // remove all latin numbers
        return el.replace(/[0-9:]/g,'').length > 0;
    });
    array.reverse().forEach(this.push.bind(this));
    done();
};

function ProcessToTable(options) {
    Transform.call(this, options);

    this._former;
    this._table = {};
}

ProcessToTable.prototype._transform = function(key, encoding, done) {
    if (typeof this._former === 'undefined') {
        this._former = key;
    }

    var value = this._former;
    if (typeof this._table[key] === 'undefined') {
        this._table[key] = [value];
    } else {
        if (typeof this._table[key].push !== 'undefined'
            && this._table[key].indexOf(value) === -1) {
           this._table[key].push(value);
       }
    }

    this._former = key;
    done();
};

ProcessToTable.prototype._flush = function(done) {
    this._table[Object.keys(this._table)[0]] = [this._former];
    done();
}

processToTable.on('end', function() {
    createText(this._table);
});

function createText(table) {
    var start = 0;
    var end = 500;
    var s = [];
    var current = Object.keys(table)[0];
    s.push(current);
    for (start; start < end; start++) {
        if (table[current]) {
            current = table[current][Math.floor(Math.random() * table[current].length)];
            if (start === end - 1) {
                current += '.';
            }
            s.push(current);
        }

    }
    var s = s.join(' ').replace(/^([a-z]{1})|([\.\?\!]{1} [a-z]{1})/g, function(match) {
        return match.toUpperCase();
    });

    console.log(s);
}

<|endoftext|>


<commit_before>
global.debug = require('./lib/debug')();

var cluster = require('cluster'),
    util = require('util');

var config = require('./lib/config'),
    env = require('./lib/env'),
    utils = require('./lib/utils');

var memory = {};

var deadWorkers;
var readyWorkers;
var suicide;

console.log('Starting Adminion game server...');

cluster.setupMaster({
    exec: "worker.js",
    silent: (config.debug) ? false : true
});

cluster.on('fork', function fork (worker) {
    // debug.emit('msg', 'Forking worker ' + worker.id + '.');
});

cluster.on('online', function online (worker) {
    // debug.emit('msg', 'Worker ' + worker.id + ' online.');
    initWorker(worker);
});

cluster.on('disconnect', function disconnect (worker) {
    debug.emit('msg', 'Worker ' + worker.id + ' disconnected.');
    delete memory[worker.id];
});

cluster.on('exit', function exit (worker) {
    debug.emit('msg', 'Worker ' + worker.id + ' died.');

    delete memory[worker.id];

    deadWorkers +=1;

    if (suicide) {
        if (deadWorkers === config.workers) {
            process.kill();
        }
    } else {
        cluster.fork();
    }

});

function restart (worker) {
    console.log('trying to restart the server...');
    // if a given worker was specified
    if (worker) {
        worker.kill();
        cluster.fork();
    } else {
        for (var id in cluster.workers) {
            cluster.workers[id].kill();
        }

        startWorkers();
    }
};

function stop () {

    suicide = true;
    deadWorkers = 0;

    for (var id in cluster.workers) {
        cluster.workers[id].kill();
    }

};

function totalMemory () {

    var masterMemory = process.memoryUsage().heapTotal;
    var workerTotal = 0;

    // debug.emit('val', 'masterMemory', masterMemory, 'master.js', 73);
    // debug.emit('val', 'memory', memory, 'master.js', 74);
    // debug.emit('val', 'workerTotal', workerTotal, 'master.js', 75);

    for (var workerId in memory) {
        workerTotal += memory[workerId];

        // debug.emit('val', 'workerId', workerId, 'master', 78);
        // debug.emit('val', 'memory[' + workerId + ']', memory[workerId], 'master', 79);
        // debug.emit('val', 'workerTotal', workerTotal, 'master', 81);

    }

    var serverTotal = masterMemory + workerTotal;

    // debug.emit('val', 'serverTotal', serverTotal, 'master', 89);

    return serverTotal / utils.MB;
};

function allReady () {
    var msg = '\n\t%s worker';

    if ( config.workers > 1 ) {
        msg += 's';
    }

    msg += ' running.';

    console.log(util.format(msg, config.workers));

    // notify the console user that the server is ready
    console.log('\nAdminion Game Server Started!\n --> %s', env.url());

    console.log('\ntotal memory usage: %s MB', totalMemory());

    process.stdout.write('> ');

    process.stdin.resume();
    process.stdin.setEncoding('utf8');

    process.stdin.on('data', function (chunk) {
        switch (chunk.trim()) {
            case 'restart':
                restart();
                break;

            case 'stop':
                stop();
                break;
        }

        process.stdout.write('> ');
    });

    process.stdin.on('end', function end() {
        process.stdout.write('end');
    });
};

function initWorker (worker) {
    // when a message from this worker is received
    worker.on('message', function message (data) {

        // debug.emit('msg', 'message received from worker ' + workerId, 'master.js', 132);

        // if the worker is saying it is ready...
        if (data['ready'] && data['ready'] === true) {
            // add 1 to the readyWorkers count
            readyWorkers += 1;

            memory[worker.id] = data['memoryUsage'];

            // if all of our workers are ready...
            if (readyWorkers === config.workers) {
                allReady();
            }
        }
    });
};

function startWorkers() {

    readyWorkers = 0;

    // start all the workers
    for (var i = 0; i < config.workers; i+=1) {

        cluster.fork();
    }
}

startWorkers();<commit_msg>"while" is much cleaner than "for", in this case<commit_after>
console.log('Starting Adminion game server...');

global.debug = require('./lib/debug')();

var cluster = require('cluster'),
    util = require('util');

var config = require('./lib/config'),
    env = require('./lib/env'),
    utils = require('./lib/utils');

var memory = {};

var deadWorkers;
var readyWorkers;
var suicide;

cluster.setupMaster({
    exec: "worker.js",
    silent: (config.debug) ? false : true
});

cluster.on('fork', function fork (worker) {
    // debug.emit('msg', 'Forking worker ' + worker.id + '.');
});

cluster.on('online', function online (worker) {
    // debug.emit('msg', 'Worker ' + worker.id + ' online.');
    initWorker(worker);
});

cluster.on('disconnect', function disconnect (worker) {
    debug.emit('msg', 'Worker ' + worker.id + ' disconnected.');
    delete memory[worker.id];
});

cluster.on('exit', function exit (worker) {
    debug.emit('msg', 'Worker ' + worker.id + ' died.');

    delete memory[worker.id];

    deadWorkers +=1;

    if (suicide) {
        if (deadWorkers === config.workers) {
            process.kill();
        }
    } else {
        cluster.fork();
    }

});

function restart (worker) {

    console.log('restarting the server...');

    // if a given worker was specified
    if (worker) {
        worker.kill();
        cluster.fork();
    } else {
        for (var id in cluster.workers) {
            cluster.workers[id].kill();
        }

        startWorkers();
    }
};

function stop () {

    suicide = true;
    deadWorkers = 0;

    for (var id in cluster.workers) {
        cluster.workers[id].kill();
    }

};

function totalMemory () {

    var masterMemory = process.memoryUsage().heapTotal;
    var workerTotal = 0;

    // debug.emit('val', 'masterMemory', masterMemory, 'master.js', 73);
    // debug.emit('val', 'memory', memory, 'master.js', 74);
    // debug.emit('val', 'workerTotal', workerTotal, 'master.js', 75);

    for (var workerId in memory) {
        workerTotal += memory[workerId];

        // debug.emit('val', 'workerId', workerId, 'master', 78);
        // debug.emit('val', 'memory[' + workerId + ']', memory[workerId], 'master', 79);
        // debug.emit('val', 'workerTotal', workerTotal, 'master', 81);

    }

    var serverTotal = masterMemory + workerTotal;

    // debug.emit('val', 'serverTotal', serverTotal, 'master', 89);

    return serverTotal / utils.MB;
};

function allReady () {
    var msg = '\n\t%s worker';

    if ( config.workers > 1 ) {
        msg += 's';
    }

    msg += ' running.';

    console.log(util.format(msg, config.workers));

    // notify the console user that the server is ready
    console.log('\nAdminion Game Server Started!\n --> %s', env.url());

    console.log('\ntotal memory usage: %s MB', totalMemory());

    process.stdout.write('> ');

    process.stdin.resume();
    process.stdin.setEncoding('utf8');

    process.stdin.on('data', function (chunk) {
        switch (chunk.trim()) {
            case 'restart':
                restart();
            break;

            case 'stop':
                stop();
            break;
        }

        process.stdout.write('> ');
    });

    process.stdin.on('end', function end() {
        process.stdout.write('end');
    });
};

function initWorker (worker) {
    // when a message from this worker is received
    worker.on('message', function message (data) {

        // debug.emit('msg', 'message received from worker ' + workerId, 'master.js', 132);

        // if the worker is saying it is ready...
        if (data['ready'] && data['ready'] === true) {
            // add 1 to the readyWorkers count
            readyWorkers += 1;

            memory[worker.id] = data['memoryUsage'];

            // if all of our workers are ready...
            if (readyWorkers === config.workers) {
                allReady();
            }
        }
    });
};

function startWorkers() {

    var forked = 0;
    readyWorkers = 0;

    // start all the workers
    while (forked < config.workers) {
        cluster.fork();
        forked +=1;
    }
}

startWorkers();<|endoftext|>