From 00cfcda9b8120694113e6377e4c4685f84e7447c Mon Sep 17 00:00:00 2001 From: Andrew Lalis Date: Wed, 17 May 2023 12:40:01 +0200 Subject: [PATCH] Added improved CLI structure. --- .gitignore | 3 +- gymboard-cli/build.d | 39 ++++++++++ gymboard-cli/source/app.d | 8 +- gymboard-cli/source/cli.d | 48 +++--------- gymboard-cli/source/command/base.d | 102 ++++++++++++++++++++++++++ gymboard-cli/source/command/package.d | 3 + 6 files changed, 160 insertions(+), 43 deletions(-) create mode 100755 gymboard-cli/build.d create mode 100644 gymboard-cli/source/command/base.d create mode 100644 gymboard-cli/source/command/package.d diff --git a/.gitignore b/.gitignore index d163863..c6ca6b4 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -build/ \ No newline at end of file +build/ +cli* \ No newline at end of file diff --git a/gymboard-cli/build.d b/gymboard-cli/build.d new file mode 100755 index 0000000..a669d4e --- /dev/null +++ b/gymboard-cli/build.d @@ -0,0 +1,39 @@ +#!/usr/bin/env rdmd +/** + * Run this script with `./build.d` to prepare the latest version of the CLI + * for use. It compiles the CLI application, and copies a "cli" executable to + * the project's root directory for use. + */ +module build; + +import std.stdio; +import std.process; +import std.file; +import std.path; + +int main() { + writeln("Building..."); + auto result = executeShell("dub build --build=release"); + if (result.status != 0) { + stderr.writefln!"Build failed: %d"(result.status); + stderr.writeln(result.output); + return result.status; + } + string finalPath = buildPath("..", "cli"); + if (exists(finalPath)) std.file.remove(finalPath); + version (Posix) { + string sourceExecutable = "gymboard-cli"; + } + version (Windows) { + string sourceExecutable = "gymboard-cli.exe"; + } + std.file.copy(sourceExecutable, finalPath); + version (Posix) { + result = executeShell("chmod +x " ~ finalPath); + if (result.status != 0) { + stderr.writefln!"Failed to enable executable permission: %d"(result.status); + return result.status; + } + } + return 0; +} diff --git a/gymboard-cli/source/app.d b/gymboard-cli/source/app.d index 7396248..3c9da4f 100644 --- a/gymboard-cli/source/app.d +++ b/gymboard-cli/source/app.d @@ -1,6 +1,7 @@ import std.stdio; import cli; +import command; import services; import consolecolors; @@ -9,11 +10,12 @@ void main() { ServiceManager serviceManager = new ServiceManager(); CliHandler cliHandler = new CliHandler(); cliHandler.register("service", new ServiceCommand(serviceManager)); - cwriteln("Gymboard CLI: Type help for more information. Type exit to exit the CLI."); - while (!cliHandler.shouldExit) { + cwriteln("\nGymboard CLI: Command-line interface for managing Gymboard services."); + cwriteln(" Type help for more information.\n Type exit to exit the CLI.\n"); + while (!cliHandler.isExitRequested) { cwrite("> ".blue); cliHandler.readAndHandleCommand(); } serviceManager.stopAll(); - cwriteln("Goodbye!".green); + cwriteln("Goodbye!".blue); } diff --git a/gymboard-cli/source/cli.d b/gymboard-cli/source/cli.d index 4da07fc..6903ebc 100644 --- a/gymboard-cli/source/cli.d +++ b/gymboard-cli/source/cli.d @@ -7,45 +7,7 @@ import std.typecons; import consolecolors; -interface CliCommand { - void handle(string[] args); -} - -class CliHandler { - private CliCommand[string] commands; - public bool shouldExit = false; - - public void register(string name, CliCommand command) { - this.commands[name] = command; - } - - public void readAndHandleCommand() { - string[] commandAndArgs = readln().strip().split!isWhite(); - if (commandAndArgs.length == 0) return; - string command = commandAndArgs[0].toLower(); - if (command == "help") { - showHelp(); - } else if (command == "exit") { - shouldExit = true; - } else if (command in commands) { - commands[command].handle(commandAndArgs.length > 1 ? commandAndArgs[1 .. $] : []); - } else { - cwritefln("Unknown command: %s".red, command.orange); - } - } -} - -void showHelp() { - writeln(q"HELP -Gymboard CLI: A tool for streamlining development. - -Commands: - -help Shows this message. -exit Exits the CLI, stopping any running services. -HELP"); -} - +import command.base; import services; class ServiceCommand : CliCommand { @@ -86,6 +48,14 @@ class ServiceCommand : CliCommand { } } + string name() const { + return "Service"; + } + + string description() const { + return "bleh"; + } + /** * Validates that a service command contains as its second argument a valid * service name. diff --git a/gymboard-cli/source/command/base.d b/gymboard-cli/source/command/base.d new file mode 100644 index 0000000..fa38304 --- /dev/null +++ b/gymboard-cli/source/command/base.d @@ -0,0 +1,102 @@ +module command.base; + +import consolecolors; +import std.stdio; +import std.string; +import std.uni; + +interface CliCommand { + void handle(string[] args); + string name() const; + string description() const; +} + +class CliHandler { + private CliCommand[string] commands; + private bool exitRequested = false; + + this() { + commands["help"] = new HelpCommand(this); + commands["exit"] = new ExitCommand(this); + } + + void register(string name, CliCommand command) { + this.commands[name.strip().toLower()] = command; + } + + void readAndHandleCommand() { + string[] commandAndArgs = readln().strip().split!isWhite(); + if (commandAndArgs.length == 0) return; + string command = commandAndArgs[0].toLower(); + if (command in commands) { + commands[command].handle(commandAndArgs.length > 1 ? commandAndArgs[1 .. $] : []); + } else { + cwritefln("Unknown command: %s".red, command.orange); + } + } + + void setExitRequested() { + this.exitRequested = true; + } + + bool isExitRequested() const { + return this.exitRequested; + } + + CliCommand[string] getCommands() { + return this.commands; + } +} + +class HelpCommand : CliCommand { + private CliHandler handler; + + this(CliHandler handler) { + this.handler = handler; + } + + void handle(string[] args) { + import std.algorithm; + + cwriteln("Gymboard CLI Help: Information about how to use this program."); + + string[] commandNames = this.handler.getCommands().keys; + sort(commandNames); + uint longestCommandNameLength = commandNames.map!(n => cast(uint) n.length).maxElement; + + foreach (name; commandNames) { + CliCommand command = this.handler.getCommands()[name]; + + string formattedName = cyan(leftJustify(name, longestCommandNameLength + 1, ' ')); + cwriteln(formattedName, command.description().grey); + } + } + + string name() const { + return "help"; + } + + string description() const { + return "Shows help information."; + } +} + +class ExitCommand : CliCommand { + private CliHandler handler; + + this(CliHandler handler) { + this.handler = handler; + } + + void handle(string[] args) { + this.handler.setExitRequested(); + } + + string name() const { + return "exit"; + } + + string description() const { + return "Exits the CLI, gracefully stopping any services or running jobs."; + } +} diff --git a/gymboard-cli/source/command/package.d b/gymboard-cli/source/command/package.d new file mode 100644 index 0000000..ef20fe8 --- /dev/null +++ b/gymboard-cli/source/command/package.d @@ -0,0 +1,3 @@ +module command; + +public import command.base;