<commit_before>/**
 * @module ShortestPath
 * @private
 */

/*eslint-disable*/
import { fillArray } from './Array.fill'

let prevTime = Date.now()

/**
 * @private
 * @todo tidy code
 * @todo implement faster algorithm (SMAWK)
 */
function getBestFit(tokens, spaceSize, offsets, width, height, fontSize, lineHeight, maxTokenSize, maxLineHeight, minLineHeight, truncatedToken, truncatedTokenSize, context) {
    let currentTime = Date.now()
    prevTime = currentTime
    const count = tokens.length
    let maxLines

    // heightRatio is how many times the unscaled fontsize * desired line height fit into the height of the box.
    let heightRatio = height/(fontSize*lineHeight)

    // Use the maxTokenSize and maxLineHeight to caclulate the minimum lines
    let lines

    /**
     * If a max font size is set, then we must have a minimum number of lines,
     * any less than this minimum will result in text being too large.
     * Sometimes these minimum lines will not fit into available height evenly,
     * most of the algorithm work on the bases of available height divided evenly
     * between the target lines.
     * The maxLineHeightQuotient is used later to calculate the scaledwidth using
     * a remainder of the minLines calculation diretly below, while the rest of the algorithm
     * uses the floored value of this.
     * This is only used at the first pass of findMinima, after that it continues as normal
     * with even line division.
     */
    let maxLineHeightQuotient

    lines = Math.ceil(maxTokenSize * heightRatio / width)

    if (maxLineHeight > 0) {
        maxLineHeightQuotient = height / maxLineHeight
        const maxLineHeightLines = Math.floor(maxLineHeightQuotient)
        lines = Math.max(lines, maxLineHeightLines)
    }

    if (minLineHeight > 0) {
        maxLines = Math.floor(height/ minLineHeight)
    }

    // figure out minimum lines
    // should implement a binary search for this
    let i = lines
    let finished = false
    let previousBreakpoint
    while (!finished) {
        let maxSpace = width/heightRatio * i * i
        for (let j = previousBreakpoint || 0; j < count+1; j++) {
            if (offsets[j] + (j + i - 1) * spaceSize > maxSpace) {
                let needsNewLine = false
                let newMaxSpace = width/heightRatio * (i + 1) * i // scale width as if new line, but don't actually use an i + 1 th line
                for (let k = j+1; k < count+1; k++) {
                    if (offsets[k] + ( k + i - 1) * spaceSize > newMaxSpace) {
                        needsNewLine = true
                        previousBreakpoint = k - 1 // shouldn't find the breakpoint earlier than this
                        break
                    }
                }
                if (!needsNewLine) {
                    // we have min
                    finished = true // finish the while
                    break // from the for, not the while
                } else {
                    // we need moar lines
                    i++
                }
                break // from the for, not the while
            } else if (j === count) {
                // from the for, and end while
                finished = true
                break
            }
        }
    }
    let minLines = i

    // make sure min lines isn't greater than max lines
    if (maxLines) {
        if (minLines > maxLines) {
            minLines = maxLines
        }
    }

    let largestLineSize

    function truncateLine(line) {
        let lineOffsets = new Array(line.length + 1)
        lineOffsets[0] = 0

        for (let i = 0; i < line.length; i++) {
            lineOffsets[i + 1] = context.measureText(line.slice(0, i)).width
        }

        let j
        for (j = lineOffsets.length; j > 0; j--) {
            if ((lineOffsets[j] + truncatedTokenSize + spaceSize) < largestLineSize) {
                let newLineSize = context.measureText(line.slice(0, j - 1) + truncatedToken).width;
                if (newLineSize + 1 < largestLineSize) { // + 1 to avoid rounding errors
                    break;
                }
            }
        }

        return line.slice(0, j - 1) + truncatedToken
    }

    function calcWidth(i, j) {
        return offsets[j] - offsets[i] + 1 + (j - i - 1) * spaceSize // there is a plus one to avoid rounding errors
    }

    function findMinima(scaledWidth, targetLines) {
        let minima = [0].concat(fillArray(Array(count), Infinity))
        let breaks = fillArray(Array(count + 1), 0)
        largestLineSize = 0
        for (let i = 0; i < count; i++) {
            let j = i + 1
            while (j <= count) {
                let w = calcWidth(i, j)
                if (w > scaledWidth) {
                    break
                }

                let cost = minima[i] + Math.pow((scaledWidth - w), 2)

                if (cost < minima[j]) {
                    minima[j] = cost
                    breaks[j] = i
                }
                j += 1
            }
        }

        let lines = []
        let lineIndexes = []
        let j = count

        while (j > 0) {
            let i = breaks[j]
            if (lineIndexes.length + 1 > targetLines && (!maxLines || (maxLines > targetLines))) {
                return false
            } else {
                lineIndexes.push({ start: i, end: j })
            }
            let width = calcWidth(i, j)
            largestLineSize = Math.max(largestLineSize, width)
            j = i
        }

        const truncate = maxLines && (lineIndexes.length > maxLines) && (targetLines === maxLines)

        function countLinesFromBreaks (nextBreak) {
            let lineCount = 0

            while (nextBreak !== 0) {
                nextBreak = breaks[nextBreak]
                lineCount++
            }

            return lineCount
        }

        let currentStartingBreak = 0
        if (truncate) {
            // Need to find the break that fits the most words into the maxLines
            j = 0
            for (let i = 0; i < breaks.length; i++) {
                if (i !== 0) {
                    let lineCount = countLinesFromBreaks(i)

                    if (lineCount === maxLines) {
                        currentStartingBreak = i
                    }

                    if (lineCount > maxLines) {
                        break
                    }
                }
            }

            lineIndexes = []
            j = currentStartingBreak
            let i = breaks[j]
            lineIndexes.push({ start: i, end: j + 1 })

            let width = calcWidth(i, j)
            largestLineSize = Math.max(largestLineSize, width)

            j = i
            // recalculate line indexes with truncated text
            while (j > 0) {
                let i = breaks[j]
                lineIndexes.push({ start: i, end: j })
                let width = calcWidth(i, j)
                largestLineSize = Math.max(largestLineSize, width)
                j = i
            }
        }

        for (let l = lineIndexes.length - 1; l >= 0; l--) {
            const lineIndex = lineIndexes[l]
            let line = tokens.slice(lineIndex.start, lineIndex.end).join(' ')
            if (l === 0 && truncate) {
                line = truncateLine(line)
            }
            lines.push({ line: line })
        }

        return lines
    }

    let currentLines = minLines
    let results
    let maxLineWidth
    let attempts = 0
    let trying = true

    const maxLineHeightQuotientFloor = Math.floor(maxLineHeightQuotient)
    let usedMaxLineHeight = false
    while (trying) {
        maxLineWidth = currentLines * width/heightRatio
        if (!usedMaxLineHeight) {
            if (Math.floor(maxLineHeightQuotient) === currentLines) {
                maxLineWidth = maxLineHeightQuotient * width/heightRatio
            }
        }

        results = findMinima(maxLineWidth, currentLines)

        if (attempts > 100) {
            throw new Error(`Can't fit text after ${attempts} attempts`);
        }
        attempts++

        if (!results) {
            if (!usedMaxLineHeight) {
                usedMaxLineHeight = true
            } else {
                currentLines++
            }
        } else {
            trying = false
        }
    }
    return {
        results: results,
        targetLines: currentLines,
        maxLineWidth: maxLineWidth,
        largestLineSize: largestLineSize
    }
}

export { getBestFit }
<commit_msg>tidy up with a little bit of white space<commit_after>/**
 * @module ShortestPath
 * @private
 */

/*eslint-disable*/
import { fillArray } from './Array.fill'

let prevTime = Date.now()

/**
 * @private
 * @todo tidy code
 * @todo implement faster algorithm (SMAWK)
 */
