From 985f859dd4f30aca96e6a580db3d77b4d0ba2f05 Mon Sep 17 00:00:00 2001 From: Jim Schubert Date: Sun, 3 Jun 2018 17:15:41 -0400 Subject: [PATCH 1/2] [cli] Completions command for suggestions This takes airlift's 'suggest' command and reuses it as a different command name, 'completion'. This gives us in-built CLI completions which are useful in the repo-level container's docker-entrypoint.sh. This previously parsed Java files for conventional usage of Command annotations, which is potentially buggy. The new implementation relies only on CLI to provide command completion suggestions. As part of this, we can prepare for bash completion scripts which can be added to our homebrew formula. The new completion command will also complete on command options, for example: cli completion generate This will provide all short and long form switches available to the generate command. --- docker-entrypoint.sh | 17 +++- .../codegen/OpenAPIGenerator.java | 26 ++--- .../codegen/cmd/CompletionCommand.java | 99 +++++++++++++++++++ 3 files changed, 125 insertions(+), 17 deletions(-) create mode 100644 modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/CompletionCommand.java diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 0311da4f42b9..d55c7fdd91aa 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -8,11 +8,19 @@ JAVA_OPTS=${JAVA_OPTS:-"-Xmx1024M -DloggerPath=conf/log4j.properties"} cli="${GEN_DIR}/modules/openapi-generator-cli" codegen="${cli}/target/openapi-generator-cli.jar" -cmdsrc="${cli}/src/main/java/org/openapitools/codegen/cmd" -pattern="@Command(name = \"$1\"" -if expr "x$1" : 'x[a-z][a-z-]*$' > /dev/null && fgrep -qe "$pattern" "$cmdsrc"/*.java || expr "$1" = 'help' > /dev/null; then - # If ${GEN_DIR} has been mapped elsewhere from default, and that location has not been built +# We code in a list of commands here as source processing is potentially buggy (requires undocumented conventional use of annotations). +# A list of known commands helps us determine if we should compile CLI. There's an edge-case where a new command not added to this +# list won't be considered a "real" command. We can get around that a bit by checking CLI completions beforehand if it exists. +commands="list,generate,meta,langs,help,config-help,validate,version" + +# if CLI jar exists, check $1 against completions available in the CLI +if [[ -f "${codegen}" && -n "$(java ${JAVA_OPTS} -jar "${codegen}" completion | grep "^$1\$" )" ]]; then + command=$1 + shift + exec java ${JAVA_OPTS} -jar "${codegen}" "${command}" "$@" +elif [[ -n "$(echo commands | tr ',' '\n' | grep "^$1\$" )" ]]; then + # If CLI jar does not exist, and $1 is a known CLI command, build the CLI jar and run that command. if [[ ! -f "${codegen}" ]]; then (cd "${GEN_DIR}" && exec mvn -am -pl "modules/openapi-generator-cli" -Duser.home=$(dirname $MAVEN_CONFIG) package) fi @@ -20,5 +28,6 @@ if expr "x$1" : 'x[a-z][a-z-]*$' > /dev/null && fgrep -qe "$pattern" "$cmdsrc"/* shift exec java ${JAVA_OPTS} -jar "${codegen}" "${command}" "$@" else + # Pass args as linux commands. This allows us to do something like: docker run -it (-e…, -v…) image ls -la exec "$@" fi diff --git a/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/OpenAPIGenerator.java b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/OpenAPIGenerator.java index 85b6379857fe..105320166095 100644 --- a/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/OpenAPIGenerator.java +++ b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/OpenAPIGenerator.java @@ -23,8 +23,6 @@ import io.airlift.airline.ParseOptionMissingValueException; import org.openapitools.codegen.cmd.*; -import java.util.Arrays; - /** * User: lanwen Date: 24.03.15 Time: 17:56 *

@@ -52,21 +50,23 @@ public static void main(String[] args) { Help.class, ConfigHelp.class, Validate.class, - Version.class + Version.class, + CompletionCommand.class ); - // If CLI is run without a command, consider this an error. - // We can check against empty args because unrecognized arguments/commands result in an exception. - // This is useful to exit with status 1, for example, so that misconfigured scripts fail fast. - // We don't want the default command to exit internally with status 1 because when the default command is something like "list", - // it would prevent scripting using the command directly. Example: - // java -jar cli.jar list --short | tr ',' '\n' | xargs -I{} echo "Doing something with {}" - if (args.length == 0) { - System.exit(1); - } - try { builder.build().parse(args).run(); + + // If CLI is run without a command, consider this an error. This exists after initial parse/run + // so we can present the configured "default command". + // We can check against empty args because unrecognized arguments/commands result in an exception. + // This is useful to exit with status 1, for example, so that misconfigured scripts fail fast. + // We don't want the default command to exit internally with status 1 because when the default command is something like "list", + // it would prevent scripting using the command directly. Example: + // java -jar cli.jar list --short | tr ',' '\n' | xargs -I{} echo "Doing something with {}" + if (args.length == 0) { + System.exit(1); + } } catch (ParseOptionMissingException | ParseOptionMissingValueException e) { System.err.printf("[error] %s%n", e.getMessage()); System.exit(1); diff --git a/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/CompletionCommand.java b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/CompletionCommand.java new file mode 100644 index 000000000000..0bcc9fd00c16 --- /dev/null +++ b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/CompletionCommand.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * NOTICE: File originally taken from: + * https://github.com/airlift/airline/blob/fc7a55e34b6361cb97235de5a1b21cba9b508f4b/src/main/java/io/airlift/airline/SuggestCommand.java#L1 + * Modifications have been made to fit the needs of OpenAPI Tools CLI. + */ +package org.openapitools.codegen.cmd; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.airlift.airline.*; +import io.airlift.airline.model.*; + +import javax.inject.Inject; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; + +import static com.google.common.collect.Lists.newArrayList; +import static io.airlift.airline.ParserUtil.createInstance; + +@Command(name = "completion", description = "Complete commands (for using in tooling such as Bash Completions).", hidden = true) +public class CompletionCommand + implements Runnable, Callable { + private static final Map> BUILTIN_SUGGESTERS = ImmutableMap.>builder() + .put(Context.GLOBAL, GlobalSuggester.class) + .put(Context.GROUP, GroupSuggester.class) + .put(Context.COMMAND, CommandSuggester.class) + .build(); + + @Inject + public GlobalMetadata metadata; + + @Arguments + public List arguments = newArrayList(); + + @Override + public Void call() { + run(); + return null; + } + + @VisibleForTesting + public Iterable generateSuggestions() { + Parser parser = new Parser(); + ParseState state = parser.parse(metadata, arguments); + + Class suggesterClass = BUILTIN_SUGGESTERS.get(state.getLocation()); + if (suggesterClass != null) { + SuggesterMetadata suggesterMetadata = MetadataLoader.loadSuggester(suggesterClass); + + if (suggesterMetadata != null) { + ImmutableMap.Builder, Object> bindings = ImmutableMap., Object>builder() + .put(GlobalMetadata.class, metadata); + + if (state.getGroup() != null) { + bindings.put(CommandGroupMetadata.class, state.getGroup()); + } + + if (state.getCommand() != null) { + bindings.put(CommandMetadata.class, state.getCommand()); + } + + Suggester suggester = createInstance(suggesterMetadata.getSuggesterClass(), + ImmutableList.of(), + null, + null, + null, + suggesterMetadata.getMetadataInjections(), + bindings.build()); + + return suggester.suggest(); + } + } + + return ImmutableList.of(); + } + + @Override + public void run() { + System.out.println(Joiner.on("\n").join(generateSuggestions())); + } +} \ No newline at end of file From 1fd460a39ec18ff5355ec447cc567fa9ac5564c7 Mon Sep 17 00:00:00 2001 From: Jim Schubert Date: Thu, 7 Jun 2018 23:35:06 -0400 Subject: [PATCH 2/2] Add piggyback license onto licensed of file borrowed from airlift/airline --- .../java/org/openapitools/codegen/cmd/CompletionCommand.java | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/CompletionCommand.java b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/CompletionCommand.java index 0bcc9fd00c16..4116fe5c1d69 100644 --- a/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/CompletionCommand.java +++ b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/CompletionCommand.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2010 the original author or authors. + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License.