<commit_before>let Promise   = require('bluebird')
let mapify    = require('es6-mapify')
let bigNumber = require('bignumber.js')
let Int64     = require('int64-buffer')

let projectRepository = require('./ProjectRepository')

let fileService = require('../Services/FileService')

let model = require('../connect')

// calculate payout distribution
let getDistribution = function (projectId, request) {
  let size          = request.size
  let distributions = JSON.parse(request.distribution)

  let result = new Map()
  let key    = []

  // create a set according to config
  for (let distribution of distributions) {
    for (let i = distribution.lower; i < distribution.upper; i = Math.round((i + distribution.space) * 10) / 10) {
      key.push(i)
      result.set(i, 0)
    }
  }

  return new Promise((resolve, reject) => {
    // get the bet cost of project
    projectRepository.getProjectById(projectId).then(project => {
      // 計算每筆 payout 到小數點第一位 以及他的出現次數
      return model.knex(request.table + projectId).select(model.knex.raw('round((`netWin` / ? + 1) * 10) / 10 as payOut, count(*) as count', [project.betCost])).where('id', '<=', size).groupBy('payOut').orderBy('payOut', 'asc')
    }).then(rows => {
      let sum = 0
      let count = 0

      let Q1     = Math.ceil(size / 4)
      let Median = Math.ceil(size / 2)
      let Q3     = Math.ceil(size * 3 / 4)

      let Q1Flag     = true
      let MedianFlag = true
      let Q3Flag     = true

      let tableData = {
        Min: 0,
        Max: 0
      }

      // categorize each payout into the set
      let i = 0
      for (let row of rows) {
        let tmp = i
        while (i < key.length - 1 && key[i] <= row.payOut) {
          i++
        }
        if (i - tmp > 0) {
          i--
        }
        result.set(key[i], result.get(key[i]) + row.count)

        count += row.count // calculate the sum of occurrences time
        sum   += key[i] * row.count // calculate the sum of payout

        // get Q1, Median and Q3
        if (Q1Flag && count > Q1) {
          tableData.Q1 = key[i]
          Q1Flag = false
        }
        if (MedianFlag && count > Median) {
          tableData.Median = key[i]
          MedianFlag = false
        }
        if (Q3Flag && count > Q3) {
          tableData.Q3 = key[i]
          Q3Flag = false
        }
      }

      // get min and max payout
      tableData.Min = key[0]
      tableData.Max = key[i]

      // get average RTP
      tableData.Avg = Math.floor(sum / size * 100) / 100

      resolve({chartData: mapify.demapify(result), tableData: tableData})
    }).catch(error => {
      console.log(error)
      reject()
    })
  })
}

// get player experience (每 400 筆紀錄一次 RTP)
let getRTP = function (projectId, request) {
  return new Promise((resolve, reject) => {
    let size  = request.size
    let step  = request.step
    let range = request.range

    let result = new Map()

    // get the bet cost of project
    projectRepository.getProjectById(projectId).then(project => {
      // 根據指定場數紀錄 RTP，並計算不同 RTP 的出現次數
      return model.knex.raw('select `rtp`, count(*) `count` from (select (sum(`netWin`) / ? + 1) `rtp`, floor((`id` - 1) / ?) `group` from `overall' + projectId + '` where `id` <= ? group by `group`) `result` group by `rtp` order by `rtp` asc', [project.betCost * step, step, size])
    }).then(rtpSet => {
      let sum   = 0
      let count = 0

      let Q1     = Math.ceil(size / step / 4)
      let Median = Math.ceil(size / step / 2)
      let Q3     = Math.ceil(size / step * 3 / 4)

      let Q1Flag     = true
      let MedianFlag = true
      let Q3Flag     = true

      // get min and max RTP
      let tableData = {
        Min: Math.floor(rtpSet[0][0].rtp * 100 / range) * range / 100,
        Max: Math.floor(rtpSet[0][rtpSet[0].length - 1].rtp * 100 / range) * range / 100
      }

      // categorize each RTP into the set
      for (let rtp of rtpSet[0]) {
        let tmp = Math.floor(rtp.rtp * 100 / range)

        count += rtp.count // calculate the sum of occurrences time
        sum   += tmp * range / 100 * rtp.count // calculate the sum of payout

        // get Q1, Median and Q3
        if (Q1Flag && count > Q1) {
          tableData.Q1 = tmp * range / 100
          Q1Flag = false
        }
        if (MedianFlag && count > Median) {
          tableData.Median = tmp * range / 100
          MedianFlag = false
        }
        if (Q3Flag && count > Q3) {
          tableData.Q3 = tmp * range / 100
          Q3Flag = false
        }

        while (tmp >= result.size) {
          result.set(result.size * range / 100, 0)
        }
        result.set(tmp * range / 100, result.get(tmp * range / 100) + rtp.count)
      }

      // get average RTP
      tableData.Avg = Math.floor(sum * step * 100 / size) / 100

      resolve({chartData: mapify.demapify(result), tableData: tableData})
    }).catch(error => {
      console.log(error)
      reject()
    })
  })
}

// get player experience (觸發 free game 壓力)
let getTotalNetWin = function (projectId, request) {
  return new Promise((resolve, reject) => {
    let size    = request.size
    let range   = request.range

    let result = new Map()
    result.set(0, 0)

    // get all spin data
    model.knex('overall' + projectId).select().where('id', '<=', size).then(rows => {
      let Min = 0
      let Max = 0

      let netWin = 0
      // categorize each netWin into the set
      for (let row of rows) {
        if (row.triger === 0) {
          netWin += row.netWin
        } else {
          let tmp = Math.floor(netWin / range)
          while (tmp < Min || tmp > Max) {
            if (tmp < Min) {
              result.set((--Min) * range, 0)
            } else {
              result.set((++Max) * range, 0)
            }
          }
          netWin = 0
          result.set(range * tmp, result.get(range * tmp) + 1)
        }
      }
      resolve(mapify.demapify(result))
    }).catch(error => {
      console.log(error)
      reject()
    })
  })
}

// get player experience (存活率分析)
let getSurvivalRate = function (projectId, request) {
  return new Promise((resolve, reject) => {
    let size = request.size
    model.knex('survivalrate' + projectId).select().where('id', '<=', size).then(rows => {
      resolve(rows)
    }).catch(error => {
      console.log(error)
      reject()
    })
  })
}

let getRawData = function (projectId, dataFormat) {
  return new Promise((resolve, reject) => {
    model.knex('others' + projectId).select().where('name', dataFormat.name).then(rows => {
      return fileService.readFile(rows[0].data)
    }).then(data => {
      let rawData = new Buffer(data)
      let formats = dataFormat.format
      let result  = {}

      return parseBinary(rawData, formats, 0, 0, result)
    }).then(result => {
      resolve(result.result)
    }).catch(error => {
      console.log(error)
      reject()
    })
  })
}

let parseBinary = function(rawData, format, deep, index, result) {
  return new Promise((resolve, reject) => {
    if (format.length <= deep) {
      resolve({result: result, index: index})
      return
    }

    if (format[deep].type === 'int') {
      result[format[deep].name] = rawData.readInt32LE(index)
      resolve(parseBinary(rawData, format, deep + 1, index + 4, result))
    } else if (format[deep].type === 'lli') {
      let buffer = Buffer.allocUnsafe(8)
      for (let i = 0; i < 8; i++) {
        buffer[i] = rawData[index + i]
      }
      result[format[deep].name] = Int64.Int64LE(buffer).toNumber()
      resolve(parseBinary(rawData, format, deep + 1, index + 8, result))
    } else if (format[deep].type === 'ull') {
      let buffer = Buffer.allocUnsafe(8)
      for (let i = 0; i < 8; i++) {
        buffer[i] = rawData[index + i]
      }
      result[format[deep].name] = Int64.Uint64LE(buffer).toNumber()
      resolve(parseBinary(rawData, format, deep + 1, index + 8, result))
    } else if (format[deep].type === 'double') {
      result[format[deep].name] = rawData.readDoubleLE(index)
      resolve(parseBinary(rawData, format, deep + 1, index + 8, result))
    } else if (format[deep].type === 'string') {
      result[format[deep].name] = rawData.toString('ascii', index, index + format[deep].length)
      resolve(parseBinary(rawData, format, deep + 1, index + format[deep].length, result))
    } else if (format[deep].type === 'object') {
      parseBinary(rawData, format[deep].content, 0, index, {}).then(res => {
        result[format[deep].name] = res.result
        resolve(parseBinary(rawData, format, deep + 1, res.index, result))
      })
    } else if (format[deep].type === 'array') {
      (function parseArray(arr) {
        return new Promise((resolve, reject) => {
          if (format[deep].length !== undefined) {
            (function loop(i) {
              parseBinary(rawData, format[deep].content, 0, index, {}).then(res => {
                arr.push(res.result[format[deep].content[0].name])
                index = res.index
                if (i < format[deep].length) {
                  loop(i + 1)
                } else {
                  resolve(arr)
                }
              })
            })(1)

          } else {
            (function loop() {
              parseBinary(rawData, format[deep].content, 0, index, {}).then(res => {
                arr.push(res.result[format[deep].content[0].name])
                index = res.index
                loop()
              }).catch(() => {
                resolve(arr)
              })
            })()
          }
        })
      })([]).then(res => {
        result[format[deep].name] = res
        resolve(parseBinary(rawData, format, deep + 1, index, result))
      })
    }
  })
}

let uploadData = function (id, table, path, field) {
  return new Promise((resolve, reject) => {
    model.knex(table + id).del().then(() => {
      let query = 'ALTER TABLE ' + table + id + ' AUTO_INCREMENT = 1'
      return model.knex.raw(query)
    }).then(() => {
      let query = 'LOAD DATA LOCAL INFILE \'' + path + '\' INTO TABLE '  + table + id +
      ' fields terminated by \',\' enclosed by \'\"\' lines terminated by \'\\n\' IGNORE 1 LINES (' + field + ')'
      return model.knex.raw(query)
    }).then(() => {
      console.log('success')
      resolve()
    }).catch(error => {
      console.log(error)
      reject()
    })
  })
}

let insertData = function (id, table, data) {
  return new Promise((resolve, reject) => {
    model.knex(table + id).insert(data).then(() => {
      resolve()
    }).catch(error => {
      console.log(error)
      reject()
    })
  })
}

let updateData = function (id, table, data) {
  return new Promise((resolve, reject) => {
    model.knex(table + id).where('name', data.name).update({data: data}).then(() => {
      resolve()
    }).catch(error => {
      console.log(error)
      reject()
    })
  })
}

module.exports = {
  getDistribution: getDistribution,
  getRTP:          getRTP,
  getTotalNetWin:  getTotalNetWin,
  getSurvivalRate: getSurvivalRate,
  getRawData:      getRawData,
  uploadData:      uploadData,
  insertData:      insertData,
  updateData:      updateData
}
<commit_msg>test parse binary<commit_after>let Promise   = require('bluebird')
let mapify    = require('es6-mapify')
let bigNumber = require('bignumber.js')
let Int64     = require('int64-buffer')

let projectRepository = require('./ProjectRepository')

let fileService = require('../Services/FileService')

let model = require('../connect')

