From: Svjatoslav Agejenko
{@code
+ * // Simple boolean prompt with default
* Boolean proceed = CLIHelper.askBoolean("Continue?", true);
- * Integer betweenFiveAndTen = CLIHelper.askInteger("Pick 5â10", 7, 5, 10, false);
+ * // Console: "Continue? [true]: "
+ *
+ * // Integer with range validation
+ * Integer port = CLIHelper.askInteger("Port number", 8080, 1, 65535, false);
+ * // Console: "Port number [8080]: " - user must enter 1-65535
+ *
+ * // String with length constraints
* String name = CLIHelper.askString("Your name", "Anonymous", 2, 20, false);
+ * // Console: "Your name [Anonymous]: " - must be 2-20 characters
+ *
+ * // File that must exist and be readable
+ * File input = CLIHelper.askFile("Input file", null, true, true, null, null, false);
+ * // Console: "Input file: " - validates file exists and is readable
+ *
+ * // Directory selection
+ * File outputDir = CLIHelper.askDirectory("Output directory", new File("."));
+ * // Console: "Output directory [.]: "
+ *
+ * // Building a simple interactive CLI
+ * public static void main(String[] args) {
+ * System.out.println("Welcome to the installer!");
+ *
+ * if (!CLIHelper.askBoolean("Proceed with installation?", true)) {
+ * System.out.println("Installation cancelled.");
+ * return;
+ * }
+ *
+ * File targetDir = CLIHelper.askDirectory(
+ * "Installation directory",
+ * new File("/opt/myapp"),
+ * false, // doesn't need to exist yet
+ * null, null, null, false
+ * );
+ *
+ * int threads = CLIHelper.askInteger("Number of threads", 4, 1, 16, false);
+ *
+ * System.out.println("Installing to: " + targetDir);
+ * System.out.println("Using " + threads + " threads");
+ * }
* }
*/
public final class CLIHelper {
@@ -57,12 +95,12 @@ public final class CLIHelper {
*
* * Behavior for empty input: + *
** Behavior for empty input: + *
** Bounds are inclusive. If {@code min} or {@code max} is {@code null}, no check is applied. *
@@ -203,12 +241,12 @@ public final class CLIHelper { * Prompts the user for an {@code int} value within optional bounds. ** Behavior for empty input: + *
** Bounds are inclusive. If {@code min} or {@code max} is {@code null}, no check is applied. *
@@ -335,12 +373,12 @@ public final class CLIHelper { * Prompts the user for a string value with optional length constraints. ** Behavior for empty input: + *
** Length constraints are inclusive. If {@code minLength} or {@code maxLength} is {@code null}, no check is applied. *
@@ -405,21 +443,21 @@ public final class CLIHelper { * ** Behavior for empty input: + *
** Attribute checks: + *
** Prompts the user to select an option from a list using fuzzy matching and cursor keys with scrolling support. + *
** Important: This functionality requires the terminal to be in raw mode for proper operation. * On Unix-like systems, this is automatically handled by setting terminal modes. @@ -23,6 +26,47 @@ import static java.lang.Math.min; * or Escape to cancel the selection. For long lists, the menu automatically scrolls to keep the * selection visible within the terminal's vertical limits. *
+ * + *{@code
+ * import eu.svjatoslav.commons.cli_helper.Menu;
+ * import java.io.IOException;
+ * import java.util.Arrays;
+ * import java.util.List;
+ *
+ * public class MenuExample {
+ * public static void main(String[] args) throws IOException {
+ * List fruits = Arrays.asList(
+ * "Apple", "Banana", "Cherry", "Date", "Elderberry",
+ * "Fig", "Grape", "Honeydew", "Kiwi", "Lemon"
+ * );
+ *
+ * String selected = Menu.askSelection("Select a fruit", fruits);
+ *
+ * if (selected != null) {
+ * System.out.println("You selected: " + selected);
+ * } else {
+ * System.out.println("Selection cancelled.");
+ * }
+ * }
+ * }
+ * }
+ *
+ * + * If the list contains {@code ["Apple", "Banana", "Cherry"]} and the user types "ap", + * only "Apple" will be shown (fuzzy match). If they type "a", both "Apple" and "Banana" + * will be shown (both contain 'a'). + *
+ * + ** Represents a command-line option (a flag or switch), which can be configured with: + *
*{@code
+ * Parser parser = new Parser();
+ *
+ * // All option types support fluent configuration
+ * FileOption input = parser.add(
+ * new FileOption("Input file")
+ * .addAliases("-i", "--input") // add multiple aliases
+ * .mustExist() // file must exist on disk
+ * .setMandatory() // option is required
+ * );
+ *
+ * // Options can be created with aliases in constructor too
+ * StringOption name = parser.add(
+ * new StringOption("User name", "defaultName")
+ * .addAliases("-n", "--name")
+ * );
+ * // name.getValue() returns "defaultName" if not specified on CLI
+ *
+ * // Check if an option was present
+ * if (input.isPresent()) {
+ * System.out.println("Input: " + input.getValue());
+ * }
+ * }
+ *
* @param * Defines how many parameters a command-line option can accept. + *
+ * + *{@code
+ * // NONE - for boolean flags
+ * new NullOption("Enable debug mode") // uses ParameterCount.NONE
+ *
+ * // ONE - for single-value options
+ * new StringOption("Output file") // uses ParameterCount.ONE
+ * new IntegerOption("Port number") // uses ParameterCount.ONE
+ * new FileOption("Input file") // uses ParameterCount.ONE
+ *
+ * // ONE_OR_MORE - for multi-value options
+ * new StringOptions("List of names") // uses ParameterCount.ONE_OR_MORE
+ * new FileOptions("Input files") // uses ParameterCount.ONE_OR_MORE
+ * }
*/
public enum ParameterCount {
/** Option has no parameters. */
diff --git a/src/main/java/eu/svjatoslav/commons/cli_helper/parameter_parser/Parser.java b/src/main/java/eu/svjatoslav/commons/cli_helper/parameter_parser/Parser.java
index 23a6333..7e8a7b7 100755
--- a/src/main/java/eu/svjatoslav/commons/cli_helper/parameter_parser/Parser.java
+++ b/src/main/java/eu/svjatoslav/commons/cli_helper/parameter_parser/Parser.java
@@ -8,20 +8,83 @@ import java.util.ArrayList;
import java.util.List;
/**
- * This class parses command-line arguments against a set of registered {@link Option} objects.
+ * - * Usage: + * This class parses command-line arguments against a set of registered {@link Option} objects. + * The parser supports fluent API for registering options, automatic validation of mandatory + * options, and generates help text showing all available options. + *
+ * + *{@code
+ * import eu.svjatoslav.commons.cli_helper.parameter_parser.Parser;
+ * import eu.svjatoslav.commons.cli_helper.parameter_parser.parameter.*;
+ * import java.io.File;
+ * import java.util.List;
+ *
+ * public class MyApp {
+ * public static void main(String[] args) {
+ * Parser parser = new Parser();
+ *
+ * // Register options with fluent API
+ * FileOption inputOpt = parser.add(
+ * new FileOption("Input file to process")
+ * .addAliases("-i", "--input")
+ * .mustExist()
+ * .setMandatory()
+ * );
+ *
+ * FileOption outputOpt = parser.add(
+ * new FileOption("Output file")
+ * .addAliases("-o", "--output")
+ * .mustNotExist()
+ * );
+ *
+ * IntegerOption verboseOpt = parser.add(
+ * new IntegerOption("Verbosity level (0-3)")
+ * .addAliases("-v", "--verbose")
+ * );
+ *
+ * NullOption helpOpt = parser.add(
+ * new NullOption("Show this help message")
+ * .addAliases("-h", "--help")
+ * );
+ *
+ * StringOptions filesOpt = parser.add(
+ * new StringOptions("Additional files to process")
+ * .addAliases("--files")
+ * );
+ *
+ * // Parse arguments
+ * if (!parser.parse(args) || helpOpt.getValue()) {
+ * parser.showHelp();
+ * System.exit(helpOpt.getValue() ? 0 : 1);
+ * }
+ *
+ * // Use the parsed values
+ * File input = inputOpt.getValue();
+ * File output = outputOpt.getValue(); // may be null if not specified
+ * int verbosity = verboseOpt.getValue(); // may throw if not specified
+ * List extraFiles = filesOpt.getValue();
+ *
+ * System.out.println("Processing: " + input);
+ * }
+ * }
+ * }
+ *
+ * {@code
- * Parser parser = new Parser();
- * FileOption fileOpt = parser.add(new FileOption("Input file").mustExist())
- * .addAliases("-f", "--file")
- * .setMandatory();
- * parser.parse(args);
- * if (!parser.checkSuccess()) {
- * parser.showHelp();
- * System.exit(1);
- * }
- * File inputFile = fileOpt.getValue();
+ * // Basic usage
+ * java MyApp -i input.txt -o output.txt
+ *
+ * // With verbosity
+ * java MyApp -i input.txt -v 2
+ *
+ * // Multiple values for ONE_OR_MORE option
+ * java MyApp -i input.txt --files file1.txt file2.txt file3.txt
+ *
+ * // Show help
+ * java MyApp --help
* }
*/
public class Parser {
@@ -59,12 +122,12 @@ public class Parser {
* Parses command-line arguments against registered options and their parameters.
* * Processes arguments sequentially: + *
** Represents a command-line option that accepts exactly one parameter interpreted as a directory path. + *
+ * + *{@code
+ * Parser parser = new Parser();
+ *
+ * // Source directory must exist
+ * DirectoryOption source = parser.add(
+ * new DirectoryOption("Source directory")
+ * .addAliases("-s", "--source")
+ * .mustExist()
+ * .setMandatory()
+ * );
+ *
+ * // Output directory should not exist
+ * DirectoryOption output = parser.add(
+ * new DirectoryOption("Output directory")
+ * .addAliases("-o", "--output")
+ * .mustNotExist()
+ * );
+ *
+ * // Working directory - existence optional
+ * DirectoryOption workDir = parser.add(
+ * new DirectoryOption("Working directory")
+ * .addAliases("-w", "--workdir")
+ * );
+ *
+ * parser.parse(args);
+ *
+ * File sourceDir = source.getValue(); // guaranteed to exist and be a directory
+ * File outputDir = output.getValue(); // null if not specified
+ * File workingDir = workDir.getValue(); // may or may not exist
+ * }
+ *
+ * {@code
+ * java MyApp --source /path/to/input --output /path/to/newdir
+ * java MyApp -s ./src -o ./build
+ * java MyApp --source existing_dir
+ * }
+ *
+ * + * Directory validation happens during parsing: + *
+ *{@code
+ * java MyApp --source nonexistent/
+ * // Output: Error! Invalid parameter "nonexistent/". It should be Existing directory.
+ *
+ * java MyApp --source regular_file.txt
+ * // Output: Error! Invalid parameter "regular_file.txt". It should be Existing directory.
+ * }
*/
public class DirectoryOption extends Option* Represents a command-line option that accepts one or more directory paths. + *
+ * + *{@code
+ * Parser parser = new Parser();
+ *
+ * // Multiple source directories that must exist
+ * DirectoryOptions sources = parser.add(
+ * new DirectoryOptions("Source directories to scan")
+ * .addAliases("--sources")
+ * .mustExist()
+ * .setMandatory()
+ * );
+ *
+ * // Multiple output directories that should not exist
+ * DirectoryOptions outputs = parser.add(
+ * new DirectoryOptions("Output directories to create")
+ * .addAliases("--outputs")
+ * .mustNotExist()
+ * );
+ *
+ * parser.parse(args);
+ *
+ * List sourceDirs = sources.getValue(); // at least one directory
+ * List outputDirs = outputs.getValue(); // may be empty
+ *
+ * for (File dir : sourceDirs) {
+ * System.out.println("Scanning: " + dir.getAbsolutePath());
+ * }
+ * }
+ *
+ * {@code
+ * java MyApp --sources src/main src/test
+ * // sourceDirs = [src/main, src/test]
+ *
+ * java MyApp --sources project1/src project2/src --outputs build/
+ * // Multiple source dirs and one output dir
+ * }
*/
public class DirectoryOptions extends Option* Defines whether a file/directory resource must exist, must not exist, or if it does not matter. + *
+ * + *{@code
+ * // MUST_EXIST - file must already exist (e.g., input file)
+ * FileOption input = new FileOption("Input file").mustExist();
+ *
+ * // MUST_NOT_EXIST - file must not exist (e.g., output file to avoid overwriting)
+ * FileOption output = new FileOption("Output file").mustNotExist();
+ *
+ * // DOES_NOT_MATTER - default, no existence check (e.g., optional config)
+ * FileOption config = new FileOption("Config file"); // default behavior
+ * }
+ *
+ * {@code
+ * // MUST_EXIST - directory must exist (e.g., source directory)
+ * DirectoryOption source = new DirectoryOption("Source dir").mustExist();
+ *
+ * // MUST_NOT_EXIST - directory must not exist (e.g., new project directory)
+ * DirectoryOption project = new DirectoryOption("New project dir").mustNotExist();
+ * }
*/
public enum ExistenceType {
/** Resource shall exist. */
diff --git a/src/main/java/eu/svjatoslav/commons/cli_helper/parameter_parser/parameter/FileOption.java b/src/main/java/eu/svjatoslav/commons/cli_helper/parameter_parser/parameter/FileOption.java
index 00c295d..012ef8d 100755
--- a/src/main/java/eu/svjatoslav/commons/cli_helper/parameter_parser/parameter/FileOption.java
+++ b/src/main/java/eu/svjatoslav/commons/cli_helper/parameter_parser/parameter/FileOption.java
@@ -10,8 +10,62 @@ import eu.svjatoslav.commons.cli_helper.parameter_parser.Option;
import java.io.File;
/**
+ * * Represents a command-line option that accepts exactly one parameter which is interpreted as a file path. * By default, {@link ExistenceType#DOES_NOT_MATTER} is used (i.e., the file may or may not exist). + *
+ * + *{@code
+ * Parser parser = new Parser();
+ *
+ * // Input file must exist
+ * FileOption input = parser.add(
+ * new FileOption("Input file to process")
+ * .addAliases("-i", "--input")
+ * .mustExist()
+ * .setMandatory()
+ * );
+ *
+ * // Output file must NOT exist (don't overwrite)
+ * FileOption output = parser.add(
+ * new FileOption("Output file")
+ * .addAliases("-o", "--output")
+ * .mustNotExist()
+ * );
+ *
+ * // Config file - existence doesn't matter
+ * FileOption config = parser.add(
+ * new FileOption("Configuration file")
+ * .addAliases("-c", "--config")
+ * );
+ *
+ * parser.parse(args);
+ *
+ * File inputFile = input.getValue(); // guaranteed to exist
+ * File outputFile = output.getValue(); // null if not specified
+ * File configFile = config.getValue(); // may or may not exist
+ * }
+ *
+ * {@code
+ * java MyApp --input data.txt --output result.txt
+ * java MyApp -i /path/to/file.txt
+ * java MyApp --input existing.txt --output newfile.txt
+ * }
+ *
+ * + * File validation happens during parsing: + *
+ *{@code
+ * java MyApp --input nonexistent.txt
+ * // Output: Error! Invalid parameter "nonexistent.txt". It should be Existing file.
+ *
+ * java MyApp --output existing.txt
+ * // Output: Error! Invalid parameter "existing.txt". It should be Non-existing file.
+ * }
*/
public class FileOption extends Option* Represents a command-line option that accepts one or more file paths. + *
+ * + *{@code
+ * Parser parser = new Parser();
+ *
+ * // Multiple input files that must exist
+ * FileOptions inputs = parser.add(
+ * new FileOptions("Input files to process")
+ * .addAliases("--inputs")
+ * .mustExist()
+ * .setMandatory()
+ * );
+ *
+ * // Multiple output files that should not exist
+ * FileOptions outputs = parser.add(
+ * new FileOptions("Output files to create")
+ * .addAliases("--outputs")
+ * .mustNotExist()
+ * );
+ *
+ * parser.parse(args);
+ *
+ * List inputFiles = inputs.getValue(); // at least one file
+ * List outputFiles = outputs.getValue(); // may be empty
+ *
+ * for (File input : inputFiles) {
+ * System.out.println("Processing: " + input.getName());
+ * }
+ * }
+ *
+ * {@code
+ * java MyApp --inputs file1.txt file2.txt file3.txt
+ * // inputFiles = [file1.txt, file2.txt, file3.txt]
+ *
+ * java MyApp --inputs src/*.java --outputs out/
+ * // Input files matched by shell glob, output directory specified
+ *
+ * java MyApp --inputs a.txt b.txt --outputs x.txt y.txt
+ * // inputs = [a.txt, b.txt], outputs = [x.txt, y.txt]
+ * }
*/
public class FileOptions extends Option* Represents a command-line option that accepts exactly one floating-point parameter. + *
+ * + *{@code
+ * Parser parser = new Parser();
+ *
+ * FloatOption scale = parser.add(
+ * new FloatOption("Scale factor")
+ * .addAliases("-s", "--scale")
+ * .setMandatory()
+ * );
+ *
+ * FloatOption threshold = parser.add(
+ * new FloatOption("Detection threshold")
+ * .addAliases("--threshold")
+ * );
+ *
+ * FloatOption rate = parser.add(
+ * new FloatOption("Learning rate")
+ * .addAliases("-r", "--rate")
+ * );
+ *
+ * parser.parse(args);
+ *
+ * float scaleFactor = scale.getValue(); // required
+ * Float thresholdValue = threshold.getValue(); // may throw if missing
+ *
+ * System.out.println("Scale: " + scaleFactor);
+ * System.out.println("Threshold: " + thresholdValue);
+ * }
+ *
+ * {@code
+ * java MyApp --scale 1.5 // scale=1.5
+ * java MyApp -s 2.0 --threshold 0.75 // scale=2.0, threshold=0.75
+ * java MyApp --scale 1.5 -r 0.01 --threshold 0.9
+ * }
+ *
+ * + * Invalid floats are rejected during parsing: + *
+ *{@code
+ * java MyApp --scale abc
+ * // Output: Error! Invalid parameter "abc". It should be Floating point number. Example: 3.14
+ * }
*/
public class FloatOption extends Option* Represents a command-line option that accepts exactly one parameter interpreted as an integer. + *
+ * + *{@code
+ * Parser parser = new Parser();
+ *
+ * IntegerOption port = parser.add(
+ * new IntegerOption("Server port")
+ * .addAliases("-p", "--port")
+ * .setMandatory()
+ * );
+ *
+ * IntegerOption timeout = parser.add(
+ * new IntegerOption("Timeout in seconds")
+ * .addAliases("-t", "--timeout")
+ * );
+ *
+ * IntegerOption retries = parser.add(
+ * new IntegerOption("Number of retry attempts")
+ * .addAliases("-r", "--retries")
+ * );
+ *
+ * parser.parse(args);
+ *
+ * int portNumber = port.getValue(); // required, will throw if missing
+ * Integer timeoutValue = timeout.getValue(); // may throw if missing
+ * Integer retryCount = retries.getValue(); // may throw if missing
+ *
+ * System.out.println("Connecting to port: " + portNumber);
+ * }
+ *
+ * {@code
+ * java MyApp --port 8080 // port=8080
+ * java MyApp -p 3000 -t 30 // port=3000, timeout=30
+ * java MyApp --port 8080 --timeout 60 -r 3 // all options specified
+ * }
+ *
+ * + * Invalid integers are rejected during parsing: + *
+ *{@code
+ * java MyApp --port abc
+ * // Output: Error! Invalid parameter "abc". It should be Integer.
+ * }
*/
public class IntegerOption extends Option* Represents a command-line option that accepts exactly zero parameters. * Often used for flags that are either present or absent. + *
+ * + *{@code
+ * Parser parser = new Parser();
+ *
+ * NullOption verbose = parser.add(
+ * new NullOption("Enable verbose output")
+ * .addAliases("-v", "--verbose")
+ * );
+ *
+ * NullOption help = parser.add(
+ * new NullOption("Show help message")
+ * .addAliases("-h", "--help")
+ * );
+ *
+ * parser.parse(args);
+ *
+ * // Check if flag was present
+ * if (help.getValue()) {
+ * parser.showHelp();
+ * System.exit(0);
+ * }
+ *
+ * if (verbose.getValue()) {
+ * System.out.println("Verbose mode enabled");
+ * }
+ * }
+ *
+ * {@code
+ * java MyApp --verbose // verbose.getValue() returns true
+ * java MyApp -v -h // both flags are true
+ * java MyApp // both flags are false
+ * }
*/
public class NullOption extends Option* Represents a command-line option that accepts exactly one string parameter. * An optional default value can be provided; if so, this option is considered "present" even without user input. + *
+ * + *{@code
+ * Parser parser = new Parser();
+ *
+ * // Without default value
+ * StringOption name = parser.add(
+ * new StringOption("User name")
+ * .addAliases("-n", "--name")
+ * .setMandatory()
+ * );
+ *
+ * // With default value
+ * StringOption format = parser.add(
+ * new StringOption("Output format", "json") // defaults to "json"
+ * .addAliases("-f", "--format")
+ * );
+ *
+ * parser.parse(args);
+ *
+ * String userName = name.getValue(); // throws if not specified
+ * String outputFormat = format.getValue(); // returns "json" if not specified
+ *
+ * System.out.println("User: " + userName);
+ * System.out.println("Format: " + outputFormat);
+ * }
+ *
+ * {@code
+ * java MyApp -n Alice // name="Alice", format="json"
+ * java MyApp --name Bob --format xml // name="Bob", format="xml"
+ * java MyApp -n Charlie // name="Charlie", format="json" (default)
+ * }
*/
public class StringOption extends Option* Represents a command-line option that accepts one or more string parameters. + *
+ * + *{@code
+ * Parser parser = new Parser();
+ *
+ * StringOptions tags = parser.add(
+ * new StringOptions("Tags to apply")
+ * .addAliases("--tags")
+ * );
+ *
+ * StringOptions files = parser.add(
+ * new StringOptions("Files to process")
+ * .addAliases("--files")
+ * .setMandatory()
+ * );
+ *
+ * parser.parse(args);
+ *
+ * List tagList = tags.getValue(); // may be empty if not specified
+ * List fileList = files.getValue(); // at least one file required
+ *
+ * System.out.println("Tags: " + tagList);
+ * for (String file : fileList) {
+ * System.out.println("Processing: " + file);
+ * }
+ * }
+ *
+ * {@code
+ * java MyApp --files input1.txt input2.txt
+ * // fileList = ["input1.txt", "input2.txt"], tagList = []
+ *
+ * java MyApp --files data.txt --tags urgent important
+ * // fileList = ["data.txt"], tagList = ["urgent", "important"]
+ *
+ * java MyApp --tags single --files only.txt
+ * // Note: order matters - tags bound to single file in wrong position
+ * }
*/
public class StringOptions extends Option