<commit_before>#!/usr/bin/env node

/**
 * build svg icon
 * @author Allenice
 * @since 2017-02-17
 */

const fs = require('fs-plus')
const path = require('path')
const Svgo = require('svgo')
const golb = require('glob')
const args = require('yargs')
  .usage('Usage: $0 -s svgSourcePath -t targetPath')
  .demandOption(['s', 't'])
  .describe('s', 'svg file path')
  .describe('t', 'generate icon path')
  .describe('tpl', 'the template file which to generate icon files')
  .help('help')
  .alias('h', 'help')
  .argv

// svg fle path
let filepath = path.join(process.cwd(), args.s, '**/*.svg')
// generated icon path
let targetPath = path.join(process.cwd(), args.t)

// the template file which to generate icon files
let tplPath = args.tpl ? path.join(process.cwd(), args.tpl) : path.join(__dirname, '../icon.tpl.txt')
let tpl = fs.readFileSync(tplPath, 'utf8')

// delete previous icons
fs.removeSync(targetPath)

let svgo = new Svgo({
  plugins: [
    {
      removeAttrs: {
        attrs: ['(path|rect|circle|polygon|line|polyline|g):(fill|stroke)']
      }
    },
    {
      removeTitle: true
    },
    {
      removeStyleElement: true
    },
    {
      removeComments: true
    },
    {
      removeDesc: true
    },
    {
      removeUselessDefs: true
    },
    {
      cleanupIDs: {
        remove: true,
        prefix: 'svgicon-'
      }
    },
    {
      convertShapeToPath: true
    }
  ]
})

// simple template compile
function compile (content, data) {
    return content.replace(/\${(\w+)}/gi, function (match, name) {
        return data[name] ? data[name] : ''
    })
}

// get file path by filename
function getFilePath (filename) {
  let filePath = filename.replace(path.resolve(args.s), '').replace(path.basename(filename), '')
  if ( /^[\/\\]/.test(filePath) ) {
    filePath = filePath.substr(1)
  }

  return filePath
}

// generate index.js, which import all icons
function generateIndex(files) {
  let content = ''
  files.forEach((filename) => {
    let name = path.basename(filename).split('.')[0]
    const filePath = getFilePath(filename)
    content += `require('./${filePath}${name}')\n`
  })

  fs.writeFile(path.join(targetPath, 'index.js'), content, 'utf-8', (err) => {
    if (err) {
      console.log(err)
      return false
    }

    console.log('Generated index.js')
  })
}

golb(filepath, function (err, files) {
  if (err) {
    console.log(err)
    return false
  }

  files = files.map((filepath) => path.normalize(filepath));

  files.forEach((filename, ix) => {
    let name = path.basename(filename).split('.')[0]
    let content = fs.readFileSync(filename, 'utf-8')
    let filePath = getFilePath(filename)

    svgo.optimize(content, (result) => {
      let data = result.data.replace(/<svg[^>]+>/gi, '').replace(/<\/svg>/gi, '')
      let viewBox = result.data.match(/viewBox="([\d\.]+\s[\d\.]+\s[\d\.]+\s[\d\.]+)"/)

      if (viewBox && viewBox.length > 1) {
        viewBox = `'${viewBox[1]}'`
      }

      // add pid attr, for css
      let reg = /<(path|rect|circle|polygon|line|polyline)\s/gi
      let id = 0
      data = data.replace(reg, function (match) {
        return match + `pid="${id++}" `
      })

      let content = compile(tpl, {
          name: `${filePath}${name}`,
          width: parseFloat(result.info.width) || 16,
          height: parseFloat(result.info.height) || 16,
          viewBox: viewBox,
          data: data
      })

      fs.writeFile(path.join(targetPath, filePath, name + '.js'), content, 'utf-8', function (err) {
        if (ix === files.length - 1) {
          generateIndex(files)
        }
        if (err) {
          console.log(err)
          return false
        }

        console.log(`Generated icon: ${filePath}${name}`)
      })
    })
  })
})

<commit_msg>improve regexp pattern for matching negative values in viewbox attribute<commit_after>#!/usr/bin/env node

/**
 * build svg icon
 * @author Allenice
 * @since 2017-02-17
 */

const fs = require('fs-plus')
const path = require('path')
const Svgo = require('svgo')
const golb = require('glob')
const args = require('yargs')
  .usage('Usage: $0 -s svgSourcePath -t targetPath')
  .demandOption(['s', 't'])
  .describe('s', 'svg file path')
  .describe('t', 'generate icon path')
  .describe('tpl', 'the template file which to generate icon files')
  .help('help')
  .alias('h', 'help')
  .argv

// svg fle path
let filepath = path.join(process.cwd(), args.s, '**/*.svg')
// generated icon path
let targetPath = path.join(process.cwd(), args.t)

// the template file which to generate icon files
let tplPath = args.tpl ? path.join(process.cwd(), args.tpl) : path.join(__dirname, '../icon.tpl.txt')
let tpl = fs.readFileSync(tplPath, 'utf8')

// delete previous icons
fs.removeSync(targetPath)

let svgo = new Svgo({
  plugins: [
    {
      removeAttrs: {
        attrs: ['(path|rect|circle|polygon|line|polyline|g):(fill|stroke)']
      }
    },
    {
      removeTitle: true
    },
    {
      removeStyleElement: true
    },
    {
      removeComments: true
    },
    {
      removeDesc: true
    },
    {
      removeUselessDefs: true
    },
    {
      cleanupIDs: {
        remove: true,
        prefix: 'svgicon-'
      }
    },
    {
      convertShapeToPath: true
    }
  ]
})

// simple template compile
function compile (content, data) {
    return content.replace(/\${(\w+)}/gi, function (match, name) {
        return data[name] ? data[name] : ''
    })
}

// get file path by filename
function getFilePath (filename) {
  let filePath = filename.replace(path.resolve(args.s), '').replace(path.basename(filename), '')
  if ( /^[\/\\]/.test(filePath) ) {
    filePath = filePath.substr(1)
  }

  return filePath
}

// generate index.js, which import all icons
function generateIndex(files) {
  let content = ''
  files.forEach((filename) => {
    let name = path.basename(filename).split('.')[0]
    const filePath = getFilePath(filename)
    content += `require('./${filePath}${name}')\n`
  })

  fs.writeFile(path.join(targetPath, 'index.js'), content, 'utf-8', (err) => {
    if (err) {
      console.log(err)
      return false
    }

    console.log('Generated index.js')
  })
}

golb(filepath, function (err, files) {
  if (err) {
    console.log(err)
    return false
  }

  files = files.map((filepath) => path.normalize(filepath));

  files.forEach((filename, ix) => {
    let name = path.basename(filename).split('.')[0]
    let content = fs.readFileSync(filename, 'utf-8')
    let filePath = getFilePath(filename)

    svgo.optimize(content, (result) => {
      let data = result.data.replace(/<svg[^>]+>/gi, '').replace(/<\/svg>/gi, '')
      let viewBox = result.data.match(/viewBox="([-\d\.]+\s[-\d\.]+\s[-\d\.]+\s[-\d\.]+)"/)

      if (viewBox && viewBox.length > 1) {
        viewBox = `'${viewBox[1]}'`
      }

      // add pid attr, for css
      let reg = /<(path|rect|circle|polygon|line|polyline)\s/gi
      let id = 0
      data = data.replace(reg, function (match) {
        return match + `pid="${id++}" `
      })

      let content = compile(tpl, {
          name: `${filePath}${name}`,
          width: parseFloat(result.info.width) || 16,
          height: parseFloat(result.info.height) || 16,
          viewBox: viewBox,
          data: data
      })

      fs.writeFile(path.join(targetPath, filePath, name + '.js'), content, 'utf-8', function (err) {
        if (ix === files.length - 1) {
          generateIndex(files)
        }
        if (err) {
          console.log(err)
          return false
        }

        console.log(`Generated icon: ${filePath}${name}`)
      })
    })
  })
})

<|endoftext|>


<commit_before>// Copyright 2019 Google LLC
//
// 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
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

'use strict';

module.exports = {
  opts: {
    readme: './README.md',
    package: './package.json',
    template: './node_modules/jsdoc-fresh',
    recurse: true,
    verbose: true,
    destination: './docs/'
  },
  plugins: [
    'plugins/markdown',
    'jsdoc-region-tag'
  ],
  source: {
    excludePattern: '(^|\\/|\\\\)[._]',
    include: [
      'build/src'
    ],
    includePattern: '\\.js$'
  },
  templates: {
    copyright: 'Copyright 2018 Google, LLC.',
    includeDate: false,
    sourceFiles: false,
    systemName: '@google-cloud/web-risk',
    theme: 'lumen'
  },
  markdown: {
    idInHeadings: true
  }
};
<commit_msg>chore: update jsdoc.js (#118)<commit_after>// Copyright 2019 Google LLC
//
// 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
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

'use strict';

module.exports = {
  opts: {
    readme: './README.md',
    package: './package.json',
    template: './node_modules/jsdoc-fresh',
    recurse: true,
    verbose: true,
    destination: './docs/'
  },
  plugins: [
    'plugins/markdown',
    'jsdoc-region-tag'
  ],
  source: {
    excludePattern: '(^|\\/|\\\\)[._]',
    include: [
      'build/src'
    ],
    includePattern: '\\.js$'
  },
  templates: {
    copyright: 'Copyright 2019 Google, LLC.',
    includeDate: false,
    sourceFiles: false,
    systemName: '@google-cloud/web-risk',
    theme: 'lumen',
    default: {
      "outputSourceFiles": false
    }
  },
  markdown: {
    idInHeadings: true
  }
};
<|endoftext|>


<commit_before>'use strict'

//----------------------------------------------------------
// Modules
//----------------------------------------------------------
// NPM
const figures = require('figures')

// Local
const states = require('./states')

//----------------------------------------------------------
// Logic
//----------------------------------------------------------
// assign defaults
const defaults = {
  colors: {},
  delay: 80,
  errorIndicator: figures.cross,
  frames: ['-', '\\', '|', '/'],
  indent: 2,
  successIndicator: figures.tick
}
defaults.colors[states.incomplete] = 'blue'
defaults.colors[states.success] = 'green'
defaults.colors[states.error] = 'red'

/**
 *
 * @param {}
 */
function typeCheckErr(opt, typeText) {
  return `node-multispinner: ${opt} option must be ${typeText}`
}

/**
 *
 * @param {}
 */
function typeCheck(opt, val) {
  switch (opt) {

    // numbers
    case 'delay':
    case 'indent':
      if (typeof val !== 'number') {
        throw new Error(typeCheckErr(opt, 'a number'))
      }
      break

    // strings
    case 'errorIndicator':
    case 'successIndicator':
      if (typeof val !== 'string') {
        throw new Error(typeCheckErr(opt, 'a string'))
      }
      break

    // object
    case 'colors':
      if (!val instanceof Object) {
        throw new Error(typeCheckErr(opt, 'an object'))
      }
      break

    // array of strings
    case 'frames':
      if (!val instanceof Array) {
        throw new Error(typeCheckErr(opt, 'an array'))
      }
      opt.map(entity => {
        if (typeof entity !== 'string') {
          throw new Error(typeCheckErr(opt, 'an array of strings'))
        }
      })
      break
  }
}

/**
 *
 * @param {}
 */
function validateOpts(opts) {
  if (!opts instanceof Object) {
    throw new Error(
      'node-multispinner: opts parameter must be a valid JS object'
    )
  }
  Object.keys(opts).map(opt => {
    // ensure each opt passed is a configurable opt
    if (!defaults.hasOwnProperty(opt)) {
      throw new Error(
        `node-multispinner: ${opt} is not a configurable option`
      )
    }
    if (opt === 'colors') {
      Object.keys(opts.colors).map(state => {
        if(!defaults.colors.hasOwnProperty(state)) {
          throw new Error(
            `node-multispinner: ${state} is not a configurable color state`
          )
        }
      })
    }

    // ensure each opt passed is correct type
    typeCheck(opt, opts[opt])
  })
}

/**
 *
 * @param {} opts
 */
module.exports = function(opts) {
  // iterate over configurable opts; bind to this[opt]
  Object.keys(defaults).map(opt => {
    if (opt === 'colors') {
      this.colors = {}
      Object.keys(defaults[opt]).map(state => {
        this.colors[state] = opts && opts.colors[state]
          ? opts.colors[state]
          : defaults.colors[state]
      })
    } else {
      this[opt] = opts && opts[opt] ? opts[opt] : defaults[opt]
    }
  })

  // check opts for errs
  if (opts) {
    validateOpts(opts)
  }
}
<commit_msg>whitespace :scream:<commit_after>'use strict'

//----------------------------------------------------------
// Modules
//----------------------------------------------------------
// NPM
const figures = require('figures')

// Local
const states = require('./states')

//----------------------------------------------------------
// Logic
//----------------------------------------------------------
// assign defaults
const defaults = {
  colors: {},
  delay: 80,
  errorIndicator: figures.cross,
  frames: ['-', '\\', '|', '/'],
  indent: 2,
  successIndicator: figures.tick
}
defaults.colors[states.incomplete] = 'blue'
defaults.colors[states.success] = 'green'
defaults.colors[states.error] = 'red'

/**
 *
 * @param {}
 */
function typeCheckErr(opt, typeText) {
  return `node-multispinner: ${opt} option must be ${typeText}`
}

/**
 *
 * @param {}
 */
function typeCheck(opt, val) {
  switch (opt) {

    // numbers
    case 'delay':
    case 'indent':
      if (typeof val !== 'number') {
        throw new Error(typeCheckErr(opt, 'a number'))
      }
      break

    // strings
    case 'errorIndicator':
    case 'successIndicator':
      if (typeof val !== 'string') {
        throw new Error(typeCheckErr(opt, 'a string'))
      }
      break

    // object
    case 'colors':
      if (!val instanceof Object) {
        throw new Error(typeCheckErr(opt, 'an object'))
      }
      break

    // array of strings
    case 'frames':
      if (!val instanceof Array) {
        throw new Error(typeCheckErr(opt, 'an array'))
      }
      opt.map(entity => {
        if (typeof entity !== 'string') {
          throw new Error(typeCheckErr(opt, 'an array of strings'))
        }
      })
      break
  }
}

/**
 *
 * @param {}
 */
function validateOpts(opts) {
  if (!opts instanceof Object) {
    throw new Error(
      'node-multispinner: opts parameter must be a valid JS object'
    )
  }
  Object.keys(opts).map(opt => {
    // ensure each opt passed is a configurable opt
    if (!defaults.hasOwnProperty(opt)) {
      throw new Error(
        `node-multispinner: ${opt} is not a configurable option`
      )
    }
    if (opt === 'colors') {
      Object.keys(opts.colors).map(state => {
        if (!defaults.colors.hasOwnProperty(state)) {
          throw new Error(
            `node-multispinner: ${state} is not a configurable color state`
          )
        }
      })
    }

    // ensure each opt passed is correct type
    typeCheck(opt, opts[opt])
  })
}

/**
 *
 * @param {} opts
 */
module.exports = function(opts) {
  // iterate over configurable opts; bind to this[opt]
  Object.keys(defaults).map(opt => {
    if (opt === 'colors') {
      this.colors = {}
      Object.keys(defaults[opt]).map(state => {
        this.colors[state] = opts && opts.colors[state]
          ? opts.colors[state]
          : defaults.colors[state]
      })
    } else {
      this[opt] = opts && opts[opt] ? opts[opt] : defaults[opt]
    }
  })

  // check opts for errs
  if (opts) {
    validateOpts(opts)
  }
}
<|endoftext|>


<commit_before>// @ts-nocheck
import * as path from 'path'

import webpack, { ModuleFilenameHelpers } from 'webpack'
import sources from 'webpack-sources'
import pLimit from 'p-limit'
import jestWorker from 'jest-worker'
import crypto from 'crypto'
import cacache from 'next/dist/compiled/cacache'
import { tracer, traceAsyncFn } from '../../../../tracer'

const isWebpack5 = parseInt(webpack.version) === 5

function getEcmaVersion(environment) {
  // ES 6th
  if (
    environment.arrowFunction ||
    environment.const ||
    environment.destructuring ||
    environment.forOf ||
    environment.module
  ) {
    return 2015
  }

  // ES 11th
  if (environment.bigIntLiteral || environment.dynamicImport) {
    return 2020
  }

  return 5
}

function buildError(error, file) {
  if (error.line) {
    return new Error(
      `${file} from Terser\n${error.message} [${file}:${error.line},${
        error.col
      }]${
        error.stack ? `\n${error.stack.split('\n').slice(1).join('\n')}` : ''
      }`
    )
  }

  if (error.stack) {
    return new Error(`${file} from Terser\n${error.message}\n${error.stack}`)
  }

  return new Error(`${file} from Terser\n${error.message}`)
}

class Webpack4Cache {
  constructor(cacheDir, { SourceMapSource, RawSource }) {
    this.cacheDir = cacheDir
    this.sources = { SourceMapSource, RawSource }
  }
  getLazyHashedEtag(obj) {
    let str
    if (obj.source) {
      str = obj.source()
    }
    const hash = crypto.createHash('md4')
    hash.update(str ? str : obj)
    return hash.digest('base64')
  }

  async getPromise(identifier, etag) {
    let cachedResult

    try {
      cachedResult = await cacache.get(this.cacheDir, etag)
    } catch (ignoreError) {
      // eslint-disable-next-line no-undefined
      return undefined
    }

    cachedResult = JSON.parse(cachedResult.data)

    const { code, name, map, input, inputSourceMap } = cachedResult

    let source

    if (map) {
      source = new this.sources.SourceMapSource(
        code,
        name,
        map,
        input,
        inputSourceMap,
        true
      )
    } else {
      source = new this.sources.RawSource(code)
    }

    return { source }
  }

  async storePromise(identifier, etag, data) {
    await cacache.put(this.cacheDir, etag, JSON.stringify(data))
  }
}

class TerserPlugin {
  constructor(options = {}) {
    const { cacheDir, terserOptions = {}, parallel } = options

    this.options = {
      cacheDir,
      parallel,
      terserOptions,
    }
  }

  async optimize(
    compilation,
    assets,
    optimizeOptions,
    cache,
    { SourceMapSource, RawSource }
  ) {
    const span = tracer.startSpan('terser-webpack-plugin-optimize', {
      attributes: {
        webpackVersion: isWebpack5 ? 5 : 4,
        compilationName: compilation.name,
      },
    })

    return traceAsyncFn(span, async () => {
      let numberOfAssetsForMinify = 0
      const assetsList = isWebpack5
        ? Object.keys(assets)
        : [
            ...Array.from(compilation.additionalChunkAssets || []),
            ...Array.from(assets).reduce((acc, chunk) => {
              return acc.concat(Array.from(chunk.files || []))
            }, []),
          ]

      const assetsForMinify = await Promise.all(
        assetsList
          .filter((name) => {
            if (
              !ModuleFilenameHelpers.matchObject.bind(
                // eslint-disable-next-line no-undefined
                undefined,
                { test: /\.[cm]?js(\?.*)?$/i }
              )(name)
            ) {
              return false
            }

            const res = compilation.getAsset(name)
            if (!res) {
              console.log(name)
              return false
            }

            const { info } = res

            // Skip double minimize assets from child compilation
            if (info.minimized) {
              return false
            }

            return true
          })
          .map(async (name) => {
            const { info, source } = compilation.getAsset(name)

            const eTag = cache.getLazyHashedEtag(source)
            const output = await cache.getPromise(name, eTag)

            if (!output) {
              numberOfAssetsForMinify += 1
            }

            return { name, info, inputSource: source, output, eTag }
          })
      )

      const numberOfWorkers = Math.min(
        numberOfAssetsForMinify,
        optimizeOptions.availableNumberOfCores
      )

      let initializedWorker

      // eslint-disable-next-line consistent-return
      const getWorker = () => {
        if (initializedWorker) {
          return initializedWorker
        }

        initializedWorker = new jestWorker(
          path.join(__dirname, './minify.js'),
          {
            numWorkers: numberOfWorkers,
            enableWorkerThreads: true,
          }
        )

        initializedWorker.getStdout().pipe(process.stdout)
        initializedWorker.getStderr().pipe(process.stderr)

        return initializedWorker
      }

      const limit = pLimit(
        numberOfAssetsForMinify > 0 ? numberOfWorkers : Infinity
      )
      const scheduledTasks = []

      for (const asset of assetsForMinify) {
        scheduledTasks.push(
          limit(async () => {
            const { name, inputSource, info, eTag } = asset
            let { output } = asset

            const assetSpan = tracer.startSpan('minify-js', {
              attributes: {
                name,
                cache: typeof output === 'undefined' ? 'MISS' : 'HIT',
              },
            })

            return traceAsyncFn(assetSpan, async () => {
              if (!output) {
                let inputSourceMap

                const {
                  source: sourceFromInputSource,
                  map,
                } = inputSource.sourceAndMap()

                const input = Buffer.isBuffer(sourceFromInputSource)
                  ? sourceFromInputSource.toString()
                  : sourceFromInputSource

                const options = {
                  name,
                  input,
                  inputSourceMap: map,
                  terserOptions: { ...this.options.terserOptions },
                }

                if (typeof options.terserOptions.module === 'undefined') {
                  if (typeof info.javascriptModule !== 'undefined') {
                    options.terserOptions.module = info.javascriptModule
                  } else if (/\.mjs(\?.*)?$/i.test(name)) {
                    options.terserOptions.module = true
                  } else if (/\.cjs(\?.*)?$/i.test(name)) {
                    options.terserOptions.module = false
                  }
                }

                try {
                  output = await getWorker().minify(options)
                } catch (error) {
                  compilation.errors.push(buildError(error, name))

                  return
                }

                if (output.map) {
                  output.source = new SourceMapSource(
                    output.code,
                    name,
                    output.map,
                    input,
                    /** @type {SourceMapRawSourceMap} */ (inputSourceMap),
                    true
                  )
                } else {
                  output.source = new RawSource(output.code)
                }

                if (isWebpack5) {
                  await cache.storePromise(name, eTag, {
                    source: output.source,
                  })
                } else {
                  await cache.storePromise(name, eTag, {
                    code: output.code,
                    map: output.map,
                    name,
                    input,
                    inputSourceMap,
                  })
                }
              }

              /** @type {AssetInfo} */
              const newInfo = { minimized: true }
              const { source } = output

              compilation.updateAsset(name, source, newInfo)
            })
          })
        )
      }

      await Promise.all(scheduledTasks)

      if (initializedWorker) {
        await initializedWorker.end()
      }
    })
  }

  /**
   * @param {Compiler} compiler
   * @returns {void}
   */
  apply(compiler) {
    const { SourceMapSource, RawSource } = compiler?.webpack?.sources || sources
    const { output } = compiler.options

    if (typeof this.options.terserOptions.ecma === 'undefined') {
      this.options.terserOptions.ecma = getEcmaVersion(output.environment || {})
    }

    const pluginName = this.constructor.name
    const availableNumberOfCores = this.options.parallel

    compiler.hooks.compilation.tap(pluginName, (compilation) => {
      // Don't run minifier against mini-css-extract-plugin
      if (compilation.name !== 'client' && compilation.name !== 'server') {
        return
      }

      const cache = isWebpack5
        ? compilation.getCache('TerserWebpackPlugin')
        : new Webpack4Cache(this.options.cacheDir, {
            SourceMapSource,
            RawSource,
          })

      const handleHashForChunk = (hash, chunk) => {
        hash.update('a')
      }

      if (isWebpack5) {
        const JSModulesHooks = webpack.javascript.JavascriptModulesPlugin.getCompilationHooks(
          compilation
        )
        JSModulesHooks.chunkHash.tap(pluginName, (chunk, hash) => {
          if (!chunk.hasRuntime()) return
          return handleHashForChunk(hash, chunk)
        })

        compilation.hooks.processAssets.tapPromise(
          {
            name: pluginName,
            stage:
              compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE,
          },
          (assets) =>
            this.optimize(
              compilation,
              assets,
              {
                availableNumberOfCores,
              },
              cache,
              { SourceMapSource, RawSource }
            )
        )

        compilation.hooks.statsPrinter.tap(pluginName, (stats) => {
          stats.hooks.print
            .for('asset.info.minimized')
            .tap('terser-webpack-plugin', (minimized, { green, formatFlag }) =>
              // eslint-disable-next-line no-undefined
              minimized ? green(formatFlag('minimized')) : undefined
            )
        })
      } else {
        compilation.mainTemplate.hooks.hashForChunk.tap(
          pluginName,
          handleHashForChunk
        )
        compilation.chunkTemplate.hooks.hashForChunk.tap(
          pluginName,
          handleHashForChunk
        )

        compilation.hooks.optimizeChunkAssets.tapPromise(
          pluginName,
          async (assets) => {
            return await this.optimize(
              compilation,
              assets,
              {
                availableNumberOfCores,
              },
              cache,
              { SourceMapSource, RawSource }
            )
          }
        )
      }
    })
  }
}

export default TerserPlugin
<commit_msg>Fix webpack 5 terser compat (#20378)<commit_after>// @ts-nocheck
import * as path from 'path'

import webpack, { ModuleFilenameHelpers, Compilation } from 'webpack'
import sources from 'webpack-sources'
import pLimit from 'p-limit'
import jestWorker from 'jest-worker'
import crypto from 'crypto'
import cacache from 'next/dist/compiled/cacache'
import { tracer, traceAsyncFn } from '../../../../tracer'

const isWebpack5 = parseInt(webpack.version) === 5

function getEcmaVersion(environment) {
  // ES 6th
  if (
    environment.arrowFunction ||
    environment.const ||
    environment.destructuring ||
    environment.forOf ||
    environment.module
  ) {
    return 2015
  }

  // ES 11th
  if (environment.bigIntLiteral || environment.dynamicImport) {
    return 2020
  }

  return 5
}

function buildError(error, file) {
  if (error.line) {
    return new Error(
      `${file} from Terser\n${error.message} [${file}:${error.line},${
        error.col
      }]${
        error.stack ? `\n${error.stack.split('\n').slice(1).join('\n')}` : ''
      }`
    )
  }

  if (error.stack) {
    return new Error(`${file} from Terser\n${error.message}\n${error.stack}`)
  }

  return new Error(`${file} from Terser\n${error.message}`)
}

class Webpack4Cache {
  constructor(cacheDir, { SourceMapSource, RawSource }) {
    this.cacheDir = cacheDir
    this.sources = { SourceMapSource, RawSource }
  }
  getLazyHashedEtag(obj) {
    let str
    if (obj.source) {
      str = obj.source()
    }
    const hash = crypto.createHash('md4')
    hash.update(str ? str : obj)
    return hash.digest('base64')
  }

  async getPromise(identifier, etag) {
    let cachedResult

    try {
      cachedResult = await cacache.get(this.cacheDir, etag)
    } catch (ignoreError) {
      // eslint-disable-next-line no-undefined
      return undefined
    }

    cachedResult = JSON.parse(cachedResult.data)

    const { code, name, map, input, inputSourceMap } = cachedResult

    let source

    if (map) {
      source = new this.sources.SourceMapSource(
        code,
        name,
        map,
        input,
        inputSourceMap,
        true
      )
    } else {
      source = new this.sources.RawSource(code)
    }

    return { source }
  }

  async storePromise(identifier, etag, data) {
    await cacache.put(this.cacheDir, etag, JSON.stringify(data))
  }
}

class TerserPlugin {
  constructor(options = {}) {
    const { cacheDir, terserOptions = {}, parallel } = options

    this.options = {
      cacheDir,
      parallel,
      terserOptions,
    }
  }

  async optimize(
    compilation,
    assets,
    optimizeOptions,
    cache,
    { SourceMapSource, RawSource }
  ) {
    const span = tracer.startSpan('terser-webpack-plugin-optimize', {
      attributes: {
        webpackVersion: isWebpack5 ? 5 : 4,
        compilationName: compilation.name,
      },
    })

    return traceAsyncFn(span, async () => {
      let numberOfAssetsForMinify = 0
      const assetsList = isWebpack5
        ? Object.keys(assets)
        : [
            ...Array.from(compilation.additionalChunkAssets || []),
            ...Array.from(assets).reduce((acc, chunk) => {
              return acc.concat(Array.from(chunk.files || []))
            }, []),
          ]

      const assetsForMinify = await Promise.all(
        assetsList
          .filter((name) => {
            if (
              !ModuleFilenameHelpers.matchObject.bind(
                // eslint-disable-next-line no-undefined
                undefined,
                { test: /\.[cm]?js(\?.*)?$/i }
              )(name)
            ) {
              return false
            }

            const res = compilation.getAsset(name)
            if (!res) {
              console.log(name)
              return false
            }

            const { info } = res

            // Skip double minimize assets from child compilation
            if (info.minimized) {
              return false
            }

            return true
          })
          .map(async (name) => {
            const { info, source } = compilation.getAsset(name)

            const eTag = cache.getLazyHashedEtag(source)
            const output = await cache.getPromise(name, eTag)

            if (!output) {
              numberOfAssetsForMinify += 1
            }

            return { name, info, inputSource: source, output, eTag }
          })
      )

      const numberOfWorkers = Math.min(
        numberOfAssetsForMinify,
        optimizeOptions.availableNumberOfCores
      )

      let initializedWorker

      // eslint-disable-next-line consistent-return
      const getWorker = () => {
        if (initializedWorker) {
          return initializedWorker
        }

        initializedWorker = new jestWorker(
          path.join(__dirname, './minify.js'),
          {
            numWorkers: numberOfWorkers,
            enableWorkerThreads: true,
          }
        )

        initializedWorker.getStdout().pipe(process.stdout)
        initializedWorker.getStderr().pipe(process.stderr)

        return initializedWorker
      }

      const limit = pLimit(
        numberOfAssetsForMinify > 0 ? numberOfWorkers : Infinity
      )
      const scheduledTasks = []

      for (const asset of assetsForMinify) {
        scheduledTasks.push(
          limit(async () => {
            const { name, inputSource, info, eTag } = asset
            let { output } = asset

            const assetSpan = tracer.startSpan('minify-js', {
              attributes: {
                name,
                cache: typeof output === 'undefined' ? 'MISS' : 'HIT',
              },
            })

            return traceAsyncFn(assetSpan, async () => {
              if (!output) {
                let inputSourceMap

                const {
                  source: sourceFromInputSource,
                  map,
                } = inputSource.sourceAndMap()

                const input = Buffer.isBuffer(sourceFromInputSource)
                  ? sourceFromInputSource.toString()
                  : sourceFromInputSource

                const options = {
                  name,
                  input,
                  inputSourceMap: map,
                  terserOptions: { ...this.options.terserOptions },
                }

                if (typeof options.terserOptions.module === 'undefined') {
                  if (typeof info.javascriptModule !== 'undefined') {
                    options.terserOptions.module = info.javascriptModule
                  } else if (/\.mjs(\?.*)?$/i.test(name)) {
                    options.terserOptions.module = true
                  } else if (/\.cjs(\?.*)?$/i.test(name)) {
                    options.terserOptions.module = false
                  }
                }

                try {
                  output = await getWorker().minify(options)
                } catch (error) {
                  compilation.errors.push(buildError(error, name))

                  return
                }

                if (output.map) {
                  output.source = new SourceMapSource(
                    output.code,
                    name,
                    output.map,
                    input,
                    /** @type {SourceMapRawSourceMap} */ (inputSourceMap),
                    true
                  )
                } else {
                  output.source = new RawSource(output.code)
                }

                if (isWebpack5) {
                  await cache.storePromise(name, eTag, {
                    source: output.source,
                  })
                } else {
                  await cache.storePromise(name, eTag, {
                    code: output.code,
                    map: output.map,
                    name,
                    input,
                    inputSourceMap,
                  })
                }
              }

              /** @type {AssetInfo} */
              const newInfo = { minimized: true }
              const { source } = output

              compilation.updateAsset(name, source, newInfo)
            })
          })
        )
      }

      await Promise.all(scheduledTasks)

      if (initializedWorker) {
        await initializedWorker.end()
      }
    })
  }

  /**
   * @param {Compiler} compiler
   * @returns {void}
   */
  apply(compiler) {
    const { SourceMapSource, RawSource } = compiler?.webpack?.sources || sources
    const { output } = compiler.options

    if (typeof this.options.terserOptions.ecma === 'undefined') {
      this.options.terserOptions.ecma = getEcmaVersion(output.environment || {})
    }

    const pluginName = this.constructor.name
    const availableNumberOfCores = this.options.parallel

    compiler.hooks.compilation.tap(pluginName, (compilation) => {
      // Don't run minifier against mini-css-extract-plugin
      if (compilation.name !== 'client' && compilation.name !== 'server') {
        return
      }

      const cache = isWebpack5
        ? compilation.getCache('TerserWebpackPlugin')
        : new Webpack4Cache(this.options.cacheDir, {
            SourceMapSource,
            RawSource,
          })

      const handleHashForChunk = (hash, chunk) => {
        hash.update('a')
      }

      if (isWebpack5) {
        const JSModulesHooks = webpack.javascript.JavascriptModulesPlugin.getCompilationHooks(
          compilation
        )
        JSModulesHooks.chunkHash.tap(pluginName, (chunk, hash) => {
          if (!chunk.hasRuntime()) return
          return handleHashForChunk(hash, chunk)
        })

        compilation.hooks.processAssets.tapPromise(
          {
            name: pluginName,
            stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE,
          },
          (assets) =>
            this.optimize(
              compilation,
              assets,
              {
                availableNumberOfCores,
              },
              cache,
              { SourceMapSource, RawSource }
            )
        )

        compilation.hooks.statsPrinter.tap(pluginName, (stats) => {
          stats.hooks.print
            .for('asset.info.minimized')
            .tap('terser-webpack-plugin', (minimized, { green, formatFlag }) =>
              // eslint-disable-next-line no-undefined
              minimized ? green(formatFlag('minimized')) : undefined
            )
        })
      } else {
        compilation.mainTemplate.hooks.hashForChunk.tap(
          pluginName,
          handleHashForChunk
        )
        compilation.chunkTemplate.hooks.hashForChunk.tap(
          pluginName,
          handleHashForChunk
        )

        compilation.hooks.optimizeChunkAssets.tapPromise(
          pluginName,
          async (assets) => {
            return await this.optimize(
              compilation,
              assets,
              {
                availableNumberOfCores,
              },
              cache,
              { SourceMapSource, RawSource }
            )
          }
        )
      }
    })
  }
}

export default TerserPlugin
<|endoftext|>


<commit_before>// Module to manage books with arbitrary identification and retrieval schemes.
var sys = require('sys');
var _ = require('underscore');
require('./lib/underscore.strings');
var strings = require('./lib/underscore.strings');
_.mixin(_s); // extend underscore with strings
var rclient = require('./redisclient');
var isbn = require('./isbn');
var book_lookup = require('./book_lookup');

// Each book-thing gets a unique number
var bookincr = "book:incr";

// Collection of all book keys, scores are created_date field
var bookzset = "book:allbycreated";

// We maintain a mapping of URIs (ISBN/ASIN,etc.) to book ID's.
// This allows for a quick lookup of arbitrary identifiers to a book.
var book_uri_hash = "book:uri"

// Keys for a book object that are eligible for serialization.
var book_keys =
    ["id", // our unique numeric identifier
     "datasource", // where information was retrieved
     "ean", // EAN/ISBN-13
     "isbn10", // ISBN-10
     "asin", // Amazon identifier (useful for kindle books)
     "librarything_work", // LibraryThing work
     "url", // where to direct users for more info (amazon referral, librarything work, etc.)
     "title", // book title
     "authors", // array of author names
     "pages", // number of pages in the book
     "dewey_decimal_number",
     "cover_image_url_small", // link to a small cover image
     "cover_image_url_medium", // link to a medium-sized cover image
     "cover_image_url_large", // link to a large cover image
     "created_date", // date this book was first entered (millis-since-epoch number)
     "modified_date" // date this book was last modified (millis-since-epoch number)
    ];

exports.isbn_to_uri = function(i) {
    var isbn_num = isbn.to_isbn_13(i);
    if (_.isNull(isbn_num)) {
        return null
    } else {
        return "urn:isbn:"+isbn_num;
    }
};

exports.asin_to_uri = function(asin) {
    if (_.isNull(asin)||!exports.isASINlike(asin)) {
        return null
    } else {
        return "http://amzn.com/"+asin;
    }
};

// Detect what our search term contains.
// Returns either "EAN", "ASIN", or "unknown"
// Return an object with type/value/uri attributes:
//  type is "EAN", "ASIN", or "unknown".
//  value will be the "sanitized" term.
//  uri is formed from the value.
exports.detect_search_type = function (search) {
    var result = {}
    var search = _.trim(search);
    var ean = isbn.to_isbn_13(search);
    if (!_.isNull(ean)) {
        result.type="EAN";
        result.value=ean
        result.uri=exports.isbn_to_uri(ean);
    } else if (exports.isASINlike(search)) {
        result.type="ASIN";
        result.value=search;
        result.uri=exports.asin_to_uri(search);
    } else {
        result.type="unknown";
        result.value=search;
        result.uri=null;
    }
    return result;
};

// there is no test that an ASIN is valid we assume that 10
// alphanum characters is an ASIN.
// This checks if a string looks like an Amazon ASIN
exports.isASINlike = function isASINlike(asin) {
    var re = /^[A-Za-z0-9]{10}$/;
    return re.test(asin);
};

// Create a book from URI
// callback will be invoked with error,book
exports.create_from_identifier = function(term,callback) {
    var search = exports.detect_search_type(term);
    console.log("search term detected as",sys.inspect(search));
    var uri = search.uri;
    if (!_.isNull(uri)) {
        // Find existing book
        exports.id_from_uri(uri,function(err,result) {
            if (err) {
                console.log(err);
                callback("Error finding book ID",null);
            } else if (_.isNull(result)) {
                console.log("consulting book lookup service for metadata.");
                // if book not found, send URI to book lookup service
                book_lookup.lookup(search.type, search.value, function(err,lookup_book) {
                    console.log("book lookup service returned");
                    var b = new Book(lookup_book);
                    make_book_id(function(err,book_id) {
                        if (err) {
                            console.log("make_book_id failed",err);
                            callback("Could not create unique identifier for book",null);
                        }
                        b.id = book_id
                        b.save(function(err) {
                            callback(err,b);
                        });
                    });
                });
            } else {
                // book already exists
                exports.get_by_id(result,callback);
            }
        });
    } else {
        // error out... book URI couldn't be determined
        callback("Book must be identified by an ISBN or Amazon ASIN.",null);
    }
};

// Get the key for a book object from its unique ID
function id_to_key (id) {
    return "book:info:"+id;
};

// Get an existing book via ID
exports.get_by_id = function(id, callback) {
    if (_.isNull(id) || _.isUndefined(id)) {
        console.log("Cannot get book with ID of null/undefined");
        callback("ID was null/undefined",null);
        return;
    }
    var client = rclient.getClient();
    var key = id_to_key(id);
    client.get(id_to_key(id),function(err,val) {
        if (err) {
            console.log("Error retrieving book");
            callback(err,null);
        } else {
            var parsed = JSON.parse(val);
            callback(err,new Book(JSON.parse(val)));
        }
    });
};

// Get an existing book via URI
exports.id_from_uri = function(uri,callback) {
    var client = rclient.getClient();
    client.hget(book_uri_hash,uri,callback);
};

function Book (attrs) {
    var context = this;
    context.load_from_json(attrs);
    return context;
}

Book.prototype.load_from_json = function(json) {
    var context = this;
    _.select(book_keys, function (key) {
        context[key] = json[key];
    });
};

// Create a unique identifier for a book
var make_book_id = function(callback) {
    var client = rclient.getClient();
    client.incr(bookincr,callback);
};

// Save a book to redis
// this writes JSON to the :info key,
// adds book to book sorted set, and updates the
// hash that points URIs to the book ID.
Book.prototype.save = function save(callback) {
    var client = rclient.getClient();
    var context = this;
    var obj_string = JSON.stringify(context);
    // if the book does not have an ID, die
    if (_.isUndefined(context.id) || _.isNull(context.id)) {
        console.log("This book does not have an identifier");
        callback("Book must have a unique identifier created before saving",null);
    }
    client.set(id_to_key(context.id),obj_string, function(err,r) {
        if (err) {
            callback(err,null);
        } else {
            // add to list of books, ordered by book id
            client.zadd(bookzset,context.id,context.id,function(err,result) {
                if (err) {
                    console.log(err);
                }
                callback(err,context);
            });
            // update hash entries for ean/asin
            context.update_indexes(null);
        }
    });
};

Book.prototype.update_indexes = function(callback) {
    var client = rclient.getClient();
    var actions_complete = 0; var actions_total = 2;
    var notify = function(err,result) {
        actions_complete++;
        if (actions_complete == actions_total) {
            if (!_.isNull(callback)) callback();
        }
    }
    if (!_.isNull(this.ean) && (!_.isUndefined(this.ean))) {
        client.hset(book_uri_hash,exports.isbn_to_uri(this.ean),this.id,notify)
    }
    if (!_.isNull(this.asin) && (!_.isUndefined(this.asin))) {
        client.hset(book_uri_hash,exports.asin_to_uri(this.asin),this.id,notify);
    }
};

Book.prototype.toJSON = function() {
    var json = {};
    var context = this;
    _.select(book_keys, function (key) {
        json[key] = context[key];
    });
    return json;
};

// How many books exist in the DB?
exports.book_count = function(callback) {
    var client = rclient.getClient();
    client.zcard(bookzset,callback);
};

exports.list_books = function(start, end, callback) {
    var client = rclient.getClient();
    client.zrange(bookzset,start,end, function(err,reply){
        var replies = 0;
        var books = [];
        if (_.isNull(reply)) {
            if (err) {
                console.log("Error:",err);
            }
            callback(err,books);
            return;
        }
        if (reply.length === 0) {
            callback(null,books);
        }
        for(var i=0; i < reply.length; i++) {
            // each reply is a book ID
            exports.get_by_id(reply[i].toString(), function(err,book) {
                if (err) {
                    console.log("error retrieving book");
                } else {
                    books.push(book);
                }
                replies++;
                if (replies == reply.length) {
                    callback(err,books);
                }
            });
        }
    });
};

exports.Book = Book;<commit_msg>Helper functions for matching badge criteria to books.<commit_after>// Module to manage books with arbitrary identification and retrieval schemes.
var sys = require('sys');
var _ = require('underscore');
require('./lib/underscore.strings');
var strings = require('./lib/underscore.strings');
_.mixin(_s); // extend underscore with strings
var rclient = require('./redisclient');
var isbn = require('./isbn');
var book_lookup = require('./book_lookup');

// Each book-thing gets a unique number
var bookincr = "book:incr";

// Collection of all book keys, scores are created_date field
var bookzset = "book:allbycreated";

// We maintain a mapping of URIs (ISBN/ASIN,etc.) to book ID's.
// This allows for a quick lookup of arbitrary identifiers to a book.
var book_uri_hash = "book:uri"

// Keys for a book object that are eligible for serialization.
var book_keys =
    ["id", // our unique numeric identifier
     "datasource", // where information was retrieved
     "ean", // EAN/ISBN-13
     "isbn10", // ISBN-10
     "asin", // Amazon identifier (useful for kindle books)
     "librarything_work", // LibraryThing work
     "url", // where to direct users for more info (amazon referral, librarything work, etc.)
     "title", // book title
     "authors", // array of author names
     "pages", // number of pages in the book
     "dewey_decimal_number",
     "cover_image_url_small", // link to a small cover image
     "cover_image_url_medium", // link to a medium-sized cover image
     "cover_image_url_large", // link to a large cover image
     "created_date", // date this book was first entered (millis-since-epoch number)
     "modified_date" // date this book was last modified (millis-since-epoch number)
    ];

exports.isbn_to_uri = function(i) {
    var isbn_num = isbn.to_isbn_13(i);
    if (_.isNull(isbn_num)) {
        return null
    } else {
        return "urn:isbn:"+isbn_num;
    }
};

exports.asin_to_uri = function(asin) {
    if (_.isNull(asin)||!exports.isASINlike(asin)) {
        return null
    } else {
        return "http://amzn.com/"+asin;
    }
};

// Detect what our search term contains.
// Returns either "EAN", "ASIN", or "unknown"
// Return an object with type/value/uri attributes:
//  type is "EAN", "ASIN", or "unknown".
//  value will be the "sanitized" term.
//  uri is formed from the value.
exports.detect_search_type = function (search) {
    var result = {}
    var search = _.trim(search);
    var ean = isbn.to_isbn_13(search);
    if (!_.isNull(ean)) {
        result.type="EAN";
        result.value=ean
        result.uri=exports.isbn_to_uri(ean);
    } else if (exports.isASINlike(search)) {
        result.type="ASIN";
        result.value=search;
        result.uri=exports.asin_to_uri(search);
    } else {
        result.type="unknown";
        result.value=search;
        result.uri=null;
    }
    return result;
};

// there is no test that an ASIN is valid we assume that 10
// alphanum characters is an ASIN.
// This checks if a string looks like an Amazon ASIN
exports.isASINlike = function isASINlike(asin) {
    var re = /^[A-Za-z0-9]{10}$/;
    return re.test(asin);
};

// Create a book from URI
// callback will be invoked with error,book
exports.create_from_identifier = function(term,callback) {
    var search = exports.detect_search_type(term);
    console.log("search term detected as",sys.inspect(search));
    var uri = search.uri;
    if (!_.isNull(uri)) {
        // Find existing book
        exports.id_from_uri(uri,function(err,result) {
            if (err) {
                console.log(err);
                callback("Error finding book ID",null);
            } else if (_.isNull(result)) {
                console.log("consulting book lookup service for metadata.");
                // if book not found, send URI to book lookup service
                book_lookup.lookup(search.type, search.value, function(err,lookup_book) {
                    console.log("book lookup service returned");
                    var b = new Book(lookup_book);
                    make_book_id(function(err,book_id) {
                        if (err) {
                            console.log("make_book_id failed",err);
                            callback("Could not create unique identifier for book",null);
                        }
                        b.id = book_id
                        b.save(function(err) {
                            callback(err,b);
                        });
                    });
                });
            } else {
                // book already exists
                exports.get_by_id(result,callback);
            }
        });
    } else {
        // error out... book URI couldn't be determined
        callback("Book must be identified by an ISBN or Amazon ASIN.",null);
    }
};

// Get the key for a book object from its unique ID
function id_to_key (id) {
    return "book:info:"+id;
};

// Get an existing book via ID
exports.get_by_id = function(id, callback) {
    if (_.isNull(id) || _.isUndefined(id)) {
        console.log("Cannot get book with ID of null/undefined");
        callback("ID was null/undefined",null);
        return;
    }
    var client = rclient.getClient();
    var key = id_to_key(id);
    client.get(id_to_key(id),function(err,val) {
        if (err) {
            console.log("Error retrieving book");
            callback(err,null);
        } else {
            var parsed = JSON.parse(val);
            callback(err,new Book(JSON.parse(val)));
        }
    });
};

// Get an existing book via URI
exports.id_from_uri = function(uri,callback) {
    var client = rclient.getClient();
    client.hget(book_uri_hash,uri,callback);
};

function Book (attrs) {
    var context = this;
    context.load_from_json(attrs);
    return context;
}

Book.prototype.load_from_json = function(json) {
    var context = this;
    _.select(book_keys, function (key) {
        context[key] = json[key];
    });
};

// Create a unique identifier for a book
var make_book_id = function(callback) {
    var client = rclient.getClient();
    client.incr(bookincr,callback);
};

// Save a book to redis
// this writes JSON to the :info key,
// adds book to book sorted set, and updates the
// hash that points URIs to the book ID.
Book.prototype.save = function save(callback) {
    var client = rclient.getClient();
    var context = this;
    var obj_string = JSON.stringify(context);
    // if the book does not have an ID, die
    if (_.isUndefined(context.id) || _.isNull(context.id)) {
        console.log("This book does not have an identifier");
        callback("Book must have a unique identifier created before saving",null);
    }
    client.set(id_to_key(context.id),obj_string, function(err,r) {
        if (err) {
            callback(err,null);
        } else {
            // add to list of books, ordered by book id
            client.zadd(bookzset,context.id,context.id,function(err,result) {
                if (err) {
                    console.log(err);
                }
                callback(err,context);
            });
            // update hash entries for ean/asin
            context.update_indexes(null);
        }
    });
};

Book.prototype.update_indexes = function(callback) {
    var client = rclient.getClient();
    var actions_complete = 0; var actions_total = 2;
    var notify = function(err,result) {
        actions_complete++;
        if (actions_complete == actions_total) {
            if (!_.isNull(callback)) callback();
        }
    }
    if (!_.isNull(this.ean) && (!_.isUndefined(this.ean))) {
        client.hset(book_uri_hash,exports.isbn_to_uri(this.ean),this.id,notify)
    }
    if (!_.isNull(this.asin) && (!_.isUndefined(this.asin))) {
        client.hset(book_uri_hash,exports.asin_to_uri(this.asin),this.id,notify);
    }
};

Book.prototype.toJSON = function() {
    var json = {};
    var context = this;
    _.select(book_keys, function (key) {
        json[key] = context[key];
    });
    return json;
};

// How many books exist in the DB?
exports.book_count = function(callback) {
    var client = rclient.getClient();
    client.zcard(bookzset,callback);
};

exports.list_books = function(start, end, callback) {
    var client = rclient.getClient();
    client.zrange(bookzset,start,end, function(err,reply){
        var replies = 0;
        var books = [];
        if (_.isNull(reply)) {
            if (err) {
                console.log("Error:",err);
            }
            callback(err,books);
            return;
        }
        if (reply.length === 0) {
            callback(null,books);
        }
        for(var i=0; i < reply.length; i++) {
            // each reply is a book ID
            exports.get_by_id(reply[i].toString(), function(err,book) {
                if (err) {
                    console.log("error retrieving book");
                } else {
                    books.push(book);
                }
                replies++;
                if (replies == reply.length) {
                    callback(err,books);
                }
            });
        }
    });
};

// Some predicates helpful for badges
// does the given pattern appear as one of the authors?
Book.prototype.author_like = function(author_pattern) {
    var authors = this.authors;
    if (_.isNull(authors) || _.isUndefined(authors)) {
        return false;
    }
    // force into array
    if (!_.isArray(this.authors)) {
        authors = [authors];
    }
    // run match on each author
    if (_.isArray(this.authors)) {
        for(var i = 0; i < this.authors.length; i++) {
            var a = this.authors[i];
            if (_.isString(a)) {
                var m = this.authors[i].match(author_substring);
                if (!_.isNull(m)) {
                    return true;
                }
            }
        }
    }
    return false;
}

// does the book title match the pattern?
Book.prototype.title_like = function(title_pattern) {
    var title = this.title
    if (_.isNull(title) || _.isUndefined(title)) {
        return false;
    }
    var m = title.match(title_pattern);
    if (!_.isNull(m)) {
        return true;
    } else {
        return false;
    }
};

exports.Book = Book;<|endoftext|>


<commit_before>jQuery.noConflict();
require_main("prototype.js");

// There is apparently no better option for actual synchronous sleep
// (in order to test the result of a setTimeout)
function busySleep(ms) {
  jQuery.ajax({
    async: false,
    url: '/wait/' + ms
  });
}

(function ($) {
  Screw.Matchers["raise"] = {
    match: function (expected, actual) {
      try {
        actual();
        return false;
      } catch (e) {
        actual.actualException = e;
        return e == expected;
      }
    },

    failure_message: function (expected, actual, not) {
      var msg = 'expected to ' + (not ? 'not ' : '') + 'throw ' + jQuery.print(expected)
      if (actual.actualException) {
        msg += '; was ' + jQuery.print(actual.actualException);
      }
      return msg;
    }
  }

  Screw.Matchers["include"] = {
    match: function (expected, actual) {
      return $.inArray(expected, actual) >= 0;
    },

    failure_message: function (expected, actual, not) {
      return 'expected ' + $.print(actual) + ' to ' + (not ? 'not ' : '') +
        'include ' + $.print(expected);
    }
  }

  Screw.Matchers["equal_date"] = {
    match: function(expected, actual) {
      return expected.getFullYear() == actual.getFullYear() &&
        expected.getMonth() == actual.getMonth() &&
        expected.getDate() == actual.getDate();
    },

    failure_message: function(expected, actual, not) {
      return 'expected ' + actual + (not ? ' to not equal ' : ' to equal ') + expected;
    }
  }

  Screw.Matchers["equal_utc_date"] = {
    match: function (expected, actual) {
      return expected.getUTCFullYear() == actual.getUTCFullYear() &&
        expected.getUTCMonth() == actual.getUTCMonth() &&
        expected.getUTCDate() == actual.getUTCDate();
    },

    failure_message: function (expected, actual, not) {
      return "expected " + actual + " to " + (not ? 'not ' : '') +
        "be on same UTC day as " + expected;
    }
  }

  Screw.Matchers["equal_utc_time"] = {
    match: function (expected, actual) {
      return expected.getTime() == actual.getTime()
    },

    failure_message: function (expected, actual, not) {
      return "expected " + actual + " to " + (not ? 'not ' : '') +
        "be exactly the same as " + expected;
    }
  }
}(jQuery));
<commit_msg>Remove unused busySleep wait attempt code<commit_after>jQuery.noConflict();
require_main("prototype.js");

(function ($) {
  Screw.Matchers["raise"] = {
    match: function (expected, actual) {
      try {
        actual();
        return false;
      } catch (e) {
        actual.actualException = e;
        return e == expected;
      }
    },

    failure_message: function (expected, actual, not) {
      var msg = 'expected to ' + (not ? 'not ' : '') + 'throw ' + jQuery.print(expected)
      if (actual.actualException) {
        msg += '; was ' + jQuery.print(actual.actualException);
      }
      return msg;
    }
  }

  Screw.Matchers["include"] = {
    match: function (expected, actual) {
      return $.inArray(expected, actual) >= 0;
    },

    failure_message: function (expected, actual, not) {
      return 'expected ' + $.print(actual) + ' to ' + (not ? 'not ' : '') +
        'include ' + $.print(expected);
    }
  }

  Screw.Matchers["equal_date"] = {
    match: function(expected, actual) {
      return expected.getFullYear() == actual.getFullYear() &&
        expected.getMonth() == actual.getMonth() &&
        expected.getDate() == actual.getDate();
    },

    failure_message: function(expected, actual, not) {
      return 'expected ' + actual + (not ? ' to not equal ' : ' to equal ') + expected;
    }
  }

  Screw.Matchers["equal_utc_date"] = {
    match: function (expected, actual) {
      return expected.getUTCFullYear() == actual.getUTCFullYear() &&
        expected.getUTCMonth() == actual.getUTCMonth() &&
        expected.getUTCDate() == actual.getUTCDate();
    },

    failure_message: function (expected, actual, not) {
      return "expected " + actual + " to " + (not ? 'not ' : '') +
        "be on same UTC day as " + expected;
    }
  }

  Screw.Matchers["equal_utc_time"] = {
    match: function (expected, actual) {
      return expected.getTime() == actual.getTime()
    },

    failure_message: function (expected, actual, not) {
      return "expected " + actual + " to " + (not ? 'not ' : '') +
        "be exactly the same as " + expected;
    }
  }
}(jQuery));
<|endoftext|>


<commit_before>var util = require('util');
var EventEmitter = require('events').EventEmitter;

function GenieMini(id) {
    this.btServiceIds = ['f000474d04514000b000000000000000'];

    this._id = id;
    this._gmCh = null;
    this._dev = null;
    this._moving = false;
    this._angle = null;
    this._position = 0;
    this._currentMove = null;
    this._stepsPerDegree = 186.56716418;

    this.orientation = null;
}

util.inherits(GenieMini, EventEmitter);

GenieMini.prototype._connectBt = function(btPeripheral, callback) {
    var self = this;
    btPeripheral.connect(function(err1) {
        if (err1) {
            if (callback) callback(false);
            return;
        }
        console.log('GenieMini(' + self._id + '): connecting via BLE');
        setTimeout(function(){
            btPeripheral.discoverServices(self.btServiceIds, function(err2, services) {
                if (services && services[0]) {
                    services[0].discoverCharacteristics([], function(err, characteristics) {
                        //console.log("characteristics:", characteristics);
                        self._gmCh = null;
                        self._dev = null;
                        for (var i = 0; i < characteristics.length; i++) {
                            ch = characteristics[i];
                            console.log("ch.uuid", ch.uuid);
                            if (ch.uuid == "2") {
                                self._gmCh = ch;
                            }
                        }
                        if (self._gmCh) {
                            try {
                                self._gmCh.write(new Buffer("012600", 'hex'));
                                self._gmCh.subscribe(function(){
                                    self._dev = btPeripheral;
                                    self._dev.connected = true;
                                    self.connected = true;
                                    self._dev.type = "bt";
                                    self._gmCh.on('data', function(data, isNotification) {
                                        self._parseIncoming(data);
                                    });
                                    console.log("GenieMini(" + self._id + "): connected!");
                                    self._init();
                                    if (callback) callback(true);
                                });
                            } catch(err3) {
                                btPeripheral.disconnect();
                                console.log("GenieMini(" + self._id + "): exception while connecting: ", err3);
                                if (callback) callback(false);
                            }
                        } else {
                            console.log("GenieMini(" + self._id + "): couldn't locate characteristics, disconnecting... ", err);
                            btPeripheral.disconnect();
                            if (callback) callback(false);
                        }
                    });
                } else {
                    console.log("GenieMini(" + self._id + "): couldn't locate services, disconnecting... ", err2);
                    btPeripheral.disconnect();
                    if (callback) callback(false);
                }

            });
        }, 1000);

        btPeripheral.once('disconnect', function() {
            console.log("GenieMini(" + self._id + "): disconnected");
            self._dev = null;
            self.connected = false;
            self.emit("status", self.getStatus());
        });

    });
}

GenieMini.prototype._init = function() {
    var self = this;
    this._write(0x005E, dataBuf, function(err) { // checks orientation
        self.emit("status", self.getStatus());
    });
}

GenieMini.prototype._parseIncoming = function(data) {
    var id = data.readUInt16LE(1);
    if(id == 0x0060 || id == 0x0061) { // moving steps
        var state = data.readUInt8(7);
        var steps = data.readInt16LE(3);
        this._moving = (state == 0x01);
        console.log("GenieMini(" + this._id + "): moving: ", this._moving, ", steps:", steps);
    } else if(id == 0x000B || id == 0x0052) { // current state
        var state = data.readUInt8(4);
        var angle = data.readUInt16LE(12);
        this.orientation = (id == 0x000B ? 'pan' : 'tilt');
        this._angle = angle;
        this._moving = (state == 0x01);
        console.log("GenieMini(" + this._id + "): moving: ", this._moving, ", angle:", angle);
        if(this._currentMove != null && this._lastAngle != null) {
            if(Math.abs(angle - this._lastAngle) < 180) {
                this._currentMove += angle - this._lastAngle;
            } else {
                if(this._lastAngle < angle) {
                    this._currentMove += 359 - (angle - this._lastAngle);
                } else {
                    this._currentMove -= 359 - (this._lastAngle - angle);
                }
            }
            console.log("GenieMini(" + this._id + "): _currentMove", this._currentMove);
        }
        this._lastAngle = angle;
    } else {
        console.log("GenieMini(" + this._id + "): unknown id", id, "data", data);
    }
}

GenieMini.prototype.connect = function(device, callback) {
    if (device && device.connect) {
        this._moving = false;
        console.log("GenieMini(" + this._id + "): connecting...");
        this._connectBt(device, callback);
    } else {
        console.log("GenieMini(" + this._id + "): invalid device, cannot connect");
        if (callback) callback("invalid device");
    }
}

GenieMini.prototype.disconnect = function() {
    console.log("GenieMini(" + this._id + "): disconnecting...");
    if (this._dev && this._dev.connected) {
        this.connected = false;
        this._dev.connected = false;
        this._dev.disconnect();
    } else {
        this._dev = null;
    }
    this.emit("status", this.getStatus());
}

GenieMini.prototype.getStatus = function() {
    var type = (this._dev && this._dev.type) ? this._dev.type : null;
    return {
        connected: this._dev && this._dev.connected,
        connectionType: type,
        motor1: this._dev && this._dev.connected,
        motor1pos: this._position / this._stepsPerDegree,
    }
}

GenieMini.prototype.disable = function(motor) {
    this._enabled = false;
}

GenieMini.prototype.enable = function(motor) {
    this._enabled = true;
}

GenieMini.prototype.move = function(motor, degrees, callback) {
    if (this._moving) {
        if (callback) callback("GenieMini(" + this._id + "): motor already running");
        return console.log("GenieMini(" + this._id + "): motor already running");
    }
    var steps = Math.round(degrees * this._stepsPerDegree);
    if(!steps) {
        console.log("GenieMini(" + this._id + "): NOT moving motor", steps, "steps, (", degrees, " degrees)");
        return callback && callback(null, this._position / this._stepsPerDegree);
    }
    console.log("GenieMini(" + this._id + "): moving motor", steps, "steps");
    if(!this._enabled) this.enable();

    var dataBuf = new Buffer(4);
    dataBuf.fill(0);
    dataBuf.writeInt32LE(steps, 0);
    this._moving = true;

    var self = this;
    self._write(0x005E, dataBuf, function(err) {
        if (!err) {
            var check = function() {
                setTimeout(function() {
                    if(self._moving === undefined) return check();;
                    if(self._moving) {
                        check(); // keep checking until stop
                    } else {
                        self._position += steps;
                        console.log("GenieMini(" + self._id + "): position:", self._position);
                        if (callback) callback(null, self._position / self._stepsPerDegree);
                    }
                }, 200);
            }
            check();
        } else {
            if (callback) callback(err);
            self._moving = false;
        }
    });
}
//<Buffer 01 5e 00 00 00 03 a4 00>
GenieMini.prototype._write = function(command, dataBuf, callback) {
    var template = new Buffer("010000", 'hex');
    var len = dataBuf ? dataBuf.length : 0;
    var COMMAND = 0;

    template.writeUInt16LE(parseInt(command), 1);

    var cmd;
    if (dataBuf) {
        cmd = Buffer.concat([template, dataBuf]);
    } else {
        cmd = template;
    }

    //console.log("GenieMini(" + this._id + "): sending data", cmd);

    try {
        this._gmCh.write(cmd);
        callback && callback();
    } catch(err) {
        callback && callback(err);
    }
}

GenieMini.prototype.constantMove = function(motor, speed, callback) {
    console.log("GenieMini(" + this._id + "): moving motor at speed ", speed, "%");
    if(!this._enabled) this.enable();

    var dataBuf = new Buffer(3);
    var self = this;
    dataBuf.fill(0);
    dataBuf.writeInt8(Math.round(speed), 0);
    dataBuf.writeUInt16LE(speed ? 0x01 : 0x00, 1);
    this._moving = true;

    if(this._watchdog) {
        clearTimeout(this._watchdog);
        this._watchdog = null;
    }
    if(speed) {
        this._watchdog = setTimeout(function(){
            console.log("GenieMini(" + self._id + "): stopping via watchdog");
            self.constantMove(motor, 0);
        }, 3000);
    }

    var sendMove = function() {
        self._write(0x003F, dataBuf, function(err) {
            if (!err) {
                var check = function() {
                    if(this._endTimer) clearTimeout(this._endTimer);
                    if(!speed) {
                        self._endTimer = setTimeout(function() {
                            if(self._moving === undefined) return;
                            if(self._moving) {
                                check(); // keep checking until stop
                            } else {
                                console.log("GenieMini(" + self._id + "): adding", self._currentMove, "° to position");
                                self._position += self._currentMove * self._stepsPerDegree;
                                self._currentMove = null;
                                if (callback) callback(null, self._position / self._stepsPerDegree);
                            }
                        }, 200);
                    } else {
                        if (callback) callback(null, null);
                    }
                }
                check();
            } else {
                if (callback) callback(err);
                self._moving = false;
            }
        });
    }

    var self = this;
    if(this._endTimer) clearTimeout(this._endTimer);
    if(self._currentMove == null) {
        self._lastAngle = null;
        self._currentMove = 0;
        console.log("GenieMini(" + self._id + "): starting move at speed, saving position");
        var d = new Buffer(2);
        d.fill(0);
        d.writeUInt16LE(0x000B, 0);
        this._moving = true;
        self._write(0x001E, d, function(err) {
            sendMove();
        });
    } else {
        sendMove();
    }
}

GenieMini.prototype.resetMotorPosition = function(motor, callback) {
    var self = this;
    var check = function() {
        if(self._moving) {
            setTimeout(check, 200); // keep checking until stop
        } else {
            self._position = 0;
            if (callback) callback();
        }
    }
    check();
}

GenieMini.prototype.setMotorPosition = function(motor, position, callback) {
    var self = this;
    var check = function() {
        if(self._moving) {
            setTimeout(check, 200); // keep checking until stop
        } else {
            console.log("GenieMini(" + self._id + "): updating position to", position);
            self._position = position;
            if (callback) callback();
        }
    }
    check();
}

module.exports = GenieMini;
<commit_msg>genie mini auto-detect orientation fix<commit_after>var util = require('util');
var EventEmitter = require('events').EventEmitter;

function GenieMini(id) {
    this.btServiceIds = ['f000474d04514000b000000000000000'];

    this._id = id;
    this._gmCh = null;
    this._dev = null;
    this._moving = false;
    this._angle = null;
    this._position = 0;
    this._currentMove = null;
    this._stepsPerDegree = 186.56716418;

    this.orientation = null;
}

util.inherits(GenieMini, EventEmitter);

GenieMini.prototype._connectBt = function(btPeripheral, callback) {
    var self = this;
    btPeripheral.connect(function(err1) {
        if (err1) {
            if (callback) callback(false);
            return;
        }
        console.log('GenieMini(' + self._id + '): connecting via BLE');
        setTimeout(function(){
            btPeripheral.discoverServices(self.btServiceIds, function(err2, services) {
                if (services && services[0]) {
                    services[0].discoverCharacteristics([], function(err, characteristics) {
                        //console.log("characteristics:", characteristics);
                        self._gmCh = null;
                        self._dev = null;
                        for (var i = 0; i < characteristics.length; i++) {
                            ch = characteristics[i];
                            console.log("ch.uuid", ch.uuid);
                            if (ch.uuid == "2") {
                                self._gmCh = ch;
                            }
                        }
                        if (self._gmCh) {
                            try {
                                self._gmCh.write(new Buffer("012600", 'hex'));
                                self._gmCh.subscribe(function(){
                                    self._dev = btPeripheral;
                                    self._dev.connected = true;
                                    self.connected = true;
                                    self._dev.type = "bt";
                                    self._gmCh.on('data', function(data, isNotification) {
                                        self._parseIncoming(data);
                                    });
                                    console.log("GenieMini(" + self._id + "): connected!");
                                    self._init();
                                    if (callback) callback(true);
                                });
                            } catch(err3) {
                                btPeripheral.disconnect();
                                console.log("GenieMini(" + self._id + "): exception while connecting: ", err3);
                                if (callback) callback(false);
                            }
                        } else {
                            console.log("GenieMini(" + self._id + "): couldn't locate characteristics, disconnecting... ", err);
                            btPeripheral.disconnect();
                            if (callback) callback(false);
                        }
                    });
                } else {
                    console.log("GenieMini(" + self._id + "): couldn't locate services, disconnecting... ", err2);
                    btPeripheral.disconnect();
                    if (callback) callback(false);
                }

            });
        }, 1000);

        btPeripheral.once('disconnect', function() {
            console.log("GenieMini(" + self._id + "): disconnected");
            self._dev = null;
            self.connected = false;
            self.emit("status", self.getStatus());
        });

    });
}

GenieMini.prototype._init = function() {
    var self = this;
    var dataBuf = new Buffer(4);
    dataBuf.fill(0);
    dataBuf.writeInt32LE(steps, 0);
    this._write(0x005E, dataBuf, function(err) { // checks orientation
        self.emit("status", self.getStatus());
    });
}

GenieMini.prototype._parseIncoming = function(data) {
    var id = data.readUInt16LE(1);
    if(id == 0x0060 || id == 0x0061) { // moving steps
        var state = data.readUInt8(7);
        var steps = data.readInt16LE(3);
        this._moving = (state == 0x01);
        console.log("GenieMini(" + this._id + "): moving: ", this._moving, ", steps:", steps);
    } else if(id == 0x000B || id == 0x0052) { // current state
        var state = data.readUInt8(4);
        var angle = data.readUInt16LE(12);
        this.orientation = (id == 0x000B ? 'pan' : 'tilt');
        this._angle = angle;
        this._moving = (state == 0x01);
        console.log("GenieMini(" + this._id + "): moving: ", this._moving, ", angle:", angle);
        if(this._currentMove != null && this._lastAngle != null) {
            if(Math.abs(angle - this._lastAngle) < 180) {
                this._currentMove += angle - this._lastAngle;
            } else {
                if(this._lastAngle < angle) {
                    this._currentMove += 359 - (angle - this._lastAngle);
                } else {
                    this._currentMove -= 359 - (this._lastAngle - angle);
                }
            }
            console.log("GenieMini(" + this._id + "): _currentMove", this._currentMove);
        }
        this._lastAngle = angle;
    } else {
        console.log("GenieMini(" + this._id + "): unknown id", id, "data", data);
    }
}

GenieMini.prototype.connect = function(device, callback) {
    if (device && device.connect) {
        this._moving = false;
        console.log("GenieMini(" + this._id + "): connecting...");
        this._connectBt(device, callback);
    } else {
        console.log("GenieMini(" + this._id + "): invalid device, cannot connect");
        if (callback) callback("invalid device");
    }
}

GenieMini.prototype.disconnect = function() {
    console.log("GenieMini(" + this._id + "): disconnecting...");
    if (this._dev && this._dev.connected) {
        this.connected = false;
        this._dev.connected = false;
        this._dev.disconnect();
    } else {
        this._dev = null;
    }
    this.emit("status", this.getStatus());
}

GenieMini.prototype.getStatus = function() {
    var type = (this._dev && this._dev.type) ? this._dev.type : null;
    return {
        connected: this._dev && this._dev.connected,
        connectionType: type,
        motor1: this._dev && this._dev.connected,
        motor1pos: this._position / this._stepsPerDegree,
    }
}

GenieMini.prototype.disable = function(motor) {
    this._enabled = false;
}

GenieMini.prototype.enable = function(motor) {
    this._enabled = true;
}

GenieMini.prototype.move = function(motor, degrees, callback) {
    if (this._moving) {
        if (callback) callback("GenieMini(" + this._id + "): motor already running");
        return console.log("GenieMini(" + this._id + "): motor already running");
    }
    var steps = Math.round(degrees * this._stepsPerDegree);
    if(!steps) {
        console.log("GenieMini(" + this._id + "): NOT moving motor", steps, "steps, (", degrees, " degrees)");
        return callback && callback(null, this._position / this._stepsPerDegree);
    }
    console.log("GenieMini(" + this._id + "): moving motor", steps, "steps");
    if(!this._enabled) this.enable();

    var dataBuf = new Buffer(4);
    dataBuf.fill(0);
    dataBuf.writeInt32LE(steps, 0);
    this._moving = true;

    var self = this;
    self._write(0x005E, dataBuf, function(err) {
        if (!err) {
            var check = function() {
                setTimeout(function() {
                    if(self._moving === undefined) return check();;
                    if(self._moving) {
                        check(); // keep checking until stop
                    } else {
                        self._position += steps;
                        console.log("GenieMini(" + self._id + "): position:", self._position);
                        if (callback) callback(null, self._position / self._stepsPerDegree);
                    }
                }, 200);
            }
            check();
        } else {
            if (callback) callback(err);
            self._moving = false;
        }
    });
}
//<Buffer 01 5e 00 00 00 03 a4 00>
GenieMini.prototype._write = function(command, dataBuf, callback) {
    var template = new Buffer("010000", 'hex');
    var len = dataBuf ? dataBuf.length : 0;
    var COMMAND = 0;

    template.writeUInt16LE(parseInt(command), 1);

    var cmd;
    if (dataBuf) {
        cmd = Buffer.concat([template, dataBuf]);
    } else {
        cmd = template;
    }

    //console.log("GenieMini(" + this._id + "): sending data", cmd);

    try {
        this._gmCh.write(cmd);
        callback && callback();
    } catch(err) {
        callback && callback(err);
    }
}

GenieMini.prototype.constantMove = function(motor, speed, callback) {
    console.log("GenieMini(" + this._id + "): moving motor at speed ", speed, "%");
    if(!this._enabled) this.enable();

    var dataBuf = new Buffer(3);
    var self = this;
    dataBuf.fill(0);
    dataBuf.writeInt8(Math.round(speed), 0);
    dataBuf.writeUInt16LE(speed ? 0x01 : 0x00, 1);
    this._moving = true;

    if(this._watchdog) {
        clearTimeout(this._watchdog);
        this._watchdog = null;
    }
    if(speed) {
        this._watchdog = setTimeout(function(){
            console.log("GenieMini(" + self._id + "): stopping via watchdog");
            self.constantMove(motor, 0);
        }, 3000);
    }

    var sendMove = function() {
        self._write(0x003F, dataBuf, function(err) {
            if (!err) {
                var check = function() {
                    if(this._endTimer) clearTimeout(this._endTimer);
                    if(!speed) {
                        self._endTimer = setTimeout(function() {
                            if(self._moving === undefined) return;
                            if(self._moving) {
                                check(); // keep checking until stop
                            } else {
                                console.log("GenieMini(" + self._id + "): adding", self._currentMove, "° to position");
                                self._position += self._currentMove * self._stepsPerDegree;
                                self._currentMove = null;
                                if (callback) callback(null, self._position / self._stepsPerDegree);
                            }
                        }, 200);
                    } else {
                        if (callback) callback(null, null);
                    }
                }
                check();
            } else {
                if (callback) callback(err);
                self._moving = false;
            }
        });
    }

    var self = this;
    if(this._endTimer) clearTimeout(this._endTimer);
    if(self._currentMove == null) {
        self._lastAngle = null;
        self._currentMove = 0;
        console.log("GenieMini(" + self._id + "): starting move at speed, saving position");
        var d = new Buffer(2);
        d.fill(0);
        d.writeUInt16LE(0x000B, 0);
        this._moving = true;
        self._write(0x001E, d, function(err) {
            sendMove();
        });
    } else {
        sendMove();
    }
}

GenieMini.prototype.resetMotorPosition = function(motor, callback) {
    var self = this;
    var check = function() {
        if(self._moving) {
            setTimeout(check, 200); // keep checking until stop
        } else {
            self._position = 0;
            if (callback) callback();
        }
    }
    check();
}

GenieMini.prototype.setMotorPosition = function(motor, position, callback) {
    var self = this;
    var check = function() {
        if(self._moving) {
            setTimeout(check, 200); // keep checking until stop
        } else {
            console.log("GenieMini(" + self._id + "): updating position to", position);
            self._position = position;
            if (callback) callback();
        }
    }
    check();
}

module.exports = GenieMini;
<|endoftext|>


<commit_before>'use strict';
const path = require('path');
const assert = require('yeoman-assert');
const helpers = require('yeoman-test');

var promptsParamsInit = {
  apiKey: 'this is the apikey',
  apiSecret: 'this is the apiSecret',
  btcTradingLimit: '123',
  strategy: 'BB',
  buyLevel: '456',
  gainLevel: '789',
  currencies: ['ABC', 'JKL', 'XYZ'],
  currenciesToStart: ['ABC', 'XYZ']
};

var promptsParamsAdd = {
  currencyToAdd: 'MNO',
  apiKey: 'this is the apikey',
  apiSecret: 'this is the apiSecret',
  btcTradingLimit: '987',
  strategy: 'GAIN',
  buyLevel: '654',
  gainLevel: '321',
  startCurrencyToAdd: true
};

describe('generator-gunbot:app init', () => {
  beforeEach(() => {
    return helpers.run(path.join(__dirname, '../generators/app'))
      .withArguments(['init'])
      .withPrompts({
        apiKey: promptsParamsInit.apiKey,
        apiSecret: promptsParamsInit.apiSecret,
        btcTradingLimit: promptsParamsInit.btcTradingLimit,
        strategy: promptsParamsInit.strategy,
        buyLevel: promptsParamsInit.buyLevel,
        gainLevel: promptsParamsInit.gainLevel,
        currencies: promptsParamsInit.currencies,
        currenciesToStart: promptsParamsInit.currenciesToStart
      });
  });

  it('creates files', () => {
    assert.file([
      'base.config.js',
      'BTC_ABC-config.js',
      'BTC_JKL-config.js',
      'BTC_XYZ-config.js'
    ]);
  });

  it('creates sets the content of the files', () => {
    assert.fileContent([
      ['base.config.js', `BTC_TRADING_LIMIT: ${promptsParamsInit.btcTradingLimit},`],
      ['BTC_ABC-config.js', `BTC_TRADING_LIMIT: ${promptsParamsInit.btcTradingLimit},`],
      ['BTC_JKL-config.js', `BTC_TRADING_LIMIT: ${promptsParamsInit.btcTradingLimit},`],
      ['BTC_XYZ-config.js', `BTC_TRADING_LIMIT: ${promptsParamsInit.btcTradingLimit},`],

      ['base.config.js', `SELL_STRATEGY: '${promptsParamsInit.strategy}',`],
      ['BTC_ABC-config.js', `SELL_STRATEGY: '${promptsParamsInit.strategy}',`],
      ['BTC_JKL-config.js', `SELL_STRATEGY: '${promptsParamsInit.strategy}',`],
      ['BTC_XYZ-config.js', `SELL_STRATEGY: '${promptsParamsInit.strategy}',`]
    ]);
  });
});

describe('generator-gunbot:app add', () => {
  beforeEach(() => {
    return helpers.run(path.join(__dirname, '../generators/app'))
      .withArguments(['add'])
      .withPrompts({
        apiKey: promptsParamsAdd.apiKey,
        apiSecret: promptsParamsAdd.apiSecret,
        btcTradingLimit: promptsParamsAdd.btcTradingLimit,
        strategy: promptsParamsAdd.strategy,
        buyLevel: promptsParamsAdd.buyLevel,
        gainLevel: promptsParamsAdd.gainLevel,
        currencyToAdd: promptsParamsAdd.currencyToAdd,
        startCurrencyToAdd: promptsParamsAdd.startCurrencyToAdd
      });
  });

  it('creates files', () => {
    assert.file([
      'BTC_MNO-config.js'
    ]);

    assert.noFile([
      'base-config.js',
      'BTC_ABC-config.js',
      'BTC_JKL-config.js',
      'BTC_XYZ-config.js'
    ]);
  });

  it('creates sets the content of the files', () => {
    assert.fileContent([
      ['BTC_MNO-config.js', `BTC_TRADING_LIMIT: ${promptsParamsAdd.btcTradingLimit},`]
    ]);
  });
});
<commit_msg>Using const instead of var.<commit_after>'use strict';
const path = require('path');
const assert = require('yeoman-assert');
const helpers = require('yeoman-test');

const promptsParamsInit = {
  apiKey: 'this is the apikey',
  apiSecret: 'this is the apiSecret',
  btcTradingLimit: '123',
  strategy: 'BB',
  buyLevel: '456',
  gainLevel: '789',
  currencies: ['ABC', 'JKL', 'XYZ'],
  currenciesToStart: ['ABC', 'XYZ']
};

const promptsParamsAdd = {
  currencyToAdd: 'MNO',
  apiKey: 'this is the apikey',
  apiSecret: 'this is the apiSecret',
  btcTradingLimit: '987',
  strategy: 'GAIN',
  buyLevel: '654',
  gainLevel: '321',
  startCurrencyToAdd: true
};

describe('generator-gunbot:app init', () => {
  beforeEach(() => {
    return helpers.run(path.join(__dirname, '../generators/app'))
      .withArguments(['init'])
      .withPrompts({
        apiKey: promptsParamsInit.apiKey,
        apiSecret: promptsParamsInit.apiSecret,
        btcTradingLimit: promptsParamsInit.btcTradingLimit,
        strategy: promptsParamsInit.strategy,
        buyLevel: promptsParamsInit.buyLevel,
        gainLevel: promptsParamsInit.gainLevel,
        currencies: promptsParamsInit.currencies,
        currenciesToStart: promptsParamsInit.currenciesToStart
      });
  });

  it('creates files', () => {
    assert.file([
      'base.config.js',
      'BTC_ABC-config.js',
      'BTC_JKL-config.js',
      'BTC_XYZ-config.js'
    ]);
  });

  it('creates sets the content of the files', () => {
    assert.fileContent([
      ['base.config.js', `BTC_TRADING_LIMIT: ${promptsParamsInit.btcTradingLimit},`],
      ['BTC_ABC-config.js', `BTC_TRADING_LIMIT: ${promptsParamsInit.btcTradingLimit},`],
      ['BTC_JKL-config.js', `BTC_TRADING_LIMIT: ${promptsParamsInit.btcTradingLimit},`],
      ['BTC_XYZ-config.js', `BTC_TRADING_LIMIT: ${promptsParamsInit.btcTradingLimit},`],

      ['base.config.js', `SELL_STRATEGY: '${promptsParamsInit.strategy}',`],
      ['BTC_ABC-config.js', `SELL_STRATEGY: '${promptsParamsInit.strategy}',`],
      ['BTC_JKL-config.js', `SELL_STRATEGY: '${promptsParamsInit.strategy}',`],
      ['BTC_XYZ-config.js', `SELL_STRATEGY: '${promptsParamsInit.strategy}',`]
    ]);
  });
});

describe('generator-gunbot:app add', () => {
  beforeEach(() => {
    return helpers.run(path.join(__dirname, '../generators/app'))
      .withArguments(['add'])
      .withPrompts({
        apiKey: promptsParamsAdd.apiKey,
        apiSecret: promptsParamsAdd.apiSecret,
        btcTradingLimit: promptsParamsAdd.btcTradingLimit,
        strategy: promptsParamsAdd.strategy,
        buyLevel: promptsParamsAdd.buyLevel,
        gainLevel: promptsParamsAdd.gainLevel,
        currencyToAdd: promptsParamsAdd.currencyToAdd,
        startCurrencyToAdd: promptsParamsAdd.startCurrencyToAdd
      });
  });

  it('creates files', () => {
    assert.file([
      'BTC_MNO-config.js'
    ]);

    assert.noFile([
      'base-config.js',
      'BTC_ABC-config.js',
      'BTC_JKL-config.js',
      'BTC_XYZ-config.js'
    ]);
  });

  it('creates sets the content of the files', () => {
    assert.fileContent([
      ['BTC_MNO-config.js', `BTC_TRADING_LIMIT: ${promptsParamsAdd.btcTradingLimit},`]
    ]);
  });
});
<|endoftext|>


<commit_before>;(function() {
  function Andro() {};

  var extend = function(target, extensions) {
    for(var property in extensions) {
      if(extensions[property] && extensions[property].constructor &&
         extensions[property].constructor === Object) {
        target[property] = extend(target[property] || {}, extensions[property]);
      }
      else {
        target[property] = extensions[property];
      }
    }

    return target;
  };

  var makeFn = function(fn, behaviour, args) {
    return function() {
      return fn.call(behaviour, args);
    };
  };

  Andro.prototype = {
    setup: function(owner) {
      if(this.isSetup(owner)) {
        throw "Object already set up, or has conflicting property called andro.";
      }
      else {
        owner.andro = {};
        owner.andro.behaviours = [];
        owner.andro.eventer = new Eventer();
      }
    },

    augment: function(owner, behaviourMixin, settings) {
      this.checkIsSetup(owner);
      if(behaviourMixin === undefined) {
        throw "You must pass a behaviour with which to augment the owner.";
      }

      var behaviour = {};
      owner.andro.behaviours.push(behaviour);
      extend(behaviour, behaviourMixin);

      // write exports to owner
      if(behaviour.setup !== undefined) {
        if(settings === undefined) {
          settings = {};
        }

        var exports = behaviour.setup(owner, owner.andro.eventer, settings);
        for(var name in exports) {
          if(owner[name] === undefined) {
            owner[name] = makeFn(exports[name], behaviour, arguments);
          }
          else {
            throw "Behaviour export would overwrite existing attribute on owner.";
          }
        }
      }

      return name; // return just in case user wants to do something mental
    },

    eventer: function(owner) {
      if(this.checkIsSetup(owner)) {
        return owner.andro.eventer;
      }
    },

    // Returns true if owner has andro obj on it.
    isSetup: function(owner) {
      return owner.andro !== undefined;
    },

    // Returns true if owner has andro obj on it, or throws exception if it doesn't.
    checkIsSetup: function(owner) {
      if(this.isSetup(owner)) {
        return true;
      }

      throw "This object is not set up for Andro.";
    }
  };

  function Eventer() {
    this.callbacks = {};
  }

  Eventer.prototype = {
    bind: function(obj, event, callback) {
      this.callbacks[event] = this.callbacks[event] || [];
      this.callbacks[event].push({
        obj: obj,
        callback: callback
      });

      return this;
    },

    unbind: function(obj, event) {
      for(var i in this.callbacks) {
        if(this.callbacks[i].obj === obj) {
          delete this.callbacks[i];
          break;
        }
      }
    },

    emit: function(event, data) {
      this.dispatch(event, data);
      return this;
    },

    dispatch: function(event, data) {
      var callbacks = this.callbacks[event];

      if(callbacks !== undefined) {
        for(var i = 0; i < callbacks.length; i++) {
          var callbackObj = callbacks[i];
          callbackObj.callback.call(callbackObj.obj, data);
        }
      }
    }
  };

  this.Andro = Andro;
}).call(this);
<commit_msg>Add identifying information to file.<commit_after>/*
  Andro.js
  by mary rose cook
  http://github.com/maryrosecook/androjs

  Mix behaviour into objects
*/

;(function() {
  function Andro() {};

  var extend = function(target, extensions) {
    for(var property in extensions) {
      if(extensions[property] && extensions[property].constructor &&
         extensions[property].constructor === Object) {
        target[property] = extend(target[property] || {}, extensions[property]);
      }
      else {
        target[property] = extensions[property];
      }
    }

    return target;
  };

  var makeFn = function(fn, behaviour, args) {
    return function() {
      return fn.call(behaviour, args);
    };
  };

  Andro.prototype = {
    setup: function(owner) {
      if(this.isSetup(owner)) {
        throw "Object already set up, or has conflicting property called andro.";
      }
      else {
        owner.andro = {};
        owner.andro.behaviours = [];
        owner.andro.eventer = new Eventer();
      }
    },

    augment: function(owner, behaviourMixin, settings) {
      this.checkIsSetup(owner);
      if(behaviourMixin === undefined) {
        throw "You must pass a behaviour with which to augment the owner.";
      }

      var behaviour = {};
      owner.andro.behaviours.push(behaviour);
      extend(behaviour, behaviourMixin);

      // write exports to owner
      if(behaviour.setup !== undefined) {
        if(settings === undefined) {
          settings = {};
        }

        var exports = behaviour.setup(owner, owner.andro.eventer, settings);
        for(var name in exports) {
          if(owner[name] === undefined) {
            owner[name] = makeFn(exports[name], behaviour, arguments);
          }
          else {
            throw "Behaviour export would overwrite existing attribute on owner.";
          }
        }
      }

      return name; // return just in case user wants to do something mental
    },

    eventer: function(owner) {
      if(this.checkIsSetup(owner)) {
        return owner.andro.eventer;
      }
    },

    // Returns true if owner has andro obj on it.
    isSetup: function(owner) {
      return owner.andro !== undefined;
    },

    // Returns true if owner has andro obj on it, or throws exception if it doesn't.
    checkIsSetup: function(owner) {
      if(this.isSetup(owner)) {
        return true;
      }

      throw "This object is not set up for Andro.";
    }
  };

  function Eventer() {
    this.callbacks = {};
  }

  Eventer.prototype = {
    bind: function(obj, event, callback) {
      this.callbacks[event] = this.callbacks[event] || [];
      this.callbacks[event].push({
        obj: obj,
        callback: callback
      });

      return this;
    },

    unbind: function(obj, event) {
      for(var i in this.callbacks) {
        if(this.callbacks[i].obj === obj) {
          delete this.callbacks[i];
          break;
        }
      }
    },

    emit: function(event, data) {
      this.dispatch(event, data);
      return this;
    },

    dispatch: function(event, data) {
      var callbacks = this.callbacks[event];

      if(callbacks !== undefined) {
        for(var i = 0; i < callbacks.length; i++) {
          var callbackObj = callbacks[i];
          callbackObj.callback.call(callbackObj.obj, data);
        }
      }
    }
  };

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