<commit_before>/**
 * @overview Book and Pages Generator Lib
 * @module books
 * @author Dominik Sigmund
 * @version 0.1
 * @description This Lib is the new way to create Pages and Books.
 * @memberof resilienz
 * @requires module:fs
 * @requires module:path
 * @requires module:child_process
 * @requires module:fs-copy-file-sync
 * @requires bin:imagemagick
 */
var fs = require('fs')
var path = require('path')
const execSync = require('child_process').execSync
const copyFileSync = require('fs-copy-file-sync')
function Books (database, layoutpath, pagespath, imagepath, bookpath) {
  var self = {}
  self.database = database
  self.layoutpath = layoutpath
  self.pagespath = pagespath
  self.imagepath = imagepath
  self.bookpath = bookpath
  self.createPage = createPage
  self.getPages = getPages
  self.printTextField = printTextField
  self.addImage = addImage
  self.printNumber = printNumber
  self.printAllNumbers = printAllNumbers
  self.createIndex = createIndex
  self.copyFixed = copyFixed
  self.combinePages = combinePages
  self.makeBook = makeBook
  self.getBook = getBook
  self.rescaleImage = rescaleImage
  return self
}

function createPage (actionid, category, page, language, callback) { // -> creates one page
  var self = this
  self.database.getLayoutInfo(actionid, page, function (error, objects) {
    if (error) {
      return callback(error)
    } else {
      var values = 0
      for (let index = 0; index < objects.length; index++) {
        const o = objects[index]
        if (o.value !== '') {
          values++
        }
      }
      if (values === 0) {
        if (category === '1') {
          copyFileSync(path.join(self.layoutpath, language, objects[0].background), path.join(self.pagespath, actionid) + '/' + category.padStart(2, '0') + '-' + page.padStart(2, '0') + '.png')
          callback(null, true)
        } else {
          console.log('Deleting ' + path.join(self.pagespath, actionid) + '/' + category.padStart(2, '0') + '-' + page.padStart(2, '0') + '.png')
          fs.unlinkSync(path.join(self.pagespath, actionid) + '/' + category.padStart(2, '0') + '-' + page.padStart(2, '0') + '.png')
          console.log('Deleting ' + path.join(self.pagespath, actionid) + '/' + category.padStart(2, '0') + '-' + (parseInt(page) + 1).toString().padStart(2, '0') + '.png')
          fs.unlinkSync(path.join(self.pagespath, actionid) + '/' + category.padStart(2, '0') + '-' + (parseInt(page) + 1).toString().padStart(2, '0') + '.png')
          callback(null, true)
        }
      } else {
        var background = path.join(self.layoutpath, language, objects[0].background)
        var target = path.join(self.pagespath, actionid) + '/' + category.padStart(2, '0') + '-tmp' + page.padStart(2, '0') + '.png'
        copyFileSync(background, target)
        for (let index = 0; index < objects.length; index++) {
          const object = objects[index]
          if (object.type === 'image' && object.value !== '') {
            self.addImage(target, object)
          } else if (object.type === 'text' && object.value !== '') { // text
            self.printTextField(target, object, 'Helvetica', 170, '#000000')
          }
        }
        // split page, save second as page+1
        if (category === '1') {
          execSync('cp ' + target + ' ' + path.join(self.pagespath, actionid) + '/' + category.padStart(2, '0') + '-' + page.padStart(2, '0') + '.png')
        } else {
          execSync('convert ' + target + ' -crop 2127x3072+0+0 ' + path.join(self.pagespath, actionid) + '/' + category.padStart(2, '0') + '-' + page.padStart(2, '0') + '.png')
          execSync('convert ' + target + ' -crop 2127x3072+2128+0 ' + path.join(self.pagespath, actionid) + '/' + category.padStart(2, '0') + '-' + (parseInt(page) + 1).toString().padStart(2, '0') + '.png')
        }
        fs.unlinkSync(target)
        callback(null, true)
      }
    }
  })
}
function printTextField (target, object, font, size, color) { // -> adds text to page
  var cmd = 'convert ' + target + ' -size ' + object.width + 'x' + object.height + ' -family "' + font + '" -pointsize ' + size + ' -density 27.8x28.2 -fill "' + color + '" -annotate +' + object.x + '+' + object.y + ' "' + object.value + '" ' + target
  console.log(cmd)
  var output = execSync(cmd, true)
  if (output.stderr || output.stdout) {
    console.log(output.stdout)
    console.error(output.stderr)
    return false
  } else {
    return true
  }
}
function addImage (target, object) { // -> add image to page
  var imgname = object.value
  object.value = this.imagepath + '/' + object.value
  var rscmd = 'convert ' + object.value + ' -resize ' + object.width + 'x' + object.height + ' -background white -compose Copy -gravity center -extent ' + object.width + 'x' + object.height + ' /tmp/' + imgname
  console.log(rscmd)
  var output = execSync(rscmd, true)
  object.value = '/tmp/' + imgname
  var cmd = ''
  if (object.spin !== 0) {
    cmd = 'convert ' + target + ' null: \\( ' + object.value + ' -sample ' + object.width + 'x' + object.height + '! -background None -rotate ' + object.spin + ' \\) -geometry \'+' + object.x + '+' + object.y + '\' -layers Composite ' + target
  } else {
    cmd = 'composite -geometry \'' + object.width + 'x' + object.height + '!+' + object.x + '+' + object.y + '\' ' + object.value + ' ' + target + ' ' + target
  }
  console.log(cmd)
  output = execSync(cmd, true)
  if (output.stderr || output.stdout) {
    console.log(output.stdout)
    console.error(output.stderr)
    return false
  } else {
    return true
  }
}
function printNumber (actionid, page) { // -> prints number on page
  var gravity = ''
  var coordinates = ''
  if (parseInt(page.realNumber) % 2 === 0) {
    gravity = 'southwest'
    coordinates = '+50+10'
  } else {
    gravity = 'southwest'
    coordinates = '+2000+10'
  }
  var cmd = 'convert ' + page.path + ' -font Helvetica -pointsize 180 -density 27.8x28.2 -gravity ' + gravity + ' -stroke \'#000C\' -strokewidth 8 -annotate ' + coordinates + ' \'' + page.realNumber + '\' -stroke none -fill white -annotate ' + coordinates + ' \'' + page.realNumber + '\' ' + page.path
  console.log(cmd)
  var output = execSync(cmd, true)
  if (output.stderr || output.stdout) {
    return output.stderr || output.stdout
  } else {
    return null
  }
}
function printAllNumbers (actionid, pages, callback) { // -> call printNumber for all pages
  for (let index = 0; index < pages.length; index++) {
    const page = pages[index]
    if (page.cat !== '00' && page.cat !== '01' && page.cat !== '99') {
      this.printNumber(actionid, page)
    }
  }
  callback(null)
}
function createIndex (actionid, language, pages, callback) { //  -> print indexpage (3) 01-03.png
  var self = this
  self.database.getCatNamesByLanguage(language, function (error, translations) {
    if (error) {
      callback(error)
    } else {
      copyFileSync(self.layoutpath + '/' + language + '/02_inhalt.png', self.pagespath + '/' + actionid + '/01-03.png')
      var startY = 1228
      for (let index = 1; index < translations.length; index++) {
        const chapter = translations[index]
        if (chapter.id === 2) {
          self.printTextField(self.pagespath + '/' + actionid + '/01-03.png', { // use printTextField to print first chapter to 02_01.png
            width: 1524,
            height: 136,
            x: 160,
            y: 700,
            value: chapter.translation + ' ... 2'
          }, 'Tempus Sans ITC TT', 198, '#0068d9')
        } else {
          var startpage = pages.find(function (x) {
            return parseInt(x.cat) === parseInt(chapter.id)
          })
          if (startpage) {
            self.printTextField(self.pagespath + '/' + actionid + '/01-03.png', { // use printTextField to print  chapter-list to 02_01.png
              width: 1524,
              height: 136,
              x: 160,
              y: startY,
              value: chapter.translation
            }, 'Tempus Sans ITC TT', 198, '#0068d9')
            self.printTextField(self.pagespath + '/' + actionid + '/01-03.png', { // use printTextField to print  chapter-list to 02_01.png
              width: 200,
              height: 136,
              x: 1880,
              y: startY,
              value: startpage.realNumber
            }, 'Tempus Sans ITC TT', 198, '#0068d9')
            startY += 120
          }
        }
      }
      callback(null)
    }
  })
}
function getPages (actionid, language, callback) { //  -> get All Pages from Folder
  var self = this
  fs.readdir(path.join(this.pagespath, actionid), function (error, files) {
    if (error) {
      callback(error)
    } else {
      var pages = []
      for (let index = 0; index < files.length; index++) {
        const file = files[index]
        if (path.extname(file) === '.png') {
          var name = path.basename(file, '.png')
          var nsplit = name.split('-')
          var po = {
            page: parseInt(nsplit[1]),
            cat: parseInt(nsplit[0]),
            realNumber: -1,
            path: path.join(self.pagespath, actionid, file)
          }
          console.log(po)
          pages.push(po)
        }
      }
      // sort by cat, page.
      pages.sort(function (a, b) {
        if (a.cat < b.cat) {
          return -1
        } else if (a.cat === b.cat) {
          if (a.page < b.page) {
            return -1
          } else if (a.page > b.page) {
            return 1
          } else { // a.page == b.page (should never happen)
            return 0
          }
        } else { // a.cat > b.cat
          return 1
        }
      })
      // create realNumber (loop, not for cats 00, 02, 99)
      var count = 1
      for (let index = 0; index < pages.length; index++) {
        const page = pages[index]
        if (page.cat !== '00' && page.cat !== '01' && page.cat !== '99') {
          page.realNumber = count
          count++
        }
      }
      callback(null, pages)
    }
  })
}
function copyFixed (actionid, language, callback) { // -> copies pages 2 and the last into the folder
  copyFileSync(path.join(this.layoutpath + '/' + language + '/02_vorwort.png'), path.join(this.pagespath + '/' + actionid + '/01-02.png'))
  copyFileSync(path.join(this.layoutpath + '/' + language + '/16_ruecken.png'), path.join(this.pagespath + '/' + actionid + '/99-00.png'))
  callback(null)
}
function combinePages (actionid, pages, callback) { // -> Combines Pages
  var pagesString = ''
  for (let index = 0; index < pages.length; index++) {
    const page = pages[index]
    pagesString += page.path + ' '
  }
  var cmd = 'convert ' + pagesString + ' -density 27.8x28.2 -page a4 -compress Zip ' + this.bookpath + '/' + actionid + '.pdf'
  console.log(cmd)
  var output = execSync(cmd, true)
  if (output.stderr || output.stdout) {
    console.log(output.stdout)
    console.error(output.stderr)
    callback(output)
  } else {
    callback(null, this.bookpath + '/' + actionid + '.pdf')
  }
}
function makeBook (actionid, language, callback) { // -> creates Book
  var self = this
  self.copyFixed(actionid, language, function (error) {
    if (error) {
      callback(error)
    } else {
      self.getPages(actionid, language, function (error, pages) {
        if (error) {
          callback(error)
        } else {
          self.createIndex(actionid, language, pages, function (error) {
            if (error) {
              callback(error)
            } else {
              self.printAllNumbers(actionid, pages, function (error) {
                if (error) {
                  callback(error)
                } else {
                  self.combinePages(actionid, pages, function (error, pdf) {
                    if (error) {
                      callback(error)
                    } else {
                      callback(null, pdf)
                    }
                  })
                }
              })
            }
          })
        }
      })
    }
  })
}
function getBook (actionid, callback) { // -> just send book
  var self = this
  fs.access(self.bookpath + '/' + actionid + '.pdf', fs.constants.R_OK | fs.constants.W_OK, function (error) {
    if (error) {
      callback(error)
    } else {
      callback(null, self.bookpath + '/' + actionid + '.pdf')
    }
  })
}
function rescaleImage (image, x, y, width, height, clientWidth, clientHeight, orgWidth, orgHeight, callback) { // -> rescales Images and saves it.
  var newWidth = (width * orgWidth) / clientWidth
  var newHeight = (width * orgHeight) / clientHeight
  var newX = (x * orgWidth) / clientWidth
  var newY = (y * orgHeight) / clientHeight
  var cmd = 'convert ' + image + ' -crop ' + newWidth + 'x' + newHeight + '' + '+' + newX + '+' + newY + ' ' + image
  console.log(cmd)
  var output = execSync(cmd, true)
  if (output.stderr || output.stdout) {
    console.log(output.stdout)
    console.error(output.stderr)
    callback(output)
  } else {
    callback(null)
  }
}
if (!String.prototype.padStart) {
  String.prototype.padStart = function padStart (targetLength, padString) {
    targetLength = targetLength >> 0 // truncate if number or convert non-number to 0;
    padString = String((typeof padString !== 'undefined' ? padString : ' '))
    if (this.length > targetLength) {
      return String(this)
    } else {
      targetLength = targetLength - this.length
      if (targetLength > padString.length) {
        padString += padString.repeat(targetLength / padString.length) // append to original to ensure we are longer than needed
      }
      return padString.slice(0, targetLength) + String(this)
    }
  }
}
module.exports = Books
<commit_msg>removed page on last page<commit_after>/**
 * @overview Book and Pages Generator Lib
 * @module books
 * @author Dominik Sigmund
 * @version 0.1
 * @description This Lib is the new way to create Pages and Books.
 * @memberof resilienz
 * @requires module:fs
 * @requires module:path
 * @requires module:child_process
 * @requires module:fs-copy-file-sync
 * @requires bin:imagemagick
 */
var fs = require('fs')
var path = require('path')
const execSync = require('child_process').execSync
const copyFileSync = require('fs-copy-file-sync')
function Books (database, layoutpath, pagespath, imagepath, bookpath) {
  var self = {}
  self.database = database
  self.layoutpath = layoutpath
  self.pagespath = pagespath
  self.imagepath = imagepath
  self.bookpath = bookpath
  self.createPage = createPage
  self.getPages = getPages
  self.printTextField = printTextField
  self.addImage = addImage
  self.printNumber = printNumber
  self.printAllNumbers = printAllNumbers
  self.createIndex = createIndex
  self.copyFixed = copyFixed
  self.combinePages = combinePages
  self.makeBook = makeBook
  self.getBook = getBook
  self.rescaleImage = rescaleImage
  return self
}

function createPage (actionid, category, page, language, callback) { // -> creates one page
  var self = this
  self.database.getLayoutInfo(actionid, page, function (error, objects) {
    if (error) {
      return callback(error)
    } else {
      var values = 0
      for (let index = 0; index < objects.length; index++) {
        const o = objects[index]
        if (o.value !== '') {
          values++
        }
      }
      if (values === 0) {
        if (category === '1') {
          copyFileSync(path.join(self.layoutpath, language, objects[0].background), path.join(self.pagespath, actionid) + '/' + category.padStart(2, '0') + '-' + page.padStart(2, '0') + '.png')
          callback(null, true)
        } else {
          console.log('Deleting ' + path.join(self.pagespath, actionid) + '/' + category.padStart(2, '0') + '-' + page.padStart(2, '0') + '.png')
          fs.unlinkSync(path.join(self.pagespath, actionid) + '/' + category.padStart(2, '0') + '-' + page.padStart(2, '0') + '.png')
          console.log('Deleting ' + path.join(self.pagespath, actionid) + '/' + category.padStart(2, '0') + '-' + (parseInt(page) + 1).toString().padStart(2, '0') + '.png')
          fs.unlinkSync(path.join(self.pagespath, actionid) + '/' + category.padStart(2, '0') + '-' + (parseInt(page) + 1).toString().padStart(2, '0') + '.png')
          callback(null, true)
        }
      } else {
        var background = path.join(self.layoutpath, language, objects[0].background)
        var target = path.join(self.pagespath, actionid) + '/' + category.padStart(2, '0') + '-tmp' + page.padStart(2, '0') + '.png'
        copyFileSync(background, target)
        for (let index = 0; index < objects.length; index++) {
          const object = objects[index]
          if (object.type === 'image' && object.value !== '') {
            self.addImage(target, object)
          } else if (object.type === 'text' && object.value !== '') { // text
            self.printTextField(target, object, 'Helvetica', 170, '#000000')
          }
        }
        // split page, save second as page+1
        if (category === '1') {
          execSync('cp ' + target + ' ' + path.join(self.pagespath, actionid) + '/' + category.padStart(2, '0') + '-' + page.padStart(2, '0') + '.png')
        } else {
          execSync('convert ' + target + ' -crop 2127x3072+0+0 ' + path.join(self.pagespath, actionid) + '/' + category.padStart(2, '0') + '-' + page.padStart(2, '0') + '.png')
          execSync('convert ' + target + ' -crop 2127x3072+2128+0 ' + path.join(self.pagespath, actionid) + '/' + category.padStart(2, '0') + '-' + (parseInt(page) + 1).toString().padStart(2, '0') + '.png')
        }
        fs.unlinkSync(target)
        callback(null, true)
      }
    }
  })
}
function printTextField (target, object, font, size, color) { // -> adds text to page
  var cmd = 'convert ' + target + ' -size ' + object.width + 'x' + object.height + ' -family "' + font + '" -pointsize ' + size + ' -density 27.8x28.2 -fill "' + color + '" -annotate +' + object.x + '+' + object.y + ' "' + object.value + '" ' + target
  console.log(cmd)
  var output = execSync(cmd, true)
  if (output.stderr || output.stdout) {
    console.log(output.stdout)
    console.error(output.stderr)
    return false
  } else {
    return true
  }
}
function addImage (target, object) { // -> add image to page
  var imgname = object.value
  object.value = this.imagepath + '/' + object.value
  var rscmd = 'convert ' + object.value + ' -resize ' + object.width + 'x' + object.height + ' -background white -compose Copy -gravity center -extent ' + object.width + 'x' + object.height + ' /tmp/' + imgname
  console.log(rscmd)
  var output = execSync(rscmd, true)
  object.value = '/tmp/' + imgname
  var cmd = ''
  if (object.spin !== 0) {
    cmd = 'convert ' + target + ' null: \\( ' + object.value + ' -sample ' + object.width + 'x' + object.height + '! -background None -rotate ' + object.spin + ' \\) -geometry \'+' + object.x + '+' + object.y + '\' -layers Composite ' + target
  } else {
    cmd = 'composite -geometry \'' + object.width + 'x' + object.height + '!+' + object.x + '+' + object.y + '\' ' + object.value + ' ' + target + ' ' + target
  }
  console.log(cmd)
  output = execSync(cmd, true)
  if (output.stderr || output.stdout) {
    console.log(output.stdout)
    console.error(output.stderr)
    return false
  } else {
    return true
  }
}
function printNumber (actionid, page) { // -> prints number on page
  var gravity = ''
  var coordinates = ''
  if (parseInt(page.realNumber) % 2 === 0) {
    gravity = 'southwest'
    coordinates = '+50+10'
  } else {
    gravity = 'southwest'
    coordinates = '+2000+10'
  }
  var cmd = 'convert ' + page.path + ' -font Helvetica -pointsize 180 -density 27.8x28.2 -gravity ' + gravity + ' -stroke \'#000C\' -strokewidth 8 -annotate ' + coordinates + ' \'' + page.realNumber + '\' -stroke none -fill white -annotate ' + coordinates + ' \'' + page.realNumber + '\' ' + page.path
  console.log(cmd)
  var output = execSync(cmd, true)
  if (output.stderr || output.stdout) {
    return output.stderr || output.stdout
  } else {
    return null
  }
}
function printAllNumbers (actionid, pages, callback) { // -> call printNumber for all pages
  for (let index = 0; index < pages.length; index++) {
    const page = pages[index]
    if (parseInt(page.cat) !== 1 && parseInt(page.cat !== 99)) {
      this.printNumber(actionid, page)
    }
  }
  callback(null)
}
function createIndex (actionid, language, pages, callback) { //  -> print indexpage (3) 01-03.png
  var self = this
  self.database.getCatNamesByLanguage(language, function (error, translations) {
    if (error) {
      callback(error)
    } else {
      copyFileSync(self.layoutpath + '/' + language + '/02_inhalt.png', self.pagespath + '/' + actionid + '/01-03.png')
      var startY = 1228
      for (let index = 1; index < translations.length; index++) {
        const chapter = translations[index]
        if (chapter.id === 2) {
          self.printTextField(self.pagespath + '/' + actionid + '/01-03.png', { // use printTextField to print first chapter to 02_01.png
            width: 1524,
            height: 136,
            x: 160,
            y: 700,
            value: chapter.translation + ' ... 2'
          }, 'Tempus Sans ITC TT', 198, '#0068d9')
        } else {
          var startpage = pages.find(function (x) {
            return parseInt(x.cat) === parseInt(chapter.id)
          })
          if (startpage) {
            self.printTextField(self.pagespath + '/' + actionid + '/01-03.png', { // use printTextField to print  chapter-list to 02_01.png
              width: 1524,
              height: 136,
              x: 160,
              y: startY,
              value: chapter.translation
            }, 'Tempus Sans ITC TT', 198, '#0068d9')
            self.printTextField(self.pagespath + '/' + actionid + '/01-03.png', { // use printTextField to print  chapter-list to 02_01.png
              width: 200,
              height: 136,
              x: 1880,
              y: startY,
              value: startpage.realNumber
            }, 'Tempus Sans ITC TT', 198, '#0068d9')
            startY += 120
          }
        }
      }
      callback(null)
    }
  })
}
function getPages (actionid, language, callback) { //  -> get All Pages from Folder
  var self = this
  fs.readdir(path.join(this.pagespath, actionid), function (error, files) {
    if (error) {
      callback(error)
    } else {
      var pages = []
      for (let index = 0; index < files.length; index++) {
        const file = files[index]
        if (path.extname(file) === '.png') {
          var name = path.basename(file, '.png')
          var nsplit = name.split('-')
          var po = {
            page: parseInt(nsplit[1]),
            cat: parseInt(nsplit[0]),
            realNumber: -1,
            path: path.join(self.pagespath, actionid, file)
          }
          console.log(po)
          pages.push(po)
        }
      }
      // sort by cat, page.
      pages.sort(function (a, b) {
        if (a.cat < b.cat) {
          return -1
        } else if (a.cat === b.cat) {
          if (a.page < b.page) {
            return -1
          } else if (a.page > b.page) {
            return 1
          } else { // a.page == b.page (should never happen)
            return 0
          }
        } else { // a.cat > b.cat
          return 1
        }
      })
      // create realNumber (loop, not for cats 00, 02, 99)
      var count = 1
      for (let index = 0; index < pages.length; index++) {
        const page = pages[index]
        if (page.cat !== '00' && page.cat !== '01' && page.cat !== '99') {
          page.realNumber = count
          count++
        }
      }
      callback(null, pages)
    }
  })
}
function copyFixed (actionid, language, callback) { // -> copies pages 2 and the last into the folder
  copyFileSync(path.join(this.layoutpath + '/' + language + '/02_vorwort.png'), path.join(this.pagespath + '/' + actionid + '/01-02.png'))
  copyFileSync(path.join(this.layoutpath + '/' + language + '/16_ruecken.png'), path.join(this.pagespath + '/' + actionid + '/99-00.png'))
  callback(null)
}
function combinePages (actionid, pages, callback) { // -> Combines Pages
  var pagesString = ''
  for (let index = 0; index < pages.length; index++) {
    const page = pages[index]
    pagesString += page.path + ' '
  }
  var cmd = 'convert ' + pagesString + ' -density 27.8x28.2 -page a4 -compress Zip ' + this.bookpath + '/' + actionid + '.pdf'
  console.log(cmd)
  var output = execSync(cmd, true)
  if (output.stderr || output.stdout) {
    console.log(output.stdout)
    console.error(output.stderr)
    callback(output)
  } else {
    callback(null, this.bookpath + '/' + actionid + '.pdf')
  }
}
function makeBook (actionid, language, callback) { // -> creates Book
  var self = this
  self.copyFixed(actionid, language, function (error) {
    if (error) {
      callback(error)
    } else {
      self.getPages(actionid, language, function (error, pages) {
        if (error) {
          callback(error)
        } else {
          self.createIndex(actionid, language, pages, function (error) {
            if (error) {
              callback(error)
            } else {
              self.printAllNumbers(actionid, pages, function (error) {
                if (error) {
                  callback(error)
                } else {
                  self.combinePages(actionid, pages, function (error, pdf) {
                    if (error) {
                      callback(error)
                    } else {
                      callback(null, pdf)
                    }
                  })
                }
              })
            }
          })
        }
      })
    }
  })
}
function getBook (actionid, callback) { // -> just send book
  var self = this
  fs.access(self.bookpath + '/' + actionid + '.pdf', fs.constants.R_OK | fs.constants.W_OK, function (error) {
    if (error) {
      callback(error)
    } else {
      callback(null, self.bookpath + '/' + actionid + '.pdf')
    }
  })
}
function rescaleImage (image, x, y, width, height, clientWidth, clientHeight, orgWidth, orgHeight, callback) { // -> rescales Images and saves it.
  var newWidth = (width * orgWidth) / clientWidth
  var newHeight = (width * orgHeight) / clientHeight
  var newX = (x * orgWidth) / clientWidth
  var newY = (y * orgHeight) / clientHeight
  var cmd = 'convert ' + image + ' -crop ' + newWidth + 'x' + newHeight + '' + '+' + newX + '+' + newY + ' ' + image
  console.log(cmd)
  var output = execSync(cmd, true)
  if (output.stderr || output.stdout) {
    console.log(output.stdout)
    console.error(output.stderr)
    callback(output)
  } else {
    callback(null)
  }
}
if (!String.prototype.padStart) {
  String.prototype.padStart = function padStart (targetLength, padString) {
    targetLength = targetLength >> 0 // truncate if number or convert non-number to 0;
    padString = String((typeof padString !== 'undefined' ? padString : ' '))
    if (this.length > targetLength) {
      return String(this)
    } else {
      targetLength = targetLength - this.length
      if (targetLength > padString.length) {
        padString += padString.repeat(targetLength / padString.length) // append to original to ensure we are longer than needed
      }
      return padString.slice(0, targetLength) + String(this)
    }
  }
}
module.exports = Books
<|endoftext|>


<commit_before>'use babel';

export default {
  config: require('./config'),

  activate() {
    if (!/^win/.test(process.platform)) {
      // Manually append /usr/local/bin as it may not be set on some systems,
      // and it's common to have node installed here. Keep it at end so it won't
      // accidentially override any other node installation
      process.env.PATH += ':/usr/local/bin';
    }

    this.instancedTools = {}; // Ordered by project path
    this.targets = {};
    this.activeTarget = {};
    this.targetsLoading = {};
    this.targetsSubscriptions = {};
    this.tools = [ require('./atom-build') ];
    this.linter = null;

    const BuildView = require('./build-view');
    this.buildView = new BuildView();

    const ErrorMatcher = require('./error-matcher');
    this.errorMatcher = new ErrorMatcher();
    this.errorMatcher.on('error', (message) => {
      atom.notifications.addError('Error matching failed!', { detail: message });
    });
    this.errorMatcher.on('matched', (match) => {
      this.buildView.scrollTo(match[0]);
    });

    atom.commands.add('atom-workspace', 'build:refresh-targets', () => this.refreshTargets());
    atom.commands.add('atom-workspace', 'build:trigger', () => this.build('trigger'));
    atom.commands.add('atom-workspace', 'build:select-active-target', () => this.selectActiveTarget());
    atom.commands.add('atom-workspace', 'build:stop', () => this.stop());
    atom.commands.add('atom-workspace', 'build:confirm', () => {
      require('./google-analytics').sendEvent('build', 'confirmed');
      document.activeElement.click();
    });
    atom.commands.add('atom-workspace', 'build:no-confirm', () => {
      if (this.saveConfirmView) {
        require('./google-analytics').sendEvent('build', 'not confirmed');
        this.saveConfirmView.cancel();
      }
    });

    atom.workspace.observeTextEditors((editor) => {
      editor.onDidSave(() => {
        if (atom.config.get('build.buildOnSave')) {
          this.build('save');
        }
      });
    });

    atom.workspace.onDidChangeActivePaneItem(() => this.updateStatusBar());
    atom.packages.onDidActivateInitialPackages(() => this.refreshTargets());

    let projectPaths = atom.project.getPaths();
    atom.project.onDidChangePaths(() => {
      const addedPaths = atom.project.getPaths().filter(el => projectPaths.indexOf(el) === -1);
      this.refreshTargets(addedPaths);
      projectPaths = atom.project.getPaths();
    });
  },

  deactivate() {
    Object.keys(this.instancedTools).map(cwd => this.instancedTools[cwd].forEach(tool => {
      tool.removeAllListeners && tool.removeAllListeners('refresh');
      tool.destructor && tool.destructor();
    }));

    if (this.child) {
      this.child.removeAllListeners();
      require('tree-kill')(this.child.pid, 'SIGKILL');
      this.child = null;
    }

    this.statusBarView && this.statusBarView.destroy();
    this.buildView && this.buildView.destroy();
    this.linter && this.linter.dispose();

    clearTimeout(this.finishedTimer);
  },

  updateStatusBar() {
    const activeTarget = this.activeTarget[require('./utils').activePath()];
    this.statusBarView && this.statusBarView.setTarget(activeTarget);
  },

  refreshTargets(refreshPaths) {
    refreshPaths = refreshPaths || atom.project.getPaths();

    const pathPromise = refreshPaths.map((p) => {
      this.targetsLoading[p] = true;
      this.targets[p] = this.targets[p] || [];

      this.instancedTools[p] = (this.instancedTools[p] || [])
        .map(t => t.removeAllListeners && t.removeAllListeners('refresh'))
        .filter(() => false); // Just empty the array

      const settingsPromise = this.tools
        .map(Tool => new Tool(p))
        .filter(tool => tool.isEligible())
        .map(tool => {
          this.instancedTools[p].push(tool);
          require('./google-analytics').sendEvent('build', 'tool eligible', tool.getNiceName());

          tool.on && tool.on('refresh', this.refreshTargets.bind(this, [ p ]));
          return Promise.resolve().then(() => tool.settings()).catch(err => {
            if (err instanceof SyntaxError) {
              atom.notifications.addError('Invalid build file.', {
                detail: 'You have a syntax error in your build file: ' + err.message,
                dismissable: true
              });
            } else {
              atom.notifications.addError('Ooops. Something went wrong.', {
                detail: err.message + (err.stack ? '\n' + err.stack : ''),
                dismissable: true
              });
            }
          });
        });

      const CompositeDisposable = require('atom').CompositeDisposable;
      return Promise.all(settingsPromise).then((settings) => {
        settings = require('./utils').uniquifySettings([].concat.apply([], settings)
          .filter(Boolean)
          .map(setting => require('./utils').getDefaultSettings(p, setting)));

        if (null === this.activeTarget[p] || !settings.find(s => s.name === this.activeTarget[p])) {
          /* Active target has been removed or not set. Set it to the highest prio target */
          this.activeTarget[p] = settings[0] ? settings[0].name : undefined;
        }

        // CompositeDisposable cannot be reused, so we must create a new instance on every refresh
        this.targetsSubscriptions[p] && this.targetsSubscriptions[p].dispose();
        this.targetsSubscriptions[p] = new CompositeDisposable();

        settings.forEach((setting, index) => {
          if (setting.keymap && !setting.atomCommandName) {
            setting.atomCommandName = `build:trigger:${setting.name}`;
          }
          const subscriptions = new CompositeDisposable();
          subscriptions.add(atom.commands.add('atom-workspace', setting.atomCommandName, this.build.bind(this, 'trigger')));

          if (setting.keymap) {
            require('./google-analytics').sendEvent('keymap', 'registered', setting.keymap);
            const keymapSpec = { 'atom-workspace, atom-text-editor': {} };
            keymapSpec['atom-workspace, atom-text-editor'][setting.keymap] = setting.atomCommandName;
            subscriptions.add(atom.keymaps.add(setting.name, keymapSpec));
          }

          this.targetsSubscriptions[p].add(subscriptions);
        });

        this.targets[p] = settings;
        this.targetsLoading[p] = false;
        this.fillTargets();
        this.updateStatusBar();
      });
    });

    Promise.all(pathPromise).then(entries => {
      if (entries.length === 0) {
        return;
      }

      if (atom.config.get('build.notificationOnRefresh')) {
        const rows = refreshPaths.map(p => `${this.targets[p].length} targets at: ${p}`);
        atom.notifications.addInfo('Build targets parsed.', {
          detail: rows.join('\n')
        });
      }
    });
  },

  fillTargets() {
    if (this.targetsView) {
      const p = require('./utils').activePath();
      this.targetsView.setActiveTarget(this.activeTarget[p]);
      this.targetsView.setItems((this.targets[p] || []).map(target => target.name));
    }
  },

  selectActiveTarget() {
    const p = require('./utils').activePath();
    const TargetsView = require('./targets-view');
    this.targetsView = new TargetsView();

    if (this.targetsLoading[p]) {
      this.targetsView.setLoading('Loading project build targets\u2026');
    } else {
      this.fillTargets();
    }

    this.targetsView.awaitSelection().then((newTarget) => {
      this.activeTarget[p] = newTarget;
      this.updateStatusBar();

      if (atom.config.get('build.selectTriggers')) {
        const workspaceElement = atom.views.getView(atom.workspace);
        atom.commands.dispatch(workspaceElement, 'build:trigger');
      }
      this.targetsView = null;
    }).catch((err) => {
      this.targetsView = null;
      this.targetsView.setError(err.message);
    });
  },

  startNewBuild(source, atomCommandName) {
    const BuildError = require('./build-error');
    const p = require('./utils').activePath();
    this.linter && this.linter.deleteMessages();

    Promise.resolve(this.targets[p]).then(targets => {
      if (!targets || 0 === targets.length) {
        throw new BuildError('No eligible build target.', 'No configuration to build this project exists.');
      }

      let target = targets.find(t => t.atomCommandName === atomCommandName);
      if (!target) {
        const targetName = this.activeTarget[p];
        target = targets.find(t => t.name === targetName);
      }
      require('./google-analytics').sendEvent('build', 'triggered');

      if (!target.exec) {
        throw new BuildError('Invalid build file.', 'No executable command specified.');
      }

      return Promise.resolve().then(() => {
        if (target.preBuild && typeof target.preBuild === 'function') {
          return target.preBuild();
        }
      }).then(() => target);
    }).then(target => {
      const replace = require('./utils').replace;
      const env = Object.assign({}, process.env, target.env);
      Object.keys(env).forEach(key => {
        env[key] = replace(env[key], target.env);
      });

      const exec = replace(target.exec, target.env);
      const args = target.args.map(arg => replace(arg, target.env));
      const cwd = replace(target.cwd, target.env);
      const isWin = process.platform === 'win32';
      const shCmd = isWin ? 'cmd' : '/bin/sh';
      const shCmdArg = isWin ? '/C' : '-c';

      if (target.sh) {
        this.child = require('child_process').spawn(
          shCmd,
          [ shCmdArg, [ require('path').normalize(exec) ].concat(args).join(' ')],
          { cwd: cwd, env: env }
        );
      } else {
        this.child = require('cross-spawn-async').spawn(
          exec,
          args,
          { cwd: cwd, env: env }
        );
      }

      let stdout = '';
      let stderr = '';
      this.child.stdout.setEncoding('utf8');
      this.child.stderr.setEncoding('utf8');
      this.child.stdout.on('data', d => stdout += d);
      this.child.stderr.on('data', d => stderr += d);
      this.child.stdout.pipe(this.buildView.terminal);
      this.child.stderr.pipe(this.buildView.terminal);

      this.child.on('error', (err) => {
        this.buildView.terminal.write((target.sh ? 'Unable to execute with shell: ' : 'Unable to execute: ') + exec + '\n');

        if (/\s/.test(exec) && !target.sh) {
          this.buildView.terminal.write('`cmd` cannot contain space. Use `args` for arguments.\n');
        }

        if ('ENOENT' === err.code) {
          this.buildView.terminal.write(`Make sure cmd:'${exec}' and cwd:'${cwd}' exists and have correct access permissions.\n`);
          this.buildView.terminal.write(`Binaries are found in these folders: ${process.env.PATH}\n`);
        }
      });

      this.child.on('close', (exitCode) => {
        this.errorMatcher.set(target.errorMatch, cwd, stdout + stderr);

        let success = (0 === exitCode);
        if (atom.config.get('build.matchedErrorFailsBuild')) {
          success = success && !this.errorMatcher.hasMatch();
        }

        this.linter && this.linter.setMessages(this.errorMatcher.getMatches().map(match => ({
          type: 'Error',
          text: match.message || 'Error from build',
          filePath: match.file,
          range: [[match.line - 1, match.col - 1], [match.line - 1, match.col - 1]]
        })));

        this.buildView.buildFinished(success);
        this.statusBarView && this.statusBarView.setBuildSuccess(success);

        if (atom.config.get('build.beepWhenDone')) {
          atom.beep();
        }

        if (success) {
          require('./google-analytics').sendEvent('build', 'succeeded');
          this.finishedTimer = setTimeout(() => {
            this.buildView.detach();
          }, 1200);
        } else {
          if (atom.config.get('build.scrollOnError')) {
            this.errorMatcher.matchFirst();
          }
          require('./google-analytics').sendEvent('build', 'failed');
        }
        this.child = null;

        if (target.postBuild && typeof target.postBuild === 'function') {
          return target.postBuild(success);
        }
      });

      this.statusBarView && this.statusBarView.buildStarted();
      this.buildView.buildStarted();
      this.buildView.setHeading([ (target.sh ? `${shCmd} ${shCmdArg} ${exec}` : exec ), ...args, '\n'].join(' '));
    }).catch((err) => {
      if (err instanceof BuildError) {
        if (source === 'save') {
          // If there is no eligible build tool, and cause of build was a save, stay quiet.
          return;
        }

        atom.notifications.addWarning(err.name, { detail: err.message });
      } else {
        atom.notifications.addError('Failed to build.', { detail: err.message });
      }
    });
  },

  abort(cb) {
    this.child.removeAllListeners('exit');
    this.child.removeAllListeners('close');
    this.child.on('exit', () => {
      this.child.removeAllListeners();
      this.child = null;
      cb && cb();
    });

    try {
      require('tree-kill')(this.child.pid, this.child.killed ? 'SIGKILL' : 'SIGTERM');
    } catch (e) {
      /* Something may have happened to the child (e.g. terminated by itself). Ignore this. */
    }

    this.child.killed = true;
  },

  build(source, event) {
    clearTimeout(this.finishedTimer);

    this.doSaveConfirm(this.unsavedTextEditors(), () => {
      const next = this.startNewBuild.bind(this, source, event ? event.type : null);
      this.child ? this.abort(next) : next();
    });
  },

  doSaveConfirm(modifiedTextEditors, continuecb, cancelcb) {
    const saveAndContinue = (save) => {
      modifiedTextEditors.forEach((textEditor) => save && textEditor.save());
      continuecb();
    };

    if (0 === modifiedTextEditors.length || atom.config.get('build.saveOnBuild')) {
      return saveAndContinue(true);
    }

    if (this.saveConfirmView) {
      this.saveConfirmView.destroy();
    }

    const SaveConfirmView = require('./save-confirm-view');
    this.saveConfirmView = new SaveConfirmView();
    this.saveConfirmView.show(saveAndContinue, cancelcb);
  },

  unsavedTextEditors() {
    return atom.workspace.getTextEditors().filter((textEditor) => {
      return textEditor.isModified() && (undefined !== textEditor.getPath());
    });
  },

  stop() {
    clearTimeout(this.finishedTimer);
    if (this.child) {
      if (!this.child.killed) {
        this.buildView.buildAbortInitiated();
      }
      this.abort(() => {
        this.buildView.buildAborted();
        this.statusBarView && this.statusBarView.buildAborted();
      });
    } else {
      this.buildView.reset();
    }
  },

  consumeLinterRegistry(registry) {
    this.linter = registry.register({ name: 'Build Linter' });
  },

  consumeBuilder(builder) {
    this.tools.push(builder);
    const Disposable = require('atom').Disposable;
    return new Disposable(() => {
      this.tools = this.tools.filter(tool => tool !== builder);
    });
  },

  consumeStatusBar(statusBar) {
    const StatusBarView = require('./status-bar-view');
    this.statusBarView = new StatusBarView(statusBar);
    this.statusBarView.onClick(() => this.selectActiveTarget());
    this.statusBarView.attach();
  }
};
<commit_msg>Linter name is `Build` not `Build Linter`.<commit_after>'use babel';

export default {
  config: require('./config'),

  activate() {
    if (!/^win/.test(process.platform)) {
      // Manually append /usr/local/bin as it may not be set on some systems,
      // and it's common to have node installed here. Keep it at end so it won't
      // accidentially override any other node installation
      process.env.PATH += ':/usr/local/bin';
    }

    this.instancedTools = {}; // Ordered by project path
    this.targets = {};
    this.activeTarget = {};
    this.targetsLoading = {};
    this.targetsSubscriptions = {};
    this.tools = [ require('./atom-build') ];
    this.linter = null;

    const BuildView = require('./build-view');
    this.buildView = new BuildView();

    const ErrorMatcher = require('./error-matcher');
    this.errorMatcher = new ErrorMatcher();
    this.errorMatcher.on('error', (message) => {
      atom.notifications.addError('Error matching failed!', { detail: message });
    });
    this.errorMatcher.on('matched', (match) => {
      this.buildView.scrollTo(match[0]);
    });

    atom.commands.add('atom-workspace', 'build:refresh-targets', () => this.refreshTargets());
    atom.commands.add('atom-workspace', 'build:trigger', () => this.build('trigger'));
    atom.commands.add('atom-workspace', 'build:select-active-target', () => this.selectActiveTarget());
    atom.commands.add('atom-workspace', 'build:stop', () => this.stop());
    atom.commands.add('atom-workspace', 'build:confirm', () => {
      require('./google-analytics').sendEvent('build', 'confirmed');
      document.activeElement.click();
    });
    atom.commands.add('atom-workspace', 'build:no-confirm', () => {
      if (this.saveConfirmView) {
        require('./google-analytics').sendEvent('build', 'not confirmed');
        this.saveConfirmView.cancel();
      }
    });

    atom.workspace.observeTextEditors((editor) => {
      editor.onDidSave(() => {
        if (atom.config.get('build.buildOnSave')) {
          this.build('save');
        }
      });
    });

    atom.workspace.onDidChangeActivePaneItem(() => this.updateStatusBar());
    atom.packages.onDidActivateInitialPackages(() => this.refreshTargets());

    let projectPaths = atom.project.getPaths();
    atom.project.onDidChangePaths(() => {
      const addedPaths = atom.project.getPaths().filter(el => projectPaths.indexOf(el) === -1);
      this.refreshTargets(addedPaths);
      projectPaths = atom.project.getPaths();
    });
  },

  deactivate() {
    Object.keys(this.instancedTools).map(cwd => this.instancedTools[cwd].forEach(tool => {
      tool.removeAllListeners && tool.removeAllListeners('refresh');
      tool.destructor && tool.destructor();
    }));

    if (this.child) {
      this.child.removeAllListeners();
      require('tree-kill')(this.child.pid, 'SIGKILL');
      this.child = null;
    }

    this.statusBarView && this.statusBarView.destroy();
    this.buildView && this.buildView.destroy();
    this.linter && this.linter.dispose();

    clearTimeout(this.finishedTimer);
  },

  updateStatusBar() {
    const activeTarget = this.activeTarget[require('./utils').activePath()];
    this.statusBarView && this.statusBarView.setTarget(activeTarget);
  },

  refreshTargets(refreshPaths) {
    refreshPaths = refreshPaths || atom.project.getPaths();

    const pathPromise = refreshPaths.map((p) => {
      this.targetsLoading[p] = true;
      this.targets[p] = this.targets[p] || [];

      this.instancedTools[p] = (this.instancedTools[p] || [])
        .map(t => t.removeAllListeners && t.removeAllListeners('refresh'))
        .filter(() => false); // Just empty the array

      const settingsPromise = this.tools
        .map(Tool => new Tool(p))
        .filter(tool => tool.isEligible())
        .map(tool => {
          this.instancedTools[p].push(tool);
          require('./google-analytics').sendEvent('build', 'tool eligible', tool.getNiceName());

          tool.on && tool.on('refresh', this.refreshTargets.bind(this, [ p ]));
          return Promise.resolve().then(() => tool.settings()).catch(err => {
            if (err instanceof SyntaxError) {
              atom.notifications.addError('Invalid build file.', {
                detail: 'You have a syntax error in your build file: ' + err.message,
                dismissable: true
              });
            } else {
              atom.notifications.addError('Ooops. Something went wrong.', {
                detail: err.message + (err.stack ? '\n' + err.stack : ''),
                dismissable: true
              });
            }
          });
        });

      const CompositeDisposable = require('atom').CompositeDisposable;
      return Promise.all(settingsPromise).then((settings) => {
        settings = require('./utils').uniquifySettings([].concat.apply([], settings)
          .filter(Boolean)
          .map(setting => require('./utils').getDefaultSettings(p, setting)));

        if (null === this.activeTarget[p] || !settings.find(s => s.name === this.activeTarget[p])) {
          /* Active target has been removed or not set. Set it to the highest prio target */
          this.activeTarget[p] = settings[0] ? settings[0].name : undefined;
        }

        // CompositeDisposable cannot be reused, so we must create a new instance on every refresh
        this.targetsSubscriptions[p] && this.targetsSubscriptions[p].dispose();
        this.targetsSubscriptions[p] = new CompositeDisposable();

        settings.forEach((setting, index) => {
          if (setting.keymap && !setting.atomCommandName) {
            setting.atomCommandName = `build:trigger:${setting.name}`;
          }
          const subscriptions = new CompositeDisposable();
          subscriptions.add(atom.commands.add('atom-workspace', setting.atomCommandName, this.build.bind(this, 'trigger')));

          if (setting.keymap) {
            require('./google-analytics').sendEvent('keymap', 'registered', setting.keymap);
            const keymapSpec = { 'atom-workspace, atom-text-editor': {} };
            keymapSpec['atom-workspace, atom-text-editor'][setting.keymap] = setting.atomCommandName;
            subscriptions.add(atom.keymaps.add(setting.name, keymapSpec));
          }

          this.targetsSubscriptions[p].add(subscriptions);
        });

        this.targets[p] = settings;
        this.targetsLoading[p] = false;
        this.fillTargets();
        this.updateStatusBar();
      });
    });

    Promise.all(pathPromise).then(entries => {
      if (entries.length === 0) {
        return;
      }

      if (atom.config.get('build.notificationOnRefresh')) {
        const rows = refreshPaths.map(p => `${this.targets[p].length} targets at: ${p}`);
        atom.notifications.addInfo('Build targets parsed.', {
          detail: rows.join('\n')
        });
      }
    });
  },

  fillTargets() {
    if (this.targetsView) {
      const p = require('./utils').activePath();
      this.targetsView.setActiveTarget(this.activeTarget[p]);
      this.targetsView.setItems((this.targets[p] || []).map(target => target.name));
    }
  },

  selectActiveTarget() {
    const p = require('./utils').activePath();
    const TargetsView = require('./targets-view');
    this.targetsView = new TargetsView();

    if (this.targetsLoading[p]) {
      this.targetsView.setLoading('Loading project build targets\u2026');
    } else {
      this.fillTargets();
    }

    this.targetsView.awaitSelection().then((newTarget) => {
      this.activeTarget[p] = newTarget;
      this.updateStatusBar();

      if (atom.config.get('build.selectTriggers')) {
        const workspaceElement = atom.views.getView(atom.workspace);
        atom.commands.dispatch(workspaceElement, 'build:trigger');
      }
      this.targetsView = null;
    }).catch((err) => {
      this.targetsView = null;
      this.targetsView.setError(err.message);
    });
  },

  startNewBuild(source, atomCommandName) {
    const BuildError = require('./build-error');
    const p = require('./utils').activePath();
    this.linter && this.linter.deleteMessages();

    Promise.resolve(this.targets[p]).then(targets => {
      if (!targets || 0 === targets.length) {
        throw new BuildError('No eligible build target.', 'No configuration to build this project exists.');
      }

      let target = targets.find(t => t.atomCommandName === atomCommandName);
      if (!target) {
        const targetName = this.activeTarget[p];
        target = targets.find(t => t.name === targetName);
      }
      require('./google-analytics').sendEvent('build', 'triggered');

      if (!target.exec) {
        throw new BuildError('Invalid build file.', 'No executable command specified.');
      }

      return Promise.resolve().then(() => {
        if (target.preBuild && typeof target.preBuild === 'function') {
          return target.preBuild();
        }
      }).then(() => target);
    }).then(target => {
      const replace = require('./utils').replace;
      const env = Object.assign({}, process.env, target.env);
      Object.keys(env).forEach(key => {
        env[key] = replace(env[key], target.env);
      });

      const exec = replace(target.exec, target.env);
      const args = target.args.map(arg => replace(arg, target.env));
      const cwd = replace(target.cwd, target.env);
      const isWin = process.platform === 'win32';
      const shCmd = isWin ? 'cmd' : '/bin/sh';
      const shCmdArg = isWin ? '/C' : '-c';

      if (target.sh) {
        this.child = require('child_process').spawn(
          shCmd,
          [ shCmdArg, [ require('path').normalize(exec) ].concat(args).join(' ')],
          { cwd: cwd, env: env }
        );
      } else {
        this.child = require('cross-spawn-async').spawn(
          exec,
          args,
          { cwd: cwd, env: env }
        );
      }

      let stdout = '';
      let stderr = '';
      this.child.stdout.setEncoding('utf8');
      this.child.stderr.setEncoding('utf8');
      this.child.stdout.on('data', d => stdout += d);
      this.child.stderr.on('data', d => stderr += d);
      this.child.stdout.pipe(this.buildView.terminal);
      this.child.stderr.pipe(this.buildView.terminal);

      this.child.on('error', (err) => {
        this.buildView.terminal.write((target.sh ? 'Unable to execute with shell: ' : 'Unable to execute: ') + exec + '\n');

        if (/\s/.test(exec) && !target.sh) {
          this.buildView.terminal.write('`cmd` cannot contain space. Use `args` for arguments.\n');
        }

        if ('ENOENT' === err.code) {
          this.buildView.terminal.write(`Make sure cmd:'${exec}' and cwd:'${cwd}' exists and have correct access permissions.\n`);
          this.buildView.terminal.write(`Binaries are found in these folders: ${process.env.PATH}\n`);
        }
      });

      this.child.on('close', (exitCode) => {
        this.errorMatcher.set(target.errorMatch, cwd, stdout + stderr);

        let success = (0 === exitCode);
        if (atom.config.get('build.matchedErrorFailsBuild')) {
          success = success && !this.errorMatcher.hasMatch();
        }

        this.linter && this.linter.setMessages(this.errorMatcher.getMatches().map(match => ({
          type: 'Error',
          text: match.message || 'Error from build',
          filePath: match.file,
          range: [[match.line - 1, match.col - 1], [match.line - 1, match.col - 1]]
        })));

        this.buildView.buildFinished(success);
        this.statusBarView && this.statusBarView.setBuildSuccess(success);

        if (atom.config.get('build.beepWhenDone')) {
          atom.beep();
        }

        if (success) {
          require('./google-analytics').sendEvent('build', 'succeeded');
          this.finishedTimer = setTimeout(() => {
            this.buildView.detach();
          }, 1200);
        } else {
          if (atom.config.get('build.scrollOnError')) {
            this.errorMatcher.matchFirst();
          }
          require('./google-analytics').sendEvent('build', 'failed');
        }
        this.child = null;

        if (target.postBuild && typeof target.postBuild === 'function') {
          return target.postBuild(success);
        }
      });

      this.statusBarView && this.statusBarView.buildStarted();
      this.buildView.buildStarted();
      this.buildView.setHeading([ (target.sh ? `${shCmd} ${shCmdArg} ${exec}` : exec ), ...args, '\n'].join(' '));
    }).catch((err) => {
      if (err instanceof BuildError) {
        if (source === 'save') {
          // If there is no eligible build tool, and cause of build was a save, stay quiet.
          return;
        }

        atom.notifications.addWarning(err.name, { detail: err.message });
      } else {
        atom.notifications.addError('Failed to build.', { detail: err.message });
      }
    });
  },

  abort(cb) {
    this.child.removeAllListeners('exit');
    this.child.removeAllListeners('close');
    this.child.on('exit', () => {
      this.child.removeAllListeners();
      this.child = null;
      cb && cb();
    });

    try {
      require('tree-kill')(this.child.pid, this.child.killed ? 'SIGKILL' : 'SIGTERM');
    } catch (e) {
      /* Something may have happened to the child (e.g. terminated by itself). Ignore this. */
    }

    this.child.killed = true;
  },

  build(source, event) {
    clearTimeout(this.finishedTimer);

    this.doSaveConfirm(this.unsavedTextEditors(), () => {
      const next = this.startNewBuild.bind(this, source, event ? event.type : null);
      this.child ? this.abort(next) : next();
    });
  },

  doSaveConfirm(modifiedTextEditors, continuecb, cancelcb) {
    const saveAndContinue = (save) => {
      modifiedTextEditors.forEach((textEditor) => save && textEditor.save());
      continuecb();
    };

    if (0 === modifiedTextEditors.length || atom.config.get('build.saveOnBuild')) {
      return saveAndContinue(true);
    }

    if (this.saveConfirmView) {
      this.saveConfirmView.destroy();
    }

    const SaveConfirmView = require('./save-confirm-view');
    this.saveConfirmView = new SaveConfirmView();
    this.saveConfirmView.show(saveAndContinue, cancelcb);
  },

  unsavedTextEditors() {
    return atom.workspace.getTextEditors().filter((textEditor) => {
      return textEditor.isModified() && (undefined !== textEditor.getPath());
    });
  },

  stop() {
    clearTimeout(this.finishedTimer);
    if (this.child) {
      if (!this.child.killed) {
        this.buildView.buildAbortInitiated();
      }
      this.abort(() => {
        this.buildView.buildAborted();
        this.statusBarView && this.statusBarView.buildAborted();
      });
    } else {
      this.buildView.reset();
    }
  },

  consumeLinterRegistry(registry) {
    this.linter = registry.register({ name: 'Build' });
  },

  consumeBuilder(builder) {
    this.tools.push(builder);
    const Disposable = require('atom').Disposable;
    return new Disposable(() => {
      this.tools = this.tools.filter(tool => tool !== builder);
    });
  },

  consumeStatusBar(statusBar) {
    const StatusBarView = require('./status-bar-view');
    this.statusBarView = new StatusBarView(statusBar);
    this.statusBarView.onClick(() => this.selectActiveTarget());
    this.statusBarView.attach();
  }
};
<|endoftext|>


