<commit_before>'use strict'

const debounce=require('crnx-base/fake-lodash/debounce')
const formatNumbers=require('crnx-base/format-numbers')
const GroupOptionOutput=require('crnx-base/group-option-output')

const width=300
const height=300
const pad=30
const maxNGridLines=10
const fontSize=10
const fontOffset=3
let audioContext
let frequencyArray
let magnitudeArray,phaseArray
let magnitudeArray0,phaseArray0

class FilterOptionOutput extends GroupOptionOutput {
	constructor(option,writeOption,i18n,generateId) {
		super(option,writeOption,i18n,generateId)
		let $freqResponseUi=$()
		const isFreqResponseUiShown=()=>$freqResponseUi.length>0
		let magnitudeCanvasContext, phaseCanvasContext // TODO remove loitering on hide
		let magnitudeLogScale=true
		const updatePlots=(filterNodes)=>{
			for (let i=0;i<magnitudeArray.length;i++) {
				magnitudeArray[i]=1
			}
			for (let i=0;i<phaseArray.length;i++) {
				phaseArray[i]=0
			}
			filterNodes.forEach(filterNode=>{
				filterNode.getFrequencyResponse(frequencyArray,magnitudeArray0,phaseArray0)
				for (let i=0;i<magnitudeArray.length;i++) {
					magnitudeArray[i]*=magnitudeArray0[i]
				}
				for (let i=0;i<phaseArray.length;i++) {
					phaseArray[i]+=phaseArray0[i]
				}
			})
			if (magnitudeLogScale) {
				for (let i=0;i<width;i++) { // convert to decibels
					magnitudeArray[i]=20*Math.log(magnitudeArray[i])/Math.LN10
				}
			}
			function wrap(v,maxAbs) {
				v%=maxAbs*2
				if (Math.abs(v)<=maxAbs) return v
				return v-(v>0?1:-1)*maxAbs*2
			}
			let k=0
			let v0=0
			for (let i=0;i<width;i++) { // unwrap phase
				let v1=wrap(phaseArray[i]/Math.PI,1)
				k+=(v1<v0 && (v0-v1)>((v1+2)-v0))
				k-=(v1>v0 && (v1-v0)>(v0-(v1-2)))
				v0=v1
				phaseArray[i]=v0+2*k
			}
			const plotResponse=(canvasContext,array,units,enforcedCenter,enforcedRange,majorGridLineTest)=>{
				let min,max
				const setPlotRange=(min0,max0)=>{
					const ph=pad/height
					min=((min0+max0)*ph-min0)/(2*ph-1)
					max=((min0+max0)*ph-max0)/(2*ph-1)
				}
				const minArr=Math.min(...array)-enforcedCenter
				const maxArr=Math.max(...array)-enforcedCenter
				setPlotRange(
					enforcedCenter+Math.min(0,-enforcedRange+Math.max(0,maxArr-enforcedRange),minArr),
					enforcedCenter+Math.max(0,enforcedRange-Math.max(0,-enforcedRange-minArr),maxArr)
				)
				const calcX=v=>width*(v-frequencyArray[0])/(frequencyArray[width-1]-frequencyArray[0])
				const calcY=v=>height*(1-(v-min)/(max-min))
				const drawGrid=(min,max,calcY,labelLimit,units,majorGridLineTest)=>{
					canvasContext.save()
					canvasContext.translate(0,0.5) // don't translate along x to keep dashes sharp
					canvasContext.fillStyle='#444'
					canvasContext.font=`${fontSize}px`
					canvasContext.textAlign='right'
					const numLogs=Math.log(max-min)-Math.log(maxNGridLines)
					let k
					let p=Infinity
					;[1,2,5].forEach(tk=>{
						const tp=Math.ceil((numLogs-Math.log(tk))/Math.LN10)
						if (tp<p) {
							p=tp
							k=tk
						}
					})
					const dv=k*Math.pow(10,p)
					const v0=Math.ceil(min/dv)*dv
					const numbers=[]
					for (let v=v0;v<max;v+=dv) {
						numbers.push(v)
					}
					const fmt=formatNumbers(numbers,p<0?-p:0)
					const getLabel=n=>`${i18n.number(n)}${units}`
					const labelWidth=Math.max(...fmt.map(n=>canvasContext.measureText(getLabel(n)).width))
					for (let v=v0,i=0;v<max;v+=dv,i++) {
						const y=Math.round(calcY(v))
						const drawLine=()=>{
							canvasContext.beginPath()
							canvasContext.moveTo(0,y)
							canvasContext.lineTo(width,y)
							canvasContext.stroke()
						}
						const majorGridLine=majorGridLineTest(Number(fmt[i]))
						if (majorGridLine) {
							canvasContext.strokeStyle='rgba(70%,70%,100%,0.3)'
							canvasContext.setLineDash([])
							canvasContext.lineWidth=3
							drawLine()
						}
						canvasContext.strokeStyle=(majorGridLine ? '#000' : '#888')
						canvasContext.setLineDash([1])
						canvasContext.lineWidth=1
						drawLine()
						if (y-fontOffset-fontSize>labelLimit) {
							canvasContext.fillText(getLabel(fmt[i]),fontOffset+labelWidth,y-fontOffset)
						}
					}
					canvasContext.restore()
					return labelWidth
				}
				canvasContext.clearRect(0,0,width,height)
				const yLabelWidth=drawGrid(min,max,calcY,0,units,majorGridLineTest)
				canvasContext.save()
				canvasContext.rotate(-Math.PI/2)
				canvasContext.translate(-height,0)
				drawGrid(frequencyArray[0],frequencyArray[width-1],calcX,fontOffset+yLabelWidth,' '+i18n('units.hertz.a'),v=>false)
				canvasContext.restore()
				canvasContext.save()
				canvasContext.translate(0.5,0.5)
				canvasContext.strokeStyle='#F00'
				canvasContext.beginPath()
				for (let i=0;i<width;i++) {
					const x=i
					const y=calcY(array[i])
					if (i==0) {
						canvasContext.moveTo(x,y)
					} else {
						canvasContext.lineTo(x,y)
					}
				}
				canvasContext.stroke()
				canvasContext.restore()
			}
			if (magnitudeLogScale) {
				plotResponse(magnitudeCanvasContext,magnitudeArray,' '+i18n('units.decibel.a'),0,1,v=>v==0)
			} else {
				plotResponse(magnitudeCanvasContext,magnitudeArray,'',1,0.1,v=>v==1)
			}
			plotResponse(phaseCanvasContext,phaseArray,'π',0,0.1,v=>v%2==0)
		}
		const delayedUpdate=debounce(()=>{
			try {
				updatePlots(this.getFilterNodes(audioContext))
			} catch (e) {}
		},50)
		let $frOption
		const This=this
		this.$output.append(
			$frOption=$("
").append(
				""+i18n('options-output.filter.frequencyResponse')+": ",
				$("<button type='button'>"+i18n('options-output.show')+"</button>").click(function(){
					const $button=$(this)
					if (!isFreqResponseUiShown()) {
						This.runIfCanCreateAudioContext(audioContext=>{
							let filterNodes
							try {
								filterNodes=This.getFilterNodes(audioContext)
							} catch (e) {
								$button.replaceWith(i18n('options-output.filter.nodeError'))
								return
							}
							let $magnitudeCanvas, $phaseCanvas
							$freqResponseUi=$freqResponseUi.add(
								" "
							).add(
								$("").append(
									$("<input type='checkbox'>").prop('checked',magnitudeLogScale).change(function(){
										magnitudeLogScale=$(this).prop('checked')
										delayedUpdate()
									}),
									" ",
									"log magnitude scale" // TODO i18n
								)
							).add(
								" "
							).add(
								$("
").append(
									$("
").append(
										"
"+i18n('options-output.filter.magnitude')+"
",
										$magnitudeCanvas=$(`<canvas width='${width}' height='${height}'></canvas>`)
									),
									$("
").append(
										"
"+i18n('options-output.filter.phase')+"
",
										$phaseCanvas=$(`<canvas width='${width}' height='${height}'></canvas>`)
									)
								)
							)
							$button.after($freqResponseUi).text(i18n('options-output.hide'))
							magnitudeCanvasContext=$magnitudeCanvas[0].getContext('2d')
							phaseCanvasContext=$phaseCanvas[0].getContext('2d')
							updatePlots(filterNodes)
							$frOption.removeClass('only-buttons')
						},$button,i18n('options-output.filter.contextError'))
					} else {
						// TODO cancel scheduled update
						$freqResponseUi.remove()
						$freqResponseUi=$()
						$button.text(i18n('options-output.show'))
						$frOption.addClass('only-buttons')
					}
				})
			)
		)
		option.addUpdateCallback(()=>{
			if (isFreqResponseUiShown()) {
				delayedUpdate()
			}
		})
	}
	runIfCanCreateAudioContext(fn,$ui,errorMessage) {
		const initAudioContext=()=>{
			audioContext=new (AudioContext || webkitAudioContext)
			frequencyArray=new Float32Array(width)
			magnitudeArray=new Float32Array(width)
			phaseArray=new Float32Array(width)
			magnitudeArray0=new Float32Array(width)
			phaseArray0=new Float32Array(width)
			const maxFreq=audioContext.sampleRate/2
			for (let i=0;i<width;i++) {
				frequencyArray[i]=maxFreq/width*(i+0.5)
			}
		}
		if (!audioContext) {
			try {
				initAudioContext()
			} catch (e) {
				$ui.replaceWith(errorMessage)
				return
			}
		}
		fn(audioContext)
	}
	// abstract
	// getFilterNodes(audioContext)
}

module.exports=FilterOptionOutput
<commit_msg>frequency response: both 0 and 1 are major grid lines for linear magnitude plot<commit_after>'use strict'

const debounce=require('crnx-base/fake-lodash/debounce')
const formatNumbers=require('crnx-base/format-numbers')
const GroupOptionOutput=require('crnx-base/group-option-output')

const width=300
const height=300
const pad=30
const maxNGridLines=10
const fontSize=10
const fontOffset=3
let audioContext
let frequencyArray
let magnitudeArray,phaseArray
let magnitudeArray0,phaseArray0

class FilterOptionOutput extends GroupOptionOutput {
	constructor(option,writeOption,i18n,generateId) {
		super(option,writeOption,i18n,generateId)
		let $freqResponseUi=$()
		const isFreqResponseUiShown=()=>$freqResponseUi.length>0
		let magnitudeCanvasContext, phaseCanvasContext // TODO remove loitering on hide
		let magnitudeLogScale=true
		const updatePlots=(filterNodes)=>{
			for (let i=0;i<magnitudeArray.length;i++) {
				magnitudeArray[i]=1
			}
			for (let i=0;i<phaseArray.length;i++) {
				phaseArray[i]=0
			}
			filterNodes.forEach(filterNode=>{
				filterNode.getFrequencyResponse(frequencyArray,magnitudeArray0,phaseArray0)
				for (let i=0;i<magnitudeArray.length;i++) {
					magnitudeArray[i]*=magnitudeArray0[i]
				}
				for (let i=0;i<phaseArray.length;i++) {
					phaseArray[i]+=phaseArray0[i]
				}
			})
			if (magnitudeLogScale) {
				for (let i=0;i<width;i++) { // convert to decibels
					magnitudeArray[i]=20*Math.log(magnitudeArray[i])/Math.LN10
				}
			}
			function wrap(v,maxAbs) {
				v%=maxAbs*2
				if (Math.abs(v)<=maxAbs) return v
				return v-(v>0?1:-1)*maxAbs*2
			}
			let k=0
			let v0=0
			for (let i=0;i<width;i++) { // unwrap phase
				let v1=wrap(phaseArray[i]/Math.PI,1)
				k+=(v1<v0 && (v0-v1)>((v1+2)-v0))
				k-=(v1>v0 && (v1-v0)>(v0-(v1-2)))
				v0=v1
				phaseArray[i]=v0+2*k
			}
			const plotResponse=(canvasContext,array,units,enforcedCenter,enforcedRange,majorGridLineTest)=>{
				let min,max
				const setPlotRange=(min0,max0)=>{
					const ph=pad/height
					min=((min0+max0)*ph-min0)/(2*ph-1)
					max=((min0+max0)*ph-max0)/(2*ph-1)
				}
				const minArr=Math.min(...array)-enforcedCenter
				const maxArr=Math.max(...array)-enforcedCenter
				setPlotRange(
					enforcedCenter+Math.min(0,-enforcedRange+Math.max(0,maxArr-enforcedRange),minArr),
					enforcedCenter+Math.max(0,enforcedRange-Math.max(0,-enforcedRange-minArr),maxArr)
				)
				const calcX=v=>width*(v-frequencyArray[0])/(frequencyArray[width-1]-frequencyArray[0])
				const calcY=v=>height*(1-(v-min)/(max-min))
				const drawGrid=(min,max,calcY,labelLimit,units,majorGridLineTest)=>{
					canvasContext.save()
					canvasContext.translate(0,0.5) // don't translate along x to keep dashes sharp
					canvasContext.fillStyle='#444'
					canvasContext.font=`${fontSize}px`
					canvasContext.textAlign='right'
					const numLogs=Math.log(max-min)-Math.log(maxNGridLines)
					let k
					let p=Infinity
					;[1,2,5].forEach(tk=>{
						const tp=Math.ceil((numLogs-Math.log(tk))/Math.LN10)
						if (tp<p) {
							p=tp
							k=tk
						}
					})
					const dv=k*Math.pow(10,p)
					const v0=Math.ceil(min/dv)*dv
					const numbers=[]
					for (let v=v0;v<max;v+=dv) {
						numbers.push(v)
					}
					const fmt=formatNumbers(numbers,p<0?-p:0)
					const getLabel=n=>`${i18n.number(n)}${units}`
					const labelWidth=Math.max(...fmt.map(n=>canvasContext.measureText(getLabel(n)).width))
					for (let v=v0,i=0;v<max;v+=dv,i++) {
						const y=Math.round(calcY(v))
						const drawLine=()=>{
							canvasContext.beginPath()
							canvasContext.moveTo(0,y)
							canvasContext.lineTo(width,y)
							canvasContext.stroke()
						}
						const majorGridLine=majorGridLineTest(Number(fmt[i]))
						if (majorGridLine) {
							canvasContext.strokeStyle='rgba(70%,70%,100%,0.3)'
							canvasContext.setLineDash([])
							canvasContext.lineWidth=3
							drawLine()
						}
						canvasContext.strokeStyle=(majorGridLine ? '#000' : '#888')
						canvasContext.setLineDash([1])
						canvasContext.lineWidth=1
						drawLine()
						if (y-fontOffset-fontSize>labelLimit) {
							canvasContext.fillText(getLabel(fmt[i]),fontOffset+labelWidth,y-fontOffset)
						}
					}
					canvasContext.restore()
					return labelWidth
				}
				canvasContext.clearRect(0,0,width,height)
				const yLabelWidth=drawGrid(min,max,calcY,0,units,majorGridLineTest)
				canvasContext.save()
				canvasContext.rotate(-Math.PI/2)
				canvasContext.translate(-height,0)
				drawGrid(frequencyArray[0],frequencyArray[width-1],calcX,fontOffset+yLabelWidth,' '+i18n('units.hertz.a'),v=>false)
				canvasContext.restore()
				canvasContext.save()
				canvasContext.translate(0.5,0.5)
				canvasContext.strokeStyle='#F00'
				canvasContext.beginPath()
				for (let i=0;i<width;i++) {
					const x=i
					const y=calcY(array[i])
					if (i==0) {
						canvasContext.moveTo(x,y)
					} else {
						canvasContext.lineTo(x,y)
					}
				}
				canvasContext.stroke()
				canvasContext.restore()
			}
			if (magnitudeLogScale) {
				plotResponse(magnitudeCanvasContext,magnitudeArray,' '+i18n('units.decibel.a'),0,1,v=>v==0)
			} else {
				plotResponse(magnitudeCanvasContext,magnitudeArray,'',1,0.1,v=>(v==0 || v==1))
			}
			plotResponse(phaseCanvasContext,phaseArray,'π',0,0.1,v=>v%2==0)
		}
		const delayedUpdate=debounce(()=>{
			try {
				updatePlots(this.getFilterNodes(audioContext))
			} catch (e) {}
		},50)
		let $frOption
		const This=this
		this.$output.append(
			$frOption=$("
").append(
				""+i18n('options-output.filter.frequencyResponse')+": ",
				$("<button type='button'>"+i18n('options-output.show')+"</button>").click(function(){
					const $button=$(this)
					if (!isFreqResponseUiShown()) {
						This.runIfCanCreateAudioContext(audioContext=>{
							let filterNodes
							try {
								filterNodes=This.getFilterNodes(audioContext)
							} catch (e) {
								$button.replaceWith(i18n('options-output.filter.nodeError'))
								return
							}
							let $magnitudeCanvas, $phaseCanvas
							$freqResponseUi=$freqResponseUi.add(
								" "
							).add(
								$("").append(
									$("<input type='checkbox'>").prop('checked',magnitudeLogScale).change(function(){
										magnitudeLogScale=$(this).prop('checked')
										delayedUpdate()
									}),
									" ",
									"log magnitude scale" // TODO i18n
								)
							).add(
								" "
							).add(
								$("
").append(
									$("
").append(
										"
"+i18n('options-output.filter.magnitude')+"
",
										$magnitudeCanvas=$(`<canvas width='${width}' height='${height}'></canvas>`)
									),
									$("
").append(
										"
"+i18n('options-output.filter.phase')+"
",
										$phaseCanvas=$(`<canvas width='${width}' height='${height}'></canvas>`)
									)
								)
							)
							$button.after($freqResponseUi).text(i18n('options-output.hide'))
							magnitudeCanvasContext=$magnitudeCanvas[0].getContext('2d')
							phaseCanvasContext=$phaseCanvas[0].getContext('2d')
							updatePlots(filterNodes)
							$frOption.removeClass('only-buttons')
						},$button,i18n('options-output.filter.contextError'))
					} else {
						// TODO cancel scheduled update
						$freqResponseUi.remove()
						$freqResponseUi=$()
						$button.text(i18n('options-output.show'))
						$frOption.addClass('only-buttons')
					}
				})
			)
		)
		option.addUpdateCallback(()=>{
			if (isFreqResponseUiShown()) {
				delayedUpdate()
			}
		})
	}
	runIfCanCreateAudioContext(fn,$ui,errorMessage) {
		const initAudioContext=()=>{
			audioContext=new (AudioContext || webkitAudioContext)
			frequencyArray=new Float32Array(width)
			magnitudeArray=new Float32Array(width)
			phaseArray=new Float32Array(width)
			magnitudeArray0=new Float32Array(width)
			phaseArray0=new Float32Array(width)
			const maxFreq=audioContext.sampleRate/2
			for (let i=0;i<width;i++) {
				frequencyArray[i]=maxFreq/width*(i+0.5)
			}
		}
		if (!audioContext) {
			try {
				initAudioContext()
			} catch (e) {
				$ui.replaceWith(errorMessage)
				return
			}
		}
		fn(audioContext)
	}
	// abstract
	// getFilterNodes(audioContext)
}

module.exports=FilterOptionOutput
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'foam.core',
  name: 'ValidationPredicate',
  properties: [
    {
      name: 'predicateFactory'
    },
    {
      class: 'FObjectProperty',
      of: 'foam.mlang.predicate.Predicate',
      name: 'predicate',
      expression: function(predicateFactory) {
        return predicateFactory ?
          predicateFactory(foam.mlang.ExpressionsSingleton.create()) :
          null;
      }
    },
    {
      class: 'StringArray',
      name: 'args'
    },
    {
      class: 'Function',
      name: 'jsFunc',
      expression: function(predicate, jsErr) {
        return function() {
          if ( ! predicate.f(this) ) return jsErr(this);
        };
      }
    },
    {
      class: 'String',
      name: 'errorString'
    },
    {
      class: 'Function',
      name: 'jsErr',
      expression: function(errorString) {
        return function() { return errorString; };
      }
    }
  ],
  methods: [
    function createErrorSlotFor(data) {
      return this.ExpressionSlot.create({
        args: this.args.map(a => data[a+'$']),
        code: this.jsFunc.bind(data)
      });
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'PropertyValidationRefinement',
  refines: 'foam.core.Property',

  properties: [
    {
      class: 'FObjectArray',
      of: 'foam.core.ValidationPredicate',
      name: 'validationPredicates',
      // We override 'compare' here because we need to avoid infinite recursion
      // that occurs when a validation predicate for a given property contains a
      // reference to the property itself.
      // This is an incorrect implementation of compare since it will always
      // return a match, even if the validation predicates are different. It
      // would be preferable to find a way to deal with circular references.
      compare: function() { return 0; }
    },
    {
      name: 'validateObj',
      expression: function(name, label, required, validationPredicates) {
        if ( validationPredicates.length ) {
          var args = foam.Array.unique(validationPredicates
            .map(vp => vp.args)
            .flat());
          return [args, function() {
            for ( var i = 0 ; i < validationPredicates.length ; i++ ) {
              var vp = validationPredicates[i];
              if ( vp.jsFunc.bind(this)() ) return vp.jsErr.bind(this)();
            }
            return null;
          }];
        }
        return !required ? null : [[name],
          function() {
            const axiom = this.cls_.getAxiomByName(name);
            return axiom.isDefaultValue(this[name]) && (`Please enter ${label.toLowerCase()}`);
          }]
      },
    },
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'StringPropertyValidationRefinement',
  refines: 'foam.core.String',
  properties: [
    'minLength',
    'maxLength',
    {
      class: 'FObjectArray',
      of: 'foam.core.ValidationPredicate',
      name: 'validationPredicates',
      factory: function() {
        var self = this;
        var a = [];
        if ( foam.Number.isInstance(this.minLength) ) {
          a.push({
            args: [this.name],
            predicateFactory: function(e) {
              return e.GTE(foam.mlang.StringLength.create({ arg1: self }), self.minLength);
            },
            errorString: `Please enter ${this.label.toLowerCase()} with at least ${this.minLength} character${this.minLength>1?'s':''}`
          });
        }
        if ( foam.Number.isInstance(this.maxLength) ) {
          a.push({
            args: [this.name],
            predicateFactory: function(e) {
              return e.LTE(foam.mlang.StringLength.create({ arg1: self }), self.maxLength);
            },
            errorString: `Please enter ${this.label.toLowerCase()} with at most ${this.maxLength} character${this.maxLength>1?'s':''}`
          });
        }
        if ( this.required && ! foam.Number.isInstance(this.minLength) ) {
          a.push({
            args: [this.name],
            predicateFactory: function(e) {
              return e.GTE(foam.mlang.StringLength.create({ arg1: self }), 1);
            },
            errorString: `Please enter ${this.label.toLowerCase()}`
          });
        }
        return a;
      }
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'FObjectPropertyValidationRefinement',
  refines: 'foam.core.FObjectProperty',
  properties: [
    {
      class: 'Boolean',
      name: 'autoValidate'
    },
    {
      name: 'validateObj',
      expression: function(name, label, required, validationPredicates, autoValidate) {
        if ( autoValidate ) {
          return [
            [`${name}$errors_`],
            function(errs) {
              return errs ? `Please enter valid ${label.toLowerCase()}` : null;
            }
          ];
        }
        return foam.core.Property.VALIDATE_OBJ.expression.apply(this, arguments);
      },
    },
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'IntPropertyValidationRefinement',
  refines: 'foam.core.Int',
  properties: [
    {
      class: 'Boolean',
      name: 'autoValidate'
    },
    {
      name: 'validationPredicates',
      factory: function() {
        if ( ! this.autoValidate ) return [];
        var self = this;
        var a = [];
        if ( foam.Number.isInstance(self.min) ) {
          a.push({
            args: [self.name],
            predicateFactory: function(e) {
              return e.GTE(self, self.min);
            },
            errorString: `Please enter ${self.label.toLowerCase()} greater than or equal to ${self.min}.`
          });
        }
        if ( foam.Number.isInstance(self.max) ) {
          a.push({
            args: [self.name],
            predicateFactory: function(e) {
              return e.LTE(self, self.max);
            },
            errorString: `Please enter ${self.label.toLowerCase()} less than or equal to ${self.max}`
          });
        }
        return a;
      }
    }
  ]
});

foam.CLASS({
  package: 'foam.core.internal',
  name: 'Errors',
//  extends: 'foam.core.Property',

  documentation: `
    A psedo-Property Axiom added to FObject which contains an object\'s validation errors.
    Adds the following attributes to an Object:

errors_

list of current errors

errors_$

Slot representation of errors_

validateObject()

calls the validateObj() method of all property Axioms, allowing them to populate errors_

  `,

  properties: [
    [ 'name', 'errors_' ]
  ],

  methods: [
    function installInProto(proto) {
      var self = this;
      Object.defineProperty(proto, 'errors_', {
        get: function() {
          return self.toSlot(this).get();
        },
        configurable: true,
        enumerable: false
      });

      Object.defineProperty(proto, 'errors_$', {
        get: function() {
          return self.toSlot(this);
        },
        configurable: true,
        enumerable: false
      });
    },

    function toSlot(obj) {
      var slotName = this.slotName_ || ( this.slotName_ = this.name + '$' );
      var slot     = obj.getPrivate_(slotName);

      if ( ! slot ) {
        slot = this.createErrorSlot_(obj)
        obj.setPrivate_(slotName, slot);
      }

      return slot;
    },

    function createErrorSlot_(obj) {
      var args = [];
      var ps   = obj.cls_.getAxiomsByClass(foam.core.Property).
        filter(function(a) { return a.validateObj; });

      for ( var i = 0 ; i < ps.length ; i++ ) {
        var p = ps[i];
        args.push(obj.slot(p.validateObj));
      }

      function validateObject() {
        var ret;

        for ( var i = 0 ; i < ps.length ; i++ ) {
          var p = ps[i];
          var err = args[i].get();
          if ( err ) (ret || (ret = [])).push([p, err]);
        }

        return ret;
      }

      return foam.core.ExpressionSlot.create({
        obj: obj,
        code: validateObject,
        args: args
      });
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'ValidationFObjectRefinement',
  refines: 'foam.core.FObject',

  axioms: [
    foam.core.internal.Errors.create()
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'EmailPropertyValidationRefinement',
  refines: 'foam.core.EMail',
  properties: [
    {
      class: 'FObjectArray',
      of: 'foam.core.ValidationPredicate',
      name: 'validationPredicates',
      factory: function() {
        var self = this;
        var ret = [
          {
            args: [this.name],
            predicateFactory: function(e) {
              return e.REG_EXP(self, /\S+@\S+\.\S+/);
            },
            errorString: 'Please enter valid email address'
          }
        ];
        if ( this.required ) {
          ret.push(
            {
              args: [this.name],
              predicateFactory: function(e) {
                return e.NEQ(self, '');
              },
              errorString: 'Please enter email address'
            }
          );
        }
        return ret;
      }
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'PhoneNumberPropertyValidationRefinement',
  refines: 'foam.core.PhoneNumber',

  properties: [
    {
      class: 'FObjectArray',
      of: 'foam.core.ValidationPredicate',
      name: 'validationPredicates',
      factory: function() {
        var self = this;
        const PHONE_NUMBER_REGEX = /^(?:\+?1[-.●]?)?\(?([0-9]{3})\)?[-.●]?([0-9]{3})[-.●]?([0-9]{4})$/;
        return this.required
          ? [
              {
                args: [this.name],
                predicateFactory: function(e) {
                  return e.HAS(self);
                },
                errorString: 'Phone number required.'
              },
              {
                args: [this.name],
                predicateFactory: function(e) {
                  return e.REG_EXP(self, PHONE_NUMBER_REGEX);
                },
                errorString: 'Invalid phone number.'
              }
            ]
          : [
              {
                args: [this.name],
                predicateFactory: function(e) {
                    return e.OR(
                      e.EQ(foam.mlang.StringLength.create({ arg1: self }), 0),
                      e.REG_EXP(self, PHONE_NUMBER_REGEX)
                    );
                },
                errorString: 'Invalid phone number.'
              }
            ];
      }
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'DatePropertyValidationRefinement',
  refines: 'foam.core.Date',
  properties: [
    {
      class: 'FObjectArray',
      of: 'foam.core.ValidationPredicate',
      name: 'validationPredicates',
      factory: function() {
        var self = this;
        return [
          {
            args: [self.name],
            predicateFactory: function(e) {
              return e.OR(
                e.NOT(e.HAS(self)), // Allow null dates.
                e.AND(
                  e.LTE(
                    self,
                    // Maximum date supported by FOAM
                    // (bounded by JavaScript's limit)
                    new Date(8640000000000000)
                  ),
                  e.GTE(
                    self,
                    // Minimum date supported by FOAM
                    // (bounded by JavaScript's limit)
                    new Date(-8640000000000000)
                  )
                )
              );
            },
            errorString: 'Invalid date value'
          }
        ];
      }
    }
  ]
});
<commit_msg>Conflict fix<commit_after>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'foam.core',
  name: 'ValidationPredicate',
  properties: [
    {
      name: 'predicateFactory'
    },
    {
      class: 'FObjectProperty',
      of: 'foam.mlang.predicate.Predicate',
      name: 'predicate',
      expression: function(predicateFactory) {
        return predicateFactory ?
          predicateFactory(foam.mlang.ExpressionsSingleton.create()) :
          null;
      }
    },
    {
      class: 'StringArray',
      name: 'args'
    },
    {
      class: 'Function',
      name: 'jsFunc',
      expression: function(predicate, jsErr) {
        return function() {
          if ( ! predicate.f(this) ) return jsErr(this);
        };
      }
    },
    {
      class: 'String',
      name: 'errorString'
    },
    {
      class: 'Function',
      name: 'jsErr',
      expression: function(errorString) {
        return function() { return errorString; };
      }
    }
  ],
  methods: [
    function createErrorSlotFor(data) {
      return this.ExpressionSlot.create({
        args: this.args.map(a => data[a+'$']),
        code: this.jsFunc.bind(data)
      });
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'PropertyValidationRefinement',
  refines: 'foam.core.Property',

  properties: [
    {
      class: 'FObjectArray',
      of: 'foam.core.ValidationPredicate',
      name: 'validationPredicates',
      // We override 'compare' here because we need to avoid infinite recursion
      // that occurs when a validation predicate for a given property contains a
      // reference to the property itself.
      // This is an incorrect implementation of compare since it will always
      // return a match, even if the validation predicates are different. It
      // would be preferable to find a way to deal with circular references.
      compare: function() { return 0; }
    },
    {
      name: 'validateObj',
      expression: function(name, label, required, validationPredicates) {
        if ( validationPredicates.length ) {
          var args = foam.Array.unique(validationPredicates
            .map(vp => vp.args)
            .flat());
          return [args, function() {
            for ( var i = 0 ; i < validationPredicates.length ; i++ ) {
              var vp = validationPredicates[i];
              if ( vp.jsFunc.bind(this)() ) return vp.jsErr.bind(this)();
            }
            return null;
          }];
        }
        return !required ? null : [[name],
          function() {
            const axiom = this.cls_.getAxiomByName(name);
            return axiom.isDefaultValue(this[name]) && (`Please enter ${label.toLowerCase()}`);
          }]
      },
    },
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'StringPropertyValidationRefinement',
  refines: 'foam.core.String',
  properties: [
    'minLength',
    'maxLength',
    {
      class: 'FObjectArray',
      of: 'foam.core.ValidationPredicate',
      name: 'validationPredicates',
      factory: function() {
        var self = this;
        var a = [];
        if ( foam.Number.isInstance(this.minLength) ) {
          a.push({
            args: [this.name],
            predicateFactory: function(e) {
              return e.GTE(foam.mlang.StringLength.create({ arg1: self }), self.minLength);
            },
            errorString: `Please enter ${this.label.toLowerCase()} with at least ${this.minLength} character${this.minLength>1?'s':''}`
          });
        }
        if ( foam.Number.isInstance(this.maxLength) ) {
          a.push({
            args: [this.name],
            predicateFactory: function(e) {
              return e.LTE(foam.mlang.StringLength.create({ arg1: self }), self.maxLength);
            },
            errorString: `Please enter ${this.label.toLowerCase()} with at most ${this.maxLength} character${this.maxLength>1?'s':''}`
          });
        }
        if ( this.required && ! foam.Number.isInstance(this.minLength) ) {
          a.push({
            args: [this.name],
            predicateFactory: function(e) {
              return e.GTE(foam.mlang.StringLength.create({ arg1: self }), 1);
            },
            errorString: `Please enter ${this.label.toLowerCase()}`
          });
        }
        return a;
      }
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'FObjectPropertyValidationRefinement',
  refines: 'foam.core.FObjectProperty',
  properties: [
    {
      class: 'Boolean',
      name: 'autoValidate'
    },
    {
      name: 'validateObj',
      expression: function(name, label, required, validationPredicates, autoValidate) {
        if ( autoValidate ) {
          return [
            [`${name}$errors_`],
            function(errs) {
              return errs ? `Please enter valid ${label.toLowerCase()}` : null;
            }
          ];
        }
        return foam.core.Property.VALIDATE_OBJ.expression.apply(this, arguments);
      },
    },
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'IntPropertyValidationRefinement',
  refines: 'foam.core.Int',
  properties: [
    {
      class: 'Boolean',
      name: 'autoValidate'
    },
    {
      name: 'validationPredicates',
      factory: function() {
        if ( ! this.autoValidate ) return [];
        var self = this;
        var a = [];
        if ( foam.Number.isInstance(self.min) ) {
          a.push({
            args: [self.name],
            predicateFactory: function(e) {
              return e.GTE(self, self.min);
            },
            errorString: `Please enter ${self.label.toLowerCase()} greater than or equal to ${self.min}.`
          });
        }
        if ( foam.Number.isInstance(self.max) ) {
          a.push({
            args: [self.name],
            predicateFactory: function(e) {
              return e.LTE(self, self.max);
            },
            errorString: `Please enter ${self.label.toLowerCase()} less than or equal to ${self.max}`
          });
        }
        return a;
      }
    }
  ]
});

foam.CLASS({
  package: 'foam.core.internal',
  name: 'Errors',
//  extends: 'foam.core.Property',

  documentation: `
    A psedo-Property Axiom added to FObject which contains an object\'s validation errors.
    Adds the following attributes to an Object:

errors_

list of current errors

errors_$

Slot representation of errors_

validateObject()

calls the validateObj() method of all property Axioms, allowing them to populate errors_

  `,

  properties: [
    [ 'name', 'errors_' ]
  ],

  methods: [
    function installInProto(proto) {
      var self = this;
      Object.defineProperty(proto, 'errors_', {
        get: function() {
          return self.toSlot(this).get();
        },
        configurable: true,
        enumerable: false
      });

      Object.defineProperty(proto, 'errors_$', {
        get: function() {
          return self.toSlot(this);
        },
        configurable: true,
        enumerable: false
      });
    },

    function toSlot(obj) {
      var slotName = this.slotName_ || ( this.slotName_ = this.name + '$' );
      var slot     = obj.getPrivate_(slotName);

      if ( ! slot ) {
        slot = this.createErrorSlot_(obj)
        obj.setPrivate_(slotName, slot);
      }

      return slot;
    },

    function createErrorSlot_(obj) {
      var args = [];
      var ps   = obj.cls_.getAxiomsByClass(foam.core.Property).
        filter(function(a) { return a.validateObj; });

      for ( var i = 0 ; i < ps.length ; i++ ) {
        var p = ps[i];
        args.push(obj.slot(p.validateObj));
      }

      function validateObject() {
        var ret;

        for ( var i = 0 ; i < ps.length ; i++ ) {
          var p = ps[i];
          var err = args[i].get();
          if ( err ) (ret || (ret = [])).push([p, err]);
        }

        return ret;
      }

      return foam.core.ExpressionSlot.create({
        obj: obj,
        code: validateObject,
        args: args
      });
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'ValidationFObjectRefinement',
  refines: 'foam.core.FObject',

  axioms: [
    foam.core.internal.Errors.create()
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'EmailPropertyValidationRefinement',
  refines: 'foam.core.EMail',
  properties: [
    {
      class: 'FObjectArray',
      of: 'foam.core.ValidationPredicate',
      name: 'validationPredicates',
      factory: function() {
        var self = this;
        var ret = [
          {
            args: [this.name],
            predicateFactory: function(e) {
              return e.OR(
                e.EQ(self, ''),
                e.REG_EXP(self, /\S+@\S+\.\S+/)
              );
            },
            errorString: 'Please enter valid email address'
          }
        ];
        if ( this.required ) {
          ret.push(
            {
              args: [this.name],
              predicateFactory: function(e) {
                return e.NEQ(self, '');
              },
              errorString: 'Please enter email address'
            }
          );
        }
        return ret;
      }
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'PhoneNumberPropertyValidationRefinement',
  refines: 'foam.core.PhoneNumber',

  properties: [
    {
      class: 'FObjectArray',
      of: 'foam.core.ValidationPredicate',
      name: 'validationPredicates',
      factory: function() {
        var self = this;
        const PHONE_NUMBER_REGEX = /^(?:\+?1[-.●]?)?\(?([0-9]{3})\)?[-.●]?([0-9]{3})[-.●]?([0-9]{4})$/;
        return this.required
          ? [
              {
                args: [this.name],
                predicateFactory: function(e) {
                  return e.HAS(self);
                },
                errorString: 'Phone number required.'
              },
              {
                args: [this.name],
                predicateFactory: function(e) {
                  return e.REG_EXP(self, PHONE_NUMBER_REGEX);
                },
                errorString: 'Invalid phone number.'
              }
            ]
          : [
              {
                args: [this.name],
                predicateFactory: function(e) {
                    return e.OR(
                      e.EQ(foam.mlang.StringLength.create({ arg1: self }), 0),
                      e.REG_EXP(self, PHONE_NUMBER_REGEX)
                    );
                },
                errorString: 'Invalid phone number.'
              }
            ];
      }
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'DatePropertyValidationRefinement',
  refines: 'foam.core.Date',
  properties: [
    {
      class: 'FObjectArray',
      of: 'foam.core.ValidationPredicate',
      name: 'validationPredicates',
      factory: function() {
        var self = this;
        return [
          {
            args: [self.name],
            predicateFactory: function(e) {
              return e.OR(
                e.NOT(e.HAS(self)), // Allow null dates.
                e.AND(
                  e.LTE(
                    self,
                    // Maximum date supported by FOAM
                    // (bounded by JavaScript's limit)
                    new Date(8640000000000000)
                  ),
                  e.GTE(
                    self,
                    // Minimum date supported by FOAM
                    // (bounded by JavaScript's limit)
                    new Date(-8640000000000000)
                  )
                )
              );
            },
            errorString: 'Invalid date value'
          }
        ];
      }
    }
  ]
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.INTERFACE({
  refines: 'foam.mlang.Expr',

  methods: [
    {
      name: 'f',
      args: [
        {
          name: 'obj',
          javaType: 'foam.core.FObject'
        }
      ],
      javaReturns: 'Object'
    },
    {
      name: 'partialEval',
      javaReturns: 'foam.mlang.Expr'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.ExprProperty',

  properties: [
    ['javaType', 'foam.mlang.Expr'],
    ['javaJSONParser', 'foam.lib.json.ExprParser']
  ]
});

debugger;
foam.INTERFACE({
  refines: 'foam.mlang.predicate.Predicate',

  methods: [
    {
      name: 'f',
      args: [
        {
          name: 'obj',
          javaType: 'foam.core.FObject'
        }
      ],
      javaReturns: 'boolean'
    },
    {
      name: 'partialEval',
      javaReturns: 'foam.mlang.predicate.Predicate'
    },
    {
      name: 'toIndex',
      javaSupport: false,
      args: [
        {
          name: 'tail',
          javaType: 'foam.dao.index.Index'
        }
      ],
      javaReturns: 'foam.dao.index.Index'
    },
    {
      name: 'toDisjunctiveNormalForm',
      javaSupport: false,
      javaReturns: 'foam.mlang.predicate.Predicate'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.PredicateProperty',

  properties: [
    ['javaType', 'foam.mlang.predicate.Predicate']
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.PredicateArray',

  properties: [
    ['javaType', 'foam.mlang.predicate.Predicate[]']
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.AbstractPredicate',

  methods: [
    {
      // TODO: This is a duplicate of the method in Predicate,
      // but it's necessary because when we refine Predicate, it doesn't
      // update classes that copied their axioms in from Predicate as a trait.
      // If we were more careful about the ordering of classes this wouldn't be
      // necessary.
      name: 'f',
      args: [
        {
          name: 'obj',
          javaType: 'foam.core.FObject'
        }
      ],
      javaCode: 'return false;',
      javaReturns: 'boolean'
    },
    {
      // TODO: Same TODO as .f method above
      name: 'toIndex',
      javaSupport: false
    },
    {
      // TODO: Same TODO as .f
      name: 'toDisjunctiveNormalForm',
      javaSupport: false,
      javaReturns: 'foam.mlang.predicate.Predicate'
    },
    {
      name: 'partialEval',
      javaCode: 'return this;',
      javaReturns: 'foam.mlang.predicate.Predicate'
    },
    {
      name: 'toString',
      javaCode: 'return classInfo_.getId();'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.AbstractExpr',

  methods: [
    {
      name: 'partialEval',
      javaCode: 'return this;',
      // Remove this javaReturns when it is inherited properly. (Traits are fixed).
      javaReturns: 'foam.mlang.Expr'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.True',

  methods: [
    {
      name: 'f',
      javaCode: 'return true;'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.False',

  methods: [
    {
      name: 'f',
      javaCode: 'return false;'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.Or',

  methods: [
    {
      name: 'f',
      javaCode: 'for ( int i = 0 ; i < getArgs().length ; i++ ) {\n'
                + '  if ( getArgs()[i].f(obj) ) return true;\n'
                + '}\n'
                + 'return false;\n'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.And',

  methods: [
    {
      name: 'f',
      javaCode: 'for ( int i = 0 ; i < getArgs().length ; i++ ) {\n'
                + '  if ( ! getArgs()[i].f(obj) ) return false;\n'
                + '}\n'
                + 'return true;'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.Contains',

  methods: [
    {
      name: 'f',
      javaCode: 'String s1 = (String)getArg1().f(obj);\n'
                + 'String s2 = (String)getArg2().f(obj);\n'
                + 'return s1 != null ? s1.indexOf(s2) != -1 : false;\n'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.StartsWithIC',

  methods: [
    {
      name: 'f',
      javaCode: 'String arg2 = ((String)getArg2().f(obj)).toUpperCase();\n'
                + 'Object arg1 = getArg1().f(obj);\n'
                + 'if ( arg1 instanceof Object[] ) {\n'
                + '  Object[] values = (Object[])arg1;\n'
                + '  for ( int i = 0 ; i < values.length ; i++ ) {\n'
                + '    if ( ((String)values[i]).toUpperCase().startsWith(arg2) ) {\n'
                + '      return true;\n'
                + '    }\n'
                + '  }\n'
                + '  return false;'
                + '}'
                + 'String value = (String)arg1;\n'
                + 'return value.toUpperCase().startsWith(arg2);\n'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.Constant',

  methods: [
    {
      name: 'f',
      javaCode: 'return getValue();'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.Eq',

  methods: [
    {
      name: 'f',
      // TODO(adamvy): Is there a better option than all the Comparable casts?
      javaCode: 'return ((Comparable)getArg1().f(obj)).compareTo((Comparable)getArg2().f(obj)) == 0;'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.Neq',

  methods: [
    {
      name: 'f',
      javaCode: 'return ((Comparable)getArg1().f(obj)).compareTo((Comparable)getArg2().f(obj)) != 0;'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.Lt',

  methods: [
    {
      name: 'f',
      javaCode: 'return ((Comparable)getArg1().f(obj)).compareTo((Comparable)getArg2().f(obj)) < 0;'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.Lte',

  methods: [
    {
      name: 'f',
      javaCode: 'return ((Comparable)getArg1().f(obj)).compareTo((Comparable)getArg2().f(obj)) <= 0;'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.Gt',

  methods: [
    {
      name: 'f',
      javaCode: 'return ((Comparable)getArg1().f(obj)).compareTo((Comparable)getArg2().f(obj)) > 0;'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.Gte',

  methods: [
    {
      name: 'f',
      javaCode: 'return ((Comparable)getArg1().f(obj)).compareTo((Comparable)getArg2().f(obj)) >= 0;'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.Not',

  methods: [
    {
      name: 'f',
      javaCode: 'return ! getArg1().f(obj);'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.Has',

  methods: [
    {
      name: 'f',
      javaCode: 'Object value = getArg1().f(obj); '+
        'return ! (value == null || '+
          '(value instanceof String && ((String)value).length() == 0) || '+
          '(value.getClass().isArray() && java.lang.reflect.Array.getLength(value) == 0));'
    }
  ]
});

foam.INTERFACE({
  refines: 'foam.mlang.order.Comparator',

  methods: [
    {
      name: 'compare',
      args: [
        {
          name: 'o1',
          javaType: 'Object'
        },
        {
          name: 'o2',
          javaType: 'Object'
        }
      ]
    },
    {
      name: 'toIndex',
      javaSupport: false,
      args: [
        {
          name: 'tail'
        }
      ]
    }
  ]
});

foam.CLASS({
  refines: 'foam.core.Property',

  methods: [
    {
      name: 'orderTail',
      javaCode: 'return null;'
    },
    {
      name: 'orderPrimaryProperty',
      javaCode: 'return this;'
    },
    {
      name: 'orderDirection',
      javaCode: 'return 1;'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.order.Desc',

  methods: [
    {
      name: 'compare',
      javaCode: 'return -1 * getArg1().compare(o1, o2);'
    },
    {
      name: 'toString',
      javaCode: 'return "DESC(" + getArg1().toString() + ")";'
    },
    {
      name: 'orderTail',
      javaCode: 'return null;'
    },
    {
      name: 'orderPrimaryProperty',
      javaCode: 'return getArg1();'
    },
    {
      name: 'orderDirection',
      javaCode: 'Object ret = getArg1().orderDirection().reverse();' +
        'ret.setSrcOrder(this); return ret;'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.order.CustomComparator',

  methods: [
    {
      name: 'orderTail',
      javaCode: 'return null;'
    },
    {
      name: 'orderPrimaryProperty',
      javaCode: 'return null;'
    },
    {
      name: 'orderDirection',
      javaCode: 'return 1;'
    }
  ]
});
<commit_msg>Correct/Change spacing<commit_after>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.INTERFACE({
  refines: 'foam.mlang.Expr',

  methods: [
    {
      name: 'f',
      args: [
        {
          name: 'obj',
          javaType: 'foam.core.FObject'
        }
      ],
      javaReturns: 'Object'
    },
    {
      name: 'partialEval',
      javaReturns: 'foam.mlang.Expr'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.ExprProperty',

  properties: [
    ['javaType', 'foam.mlang.Expr'],
    ['javaJSONParser', 'foam.lib.json.ExprParser']
  ]
});

debugger;
foam.INTERFACE({
  refines: 'foam.mlang.predicate.Predicate',

  methods: [
    {
      name: 'f',
      args: [
        {
          name: 'obj',
          javaType: 'foam.core.FObject'
        }
      ],
      javaReturns: 'boolean'
    },
    {
      name: 'partialEval',
      javaReturns: 'foam.mlang.predicate.Predicate'
    },
    {
      name: 'toIndex',
      javaSupport: false,
      args: [
        {
          name: 'tail',
          javaType: 'foam.dao.index.Index'
        }
      ],
      javaReturns: 'foam.dao.index.Index'
    },
    {
      name: 'toDisjunctiveNormalForm',
      javaSupport: false,
      javaReturns: 'foam.mlang.predicate.Predicate'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.PredicateProperty',

  properties: [
    ['javaType', 'foam.mlang.predicate.Predicate']
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.PredicateArray',

  properties: [
    ['javaType', 'foam.mlang.predicate.Predicate[]']
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.AbstractPredicate',

  methods: [
    {
      // TODO: This is a duplicate of the method in Predicate,
      // but it's necessary because when we refine Predicate, it doesn't
      // update classes that copied their axioms in from Predicate as a trait.
      // If we were more careful about the ordering of classes this wouldn't be
      // necessary.
      name: 'f',
      args: [
        {
          name: 'obj',
          javaType: 'foam.core.FObject'
        }
      ],
      javaCode: 'return false;',
      javaReturns: 'boolean'
    },
    {
      // TODO: Same TODO as .f method above
      name: 'toIndex',
      javaSupport: false
    },
    {
      // TODO: Same TODO as .f
      name: 'toDisjunctiveNormalForm',
      javaSupport: false,
      javaReturns: 'foam.mlang.predicate.Predicate'
    },
    {
      name: 'partialEval',
      javaCode: 'return this;',
      javaReturns: 'foam.mlang.predicate.Predicate'
    },
    {
      name: 'toString',
      javaCode: 'return classInfo_.getId();'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.AbstractExpr',

  methods: [
    {
      name: 'partialEval',
      javaCode: 'return this;',
      // Remove this javaReturns when it is inherited properly. (Traits are fixed).
      javaReturns: 'foam.mlang.Expr'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.True',

  methods: [
    {
      name: 'f',
      javaCode: 'return true;'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.False',

  methods: [
    {
      name: 'f',
      javaCode: 'return false;'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.Or',

  methods: [
    {
      name: 'f',
      javaCode: 'for ( int i = 0 ; i < getArgs().length ; i++ ) {\n'
                + '  if ( getArgs()[i].f(obj) ) return true;\n'
                + '}\n'
                + 'return false;\n'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.And',

  methods: [
    {
      name: 'f',
      javaCode: 'for ( int i = 0 ; i < getArgs().length ; i++ ) {\n'
                + '  if ( ! getArgs()[i].f(obj) ) return false;\n'
                + '}\n'
                + 'return true;'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.Contains',

  methods: [
    {
      name: 'f',
      javaCode: 'String s1 = (String)getArg1().f(obj);\n'
                + 'String s2 = (String)getArg2().f(obj);\n'
                + 'return s1 != null ? s1.indexOf(s2) != -1 : false;\n'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.StartsWithIC',

  methods: [
    {
      name: 'f',
      javaCode: 'String arg2 = ((String)getArg2().f(obj)).toUpperCase();\n'
                + 'Object arg1 = getArg1().f(obj);\n'
                + 'if ( arg1 instanceof Object[] ) {\n'
                + '  Object[] values = (Object[])arg1;\n'
                + '  for ( int i = 0 ; i < values.length ; i++ ) {\n'
                + '    if ( ((String)values[i]).toUpperCase().startsWith(arg2) ) {\n'
                + '      return true;\n'
                + '    }\n'
                + '  }\n'
                + '  return false;'
                + '}'
                + 'String value = (String)arg1;\n'
                + 'return value.toUpperCase().startsWith(arg2);\n'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.Constant',

  methods: [
    {
      name: 'f',
      javaCode: 'return getValue();'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.Eq',

  methods: [
    {
      name: 'f',
      // TODO(adamvy): Is there a better option than all the Comparable casts?
      javaCode: 'return ((Comparable)getArg1().f(obj)).compareTo((Comparable)getArg2().f(obj)) == 0;'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.Neq',

  methods: [
    {
      name: 'f',
      javaCode: 'return ((Comparable)getArg1().f(obj)).compareTo((Comparable)getArg2().f(obj)) != 0;'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.Lt',

  methods: [
    {
      name: 'f',
      javaCode: 'return ((Comparable)getArg1().f(obj)).compareTo((Comparable)getArg2().f(obj)) < 0;'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.Lte',

  methods: [
    {
      name: 'f',
      javaCode: 'return ((Comparable)getArg1().f(obj)).compareTo((Comparable)getArg2().f(obj)) <= 0;'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.Gt',

  methods: [
    {
      name: 'f',
      javaCode: 'return ((Comparable)getArg1().f(obj)).compareTo((Comparable)getArg2().f(obj)) > 0;'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.Gte',

  methods: [
    {
      name: 'f',
      javaCode: 'return ((Comparable)getArg1().f(obj)).compareTo((Comparable)getArg2().f(obj)) >= 0;'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.Not',

  methods: [
    {
      name: 'f',
      javaCode: 'return ! getArg1().f(obj);'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.predicate.Has',

  methods: [
    {
      name: 'f',
      javaCode: 'Object value = getArg1().f(obj);'+
        'return ! (value == null || '+
          '(value instanceof String && ((String)value).length() == 0) || '+
          '(value.getClass().isArray() && java.lang.reflect.Array.getLength(value) == 0));'
    }
  ]
});

foam.INTERFACE({
  refines: 'foam.mlang.order.Comparator',

  methods: [
    {
      name: 'compare',
      args: [
        {
          name: 'o1',
          javaType: 'Object'
        },
        {
          name: 'o2',
          javaType: 'Object'
        }
      ]
    },
    {
      name: 'toIndex',
      javaSupport: false,
      args: [
        {
          name: 'tail'
        }
      ]
    }
  ]
});

foam.CLASS({
  refines: 'foam.core.Property',

  methods: [
    {
      name: 'orderTail',
      javaCode: 'return null;'
    },
    {
      name: 'orderPrimaryProperty',
      javaCode: 'return this;'
    },
    {
      name: 'orderDirection',
      javaCode: 'return 1;'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.order.Desc',

  methods: [
    {
      name: 'compare',
      javaCode: 'return -1 * getArg1().compare(o1, o2);'
    },
    {
      name: 'toString',
      javaCode: 'return "DESC(" + getArg1().toString() + ")";'
    },
    {
      name: 'orderTail',
      javaCode: 'return null;'
    },
    {
      name: 'orderPrimaryProperty',
      javaCode: 'return getArg1();'
    },
    {
      name: 'orderDirection',
      javaCode: 'Object ret = getArg1().orderDirection().reverse();' +
        'ret.setSrcOrder(this); return ret;'
    }
  ]
});

foam.CLASS({
  refines: 'foam.mlang.order.CustomComparator',

  methods: [
    {
      name: 'orderTail',
      javaCode: 'return null;'
    },
    {
      name: 'orderPrimaryProperty',
      javaCode: 'return null;'
    },
    {
      name: 'orderDirection',
      javaCode: 'return 1;'
    }
  ]
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2017 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
  A Faceted Axiom, when added to a Class, makes it implement
  the Facet-Manager Pattern, meaning that calls to create() can
  be intercepted and return a special Facet class depending on the
  value of the 'of' create argument.

  Ex.:
  foam.CLASS({
    name: 'View',
    axioms: [ foam.pattern.Faceted.create() ],
    properties: [ 'of' ],
    methods: [ function view() { return 'default' } ]
  });

  foam.CLASS({name: 'A'});
  foam.CLASS({name: 'B'});
  foam.CLASS({name: 'C'});
  foam.CLASS({name: 'BView', extends: 'View', methods: [function view() { return 'BView'; }]});
  foam.CLASS({name: 'CView', extends: 'View', methods: [function view() { return 'CView'; }]});

  console.log(View.create({of: A}));
  console.log(View.create({of: B}));
  console.log(View.create({of: C}));
*/
foam.CLASS({
  package: 'foam.pattern',
  name: 'Faceted',

  methods: [
    function installInClass(cls) {
      cls.getFacetOf = function(of, X) {
        if ( ! of ) return this;
        X = X || foam.__context__;

        var facets = this.private_.facets || ( this.private_.facets = {} );

        if ( ! facets.hasOwnProperty(of.name) ) {
          var id = this.package ?
            this.package + '.' + of.name + this.name :
            of.name + this.name ;
          console.log('loooking: ', id);
          facets[of.name] = X.lookup(id, true) || this;
        }

        return facets[of.name];
      };

      var oldCreate = cls.create;

      cls.create = function(args, X) {
        var facetCls = this.getFacetOf(args && args.of, X);

        return facetCls === this ?
          oldCreate.apply(this, arguments) :
          facetCls.create(args, X) ;
      }
    }
  ]
});
<commit_msg>Avoid nested calls in Faceted.<commit_after>/**
 * @license
 * Copyright 2017 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
  A Faceted Axiom, when added to a Class, makes it implement
  the Facet-Manager Pattern, meaning that calls to create() can
  be intercepted and return a special Facet class depending on the
  value of the 'of' create argument.

  Ex.:
  foam.CLASS({
    name: 'View',
    axioms: [ foam.pattern.Faceted.create() ],
    properties: [ 'of' ],
    methods: [ function view() { return 'default' } ]
  });

  foam.CLASS({name: 'A'});
  foam.CLASS({name: 'B'});
  foam.CLASS({name: 'C'});
  foam.CLASS({name: 'BView', extends: 'View', methods: [function view() { return 'BView'; }]});
  foam.CLASS({name: 'CView', extends: 'View', methods: [function view() { return 'CView'; }]});

  console.log(View.create({of: A}));
  console.log(View.create({of: B}));
  console.log(View.create({of: C}));
*/
foam.CLASS({
  package: 'foam.pattern',
  name: 'Faceted',

  methods: [
    function installInClass(cls) {
      cls.getFacetOf = function(of, X) {
        if ( ! of ) return this;
        X = X || foam.__context__;

        var facets = this.private_.facets || ( this.private_.facets = {} );

        if ( ! facets.hasOwnProperty(of.name) ) {
          var id = this.package ?
            this.package + '.' + of.name + this.name :
            of.name + this.name ;
          facets[of.name] = X.lookup(id, true) || this;
        }

        return facets[of.name];
      };

      var oldCreate = cls.create;

      // ignoreFacets is set to true when called to prevent a second-level
      // of facet checking
      cls.create = function(args, X, ignoreFacets) {
        if ( ignoreFacets) return oldCreate.apply(this, arguments);
        var facetCls = this.getFacetOf(args && args.of, X);

        return facetCls === this ?
          oldCreate.apply(this, arguments) :
          facetCls.create(args, X, true)   ;
      }
    }
  ]
});
<|endoftext|>


<commit_before>describe("About Functions", function() {

  it("should declare functions", function() {

    function add(a, b) {
      return a + b;
    }

    expect(add(1, 2)).toBe(3);
  });

  it("should know internal variables override outer variables", function () {
    var message = "Outer";

    function getMessage() {
      return message;
    }

    function overrideMessage() {
      var message = "Inner";
      return message;
    }

    expect(getMessage()).toBe('Outer');
    expect(overrideMessage()).toBe('Inner');
    expect(message).toBe('Outer');
  });

  it("should have lexical scoping", function () {
    var variable = "top-level";
    function parentfunction() {
        var variable = "local";
      function childfunction() {
          return variable;
      }
      return childfunction();
    }
    expect(parentfunction()).toBe('local');
  });

  it("should use lexical scoping to synthesise functions", function () {

    function makeIncreaseByFunction(increaseByAmount) {
      return function (numberToIncrease) {
        return numberToIncrease + increaseByAmount;
      };
    }

    var increaseBy3 = makeIncreaseByFunction(3);
    var increaseBy5 = makeIncreaseByFunction(5);

    expect(increaseBy3(10) + increaseBy5(10)).toBe(FILL_ME_IN);
  });

  it("should allow extra function arguments", function () {

    function returnFirstArg(firstArg) {
      return firstArg;
    }

    expect(returnFirstArg("first", "second", "third")).toBe(FILL_ME_IN);

    function returnSecondArg(firstArg, secondArg) {
      return secondArg;
    }

    expect(returnSecondArg("only give first arg")).toBe(FILL_ME_IN);

    function returnAllArgs() {
      var argsArray = [];
      for (var i = 0; i < arguments.length; i += 1) {
        argsArray.push(arguments[i]);
      }
      return argsArray.join(",");
    }

    expect(returnAllArgs("first", "second", "third")).toBe(FILL_ME_IN);
  });

  it("should pass functions as values", function () {

    var appendRules = function (name) {
      return name + " rules!";
    };

    var appendDoubleRules = function (name) {
      return name + " totally rules!";
    };

    var praiseSinger = { givePraise: appendRules };
    expect(praiseSinger.givePraise("John")).toBe(FILL_ME_IN);

    praiseSinger.givePraise = appendDoubleRules;
    expect(praiseSinger.givePraise("Mary")).toBe(FILL_ME_IN);

  });

  it("should use function body as a string", function () {
    var add = new Function("a", "b", "return a + b;");
    expect(add(1, 2)).toBe(FILL_ME_IN);

    var multiply = function (a, b) {
      //An internal comment
      return a * b;
    };
    expect(multiply.toString()).toBe(FILL_ME_IN);
  });
});
<commit_msg>use correct actual value to match expectation<commit_after>describe("About Functions", function() {

  it("should declare functions", function() {

    function add(a, b) {
      return a + b;
    }

    expect(add(1, 2)).toBe(3);
  });

  it("should know internal variables override outer variables", function () {
    var message = "Outer";

    function getMessage() {
      return message;
    }

    function overrideMessage() {
      var message = "Inner";
      return message;
    }

    expect(getMessage()).toBe('Outer');
    expect(overrideMessage()).toBe('Inner');
    expect(message).toBe('Outer');
  });

  it("should have lexical scoping", function () {
    var variable = "top-level";
    function parentfunction() {
        var variable = "local";
      function childfunction() {
          return variable;
      }
      return childfunction();
    }
    expect(parentfunction()).toBe('local');
  });

  it("should use lexical scoping to synthesise functions", function () {

    function makeIncreaseByFunction(increaseByAmount) {
      return function (numberToIncrease) {
        return numberToIncrease + increaseByAmount;
      };
    }

    var increaseBy3 = makeIncreaseByFunction(3);
    var increaseBy5 = makeIncreaseByFunction(5);

    expect(increaseBy3(10) + increaseBy5(10)).toBe(28);
  });

  it("should allow extra function arguments", function () {

    function returnFirstArg(firstArg) {
      return firstArg;
    }

    expect(returnFirstArg("first", "second", "third")).toBe(FILL_ME_IN);

    function returnSecondArg(firstArg, secondArg) {
      return secondArg;
    }

    expect(returnSecondArg("only give first arg")).toBe(FILL_ME_IN);

    function returnAllArgs() {
      var argsArray = [];
      for (var i = 0; i < arguments.length; i += 1) {
        argsArray.push(arguments[i]);
      }
      return argsArray.join(",");
    }

    expect(returnAllArgs("first", "second", "third")).toBe(FILL_ME_IN);
  });

  it("should pass functions as values", function () {

    var appendRules = function (name) {
      return name + " rules!";
    };

    var appendDoubleRules = function (name) {
      return name + " totally rules!";
    };

    var praiseSinger = { givePraise: appendRules };
    expect(praiseSinger.givePraise("John")).toBe(FILL_ME_IN);

    praiseSinger.givePraise = appendDoubleRules;
    expect(praiseSinger.givePraise("Mary")).toBe(FILL_ME_IN);

  });

  it("should use function body as a string", function () {
    var add = new Function("a", "b", "return a + b;");
    expect(add(1, 2)).toBe(FILL_ME_IN);

    var multiply = function (a, b) {
      //An internal comment
      return a * b;
    };
    expect(multiply.toString()).toBe(FILL_ME_IN);
  });
});
<|endoftext|>


<commit_before>describe("About Functions", function() {

  it("should declare functions", function() {

    function add(a, b) {
      return a + b;
    }

    expect(add(1, 2)).toBe(FILL_ME_IN);
  });

  it("should know internal variables override outer variables", function () {
    var message = "Outer";

    function getMessage() {
      return message;
    }

    function overrideMessage() {
      var message = "Inner";
      return message;
    }

    expect(getMessage()).toBe(FILL_ME_IN);
    expect(overrideMessage()).toBe(FILL_ME_IN);
    expect(message).toBe(FILL_ME_IN);
  });

  it("should have lexical scoping", function () {
    var variable = "top-level";
    function parentfunction() {
        var variable = "local";
      function childfunction() {
          return variable;
      }
      return childfunction();
    }
    expect(parentfunction()).toBe(FILL_ME_IN);
  });

  it("should use lexical scoping to synthesise functions", function () {

    function makeIncreaseByFunction(increaseByAmount) {
      return function (numberToIncrease) {
        return numberToIncrease + increaseByAmount;
      };
    }

    var increaseBy3 = makeIncreaseByFunction(3);
    var increaseBy5 = makeIncreaseByFunction(5);

    expect(increaseBy3(10) + increaseBy5(10)).toBe(FILL_ME_IN);
  });

  it("should allow extra function arguments", function () {

    function returnFirstArg(firstArg) {
      return firstArg;
    }

    expect(returnFirstArg("first", "second", "third")).toBe(FILL_ME_IN);

    function returnSecondArg(firstArg, secondArg) {
      return secondArg;
    }

    expect(returnSecondArg("only give first arg")).toBe(FILL_ME_IN);

    function returnAllArgs() {
      var argsArray = [];
      for (var i = 0; i < arguments.length; i += 1) {
        argsArray.push(arguments[i]);
      }
      return argsArray.join(",");
    }

    expect(returnAllArgs("first", "second", "third")).toBe(FILL_ME_IN);
  });

  it("should pass functions as values", function () {

    var appendRules = function (name) {
      return name + " rules!";
    };

    var appendDoubleRules = function (name) {
      return name + " totally rules!";
    };

    var praiseSinger = { givePraise: appendRules };
    expect(praiseSinger.givePraise("John")).toBe(FILL_ME_IN);

    praiseSinger.givePraise = appendDoubleRules;
    expect(praiseSinger.givePraise("Mary")).toBe(FILL_ME_IN);

  });

  it("should use function body as a string", function () {
    var add = new Function("a", "b", "return a + b;");
    expect(add(1, 2)).toBe(FILL_ME_IN);

    var multiply = function (a, b) {
      //An internal comment
      return a * b;
    };
    expect(multiply.toString()).toBe(FILL_ME_IN);
  });
});
<commit_msg>finished Functions<commit_after>describe("About Functions", function() {

  it("should declare functions", function() {

    function add(a, b) {
      return a + b;
    }

    expect(add(1, 2)).toBe(3);
  });

  it("should know internal variables override outer variables", function () {
    var message = "Outer";

    function getMessage() {
      return message;
    }

    function overrideMessage() {
      var message = "Inner";
      return message;
    }

    expect(getMessage()).toBe("Outer");
    expect(overrideMessage()).toBe("Inner");
    expect(message).toBe("Outer");
  });

  it("should have lexical scoping", function () {
    var variable = "top-level";
    function parentfunction() {
        var variable = "local";
      function childfunction() {
          return variable;
      }
      return childfunction();
    }
    expect(parentfunction()).toBe("local");
  });

  it("should use lexical scoping to synthesise functions", function () {

    function makeIncreaseByFunction(increaseByAmount) {
      return function (numberToIncrease) {
        return numberToIncrease + increaseByAmount;
      };
    }

    var increaseBy3 = makeIncreaseByFunction(3);
    var increaseBy5 = makeIncreaseByFunction(5);

    expect(increaseBy3(10) + increaseBy5(10)).toBe(28);
  });

  it("should allow extra function arguments", function () {

    function returnFirstArg(firstArg) {
      return firstArg;
    }

    expect(returnFirstArg("first", "second", "third")).toBe("first");

    function returnSecondArg(firstArg, secondArg) {
      return secondArg;
    }

    expect(returnSecondArg("only give first arg")).toBe(undefined);

    function returnAllArgs() {
      var argsArray = [];
      for (var i = 0; i < arguments.length; i += 1) {
        argsArray.push(arguments[i]);
      }
      return argsArray.join(",");
    }

    expect(returnAllArgs("first", "second", "third")).toBe("first,second,third");
  });

  it("should pass functions as values", function () {

    var appendRules = function (name) {
      return name + " rules!";
    };

    var appendDoubleRules = function (name) {
      return name + " totally rules!";
    };

    var praiseSinger = { givePraise: appendRules };
    expect(praiseSinger.givePraise("John")).toBe("John rules!");

    praiseSinger.givePraise = appendDoubleRules;
    expect(praiseSinger.givePraise("Mary")).toBe("Mary totally rules!");

  });

  it("should use function body as a string", function () {
    var add = new Function("a", "b", "return a + b;");
    expect(add(1, 2)).toBe(3);

    var multiply = function (a, b) {
      //An internal comment
      return a * b;
    };
    expect(multiply.toString()).toBe("function (a, b) {
      //An internal comment
      return a * b;
    }");
  });
});
<|endoftext|>


<commit_before>describe("About Functions", function () {

  describe("Default Parameters", function () {

    it("should understand default parameters basic usage", function () {

      function greeting(string = 'party') {
        return 'Welcome to the ' + string + ' pal!';
      }

      expect(greeting()).toEqual(FILL_ME_IN);
      expect(greeting('get together')).toEqual(FILL_ME_IN);
      expect(undefined).toEqual(FILL_ME_IN);
      expect(null).toEqual(FILL_ME_IN);

      function getDefaultValue() {
        return 'party';
      }

      function greetingAgain(string = getDefaultValue()) {
        return 'Welcome to the ' + string + ' pal!';
      }

      expect(greetingAgain()).toEqual(FILL_ME_IN);
    });
  });

  // describe('Rest Paramaters', function() {
  //
  // })
  //
  // describe('Spread Parameters', function() {
  //
  // })
  //
  // describe('Arrow Functions', function() {
  //
  // })
});
<commit_msg>feat: Add initial arrow function test<commit_after>describe("About Functions", function () {

  describe("Default Parameters", function () {

    it("should understand default parameters basic usage", function () {

      function greeting(string = 'party') {
        return 'Welcome to the ' + string + ' pal!';
      }

      expect(greeting()).toEqual(FILL_ME_IN);
      expect(greeting('get together')).toEqual(FILL_ME_IN);
      expect(undefined).toEqual(FILL_ME_IN);
      expect(null).toEqual(FILL_ME_IN);

      function getDefaultValue() {
        return 'party';
      }

      function greetingAgain(string = getDefaultValue()) {
        return 'Welcome to the ' + string + ' pal!';
      }

      expect(greetingAgain()).toEqual(FILL_ME_IN);
    });
  });

  // describe('Rest Paramaters', function() {
  //
  // })
  //
  // describe('Spread Parameters', function() {
  //
  // })
  //
  describe('Arrow Functions', function() {

    it('shound understand basic usage', function() {

      var arrowFn = () => { 'hello world!'; };

      expect(arrowFn()).toEqual(FILL_ME_IN);

      var arrowFnTwo = (number) => { number * 2; }

      expect(arrowFnTwo(4)).toEqual(FILL_ME_IN);

    })

  })
});
<|endoftext|>


<commit_before>describe("About Functions", function() {

  it("should declare functions", function() {

    function add(a, b) {
      return a + b;
    }

    expect(add(1, 2)).toBe(FILL_ME_IN);
  });

  it("should know internal variables override outer variables", function () {
    var message = "Outer";

    function getMessage() {
      return message;
    }

    function overrideMessage() {
      var message = "Inner";
      return message;
    }

    expect(getMessage()).toBe(FILL_ME_IN);
    expect(overrideMessage()).toBe(FILL_ME_IN);
    expect(message).toBe(FILL_ME_IN);
  });

  it("should have lexical scoping", function () {
    var variable = "top-level";
    function parentfunction() {
        var variable = "local";
      function childfunction() {
          return variable;
      }
      return childfunction();
    }
    expect(parentfunction()).toBe(FILL_ME_IN);
  });

  it("should use lexical scoping to synthesise functions", function () {

    function makeIncreaseByFunction(increaseByAmount) {
      return function (numberToIncrease) {
        return numberToIncrease + increaseByAmount;
      };
    }

    var increaseBy3 = makeIncreaseByFunction(3);
    var increaseBy5 = makeIncreaseByFunction(5);

    expect(increaseBy3(10) + increaseBy5(10)).toBe(FILL_ME_IN);
  });

  it("should allow extra function arguments", function () {

    function returnFirstArg(firstArg) {
      return firstArg;
    }

    expect(returnFirstArg("first", "second", "third")).toBe(FILL_ME_IN);

    function returnSecondArg(firstArg, secondArg) {
      return secondArg;
    }

    expect(returnSecondArg("only give first arg")).toBe(FILL_ME_IN);

    function returnAllArgs() {
      var argsArray = [];
      for (var i = 0; i < arguments.length; i += 1) {
        argsArray.push(arguments[i]);
      }
      return argsArray.join(",");
    }

    expect(returnAllArgs("first", "second", "third")).toBe(FILL_ME_IN);
  });

  it("should pass functions as values", function () {

    var appendRules = function (name) {
      return name + " rules!";
    };

    var appendDoubleRules = function (name) {
      return name + " totally rules!";
    };

    var praiseSinger = { givePraise: appendRules };
    expect(praiseSinger.givePraise("John")).toBe(FILL_ME_IN);

    praiseSinger.givePraise = appendDoubleRules;
    expect(praiseSinger.givePraise("Mary")).toBe(FILL_ME_IN);

  });

  it("should use function body as a string", function () {
    var add = new Function("a", "b", "return a + b;");
    expect(add(1, 2)).toBe(FILL_ME_IN);

    var multiply = function (a, b) {
      //An internal comment
      return a * b;
    };
    expect(multiply.toString()).toBe(FILL_ME_IN);
  });
});
<commit_msg>Completed AboutFunctions.<commit_after>describe("About Functions", function() {

  it("should declare functions", function() {

    function add(a, b) {
      return a + b;
    }

    expect(add(1, 2)).toBe(3);
  });

  it("should know internal variables override outer variables", function () {
    var message = "Outer";

    function getMessage() {
      return message;
    }

    function overrideMessage() {
      var message = "Inner";
      return message;
    }

    expect(getMessage()).toBe("Outer");
    expect(overrideMessage()).toBe("Inner");
    expect(message).toBe("Outer");
  });

  it("should have lexical scoping", function () {
    var variable = "top-level";
    function parentfunction() {
        var variable = "local";
      function childfunction() {
          return variable;
      }
      return childfunction();
    }
    expect(parentfunction()).toBe("local");
  });

  it("should use lexical scoping to synthesise functions", function () {

    function makeIncreaseByFunction(increaseByAmount) {
      return function (numberToIncrease) {
        return numberToIncrease + increaseByAmount;
      };
    }

    var increaseBy3 = makeIncreaseByFunction(3);
    var increaseBy5 = makeIncreaseByFunction(5);

    expect(increaseBy3(10) + increaseBy5(10)).toBe(28);
  });

  it("should allow extra function arguments", function () {

    function returnFirstArg(firstArg) {
      return firstArg;
    }

    expect(returnFirstArg("first", "second", "third")).toBe("first");

    function returnSecondArg(firstArg, secondArg) {
      return secondArg;
    }

    expect(returnSecondArg("only give first arg")).toBe(undefined);

    function returnAllArgs() {
      var argsArray = [];
      for (var i = 0; i < arguments.length; i += 1) {
        argsArray.push(arguments[i]);
      }
      return argsArray.join(",");
    }

    expect(returnAllArgs("first", "second", "third")).toBe("first,second,third");
  });

  it("should pass functions as values", function () {

    var appendRules = function (name) {
      return name + " rules!";
    };

    var appendDoubleRules = function (name) {
      return name + " totally rules!";
    };

    var praiseSinger = { givePraise: appendRules };
    expect(praiseSinger.givePraise("John")).toBe("John rules!");

    praiseSinger.givePraise = appendDoubleRules;
    expect(praiseSinger.givePraise("Mary")).toBe("Mary totally rules!");

  });

  it("should use function body as a string", function () {
    var add = new Function("a", "b", "return a + b;");
    expect(add(1, 2)).toBe(3);

    var multiply = function (a, b) {
      //An internal comment
      return a * b;
    };
    expect(multiply.toString()).toBe(`function (a, b) {
      //An internal comment
      return a * b;
    }`);
  });
});
<|endoftext|>


<commit_before>describe("About Functions", function() {

  it("should declare functions", function() {

    function add(a, b) {
      return a + b;
    }

    expect(add(1, 2)).toBe(3);
  });

  it("should know internal variables override outer variables", function () {
    var message = "Outer";

    function getMessage() {
      return message;
    }

    function overrideMessage() {
      var message = "Inner";
      return message;
    }

    expect(getMessage()).toBe('Outer');
    expect(overrideMessage()).toBe('Inner');
    expect(message).toBe('Outer');
  });

  it("should have lexical scoping", function () {
    var variable = "top-level";
    function parentfunction() {
        var variable = "local";
      function childfunction() {
          return variable;
      }
      return childfunction();
    }
    expect(parentfunction()).toBe('local');
  });

  it("should use lexical scoping to synthesise functions", function () {

    function makeIncreaseByFunction(increaseByAmount) {
      return function (numberToIncrease) {
        return numberToIncrease + increaseByAmount;
      };
    }

    var increaseBy3 = makeIncreaseByFunction(3);
    var increaseBy5 = makeIncreaseByFunction(5);

    expect(increaseBy3(10) + increaseBy5(10)).toBe(FILL_ME_IN);
  });

  it("should allow extra function arguments", function () {

    function returnFirstArg(firstArg) {
      return firstArg;
    }

    expect(returnFirstArg("first", "second", "third")).toBe(FILL_ME_IN);

    function returnSecondArg(firstArg, secondArg) {
      return secondArg;
    }

    expect(returnSecondArg("only give first arg")).toBe(FILL_ME_IN);

    function returnAllArgs() {
      var argsArray = [];
      for (var i = 0; i < arguments.length; i += 1) {
        argsArray.push(arguments[i]);
      }
      return argsArray.join(",");
    }

    expect(returnAllArgs("first", "second", "third")).toBe(FILL_ME_IN);
  });

  it("should pass functions as values", function () {

    var appendRules = function (name) {
      return name + " rules!";
    };

    var appendDoubleRules = function (name) {
      return name + " totally rules!";
    };

    var praiseSinger = { givePraise: appendRules };
    expect(praiseSinger.givePraise("John")).toBe(FILL_ME_IN);

    praiseSinger.givePraise = appendDoubleRules;
    expect(praiseSinger.givePraise("Mary")).toBe(FILL_ME_IN);

  });

  it("should use function body as a string", function () {
    var add = new Function("a", "b", "return a + b;");
    expect(add(1, 2)).toBe(FILL_ME_IN);

    var multiply = function (a, b) {
      //An internal comment
      return a * b;
    };
    expect(multiply.toString()).toBe(FILL_ME_IN);
  });
});
<commit_msg>completed 'should use lexical scoping to synthesise functions'<commit_after>describe("About Functions", function() {

  it("should declare functions", function() {

    function add(a, b) {
      return a + b;
    }

    expect(add(1, 2)).toBe(3);
  });

  it("should know internal variables override outer variables", function () {
    var message = "Outer";

    function getMessage() {
      return message;
    }

    function overrideMessage() {
      var message = "Inner";
      return message;
    }

    expect(getMessage()).toBe('Outer');
    expect(overrideMessage()).toBe('Inner');
    expect(message).toBe('Outer');
  });

  it("should have lexical scoping", function () {
    var variable = "top-level";
    function parentfunction() {
        var variable = "local";
      function childfunction() {
          return variable;
      }
      return childfunction();
    }
    expect(parentfunction()).toBe('local');
  });

  it("should use lexical scoping to synthesise functions", function () {

    function makeIncreaseByFunction(increaseByAmount) {
      return function (numberToIncrease) {
        return numberToIncrease + increaseByAmount;
      };
    }

    var increaseBy3 = makeIncreaseByFunction(3);
    var increaseBy5 = makeIncreaseByFunction(5);

    expect(increaseBy3(10) + increaseBy5(10)).toBe(28);
  });

  it("should allow extra function arguments", function () {

    function returnFirstArg(firstArg) {
      return firstArg;
    }

    expect(returnFirstArg("first", "second", "third")).toBe(FILL_ME_IN);

    function returnSecondArg(firstArg, secondArg) {
      return secondArg;
    }

    expect(returnSecondArg("only give first arg")).toBe(FILL_ME_IN);

    function returnAllArgs() {
      var argsArray = [];
      for (var i = 0; i < arguments.length; i += 1) {
        argsArray.push(arguments[i]);
      }
      return argsArray.join(",");
    }

    expect(returnAllArgs("first", "second", "third")).toBe(FILL_ME_IN);
  });

  it("should pass functions as values", function () {

    var appendRules = function (name) {
      return name + " rules!";
    };

    var appendDoubleRules = function (name) {
      return name + " totally rules!";
    };

    var praiseSinger = { givePraise: appendRules };
    expect(praiseSinger.givePraise("John")).toBe(FILL_ME_IN);

    praiseSinger.givePraise = appendDoubleRules;
    expect(praiseSinger.givePraise("Mary")).toBe(FILL_ME_IN);

  });

  it("should use function body as a string", function () {
    var add = new Function("a", "b", "return a + b;");
    expect(add(1, 2)).toBe(FILL_ME_IN);

    var multiply = function (a, b) {
      //An internal comment
      return a * b;
    };
    expect(multiply.toString()).toBe(FILL_ME_IN);
  });
});
<|endoftext|>


<commit_before>var _ = require('underscore');
var config = require('../../cdb.config');
var LayerModelBase = require('./layer-model-base');
var InfowindowTemplate = require('./infowindow-template');
var TooltipTemplate = require('./tooltip-template');
var Legends = require('./legends/legends');

var ATTRIBUTES_THAT_TRIGGER_VIS_RELOAD = ['sql', 'source', 'sql_wrap', 'cartocss'];

var RasterLayer = LayerModelBase.extend({
  defaults: {
    type: 'Raster',
    attribution: config.get('cartodb_attributions'),
    visible: true
  },

  initialize: function (attrs, options) {
    attrs = attrs || {};
    options = options || {};
    if (!options.vis) throw new Error('vis is required');

    this._vis = options.vis;
    if (attrs && attrs.cartocss) {
      this.set('initialStyle', attrs.cartocss);
    }

    // Hopefully in the future (@apercas)
      // // PUBLIC PROPERTIES
      // this.infowindow = new InfowindowTemplate(attrs.infowindow);
      // this.tooltip = new TooltipTemplate(attrs.tooltip);
      // this.unset('infowindow');
      // this.unset('tooltip');

      // this.legends = new Legends(attrs.legends, {
      //   visModel: this._vis
      // });
      // this.unset('legends');

      // this.bind('change', this._onAttributeChanged, this);
      // this.infowindow.fields.bind('reset add remove', this._reloadVis, this);
      // this.tooltip.fields.bind('reset add remove', this._reloadVis, this);

    // Pretty basic raster config
    const RASTERCONFIG = {
        "version": "1.3.1",
        "layers": [
            {
                "type": "cartodb",
                "options": {
                    "sql": `SELECT * FROM ${attrs.layer_name}`,
                    "cartocss": `#${attrs.layer_name} {raster-opacity: 0.8;}`,
                    "cartocss_version": "2.3.0",
                    "geom_column": "the_raster_webmercator",
                    "geom_type": "raster"
                }
            }
        ]
    };
    this.config = RASTERCONFIG;
    this._newRasterLayer();

    LayerModelBase.prototype.initialize.apply(this, arguments);
  },

  _onAttributeChanged: function () {
    var reloadVis = _.any(ATTRIBUTES_THAT_TRIGGER_VIS_RELOAD, function (attr) {
      if (this.hasChanged(attr)) {
        if (attr === 'cartocss' && this._dataProvider) {
          return false;
        }
        return true;
      }
    }, this);

    if (reloadVis) {
      this._reloadVis();
    }
  },

  _reloadVis: function () {
    this._vis.reload({
      sourceId: this.get('id')
    });
  },

  restoreCartoCSS: function () {
    this.set('cartocss', this.get('initialStyle'));
  },

  isVisible: function () {
    return this.get('visible');
  },

  _hasInfowindowFields: function () {
    return this.infowindow.hasFields();
  },

  _hasTooltipFields: function () {
    return this.tooltip.hasFields();
  },

  getGeometryType: function () {
    if (this._dataProvider) {
      var index = this._dataProvider._layerIndex;
      var sublayer = this._dataProvider._vectorLayerView.renderers[index];
      return sublayer.inferGeometryType();
    }
    return null;
  },

  getInteractiveColumnNames: function () {
    return _.chain(['cartodb_id'])
      .union(this.infowindow.getFieldNames())
      .union(this.tooltip.getFieldNames())
      .uniq()
      .value();
  },

  isInfowindowEnabled: function () {
    return this.infowindow.hasTemplate();
  },

  isTooltipEnabled: function () {
    return this.tooltip.hasTemplate();
  },

  getName: function () {
    return this.get('layer_name');
  },

  setDataProvider: function (dataProvider) {
    this._dataProvider = dataProvider;
  },

  getDataProvider: function () {
    return this._dataProvider;
  },

  _newRasterLayer: function () {
    const USER    = location.href.split('user/')[1].split('/')[0]
    const DOMAIN  = location.href.split('//')[1].split('/')[0];
    const APIURL  = `http://${DOMAIN}/user/${USER}/api/v1/map`;
    const SELF    = this;

    function currentEndpoint() {
      return APIURL;
    }
    function getourThis(){
      return SELF;
    }

    //based on torque.js/lib/torque/provider/windshaft.js
    var request = new XMLHttpRequest();
    request.open('POST', currentEndpoint(), true);
    request.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
    request.onload = function() {
        if (this.status >= 200 && this.status < 400){
            const DATA = JSON.parse(this.response);
            const SELF = getourThis();
            rasterLayer = L.tileLayer(`${currentEndpoint()}/${DATA.layergroupid}/{z}/{x}/{y}.png`, {
                maxZoom: 18
            }).addTo(SELF._vis.map);
        } else {
            throw 'Error calling server: Error ' + this.status + ' -> ' + this.response;
        }
    };
    request.send(JSON.stringify(this.config));

  }
});

module.exports = RasterLayer;
<commit_msg>added apikey for private rasters<commit_after>var _ = require('underscore');
var config = require('../../cdb.config');
var LayerModelBase = require('./layer-model-base');
var InfowindowTemplate = require('./infowindow-template');
var TooltipTemplate = require('./tooltip-template');
var Legends = require('./legends/legends');

var ATTRIBUTES_THAT_TRIGGER_VIS_RELOAD = ['sql', 'source', 'sql_wrap', 'cartocss'];

var RasterLayer = LayerModelBase.extend({
  defaults: {
    type: 'Raster',
    attribution: config.get('cartodb_attributions'),
    visible: true
  },

  initialize: function (attrs, options) {
    attrs = attrs || {};
    options = options || {};
    if (!options.vis) throw new Error('vis is required');

    this._vis = options.vis;
    if (attrs && attrs.cartocss) {
      this.set('initialStyle', attrs.cartocss);
    }

    // Hopefully in the future (@apercas)
      // // PUBLIC PROPERTIES
      // this.infowindow = new InfowindowTemplate(attrs.infowindow);
      // this.tooltip = new TooltipTemplate(attrs.tooltip);
      // this.unset('infowindow');
      // this.unset('tooltip');

      // this.legends = new Legends(attrs.legends, {
      //   visModel: this._vis
      // });
      // this.unset('legends');

      // this.bind('change', this._onAttributeChanged, this);
      // this.infowindow.fields.bind('reset add remove', this._reloadVis, this);
      // this.tooltip.fields.bind('reset add remove', this._reloadVis, this);

    // Pretty basic raster config
    const RASTERCONFIG = {
        "version": "1.3.1",
        "layers": [
            {
                "type": "cartodb",
                "options": {
                    "sql": `SELECT * FROM ${attrs.layer_name}`,
                    "cartocss": `#${attrs.layer_name} {raster-opacity: 0.8;}`,
                    "cartocss_version": "2.3.0",
                    "geom_column": "the_raster_webmercator",
                    "geom_type": "raster"
                }
            }
        ]
    };
    this.config = RASTERCONFIG;
    this._newRasterLayer();

    LayerModelBase.prototype.initialize.apply(this, arguments);
  },

  _onAttributeChanged: function () {
    var reloadVis = _.any(ATTRIBUTES_THAT_TRIGGER_VIS_RELOAD, function (attr) {
      if (this.hasChanged(attr)) {
        if (attr === 'cartocss' && this._dataProvider) {
          return false;
        }
        return true;
      }
    }, this);

    if (reloadVis) {
      this._reloadVis();
    }
  },

  _reloadVis: function () {
    this._vis.reload({
      sourceId: this.get('id')
    });
  },

  restoreCartoCSS: function () {
    this.set('cartocss', this.get('initialStyle'));
  },

  isVisible: function () {
    return this.get('visible');
  },

  _hasInfowindowFields: function () {
    return this.infowindow.hasFields();
  },

  _hasTooltipFields: function () {
    return this.tooltip.hasFields();
  },

  getGeometryType: function () {
    if (this._dataProvider) {
      var index = this._dataProvider._layerIndex;
      var sublayer = this._dataProvider._vectorLayerView.renderers[index];
      return sublayer.inferGeometryType();
    }
    return null;
  },

  getInteractiveColumnNames: function () {
    return _.chain(['cartodb_id'])
      .union(this.infowindow.getFieldNames())
      .union(this.tooltip.getFieldNames())
      .uniq()
      .value();
  },

  isInfowindowEnabled: function () {
    return this.infowindow.hasTemplate();
  },

  isTooltipEnabled: function () {
    return this.tooltip.hasTemplate();
  },

  getName: function () {
    return this.get('layer_name');
  },

  setDataProvider: function (dataProvider) {
    this._dataProvider = dataProvider;
  },

  getDataProvider: function () {
    return this._dataProvider;
  },

  _newRasterLayer: function () {
    const USER    = location.href.split('user/')[1].split('/')[0]
    const DOMAIN  = location.href.split('//')[1].split('/')[0];
    const APIURL  = `http://${DOMAIN}/user/${USER}/api/v1/map`;
    const SELF    = this;

    function currentEndpoint() {
      return APIURL;
    }
    function getourThis(){
      return SELF;
    }

    //based on torque.js/lib/torque/provider/windshaft.js
    var request = new XMLHttpRequest();
    request.open('POST', currentEndpoint(), true);
    request.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
    request.onload = function() {
        if (this.status >= 200 && this.status < 400){
            const DATA      = JSON.parse(this.response);
            const SELF      = getourThis();
            const APIKEY    = SELF._vis.attributes.apiKey;
            const ENDPOINT  = `${currentEndpoint()}/${DATA.layergroupid}/{z}/{x}/{y}.png?api_key=${APIKEY}`;
            rasterLayer = L.tileLayer(ENDPOINT, {
                maxZoom: 18
            }).addTo(SELF._vis.map);
        } else {
            throw 'Error calling server: Error ' + this.status + ' -> ' + this.response;
        }
    };
    request.send(JSON.stringify(this.config));

  }
});

module.exports = RasterLayer;
<|endoftext|>


<commit_before>import Validator from 'ember-cli-data-validation/validator';

export default Validator.extend({
  validate: function(attribute, value, meta, model) {
    // add your custom validation
  }
});
<commit_msg>Update __name__.js<commit_after>import Validator from 'ember-cli-data-validation/validator';

export default Validator.extend({
  validate: function(name, value, attribute, model) {
    // add your custom validation
  }
});
<|endoftext|>