diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8587dc1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 safaritrader + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..22c70c5 --- /dev/null +++ b/README.md @@ -0,0 +1,97 @@ +# Custom Command-Line Interface (CLI) Project + +Welcome to the Custom CLI project! This project provides an interactive command-line interface implemented in JavaScript, along with a GUI-based Command Creator to build custom commands and sub-commands. + +> [!IMPORTANT] +> All of codes in this repository Made with the ChatGPT :white_flag: +> [![ License](https://img.shields.io/badge/Chat_GPT_Version-o1_preview-blue&?color=red)](https://chatgpt.com) + +##[Demo](https://safaritrader.github.io/js-cli/index.html) + +## Features + +- **Interactive CLI**: Navigate through commands and frameworks, execute actions, and receive feedback in real-time. +- **Dynamic Command Hierarchy**: Supports infinite nesting of commands and sub-commands. +- **Command History Navigation**: Use the up and down arrow keys to navigate through previous commands. +- **Tree-Structured Help Display**: Type `help` to see a hierarchical list of available commands. +- **Dark Dracula Theme**: A visually appealing dark theme inspired by PyCharm's Dracula theme. +- **GUI-based Command Creator**: Build custom commands with specific handlers using a user-friendly interface. + +
+ +Getting Started + + +### Launch the CLI Interface + +- Open [`cli.html`](https://safaritrader.github.io/js-cli/cli.html) in your web browser. +- Type commands into the input field and press **Enter** to execute them. +- Use `help` to see available commands. + +### Use the Command Creator + +- Open [`command_creator.html`](https://safaritrader.github.io/js-cli/command_creator.html) in your web browser. +- Use the GUI to create custom commands and sub-commands. +- Generate the JavaScript code and integrate it into your CLI. + +## How to Add Custom Commands + +1. **Open the Command Creator**: Use the Command Creator page to build your commands. +2. **Define Commands**: Enter command names, types, descriptions, and handler code. +3. **Generate Code**: Click **Generate Code** to create the JavaScript code for your commands. +4. **Copy and Integrate**: Copy the generated code and paste it into `js/cli.js` after the CLI initialization code. +5. **Test Your Commands**: Reload the CLI page and test your new commands. + +## Project Structure + +JS-CLI/ ├── index.html ├── cli.html ├── command_creator.html ├── css/ │ └── style.css ├── js/ │ ├── cli.js │ └── command_creator.js └── README.md + + +## Customization + +Feel free to modify and extend the project to suit your needs. You can: + +- Add new commands and frameworks. +- Customize the theme and styles. +- Integrate additional features such as syntax highlighting or command auto-completion. + +## Acknowledgments + +This project was developed to provide a flexible platform for creating interactive command-line interfaces in web environments. It combines practical functionality with an intuitive user experience. + +--- + +## **Deploying to GitHub Pages** + +To host your site on GitHub Pages: + +1. **Push Your Code to GitHub**: Commit and push all your files to your GitHub repository. +2. **Enable GitHub Pages**: + - Go to your repository's settings. + - Scroll down to **GitHub Pages**. + - Under **Source**, select the branch you want to use (usually `main` or `master`). + - Click **Save**. +3. **Access Your Site**: After a few minutes, your site will be available at: + - `https://.github.io` (for user/organization sites) + - `https://.github.io/` (for project sites) + +--- + +
+ +## **Final Notes** + +By following this guide, you should have a fully functional GitHub Pages site hosting your CLI project and command creator. Users can interact with the CLI directly in their browsers and use the Command Creator to build and customize commands. + +This project demonstrates how JavaScript and HTML can be leveraged to create interactive web-based command-line interfaces. The use of classes, event handling, and dynamic DOM manipulation showcases modern web development techniques. + +Feel free to share your project with others, contribute to its development, and explore new features and enhancements! + +--- + +**Enjoy building and customizing your CLI project!** +## 📞 Contact + +[Hassan Safari] - Telegram : @safari_trader - info@global-fxs.com + +Project Link: https://github.com/safaritrader/js-cli \ No newline at end of file diff --git a/cli.html b/cli.html new file mode 100644 index 0000000..76bd114 --- /dev/null +++ b/cli.html @@ -0,0 +1,106 @@ + + + + + JS CLI + + + + + + +

CLI Test

+
+ + + + + + diff --git a/command_creator.html b/command_creator.html new file mode 100644 index 0000000..591bf56 --- /dev/null +++ b/command_creator.html @@ -0,0 +1,29 @@ + + + + + Command Creator + + + + + +

Command Creator

+
+ + + + + + + + + diff --git a/css/command.css b/css/command.css new file mode 100644 index 0000000..902b5e8 --- /dev/null +++ b/css/command.css @@ -0,0 +1,187 @@ +/* Import a monospaced font for better code readability */ +@import url('https://fonts.googleapis.com/css2?family=Source+Code+Pro&display=swap'); + +body { + font-family: 'Source Code Pro', monospace; + background-color: #282a36; /* Dracula Background */ + color: #f8f8f2; /* Dracula Foreground */ + margin: 20px; +} + +h1 { + color: #f8f8f2; /* Foreground color */ + border-bottom: 1px solid #44475a; + padding-bottom: 10px; +} + +.command { + border: 1px solid #44475a; /* Dracula Current Line */ + padding: 15px; + margin-bottom: 15px; + background-color: #343746; /* Slightly lighter than background */ + border-radius: 5px; +} + +.subcommands { + margin-left: 20px; + border-left: 2px solid #6272a4; + padding-left: 15px; + margin-top: 15px; +} + +label { + display: block; + margin-top: 15px; + color: #8be9fd; /* Dracula Cyan */ +} + +input[type="text"], +select, +textarea { + width: 100%; + padding: 10px; + margin-top: 5px; + box-sizing: border-box; + background-color: #44475a; /* Dracula Current Line */ + color: #f8f8f2; /* Foreground color */ + border: 1px solid #6272a4; /* Dracula Comment */ + border-radius: 3px; +} + +input[type="text"]:focus, +select:focus, +textarea:focus { + outline: none; + border-color: #50fa7b; /* Dracula Green */ + box-shadow: 0 0 5px rgba(80, 250, 123, 0.5); +} + +textarea { + resize: vertical; + min-height: 80px; +} + +button { + margin-top: 15px; + background-color: #6272a4; /* Dracula Comment */ + color: #f8f8f2; /* Foreground color */ + border: none; + padding: 10px 20px; + cursor: pointer; + border-radius: 3px; + font-weight: bold; + transition: background-color 0.3s ease; +} + +button:hover { + background-color: #50fa7b; /* Dracula Green */ + color: #282a36; /* Background color */ +} + +#generatedCode { + width: 100%; + height: 300px; + margin-top: 20px; + padding: 15px; + box-sizing: border-box; + font-family: 'Source Code Pro', monospace; + background-color: #282a36; /* Dracula Background */ + color: #f8f8f2; /* Foreground color */ + border: 1px solid #44475a; /* Dracula Current Line */ + border-radius: 5px; + overflow: auto; +} + +#copyButton { + margin-top: 10px; + background-color: #bd93f9; /* Dracula Purple */ +} + +#copyButton:hover { + background-color: #ff79c6; /* Dracula Pink */ +} + +#addCommandButton { + background-color: #ffb86c; /* Dracula Orange */ +} + +#addCommandButton:hover { + background-color: #ff79c6; /* Dracula Pink */ +} + +#generateButton { + background-color: #50fa7b; /* Dracula Green */ + margin-left: 10px; +} + +#generateButton:hover { + background-color: #f1fa8c; /* Dracula Yellow */ + color: #282a36; /* Background color */ +} + +input::placeholder, +textarea::placeholder { + color: #6272a4; /* Dracula Comment */ +} + +button:focus { + outline: none; + box-shadow: 0 0 5px rgba(248, 248, 242, 0.5); +} + +.command > button { + margin-right: 10px; +} + +.removeCommandButton { + background-color: #ff5555; /* Dracula Red */ +} + +.removeCommandButton:hover { + background-color: #ff79c6; /* Dracula Pink */ +} + +.addSubCommandButton { + background-color: #8be9fd; /* Dracula Cyan */ +} + +.addSubCommandButton:hover { + background-color: #50fa7b; /* Dracula Green */ +} + +::selection { + background-color: #44475a; /* Dracula Selection */ +} + +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-track { + background: #282a36; /* Dracula Background */ +} + +::-webkit-scrollbar-thumb { + background-color: #6272a4; /* Dracula Comment */ + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background-color: #50fa7b; /* Dracula Green */ +} + +@media (max-width: 600px) { + body { + margin: 10px; + } + + button { + width: 100%; + margin-top: 10px; + } + + .command > button { + width: auto; + margin-right: 0; + } +} diff --git a/css/header.css b/css/header.css new file mode 100644 index 0000000..886652b --- /dev/null +++ b/css/header.css @@ -0,0 +1,64 @@ +/* Navigation Menu Styles */ +nav { + background-color: #343746; /* Slightly lighter than background */ + border-bottom: 1px solid #44475a; /* Dracula Current Line */ + padding: 10px 20px; +} + +nav ul { + list-style-type: none; + margin: 0; + padding: 0; + display: flex; + align-items: center; +} + +nav ul li { + margin-right: 20px; +} + +nav ul li:last-child { + margin-right: 0; +} + +nav ul li a { + color: #f8f8f2; /* Dracula Foreground */ + text-decoration: none; + font-weight: bold; + padding: 8px 12px; + border-radius: 4px; + transition: background-color 0.3s ease; +} + +nav ul li a:hover { + background-color: #44475a; /* Dracula Current Line */ +} + +nav ul li a.active { + background-color: #6272a4; /* Dracula Comment */ + color: #f8f8f2; +} + +@media (max-width: 600px) { + nav ul { + flex-direction: column; + align-items: flex-start; + } + nav ul li { + margin-bottom: 10px; + } + nav ul li a { + display: block; + width: 100%; + } +} +footer { + margin-top: 2em; + padding-top: 1em; + border-top: 1px solid #44475a; /* Dracula Current Line */ + color: #6272a4; /* Dracula Comment */ + text-align: center; +} +footer p{ + color: #f8f8f2; /* Dracula Comment */ +} \ No newline at end of file diff --git a/css/styles.css b/css/styles.css new file mode 100644 index 0000000..5962ec6 --- /dev/null +++ b/css/styles.css @@ -0,0 +1,171 @@ +/* Import the 'Source Code Pro' font */ +@import url('https://fonts.googleapis.com/css2?family=Source+Code+Pro&display=swap'); + +/* Global Styles */ +body { + font-family: 'Source Code Pro', monospace; + background-color: #282a36; /* Dracula Background */ + color: #f8f8f2; /* Dracula Foreground */ + margin: 0; + padding: 20px; + line-height: 1.6; +} + +h1, h2, h3, h4, h5, h6 { + color: #f8f8f2; /* Foreground color */ + margin-top: 1em; + margin-bottom: 0.5em; +} + +p { + color: #f8f8f2; + margin-bottom: 1em; +} + +a { + color: #8be9fd; /* Dracula Cyan */ + text-decoration: none; +} + +a:hover { + color: #50fa7b; /* Dracula Green */ +} + +ul, ol { + margin-left: 20px; + margin-bottom: 1em; +} + +li { + margin-bottom: 0.5em; +} + +footer { + margin-top: 2em; + padding-top: 1em; + border-top: 1px solid #44475a; /* Dracula Current Line */ + color: #6272a4; /* Dracula Comment */ + text-align: center; +} + +code { + background-color: #44475a; /* Dracula Current Line */ + padding: 2px 4px; + border-radius: 4px; + color: #f8f8f2; +} + +pre { + background-color: #44475a; + padding: 10px; + border-radius: 5px; + overflow: auto; + color: #f8f8f2; +} + +hr { + border: none; + border-top: 1px solid #44475a; + margin: 2em 0; +} + +table { + width: 100%; + border-collapse: collapse; + margin-bottom: 1em; +} + +th, td { + padding: 10px; + border: 1px solid #44475a; +} + +th { + background-color: #6272a4; /* Dracula Comment */ + color: #f8f8f2; +} + +td { + background-color: #343746; /* Slightly lighter than background */ + color: #f8f8f2; +} + +blockquote { + border-left: 4px solid #6272a4; + padding-left: 10px; + color: #f8f8f2; + margin-left: 0; + margin-right: 0; +} + +img { + max-width: 100%; + height: auto; +} + +button, input[type="submit"], input[type="reset"] { + background-color: #6272a4; /* Dracula Comment */ + color: #f8f8f2; + border: none; + padding: 10px 20px; + cursor: pointer; + border-radius: 3px; + font-weight: bold; + transition: background-color 0.3s ease; +} + +button:hover, input[type="submit"]:hover, input[type="reset"]:hover { + background-color: #50fa7b; /* Dracula Green */ + color: #282a36; /* Background color */ +} + +input, textarea, select { + background-color: #44475a; /* Dracula Current Line */ + color: #f8f8f2; + border: 1px solid #6272a4; + padding: 10px; + border-radius: 3px; + font-size: 16px; + width: 100%; + box-sizing: border-box; + margin-bottom: 1em; +} + +input:focus, textarea:focus, select:focus { + outline: none; + border-color: #50fa7b; /* Dracula Green */ + box-shadow: 0 0 5px rgba(80, 250, 123, 0.5); +} + +label { + display: block; + margin-bottom: 0.5em; + color: #8be9fd; /* Dracula Cyan */ +} + +::selection { + background-color: #44475a; /* Dracula Selection */ +} + +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-track { + background: #282a36; /* Dracula Background */ +} + +::-webkit-scrollbar-thumb { + background-color: #6272a4; /* Dracula Comment */ + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background-color: #50fa7b; /* Dracula Green */ +} + +@media (max-width: 600px) { + body { + padding: 10px; + } +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..56e6563 --- /dev/null +++ b/index.html @@ -0,0 +1,52 @@ + + + + + Custom CLI Documentation + + + + + + + +

Custom Command-Line Interface (CLI) Project

+ +

Welcome to the Custom CLI project! This project provides a versatile command-line interface implemented in JavaScript, along with a GUI-based Command Creator to build custom commands and sub-commands.

+ +

Features

+ + +

Get Started

+

Choose an option below to explore the project:

+ + +

Documentation

+

For detailed instructions on how to use the CLI and Command Creator, refer to the README file.

+ +

About the Project

+

This project was developed to provide a flexible and extensible command-line interface that can be easily customized to suit various needs. The Command Creator allows users to build their own commands with specific handlers and integrate them into the CLI seamlessly.

+ +

I hope you find this tool useful and enjoy exploring its capabilities!

+ + + + diff --git a/js-cli.jpg b/js-cli.jpg new file mode 100644 index 0000000..8211782 Binary files /dev/null and b/js-cli.jpg differ diff --git a/js/cli.js b/js/cli.js new file mode 100644 index 0000000..132fae1 --- /dev/null +++ b/js/cli.js @@ -0,0 +1,182 @@ +class Command { + constructor(name, type = 'self', description = '', handler = null) { + this.name = name; + this.type = type; // 'framework' or 'self' + this.description = description; + this.handler = handler; // Function to execute + this.subCommands = {}; + } + + addSubCommand(command) { + this.subCommands[command.name] = command; + } + + getSubCommand(name) { + return this.subCommands[name]; + } + + hasSubCommand(name) { + return this.subCommands.hasOwnProperty(name); + } +} + +class CLI { + constructor(outputElement) { + this.outputElement = outputElement; + this.root = new Command('root', 'framework', 'Root framework'); + this.currentFramework = this.root; + this.lastAnswer = null; + this.isAwaitingInput = false; + this.awaitingCommand = null; + this.commandHistory = []; + this.historyIndex = -1; + } + + registerCommand(command, parent = this.root) { + parent.addSubCommand(command); + } + + parseInput(input) { + if (input.trim() === '') return; + + // Add command to history + this.commandHistory.push(input); + this.historyIndex = this.commandHistory.length; + + if (input.trim() === 'exit()') { + this.currentFramework = this.root; + this.print('Exited to root framework.'); + return; + } + + if (input.trim() === 'clear') { + this.clearOutput(); + return; + } + + if (input.trim() === 'help') { + this.displayHelp(this.currentFramework); + return; + } + + if (this.isAwaitingInput && this.awaitingCommand) { + this.processAwaitingInput(input); + return; + } + + const args = input.split(' '); + const commandSequence = args.filter(arg => !arg.startsWith('-')); + const options = args.filter(arg => arg.startsWith('-')); + + let command = this.currentFramework; + for (let cmdName of commandSequence) { + if (command.hasSubCommand(cmdName)) { + command = command.getSubCommand(cmdName); + } else { + this.print(`Command "${cmdName}" not found.`); + return; + } + } + + if (options.includes('-help')) { + this.displayHelp(command); + return; + } + + this.executeCommand(command); + } + + executeCommand(command) { + if (command.type === 'framework') { + this.currentFramework = command; + this.print(`Switched to framework: ${command.name} Use exit()`); + if (command.description) { + this.print(command.description); + } + } else { + if (command.handler) { + const result = command.handler(this.lastAnswer); + if (result && result.requiresInput) { + this.isAwaitingInput = true; + this.awaitingCommand = command; + this.print(result.message); + } else if (result && result.response) { + this.print(result.response); + } + } + } + } + + processAwaitingInput(input) { + const result = this.awaitingCommand.handler(this.lastAnswer, input); + this.lastAnswer = input; + if (result && result.response) { + this.print(result.response); + } + this.isAwaitingInput = false; + this.awaitingCommand = null; + } + + displayHelp(command) { + this.print(`Commands for framework "${command.name}":`); + this.displaySubCommands(command, 0); + } + + displaySubCommands(command, indentLevel) { + const indent = ' '.repeat(indentLevel); + for (let subCmdName in command.subCommands) { + const subCmd = command.subCommands[subCmdName]; + this.print(`${indent}- ${subCmd.name}: ${subCmd.description}`); + if (Object.keys(subCmd.subCommands).length > 0) { + this.displaySubCommands(subCmd, indentLevel + 1); + } + } + } + + print(message) { + this.outputElement.innerHTML += message + '\n'; + this.outputElement.scrollTop = this.outputElement.scrollHeight; + } + + clearOutput() { + this.outputElement.innerHTML = ''; + } +} + +// Create CLI instance +const outputElement = document.getElementById('output'); +const cli = new CLI(outputElement); + +function clearHandler() { + cli.clearOutput(); +} + +// Input handling +const inputElement = document.getElementById('input'); +inputElement.addEventListener('keydown', function(event) { + if (event.key === 'Enter') { + const input = inputElement.value; + cli.print(`> ${input}`); // Echo the command + cli.parseInput(input); + inputElement.value = ''; + } else if (event.key === 'ArrowUp') { + if (cli.historyIndex > 0) { + cli.historyIndex--; + inputElement.value = cli.commandHistory[cli.historyIndex]; + } + event.preventDefault(); + } else if (event.key === 'ArrowDown') { + if (cli.historyIndex < cli.commandHistory.length - 1) { + cli.historyIndex++; + inputElement.value = cli.commandHistory[cli.historyIndex]; + } else { + cli.historyIndex = cli.commandHistory.length; + inputElement.value = ''; + } + event.preventDefault(); + } +}); + +// Welcome message +cli.print('Welcome to the CLI. Type "type" to switch to the type framework.'); +cli.print('Type "help" to see available commands.'); diff --git a/js/command_creator.js b/js/command_creator.js new file mode 100644 index 0000000..c756e1d --- /dev/null +++ b/js/command_creator.js @@ -0,0 +1,129 @@ +let commandCount = 0; + +function createCommandElement(parent) { + commandCount++; + const commandDiv = document.createElement('div'); + commandDiv.className = 'command'; + commandDiv.innerHTML = ` + + + + + + +
+ `; + + // Add event listeners for the buttons + const addSubCommandButton = commandDiv.querySelector('.addSubCommandButton'); + const removeCommandButton = commandDiv.querySelector('.removeCommandButton'); + const subcommandsDiv = commandDiv.querySelector('.subcommands'); + + addSubCommandButton.addEventListener('click', () => { + const subCommand = createCommandElement(commandDiv); + subcommandsDiv.appendChild(subCommand); + }); + + removeCommandButton.addEventListener('click', () => { + commandDiv.remove(); + }); + + return commandDiv; +} + +const commandsContainer = document.getElementById('commandsContainer'); +const addCommandButton = document.getElementById('addCommandButton'); +const generateButton = document.getElementById('generateButton'); +const generatedCodeTextarea = document.getElementById('generatedCode'); +const copyButton = document.getElementById('copyButton'); + +addCommandButton.addEventListener('click', () => { + const commandElement = createCommandElement(); + commandsContainer.appendChild(commandElement); +}); + +function generateCode() { + let code = ''; + + function processCommand(commandElement, parentName) { + const commandName = commandElement.querySelector('input[name="commandName"]').value.trim(); + const commandType = commandElement.querySelector('select[name="commandType"]').value; + const commandDescription = commandElement.querySelector('input[name="commandDescription"]').value.trim(); + const handlerCode = commandElement.querySelector('textarea[name="handlerCode"]').value.trim(); + const subcommands = commandElement.querySelector('.subcommands').children; + + if (!commandName) { + alert('Command name is required.'); + throw new Error('Command name is missing.'); + } + + // Generate handler function name + const handlerFunctionName = `${commandName}Handler`; + + // Add handler function code + if (handlerCode) { + code += ` +function ${handlerFunctionName}(lastAnswer, input) { + ${handlerCode} +} +`; + } else { + code += ` +function ${handlerFunctionName}(lastAnswer, input) { + // Handler code for ${commandName} +} +`; + } + + // Create command instance + code += ` +const ${commandName}Command = new Command('${commandName}', '${commandType}', '${commandDescription}', ${handlerFunctionName}); +`; + + // Register command + if (parentName) { + code += `cli.registerCommand(${commandName}Command, ${parentName}Command);\n`; + } else { + code += `cli.registerCommand(${commandName}Command);\n`; + } + + // Process sub-commands + for (let subcommandElement of subcommands) { + processCommand(subcommandElement, commandName); + } + } + + // Process top-level commands + const topCommands = commandsContainer.children; + for (let topCommand of topCommands) { + processCommand(topCommand, null); + } + + generatedCodeTextarea.value = code; +} + +generateButton.addEventListener('click', () => { + try { + generatedCodeTextarea.value = ''; + generateCode(); + } catch (error) { + console.error(error); + } +}); + +copyButton.addEventListener('click', () => { + generatedCodeTextarea.select(); + document.execCommand('copy'); + alert('Code copied to clipboard.'); +}); \ No newline at end of file