// calculate payout distribution
let getDistribution = function (projectId, request) {
  let size          = request.size
  let distributions = JSON.parse(request.distribution)

  let result = new Map()
  let key    = []

  // create a set according to config
  for (let distribution of distributions) {
    for (let i = distribution.lower; i < distribution.upper; i = Math.round((i + distribution.space) * 10) / 10) {
      key.push(i)
      result.set(i, 0)
    }
  }

  return new Promise((resolve, reject) => {
    // get the bet cost of project
    projectRepository.getProjectById(projectId).then(project => {
      // 計算每筆 payout 到小數點第一位 以及他的出現次數
      return model.knex(request.table + projectId).select(model.knex.raw('round((`netWin` / ? + 1) * 10) / 10 as payOut, count(*) as count', [project.betCost])).where('id', '<=', size).groupBy('payOut').orderBy('payOut', 'asc')
    }).then(rows => {
      let sum = 0
      let count = 0

      let Q1     = Math.ceil(size / 4)
      let Median = Math.ceil(size / 2)
      let Q3     = Math.ceil(size * 3 / 4)

      let Q1Flag     = true
      let MedianFlag = true
      let Q3Flag     = true

      let tableData = {
        Min: 0,
        Max: 0
      }

      // categorize each payout into the set
      let i = 0
      for (let row of rows) {
        let tmp = i
        while (i < key.length - 1 && key[i] <= row.payOut) {
          i++
        }
        if (i - tmp > 0) {
          i--
        }
        result.set(key[i], result.get(key[i]) + row.count)

        count += row.count // calculate the sum of occurrences time
        sum   += key[i] * row.count // calculate the sum of payout

        // get Q1, Median and Q3
        if (Q1Flag && count > Q1) {
          tableData.Q1 = key[i]
          Q1Flag = false
        }
        if (MedianFlag && count > Median) {
          tableData.Median = key[i]
          MedianFlag = false
        }
        if (Q3Flag && count > Q3) {
          tableData.Q3 = key[i]
          Q3Flag = false
        }
      }

      // get min and max payout
      tableData.Min = key[0]
      tableData.Max = key[i]

      // get average RTP
      tableData.Avg = Math.floor(sum / size * 100) / 100

      resolve({chartData: mapify.demapify(result), tableData: tableData})
    }).catch(error => {
      console.log(error)
      reject()
    })
  })
}

// get player experience (每 400 筆紀錄一次 RTP)
let getRTP = function (projectId, request) {
  return new Promise((resolve, reject) => {
    let size  = request.size
    let step  = request.step
    let range = request.range

    let result = new Map()

    // get the bet cost of project
    projectRepository.getProjectById(projectId).then(project => {
      // 根據指定場數紀錄 RTP，並計算不同 RTP 的出現次數
      return model.knex.raw('select `rtp`, count(*) `count` from (select (sum(`netWin`) / ? + 1) `rtp`, floor((`id` - 1) / ?) `group` from `overall' + projectId + '` where `id` <= ? group by `group`) `result` group by `rtp` order by `rtp` asc', [project.betCost * step, step, size])
    }).then(rtpSet => {
      let sum   = 0
      let count = 0

      let Q1     = Math.ceil(size / step / 4)
      let Median = Math.ceil(size / step / 2)
      let Q3     = Math.ceil(size / step * 3 / 4)

      let Q1Flag     = true
      let MedianFlag = true
      let Q3Flag     = true

      // get min and max RTP
      let tableData = {
        Min: Math.floor(rtpSet[0][0].rtp * 100 / range) * range / 100,
        Max: Math.floor(rtpSet[0][rtpSet[0].length - 1].rtp * 100 / range) * range / 100
      }

      // categorize each RTP into the set
      for (let rtp of rtpSet[0]) {
        let tmp = Math.floor(rtp.rtp * 100 / range)

        count += rtp.count // calculate the sum of occurrences time
        sum   += tmp * range / 100 * rtp.count // calculate the sum of payout

        // get Q1, Median and Q3
        if (Q1Flag && count > Q1) {
          tableData.Q1 = tmp * range / 100
          Q1Flag = false
        }
        if (MedianFlag && count > Median) {
          tableData.Median = tmp * range / 100
          MedianFlag = false
        }
        if (Q3Flag && count > Q3) {
          tableData.Q3 = tmp * range / 100
          Q3Flag = false
        }

        while (tmp >= result.size) {
          result.set(result.size * range / 100, 0)
        }
        result.set(tmp * range / 100, result.get(tmp * range / 100) + rtp.count)
      }

      // get average RTP
      tableData.Avg = Math.floor(sum * step * 100 / size) / 100

      resolve({chartData: mapify.demapify(result), tableData: tableData})
    }).catch(error => {
      console.log(error)
      reject()
    })
  })
}

// get player experience (觸發 free game 壓力)
let getTotalNetWin = function (projectId, request) {
  return new Promise((resolve, reject) => {
    let size    = request.size
    let range   = request.range

    let result = new Map()
    result.set(0, 0)

    // get all spin data
    model.knex('overall' + projectId).select().where('id', '<=', size).then(rows => {
      let Min = 0
      let Max = 0

      let netWin = 0
      // categorize each netWin into the set
      for (let row of rows) {
        if (row.triger === 0) {
          netWin += row.netWin
        } else {
          let tmp = Math.floor(netWin / range)
          while (tmp < Min || tmp > Max) {
            if (tmp < Min) {
              result.set((--Min) * range, 0)
            } else {
              result.set((++Max) * range, 0)
            }
          }
          netWin = 0
          result.set(range * tmp, result.get(range * tmp) + 1)
        }
      }
      resolve(mapify.demapify(result))
    }).catch(error => {
      console.log(error)
      reject()
    })
  })
}

// get player experience (存活率分析)
let getSurvivalRate = function (projectId, request) {
  return new Promise((resolve, reject) => {
    let size = request.size
    model.knex('survivalrate' + projectId).select().where('id', '<=', size).then(rows => {
      resolve(rows)
    }).catch(error => {
      console.log(error)
      reject()
    })
  })
}

let getRawData = function (projectId, dataFormat) {
  return new Promise((resolve, reject) => {
    model.knex('others' + projectId).select().where('name', dataFormat.name).then(rows => {
      return fileService.readFile(rows[0].data)
    }).then(data => {
      let rawData = new Buffer(data)
      let formats = dataFormat.format
      let result  = {}

      return parseBinary(rawData, formats, 0, 0, result)
    }).then(result => {
      resolve(result.result)
    }).catch(error => {
      console.log(error)
      reject()
    })
  })
}

let parseBinary = function(rawData, format, deep, index, result) {
  return new Promise((resolve, reject) => {
    if (format.length <= deep) {
      resolve({result: result, index: index})
      return
    }

    let length = rawData.length

    if (format[deep].type === 'int') {
      if (index > length - 4) {
        reject()
      }
      result[format[deep].name] = rawData.readInt32LE(index)
      resolve(parseBinary(rawData, format, deep + 1, index + 4, result))
    } else if (format[deep].type === 'lli') {
      if (index > length - 8) {
        reject()
      }
      let buffer = Buffer.allocUnsafe(8)
      for (let i = 0; i < 8; i++) {
        buffer[i] = rawData[index + i]
      }
      result[format[deep].name] = Int64.Int64LE(buffer).toNumber()
      resolve(parseBinary(rawData, format, deep + 1, index + 8, result))
    } else if (format[deep].type === 'ull') {
      if (index > length - 8) {
        reject()
      }
      let buffer = Buffer.allocUnsafe(8)
      for (let i = 0; i < 8; i++) {
        buffer[i] = rawData[index + i]
      }
      result[format[deep].name] = Int64.Uint64LE(buffer).toNumber()
      resolve(parseBinary(rawData, format, deep + 1, index + 8, result))
    } else if (format[deep].type === 'double') {
      if (index > length - 8) {
        reject()
      }
      result[format[deep].name] = rawData.readDoubleLE(index)
      resolve(parseBinary(rawData, format, deep + 1, index + 8, result))
    } else if (format[deep].type === 'string') {
      if (index > length - format[deep].length) {
        reject()
      }
      result[format[deep].name] = rawData.toString('ascii', index, index + format[deep].length)
      resolve(parseBinary(rawData, format, deep + 1, index + format[deep].length, result))
    } else if (format[deep].type === 'object') {
      parseBinary(rawData, format[deep].content, 0, index, {}).then(res => {
        result[format[deep].name] = res.result
        resolve(parseBinary(rawData, format, deep + 1, res.index, result))
      })
    } else if (format[deep].type === 'array') {
      (function parseArray(arr) {
        return new Promise((resolve, reject) => {
          if (format[deep].length !== undefined) {
            (function loop(i) {
              parseBinary(rawData, format[deep].content, 0, index, {}).then(res => {
                arr.push(res.result[format[deep].content[0].name])
                index = res.index
                if (i < format[deep].length) {
                  loop(i + 1)
                } else {
                  resolve(arr)
                }
              })
            })(1)

          } else {
            (function loop() {
              parseBinary(rawData, format[deep].content, 0, index, {}).then(res => {
                arr.push(res.result[format[deep].content[0].name])
                index = res.index
                loop()
              }).catch(() => {
                resolve(arr)
              })
            })()
          }
        })
      })([]).then(res => {
        result[format[deep].name] = res
        resolve(parseBinary(rawData, format, deep + 1, index, result))
      })
    }
  })
}

let uploadData = function (id, table, path, field) {
  return new Promise((resolve, reject) => {
    model.knex(table + id).del().then(() => {
      let query = 'ALTER TABLE ' + table + id + ' AUTO_INCREMENT = 1'
      return model.knex.raw(query)
    }).then(() => {
      let query = 'LOAD DATA LOCAL INFILE \'' + path + '\' INTO TABLE '  + table + id +
      ' fields terminated by \',\' enclosed by \'\"\' lines terminated by \'\\n\' IGNORE 1 LINES (' + field + ')'
      return model.knex.raw(query)
    }).then(() => {
      console.log('success')
      resolve()
    }).catch(error => {
      console.log(error)
      reject()
    })
  })
}

let insertData = function (id, table, data) {
  return new Promise((resolve, reject) => {
    model.knex(table + id).insert(data).then(() => {
      resolve()
    }).catch(error => {
      console.log(error)
      reject()
    })
  })
}

let updateData = function (id, table, data) {
  return new Promise((resolve, reject) => {
    model.knex(table + id).where('name', data.name).update({data: data}).then(() => {
      resolve()
    }).catch(error => {
      console.log(error)
      reject()
    })
  })
}

module.exports = {
  getDistribution: getDistribution,
  getRTP:          getRTP,
  getTotalNetWin:  getTotalNetWin,
  getSurvivalRate: getSurvivalRate,
  getRawData:      getRawData,
  uploadData:      uploadData,
  insertData:      insertData,
  updateData:      updateData
}
<|endoftext|>


<commit_before>let Promise   = require('bluebird')
let mapify    = require('es6-mapify')
let bigNumber = require('bignumber.js')

let projectRepository = require('./ProjectRepository')

let model = require('../connect')

let calPayOutDistribution = function (tableIndex, projectId, request) {
  let table = ['overall', 'basegame', 'freegame']

  let size          = request.size
  let distributions = JSON.parse(request.distribution)

  let result = new Map()
  let key    = []

  for (let distribution of distributions) {
    for (let i = distribution.lower; i < distribution.upper; i = Math.round((i + distribution.space) * 10) / 10) {
      key.push(i)
      result.set(i, 0)
    }
  }

  return new Promise((resolve, reject) => {
    projectRepository.getProjectById(projectId).then(project => {
      return model.knex(table[tableIndex] + projectId).select(model.knex.raw('round((`netWin` / ? + 1) * 10) / 10 as payOut, count(*) as count', [project.betCost])).where('id', '<=', size).groupBy('payOut').orderBy('payOut', 'asc')
    }).then(rows => {
      let sum = 0
      let count = 0

      let Q1     = Math.ceil(size / 4)
      let Median = Math.ceil(size / 2)
      let Q3     = Math.ceil(size * 3 / 4)

      let Q1Flag     = true
      let MedianFlag = true
      let Q3Flag     = true

      let tableData = {
        Min: 0,
        Max: 0
      }

      let i = 0
      for (let row of rows) {
        while (i < key.length - 1 && key[i] < row.payOut) {
          i++
        }
        result.set(key[i], result.get(key[i]) + row.count)

        count += row.count
        sum   += key[i] * row.count

        if (Q1Flag && count > Q1) {
          tableData.Q1 = key[i]
          Q1Flag = false
        }
        if (MedianFlag && count > Median) {
          tableData.Median = key[i]
          MedianFlag = false
        }
        if (Q3Flag && count > Q3) {
          tableData.Q3 = key[i]
          Q3Flag = false
        }
      }

      tableData.Min = key[0]
      tableData.Max = key[i]

      tableData.Avg = Math.floor(sum / size * 100) / 100

      resolve({chartData: mapify.demapify(result), tableData: tableData})
    }).catch(error => {
      console.log(error)
      reject()
    })
  })
}

let getOverAll = function (projectId, request) {
  return new Promise((resolve, reject) => {
    calPayOutDistribution(0, projectId, request).then(result => {
      resolve(result)
    }).catch(() => {
      reject()
    })
  })
}

let getBaseGame = function (projectId, request) {
  return new Promise((resolve, reject) => {
    calPayOutDistribution(1, projectId, request).then(result => {
      resolve(result)
    }).catch(() => {
      reject()
    })
  })
}

let getFreeGame = function (projectId, request) {
  return new Promise((resolve, reject) => {
    calPayOutDistribution(2, projectId, request).then(result => {
      resolve(result)
    }).catch(() => {
      reject()
    })
  })
}

let getRTP = function (projectId, request) {
  return new Promise((resolve, reject) => {
    let size  = request.size
    let step  = request.step
    let range = request.range

    let result = new Map()

    projectRepository.getProjectById(projectId).then(project => {
      return model.knex.raw('select `rtp`, count(*) `count` from (select (sum(`netWin`) / ? + 1) `rtp`, floor((`id` - 1) / ?) `group` from `overall' + projectId + '` where `id` <= ? group by `group`) `result` group by `rtp` order by `rtp` asc', [project.betCost * step, step, size])
    }).then(rtpSet => {
      let sum   = 0
      let count = 0

      let Q1     = Math.ceil(size / step / 4)
      let Median = Math.ceil(size / step / 2)
      let Q3     = Math.ceil(size / step * 3 / 4)

      let Q1Flag     = true
      let MedianFlag = true
      let Q3Flag     = true

      let tableData = {
        Min: Math.floor(rtpSet[0][0].rtp * 100 / range) * range / 100,
        Max: Math.floor(rtpSet[0][rtpSet[0].length - 1].rtp * 100 / range) * range / 100
      }

      for (let rtp of rtpSet[0]) {
        let tmp = Math.floor(rtp.rtp * 100 / range)

        count += rtp.count
        sum   += tmp * range / 100 * rtp.count

        if (Q1Flag && count > Q1) {
          tableData.Q1 = tmp * range / 100
          Q1Flag = false
        }
        if (MedianFlag && count > Median) {
          tableData.Median = tmp * range / 100
          MedianFlag = false
        }
        if (Q3Flag && count > Q3) {
          tableData.Q3 = tmp * range / 100
          Q3Flag = false
        }

        while (tmp >= result.size) {
          result.set(result.size * range / 100, 0)
        }
        result.set(tmp * range / 100, result.get(tmp * range / 100) + rtp.count)
      }

      tableData.Avg = Math.floor(sum * step * 100 / size) / 100

      resolve({chartData: mapify.demapify(result), tableData: tableData})
    }).catch(error => {
      console.log(error)
      reject()
    })
  })
}

let getTotalNetWin = function (projectId, request) {
  return new Promise((resolve, reject) => {
    let size    = request.size
    let range   = request.range

    let result = new Map()
    result.set(0, 0)

    model.knex('overall' + projectId).select().where('id', '<=', size).then(rows => {
      let Min = 0
      let Max = 0

      let netWin = 0
      for (let row of rows) {
        if (row.triger === 0) {
          netWin += row.netWin
        } else {
          let tmp = Math.floor(netWin / range)
          while (tmp < Min || tmp > Max) {
            if (tmp < Min) {
              result.set((--Min) * range, 0)
            } else {
              result.set((++Max) * range, 0)
            }
          }
          netWin = 0
          result.set(range * tmp, result.get(range * tmp) + 1)
        }
      }
      resolve(mapify.demapify(result))
    }).catch(error => {
      console.log(error)
      reject()
    })
  })
}

let getSurvivalRate = function (projectId, request) {
  return new Promise((resolve, reject) => {
    let size = request.size
    model.knex('survivalrate' + projectId).select().where('id', '<=', size).then(rows => {
      resolve(rows)
    }).catch(error => {
      console.log(error)
      reject()
    })
  })
}

module.exports = {
  getOverAll:      getOverAll,
  getBaseGame:     getBaseGame,
  getFreeGame:     getFreeGame,
  getRTP:          getRTP,
  getTotalNetWin:  getTotalNetWin,
  getSurvivalRate: getSurvivalRate
}
<commit_msg>fix some bugs<commit_after>let Promise   = require('bluebird')
let mapify    = require('es6-mapify')
let bigNumber = require('bignumber.js')

let projectRepository = require('./ProjectRepository')

let model = require('../connect')

let calPayOutDistribution = function (tableIndex, projectId, request) {
  let table = ['overall', 'basegame', 'freegame']

  let size          = request.size
  let distributions = JSON.parse(request.distribution)

  let result = new Map()
  let key    = []

  for (let distribution of distributions) {
    for (let i = distribution.lower; i < distribution.upper; i = Math.round((i + distribution.space) * 10) / 10) {
      key.push(i)
      result.set(i, 0)
    }
  }

  return new Promise((resolve, reject) => {
    projectRepository.getProjectById(projectId).then(project => {
      return model.knex(table[tableIndex] + projectId).select(model.knex.raw('(`netWin` / ? + 1) as payOut, count(*) as count', [project.betCost])).where('id', '<=', size).groupBy('payOut').orderBy('payOut', 'asc')
    }).then(rows => {
      let sum = 0
      let count = 0

      let Q1     = Math.ceil(size / 4)
      let Median = Math.ceil(size / 2)
      let Q3     = Math.ceil(size * 3 / 4)

      let Q1Flag     = true
      let MedianFlag = true
      let Q3Flag     = true

      let tableData = {
        Min: 0,
        Max: 0
      }

      let i = 0
      for (let row of rows) {
        while (i < key.length - 1 && key[i] < row.payOut) {
          i++
        }
        result.set(key[i], result.get(key[i]) + row.count)

        count += row.count
        sum   += key[i] * row.count

        if (Q1Flag && count > Q1) {
          tableData.Q1 = key[i]
          Q1Flag = false
        }
        if (MedianFlag && count > Median) {
          tableData.Median = key[i]
          MedianFlag = false
        }
        if (Q3Flag && count > Q3) {
          tableData.Q3 = key[i]
          Q3Flag = false
        }
      }

      tableData.Min = key[0]
      tableData.Max = key[i]

      tableData.Avg = Math.floor(sum / size * 100) / 100

      resolve({chartData: mapify.demapify(result), tableData: tableData})
    }).catch(error => {
      console.log(error)
      reject()
    })
  })
}

let getOverAll = function (projectId, request) {
  return new Promise((resolve, reject) => {
    calPayOutDistribution(0, projectId, request).then(result => {
      resolve(result)
    }).catch(() => {
      reject()
    })
  })
}

let getBaseGame = function (projectId, request) {
  return new Promise((resolve, reject) => {
    calPayOutDistribution(1, projectId, request).then(result => {
      resolve(result)
    }).catch(() => {
      reject()
    })
  })
}

let getFreeGame = function (projectId, request) {
  return new Promise((resolve, reject) => {
    calPayOutDistribution(2, projectId, request).then(result => {
      resolve(result)
    }).catch(() => {
      reject()
    })
  })
}

let getRTP = function (projectId, request) {
  return new Promise((resolve, reject) => {
    let size  = request.size
    let step  = request.step
    let range = request.range

    let result = new Map()

    projectRepository.getProjectById(projectId).then(project => {
      return model.knex.raw('select `rtp`, count(*) `count` from (select (sum(`netWin`) / ? + 1) `rtp`, floor((`id` - 1) / ?) `group` from `overall' + projectId + '` where `id` <= ? group by `group`) `result` group by `rtp` order by `rtp` asc', [project.betCost * step, step, size])
    }).then(rtpSet => {
      let sum   = 0
      let count = 0

      let Q1     = Math.ceil(size / step / 4)
      let Median = Math.ceil(size / step / 2)
      let Q3     = Math.ceil(size / step * 3 / 4)

      let Q1Flag     = true
      let MedianFlag = true
      let Q3Flag     = true

      let tableData = {
        Min: Math.floor(rtpSet[0][0].rtp * 100 / range) * range / 100,
        Max: Math.floor(rtpSet[0][rtpSet[0].length - 1].rtp * 100 / range) * range / 100
      }

      for (let rtp of rtpSet[0]) {
        let tmp = Math.floor(rtp.rtp * 100 / range)

        count += rtp.count
        sum   += tmp * range / 100 * rtp.count

        if (Q1Flag && count > Q1) {
          tableData.Q1 = tmp * range / 100
          Q1Flag = false
        }
        if (MedianFlag && count > Median) {
          tableData.Median = tmp * range / 100
          MedianFlag = false
        }
        if (Q3Flag && count > Q3) {
          tableData.Q3 = tmp * range / 100
          Q3Flag = false
        }

        while (tmp >= result.size) {
          result.set(result.size * range / 100, 0)
        }
        result.set(tmp * range / 100, result.get(tmp * range / 100) + rtp.count)
      }

      tableData.Avg = Math.floor(sum * step * 100 / size) / 100

      resolve({chartData: mapify.demapify(result), tableData: tableData})
    }).catch(error => {
      console.log(error)
      reject()
    })
  })
}

