Latest commit

History

45 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

JModule

JModule is a simple, lightweight Java library written to help people easily write clean, organized, and highly customizable command-line applications. JModule works by running a console client containing multiple modules, each containing their own commands. The application user can switch between modules to access their commands and can view a customizable help page for each module. This design allows the developer to organize commands by functionality, leading to an cleaner flow and easier overall user experience.

Features

  • Commands can be organized into modules
  • A range of customizations that can be accessed through simple functions
  • Tab completion
  • History toggling with ↑ and ↓
  • Insert mode using ← and →
  • Chained commands using ;
  • Options (such as -v, --verbose, etc.)
  • Indefinite/Bounded number of required parameters for commands.
  • JModule implements its own keylistener (not Java.awt), allowing for it to detect individual bytes passed through the command line.
  • Fully-fledged example application, documentation, and usage guide.

As of the current version v1.3.1, JModule is optimized to run on *nix systems and does not yet support the windows command prompt. However, in future versions I plan to add Windows compatiblity.

Getting started

Setting up

In order to use JModule, download the latest version of JModule.jar from the releases page and add the jar to your preferred classpath. JModule's API is contained in two packages: com.jmodule.def (defining commands and parameters) and com.jmodule.exec (organization and execution of commands). The following classes are essential to use JModule

importcom.jmodule.def.Command;
importcom.jmodule.def.CommandLogic;
importcom.jmodule.exec.ConsoleClient;
importcom.jmodule.exec.Module;

The remaining classes are optional and add deeper functionality into the JModule API

importcom.jmodule.def.BoundedCommand;
importcom.jmodule.def.IndefiniteCommand; importcom.jmodule.def.Option;

Example App

JModule includes an Example Application, which is a simple arithmetic program that utilizes most of the functionality in JModule's API. To get a feel for the flow of a JModule application, make sure you have JModule in your classpath, clone or download the file, compile the java file using ~$ javac ExampleApp.java and run it with ~$ java ExampleApp. The code is full of comments to help you understand JModule's functionality. Feel free to modify or use the example code as a reference for writing your own JModule applications.

Writing a JModule application

Commands

Each JModule command runs based on its own command logic, an abstract class that must override the method execute(String[] args) to define the command's execution. A command is instantiated with a name, a description and logic. The simplest command will just have logic and no parameters

CommandhelloCmd = newCommand("hello", "Says hello", newCommandLogic() {
@Overridepublicvoidexecute(String[] args) {
System.out.println("Hello world!");
}
});

The command's name (converted to lowercase and with spaces removed) acts as its default command-line reference. We can also add alternate references to any command. This can be done with the following method

helloCmd.addReference("greet");

Our hello command can now be called from the console by either typing hello or greet.

~$ hello
Hello world!
~$ greet
Hello world!

Parameters

We can also add some parameters to our command by defining them as a String[] in the CommandLogic constructor.

CommandhelloCmd = newCommand("hello", "Says hello to the user", newCommandLogic(
newString[] {
"first name",
"last name"
}) {
@Overridepublicvoidexecute(String[] args) {
System.out.println("Hello, " + args[0] + " " + args[1] + "!");
}
});
helloCmd.addReference("greet");

Parameters in regular command are required by user input for the command to run. When commands are executed with an incorrect number of parameters or the module help page is accessed, the command's usage information will print to the console. Since we added a few parameters and an alternate reference to our hello command, the generated usage statement will be as follows.

Usage: ~$ hello <first name> <last name>
OR greet ~

If we want to change or add to the default usage statement, the method yourCommand.resetUsage(String reset) can be used to replace the default usage statement with your own statement, and the method yourCommand.appendUsage(String append) can be used to add a new line to the existing usage statement. To see examples of these implementations, consult the Example App.

Options

Sometimes we may want to give the user the choice to change the functionality of a command without having to worry about creating new commands or parameters. This can be done through the use of options. Options are arguments passed to the command that do not count as parameters and can be used anywhere in the command's parameters. They are, by nature, optional. Each option is initialized with a one-character flag, denoted by a single dash (for example, -v) and a description that shows up in the enclosing command's usage statement. Additionally, just like Commands, Options can have any number of references added to it. References can be multiple characters, are typically full words or word fragments, and are denoted by a double dash (for example, --ver or --verbose). An option can have any number of references added to it, and references are reccommended but not required. Options are added to CommandLogic with the function addOption(Option o) and their behavior can be defined within CommandLogic.execute(String[] args) by using the boolean if (onOption(char flag)) { /*logic here */ } that will return true if the option exists in the command and is called by the user. As an example, let's write a 'goodbye' command with a few options.

CommandgoodbyeCmd = newCommand("goodbye", "Says goodbye", newCommandLogic() {
@Overridepublicvoidexecute(String[] args) {
if (onOption('p')) {
System.out.print("Farewell, ");
} else {
System.out.print("Goodbye, ");
}
if (onOption('d')) {
System.out.print("cruel ");
}
System.out.print("world!\n\n");
}
}.addOption(newOption('p', "Makes the message polite")
.addReference("polite"))
.addOption(newOption('d', "Makes the message depressing")
.addReference("dep")
.addReference("depressing"))
);

Our 'goodbye' command can be tested from the command line as so:

~$ goodbye
Goodbye, world!
~$ goodbye -p
Farewell, world!
~$ goodbye --polite
Farewell, world!
~$ goodbye --depressing
Goodbye, cruel world!
~$ goodbye -d -p
Farewell, cruel world!

JModule also supports calling multiple options quickly by combining flags as such

~$ goodbye -dp
Farewell, cruel world!

Just like parameters, options added to commands will show up in the command's usage statement.

'goodbye'
Says goodbye
Usage: ~$ goodbye
Options:
-p, --polite: Makes the message polite
-d, --dep, --depressing: Makes the message depressing

Indefinite and Bounded Commands

Sometimes we may want our commands to have an undefined number or a range of numbers of possible parameters. In order to do this, we can either use an Indefinite Command, Bounded Command with an open range, or a Bounded Command with a closed range. These commands should have their parameters defined in their logic's constructor, but they will have no effect on the execution of the command; they will only affect the command's usage statement.

Indefinite Commands

Indefinite commands may have any number of parameters passed to it by the user. An example implementation of this would be a command to list names

CommandlistCmd = newIndefiniteCommand("list", "Lists given names, separated by commas", newCommandLogic(
newString[] {
"names..."
}) {
@Overridepublicvoidexecute(String[] args) {
for (inti = 0; i < args.length; i++) {
System.out.print(args[i]);
if (i != args.length - 1) {
System.out.print(", ");
}
}
}
});

Bounded Commands with open parameters

Bounded commands with open parameters are instantiated the same way as Indefinite or regular Commands, but a minimum and maximum number of parameters is specified. To have a bounded command with open parameters, specify only the minimum at the end of the BoundedCommand constructor. For example, a function to list the names of people all with the same last name, where the last name is taken as the first parameter, would have a minimum of two parameters and could look something like this.

CommandfamCmd = newBoundedCommand("famlist", "Lists the full names of family members who all have the same last name", newCommandLogic(
newString[] {
"family name",
"first names..."
}) {
@Overridepublicvoidexecute(String[] args) {
StringfamilyName = args[0];
for (inti = 1; i < args.length; i++) {
System.out.print(args[i] + " " + familyName);
if (i != args.length - 1) {
System.out.print(", ");
}
}
}
}, 2); // set minimum number of parameters to 2

Bounded Commands with closed parameters

Bounded commands can also have closed parameters, meaning both a minimum and maximum number of parameters is specified at the end of the constructor. An example would be a function that multiplies up to 4 numbers.

CommandmultCmd = newBoundedCommand("multiply", "Multiplies up to 4 numbers", newCommandLogic(
newString[] {
"factors..."
}) {
@Overridepublicvoidexecute(String[] args) {
intproduct = 0;
try {
for (Stringstr : args) {
intfactor = Integer.parseInt(str);
product *= factor;
}
System.out.print("Product: " + product);
...
}
}, 0, 4); // set minimum number of parameters to 0 and maximum to 4

Creating a custom control flow

Organizing Commands into Modules

Useful command-line applications will have a variety of possible commands. Let's assume that we've created a few arithmetic commands, a 'quizme' command taking no parameters that asks you simple math questions, and an 'info' command that reports how well you've done on past quizzes. The implementations for these commands can all be found in ExampleApp.java. To ease the user experience, we can organize commands with similar functionality into their own modules. In this instance, let's put the arithmetic commands in one module called math and the 'info' and 'quizme' commands in another module called quiz.

Modulemath = newModule("math");
math.addCommand(addCmd);
math.addCommand(subCmd);
math.addCommand(multCmd);
Modulequiz = newModule("quiz");
quiz.addCommand(quizCmd);
quiz.addCommand(infoCmd);

Each command has a name and description defined on instantiation, and a usage statement defined by its parameters and/or user customization. These statements are all helpful, and can be viewed by using the help command in the console.

~ math $ help
MATH -- POSSIBLE COMMANDS
'add'
Adds 2 numbers together
Usage: ~$ add <first number> <second number>
'subtract'
Subtracts 2 numbers
Usage: ~$ subtract <first number> <second number>
OR sub ~
'multiply'
Multiplies 2 or more numbers
Usage: ~$ multiply <First number> <Factors...>
OR mult ~
OR mul ~
'help'
Displays the help page for the current module.
Usage: ~$ help
Type the name of another module to switch to that module:
- 'quiz'
Type 'exit' at any time to exit the program

The help and exit commands are defined by default and do not need to be defined in your app. Similarly to command usage staments, module help pages are generated as a standard help page of the style shown above, and can be edited with yourModule.resetHelpPage(String reset) and yourModule.appendHelpPage(String append).

Organizing modules into a Console Client

We can organize our modules into a client by specifying the name of the app and the home module in the constructor, add other modules with addModule(), and run the console app with runConsole().

ConsoleClientclient = newConsoleClient("ExampleEducationApp", math);
client.addModule(quiz);
client.runConsole();

JModule also supports the ability to write non-modular apps. To do this, just throw all your commands into a single module and set it up as the client's home module. The name of the module will not show up in the prompt and the help page will show the app name rather than the home module name.

Customizable CLI prompt

JModule supports the ability to customize the prompt that will be printed to the CLI for each command. As of the current version, there are four possible prompt customization functions.

  • Prompt display name
    By default, the prompt will begin with the specified app name, with its spaces removed. However, we can change this if we'd like. For example, we can change the app name of our example application to shorten the name and include the version.

    client.setPromptDisplayName("ExampleApp-v1.0");

    Our prompt in its home module will now print as ExampleApp-v1.0: math$.
    This is useful to add version information that is not specified in the app name, or to shorten the name that is printed to the CLI.

  • Separators
    By default, JModule apps separate the app name from the module name with ": ", a colon followed by a space. We can change this from our app. For example, let's change the separator to a slash with no space in our app.

    client.setModuleSeparator("/");

    Our prompt in its home module will now display as ExampleApp-v1.0/math$.
    We can also change the prompt/user input separator. By default, JModule sets this to "$". We can change this in our example application.

    client.setPromptSeparator(">");

    Our prompt in its home module will now print as ExampleApp-v1.0/math> . The app will automatically print a space after the prompt to separate it from user input.

  • History index display
    JModule apps with history logging enabled can also display the history index to the CLI, much like the bash command prompt. Enable this by using

    client.setHistoryIndexDisplayEnabled(true);

    This will print the number of previous commands, preceded by a space, after the appname and module and before the prompt separator. Adding this line to our code, out prompt will now print as ExampleApp-v1.0/math 0>
    Note: in order to enable this function, history logging must be enabled (see below).

Further customizations

JModule apps can be customized to include a number of functionalities that could be useful to the user of your application. They are all set to false by default, but can be enabled with simple functions.

  • History Logging
    client.enableHistoryLogging(true);
    Enable history logging to allow the user to cycle through their previous commands using the ↑ and ↓ arrows. Whatever characters they have typed before toggling back through their history will be preserved if they toggle back to their current location. Enabling this funciton also allows you to enable history index display on the prompt.
  • Tab Completion
    client.enableTabCompletion(true);
    Enabling tab completion allows the user to use the tab key to cycle through possible commands in their current module that start with what they already have typed on the command line. If the user hasn't typed anything, tab will cycle through all of the possible commands in the current module.
  • Alerts
    client.enableAlerts(true);
    Enabling alerts allows the app to trigger the system's default alert (typically a sound such as this one). These alerts are triggered any time the user uses a special key that is unable to have any effect on the CLI. For example, an alert could trigger when the user presses delete with no characters typed in, or using tab toggling when no commands match what they've currently typed.

To take an in-depth look at the fully implemented example application, ExampleApp.java is outfitted with helpful comments and defines all its logic classes in the same file for readability.

Planned future updates

  • More flexible parameter options
    • Separate class for parameters
    • Mandatory and optional toggles for parameters
  • Ability for the developer to implement custom keylisteners using the JModule API
  • Ability to hide input for certain commands, such as passwords
  • Windows compatibility
  • JUnit tests

Documentation

The source code Javadoc for JModule (as of version 1.3.0) can be found here. I update the Javadoc with every major version release.

Older versions

Contact

Thanks for checking out JModule! Feel free to contact me at pierce@kelaita.com with any questions or suggestions, or if you want to contribute.

About

Java CLI development library that lets you organize commands by functionality. Supports tab completion, history toggling, chained commands, and other useful features

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

45 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

JModule

JModule is a simple, lightweight Java library written to help people easily write clean, organized, and highly customizable command-line applications. JModule works by running a console client containing multiple modules, each containing their own commands. The application user can switch between modules to access their commands and can view a customizable help page for each module. This design allows the developer to organize commands by functionality, leading to an cleaner flow and easier overall user experience.

Features

  • Commands can be organized into modules
  • A range of customizations that can be accessed through simple functions
  • Tab completion
  • History toggling with ↑ and ↓
  • Insert mode using ← and →
  • Chained commands using ;
  • Options (such as -v, --verbose, etc.)
  • Indefinite/Bounded number of required parameters for commands.
  • JModule implements its own keylistener (not Java.awt), allowing for it to detect individual bytes passed through the command line.
  • Fully-fledged example application, documentation, and usage guide.

As of the current version v1.3.1, JModule is optimized to run on *nix systems and does not yet support the windows command prompt. However, in future versions I plan to add Windows compatiblity.

Getting started

Setting up

In order to use JModule, download the latest version of JModule.jar from the releases page and add the jar to your preferred classpath. JModule's API is contained in two packages: com.jmodule.def (defining commands and parameters) and com.jmodule.exec (organization and execution of commands). The following classes are essential to use JModule

importcom.jmodule.def.Command;
importcom.jmodule.def.CommandLogic;
importcom.jmodule.exec.ConsoleClient;
importcom.jmodule.exec.Module;

The remaining classes are optional and add deeper functionality into the JModule API

importcom.jmodule.def.BoundedCommand;
importcom.jmodule.def.IndefiniteCommand; importcom.jmodule.def.Option;

Example App

JModule includes an Example Application, which is a simple arithmetic program that utilizes most of the functionality in JModule's API. To get a feel for the flow of a JModule application, make sure you have JModule in your classpath, clone or download the file, compile the java file using ~$ javac ExampleApp.java and run it with ~$ java ExampleApp. The code is full of comments to help you understand JModule's functionality. Feel free to modify or use the example code as a reference for writing your own JModule applications.

Writing a JModule application

Commands

Each JModule command runs based on its own command logic, an abstract class that must override the method execute(String[] args) to define the command's execution. A command is instantiated with a name, a description and logic. The simplest command will just have logic and no parameters

CommandhelloCmd = newCommand("hello", "Says hello", newCommandLogic() {
@Overridepublicvoidexecute(String[] args) {
System.out.println("Hello world!");
}
});

The command's name (converted to lowercase and with spaces removed) acts as its default command-line reference. We can also add alternate references to any command. This can be done with the following method

helloCmd.addReference("greet");

Our hello command can now be called from the console by either typing hello or greet.

~$ hello
Hello world!
~$ greet
Hello world!

Parameters

We can also add some parameters to our command by defining them as a String[] in the CommandLogic constructor.

CommandhelloCmd = newCommand("hello", "Says hello to the user", newCommandLogic(
newString[] {
"first name",
"last name"
}) {
@Overridepublicvoidexecute(String[] args) {
System.out.println("Hello, " + args[0] + " " + args[1] + "!");
}
});
helloCmd.addReference("greet");

Parameters in regular command are required by user input for the command to run. When commands are executed with an incorrect number of parameters or the module help page is accessed, the command's usage information will print to the console. Since we added a few parameters and an alternate reference to our hello command, the generated usage statement will be as follows.

Usage: ~$ hello <first name> <last name>
OR greet ~

If we want to change or add to the default usage statement, the method yourCommand.resetUsage(String reset) can be used to replace the default usage statement with your own statement, and the method yourCommand.appendUsage(String append) can be used to add a new line to the existing usage statement. To see examples of these implementations, consult the Example App.

Options

Sometimes we may want to give the user the choice to change the functionality of a command without having to worry about creating new commands or parameters. This can be done through the use of options. Options are arguments passed to the command that do not count as parameters and can be used anywhere in the command's parameters. They are, by nature, optional. Each option is initialized with a one-character flag, denoted by a single dash (for example, -v) and a description that shows up in the enclosing command's usage statement. Additionally, just like Commands, Options can have any number of references added to it. References can be multiple characters, are typically full words or word fragments, and are denoted by a double dash (for example, --ver or --verbose). An option can have any number of references added to it, and references are reccommended but not required. Options are added to CommandLogic with the function addOption(Option o) and their behavior can be defined within CommandLogic.execute(String[] args) by using the boolean if (onOption(char flag)) { /*logic here */ } that will return true if the option exists in the command and is called by the user. As an example, let's write a 'goodbye' command with a few options.

CommandgoodbyeCmd = newCommand("goodbye", "Says goodbye", newCommandLogic() {
@Overridepublicvoidexecute(String[] args) {
if (onOption('p')) {
System.out.print("Farewell, ");
} else {
System.out.print("Goodbye, ");
}
if (onOption('d')) {
System.out.print("cruel ");
}
System.out.print("world!\n\n");
}
}.addOption(newOption('p', "Makes the message polite")
.addReference("polite"))
.addOption(newOption('d', "Makes the message depressing")
.addReference("dep")
.addReference("depressing"))
);

Our 'goodbye' command can be tested from the command line as so:

~$ goodbye
Goodbye, world!
~$ goodbye -p
Farewell, world!
~$ goodbye --polite
Farewell, world!
~$ goodbye --depressing
Goodbye, cruel world!
~$ goodbye -d -p
Farewell, cruel world!

JModule also supports calling multiple options quickly by combining flags as such

~$ goodbye -dp
Farewell, cruel world!

Just like parameters, options added to commands will show up in the command's usage statement.

'goodbye'
Says goodbye
Usage: ~$ goodbye
Options:
-p, --polite: Makes the message polite
-d, --dep, --depressing: Makes the message depressing

Indefinite and Bounded Commands

Sometimes we may want our commands to have an undefined number or a range of numbers of possible parameters. In order to do this, we can either use an Indefinite Command, Bounded Command with an open range, or a Bounded Command with a closed range. These commands should have their parameters defined in their logic's constructor, but they will have no effect on the execution of the command; they will only affect the command's usage statement.

Indefinite Commands

Indefinite commands may have any number of parameters passed to it by the user. An example implementation of this would be a command to list names

CommandlistCmd = newIndefiniteCommand("list", "Lists given names, separated by commas", newCommandLogic(
newString[] {
"names..."
}) {
@Overridepublicvoidexecute(String[] args) {
for (inti = 0; i < args.length; i++) {
System.out.print(args[i]);
if (i != args.length - 1) {
System.out.print(", ");
}
}
}
});

Bounded Commands with open parameters

Bounded commands with open parameters are instantiated the same way as Indefinite or regular Commands, but a minimum and maximum number of parameters is specified. To have a bounded command with open parameters, specify only the minimum at the end of the BoundedCommand constructor. For example, a function to list the names of people all with the same last name, where the last name is taken as the first parameter, would have a minimum of two parameters and could look something like this.

CommandfamCmd = newBoundedCommand("famlist", "Lists the full names of family members who all have the same last name", newCommandLogic(
newString[] {
"family name",
"first names..."
}) {
@Overridepublicvoidexecute(String[] args) {
StringfamilyName = args[0];
for (inti = 1; i < args.length; i++) {
System.out.print(args[i] + " " + familyName);
if (i != args.length - 1) {
System.out.print(", ");
}
}
}
}, 2); // set minimum number of parameters to 2

Bounded Commands with closed parameters

Bounded commands can also have closed parameters, meaning both a minimum and maximum number of parameters is specified at the end of the constructor. An example would be a function that multiplies up to 4 numbers.

CommandmultCmd = newBoundedCommand("multiply", "Multiplies up to 4 numbers", newCommandLogic(
newString[] {
"factors..."
}) {
@Overridepublicvoidexecute(String[] args) {
intproduct = 0;
try {
for (Stringstr : args) {
intfactor = Integer.parseInt(str);
product *= factor;
}
System.out.print("Product: " + product);
...
}
}, 0, 4); // set minimum number of parameters to 0 and maximum to 4

Creating a custom control flow

Organizing Commands into Modules

Useful command-line applications will have a variety of possible commands. Let's assume that we've created a few arithmetic commands, a 'quizme' command taking no parameters that asks you simple math questions, and an 'info' command that reports how well you've done on past quizzes. The implementations for these commands can all be found in ExampleApp.java. To ease the user experience, we can organize commands with similar functionality into their own modules. In this instance, let's put the arithmetic commands in one module called math and the 'info' and 'quizme' commands in another module called quiz.

Modulemath = newModule("math");
math.addCommand(addCmd);
math.addCommand(subCmd);
math.addCommand(multCmd);
Modulequiz = newModule("quiz");
quiz.addCommand(quizCmd);
quiz.addCommand(infoCmd);

Each command has a name and description defined on instantiation, and a usage statement defined by its parameters and/or user customization. These statements are all helpful, and can be viewed by using the help command in the console.

~ math $ help
MATH -- POSSIBLE COMMANDS
'add'
Adds 2 numbers together
Usage: ~$ add <first number> <second number>
'subtract'
Subtracts 2 numbers
Usage: ~$ subtract <first number> <second number>
OR sub ~
'multiply'
Multiplies 2 or more numbers
Usage: ~$ multiply <First number> <Factors...>
OR mult ~
OR mul ~
'help'
Displays the help page for the current module.
Usage: ~$ help
Type the name of another module to switch to that module:
- 'quiz'
Type 'exit' at any time to exit the program

The help and exit commands are defined by default and do not need to be defined in your app. Similarly to command usage staments, module help pages are generated as a standard help page of the style shown above, and can be edited with yourModule.resetHelpPage(String reset) and yourModule.appendHelpPage(String append).

Organizing modules into a Console Client

We can organize our modules into a client by specifying the name of the app and the home module in the constructor, add other modules with addModule(), and run the console app with runConsole().

ConsoleClientclient = newConsoleClient("ExampleEducationApp", math);
client.addModule(quiz);
client.runConsole();

JModule also supports the ability to write non-modular apps. To do this, just throw all your commands into a single module and set it up as the client's home module. The name of the module will not show up in the prompt and the help page will show the app name rather than the home module name.

Customizable CLI prompt

JModule supports the ability to customize the prompt that will be printed to the CLI for each command. As of the current version, there are four possible prompt customization functions.

  • Prompt display name
    By default, the prompt will begin with the specified app name, with its spaces removed. However, we can change this if we'd like. For example, we can change the app name of our example application to shorten the name and include the version.

    client.setPromptDisplayName("ExampleApp-v1.0");

    Our prompt in its home module will now print as ExampleApp-v1.0: math$.
    This is useful to add version information that is not specified in the app name, or to shorten the name that is printed to the CLI.

  • Separators
    By default, JModule apps separate the app name from the module name with ": ", a colon followed by a space. We can change this from our app. For example, let's change the separator to a slash with no space in our app.

    client.setModuleSeparator("/");

    Our prompt in its home module will now display as ExampleApp-v1.0/math$.
    We can also change the prompt/user input separator. By default, JModule sets this to "$". We can change this in our example application.

    client.setPromptSeparator(">");

    Our prompt in its home module will now print as ExampleApp-v1.0/math> . The app will automatically print a space after the prompt to separate it from user input.

  • History index display
    JModule apps with history logging enabled can also display the history index to the CLI, much like the bash command prompt. Enable this by using

    client.setHistoryIndexDisplayEnabled(true);

    This will print the number of previous commands, preceded by a space, after the appname and module and before the prompt separator. Adding this line to our code, out prompt will now print as ExampleApp-v1.0/math 0>
    Note: in order to enable this function, history logging must be enabled (see below).

Further customizations

JModule apps can be customized to include a number of functionalities that could be useful to the user of your application. They are all set to false by default, but can be enabled with simple functions.

  • History Logging
    client.enableHistoryLogging(true);
    Enable history logging to allow the user to cycle through their previous commands using the ↑ and ↓ arrows. Whatever characters they have typed before toggling back through their history will be preserved if they toggle back to their current location. Enabling this funciton also allows you to enable history index display on the prompt.
  • Tab Completion
    client.enableTabCompletion(true);
    Enabling tab completion allows the user to use the tab key to cycle through possible commands in their current module that start with what they already have typed on the command line. If the user hasn't typed anything, tab will cycle through all of the possible commands in the current module.
  • Alerts
    client.enableAlerts(true);
    Enabling alerts allows the app to trigger the system's default alert (typically a sound such as this one). These alerts are triggered any time the user uses a special key that is unable to have any effect on the CLI. For example, an alert could trigger when the user presses delete with no characters typed in, or using tab toggling when no commands match what they've currently typed.

To take an in-depth look at the fully implemented example application, ExampleApp.java is outfitted with helpful comments and defines all its logic classes in the same file for readability.

Planned future updates

  • More flexible parameter options
    • Separate class for parameters
    • Mandatory and optional toggles for parameters
  • Ability for the developer to implement custom keylisteners using the JModule API
  • Ability to hide input for certain commands, such as passwords
  • Windows compatibility
  • JUnit tests

Documentation

The source code Javadoc for JModule (as of version 1.3.0) can be found here. I update the Javadoc with every major version release.

Older versions

Contact

Thanks for checking out JModule! Feel free to contact me at pierce@kelaita.com with any questions or suggestions, or if you want to contribute.

About

Java CLI development library that lets you organize commands by functionality. Supports tab completion, history toggling, chained commands, and other useful features

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

45 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

JModule

JModule is a simple, lightweight Java library written to help people easily write clean, organized, and highly customizable command-line applications. JModule works by running a console client containing multiple modules, each containing their own commands. The application user can switch between modules to access their commands and can view a customizable help page for each module. This design allows the developer to organize commands by functionality, leading to an cleaner flow and easier overall user experience.

Features

  • Commands can be organized into modules
  • A range of customizations that can be accessed through simple functions
  • Tab completion
  • History toggling with ↑ and ↓
  • Insert mode using ← and →
  • Chained commands using ;
  • Options (such as -v, --verbose, etc.)
  • Indefinite/Bounded number of required parameters for commands.
  • JModule implements its own keylistener (not Java.awt), allowing for it to detect individual bytes passed through the command line.
  • Fully-fledged example application, documentation, and usage guide.

As of the current version v1.3.1, JModule is optimized to run on *nix systems and does not yet support the windows command prompt. However, in future versions I plan to add Windows compatiblity.

Getting started

Setting up

In order to use JModule, download the latest version of JModule.jar from the releases page and add the jar to your preferred classpath. JModule's API is contained in two packages: com.jmodule.def (defining commands and parameters) and com.jmodule.exec (organization and execution of commands). The following classes are essential to use JModule

importcom.jmodule.def.Command;
importcom.jmodule.def.CommandLogic;
importcom.jmodule.exec.ConsoleClient;
importcom.jmodule.exec.Module;

The remaining classes are optional and add deeper functionality into the JModule API

importcom.jmodule.def.BoundedCommand;
importcom.jmodule.def.IndefiniteCommand; importcom.jmodule.def.Option;

Example App

JModule includes an Example Application, which is a simple arithmetic program that utilizes most of the functionality in JModule's API. To get a feel for the flow of a JModule application, make sure you have JModule in your classpath, clone or download the file, compile the java file using ~$ javac ExampleApp.java and run it with ~$ java ExampleApp. The code is full of comments to help you understand JModule's functionality. Feel free to modify or use the example code as a reference for writing your own JModule applications.

Writing a JModule application

Commands

Each JModule command runs based on its own command logic, an abstract class that must override the method execute(String[] args) to define the command's execution. A command is instantiated with a name, a description and logic. The simplest command will just have logic and no parameters

CommandhelloCmd = newCommand("hello", "Says hello", newCommandLogic() {
@Overridepublicvoidexecute(String[] args) {
System.out.println("Hello world!");
}
});

The command's name (converted to lowercase and with spaces removed) acts as its default command-line reference. We can also add alternate references to any command. This can be done with the following method

helloCmd.addReference("greet");

Our hello command can now be called from the console by either typing hello or greet.

~$ hello
Hello world!
~$ greet
Hello world!

Parameters

We can also add some parameters to our command by defining them as a String[] in the CommandLogic constructor.

CommandhelloCmd = newCommand("hello", "Says hello to the user", newCommandLogic(
newString[] {
"first name",
"last name"
}) {
@Overridepublicvoidexecute(String[] args) {
System.out.println("Hello, " + args[0] + " " + args[1] + "!");
}
});
helloCmd.addReference("greet");

Parameters in regular command are required by user input for the command to run. When commands are executed with an incorrect number of parameters or the module help page is accessed, the command's usage information will print to the console. Since we added a few parameters and an alternate reference to our hello command, the generated usage statement will be as follows.

Usage: ~$ hello <first name> <last name>
OR greet ~

If we want to change or add to the default usage statement, the method yourCommand.resetUsage(String reset) can be used to replace the default usage statement with your own statement, and the method yourCommand.appendUsage(String append) can be used to add a new line to the existing usage statement. To see examples of these implementations, consult the Example App.

Options

Sometimes we may want to give the user the choice to change the functionality of a command without having to worry about creating new commands or parameters. This can be done through the use of options. Options are arguments passed to the command that do not count as parameters and can be used anywhere in the command's parameters. They are, by nature, optional. Each option is initialized with a one-character flag, denoted by a single dash (for example, -v) and a description that shows up in the enclosing command's usage statement. Additionally, just like Commands, Options can have any number of references added to it. References can be multiple characters, are typically full words or word fragments, and are denoted by a double dash (for example, --ver or --verbose). An option can have any number of references added to it, and references are reccommended but not required. Options are added to CommandLogic with the function addOption(Option o) and their behavior can be defined within CommandLogic.execute(String[] args) by using the boolean if (onOption(char flag)) { /*logic here */ } that will return true if the option exists in the command and is called by the user. As an example, let's write a 'goodbye' command with a few options.

CommandgoodbyeCmd = newCommand("goodbye", "Says goodbye", newCommandLogic() {
@Overridepublicvoidexecute(String[] args) {
if (onOption('p')) {
System.out.print("Farewell, ");
} else {
System.out.print("Goodbye, ");
}
if (onOption('d')) {
System.out.print("cruel ");
}
System.out.print("world!\n\n");
}
}.addOption(newOption('p', "Makes the message polite")
.addReference("polite"))
.addOption(newOption('d', "Makes the message depressing")
.addReference("dep")
.addReference("depressing"))
);

Our 'goodbye' command can be tested from the command line as so:

~$ goodbye
Goodbye, world!
~$ goodbye -p
Farewell, world!
~$ goodbye --polite
Farewell, world!
~$ goodbye --depressing
Goodbye, cruel world!
~$ goodbye -d -p
Farewell, cruel world!

JModule also supports calling multiple options quickly by combining flags as such

~$ goodbye -dp
Farewell, cruel world!

Just like parameters, options added to commands will show up in the command's usage statement.

'goodbye'
Says goodbye
Usage: ~$ goodbye
Options:
-p, --polite: Makes the message polite
-d, --dep, --depressing: Makes the message depressing

Indefinite and Bounded Commands

Sometimes we may want our commands to have an undefined number or a range of numbers of possible parameters. In order to do this, we can either use an Indefinite Command, Bounded Command with an open range, or a Bounded Command with a closed range. These commands should have their parameters defined in their logic's constructor, but they will have no effect on the execution of the command; they will only affect the command's usage statement.

Indefinite Commands

Indefinite commands may have any number of parameters passed to it by the user. An example implementation of this would be a command to list names

CommandlistCmd = newIndefiniteCommand("list", "Lists given names, separated by commas", newCommandLogic(
newString[] {
"names..."
}) {
@Overridepublicvoidexecute(String[] args) {
for (inti = 0; i < args.length; i++) {
System.out.print(args[i]);
if (i != args.length - 1) {
System.out.print(", ");
}
}
}
});

Bounded Commands with open parameters

Bounded commands with open parameters are instantiated the same way as Indefinite or regular Commands, but a minimum and maximum number of parameters is specified. To have a bounded command with open parameters, specify only the minimum at the end of the BoundedCommand constructor. For example, a function to list the names of people all with the same last name, where the last name is taken as the first parameter, would have a minimum of two parameters and could look something like this.

CommandfamCmd = newBoundedCommand("famlist", "Lists the full names of family members who all have the same last name", newCommandLogic(
newString[] {
"family name",
"first names..."
}) {
@Overridepublicvoidexecute(String[] args) {
StringfamilyName = args[0];
for (inti = 1; i < args.length; i++) {
System.out.print(args[i] + " " + familyName);
if (i != args.length - 1) {
System.out.print(", ");
}
}
}
}, 2); // set minimum number of parameters to 2

Bounded Commands with closed parameters

Bounded commands can also have closed parameters, meaning both a minimum and maximum number of parameters is specified at the end of the constructor. An example would be a function that multiplies up to 4 numbers.

CommandmultCmd = newBoundedCommand("multiply", "Multiplies up to 4 numbers", newCommandLogic(
newString[] {
"factors..."
}) {
@Overridepublicvoidexecute(String[] args) {
intproduct = 0;
try {
for (Stringstr : args) {
intfactor = Integer.parseInt(str);
product *= factor;
}
System.out.print("Product: " + product);
...
}
}, 0, 4); // set minimum number of parameters to 0 and maximum to 4

Creating a custom control flow

Organizing Commands into Modules

Useful command-line applications will have a variety of possible commands. Let's assume that we've created a few arithmetic commands, a 'quizme' command taking no parameters that asks you simple math questions, and an 'info' command that reports how well you've done on past quizzes. The implementations for these commands can all be found in ExampleApp.java. To ease the user experience, we can organize commands with similar functionality into their own modules. In this instance, let's put the arithmetic commands in one module called math and the 'info' and 'quizme' commands in another module called quiz.

Modulemath = newModule("math");
math.addCommand(addCmd);
math.addCommand(subCmd);
math.addCommand(multCmd);
Modulequiz = newModule("quiz");
quiz.addCommand(quizCmd);
quiz.addCommand(infoCmd);

Each command has a name and description defined on instantiation, and a usage statement defined by its parameters and/or user customization. These statements are all helpful, and can be viewed by using the help command in the console.

~ math $ help
MATH -- POSSIBLE COMMANDS
'add'
Adds 2 numbers together
Usage: ~$ add <first number> <second number>
'subtract'
Subtracts 2 numbers
Usage: ~$ subtract <first number> <second number>
OR sub ~
'multiply'
Multiplies 2 or more numbers
Usage: ~$ multiply <First number> <Factors...>
OR mult ~
OR mul ~
'help'
Displays the help page for the current module.
Usage: ~$ help
Type the name of another module to switch to that module:
- 'quiz'
Type 'exit' at any time to exit the program

The help and exit commands are defined by default and do not need to be defined in your app. Similarly to command usage staments, module help pages are generated as a standard help page of the style shown above, and can be edited with yourModule.resetHelpPage(String reset) and yourModule.appendHelpPage(String append).

Organizing modules into a Console Client

We can organize our modules into a client by specifying the name of the app and the home module in the constructor, add other modules with addModule(), and run the console app with runConsole().

ConsoleClientclient = newConsoleClient("ExampleEducationApp", math);
client.addModule(quiz);
client.runConsole();

JModule also supports the ability to write non-modular apps. To do this, just throw all your commands into a single module and set it up as the client's home module. The name of the module will not show up in the prompt and the help page will show the app name rather than the home module name.

Customizable CLI prompt

JModule supports the ability to customize the prompt that will be printed to the CLI for each command. As of the current version, there are four possible prompt customization functions.

  • Prompt display name
    By default, the prompt will begin with the specified app name, with its spaces removed. However, we can change this if we'd like. For example, we can change the app name of our example application to shorten the name and include the version.

    client.setPromptDisplayName("ExampleApp-v1.0");

    Our prompt in its home module will now print as ExampleApp-v1.0: math$.
    This is useful to add version information that is not specified in the app name, or to shorten the name that is printed to the CLI.

  • Separators
    By default, JModule apps separate the app name from the module name with ": ", a colon followed by a space. We can change this from our app. For example, let's change the separator to a slash with no space in our app.

    client.setModuleSeparator("/");

    Our prompt in its home module will now display as ExampleApp-v1.0/math$.
    We can also change the prompt/user input separator. By default, JModule sets this to "$". We can change this in our example application.

    client.setPromptSeparator(">");

    Our prompt in its home module will now print as ExampleApp-v1.0/math> . The app will automatically print a space after the prompt to separate it from user input.

  • History index display
    JModule apps with history logging enabled can also display the history index to the CLI, much like the bash command prompt. Enable this by using

    client.setHistoryIndexDisplayEnabled(true);

    This will print the number of previous commands, preceded by a space, after the appname and module and before the prompt separator. Adding this line to our code, out prompt will now print as ExampleApp-v1.0/math 0>
    Note: in order to enable this function, history logging must be enabled (see below).

Further customizations

JModule apps can be customized to include a number of functionalities that could be useful to the user of your application. They are all set to false by default, but can be enabled with simple functions.

  • History Logging
    client.enableHistoryLogging(true);
    Enable history logging to allow the user to cycle through their previous commands using the ↑ and ↓ arrows. Whatever characters they have typed before toggling back through their history will be preserved if they toggle back to their current location. Enabling this funciton also allows you to enable history index display on the prompt.
  • Tab Completion
    client.enableTabCompletion(true);
    Enabling tab completion allows the user to use the tab key to cycle through possible commands in their current module that start with what they already have typed on the command line. If the user hasn't typed anything, tab will cycle through all of the possible commands in the current module.
  • Alerts
    client.enableAlerts(true);
    Enabling alerts allows the app to trigger the system's default alert (typically a sound such as this one). These alerts are triggered any time the user uses a special key that is unable to have any effect on the CLI. For example, an alert could trigger when the user presses delete with no characters typed in, or using tab toggling when no commands match what they've currently typed.

To take an in-depth look at the fully implemented example application, ExampleApp.java is outfitted with helpful comments and defines all its logic classes in the same file for readability.

Planned future updates

  • More flexible parameter options
    • Separate class for parameters
    • Mandatory and optional toggles for parameters
  • Ability for the developer to implement custom keylisteners using the JModule API
  • Ability to hide input for certain commands, such as passwords
  • Windows compatibility
  • JUnit tests

Documentation

The source code Javadoc for JModule (as of version 1.3.0) can be found here. I update the Javadoc with every major version release.

Older versions

Contact

Thanks for checking out JModule! Feel free to contact me at pierce@kelaita.com with any questions or suggestions, or if you want to contribute.

About

Java CLI development library that lets you organize commands by functionality. Supports tab completion, history toggling, chained commands, and other useful features

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

45 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

JModule

JModule is a simple, lightweight Java library written to help people easily write clean, organized, and highly customizable command-line applications. JModule works by running a console client containing multiple modules, each containing their own commands. The application user can switch between modules to access their commands and can view a customizable help page for each module. This design allows the developer to organize commands by functionality, leading to an cleaner flow and easier overall user experience.

Features

  • Commands can be organized into modules
  • A range of customizations that can be accessed through simple functions
  • Tab completion
  • History toggling with ↑ and ↓
  • Insert mode using ← and →
  • Chained commands using ;
  • Options (such as -v, --verbose, etc.)
  • Indefinite/Bounded number of required parameters for commands.
  • JModule implements its own keylistener (not Java.awt), allowing for it to detect individual bytes passed through the command line.
  • Fully-fledged example application, documentation, and usage guide.

As of the current version v1.3.1, JModule is optimized to run on *nix systems and does not yet support the windows command prompt. However, in future versions I plan to add Windows compatiblity.

Getting started

Setting up

In order to use JModule, download the latest version of JModule.jar from the releases page and add the jar to your preferred classpath. JModule's API is contained in two packages: com.jmodule.def (defining commands and parameters) and com.jmodule.exec (organization and execution of commands). The following classes are essential to use JModule

importcom.jmodule.def.Command;
importcom.jmodule.def.CommandLogic;
importcom.jmodule.exec.ConsoleClient;
importcom.jmodule.exec.Module;

The remaining classes are optional and add deeper functionality into the JModule API

importcom.jmodule.def.BoundedCommand;
importcom.jmodule.def.IndefiniteCommand; importcom.jmodule.def.Option;

Example App

JModule includes an Example Application, which is a simple arithmetic program that utilizes most of the functionality in JModule's API. To get a feel for the flow of a JModule application, make sure you have JModule in your classpath, clone or download the file, compile the java file using ~$ javac ExampleApp.java and run it with ~$ java ExampleApp. The code is full of comments to help you understand JModule's functionality. Feel free to modify or use the example code as a reference for writing your own JModule applications.

Writing a JModule application

Commands

Each JModule command runs based on its own command logic, an abstract class that must override the method execute(String[] args) to define the command's execution. A command is instantiated with a name, a description and logic. The simplest command will just have logic and no parameters

CommandhelloCmd = newCommand("hello", "Says hello", newCommandLogic() {
@Overridepublicvoidexecute(String[] args) {
System.out.println("Hello world!");
}
});

The command's name (converted to lowercase and with spaces removed) acts as its default command-line reference. We can also add alternate references to any command. This can be done with the following method

helloCmd.addReference("greet");

Our hello command can now be called from the console by either typing hello or greet.

~$ hello
Hello world!
~$ greet
Hello world!

Parameters

We can also add some parameters to our command by defining them as a String[] in the CommandLogic constructor.

CommandhelloCmd = newCommand("hello", "Says hello to the user", newCommandLogic(
newString[] {
"first name",
"last name"
}) {
@Overridepublicvoidexecute(String[] args) {
System.out.println("Hello, " + args[0] + " " + args[1] + "!");
}
});
helloCmd.addReference("greet");

Parameters in regular command are required by user input for the command to run. When commands are executed with an incorrect number of parameters or the module help page is accessed, the command's usage information will print to the console. Since we added a few parameters and an alternate reference to our hello command, the generated usage statement will be as follows.

Usage: ~$ hello <first name> <last name>
OR greet ~

If we want to change or add to the default usage statement, the method yourCommand.resetUsage(String reset) can be used to replace the default usage statement with your own statement, and the method yourCommand.appendUsage(String append) can be used to add a new line to the existing usage statement. To see examples of these implementations, consult the Example App.

Options

Sometimes we may want to give the user the choice to change the functionality of a command without having to worry about creating new commands or parameters. This can be done through the use of options. Options are arguments passed to the command that do not count as parameters and can be used anywhere in the command's parameters. They are, by nature, optional. Each option is initialized with a one-character flag, denoted by a single dash (for example, -v) and a description that shows up in the enclosing command's usage statement. Additionally, just like Commands, Options can have any number of references added to it. References can be multiple characters, are typically full words or word fragments, and are denoted by a double dash (for example, --ver or --verbose). An option can have any number of references added to it, and references are reccommended but not required. Options are added to CommandLogic with the function addOption(Option o) and their behavior can be defined within CommandLogic.execute(String[] args) by using the boolean if (onOption(char flag)) { /*logic here */ } that will return true if the option exists in the command and is called by the user. As an example, let's write a 'goodbye' command with a few options.

CommandgoodbyeCmd = newCommand("goodbye", "Says goodbye", newCommandLogic() {
@Overridepublicvoidexecute(String[] args) {
if (onOption('p')) {
System.out.print("Farewell, ");
} else {
System.out.print("Goodbye, ");
}
if (onOption('d')) {
System.out.print("cruel ");
}
System.out.print("world!\n\n");
}
}.addOption(newOption('p', "Makes the message polite")
.addReference("polite"))
.addOption(newOption('d', "Makes the message depressing")
.addReference("dep")
.addReference("depressing"))
);

Our 'goodbye' command can be tested from the command line as so:

~$ goodbye
Goodbye, world!
~$ goodbye -p
Farewell, world!
~$ goodbye --polite
Farewell, world!
~$ goodbye --depressing
Goodbye, cruel world!
~$ goodbye -d -p
Farewell, cruel world!

JModule also supports calling multiple options quickly by combining flags as such

~$ goodbye -dp
Farewell, cruel world!

Just like parameters, options added to commands will show up in the command's usage statement.

'goodbye'
Says goodbye
Usage: ~$ goodbye
Options:
-p, --polite: Makes the message polite
-d, --dep, --depressing: Makes the message depressing

Indefinite and Bounded Commands

Sometimes we may want our commands to have an undefined number or a range of numbers of possible parameters. In order to do this, we can either use an Indefinite Command, Bounded Command with an open range, or a Bounded Command with a closed range. These commands should have their parameters defined in their logic's constructor, but they will have no effect on the execution of the command; they will only affect the command's usage statement.

Indefinite Commands

Indefinite commands may have any number of parameters passed to it by the user. An example implementation of this would be a command to list names

CommandlistCmd = newIndefiniteCommand("list", "Lists given names, separated by commas", newCommandLogic(
newString[] {
"names..."
}) {
@Overridepublicvoidexecute(String[] args) {
for (inti = 0; i < args.length; i++) {
System.out.print(args[i]);
if (i != args.length - 1) {
System.out.print(", ");
}
}
}
});

Bounded Commands with open parameters

Bounded commands with open parameters are instantiated the same way as Indefinite or regular Commands, but a minimum and maximum number of parameters is specified. To have a bounded command with open parameters, specify only the minimum at the end of the BoundedCommand constructor. For example, a function to list the names of people all with the same last name, where the last name is taken as the first parameter, would have a minimum of two parameters and could look something like this.

CommandfamCmd = newBoundedCommand("famlist", "Lists the full names of family members who all have the same last name", newCommandLogic(
newString[] {
"family name",
"first names..."
}) {
@Overridepublicvoidexecute(String[] args) {
StringfamilyName = args[0];
for (inti = 1; i < args.length; i++) {
System.out.print(args[i] + " " + familyName);
if (i != args.length - 1) {
System.out.print(", ");
}
}
}
}, 2); // set minimum number of parameters to 2

Bounded Commands with closed parameters

Bounded commands can also have closed parameters, meaning both a minimum and maximum number of parameters is specified at the end of the constructor. An example would be a function that multiplies up to 4 numbers.

CommandmultCmd = newBoundedCommand("multiply", "Multiplies up to 4 numbers", newCommandLogic(
newString[] {
"factors..."
}) {
@Overridepublicvoidexecute(String[] args) {
intproduct = 0;
try {
for (Stringstr : args) {
intfactor = Integer.parseInt(str);
product *= factor;
}
System.out.print("Product: " + product);
...
}
}, 0, 4); // set minimum number of parameters to 0 and maximum to 4

Creating a custom control flow

Organizing Commands into Modules

Useful command-line applications will have a variety of possible commands. Let's assume that we've created a few arithmetic commands, a 'quizme' command taking no parameters that asks you simple math questions, and an 'info' command that reports how well you've done on past quizzes. The implementations for these commands can all be found in ExampleApp.java. To ease the user experience, we can organize commands with similar functionality into their own modules. In this instance, let's put the arithmetic commands in one module called math and the 'info' and 'quizme' commands in another module called quiz.

Modulemath = newModule("math");
math.addCommand(addCmd);
math.addCommand(subCmd);
math.addCommand(multCmd);
Modulequiz = newModule("quiz");
quiz.addCommand(quizCmd);
quiz.addCommand(infoCmd);

Each command has a name and description defined on instantiation, and a usage statement defined by its parameters and/or user customization. These statements are all helpful, and can be viewed by using the help command in the console.

~ math $ help
MATH -- POSSIBLE COMMANDS
'add'
Adds 2 numbers together
Usage: ~$ add <first number> <second number>
'subtract'
Subtracts 2 numbers
Usage: ~$ subtract <first number> <second number>
OR sub ~
'multiply'
Multiplies 2 or more numbers
Usage: ~$ multiply <First number> <Factors...>
OR mult ~
OR mul ~
'help'
Displays the help page for the current module.
Usage: ~$ help
Type the name of another module to switch to that module:
- 'quiz'
Type 'exit' at any time to exit the program

The help and exit commands are defined by default and do not need to be defined in your app. Similarly to command usage staments, module help pages are generated as a standard help page of the style shown above, and can be edited with yourModule.resetHelpPage(String reset) and yourModule.appendHelpPage(String append).

Organizing modules into a Console Client

We can organize our modules into a client by specifying the name of the app and the home module in the constructor, add other modules with addModule(), and run the console app with runConsole().

ConsoleClientclient = newConsoleClient("ExampleEducationApp", math);
client.addModule(quiz);
client.runConsole();

JModule also supports the ability to write non-modular apps. To do this, just throw all your commands into a single module and set it up as the client's home module. The name of the module will not show up in the prompt and the help page will show the app name rather than the home module name.

Customizable CLI prompt

JModule supports the ability to customize the prompt that will be printed to the CLI for each command. As of the current version, there are four possible prompt customization functions.

  • Prompt display name
    By default, the prompt will begin with the specified app name, with its spaces removed. However, we can change this if we'd like. For example, we can change the app name of our example application to shorten the name and include the version.

    client.setPromptDisplayName("ExampleApp-v1.0");

    Our prompt in its home module will now print as ExampleApp-v1.0: math$.
    This is useful to add version information that is not specified in the app name, or to shorten the name that is printed to the CLI.

  • Separators
    By default, JModule apps separate the app name from the module name with ": ", a colon followed by a space. We can change this from our app. For example, let's change the separator to a slash with no space in our app.

    client.setModuleSeparator("/");

    Our prompt in its home module will now display as ExampleApp-v1.0/math$.
    We can also change the prompt/user input separator. By default, JModule sets this to "$". We can change this in our example application.

    client.setPromptSeparator(">");

    Our prompt in its home module will now print as ExampleApp-v1.0/math> . The app will automatically print a space after the prompt to separate it from user input.

  • History index display
    JModule apps with history logging enabled can also display the history index to the CLI, much like the bash command prompt. Enable this by using

    client.setHistoryIndexDisplayEnabled(true);

    This will print the number of previous commands, preceded by a space, after the appname and module and before the prompt separator. Adding this line to our code, out prompt will now print as ExampleApp-v1.0/math 0>
    Note: in order to enable this function, history logging must be enabled (see below).

Further customizations

JModule apps can be customized to include a number of functionalities that could be useful to the user of your application. They are all set to false by default, but can be enabled with simple functions.

  • History Logging
    client.enableHistoryLogging(true);
    Enable history logging to allow the user to cycle through their previous commands using the ↑ and ↓ arrows. Whatever characters they have typed before toggling back through their history will be preserved if they toggle back to their current location. Enabling this funciton also allows you to enable history index display on the prompt.
  • Tab Completion
    client.enableTabCompletion(true);
    Enabling tab completion allows the user to use the tab key to cycle through possible commands in their current module that start with what they already have typed on the command line. If the user hasn't typed anything, tab will cycle through all of the possible commands in the current module.
  • Alerts
    client.enableAlerts(true);
    Enabling alerts allows the app to trigger the system's default alert (typically a sound such as this one). These alerts are triggered any time the user uses a special key that is unable to have any effect on the CLI. For example, an alert could trigger when the user presses delete with no characters typed in, or using tab toggling when no commands match what they've currently typed.

To take an in-depth look at the fully implemented example application, ExampleApp.java is outfitted with helpful comments and defines all its logic classes in the same file for readability.

Planned future updates

  • More flexible parameter options
    • Separate class for parameters
    • Mandatory and optional toggles for parameters
  • Ability for the developer to implement custom keylisteners using the JModule API
  • Ability to hide input for certain commands, such as passwords
  • Windows compatibility
  • JUnit tests

Documentation

The source code Javadoc for JModule (as of version 1.3.0) can be found here. I update the Javadoc with every major version release.

Older versions

Contact

Thanks for checking out JModule! Feel free to contact me at pierce@kelaita.com with any questions or suggestions, or if you want to contribute.

About

Java CLI development library that lets you organize commands by functionality. Supports tab completion, history toggling, chained commands, and other useful features

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

45 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

JModule

JModule is a simple, lightweight Java library written to help people easily write clean, organized, and highly customizable command-line applications. JModule works by running a console client containing multiple modules, each containing their own commands. The application user can switch between modules to access their commands and can view a customizable help page for each module. This design allows the developer to organize commands by functionality, leading to an cleaner flow and easier overall user experience.

Features

  • Commands can be organized into modules
  • A range of customizations that can be accessed through simple functions
  • Tab completion
  • History toggling with ↑ and ↓
  • Insert mode using ← and →
  • Chained commands using ;
  • Options (such as -v, --verbose, etc.)
  • Indefinite/Bounded number of required parameters for commands.
  • JModule implements its own keylistener (not Java.awt), allowing for it to detect individual bytes passed through the command line.
  • Fully-fledged example application, documentation, and usage guide.

As of the current version v1.3.1, JModule is optimized to run on *nix systems and does not yet support the windows command prompt. However, in future versions I plan to add Windows compatiblity.

Getting started

Setting up

In order to use JModule, download the latest version of JModule.jar from the releases page and add the jar to your preferred classpath. JModule's API is contained in two packages: com.jmodule.def (defining commands and parameters) and com.jmodule.exec (organization and execution of commands). The following classes are essential to use JModule

importcom.jmodule.def.Command;
importcom.jmodule.def.CommandLogic;
importcom.jmodule.exec.ConsoleClient;
importcom.jmodule.exec.Module;

The remaining classes are optional and add deeper functionality into the JModule API

importcom.jmodule.def.BoundedCommand;
importcom.jmodule.def.IndefiniteCommand; importcom.jmodule.def.Option;

Example App

JModule includes an Example Application, which is a simple arithmetic program that utilizes most of the functionality in JModule's API. To get a feel for the flow of a JModule application, make sure you have JModule in your classpath, clone or download the file, compile the java file using ~$ javac ExampleApp.java and run it with ~$ java ExampleApp. The code is full of comments to help you understand JModule's functionality. Feel free to modify or use the example code as a reference for writing your own JModule applications.

Writing a JModule application

Commands

Each JModule command runs based on its own command logic, an abstract class that must override the method execute(String[] args) to define the command's execution. A command is instantiated with a name, a description and logic. The simplest command will just have logic and no parameters

CommandhelloCmd = newCommand("hello", "Says hello", newCommandLogic() {
@Overridepublicvoidexecute(String[] args) {
System.out.println("Hello world!");
}
});

The command's name (converted to lowercase and with spaces removed) acts as its default command-line reference. We can also add alternate references to any command. This can be done with the following method

helloCmd.addReference("greet");

Our hello command can now be called from the console by either typing hello or greet.

~$ hello
Hello world!
~$ greet
Hello world!

Parameters

We can also add some parameters to our command by defining them as a String[] in the CommandLogic constructor.

CommandhelloCmd = newCommand("hello", "Says hello to the user", newCommandLogic(
newString[] {
"first name",
"last name"
}) {
@Overridepublicvoidexecute(String[] args) {
System.out.println("Hello, " + args[0] + " " + args[1] + "!");
}
});
helloCmd.addReference("greet");

Parameters in regular command are required by user input for the command to run. When commands are executed with an incorrect number of parameters or the module help page is accessed, the command's usage information will print to the console. Since we added a few parameters and an alternate reference to our hello command, the generated usage statement will be as follows.

Usage: ~$ hello <first name> <last name>
OR greet ~

If we want to change or add to the default usage statement, the method yourCommand.resetUsage(String reset) can be used to replace the default usage statement with your own statement, and the method yourCommand.appendUsage(String append) can be used to add a new line to the existing usage statement. To see examples of these implementations, consult the Example App.

Options

Sometimes we may want to give the user the choice to change the functionality of a command without having to worry about creating new commands or parameters. This can be done through the use of options. Options are arguments passed to the command that do not count as parameters and can be used anywhere in the command's parameters. They are, by nature, optional. Each option is initialized with a one-character flag, denoted by a single dash (for example, -v) and a description that shows up in the enclosing command's usage statement. Additionally, just like Commands, Options can have any number of references added to it. References can be multiple characters, are typically full words or word fragments, and are denoted by a double dash (for example, --ver or --verbose). An option can have any number of references added to it, and references are reccommended but not required. Options are added to CommandLogic with the function addOption(Option o) and their behavior can be defined within CommandLogic.execute(String[] args) by using the boolean if (onOption(char flag)) { /*logic here */ } that will return true if the option exists in the command and is called by the user. As an example, let's write a 'goodbye' command with a few options.

CommandgoodbyeCmd = newCommand("goodbye", "Says goodbye", newCommandLogic() {
@Overridepublicvoidexecute(String[] args) {
if (onOption('p')) {
System.out.print("Farewell, ");
} else {
System.out.print("Goodbye, ");
}
if (onOption('d')) {
System.out.print("cruel ");
}
System.out.print("world!\n\n");
}
}.addOption(newOption('p', "Makes the message polite")
.addReference("polite"))
.addOption(newOption('d', "Makes the message depressing")
.addReference("dep")
.addReference("depressing"))
);

Our 'goodbye' command can be tested from the command line as so:

~$ goodbye
Goodbye, world!
~$ goodbye -p
Farewell, world!
~$ goodbye --polite
Farewell, world!
~$ goodbye --depressing
Goodbye, cruel world!
~$ goodbye -d -p
Farewell, cruel world!

JModule also supports calling multiple options quickly by combining flags as such

~$ goodbye -dp
Farewell, cruel world!

Just like parameters, options added to commands will show up in the command's usage statement.

'goodbye'
Says goodbye
Usage: ~$ goodbye
Options:
-p, --polite: Makes the message polite
-d, --dep, --depressing: Makes the message depressing

Indefinite and Bounded Commands

Sometimes we may want our commands to have an undefined number or a range of numbers of possible parameters. In order to do this, we can either use an Indefinite Command, Bounded Command with an open range, or a Bounded Command with a closed range. These commands should have their parameters defined in their logic's constructor, but they will have no effect on the execution of the command; they will only affect the command's usage statement.

Indefinite Commands

Indefinite commands may have any number of parameters passed to it by the user. An example implementation of this would be a command to list names

CommandlistCmd = newIndefiniteCommand("list", "Lists given names, separated by commas", newCommandLogic(
newString[] {
"names..."
}) {
@Overridepublicvoidexecute(String[] args) {
for (inti = 0; i < args.length; i++) {
System.out.print(args[i]);
if (i != args.length - 1) {
System.out.print(", ");
}
}
}
});

Bounded Commands with open parameters

Bounded commands with open parameters are instantiated the same way as Indefinite or regular Commands, but a minimum and maximum number of parameters is specified. To have a bounded command with open parameters, specify only the minimum at the end of the BoundedCommand constructor. For example, a function to list the names of people all with the same last name, where the last name is taken as the first parameter, would have a minimum of two parameters and could look something like this.

CommandfamCmd = newBoundedCommand("famlist", "Lists the full names of family members who all have the same last name", newCommandLogic(
newString[] {
"family name",
"first names..."
}) {
@Overridepublicvoidexecute(String[] args) {
StringfamilyName = args[0];
for (inti = 1; i < args.length; i++) {
System.out.print(args[i] + " " + familyName);
if (i != args.length - 1) {
System.out.print(", ");
}
}
}
}, 2); // set minimum number of parameters to 2

Bounded Commands with closed parameters

Bounded commands can also have closed parameters, meaning both a minimum and maximum number of parameters is specified at the end of the constructor. An example would be a function that multiplies up to 4 numbers.

CommandmultCmd = newBoundedCommand("multiply", "Multiplies up to 4 numbers", newCommandLogic(
newString[] {
"factors..."
}) {
@Overridepublicvoidexecute(String[] args) {
intproduct = 0;
try {
for (Stringstr : args) {
intfactor = Integer.parseInt(str);
product *= factor;
}
System.out.print("Product: " + product);
...
}
}, 0, 4); // set minimum number of parameters to 0 and maximum to 4

Creating a custom control flow

Organizing Commands into Modules

Useful command-line applications will have a variety of possible commands. Let's assume that we've created a few arithmetic commands, a 'quizme' command taking no parameters that asks you simple math questions, and an 'info' command that reports how well you've done on past quizzes. The implementations for these commands can all be found in ExampleApp.java. To ease the user experience, we can organize commands with similar functionality into their own modules. In this instance, let's put the arithmetic commands in one module called math and the 'info' and 'quizme' commands in another module called quiz.

Modulemath = newModule("math");
math.addCommand(addCmd);
math.addCommand(subCmd);
math.addCommand(multCmd);
Modulequiz = newModule("quiz");
quiz.addCommand(quizCmd);
quiz.addCommand(infoCmd);

Each command has a name and description defined on instantiation, and a usage statement defined by its parameters and/or user customization. These statements are all helpful, and can be viewed by using the help command in the console.

~ math $ help
MATH -- POSSIBLE COMMANDS
'add'
Adds 2 numbers together
Usage: ~$ add <first number> <second number>
'subtract'
Subtracts 2 numbers
Usage: ~$ subtract <first number> <second number>
OR sub ~
'multiply'
Multiplies 2 or more numbers
Usage: ~$ multiply <First number> <Factors...>
OR mult ~
OR mul ~
'help'
Displays the help page for the current module.
Usage: ~$ help
Type the name of another module to switch to that module:
- 'quiz'
Type 'exit' at any time to exit the program

The help and exit commands are defined by default and do not need to be defined in your app. Similarly to command usage staments, module help pages are generated as a standard help page of the style shown above, and can be edited with yourModule.resetHelpPage(String reset) and yourModule.appendHelpPage(String append).

Organizing modules into a Console Client

We can organize our modules into a client by specifying the name of the app and the home module in the constructor, add other modules with addModule(), and run the console app with runConsole().

ConsoleClientclient = newConsoleClient("ExampleEducationApp", math);
client.addModule(quiz);
client.runConsole();

JModule also supports the ability to write non-modular apps. To do this, just throw all your commands into a single module and set it up as the client's home module. The name of the module will not show up in the prompt and the help page will show the app name rather than the home module name.

Customizable CLI prompt

JModule supports the ability to customize the prompt that will be printed to the CLI for each command. As of the current version, there are four possible prompt customization functions.

  • Prompt display name
    By default, the prompt will begin with the specified app name, with its spaces removed. However, we can change this if we'd like. For example, we can change the app name of our example application to shorten the name and include the version.

    client.setPromptDisplayName("ExampleApp-v1.0");

    Our prompt in its home module will now print as ExampleApp-v1.0: math$.
    This is useful to add version information that is not specified in the app name, or to shorten the name that is printed to the CLI.

  • Separators
    By default, JModule apps separate the app name from the module name with ": ", a colon followed by a space. We can change this from our app. For example, let's change the separator to a slash with no space in our app.

    client.setModuleSeparator("/");

    Our prompt in its home module will now display as ExampleApp-v1.0/math$.
    We can also change the prompt/user input separator. By default, JModule sets this to "$". We can change this in our example application.

    client.setPromptSeparator(">");

    Our prompt in its home module will now print as ExampleApp-v1.0/math> . The app will automatically print a space after the prompt to separate it from user input.

  • History index display
    JModule apps with history logging enabled can also display the history index to the CLI, much like the bash command prompt. Enable this by using

    client.setHistoryIndexDisplayEnabled(true);

    This will print the number of previous commands, preceded by a space, after the appname and module and before the prompt separator. Adding this line to our code, out prompt will now print as ExampleApp-v1.0/math 0>
    Note: in order to enable this function, history logging must be enabled (see below).

Further customizations

JModule apps can be customized to include a number of functionalities that could be useful to the user of your application. They are all set to false by default, but can be enabled with simple functions.

  • History Logging
    client.enableHistoryLogging(true);
    Enable history logging to allow the user to cycle through their previous commands using the ↑ and ↓ arrows. Whatever characters they have typed before toggling back through their history will be preserved if they toggle back to their current location. Enabling this funciton also allows you to enable history index display on the prompt.
  • Tab Completion
    client.enableTabCompletion(true);
    Enabling tab completion allows the user to use the tab key to cycle through possible commands in their current module that start with what they already have typed on the command line. If the user hasn't typed anything, tab will cycle through all of the possible commands in the current module.
  • Alerts
    client.enableAlerts(true);
    Enabling alerts allows the app to trigger the system's default alert (typically a sound such as this one). These alerts are triggered any time the user uses a special key that is unable to have any effect on the CLI. For example, an alert could trigger when the user presses delete with no characters typed in, or using tab toggling when no commands match what they've currently typed.

To take an in-depth look at the fully implemented example application, ExampleApp.java is outfitted with helpful comments and defines all its logic classes in the same file for readability.

Planned future updates

  • More flexible parameter options
    • Separate class for parameters
    • Mandatory and optional toggles for parameters
  • Ability for the developer to implement custom keylisteners using the JModule API
  • Ability to hide input for certain commands, such as passwords
  • Windows compatibility
  • JUnit tests

Documentation

The source code Javadoc for JModule (as of version 1.3.0) can be found here. I update the Javadoc with every major version release.

Older versions

Contact

Thanks for checking out JModule! Feel free to contact me at pierce@kelaita.com with any questions or suggestions, or if you want to contribute.

About

Java CLI development library that lets you organize commands by functionality. Supports tab completion, history toggling, chained commands, and other useful features

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

45 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

JModule

JModule is a simple, lightweight Java library written to help people easily write clean, organized, and highly customizable command-line applications. JModule works by running a console client containing multiple modules, each containing their own commands. The application user can switch between modules to access their commands and can view a customizable help page for each module. This design allows the developer to organize commands by functionality, leading to an cleaner flow and easier overall user experience.

Features

  • Commands can be organized into modules
  • A range of customizations that can be accessed through simple functions
  • Tab completion
  • History toggling with ↑ and ↓
  • Insert mode using ← and →
  • Chained commands using ;
  • Options (such as -v, --verbose, etc.)
  • Indefinite/Bounded number of required parameters for commands.
  • JModule implements its own keylistener (not Java.awt), allowing for it to detect individual bytes passed through the command line.
  • Fully-fledged example application, documentation, and usage guide.

As of the current version v1.3.1, JModule is optimized to run on *nix systems and does not yet support the windows command prompt. However, in future versions I plan to add Windows compatiblity.

Getting started

Setting up

In order to use JModule, download the latest version of JModule.jar from the releases page and add the jar to your preferred classpath. JModule's API is contained in two packages: com.jmodule.def (defining commands and parameters) and com.jmodule.exec (organization and execution of commands). The following classes are essential to use JModule

importcom.jmodule.def.Command;
importcom.jmodule.def.CommandLogic;
importcom.jmodule.exec.ConsoleClient;
importcom.jmodule.exec.Module;

The remaining classes are optional and add deeper functionality into the JModule API

importcom.jmodule.def.BoundedCommand;
importcom.jmodule.def.IndefiniteCommand; importcom.jmodule.def.Option;

Example App

JModule includes an Example Application, which is a simple arithmetic program that utilizes most of the functionality in JModule's API. To get a feel for the flow of a JModule application, make sure you have JModule in your classpath, clone or download the file, compile the java file using ~$ javac ExampleApp.java and run it with ~$ java ExampleApp. The code is full of comments to help you understand JModule's functionality. Feel free to modify or use the example code as a reference for writing your own JModule applications.

Writing a JModule application

Commands

Each JModule command runs based on its own command logic, an abstract class that must override the method execute(String[] args) to define the command's execution. A command is instantiated with a name, a description and logic. The simplest command will just have logic and no parameters

CommandhelloCmd = newCommand("hello", "Says hello", newCommandLogic() {
@Overridepublicvoidexecute(String[] args) {
System.out.println("Hello world!");
}
});

The command's name (converted to lowercase and with spaces removed) acts as its default command-line reference. We can also add alternate references to any command. This can be done with the following method

helloCmd.addReference("greet");

Our hello command can now be called from the console by either typing hello or greet.

~$ hello
Hello world!
~$ greet
Hello world!

Parameters

We can also add some parameters to our command by defining them as a String[] in the CommandLogic constructor.

CommandhelloCmd = newCommand("hello", "Says hello to the user", newCommandLogic(
newString[] {
"first name",
"last name"
}) {
@Overridepublicvoidexecute(String[] args) {
System.out.println("Hello, " + args[0] + " " + args[1] + "!");
}
});
helloCmd.addReference("greet");

Parameters in regular command are required by user input for the command to run. When commands are executed with an incorrect number of parameters or the module help page is accessed, the command's usage information will print to the console. Since we added a few parameters and an alternate reference to our hello command, the generated usage statement will be as follows.

Usage: ~$ hello <first name> <last name>
OR greet ~

If we want to change or add to the default usage statement, the method yourCommand.resetUsage(String reset) can be used to replace the default usage statement with your own statement, and the method yourCommand.appendUsage(String append) can be used to add a new line to the existing usage statement. To see examples of these implementations, consult the Example App.

Options

Sometimes we may want to give the user the choice to change the functionality of a command without having to worry about creating new commands or parameters. This can be done through the use of options. Options are arguments passed to the command that do not count as parameters and can be used anywhere in the command's parameters. They are, by nature, optional. Each option is initialized with a one-character flag, denoted by a single dash (for example, -v) and a description that shows up in the enclosing command's usage statement. Additionally, just like Commands, Options can have any number of references added to it. References can be multiple characters, are typically full words or word fragments, and are denoted by a double dash (for example, --ver or --verbose). An option can have any number of references added to it, and references are reccommended but not required. Options are added to CommandLogic with the function addOption(Option o) and their behavior can be defined within CommandLogic.execute(String[] args) by using the boolean if (onOption(char flag)) { /*logic here */ } that will return true if the option exists in the command and is called by the user. As an example, let's write a 'goodbye' command with a few options.

CommandgoodbyeCmd = newCommand("goodbye", "Says goodbye", newCommandLogic() {
@Overridepublicvoidexecute(String[] args) {
if (onOption('p')) {
System.out.print("Farewell, ");
} else {
System.out.print("Goodbye, ");
}
if (onOption('d')) {
System.out.print("cruel ");
}
System.out.print("world!\n\n");
}
}.addOption(newOption('p', "Makes the message polite")
.addReference("polite"))
.addOption(newOption('d', "Makes the message depressing")
.addReference("dep")
.addReference("depressing"))
);

Our 'goodbye' command can be tested from the command line as so:

~$ goodbye
Goodbye, world!
~$ goodbye -p
Farewell, world!
~$ goodbye --polite
Farewell, world!
~$ goodbye --depressing
Goodbye, cruel world!
~$ goodbye -d -p
Farewell, cruel world!

JModule also supports calling multiple options quickly by combining flags as such

~$ goodbye -dp
Farewell, cruel world!

Just like parameters, options added to commands will show up in the command's usage statement.

'goodbye'
Says goodbye
Usage: ~$ goodbye
Options:
-p, --polite: Makes the message polite
-d, --dep, --depressing: Makes the message depressing

Indefinite and Bounded Commands

Sometimes we may want our commands to have an undefined number or a range of numbers of possible parameters. In order to do this, we can either use an Indefinite Command, Bounded Command with an open range, or a Bounded Command with a closed range. These commands should have their parameters defined in their logic's constructor, but they will have no effect on the execution of the command; they will only affect the command's usage statement.

Indefinite Commands

Indefinite commands may have any number of parameters passed to it by the user. An example implementation of this would be a command to list names

CommandlistCmd = newIndefiniteCommand("list", "Lists given names, separated by commas", newCommandLogic(
newString[] {
"names..."
}) {
@Overridepublicvoidexecute(String[] args) {
for (inti = 0; i < args.length; i++) {
System.out.print(args[i]);
if (i != args.length - 1) {
System.out.print(", ");
}
}
}
});

Bounded Commands with open parameters

Bounded commands with open parameters are instantiated the same way as Indefinite or regular Commands, but a minimum and maximum number of parameters is specified. To have a bounded command with open parameters, specify only the minimum at the end of the BoundedCommand constructor. For example, a function to list the names of people all with the same last name, where the last name is taken as the first parameter, would have a minimum of two parameters and could look something like this.

CommandfamCmd = newBoundedCommand("famlist", "Lists the full names of family members who all have the same last name", newCommandLogic(
newString[] {
"family name",
"first names..."
}) {
@Overridepublicvoidexecute(String[] args) {
StringfamilyName = args[0];
for (inti = 1; i < args.length; i++) {
System.out.print(args[i] + " " + familyName);
if (i != args.length - 1) {
System.out.print(", ");
}
}
}
}, 2); // set minimum number of parameters to 2

Bounded Commands with closed parameters

Bounded commands can also have closed parameters, meaning both a minimum and maximum number of parameters is specified at the end of the constructor. An example would be a function that multiplies up to 4 numbers.

CommandmultCmd = newBoundedCommand("multiply", "Multiplies up to 4 numbers", newCommandLogic(
newString[] {
"factors..."
}) {
@Overridepublicvoidexecute(String[] args) {
intproduct = 0;
try {
for (Stringstr : args) {
intfactor = Integer.parseInt(str);
product *= factor;
}
System.out.print("Product: " + product);
...
}
}, 0, 4); // set minimum number of parameters to 0 and maximum to 4

Creating a custom control flow

Organizing Commands into Modules

Useful command-line applications will have a variety of possible commands. Let's assume that we've created a few arithmetic commands, a 'quizme' command taking no parameters that asks you simple math questions, and an 'info' command that reports how well you've done on past quizzes. The implementations for these commands can all be found in ExampleApp.java. To ease the user experience, we can organize commands with similar functionality into their own modules. In this instance, let's put the arithmetic commands in one module called math and the 'info' and 'quizme' commands in another module called quiz.

Modulemath = newModule("math");
math.addCommand(addCmd);
math.addCommand(subCmd);
math.addCommand(multCmd);
Modulequiz = newModule("quiz");
quiz.addCommand(quizCmd);
quiz.addCommand(infoCmd);

Each command has a name and description defined on instantiation, and a usage statement defined by its parameters and/or user customization. These statements are all helpful, and can be viewed by using the help command in the console.

~ math $ help
MATH -- POSSIBLE COMMANDS
'add'
Adds 2 numbers together
Usage: ~$ add <first number> <second number>
'subtract'
Subtracts 2 numbers
Usage: ~$ subtract <first number> <second number>
OR sub ~
'multiply'
Multiplies 2 or more numbers
Usage: ~$ multiply <First number> <Factors...>
OR mult ~
OR mul ~
'help'
Displays the help page for the current module.
Usage: ~$ help
Type the name of another module to switch to that module:
- 'quiz'
Type 'exit' at any time to exit the program

The help and exit commands are defined by default and do not need to be defined in your app. Similarly to command usage staments, module help pages are generated as a standard help page of the style shown above, and can be edited with yourModule.resetHelpPage(String reset) and yourModule.appendHelpPage(String append).

Organizing modules into a Console Client

We can organize our modules into a client by specifying the name of the app and the home module in the constructor, add other modules with addModule(), and run the console app with runConsole().

ConsoleClientclient = newConsoleClient("ExampleEducationApp", math);
client.addModule(quiz);
client.runConsole();

JModule also supports the ability to write non-modular apps. To do this, just throw all your commands into a single module and set it up as the client's home module. The name of the module will not show up in the prompt and the help page will show the app name rather than the home module name.

Customizable CLI prompt

JModule supports the ability to customize the prompt that will be printed to the CLI for each command. As of the current version, there are four possible prompt customization functions.

  • Prompt display name
    By default, the prompt will begin with the specified app name, with its spaces removed. However, we can change this if we'd like. For example, we can change the app name of our example application to shorten the name and include the version.

    client.setPromptDisplayName("ExampleApp-v1.0");

    Our prompt in its home module will now print as ExampleApp-v1.0: math$.
    This is useful to add version information that is not specified in the app name, or to shorten the name that is printed to the CLI.

  • Separators
    By default, JModule apps separate the app name from the module name with ": ", a colon followed by a space. We can change this from our app. For example, let's change the separator to a slash with no space in our app.

    client.setModuleSeparator("/");

    Our prompt in its home module will now display as ExampleApp-v1.0/math$.
    We can also change the prompt/user input separator. By default, JModule sets this to "$". We can change this in our example application.

    client.setPromptSeparator(">");

    Our prompt in its home module will now print as ExampleApp-v1.0/math> . The app will automatically print a space after the prompt to separate it from user input.

  • History index display
    JModule apps with history logging enabled can also display the history index to the CLI, much like the bash command prompt. Enable this by using

    client.setHistoryIndexDisplayEnabled(true);

    This will print the number of previous commands, preceded by a space, after the appname and module and before the prompt separator. Adding this line to our code, out prompt will now print as ExampleApp-v1.0/math 0>
    Note: in order to enable this function, history logging must be enabled (see below).

Further customizations

JModule apps can be customized to include a number of functionalities that could be useful to the user of your application. They are all set to false by default, but can be enabled with simple functions.

  • History Logging
    client.enableHistoryLogging(true);
    Enable history logging to allow the user to cycle through their previous commands using the ↑ and ↓ arrows. Whatever characters they have typed before toggling back through their history will be preserved if they toggle back to their current location. Enabling this funciton also allows you to enable history index display on the prompt.
  • Tab Completion
    client.enableTabCompletion(true);
    Enabling tab completion allows the user to use the tab key to cycle through possible commands in their current module that start with what they already have typed on the command line. If the user hasn't typed anything, tab will cycle through all of the possible commands in the current module.
  • Alerts
    client.enableAlerts(true);
    Enabling alerts allows the app to trigger the system's default alert (typically a sound such as this one). These alerts are triggered any time the user uses a special key that is unable to have any effect on the CLI. For example, an alert could trigger when the user presses delete with no characters typed in, or using tab toggling when no commands match what they've currently typed.

To take an in-depth look at the fully implemented example application, ExampleApp.java is outfitted with helpful comments and defines all its logic classes in the same file for readability.

Planned future updates

  • More flexible parameter options
    • Separate class for parameters
    • Mandatory and optional toggles for parameters
  • Ability for the developer to implement custom keylisteners using the JModule API
  • Ability to hide input for certain commands, such as passwords
  • Windows compatibility
  • JUnit tests

Documentation

The source code Javadoc for JModule (as of version 1.3.0) can be found here. I update the Javadoc with every major version release.

Older versions

Contact

Thanks for checking out JModule! Feel free to contact me at pierce@kelaita.com with any questions or suggestions, or if you want to contribute.

About

Java CLI development library that lets you organize commands by functionality. Supports tab completion, history toggling, chained commands, and other useful features

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

45 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

JModule

JModule is a simple, lightweight Java library written to help people easily write clean, organized, and highly customizable command-line applications. JModule works by running a console client containing multiple modules, each containing their own commands. The application user can switch between modules to access their commands and can view a customizable help page for each module. This design allows the developer to organize commands by functionality, leading to an cleaner flow and easier overall user experience.

Features

  • Commands can be organized into modules
  • A range of customizations that can be accessed through simple functions
  • Tab completion
  • History toggling with ↑ and ↓
  • Insert mode using ← and →
  • Chained commands using ;
  • Options (such as -v, --verbose, etc.)
  • Indefinite/Bounded number of required parameters for commands.
  • JModule implements its own keylistener (not Java.awt), allowing for it to detect individual bytes passed through the command line.
  • Fully-fledged example application, documentation, and usage guide.

As of the current version v1.3.1, JModule is optimized to run on *nix systems and does not yet support the windows command prompt. However, in future versions I plan to add Windows compatiblity.

Getting started

Setting up

In order to use JModule, download the latest version of JModule.jar from the releases page and add the jar to your preferred classpath. JModule's API is contained in two packages: com.jmodule.def (defining commands and parameters) and com.jmodule.exec (organization and execution of commands). The following classes are essential to use JModule

importcom.jmodule.def.Command;
importcom.jmodule.def.CommandLogic;
importcom.jmodule.exec.ConsoleClient;
importcom.jmodule.exec.Module;

The remaining classes are optional and add deeper functionality into the JModule API

importcom.jmodule.def.BoundedCommand;
importcom.jmodule.def.IndefiniteCommand; importcom.jmodule.def.Option;

Example App

JModule includes an Example Application, which is a simple arithmetic program that utilizes most of the functionality in JModule's API. To get a feel for the flow of a JModule application, make sure you have JModule in your classpath, clone or download the file, compile the java file using ~$ javac ExampleApp.java and run it with ~$ java ExampleApp. The code is full of comments to help you understand JModule's functionality. Feel free to modify or use the example code as a reference for writing your own JModule applications.

Writing a JModule application

Commands

Each JModule command runs based on its own command logic, an abstract class that must override the method execute(String[] args) to define the command's execution. A command is instantiated with a name, a description and logic. The simplest command will just have logic and no parameters

CommandhelloCmd = newCommand("hello", "Says hello", newCommandLogic() {
@Overridepublicvoidexecute(String[] args) {
System.out.println("Hello world!");
}
});

The command's name (converted to lowercase and with spaces removed) acts as its default command-line reference. We can also add alternate references to any command. This can be done with the following method

helloCmd.addReference("greet");

Our hello command can now be called from the console by either typing hello or greet.

~$ hello
Hello world!
~$ greet
Hello world!

Parameters

We can also add some parameters to our command by defining them as a String[] in the CommandLogic constructor.

CommandhelloCmd = newCommand("hello", "Says hello to the user", newCommandLogic(
newString[] {
"first name",
"last name"
}) {
@Overridepublicvoidexecute(String[] args) {
System.out.println("Hello, " + args[0] + " " + args[1] + "!");
}
});
helloCmd.addReference("greet");

Parameters in regular command are required by user input for the command to run. When commands are executed with an incorrect number of parameters or the module help page is accessed, the command's usage information will print to the console. Since we added a few parameters and an alternate reference to our hello command, the generated usage statement will be as follows.

Usage: ~$ hello <first name> <last name>
OR greet ~

If we want to change or add to the default usage statement, the method yourCommand.resetUsage(String reset) can be used to replace the default usage statement with your own statement, and the method yourCommand.appendUsage(String append) can be used to add a new line to the existing usage statement. To see examples of these implementations, consult the Example App.

Options

Sometimes we may want to give the user the choice to change the functionality of a command without having to worry about creating new commands or parameters. This can be done through the use of options. Options are arguments passed to the command that do not count as parameters and can be used anywhere in the command's parameters. They are, by nature, optional. Each option is initialized with a one-character flag, denoted by a single dash (for example, -v) and a description that shows up in the enclosing command's usage statement. Additionally, just like Commands, Options can have any number of references added to it. References can be multiple characters, are typically full words or word fragments, and are denoted by a double dash (for example, --ver or --verbose). An option can have any number of references added to it, and references are reccommended but not required. Options are added to CommandLogic with the function addOption(Option o) and their behavior can be defined within CommandLogic.execute(String[] args) by using the boolean if (onOption(char flag)) { /*logic here */ } that will return true if the option exists in the command and is called by the user. As an example, let's write a 'goodbye' command with a few options.

CommandgoodbyeCmd = newCommand("goodbye", "Says goodbye", newCommandLogic() {
@Overridepublicvoidexecute(String[] args) {
if (onOption('p')) {
System.out.print("Farewell, ");
} else {
System.out.print("Goodbye, ");
}
if (onOption('d')) {
System.out.print("cruel ");
}
System.out.print("world!\n\n");
}
}.addOption(newOption('p', "Makes the message polite")
.addReference("polite"))
.addOption(newOption('d', "Makes the message depressing")
.addReference("dep")
.addReference("depressing"))
);

Our 'goodbye' command can be tested from the command line as so:

~$ goodbye
Goodbye, world!
~$ goodbye -p
Farewell, world!
~$ goodbye --polite
Farewell, world!
~$ goodbye --depressing
Goodbye, cruel world!
~$ goodbye -d -p
Farewell, cruel world!

JModule also supports calling multiple options quickly by combining flags as such

~$ goodbye -dp
Farewell, cruel world!

Just like parameters, options added to commands will show up in the command's usage statement.

'goodbye'
Says goodbye
Usage: ~$ goodbye
Options:
-p, --polite: Makes the message polite
-d, --dep, --depressing: Makes the message depressing

Indefinite and Bounded Commands

Sometimes we may want our commands to have an undefined number or a range of numbers of possible parameters. In order to do this, we can either use an Indefinite Command, Bounded Command with an open range, or a Bounded Command with a closed range. These commands should have their parameters defined in their logic's constructor, but they will have no effect on the execution of the command; they will only affect the command's usage statement.

Indefinite Commands

Indefinite commands may have any number of parameters passed to it by the user. An example implementation of this would be a command to list names

CommandlistCmd = newIndefiniteCommand("list", "Lists given names, separated by commas", newCommandLogic(
newString[] {
"names..."
}) {
@Overridepublicvoidexecute(String[] args) {
for (inti = 0; i < args.length; i++) {
System.out.print(args[i]);
if (i != args.length - 1) {
System.out.print(", ");
}
}
}
});

Bounded Commands with open parameters

Bounded commands with open parameters are instantiated the same way as Indefinite or regular Commands, but a minimum and maximum number of parameters is specified. To have a bounded command with open parameters, specify only the minimum at the end of the BoundedCommand constructor. For example, a function to list the names of people all with the same last name, where the last name is taken as the first parameter, would have a minimum of two parameters and could look something like this.

CommandfamCmd = newBoundedCommand("famlist", "Lists the full names of family members who all have the same last name", newCommandLogic(
newString[] {
"family name",
"first names..."
}) {
@Overridepublicvoidexecute(String[] args) {
StringfamilyName = args[0];
for (inti = 1; i < args.length; i++) {
System.out.print(args[i] + " " + familyName);
if (i != args.length - 1) {
System.out.print(", ");
}
}
}
}, 2); // set minimum number of parameters to 2

Bounded Commands with closed parameters

Bounded commands can also have closed parameters, meaning both a minimum and maximum number of parameters is specified at the end of the constructor. An example would be a function that multiplies up to 4 numbers.

CommandmultCmd = newBoundedCommand("multiply", "Multiplies up to 4 numbers", newCommandLogic(
newString[] {
"factors..."
}) {
@Overridepublicvoidexecute(String[] args) {
intproduct = 0;
try {
for (Stringstr : args) {
intfactor = Integer.parseInt(str);
product *= factor;
}
System.out.print("Product: " + product);
...
}
}, 0, 4); // set minimum number of parameters to 0 and maximum to 4

Creating a custom control flow

Organizing Commands into Modules

Useful command-line applications will have a variety of possible commands. Let's assume that we've created a few arithmetic commands, a 'quizme' command taking no parameters that asks you simple math questions, and an 'info' command that reports how well you've done on past quizzes. The implementations for these commands can all be found in ExampleApp.java. To ease the user experience, we can organize commands with similar functionality into their own modules. In this instance, let's put the arithmetic commands in one module called math and the 'info' and 'quizme' commands in another module called quiz.

Modulemath = newModule("math");
math.addCommand(addCmd);
math.addCommand(subCmd);
math.addCommand(multCmd);
Modulequiz = newModule("quiz");
quiz.addCommand(quizCmd);
quiz.addCommand(infoCmd);

Each command has a name and description defined on instantiation, and a usage statement defined by its parameters and/or user customization. These statements are all helpful, and can be viewed by using the help command in the console.

~ math $ help
MATH -- POSSIBLE COMMANDS
'add'
Adds 2 numbers together
Usage: ~$ add <first number> <second number>
'subtract'
Subtracts 2 numbers
Usage: ~$ subtract <first number> <second number>
OR sub ~
'multiply'
Multiplies 2 or more numbers
Usage: ~$ multiply <First number> <Factors...>
OR mult ~
OR mul ~
'help'
Displays the help page for the current module.
Usage: ~$ help
Type the name of another module to switch to that module:
- 'quiz'
Type 'exit' at any time to exit the program

The help and exit commands are defined by default and do not need to be defined in your app. Similarly to command usage staments, module help pages are generated as a standard help page of the style shown above, and can be edited with yourModule.resetHelpPage(String reset) and yourModule.appendHelpPage(String append).

Organizing modules into a Console Client

We can organize our modules into a client by specifying the name of the app and the home module in the constructor, add other modules with addModule(), and run the console app with runConsole().

ConsoleClientclient = newConsoleClient("ExampleEducationApp", math);
client.addModule(quiz);
client.runConsole();

JModule also supports the ability to write non-modular apps. To do this, just throw all your commands into a single module and set it up as the client's home module. The name of the module will not show up in the prompt and the help page will show the app name rather than the home module name.

Customizable CLI prompt

JModule supports the ability to customize the prompt that will be printed to the CLI for each command. As of the current version, there are four possible prompt customization functions.

  • Prompt display name
    By default, the prompt will begin with the specified app name, with its spaces removed. However, we can change this if we'd like. For example, we can change the app name of our example application to shorten the name and include the version.

    client.setPromptDisplayName("ExampleApp-v1.0");

    Our prompt in its home module will now print as ExampleApp-v1.0: math$.
    This is useful to add version information that is not specified in the app name, or to shorten the name that is printed to the CLI.

  • Separators
    By default, JModule apps separate the app name from the module name with ": ", a colon followed by a space. We can change this from our app. For example, let's change the separator to a slash with no space in our app.

    client.setModuleSeparator("/");

    Our prompt in its home module will now display as ExampleApp-v1.0/math$.
    We can also change the prompt/user input separator. By default, JModule sets this to "$". We can change this in our example application.

    client.setPromptSeparator(">");

    Our prompt in its home module will now print as ExampleApp-v1.0/math> . The app will automatically print a space after the prompt to separate it from user input.

  • History index display
    JModule apps with history logging enabled can also display the history index to the CLI, much like the bash command prompt. Enable this by using

    client.setHistoryIndexDisplayEnabled(true);

    This will print the number of previous commands, preceded by a space, after the appname and module and before the prompt separator. Adding this line to our code, out prompt will now print as ExampleApp-v1.0/math 0>
    Note: in order to enable this function, history logging must be enabled (see below).

Further customizations

JModule apps can be customized to include a number of functionalities that could be useful to the user of your application. They are all set to false by default, but can be enabled with simple functions.

  • History Logging
    client.enableHistoryLogging(true);
    Enable history logging to allow the user to cycle through their previous commands using the ↑ and ↓ arrows. Whatever characters they have typed before toggling back through their history will be preserved if they toggle back to their current location. Enabling this funciton also allows you to enable history index display on the prompt.
  • Tab Completion
    client.enableTabCompletion(true);
    Enabling tab completion allows the user to use the tab key to cycle through possible commands in their current module that start with what they already have typed on the command line. If the user hasn't typed anything, tab will cycle through all of the possible commands in the current module.
  • Alerts
    client.enableAlerts(true);
    Enabling alerts allows the app to trigger the system's default alert (typically a sound such as this one). These alerts are triggered any time the user uses a special key that is unable to have any effect on the CLI. For example, an alert could trigger when the user presses delete with no characters typed in, or using tab toggling when no commands match what they've currently typed.

To take an in-depth look at the fully implemented example application, ExampleApp.java is outfitted with helpful comments and defines all its logic classes in the same file for readability.

Planned future updates

  • More flexible parameter options
    • Separate class for parameters
    • Mandatory and optional toggles for parameters
  • Ability for the developer to implement custom keylisteners using the JModule API
  • Ability to hide input for certain commands, such as passwords
  • Windows compatibility
  • JUnit tests

Documentation

The source code Javadoc for JModule (as of version 1.3.0) can be found here. I update the Javadoc with every major version release.

Older versions

Contact

Thanks for checking out JModule! Feel free to contact me at pierce@kelaita.com with any questions or suggestions, or if you want to contribute.

About

Java CLI development library that lets you organize commands by functionality. Supports tab completion, history toggling, chained commands, and other useful features

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

45 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

JModule

JModule is a simple, lightweight Java library written to help people easily write clean, organized, and highly customizable command-line applications. JModule works by running a console client containing multiple modules, each containing their own commands. The application user can switch between modules to access their commands and can view a customizable help page for each module. This design allows the developer to organize commands by functionality, leading to an cleaner flow and easier overall user experience.

Features

  • Commands can be organized into modules
  • A range of customizations that can be accessed through simple functions
  • Tab completion
  • History toggling with ↑ and ↓
  • Insert mode using ← and →
  • Chained commands using ;
  • Options (such as -v, --verbose, etc.)
  • Indefinite/Bounded number of required parameters for commands.
  • JModule implements its own keylistener (not Java.awt), allowing for it to detect individual bytes passed through the command line.
  • Fully-fledged example application, documentation, and usage guide.

As of the current version v1.3.1, JModule is optimized to run on *nix systems and does not yet support the windows command prompt. However, in future versions I plan to add Windows compatiblity.

Getting started

Setting up

In order to use JModule, download the latest version of JModule.jar from the releases page and add the jar to your preferred classpath. JModule's API is contained in two packages: com.jmodule.def (defining commands and parameters) and com.jmodule.exec (organization and execution of commands). The following classes are essential to use JModule

importcom.jmodule.def.Command;
importcom.jmodule.def.CommandLogic;
importcom.jmodule.exec.ConsoleClient;
importcom.jmodule.exec.Module;

The remaining classes are optional and add deeper functionality into the JModule API

importcom.jmodule.def.BoundedCommand;
importcom.jmodule.def.IndefiniteCommand; importcom.jmodule.def.Option;

Example App

JModule includes an Example Application, which is a simple arithmetic program that utilizes most of the functionality in JModule's API. To get a feel for the flow of a JModule application, make sure you have JModule in your classpath, clone or download the file, compile the java file using ~$ javac ExampleApp.java and run it with ~$ java ExampleApp. The code is full of comments to help you understand JModule's functionality. Feel free to modify or use the example code as a reference for writing your own JModule applications.

Writing a JModule application

Commands

Each JModule command runs based on its own command logic, an abstract class that must override the method execute(String[] args) to define the command's execution. A command is instantiated with a name, a description and logic. The simplest command will just have logic and no parameters

CommandhelloCmd = newCommand("hello", "Says hello", newCommandLogic() {
@Overridepublicvoidexecute(String[] args) {
System.out.println("Hello world!");
}
});

The command's name (converted to lowercase and with spaces removed) acts as its default command-line reference. We can also add alternate references to any command. This can be done with the following method

helloCmd.addReference("greet");

Our hello command can now be called from the console by either typing hello or greet.

~$ hello
Hello world!
~$ greet
Hello world!

Parameters

We can also add some parameters to our command by defining them as a String[] in the CommandLogic constructor.

CommandhelloCmd = newCommand("hello", "Says hello to the user", newCommandLogic(
newString[] {
"first name",
"last name"
}) {
@Overridepublicvoidexecute(String[] args) {
System.out.println("Hello, " + args[0] + " " + args[1] + "!");
}
});
helloCmd.addReference("greet");

Parameters in regular command are required by user input for the command to run. When commands are executed with an incorrect number of parameters or the module help page is accessed, the command's usage information will print to the console. Since we added a few parameters and an alternate reference to our hello command, the generated usage statement will be as follows.

Usage: ~$ hello <first name> <last name>
OR greet ~

If we want to change or add to the default usage statement, the method yourCommand.resetUsage(String reset) can be used to replace the default usage statement with your own statement, and the method yourCommand.appendUsage(String append) can be used to add a new line to the existing usage statement. To see examples of these implementations, consult the Example App.

Options

Sometimes we may want to give the user the choice to change the functionality of a command without having to worry about creating new commands or parameters. This can be done through the use of options. Options are arguments passed to the command that do not count as parameters and can be used anywhere in the command's parameters. They are, by nature, optional. Each option is initialized with a one-character flag, denoted by a single dash (for example, -v) and a description that shows up in the enclosing command's usage statement. Additionally, just like Commands, Options can have any number of references added to it. References can be multiple characters, are typically full words or word fragments, and are denoted by a double dash (for example, --ver or --verbose). An option can have any number of references added to it, and references are reccommended but not required. Options are added to CommandLogic with the function addOption(Option o) and their behavior can be defined within CommandLogic.execute(String[] args) by using the boolean if (onOption(char flag)) { /*logic here */ } that will return true if the option exists in the command and is called by the user. As an example, let's write a 'goodbye' command with a few options.

CommandgoodbyeCmd = newCommand("goodbye", "Says goodbye", newCommandLogic() {
@Overridepublicvoidexecute(String[] args) {
if (onOption('p')) {
System.out.print("Farewell, ");
} else {
System.out.print("Goodbye, ");
}
if (onOption('d')) {
System.out.print("cruel ");
}
System.out.print("world!\n\n");
}
}.addOption(newOption('p', "Makes the message polite")
.addReference("polite"))
.addOption(newOption('d', "Makes the message depressing")
.addReference("dep")
.addReference("depressing"))
);

Our 'goodbye' command can be tested from the command line as so:

~$ goodbye
Goodbye, world!
~$ goodbye -p
Farewell, world!
~$ goodbye --polite
Farewell, world!
~$ goodbye --depressing
Goodbye, cruel world!
~$ goodbye -d -p
Farewell, cruel world!

JModule also supports calling multiple options quickly by combining flags as such

~$ goodbye -dp
Farewell, cruel world!

Just like parameters, options added to commands will show up in the command's usage statement.

'goodbye'
Says goodbye
Usage: ~$ goodbye
Options:
-p, --polite: Makes the message polite
-d, --dep, --depressing: Makes the message depressing

Indefinite and Bounded Commands

Sometimes we may want our commands to have an undefined number or a range of numbers of possible parameters. In order to do this, we can either use an Indefinite Command, Bounded Command with an open range, or a Bounded Command with a closed range. These commands should have their parameters defined in their logic's constructor, but they will have no effect on the execution of the command; they will only affect the command's usage statement.

Indefinite Commands

Indefinite commands may have any number of parameters passed to it by the user. An example implementation of this would be a command to list names

CommandlistCmd = newIndefiniteCommand("list", "Lists given names, separated by commas", newCommandLogic(
newString[] {
"names..."
}) {
@Overridepublicvoidexecute(String[] args) {
for (inti = 0; i < args.length; i++) {
System.out.print(args[i]);
if (i != args.length - 1) {
System.out.print(", ");
}
}
}
});

Bounded Commands with open parameters

Bounded commands with open parameters are instantiated the same way as Indefinite or regular Commands, but a minimum and maximum number of parameters is specified. To have a bounded command with open parameters, specify only the minimum at the end of the BoundedCommand constructor. For example, a function to list the names of people all with the same last name, where the last name is taken as the first parameter, would have a minimum of two parameters and could look something like this.

CommandfamCmd = newBoundedCommand("famlist", "Lists the full names of family members who all have the same last name", newCommandLogic(
newString[] {
"family name",
"first names..."
}) {
@Overridepublicvoidexecute(String[] args) {
StringfamilyName = args[0];
for (inti = 1; i < args.length; i++) {
System.out.print(args[i] + " " + familyName);
if (i != args.length - 1) {
System.out.print(", ");
}
}
}
}, 2); // set minimum number of parameters to 2

Bounded Commands with closed parameters

Bounded commands can also have closed parameters, meaning both a minimum and maximum number of parameters is specified at the end of the constructor. An example would be a function that multiplies up to 4 numbers.

CommandmultCmd = newBoundedCommand("multiply", "Multiplies up to 4 numbers", newCommandLogic(
newString[] {
"factors..."
}) {
@Overridepublicvoidexecute(String[] args) {
intproduct = 0;
try {
for (Stringstr : args) {
intfactor = Integer.parseInt(str);
product *= factor;
}
System.out.print("Product: " + product);
...
}
}, 0, 4); // set minimum number of parameters to 0 and maximum to 4

Creating a custom control flow

Organizing Commands into Modules

Useful command-line applications will have a variety of possible commands. Let's assume that we've created a few arithmetic commands, a 'quizme' command taking no parameters that asks you simple math questions, and an 'info' command that reports how well you've done on past quizzes. The implementations for these commands can all be found in ExampleApp.java. To ease the user experience, we can organize commands with similar functionality into their own modules. In this instance, let's put the arithmetic commands in one module called math and the 'info' and 'quizme' commands in another module called quiz.

Modulemath = newModule("math");
math.addCommand(addCmd);
math.addCommand(subCmd);
math.addCommand(multCmd);
Modulequiz = newModule("quiz");
quiz.addCommand(quizCmd);
quiz.addCommand(infoCmd);

Each command has a name and description defined on instantiation, and a usage statement defined by its parameters and/or user customization. These statements are all helpful, and can be viewed by using the help command in the console.

~ math $ help
MATH -- POSSIBLE COMMANDS
'add'
Adds 2 numbers together
Usage: ~$ add <first number> <second number>
'subtract'
Subtracts 2 numbers
Usage: ~$ subtract <first number> <second number>
OR sub ~
'multiply'
Multiplies 2 or more numbers
Usage: ~$ multiply <First number> <Factors...>
OR mult ~
OR mul ~
'help'
Displays the help page for the current module.
Usage: ~$ help
Type the name of another module to switch to that module:
- 'quiz'
Type 'exit' at any time to exit the program

The help and exit commands are defined by default and do not need to be defined in your app. Similarly to command usage staments, module help pages are generated as a standard help page of the style shown above, and can be edited with yourModule.resetHelpPage(String reset) and yourModule.appendHelpPage(String append).

Organizing modules into a Console Client

We can organize our modules into a client by specifying the name of the app and the home module in the constructor, add other modules with addModule(), and run the console app with runConsole().

ConsoleClientclient = newConsoleClient("ExampleEducationApp", math);
client.addModule(quiz);
client.runConsole();

JModule also supports the ability to write non-modular apps. To do this, just throw all your commands into a single module and set it up as the client's home module. The name of the module will not show up in the prompt and the help page will show the app name rather than the home module name.

Customizable CLI prompt

JModule supports the ability to customize the prompt that will be printed to the CLI for each command. As of the current version, there are four possible prompt customization functions.

  • Prompt display name
    By default, the prompt will begin with the specified app name, with its spaces removed. However, we can change this if we'd like. For example, we can change the app name of our example application to shorten the name and include the version.

    client.setPromptDisplayName("ExampleApp-v1.0");

    Our prompt in its home module will now print as ExampleApp-v1.0: math$.
    This is useful to add version information that is not specified in the app name, or to shorten the name that is printed to the CLI.

  • Separators
    By default, JModule apps separate the app name from the module name with ": ", a colon followed by a space. We can change this from our app. For example, let's change the separator to a slash with no space in our app.

    client.setModuleSeparator("/");

    Our prompt in its home module will now display as ExampleApp-v1.0/math$.
    We can also change the prompt/user input separator. By default, JModule sets this to "$". We can change this in our example application.

    client.setPromptSeparator(">");

    Our prompt in its home module will now print as ExampleApp-v1.0/math> . The app will automatically print a space after the prompt to separate it from user input.

  • History index display
    JModule apps with history logging enabled can also display the history index to the CLI, much like the bash command prompt. Enable this by using

    client.setHistoryIndexDisplayEnabled(true);

    This will print the number of previous commands, preceded by a space, after the appname and module and before the prompt separator. Adding this line to our code, out prompt will now print as ExampleApp-v1.0/math 0>
    Note: in order to enable this function, history logging must be enabled (see below).

Further customizations

JModule apps can be customized to include a number of functionalities that could be useful to the user of your application. They are all set to false by default, but can be enabled with simple functions.

  • History Logging
    client.enableHistoryLogging(true);
    Enable history logging to allow the user to cycle through their previous commands using the ↑ and ↓ arrows. Whatever characters they have typed before toggling back through their history will be preserved if they toggle back to their current location. Enabling this funciton also allows you to enable history index display on the prompt.
  • Tab Completion
    client.enableTabCompletion(true);
    Enabling tab completion allows the user to use the tab key to cycle through possible commands in their current module that start with what they already have typed on the command line. If the user hasn't typed anything, tab will cycle through all of the possible commands in the current module.
  • Alerts
    client.enableAlerts(true);
    Enabling alerts allows the app to trigger the system's default alert (typically a sound such as this one). These alerts are triggered any time the user uses a special key that is unable to have any effect on the CLI. For example, an alert could trigger when the user presses delete with no characters typed in, or using tab toggling when no commands match what they've currently typed.

To take an in-depth look at the fully implemented example application, ExampleApp.java is outfitted with helpful comments and defines all its logic classes in the same file for readability.

Planned future updates

  • More flexible parameter options
    • Separate class for parameters
    • Mandatory and optional toggles for parameters
  • Ability for the developer to implement custom keylisteners using the JModule API
  • Ability to hide input for certain commands, such as passwords
  • Windows compatibility
  • JUnit tests

Documentation

The source code Javadoc for JModule (as of version 1.3.0) can be found here. I update the Javadoc with every major version release.

Older versions

Contact

Thanks for checking out JModule! Feel free to contact me at pierce@kelaita.com with any questions or suggestions, or if you want to contribute.

About

Java CLI development library that lets you organize commands by functionality. Supports tab completion, history toggling, chained commands, and other useful features

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages