diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/InitCommand.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/InitCommand.java new file mode 100644 index 00000000000..397092b1c78 --- /dev/null +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/InitCommand.java @@ -0,0 +1,175 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +package software.amazon.smithy.cli.commands; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.function.Consumer; +import software.amazon.smithy.cli.ArgumentReceiver; +import software.amazon.smithy.cli.Arguments; +import software.amazon.smithy.cli.Command; +import software.amazon.smithy.cli.HelpPrinter; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.utils.IoUtils; + +public class InitCommand implements Command { + private static final String TEMP_DIRECTORY = ".temp"; + private static final String SMITHY_TEMPLATE_JSON = "smithy-templates.json"; + + private final String parentCommandName; + + public InitCommand(String parentCommandName) { + this.parentCommandName = parentCommandName; + } + + @Override + public String getName() { + return "init"; + } + + @Override + public String getSummary() { + return "Initialize a smithy project by template"; + } + + @Override + public int execute(Arguments arguments, Env env) { + arguments.addReceiver(new Options()); + CommandAction action = HelpActionWrapper.fromCommand(this, parentCommandName, this::run); + return action.apply(arguments, env); + } + + private int run(Arguments arguments, Env env) { + Options options = arguments.getReceiver(Options.class); + try { + this.cloneTemplate(options.repositoryUrl, options.template, options.directory); + } catch (IOException | InterruptedException | URISyntaxException e) { + throw new RuntimeException(e); + } + + return 0; + } + + private void cloneTemplate(String repositoryUrl, String templateName, String directory) + throws IOException, InterruptedException, URISyntaxException { + + if (templateName == null || templateName.isEmpty()) { + throw new IllegalArgumentException("Please specify template via `--template` argument"); + } + + // Use templateName if directory is not specified + if (directory == null) { + directory = templateName; + } + + // Clone the repository without downloading files + new ProcessBuilder( + "git", "clone", "--filter=blob:none", "--no-checkout", + "--depth", "1", "--sparse", repositoryUrl, TEMP_DIRECTORY) + .inheritIO().start().waitFor(); + + // Download template json file + new ProcessBuilder("git", "sparse-checkout", "set", "--no-cone", SMITHY_TEMPLATE_JSON) + .directory(new File(TEMP_DIRECTORY)) + .start().waitFor(); + + new ProcessBuilder("git", "checkout") + .directory(new File(TEMP_DIRECTORY)) + .start().waitFor(); + + String templatePath = readJsonFileAsNode(Paths.get(TEMP_DIRECTORY, SMITHY_TEMPLATE_JSON)) + .expectObjectNode() + .expectObjectMember("templates") + .expectObjectMember(templateName) + .getStringMember("path").get().getValue(); + + // Specify the subdirectory to download + new ProcessBuilder("git", "sparse-checkout", "set", "--no-cone", templatePath) + .directory(new File(TEMP_DIRECTORY)) + .start().waitFor(); + + new ProcessBuilder("git", "checkout") + .directory(new File(TEMP_DIRECTORY)) + .start().waitFor(); + + Files.move( + Paths.get(TEMP_DIRECTORY, templatePath), + Paths.get(directory), + StandardCopyOption.REPLACE_EXISTING); + + deleteDirectory(new File(TEMP_DIRECTORY)); + } + + private Node readJsonFileAsNode(Path jsonFilePath) throws IOException { + return Node.parse(IoUtils.toUtf8String(Files.newInputStream(jsonFilePath))); + } + + private boolean deleteDirectory(File directoryToBeDeleted) { + File[] allContents = directoryToBeDeleted.listFiles(); + if (allContents != null) { + for (File file : allContents) { + deleteDirectory(file); + } + } + return directoryToBeDeleted.delete(); + } + + private static final class Options implements ArgumentReceiver { + private String template; + + private String directory; + + private String repositoryUrl = "https://github.com/smithy-lang/smithy-examples.git"; + + @Override + public boolean testOption(String name) { + return false; + } + + @Override + public Consumer testParameter(String name) { + switch (name) { + case "--template": + case "-t": + return value -> template = value; + case "--url": + return value -> repositoryUrl = value; + case "--directory": + case "-d": + return value -> directory = value; + default: + return value -> { + }; + } + } + + @Override + public void registerHelp(HelpPrinter printer) { + printer.param("--template", "-t", "quickstart-cli", + "Specify the template to be used in the Smithy project"); + printer.param("--url", null, + "https://github.com/smithy-lang/smithy-examples.git", + "Smithy templates repository url"); + printer.param("--directory", "-d", "new-smithy-project", + "Smithy project directory"); + } + } +} diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/SmithyCommand.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/SmithyCommand.java index 167615aa1e4..c8854d73fcb 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/SmithyCommand.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/SmithyCommand.java @@ -49,7 +49,8 @@ public SmithyCommand(DependencyResolver.Factory dependencyResolverFactory) { new CleanCommand(getName()), migrateCommand, deprecated1To2Command, - new WarmupCommand(getName()) + new WarmupCommand(getName()), + new InitCommand(getName()) ); }