let getTotalNetWin = function (projectId, request) {
  return new Promise((resolve, reject) => {
    let size    = request.size
    let range   = request.range

    let result = new Map()
    result.set(0, 0)

    model.knex('overall' + projectId).select().where('id', '<=', size).then(rows => {
      let Min = 0
      let Max = 0

      let netWin = 0
      for (let row of rows) {
        if (row.triger === 0) {
          netWin += row.netWin
        } else {
          let tmp = Math.floor(netWin / range)
          while (tmp < Min || tmp > Max) {
            if (tmp < Min) {
              result.set((--Min) * range, 0)
            } else {
              result.set((++Max) * range, 0)
            }
          }
          netWin = 0
          result.set(range * tmp, result.get(range * tmp) + 1)
        }
      }
      resolve(mapify.demapify(result))
    }).catch(error => {
      console.log(error)
      reject()
    })
  })
}

let getSurvivalRate = function (projectId, request) {
  return new Promise((resolve, reject) => {
    let size = request.size
    model.knex('survivalrate' + projectId).select().where('id', '<=', size).then(rows => {
      resolve(rows)
    }).catch(error => {
      console.log(error)
      reject()
    })
  })
}

module.exports = {
  getOverAll:      getOverAll,
  getBaseGame:     getBaseGame,
  getFreeGame:     getFreeGame,
  getRTP:          getRTP,
  getTotalNetWin:  getTotalNetWin,
  getSurvivalRate: getSurvivalRate
}
<|endoftext|>


<commit_before>/**
 * DEVELOPMENT WEBPACK CONFIGURATION
 */

const path = require('path');
const fs = require('fs');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const logger = require('../../server/logger');
const cheerio = require('cheerio');
const pkg = require(path.resolve(process.cwd(), 'package.json'));
const dllPlugin = pkg.dllPlugin;

const plugins = [
  new webpack.HotModuleReplacementPlugin(), // Tell webpack we want hot reloading
  new webpack.NoErrorsPlugin(),
  new HtmlWebpackPlugin({
    inject: true, // Inject all files that are generated by webpack, e.g. bundle.js
    templateContent: templateContent(), // eslint-disable-line no-use-before-define
  }),
];

module.exports = require('./webpack.base.babel')({
  // Add hot reloading in development
  entry: [
    'eventsource-polyfill', // Necessary for hot reloading with IE
    'webpack-hot-middleware/client',
    path.join(process.cwd(), 'app/app.js'), // Start with js/app.js
  ],

  // Don't use hashes in dev mode for better performance
  output: {
    filename: '[name].js',
    chunkFilename: '[name].chunk.js',
  },

  // Add development plugins
  plugins: dependencyHandlers().concat(plugins), // eslint-disable-line no-use-before-define

  // Tell babel that we want to hot-reload
  babelQuery: {
    presets: ['react-hmre'],
  },

  // Emit a source map for easier debugging
  devtool: 'cheap-module-eval-source-map',
});

/**
 * Select which plugins to use to optimize the bundle's handling of
 * third party dependencies.
 *
 * If there is a dllPlugin key on the project's package.json, the
 * Webpack DLL Plugin will be used.  Otherwise the CommonsChunkPlugin
 * will be used.
 *
 */
function dependencyHandlers() {
  // Don't do anything during the DLL Build step
  if (process.env.BUILDING_DLL) { return []; }

  // If the package.json does not have a dllPlugin property, use the CommonsChunkPlugin
  if (!dllPlugin) {
    return [
      new webpack.optimize.CommonsChunkPlugin({
        name: 'vendor',
        children: true,
        minChunks: 2,
        async: true,
      }),
    ];
  }

  const dllPath = path.resolve(process.cwd(), dllPlugin.path || 'node_modules/react-boilerplate-dlls');

  /**
   * If DLLs aren't explicitly defined, we assume all production dependencies listed in package.json
   * Reminder: You need to exclude any server side dependencies by listing them in dllConfig.exclude
   *
   * @see https://github.com/mxstbr/react-boilerplate/tree/master/docs/general/webpack.md
   */
  if (!dllPlugin.dlls) {
    const manifestPath = path.resolve(dllPath, 'reactBoilerplateDeps.json');

    if (!fs.existsSync(manifestPath)) {
      logger.error('The DLL manifest is missing. Please run `npm run build:dll`');
      process.exit(0);
    }

    return [
      new webpack.DllReferencePlugin({
        context: process.cwd(),
        manifest: require(manifestPath), // eslint-disable-line global-require
      }),
    ];
  }

  // If DLLs are explicitly defined, we automatically create a DLLReferencePlugin for each of them.
  const dllManifests = Object.keys(dllPlugin.dlls).map((name) => path.join(dllPath, `/${name}.json`));

  return dllManifests.map((manifestPath) => {
    if (!fs.existsSync(path)) {
      if (!fs.existsSync(manifestPath)) {
        logger.error(`The following Webpack DLL manifest is missing: ${path.basename(manifestPath)}`);
        logger.error(`Expected to find it in ${dllPath}`);
        logger.error('Please run: npm run build:dll');

        process.exit(0);
      }
    }

    return new webpack.DllReferencePlugin({
      context: process.cwd(),
      manifest: require(manifestPath), // eslint-disable-line global-require
    });
  });
}

/**
 * We dynamically generate the HTML content in development so that the different
 * DLL Javascript files are loaded in script tags and available to our application.
 */
function templateContent() {
  const html = fs.readFileSync(
    path.resolve(process.cwd(), 'app/index.html')
  ).toString();

  if (!dllPlugin) { return html; }

  const doc = cheerio(html);
  const body = doc.find('body');
  const dllNames = !dllPlugin.dlls ? ['reactBoilerplateDeps'] : Object.keys(dllPlugin.dlls);

  dllNames.forEach((dllName) => body.append(`<script data-dll='true' src='/${dllName}.dll.js'></script>`));

  return doc.toString();
}
<commit_msg>Change to get source map debugging working in Chrome<commit_after>/**
 * DEVELOPMENT WEBPACK CONFIGURATION
 */

const path = require('path');
const fs = require('fs');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const logger = require('../../server/logger');
const cheerio = require('cheerio');
const pkg = require(path.resolve(process.cwd(), 'package.json'));
const dllPlugin = pkg.dllPlugin;

const plugins = [
  new webpack.HotModuleReplacementPlugin(), // Tell webpack we want hot reloading
  new webpack.NoErrorsPlugin(),
  new HtmlWebpackPlugin({
    inject: true, // Inject all files that are generated by webpack, e.g. bundle.js
    templateContent: templateContent(), // eslint-disable-line no-use-before-define
  }),
];

module.exports = require('./webpack.base.babel')({
  // Add hot reloading in development
  entry: [
    'eventsource-polyfill', // Necessary for hot reloading with IE
    'webpack-hot-middleware/client',
    path.join(process.cwd(), 'app/app.js'), // Start with js/app.js
  ],

  // Don't use hashes in dev mode for better performance
  output: {
    filename: '[name].js',
    chunkFilename: '[name].chunk.js',
  },

  // Add development plugins
  plugins: dependencyHandlers().concat(plugins), // eslint-disable-line no-use-before-define

  // Tell babel that we want to hot-reload
  babelQuery: {
    presets: ['react-hmre'],
  },

  // Emit a source map for easier debugging
  devtool: 'eval-source-map',
});

/**
 * Select which plugins to use to optimize the bundle's handling of
 * third party dependencies.
 *
 * If there is a dllPlugin key on the project's package.json, the
 * Webpack DLL Plugin will be used.  Otherwise the CommonsChunkPlugin
 * will be used.
 *
 */
function dependencyHandlers() {
  // Don't do anything during the DLL Build step
  if (process.env.BUILDING_DLL) { return []; }

  // If the package.json does not have a dllPlugin property, use the CommonsChunkPlugin
  if (!dllPlugin) {
    return [
      new webpack.optimize.CommonsChunkPlugin({
        name: 'vendor',
        children: true,
        minChunks: 2,
        async: true,
      }),
    ];
  }

  const dllPath = path.resolve(process.cwd(), dllPlugin.path || 'node_modules/react-boilerplate-dlls');

  /**
   * If DLLs aren't explicitly defined, we assume all production dependencies listed in package.json
   * Reminder: You need to exclude any server side dependencies by listing them in dllConfig.exclude
   *
   * @see https://github.com/mxstbr/react-boilerplate/tree/master/docs/general/webpack.md
   */
  if (!dllPlugin.dlls) {
    const manifestPath = path.resolve(dllPath, 'reactBoilerplateDeps.json');

    if (!fs.existsSync(manifestPath)) {
      logger.error('The DLL manifest is missing. Please run `npm run build:dll`');
      process.exit(0);
    }

    return [
      new webpack.DllReferencePlugin({
        context: process.cwd(),
        manifest: require(manifestPath), // eslint-disable-line global-require
      }),
    ];
  }

  // If DLLs are explicitly defined, we automatically create a DLLReferencePlugin for each of them.
  const dllManifests = Object.keys(dllPlugin.dlls).map((name) => path.join(dllPath, `/${name}.json`));

  return dllManifests.map((manifestPath) => {
    if (!fs.existsSync(path)) {
      if (!fs.existsSync(manifestPath)) {
        logger.error(`The following Webpack DLL manifest is missing: ${path.basename(manifestPath)}`);
        logger.error(`Expected to find it in ${dllPath}`);
        logger.error('Please run: npm run build:dll');

        process.exit(0);
      }
    }

    return new webpack.DllReferencePlugin({
      context: process.cwd(),
      manifest: require(manifestPath), // eslint-disable-line global-require
    });
  });
}

/**
 * We dynamically generate the HTML content in development so that the different
 * DLL Javascript files are loaded in script tags and available to our application.
 */
function templateContent() {
  const html = fs.readFileSync(
    path.resolve(process.cwd(), 'app/index.html')
  ).toString();

  if (!dllPlugin) { return html; }

  const doc = cheerio(html);
  const body = doc.find('body');
  const dllNames = !dllPlugin.dlls ? ['reactBoilerplateDeps'] : Object.keys(dllPlugin.dlls);

  dllNames.forEach((dllName) => body.append(`<script data-dll='true' src='/${dllName}.dll.js'></script>`));

  return doc.toString();
}
<|endoftext|>


<commit_before>/**
 * Copyright (C) 2019  The Software Heritage developers
 * See the AUTHORS file at the top-level directory of this distribution
 * License: GNU Affero General Public License version 3, or any later version
 * See top-level LICENSE file for more information
 */

// This is a plugin for webpack >= 4 enabling to generate a Web Labels page intended
// to be consume by the LibreJS Firefox plugin (https://www.gnu.org/software/librejs/).
// See README.md for its complete documentation.

const ejs = require('ejs');
const fs = require('fs');
const log = require('webpack-log');
const path = require('path');
const schema = require('./plugin-options-schema.json');
const spdxParse = require('spdx-expression-parse');
const spdxLicensesMapping = require('./spdx-licenses-mapping');
const validateOptions = require('schema-utils');

const pluginName = 'GenerateWebLabelsPlugin';

class GenerateWebLabelsPlugin {

  constructor(opts) {
    // check that provided options match JSON schema
    validateOptions(schema, opts, pluginName);
    this.options = opts || {};
    this.weblabelsDirName = this.options['outputDir'] || 'jssources';
    this.outputType = this.options['outputType'] || 'html';
    // source file extension handled by webpack and compiled to js
    this.srcExts = ['js', 'ts', 'coffee', 'lua'];
    this.srcExtsRegexp = new RegExp('^.*.(' + this.srcExts.join('|') + ')$');
    this.chunkNameToJsAsset = {};
    this.chunkJsAssetToSrcFiles = {};
    this.srcIdsInChunkJsAsset = {};
    this.packageJsonCache = {};
    this.packageLicenseFile = {};
    this.exclude = [];
    this.copiedFiles = new Set();
    this.logger = log({name: pluginName});
    // populate module prefix patterns to exclude
    if (Array.isArray(this.options['exclude'])) {
      this.options['exclude'].forEach(toExclude => {
        if (!toExclude.startsWith('.')) {
          this.exclude.push('./' + path.join('node_modules', toExclude));
        } else {
          this.exclude.push(toExclude);
        }
      });
    }
  }

  apply(compiler) {
    compiler.hooks.done.tap(pluginName, statsObj => {

      // get the stats object in JSON format
      let stats = statsObj.toJson();
      this.stats = stats;

      // set output folder
      this.weblabelsOutputDir = path.join(stats.outputPath, this.weblabelsDirName);
      this.recursiveMkdir(this.weblabelsOutputDir);

      // map each generated webpack chunk to its js asset
      Object.keys(stats.assetsByChunkName).forEach((chunkName, i) => {
        if (Array.isArray(stats.assetsByChunkName[chunkName])) {
          for (let asset of stats.assetsByChunkName[chunkName]) {
            if (asset.endsWith('.js')) {
              this.chunkNameToJsAsset[chunkName] = asset;
              this.chunkNameToJsAsset[i] = asset;
              break;
            }
          }
        } else if (stats.assetsByChunkName[chunkName].endsWith('.js')) {
          this.chunkNameToJsAsset[chunkName] = stats.assetsByChunkName[chunkName];
          this.chunkNameToJsAsset[i] = stats.assetsByChunkName[chunkName];
        }
      });

      // iterate on all bundled webpack modules
      stats.modules.forEach(mod => {

        let srcFilePath = mod.name;

        // do not process non js related modules
        if (!this.srcExtsRegexp.test(srcFilePath)) {
          return;
        }

        // do not process modules unrelated to a source file
        if (!srcFilePath.startsWith('./')) {
          return;
        }

        // do not process modules in the exclusion list
        for (let toExclude of this.exclude) {
          if (srcFilePath.startsWith(toExclude)) {
            return;
          }
        }

        // remove webpack loader call if any
        let loaderEndPos = srcFilePath.indexOf('!');
        if (loaderEndPos !== -1) {
          srcFilePath = srcFilePath.slice(loaderEndPos + 1);
        }

        // iterate on all chunks containing the module
        mod.chunks.forEach(chunk => {

          let chunkJsAsset = stats.publicPath + this.chunkNameToJsAsset[chunk];

          // init the chunk to source files mapping if needed
          if (!this.chunkJsAssetToSrcFiles.hasOwnProperty(chunkJsAsset)) {
            this.chunkJsAssetToSrcFiles[chunkJsAsset] = [];
            this.srcIdsInChunkJsAsset[chunkJsAsset] = new Set();
          }
          // check if the source file needs to be replaces
          if (this.options['srcReplace'] && this.options['srcReplace'].hasOwnProperty(srcFilePath)) {
            srcFilePath = this.options['srcReplace'][srcFilePath];
          }

          // init source file metadata
          let srcFileData = {'id': this.cleanupPath(srcFilePath)};

          // find and parse the corresponding package.json file
          let packageJsonPath;
          let nodeModule = srcFilePath.startsWith('./node_modules/');
          if (nodeModule) {
            packageJsonPath = this.findPackageJsonPath(srcFilePath);
          } else {
            packageJsonPath = './package.json';
          }
          let packageJson = this.parsePackageJson(packageJsonPath);

          // extract license information, overriding it if needed
          let licenseOverridden = false;
          let licenseFilePath;
          if (this.options['licenseOverride']) {
            for (let srcFilePrefixKey of Object.keys(this.options['licenseOverride'])) {
              let srcFilePrefix = srcFilePrefixKey;
              if (!srcFilePrefixKey.startsWith('.')) {
                srcFilePrefix = './' + path.join('node_modules', srcFilePrefixKey);
              }
              if (srcFilePath.startsWith(srcFilePrefix)) {
                let spdxLicenseExpression = this.options['licenseOverride'][srcFilePrefixKey]['spdxLicenseExpression'];
                licenseFilePath = this.options['licenseOverride'][srcFilePrefixKey]['licenseFilePath'];
                let parsedSpdxLicenses = this.parseSpdxLicenseExpression(spdxLicenseExpression, `file ${srcFilePath}`);
                srcFileData['licenses'] = this.spdxToWebLabelsLicenses(parsedSpdxLicenses);
                licenseOverridden = true;
                break;
              }
            }
          }

          if (!licenseOverridden) {
            srcFileData['licenses'] = this.extractLicenseInformation(packageJson);
            let licenseDir = path.join(...packageJsonPath.split('/').slice(0, -1));
            licenseFilePath = this.findLicenseFile(licenseDir);
          }

          // copy original license file and get its url
          let licenseCopyUrl = this.copyLicenseFile(licenseFilePath);
          srcFileData['licenses'].forEach(license => {
            license['copy_url'] = licenseCopyUrl;
          });

          // generate url for downloading non-minified source code
          srcFileData['src_url'] = stats.publicPath + path.join(this.weblabelsDirName, srcFileData['id']);

          // add source file metadata to the webpack chunk
          this.addSrcFileDataToJsChunkAsset(chunkJsAsset, srcFileData);
          // copy non-minified source to output folder
          this.copyFileToOutputPath(srcFilePath);
        });
      });

      // process additional scripts if needed
      if (this.options['additionalScripts']) {
        for (let script of Object.keys(this.options['additionalScripts'])) {
          let scriptFilesData = this.options['additionalScripts'][script];
          if (script.indexOf('://') === -1 && !script.startsWith('/')) {
            script = stats.publicPath + script;
          }
          this.chunkJsAssetToSrcFiles[script] = [];
          this.srcIdsInChunkJsAsset[script] = new Set();
          for (let scriptSrc of scriptFilesData) {
            let scriptSrcData = {'id': scriptSrc['id']};
            let licenceFilePath = scriptSrc['licenseFilePath'];
            let parsedSpdxLicenses = this.parseSpdxLicenseExpression(scriptSrc['spdxLicenseExpression'],
                                                                     `file ${scriptSrc['path']}`);
            scriptSrcData['licenses'] = this.spdxToWebLabelsLicenses(parsedSpdxLicenses);
            let licenseCopyUrl = this.copyLicenseFile(licenceFilePath);
            scriptSrcData['licenses'].forEach(license => {
              license['copy_url'] = licenseCopyUrl;
            });
            if (scriptSrc['path'].indexOf('://') === -1 && !scriptSrc['path'].startsWith('/')) {
              scriptSrcData['src_url'] = stats.publicPath + path.join(this.weblabelsDirName, scriptSrc['id']);
            } else {
              scriptSrcData['src_url'] = scriptSrc['path'];
            }
            this.addSrcFileDataToJsChunkAsset(script, scriptSrcData);
            this.copyFileToOutputPath(scriptSrc['path']);
          }
        }
      }

      if (this.outputType === 'json') {
        // generate the jslicenses.json file
        let weblabelsData = JSON.stringify(this.chunkJsAssetToSrcFiles);
        let weblabelsJsonFile = path.join(this.weblabelsOutputDir, 'jslicenses.json');
        fs.writeFileSync(weblabelsJsonFile, weblabelsData);
      } else {
        // generate the jslicenses.html file
        let weblabelsPageFile = path.join(this.weblabelsOutputDir, 'jslicenses.html');
        ejs.renderFile(path.join(__dirname, 'jslicenses.ejs'),
                       {'jslicenses_data': this.chunkJsAssetToSrcFiles},
                       {'rmWhitespace': true},
                       (e, str) => {
                         fs.writeFileSync(weblabelsPageFile, str);
                       });
      }
    });
  }

  addSrcFileDataToJsChunkAsset(chunkJsAsset, srcFileData) {
    if (!this.srcIdsInChunkJsAsset[chunkJsAsset].has(srcFileData['id'])) {
      this.chunkJsAssetToSrcFiles[chunkJsAsset].push(srcFileData);
      this.srcIdsInChunkJsAsset[chunkJsAsset].add(srcFileData['id']);
    }
  }

