Added improved CLI structure.

This commit is contained in:
Andrew Lalis 2023-05-17 12:40:01 +02:00
parent ba4a0c16d6
commit 00cfcda9b8
6 changed files with 160 additions and 43 deletions

3
.gitignore vendored
View File

@ -1 +1,2 @@
build/ build/
cli*

39
gymboard-cli/build.d Executable file
View File

@ -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;
}

View File

@ -1,6 +1,7 @@
import std.stdio; import std.stdio;
import cli; import cli;
import command;
import services; import services;
import consolecolors; import consolecolors;
@ -9,11 +10,12 @@ void main() {
ServiceManager serviceManager = new ServiceManager(); ServiceManager serviceManager = new ServiceManager();
CliHandler cliHandler = new CliHandler(); CliHandler cliHandler = new CliHandler();
cliHandler.register("service", new ServiceCommand(serviceManager)); cliHandler.register("service", new ServiceCommand(serviceManager));
cwriteln("Gymboard CLI: Type <cyan>help</cyan> for more information. Type <red>exit</red> to exit the CLI."); cwriteln("\n<blue>Gymboard CLI</blue>: <grey>Command-line interface for managing Gymboard services.</grey>");
while (!cliHandler.shouldExit) { cwriteln(" Type <cyan>help</cyan> for more information.\n Type <red>exit</red> to exit the CLI.\n");
while (!cliHandler.isExitRequested) {
cwrite("&gt; ".blue); cwrite("&gt; ".blue);
cliHandler.readAndHandleCommand(); cliHandler.readAndHandleCommand();
} }
serviceManager.stopAll(); serviceManager.stopAll();
cwriteln("Goodbye!".green); cwriteln("Goodbye!".blue);
} }

View File

@ -7,45 +7,7 @@ import std.typecons;
import consolecolors; import consolecolors;
interface CliCommand { import command.base;
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 services; import services;
class ServiceCommand : CliCommand { 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 * Validates that a service command contains as its second argument a valid
* service name. * service name.

View File

@ -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("<blue>Gymboard CLI Help</blue>: <grey>Information about how to use this program.</grey>");
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.";
}
}

View File

@ -0,0 +1,3 @@
module command;
public import command.base;