├── .gitignore ├── LICENSE ├── README.md ├── examples └── sample_menu.js ├── index.d.ts ├── index.js ├── lib ├── menuitem.js └── nodemenu.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /node_modules 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2009-2014 TJ Holowaychuk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | node-menu 2 | ========= 3 | 4 | This module allows to create console menu for your REPL application. It allows you to register menu items with their handlers. Optionally you may provide handler's owner object and list of arguments being passed to handler. 5 | 6 | ## Installation 7 | 8 | npm install node-menu 9 | 10 | ## Methods 11 | 12 | ```javascript 13 | var menu = require('node-menu'); 14 | ``` 15 | 16 | Each method returns reference on self object, so calls could be chained. 17 | 18 | ### menu.addItem(title, handler, owner, args) 19 | 20 | Add item to the menu. Returns __menu__ for chaining calls. 21 | 22 | - _title_ - title of the menu item; 23 | - _handler_ - item handler function; 24 | - _owner_ - owner object of the function (this); 25 | - _args_ - array of objects argument names being passed to the function, argument is an object with two fields: 'name' and 'type'. Available types are: 'numeric', 'bool' and 'string'; 26 | 27 | ```javascript 28 | menu.addItem( 29 | 'Menu Item', 30 | function(str, bool, num1, num2) { 31 | console.log('String: ' + str); 32 | if (bool) { 33 | console.log('bool is true'); 34 | } else { 35 | console.log('bool is false'); 36 | } 37 | var sum = num1 + num2; 38 | console.log('num1 + num2: ' + sum); 39 | }, 40 | null, 41 | [ 42 | {'name': 'Str Arg', 'type': 'string'}, 43 | {'name': 'Bool Arg', 'type': 'bool'}, 44 | {'name': 'num1', 'type': 'numeric'}, 45 | {'name': 'num2', 'type': 'numeric'} 46 | ]); 47 | ``` 48 | 49 | ### menu.addDelimiter(delimiter, cnt, title) 50 | 51 | Adds delimiter to the menu. Returns __menu__ for chaining calls. 52 | 53 | - _delimiter_ - delimiter character; 54 | - _cnt_ - delimiter's repetition count; 55 | - _title_ - title of the delimiter, will be printed in the middle of the delimiter line; 56 | 57 | The output of the delimiter: 58 | 59 | menu.addDelimiter('-', 33, 'Main Menu') 60 | ------------Main Menu------------ 61 | 62 | menu.addDelimiter('*', 33) 63 | ********************************* 64 | 65 | ### menu.enableDefaultHeader() 66 | 67 | Turns on default header (turned on by default). Returns __menu__ for chaining calls. 68 | 69 | ```javascript 70 | menu.enableDefaultHeader() 71 | ``` 72 | 73 | ### menu.disableDefaultHeader() 74 | 75 | Turns off default header. No header will be printed in this case. Returns __menu__ for chaining calls. 76 | 77 | ```javascript 78 | menu.disableDefaultHeader() 79 | ``` 80 | 81 | ### menu.customHeader(customHeaderFunc) 82 | 83 | Turns off default header and prints custom header passed in __customHeaderFunc__. Returns __menu__ for chaining calls. 84 | 85 | ```javascript 86 | menu.customHeader(function() { 87 | process.stdout.write("\nCustom header\n"); 88 | }) 89 | ``` 90 | 91 | ### menu.enableDefaultPrompt() 92 | 93 | Turns on default prompt (turned on by default). Returns __menu__ for chaining calls. 94 | 95 | ```javascript 96 | menu.enableDefaultPrompt() 97 | ``` 98 | 99 | ### menu.disableDefaultPrompt() 100 | 101 | Turns off default prompt. No prompt will be printed in this case. Returns __menu__ for chaining calls. 102 | 103 | ```javascript 104 | menu.disableDefaultPrompt() 105 | ``` 106 | 107 | ### menu.customPrompt(customPromptFunc) 108 | 109 | Turns off default prompt and prints custom header passed in __customPromptFunc__. Returns __menu__ for chaining calls. 110 | 111 | ```javascript 112 | menu.customPrompt(function() { 113 | process.stdout.write("Custom prompt\n"); 114 | }) 115 | ``` 116 | 117 | ### menu.resetMenu() 118 | 119 | Clears all data and listeners from the menu object so the object can be updated and reused. 120 | 121 | ### menu.continueCallback(continueCallback) 122 | 123 | Set callback which must be invoked when __"Enter"__ button pressed to continue. 124 | 125 | ```javascript 126 | menu.resetMenu() 127 | ``` 128 | 129 | ### menu.start() 130 | 131 | Start menu. 132 | 133 | ## Example 134 | 135 | ## Live Example 136 | 137 | 138 | 139 | ## Source 140 | 141 | ```javascript 142 | var menu = require('node-menu'); 143 | 144 | var TestObject = function() { 145 | var self = this; 146 | self.fieldA = 'FieldA'; 147 | self.fieldB = 'FieldB'; 148 | } 149 | 150 | TestObject.prototype.printFieldA = function() { 151 | console.log(this.fieldA); 152 | } 153 | 154 | TestObject.prototype.printFieldB = function(arg) { 155 | console.log(this.fieldB + arg); 156 | } 157 | 158 | var testObject = new TestObject(); 159 | var timeout; 160 | 161 | menu.addDelimiter('-', 40, 'Main Menu') 162 | .addItem( 163 | 'No parameters', 164 | function() { 165 | console.log('No parameters is invoked'); 166 | }) 167 | .addItem( 168 | "Print Field A", 169 | testObject.printFieldA, 170 | testObject) 171 | .addItem( 172 | 'Print Field B concatenated with arg1', 173 | testObject.printFieldB, 174 | testObject, 175 | [{'name': 'arg1', 'type': 'string'}]) 176 | .addItem( 177 | 'Sum', 178 | function(op1, op2) { 179 | var sum = op1 + op2; 180 | console.log('Sum ' + op1 + '+' + op2 + '=' + sum); 181 | }, 182 | null, 183 | [{'name': 'op1', 'type': 'numeric'}, {'name': 'op2', 'type': 'numeric'}]) 184 | .addItem( 185 | 'String and Bool parameters', 186 | function(str, b) { 187 | console.log("String is: " + str); 188 | console.log("Bool is: " + b); 189 | }, 190 | null, 191 | [{'name': 'str', 'type': 'string'}, {'name': 'bool', 'type': 'bool'}]) 192 | .addItem( 193 | 'Long lasting task which is terminated when Enter pressed', 194 | function(time) { 195 | console.log("Starting long lasting job for " + time + " sec"); 196 | timeout = setTimeout(function() { 197 | console.log("Long lasting job is done"); 198 | timeout = undefined; 199 | }, time * 1000); 200 | }, 201 | null, 202 | [{'name': 'Job execution time, sec', 'type': 'numeric'}]) 203 | .addDelimiter('*', 40) 204 | .continueCallback(function () { 205 | if (timeout) { 206 | clearTimeout(timeout); 207 | console.log("Timeout cleaned"); 208 | timeout = undefined; 209 | } 210 | }) 211 | // .customHeader(function() { 212 | // process.stdout.write("Hello\n"); 213 | // }) 214 | // .disableDefaultHeader() 215 | // .customPrompt(function() { 216 | // process.stdout.write("\nEnter your selection:\n"); 217 | // }) 218 | // .disableDefaultPrompt() 219 | .start(); 220 | ``` 221 | 222 | Output of this example: 223 | 224 | _ __ __ __ ___ 225 | / | / /____ ____/ /___ / |/ /___ ____ __ __ 226 | / |/ // __ \ / __ // _ \ / /|_/ // _ \ / __ \ / / / / 227 | / /| // /_/ // /_/ // __// / / // __// / / // /_/ / 228 | /_/ |_/ \____/ \__,_/ \___//_/ /_/ \___//_/ /_/ \__,_/ v.1.0.0 229 | 230 | ---------------Main Menu--------------- 231 | 1. No parameters 232 | 2. Print Field A 233 | 3. Print Field B concatenated with arg1: "arg1" 234 | 4. Sum: "op1" "op2" 235 | 5. String and Bool parameters: "str" "bool" 236 | *************************************** 237 | 6. Quit 238 | 239 | Please provide input at prompt as: >> ItemNumber arg1 arg2 ... (i.e. >> 2 "string with spaces" 2 4 noSpacesString true) 240 | 241 | >> 242 | 243 | To invoke item without arguments just type number and Enter. To invoke item with arguments, type number then arguments delimited with space. If string argument has spaces it must be double quoted. 244 | 245 | # Additional links 246 | 247 | Similar library for other languages: 248 | - [Java](https://github.com/nbu/java-menu) 249 | - [C#](https://github.com/nbu/charp-menu) 250 | -------------------------------------------------------------------------------- /examples/sample_menu.js: -------------------------------------------------------------------------------- 1 | var menu = require('../index'); 2 | 3 | var TestObject = function() { 4 | var self = this; 5 | self.fieldA = 'FieldA'; 6 | self.fieldB = 'FieldB'; 7 | }; 8 | 9 | TestObject.prototype.printFieldA = function() { 10 | console.log(this.fieldA); 11 | }; 12 | 13 | TestObject.prototype.printFieldB = function(arg) { 14 | console.log(this.fieldB + arg); 15 | }; 16 | 17 | var testObject = new TestObject(); 18 | var timeout; 19 | 20 | menu.addDelimiter('-', 40, 'Main Menu') 21 | .addItem( 22 | 'No parameters', 23 | function() { 24 | console.log('No parameters is invoked'); 25 | }) 26 | .addItem( 27 | "Print Field A", 28 | testObject.printFieldA, 29 | testObject) 30 | .addItem( 31 | 'Print Field B concatenated with arg1', 32 | testObject.printFieldB, 33 | testObject, 34 | [{'name': 'arg1', 'type': 'string'}]) 35 | .addItem( 36 | 'Sum', 37 | function(op1, op2) { 38 | var sum = op1 + op2; 39 | console.log('Sum ' + op1 + '+' + op2 + '=' + sum); 40 | }, 41 | null, 42 | [{'name': 'op1', 'type': 'numeric'}, {'name': 'op2', 'type': 'numeric'}]) 43 | .addItem( 44 | 'String and Bool parameters', 45 | function(str, b) { 46 | console.log("String is: " + str); 47 | console.log("Bool is: " + b); 48 | }, 49 | null, 50 | [{'name': 'str', 'type': 'string'}, {'name': 'bool', 'type': 'bool'}]) 51 | .addItem( 52 | 'Long lasting task which is terminated when Enter pressed', 53 | function(time) { 54 | console.log("Starting long lasting job for " + time + " sec"); 55 | timeout = setTimeout(function() { 56 | console.log("Long lasting job is done"); 57 | timeout = undefined; 58 | }, time * 1000); 59 | }, 60 | null, 61 | [{'name': 'Job execution time, sec', 'type': 'numeric'}]) 62 | .addDelimiter('*', 40) 63 | .continueCallback(function () { 64 | if (timeout) { 65 | clearTimeout(timeout); 66 | console.log("Timeout cleaned"); 67 | timeout = undefined; 68 | } 69 | }) 70 | // .customHeader(function() { 71 | // process.stdout.write("Hello\n"); 72 | // }) 73 | // .disableDefaultHeader() 74 | // .customPrompt(function() { 75 | // process.stdout.write("\nEnter your selection:\n"); 76 | // }) 77 | // .disableDefaultPrompt() 78 | .start(); 79 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace NodeMenu { 2 | interface args { 3 | name: string, 4 | type: 'numeric'|'bool'|'string' 5 | } 6 | } 7 | 8 | declare class NodeMenu { 9 | constructor(); 10 | enableDefaultHeader(): NodeMenu; 11 | disableDefaultHeader(): NodeMenu; 12 | customHeader(customHeaderFunc: Function): NodeMenu; 13 | enableDefaultPrompt(): NodeMenu; 14 | disableDefaultPrompt(): NodeMenu; 15 | customPrompt(customPromptFunc: Function): NodeMenu; 16 | resetMenu(): NodeMenu; 17 | continueCallback(continueCallback: Function): NodeMenu; 18 | addItem(title: string, handler: Function, owner?: any, args?: NodeMenu.args[]): NodeMenu; 19 | addDelimiter(delimiter: string, cnt: number, title?: string): NodeMenu; 20 | start(): void; 21 | } 22 | export = NodeMenu; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by bnebosen on 4/15/2014. 3 | */ 4 | 5 | module.exports = require('./lib/nodemenu.js'); 6 | -------------------------------------------------------------------------------- /lib/menuitem.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | 3 | var MenuType = { 4 | ACTION : 1, 5 | DELIMITER : 2 6 | }; 7 | 8 | var MenuItem = function(menuType, number, title, handler, owner, args, callback) { 9 | var self = this; 10 | self.title = title; 11 | self.handler = handler; 12 | self.menuType = menuType; 13 | self.number = number; 14 | self.callback = callback; 15 | 16 | if (menuType === MenuType.DELIMITER) { 17 | self.delimiter = Array(80).join('-'); 18 | } 19 | 20 | self.owner = owner ? owner : self; 21 | self.args = args ? args : []; 22 | }; 23 | 24 | MenuItem.prototype.validate = function(argv) { 25 | var self = this; 26 | var res; 27 | 28 | if (!argv) { 29 | res = { 30 | lengthMatch: false, 31 | typesMatch: null 32 | }; 33 | 34 | return res; 35 | } 36 | 37 | var lengthMatch = self.args.length == argv.length; 38 | var typesMatch = true; 39 | 40 | for (var i = 0; i < self.args.length; ++i) { 41 | if (self.args[i].type === 'numeric') { 42 | if (!_.isFinite(argv[i])) { 43 | typesMatch = false; 44 | break; 45 | } 46 | } else if (self.args[i].type === 'bool') { 47 | if (!_.isBoolean(argv[i])) { 48 | typesMatch = false; 49 | break; 50 | } 51 | } else if (self.args[i].type !== 'string') { 52 | typesMatch = false; 53 | } 54 | } 55 | 56 | res = { 57 | lengthMatch: lengthMatch, 58 | typesMatch: typesMatch 59 | }; 60 | 61 | return res; 62 | }; 63 | 64 | MenuItem.prototype.getPrintableString = function() { 65 | var self = this; 66 | var res = ''; 67 | var first = true; 68 | 69 | if (self.menuType === MenuType.ACTION) { 70 | res = self.title; 71 | 72 | for (var i = 0; i < self.args.length; ++i) { 73 | if (first) { 74 | res += ':'; 75 | first = false; 76 | } 77 | 78 | res += ' "' + self.args[i].name + '"'; 79 | } 80 | } else if (self.menuType === MenuType.DELIMITER) { 81 | res = self.delimiter; 82 | if (self.title && self.title.length > 0) { 83 | var l1 = res.length; 84 | var l2 = self.title.length; 85 | var pos = l1 / 2 - l2 / 2; 86 | res = res.substring(0, pos) + self.title + res.substring(pos + l2); 87 | } 88 | } 89 | return res; 90 | }; 91 | 92 | MenuItem.prototype.setDelimiter = function(delimiter, cnt, title) { 93 | var self = this; 94 | self.delimiter = Array(cnt).join(delimiter); 95 | self.title = title; 96 | }; 97 | 98 | module.exports = MenuItem; 99 | module.exports.MenuType = MenuType; 100 | -------------------------------------------------------------------------------- /lib/nodemenu.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by bnebosen on 4/15/2014. 3 | */ 4 | var MenuItem = require('./menuitem'); 5 | var MenuType = MenuItem.MenuType; 6 | var _ = require('underscore'); 7 | 8 | var NodeMenu = function() { 9 | var self = this; 10 | 11 | self.CLEAR_CODE = "\u001b[2J\u001b[0;0H"; 12 | 13 | self.consoleOutput = console.log; 14 | self.menuItems = []; 15 | self.waitToContinue = false; 16 | self.itemNo = 0; 17 | self.printDefaultHeader = true; 18 | self.printDefaultPrompt = true; 19 | self.isReseted = false; 20 | }; 21 | 22 | /** 23 | * Turns on default header (turned on by default). 24 | * 25 | * @returns {NodeMenu} for chaining calls 26 | */ 27 | NodeMenu.prototype.enableDefaultHeader = function() { 28 | var self = this; 29 | self.printDefaultHeader = true; 30 | return self; 31 | }; 32 | 33 | /** 34 | * Turns off default header. No header will be printed in this case. 35 | * 36 | * @returns {NodeMenu} for chaining calls 37 | */ 38 | NodeMenu.prototype.disableDefaultHeader = function() { 39 | var self = this; 40 | self.printDefaultHeader = false; 41 | return self; 42 | }; 43 | 44 | /** 45 | * Callback for printing custom header 46 | * 47 | * @callback customHeaderFunc 48 | */ 49 | 50 | /** 51 | * Turns off default header and prints custom header passed in {customHeaderFunc}. 52 | * 53 | * @param {customHeaderFunc} customHeaderFunc callback function for printing custom header 54 | * @returns {NodeMenu} for chaining calls 55 | */ 56 | NodeMenu.prototype.customHeader = function(customHeaderFunc) { 57 | var self = this; 58 | self.printDefaultHeader = false; 59 | self.customHeaderFunc = customHeaderFunc; 60 | return self; 61 | }; 62 | 63 | /** 64 | * Turns on default prompt (turned on by default). 65 | * 66 | * @returns {NodeMenu} for chaining calls 67 | */ 68 | NodeMenu.prototype.enableDefaultPrompt = function() { 69 | var self = this; 70 | self.printDefaultPrompt = true; 71 | return self; 72 | }; 73 | 74 | /** 75 | * Turns off default prompt. No prompt will be printed in this case. 76 | * 77 | * @returns {NodeMenu} for chaining calls 78 | */ 79 | NodeMenu.prototype.disableDefaultPrompt = function() { 80 | var self = this; 81 | self.printDefaultPrompt = false; 82 | return self; 83 | }; 84 | 85 | /** 86 | * Callback for printing custom prompt 87 | * 88 | * @callback customPromptFunc 89 | */ 90 | 91 | /** 92 | * Turns off default prompt and prints custom header passed in {customPromptFunc}. 93 | * 94 | * @param {customPromptFunc} customPromptFunc 95 | * @returns {NodeMenu} for chaining calls 96 | */ 97 | NodeMenu.prototype.customPrompt = function(customPromptFunc) { 98 | var self = this; 99 | self.printDefaultPrompt = false; 100 | self.customPromptFunc = customPromptFunc; 101 | return self; 102 | }; 103 | 104 | /** 105 | * Clears all data and listeners from the menu object so the object can be updated and reused. 106 | * 107 | * @returns {NodeMenu} for chaining calls 108 | */ 109 | NodeMenu.prototype.resetMenu = function() { 110 | var self = this; 111 | self.menuItems = []; 112 | self.waitToContinue = false; 113 | self.itemNo = 0; 114 | self.isReseted = true; 115 | process.stdin.removeAllListeners('data'); 116 | return self; 117 | }; 118 | 119 | /** 120 | * Callback for user defined actions when "Enter" button pressed 121 | * 122 | * @callback continueCallback 123 | */ 124 | 125 | /** 126 | * Set callback which must be invoked when "Enter" button pressed to continue. 127 | * 128 | * @param {continueCallback} continueCallback 129 | * @returns {NodeMenu} for chaining calls 130 | */ 131 | NodeMenu.prototype.continueCallback = function(continueCallback) { 132 | var self = this; 133 | self.continueCallback = continueCallback && _.isFunction(continueCallback) ? continueCallback : undefined; 134 | return self; 135 | }; 136 | 137 | /** 138 | * Add item to the menu. 139 | * @example 140 | *

