Skip to content

Instantly share code, notes, and snippets.

@ryanjduffy
Last active December 14, 2015 00:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ryanjduffy/4996593 to your computer and use it in GitHub Desktop.
Save ryanjduffy/4996593 to your computer and use it in GitHub Desktop.
Enyo CLI fiddle
BODY {
font-family:Courier New;
white-space:pre;
}
.cursor {
position:absolute;
top:0em;
border-bottom:2px solid black;
height:1em;
}
.cursor.insert {
border-bottom:4px solid black
}
<script>
new ex.Console().renderInto(document.body);
</script>
enyo.machine.script = function(inSrc, onLoad, onError) {
if (!enyo.runtimeLoading) {
document.write('<scri' + 'pt src="' + inSrc + '"' + (onLoad ? ' onload="' + onLoad + '"' : '') + (onError ? ' onerror="' + onError + '"' : '') + '></scri' + 'pt>');
} else {
var script = document.createElement('script');
script.src = inSrc;
script.onload = onLoad;
script.onerror = onError;
document.getElementsByTagName('head')[0].appendChild(script);
}
};
enyo.kind({
name: "cli.Command",
kind: "Component",
events: {
onRegisterCommand:"",
onUnregisterCommand:"",
onCommandResponse: "",
onCommandError: ""
},
create:function() {
this.inherited(arguments);
if(this.command) {
this.doRegisterCommand({command:this.command, handler:this});
}
},
destroy:function() {
this.doUnregisterCommand({command:this.command});
this.inherited(arguments);
},
execute: function(sender, event) {
if (!this.command || (this.command && this.command === event.command)) {
if (enyo.isString(this.commandHandler) && this.owner[this.commandHandler]) {
this.owner[this.commandHandler](sender, event);
} else if (enyo.isFunction(this.commandHandler)) {
this.commandHandler(sender, event);
}
}
},
commandHandler: function(sender, event) {},
hasArgument:function(command, arg) {
arg = enyo.isArray(arg) ? arg : [arg];
var ok = true;
for(var i=0;i<arg.length && ok;i++) {
ok = typeof(command.args[arg[i]]) !== "undefined";
}
return ok;
},
print:function(lines, suppress) {
if(!this.outputLines) {
this.outputLines = [];
this.dataLines = [];
}
lines = enyo.isArray(lines) ? lines : [lines];
for(var i=0,l=lines.length;i<l;i++) {
this.outputLines.push({content:lines[i]});
suppress || this.dataLines.push(lines[i]);
}
},
flush:function() {
this.doCommandResponse({response:this.outputLines, data:this.dataLines});
this.outputLines = [];
this.dataLines = [];
},
err:function(message, code) {
this.doCommandError({message:message, code:code});
}
});
enyo.kind({
name: "cli.CommandParser",
kind: "Component",
published: {
context: ""
},
events: {
onCommand: ""
},
create:function() {
this.inherited(arguments);
this.commands = {};
},
addCommand:function(c) {
this.commands[c.command] = c.handler;
},
removeCommand:function(c) {
if(this.commands[c.command]) {
delete this.commands[c.command];
}
},
parse: function(s) {
var parts = s.match(/([\-:][\w]+|"[^"]+"|[^ ]+)/g),
c = {
command: parts[0],
args: {},
argList: []
};
for (var i = 1; i < parts.length; i++) {
var p = parts[i],
p2 = parts[i + 1];
p = p && p.replace(/(^"|"$)/g,"");
p2 = p2 && p2.replace(/(^"|"$)/g,"");
c.argList.push(p);
if (p.charAt(0) === "-" && p.length > 1) {
var parm = p.substring(1);
if (p2 && p2.charAt(0) !== "-") {
if (c.args[parm]) {
if (enyo.isArray(c.args[parm])) {
c.args[parm].push(p2);
} else {
c.args[parm] = [c.args[parm], p2];
}
} else {
c.args[parm] = p2;
}
c.argList.push(p2);
i++;
} else {
c.args[parm] = "";
}
}
}
var stop = false;
c.stop = function() {
stop = true;
}
c.context = this.context;
// send event to allow global command handling first
this.doCommand(c);
if(!stop && this.commands[c.command]) {
this.commands[c.command].execute(this, c);
} else {
throw new (function CommandNotFound(command) {
this.message = "Command not found: " + command;
})(c.command);
}
}
});
enyo.kind({
name:"cli.Cursor",
kind:"Control",
classes:"cursor",
content:"&nbsp;",
allowHtml:true,
published:{
position:0,
insert:false
},
create:function() {
this.inherited(arguments);
this.insertChanged();
},
rendered:function() {
this.inherited(arguments);
if(this.hasNode()) {
this.size = this.getBounds().width;
this.applyStyle("width", this.size+"px");
this.positionChanged();
}
},
insertChanged:function() {
this.addRemoveClass("insert", this.insert);
this.show();
this.animate();
},
positionChanged:function() {
this.applyStyle("left", this.position*this.size + "px");
this.show();
this.animate();
},
animate:function() {
enyo.job("animate"+this.id, enyo.bind(this, function() {
this.setShowing(!this.showing);
this.animate();
}), 500);
},
toggleInsert:function() {
this.setInsert(!this.insert);
}
});
enyo.kind({
name: "cli.CommandLine",
kind: "Control",
whitespace:/^\s*$/,
// design-time parameter
excludeInternalCommands: false,
published: {
prompt: "cli>",
input: 0,
commands: "",
context: ""
},
events: {
onCommand: ""
},
handlers: {
onCommandResponse: "commandResponse",
onCommandError: "commandError",
onRegisterCommand:"registerCommand",
onUnregisterCommand:"unregisterCommand",
onLoadCommand:"loadCommand",
onClear:"clearScreen",
ontap:"focusInput"
},
components: [
{name:"inputProxy", kind:"enyo.Input", style:"position:absolute;left:-10000px", oninput:"inputProxyChanged"},
{name: "scroller", kind: "Scroller", style:"height:100%;width:100%", components: [
{name: "log"},
{style:"position:relative", components:[
{name: "input", kind: "Control"},
{name:"cursor", kind:"cli.Cursor"}
]}
]},
{name: "client", kind: "cli.CommandParser", onCommand: "doCommand"}
],
internalCommands:[
{kind: "cli.Prompt"},
{kind: "cli.Echo"},
{kind: "cli.Help"},
{kind: "cli.Loader"},
{kind: "cli.Clear"}
],
create: function() {
this.inherited(arguments);
this.input = [];
this.commandHistory = [];
this.commandHistoryIndex = 0;
this.cursorOffset = 0;
this.displayInput();
enyo.dispatcher.listen(document, "keydown", enyo.bind(this, "keyPressed"));
this.contextChanged();
this.commandsChanged();
},
rendered:function() {
this.inherited(arguments);
this.focusInput();
},
focusInput:function() {
this.$.inputProxy.focus();
},
inputProxyChanged:function(source, event) {
this.input = this.$.inputProxy.getValue().split('');
this.displayInput();
},
contextChanged: function() {
this.$.client.setContext(this.context);
},
commandsChanged: function() {
this.$.client.destroyComponents();
if (enyo.isArray(this.commands)) {
var commands = (this.excludeInternalCommands) ? this.commands : this.internalCommands.concat(this.commands);
this.$.client.createComponents(commands, {
owner: this.$.client
});
}
},
loadCommand:function(source, event) {
this.$.client.createComponent({kind:event.kind, command:event.command, owner: this.$.client});
},
displayInput:function() {
this.$.input.setContent(this.prompt + this.input.join(""));
this.$.cursor.setPosition(this.prompt.length + this.input.length+this.cursorOffset);
},
offsetCursor:function(i) {
this.cursorOffset = Math.min(0, Math.max(this.cursorOffset+i, -this.input.length));
this.displayInput();
},
registerCommand:function(source, command) {
this.$.client.addCommand(command);
},
unregisterCommand:function(source, command) {
this.$.client.removeCommand(command);
},
map: function(e) {
var codes = {
188: [",", "<"],
190: [".", ">"],
191: ["/", "?"],
192: ["`", "~"],
219: ["[", "{"],
220: ["\\", "|"],
221: ["]", "}"],
222: ["'", "\""],
59: [";", ":"],
186: [";", ":"],
61: ["=", "+"],
187: ["=", "+"],
107: ["=", "+"],
189: ["-", "_"],
109: ["-", "_"],
48: ["0", ")"],
49: ["1", "!"],
50: ["2", "@"],
51: ["3", "#"],
52: ["4", "$"],
53: ["5", "%"],
54: ["6", "^"],
55: ["7", "&"],
56: ["8", "*"],
57: ["9", "("],
32: [" ", " "]
};
if (e.keyCode >= 65 && e.keyCode <= 90) {
return String.fromCharCode(e.shiftKey ? e.keyCode : e.keyCode + 32);
} else if (codes[e.keyCode]) {
return codes[e.keyCode][e.shiftKey ? 1 : 0];
}
},
keyPressed: function(e) {
switch (e.keyCode) {
case 13:
this.execCommand(this.input.join(""))
break;
case 38: // up
this.loadCommandHistory(1);
break;
case 40: // down
this.loadCommandHistory(-1);
break;
case 45:
this.$.cursor.toggleInsert();
break;
case 37: // left
this.offsetCursor(-1);
return;
case 39:
this.offsetCursor(1);
return;
default:
return;
}
//this.log(e.keyCode, c);
this.displayInput();
e.preventDefault();
},
push:function(c) {
if(!c) return;
var index = this.input.length+this.cursorOffset;
if(this.$.cursor.getInsert()) {
this.input.splice(index, 0, c);
} else {
this.offsetCursor(1);
this.input[index] = c;
}
},
loadCommandHistory:function(n) {
this.cursorOffset = 0;
this.commandHistoryIndex = Math.max(-1, Math.min(this.commandHistory.length-1, this.commandHistoryIndex+n));
var cmd = this.commandHistory[this.commandHistoryIndex];
this.input = (cmd) ? cmd.match(/./g) : [];
this.$.inputProxy.setValue(this.input.join(""));
},
execCommand: function(cmd) {
this.cursorOffset = 0;
this.commandHistoryIndex = -1;
this.commandHistory.unshift(cmd);
this.$.log.createComponent({
content: this.prompt + cmd
}).render();
this.input = [];
this.$.inputProxy.setValue("");
if(!this.whitespace.test(cmd)) {
this.$.input.hide();
try {
this.$.client.parse(cmd);
} catch (e) {
this.$.log.createComponent({
content:e.message
}).render();
this.$.input.show();
}
} else {
// be sure to scroll if the user just hits enter with no command
this.$.scroller.scrollToBottom();
}
},
commandResponse: function(source, event) {
this.$.log.createComponents(event.response);
this.commandComplete();
},
commandError: function(source, error) {
this.$.log.createComponent({
content: error.message,
classes: "error",
allowHtml: true
});
this.commandComplete();
},
commandComplete:function() {
this.$.log.render();
this.$.input.show();
this.$.scroller.scrollToBottom();
},
clearScreen:function() {
this.$.log.destroyClientControls();
this.$.log.render();
}
});
enyo.kind({
name: "cli.Prompt",
kind: "cli.Command",
command:"prompt",
commandHandler:function(source, command) {
if(command.argList.length !== 1) {
this.err("prompt new-prompt");
} else {
this.bubble("onChangePrompt", {prompt:command.argList[0]});
this.flush();
}
}
});
enyo.kind({
name:"cli.Echo",
kind:"cli.Command",
command:"echo",
commandHandler:function(source, command) {
this.print(command.argList.join(" "));
this.flush();
}
});
enyo.kind({
name:"cli.Loader",
kind:"cli.Command",
command:"load",
create:function() {
this.inherited(arguments);
enyo.kind.features.push(enyo.bind(this, "inspectKind"));
},
inspectKind:function(ctor, props) {
var c = new ctor();
if(c instanceof cli.Command) {
var e = {command:ctor.prototype.command, kind:ctor.prototype.kindName};
this.print("Loaded " + e.kind + " for command " + e.command);
// have to defer a moment because the feature is called just before setObject so the command doesn't yet exist
enyo.asyncMethod(this, function() {
this.bubble("onLoadCommand", e);
});
}
c.destroy();
},
commandHandler:function(source, command) {
if(this.hasArgument(command, "url")) {
this._commands = [];
enyo.runtimeLoading = true;
var me = this;
enyo.machine.script(command.args.url, function() {
me.flush();
}, function() {
me.err("load failed");
});
} else {
this.err(this.command + " -url [url]");
}
}
});
enyo.kind({
name:"cli.Help",
kind:"cli.Command",
command:"help",
commandHandler:function(source, command) {
var response = [{content:"Available Commands"}];
var c$ = [];
for(var k in source.commands) {
c$.push(k);
}
c$.sort();
for(var i=0,c;c=c$[i];i++) {
this.print("* "+c);
}
this.flush();
}
});
enyo.kind({
name:"cli.Clear",
kind:"cli.Command",
command:"clear",
events:{
onClear:""
},
commandHandler:function() {
this.flush();
this.doClear();
}
});
Parse.initialize("vJQ94NavOWF8FLteB27syCBJa001CkdwqoYmIpx1", "Cb9JhAOSu6M8Dm3mj3NuiyzC6tVGsgZbrdKZeMQ9");
enyo.kind({
name:"pd.App",
kind:"cli.Command",
command:"pd",
commandHandler:function(source, command) {
if(this.hasArgument(command, ["login", "p"])) {
this.login(command.args.login, command.args.p);
} else if(this.hasArgument(command, "create")) {
this.createGame(["renee@thisduffylife.com"], []);
} else if(this.hasArgument(command, "list") && this.user) {
this.listGames(command.args.r !== undefined);
} else if(this.hasArgument(command, ["join"])) {
this.joinGame(command.args.join);
} else if(this.hasArgument(command, ["bid", "c", "n"]) && this.user) {
this.bid(command.args.n, command.args.c);
} else if(this.hasArgument(command, ["call"])) {
this.call(command.args.g);
} else {
this.err("bummer " + enyo.json.stringify(command.args));
}
},
login:function(username, password) {
var my = this;
Parse.User.logIn(username, password, {
success:function(u) {
// reset state ...
my.games = null;
my.game = null;
my.user = u;
my.print(username + " logged in");
my.flush();
},
error:function() {
my.err("login failed");
}
});
},
createGame:function(emails, ids) {
Parse.Cloud.run("newGame", {
emails:emails,
ids:ids
}, {
success:enyo.bind(this, function(result) {
this.game = result.game;
this.displayGame();
this.flush();
}),
error:enyo.bind(this, function() {
this.err("New game failed...");
})
});
},
getGame:function(gameId) {
if(this.games) {
for(var i=0;i<this.games.length;i++) {
if(this.games[i].objectId == gameId) {
return this.games[i];
}
}
}
},
joinGame:function(gameId) {
var g = this.getGame(gameId);
if(g) {
this.game = g;
this.print("Joined " + gameId);
this.displayGame();
this.flush();
} else {
this.doCommandError({message:gameId + " not found"});
}
},
displayGame:function() {
var g = this.game;
var response = [];
var bids = g.round.bids;
var dice;
this.print("*** Players ***", true);
enyo.forEach(g.players, enyo.bind(this, function(p) {
var bid;
for(var i=bids.length-1, b;b=bids[i];i--) {
if(p.objectId === b.player) {
bid = b;
break;
}
}
this.print(((g.round.nextPlayer === p.objectId) ? "-> " : " ") + p.name + " (" + p.count + ") " + ( bid ? bid.count + " " + (parseInt(bid.number)+1) + "s" : ""));
if(p.me) {
dice = g.round.dice[p.objectId];
}
}));
if(dice) {
this.print("*** Dice ***", true);
var s = [];
for(var i=0;i<6;i++) {
var d = dice[i];
for(var j=0;j<d;j++) {
s.push("["+(i+1)+"]");
}
}
this.print(s.join(" "));
}
},
bid:function(number, count) {
Parse.Cloud.run("bid", {
game:this.game.objectId,
number:parseInt(number)-1,
count:parseInt(count)
}, {
success:enyo.bind(this, function(response) {
this.game = response.game;
this.displayGame();
this.flush();
}),
error:enyo.bind(this, function() {
this.err("Bidding failed ...");
})
});
},
call:function(game) {
game = game || this.game;
if(game) {
Parse.Cloud.run("call", {
game:game.objectId || game
}, {
success:enyo.bind(this, function(result) {
this.game = result.game;
this.print("Call was " + (result.result.success ? "successful!" : "unsuccessful. /sadpanda"));
this.displayGame();
this.flush();
}),
error:enyo.bind(this, function(e) {
this.err("boom!");
})
});
} else {
this.err("You must join a provide a game id (-g [id]) or -join a game");
}
},
listGames:function(refresh) {
if(!this.games || refresh) {
Parse.Cloud.run("retrieveGames", null, {
success:enyo.bind(this, function(response) {
this.games = response.games;
this.printGames();
}),
error:enyo.bind(this, function() {
this.err("unable to retrieve games");
})
});
} else {
this.printGames();
}
},
printGames:function() {
for(var i=0;i<this.games.length;i++) {
var game = this.games[i];
var me = this.findMe(game);
if(me.objectId === game.round.nextPlayer) {
this.print("* " + game.objectId + "(" + game.players.length + ")");
} else {
this.print(game.objectId + "(" + game.players.length + ")");
}
}
if(this.games.length === 0) {
this.print("No active games");
}
this.flush();
},
findMe:function(game) {
var g = game || this.game;
if(g) {
for(var i=0,l=g.players.length;i<l;i++) {
var p = g.players[i];
if(p.me) {
return p;
}
}
}
}
});
enyo.kind({
name: "google.Search",
kind: "cli.Command",
command: "google",
appId: "AIzaSyB3cw_4PXUfu5ifTxdKGqasYGqHANisYeQ",
cx: "006141722805653119788:mykgrzier4g",
host: "https://www.googleapis.com/customsearch/v1",
commandHandler: function(sender, command) {
if(command.argList.length === 0 || command.args.h) {
this.help();
} else if (!!command.args.v) {
this.view(command.args.v-1);
} else if (command.args.q) {
this.query(command.args.q);
} else if (this.hasArgument(command, "next")) {
this.next();
}
},
view:function(index) {
if(this._lastResponse) {
var i = this._lastResponse.items[index];
if(i) {
window.open(i.link, "_blank");
this.flush();
} else {
this.err("Result #" + (index+1) + " was not found");
}
} else {
this.err("No buffered query");
}
},
help:function() {
this.err("Usage: " + this.command + " [-q <query>] [-next] [-v <result number> ]");
},
next:function() {
if (this._lastResponse) {
var np = this._lastResponse.queries.nextPage;
if (np) {
this.query(np[0].searchTerms, np[0].count, np[0].startIndex);
} else {
this.err("No more results");
}
} else {
this.err("No buffered query. Use " + this.command + " -q <search terms>");
}
},
query: function(terms, count, startIndex) {
var count = count || 10,
startIndex = startIndex || 1,
url = this.host + "?alt=json&key=" + this.appId + "&cx=" + this.cx + "&q=" + encodeURIComponent(terms) + "&num=" + count + "&start=" + startIndex,
x = new enyo.Ajax({
method: "GET",
url: url
});
x.go().response(this, function(sender, response) {
this._lastResponse = response;
var start = response.queries.request[0].startIndex;
for (var i = 0; i < response.items.length; i++) {
// todo: handle print classes
// "overflow:hidden;text-overflow:ellipsis;height:1em;"
this.print((i + start) + ": " + response.items[i].htmlTitle);
}
this.flush();
});
}
});
//* Using the great code from https://github.com/silentmatt/js-expression-eval
enyo.kind({
name:"ex.Calc",
command:"calc",
kind:"cli.Command",
create:function() {
this.inherited(arguments);
this.variables = {};
},
commandHandler:function(sender, command) {
if(this.hasArgument(command, "h")) {
this.err("Mathematical Expression Evaluator (from <a href=\"https://github.com/silentmatt/js-expression-eval\" target=\"blank\">https://github.com/silentmatt/js-expression-eval</a>)<br>Usage: " + this.command + " [-s variable=value] [expression]");
} else if(this.hasArgument(command, "s")) {
var s$ = enyo.isArray(command.args.s) ? command.args.s : [command.args.s];
for(var i=0;i<s$.length;i++) {
var s = s$[i].split("=");
if(s[0]) {
if(s[1]) {
var x = parseFloat(s[1]);
if(isNaN(x)) {
this.err(s[0] + " was not a number");
return;
} else {
this.variables[s[0]] = x;
}
} else {
delete this.variables[s[0]];
}
}
}
for(var v in this.variables) {
this.print(v+"="+this.variables[v]);
}
this.flush();
} else {
var ex = Parser.parse(command.argList.join(" ")).simplify(this.variables),
vars = ex.variables(),
response;
if(vars.length === 0) {
response = ex.evaluate();
} else {
response = ex.toString();
}
this.print(response);
this.flush();
}
}
});
enyo.kind({
name: "ex.Console",
kind: "Control",
fit:true,
handlers:{
onChangePrompt:"promptChanged"
},
components: [
{name: "console", kind: "cli.CommandLine", classes:"enyo-fit", onCommand: "inspectCommand", commands: [
{kind: "ex.Calc"},
{kind: "google.Search"},
{kind:"pd.App"}
]}
],
create:function() {
this.inherited(arguments);
//this.$.console.execCommand("help");
//this.$.console.execCommand("pd -login renee@thisduffylife.com -p renee");
this.$.console.execCommand("pd -login admin@tiqtech.com -p Y4rM8ies");
},
promptChanged:function(source, event) {
this.$.console.setPrompt(event.prompt+">");
},
inspectCommand: function(source, command) {}
});
new ex.Console().renderInto(document.body);
name: CLI
description: Enyo-based command line interface
authors:
- Ryan Duffy
resources:
- https://raw.github.com/silentmatt/js-expression-eval/master/parser.js
- https://parse.com/downloads/javascript/parse/latest/min.js
normalize_css: no
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment