UNPKG

censql

Version:

A NodeJS command line client for SAP HANA

392 lines (290 loc) 11.4 kB
var readline = require('historic-readline'); var charm = require('charm')(process.stdout); var colors = require("colors"); var path = require('path'); var async = require('async'); var stripColorCodes = require('stripcolorcodes'); var osHomedir = require('os-homedir'); var StudioSession = require("./studio/StudioSession.js"); var StudioGraphics = require("./studio/StudioGraphics.js"); var ScreenManager = function(isBatch, settings, commandHandler) { this.isBatch = isBatch; this.settings = settings; this.commandHandler = commandHandler; } /** * Initialize settings and prepare the screen manager */ ScreenManager.prototype.init = function() { this.loadDataFormatters(); this.loadPipeHandlers(); if (!this.isBatch) { this.printHeader } this.setupInput(); } ScreenManager.prototype.loadPipeHandlers = function() { this.pipeHandlers = {}; this.pipeHandlersNames = []; this.pipeHandlers["cut"] = require("./pipeCommands/cut.js"); this.pipeHandlers["grep"] = require("./pipeCommands/grep.js"); this.pipeHandlers["head"] = require("./pipeCommands/head.js"); this.pipeHandlers["tail"] = require("./pipeCommands/tail.js"); this.pipeHandlers["wc"] = require("./pipeCommands/wc.js"); this.pipeHandlers["tac"] = require("./pipeCommands/tac.js"); this.pipeHandlersNames = Object.keys(this.pipeHandlers); } ScreenManager.prototype.loadDataFormatters = function() { this.formatters = {}; this.formatters["bar-chart"] = require("./dataFormatters/bar-chart.js"); this.formatters["bc"] = require("./dataFormatters/bc.js"); this.formatters["c"] = require("./dataFormatters/c.js"); this.formatters["csv"] = require("./dataFormatters/csv.js"); this.formatters["default"] = require("./dataFormatters/default.js"); this.formatters["g"] = require("./dataFormatters/g.js"); this.formatters["group"] = require("./dataFormatters/group.js"); this.formatters["jj"] = require("./dataFormatters/jj.js"); this.formatters["j"] = require("./dataFormatters/j.js"); this.formatters["json"] = require("./dataFormatters/json.js"); this.formatters["key-value-bar-chart"] = require("./dataFormatters/key-value-bar-chart.js"); this.formatters["kvbc"] = require("./dataFormatters/kvbc.js"); this.formatters["lg"] = require("./dataFormatters/lg.js"); this.formatters["line-graph"] = require("./dataFormatters/line-graph.js"); this.formatters["message"] = require("./dataFormatters/message.js"); this.formatters["m"] = require("./dataFormatters/m.js"); this.formatters["pretty-json"] = require("./dataFormatters/pretty-json.js"); this.formatters["table"] = require("./dataFormatters/table.js"); this.formatters["t"] = require("./dataFormatters/t.js"); this.formattersNames = Object.keys(this.formatters); } /** * Add an input handler to the cli and pass it to the commandHandler */ ScreenManager.prototype.setupInput = function() { if (!process.stdout.isTTY) { process.stdout.on('error', function(err) { if (err.code == "EPIPE") { process.exit(0); } }) return; } process.stdin.pause(); readline.createInterface({ input: process.stdin, output: process.stdout, path: path.join(osHomedir(), ".censql", "censql_hist"), maxLength: 2000, prompt: colors.cyan("> "), next: function(rl) { this.rl = rl; this.rl.on('line', function(line) { if (global.censql.RUNNING_PROCESS) return; /** * Check terminal width */ global.graphWidth = process.stdout.columns; /** * Stop taking user input until we complete this request */ process.stdin.pause(); /** * Save that we already have a running process * @type {Boolean} */ global.censql.RUNNING_PROCESS = true; /** * Hide user input whilst a command is running (simply pausing stdin still allows scrolling through history) */ process.stdin._events._keypress_full = process.stdin._events.keypress; process.stdin._events.keypress = function(ch, key) { if (key && key.ctrl && key.name == 'c') { global.SHOULD_EXIT = true; } }; /** * Send the user command to the command handler */ this.commandHandler.handleCommand(line, function(err, output) { /** * Print the command to the screen however the command handler thinks is best */ this.printCommandOutput(line, output, function() { /** * Re-enable the command line */ process.stdin.resume(); /** * Reset running app state * @type {Boolean} */ global.censql.RUNNING_PROCESS = false; /** * Should the running process exit? * @type {Boolean} */ global.SHOULD_EXIT = false; /** * Reset the keypress function for stdin */ process.stdin._events.keypress = process.stdin._events._keypress_full; }.bind(this)); }.bind(this)); }.bind(this)); /** * On close, give the user a pretty message and then stop the entire program */ this.rl.on('close', function() { this.print(colors.green('\n\nHave a great day!\n')); this.rl.close(); process.exit(0); }.bind(this)); /** * When the user ^Cs make sure they weren't just trying to clear the command */ this.rl.on('SIGINT', function() { if (global.censql.RUNNING_PROCESS) { global.SHOULD_EXIT = true; return; } charm.erase("line"); charm.left(99999); charm.up(1); /** * Exit command promt */ this.rl.close(); /** * Exit program */ process.exit(0); }.bind(this)); }.bind(this) }); } /** * Print a pretty header when the user enters interactive mode */ ScreenManager.prototype.printHeader = function() { this.print(colors.cyan(colors.bold(colors.underline("Welcome to CenSQL for SAP HANA!\n\n\n")))); this.print(colors.yellow("Connecting to HANA...")) } /** * The rest of the program is ready for user input, start listening on stdin */ ScreenManager.prototype.ready = function(hdb) { if (!this.isBatch) { this.clear() } /** * Should we enter the cli or studio mode? */ if (this.settings.studio) { global.graphWidth = process.stdout.columns / 1.5; this.graphics = new StudioGraphics(this); this.studio = new StudioSession(this, hdb, this.commandHandler); } else { global.graphWidth = process.stdout.columns; this.print(colors.cyan(colors.bold("For help type \\h\n-----------------------------------------------------\n\n"))); this.print(colors.cyan("> ")); process.stdin.resume(); } } ScreenManager.prototype.printCommandOutput = function(command, outputs, callback, dontPrintPrompt) { this.renderCommandOutput(command, outputs, function(err, lines) { this.renderLines([].concat.apply([], lines), function() { /** * Dont display a prompt for batch requests */ if (!this.isBatch && !dontPrintPrompt) { this.print("\n" + colors.cyan("> ")); } callback(); }.bind(this)) }.bind(this)); } /** * Print the output to a command entered by the user * @param {String} command The command the user ran * @param {Array} outputs the data and how to display it */ ScreenManager.prototype.renderCommandOutput = function(command, outputs, callback) { var cSegs = command.split("||"); var initialCommand = ""; /** * The parts of the command * @type {String[]} */ var cParts = []; var hasReachedPipe = false; for (var i = 0; i < cSegs.length; i++) { var splitOnPipes = cSegs[i].split(/[^\\]\|/); // console.log(splitOnPipes) if (!hasReachedPipe) { if (splitOnPipes.length > 1) { hasReachedPipe = true; initialCommand += splitOnPipes[0]; splitOnPipes.shift() } else { initialCommand += cSegs[i]; if (i + 1 < cSegs.length) { initialCommand += " || "; } } } if (hasReachedPipe) { cParts = cParts.concat(splitOnPipes); } }; cParts.unshift(initialCommand); async.mapSeries(outputs, function(output, callback) { /** * Pass the data to the chosen formatter */ var lines = this.formatters[output[3]](output[0], output[2], output[4], this.settings, output[5], output[6], output[7]); callback(null, this.processPipes(lines, cParts, this.settings)); }.bind(this), function(err, lines) { callback(err, lines); }.bind(this)) } /** * Send the data through all the piped commands they added */ ScreenManager.prototype.processPipes = function(linesIn, commandParts) { var linesOut = linesIn.slice(0); for (var j = 1; j < commandParts.length; j++) { var commandName = commandParts[j].trim().split(" ")[0].toLowerCase(); if (!this.pipeHandlers[commandName]) { linesOut = ["Unknown command: " + commandName + ". try \\h for help"]; continue; } linesOut = this.pipeHandlers[commandName](linesOut, commandParts[j]) } return linesOut; } /** * Draw the data line by line, removing colour if the user requested no colour */ ScreenManager.prototype.renderLines = function(lines, callback) { async.mapSeries(lines, function(line, callback) { this.print(line + "\n", callback); }.bind(this), callback ? callback : null); } ScreenManager.prototype.error = function(message, callback) { this.print(colors.red(message), callback); } ScreenManager.prototype.print = function(message, callback) { if (!this.settings.colour) { process.stdout.write(stripColorCodes(message || "NULL"), callback ? callback : null); } else { process.stdout.write(message || "NULL", callback ? callback : null); } } ScreenManager.prototype.clear = function() { charm.erase("screen"); this.goto(1, 1); } ScreenManager.prototype.goto = function(x, y) { charm.position(x, y); } module.exports = ScreenManager;