В результате моего поверхностного изучения ES6 родилось это (Автору вряд ли подойдет, но смысл схож):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>CLI</title>
<style>
body{
background:#333;
color:#FFF;
font-size:16px;
line-height:20px;
}
.command-line{
position:relative;
padding-left:50px;
}
.command-line input,.command-line input:focus,#console{
width:100%;
color:inherit;
font-size:inherit;
font-family:inherit;
background:transparent;
border:none;
outline:none;
cursor:default;
}
.command-line:before{
content:'[root]#';
position:absolute;
left:0;
}
#console{
display:block;
padding:0;
margin:0;
}
.error{
color:red;
}
</style>
<script type="text/javascript">
var INPUT,
CONSOLE,
__history=[],
__history_pos=0;
document.addEventListener('DOMContentLoaded',function(){
var classes=['PhoneBook'],
functions=['clear'];
INPUT=document.getElementById('command-line');
CONSOLE=document.getElementById('console');
if(!INPUT || !CONSOLE)
throw new Error('Missing one or more required HTMLElements');
INPUT.addEventListener('blur',()=>INPUT.focus());
INPUT.addEventListener('keydown',(e)=>{
var keyCode=e.keyCode||e.charCode,
offset=__history.length-__history_pos-1+2*(keyCode==40),
val=__history[offset];
if([38,40].indexOf(keyCode)<0)
return;
e.preventDefault();
if(offset<0)
return;
else if(!val)
val='';
INPUT.value=val;
__history_pos=val?__history.length-offset:0;
});
INPUT.parentNode.addEventListener('submit',(e)=>{
e.preventDefault();
var command=INPUT.value;
__history.push(command);
__history_pos=0;
CONSOLE.innerHTML+=command+'\n';
INPUT.value='';
command=command.trim().split(' ').map(i=>i.trim()).filter(i=>!!i.length);
if(!command.length)
return;
var containers=[classes,functions],
find_function;
if(find_function=command.length==1)
containers=containers.reverse();
var name=command.shift(),
index;
for(var i in containers){
if(!containers.hasOwnProperty(i) || (index=containers[i].indexOf(name))<0)
continue;
try{
var res;
if(i==0 && find_function)
res=window[containers[i][index]](...command);
else{
var _class=eval(`new ${containers[i][index]}()`),
method=command.shift();
if(!method)
break;
if(typeof _class[method]!='function')
throw new Error(`Method «${method}» not found in class «${containers[i][index]}»`);
res=_class[method](...command);
};
if(typeof res=='string')
CONSOLE.innerHTML+=res+'\n';
}catch(e){
CONSOLE.innerHTML+=`<span class="error">${e.message}</span>`+'\n';
};
break;
};
});
});
function clear(){
CONSOLE.innerHTML='Cleared\n';
}
var __PhoneBook_pseudo_db={};
class PhoneBook{
constructor(){
this._list=__PhoneBook_pseudo_db;
}
get __list(){
if(!(this instanceof PhoneBook))
throw new Error('Illegal invocation');
return this._list;
}
set __list(list){
if(!(this instanceof PhoneBook))
throw new Error('Illegal invocation');
this._list=list;
return this;
}
show(){
var row_sep='---------------------\n',
result='Name\t\tPhone(s)\n'+row_sep;
for(var name in this._list)
result+=`${name}\t\t${this._list[name].join(', ')}\n`+row_sep;
return result;
}
add(...args){
var name=args.shift();
this._list[name]=(this._list[name]||[]).concat(...args);
return 'Added';
}
remove(...args){
for(var name of args)
delete this._list[name];
return 'Removed';
}
edit(...args){
this._list[args.shift()]=args;
return 'Edited';
}
clear(){
this._list={};
return 'Cleared';
}
};
</script>
</head>
<body>
<pre id="console"></pre>
<div class="command-line">
<form><input id="command-line" type="text" autofocus autocomplete="off"/></form>
</div>
</body>
</html>