<commit_before>
// npm build command

// everything about the installation after the creation of
// the .npm/{name}/{version}/package folder.
// linking the main.js and libs folder, dropping these into
// the npm.root, resolving dependencies, etc.

// This runs AFTER install or link are completed.

var npm = require("../npm")
  , log = require("./utils/log")
  , rm = require("./utils/rm-rf")
  , chain = require("./utils/chain")
  , fetch = require("./utils/fetch")
  , fs = require("fs")
  , path = require("path")
  , semver = require("./utils/semver")
  , mkdir = require("./utils/mkdir-p")
  , lifecycle = require("./utils/lifecycle")
  , readJson = require("./utils/read-json")
  , ini = require("./utils/ini")
  , sys = require("sys")
  , writeShim = require("./utils/write-shim")
  , link = require("./utils/link")
  , linkIfExists = link.ifExists
  , shimIfExists = writeShim.ifExists
  , readInstalled = require("./utils/read-installed")

module.exports = build

// pkg is either a "package" folder, or a package.json data obj, or an
// object that has the package.json data on the _data member.
function build (args, cb) {
  readAll(args, function (er, args) {
    if (er) return cb(er)
    chain(args.map(function (a) {
      return [buildStep, a]
    }).concat(
      [ [ linkDependencies, args]
      , [ finishBuild, args ]
      ]).concat(cb))
  })
}

function buildStep (pkg, cb) {
  pkg = pkg && pkg._data || pkg
  if (!pkg) return cb(new Error("Invalid package data "+sys.inspect(pkg)))
  chain
    ( [lifecycle, pkg, "preinstall"]
      // make sure that all dependencies are already installed.
    , [resolveDependencies, pkg]

      // generate ROOT/.npm/{name}/{version}/main.js
    , [createMain, pkg]

      // symlink ROOT/{name}-{version}.js to ROOT/.npm/{name}/{version}/main.js
    , [linkMain, pkg]

      // shim ROOT/.npm/{name}-{version}/{module-name}
      // to ROOT/.npm/{name}/{version}/package/{module}
    , [linkModules, pkg, path.join(npm.root, pkg.name+"-"+pkg.version)]

      // symlink any bins into the node install prefix
    , [linkBins, pkg]

    , cb
    )
}
function autoUpdate (pkg, cb) {
  var auto = npm.config.get("update-dependents")
  if (!auto) return log(
    "update-dependents disabled by config", "update-dependents", cb)
  pkg = pkg && pkg._data || pkg
  if (auto === "always") {
    return npm.commands["update-dependents"]([pkg], cb)
  }
  readInstalled(pkg.name, function (er, inst) {
    if (er) return cb(er)
    var versions = Object.keys(inst[pkg.name])
                   .filter(function (v) { return v !== pkg.version })
                   .sort(semver.sort)
      , maxHave = versions.pop()
    if (!maxHave) return log(
      "only version, no dependencies to update", "update-dependents", cb)
    if (!semver.gt(pkg.version, maxHave)) return log(
      "downgrade, not updating dependencencies", "update-dependents", cb)
    npm.commands["update-dependents"]([pkg], cb)
  })
}
function autoActivate (pkg, cb) {
  var auto = npm.config.get("auto-activate")
  if (!auto) return log(
    "auto-activate disabled by config", "auto-activate", cb)
  pkg = pkg && pkg._data || pkg
  // get the list of versions of this package.
  readInstalled(pkg.name, function (er, inst) {
    if (er) return cb(er)
    var versions = Object.keys(inst)
    if (auto !== "always" && versions.indexOf("active") !== -1) {
      log("another version already active", "auto-activate")
      log("to activate, do:   npm activate "+
        pkg.name+" "+pkg.version, "auto-activate")
      return cb()
    }
    log(pkg.name+"@"+pkg.version, "activate")
    npm.commands.activate([pkg.name+"@"+pkg.version], cb)
  })
}

function readAll (folders, cb) {
  folders = folders.slice(0)
  var data = []
  ;(function R (folder) {
    if (!folder) return cb(null, data)
    if (typeof folder === "object") {
      data.push(folder)
      return R(folders.pop())
    }
    readJson(path.join(folder, "package.json"), function (er, d) {
      if (er) return cb(er)
      data.push(d)
      return R(folders.pop())
    })
  })(folders.pop())
}

// make sure that all the dependencies have been installed.
function resolveDependencies (pkg, topCb) {
  if (!pkg) return topCb(new Error("Package not found to resolve dependencies"))
  // link foo-1.0.3 to ROOT/.npm/{pkg}/{version}/dependencies/foo

  var found = []
  chain
    ( [mkdir, path.join(npm.dir, pkg.name, pkg.version, "dependencies")]
    , function (cb) {
        if (!pkg.dependencies) return topCb()
        // don't create anything until they're all verified.
        var reqs = []
        for (var i in pkg.dependencies) reqs.push({name:i, version:pkg.dependencies[i]})
        if (!reqs.length) return topCb()
        ;(function R (req) {
          if (!req) {
            found.forEach(function (req) {
              log(req.name + "@" + req.version, "found")
            })
            return cb()
          }
          log(req.name+"-"+req.version, "required")
          // see if we have this thing installed.
          fs.readdir(path.join(npm.dir, req.name), function (er, versions) {
            if (er) return cb(new Error(
              "Required package: "+req.name+"("+req.version+") not found."))
            // TODO: Get the "stable" version if there is one.
            // Look that up from the registry.
            var satis = semver.maxSatisfying(versions, req.version)
            if (satis) {
              found.push({name:req.name, version:satis})
              return R(reqs.pop())
            }
            return cb(new Error(
              "Required package: "+req.name+"("+req.version+") not found. "+
              "(Found: "+JSON.stringify(versions)+")"))
          })
        })(reqs.pop())
      }

      // save the resolved dependencies on the pkg data for later
    , function (cb) { pkg._resolvedDeps = found; cb() }
    , topCb
    )
}

function linkDependencies (args, cb) {
  // at this point, we know that all the packages in the "args" list have
  // been installed, and so have all the dependencies in each of their
  // _resolvedDeps array.
  // link each package's _resolvedDeps properly.
  var c = []
  args.filter(function (pkg) {
    return pkg._resolvedDeps && pkg._resolvedDeps.length
  }).forEach(function (pkg) {
    c.push( [ dependentLink, pkg ]
          , [ dependencyLink, pkg ]
          )
  })
  c.push(cb)
  chain(c)
}
// for each dependency, link this pkg into the proper "dependent" folder
function dependentLink (pkg, cb) {
  if (!pkg._resolvedDeps || !pkg._resolvedDeps.length) return cb()
  chain(pkg._resolvedDeps.map(function (dep) {
    var dependents = path.join(npm.dir, dep.name, dep.version, "dependents")
      , to = path.join(dependents, pkg.name + "-" + pkg.version)
      , from = path.join(npm.dir, pkg.name, pkg.version)
    return [link, from, to]
  }).concat(cb))
}
// link each dep into this pkg's "dependencies" folder
function dependencyLink (pkg, cb) {
  if (!pkg._resolvedDeps || !pkg._resolvedDeps.length) return cb()
  chain(pkg._resolvedDeps.map(function (dep) { return function (cb) {
    pkg.link = pkg.link || {}
    var dependencies = path.join(npm.dir, pkg.name, pkg.version, "dependencies")
      , dependsOn = path.join( npm.dir, pkg.name, pkg.version
                             , "dependson", dep.name + "-" + dep.version
                             )
      , fromRoot = path.join(npm.dir, dep.name, dep.version)
      , fromLib = path.join(npm.root, dep.name + "-" + dep.version)
      , fromMain = fromLib + ".js"
      , toLib = path.join(dependencies, dep.name)
      , toMain = toLib+".js"
      , linkToLib = (dep.name in pkg.link)
        ? path.join(npm.dir, pkg.name, pkg.version, "package", pkg.link[dep.name])
        : null
      , linkToMain = linkToLib ? linkToLib + ".js" : null
    chain([ [ mkdir, dependsOn ]
          , [ link, fromRoot, dependsOn ]
          , [ shimIfExists, fromMain, toMain ]
          , [ linkModules, dep, toLib ]
          , linkToLib && [ linkModules, dep, linkToLib ]
          , linkToMain && [ shimIfExists, fromMain, linkToMain ]
          , cb
          ].filter(function (_) { return _ }))
  }}).concat(cb))
}

function createMain (pkg,cb) {
  if (!pkg.main) return cb()
  log(pkg.main, "main")
  writeShim
    ( path.join(npm.dir, pkg.name, pkg.version, "package", pkg.main)
    , path.join(npm.dir, pkg.name, pkg.version, "main.js")
    , path.join(npm.dir, pkg.name, pkg.version, "dependencies")
    , cb
    )
}

// shim {target}/{module-name} to ROOT/.npm/{name}/{version}/package/{module-path}
function linkModules (pkg, target, cb) {
  var mod = pkg.modules
  if (!mod) {
    var lib = pkg.directories && pkg.directories.lib || pkg.lib
    if (!lib) return cb()
    log("Auto-linking the 'directories.lib' is deprecated and will be removed.", "! WARNING !")
    log("Use the package 'modules' feature instead.", "! WARNING !")
    return linkDefaultModules(pkg, lib, target, cb)
  }

  var versionDir = path.join(npm.dir, pkg.name, pkg.version)
    , pkgDir = path.join(versionDir, "package")

  var modKeys = Object.keys(mod)
    , m = modKeys.length
    , errState = null
  function cb_ (er) {
    if (errState) return
    if (er) return cb(errState = er)
    if (-- m === 0) return cb()
  }
  // chain(Object.keys(mod).map(function (key) {
  modKeys.forEach(function (key) {
    var value = mod[key] + ".js"
    key = key + ".js"
    var from = path.join(pkgDir, value)
      , dep = path.join(versionDir, "dependencies")
      , to = path.join(target, key)
    shim(from, to, dep, cb_)
  })
}

/////
///// DEPRECATED
///// FIXME: remove by 0.2.0
/////
// shim ROOT/{name}-{version}/**/*.js to ROOT/.npm/{name}/{version}/{lib}/**/*.js
function linkDefaultModules(pkg, lib, target, cb) {
  var pkgDir = path.join(npm.dir, pkg.name, pkg.version, "package")
    , libDir = path.join(pkgDir, lib)

  // create a modules hash from the lib folder.
  // this sucks, and is going away eventually.
  pkg.modules = {}
  walk(libDir).map(function (filename) {
    filename = filename.replace(/\.(js|node)$/, '')
    pkg.modules[filename.substr(libDir.length + 1)] = filename.substr(pkgDir.length + 1)
  })
  return linkModules(pkg, target, cb)
}
function walk (filename) {
  var filenames = []
    , stats = fs.statSync(filename)
  if(stats.isFile() && filename.match(/\.(js|node)$/)) {
    // Filename
    filenames.push(filename)
  } else if (stats.isDirectory()) {
    // Directory - walk recursive
    var files = fs.readdirSync(filename)
    for (var i = 0; i < files.length; i++) {
      walk(filename + '/' + files[i]).forEach(function (fn) {
        filenames.push(fn)
      })
    }
  }
  return filenames
}
///// end
///// DEPRECATED
///// FIXME: remove by 0.1.30
/////

function shim (from, to, dep, cb) {
  fs.lstat(to, function (er) {
    if (!er) rm(to, function (er) {
      if (er) cb(er)
      else shim(from, to, dep, cb)
    })
    else {
      writeShim(from, to, dep, cb)
    }
  })
}

function linkMain (pkg, cb) {
  if (!pkg.main) return cb()
  var from = path.join(npm.dir, pkg.name, pkg.version, "main.js")
    , dep = path.join(npm.dir, pkg.name, pkg.version, "dependencies")
    , to = path.join(npm.root, pkg.name+"-"+pkg.version+".js")
  shim(from, to, dep, cb)
}

function linkBins (pkg, cb) {
  if (!pkg.bin) return cb()
  log(pkg._id, "linkBins")
  var binroot = npm.config.get("binroot")
    , dep = path.join(npm.dir, pkg.name, pkg.version, "dependencies")
  if (!binroot) return cb()
  if (!process.env.PATH || -1 === process.env.PATH.indexOf(binroot)) {
    log("bins installing to "+binroot+", outside PATH", "warning")
  }
  chain(Object.keys(pkg.bin).filter(function (i) {
    return i.charAt(0) !== "_"
  }).map(function (i) {
    log(i+" "+pkg.bin[i], "linkBin")
    var to = path.join(binroot, i+"-"+pkg.version)
      , from = path.join(npm.dir, pkg.name, pkg.version, "package", pkg.bin[i])
    return [shimTest, from, to, dep]
  }).concat(log.er(cb, "failed to link bins")))
}

