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..4116fe5c1d69 --- /dev/null +++ b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/CompletionCommand.java @@ -0,0 +1,100 @@ +/* + * 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. + * 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