  cleanupPath(moduleFilePath) {
    return moduleFilePath.replace(/^[./]*node_modules\//, '').replace(/^.\//, '');
  }

  findPackageJsonPath(srcFilePath) {
    let pathSplit = srcFilePath.split('/');
    let packageJsonPath;
    for (let i = 3; i < pathSplit.length; ++i) {
      packageJsonPath = path.join(...pathSplit.slice(0, i), 'package.json');
      if (fs.existsSync(packageJsonPath)) {
        break;
      }
    }
    return packageJsonPath;
  }

  findLicenseFile(packageJsonDir) {
    if (!this.packageLicenseFile.hasOwnProperty(packageJsonDir)) {
      let foundLicenseFile;
      fs.readdirSync(packageJsonDir).forEach(file => {
        if (foundLicenseFile) {
          return;
        }
        if (file.toLowerCase().startsWith('license')) {
          foundLicenseFile = path.join(packageJsonDir, file);
        }
      });
      this.packageLicenseFile[packageJsonDir] = foundLicenseFile;
    }
    return this.packageLicenseFile[packageJsonDir];
  }

  copyLicenseFile(licenseFilePath) {
    let licenseCopyPath = '';
    if (licenseFilePath && fs.existsSync(licenseFilePath)) {
      let ext = '';
      // add a .txt extension in order to serve license file with text/plain
      // content type to client browsers
      if (licenseFilePath.toLowerCase().indexOf('license.') === -1) {
        ext = '.txt';
      }
      this.copyFileToOutputPath(licenseFilePath, ext);
      licenseFilePath = this.cleanupPath(licenseFilePath);
      licenseCopyPath = this.stats.publicPath + path.join(this.weblabelsDirName, licenseFilePath + ext);
    }
    return licenseCopyPath;
  }

  parsePackageJson(packageJsonPath) {
    if (!this.packageJsonCache.hasOwnProperty(packageJsonPath)) {
      let packageJsonStr = fs.readFileSync(packageJsonPath).toString('utf8');
      this.packageJsonCache[packageJsonPath] = JSON.parse(packageJsonStr);
    }
    return this.packageJsonCache[packageJsonPath];
  }

  parseSpdxLicenseExpression(spdxLicenseExpression, context) {
    let parsedLicense;
    try {
      parsedLicense = spdxParse(spdxLicenseExpression);
      if (spdxLicenseExpression.indexOf('AND') !== -1) {
        this.logger.warn(`The SPDX license expression '${spdxLicenseExpression}' associated to ${context} ` +
                         'contains an AND operator, this is currently not properly handled and erroneous ' +
                         'licenses information may be provided to LibreJS');
      }
    } catch (e) {
      this.logger.warn(`Unable to parse the SPDX license expression '${spdxLicenseExpression}' associated to ${context}.`);
      this.logger.warn('Some generated JavaScript assets may be blocked by LibreJS due to missing license information.');
      parsedLicense = {'license': spdxLicenseExpression};
    }
    return parsedLicense;
  }

  spdxToWebLabelsLicense(spdxLicenceId) {
    for (let i = 0; i < spdxLicensesMapping.length; ++i) {
      if (spdxLicensesMapping[i]['spdx_ids'].indexOf(spdxLicenceId) !== -1) {
        let licenseData = Object.assign({}, spdxLicensesMapping[i]);
        delete licenseData['spdx_ids'];
        delete licenseData['magnet_link'];
        licenseData['copy_url'] = '';
        return licenseData;
      }
    }
    this.logger.warn(`Unable to associate the SPDX license identifier '${spdxLicenceId}' to a LibreJS supported license.`);
    this.logger.warn('Some generated JavaScript assets may be blocked by LibreJS due to missing license information.');
    return {
      'name': spdxLicenceId,
      'url': '',
      'copy_url': ''
    };
  }

  spdxToWebLabelsLicenses(spdxLicenses) {
    // This method simply extracts all referenced licenses in the SPDX expression
    // regardless of their combinations.
    // TODO: Handle licenses combination properly once LibreJS has a spec for it.
    let ret = [];
    if (spdxLicenses.hasOwnProperty('license')) {
      ret.push(this.spdxToWebLabelsLicense(spdxLicenses['license']));
    } else if (spdxLicenses.hasOwnProperty('left')) {
      if (spdxLicenses['left'].hasOwnProperty('license')) {
        let licenseData = this.spdxToWebLabelsLicense(spdxLicenses['left']['license']);
        ret.push(licenseData);
      } else {
        ret = ret.concat(this.spdxToWebLabelsLicenses(spdxLicenses['left']));
      }
      ret = ret.concat(this.spdxToWebLabelsLicenses(spdxLicenses['right']));
    }
    return ret;
  }

  extractLicenseInformation(packageJson) {
    let spdxLicenseExpression;
    if (packageJson.hasOwnProperty('license')) {
      spdxLicenseExpression = packageJson['license'];
    } else if (packageJson.hasOwnProperty('licenses')) {
      // for node packages using deprecated licenses property
      let licenses = packageJson['licenses'];
      if (Array.isArray(licenses)) {
        let l = [];
        licenses.forEach(license => {
          l.push(license['type']);
        });
        spdxLicenseExpression = l.join(' OR ');
      } else {
        spdxLicenseExpression = licenses['type'];
      }
    }
    let parsedSpdxLicenses = this.parseSpdxLicenseExpression(spdxLicenseExpression,
                                                             `module ${packageJson['name']}`);
    return this.spdxToWebLabelsLicenses(parsedSpdxLicenses);
  }

  copyFileToOutputPath(srcFilePath, ext = '') {
    if (this.copiedFiles.has(srcFilePath) || srcFilePath.indexOf('://') !== -1 ||
        !fs.existsSync(srcFilePath)) {
      return;
    }
    let destPath = this.cleanupPath(srcFilePath);
    let destDir = path.join(this.weblabelsOutputDir, ...destPath.split('/').slice(0, -1));
    this.recursiveMkdir(destDir);
    destPath = path.join(this.weblabelsOutputDir, destPath + ext);
    fs.copyFileSync(srcFilePath, destPath);
    this.copiedFiles.add(srcFilePath);
  }

  recursiveMkdir(destPath) {
    let destPathSplit = destPath.split('/');
    for (let i = 1; i < destPathSplit.length; ++i) {
      let currentPath = path.join('/', ...destPathSplit.slice(0, i + 1));
      if (!fs.existsSync(currentPath)) {
        fs.mkdirSync(currentPath);
      }
    }
  }

};

module.exports = GenerateWebLabelsPlugin;
<commit_msg>generate-weblabels-webpack-plugin: Sort bundled js files by name<commit_after>/**
 * Copyright (C) 2019  The Software Heritage developers
 * See the AUTHORS file at the top-level directory of this distribution
 * License: GNU Affero General Public License version 3, or any later version
 * See top-level LICENSE file for more information
 */

// This is a plugin for webpack >= 4 enabling to generate a Web Labels page intended
// to be consume by the LibreJS Firefox plugin (https://www.gnu.org/software/librejs/).
// See README.md for its complete documentation.

const ejs = require('ejs');
const fs = require('fs');
const log = require('webpack-log');
const path = require('path');
const schema = require('./plugin-options-schema.json');
const spdxParse = require('spdx-expression-parse');
const spdxLicensesMapping = require('./spdx-licenses-mapping');
const validateOptions = require('schema-utils');

const pluginName = 'GenerateWebLabelsPlugin';

class GenerateWebLabelsPlugin {

  constructor(opts) {
    // check that provided options match JSON schema
    validateOptions(schema, opts, pluginName);
    this.options = opts || {};
    this.weblabelsDirName = this.options['outputDir'] || 'jssources';
    this.outputType = this.options['outputType'] || 'html';
    // source file extension handled by webpack and compiled to js
    this.srcExts = ['js', 'ts', 'coffee', 'lua'];
    this.srcExtsRegexp = new RegExp('^.*.(' + this.srcExts.join('|') + ')$');
    this.chunkNameToJsAsset = {};
    this.chunkJsAssetToSrcFiles = {};
    this.srcIdsInChunkJsAsset = {};
    this.packageJsonCache = {};
    this.packageLicenseFile = {};
    this.exclude = [];
    this.copiedFiles = new Set();
    this.logger = log({name: pluginName});
    // populate module prefix patterns to exclude
    if (Array.isArray(this.options['exclude'])) {
      this.options['exclude'].forEach(toExclude => {
        if (!toExclude.startsWith('.')) {
          this.exclude.push('./' + path.join('node_modules', toExclude));
        } else {
          this.exclude.push(toExclude);
        }
      });
    }
  }

  apply(compiler) {
    compiler.hooks.done.tap(pluginName, statsObj => {

      // get the stats object in JSON format
      let stats = statsObj.toJson();
      this.stats = stats;

      // set output folder
      this.weblabelsOutputDir = path.join(stats.outputPath, this.weblabelsDirName);
      this.recursiveMkdir(this.weblabelsOutputDir);

      // map each generated webpack chunk to its js asset
      Object.keys(stats.assetsByChunkName).forEach((chunkName, i) => {
        if (Array.isArray(stats.assetsByChunkName[chunkName])) {
          for (let asset of stats.assetsByChunkName[chunkName]) {
            if (asset.endsWith('.js')) {
              this.chunkNameToJsAsset[chunkName] = asset;
              this.chunkNameToJsAsset[i] = asset;
              break;
            }
          }
        } else if (stats.assetsByChunkName[chunkName].endsWith('.js')) {
          this.chunkNameToJsAsset[chunkName] = stats.assetsByChunkName[chunkName];
          this.chunkNameToJsAsset[i] = stats.assetsByChunkName[chunkName];
        }
      });

      // iterate on all bundled webpack modules
      stats.modules.forEach(mod => {

        let srcFilePath = mod.name;

        // do not process non js related modules
        if (!this.srcExtsRegexp.test(srcFilePath)) {
          return;
        }

        // do not process modules unrelated to a source file
        if (!srcFilePath.startsWith('./')) {
          return;
        }

        // do not process modules in the exclusion list
        for (let toExclude of this.exclude) {
          if (srcFilePath.startsWith(toExclude)) {
            return;
          }
        }

        // remove webpack loader call if any
        let loaderEndPos = srcFilePath.indexOf('!');
        if (loaderEndPos !== -1) {
          srcFilePath = srcFilePath.slice(loaderEndPos + 1);
        }

        // iterate on all chunks containing the module
        mod.chunks.forEach(chunk => {

          let chunkJsAsset = stats.publicPath + this.chunkNameToJsAsset[chunk];

          // init the chunk to source files mapping if needed
          if (!this.chunkJsAssetToSrcFiles.hasOwnProperty(chunkJsAsset)) {
            this.chunkJsAssetToSrcFiles[chunkJsAsset] = [];
            this.srcIdsInChunkJsAsset[chunkJsAsset] = new Set();
          }
          // check if the source file needs to be replaces
          if (this.options['srcReplace'] && this.options['srcReplace'].hasOwnProperty(srcFilePath)) {
            srcFilePath = this.options['srcReplace'][srcFilePath];
          }

          // init source file metadata
          let srcFileData = {'id': this.cleanupPath(srcFilePath)};

          // find and parse the corresponding package.json file
          let packageJsonPath;
          let nodeModule = srcFilePath.startsWith('./node_modules/');
          if (nodeModule) {
            packageJsonPath = this.findPackageJsonPath(srcFilePath);
          } else {
            packageJsonPath = './package.json';
          }
          let packageJson = this.parsePackageJson(packageJsonPath);

          // extract license information, overriding it if needed
          let licenseOverridden = false;
          let licenseFilePath;
          if (this.options['licenseOverride']) {
            for (let srcFilePrefixKey of Object.keys(this.options['licenseOverride'])) {
              let srcFilePrefix = srcFilePrefixKey;
              if (!srcFilePrefixKey.startsWith('.')) {
                srcFilePrefix = './' + path.join('node_modules', srcFilePrefixKey);
              }
              if (srcFilePath.startsWith(srcFilePrefix)) {
                let spdxLicenseExpression = this.options['licenseOverride'][srcFilePrefixKey]['spdxLicenseExpression'];
                licenseFilePath = this.options['licenseOverride'][srcFilePrefixKey]['licenseFilePath'];
                let parsedSpdxLicenses = this.parseSpdxLicenseExpression(spdxLicenseExpression, `file ${srcFilePath}`);
                srcFileData['licenses'] = this.spdxToWebLabelsLicenses(parsedSpdxLicenses);
                licenseOverridden = true;
                break;
              }
            }
          }

          if (!licenseOverridden) {
            srcFileData['licenses'] = this.extractLicenseInformation(packageJson);
            let licenseDir = path.join(...packageJsonPath.split('/').slice(0, -1));
            licenseFilePath = this.findLicenseFile(licenseDir);
          }

          // copy original license file and get its url
          let licenseCopyUrl = this.copyLicenseFile(licenseFilePath);
          srcFileData['licenses'].forEach(license => {
            license['copy_url'] = licenseCopyUrl;
          });

          // generate url for downloading non-minified source code
          srcFileData['src_url'] = stats.publicPath + path.join(this.weblabelsDirName, srcFileData['id']);

          // add source file metadata to the webpack chunk
          this.addSrcFileDataToJsChunkAsset(chunkJsAsset, srcFileData);
          // copy non-minified source to output folder
          this.copyFileToOutputPath(srcFilePath);
        });
      });

      // process additional scripts if needed
      if (this.options['additionalScripts']) {
        for (let script of Object.keys(this.options['additionalScripts'])) {
          let scriptFilesData = this.options['additionalScripts'][script];
          if (script.indexOf('://') === -1 && !script.startsWith('/')) {
            script = stats.publicPath + script;
          }
          this.chunkJsAssetToSrcFiles[script] = [];
          this.srcIdsInChunkJsAsset[script] = new Set();
          for (let scriptSrc of scriptFilesData) {
            let scriptSrcData = {'id': scriptSrc['id']};
            let licenceFilePath = scriptSrc['licenseFilePath'];
            let parsedSpdxLicenses = this.parseSpdxLicenseExpression(scriptSrc['spdxLicenseExpression'],
                                                                     `file ${scriptSrc['path']}`);
            scriptSrcData['licenses'] = this.spdxToWebLabelsLicenses(parsedSpdxLicenses);
            let licenseCopyUrl = this.copyLicenseFile(licenceFilePath);
            scriptSrcData['licenses'].forEach(license => {
              license['copy_url'] = licenseCopyUrl;
            });
            if (scriptSrc['path'].indexOf('://') === -1 && !scriptSrc['path'].startsWith('/')) {
              scriptSrcData['src_url'] = stats.publicPath + path.join(this.weblabelsDirName, scriptSrc['id']);
            } else {
              scriptSrcData['src_url'] = scriptSrc['path'];
            }
            this.addSrcFileDataToJsChunkAsset(script, scriptSrcData);
            this.copyFileToOutputPath(scriptSrc['path']);
          }
        }
      }

      for (let srcFiles of Object.values(this.chunkJsAssetToSrcFiles)) {
        srcFiles.sort((a, b) => a.id.localeCompare(b.id));
      }

      if (this.outputType === 'json') {
        // generate the jslicenses.json file
        let weblabelsData = JSON.stringify(this.chunkJsAssetToSrcFiles);
        let weblabelsJsonFile = path.join(this.weblabelsOutputDir, 'jslicenses.json');
        fs.writeFileSync(weblabelsJsonFile, weblabelsData);
      } else {
        // generate the jslicenses.html file
        let weblabelsPageFile = path.join(this.weblabelsOutputDir, 'jslicenses.html');
        ejs.renderFile(path.join(__dirname, 'jslicenses.ejs'),
                       {'jslicenses_data': this.chunkJsAssetToSrcFiles},
                       {'rmWhitespace': true},
                       (e, str) => {
                         fs.writeFileSync(weblabelsPageFile, str);
                       });
      }
    });
  }

