Skip to content

Commit 0221b73

Browse files
authored
Merge pull request #10243 from ilopmar/create_app_programatically
Create application programatically
2 parents 4251ff4 + 79b8485 commit 0221b73

File tree

4 files changed

+90
-60
lines changed

4 files changed

+90
-60
lines changed

grails-shell/src/main/groovy/org/grails/cli/GrailsCli.groovy

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,7 @@ class GrailsCli {
670670
@Canonical
671671
public static class ExecutionContextImpl implements ExecutionContext {
672672
CommandLine commandLine
673-
@Delegate(excludes = 'getConsole') ProjectContext projectContext
673+
@Delegate(excludes = ['getConsole', 'getBaseDir']) ProjectContext projectContext
674674
GrailsConsole console = GrailsConsole.getInstance()
675675

676676
ExecutionContextImpl(CodeGenConfig config) {
@@ -702,6 +702,11 @@ class GrailsCli {
702702
}
703703
}
704704
}
705+
706+
@Override
707+
File getBaseDir() {
708+
this.projectContext?.baseDir ?: new File(".")
709+
}
705710
}
706711

707712
@Canonical

grails-shell/src/main/groovy/org/grails/cli/profile/commands/CreateAppCommand.groovy

Lines changed: 80 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,27 @@ package org.grails.cli.profile.commands
1818

1919
import grails.build.logging.GrailsConsole
2020
import grails.io.IOUtils
21-
import grails.util.BuildSettings
2221
import grails.util.Environment
2322
import grails.util.GrailsNameUtils
2423
import groovy.transform.CompileDynamic
2524
import groovy.transform.CompileStatic
2625
import groovy.transform.TypeCheckingMode
27-
import org.eclipse.aether.artifact.DefaultArtifact
2826
import org.eclipse.aether.graph.Dependency
2927
import org.grails.build.logging.GrailsConsoleAntBuilder
3028
import org.grails.build.parsing.CommandLine
3129
import org.grails.cli.GrailsCli
32-
import org.grails.cli.profile.*
30+
import org.grails.cli.profile.CommandDescription
31+
import org.grails.cli.profile.ExecutionContext
32+
import org.grails.cli.profile.Feature
33+
import org.grails.cli.profile.Profile
34+
import org.grails.cli.profile.ProfileRepository
35+
import org.grails.cli.profile.ProfileRepositoryAware
3336
import org.grails.io.support.FileSystemResource
3437
import org.grails.io.support.Resource
3538

39+
import java.nio.file.Path
40+
import java.nio.file.Paths
41+
3642
/**
3743
* Command for creating Grails applications
3844
*
@@ -52,6 +58,7 @@ class CreateAppCommand extends ArgumentCompletingCommand implements ProfileRepos
5258
Map<String, String> variables = [:]
5359
String appname
5460
String groupname
61+
String defaultpackagename
5562
File targetDirectory
5663
List<String> binaryFileExtensions = ['png','gif','jpg','jpeg','ico','icns','pdf','zip','jar','class']
5764

@@ -128,25 +135,27 @@ class CreateAppCommand extends ArgumentCompletingCommand implements ProfileRepos
128135
return super.complete(commandLine, desc, candidates, cursor)
129136
}
130137

131-
@Override
132-
boolean handle(ExecutionContext executionContext) {
133-
if(profileRepository == null) throw new IllegalStateException("Property 'profileRepository' must be set")
134-
135-
136-
def mainCommandLine = executionContext.commandLine
137-
def profileName = evaluateProfileName(mainCommandLine)
138+
boolean handle(CreateAppCommandObject cmd) {
139+
if (profileRepository == null) throw new IllegalStateException("Property 'profileRepository' must be set")
138140

141+
String profileName = cmd.profileName
139142
Profile profileInstance = profileRepository.getProfile(profileName)
140-
if( !validateProfile(profileInstance, profileName, executionContext)) {
143+
if (!validateProfile(profileInstance, profileName)) {
141144
return false
142145
}
143-
List<Feature> features = evaluateFeatures(profileInstance, mainCommandLine).toList()
144-
if(profileInstance) {
145146

146-
if( !initializeVariables(profileInstance, mainCommandLine) ) {
147+
List<Feature> features = evaluateFeatures(profileInstance, cmd.features).toList()
148+
149+
if (profileInstance) {
150+
if (!initializeGroupAndName(cmd.appName, cmd.inplace)) {
147151
return false
148152
}
149-
targetDirectory = mainCommandLine.hasOption('inplace') || GrailsCli.isInteractiveModeActive() ? new File(".").canonicalFile : new File(appname)
153+
154+
initializeVariables(cmd.appName, defaultpackagename, profileName, cmd.grailsVersion)
155+
156+
Path appFullDirectory = Paths.get(cmd.baseDir.path, appname)
157+
targetDirectory = cmd.inplace ? new File(".").canonicalFile : appFullDirectory.toFile()
158+
150159
File applicationYmlFile = new File(targetDirectory, "grails-app/conf/application.yml")
151160

152161
def profiles = profileRepository.getProfileAndDependencies(profileInstance)
@@ -189,7 +198,7 @@ class CreateAppCommand extends ArgumentCompletingCommand implements ProfileRepos
189198
}
190199

191200
replaceBuildTokens(profileName, profileInstance, features, targetDirectory)
192-
executionContext.console.addStatus(
201+
GrailsConsole.instance.addStatus(
193202
"${name == 'create-plugin' ? 'Plugin' : 'Application'} created at $targetDirectory.absolutePath"
194203
)
195204
GrailsCli.tiggerAppLoad()
@@ -201,15 +210,39 @@ class CreateAppCommand extends ArgumentCompletingCommand implements ProfileRepos
201210
}
202211
}
203212

204-
protected boolean validateProfile(Profile profileInstance, String profileName, ExecutionContext executionContext) {
213+
@Override
214+
boolean handle(ExecutionContext executionContext) {
215+
CommandLine commandLine = executionContext.commandLine
216+
217+
String profileName = evaluateProfileName(commandLine)
218+
219+
boolean inPlace = commandLine.hasOption('inplace') || GrailsCli.isInteractiveModeActive()
220+
String appName = commandLine.remainingArgs ? commandLine.remainingArgs[0] : ""
221+
222+
List<String> features = commandLine.optionValue("features")?.toString()?.split(',')?.toList()
223+
224+
CreateAppCommandObject cmd = new CreateAppCommandObject(
225+
appName: appName,
226+
baseDir: executionContext.baseDir,
227+
profileName: profileName,
228+
grailsVersion: Environment.getPackage().getImplementationVersion() ?: GRAILS_VERSION_FALLBACK_IN_IDE_ENVIRONMENTS_FOR_RUNNING_TESTS,
229+
features: features,
230+
inplace: inPlace
231+
)
232+
233+
return this.handle(cmd)
234+
}
235+
236+
protected boolean validateProfile(Profile profileInstance, String profileName) {
205237
if (profileInstance == null) {
206-
executionContext.console.error("Profile not found for name [$profileName]")
238+
GrailsConsole.instance.error("Profile not found for name [$profileName]")
207239
return false
208240
}
209241
return true
210242
}
211243

212244
private Map<URL, File> unzippedDirectories = new LinkedHashMap<URL, File>()
245+
213246
@CompileDynamic
214247
protected File unzipProfile(AntBuilder ant, Resource location) {
215248

@@ -313,15 +346,15 @@ class CreateAppCommand extends ArgumentCompletingCommand implements ProfileRepos
313346
mainCommandLine.optionValue('profile')?.toString() ?: getDefaultProfile()
314347
}
315348

316-
protected Iterable<Feature> evaluateFeatures(Profile profile, CommandLine commandLine) {
317-
String[] requestedFeatures = commandLine.optionValue("features")?.toString()?.split(',')
318-
if(requestedFeatures) {
319-
List<String> requestedFeaturesList = requestedFeatures.toList()
349+
protected Iterable<Feature> evaluateFeatures(Profile profile, List<String> requestedFeatures) {
350+
if (requestedFeatures) {
320351
List<String> allFeatureNames = profile.features*.name
321-
List<String> validFeatureNames = requestedFeaturesList.intersect(allFeatureNames)
322-
requestedFeaturesList.removeAll(allFeatureNames)
323-
requestedFeaturesList.each { String invalidFeature ->
324-
List possibleSolutions = allFeatureNames.findAll { it.substring(0, 2) == invalidFeature.substring(0, 2) }
352+
List<String> validFeatureNames = requestedFeatures.intersect(allFeatureNames)
353+
requestedFeatures.removeAll(allFeatureNames)
354+
requestedFeatures.each { String invalidFeature ->
355+
List possibleSolutions = allFeatureNames.findAll {
356+
it.substring(0, 2) == invalidFeature.substring(0, 2)
357+
}
325358
StringBuilder warning = new StringBuilder("Feature ${invalidFeature} does not exist in the profile ${profile.name}!")
326359
if (possibleSolutions) {
327360
warning.append(" Possible solutions: ")
@@ -340,7 +373,6 @@ class CreateAppCommand extends ArgumentCompletingCommand implements ProfileRepos
340373
ProfileRepository.DEFAULT_PROFILE_NAME
341374
}
342375

343-
344376
private void appendToYmlSubDocument(File applicationYmlFile, String previousApplicationYml) {
345377
String newApplicationYml = applicationYmlFile.text
346378
if(previousApplicationYml && newApplicationYml != previousApplicationYml) {
@@ -353,49 +385,45 @@ class CreateAppCommand extends ArgumentCompletingCommand implements ProfileRepos
353385
applicationYmlFile.text = appended.toString()
354386
}
355387
}
356-
357-
protected boolean initializeVariables(Profile profile, CommandLine commandLine) {
358-
String defaultPackage
359388

360-
def args = commandLine.getRemainingArgs()
361-
boolean inPlace = commandLine.hasOption('inplace') || GrailsCli.isInteractiveModeActive()
362-
363-
if(!args && !inPlace) {
389+
protected boolean initializeGroupAndName(String appName, boolean inplace) {
390+
if (!appName && !inplace) {
364391
GrailsConsole.getInstance().error("Specify an application name or use --inplace to create an application in the current directory")
365392
return false
366393
}
367-
String groupAndAppName = args ? args[0] : null
368-
if(inPlace) {
394+
String groupAndAppName = appName
395+
if(inplace) {
369396
appname = new File(".").canonicalFile.name
370397
if(!groupAndAppName) {
371398
groupAndAppName = appname
372399
}
373400
}
374-
401+
375402
if(!groupAndAppName) {
376403
GrailsConsole.getInstance().error("Specify an application name or use --inplace to create an application in the current directory")
377404
return false
378405
}
379406

380407
try {
381-
defaultPackage = establishGroupAndAppName(groupAndAppName)
408+
defaultpackagename = establishGroupAndAppName(groupAndAppName)
382409
} catch (IllegalArgumentException e ) {
383410
GrailsConsole.instance.error(e.message)
384411
return false
385412
}
413+
}
386414

387-
415+
private void initializeVariables(String appname, String defaultPackage, String profileName, String grailsVersion) {
388416
variables.APPNAME = appname
389417

390418
variables['grails.codegen.defaultPackage'] = defaultPackage
391-
variables['grails.codegen.defaultPackage.path'] = defaultPackage.replace('.', '/')
419+
variables['grails.codegen.defaultPackage.path'] = defaultPackage.replace('.', '/')
392420

393421
def projectClassName = GrailsNameUtils.getNameFromScript(appname)
394422
variables['grails.codegen.projectClassName'] = projectClassName
395423
variables['grails.codegen.projectNaturalName'] = GrailsNameUtils.getNaturalName(projectClassName)
396424
variables['grails.codegen.projectName'] = GrailsNameUtils.getScriptName(projectClassName)
397-
variables['grails.profile'] = profile.name
398-
variables['grails.version'] = Environment.getPackage().getImplementationVersion() ?: GRAILS_VERSION_FALLBACK_IN_IDE_ENVIRONMENTS_FOR_RUNNING_TESTS
425+
variables['grails.profile'] = profileName
426+
variables['grails.version'] = grailsVersion
399427
variables['grails.app.name'] = appname
400428
variables['grails.app.group'] = groupname
401429
}
@@ -416,7 +444,7 @@ class CreateAppCommand extends ArgumentCompletingCommand implements ProfileRepos
416444
}
417445

418446
private String createValidPackageName() {
419-
String defaultPackage = appname.split(/[-]+/).collect { String token -> (token.toLowerCase().toCharArray().findAll { char ch -> Character.isJavaIdentifierPart(ch) } as char[]) as String }.join('.')
447+
String defaultPackage = appname.split(/[-]+/).collect { String token -> (token.toLowerCase().toCharArray().findAll { char ch -> Character.isJavaIdentifierPart(ch) } as char[]) as String }.join('.')
420448
if(!GrailsNameUtils.isValidJavaPackage(defaultPackage)) {
421449
throw new IllegalArgumentException("Cannot create a valid package name for [$appname]. Please specify a name that is also a valid Java package.")
422450
}
@@ -522,4 +550,13 @@ class CreateAppCommand extends ArgumentCompletingCommand implements ProfileRepos
522550

523551
return v ? "${artifact.groupId}:${artifact.artifactId}:${v}" : "${artifact.groupId}:${artifact.artifactId}"
524552
}
553+
554+
static class CreateAppCommandObject {
555+
String appName
556+
File baseDir
557+
String profileName
558+
String grailsVersion
559+
List<String> features
560+
boolean inplace = false
561+
}
525562
}

grails-shell/src/main/groovy/org/grails/cli/profile/commands/CreatePluginCommand.groovy

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,8 @@
1717
package org.grails.cli.profile.commands
1818

1919
import groovy.transform.CompileStatic
20-
import org.grails.build.parsing.CommandLine
2120
import org.grails.cli.profile.ExecutionContext
2221
import org.grails.cli.profile.Profile
23-
24-
2522
/**
2623
* A command for creating a plugin
2724
*
@@ -50,16 +47,14 @@ class CreatePluginCommand extends CreateAppCommand {
5047
@Override
5148
protected String getDefaultProfile() { "web-plugin" }
5249

53-
@Override
5450
protected boolean validateProfile(Profile profileInstance, String profileName, ExecutionContext executionContext) {
55-
5651
def pluginProfile = profileInstance.extends.find() { Profile parent -> parent.name == 'plugin' }
5752
if(profileName != 'plugin' && pluginProfile == null) {
5853
executionContext.console.error("No valid plugin profile found for name [$profileName]")
5954
return false
6055
}
6156
else {
62-
return super.validateProfile(profileInstance, profileName, executionContext)
57+
return super.validateProfile(profileInstance, profileName)
6358
}
6459
}
6560
}

grails-shell/src/test/groovy/org/grails/cli/profile/commands/CreateAppCommandSpec.groovy

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
package org.grails.cli.profile.commands
22

33
import grails.build.logging.GrailsConsole
4-
import org.grails.build.parsing.CommandLine
5-
import org.grails.build.parsing.DefaultCommandLine
64
import org.grails.cli.profile.Feature
75
import org.grails.cli.profile.Profile
86
import org.spockframework.util.StringMessagePrintStream
97
import spock.lang.Shared
108
import spock.lang.Specification
11-
129
/**
1310
* Created by Jim on 7/18/2016.
1411
*/
@@ -39,10 +36,9 @@ class CreateAppCommandSpec extends Specification {
3936
2 * getFeatures() >> [bar]
4037
1 * getRequiredFeatures() >> []
4138
}
42-
CommandLine commandLine = new DefaultCommandLine().parseNew(["create-app", "foo", "-features=foo,bar"] as String[])
4339