function getBestFit(tokens, spaceSize, offsets, width, height, fontSize, lineHeight, maxTokenSize, maxLineHeight, minLineHeight, truncatedToken, truncatedTokenSize, context) {
    let currentTime = Date.now()
    prevTime = currentTime
    const count = tokens.length
    let maxLines

    // heightRatio is how many times the unscaled fontsize * desired line height fit into the height of the box.
    let heightRatio = height/(fontSize*lineHeight)

    // Use the maxTokenSize and maxLineHeight to caclulate the minimum lines
    let lines

    /**
     * If a max font size is set, then we must have a minimum number of lines,
     * any less than this minimum will result in text being too large.
     * Sometimes these minimum lines will not fit into available height evenly,
     * most of the algorithm work on the bases of available height divided evenly
     * between the target lines.
     * The maxLineHeightQuotient is used later to calculate the scaledwidth using
     * a remainder of the minLines calculation diretly below, while the rest of the algorithm
     * uses the floored value of this.
     * This is only used at the first pass of findMinima, after that it continues as normal
     * with even line division.
     */
    let maxLineHeightQuotient

    lines = Math.ceil(maxTokenSize * heightRatio / width)

    if (maxLineHeight > 0) {
        maxLineHeightQuotient = height / maxLineHeight
        const maxLineHeightLines = Math.floor(maxLineHeightQuotient)
        lines = Math.max(lines, maxLineHeightLines)
    }

    if (minLineHeight > 0) {
        maxLines = Math.floor(height/ minLineHeight)
    }

    // figure out minimum lines
    // should implement a binary search for this
    let i = lines
    let finished = false
    let previousBreakpoint
    while (!finished) {
        let maxSpace = width/heightRatio * i * i
        for (let j = previousBreakpoint || 0; j < count+1; j++) {
            if (offsets[j] + (j + i - 1) * spaceSize > maxSpace) {
                let needsNewLine = false
                let newMaxSpace = width/heightRatio * (i + 1) * i // scale width as if new line, but don't actually use an i + 1 th line
                for (let k = j+1; k < count+1; k++) {
                    if (offsets[k] + ( k + i - 1) * spaceSize > newMaxSpace) {
                        needsNewLine = true
                        previousBreakpoint = k - 1 // shouldn't find the breakpoint earlier than this
                        break
                    }
                }
                if (!needsNewLine) {
                    // we have min
                    finished = true // finish the while
                    break // from the for, not the while
                } else {
                    // we need moar lines
                    i++
                }
                break // from the for, not the while
            } else if (j === count) {
                // from the for, and end while
                finished = true
                break
            }
        }
    }

    let minLines = i

    // make sure min lines isn't greater than max lines
    if (maxLines) {
        if (minLines > maxLines) {
            minLines = maxLines
        }
    }

    let largestLineSize

    function truncateLine(line) {
        let lineOffsets = new Array(line.length + 1)
        lineOffsets[0] = 0

        for (let i = 0; i < line.length; i++) {
            lineOffsets[i + 1] = context.measureText(line.slice(0, i)).width
        }

        let j
        for (j = lineOffsets.length; j > 0; j--) {
            if ((lineOffsets[j] + truncatedTokenSize + spaceSize) < largestLineSize) {
                let newLineSize = context.measureText(line.slice(0, j - 1) + truncatedToken).width;
                if (newLineSize + 1 < largestLineSize) { // + 1 to avoid rounding errors
                    break;
                }
            }
        }

        return line.slice(0, j - 1) + truncatedToken
    }

    function calcWidth(i, j) {
        return offsets[j] - offsets[i] + 1 + (j - i - 1) * spaceSize // there is a plus one to avoid rounding errors
    }

    function findMinima(scaledWidth, targetLines) {
        let minima = [0].concat(fillArray(Array(count), Infinity))
        let breaks = fillArray(Array(count + 1), 0)

        largestLineSize = 0
        for (let i = 0; i < count; i++) {
            let j = i + 1
            while (j <= count) {
                let w = calcWidth(i, j)
                if (w > scaledWidth) {
                    break
                }

                let cost = minima[i] + Math.pow((scaledWidth - w), 2)

                if (cost < minima[j]) {
                    minima[j] = cost
                    breaks[j] = i
                }
                j += 1
            }
        }

        let lines = []
        let lineIndexes = []
        let j = count

        while (j > 0) {
            let i = breaks[j]
            if (lineIndexes.length + 1 > targetLines && (!maxLines || (maxLines > targetLines))) {
                return false
            } else {
                lineIndexes.push({ start: i, end: j })
            }
            let width = calcWidth(i, j)
            largestLineSize = Math.max(largestLineSize, width)
            j = i
        }

        const truncate = maxLines && (lineIndexes.length > maxLines) && (targetLines === maxLines)

        function countLinesFromBreaks (nextBreak) {
            let lineCount = 0

            while (nextBreak !== 0) {
                nextBreak = breaks[nextBreak]
                lineCount++
            }

            return lineCount
        }

        let currentStartingBreak = 0
        if (truncate) {
            // Need to find the break that fits the most words into the maxLines
            j = 0
            for (let i = 0; i < breaks.length; i++) {
                if (i !== 0) {
                    let lineCount = countLinesFromBreaks(i)

                    if (lineCount === maxLines) {
                        currentStartingBreak = i
                    }

                    if (lineCount > maxLines) {
                        break
                    }
                }
            }

            lineIndexes = []
            j = currentStartingBreak
            let i = breaks[j]
            lineIndexes.push({ start: i, end: j + 1 })

            let width = calcWidth(i, j)
            largestLineSize = Math.max(largestLineSize, width)

            j = i
            // recalculate line indexes with truncated text
            while (j > 0) {
                let i = breaks[j]
                lineIndexes.push({ start: i, end: j })
                let width = calcWidth(i, j)
                largestLineSize = Math.max(largestLineSize, width)
                j = i
            }
        }

        for (let l = lineIndexes.length - 1; l >= 0; l--) {
            const lineIndex = lineIndexes[l]
            let line = tokens.slice(lineIndex.start, lineIndex.end).join(' ')
            if (l === 0 && truncate) {
                line = truncateLine(line)
            }
            lines.push({ line: line })
        }

        return lines
    }

    let currentLines = minLines
    let results
    let maxLineWidth
    let attempts = 0
    let trying = true

    const maxLineHeightQuotientFloor = Math.floor(maxLineHeightQuotient)
    let usedMaxLineHeight = false
    while (trying) {
        maxLineWidth = currentLines * width/heightRatio
        if (!usedMaxLineHeight) {
            if (Math.floor(maxLineHeightQuotient) === currentLines) {
                maxLineWidth = maxLineHeightQuotient * width/heightRatio
            }
        }

        results = findMinima(maxLineWidth, currentLines)

        if (attempts > 100) {
            throw new Error(`Can't fit text after ${attempts} attempts`);
        }
        attempts++

        if (!results) {
            if (!usedMaxLineHeight) {
                usedMaxLineHeight = true
            } else {
                currentLines++
            }
        } else {
            trying = false
        }
    }
    return {
        results: results,
        targetLines: currentLines,
        maxLineWidth: maxLineWidth,
        largestLineSize: largestLineSize
    }
}

export { getBestFit }
<|endoftext|>


<commit_before>import { addDecorator, addParameters } from '@storybook/react';
import { withInfo } from '@storybook/addon-info';
import { COLOR } from '../src/constants';
import styles from '@sambego/storybook-styles';
import PropTable from './components/propTable';

addParameters({
  backgrounds: [
    { name: 'Aqua lightest', value: COLOR.AQUA.LIGHTEST },
    { name: 'Neutral', value: COLOR.NEUTRAL.NORMAL },
    { name: 'Gold lightest', value: COLOR.GOLD.LIGHTEST },
    { name: 'Mint lightest', value: COLOR.MINT.LIGHTEST },
    { name: 'Ruby lightest', value: COLOR.RUBY.LIGHTEST },
    { name: 'Teal lightest', value: COLOR.TEAL.LIGHTEST },
    { name: 'Violet lightest', value: COLOR.VIOLET.LIGHTEST },
    { name: 'Teal darkest', value: COLOR.TEAL.DARKEST },
  ],
});

addParameters({
  options: {
    showRoots: true,
  },
});

// addon-info
addDecorator(
  withInfo({
    inline: true,
    source: true,
    styles: stylesheet => {
      stylesheet.infoBody = {
        color: COLOR.TEAL.DARKEST,
        fontFamily: 'Inter-Regular',
        fontSize: '14px',
        margin: '48px 0',
        padding: '0',
      };

      stylesheet.header = {
        h1: {
          fontFamily: 'Inter-Medium',
          fontSize: '24px',
          fontWeight: 500,
          lineHeight: '30px',
          margin: 0,
        },
        h2: {
          color: COLOR.NEUTRAL.DARKEST,
          fontFamily: 'Inter-Medium',
          fontSize: '18px',
          fontWeight: 500,
          lineHeight: '24px',
          margin: '24px 0 10px 0',
        },
        body: {
          borderBottom: `1px solid ${COLOR.NEUTRAL.NORMAL}`,
          paddingTop: 10,
          marginBottom: 10,
        },
      };

      stylesheet.source = {
        h1: {
          borderBottom: `1px solid ${COLOR.NEUTRAL.NORMAL}`,
          color: COLOR.NEUTRAL.DARKEST,
          fontFamily: 'Inter-Medium',
          margin: '24px 0 10px 0',
          fontWeight: 500,
          fontSize: '18px',
          lineHeight: '24px',
          padding: '12px 0',
        },
      };

      stylesheet.propTableHead = {
        color: COLOR.TEAL.DARKEST,
        fontFamily: 'Inter-Medium',
        fontWeight: 500,
        fontSize: '16px',
        lineHeight: '21px',
        margin: '20px 0 0 0',
      };

      return stylesheet;
    },
    TableComponent: PropTable,
  }),
);

// addon-styles
addDecorator(
  styles({
    margin: '24px auto',
    maxWidth: '90%',
  }),
);
<commit_msg>Add a sort function to control the group order in the navigation bar<commit_after>import { addDecorator, addParameters } from '@storybook/react';
import { withInfo } from '@storybook/addon-info';
import { COLOR } from '../src/constants';
import styles from '@sambego/storybook-styles';
import PropTable from './components/propTable';

addParameters({
  backgrounds: [
    { name: 'Aqua lightest', value: COLOR.AQUA.LIGHTEST },
    { name: 'Neutral', value: COLOR.NEUTRAL.NORMAL },
    { name: 'Gold lightest', value: COLOR.GOLD.LIGHTEST },
    { name: 'Mint lightest', value: COLOR.MINT.LIGHTEST },
    { name: 'Ruby lightest', value: COLOR.RUBY.LIGHTEST },
    { name: 'Teal lightest', value: COLOR.TEAL.LIGHTEST },
    { name: 'Violet lightest', value: COLOR.VIOLET.LIGHTEST },
    { name: 'Teal darkest', value: COLOR.TEAL.DARKEST },
  ],
});

addParameters({
  options: {
    showRoots: true,
    storySort: (a, b) => (a[1].kind === b[1].kind ? 0 : a[1].id.localeCompare(b[1].id, undefined, { numeric: true })),
  },
});

// addon-info
addDecorator(
  withInfo({
    inline: true,
    source: true,
    styles: stylesheet => {
      stylesheet.infoBody = {
        color: COLOR.TEAL.DARKEST,
        fontFamily: 'Inter-Regular',
        fontSize: '14px',
        margin: '48px 0',
        padding: '0',
      };

      stylesheet.header = {
        h1: {
          fontFamily: 'Inter-Medium',
          fontSize: '24px',
          fontWeight: 500,
          lineHeight: '30px',
          margin: 0,
        },
        h2: {
          color: COLOR.NEUTRAL.DARKEST,
          fontFamily: 'Inter-Medium',
          fontSize: '18px',
          fontWeight: 500,
          lineHeight: '24px',
          margin: '24px 0 10px 0',
        },
        body: {
          borderBottom: `1px solid ${COLOR.NEUTRAL.NORMAL}`,
          paddingTop: 10,
          marginBottom: 10,
        },
      };

      stylesheet.source = {
        h1: {
          borderBottom: `1px solid ${COLOR.NEUTRAL.NORMAL}`,
          color: COLOR.NEUTRAL.DARKEST,
          fontFamily: 'Inter-Medium',
          margin: '24px 0 10px 0',
          fontWeight: 500,
          fontSize: '18px',
          lineHeight: '24px',
          padding: '12px 0',
        },
      };

      stylesheet.propTableHead = {
        color: COLOR.TEAL.DARKEST,
        fontFamily: 'Inter-Medium',
        fontWeight: 500,
        fontSize: '16px',
        lineHeight: '21px',
        margin: '20px 0 0 0',
      };

      return stylesheet;
    },
    TableComponent: PropTable,
  }),
);

// addon-styles
addDecorator(
  styles({
    margin: '24px auto',
    maxWidth: '90%',
  }),
);
<|endoftext|>


<commit_before>const path=require('path')

module.exports = {
    "make_targets": {
        "win32": [
          "squirrel"
        ]
    }
    ,
    "electronPackagerConfig": {
        "packageManager": "npm",
        "extraResources": [
          "src/",
          "visifile/"
        ],
        "icon": path.resolve(__dirname,"src/VisiFile.icns")
    }
    ,
    "electronRebuildConfig": {}
    ,
    "electronWinstallerConfig": {
        "setupIcon": path.resolve(__dirname,"src/visifilecolor_win.ico")
    }
}
<commit_msg>Make windows exe<commit_after>const path=require('path')

module.exports = {
    "make_targets": {
        "win32": [
          "squirrel"
        ]
    }
    ,
    "electronPackagerConfig": {
        "packageManager": "npm",
        "extraResources": [
          "src/",
          "visifile/"
        ],
        "icon": path.resolve(__dirname,"src/visifilecolor_win.ico")
    }
    ,
    "electronRebuildConfig": {}
    ,
    "electronWinstallerConfig": {
        "setupIcon": path.resolve(__dirname,"src/visifilecolor_win.ico")
    }
}
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2012 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.
 */
