ua-parser-caps
Version:
capabilities for ua-parser2
276 lines (238 loc) • 5.6 kB
JavaScript
/**
* handle capabilities tree
*
* Copyright (c) 2013 commenthol
* Released under the MIT License
*/
/**
* Module dependencies
*/
var path = require('path')
var jsSelect = require('js-select')
var async = require('async')
var merge = require('mergee').mergeExt
var capsFile = require('./util/file.js')
var parser = require('./parser.js')
/**
* tree object constructor
*/
function Tree () {
this.tree = {}
this.isConverted = false
}
module.exports = Tree
/**
* load one or more capabilities files and join them to one single
* capabilities tree.
*
* @param {Array|String} files : filename(s) of the capability files to load
*/
Tree.prototype.loadSync = function (files) {
var self = this
files = files || []
if (typeof (files) === 'string') {
files = [ files ]
}
if (files.length === 1 && !this._fastload(files[0])) {
// tree is fast loaded
return
}
files.forEach(function (file) {
var tree
tree = capsFile.loadSync(file)
self.add(tree)
})
self.convert()
}
/**
* load one or more capabilities files asynchronously and join them to one single
* capabilities tree.
*
* @param {Array|String} files : filename(s) of the capability files to load
* @param
*/
Tree.prototype.load = function (files, cb) {
var self = this
files = files || []
if (typeof (files) === 'string') {
files = [ files ]
}
if (files.length === 1 && !this._fastload(files[0])) {
// tree is fast loaded
return cb()
}
async.eachSeries(files, function (file, callback) {
capsFile.load(file, function (err, tree) {
if (err) {
callback(err)
} else {
self.add(tree)
callback()
}
})
}, function (err) {
if (err) {
// TODO
} else {
self.convert()
}
cb(err)
})
}
/**
* fast load capabilities as node module
* @param {String} file - the module name to require
* @return {Error}
*/
Tree.prototype._fastload = function (file) {
if (path.extname(file) === '.js') {
try {
this.tree = require(file)
return
} catch (e) {
return e
}
}
return new Error('bad extension')
}
/**
* add an new tree
*
* @param {Object} tree
*/
Tree.prototype.add = function (tree) {
if (!this.isConverted) {
if (Tree.isTree(tree)) {
Tree.flattenExtends(tree)
this.tree = merge({ ignoreNull: true }, this.tree, Tree.moveIntoArray(tree))
}
}
}
/**
* convert the tree for use within parser
*
* @param {Object} tree
*/
Tree.prototype.convert = function () {
if (!this.isConverted) {
this.isConverted = true
this.tree = Tree.convertRegexes(this.tree)
this.tree = Tree.convertDevice(this.tree)
}
}
/**
* returns the tree
*
* @return {Object} tree
*/
Tree.prototype.get = function () {
return this.tree
}
/**
* print the tree in case of debugging
*/
Tree.prototype.print = function () {
console.log(JSON.stringify(this.tree, null, ' '))
}
/**
* check if tree is a valid capabilities tree
*/
Tree.isTree = function (tree) {
return (tree && (tree.default || tree.os || tree.ua || tree.device))
}
/**
* @private
*/
Tree._find = function _find (opts, obj, fn) {
var i
if (typeof obj === 'object' && obj) {
if (Array.isArray(obj)) {
for (i = 0; i < obj.length; i++) {
_find(opts, obj[i], fn)
}
} else if (opts.key in obj) {
obj = fn(obj)
} else {
for (i in obj) {
if (Object.prototype.hasOwnProperty.call(obj, i) && !opts.test[i]) {
_find(opts, obj[i], fn)
}
}
}
}
}
/**
* flatten all `extends` before merging trees
*
* @param {Object} tree : containing `extends`
* @return {Object} tree with flattened extends
*/
Tree.flattenExtends = function (tree) {
var parse = parser.parser(tree)
var opts = {
key: 'extends',
test: { capabilities: 1 }
}
Tree._find(opts, tree, function (node) {
return parse.flattenExtend(node)
})
return tree
}
/**
* move the regexes into an array of arrays
*
* @param {Object} tree : containing `regexes` arrays
* @return {Object} tree with regexes array mapped into another array
*/
Tree.moveIntoArray = function moveIntoArray (tree) {
jsSelect(tree, '.regexes').update(function (node) {
node = [ node ]
return moveIntoArray(node)
})
return tree
}
/**
* convert all regex strings from the capabilities into regular
* expressions to speed up later processing
*
* @param {Object} tree with converted regular expressions
*/
Tree.convertRegexes = function (tree) {
jsSelect(tree, '.regexes').update(function (regexes) {
var i, j
if (regexes) {
for (i = 0; i < regexes.length; i += 1) {
for (j = 0; j < regexes[i].length; j += 1) {
if (regexes[i][j].regex && typeof (regexes[i][j].regex) === 'string') {
regexes[i][j].regex = new RegExp(regexes[i][j].regex, 'i')
} else if (regexes[i][j].regex_not && typeof (regexes[i][j].regex_not) === 'string') {
regexes[i][j].regex_not = new RegExp(regexes[i][j].regex_not, 'i')
}
}
}
}
return regexes
})
return tree
}
/**
* Normalize all brand and model nodes
*
* @param {Object} tree: capabilities tree
*/
Tree.convertDevice = function (tree) {
var nodes
nodes = jsSelect(tree, '.brand .model, .brand, .device > .family')
nodes.update(function (node) {
var attr, norm
for (attr in node) {
norm = parser.normalizeDevice(attr)
if (attr !== norm) {
node[norm] = node[attr]
}
}
return node
})
return tree
}