UNPKG

6.53 kBJavaScriptView Raw
1var fs = require('fs-extra');
2
3var async = require('async');
4var nomnom = require('nomnom');
5
6var generateInfo = require('./generate-info');
7
8
9/**
10 * Get the configuration from the config file. If configPath is provided
11 * it is assumed to be a JSON file with an 'exports' member that is a list
12 * of symbol names or patterns.
13 *
14 * @param {string} configPath Path to config file.
15 * @param {function(Error, Object)} callback Called with config object.
16 */
17function getConfig(configPath, callback) {
18 if (configPath) {
19 fs.readFile(configPath, function(err, data) {
20 if (err) {
21 callback(err);
22 return;
23 }
24 var obj;
25 try {
26 obj = JSON.parse(String(data));
27 } catch (err2) {
28 callback(new Error('Trouble parsing file as JSON: ' + configPath));
29 return;
30 }
31 var patterns = obj.exports;
32 if (patterns && !Array.isArray(patterns)) {
33 callback(new Error('Expected an exports array, got: ' + patterns));
34 return;
35 }
36 var namespace = obj.namespace;
37 if (namespace && typeof namespace !== 'string') {
38 callback(new Error('Expected an namespace string, got: ' +
39 namespace));
40 return;
41 }
42 callback(null, obj);
43 });
44 } else {
45 process.nextTick(function() {
46 callback(null, {exports: ['*']});
47 });
48 }
49}
50
51
52/**
53 * Read the symbols from info file.
54 * @param {Array.<string>} patterns List of patterns to pass along.
55 * @param {function(Error, Array.<string>, Array.<Object>)} callback Called
56 * with the patterns and symbols (or any error).
57 */
58function getSymbols(patterns, callback) {
59 generateInfo(function(err) {
60 if (err) {
61 callback(new Error('Trouble generating info: ' + err.message));
62 return;
63 }
64 var symbols = require('../build/info.json').symbols;
65 callback(null, patterns, symbols);
66 });
67}
68
69
70/**
71 * Generate a list of symbol names given a list of patterns. Patterns may
72 * include a * wildcard at the end of the string, in which case all symbol names
73 * that start with the preceding string will be matched (e.g 'foo.Bar#*' will
74 * match all symbol names that start with 'foo.Bar#').
75 *
76 * @param {Array.<string>} patterns A list of symbol names to match. Wildcards
77 * at the end of a string will match multiple names.
78 * @param {Array.<Object>} symbols List of symbols.
79 * @param {function(Error, Array.<Object>, Array.<string>)} callback Called with
80 * the filtered list of symbols and a list of all provides (or any error).
81 */
82function filterSymbols(patterns, symbols, callback) {
83 var matches = [];
84
85 var provides = {};
86 var lookup = {};
87 symbols.forEach(function(symbol) {
88 lookup[symbol.name] = symbol;
89 symbol.provides.forEach(function(provide) {
90 provides[provide] = true;
91 });
92 });
93
94 patterns.forEach(function(name) {
95 var match = false;
96 var pattern = (name.substr(-1) === '*');
97 if (pattern) {
98 name = name.substr(0, name.length - 1);
99 symbols.forEach(function(symbol) {
100 if (symbol.name.indexOf(name) === 0) {
101 matches.push(symbol);
102 match = true;
103 }
104 });
105 } else {
106 var symbol = lookup[name];
107 if (symbol) {
108 matches.push(symbol);
109 match = true;
110 }
111 }
112 if (!match) {
113 var message = 'No matching symbol found: ' + name + (pattern ? '*' : '');
114 callback(new Error(message));
115 }
116 });
117
118 callback(null, matches, Object.keys(provides).sort());
119}
120
121
122/**
123 * Generate goog code to export a named symbol.
124 * @param {string} name Symbol name.
125 * @param {string|undefined} namespace Target object for exported
126 * symbols.
127 * @return {string} Export code.
128 */
129function formatSymbolExport(name, namespace) {
130 return 'goog.exportSymbol(\n' +
131 ' \'' + name + '\',\n' +
132 ' ' + name +
133 (namespace ? ',\n ' + namespace : '') + ');\n';
134}
135
136
137/**
138 * Generate goog code to export a property.
139 * @param {string} name Property long name (e.g. foo.Bar#baz).
140 * @return {string} Export code.
141 */
142function formatPropertyExport(name) {
143 var parts = name.split('#');
144 var prototype = parts[0] + '.prototype';
145 var property = parts[1];
146 return 'goog.exportProperty(\n' +
147 ' ' + prototype + ',\n' +
148 ' \'' + property + '\',\n' +
149 ' ' + prototype + '.' + property + ');\n';
150}
151
152
153/**
154 * Generate export code given a list symbol names.
155 * @param {Array.<Object>} symbols List of symbols.
156 * @param {string|undefined} namespace Target object for exported symbols.
157 * @param {Array.<string>} provides List of all provides.
158 * @return {string} Export code.
159 */
160function generateExports(symbols, namespace, provides) {
161 var blocks = [];
162 provides.forEach(function(provide) {
163 blocks.push('goog.require(\'' + provide + '\');');
164 });
165 blocks.push('\n\n');
166 symbols.forEach(function(symbol) {
167 var name = symbol.name;
168 if (name.indexOf('#') > 0) {
169 blocks.push(formatPropertyExport(name));
170 } else {
171 blocks.push(formatSymbolExport(name, namespace));
172 }
173 });
174 blocks.unshift(
175 '/**\n' +
176 ' * @fileoverview Custom exports file.\n' +
177 ' * @suppress {checkVars,extraRequire}\n' +
178 ' */\n');
179 return blocks.join('\n');
180}
181
182
183/**
184 * Generate the exports code.
185 *
186 * @param {Object} config Config object with exports and (optional) namespace.
187 * @param {function(Error, string)} callback Called with the exports code or any
188 * error generating it.
189 */
190function main(config, callback) {
191 async.waterfall([
192 getSymbols.bind(null, config.exports),
193 filterSymbols,
194 function(symbols, provides, done) {
195 var code, err;
196 try {
197 code = generateExports(symbols, config.namespace, provides);
198 } catch (e) {
199 err = e;
200 }
201 done(err, code);
202 }
203 ], callback);
204}
205
206
207/**
208 * If running this module directly, read the config file, call the main
209 * function, and write the output file.
210 */
211if (require.main === module) {
212 var options = nomnom.options({
213 output: {
214 position: 0,
215 required: true,
216 help: 'Output file path'
217 },
218 config: {
219 abbr: 'c',
220 help: 'Path to JSON config file',
221 metavar: 'CONFIG'
222 }
223 }).parse();
224
225 async.waterfall([
226 getConfig.bind(null, options.config),
227 main,
228 fs.outputFile.bind(fs, options.output)
229 ], function(err) {
230 if (err) {
231 process.stderr.write(err.message + '\n');
232 process.exit(1);
233 } else {
234 process.exit(0);
235 }
236 });
237}
238
239
240/**
241 * Export main function.
242 */
243module.exports = main;