MODEL({
  name: 'UnitTest',
  plural: 'Unit Tests',
  tableProperties: [ 'description', 'passed', 'failed' ],
  properties:
  [
    {
      model_: 'Property',
      name: 'name',
      type: 'String',
      required: true,
      displayWidth: 50,
      help: "The unit test's name."
    },
    {
      model_: 'Property',
      name: 'description',
      type: 'String',
      displayWidth: 70,
      displayHeight: 5,
      defaultValue: '',
      // defaultValueFn: function() { return "Test " + this.name; },
      help: 'A multi-line description of the unit test.'
    },
    {
      model_: 'BooleanProperty',
      name: 'disabled',
      defaultValue: false
    },
    {
      model_: 'IntProperty',
      name: 'passed',
      required: true,
      transient: true,
      displayWidth: 8,
      displayHeight: 1,
      view: 'IntFieldView',
      help: 'Number of sub-tests to pass.'
    },
    {
      model_: 'IntProperty',
      name: 'failed',
      required: true,
      transient: true,
      displayWidth: 8,
      displayHeight: 1,
      help: 'Number of sub-tests to fail.'
    },
    {
      model_: 'BooleanProperty',
      name: 'async',
      defaultValue: false
    },
    {
      model_: 'FunctionProperty',
      name: 'code',
      label: 'Test Code',
      displayWidth: 80,
      displayHeight: 30,
      fromElement: function(e) {
        var txt = e.innerHTML;

        txt =
          txt.trim().startsWith('function') ? txt                               :
          this.async                        ? 'function(ret) {\n' + txt + '\n}' :
                                              'function() {\n'    + txt + '\n}' ;

        return eval('(' + txt + ')');
      },
      preSet: function(_, value) {
        if ( typeof value === 'string' ) {
          if ( value.startsWith('function') ) {
            value = eval('(' + value + ')');
          } else {
            value = new Function(value);
          }
        }

        // Now value is a function either way.
        // We just need to check that if it's async it has an argument.
        if ( typeof value === 'function' && this.async && value.length === 0 ) {
          var str = value.toString();
          return eval('(function(ret)' + str.substring(str.indexOf('{')) + ')');
        } else {
          return value;
        }
      }
    },
    {
      model_: 'BooleanProperty',
      name: 'hasRun',
      transient: true,
      hidden: true,
      defaultValue: false
    },
    {
      model_: 'Property',
      name: 'results',
      type: 'String',
      mode: 'read-only',
      view: 'UnitTestResultView',
      transient: true,
      required: true,
      displayWidth: 80,
      displayHeight: 20
    },
    {
      model_: 'StringArrayProperty',
      name:  'tags',
      label: 'Tags'
    },
    {
      name: 'parentTest'
    },
    {
      name: 'runChildTests',
      help: 'Whether the nested child tests should be run when this test is. Defaults to true; set to false for UITests.',
      transient: true,
      defaultValue: true
    }
  ],

  relationships: [
    {
      name: 'tests',
      label: 'Sub-tests of this test.',
      relatedModel: 'UnitTest',
      relatedProperty: 'parentTest'
    }
  ],

  actions: [
    {
      name:  'test',
      help:  'Run the unit tests.',

      action: function(obj) { asynchronized(this.atest(), this.LOCK)(function() {}); }
    }
  ],

  methods:{
    // Lock to prevent more than one top-level Test from running at once.
    LOCK: {},

    // Run test asynchronously as an afunc.
    atest: function() {
      var self = this;

      if ( this.hasRun ) return anop;
      this.hasRun = true;

      // Copy the test methods into the context.=
      // The context becomes "this" inside the tests.
      // The UnitTest object itself becomes this.test inside tests.
      this.X = this.X.sub({}, this.name);
      this.X.log    = this.log;
      this.X.jlog   = this.jlog;
      this.X.assert = this.assert;
      this.X.fail   = this.fail;
      this.X.ok     = this.ok;
      this.X.append = this.append.bind(this);
      this.X.test   = this;

      this.results = '';

      this.passed = 0;
      this.failed = 0;

      var code;
      code = eval('(' + this.code.toString() + ')');

      var afuncs = [];
      var oldLog;

      afuncs.push(function(ret) {
        oldLog = console.log;
        console.log = self.log.bind(self.X);
        ret();
      });

      afuncs.push(this.async ? code.bind(this.X) : code.abind(this.X));

      afuncs.push(function(ret) {
        console.log = oldLog;
        ret();
      });

      if ( this.runChildTests ) {
        // TODO: This is horrendous, but I can't see a better way.
        // It would nest quite neatly if there were afunc DAO ops.
        var future = this.tests.select([].sink);
        afuncs.push(function(ret) {
          future(function(innerTests) {
            var afuncsInner = [];
            innerTests.forEach(function(test) {
              afuncsInner.push(function(ret) {
                test.X = self.X.sub();
                test.atest()(ret);
              });
              afuncsInner.push(function(ret) {
                self.passed += test.passed;
                self.failed += test.failed;
                ret();
              });
            });
            if ( afuncsInner.length ) {
              aseq.apply(this, afuncsInner)(ret);
            } else {
              ret();
            }
          });
        });
      }

      afuncs.push(function(ret) {
        self.hasRun = true;
        ret();
      });

      return aseq.apply(this, afuncs);
    },
    append: function(s) { this.results += s; },
    log: function(/*arguments*/) {
      for ( var i = 0 ; i < arguments.length ; i++ )
        this.append(arguments[i]);
      this.append('\n');
    },
    jlog: function(/*arguments*/) {
      for ( var i = 0 ; i < arguments.length ; i++ )
        this.append(JSONUtil.stringify(arguments[i]));
      this.append('\n');
    },
    addHeader: function(name) {
      this.log('
' + name + '
');
    },
    assert: function(condition, comment) {
      if ( condition ) this.passed++; else this.failed++;
      this.log(
        (comment ? comment : '(no message)') +
        ' ' +
        (condition ? "OK" : "ERROR"));
    },
    fail: function(comment) {
      this.assert(false, comment);
    },
    ok: function(comment) {
      this.assert(true, comment);
    },
    hasFailed: function() {
      return this.failed > 0;
    }
  }
});

MODEL({
  name: 'RegressionTest',
  label: 'Regression Test',
  help: 'A UnitTest with a gold output, which is compared with the output of the live test.',

  extendsModel: 'UnitTest',

  properties: [
    {
      name: 'master'
    },
    {
      name: 'results',
      view: 'RegressionTestResultView'
    },
    {
      model_: 'BooleanProperty',
      name: 'regression',
      hidden: true,
      transient: true,
      defaultValue: false
    }
  ],

  actions: [
    {
      name: 'update',
      isEnabled: function() { return ! this.results.equals(this.master); },
      action: function() {
        console.warn('updating test', this.$UID, this.name, this.master, this.results);
        this.master = this.results;
      }
    }
  ],

  methods: {
    atest: function() {
      // Run SUPER's atest, which returns the unexecuted afunc.
      var sup = this.SUPER();
      // Now we append a last piece that updates regression based on the results.
      return aseq(
        sup,
        function(ret) {
          this.regression = this.hasRun && ! this.results.equals(this.master);
          ret();
        }.bind(this)
      );
    }
  },
  hasFailed: function() {
    return this.regression;
  }
});

MODEL({
  name: 'Relationship',
  tableProperties: [
    'name', 'label', 'relatedModel', 'relatedProperty'
  ],
  properties: [
    {
      name:  'name',
      type:  'String',
      displayWidth: 30,
      displayHeight: 1,
      defaultValueFn: function() { return GLOBAL[this.relatedModel] ? GLOBAL[this.relatedModel].plural : ''; },
      help: 'The coding identifier for the action.'
    },
    {
      name: 'label',
      type: 'String',
      displayWidth: 70,
      displayHeight: 1,
      defaultValueFn: function() { return this.name.labelize(); },
      help: 'The display label for the action.'
    },
    {
      name: 'help',
      label: 'Help Text',
      type: 'String',
      displayWidth: 70,
      displayHeight: 6,
      defaultValue: '',
      help: 'Help text associated with the relationship.'
    },
    {
      name: 'documentation',
      type: 'Documentation',
      view: 'DocModelView',
      help: 'Documentation associated with this entity.',
    },
    {
      name:  'relatedModel',
      type:  'String',
      required: true,
      displayWidth: 30,
      displayHeight: 1,
      defaultValue: '',
      help: 'The name of the related Model.'
    },
    {
      name:  'relatedProperty',
      type:  'String',
      required: true,
      displayWidth: 30,
      displayHeight: 1,
      defaultValue: '',
      help: 'The join property of the related Model.'
    }
  ]/*,
  methods: {
    dao: function() {
      var m = this.X[this.relatedModel];
      return this.X[m.name + 'DAO'];
    },
    JOIN: function(sink, opt_where) {
      var m = this.X[this.relatedModel];
      var dao = this.X[m.name + 'DAO'] || this.X[m.plural];
      return MAP(JOIN(
        dao.where(opt_where || TRUE),
        m.getProperty(this.relatedProperty),
        []), sink);
    }
  }*/
});

MODEL({
  name: 'Issue',
  plural: 'Issues',
  help: 'An issue describes a question, feature request, or defect.',
  ids: [
    'id'
  ],
  tableProperties:
  [
    'id', 'severity', 'status', 'summary', 'assignedTo'
  ],
  properties:
  [
    {
      model_: 'IntProperty',
      name: 'id',
      label: 'Issue ID',
      displayWidth: 12,
      help: 'Issue\'s unique sequence number.'
    },
    {
      name: 'severity',
      view: {
        create: function() { return ChoiceView.create({choices: [
          'Feature',
          'Minor',
          'Major',
          'Question'
        ]});}
      },
      defaultValue: 'String',
      help: 'The severity of the issue.'
    },
    {
      name: 'status',
      type: 'String',
      required: true,
      view: {
        create: function() { return ChoiceView.create({choices: [
          'Open',
          'Accepted',
          'Complete',
          'Closed'
        ]});}
      },
      defaultValue: 'String',
      help: 'The status of the issue.'
    },
    {
      model_: 'Property',
      name: 'summary',
      type: 'String',
      required: true,
      displayWidth: 70,
      displayHeight: 1,
      help: 'A one line summary of the issue.'
    },
    {
      model_: 'Property',
      name: 'created',
      type: 'DateTime',
      required: true,
      displayWidth: 50,
      displayHeight: 1,
      factory: function() { return new Date(); },
      help: 'When this issue was created.'
    },
    {
      model_: 'Property',
      name: 'createdBy',
      type: 'String',
      defaultValue: 'kgr',
      required: true,
      displayWidth: 30,
      displayHeight: 1,
      help: 'Who created the issue.'
    },
    {
      model_: 'Property',
      name: 'assignedTo',
      type: 'String',
      defaultValue: 'kgr',
      displayWidth: 30,
      displayHeight: 1,
      help: 'Who the issue is currently assigned to.'
    },
    {
      model_: 'Property',
      name: 'notes',
      displayWidth: 75,
      displayHeight: 20,
      view: 'TextAreaView',
      help: 'Notes describing issue.'
    }
  ],
  tests: [
    {
      model_: 'UnitTest',
      description: 'test1',
      code: function() {this.addHeader("header");this.ok("pass");this.fail("fail");}
    }
  ]
});

(function() {
  for ( var i = 0 ; i < Model.templates.length ; i++ ){
      Model.templates[i] = JSONUtil.mapToObj(Model.templates[i]);
  }
  (function() {
    var a = Model.properties;
    for ( var i = 0 ; i < a.length ; i++ ) {
      if ( ! Property.isInstance(a[i]) ) {
        a[i] = Property.getPrototype().create(a[i]);
      }
    }
  })();
})();
<commit_msg>Formatting<commit_after>/**
 * @license
 * Copyright 2012 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.
 */
MODEL({
  name: 'UnitTest',
  plural: 'Unit Tests',
  tableProperties: [ 'description', 'passed', 'failed' ],
  properties:
  [
    {
      model_: 'Property',
      name: 'name',
      type: 'String',
      required: true,
      displayWidth: 50,
      help: "The unit test's name."
    },
    {
      model_: 'Property',
      name: 'description',
      type: 'String',
      displayWidth: 70,
      displayHeight: 5,
      defaultValue: '',
      // defaultValueFn: function() { return "Test " + this.name; },
      help: 'A multi-line description of the unit test.'
    },
    {
      model_: 'BooleanProperty',
      name: 'disabled',
      defaultValue: false
    },
    {
      model_: 'IntProperty',
      name: 'passed',
      required: true,
      transient: true,
      displayWidth: 8,
      displayHeight: 1,
      view: 'IntFieldView',
      help: 'Number of sub-tests to pass.'
    },
    {
      model_: 'IntProperty',
      name: 'failed',
      required: true,
      transient: true,
      displayWidth: 8,
      displayHeight: 1,
      help: 'Number of sub-tests to fail.'
    },
    {
      model_: 'BooleanProperty',
      name: 'async',
      defaultValue: false
    },
    {
      model_: 'FunctionProperty',
      name: 'code',
      label: 'Test Code',
      displayWidth: 80,
      displayHeight: 30,
      fromElement: function(e) {
        var txt = e.innerHTML;

        txt =
          txt.trim().startsWith('function') ? txt                               :
          this.async                        ? 'function(ret) {\n' + txt + '\n}' :
                                              'function() {\n'    + txt + '\n}' ;

        return eval('(' + txt + ')');
      },
      preSet: function(_, value) {
        if ( typeof value === 'string' ) {
          if ( value.startsWith('function') ) {
            value = eval('(' + value + ')');
          } else {
            value = new Function(value);
          }
        }

        // Now value is a function either way.
        // We just need to check that if it's async it has an argument.
        if ( typeof value === 'function' && this.async && value.length === 0 ) {
          var str = value.toString();
          return eval('(function(ret)' + str.substring(str.indexOf('{')) + ')');
        } else {
          return value;
        }
      }
    },
    {
      model_: 'BooleanProperty',
      name: 'hasRun',
      transient: true,
      hidden: true,
      defaultValue: false
    },
    {
      model_: 'Property',
      name: 'results',
      type: 'String',
      mode: 'read-only',
      view: 'UnitTestResultView',
      transient: true,
      required: true,
      displayWidth: 80,
      displayHeight: 20
    },
    {
      model_: 'StringArrayProperty',
      name:  'tags',
      label: 'Tags'
    },
    {
      name: 'parentTest'
    },
    {
      name: 'runChildTests',
      help: 'Whether the nested child tests should be run when this test is. Defaults to true; set to false for UITests.',
      transient: true,
      defaultValue: true
    }
  ],

  relationships: [
    {
      name: 'tests',
      label: 'Sub-tests of this test.',
      relatedModel: 'UnitTest',
      relatedProperty: 'parentTest'
    }
  ],

  actions: [
    {
      name:  'test',
      help:  'Run the unit tests.',

      action: function(obj) { asynchronized(this.atest(), this.LOCK)(function() {}); }
    }
  ],

  methods:{
    // Lock to prevent more than one top-level Test from running at once.
    LOCK: {},

    // Run test asynchronously as an afunc.
    atest: function() {
      var self = this;

      if ( this.hasRun ) return anop;
      this.hasRun = true;

      // Copy the test methods into the context.=
      // The context becomes "this" inside the tests.
      // The UnitTest object itself becomes this.test inside tests.
      this.X = this.X.sub({}, this.name);
      this.X.log    = this.log;
      this.X.jlog   = this.jlog;
      this.X.assert = this.assert;
      this.X.fail   = this.fail;
      this.X.ok     = this.ok;
      this.X.append = this.append.bind(this);
      this.X.test   = this;

      this.results = '';

      this.passed = 0;
      this.failed = 0;

      var code;
      code = eval('(' + this.code.toString() + ')');

      var afuncs = [];
      var oldLog;

      afuncs.push(function(ret) {
        oldLog = console.log;
        console.log = self.log.bind(self.X);
        ret();
      });

      afuncs.push(this.async ? code.bind(this.X) : code.abind(this.X));

      afuncs.push(function(ret) {
        console.log = oldLog;
        ret();
      });

      if ( this.runChildTests ) {
        // TODO: This is horrendous, but I can't see a better way.
        // It would nest quite neatly if there were afunc DAO ops.
        var future = this.tests.select([].sink);
        afuncs.push(function(ret) {
          future(function(innerTests) {
            var afuncsInner = [];
            innerTests.forEach(function(test) {
              afuncsInner.push(function(ret) {
                test.X = self.X.sub();
                test.atest()(ret);
              });
              afuncsInner.push(function(ret) {
                self.passed += test.passed;
                self.failed += test.failed;
                ret();
              });
            });
            if ( afuncsInner.length ) {
              aseq.apply(this, afuncsInner)(ret);
            } else {
              ret();
            }
          });
        });
      }

      afuncs.push(function(ret) {
        self.hasRun = true;
        ret();
      });

      return aseq.apply(this, afuncs);
    },
    append: function(s) { this.results += s; },
    log: function(/*arguments*/) {
      for ( var i = 0 ; i < arguments.length ; i++ )
        this.append(arguments[i]);
      this.append('\n');
    },
    jlog: function(/*arguments*/) {
      for ( var i = 0 ; i < arguments.length ; i++ )
        this.append(JSONUtil.stringify(arguments[i]));
      this.append('\n');
    },
    addHeader: function(name) {
      this.log('
' + name + '
');
    },
    assert: function(condition, comment) {
      if ( condition ) this.passed++; else this.failed++;
      this.log(
        (comment ? comment : '(no message)') +
        ' ' +
        (condition ? "OK" : "ERROR"));
    },
    fail: function(comment) {
      this.assert(false, comment);
    },
    ok: function(comment) {
      this.assert(true, comment);
    },
    hasFailed: function() {
      return this.failed > 0;
    }
  }
});

MODEL({
  name: 'RegressionTest',
  label: 'Regression Test',
  help: 'A UnitTest with a gold output, which is compared with the output of the live test.',

  extendsModel: 'UnitTest',

  properties: [
    {
      name: 'master'
    },
    {
      name: 'results',
      view: 'RegressionTestResultView'
    },
    {
      model_: 'BooleanProperty',
      name: 'regression',
      hidden: true,
      transient: true,
      defaultValue: false
    }
  ],

  actions: [
    {
      name: 'update',
      isEnabled: function() { return ! this.results.equals(this.master); },
      action: function() {
        console.warn('updating test', this.$UID, this.name, this.master, this.results);
        this.master = this.results;
      }
    }
  ],

  methods: {
    atest: function() {
      // Run SUPER's atest, which returns the unexecuted afunc.
      var sup = this.SUPER();
      // Now we append a last piece that updates regression based on the results.
      return aseq(
        sup,
        function(ret) {
          this.regression = this.hasRun && ! this.results.equals(this.master);
          ret();
        }.bind(this)
      );
    }
  },
  hasFailed: function() {
    return this.regression;
  }
});

MODEL({
  name: 'Relationship',
  tableProperties: [
    'name', 'label', 'relatedModel', 'relatedProperty'
  ],
  properties: [
    {
      name:  'name',
      type:  'String',
      displayWidth: 30,
      displayHeight: 1,
      defaultValueFn: function() { return GLOBAL[this.relatedModel] ? GLOBAL[this.relatedModel].plural : ''; },
      help: 'The coding identifier for the action.'
    },
    {
      name: 'label',
      type: 'String',
      displayWidth: 70,
      displayHeight: 1,
      defaultValueFn: function() { return this.name.labelize(); },
      help: 'The display label for the action.'
    },
    {
      name: 'help',
      label: 'Help Text',
      type: 'String',
      displayWidth: 70,
      displayHeight: 6,
      defaultValue: '',
      help: 'Help text associated with the relationship.'
    },
    {
      name: 'documentation',
      type: 'Documentation',
      view: 'DocModelView',
      help: 'Documentation associated with this entity.',
    },
    {
      name:  'relatedModel',
      type:  'String',
      required: true,
      displayWidth: 30,
      displayHeight: 1,
      defaultValue: '',
      help: 'The name of the related Model.'
    },
    {
      name:  'relatedProperty',
      type:  'String',
      required: true,
      displayWidth: 30,
      displayHeight: 1,
      defaultValue: '',
      help: 'The join property of the related Model.'
    }
  ]/*,
  methods: {
    dao: function() {
      var m = this.X[this.relatedModel];
      return this.X[m.name + 'DAO'];
    },
    JOIN: function(sink, opt_where) {
      var m = this.X[this.relatedModel];
      var dao = this.X[m.name + 'DAO'] || this.X[m.plural];
      return MAP(JOIN(
        dao.where(opt_where || TRUE),
        m.getProperty(this.relatedProperty),
        []), sink);
    }
  }*/
});

MODEL({
  name: 'Issue',
  plural: 'Issues',
  help: 'An issue describes a question, feature request, or defect.',
  ids: [
    'id'
  ],
  tableProperties:
  [
    'id', 'severity', 'status', 'summary', 'assignedTo'
  ],
  properties:
  [
    {
      model_: 'IntProperty',
      name: 'id',
      label: 'Issue ID',
      displayWidth: 12,
      help: 'Issue\'s unique sequence number.'
    },
    {
      name: 'severity',
      view: {
        create: function() { return ChoiceView.create({choices: [
          'Feature',
          'Minor',
          'Major',
          'Question'
        ]});}
      },
      defaultValue: 'String',
      help: 'The severity of the issue.'
    },
    {
      name: 'status',
      type: 'String',
      required: true,
      view: {
        create: function() { return ChoiceView.create({choices: [
          'Open',
          'Accepted',
          'Complete',
          'Closed'
        ]});}
      },
      defaultValue: 'String',
      help: 'The status of the issue.'
    },
    {
      model_: 'Property',
      name: 'summary',
      type: 'String',
      required: true,
      displayWidth: 70,
      displayHeight: 1,
      help: 'A one line summary of the issue.'
    },
    {
      model_: 'Property',
      name: 'created',
      type: 'DateTime',
      required: true,
      displayWidth: 50,
      displayHeight: 1,
      factory: function() { return new Date(); },
      help: 'When this issue was created.'
    },
    {
      model_: 'Property',
      name: 'createdBy',
      type: 'String',
      defaultValue: 'kgr',
      required: true,
      displayWidth: 30,
      displayHeight: 1,
      help: 'Who created the issue.'
    },
    {
      model_: 'Property',
      name: 'assignedTo',
      type: 'String',
      defaultValue: 'kgr',
      displayWidth: 30,
      displayHeight: 1,
      help: 'Who the issue is currently assigned to.'
    },
    {
      model_: 'Property',
      name: 'notes',
      displayWidth: 75,
      displayHeight: 20,
      view: 'TextAreaView',
      help: 'Notes describing issue.'
    }
  ],
  tests: [
    {
      model_: 'UnitTest',
      description: 'test1',
      code: function() {this.addHeader("header");this.ok("pass");this.fail("fail");}
    }
  ]
});

(function() {
  for ( var i = 0 ; i < Model.templates.length ; i++ )
    Model.templates[i] = JSONUtil.mapToObj(Model.templates[i]);

  (function() {
    var a = Model.properties;
    for ( var i = 0 ; i < a.length ; i++ ) {
      if ( ! Property.isInstance(a[i]) ) {
        a[i] = Property.getPrototype().create(a[i]);
      }
    }
  })();
})();
<|endoftext|>


<commit_before>/**
 * The Colors class that manages the colors displayed in the console.
 */
class Colors {

	/**
	 * @typedef {Object} ColorsFormatOptions
	 * @property {(string|string[])} style The style or styles to apply
	 * @property {string} background The format for the background
	 * @property {string} text The format for the text
	 */

	/**
	 * @typedef {Object} ColorsFormatData
	 * @property {string[]} opening The opening format data styles
	 * @property {string[]} closing The closing format data styles
	 * @private
	 */

	/**
	 * Constructs our Colors instance
	 * @param {ColorsFormatOptions} [options = {}] The options for this format
	 * @since 0.4.0
	 */
	constructor(options = {}) {
		const { opening, closing } = this.constructor.text(options.text, this.constructor.background(options.background, this.constructor.style(options.style)));

		/**
		 * The opening tags
		 * @type {string}
		 * @since 0.5.0
		 */
		this.opening = this.constructor.useColors ? `\u001B[${opening.join(';')}m` : '';

		/**
		 * The closing tags
		 * @type {string}
		 * @since 0.5.0
		 */
		this.closing = this.constructor.useColors ? `\u001B[${closing.join(';')}m` : '';
	}

	/**
	 * Format a string
	 * @since 0.4.0
	 * @param {string} string The string to format
	 * @returns {string}
	 */
	format(string) {
		return this.opening + string + this.closing;
	}

	/**
	 * Apply the style
	 * @since 0.5.0
	 * @param {(string|string[])} styles The style or styles to apply
	 * @param {ColorsFormatData} [data={}] The data
	 * @returns {ColorsFormatData}
	 * @private
	 */
	static style(styles, { opening = [], closing = [] } = {}) {
		if (styles) {
			if (!Array.isArray(styles)) styles = [styles];
			for (let style in styles) {
				style = style.toLowerCase();
				if (!(style in this.STYLES)) continue;
				opening.push(this.STYLES[style]);
				closing.push(this.CLOSE[style]);
			}
		}
		return { opening, closing };
	}

	/**
	 * Apply the background
	 * @since 0.5.0
	 * @param {ColorsFormatType} background The background to apply
	 * @param {ColorsFormatData} [data={}] The data
	 * @returns {ColorsFormatData}
	 * @private
	 */
	static background(background, { opening = [], closing = [] } = {}) {
		if (background && background.toLowerCase() in this.BACKGROUNDS) {
			opening.push(Colors.BACKGROUNDS[background.toLowerCase()]);
			closing.push(Colors.CLOSE.background);
		}
		return { opening, closing };
	}

	/**
	 * Apply the text format
	 * @since 0.5.0
	 * @param {ColorsFormatType} text The text format to apply
	 * @param {ColorsFormatData} [data={}] The data
	 * @returns {ColorsFormatData}
	 * @private
	 */
	static text(text, { opening = [], closing = [] } = {}) {
		if (text && text.toLowerCase() in this.TEXTS) {
			opening.push(Colors.TEXTS[text.toLowerCase()]);
			closing.push(Colors.CLOSE.text);
		}
		return { opening, closing };
	}

}

/**
 * Determines if this class should be constructed with colors or not
 * @type {?boolean}
 * @static
 * @private
 */
Colors.useColors = null;

/**
 * The close codes
 * @type {object}
 * @static
 * @private
 */
Colors.CLOSE = {
	normal: 0,
	bold: 22,
	dim: 22,
	italic: 23,
	underline: 24,
	inverse: 27,
	hidden: 28,
	strikethrough: 29,
	text: 39,
	background: 49
};

/**
 * The style codes
 * @type {object}
 * @static
 * @private
 */
Colors.STYLES = {
	normal: 0,
	bold: 1,
	dim: 2,
	italic: 3,
	underline: 4,
	inverse: 7,
	hidden: 8,
	strikethrough: 9
};

/**
 * The text codes
 * @type {object}
 * @static
 * @private
 */
Colors.TEXTS = {
	black: 30,
	red: 31,
	green: 32,
	yellow: 33,
	blue: 34,
	magenta: 35,
	cyan: 36,
	lightgray: 37,
	lightgrey: 37,
	gray: 90,
	grey: 90,
	lightred: 91,
	lightgreen: 92,
	lightyellow: 93,
	lightblue: 94,
	lightmagenta: 95,
	lightcyan: 96,
	white: 97
};

/**
 * The background codes
 * @type {object}
 * @static
 * @private
 */
Colors.BACKGROUNDS = {
	black: 40,
	red: 41,
	green: 42,
	yellow: 43,
	blue: 44,
	magenta: 45,
	cyan: 46,
	gray: 47,
	grey: 47,
	lightgray: 100,
	lightgrey: 100,
	lightred: 101,
	lightgreen: 102,
	lightyellow: 103,
	lightblue: 104,
	lightmagenta: 105,
	lightcyan: 106,
	white: 107
};

module.exports = Colors;
<commit_msg>misc: Remove for-in loop in Colors.style (#466)<commit_after>/**
 * The Colors class that manages the colors displayed in the console.
 */
class Colors {

	/**
	 * @typedef {Object} ColorsFormatOptions
	 * @property {(string|string[])} style The style or styles to apply
	 * @property {string} background The format for the background
	 * @property {string} text The format for the text
	 */

	/**
	 * @typedef {Object} ColorsFormatData
	 * @property {string[]} opening The opening format data styles
	 * @property {string[]} closing The closing format data styles
	 * @private
	 */

	/**
	 * Constructs our Colors instance
	 * @param {ColorsFormatOptions} [options = {}] The options for this format
	 * @since 0.4.0
	 */
	constructor(options = {}) {
		const { opening, closing } = this.constructor.text(options.text, this.constructor.background(options.background, this.constructor.style(options.style)));

		/**
		 * The opening tags
		 * @type {string}
		 * @since 0.5.0
		 */
		this.opening = this.constructor.useColors ? `\u001B[${opening.join(';')}m` : '';

		/**
		 * The closing tags
		 * @type {string}
		 * @since 0.5.0
		 */
		this.closing = this.constructor.useColors ? `\u001B[${closing.join(';')}m` : '';
	}

	/**
	 * Format a string
	 * @since 0.4.0
	 * @param {string} string The string to format
	 * @returns {string}
	 */
	format(string) {
		return this.opening + string + this.closing;
	}

	/**
	 * Apply the style
	 * @since 0.5.0
	 * @param {(string|string[])} styles The style or styles to apply
	 * @param {ColorsFormatData} [data={}] The data
	 * @returns {ColorsFormatData}
	 * @private
	 */
	static style(styles, { opening = [], closing = [] } = {}) {
		if (styles) {
			if (!Array.isArray(styles)) styles = [styles];
			for (let style of styles) {
				style = style.toLowerCase();
				if (!(style in this.STYLES)) continue;
				opening.push(this.STYLES[style]);
				closing.push(this.CLOSE[style]);
			}
		}
		return { opening, closing };
	}

	/**
	 * Apply the background
	 * @since 0.5.0
	 * @param {ColorsFormatType} background The background to apply
	 * @param {ColorsFormatData} [data={}] The data
	 * @returns {ColorsFormatData}
	 * @private
	 */
	static background(background, { opening = [], closing = [] } = {}) {
		if (background && background.toLowerCase() in this.BACKGROUNDS) {
			opening.push(Colors.BACKGROUNDS[background.toLowerCase()]);
			closing.push(Colors.CLOSE.background);
		}
		return { opening, closing };
	}

	/**
	 * Apply the text format
	 * @since 0.5.0
	 * @param {ColorsFormatType} text The text format to apply
	 * @param {ColorsFormatData} [data={}] The data
	 * @returns {ColorsFormatData}
	 * @private
	 */
	static text(text, { opening = [], closing = [] } = {}) {
		if (text && text.toLowerCase() in this.TEXTS) {
			opening.push(Colors.TEXTS[text.toLowerCase()]);
			closing.push(Colors.CLOSE.text);
		}
		return { opening, closing };
	}

}

/**
 * Determines if this class should be constructed with colors or not
 * @type {?boolean}
 * @static
 * @private
 */
Colors.useColors = null;

/**
 * The close codes
 * @type {object}
 * @static
 * @private
 */
Colors.CLOSE = {
	normal: 0,
	bold: 22,
	dim: 22,
	italic: 23,
	underline: 24,
	inverse: 27,
	hidden: 28,
	strikethrough: 29,
	text: 39,
	background: 49
};

/**
 * The style codes
 * @type {object}
 * @static
 * @private
 */
Colors.STYLES = {
	normal: 0,
	bold: 1,
	dim: 2,
	italic: 3,
	underline: 4,
	inverse: 7,
	hidden: 8,
	strikethrough: 9
};

/**
 * The text codes
 * @type {object}
 * @static
 * @private
 */
Colors.TEXTS = {
	black: 30,
	red: 31,
	green: 32,
	yellow: 33,
	blue: 34,
	magenta: 35,
	cyan: 36,
	lightgray: 37,
	lightgrey: 37,
	gray: 90,
	grey: 90,
	lightred: 91,
	lightgreen: 92,
	lightyellow: 93,
	lightblue: 94,
	lightmagenta: 95,
	lightcyan: 96,
	white: 97
};

/**
 * The background codes
 * @type {object}
 * @static
 * @private
 */
Colors.BACKGROUNDS = {
	black: 40,
	red: 41,
	green: 42,
	yellow: 43,
	blue: 44,
	magenta: 45,
	cyan: 46,
	gray: 47,
	grey: 47,
	lightgray: 100,
	lightgrey: 100,
	lightred: 101,
	lightgreen: 102,
	lightyellow: 103,
	lightblue: 104,
	lightmagenta: 105,
	lightcyan: 106,
	white: 107
};

module.exports = Colors;
<|endoftext|>


<commit_before>// Copyright 2012 Rodney Muras.  All Rights Reserved.

var util = require('util'),
    uuid = require('node-uuid');

var Ebb;

(function() {

    var Flow = (function() {
        function Flow()
    })();

    var _trace = function(state, step) {
        if (state.trace) {
            console.log( "~~%s~~%s~~", state.name, step.name );

            if ( state.inspect)
                console.log('context: %s\r\n', util.inspect(state.context));
        }
    };

    var _join = function(_state, cb) {

      // push a no-op to stall the stack until cb fires
      _state.stack.unshift( {fn: function() {}, noop: true, name: "noop" });

      return function() {
          try {
            // bypass callback during fault
            if (!_state.fault) {
                cb.apply(_state.context, arguments);
                if (_state.automatic)
                    process.nextTick( _state.continue ); // advance the flow
            }
            else if (_state.trace) {
                console.log( "~~%s~~anon__ - ABORTED!", _state.name );
            }

          }
          catch(e) {
            _state.throw(e);
          }
      }
    }

    var _throw = function(_state, e) {
        _state.error = e;
        _state.fault = true;

        if (_state.catch) {
            if (_globalOpts.debug)
                _trace(_state, {name: 'catch'});

            _state.catch.call(_state.context, _state, e);
        }

        // how is ths getting called after _cathc??????????????
        //_finally(_state);
    }

    var _finally = function(_state) {
        if (_state.finally) {
            if (_globalOpts.debug)
                _trace(_state, {name: 'finally'});

            _state.finally.call(_state.context, _state);
        }

        // restart the parent flow
        if (_state.parentFlow)
            _state.parentFlow.continue();
    }

    var _next = function(_state) {
        var step = _state.stack.shift();  //next step
        if(step) {
            _state.continue = function() { _next(_state); }
            _state.throw = function(e) { _throw(_state, e); }
            _state.join = function(_cb) { return _join(_state, _cb); }

            if (_globalOpts.debug & !step.noop)
                _trace(_state, step);

            try {

                // call the current step
                step.fn.call(_state.context, _state);

                // schedule the next step unless we we hooked a callback
                if (_state.automatic & !step.noop)
                    process.nextTick( _state.continue );

                return; //to continue
            }
            catch(e) {
                _throw.call(_state.context, _state, e);
            }
        }
        _finally.call(_state.context, _state);
    }

    module.exports = {
        flow: function(context, steps) {
            //if context is another flow, we chain them
            // inserta noop into our parents stack to stall it our
            // and setup a post finally step in our stack (or func)
            // to call our parents continue when we are done

            var _state = {
                stack: Array()
              , steps: steps
              , automatic: true
              , name: 'flow'
              , trace: false
              , inspect: false
              , context: context
              , traceId: uuid.v1()
              , catch: undefined
              , finally: undefined
              , join: undefined
              , parentFlow: null
            };

            for( var step in steps)
                if (step == 'catch')
                    _state.catch = steps[step];
                else if (step == 'finally')
                    _state.finally = steps[step];
                else
                    _state.stack.push({fn: steps[step], name: step});

            //start the flow on next tick to alloc chained trace optoins to run!
            process.nextTick( function(){_next(_state);});

            return (function(){
                var _methods = {
                    trace: function(options) {
                        _state.trace = true;
                        _state.name = options.name;
                        _state.inspect = options.inspect;
                        return _methods;
                    }.bind(this)
                  , join: function(parentFlow) {
                        _state.parentFlow = parentFLow;
                        parentFlow.stack.unshift( {fn: function() {}, noop: true, name: "noop" });
                        return _methods;
                    }.bind(this)
                }

                return _methods;
            }).bind(this)();
        }

        // module level options
      , configure: function(options) {
                _globalOpts = options;
            }.bind(this)
    }

})(Ebb || (Ebb = {}));

<commit_msg>updated package.json<commit_after><|endoftext|>


<commit_before>import assert from 'power-assert';
import sinon from 'sinon';
import proxyquire from 'proxyquire';

const mock = {
  angular: {
    bootstrap: () => {},
    version: {full: '1.3.14', major: 1, minor: 3, dot: 14}
  },
  document: {
    querySelector: () => {}
  }
};

const stub = {
  angular: {
    bootstrap: sinon.stub(mock.angular, 'bootstrap')
  },
  document: {
    querySelector: sinon.stub(mock.document, 'querySelector').returns('element')
  }
};

const t = proxyquire('../../src/toccata', {
  './v1/v1': proxyquire('../../src/v1/v1', {
    '../browser-dependencies': {
      document: mock.document
    }
  })
});

const toccata = t.toccata(mock.angular);
const uuidRegExp = /[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}/;

describe('Toccata V1', () => {
  describe('props', () => {
    it('should NOT be had "For"', () => {
      assert(!toccata.For);
    });

    it('should be set uuid', () => {
      const actual = uuidRegExp.test(toccata._uuid);
      assert(actual === true);
    });
  });

  describe('bootstrap', () => {
    it('should be set a function', () => {
      const actual = toccata.bootstrap;
      assert(typeof actual === 'function');
    });

    context('when "component" is not', () => {
      it('should be thrown an exception', () => {
        assert.throws(() => toccata.bootstrap(), /bootstrap requires the component constructor/);
      });
    });

    context('when "requires" is not', () => {
      let component;
      beforeEach(() => {
        component = {};
        component._toccataSelectorCache = 'selector';
      });

      it('should be given an element to bootstrap arg[0]', () => {
        toccata.bootstrap(component);
        assert(stub.angular.bootstrap.getCall(0).args[0] === 'element');
      });

      it('should be given an array contains uuid to bootstrap arg[1]', () => {
        toccata.bootstrap(component);
        const actual = stub.angular.bootstrap.getCall(0).args[1];
        assert(uuidRegExp.test(actual[0]));
      });
    });
  });
});<commit_msg>test(v1): add tests<commit_after>import assert from 'power-assert';
import sinon from 'sinon';
import proxyquire from 'proxyquire';

const mock = {
  angular: {
    bootstrap: () => {},
    version: {full: '1.3.14', major: 1, minor: 3, dot: 14}
  },
  document: {
    querySelector: () => {}
  }
};

const stub = {
  angular: {
    bootstrap: sinon.stub(mock.angular, 'bootstrap')
  },
  document: {
    querySelector: sinon.stub(mock.document, 'querySelector').returns('element')
  }
};

const t = proxyquire('../../src/toccata', {
  './v1/v1': proxyquire('../../src/v1/v1', {
    '../browser-dependencies': {
      document: mock.document
    }
  })
});

const toccata = t.toccata(mock.angular);
const uuidRegExp = /[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}/;

describe('Toccata V1', () => {
  beforeEach(() => {
    Object.keys(stub).forEach(stubName => {
      Object.keys(stub[stubName]).forEach(method => stub[stubName][method].reset());
    });
  });

  describe('props', () => {
    it('should NOT be had "For"', () => {
      assert(!toccata.For);
    });

    it('should be set uuid', () => {
      const actual = uuidRegExp.test(toccata._uuid);
      assert(actual === true);
    });
  });

  describe('bootstrap', () => {
    let component;
    beforeEach(() => {
      component = {};
      component._toccataSelectorCache = 'selector';
    });

    it('should be set a function', () => {
      const actual = toccata.bootstrap;
      assert(typeof actual === 'function');
    });

    context('when "component" is not', () => {
      it('should be thrown an exception', () => {
        assert.throws(() => toccata.bootstrap(), /bootstrap requires the component constructor/);
      });
    });

    context('when "_toccataSelectorCache" is not', () => {
      beforeEach(() => {
        delete component._toccataSelectorCache;
      });

      it('should be thrown an exception', () => {
        assert.throws(() => toccata.bootstrap(component), /bootstrap requires the selector of component/);
      });
    });

    context('when "requires" is not', () => {
      it('should be given an element to bootstrap arg[0]', () => {
        toccata.bootstrap(component);
        assert(stub.angular.bootstrap.getCall(0).args[0] === 'element');
      });

      it('should be given an array contains uuid to bootstrap arg[1]', () => {
        toccata.bootstrap(component);
        const actual = stub.angular.bootstrap.getCall(0).args[1];
        assert(uuidRegExp.test(actual[0]));
      });
    });

    context('when "requires" is exists', () => {
      let requires;
      beforeEach(() => {
        requires = ['some-module'];
      });

      it('should be given an array contains module to bootstrap arg[1]', () => {
        toccata.bootstrap(component, requires);
        const actual = stub.angular.bootstrap.getCall(0).args[1];
        assert(actual[0] === 'some-module');
      });

      it('should be given an array contains uuid to bootstrap arg[1]', () => {
        toccata.bootstrap(component, requires);
        const actual = stub.angular.bootstrap.getCall(0).args[1];
        assert(uuidRegExp.test(actual[1]));
      });
    });
  });
});<|endoftext|>


<commit_before>/**
 Copyright 2014 Gordon Williams (gw@pur3.co.uk)

 This Source Code is subject to the terms of the Mozilla Public
 License, v2.0. If a copy of the MPL was not distributed with this
 file, You can obtain one at http://mozilla.org/MPL/2.0/.

 ------------------------------------------------------------------
  Automatically load any referenced modules
 ------------------------------------------------------------------
**/
"use strict";
(function(){

  function init() {
    Espruino.Core.Config.add("MODULE_URL", {
      section : "Communications",
      name : "Module URL",
      description : "Where to search online for modules when `require()` is used",
      type : "string",
      defaultValue : "https://www.espruino.com/modules"
    });
    Espruino.Core.Config.add("MODULE_EXTENSIONS", {
      section : "Communications",
      name : "Module Extensions",
      description : "The file extensions to use for each module. These are checked in order and the first that exists is used. One or more file extensions (including the dot) separated by `|`",
      type : "string",
      defaultValue : ".min.js|.js"
    });
    Espruino.Core.Config.add("MODULE_AS_FUNCTION", {
      section : "Communications",
      name : "Modules uploaded as functions (BETA)",
      description : "Espruino 1v90 and later ONLY. Upload modules as Functions, allowing any functions inside them to be loaded directly from flash when 'Save on Send' is enabled.",
      type : "boolean",
      defaultValue : false
    });

    Espruino.Core.Config.add("MODULE_PROXY_ENABLED", {
      section : "Communications",
      name : "Enable Proxy",
      description : "Enable Proxy for loading the modules when `require()` is used (only in native IDE)",
      type : "boolean",
      defaultValue : false
    });

    Espruino.Core.Config.add("MODULE_PROXY_URL", {
      section : "Communications",
      name : "Proxy URL",
      description : "Proxy URL for loading the modules when `require()` is used (only in native IDE)",
      type : "string",
      defaultValue : ""
    });

    Espruino.Core.Config.add("MODULE_PROXY_PORT", {
      section : "Communications",
      name : "Proxy Port",
      description : "Proxy Port for loading the modules when `require()` is used (only in native IDE)",
      type : "string",
      defaultValue : ""
    });

    // When code is sent to Espruino, search it for modules and add extra code required to load them
    Espruino.addProcessor("transformForEspruino", function(code, callback) {
      if (Espruino.Config.ROLLUP) {
        return loadModulesRollup(code, callback);
      }
      loadModules(code, callback);
    });

    // Append the 'getModule' processor as the last (plugins get initialized after Espruino.Core modules)
    Espruino.Plugins.CoreModules = {
      init: function() {
        Espruino.addProcessor("getModule", function(data, callback) {
          if (data.moduleCode!==undefined) { // already provided be previous getModule processor
            return callback(data);
          }

          fetchGetModule(data, callback);
        });
      }
    };
  }

  function isBuiltIn(module) {
    var d = Espruino.Core.Env.getData();
    // If we got data from the device itself, use that as the
    // definitive answer
    if ("string" == typeof d.MODULES)
      return d.MODULES.split(",").indexOf(module)>=0;
    // Otherwise try and figure it out from JSON
    if ("info" in d &&
        "builtin_modules" in d.info &&
        d.info.builtin_modules.indexOf(module)>=0)
      return true;
    // Otherwise assume we don't have it
    return false;
  }

  /** Find any instances of require(...) in the code string and return a list */
  var getModulesRequired = function(code) {
    var modules = [];

    var lex = Espruino.Core.Utils.getLexer(code);
    var tok = lex.next();
    var state = 0;
    while (tok!==undefined) {
      if (state==0 && tok.str=="require") {
        state=1;
      } else if (state==1 && tok.str=="(") {
        state=2;
      } else if (state==2 && (tok.type=="STRING")) {
        state=0;
        var module = tok.value;
        if (!isBuiltIn(module) && modules.indexOf(module)<0)
          modules.push(module);
      } else
        state = 0;
      tok = lex.next();
    }

    return modules;
  };

  /** Download modules from MODULE_URL/.. */
  function fetchGetModule(data, callback) {
    var fullModuleName = data.moduleName;

    // try and load the module the old way...
    console.log("loadModule("+fullModuleName+")");

    var urls = []; // Array of where to look for this module
    var modName; // Simple name of the module
    if(Espruino.Core.Utils.isURL(fullModuleName)) {
      modName = fullModuleName.substr(fullModuleName.lastIndexOf("/") + 1).split(".")[0];
      urls = [ fullModuleName ];
    } else {
      modName = fullModuleName;
      Espruino.Config.MODULE_URL.split("|").forEach(function (url) {
        url = url.trim();
        if (url.length!=0)
        Espruino.Config.MODULE_EXTENSIONS.split("|").forEach(function (extension) {
          urls.push(url + "/" + fullModuleName + extension);
        })
      });
    };

    // Recursively go through all the urls
    (function download(urls) {
      if (urls.length==0) {
        return callback(data);
      }
      var dlUrl = urls[0];
      Espruino.Core.Utils.getURL(dlUrl, function (code) {
        if (code!==undefined) {
          // we got it!
          data.moduleCode = code;
          data.isMinified = dlUrl.substr(-7)==".min.js";
          return callback(data);
        } else {
          // else try next
          download(urls.slice(1));
        }
      });
    })(urls);
  }

  /** Called from loadModule when a module is loaded. Parse it for other modules it might use
   *  and resolve dfd after all submodules have been loaded */
  function moduleLoaded(resolve, requires, modName, data, loadedModuleData, alreadyMinified){
    // Check for any modules used from this module that we don't already have
    var newRequires = getModulesRequired(data);
    console.log(" - "+modName+" requires "+JSON.stringify(newRequires));
    // if we need new modules, set them to load and get their promises
    var newPromises = [];
    for (var i in newRequires) {
      if (requires.indexOf(newRequires[i])<0) {
        console.log("   Queueing "+newRequires[i]);
        requires.push(newRequires[i]);
        newPromises.push(loadModule(requires, newRequires[i], loadedModuleData));
      } else {
        console.log("   Already loading "+newRequires[i]);
      }
    }

    var loadProcessedModule = function (module) {
      // add the module to the beginning of our array
      if (Espruino.Config.MODULE_AS_FUNCTION)
        loadedModuleData.unshift("Modules.addCached(" + JSON.stringify(module.name) + ",function(){" + module.code + "});");
      else
        loadedModuleData.unshift("Modules.addCached(" + JSON.stringify(module.name) + "," + JSON.stringify(module.code) + ");");
      // if we needed to load something, wait until we have all promises complete before resolving our promise!
      Promise.all(newPromises).then(function(){ resolve(); });
    }
    if (alreadyMinified)
      loadProcessedModule({code:data,name:modName});
    else
      Espruino.callProcessor("transformModuleForEspruino", {code:data,name:modName}, loadProcessedModule);
  }

  /** Given a module name (which could be a URL), try and find it. Return
   * a deferred thingybob which signals when we're done. */
  function loadModule(requires, fullModuleName, loadedModuleData) {
    return new Promise(function(resolve, reject) {
      // First off, try and find this module using callProcessor
      Espruino.callProcessor("getModule",
        { moduleName:fullModuleName, moduleCode:undefined, isMinified:false },
        function(data) {
          if (data.moduleCode===undefined) {
            Espruino.Core.Notifications.warning("Module "+fullModuleName+" not found");
            return resolve();
          }

          // great! it found something. Use it.
          moduleLoaded(resolve, requires, fullModuleName, data.moduleCode, loadedModuleData, data.isMinified);
        });
    });
  }

  /** Finds instances of 'require' and then ensures that
   those modules are loaded into the module cache beforehand
   (by inserting the relevant 'addCached' commands into 'code' */
  function loadModules(code, callback){
    var loadedModuleData = [];
    var requires = getModulesRequired(code);
    if (requires.length == 0) {
      // no modules needed - just return
      callback(code);
    } else {
      Espruino.Core.Status.setStatus("Loading modules");
      // Kick off the module loading (each returns a promise)
      var promises = requires.map(function (moduleName) {
        return loadModule(requires, moduleName, loadedModuleData);
      });
      // When all promises are complete
      Promise.all(promises).then(function(){
        callback(loadedModuleData.join("\n") + "\n" + code);
      });
    }
  }

  function loadModulesRollup(code, callback) {
    rollupTools.loadModulesRollup(code)
      .then(generated => {
        const minified = generated.code;
        console.log('rollup: '+minified.length+' bytes');

        // FIXME: needs warnings?
        Espruino.Core.Notifications.info('Rollup no errors. Bundling ' + code.length + ' bytes to ' + minified.length + ' bytes');
        callback(minified);
      })
      .catch(err => {
        console.log('rollup:error', err);
        Espruino.Core.Notifications.error("Rollup errors - Bundling failed: " + String(err).trim());
        callback(code);
      });
  }

  Espruino.Core.Modules = {
    init : init
  };
}());
<commit_msg>Fix Module dependancies can be in the wrong order #110<commit_after>/**
 Copyright 2014 Gordon Williams (gw@pur3.co.uk)

 This Source Code is subject to the terms of the Mozilla Public
 License, v2.0. If a copy of the MPL was not distributed with this
 file, You can obtain one at http://mozilla.org/MPL/2.0/.

 ------------------------------------------------------------------
  Automatically load any referenced modules
 ------------------------------------------------------------------
**/
"use strict";
(function(){

  function init() {
    Espruino.Core.Config.add("MODULE_URL", {
      section : "Communications",
      name : "Module URL",
      description : "Where to search online for modules when `require()` is used",
      type : "string",
      defaultValue : "https://www.espruino.com/modules"
    });
    Espruino.Core.Config.add("MODULE_EXTENSIONS", {
      section : "Communications",
      name : "Module Extensions",
      description : "The file extensions to use for each module. These are checked in order and the first that exists is used. One or more file extensions (including the dot) separated by `|`",
      type : "string",
      defaultValue : ".min.js|.js"
    });
    Espruino.Core.Config.add("MODULE_AS_FUNCTION", {
      section : "Communications",
      name : "Modules uploaded as functions (BETA)",
      description : "Espruino 1v90 and later ONLY. Upload modules as Functions, allowing any functions inside them to be loaded directly from flash when 'Save on Send' is enabled.",
      type : "boolean",
      defaultValue : false
    });

    Espruino.Core.Config.add("MODULE_PROXY_ENABLED", {
      section : "Communications",
      name : "Enable Proxy",
      description : "Enable Proxy for loading the modules when `require()` is used (only in native IDE)",
      type : "boolean",
      defaultValue : false
    });

    Espruino.Core.Config.add("MODULE_PROXY_URL", {
      section : "Communications",
      name : "Proxy URL",
      description : "Proxy URL for loading the modules when `require()` is used (only in native IDE)",
      type : "string",
      defaultValue : ""
    });

    Espruino.Core.Config.add("MODULE_PROXY_PORT", {
      section : "Communications",
      name : "Proxy Port",
      description : "Proxy Port for loading the modules when `require()` is used (only in native IDE)",
      type : "string",
      defaultValue : ""
    });

    // When code is sent to Espruino, search it for modules and add extra code required to load them
    Espruino.addProcessor("transformForEspruino", function(code, callback) {
      if (Espruino.Config.ROLLUP) {
        return loadModulesRollup(code, callback);
      }
      loadModules(code, callback);
    });

    // Append the 'getModule' processor as the last (plugins get initialized after Espruino.Core modules)
    Espruino.Plugins.CoreModules = {
      init: function() {
        Espruino.addProcessor("getModule", function(data, callback) {
          if (data.moduleCode!==undefined) { // already provided be previous getModule processor
            return callback(data);
          }

          fetchGetModule(data, callback);
        });
      }
    };
  }

  function isBuiltIn(module) {
    var d = Espruino.Core.Env.getData();
    // If we got data from the device itself, use that as the
    // definitive answer
    if ("string" == typeof d.MODULES)
      return d.MODULES.split(",").indexOf(module)>=0;
    // Otherwise try and figure it out from JSON
    if ("info" in d &&
        "builtin_modules" in d.info &&
        d.info.builtin_modules.indexOf(module)>=0)
      return true;
    // Otherwise assume we don't have it
    return false;
  }

  /** Find any instances of require(...) in the code string and return a list */
  var getModulesRequired = function(code) {
    var modules = [];

    var lex = Espruino.Core.Utils.getLexer(code);
    var tok = lex.next();
    var state = 0;
    while (tok!==undefined) {
      if (state==0 && tok.str=="require") {
        state=1;
      } else if (state==1 && tok.str=="(") {
        state=2;
      } else if (state==2 && (tok.type=="STRING")) {
        state=0;
        var module = tok.value;
        if (!isBuiltIn(module) && modules.indexOf(module)<0)
          modules.push(module);
      } else
        state = 0;
      tok = lex.next();
    }

    return modules;
  };

  /** Download modules from MODULE_URL/.. */
  function fetchGetModule(data, callback) {
    var fullModuleName = data.moduleName;

    // try and load the module the old way...
    console.log("loadModule("+fullModuleName+")");

    var urls = []; // Array of where to look for this module
    var modName; // Simple name of the module
    if(Espruino.Core.Utils.isURL(fullModuleName)) {
      modName = fullModuleName.substr(fullModuleName.lastIndexOf("/") + 1).split(".")[0];
      urls = [ fullModuleName ];
    } else {
      modName = fullModuleName;
      Espruino.Config.MODULE_URL.split("|").forEach(function (url) {
        url = url.trim();
        if (url.length!=0)
        Espruino.Config.MODULE_EXTENSIONS.split("|").forEach(function (extension) {
          urls.push(url + "/" + fullModuleName + extension);
        })
      });
    };

    // Recursively go through all the urls
    (function download(urls) {
      if (urls.length==0) {
        return callback(data);
      }
      var dlUrl = urls[0];
      Espruino.Core.Utils.getURL(dlUrl, function (code) {
        if (code!==undefined) {
          // we got it!
          data.moduleCode = code;
          data.isMinified = dlUrl.substr(-7)==".min.js";
          return callback(data);
        } else {
          // else try next
          download(urls.slice(1));
        }
      });
    })(urls);
  }

  /** Called from loadModule when a module is loaded. Parse it for other modules it might use
   *  and resolve dfd after all submodules have been loaded */
  function moduleLoaded(resolve, requires, modName, data, loadedModuleData, alreadyMinified){
    // Check for any modules used from this module that we don't already have
    var newRequires = getModulesRequired(data);
    console.log(" - "+modName+" requires "+JSON.stringify(newRequires));
    // if we need new modules, set them to load and get their promises
    var newPromises = [];
    for (var i in newRequires) {
      if (requires.indexOf(newRequires[i])<0) {
        console.log("   Queueing "+newRequires[i]);
        requires.push(newRequires[i]);
        newPromises.push(loadModule(requires, newRequires[i], loadedModuleData));
      } else {
        console.log("   Already loading "+newRequires[i]);
      }
    }

    var loadProcessedModule = function (module) {
      // if we needed to load something, wait until it's loaded before resolving this
      Promise.all(newPromises).then(function(){
        // add the module to end of our array
        if (Espruino.Config.MODULE_AS_FUNCTION)
          loadedModuleData.push("Modules.addCached(" + JSON.stringify(module.name) + ",function(){" + module.code + "});");
        else
          loadedModuleData.push("Modules.addCached(" + JSON.stringify(module.name) + "," + JSON.stringify(module.code) + ");");
        // We're done
        resolve();
      });
    }
    if (alreadyMinified)
      loadProcessedModule({code:data,name:modName});
    else
      Espruino.callProcessor("transformModuleForEspruino", {code:data,name:modName}, loadProcessedModule);
  }

  /** Given a module name (which could be a URL), try and find it. Return
   * a deferred thingybob which signals when we're done. */
  function loadModule(requires, fullModuleName, loadedModuleData) {
    return new Promise(function(resolve, reject) {
      // First off, try and find this module using callProcessor
      Espruino.callProcessor("getModule",
        { moduleName:fullModuleName, moduleCode:undefined, isMinified:false },
        function(data) {
          if (data.moduleCode===undefined) {
            Espruino.Core.Notifications.warning("Module "+fullModuleName+" not found");
            return resolve();
          }

          // great! it found something. Use it.
          moduleLoaded(resolve, requires, fullModuleName, data.moduleCode, loadedModuleData, data.isMinified);
        });
    });
  }

  /** Finds instances of 'require' and then ensures that
   those modules are loaded into the module cache beforehand
   (by inserting the relevant 'addCached' commands into 'code' */
  function loadModules(code, callback){
    var loadedModuleData = [];
    var requires = getModulesRequired(code);
    if (requires.length == 0) {
      // no modules needed - just return
      callback(code);
    } else {
      Espruino.Core.Status.setStatus("Loading modules");
      // Kick off the module loading (each returns a promise)
      var promises = requires.map(function (moduleName) {
        return loadModule(requires, moduleName, loadedModuleData);
      });
      // When all promises are complete
      Promise.all(promises).then(function(){
        callback(loadedModuleData.join("\n") + "\n" + code);
      });
    }
  }

  function loadModulesRollup(code, callback) {
    rollupTools.loadModulesRollup(code)
      .then(generated => {
        const minified = generated.code;
        console.log('rollup: '+minified.length+' bytes');

        // FIXME: needs warnings?
        Espruino.Core.Notifications.info('Rollup no errors. Bundling ' + code.length + ' bytes to ' + minified.length + ' bytes');
        callback(minified);
      })
      .catch(err => {
        console.log('rollup:error', err);
        Espruino.Core.Notifications.error("Rollup errors - Bundling failed: " + String(err).trim());
        callback(code);
      });
  }

  Espruino.Core.Modules = {
    init : init
  };
}());
<|endoftext|>


<commit_before>// the url map for game-related actions.

var Jack = require("jack"), HashP = require("hashp").HashP;

var map = {};

var couch = require("./couch").CouchDB("http://localhost:5984/jic-jack-jo");

var emptyGame = {
    x : null,
    o : null,
    state : [["","",""],["","",""],["","","x"]],
    turn : "x",
    win : null
};

function gameURL (env, game_id) {
    return env["jsgi.url_scheme"]+"://"+env.HTTP_HOST+env.SCRIPT_NAME+"/"+game_id;
}

function sitDown (user) {
    // see if there is an empty seat.
    var seat = couch.GET("_design/jic-jack-jo/_view/open_seat").rows[0].value;
    var game;
    if (seat[0] === "open") {
        game = couch.GET(seat[1]);
        // make sure i'm not sitting there already...
        if (game) {
            if (game.x !== user && game.o !== user) {
                // sit down
                game[ seat[2] ] = user;
                if (couch.PUT(seat[1], game, true).status !== 201) return sitDown(user);
                return game;
            }
            // already sitting.
            return game;
        }
    }
    // either the seats are all full, or the GET failed.
    game = emptyGame;
    game.x = user;
    game._id = seat[1];
    if (!couch.PUT(seat[1], game, true)) return sitDown(user);
    return game;
}
map["/new"] = require("./allowed-methods").AllowedMethods(function (env) {
    var game = sitDown(env["jjj.user"]),
        location = gameURL(env, game._id);
    return {
        status : 302,
        headers : {
            "Content-Type":"text/plain",
            "Location":location
        },
        body : [JSON.stringify({"error":"Found", "location":location})]
    };
}, ["POST", "PUT", "HEAD", "GET"]);

map["/move"] = require("./move").app;

map["/"] = function (env) {

    var id = env.PATH_INFO.replace(/^.*?(game-[0-9]+).*$/g, '$1'),
        game = couch.GET(id);

    if (!game) return {
        status : 404,
        headers : {"Content-Type":"application/json"},
        body : [JSON.stringify({error:"not_found"})]
    }

    // make sure that it's a proper url.
    var cannonical = "/" + game._id,
        location = gameURL(env, game._id);
    if (env.PATH_INFO !== cannonical) return {
        status : 301,
        headers : {
            "Content-Type":"application/json",
            "Location" : location
        },
        body : [JSON.stringify({"error":"Moved Permanently","location":location})]
    };

    game.you = env["jjj.user"];

    return {
        status :200,
        headers : {"Content-Type":"application/json"},
        body : [JSON.stringify(game)]
    };
};

exports.app = Jack.URLMap(map);<commit_msg>Mostly cosmetic. Also: Oops.  Had an "x" in the allegedly empty game state.<commit_after>// the url map for game-related actions.

var Jack = require("jack"),
    HashP = require("hashp").HashP,
    map = {},
    couch = require("./couch").CouchDB("http://localhost:5984/jic-jack-jo"),
    emptyGame = {
        x : null,
        o : null,
        state : [["","",""],["","",""],["","",""]],
        turn : "x",
        win : null
    };

function gameURL (env, game_id) {
    return env["jsgi.url_scheme"]+"://"+env.HTTP_HOST+env.SCRIPT_NAME+"/"+game_id;
};

function sitDown (user) {
    // see if there is an empty seat.
    var seat = couch.GET("_design/jic-jack-jo/_view/open_seat").rows[0].value;
    var game;
    if (seat[0] === "open") {
        game = couch.GET(seat[1]);
        // make sure i'm not sitting there already...
        if (game) {
            if (game.x !== user && game.o !== user) {
                // sit down
                game[ seat[2] ] = user;
                if (couch.PUT(seat[1], game, true).status !== 201) return sitDown(user);
                return game;
            }
            // already sitting.
            return game;
        }
    }
    // either the seats are all full, or the GET failed.
    game = emptyGame;
    game.x = user;
    game._id = seat[1];
    if (!couch.PUT(seat[1], game, true)) return sitDown(user);
    return game;
};
map["/new"] = require("./allowed-methods").AllowedMethods(function (env) {
    var game = sitDown(env["jjj.user"]),
        location = gameURL(env, game._id);
    return {
        status : 302,
        headers : {
            "Content-Type":"text/plain",
            "Location":location
        },
        body : [JSON.stringify({"error":"Found", "location":location})]
    };
}, ["POST", "PUT", "HEAD", "GET"]);

map["/move"] = require("./move").app;

map["/"] = function (env) {

    var id = env.PATH_INFO.replace(/^.*?(game-[0-9]+).*$/g, '$1'),
        game = couch.GET(id);

    if (!game) return {
        status : 404,
        headers : {"Content-Type":"application/json"},
        body : [JSON.stringify({error:"not_found"})]
    }

    // make sure that it's a proper url.
    var cannonical = "/" + game._id,
        location = gameURL(env, game._id);
    if (env.PATH_INFO !== cannonical) return {
        status : 301,
        headers : {
            "Content-Type":"application/json",
            "Location" : location
        },
        body : [JSON.stringify({"error":"Moved Permanently","location":location})]
    };

    game.you = env["jjj.user"];

    return {
        status :200,
        headers : {"Content-Type":"application/json"},
        body : [JSON.stringify(game)]
    };
};

exports.app = Jack.URLMap(map);<|endoftext|>