
org.grails.cli.profile.AbstractProfile.groovy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of grails-shell Show documentation
Show all versions of grails-shell Show documentation
Grails Web Application Framework
/*
* Copyright 2015 original 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.
*/
package org.grails.cli.profile
import grails.io.IOUtils
import grails.util.BuildSettings
import grails.util.CosineSimilarity
import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import groovy.transform.ToString
import jline.console.completer.ArgumentCompleter
import jline.console.completer.Completer
import org.eclipse.aether.artifact.DefaultArtifact
import org.eclipse.aether.graph.Dependency
import org.eclipse.aether.graph.Exclusion
import org.eclipse.aether.util.graph.selector.ExclusionDependencySelector
import org.grails.build.parsing.ScriptNameResolver
import org.grails.cli.interactive.completers.StringsCompleter
import org.grails.cli.profile.commands.CommandRegistry
import org.grails.cli.profile.commands.DefaultMultiStepCommand
import org.grails.cli.profile.commands.script.GroovyScriptCommand
import org.grails.config.NavigableMap
import org.grails.io.support.Resource
import org.yaml.snakeyaml.Yaml
import java.util.regex.Matcher
/**
* Abstract implementation of the profile class
*
* @author Graeme Rocher
* @since 3.1
*/
@CompileStatic
@ToString(includes = ['name'])
abstract class AbstractProfile implements Profile {
protected final Resource profileDir
protected String name
protected List parentProfiles
protected Map commandsByName
protected NavigableMap navigableConfig
protected ProfileRepository profileRepository
protected List dependencies = []
protected List repositories = []
protected List parentNames = []
protected List buildRepositories = []
protected List buildPlugins = []
protected List buildExcludes = []
protected final List internalCommands = []
protected List buildMerge = null
protected List features = []
protected Set defaultFeaturesNames = []
protected Set requiredFeatureNames = []
final ClassLoader classLoader
protected ExclusionDependencySelector exclusionDependencySelector = new ExclusionDependencySelector()
protected String description = "";
protected String version = BuildSettings.package.implementationVersion
AbstractProfile(Resource profileDir) {
this(profileDir, getClass().getClassLoader())
}
AbstractProfile(Resource profileDir, ClassLoader classLoader) {
this.classLoader = classLoader
this.profileDir = profileDir
def url = profileDir.getURL()
def jarFile = IOUtils.findJarFile(url)
def pattern = ~/.+-(\d.+)\.jar/
def path
if(jarFile != null) {
path = jarFile.name
}
else if(url != null){
def p = url.path
path = p.substring(0, p.indexOf('.jar') + 4)
}
if(path) {
def matcher = pattern.matcher(path)
if(matcher.matches()) {
this.version = matcher.group(1)
}
}
}
String getVersion() {
return version
}
protected void initialize() {
def profileYml = profileDir.createRelative("profile.yml")
def profileConfig = (Map) new Yaml().loadAs(profileYml.getInputStream(), Map)
name = profileConfig.get("name")?.toString()
description = profileConfig.get("description")?.toString() ?: ''
def parents = profileConfig.get("extends")
if(parents) {
parentNames = parents.toString().split(',').collect() { String name -> name.trim() }
}
if(this.name == null) {
throw new IllegalStateException("Profile name not set. Profile for path ${profileDir.URL} is invalid")
}
def map = new NavigableMap()
map.merge(profileConfig)
navigableConfig = map
def commandsByName = profileConfig.get("commands")
if(commandsByName instanceof Map) {
def commandsMap = (Map) commandsByName
for(clsName in commandsMap.keySet()) {
def fileName = commandsMap[clsName].toString()
if(fileName.endsWith(".groovy")) {
GroovyScriptCommand cmd = (GroovyScriptCommand)classLoader.loadClass(clsName.toString()).newInstance()
cmd.profile = this
cmd.profileRepository = profileRepository
internalCommands.add cmd
}
else if(fileName.endsWith('.yml')) {
def yamlCommand = profileDir.createRelative("commands/$fileName")
if(yamlCommand.exists()) {
def data = new Yaml().loadAs(yamlCommand.getInputStream(), Map.class)
Command cmd = new DefaultMultiStepCommand(clsName.toString(), this, data)
Object minArguments = data?.minArguments
cmd.minArguments = minArguments instanceof Integer ? (Integer)minArguments : 1
internalCommands.add cmd
}
}
}
}
def featuresConfig = profileConfig.get("features")
if(featuresConfig instanceof Map) {
Map featureMap = (Map) featuresConfig
def featureList = (List) featureMap.get("provided") ?: Collections.emptyList()
def defaultFeatures = (List) featureMap.get("defaults") ?: Collections.emptyList()
def requiredFeatures = (List) featureMap.get("required") ?: Collections.emptyList()
for (fn in featureList) {
def featureData = profileDir.createRelative("features/${fn}/feature.yml")
if(featureData.exists()) {
def f = new DefaultFeature(this, fn.toString(), profileDir.createRelative("features/$fn/"))
features.add f
}
}
defaultFeaturesNames.addAll(defaultFeatures)
requiredFeatureNames.addAll(requiredFeatures)
}
def dependencyMap = profileConfig.get("dependencies")
if(dependencyMap instanceof Map) {
for(entry in ((Map)dependencyMap)) {
def scope = entry.key
def value = entry.value
if(value instanceof List) {
if("excludes".equals(scope)) {
List exclusions =[]
for(dep in ((List)value)) {
def artifact = new DefaultArtifact(dep.toString())
exclusions.add new Exclusion(artifact.groupId ?: null, artifact.artifactId ?: null, artifact.classifier ?: null, artifact.extension ?: null)
}
exclusionDependencySelector = new ExclusionDependencySelector(exclusions)
}
else {
for(dep in ((List)value)) {
String coords = dep.toString()
if(coords.count(':') == 1) {
coords = "$coords:BOM"
}
dependencies.add new Dependency(new DefaultArtifact(coords),scope.toString())
}
}
}
}
}
this.repositories = (List)navigableConfig.get("repositories", [])
this.buildRepositories = (List)navigableConfig.get("build.repositories", [])
this.buildPlugins = (List)navigableConfig.get("build.plugins", [])
this.buildExcludes = (List)navigableConfig.get("build.excludes", [])
this.buildMerge = (List)navigableConfig.get("build.merge", null)
}
String getDescription() {
return description
}
@Override
Iterable getDefaultFeatures() {
getFeatures().findAll() { Feature f -> defaultFeaturesNames.contains(f.name) }
}
@Override
Iterable getRequiredFeatures() {
def requiredFeatureInstances = getFeatures().findAll() { Feature f -> requiredFeatureNames.contains(f.name) }
if(requiredFeatureInstances.size() != requiredFeatureNames.size()) {
throw new IllegalStateException("One or more required features were not found on the classpath. Required features: $requiredFeatureNames")
}
return requiredFeatureInstances
}
@Override
Iterable getFeatures() {
Set calculatedFeatures = []
def parents = getExtends()
for(profile in parents) {
calculatedFeatures.addAll profile.features
}
calculatedFeatures.addAll(features)
return calculatedFeatures
}
@Override
List getBuildMergeProfileNames() {
if(buildMerge != null) {
return this.buildMerge
}
else {
List mergeNames = []
for(parent in getExtends()) {
mergeNames.add(parent.name)
}
mergeNames.add(name)
return mergeNames
}
}
@Override
List getBuildRepositories() {
List calculatedRepositories = []
def parents = getExtends()
for(profile in parents) {
calculatedRepositories.addAll(profile.buildRepositories)
}
calculatedRepositories.addAll(buildRepositories)
return calculatedRepositories
}
@Override
List getBuildPlugins() {
List calculatedPlugins = []
def parents = getExtends()
for(profile in parents) {
def dependencies = profile.buildPlugins
for(dep in dependencies) {
if(!buildExcludes.contains(dep))
calculatedPlugins.add(dep)
}
}
calculatedPlugins.addAll(buildPlugins)
return calculatedPlugins
}
@Override
List getRepositories() {
List calculatedRepositories = []
def parents = getExtends()
for(profile in parents) {
calculatedRepositories.addAll(profile.repositories)
}
calculatedRepositories.addAll(repositories)
return calculatedRepositories
}
List getDependencies() {
List calculatedDependencies = []
def parents = getExtends()
for(profile in parents) {
def dependencies = profile.dependencies
for(dep in dependencies) {
if(exclusionDependencySelector.selectDependency(dep)) {
calculatedDependencies.add(dep)
}
}
}
calculatedDependencies.addAll(dependencies)
return calculatedDependencies
}
ProfileRepository getProfileRepository() {
return profileRepository
}
void setProfileRepository(ProfileRepository profileRepository) {
this.profileRepository = profileRepository
}
Resource getProfileDir() {
return profileDir
}
@Override
NavigableMap getConfiguration() {
navigableConfig
}
@Override
Resource getTemplate(String path) {
return profileDir.createRelative("templates/$path")
}
@Override
public Iterable getExtends() {
return parentNames.collect() { String name ->
def parent = profileRepository.getProfile(name)
if(parent == null) {
throw new IllegalStateException("Profile [$name] declares an invalid dependency on parent profile [$name]")
}
return parent
}
}
@Override
public Iterable getCompleters(ProjectContext context) {
def commands = getCommands(context)
Collection completers = []
for(Command cmd in commands) {
def description = cmd.description
def commandNameCompleter = new StringsCompleter(cmd.name)
if(cmd instanceof Completer) {
completers << new ArgumentCompleter(commandNameCompleter, (Completer)cmd)
}else {
if(description.completer) {
if(description.flags) {
completers << new ArgumentCompleter(commandNameCompleter,
description.completer,
new StringsCompleter(description.flags.collect() { CommandArgument arg -> "-$arg.name".toString() }))
}
else {
completers << new ArgumentCompleter(commandNameCompleter, description.completer)
}
}
else {
if(description.flags) {
completers << new ArgumentCompleter(commandNameCompleter, new StringsCompleter(description.flags.collect() { CommandArgument arg -> "-$arg.name".toString() }))
}
else {
completers << commandNameCompleter
}
}
}
}
return completers
}
@Override
Command getCommand(ProjectContext context, String name) {
getCommands(context)
return commandsByName[name]
}
@Override
Iterable getCommands(ProjectContext context) {
if(commandsByName == null) {
commandsByName = [:]
List excludes = []
def registerCommand = { Command command ->
def name = command.name
if(!commandsByName.containsKey(name) && !excludes.contains(name)) {
if(command instanceof ProfileRepositoryAware) {
((ProfileRepositoryAware)command).setProfileRepository(profileRepository)
}
commandsByName[name] = command
def desc = command.description
def synonyms = desc.synonyms
if(synonyms) {
for(syn in synonyms) {
commandsByName[syn] = command
}
}
if(command instanceof ProjectContextAware) {
((ProjectContextAware)command).projectContext = context
}
if(command instanceof ProfileCommand) {
((ProfileCommand)command).profile = this
}
}
}
CommandRegistry.findCommands(this).each(registerCommand)
def parents = getExtends()
if(parents) {
excludes = (List)configuration.navigate("command", "excludes") ?: []
registerParentCommands(context, parents, registerCommand)
}
}
return commandsByName.values()
}
protected void registerParentCommands(ProjectContext context, Iterable parents, Closure registerCommand) {
for (parent in parents) {
parent.getCommands(context).each registerCommand
def extended = parent.extends
if(extended) {
registerParentCommands context, extended, registerCommand
}
}
}
@Override
boolean hasCommand(ProjectContext context, String name) {
getCommands(context) // ensure initialization
return commandsByName.containsKey(name)
}
@Override
boolean handleCommand(ExecutionContext context) {
getCommands(context) // ensure initialization
def commandLine = context.commandLine
def commandName = commandLine.commandName
def cmd = commandsByName[commandName]
if(cmd) {
def requiredArguments = cmd?.description?.arguments
int requiredArgumentCount = requiredArguments?.findAll() { CommandArgument ca -> ca.required }?.size() ?: 0
if(commandLine.remainingArgs.size() < requiredArgumentCount) {
context.console.error "Command [$commandName] missing required arguments: ${requiredArguments*.name}. Type 'grails help $commandName' for more info."
return false
}
else {
return cmd.handle(context)
}
}
else {
// Apply command name expansion (rA for run-app, tA for test-app etc.)
cmd = commandsByName.values().find() { Command c ->
ScriptNameResolver.resolvesTo(commandName, c.name)
}
if(cmd) {
return cmd.handle(context)
}
else {
context.console.error("Command not found ${context.commandLine.commandName}")
def mostSimilar = CosineSimilarity.mostSimilar(commandName, commandsByName.keySet())
List topMatches = mostSimilar.subList(0, Math.min(3, mostSimilar.size()));
if(topMatches) {
context.console.log("Did you mean: ${topMatches.join(' or ')}?")
}
return false
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy