diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..0b88d4d --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,32 @@ +name: 🧱 Build MyCMD-GUI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + name: 🔨 Build and Package + runs-on: ubuntu-latest + + steps: + - name: 🧩 Checkout source code + uses: actions/checkout@v4 + + - name: ☕ Set up Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + cache: maven + + - name: 🧰 Build using Maven Wrapper + run: ./mvnw -B clean package + + - name: 📦 Upload built JAR artifact + uses: actions/upload-artifact@v4 + with: + name: MyCMD-GUI + path: target/*.jar diff --git a/.gitignore b/.gitignore index 9f97022..7ab214a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,51 @@ -target/ \ No newline at end of file + +*.class + + +*.log + + +*.ctxt + + +.mtj.tmp/ + + +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# IntelliJ / VS Code / Eclipse +.idea/ +.vscode/ +*.iml +.project +.classpath +.settings/ + + +target/ +out/ + + +.mvn/wrapper/maven-wrapper.jar + + +.DS_Store +Thumbs.db + + +javafx_cache/ + + +launcher/*.exe +launcher/*.lnk +installer/output/ + + +.idea/ +.vscode/ diff --git a/launcher/MyCMD.bat b/launcher/MyCMD.bat new file mode 100644 index 0000000..ae42163 --- /dev/null +++ b/launcher/MyCMD.bat @@ -0,0 +1,9 @@ +@echo off +setlocal +set MYCMD_LAUNCHED=true + +:: Hide CMD window by using javaw instead of java +javaw -jar target\MyCMD-GUI-1.0-SNAPSHOT.jar + +endlocal +exit diff --git a/lib/.gitkeep b/lib/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/lib/.gitkeep @@ -0,0 +1 @@ + diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/mvnw @@ -0,0 +1 @@ + diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1 @@ + diff --git a/pom.xml b/pom.xml index 5d226e0..7b4ff9f 100644 --- a/pom.xml +++ b/pom.xml @@ -1,76 +1,80 @@ + + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 com.mycmd - MyCMD - 1.0 - jar - - MyCMD - A custom CMD shell written in Java + MyCMD-GUI + 1.0.0 + MyCMD-GUI + Dark futuristic JavaFX shell for MyCMD + UTF-8 17 17 + 21 + - org.projectlombok - lombok - 1.18.42 - provided + org.openjfx + javafx-controls + ${javafx.version} + + + org.openjfx + javafx-fxml + ${javafx.version} + + org.apache.maven.plugins maven-compiler-plugin - 3.14.1 + 3.10.1 ${maven.compiler.source} ${maven.compiler.target} - - - org.projectlombok - lombok - 1.18.42 - - - + + - org.apache.maven.plugins - maven-jar-plugin - 3.4.2 + org.openjfx + javafx-maven-plugin + ${javafx.version} - - - com.mycmd.App - - + com.mycmd.gui.MainApp - - com.diffplug.spotless - spotless-maven-plugin - 3.0.0 - - - - 1.17.0 - - - - + org.apache.maven.plugins + maven-shade-plugin + 3.3.0 + + + package + + shade + + + + + com.mycmd.gui.MainApp + + + + + - \ No newline at end of file + diff --git a/scripts/build-linux.sh b/scripts/build-linux.sh index 41502ec..6e7b6e6 100644 --- a/scripts/build-linux.sh +++ b/scripts/build-linux.sh @@ -1,22 +1,11 @@ #!/bin/bash -echo "=== Building MyCMD for Linux ===" +echo "🏗️ Building MyCMD for Linux..." -mvn clean package +# Clean and package with Maven Wrapper +./mvnw clean package -rm -rf dist -mkdir -p dist/bin dist/lib dist/icons +# Move JAR to dist folder +mkdir -p dist +cp target/MyCMD-GUI*.jar dist/MyCMD-GUI.jar -cp target/MyCMD-1.0.jar dist/lib/dependencies.jar -cp icons/mycmd.ico dist/icons/mycmd.ico - -# Example jpackage usage -jpackage \ - --name MyCMD \ - --input dist/lib \ - --main-jar dependencies.jar \ - --main-class com.mycmd.App \ - --icon icons/mycmd.ico \ - --type deb \ - --dest dist - -echo "Build complete. Installer is in dist/" +echo "✅ Build complete! File located in dist/MyCMD-GUI.jar" diff --git a/scripts/build-windows.bat b/scripts/build-windows.bat index 3ea6627..278cf79 100644 --- a/scripts/build-windows.bat +++ b/scripts/build-windows.bat @@ -1,17 +1,12 @@ @echo off -echo === Building MyCMD for Windows === +echo 🏗️ Building MyCMD for Windows... -REM Clean and build jar -mvn clean package +REM Clean and package using Maven Wrapper +call mvnw.cmd clean package -REM Create dist structure -rmdir /s /q dist -mkdir dist\bin -mkdir dist\lib -mkdir dist\icons +REM Move output JARs to /dist folder +if not exist dist mkdir dist +copy target\MyCMD-GUI*.jar dist\MyCMD-GUI.jar -REM Copy jar + icon -copy target\MyCMD-1.0.jar dist\lib\dependencies.jar -copy icons\mycmd.ico dist\icons\mycmd.ico - -echo Build complete. Use Launch4j + Inno Setup to package installer. +echo ✅ Build complete! File located in dist\MyCMD-GUI.jar +pause diff --git a/src/main/java/com/mycmd/App.java b/src/main/java/com/mycmd/App.java index 97cf691..4608f28 100644 --- a/src/main/java/com/mycmd/App.java +++ b/src/main/java/com/mycmd/App.java @@ -1,239 +1,22 @@ package com.mycmd; -import com.mycmd.commands.*; -import java.util.*; -import java.util.Scanner; +import com.mycmd.gui.MainApp; +import javafx.application.Application; +/** + * Entry point for MyCMD-GUI. + */ public class App { public static void main(String[] args) { - ShellContext context = new ShellContext(); - - // Register commands - Map commands = new HashMap<>(); - registerCommands(commands); - - System.out.println("MyCMD [Version 1.0]"); - System.out.println("(c) 2025 MyCMD Organization. All rights reserved."); - - Scanner sc = new Scanner(System.in); - context.setScanner(sc); // ← Added this line - - while (true) { - System.out.print(context.getCurrentDir().getAbsolutePath() + ">"); - String input = sc.nextLine().trim(); - if (input.isEmpty()) continue; - - // Resolve aliases before processing - input = resolveAliases(input, context); - - String[] parts = input.split("\\s+"); - String cmd = parts[0].toLowerCase(); - String[] cmdArgs = Arrays.copyOfRange(parts, 1, parts.length); - - Command command = commands.get(cmd); - if (command != null) { - try { - command.execute(cmdArgs, context); - // Add to history after successful execution - context.addToHistory(input); - } catch (Exception e) { - System.out.println("Error: " + e.getMessage()); - } - } else { - // Single, clear not-recognized message + optional suggestion - System.out.println( - "Unknown command: '" - + cmd - + "'. Enter '" - + CommandNames.HELP - + "' to list all available commands."); - - // compute suggestion safely - try { - List validCommands = new ArrayList<>(commands.keySet()); - String suggestion = StringUtils.findClosest(cmd, validCommands); - if (suggestion != null && !suggestion.equalsIgnoreCase(cmd)) { - System.out.println("Did you mean '" + suggestion + "'?"); - } - } catch (Exception ex) { - // don't let suggestion errors break the shell - } - } + // Security check to prevent CMD access + String launchedFrom = System.getenv("MYCMD_LAUNCHED"); + if (launchedFrom == null || !launchedFrom.equalsIgnoreCase("true")) { + System.out.println("❌ MyCMD-GUI cannot be run directly from CMD."); + System.out.println("➡️ Please use the official launcher (MyCMD.bat)."); + return; } - } - - private static String resolveAliases(String input, ShellContext context) { - String[] parts = input.split("\\s+", 2); - String cmd = parts[0]; - String rest = parts.length > 1 ? parts[1] : ""; - - // Check if the command is an alias - if (context.hasAlias(cmd)) { - String aliasCommand = context.getAlias(cmd); - // Replace the alias with its command, preserving arguments - return rest.isEmpty() ? aliasCommand : aliasCommand + " " + rest; - } - - return input; - } - - private static final class CommandNames { - private CommandNames() {} - - private static final String ALIAS = "alias"; - private static final String ARP = "arp"; - private static final String ASSOC = "assoc"; - private static final String ATTRIB = "attrib"; - private static final String CD = "cd"; - private static final String CHKDSK = "chkdsk"; - private static final String CHOICE = "choice"; - private static final String CLEARHISTORY = "clearhistory"; - private static final String CLIP = "clip"; - private static final String CLS = "cls"; - private static final String COLOR = "color"; - private static final String COMPACT = "compact"; - private static final String COPY = "copy"; - private static final String DATE = "date"; - private static final String DEL = "del"; - private static final String DIR = "dir"; - private static final String DRIVERQUERY = "driverquery"; - private static final String ECHO = "echo"; - private static final String EXIT = "exit"; - private static final String FC = "fc"; - private static final String FIND = "find"; - private static final String FINDSTR = "findstr"; - private static final String FORFILES = "forfiles"; - private static final String FSUTIL = "fsutil"; - private static final String FTYPE = "ftype"; - private static final String GETMAC = "getmac"; - private static final String HELP = "help"; - private static final String HISTORY = "history"; - private static final String HOSTNAME = "hostname"; - private static final String IPCONFIG = "ipconfig"; - private static final String LABEL = "label"; - private static final String LS = "ls"; - private static final String MKDIR = "mkdir"; - private static final String MORE = "more"; - private static final String MOVE = "move"; - private static final String MSG = "msg"; - private static final String NET = "net"; - private static final String NETSH = "netsh"; - private static final String NETSTAT = "netstat"; - private static final String NSLOOKUP = "nslookup"; - private static final String PATH = "path"; - private static final String PAUSE = "pause"; - private static final String PING = "ping"; - private static final String PWD = "pwd"; - private static final String REM = "rem"; - private static final String RENAME = "rename"; - private static final String REPLACE = "replace"; - private static final String RMDIR = "rmdir"; - private static final String ROBOCOPY = "robocopy"; - private static final String ROUTE = "route"; - private static final String SET = "set"; - private static final String SFC = "sfc"; - private static final String SHUTDOWN = "shutdown"; - private static final String SORT = "sort"; - private static final String START = "start"; - private static final String SYSTEMINFO = "systeminfo"; - private static final String TASKKILL = "taskkill"; - private static final String TASKLIST = "tasklist"; - private static final String TELNET = "telnet"; - private static final String TIME = "time"; - private static final String TIMEOUT = "timeout"; - private static final String TITLE = "title"; - private static final String TOUCH = "touch"; - private static final String TRACERT = "tracert"; - private static final String TREE = "tree"; - private static final String TYPE = "type"; - private static final String UNALIAS = "unalias"; - private static final String UPTIME = "uptime"; - private static final String VER = "ver"; - private static final String VERIFY = "verify"; - private static final String VOL = "vol"; - private static final String WHOAMI = "whoami"; - private static final String WMIC = "wmic"; - private static final String XCOPY = "xcopy"; - private static final String SEARCHHISTORY = "searchhistory"; - private static final String ISEARCH = "isearch"; - } - private static void registerCommands(Map commands) { - commands.put(CommandNames.ALIAS, new AliasCommand()); - commands.put(CommandNames.ARP, new ArpCommand()); - commands.put(CommandNames.ASSOC, new AssocCommand()); - commands.put(CommandNames.ATTRIB, new AttribCommand()); - commands.put(CommandNames.CD, new CdCommand()); - commands.put(CommandNames.CHKDSK, new ChkdskCommand()); - commands.put(CommandNames.CHOICE, new ChoiceCommand()); - commands.put(CommandNames.CLEARHISTORY, new ClearHistoryCommand()); - commands.put(CommandNames.CLIP, new ClipCommand()); - commands.put(CommandNames.CLS, new ClsCommand()); - commands.put(CommandNames.COLOR, new ColorCommand()); - commands.put(CommandNames.COMPACT, new CompactCommand()); - commands.put(CommandNames.COPY, new CopyCommand()); - commands.put(CommandNames.DATE, new DateCommand()); - commands.put(CommandNames.DEL, new DelCommand()); - commands.put(CommandNames.DIR, new DirCommand()); - commands.put(CommandNames.DRIVERQUERY, new DriverqueryCommand()); - commands.put(CommandNames.ECHO, new EchoCommand()); - commands.put(CommandNames.EXIT, new ExitCommand()); - commands.put(CommandNames.FC, new FcCommand()); - commands.put(CommandNames.FIND, new FindCommand()); - commands.put(CommandNames.FINDSTR, new FindstrCommand()); - commands.put(CommandNames.FORFILES, new ForfilesCommand()); - commands.put(CommandNames.FSUTIL, new FsutilCommand()); - commands.put(CommandNames.FTYPE, new FtypeCommand()); - commands.put(CommandNames.GETMAC, new GetmacCommand()); - commands.put(CommandNames.HELP, new HelpCommand(commands)); - commands.put(CommandNames.HISTORY, new HistoryCommand()); - commands.put(CommandNames.HOSTNAME, new HostnameCommand()); - commands.put(CommandNames.IPCONFIG, new IpConfig()); - commands.put(CommandNames.LABEL, new LabelCommand()); - commands.put(CommandNames.LS, new LsCommand()); - commands.put(CommandNames.MKDIR, new MkdirCommand()); - commands.put(CommandNames.MORE, new MoreCommand()); - commands.put(CommandNames.MOVE, new MoveCommand()); - commands.put(CommandNames.MSG, new MsgCommand()); - commands.put(CommandNames.NET, new NetCommand()); - commands.put(CommandNames.NETSH, new NetshCommand()); - commands.put(CommandNames.NETSTAT, new NetstatCommand()); - commands.put(CommandNames.NSLOOKUP, new NslookupCommand()); - commands.put(CommandNames.PATH, new PathCommand()); - commands.put(CommandNames.PAUSE, new PauseCommand()); - commands.put(CommandNames.PING, new PingCommand()); - commands.put(CommandNames.PWD, new PwdCommand()); - commands.put(CommandNames.REM, new RemCommand()); - commands.put(CommandNames.RENAME, new RenameCommand()); - commands.put(CommandNames.REPLACE, new ReplaceCommand()); - commands.put(CommandNames.RMDIR, new RmdirCommand()); - commands.put(CommandNames.ROBOCOPY, new RobocopyCommand()); - commands.put(CommandNames.ROUTE, new RouteCommand()); - commands.put(CommandNames.SET, new SetCommand()); - commands.put(CommandNames.SFC, new SfcCommand()); - commands.put(CommandNames.SHUTDOWN, new ShutdownCommand()); - commands.put(CommandNames.SORT, new SortCommand()); - commands.put(CommandNames.START, new StartCommand()); - commands.put(CommandNames.SYSTEMINFO, new SysteminfoCommand()); - commands.put(CommandNames.TASKKILL, new TaskkillCommand()); - commands.put(CommandNames.TASKLIST, new TasklistCommand()); - commands.put(CommandNames.TELNET, new TelnetCommand()); - commands.put(CommandNames.TIME, new TimeCommand()); - commands.put(CommandNames.TIMEOUT, new TimeoutCommand()); - commands.put(CommandNames.TITLE, new TitleCommand()); - commands.put(CommandNames.TOUCH, new TouchCommand()); - commands.put(CommandNames.TRACERT, new TracertCommand()); - commands.put(CommandNames.TREE, new TreeCommand()); - commands.put(CommandNames.TYPE, new TypeCommand()); - commands.put(CommandNames.UNALIAS, new UnaliasCommand()); - commands.put(CommandNames.UPTIME, new UptimeCommand()); - commands.put(CommandNames.VER, new VersionCommand()); - commands.put(CommandNames.VERIFY, new VerifyCommand()); - commands.put(CommandNames.VOL, new VolCommand()); - commands.put(CommandNames.WHOAMI, new WhoamiCommand()); - commands.put(CommandNames.WMIC, new WmicCommand()); - commands.put(CommandNames.XCOPY, new XcopyCommand()); - commands.put(CommandNames.SEARCHHISTORY, new SearchHistoryCommand()); - commands.put(CommandNames.ISEARCH, new InteractiveSearchCommand()); + // Launch JavaFX GUI + Application.launch(MainApp.class, args); } } diff --git a/src/main/java/com/mycmd/Command.java b/src/main/java/com/mycmd/Command.java index ce67f47..fac7b07 100644 --- a/src/main/java/com/mycmd/Command.java +++ b/src/main/java/com/mycmd/Command.java @@ -3,29 +3,11 @@ import java.io.IOException; /** - * Represents a shell command that can be executed inside the MyCMD shell. Implementations perform - * their operation when {@link #execute(String[], ShellContext)} is called. + * Interfaace for all commands. + * Every command impleements this */ public interface Command { - /** - * Execute the command. - * - * @param args command-line style arguments passed to the command. May be empty but will not be - * null. - * @param context current shell context containing state such as the current working directory. - * @throws IOException if the command cannot complete successfully. - */ void execute(String[] args, ShellContext context) throws IOException; - - /** - * Short description of the command. Default is empty so existing implementations do not break. - */ - default String description() { - return ""; - } - - /** Usage string for the command. Default is empty. */ - default String usage() { - return ""; - } + String description(); + String usage(); } diff --git a/src/main/java/com/mycmd/CommandRegistry.java b/src/main/java/com/mycmd/CommandRegistry.java new file mode 100644 index 0000000..aaf0358 --- /dev/null +++ b/src/main/java/com/mycmd/CommandRegistry.java @@ -0,0 +1,23 @@ +package com.mycmd; + +import java.util.HashMap; +import java.util.Map; + +/** + * Registers and retrieves commands by name. + */ +public class CommandRegistry { + private final Map commands = new HashMap<>(); + + public void register(String name, Command cmd) { + commands.put(name.toLowerCase(), cmd); + } + + public Command get(String name) { + return commands.get(name.toLowerCase()); + } + + public Map getAll() { + return commands; + } +} diff --git a/src/main/java/com/mycmd/ConsoleShell.java b/src/main/java/com/mycmd/ConsoleShell.java new file mode 100644 index 0000000..a35f824 --- /dev/null +++ b/src/main/java/com/mycmd/ConsoleShell.java @@ -0,0 +1,27 @@ +package com.mycmd; + +import java.util.Scanner; + +/** + * Developer console mode for debugging. + */ +public class ConsoleShell { + + public static void main(String[] args) { + CommandRegistry registry = new CommandRegistry(); + ShellContext context = new ShellContext(); + ShellEngine engine = new ShellEngine(registry, context); + + Scanner sc = new Scanner(System.in); + System.out.println("MyCMD Developer Console Mode\n(Type 'exit' to quit)"); + + while (true) { + System.out.print("> "); + String input = sc.nextLine(); + if (input.equalsIgnoreCase("exit")) break; + engine.execute(input); + } + + sc.close(); + } +} diff --git a/src/main/java/com/mycmd/ShellContext.java b/src/main/java/com/mycmd/ShellContext.java index 8816f41..66dd337 100644 --- a/src/main/java/com/mycmd/ShellContext.java +++ b/src/main/java/com/mycmd/ShellContext.java @@ -1,174 +1,42 @@ package com.mycmd; -import java.io.*; -import java.time.Instant; -import java.util.*; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NonNull; -import lombok.Setter; +import java.io.File; +import java.util.HashMap; +import java.util.Map; -@Getter(AccessLevel.PUBLIC) -public class ShellContext { - - @Setter @NonNull private File currentDir; - private List history; - private Map aliases; - - private static final String ALIAS_FILE = ".mycmd_aliases"; - private static final int MAX_HISTORY = 100; - private final List commandHistory; - private final Instant startTime; +public class ShellContext { - private final Map envVars = new HashMap<>(); + private File currentDir; + private final Map aliases; private Scanner scanner; public ShellContext() { this.currentDir = new File(System.getProperty("user.dir")); - this.history = new ArrayList<>(); this.aliases = new HashMap<>(); - this.commandHistory = new ArrayList<>(); - this.startTime = Instant.now(); - this.scanner = null; // Will be set by App.java - loadAliases(); } - // ==================== Scanner Management ==================== - - /** - * Set the shared Scanner instance for all commands to use. - * Should only be called once by App.java during initialization. - */ - public void setScanner(Scanner scanner) { - if (this.scanner != null) { - throw new IllegalStateException("Scanner already initialized"); - } - if (scanner == null) { - throw new IllegalArgumentException("Scanner cannot be null"); - } - this.scanner = scanner; + public File getCurrentDir() { + return currentDir; } - /** - * Get the shared Scanner instance. - * All commands should use this instead of creating their own Scanner. - * @return the shared Scanner instance - * @throws IllegalStateException if Scanner hasn't been initialized - */ - public Scanner getScanner() { - if (scanner == null) { - throw new IllegalStateException("Scanner not initialized in ShellContext"); - } - return scanner; - } - - public void addToHistory(String command) { - history.add(command); - commandHistory.add(command); - if (history.size() > MAX_HISTORY) { - history.remove(0); + public void setCurrentDir(File dir) { + if (dir != null && dir.exists() && dir.isDirectory()) { + this.currentDir = dir; } } - /** RETAINED FOR SAFETY: Returns a DEFENSIVE COPY instead of the raw Map. */ - public List getHistory() { - return new ArrayList<>(history); - } - public Map getAliases() { - return new HashMap<>(aliases); - } - - public Map getEnvVars() { - return new HashMap<>(envVars); - } - - public void clearHistory() { - history.clear(); + return aliases; } public void addAlias(String name, String command) { aliases.put(name, command); - saveAliases(); - } - - public void removeAlias(String name) { - aliases.remove(name); - saveAliases(); - } - - public String getAlias(String name) { - return aliases.get(name); } - public boolean hasAlias(String name) { - return aliases.containsKey(name); - } - - public void setEnvVar(String key, String value) { - envVars.put(key, value); - } - - public String getEnvVar(String key) { - return envVars.get(key); - } - - private void loadAliases() { - File aliasFile = new File(System.getProperty("user.home"), ALIAS_FILE); - // ... (method body remains the same) - if (!aliasFile.exists()) { - return; - } - - try (BufferedReader reader = new BufferedReader(new FileReader(aliasFile))) { - String line; - while ((line = reader.readLine()) != null) { - line = line.trim(); - if (line.isEmpty() || line.startsWith("#")) { - continue; - } - String[] parts = line.split("=", 2); - if (parts.length == 2) { - String name = parts[0].trim(); - String command = parts[1].trim(); - aliases.put(name, command); - } - } - } catch (IOException e) { - System.err.println("Warning: Could not load aliases: " + e.getMessage()); - } - } - - private void saveAliases() { - File aliasFile = new File(System.getProperty("user.home"), ALIAS_FILE); - // ... (method body remains the same) - try (BufferedWriter writer = new BufferedWriter(new FileWriter(aliasFile))) { - writer.write("# MyCMD Aliases Configuration\n"); - writer.write("# Format: aliasName=command\n\n"); - for (Map.Entry entry : aliases.entrySet()) { - writer.write(entry.getKey() + "=" + entry.getValue() + "\n"); - } - } catch (IOException e) { - System.err.println("Warning: Could not save aliases: " + e.getMessage()); - } - } - - /** - * Resolve the given path (absolute or relative) to a File using the current directory. If the - * provided path is absolute, returns it directly; otherwise returns a File rooted at - * currentDir. - */ - public File resolvePath(String path) { - if (path == null || path.trim().isEmpty()) { - return currentDir; - } - File f = new File(path); - if (f.isAbsolute()) { - return f; - } else { - return new File(currentDir, path); - } + public String resolveAlias(String cmd) { + return aliases.getOrDefault(cmd, cmd); } } + diff --git a/src/main/java/com/mycmd/ShellEngine.java b/src/main/java/com/mycmd/ShellEngine.java new file mode 100644 index 0000000..c22e963 --- /dev/null +++ b/src/main/java/com/mycmd/ShellEngine.java @@ -0,0 +1,38 @@ +package com.mycmd; + +import java.io.IOException; +import java.util.Arrays; + +/** + * Central execution engine. + */ +public class ShellEngine { + + private final CommandRegistry registry; + private final ShellContext context; + + public ShellEngine(CommandRegistry registry, ShellContext context) { + this.registry = registry; + this.context = context; + } + + public void execute(String input) { + if (input == null || input.trim().isEmpty()) return; + + String[] parts = input.trim().split("\\s+"); + String cmdName = context.resolveAlias(parts[0]); + String[] args = Arrays.copyOfRange(parts, 1, parts.length); + + Command cmd = registry.get(cmdName); + if (cmd == null) { + System.out.println("❌ Unknown command: " + cmdName); + return; + } + + try { + cmd.execute(args, context); + } catch (IOException e) { + System.out.println("⚠️ Error executing command: " + e.getMessage()); + } + } +} diff --git a/src/main/java/com/mycmd/StringUtils.java b/src/main/java/com/mycmd/StringUtils.java index f8a2f13..f2e8827 100644 --- a/src/main/java/com/mycmd/StringUtils.java +++ b/src/main/java/com/mycmd/StringUtils.java @@ -1,88 +1,20 @@ package com.mycmd; -import java.util.Collection; -import java.util.Objects; - /** - * Small utility for string-based helper methods. Provides a findClosest(...) implementation using - * Levenshtein distance. + * String helper functions for commands. */ -public final class StringUtils { - - private StringUtils() {} - - /** - * Find the closest string in candidates to the input. Returns null when no candidate is close - * enough. - * - * @param input the input string - * @param candidates candidate strings - * @return the closest candidate or null - */ - public static String findClosest(String input, Collection candidates) { - if (input == null || input.isEmpty() || candidates == null || candidates.isEmpty()) { - return null; - } - - String best = null; - int bestDistance = Integer.MAX_VALUE; - - for (String candidate : candidates) { - if (candidate == null || candidate.isEmpty()) continue; - if (candidate.equalsIgnoreCase(input)) { - // exact match ignoring case - return immediately - return candidate; - } - int distance = levenshteinDistance(input.toLowerCase(), candidate.toLowerCase()); - if (distance < bestDistance) { - bestDistance = distance; - best = candidate; - } - } - - // Choose a threshold: allow suggestion only when the distance is reasonably small. - // Here we allow suggestions when distance <= max(1, input.length()/3) - int threshold = Math.max(1, input.length() / 3); - if (bestDistance <= threshold) { - return best; - } - return null; +public class StringUtils { + public static boolean isEmpty(String s) { + return s == null || s.trim().isEmpty(); } - // Classic iterative Levenshtein algorithm (memory O(min(m,n))) - private static int levenshteinDistance(String a, String b) { - if (Objects.equals(a, b)) return 0; - if (a.isEmpty()) return b.length(); - if (b.isEmpty()) return a.length(); - - // ensure a is the shorter - if (a.length() > b.length()) { - String tmp = a; - a = b; - b = tmp; - } - - int[] previous = new int[a.length() + 1]; - int[] current = new int[a.length() + 1]; - - for (int i = 0; i <= a.length(); i++) previous[i] = i; - - for (int j = 1; j <= b.length(); j++) { - current[0] = j; - char bj = b.charAt(j - 1); - for (int i = 1; i <= a.length(); i++) { - int cost = (a.charAt(i - 1) == bj) ? 0 : 1; - current[i] = min3(current[i - 1] + 1, previous[i] + 1, previous[i - 1] + cost); - } - // swap previous and current - int[] temporary = previous; - previous = current; - current = temporary; + public static String join(String[] arr, int start) { + StringBuilder sb = new StringBuilder(); + for (int i = start; i < arr.length; i++) { + if (i > start) sb.append(' '); + sb.append(arr[i]); } - return previous[a.length()]; - } - - private static int min3(int x, int y, int z) { - return Math.min(x, Math.min(y, z)); + return sb.toString(); } } + diff --git a/src/main/java/com/mycmd/gui/MainApp.java b/src/main/java/com/mycmd/gui/MainApp.java new file mode 100644 index 0000000..60f3867 --- /dev/null +++ b/src/main/java/com/mycmd/gui/MainApp.java @@ -0,0 +1,56 @@ +package com.mycmd.gui; + +import com.mycmd.*; +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.image.Image; +import javafx.stage.Stage; + +/** + * JavaFX entry point — futuristic terminal window for MyCMD. + */ +public class MainApp extends Application { + + private static ShellEngine engine; + private static ShellContext context; + private static CommandRegistry registry; + + @Override + public void start(Stage stage) throws Exception { + // initialize core shell + context = new ShellContext(); + registry = new CommandRegistry(); + engine = new ShellEngine(registry, context); + + // register built-in commands (auto load) + registerBuiltIns(); + + FXMLLoader loader = new FXMLLoader( + getClass().getResource("/com/mycmd/gui/terminal.fxml")); + Scene scene = new Scene(loader.load()); + scene.getStylesheets().add( + getClass().getResource("/com/mycmd/gui/style.css").toExternalForm()); + + TerminalController controller = loader.getController(); + controller.init(engine, context); + + stage.setTitle("MyCMD ░▓ Java Terminal ▓░"); + stage.getIcons().add( + new Image(getClass().getResourceAsStream("/com/mycmd/gui/icon.png"))); + stage.setScene(scene); + stage.setResizable(false); + stage.show(); + } + + private void registerBuiltIns() { + registry.register("alias", new com.mycmd.commands.AliasCommand()); + registry.register("dir", new com.mycmd.commands.DirCommand()); + registry.register("echo", new com.mycmd.commands.EchoCommand()); + // add others automatically here as you expand + } + + public static void main(String[] args) { + launch(); + } +} diff --git a/src/main/java/com/mycmd/gui/TerminalController.java b/src/main/java/com/mycmd/gui/TerminalController.java new file mode 100644 index 0000000..6ab93b3 --- /dev/null +++ b/src/main/java/com/mycmd/gui/TerminalController.java @@ -0,0 +1,44 @@ +package com.mycmd.gui; + +import com.mycmd.ShellContext; +import com.mycmd.ShellEngine; +import javafx.fxml.FXML; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextField; +import javafx.scene.input.KeyCode; + +/** + * Controller for the FXML terminal. + */ +public class TerminalController { + + @FXML private TextArea outputArea; + @FXML private TextField inputField; + @FXML private ScrollPane scrollPane; + + private ShellEngine engine; + private ShellContext context; + + public void init(ShellEngine engine, ShellContext context) { + this.engine = engine; + this.context = context; + + output("💻 Welcome to MyCMD - Java made Terminal"); + output("Type 'help' for available commands.\n"); + + inputField.setOnKeyPressed(event -> { + if (event.getCode() == KeyCode.ENTER) { + String input = inputField.getText(); + inputField.clear(); + output("> " + input); + engine.execute(input); + scrollPane.setVvalue(1.0); + } + }); + } + + private void output(String text) { + outputArea.appendText(text + "\n"); + } +} diff --git a/src/main/resources/com/mycmd/config/default.properties b/src/main/resources/com/mycmd/config/default.properties new file mode 100644 index 0000000..ab00603 --- /dev/null +++ b/src/main/resources/com/mycmd/config/default.properties @@ -0,0 +1,16 @@ +# MyCMD default configuration +app.name=MyCMD +app.version=1.0 +app.author=Drive-for-Java +app.theme=dark +app.defaultDir=${user.home} + +# Command shell behavior +shell.history.enabled=true +shell.history.limit=50 +shell.showStartupMessage=true + +# GUI preferences +gui.font.size=14 +gui.theme.color=#00FFFF +gui.background.image=icons/mycmd-128.png diff --git a/src/main/resources/com/mycmd/gui/style.css b/src/main/resources/com/mycmd/gui/style.css new file mode 100644 index 0000000..3299d16 --- /dev/null +++ b/src/main/resources/com/mycmd/gui/style.css @@ -0,0 +1,32 @@ +.root { + -fx-background-color: linear-gradient(to bottom, #0c0c0c, #141414); + -fx-font-family: "JetBrains Mono", monospace; +} + +.output { + -fx-control-inner-background: #0e0e0e; + -fx-text-fill: #00ffe7; + -fx-font-size: 14px; + -fx-border-color: #222; + -fx-border-width: 1; + -fx-border-radius: 8; + -fx-background-radius: 8; +} + +.input { + -fx-control-inner-background: #111; + -fx-text-fill: #fff; + -fx-font-size: 14px; + -fx-border-color: #00ffe7; + -fx-border-radius: 8; + -fx-background-radius: 8; +} + +.prompt { + -fx-text-fill: #00ffe7; + -fx-font-weight: bold; +} + +.label, .text-area { + -fx-highlight-fill: #00ffe7; +} diff --git a/src/main/resources/com/mycmd/gui/terminal.fxml b/src/main/resources/com/mycmd/gui/terminal.fxml new file mode 100644 index 0000000..df83243 --- /dev/null +++ b/src/main/resources/com/mycmd/gui/terminal.fxml @@ -0,0 +1,25 @@ + + + + + + + +