function shimTest (from, to, dep, cb) {
  // if it needs a shim, then call writeShim
  // otherwise, just link it in.
  if (from.match(/\.(node|js)$/)) return writeShim(from, to, dep, cb)
  fs.readFile(from, function (er, data) {
    if (er) return cb(er)
    data = data.toString("ascii")
    var envNode = data.match(/#!(\/usr\/bin\/)?env node/)
      , node = data.match(/#!(\/usr(\/local)?\/bin\/)?node/)
    if (envNode && envNode.index === 0 || node && node.index === 0) {
      return writeShim(from, to, dep, cb)
    }
    return link(from, to, cb)
  })
}

function finishBuild (args, cb) {
  chain(args.map(function (pkg) { return function (cb) {
    chain
      ( [lifecycle, pkg, "install"]
      , [lifecycle, pkg, "postinstall"]
      , [autoActivate, pkg]
      , [autoUpdate, pkg]
      , cb
      )
  }}).concat(args.map(function (pkg) {
    pkg = pkg && pkg._data || pkg
    return [ log, "Success: "+(pkg.name + "@"+pkg.version), "build" ]
  })).concat(cb))
}
<commit_msg>Build all the modules shims at once.<commit_after>
// npm build command

// everything about the installation after the creation of
// the .npm/{name}/{version}/package folder.
// linking the main.js and libs folder, dropping these into
// the npm.root, resolving dependencies, etc.

// This runs AFTER install or link are completed.

var npm = require("../npm")
  , log = require("./utils/log")
  , rm = require("./utils/rm-rf")
  , chain = require("./utils/chain")
  , fetch = require("./utils/fetch")
  , fs = require("fs")
  , path = require("path")
  , semver = require("./utils/semver")
  , mkdir = require("./utils/mkdir-p")
  , lifecycle = require("./utils/lifecycle")
  , readJson = require("./utils/read-json")
  , ini = require("./utils/ini")
  , sys = require("sys")
  , writeShim = require("./utils/write-shim")
  , link = require("./utils/link")
  , linkIfExists = link.ifExists
  , shimIfExists = writeShim.ifExists
  , readInstalled = require("./utils/read-installed")

module.exports = build

// pkg is either a "package" folder, or a package.json data obj, or an
// object that has the package.json data on the _data member.
function build (args, cb) {
  readAll(args, function (er, args) {
    if (er) return cb(er)
    chain(args.map(function (a) {
      return [buildStep, a]
    }).concat(
      [ [ linkDependencies, args]
      , [ finishBuild, args ]
      ]).concat(cb))
  })
}

function buildStep (pkg, cb) {
  pkg = pkg && pkg._data || pkg
  if (!pkg) return cb(new Error("Invalid package data "+sys.inspect(pkg)))
  chain
    ( [lifecycle, pkg, "preinstall"]
      // make sure that all dependencies are already installed.
    , [resolveDependencies, pkg]

      // generate ROOT/.npm/{name}/{version}/main.js
    , [createMain, pkg]

      // symlink ROOT/{name}-{version}.js to ROOT/.npm/{name}/{version}/main.js
    , [linkMain, pkg]

      // shim ROOT/.npm/{name}-{version}/{module-name}
      // to ROOT/.npm/{name}/{version}/package/{module}
    , [linkModules, pkg, path.join(npm.root, pkg.name+"-"+pkg.version)]

      // symlink any bins into the node install prefix
    , [linkBins, pkg]

    , cb
    )
}
function autoUpdate (pkg, cb) {
  var auto = npm.config.get("update-dependents")
  if (!auto) return log(
    "update-dependents disabled by config", "update-dependents", cb)
  pkg = pkg && pkg._data || pkg
  if (auto === "always") {
    return npm.commands["update-dependents"]([pkg], cb)
  }
  readInstalled(pkg.name, function (er, inst) {
    if (er) return cb(er)
    var versions = Object.keys(inst[pkg.name])
                   .filter(function (v) { return v !== pkg.version })
                   .sort(semver.sort)
      , maxHave = versions.pop()
    if (!maxHave) return log(
      "only version, no dependencies to update", "update-dependents", cb)
    if (!semver.gt(pkg.version, maxHave)) return log(
      "downgrade, not updating dependencencies", "update-dependents", cb)
    npm.commands["update-dependents"]([pkg], cb)
  })
}
function autoActivate (pkg, cb) {
  var auto = npm.config.get("auto-activate")
  if (!auto) return log(
    "auto-activate disabled by config", "auto-activate", cb)
  pkg = pkg && pkg._data || pkg
  // get the list of versions of this package.
  readInstalled(pkg.name, function (er, inst) {
    if (er) return cb(er)
    var versions = Object.keys(inst)
    if (auto !== "always" && versions.indexOf("active") !== -1) {
      log("another version already active", "auto-activate")
      log("to activate, do:   npm activate "+
        pkg.name+" "+pkg.version, "auto-activate")
      return cb()
    }
    log(pkg.name+"@"+pkg.version, "activate")
    npm.commands.activate([pkg.name+"@"+pkg.version], cb)
  })
}

function readAll (folders, cb) {
  folders = folders.slice(0)
  var data = []
  ;(function R (folder) {
    if (!folder) return cb(null, data)
    if (typeof folder === "object") {
      data.push(folder)
      return R(folders.pop())
    }
    readJson(path.join(folder, "package.json"), function (er, d) {
      if (er) return cb(er)
      data.push(d)
      return R(folders.pop())
    })
  })(folders.pop())
}

// make sure that all the dependencies have been installed.
function resolveDependencies (pkg, topCb) {
  if (!pkg) return topCb(new Error("Package not found to resolve dependencies"))
  // link foo-1.0.3 to ROOT/.npm/{pkg}/{version}/dependencies/foo

  var found = []
  chain
    ( [mkdir, path.join(npm.dir, pkg.name, pkg.version, "dependencies")]
    , function (cb) {
        if (!pkg.dependencies) return topCb()
        // don't create anything until they're all verified.
        var reqs = []
        for (var i in pkg.dependencies) reqs.push({name:i, version:pkg.dependencies[i]})
        if (!reqs.length) return topCb()
        ;(function R (req) {
          if (!req) {
            found.forEach(function (req) {
              log(req.name + "@" + req.version, "found")
            })
            return cb()
          }
          log(req.name+"-"+req.version, "required")
          // see if we have this thing installed.
          fs.readdir(path.join(npm.dir, req.name), function (er, versions) {
            if (er) return cb(new Error(
              "Required package: "+req.name+"("+req.version+") not found."))
            // TODO: Get the "stable" version if there is one.
            // Look that up from the registry.
            var satis = semver.maxSatisfying(versions, req.version)
            if (satis) {
              found.push({name:req.name, version:satis})
              return R(reqs.pop())
            }
            return cb(new Error(
              "Required package: "+req.name+"("+req.version+") not found. "+
              "(Found: "+JSON.stringify(versions)+")"))
          })
        })(reqs.pop())
      }

      // save the resolved dependencies on the pkg data for later
    , function (cb) { pkg._resolvedDeps = found; cb() }
    , topCb
    )
}

function linkDependencies (args, cb) {
  // at this point, we know that all the packages in the "args" list have
  // been installed, and so have all the dependencies in each of their
  // _resolvedDeps array.
  // link each package's _resolvedDeps properly.
  var c = []
  args.filter(function (pkg) {
    return pkg._resolvedDeps && pkg._resolvedDeps.length
  }).forEach(function (pkg) {
    c.push( [ dependentLink, pkg ]
          , [ dependencyLink, pkg ]
          )
  })
  c.push(cb)
  chain(c)
}
// for each dependency, link this pkg into the proper "dependent" folder
function dependentLink (pkg, cb) {
  if (!pkg._resolvedDeps || !pkg._resolvedDeps.length) return cb()
  chain(pkg._resolvedDeps.map(function (dep) {
    var dependents = path.join(npm.dir, dep.name, dep.version, "dependents")
      , to = path.join(dependents, pkg.name + "-" + pkg.version)
      , from = path.join(npm.dir, pkg.name, pkg.version)
    return [link, from, to]
  }).concat(cb))
}
// link each dep into this pkg's "dependencies" folder
function dependencyLink (pkg, cb) {
  if (!pkg._resolvedDeps || !pkg._resolvedDeps.length) return cb()
  chain(pkg._resolvedDeps.map(function (dep) { return function (cb) {
    pkg.link = pkg.link || {}
    var dependencies = path.join(npm.dir, pkg.name, pkg.version, "dependencies")
      , dependsOn = path.join( npm.dir, pkg.name, pkg.version
                             , "dependson", dep.name + "-" + dep.version
                             )
      , fromRoot = path.join(npm.dir, dep.name, dep.version)
      , fromLib = path.join(npm.root, dep.name + "-" + dep.version)
      , fromMain = fromLib + ".js"
      , toLib = path.join(dependencies, dep.name)
      , toMain = toLib+".js"
      , linkToLib = (dep.name in pkg.link)
        ? path.join(npm.dir, pkg.name, pkg.version, "package", pkg.link[dep.name])
        : null
      , linkToMain = linkToLib ? linkToLib + ".js" : null
    chain([ [ mkdir, dependsOn ]
          , [ link, fromRoot, dependsOn ]
          , [ shimIfExists, fromMain, toMain ]
          , [ linkModules, dep, toLib ]
          , linkToLib && [ linkModules, dep, linkToLib ]
          , linkToMain && [ shimIfExists, fromMain, linkToMain ]
          , cb
          ].filter(function (_) { return _ }))
  }}).concat(cb))
}

function createMain (pkg,cb) {
  if (!pkg.main) return cb()
  log(pkg.main, "main")
  writeShim
    ( path.join(npm.dir, pkg.name, pkg.version, "package", pkg.main)
    , path.join(npm.dir, pkg.name, pkg.version, "main.js")
    , path.join(npm.dir, pkg.name, pkg.version, "dependencies")
    , cb
    )
}

// shim {target}/{module-name} to ROOT/.npm/{name}/{version}/package/{module-path}
function linkModules (pkg, target, cb) {
  var mod = pkg.modules
  if (!mod) {
    var lib = pkg.directories && pkg.directories.lib || pkg.lib
    if (!lib) return cb()
    log("Auto-linking the 'directories.lib' is deprecated and will be removed.", "! WARNING !")
    log("Use the package 'modules' feature instead.", "! WARNING !")
    return linkDefaultModules(pkg, lib, target, cb)
  }

  var versionDir = path.join(npm.dir, pkg.name, pkg.version)
    , pkgDir = path.join(versionDir, "package")

  var modKeys = Object.keys(mod)
    , m = modKeys.length
    , errState = null
  function cb_ (er) {
    if (errState) return
    if (er) return cb(errState = er)
    if (-- m === 0) return cb()
  }
  modKeys.forEach(function (key) {
    var value = mod[key]
      , from = path.join(pkgDir, value)
      , dep = path.join(versionDir, "dependencies")
      , to = path.join(target, key+".js")
    shim(from, to, dep, cb_)
  })
}

/////
///// DEPRECATED
///// FIXME: remove by 0.2.0
/////
// shim ROOT/{name}-{version}/**/*.js to ROOT/.npm/{name}/{version}/{lib}/**/*.js
function linkDefaultModules(pkg, lib, target, cb) {
  var pkgDir = path.join(npm.dir, pkg.name, pkg.version, "package")
    , libDir = path.join(pkgDir, lib)

  // create a modules hash from the lib folder.
  // this sucks, and is going away eventually.
  pkg.modules = {}
  walk(libDir).map(function (filename) {
    filename = filename.replace(/\.(js|node)$/, '')
    pkg.modules[filename.substr(libDir.length + 1)] = filename.substr(pkgDir.length + 1)
  })
  return linkModules(pkg, target, cb)
}
function walk (filename) {
  var filenames = []
    , stats = fs.statSync(filename)
  if(stats.isFile() && filename.match(/\.(js|node)$/)) {
    // Filename
    filenames.push(filename)
  } else if (stats.isDirectory()) {
    // Directory - walk recursive
    var files = fs.readdirSync(filename)
    for (var i = 0; i < files.length; i++) {
      walk(filename + '/' + files[i]).forEach(function (fn) {
        filenames.push(fn)
      })
    }
  }
  return filenames
}
///// end
///// DEPRECATED
///// FIXME: remove by 0.1.30
/////

function shim (from, to, dep, cb) {
  fs.lstat(to, function (er) {
    if (!er) rm(to, function (er) {
      if (er) cb(er)
      else shim(from, to, dep, cb)
    })
    else {
      writeShim(from, to, dep, cb)
    }
  })
}

function linkMain (pkg, cb) {
  if (!pkg.main) return cb()
  var from = path.join(npm.dir, pkg.name, pkg.version, "main.js")
    , dep = path.join(npm.dir, pkg.name, pkg.version, "dependencies")
    , to = path.join(npm.root, pkg.name+"-"+pkg.version+".js")
  shim(from, to, dep, cb)
}

function linkBins (pkg, cb) {
  if (!pkg.bin) return cb()
  log(pkg._id, "linkBins")
  var binroot = npm.config.get("binroot")
    , dep = path.join(npm.dir, pkg.name, pkg.version, "dependencies")
  if (!binroot) return cb()
  if (!process.env.PATH || -1 === process.env.PATH.indexOf(binroot)) {
    log("bins installing to "+binroot+", outside PATH", "warning")
  }
  chain(Object.keys(pkg.bin).filter(function (i) {
    return i.charAt(0) !== "_"
  }).map(function (i) {
    log(i+" "+pkg.bin[i], "linkBin")
    var to = path.join(binroot, i+"-"+pkg.version)
      , from = path.join(npm.dir, pkg.name, pkg.version, "package", pkg.bin[i])
    return [shimTest, from, to, dep]
  }).concat(log.er(cb, "failed to link bins")))
}

function shimTest (from, to, dep, cb) {
  // if it needs a shim, then call writeShim
  // otherwise, just link it in.
  if (from.match(/\.(node|js)$/)) return writeShim(from, to, dep, cb)
  fs.readFile(from, function (er, data) {
    if (er) return cb(er)
    data = data.toString("ascii")
    var envNode = data.match(/#!(\/usr\/bin\/)?env node/)
      , node = data.match(/#!(\/usr(\/local)?\/bin\/)?node/)
    if (envNode && envNode.index === 0 || node && node.index === 0) {
      return writeShim(from, to, dep, cb)
    }
    return link(from, to, cb)
  })
}

function finishBuild (args, cb) {
  chain(args.map(function (pkg) { return function (cb) {
    chain
      ( [lifecycle, pkg, "install"]
      , [lifecycle, pkg, "postinstall"]
      , [autoActivate, pkg]
      , [autoUpdate, pkg]
      , cb
      )
  }}).concat(args.map(function (pkg) {
    pkg = pkg && pkg._data || pkg
    return [ log, "Success: "+(pkg.name + "@"+pkg.version), "build" ]
  })).concat(cb))
}
<|endoftext|>


<commit_before>const fs = require('fs')
const path = require('path')
const heml = require('heml')
const nunjucks = require('nunjucks')
const htmlMinify = require('html-minifier').minify

/**
 * Builds out an email given the template name and data for the template
 *
 * @param   {string} templateName specifies the template to retrieve (ex: 'verifyEmail')
 * @param   {Object} templateData an object consisting of template data (ex: { 'name': 'Richard', 'verificationUrl': 'http://example.com/' })
 * @returns {Promise<string>}
 */
const build = async (templateName, templateData) => {
  const nunjuckTemplate = await getTemplate(templateName)
  const hemlHtml = await renderTemplate(nunjuckTemplate, templateData)
  const { html } = await renderHeml(hemlHtml)
  const minifiedHtml = minify(html)
  return minifiedHtml
}

/**
 * Reads a raw template from './templates/'
 *
 * @param   {string} templateName specifies the template to retrieve (ex: 'verifyEmail')
 * @returns {Promise<string>}
 */
const getTemplate = (templateName) => {
  return new Promise((resolve, reject) => {
    fs.readFile(`${__dirname + '/../templates/' + templateName}.njk`, 'utf8', (error, data) => {
      if (error) {
        reject(error)
        return
      }

      resolve(data)
    })
  })
}

/**
 * Builds nunjuck templates, including injecting template data
 *
 * @param   {string} nunjuckTemplate
 * @param   {Object} templateData
 * @returns {Promise<string>}
 */
const renderTemplate = (nunjuckTemplate, templateData) => {
  return new Promise((resolve, reject) => {
    const dirname = path.resolve(`${__dirname}/../templates/`)
    const data = Object.assign({}, templateData, { dirname })

    nunjucks.renderString(nunjuckTemplate, data, (error, result) => {
      if (error) {
        throw new Error(error)
        reject()
      }

      resolve(result)
    })
  })
}

/**
 * Renders heml to old-school table html
 *
 * @param   {string} hemlHtml
 * @returns {Promise<string>}
 */
const renderHeml = async (hemlHtml) => {
  const options = {
    validate: 'soft', // validation levels - 'strict'|'soft'|'none'
    cheerio: {}, // config passed to cheerio parser
    juice: { applyWidthAttributes: false },
    beautify: {}, // config passed to js-beautify html method
    elements: [] // any custom elements you want to use
  }

  return await heml(hemlHtml, options)
}

/**
 * Minifies html
 *
 * @param   {string} html
 * @returns {string}
 */
const minify = (html) => {
  // https://github.com/kangax/html-minifier#options-quick-reference
  const options = { minifyCSS: true, collapseWhitespace: true }
  return htmlMinify(html, options)
}

module.exports = build
<commit_msg>Hotfix: Update doctype<commit_after>const fs = require('fs')
const path = require('path')
const heml = require('heml')
const nunjucks = require('nunjucks')
const htmlMinify = require('html-minifier').minify

/**
 * Builds out an email given the template name and data for the template
 *
 * @param   {string} templateName specifies the template to retrieve (ex: 'verifyEmail')
 * @param   {Object} templateData an object consisting of template data (ex: { 'name': 'Richard', 'verificationUrl': 'http://example.com/' })
 * @returns {Promise<string>}
 */
const build = async (templateName, templateData) => {
  const nunjuckTemplate = await getTemplate(templateName)
  const hemlHtml = await renderTemplate(nunjuckTemplate, templateData)
  const { html } = await renderHeml(hemlHtml)
  const minifiedHtml = minify(html)

  // TODO: Remove this hack when this change in heml is released: https://github.com/SparkPost/heml/commit/5845ee4b0c299e274d860b43722c9225de093565
  const badDoctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" />'
  const goodDoctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
  const fixedHtml = minifiedHtml.replace(badDoctype, goodDoctype)

  return fixedHtml
}

/**
 * Reads a raw template from './templates/'
 *
 * @param   {string} templateName specifies the template to retrieve (ex: 'verifyEmail')
 * @returns {Promise<string>}
 */
const getTemplate = (templateName) => {
  return new Promise((resolve, reject) => {
    fs.readFile(`${__dirname + '/../templates/' + templateName}.njk`, 'utf8', (error, data) => {
      if (error) {
        reject(error)
        return
      }

      resolve(data)
    })
  })
}

/**
 * Builds nunjuck templates, including injecting template data
 *
 * @param   {string} nunjuckTemplate
 * @param   {Object} templateData
 * @returns {Promise<string>}
 */
const renderTemplate = (nunjuckTemplate, templateData) => {
  return new Promise((resolve, reject) => {
    const dirname = path.resolve(`${__dirname}/../templates/`)
    const data = Object.assign({}, templateData, { dirname })

    nunjucks.renderString(nunjuckTemplate, data, (error, result) => {
      if (error) {
        throw new Error(error)
        reject()
      }

      resolve(result)
    })
  })
}

/**
 * Renders heml to old-school table html
 *
 * @param   {string} hemlHtml
 * @returns {Promise<string>}
 */
const renderHeml = async (hemlHtml) => {
  const options = {
    validate: 'soft', // validation levels - 'strict'|'soft'|'none'
    cheerio: {}, // config passed to cheerio parser
    juice: { applyWidthAttributes: false },
    beautify: {}, // config passed to js-beautify html method
    elements: [] // any custom elements you want to use
  }

  return await heml(hemlHtml, options)
}

/**
 * Minifies html
 *
 * @param   {string} html
 * @returns {string}
 */
const minify = (html) => {
  // https://github.com/kangax/html-minifier#options-quick-reference
  const options = { minifyCSS: true, collapseWhitespace: true }
  return htmlMinify(html, options)
}

module.exports = build
<|endoftext|>


<commit_before>const db = require('./db')

function cache ({fn, minutes=5, log=''}) {
  this.expires = new Date(new Date().getTime() + minutes *  60 * 1000)
  this.result = false
  this.resolve = () => {
    if (this.result && this.expires > new Date().getTime()) {
  return Promise.resolve(true)
        .then(() => this.result)
    } else {
    return Promise.resolve(true)
        .then(() => fn())
        .then(res => {
          this.result = res
        this.expires = new Date(new Date().getTime() + minutes * 60000)
          return res
        })
        .then(res => {
          if (log === 'Index') {
            try {
              db.saveApiResponse(res)
            } catch (e) {
              console.log('Error saving to history database')
            }
          }
          return res
        })
    }
  }
  return this
}

module.exports = (obj) => new cache(obj)<commit_msg>giveup this service<commit_after>const db = require('./db')

function cache ({fn, minutes=5, log=''}) {
  this.expires = new Date(new Date().getTime() + minutes *  60 * 1000)
  this.result = false
  this.resolve = () => {
    if (this.result && this.expires > new Date().getTime()) {
      return Promise.resolve(true)
        .then(() => this.result)
    } else {
      return Promise.resolve(true)
        .then(() => fn())
        .then(res => {
          this.result = res
        this.expires = new Date(new Date().getTime() + minutes * 60000)
          return res
        })
        .then(res => {
          if (log === 'Index') {
            try {
              db.saveApiResponse(res)
            } catch (e) {
              console.log('Error saving to history database')
            }
          }
          return res
        })
    }
  }
  return this
}

module.exports = (obj) => new cache(obj)<|endoftext|>


<commit_before>'use strict'

import React, { PropTypes } from 'react'

const { MediaRecorder, Blob, alert } = window

const Recorder = React.createClass({
  start () {
    this.mediaRecorder.start()
  },

  stop () {
    this.mediaRecorder.stop()
  },

  pause () {
    this.mediaRecorder.pause()
  },

  resume () {
    this.mediaRecorder.resume()
  },

  componentDidMount () {
    navigator.getUserMedia = (navigator.getUserMedia ||
                              navigator.mozGetUserMedia ||
                              navigator.msGetUserMedia ||
                              navigator.webkitGetUserMedia)

    if (navigator.getUserMedia && MediaRecorder) {
      const constraints = {audio: true}
      this.chunks = []
      const { blobOpts, onStop, onError, mediaOpts, onPause, onResume, onStart, gotStream } = this.props

      const onErr = err => {
        console.warn(err)
        if (onError) onError(err)
      }

      const onSuccess = stream => {
        this.mediaRecorder = new MediaRecorder(stream, mediaOpts || {})

        this.mediaRecorder.ondataavailable = e => {
          this.chunks.push(e.data)
        }

        this.mediaRecorder.onstop = e => {
          const blob = new Blob(this.chunks, blobOpts || {type: 'audio/wav'})
          this.chunks = []
          onStop(blob)
        }

        this.mediaRecorder.onerror = onErr
        if (onPause) this.mediaRecorder.onpause = onPause
        if (onResume) this.mediaRecorder.onresume = onResume
        if (onStart) this.mediaRecorder.onstart = onStart
        if (gotStream) gotStream(stream)
      }

      navigator.getUserMedia(constraints, onSuccess, onErr)
    } else {
      console.warn('Audio recording APIs not supported by this browser')
      const { onMissingAPIs } = this.props
      if (onMissingAPIs) {
        onMissingAPIs(navigator.getUserMedia, MediaRecorder)
      } else {
        alert('Your browser doesn\'t support native microphone recording. For best results, we recommend using Google Chrome or Mozilla Firefox to use this site.')
      }
    }
  },

  componentDidUpdate (prevProps) {
    if (this.props.command && this.props.command !== 'none' && prevProps.command !== this.props.command) {
      this[this.props.command]()
    }
  },

  componentWillUnmount () {
    if (this.onUnmount) this.onUnmount()
  },

  render () {
    return false
  },

  propTypes: {
    command: PropTypes.oneOf(['start', 'stop', 'pause', 'resume', 'none']),
    onStop: PropTypes.func.isRequired,
    onMissingAPIs: PropTypes.func,
    onError: PropTypes.func,
    onPause: PropTypes.func,
    onStart: PropTypes.func,
    onResume: PropTypes.func,
    onUnmount: PropTypes.func,
    gotStream: PropTypes.func,
    blobOpts: PropTypes.object,
    mediaOpts: PropTypes.object
  }
})

export default Recorder
<commit_msg>fix bug for unmount<commit_after>'use strict'

import React, { PropTypes } from 'react'

const { MediaRecorder, Blob, alert } = window

const Recorder = React.createClass({
  start () {
    this.mediaRecorder.start()
  },

  stop () {
    this.mediaRecorder.stop()
  },

  pause () {
    this.mediaRecorder.pause()
  },

  resume () {
    this.mediaRecorder.resume()
  },

  componentDidMount () {
    navigator.getUserMedia = (navigator.getUserMedia ||
                              navigator.mozGetUserMedia ||
                              navigator.msGetUserMedia ||
                              navigator.webkitGetUserMedia)

    if (navigator.getUserMedia && MediaRecorder) {
      const constraints = {audio: true}
      this.chunks = []
      const { blobOpts, onStop, onError, mediaOpts, onPause, onResume, onStart, gotStream } = this.props

      const onErr = err => {
        console.warn(err)
        if (onError) onError(err)
      }

      const onSuccess = stream => {
        this.mediaRecorder = new MediaRecorder(stream, mediaOpts || {})

        this.mediaRecorder.ondataavailable = e => {
          this.chunks.push(e.data)
        }

        this.mediaRecorder.onstop = e => {
          const blob = new Blob(this.chunks, blobOpts || {type: 'audio/wav'})
          this.chunks = []
          onStop(blob)
        }

        this.mediaRecorder.onerror = onErr
        if (onPause) this.mediaRecorder.onpause = onPause
        if (onResume) this.mediaRecorder.onresume = onResume
        if (onStart) this.mediaRecorder.onstart = onStart
        if (gotStream) gotStream(stream)
      }

      navigator.getUserMedia(constraints, onSuccess, onErr)
    } else {
      console.warn('Audio recording APIs not supported by this browser')
      const { onMissingAPIs } = this.props
      if (onMissingAPIs) {
        onMissingAPIs(navigator.getUserMedia, MediaRecorder)
      } else {
        alert('Your browser doesn\'t support native microphone recording. For best results, we recommend using Google Chrome or Mozilla Firefox to use this site.')
      }
    }
  },

  componentDidUpdate (prevProps) {
    if (this.props.command && this.props.command !== 'none' && prevProps.command !== this.props.command) {
      this[this.props.command]()
    }
  },

  componentWillUnmount () {
    if (this.props.onUnmount) this.props.onUnmount()
  },

  render () {
    return false
  },

  propTypes: {
    command: PropTypes.oneOf(['start', 'stop', 'pause', 'resume', 'none']),
    onStop: PropTypes.func.isRequired,
    onMissingAPIs: PropTypes.func,
    onError: PropTypes.func,
    onPause: PropTypes.func,
    onStart: PropTypes.func,
    onResume: PropTypes.func,
    onUnmount: PropTypes.func,
    gotStream: PropTypes.func,
    blobOpts: PropTypes.object,
    mediaOpts: PropTypes.object
  }
})

export default Recorder
<|endoftext|>