4440
when:
45-
Iterable<Feature> features = new CreateAppCommand().evaluateFeatures(profile, commandLine)
41+
Iterable<Feature> features = new CreateAppCommand().evaluateFeatures(profile, ['foo', 'bar'])
4642

4743
then:
4844
features.size() == 1
@@ -63,10 +59,9 @@ class CreateAppCommandSpec extends Specification {
6359
2 * getFeatures() >> [foo, bar]
6460
1 * getRequiredFeatures() >> []
6561
}
66-
CommandLine commandLine = new DefaultCommandLine().parseNew(["create-app", "foo", "-features=foo,bar"] as String[])
6762

6863
when:
69-
Iterable<Feature> features = new CreateAppCommand().evaluateFeatures(profile, commandLine)
64+
Iterable<Feature> features = new CreateAppCommand().evaluateFeatures(profile, ['foo', 'bar'])
7065

7166
then:
7267
features.size() == 2
@@ -85,11 +80,9 @@ class CreateAppCommandSpec extends Specification {
8580
2 * getFeatures() >> [bar]
8681
1 * getRequiredFeatures() >> []
8782
}
88-
CommandLine commandLine = new DefaultCommandLine().parseNew(["create-app", "foo", "-features=mongo"] as String[])
8983

9084
when:
91-
Iterable<Feature> features = new CreateAppCommand().evaluateFeatures(profile, commandLine)
92-
85+
Iterable<Feature> features = new CreateAppCommand().evaluateFeatures(profile, ['mongo'])
9386

9487
then:
9588
features.size() == 0

0 commit comments

Comments
 (0)