From 073ebd3bfc236cd678b61f41159ac3501a6fedbd Mon Sep 17 00:00:00 2001 From: Jonathan Gamba Date: Mon, 12 Feb 2024 19:30:33 -0600 Subject: [PATCH] #27540 Refactor file listing commands for modularity Moved common functionality of FilesTree and FilesLs into new AbstractFilesListingCommand for code reuse and simplified their implementations appropriately. The changes aim to improve maintainability and code clarity by avoiding repetition. --- .../files/AbstractFilesListingCommand.java | 102 +++++++++++++++ .../cli/command/files/FilesListingMixin.java | 50 ++++++++ .../com/dotcms/cli/command/files/FilesLs.java | 120 +----------------- .../dotcms/cli/command/files/FilesTree.java | 120 +----------------- 4 files changed, 157 insertions(+), 235 deletions(-) create mode 100644 tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/files/AbstractFilesListingCommand.java create mode 100644 tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/files/FilesListingMixin.java diff --git a/tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/files/AbstractFilesListingCommand.java b/tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/files/AbstractFilesListingCommand.java new file mode 100644 index 000000000000..32b9402cc7f7 --- /dev/null +++ b/tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/files/AbstractFilesListingCommand.java @@ -0,0 +1,102 @@ +package com.dotcms.cli.command.files; + +import com.dotcms.api.LanguageAPI; +import com.dotcms.api.client.files.traversal.RemoteTraversalService; +import com.dotcms.api.traversal.TreeNode; +import com.dotcms.cli.common.ConsoleLoadingAnimation; +import com.dotcms.model.language.Language; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import javax.inject.Inject; +import org.apache.commons.lang3.tuple.Pair; +import org.eclipse.microprofile.context.ManagedExecutor; +import picocli.CommandLine; + +/** + * This abstract class is used for implementing files listing commands. It provides common + * functionality for listing the contents of a remote directory. + */ +public abstract class AbstractFilesListingCommand extends AbstractFilesCommand { + + @CommandLine.Mixin + FilesListingMixin filesMixin; + + @Inject + RemoteTraversalService remoteTraversalService; + + @CommandLine.Spec + CommandLine.Model.CommandSpec spec; + + @Inject + ManagedExecutor executor; + + /** + * Executes the listing of a remote folder at the specified depth. + * + * @param depth the depth of the folder traversal + * @return an exit code indicating the success of the operation + * @throws ExecutionException if an exception occurs during execution + * @throws InterruptedException if the execution is interrupted + */ + protected Integer listing(final Integer depth) throws ExecutionException, InterruptedException { + + // Checking for unmatched arguments + output.throwIfUnmatchedArguments(spec.commandLine()); + + var includeFolderPatterns = parsePatternOption(filesMixin.includeFolderPatternsOption); + var includeAssetPatterns = parsePatternOption(filesMixin.includeAssetPatternsOption); + var excludeFolderPatterns = parsePatternOption(filesMixin.excludeFolderPatternsOption); + var excludeAssetPatterns = parsePatternOption(filesMixin.excludeAssetPatternsOption); + + CompletableFuture, TreeNode>> folderTraversalFuture = executor.supplyAsync( + () -> + // Service to handle the traversal of the folder + remoteTraversalService.traverseRemoteFolder( + filesMixin.folderPath, + depth, + true, + includeFolderPatterns, + includeAssetPatterns, + excludeFolderPatterns, + excludeAssetPatterns + ) + ); + + // ConsoleLoadingAnimation instance to handle the waiting "animation" + ConsoleLoadingAnimation consoleLoadingAnimation = new ConsoleLoadingAnimation( + output, + folderTraversalFuture + ); + + CompletableFuture animationFuture = executor.runAsync( + consoleLoadingAnimation + ); + + // Waits for the completion of both the folder traversal and console loading animation tasks. + // This line blocks the current thread until both CompletableFuture instances + // (folderTraversalFuture and animationFuture) have completed. + CompletableFuture.allOf(folderTraversalFuture, animationFuture).join(); + final var result = folderTraversalFuture.get(); + + if (result == null) { + output.error(String.format( + "Error occurred while pulling folder info: [%s].", filesMixin.folderPath)); + return CommandLine.ExitCode.SOFTWARE; + } + + // We need to retrieve the languages + final LanguageAPI languageAPI = clientFactory.getClient(LanguageAPI.class); + final List languages = languageAPI.list().entity(); + + // Display the result + StringBuilder sb = new StringBuilder(); + TreePrinter.getInstance() + .filteredFormat(sb, result.getRight(), !filesMixin.excludeEmptyFolders, languages); + + output.info(sb.toString()); + + return CommandLine.ExitCode.OK; + } + +} diff --git a/tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/files/FilesListingMixin.java b/tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/files/FilesListingMixin.java new file mode 100644 index 000000000000..df2716d252f2 --- /dev/null +++ b/tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/files/FilesListingMixin.java @@ -0,0 +1,50 @@ +package com.dotcms.cli.command.files; + +import picocli.CommandLine; +import picocli.CommandLine.Parameters; + +/** + * Mixin class that provides options for listing the contents of a remote dotCMS directory + */ +public class FilesListingMixin { + + @Parameters(index = "0", arity = "1", paramLabel = "path", + description = "dotCMS path to the directory to list the contents of " + + "- Format: //{site}/{folder}") + String folderPath; + + @CommandLine.Option(names = {"-ee", "--excludeEmptyFolders"}, defaultValue = "false", + description = + "When this option is enabled, the tree display will exclude folders that do " + + "not contain any assets, as well as folders that have no children with assets. " + + "This can be useful for users who want to focus on the folder structure that " + + "contains assets, making the output more concise and easier to navigate. By " + + "default, this option is disabled, and all folders, including empty ones, " + + "will be displayed in the tree.") + boolean excludeEmptyFolders; + + @CommandLine.Option(names = {"-ef", "--excludeFolder"}, + paramLabel = "patterns", + description = "Exclude directories matching the given glob patterns. Multiple " + + "patterns can be specified, separated by commas.") + String excludeFolderPatternsOption; + + @CommandLine.Option(names = {"-ea", "--excludeAsset"}, + paramLabel = "patterns", + description = "Exclude assets matching the given glob patterns. Multiple " + + "patterns can be specified, separated by commas.") + String excludeAssetPatternsOption; + + @CommandLine.Option(names = {"-if", "--includeFolder"}, + paramLabel = "patterns", + description = "Include directories matching the given glob patterns. Multiple " + + "patterns can be specified, separated by commas.") + String includeFolderPatternsOption; + + @CommandLine.Option(names = {"-ia", "--includeAsset"}, + paramLabel = "patterns", + description = "Include assets matching the given glob patterns. Multiple " + + "patterns can be specified, separated by commas.") + String includeAssetPatternsOption; + +} diff --git a/tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/files/FilesLs.java b/tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/files/FilesLs.java index 22515b092d24..af86107dd6a8 100644 --- a/tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/files/FilesLs.java +++ b/tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/files/FilesLs.java @@ -1,21 +1,10 @@ package com.dotcms.cli.command.files; -import com.dotcms.api.LanguageAPI; -import com.dotcms.api.client.files.traversal.RemoteTraversalService; -import com.dotcms.api.traversal.TreeNode; import com.dotcms.cli.command.DotCommand; -import com.dotcms.cli.common.ConsoleLoadingAnimation; import com.dotcms.cli.common.OutputOptionMixin; -import com.dotcms.model.language.Language; -import java.util.List; import java.util.concurrent.Callable; -import java.util.concurrent.CompletableFuture; import javax.enterprise.context.control.ActivateRequestContext; -import javax.inject.Inject; -import org.apache.commons.lang3.tuple.Pair; -import org.eclipse.microprofile.context.ManagedExecutor; import picocli.CommandLine; -import picocli.CommandLine.Parameters; /** * Command to lists the files and directories in the specified directory. @@ -29,117 +18,13 @@ "" // empty string here so we can have a new line } ) -public class FilesLs extends AbstractFilesCommand implements Callable, DotCommand { +public class FilesLs extends AbstractFilesListingCommand implements Callable, DotCommand { static final String NAME = "ls"; - @Parameters(index = "0", arity = "1", paramLabel = "path", - description = "dotCMS path to the directory to list the contents of " - + "- Format: //{site}/{folder}") - String folderPath; - - @CommandLine.Option(names = {"-ee", "--excludeEmptyFolders"}, defaultValue = "false", - description = - "When this option is enabled, the tree display will exclude folders that do " - + "not contain any assets, as well as folders that have no children with assets. " - + "This can be useful for users who want to focus on the folder structure that " - + "contains assets, making the output more concise and easier to navigate. By " - + "default, this option is disabled, and all folders, including empty ones, " - + "will be displayed in the tree.") - boolean excludeEmptyFolders; - - @CommandLine.Option(names = {"-ef", "--excludeFolder"}, - paramLabel = "patterns", - description = "Exclude directories matching the given glob patterns. Multiple " - + "patterns can be specified, separated by commas.") - String excludeFolderPatternsOption; - - @CommandLine.Option(names = {"-ea", "--excludeAsset"}, - paramLabel = "patterns", - description = "Exclude assets matching the given glob patterns. Multiple " - + "patterns can be specified, separated by commas.") - String excludeAssetPatternsOption; - - @CommandLine.Option(names = {"-if", "--includeFolder"}, - paramLabel = "patterns", - description = "Include directories matching the given glob patterns. Multiple " - + "patterns can be specified, separated by commas.") - String includeFolderPatternsOption; - - @CommandLine.Option(names = {"-ia", "--includeAsset"}, - paramLabel = "patterns", - description = "Include assets matching the given glob patterns. Multiple " - + "patterns can be specified, separated by commas.") - String includeAssetPatternsOption; - - @Inject - RemoteTraversalService remoteTraversalService; - - @CommandLine.Spec - CommandLine.Model.CommandSpec spec; - - @Inject - ManagedExecutor executor; - @Override public Integer call() throws Exception { - - // Checking for unmatched arguments - output.throwIfUnmatchedArguments(spec.commandLine()); - - var includeFolderPatterns = parsePatternOption(includeFolderPatternsOption); - var includeAssetPatterns = parsePatternOption(includeAssetPatternsOption); - var excludeFolderPatterns = parsePatternOption(excludeFolderPatternsOption); - var excludeAssetPatterns = parsePatternOption(excludeAssetPatternsOption); - - CompletableFuture, TreeNode>> folderTraversalFuture = executor.supplyAsync( - () -> { - // Service to handle the traversal of the folder - return remoteTraversalService.traverseRemoteFolder( - folderPath, - 0, - true, - includeFolderPatterns, - includeAssetPatterns, - excludeFolderPatterns, - excludeAssetPatterns - ); - }); - - // ConsoleLoadingAnimation instance to handle the waiting "animation" - ConsoleLoadingAnimation consoleLoadingAnimation = new ConsoleLoadingAnimation( - output, - folderTraversalFuture - ); - - CompletableFuture animationFuture = executor.runAsync( - consoleLoadingAnimation - ); - - // Waits for the completion of both the folder traversal and console loading animation tasks. - // This line blocks the current thread until both CompletableFuture instances - // (folderTraversalFuture and animationFuture) have completed. - CompletableFuture.allOf(folderTraversalFuture, animationFuture).join(); - final var result = folderTraversalFuture.get(); - - if (result == null) { - output.error(String.format( - "Error occurred while pulling folder info: [%s].", folderPath)); - return CommandLine.ExitCode.SOFTWARE; - } - - // We need to retrieve the languages - final LanguageAPI languageAPI = clientFactory.getClient(LanguageAPI.class); - final List languages = languageAPI.list().entity(); - - // Display the result - StringBuilder sb = new StringBuilder(); - TreePrinter.getInstance().filteredFormat(sb, result.getRight(), !excludeEmptyFolders, languages); - - output.info(sb.toString()); - - - return CommandLine.ExitCode.OK; + return listing(0); } @Override @@ -152,5 +37,4 @@ public OutputOptionMixin getOutput() { return output; } - } diff --git a/tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/files/FilesTree.java b/tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/files/FilesTree.java index 18bc5360823b..589b4118aa1f 100644 --- a/tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/files/FilesTree.java +++ b/tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/files/FilesTree.java @@ -1,21 +1,10 @@ package com.dotcms.cli.command.files; -import com.dotcms.api.LanguageAPI; -import com.dotcms.api.client.files.traversal.RemoteTraversalService; -import com.dotcms.api.traversal.TreeNode; import com.dotcms.cli.command.DotCommand; -import com.dotcms.cli.common.ConsoleLoadingAnimation; import com.dotcms.cli.common.OutputOptionMixin; -import com.dotcms.model.language.Language; -import java.util.List; import java.util.concurrent.Callable; -import java.util.concurrent.CompletableFuture; import javax.enterprise.context.control.ActivateRequestContext; -import javax.inject.Inject; -import org.apache.commons.lang3.tuple.Pair; -import org.eclipse.microprofile.context.ManagedExecutor; import picocli.CommandLine; -import picocli.CommandLine.Parameters; /** * Command to display a hierarchical tree view of the files and subdirectories within the specified @@ -31,15 +20,11 @@ "" // empty string here so we can have a new line } ) -public class FilesTree extends AbstractFilesCommand implements Callable, DotCommand { +public class FilesTree extends AbstractFilesListingCommand implements Callable, + DotCommand { static final String NAME = "tree"; - @Parameters(index = "0", arity = "1", paramLabel = "path", - description = "dotCMS path to the directory to list the contents of " - + "- Format: //{site}/{folder}") - String folderPath; - @CommandLine.Option(names = {"-d", "--depth"}, description = "Limits the depth of the directory tree to levels. " + "The default value is 0, which means that only the files and directories in " @@ -47,108 +32,9 @@ public class FilesTree extends AbstractFilesCommand implements Callable + "there is no limit on the depth of the directory tree.") Integer depth; - @CommandLine.Option(names = {"-ee", "--excludeEmptyFolders"}, defaultValue = "false", - description = - "When this option is enabled, the tree display will exclude folders that do " - + "not contain any assets, as well as folders that have no children with assets. " - + "This can be useful for users who want to focus on the folder structure that " - + "contains assets, making the output more concise and easier to navigate. By " - + "default, this option is disabled, and all folders, including empty ones, " - + "will be displayed in the tree.") - boolean excludeEmptyFolders; - - @CommandLine.Option(names = {"-ef", "--excludeFolder"}, - paramLabel = "patterns", - description = "Exclude directories matching the given glob patterns. Multiple " - + "patterns can be specified, separated by commas.") - String excludeFolderPatternsOption; - - @CommandLine.Option(names = {"-ea", "--excludeAsset"}, - paramLabel = "patterns", - description = "Exclude assets matching the given glob patterns. Multiple " - + "patterns can be specified, separated by commas.") - String excludeAssetPatternsOption; - - @CommandLine.Option(names = {"-if", "--includeFolder"}, - paramLabel = "patterns", - description = "Include directories matching the given glob patterns. Multiple " - + "patterns can be specified, separated by commas.") - String includeFolderPatternsOption; - - @CommandLine.Option(names = {"-ia", "--includeAsset"}, - paramLabel = "patterns", - description = "Include assets matching the given glob patterns. Multiple " - + "patterns can be specified, separated by commas.") - String includeAssetPatternsOption; - - @Inject - RemoteTraversalService remoteTraversalService; - - @CommandLine.Spec - CommandLine.Model.CommandSpec spec; - - @Inject - ManagedExecutor executor; - @Override public Integer call() throws Exception { - - // Checking for unmatched arguments - output.throwIfUnmatchedArguments(spec.commandLine()); - - var includeFolderPatterns = parsePatternOption(includeFolderPatternsOption); - var includeAssetPatterns = parsePatternOption(includeAssetPatternsOption); - var excludeFolderPatterns = parsePatternOption(excludeFolderPatternsOption); - var excludeAssetPatterns = parsePatternOption(excludeAssetPatternsOption); - - CompletableFuture, TreeNode>> folderTraversalFuture = executor.supplyAsync( - () -> { - // Service to handle the traversal of the folder - return remoteTraversalService.traverseRemoteFolder( - folderPath, - depth, - true, - includeFolderPatterns, - includeAssetPatterns, - excludeFolderPatterns, - excludeAssetPatterns - ); - }); - - // ConsoleLoadingAnimation instance to handle the waiting "animation" - ConsoleLoadingAnimation consoleLoadingAnimation = new ConsoleLoadingAnimation( - output, - folderTraversalFuture - ); - - CompletableFuture animationFuture = executor.runAsync( - consoleLoadingAnimation - ); - - // Waits for the completion of both the folder traversal and console loading animation tasks. - // This line blocks the current thread until both CompletableFuture instances - // (folderTraversalFuture and animationFuture) have completed. - CompletableFuture.allOf(folderTraversalFuture, animationFuture).join(); - final var result = folderTraversalFuture.get(); - - if (result == null) { - output.error(String.format( - "Error occurred while pulling folder info: [%s].", folderPath)); - return CommandLine.ExitCode.SOFTWARE; - } - - // We need to retrieve the languages - final LanguageAPI languageAPI = clientFactory.getClient(LanguageAPI.class); - final List languages = languageAPI.list().entity(); - - // Display the result - StringBuilder sb = new StringBuilder(); - TreePrinter.getInstance() - .filteredFormat(sb, result.getRight(), !excludeEmptyFolders, languages); - - output.info(sb.toString()); - - return CommandLine.ExitCode.OK; + return listing(depth); } @Override