Skip to content

Commit c2c879d

Browse files
pditommasoclaude
andauthored
Add -template option to plugin create command and improve class name normalization (#6334) [ci fast]
- Add hidden -template CLI option to specify plugin template version - Support both tags (starting with 'v') and branches for template selection - Default template version set to v0.3.0 - Update PluginRefactor to remove 'Nf' prefix from normalized class names - Add comprehensive tests for prefix removal logic 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Claude <[email protected]>
1 parent 14be407 commit c2c879d

File tree

4 files changed

+62
-31
lines changed

4 files changed

+62
-31
lines changed

modules/nextflow/src/main/groovy/nextflow/cli/CmdPlugin.groovy

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ class CmdPlugin extends CmdBase {
4848

4949
@Parameter(hidden = true)
5050
List<String> args
51+
52+
@Parameter(names = ['-template'], description = 'Plugin template version to use', hidden = true)
53+
String templateVersion = 'v0.3.0'
5154

5255
@Override
5356
void run() {
@@ -64,7 +67,7 @@ class CmdPlugin extends CmdBase {
6467
Plugins.pull(args[1].tokenize(','))
6568
}
6669
else if( args[0] == 'create' ) {
67-
createPlugin(args)
70+
createPlugin(args, templateVersion)
6871
}
6972
// plugin run command
7073
else if( args[0].contains(CMD_SEP) ) {
@@ -98,26 +101,26 @@ class CmdPlugin extends CmdBase {
98101
}
99102
}
100103

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

105108
final refactor = new PluginRefactor()
106109
if( args.size()>1 ) {
107110
refactor.withPluginName(args[1])
108-
refactor.withOrgName(args[2])
111+
refactor.withProviderName(args[2])
109112
refactor.withPluginDir(Path.of(args[3] ?: refactor.pluginName).toFile())
110113
}
111114
else {
112115
// Prompt for plugin name
113116
print "Enter plugin name: "
114117
refactor.withPluginName(readLine())
115118

116-
// Prompt for maintainer organization
117-
print "Enter organization: "
119+
// Prompt for provider name
120+
print "Enter provider name: "
121+
refactor.withProviderName(readLine())
118122

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

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

134137
// clone the template repo
135-
clonePluginTemplate(targetDir)
138+
clonePluginTemplate(targetDir, templateVersion)
136139
// now refactor the template code
137140
refactor.apply()
138141
// remove git plat
@@ -148,15 +151,25 @@ class CmdPlugin extends CmdBase {
148151
: new BufferedReader(new InputStreamReader(System.in)).readLine()
149152
}
150153

151-
static private void clonePluginTemplate(File targetDir) {
154+
static private void clonePluginTemplate(File targetDir, String templateVersion) {
152155
final templateUri = "https://github.com/nextflow-io/nf-plugin-template.git"
156+
final isTag = templateVersion.startsWith('v')
157+
final refSpec = isTag ? "refs/tags/$templateVersion".toString() : templateVersion
158+
153159
try {
154-
Git.cloneRepository()
160+
final gitCmd = Git.cloneRepository()
155161
.setURI(templateUri)
156162
.setDirectory(targetDir)
157-
.setBranchesToClone(["refs/tags/v0.2.0"])
158-
.setBranch("refs/tags/v0.2.0")
159-
.call()
163+
164+
if (isTag) {
165+
gitCmd.setBranchesToClone([refSpec])
166+
gitCmd.setBranch(refSpec)
167+
} else {
168+
// For branches, let Git handle the default behavior
169+
gitCmd.setBranch(templateVersion)
170+
}
171+
172+
gitCmd.call()
160173
}
161174
catch (Exception e) {
162175
throw new AbortOperationException("Unable to clone pluging template repository - cause: ${e.message}")

modules/nextflow/src/test/groovy/nextflow/cli/CmdPluginCreateTest.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class CmdPluginCreateTest extends Specification {
4040
def args = [
4141
'create',
4242
'hello world plugin',
43-
'foo',
43+
'Foo',
4444
folder.toAbsolutePath().toString() + '/hello']
4545

4646
when:
@@ -58,7 +58,7 @@ class CmdPluginCreateTest extends Specification {
5858
Files.exists(folder.resolve('hello/src/test/groovy/foo/plugin/HelloWorldObserverTest.groovy'))
5959
and:
6060
Path.of(folder.resolve('hello/settings.gradle').toUri()).text.contains("rootProject.name = 'hello-world-plugin'")
61-
Path.of(folder.resolve('hello/build.gradle').toUri()).text.contains("provider = 'foo'")
61+
Path.of(folder.resolve('hello/build.gradle').toUri()).text.contains("provider = 'Foo'")
6262
Path.of(folder.resolve('hello/build.gradle').toUri()).text.contains("className = 'foo.plugin.HelloWorldPlugin'")
6363
Path.of(folder.resolve('hello/build.gradle').toUri()).text.contains("'foo.plugin.HelloWorldExtension'")
6464
Path.of(folder.resolve('hello/build.gradle').toUri()).text.contains("'foo.plugin.HelloWorldFactory'")

modules/nf-commons/src/main/nextflow/plugin/util/PluginRefactor.groovy

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ class PluginRefactor {
3030

3131
private String pluginName
3232

33-
private String orgName
33+
private String providerName
34+
35+
private String packageName
3436

3537
private File pluginDir
3638

@@ -46,8 +48,12 @@ class PluginRefactor {
4648
return pluginName
4749
}
4850

49-
String getOrgName() {
50-
return orgName
51+
String getPackageName() {
52+
return packageName
53+
}
54+
55+
String getProviderName() {
56+
return providerName
5157
}
5258

5359
File getPluginDir() {
@@ -69,37 +75,43 @@ class PluginRefactor {
6975
return this
7076
}
7177

72-
PluginRefactor withOrgName(String name) {
73-
this.orgName = normalizeToPackageNameSegment(name)
74-
if( !orgName )
75-
throw new AbortOperationException("Invalid organization name: '$name'")
78+
PluginRefactor withProviderName(String name) {
79+
this.providerName = name?.trim()
80+
if( !providerName )
81+
throw new AbortOperationException("Provider name cannot be empty or blank")
82+
this.packageName = normalizeToPackageNameSegment(name)
83+
if( !packageName )
84+
throw new AbortOperationException("Invalid provider name: $name")
7685
return this
7786
}
7887

7988
protected void init() {
8089
if( !pluginName )
8190
throw new IllegalStateException("Missing plugin name")
82-
if( !orgName )
83-
throw new IllegalStateException("Missing organization name")
91+
if( !providerName )
92+
throw new IllegalStateException("Missing provider name")
8493
// initial
8594
this.gradleBuildFile = new File(pluginDir, 'build.gradle')
8695
this.gradleSettingsFile = new File(pluginDir, 'settings.gradle')
8796
if( !gradleBuildFile.exists() )
8897
throw new AbortOperationException("Plugin file does not exist: $gradleBuildFile")
8998
if( !gradleSettingsFile.exists() )
9099
throw new AbortOperationException("Plugin file does not exist: $gradleSettingsFile")
91-
if( !orgName )
92-
throw new AbortOperationException("Plugin org name is missing")
100+
if( !providerName )
101+
throw new AbortOperationException("Plugin provider name is missing")
102+
if( !packageName )
103+
throw new AbortOperationException("Plugin package name is missing")
93104
// packages to be updates
94-
tokenMapping.put('acme', orgName)
105+
tokenMapping.put('acme', packageName)
106+
tokenMapping.put('provider-name', providerName)
95107
tokenMapping.put('nf-plugin-template', pluginName)
96108
}
97109

98110
void apply() {
99111
init()
100112
replacePrefixInFiles(pluginDir, pluginClassPrefix)
101-
renameDirectory(new File(pluginDir, "src/main/groovy/acme"), new File(pluginDir, "src/main/groovy/${orgName}"))
102-
renameDirectory(new File(pluginDir, "src/test/groovy/acme"), new File(pluginDir, "src/test/groovy/${orgName}"))
113+
renameDirectory(new File(pluginDir, "src/main/groovy/acme"), new File(pluginDir, "src/main/groovy/${packageName}"))
114+
renameDirectory(new File(pluginDir, "src/test/groovy/acme"), new File(pluginDir, "src/test/groovy/${packageName}"))
103115
updateClassNamesAndSymbols(pluginDir)
104116
}
105117

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

179193
static String normalizeToKebabCase(String input) {

modules/nf-commons/src/test/nextflow/plugin/util/PluginRefactorTest.groovy

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ class PluginRefactorTest extends Specification {
4040
" mixed---separators___" || "MixedSeparators"
4141
"alreadyPascalCase" || "AlreadyPascalCase"
4242
"foo-plugin" || "Foo"
43+
"nf-hello" || "Hello"
44+
"nf-my-plugin" || "My"
45+
"NfHelloWorld" || "HelloWorld"
46+
"my-nf-plugin" || "MyNf"
4347
}
4448

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

0 commit comments

Comments
 (0)