-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcli.min.js
More file actions
8 lines (8 loc) · 9.23 KB
/
cli.min.js
File metadata and controls
8 lines (8 loc) · 9.23 KB
1
2
3
4
5
6
7
8
/**
* Command Line Interpreter – a console GUI element for text-based user interaction within web applications.
* @version 1.1.0
* @copyright (c) 2025 Christoph Zager
* @license MIT
* @link https://github.com/chzager/cli
*/
class CommandLineInterpreter{static builtInCommands={clear:(e,...t)=>{switch(t?.[0]){case"--?":e.writeLn("Usage: clear").writeLn("Clear all output.");break;case void 0:e.body.replaceChildren();break;default:e.writeLn(`clear: Invalid argument: ${t[0]}`)}},help:(e,...t)=>{switch(t?.[0]){case"--?":e.writeLn("Usage: help").writeLn("Display a list of all available commands.");break;case void 0:e.writeLn("These are the available commands:").writeLn(Array.from(e.commands.keys()).sort().map((e=>`\t${e}`)).join("\n")).writeLn("\nTry `<command> --?` for more information on a specific command.");break;default:e.writeLn(`help: Invalid argument: ${t[0]}`)}},history:(e,...t)=>{switch(t?.[0]){case"--?":e.writeLn("Usage: history [option]").writeLn("Display or manage the list of all that has been entered.").writeLn("Optional arguments to manage the history:").writeLn([" \t--clean\tRemove duplicate entries and invalid commands"," \t--clear\tClean the entire history"," \t--limit [<n>]\tDisplay or set the limit of history items"].join("\n"));break;case"--clear":e.history=[],e.history.position=-1,e.memorize("history",e.history);break;case"--clean":const i=Object.keys(CommandLineInterpreter.builtInCommands),n=Array.from(e.commands.keys()).filter((e=>!i.includes(e)));e.history=Array.from(new Set(e.history)).filter((e=>n.includes(/^\S+/.exec(e)?.[0]??""))),e.history.position=e.history.length,e.memorize("history",e.history),e.writeLn("The history has been cleaned.");break;case"--limit":const r=e.getNamedArgumentValue(t,"--limit")||e.history.limit;"number"==typeof r||/[0-9]+/.test(r)?(e.history.limit=Number(r),e.history.splice(0,e.history.length-e.history.limit),e.writeLn(`The history is limited to ${e.history.limit} itmes.`),e.memorize("history",e.history),e.memorize("history_limit",e.history.limit)):e.writeLn(`history: Not a number: ${r}`);break;case void 0:e.writeLn(e.history.map((e=>e.replace(/([*_])/g,"\\$1"))).join("\n"));break;default:e.writeLn(`history: Invalid argument: ${t[0]}`)}}};constructor(e,t,i){const n=e=>{const t=e.target;switch(e.key){case"PageUp":this.body.scrollBy(0,0-(this.body.clientHeight-10));break;case"PageDown":this.body.scrollBy(0,this.body.clientHeight-10);break;case"ArrowUp":if(this.history.position>0&&this.history.length>0){e.preventDefault(),this.history.position-=1,t.innerText=this.history[this.history.position];const i=window.getSelection();if(i){i.removeAllRanges();const e=document.createRange();e.setStartAfter(t.childNodes[0]),i.addRange(e)}}break;case"ArrowDown":this.history.position<this.history.length&&(this.history.position+=1,t.innerText=this.history.position===this.history.length?"":this.history[this.history.position]);break;case"Escape":t.innerText="";break;case"Enter":const i=t.innerText;t.replaceWith(CommandLineInterpreter.createElement("span.input",i+"\n")),/\w/.test(i)&&(this.history[this.history.length-1]!==i&&this.history.push(i.trim()),this.history.splice(0,this.history.length-this.history.limit),this.history.position=this.history.length,this.memorize("history",this.history)),this.eval(i.trim()).finally((()=>{this.receiveInput(this.prompt,n)}))}};if(this.prompt=i?.prompt||"\nCLI> ",this.options={richtextEnabled:i?.richtextEnabled??!0,tabWidth:i?.tabWidth||2},this.id=i?.id||this.constructor.name,"custom"!==i?.theme){const e=`link[rel="stylesheet"][href="https://cdn.jsdelivr.net/gh/chzager/cli/themes/${i?.theme||"default"}.css"]`;document.head.querySelector(e)||document.head.append(CommandLineInterpreter.createElement(e))}const r=JSON.parse(localStorage.getItem(this.id)||"{}");this.history=r?.history??[],this.history.limit=Number(r?.history_limit)||50,this.history.position=this.history.length,this.commands=new Map;for(const t of[CommandLineInterpreter.builtInCommands,e])for(const[e,i]of Object.entries(t))"function"==typeof i&&this.commands.set(e,i);this.body=t instanceof HTMLElement?t:document.body.appendChild(CommandLineInterpreter.createElement("div")),this.body.classList.add(CommandLineInterpreter.name.toLowerCase()),this.body.onclick=()=>{if(""===window.getSelection()?.toString()){const e=this.body.querySelector('.input[contenteditable="true"]');e instanceof HTMLElement&&e.focus()}},i?.motd&&this.writeLn(i.motd),this.eval(i?.startup??"").then((()=>{this.receiveInput(this.prompt,n)}))}eval(e){return new Promise((t=>{const i=e=>{this.writeLn(`[31m${e}`),console.error(e)},n=e=>new Promise((t=>{let n=!0;const r=/(\S+)(.*)/.exec(e);if(r){const e=r[1],s=this.commands.get(e);if("function"==typeof s){const e=[];for(const t of(r[2]??"").matchAll(/"([^"]*)"|\S+/g))e.push(t[1]||t[0]);try{const r=s(this,...e);r instanceof Promise&&(n=!1,r.catch(i).finally(t))}catch(e){i(e)}}else this.writeLn(`Unknown command: ${e}`)}n&&t()})),r=()=>{const e=s.shift();e?n(e).finally(r):t()},s=e.split(/\s*[;\n]+\s*/).filter((e=>!!e.trim()));r()}))}receiveInput(e,t){this.body.normalize(),this.write(e);const i=CommandLineInterpreter.createElement('span.input[contenteditable="true"][spellcheck="false"][autocorrect="off"][autocapitalize="none"]');i.addEventListener("paste",(e=>{e.preventDefault();const t=e.clipboardData.getData("text/plain"),n=window.getSelection();if(n){const e=n.getRangeAt(0),r=e.startOffset,s=i.innerText;i.innerText=s.substring(0,r)+t+s.substring(e.endOffset,s.length),n.removeAllRanges();const o=document.createRange();o.setStart(i.childNodes[0],r+t.length),n.addRange(o)}})),i.onkeydown=e=>{e.stopImmediatePropagation(),t(e)},setTimeout((()=>{this.body.appendChild(i),this.body.scrollTo(0,this.body.scrollHeight),i.focus()}),10)}memorize(e,t){if(localStorage){const i=JSON.parse(localStorage.getItem(this.id)||"{}");i[e]=t,localStorage.setItem(this.id,JSON.stringify(i))}}namedArgumentExists(e,...t){for(const i of t)if(e.includes(i))return!0;return!1}getNamedArgumentValue(e,...t){for(const i of t){const t=e.indexOf(i);if(t>-1)return e.length>t+1&&!1===/^--?/.test(e[t+1])?e[t+1]:""}}write(e){const t=e=>{const i=[];let n;for(;n=/((\\)([*_´]))|((\*{1,2}|_{1,2}|`)([^\s].*?)\5)|((http)s?:\/\/\S+\b)|((\x1b\[)(3[0-7]|0)m(.*))/g.exec(e);){const r=n[2]??n[5]??n[8]??n[10],s=n[3]??n[6]??n[7]??n[12],o=e.indexOf(r);if("\\"===r)i.push(e.substring(0,o),s),e=e.substring(o+2);else{let a="span",c=[],l=s.length;if("["===r){l+=n[11].length+3;let e="--cli-color-foreground";switch(n[11]){case"30":e="--cli-color-black";break;case"31":e="--cli-color-red";break;case"32":e="--cli-color-green";break;case"33":e="--cli-color-yellow";break;case"34":e="--cli-color-blue";break;case"35":e="--cli-color-magenta";break;case"36":e="--cli-color-cyan";break;case"37":e="--cli-color-white";break}a=`span[style="color:var(${e})"]`,c=t(s)}else if("http"===r)a=`a[href="${s}"][target="_blank"]`,c=[s];else{switch(r){case"**":case"__":a="b",c=t(s);break;case"*":case"_":a="i",c=t(s);break;case"`":a="code",c=[s];break}l+=2*r.length}i.push(e.substring(0,o),CommandLineInterpreter.createElement(a,...c)),e=e.substring(o+l)}}return i.push(e),i},i=e=>{const i=[],n=e.split("\n").map((e=>e.split("\t").map(((e,n)=>{const r=CommandLineInterpreter.createElement("p",...t(e));return i[n]=Math.max(i[n]||0,r.innerText.length),r}))));for(const e of n){const t=e.length;for(let n=0;n<t;n+=1){const r=e[n],s=r.innerText,o=i[n]-s.length;o>0&&(/^[\-\+]?\d+(\.\d*)?\s*$/.test(s)?r.prepend(" ".repeat(o)):n<t-1&&r.append(" ".repeat(o))),r.append(n<t-1?" ".repeat(this.options.tabWidth):"\n")}}return n.map((e=>e.map((e=>Array.from(e.childNodes))))).flat(2)};return this.options.richtextEnabled?/\t/.test(e)?this.body.append(...i(e.replace(/[\s\n]*$/g,""))):this.body.append(...t(e)):this.body.append(e.replace(/\x1b\[\d+m/g,"")),this.body.scrollTo(0,this.body.scrollHeight),this}writeLn(e){return this.write(e+"\n")}readKey(e,t){return new Promise((i=>{const n=[...e.map((e=>e.toLowerCase()))];this.receiveInput(t||e.join("/").toUpperCase()+"? ",(e=>{if(!1===/^f\d/i.test(e.key)&&(e.preventDefault(),n.includes(e.key.toLowerCase()))){const t=e.key;e.target.replaceWith(CommandLineInterpreter.createElement("span",t+"\n")),i(t.toUpperCase())}}))}))}readLn(e){return new Promise((t=>{this.receiveInput(e||"> ",(e=>{const i=e.target;switch(e.key){case"Escape":i.innerText="";break;case"Enter":i.replaceWith(CommandLineInterpreter.createElement("span.input",i.innerText+"\n")),t(i.innerText)}}))}))}readSecret(e){return new Promise((t=>{let i="";this.receiveInput(e||"> ",(e=>{switch(e.key){case"Enter":e.target.replaceWith(CommandLineInterpreter.createElement("span","\n")),t(i);break;case"Backspace":i=i.substring(0,i.length-1);break;default:1===e.key.length&&(e.preventDefault(),i+=e.key)}}))}))}static createElement(e,...t){const i=document.createElement(/^[a-z0-1]+/.exec(e)?.[0]||"div");for(const t of e.matchAll(/\[(.+?)=["'](.+?)["']\]/g))i.setAttribute(t[1],t[2]);for(const t of e.replaceAll(/\[.+\]/g,"").matchAll(/\.([^.[\s]+)/g))i.classList.add(t[1]);return i.append(...t),i}}addEventListener("DOMContentLoaded",(()=>{document.head.appendChild(CommandLineInterpreter.createElement("style",`.${CommandLineInterpreter.name.toLowerCase()} * {${["background-color: transparent;","color: inherit;","font-family: inherit;","font-size: inherit;","padding: 0;","margin: 0;","border: none;","outline: none;","white-space: inherit;"].join("")}}`))}));