diff --git a/pom.xml b/pom.xml index 0bc907e..ef5a54d 100644 --- a/pom.xml +++ b/pom.xml @@ -69,9 +69,9 @@ - org.cactoos - cactoos - 0.57.0 + info.picocli + picocli + 4.7.7 org.junit.jupiter diff --git a/src/main/java/org/eolang/aoi/Application.java b/src/main/java/org/eolang/aoi/Application.java index 775005a..96aa90f 100644 --- a/src/main/java/org/eolang/aoi/Application.java +++ b/src/main/java/org/eolang/aoi/Application.java @@ -4,75 +4,64 @@ */ package org.eolang.aoi; -import java.io.PrintStream; -import java.util.Arrays; -import java.util.List; -import org.cactoos.io.ResourceOf; -import org.cactoos.text.TextOf; +import java.nio.file.Path; +import java.util.concurrent.Callable; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; /** - * Command-line interface for the AOI (Abstract Object Inference) tool. - * - *

This class handles command-line argument parsing and coordinates the execution - * of the AOI tool.

+ * Command-line interface for the AOI tool. + *

+ * Implementing {@link Callable} allows Picocli to treat this class as a command that returns an + * exit code. + *

* * @since 0.0.4 */ -public final class Application { - +@Command( + name = "aoi", + description = { + "AOI analyzes EO programs to infer object types. It finds .xmir files in the", + "input directory and generates .xml files with type information." + }, + descriptionHeading = "%nDescription:%n", + parameterListHeading = "%nArguments:%n", + optionListHeading = "%nOptions:%n" +) +public final class Application implements Callable { /** - * Command-line arguments passed to the application. + * A standard help option that triggers Picocli's built-in help display. */ - private final String[] args; + @Option( + names = "--help", + usageHelp = true, + description = "Print this message and exit." + ) + private boolean help; /** - * Output stream for printing messages and help text. + * The first positional argument, representing the input directory. */ - private final PrintStream out; + @Parameters( + index = "0", + paramLabel = "", + description = "Directory with .xmir files (searches recursively)" + ) + private Path input; /** - * Creates a new application instance. - * - *

The arguments array is defensively copied to prevent external modification.

- * - * @param args Command-line arguments - * @param out Output stream for messages + * The second positional argument, representing the output directory. */ - public Application(final String[] args, final PrintStream out) { - this.args = Arrays.copyOf(args, args.length); - this.out = out; - } + @Parameters( + index = "1", + paramLabel = "", + description = "Directory for .xml files with inferred types" + ) + private Path output; - /** - * Executes the main application logic. - * - *

Note: {@code --help} and {@code --version} flags take precedence over other arguments and - * will be processed even if other arguments are invalid.

- * - * @throws IllegalArgumentException if the number of arguments is not exactly 2 (when neither - * {@code --help} nor {@code --version} is specified) - */ - public void run() { - final List arguments = Arrays.asList(this.args); - if (arguments.contains("--help")) { - this.out.print(new TextOf(new ResourceOf("org/eolang/aoi/help.txt"))); - } else if (arguments.contains("--version")) { - this.out.printf( - "aoi version %s", - new TextOf(new ResourceOf("org/eolang/aoi/version.txt")) - ); - } else { - if (arguments.size() != 2) { - throw new IllegalArgumentException( - "Expected 2 arguments (input dir and output dir), but got %d: %s".formatted( - arguments.size(), - String.join(", ", arguments) - ) - ); - } - this.out.printf( - "Input directory: %s, Output directory: %s%n", this.args[0], this.args[1] - ); - } + @Override + public Integer call() throws Exception { + return 0; } } diff --git a/src/main/java/org/eolang/aoi/Main.java b/src/main/java/org/eolang/aoi/Main.java index 466bc76..9013d32 100644 --- a/src/main/java/org/eolang/aoi/Main.java +++ b/src/main/java/org/eolang/aoi/Main.java @@ -4,6 +4,8 @@ */ package org.eolang.aoi; +import picocli.CommandLine; + /** * Main entry point for the AOI (Abstract Object Inference) application. * @@ -18,11 +20,11 @@ private Main() { } /** - * Runs the application. + * Executes the CLI and terminates the JVM with the resulting exit code. * - * @param args Command-line arguments + * @param args Command-line arguments passed to the application. */ public static void main(final String[] args) { - new Application(args, System.out).run(); + System.exit(new CommandLine(new Application()).execute(args)); } } diff --git a/src/main/resources/org/eolang/aoi/help.txt b/src/main/resources/org/eolang/aoi/help.txt deleted file mode 100644 index d2a180a..0000000 --- a/src/main/resources/org/eolang/aoi/help.txt +++ /dev/null @@ -1,14 +0,0 @@ -AOI - Abstract Object Inference Tool - -Usage: aoi [options] - -AOI analyzes EO programs to infer object types. It finds .xmir files -in the input directory and generates .xml files with type information. - -Arguments: - Directory with .xmir files (searches recursively) - Output directory for .xml files with inferred types - -Options: - --help Print this message and exit. - --version Print the version of aoi and exit.. diff --git a/src/test/java/org/eolang/aoi/ApplicationTest.java b/src/test/java/org/eolang/aoi/ApplicationTest.java new file mode 100644 index 0000000..136e0de --- /dev/null +++ b/src/test/java/org/eolang/aoi/ApplicationTest.java @@ -0,0 +1,110 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com + * SPDX-License-Identifier: MIT + */ +package org.eolang.aoi; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.file.Path; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import picocli.CommandLine; + +/** + * Tests for {@link Application}. + * + * @since 0.0.5 + * @todo #152:15min Enable help flag tests after adding support for the short `-h` flag. + * Once implemented, remove @Disabled from the following tests: + * {@link ApplicationTest#applicationPrintsHelpMessageWhenHelpFlagIsPassed}, + * {@link ApplicationTest#applicationExitsWithZeroCodeWhenHelpFlagIsPassed} + */ +final class ApplicationTest { + @Test + void applicationExitsWithZeroCodeOnSuccessfulExecution( + @TempDir final Path input, + @TempDir final Path output + ) { + final CommandLine cmd = new CommandLine(new Application()); + final int exit = cmd.execute(input.toString(), output.toString()); + MatcherAssert.assertThat( + "Application did not exit with a zero code on successful execution", + exit, + Matchers.is(0) + ); + } + + @Test + void applicationPrintsNothingToStderrOnSuccessfulExecution( + @TempDir final Path input, + @TempDir final Path output + ) { + final CommandLine cmd = new CommandLine(new Application()); + final StringWriter stderr = new StringWriter(); + cmd.setErr(new PrintWriter(stderr)); + cmd.execute(input.toString(), output.toString()); + MatcherAssert.assertThat( + "Application printed to stderr during a successful execution", + stderr.toString(), + Matchers.emptyString() + ); + } + + @Disabled + @ParameterizedTest + @ValueSource(strings = {"--help", "-h"}) + void applicationPrintsHelpMessageWhenHelpFlagIsPassed(final String flag) { + final CommandLine cmd = new CommandLine(new Application()); + final StringWriter stdout = new StringWriter(); + cmd.setOut(new PrintWriter(stdout)); + cmd.execute(flag); + MatcherAssert.assertThat( + "Application did not print the help message to stdout for the %s flag".formatted(flag), + stdout.toString(), + Matchers.containsString("Usage:") + ); + } + + @Disabled + @ParameterizedTest + @ValueSource(strings = {"--help", "-h"}) + void applicationExitsWithZeroCodeWhenHelpFlagIsPassed(final String flag) { + final CommandLine cmd = new CommandLine(new Application()); + final int exit = cmd.execute(flag); + MatcherAssert.assertThat( + "Application did not exit with a zero code when the %s flag was used".formatted(flag), + exit, + Matchers.is(0) + ); + } + + @Test + void applicationExitsWithNonZeroCodeWhenNoArgumentsAreProvided() { + final CommandLine cmd = new CommandLine(new Application()); + cmd.setErr(new PrintWriter(new StringWriter())); + final int exit = cmd.execute(); + MatcherAssert.assertThat( + "Application did not exit with a non-zero code when required arguments were missing", + exit, + Matchers.not(0) + ); + } + + @Test + void applicationExitsWithNonZeroCodeWhenOneArgumentIsMissing() { + final CommandLine cmd = new CommandLine(new Application()); + cmd.setErr(new PrintWriter(new StringWriter())); + final int exit = cmd.execute("input/dir"); + MatcherAssert.assertThat( + "Application did not exit with a non-zero code when one argument was missing", + exit, + Matchers.not(0) + ); + } +} diff --git a/src/test/java/org/eolang/aoi/DummyTest.java b/src/test/java/org/eolang/aoi/DummyTest.java deleted file mode 100644 index dbcf967..0000000 --- a/src/test/java/org/eolang/aoi/DummyTest.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com - * SPDX-License-Identifier: MIT - */ -package org.eolang.aoi; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -/** - * Temporary dummy test to prevent the "No tests to run!" error from Maven Surefire. - * - * @since 0.2 - */ -final class DummyTest { - /** - * Dummy test method. Always passes. - * - * @since 0.2 - */ - @Test - void shouldAlwaysPass() { - Assertions.assertTrue( - true, - "This test should always pass." - ); - } -} diff --git a/src/test/java/org/eolang/aoi/package-info.java b/src/test/java/org/eolang/aoi/package-info.java index f1c9971..0839f36 100644 --- a/src/test/java/org/eolang/aoi/package-info.java +++ b/src/test/java/org/eolang/aoi/package-info.java @@ -2,4 +2,10 @@ * SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com * SPDX-License-Identifier: MIT */ + +/** + * Tests for {@link org.eolang.aoi}. + * + * @since 0.0.5 + */ package org.eolang.aoi;