001/*
002 * Stallion Core: A Modern Web Framework
003 *
004 * Copyright (C) 2015 - 2016 Stallion Software LLC.
005 *
006 * This program is free software: you can redistribute it and/or modify it under the terms of the
007 * GNU General Public License as published by the Free Software Foundation, either version 2 of
008 * the License, or (at your option) any later version. This program is distributed in the hope that
009 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
011 * License for more details. You should have received a copy of the GNU General Public License
012 * along with this program.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>.
013 *
014 *
015 *
016 */
017
018package io.stallion.boot;
019
020import io.stallion.templating.JinjaTemplating;
021import org.apache.commons.io.FileUtils;
022import org.apache.commons.io.IOUtils;
023import org.apache.commons.lang3.ArrayUtils;
024
025import java.io.File;
026import java.io.IOException;
027import java.nio.file.FileSystems;
028import java.nio.file.Files;
029import java.nio.file.Paths;
030import java.nio.file.attribute.PosixFileAttributes;
031import java.nio.file.attribute.PosixFilePermission;
032import java.util.List;
033import java.util.Map;
034import java.util.Scanner;
035
036import static io.stallion.utils.Literals.*;
037
038/**
039 * A command-line StallionRunAction that will walk the user through generating the
040 * scaffolding for a brand new Stallion java plugin, including setting up the maven
041 * pom.xml and a directory structure.
042 */
043public class NewJavaPluginRunAction implements StallionRunAction<CommandOptionsBase> {
044    Scanner scanner = new Scanner (System.in);
045    private String targetFolder = "";
046    private CommandOptionsBase options;
047    private JinjaTemplating templating;
048
049    private String pluginName = "";
050    private String pluginNameTitleCase = "";
051    private String javaPackageName = "";
052    private String groupId = "";
053    private String artifactId = "";
054
055
056    @Override
057    public String getActionName() {
058        return "new-java-plugin";
059    }
060
061    @Override
062    public String getHelp() {
063        return "Create a stallion plugin with a maven pom.xml";
064    }
065
066    @Override
067    public void loadApp(CommandOptionsBase options) {
068
069    }
070
071    @Override
072    public CommandOptionsBase newCommandOptions() {
073        return new CommandOptionsBase();
074    }
075
076    @Override
077    public void execute(CommandOptionsBase options) throws Exception {
078        this.options = options;
079        String folder = or(options.getTargetPath(), Paths.get(".").toAbsolutePath().normalize().toString());
080        makeNewApp(folder);
081    }
082    public void makeNewApp(String javaFolder) throws Exception {
083        targetFolder = javaFolder;
084
085        templating = new JinjaTemplating(targetFolder, false);
086
087
088        setGroupId(promptForInputOfLength("What is the maven group name? ", 5));
089        setPluginName(promptForInputOfLength("What is the plugin package name (will be appended to the groupid)? ", 2));
090        setArtifactId(promptForInputOfLength("What maven artifact id? ", 5));
091
092
093        setPluginNameTitleCase(getPluginName().substring(0, 1).toUpperCase() + getPluginName().substring(1));
094        setJavaPackageName(getGroupId() + "." + getPluginName());
095
096
097        File dir = new File(javaFolder);
098        if (!dir.isDirectory()) {
099            FileUtils.forceMkdir(dir);
100        }
101
102
103        String sourceFolder = "/src/main/java/" + getJavaPackageName().replaceAll("\\.", "/");
104        List<String> paths = list(
105                targetFolder + "/src/main/resources",
106                targetFolder + "/src/test/resources",
107                targetFolder + "/src/main/java/" + getJavaPackageName().replaceAll("\\.", "/"),
108                targetFolder + "/src/test/java/" + getJavaPackageName().replaceAll("\\.", "/")
109        );
110        for(String path: paths) {
111            File file = new File(path);
112            if (!file.isDirectory()) {
113                FileUtils.forceMkdir(file);
114            }
115        }
116        Map ctx = map(val("config", this));
117        copyTemplate("/templates/wizard/pom.xml.jinja", "pom.xml", ctx);
118        copyTemplate("/templates/wizard/PluginBooter.java.jinja", sourceFolder + "/" + pluginNameTitleCase + "Plugin.java", ctx);
119        copyTemplate("/templates/wizard/PluginSettings.java.jinja", sourceFolder + "/" + pluginNameTitleCase + "Settings.java", ctx);
120        copyTemplate("/templates/wizard/MainRunner.java.jinja", sourceFolder + "/MainRunner.java", ctx);
121        copyTemplate("/templates/wizard/Endpoints.java.jinja", sourceFolder + "/Endpoints.java", ctx);
122        copyTemplate("/templates/wizard/build.py.jinja", "build.py", ctx);
123        copyTemplate("/templates/wizard/run-dev.jinja", "run-dev.sh", ctx);
124
125        Files.setPosixFilePermissions(FileSystems.getDefault().getPath(targetFolder + "/build.py"),
126                set(PosixFilePermission.OWNER_EXECUTE, PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE)
127        );
128        Files.setPosixFilePermissions(FileSystems.getDefault().getPath(targetFolder + "/run-dev.sh"),
129                set(PosixFilePermission.OWNER_EXECUTE, PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE)
130        );
131        copyFile("/templates/wizard/app.bundle", "src/main/resources/assets/app.bundle");
132        copyFile("/templates/wizard/app.js", "src/main/resources/assets/app.js");
133        copyFile("/templates/wizard/app.scss", "src/main/resources/assets/app.scss");
134        copyFile("/templates/wizard/file1.js", "src/main/resources/assets/common/file1.js");
135        copyFile("/templates/wizard/file2.js", "src/main/resources/assets/common/file2.js");
136        copyFile("/assets/vendor/jquery-1.11.3.js", "src/main/resources/assets/vendor/jquery-1.11.3.js");
137        copyFile("/assets/basic/stallion.js", "src/main/resources/assets/vendor/stallion.js");
138        copyFile("/templates/wizard/app.jinja", "src/main/resources/templates/app.jinja");
139
140    }
141
142
143
144    public void copyTemplate(String resourcePath, String relativePath, Map ctx) throws IOException  {
145        String source = templating.renderTemplate(
146                IOUtils.toString(getClass().getResource(resourcePath), UTF8),
147                ctx);
148        File file = new File(targetFolder + "/" + relativePath);
149        File parent = file.getParentFile();
150        if (!parent.isDirectory()) {
151            parent.mkdirs();
152        }
153        FileUtils.writeStringToFile(file, source, "utf-8");
154    }
155
156    public void copyFile(String resourcePath, String relativePath) throws IOException {
157        File file = new File(targetFolder + "/" + relativePath);
158        File parent = file.getParentFile();
159        if (!parent.isDirectory()) {
160            parent.mkdirs();
161        }
162        String source = IOUtils.toString(getClass().getResource(resourcePath), UTF8);
163        FileUtils.writeStringToFile(file, source, "utf-8");
164    }
165
166
167
168    public boolean promptYesNo(String prompt) {
169        System.out.print(prompt + " Answer yes/no or y/n: ");
170        for (int i : safeLoop(100)) {
171            String input = scanner.next().trim().toLowerCase();
172            if (input.startsWith("y")) {
173                return true;
174            } else if (input.startsWith("n")) {
175                return false;
176            }
177            System.out.println("Please answer y or n.");
178        }
179        return false;
180    }
181
182    public String promptForInputOfLength(String prompt, int length) {
183        System.out.print(prompt);
184        for (int i : safeLoop(100)) {
185            String input = scanner.next().trim();
186            if (input.length() >= length) {
187                return input;
188            }
189            System.out.println("Input must be at least" + length + " characters.");
190        }
191        return "";
192    }
193
194    public String promptForInputOfLength(String prompt, int length, String contains) {
195        System.out.print(prompt);
196        for (int i : safeLoop(100)) {
197            String input = scanner.next().trim();
198            if (input.length() >= length && input.contains(contains)) {
199                return input;
200            }
201            System.out.println("Input must be at least" + length + " characters and contain " + contains);
202        }
203        return "";
204    }
205
206
207    public String promptForValidInput(String prompt, String...validChoices) {
208        System.out.print(prompt);
209        for (int i: safeLoop(100)) {
210            String input = scanner.next().trim();
211            if (ArrayUtils.contains(validChoices, input)) {
212                return input;
213            }
214            System.out.println("Invalid choice!");
215        }
216        return "";
217    }
218
219
220    public String getPluginName() {
221        return pluginName;
222    }
223
224    public NewJavaPluginRunAction setPluginName(String pluginName) {
225        this.pluginName = pluginName;
226        return this;
227    }
228
229    public String getJavaPackageName() {
230        return javaPackageName;
231    }
232
233    public NewJavaPluginRunAction setJavaPackageName(String javaPackageName) {
234        this.javaPackageName = javaPackageName;
235        return this;
236    }
237
238    public String getPluginNameTitleCase() {
239        return pluginNameTitleCase;
240    }
241
242    public NewJavaPluginRunAction setPluginNameTitleCase(String pluginNameTitleCase) {
243        this.pluginNameTitleCase = pluginNameTitleCase;
244        return this;
245    }
246
247    public String getGroupId() {
248        return groupId;
249    }
250
251    public NewJavaPluginRunAction setGroupId(String groupId) {
252        this.groupId = groupId;
253        return this;
254    }
255
256    public String getArtifactId() {
257        return artifactId;
258    }
259
260    public NewJavaPluginRunAction setArtifactId(String artifactId) {
261        this.artifactId = artifactId;
262        return this;
263    }
264}