141 |  * 'Menu Item',
142 |  * function(str, bool, num1, num2) {
143 |  *        console.log('String: ' + str);
144 |  *        if (bool) {
145 |  *            console.log('bool is true');
146 |  *        } else {
147 |  *            console.log('bool is false');
148 |  *        }
149 |  *        var sum = num1 + num2;
150 |  *        console.log('num1 + num2: ' + sum);
151 |  *    },
152 |  * null,
153 |  * [
154 |  * {'name': 'Str Arg', 'type': 'string'},
155 |  * {'name': 'Bool Arg', 'type': 'bool'},
156 |  * {'name': 'num1', 'type': 'numeric'},
157 |  * {'name': 'num2', 'type': 'numeric'}
158 |  * ]);
159 |  * 
160 | * 161 | * @param title - title of the menu item 162 | * @param handler - item handler function 163 | * @param owner - owner object of the function (this) 164 | * @param args - array of objects argument names being passed to the function, argument is an object with two fields: 'name' and 'type'. Available types are: 'numeric', 'bool' and 'string'; 165 | * @returns {NodeMenu} for chaining calls 166 | */ 167 | NodeMenu.prototype.addItem = function(title, handler, owner, args) { 168 | var self = this; 169 | self.menuItems.push(new MenuItem(MenuType.ACTION, ++self.itemNo, title, handler, owner, args)); 170 | return self; 171 | }; 172 | 173 | /** 174 | * Adds delimiter to the menu. 175 | * 176 | * @param delimiter - delimiter character 177 | * @param cnt - delimiter's repetition count 178 | * @param title - title of the delimiter, will be printed in the middle of the delimiter line 179 | * @returns {NodeMenu} for chaining calls 180 | */ 181 | NodeMenu.prototype.addDelimiter = function(delimiter, cnt, title) { 182 | var self = this; 183 | var menuItem = new MenuItem(MenuType.DELIMITER); 184 | if (delimiter) { 185 | menuItem.setDelimiter(delimiter, cnt, title); 186 | } 187 | 188 | self.menuItems.push(menuItem); 189 | return self; 190 | }; 191 | 192 | /** 193 | * Start menu 194 | */ 195 | NodeMenu.prototype.start = function() { 196 | var self = this; 197 | self.addItem('Quit', function() { 198 | process.stdin.end(); 199 | process.exit(); 200 | }); 201 | 202 | self._printMenu(); 203 | 204 | process.stdin.resume(); 205 | process.stdin.setEncoding('utf8'); 206 | process.stdin.on('data', function(text) { 207 | var args = self._parseInput(text); 208 | var item = null; 209 | if (args && ('itemNo' in args)) { 210 | for (var i = 0; i < self.menuItems.length; ++i) { 211 | if (self.menuItems[i].number == args.itemNo) { 212 | item = self.menuItems[i]; 213 | break; 214 | } 215 | } 216 | } 217 | 218 | if (self.waitToContinue) { 219 | if (self.continueCallback) { 220 | self.continueCallback(); 221 | } 222 | 223 | self._printMenu(); 224 | self.waitToContinue = false; 225 | return; 226 | } 227 | 228 | if (!item) { 229 | self.consoleOutput('Command not found: ' + text); 230 | } else { 231 | var valid = item.validate(args.argv); 232 | if (!valid.lengthMatch) { 233 | self.consoleOutput('Invalid number of input parameters'); 234 | } else if (!valid.typesMatch) { 235 | self.consoleOutput('Invalid types of input parameters'); 236 | } else { 237 | item.handler.apply(item.owner, args.argv); 238 | } 239 | } 240 | 241 | if (self.isReseted) { 242 | self.isReseted = false; 243 | return; 244 | } 245 | 246 | self.consoleOutput('Press Enter to continue...'); 247 | self.waitToContinue = true; 248 | }); 249 | }; 250 | 251 | NodeMenu.prototype._parseInput = function(text) { 252 | var self = this; 253 | 254 | var patt=/(".*?")|([^\s]{1,})/g; 255 | var match = text.match(patt); 256 | var res = null; 257 | 258 | if (match && match.length > 0) { 259 | if (isNaN(match[0])) { 260 | self.consoleOutput("Invalid item number"); 261 | return res; 262 | } 263 | 264 | res = {}; 265 | res.itemNo = parseInt(match[0]); 266 | res.argv = match.slice(1); 267 | for (var i = 0; i < res.argv.length; ++i) { 268 | res.argv[i] = res.argv[i].replace(/"/g, ''); 269 | if (res.argv[i].trim !== '') { 270 | var num = Number(res.argv[i]); 271 | if (!isNaN(num)) { 272 | res.argv[i] = num; 273 | } else if (res.argv[i] === 'true' || res.argv[i] === 'false') { 274 | res.argv[i] = (res.argv[i] === 'true'); 275 | } 276 | } 277 | } 278 | } 279 | 280 | return res; 281 | }; 282 | 283 | NodeMenu.prototype._printHeader = function() { 284 | var self = this; 285 | 286 | if (self.printDefaultHeader) { 287 | process.stdout.write(" \n\ 288 | _ __ __ __ ___ \n\ 289 | / | / /____ ____/ /___ / |/ /___ ____ __ __ \n\ 290 | / |/ // __ \\ / __ // _ \\ / /|_/ // _ \\ / __ \\ / / / / \n\ 291 | / /| // /_/ // /_/ // __// / / // __// / / // /_/ / \n\ 292 | /_/ |_/ \\____/ \\__,_/ \\___//_/ /_/ \\___//_/ /_/ \\__,_/ v.1.0.0 \n\ 293 | \n\ 294 | \n"); 295 | } else if (_.isFunction(self.customHeaderFunc)) { 296 | self.customHeaderFunc(); 297 | } 298 | }; 299 | 300 | NodeMenu.prototype._printPrompt = function() { 301 | var self = this; 302 | 303 | if (self.printDefaultPrompt) { 304 | self.consoleOutput('\nPlease provide input at prompt as: >> ItemNumber arg1 arg2 ... (i.e. >> 2 "string with spaces" 2 4 noSpacesString true)'); 305 | } else if (_.isFunction(self.customPromptFunc)) { 306 | self.customPromptFunc(); 307 | } 308 | }; 309 | 310 | NodeMenu.prototype._printMenu = function() { 311 | var self = this; 312 | 313 | console.log(self.CLEAR_CODE); 314 | 315 | self._printHeader(); 316 | for (var i = 0; i < self.menuItems.length; ++i) { 317 | var menuItem = self.menuItems[i]; 318 | printableMenu = menuItem.getPrintableString(); 319 | if (menuItem.menuType === MenuType.ACTION) { 320 | self.consoleOutput(menuItem.number + ". " + printableMenu); 321 | } else if (menuItem.menuType === MenuType.DELIMITER) { 322 | self.consoleOutput(printableMenu); 323 | } 324 | } 325 | 326 | self._printPrompt(); 327 | 328 | process.stdout.write("\n>> "); 329 | }; 330 | 331 | module.exports = new NodeMenu(); 332 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-menu", 3 | "description": "Allows to create command line menu for REPL applications", 4 | "version": "1.3.3", 5 | "author": "Borys Nebosenko ", 6 | "keywords": [ 7 | "menu", 8 | "shell", 9 | "repl", 10 | "console", 11 | "terminal" 12 | ], 13 | "repository": "git://github.com/nbu/node-menu", 14 | "dependencies": { 15 | "underscore": "1.6.0" 16 | }, 17 | "engines": { 18 | "node": ">= 0.10.0" 19 | }, 20 | "license": "MIT" 21 | } 22 | --------------------------------------------------------------------------------