Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 27 additions & 14 deletions modules/nextflow/src/main/groovy/nextflow/cli/CmdPlugin.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ class CmdPlugin extends CmdBase {

@Parameter(hidden = true)
List<String> args

@Parameter(names = ['-template'], description = 'Plugin template version to use', hidden = true)
String templateVersion = 'v0.3.0'

@Override
void run() {
Expand All @@ -64,7 +67,7 @@ class CmdPlugin extends CmdBase {
Plugins.pull(args[1].tokenize(','))
}
else if( args[0] == 'create' ) {
createPlugin(args)
createPlugin(args, templateVersion)
}
// plugin run command
else if( args[0].contains(CMD_SEP) ) {
Expand Down Expand Up @@ -98,26 +101,26 @@ class CmdPlugin extends CmdBase {
}
}

static createPlugin(List<String> args) {
static createPlugin(List<String> args, String templateVersion) {
if( args != ['create'] && (args[0] != 'create' || !(args.size() in [3, 4])) )
throw new AbortOperationException("Invalid create parameters - usage: nextflow plugin create <Plugin name> <Organization name>")
throw new AbortOperationException("Invalid create parameters - usage: nextflow plugin create <Plugin name> <Provider name>")

final refactor = new PluginRefactor()
if( args.size()>1 ) {
refactor.withPluginName(args[1])
refactor.withOrgName(args[2])
refactor.withProviderName(args[2])
refactor.withPluginDir(Path.of(args[3] ?: refactor.pluginName).toFile())
}
else {
// Prompt for plugin name
print "Enter plugin name: "
refactor.withPluginName(readLine())

// Prompt for maintainer organization
print "Enter organization: "
// Prompt for provider name
print "Enter provider name: "
refactor.withProviderName(readLine())

// Prompt for plugin path (default to the normalised plugin name)
refactor.withOrgName(readLine())
// Prompt for project path (default to the normalised plugin name)
print "Enter project path [${refactor.pluginName}]: "
refactor.withPluginDir(Path.of(readLine() ?: refactor.pluginName).toFile())

Expand All @@ -132,7 +135,7 @@ class CmdPlugin extends CmdBase {
final File targetDir = refactor.getPluginDir()

// clone the template repo
clonePluginTemplate(targetDir)
clonePluginTemplate(targetDir, templateVersion)
// now refactor the template code
refactor.apply()
// remove git plat
Expand All @@ -148,15 +151,25 @@ class CmdPlugin extends CmdBase {
: new BufferedReader(new InputStreamReader(System.in)).readLine()
}

static private void clonePluginTemplate(File targetDir) {
static private void clonePluginTemplate(File targetDir, String templateVersion) {
final templateUri = "https://github.com/nextflow-io/nf-plugin-template.git"
final isTag = templateVersion.startsWith('v')
final refSpec = isTag ? "refs/tags/$templateVersion".toString() : templateVersion

try {
Git.cloneRepository()
final gitCmd = Git.cloneRepository()
.setURI(templateUri)
.setDirectory(targetDir)
.setBranchesToClone(["refs/tags/v0.2.0"])
.setBranch("refs/tags/v0.2.0")
.call()

if (isTag) {
gitCmd.setBranchesToClone([refSpec])
gitCmd.setBranch(refSpec)
} else {
// For branches, let Git handle the default behavior
gitCmd.setBranch(templateVersion)
}

gitCmd.call()
}
catch (Exception e) {
throw new AbortOperationException("Unable to clone pluging template repository - cause: ${e.message}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class CmdPluginCreateTest extends Specification {
def args = [
'create',
'hello world plugin',
'foo',
'Foo',
folder.toAbsolutePath().toString() + '/hello']

when:
Expand All @@ -58,7 +58,7 @@ class CmdPluginCreateTest extends Specification {
Files.exists(folder.resolve('hello/src/test/groovy/foo/plugin/HelloWorldObserverTest.groovy'))
and:
Path.of(folder.resolve('hello/settings.gradle').toUri()).text.contains("rootProject.name = 'hello-world-plugin'")
Path.of(folder.resolve('hello/build.gradle').toUri()).text.contains("provider = 'foo'")
Path.of(folder.resolve('hello/build.gradle').toUri()).text.contains("provider = 'Foo'")
Path.of(folder.resolve('hello/build.gradle').toUri()).text.contains("className = 'foo.plugin.HelloWorldPlugin'")
Path.of(folder.resolve('hello/build.gradle').toUri()).text.contains("'foo.plugin.HelloWorldExtension'")
Path.of(folder.resolve('hello/build.gradle').toUri()).text.contains("'foo.plugin.HelloWorldFactory'")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ class PluginRefactor {

private String pluginName

private String orgName
private String providerName

private String packageName

private File pluginDir

Expand All @@ -46,8 +48,12 @@ class PluginRefactor {
return pluginName
}

String getOrgName() {
return orgName
String getPackageName() {
return packageName
}

String getProviderName() {
return providerName
}

File getPluginDir() {
Expand All @@ -69,37 +75,43 @@ class PluginRefactor {
return this
}

PluginRefactor withOrgName(String name) {
this.orgName = normalizeToPackageNameSegment(name)
if( !orgName )
throw new AbortOperationException("Invalid organization name: '$name'")
PluginRefactor withProviderName(String name) {
this.providerName = name?.trim()
if( !providerName )
throw new AbortOperationException("Provider name cannot be empty or blank")
this.packageName = normalizeToPackageNameSegment(name)
if( !packageName )
throw new AbortOperationException("Invalid provider name: $name")
return this
}

protected void init() {
if( !pluginName )
throw new IllegalStateException("Missing plugin name")
if( !orgName )
throw new IllegalStateException("Missing organization name")
if( !providerName )
throw new IllegalStateException("Missing provider name")
// initial
this.gradleBuildFile = new File(pluginDir, 'build.gradle')
this.gradleSettingsFile = new File(pluginDir, 'settings.gradle')
if( !gradleBuildFile.exists() )
throw new AbortOperationException("Plugin file does not exist: $gradleBuildFile")
if( !gradleSettingsFile.exists() )
throw new AbortOperationException("Plugin file does not exist: $gradleSettingsFile")
if( !orgName )
throw new AbortOperationException("Plugin org name is missing")
if( !providerName )
throw new AbortOperationException("Plugin provider name is missing")
if( !packageName )
throw new AbortOperationException("Plugin package name is missing")
// packages to be updates
tokenMapping.put('acme', orgName)
tokenMapping.put('acme', packageName)
tokenMapping.put('provider-name', providerName)
tokenMapping.put('nf-plugin-template', pluginName)
}

void apply() {
init()
replacePrefixInFiles(pluginDir, pluginClassPrefix)
renameDirectory(new File(pluginDir, "src/main/groovy/acme"), new File(pluginDir, "src/main/groovy/${orgName}"))
renameDirectory(new File(pluginDir, "src/test/groovy/acme"), new File(pluginDir, "src/test/groovy/${orgName}"))
renameDirectory(new File(pluginDir, "src/main/groovy/acme"), new File(pluginDir, "src/main/groovy/${packageName}"))
renameDirectory(new File(pluginDir, "src/test/groovy/acme"), new File(pluginDir, "src/test/groovy/${packageName}"))
updateClassNamesAndSymbols(pluginDir)
}

Expand Down Expand Up @@ -173,7 +185,9 @@ class PluginRefactor {
.trim()
// Split by whitespace, capitalize each word, join them
final parts = cleaned.split(/\s+/).collect { it.capitalize() }
return parts.join('').replace('Plugin','')
final result = parts.join('').replace('Plugin','')
// Remove "Nf" prefix only
return result.startsWith('Nf') ? result.substring(2) : result
}

static String normalizeToKebabCase(String input) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ class PluginRefactorTest extends Specification {
" mixed---separators___" || "MixedSeparators"
"alreadyPascalCase" || "AlreadyPascalCase"
"foo-plugin" || "Foo"
"nf-hello" || "Hello"
"nf-my-plugin" || "My"
"NfHelloWorld" || "HelloWorld"
"my-nf-plugin" || "MyNf"
}

def "should normalize strings into kebab-case names"() {
Expand Down