  addSrcFileDataToJsChunkAsset(chunkJsAsset, srcFileData) {
    if (!this.srcIdsInChunkJsAsset[chunkJsAsset].has(srcFileData['id'])) {
      this.chunkJsAssetToSrcFiles[chunkJsAsset].push(srcFileData);
      this.srcIdsInChunkJsAsset[chunkJsAsset].add(srcFileData['id']);
    }
  }

  cleanupPath(moduleFilePath) {
    return moduleFilePath.replace(/^[./]*node_modules\//, '').replace(/^.\//, '');
  }

  findPackageJsonPath(srcFilePath) {
    let pathSplit = srcFilePath.split('/');
    let packageJsonPath;
    for (let i = 3; i < pathSplit.length; ++i) {
      packageJsonPath = path.join(...pathSplit.slice(0, i), 'package.json');
      if (fs.existsSync(packageJsonPath)) {
        break;
      }
    }
    return packageJsonPath;
  }

  findLicenseFile(packageJsonDir) {
    if (!this.packageLicenseFile.hasOwnProperty(packageJsonDir)) {
      let foundLicenseFile;
      fs.readdirSync(packageJsonDir).forEach(file => {
        if (foundLicenseFile) {
          return;
        }
        if (file.toLowerCase().startsWith('license')) {
          foundLicenseFile = path.join(packageJsonDir, file);
        }
      });
      this.packageLicenseFile[packageJsonDir] = foundLicenseFile;
    }
    return this.packageLicenseFile[packageJsonDir];
  }

  copyLicenseFile(licenseFilePath) {
    let licenseCopyPath = '';
    if (licenseFilePath && fs.existsSync(licenseFilePath)) {
      let ext = '';
      // add a .txt extension in order to serve license file with text/plain
      // content type to client browsers
      if (licenseFilePath.toLowerCase().indexOf('license.') === -1) {
        ext = '.txt';
      }
      this.copyFileToOutputPath(licenseFilePath, ext);
      licenseFilePath = this.cleanupPath(licenseFilePath);
      licenseCopyPath = this.stats.publicPath + path.join(this.weblabelsDirName, licenseFilePath + ext);
    }
    return licenseCopyPath;
  }

  parsePackageJson(packageJsonPath) {
    if (!this.packageJsonCache.hasOwnProperty(packageJsonPath)) {
      let packageJsonStr = fs.readFileSync(packageJsonPath).toString('utf8');
      this.packageJsonCache[packageJsonPath] = JSON.parse(packageJsonStr);
    }
    return this.packageJsonCache[packageJsonPath];
  }

  parseSpdxLicenseExpression(spdxLicenseExpression, context) {
    let parsedLicense;
    try {
      parsedLicense = spdxParse(spdxLicenseExpression);
      if (spdxLicenseExpression.indexOf('AND') !== -1) {
        this.logger.warn(`The SPDX license expression '${spdxLicenseExpression}' associated to ${context} ` +
                         'contains an AND operator, this is currently not properly handled and erroneous ' +
                         'licenses information may be provided to LibreJS');
      }
    } catch (e) {
      this.logger.warn(`Unable to parse the SPDX license expression '${spdxLicenseExpression}' associated to ${context}.`);
      this.logger.warn('Some generated JavaScript assets may be blocked by LibreJS due to missing license information.');
      parsedLicense = {'license': spdxLicenseExpression};
    }
    return parsedLicense;
  }

  spdxToWebLabelsLicense(spdxLicenceId) {
    for (let i = 0; i < spdxLicensesMapping.length; ++i) {
      if (spdxLicensesMapping[i]['spdx_ids'].indexOf(spdxLicenceId) !== -1) {
        let licenseData = Object.assign({}, spdxLicensesMapping[i]);
        delete licenseData['spdx_ids'];
        delete licenseData['magnet_link'];
        licenseData['copy_url'] = '';
        return licenseData;
      }
    }
    this.logger.warn(`Unable to associate the SPDX license identifier '${spdxLicenceId}' to a LibreJS supported license.`);
    this.logger.warn('Some generated JavaScript assets may be blocked by LibreJS due to missing license information.');
    return {
      'name': spdxLicenceId,
      'url': '',
      'copy_url': ''
    };
  }

  spdxToWebLabelsLicenses(spdxLicenses) {
    // This method simply extracts all referenced licenses in the SPDX expression
    // regardless of their combinations.
    // TODO: Handle licenses combination properly once LibreJS has a spec for it.
    let ret = [];
    if (spdxLicenses.hasOwnProperty('license')) {
      ret.push(this.spdxToWebLabelsLicense(spdxLicenses['license']));
    } else if (spdxLicenses.hasOwnProperty('left')) {
      if (spdxLicenses['left'].hasOwnProperty('license')) {
        let licenseData = this.spdxToWebLabelsLicense(spdxLicenses['left']['license']);
        ret.push(licenseData);
      } else {
        ret = ret.concat(this.spdxToWebLabelsLicenses(spdxLicenses['left']));
      }
      ret = ret.concat(this.spdxToWebLabelsLicenses(spdxLicenses['right']));
    }
    return ret;
  }

  extractLicenseInformation(packageJson) {
    let spdxLicenseExpression;
    if (packageJson.hasOwnProperty('license')) {
      spdxLicenseExpression = packageJson['license'];
    } else if (packageJson.hasOwnProperty('licenses')) {
      // for node packages using deprecated licenses property
      let licenses = packageJson['licenses'];
      if (Array.isArray(licenses)) {
        let l = [];
        licenses.forEach(license => {
          l.push(license['type']);
        });
        spdxLicenseExpression = l.join(' OR ');
      } else {
        spdxLicenseExpression = licenses['type'];
      }
    }
    let parsedSpdxLicenses = this.parseSpdxLicenseExpression(spdxLicenseExpression,
                                                             `module ${packageJson['name']}`);
    return this.spdxToWebLabelsLicenses(parsedSpdxLicenses);
  }

  copyFileToOutputPath(srcFilePath, ext = '') {
    if (this.copiedFiles.has(srcFilePath) || srcFilePath.indexOf('://') !== -1 ||
        !fs.existsSync(srcFilePath)) {
      return;
    }
    let destPath = this.cleanupPath(srcFilePath);
    let destDir = path.join(this.weblabelsOutputDir, ...destPath.split('/').slice(0, -1));
    this.recursiveMkdir(destDir);
    destPath = path.join(this.weblabelsOutputDir, destPath + ext);
    fs.copyFileSync(srcFilePath, destPath);
    this.copiedFiles.add(srcFilePath);
  }

  recursiveMkdir(destPath) {
    let destPathSplit = destPath.split('/');
    for (let i = 1; i < destPathSplit.length; ++i) {
      let currentPath = path.join('/', ...destPathSplit.slice(0, i + 1));
      if (!fs.existsSync(currentPath)) {
        fs.mkdirSync(currentPath);
      }
    }
  }

};

module.exports = GenerateWebLabelsPlugin;
<|endoftext|>


<commit_before>/*
  Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com>

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/*jslint node:true browser:true */
/*global esmorph:true,esprima:true */

(function (exports) {
    'use strict';

    var Syntax = {
        AssignmentExpression: 'AssignmentExpression',
        ArrayExpression: 'ArrayExpression',
        BlockStatement: 'BlockStatement',
        BinaryExpression: 'BinaryExpression',
        BreakStatement: 'BreakStatement',
        CallExpression: 'CallExpression',
        CatchClause: 'CatchClause',
        ConditionalExpression: 'ConditionalExpression',
        ContinueStatement: 'ContinueStatement',
        DoWhileStatement: 'DoWhileStatement',
        DebuggerStatement: 'DebuggerStatement',
        EmptyStatement: 'EmptyStatement',
        ExpressionStatement: 'ExpressionStatement',
        ForStatement: 'ForStatement',
        ForInStatement: 'ForInStatement',
        FunctionDeclaration: 'FunctionDeclaration',
        FunctionExpression: 'FunctionExpression',
        Identifier: 'Identifier',
        IfStatement: 'IfStatement',
        Literal: 'Literal',
        LabeledStatement: 'LabeledStatement',
        LogicalExpression: 'LogicalExpression',
        MemberExpression: 'MemberExpression',
        NewExpression: 'NewExpression',
        ObjectExpression: 'ObjectExpression',
        Program: 'Program',
        Property: 'Property',
        ReturnStatement: 'ReturnStatement',
        SequenceExpression: 'SequenceExpression',
        SwitchStatement: 'SwitchStatement',
        SwitchCase: 'SwitchCase',
        ThisExpression: 'ThisExpression',
        ThrowStatement: 'ThrowStatement',
        TryStatement: 'TryStatement',
        UnaryExpression: 'UnaryExpression',
        UpdateExpression: 'UpdateExpression',
        VariableDeclaration: 'VariableDeclaration',
        VariableDeclarator: 'VariableDeclarator',
        WhileStatement: 'WhileStatement',
        WithStatement: 'WithStatement'
    };

    // Executes visitor on the object and its children (recursively).

    function traverse(object, visitor, master) {
        var key, child, parent, path;

        parent = (typeof master === 'undefined') ? [] : master;

        if (visitor.call(null, object, parent) === false) {
            return;
        }
        for (key in object) {
            if (object.hasOwnProperty(key)) {
                child = object[key];
                path = [ object ];
                path.push(parent);
                if (typeof child === 'object' && child !== null) {
                    traverse(child, visitor, path);
                }
            }
        }
    }

    // Insert a prolog in the body of every function.
    // It will be in the form of a function call:
    //
    //     traceName(object);
    //
    // where the object contains the following properties:
    //
    //    'name' holds the name of the function
    //    'lineNumber' holds the starting line number of the function block
    //    'range' contains the index-based range of the function
    //
    // The name of the function represents the associated reference for
    // the function (deduced on a best-effort basis if it is not
    // a function declaration).
    //
    // If traceName is a function instead of a string, it will be invoked and
    // the result will be used as the entire prolog. The arguments for the
    // invocation are the function name, range, and location info.

    function traceFunctionEntrance(traceName) {

        return function (code) {
            var tree,
                functionList,
                param,
                signature,
                pos,
                i;

            tree = esprima.parse(code, { range: true, loc: true });

            functionList = [];
            traverse(tree, function (node, path) {
                var parent;
                if (node.type === Syntax.FunctionDeclaration) {
                    functionList.push({
                        name: node.id.name,
                        range: node.range,
                        loc: node.loc,
                        blockStart: node.body.range[0]
                    });
                } else if (node.type === Syntax.FunctionExpression) {
                    parent = path[0];
                    if (parent.type === Syntax.AssignmentExpression) {
                        if (typeof parent.left.range !== 'undefined') {
                            functionList.push({
                                name: code.slice(parent.left.range[0],
                                          parent.left.range[1]).replace('"','\\"'),
                                range: node.range,
                                loc: node.loc,
                                blockStart: node.body.range[0]
                            });
                        }
                    } else if (parent.type === Syntax.VariableDeclarator) {
                        functionList.push({
                            name: parent.id.name,
                            range: node.range,
                            loc: node.loc,
                            blockStart: node.body.range[0]
                        });
                    } else if (parent.type === Syntax.CallExpression) {
                        functionList.push({
                            name: parent.id ? parent.id.name : '[Anonymous]',
                            range: node.range,
                            loc: node.loc,
                            blockStart: node.body.range[0]
                        });
                    } else if (typeof parent.length === 'number') {
                        functionList.push({
                            name: parent.id ? parent.id.name : '[Anonymous]',
                            range: node.range,
                            loc: node.loc,
                            blockStart: node.body.range[0]
                        });
                    } else if (typeof parent.key !== 'undefined') {
                        if (parent.key.type === 'Identifier') {
                            if (parent.value === node && parent.key.name) {
                                functionList.push({
                                    name: parent.key.name,
                                    range: node.range,
                                    loc: node.loc,
                                    blockStart: node.body.range[0]
                                });
                            }
                        }
                    }
                }
            });

            // Insert the instrumentation code from the last entry.
            // This is to ensure that the range for each entry remains valid)
            // (it won't shift due to some new inserting string before the range).
            for (i = functionList.length - 1; i >= 0; i -= 1) {
                param = {
                    name: functionList[i].name,
                    range: functionList[i].range,
                    loc: functionList[i].loc
                };
                if (typeof traceName === 'function') {
                    signature = traceName.call(null, param);
                } else {
                    signature = traceName + '({ ';
                    signature += 'name: \'' + functionList[i].name + '\', ';
                    if (typeof functionList[i].loc !== 'undefined') {
                        signature += 'lineNumber: ' + functionList[i].loc.start.line + ', ';
                    }
                    signature += 'range: [' + functionList[i].range[0] + ', ' +
                        functionList[i].range[1] + '] ';
                    signature += '});';
                }
                pos = functionList[i].blockStart + 1;
                code = code.slice(0, pos) + '\n' + signature + code.slice(pos, code.length);
            }

            return code;
        };
    }

    function modify(code, modifiers) {
        var i;

        if (Object.prototype.toString.call(modifiers) === '[object Array]') {
            for (i = 0; i < modifiers.length; i += 1) {
                code = modifiers[i].call(null, code);
            }
        } else if (typeof modifiers === 'function') {
            code = modifiers.call(null, code);
        } else {
            throw new Error('Wrong use of esmorph.modify() function');
        }

        return code;
    }

    // Sync with package.json.
    exports.version = '0.0.0-dev';

    exports.modify = modify;

    exports.Tracer = {
        FunctionEntrance: traceFunctionEntrance
    };

}(typeof exports === 'undefined' ? (esmorph = {}) : exports));
<commit_msg>Update to the right version of esmorph.<commit_after>/*
  Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com>

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/*jslint node:true browser:true */
/*global esmorph:true,esprima:true */

(function (exports) {
    'use strict';

    var Syntax = {
        AssignmentExpression: 'AssignmentExpression',
        ArrayExpression: 'ArrayExpression',
        BlockStatement: 'BlockStatement',
        BinaryExpression: 'BinaryExpression',
        BreakStatement: 'BreakStatement',
        CallExpression: 'CallExpression',
        CatchClause: 'CatchClause',
        ConditionalExpression: 'ConditionalExpression',
        ContinueStatement: 'ContinueStatement',
        DoWhileStatement: 'DoWhileStatement',
        DebuggerStatement: 'DebuggerStatement',
        EmptyStatement: 'EmptyStatement',
        ExpressionStatement: 'ExpressionStatement',
        ForStatement: 'ForStatement',
        ForInStatement: 'ForInStatement',
        FunctionDeclaration: 'FunctionDeclaration',
        FunctionExpression: 'FunctionExpression',
        Identifier: 'Identifier',
        IfStatement: 'IfStatement',
        Literal: 'Literal',
        LabeledStatement: 'LabeledStatement',
        LogicalExpression: 'LogicalExpression',
        MemberExpression: 'MemberExpression',
        NewExpression: 'NewExpression',
        ObjectExpression: 'ObjectExpression',
        Program: 'Program',
        Property: 'Property',
        ReturnStatement: 'ReturnStatement',
        SequenceExpression: 'SequenceExpression',
        SwitchStatement: 'SwitchStatement',
        SwitchCase: 'SwitchCase',
        ThisExpression: 'ThisExpression',
        ThrowStatement: 'ThrowStatement',
        TryStatement: 'TryStatement',
        UnaryExpression: 'UnaryExpression',
        UpdateExpression: 'UpdateExpression',
        VariableDeclaration: 'VariableDeclaration',
        VariableDeclarator: 'VariableDeclarator',
        WhileStatement: 'WhileStatement',
        WithStatement: 'WithStatement'
    };

    // Executes visitor on the object and its children (recursively).

    function traverse(object, visitor, master) {
        var key, child, parent, path;

        parent = (typeof master === 'undefined') ? [] : master;

        if (visitor.call(null, object, parent) === false) {
            return;
        }
        for (key in object) {
            if (object.hasOwnProperty(key)) {
                child = object[key];
                path = [ object ];
                path.push(parent);
                if (typeof child === 'object' && child !== null) {
                    traverse(child, visitor, path);
                }
            }
        }
    }

    // Insert a prolog in the body of every function.
    // It will be in the form of a function call:
    //
    //     traceName(object);
    //
    // where the object contains the following properties:
    //
    //    'name' holds the name of the function
    //    'lineNumber' holds the starting line number of the function block
    //    'range' contains the index-based range of the function
    //
    // The name of the function represents the associated reference for
    // the function (deduced on a best-effort basis if it is not
    // a function declaration).
    //
    // If traceName is a function instead of a string, it will be invoked and
    // the result will be used as the entire prolog. The arguments for the
    // invocation are the function name, range, and location info.

    function traceFunctionEntrance(traceName) {

        return function (code) {
            var tree,
                functionList,
                param,
                signature,
                pos,
                i;

            tree = esprima.parse(code, { range: true, loc: true });

            functionList = [];
            traverse(tree, function (node, path) {
                var parent;
                if (node.type === Syntax.FunctionDeclaration) {
                    functionList.push({
                        name: node.id.name,
                        range: node.range,
                        loc: node.loc,
                        blockStart: node.body.range[0]
                    });
                } else if (node.type === Syntax.FunctionExpression) {
                    parent = path[0];
                    if (parent.type === Syntax.AssignmentExpression) {
                        if (typeof parent.left.range !== 'undefined') {
                            functionList.push({
                                name: code.slice(parent.left.range[0],
                                          parent.left.range[1]).replace(/"/g, '\\"'),
                                range: node.range,
                                loc: node.loc,
                                blockStart: node.body.range[0]
                            });
                        }
                    } else if (parent.type === Syntax.VariableDeclarator) {
                        functionList.push({
                            name: parent.id.name,
                            range: node.range,
                            loc: node.loc,
                            blockStart: node.body.range[0]
                        });
                    } else if (parent.type === Syntax.CallExpression) {
                        functionList.push({
                            name: parent.id ? parent.id.name : '[Anonymous]',
                            range: node.range,
                            loc: node.loc,
                            blockStart: node.body.range[0]
                        });
                    } else if (typeof parent.length === 'number') {
                        functionList.push({
                            name: parent.id ? parent.id.name : '[Anonymous]',
                            range: node.range,
                            loc: node.loc,
                            blockStart: node.body.range[0]
                        });
                    } else if (typeof parent.key !== 'undefined') {
                        if (parent.key.type === 'Identifier') {
                            if (parent.value === node && parent.key.name) {
                                functionList.push({
                                    name: parent.key.name,
                                    range: node.range,
                                    loc: node.loc,
                                    blockStart: node.body.range[0]
                                });
                            }
                        }
                    }
                }
            });

            // Insert the instrumentation code from the last entry.
            // This is to ensure that the range for each entry remains valid)
            // (it won't shift due to some new inserting string before the range).
            for (i = functionList.length - 1; i >= 0; i -= 1) {
                param = {
                    name: functionList[i].name,
                    range: functionList[i].range,
                    loc: functionList[i].loc
                };
                if (typeof traceName === 'function') {
                    signature = traceName.call(null, param);
                } else {
                    signature = traceName + '({ ';
                    signature += 'name: \'' + functionList[i].name + '\', ';
                    if (typeof functionList[i].loc !== 'undefined') {
                        signature += 'lineNumber: ' + functionList[i].loc.start.line + ', ';
                    }
                    signature += 'range: [' + functionList[i].range[0] + ', ' +
                        functionList[i].range[1] + '] ';
                    signature += '});';
                }
                pos = functionList[i].blockStart + 1;
                code = code.slice(0, pos) + '\n' + signature + code.slice(pos, code.length);
            }

            return code;
        };
    }

    function modify(code, modifiers) {
        var i;

        if (Object.prototype.toString.call(modifiers) === '[object Array]') {
            for (i = 0; i < modifiers.length; i += 1) {
                code = modifiers[i].call(null, code);
            }
        } else if (typeof modifiers === 'function') {
            code = modifiers.call(null, code);
        } else {
            throw new Error('Wrong use of esmorph.modify() function');
        }

        return code;
    }

    // Sync with package.json.
    exports.version = '0.0.0-dev';

    exports.modify = modify;

    exports.Tracer = {
        FunctionEntrance: traceFunctionEntrance
    };

}(typeof exports === 'undefined' ? (esmorph = {}) : exports));
<|endoftext|>