UNPKG

6.86 kBJavaScriptView Raw
1'use strict';
2
3/**
4 * Build in Native modules.
5 */
6var path = require('path')
7 , fs = require('fs')
8 , vm = require('vm');
9
10/**
11 * Third party modules.
12 */
13var request = require('request')
14 , yaml = require('yamlparser');
15
16/**
17 * Update the regexp.js file
18 *
19 * @param {Function} callback Completion callback.
20 * @api public
21 */
22exports.update = function update(callback) {
23 // Prepend local additions that are missing from the source
24 fs.readFile(exports.before, 'utf8', function reading(err, before) {
25 if (err) return callback(err);
26
27 // Fetch the remote resource as that is frequently updated
28 request(exports.remote, function downloading(err, res, remote) {
29 if (err) return callback(err);
30 if (res.statusCode !== 200) return callback(new Error('Invalid statusCode returned'));
31
32 // Append get some local additions that are missing from the source
33 fs.readFile(exports.after, 'utf8', function reading(err, after) {
34 if (err) return callback(err);
35
36 // Parse the contents
37 exports.parse([ before, remote, after ], function parsing(err, results, source) {
38 callback(err, results);
39
40 if (source && !err) {
41 fs.writeFile(exports.output, source, function idk(err) {
42 if (err) {
43 console.error('Failed to save the generated file due to reasons', err);
44 }
45 });
46 }
47 });
48 });
49 });
50 });
51};
52
53/**
54 * Parse the given sources.
55 *
56 * @param {Array} sources String versions of the source
57 * @param {Function} callback completion callback
58 * @api public
59 */
60exports.parse = function parse(sources, callback) {
61 var results = {};
62
63 var data = sources.reduce(function parser(memo, data) {
64 // Try to repair some of the odd structures that are in the yaml files
65 // before parsing it so we generate a uniform structure:
66
67 // Normalize the Operating system versions:
68 data = data.replace(/os_v([1-3])_replacement/gim, function replace(match, version) {
69 return 'v'+ version +'_replacement';
70 });
71
72 // Make sure that we are able to parse the yaml string
73 try { data = yaml.eval(data); }
74 catch (e) {
75 callback(e);
76 callback = null;
77 return memo;
78 }
79
80 // merge the data with the memo;
81 Object.keys(data).forEach(function (key) {
82 var results = data[key];
83 memo[key] = memo[key] || [];
84
85 for (var i = 0, l = results.length; i < l; i++) {
86 memo[key].push(results[i]);
87 }
88 });
89
90 return memo;
91 }, {});
92
93 [
94 {
95 resource: 'user_agent_parsers'
96 , replacement: 'family_replacement'
97 , name: 'browser'
98 }
99 , {
100 resource: 'device_parsers'
101 , replacement: 'device_replacement'
102 , name: 'device'
103 }
104 , {
105 resource: 'os_parsers'
106 , replacement: 'os_replacement'
107 , name: 'os'
108 }
109 ].forEach(function parsing(details) {
110 results[details.resource] = results[details.resource] || [];
111
112 var resources = data[details.resource]
113 , name = details.resource.replace('_parsers', '')
114 , resource
115 , parser;
116
117 for (var i = 0, l = resources.length; i < l; i++) {
118 resource = resources[i];
119
120 // We need to JSON stringify the data to properly add slashes escape other
121 // kinds of crap in the RegularExpression. If we don't do thing we get
122 // some illegal token warnings.
123 parser = 'parser = Object.create(null);\n';
124 parser += 'parser[0] = new RegExp('+ JSON.stringify(resource.regex) + ');\n';
125
126 // Check if we have replacement for the parsed family name
127 if (resource[details.replacement]) {
128 parser += 'parser[1] = "'+ resource[details.replacement].replace('"', '\\"') +'";';
129 } else {
130 parser += 'parser[1] = 0;';
131 }
132
133 parser += '\n';
134
135 if (resource.v1_replacement) {
136 parser += 'parser[2] = "'+ resource.v1_replacement.replace('"', '\\"') +'";';
137 } else {
138 parser += 'parser[2] = 0;';
139 }
140
141 parser += '\n';
142
143 if (resource.v2_replacement) {
144 parser += 'parser[3] = "'+ resource.v2_replacement.replace('"', '\\"') +'";';
145 } else {
146 parser += 'parser[3] = 0;';
147 }
148
149 parser += '\n';
150
151 if (resource.v3_replacement) {
152 parser += 'parser[4] = "'+ resource.v3_replacement.replace('"', '\\"') +'";';
153 } else {
154 parser += 'parser[4] = 0;';
155 }
156
157 parser += '\n';
158 parser += 'exports.'+ details.name +'['+ i +'] = parser;';
159 results[details.resource].push(parser);
160 }
161 });
162
163 // Generate a correct format
164 exports.generate(results, callback);
165};
166
167/**
168 * Generate the regular expressions file source code.
169 *
170 * @param {Object} results The parsed result of the regexp.yaml.
171 * @param {Function} callback Completion callback
172 * @api public
173 */
174exports.generate = function generate(results, callback) {
175 var regexps = [
176 '"use strict";'
177 , exports.LEADER
178 , 'var parser;'
179 , 'exports.browser = Object.create(null);'
180 , results.user_agent_parsers.join('\n')
181 , 'exports.browser.length = '+ results.user_agent_parsers.length +';'
182
183 , 'exports.device = Object.create(null);'
184 , results.device_parsers.join('\n')
185 , 'exports.device.length = '+ results.device_parsers.length +';'
186
187 , 'exports.os = Object.create(null);'
188 , results.os_parsers.join('\n')
189 , 'exports.os.length = '+ results.os_parsers.length +';'
190 ].join('\n\n');
191
192 // Now that we have generated the structure for the RegExps export file we
193 // need to validate that we created a JavaScript compatible file, if we would
194 // write the file without checking it's content we could be breaking the
195 // module.
196 var sandbox = {
197 exports: {} // Emulate a module context, so everything is attached here
198 };
199
200 // Crossing our fingers that it worked
201 try { vm.runInNewContext(regexps, sandbox, 'validating.vm'); }
202 catch (e) { return callback(e, null, regexps); }
203
204 callback(undefined, sandbox.exports, regexps);
205};
206
207/**
208 * The location of the ua-parser regexes yaml file.
209 *
210 * @type {String}
211 * @api private
212 */
213exports.remote = 'https://raw.githubusercontent.com/ua-parser/uap-core/master/regexes.yaml';
214
215/**
216 * The locations of our local regexes yaml files.
217 *
218 * @type {String}
219 * @api private
220 */
221exports.before = path.resolve(__dirname, '..', 'static', 'user_agent.before.yaml');
222exports.after = path.resolve(__dirname, '..', 'static', 'user_agent.after.yaml');
223
224/**
225 * The the output location for the generated regexps file
226 *
227 * @type {String}
228 * @api private
229 */
230exports.output = path.resolve(__dirname, '..', 'lib', 'regexps.js');
231
232/**
233 * The leader that needs to be added so people know they shouldn't touch all the
234 * things.
235 *
236 * @type {String}
237 * @api private
238 */
239exports.LEADER = fs.readFileSync(path.join(__dirname, 'donotedit'), 'UTF-8');