Skip to content

Commit cf9346c

Browse files
committed
cli can be called from script
I keep finding myself reimplementing the cli script and it would be much easier if I could simply require it and use it in scripts. The basic change is that main accepts an options argument. The rest of the changes: add documentation to the source code, expose a method for processing grammar files in any combination the cli accepts, and expose a method for generating a parser as a string. It's all pretty much the same code as was there, just shifted around so I could expose things to users and wrapped up so things don't go haywire.
1 parent 74b9a34 commit cf9346c

File tree

1 file changed

+269
-98
lines changed

1 file changed

+269
-98
lines changed

lib/cli.js

Lines changed: 269 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,207 @@
11
#!/usr/bin/env node
2+
/*jslint
3+
white: true,
4+
vars: true,
5+
stupid: true,
6+
node: true
7+
*/
28

3-
var jison = require('./jison.js');
4-
var nomnom = require('nomnom');
5-
var fs = require('fs');
6-
var path = require('path');
7-
var ebnfParser = require('ebnf-parser');
8-
var lexParser = require('lex-parser');
9-
var cjson = require('cjson');
9+
/**
10+
* jison's cli
11+
* @fileoverview The cli for jison
12+
*/
1013

11-
var version = require('../package.json').version;
12-
13-
var opts = require("nomnom")
14-
.script('jison')
15-
.option('file', {
16-
flag: true,
17-
position: 0,
18-
help: 'file containing a grammar'
19-
})
20-
.option('lexfile', {
21-
flag: true,
22-
position: 1,
23-
help: 'file containing a lexical grammar'
24-
})
25-
.option('json', {
26-
abbr: 'j',
27-
flag: true,
28-
help: 'force jison to expect a grammar in JSON format'
29-
})
30-
.option('outfile', {
31-
abbr: 'o',
32-
metavar: 'FILE',
33-
help: 'Filename and base module name of the generated parser'
34-
})
35-
.option('debug', {
36-
abbr: 't',
37-
flag: true,
38-
default: false,
39-
help: 'Debug mode'
40-
})
41-
.option('module-type', {
42-
abbr: 'm',
43-
default: 'commonjs',
44-
metavar: 'TYPE',
45-
help: 'The type of module to generate (commonjs, amd, js)'
46-
})
47-
.option('parser-type', {
48-
abbr: 'p',
49-
default: 'lalr',
50-
metavar: 'TYPE',
51-
help: 'The type of algorithm to use for the parser (lr0, slr, lalr, lr)'
52-
})
53-
.option('version', {
54-
abbr: 'V',
55-
flag: true,
56-
help: 'print version and exit',
57-
callback: function() {
58-
return version;
14+
/**
15+
* Container for cli related functions.
16+
* @namespace Container for cli related functions.
17+
*/
18+
var cli = exports;
19+
/**
20+
* Generates a parser and writes it to a file.
21+
* @param {Object} opts Options object.
22+
*
23+
* Options:
24+
* file: {String} Optional. Path to a file containing a grammar. If no file is
25+
* specified input will be read from stdin.
26+
*
27+
* lexfile: {String} Optional. Path to a file containing a lexical grammar.
28+
*
29+
* json: {Boolean} Optional. Set to true if `file` is in json format.
30+
*
31+
* outfile: {String} Optional. The path and filename where the parser should be
32+
* written to. Defaults to the path and filename given for `file` with the file
33+
* extension replaced by `js`.
34+
*
35+
* debug: {Boolean} Optional. Debug mode. Defaults to false.
36+
*
37+
* module-type: {String} Optional. The module type of the generated parser.
38+
* Options are: commonjs, amd, and js. Defaults to commonjs.
39+
*
40+
* parser-type: {String} Optional. The type of parser to generate. Options are:
41+
* lr0, slr, lalr, and lr. Defaults to lalr.
42+
*
43+
* @example
44+
* // grammar to process is not json and contains grammars for lexer and
45+
* // parser.
46+
* var jisonCli = require('./node_modules/jison/lib/cli.js');
47+
* var options = {
48+
* file : "myfile.jison",
49+
* moduleName : "myModule"
50+
* };
51+
* jisonCli.main(options);
52+
* @example
53+
* // grammar to process is not json and is divided into two files containing
54+
* // the grammars for the lexer and parser seperately.
55+
* var jisonCli = require('./node_modules/jison/lib/cli.js');
56+
* var options = {
57+
* file : "myfile.y",
58+
* lexfile : "myfile.l",
59+
* moduleName : "myModule"
60+
* };
61+
* jisonCli.main(options);
62+
* @example
63+
* // grammar to process is in json format, desired module type is amd, and
64+
* // desired parser type is lr0.
65+
* var jisonCli = require('./node_modules/jison/lib/cli.js');
66+
* var options = {
67+
* file : "myfile.json",
68+
* moduleName : "myModule",
69+
* json : true,
70+
* "module-type" : "amd",
71+
* "parser-type" : "lr0"
72+
* };
73+
* jisonCli.main(options);
74+
*/
75+
cli.main = function cli_main(opts) {
76+
"use strict";
77+
opts = opts || {};
78+
/**
79+
* Generates a parser as a string.
80+
* @private
81+
*/
82+
function processGrammar(raw, lex, opts) {
83+
var grammar,
84+
parser;
85+
if (!opts.json) {
86+
grammar = cli.processGrammars(raw, lex, opts.json);
87+
}
88+
parser = cli.generateParserString(opts, grammar);
89+
return parser;
5990
}
60-
})
61-
.parse();
62-
91+
/**
92+
* Processes input from a file.
93+
* @private
94+
* @requires <a href="http://nodejs.org/api/fs.html">fs</a>
95+
* @requires <a href="http://nodejs.org/api/path.html">path</a>
96+
*/
97+
function processInputFile () {
98+
var fs = require('fs');
99+
var path = require('path');
63100

64-
exports.main = function () {
65-
if (opts.file) {
66-
var raw = fs.readFileSync(path.normalize(opts.file), 'utf8');
67-
var jsonMode = path.extname(opts.file) === '.json' || opts.json;
68-
var name = path.basename((opts.outfile||opts.file)).replace(/\..*$/g,'');
101+
// getting raw files
69102
var lex;
70-
71103
if (opts.lexfile) {
72104
lex = fs.readFileSync(path.normalize(opts.lexfile), 'utf8');
73105
}
106+
var raw = fs.readFileSync(path.normalize(opts.file), 'utf8');
74107

75-
fs.writeFileSync(opts.outfile||(name + '.js'), processGrammar(raw, lex, name, jsonMode));
76-
} else {
108+
// making best guess at json mode
109+
opts.json = path.extname(opts.file) === '.json' || opts.json;
110+
111+
// setting output file name and module name based on input file name
112+
// if they aren't specified.
113+
var name = path.basename((opts.outfile || opts.file));
114+
/*jslint regexp: true */
115+
name = name.replace(/\..*$/g, '');
116+
/*jslint regexp: false */
117+
opts.outfile = opts.outfile || (name + '.js');
118+
if (!opts.moduleName && name) {
119+
opts.moduleName = name.replace(/-\w/g,
120+
function (match) {
121+
return match.charAt(1).toUpperCase();
122+
});
123+
}
124+
125+
var parser = processGrammar(raw, lex, opts);
126+
fs.writeFileSync(opts.outfile, parser);
127+
}
128+
/**
129+
* Reads from stdin and calls the callback on the data received.
130+
* @param {Function} cb The callback function to execute on the received
131+
* data.
132+
* @private
133+
*/
134+
function readin(cb) {
135+
var stdin = process.openStdin(),
136+
data = '';
137+
138+
stdin.setEncoding('utf8');
139+
stdin.addListener('data', function (chunk) {
140+
data += chunk;
141+
});
142+
stdin.addListener('end', function () {
143+
cb(data);
144+
});
145+
}
146+
/**
147+
* Processes input from stdin.
148+
* @private
149+
*/
150+
function processStdin () {
77151
readin(function (raw) {
78-
console.log(processGrammar(raw, null, null, opts.json));
152+
console.log(processGrammar(raw, null, opts));
79153
});
80154
}
155+
156+
// if an input file wasn't given, assume input on stdin
157+
if (opts.file) {
158+
processInputFile();
159+
} else {
160+
processStdin();
161+
}
81162
};
163+
/**
164+
* Generates a parser and returns it as a string.
165+
* @param {Object} opts Optional. An options object. Options are parser-type,
166+
* module-type, and debug. Defaults to {}; see the description and examples for
167+
* these optons in {@link Jison.cli.main}
168+
* @param {String|Object} grammar The grammar to generate a parser from.
169+
* @returns {String} Returns the generated parser as a string.
170+
* @requires <a href="https://npmjs.org/package/jison/">jison</a>
171+
*/
172+
cli.generateParserString = function generateParserString(opts, grammar) {
173+
"use strict";
174+
opts = opts || {};
175+
var jison = require('./jison.js');
176+
177+
var settings = grammar.options || {};
178+
179+
if (opts['parser-type']) {
180+
settings.type = opts['parser-type'];
181+
}
182+
settings.debug = opts.debug;
183+
if (!settings.moduleType) {
184+
settings.moduleType = opts['module-type'];
185+
}
186+
187+
var generator = new jison.Generator(grammar, settings);
188+
return generator.generate(settings);
189+
};
190+
/**
191+
* Processes grammar files of various format.
192+
* @param {String} file Contents of a jison grammar file.
193+
* @param {String} lexFile Contents of a lexer grammar file.
194+
* @param {Boolean} jsonMode Set to true if `file` is in json format.
195+
* @returns {Object} Returns the parsed grammar object.
196+
* @requires <a href="https://npmjs.org/package/ebnf-parser">ebnf-parser</a>
197+
* @requires <a href="https://npmjs.org/package/cjson">cjson</a>
198+
* @requires <a href="https://npmjs.org/package/lex-parser">lex-parser</a>
199+
*/
200+
cli.processGrammars = function processGrammars(file, lexFile, jsonMode) {
201+
"use strict";
202+
var ebnfParser = require('ebnf-parser');
203+
var cjson = require('cjson');
82204

83-
function processGrammar (file, lexFile, name, jsonMode) {
84205
var grammar;
85206
if (jsonMode) {
86207
grammar = cjson.parse(file);
@@ -97,37 +218,87 @@ function processGrammar (file, lexFile, name, jsonMode) {
97218
}
98219
}
99220
}
100-
101-
var settings = grammar.options || {};
102-
103-
if (opts['parser-type']) settings.type = opts['parser-type'];
104-
if (lexFile) grammar.lex = lexParser.parse(lexFile);
105-
settings.debug = opts.debug;
106-
if (!settings.moduleType) settings.moduleType = opts['module-type'];
107-
if (!settings.moduleName && name) {
108-
settings.moduleName = name.replace(/-\w/g,
109-
function (match){
110-
return match.charAt(1).toUpperCase();
111-
});
221+
if (lexFile) {
222+
grammar.lex = require('lex-parser').parse(lexFile);
112223
}
224+
return grammar;
225+
};
226+
/**
227+
* Initialization function, grabs commandline arguments and passes them to
228+
* `cli.main` if this script was called from the commandline.
229+
* @private
230+
* @methodOf cli
231+
* @requires <a href="https://npmjs.org/package/nomnom">nomnom</a>
232+
*/
233+
function cli_init () {
234+
"use strict";
235+
/**
236+
* Gets options from the commandline.
237+
* @private
238+
* @requires <a href="https://npmjs.org/package/nomnom">nomnom</a>
239+
*/
240+
function getCommandlineOptions () {
241+
var version = require('../package.json').version;
242+
var opts = require("nomnom")
243+
.script('jison')
244+
.option('file', {
245+
flag : true,
246+
position : 0,
247+
help : 'file containing a grammar'
248+
})
249+
.option('lexfile', {
250+
flag : true,
251+
position : 1,
252+
help : 'file containing a lexical grammar'
253+
})
254+
.option('json', {
255+
abbr : 'j',
256+
flag : true,
257+
help : 'force jison to expect a grammar in JSON format'
258+
})
259+
.option('outfile', {
260+
abbr : 'o',
261+
metavar : 'FILE',
262+
help : 'Filename and base module name of the generated parser'
263+
})
264+
.option('debug', {
265+
abbr : 't',
266+
flag : true,
267+
default:
268+
false,
269+
help : 'Debug mode'
270+
})
271+
.option('module-type', {
272+
abbr : 'm',
273+
default:
274+
'commonjs',
275+
metavar : 'TYPE',
276+
help : 'The type of module to generate (commonjs, amd, js)'
277+
})
278+
.option('parser-type', {
279+
abbr : 'p',
280+
default:
281+
'lalr',
282+
metavar : 'TYPE',
283+
help : 'The type of algorithm to use for the parser (lr0, slr,' +
284+
'lalr, lr)'
285+
})
286+
.option('version', {
287+
abbr : 'V',
288+
flag : true,
289+
help : 'print version and exit',
290+
callback : function () {
291+
return version;
292+
}
293+
}).parse();
294+
295+
return opts;
296+
} // end of getCommandlineOptions
113297

114-
var generator = new jison.Generator(grammar, settings);
115-
return generator.generate(settings);
116-
}
117-
118-
function readin (cb) {
119-
var stdin = process.openStdin(),
120-
data = '';
121-
122-
stdin.setEncoding('utf8');
123-
stdin.addListener('data', function (chunk) {
124-
data += chunk;
125-
});
126-
stdin.addListener('end', function () {
127-
cb(data);
128-
});
298+
if (require.main === module) {
299+
var opts = getCommandlineOptions();
300+
cli.main(opts);
301+
}
129302
}
130303

131-
if (require.main === module)
132-
exports.main();
133-
304+
cli_init();

0 commit comments

Comments
 (0)