Skip to content

Commit e2cf820

Browse files
committed
Generate plugin index file
Signed-off-by: Ben Sherman <[email protected]>
1 parent 726bf48 commit e2cf820

File tree

4 files changed

+267
-74
lines changed

4 files changed

+267
-74
lines changed

modules/nextflow/src/main/groovy/nextflow/config/schema/JsonRenderer.groovy

Lines changed: 3 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@ package nextflow.config.schema
1818
import groovy.json.JsonOutput
1919
import groovy.transform.TypeChecked
2020
import nextflow.plugin.Plugins
21+
import nextflow.plugin.index.ConfigSchema
2122
import nextflow.script.dsl.Description
22-
import nextflow.script.types.Types
23-
import org.codehaus.groovy.ast.ClassNode
2423

2524
@TypeChecked
2625
class JsonRenderer {
@@ -38,86 +37,16 @@ class JsonRenderer {
3837
final description = clazz.getAnnotation(Description)?.value()
3938
if( scopeName == '' ) {
4039
SchemaNode.Scope.of(clazz, '').children().each { name, node ->
41-
result.put(name, fromNode(node))
40+
result.put(name, ConfigSchema.of(node))
4241
}
4342
continue
4443
}
4544
if( !scopeName )
4645
continue
4746
final node = SchemaNode.Scope.of(clazz, description)
48-
result.put(scopeName, fromNode(node, scopeName))
47+
result.put(scopeName, ConfigSchema.of(node, scopeName))
4948
}
5049
return result
5150
}
5251

53-
private static Map<String,?> fromNode(SchemaNode node, String name=null) {
54-
if( node instanceof SchemaNode.Option )
55-
return fromOption(node)
56-
if( node instanceof SchemaNode.Placeholder )
57-
return fromPlaceholder(node)
58-
if( node instanceof SchemaNode.Scope )
59-
return fromScope(node, name)
60-
throw new IllegalStateException()
61-
}
62-
63-
private static Map<String,?> fromOption(SchemaNode.Option node) {
64-
final description = node.description().stripIndent(true).trim()
65-
final type = fromType(new ClassNode(node.type()))
66-
67-
return [
68-
type: 'Option',
69-
spec: [
70-
description: description,
71-
type: type
72-
]
73-
]
74-
}
75-
76-
private static Map<String,?> fromPlaceholder(SchemaNode.Placeholder node) {
77-
final description = node.description().stripIndent(true).trim()
78-
final placeholderName = node.placeholderName()
79-
final scope = fromScope(node.scope())
80-
81-
return [
82-
type: 'Placeholder',
83-
spec: [
84-
description: description,
85-
placeholderName: placeholderName,
86-
scope: scope.spec
87-
]
88-
]
89-
}
90-
91-
private static Map<String,?> fromScope(SchemaNode.Scope node, String scopeName=null) {
92-
final description = node.description().stripIndent(true).trim()
93-
final children = node.children().collectEntries { name, child ->
94-
Map.entry(name, fromNode(child, name))
95-
}
96-
97-
return [
98-
type: 'Scope',
99-
spec: [
100-
description: withLink(scopeName, description),
101-
children: children
102-
]
103-
]
104-
}
105-
106-
private static String withLink(String scopeName, String description) {
107-
return scopeName
108-
? "$description\n\n[Read more](https://nextflow.io/docs/latest/reference/config.html#$scopeName)\n"
109-
: description
110-
}
111-
112-
private static Object fromType(ClassNode cn) {
113-
final name = Types.getName(cn.getTypeClass())
114-
if( cn.isUsingGenerics() ) {
115-
final typeArguments = cn.getGenericsTypes().collect { gt -> fromType(gt.getType()) }
116-
return [ name: name, typeArguments: typeArguments ]
117-
}
118-
else {
119-
return name
120-
}
121-
}
122-
12352
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Copyright 2024-2025, Seqera Labs
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package nextflow.plugin.index
17+
18+
import groovy.transform.CompileStatic
19+
import nextflow.config.schema.SchemaNode
20+
import nextflow.script.types.Types
21+
import org.codehaus.groovy.ast.ClassNode
22+
23+
@CompileStatic
24+
class ConfigSchema {
25+
26+
static Map<String,?> of(SchemaNode node, String name=null) {
27+
return fromNode(node, name)
28+
}
29+
30+
private static Map<String,?> fromNode(SchemaNode node, String name=null) {
31+
if( node instanceof SchemaNode.Option )
32+
return fromOption(node)
33+
if( node instanceof SchemaNode.Placeholder )
34+
return fromPlaceholder(node)
35+
if( node instanceof SchemaNode.Scope )
36+
return fromScope(node, name)
37+
throw new IllegalStateException()
38+
}
39+
40+
private static Map<String,?> fromOption(SchemaNode.Option node) {
41+
final description = node.description().stripIndent(true).trim()
42+
final type = fromType(new ClassNode(node.type()))
43+
44+
return [
45+
type: 'ConfigOption',
46+
spec: [
47+
description: description,
48+
type: type
49+
]
50+
]
51+
}
52+
53+
private static Map<String,?> fromPlaceholder(SchemaNode.Placeholder node) {
54+
final description = node.description().stripIndent(true).trim()
55+
final placeholderName = node.placeholderName()
56+
final scope = fromScope(node.scope())
57+
58+
return [
59+
type: 'ConfigPlaceholderScope',
60+
spec: [
61+
description: description,
62+
placeholderName: placeholderName,
63+
scope: scope.spec
64+
]
65+
]
66+
}
67+
68+
private static Map<String,?> fromScope(SchemaNode.Scope node, String scopeName=null) {
69+
final description = node.description().stripIndent(true).trim()
70+
final children = node.children().collectEntries { name, child ->
71+
Map.entry(name, fromNode(child, name))
72+
}
73+
74+
return [
75+
type: 'ConfigScope',
76+
spec: [
77+
name: scopeName,
78+
description: withLink(scopeName, description),
79+
children: children
80+
]
81+
]
82+
}
83+
84+
private static String withLink(String scopeName, String description) {
85+
return scopeName
86+
? "$description\n\n[Read more](https://nextflow.io/docs/latest/reference/config.html#$scopeName)\n"
87+
: description
88+
}
89+
90+
private static Object fromType(ClassNode cn) {
91+
final name = Types.getName(cn.getTypeClass())
92+
if( cn.isUsingGenerics() ) {
93+
final typeArguments = cn.getGenericsTypes().collect { gt -> fromType(gt.getType()) }
94+
return [ name: name, typeArguments: typeArguments ]
95+
}
96+
else {
97+
return name
98+
}
99+
}
100+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2024-2025, Seqera Labs
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package nextflow.plugin.index
17+
18+
import java.lang.reflect.Method
19+
20+
import groovy.transform.CompileStatic
21+
import nextflow.script.dsl.Description
22+
import nextflow.script.types.Types
23+
import org.codehaus.groovy.ast.ClassNode
24+
25+
@CompileStatic
26+
class FunctionSchema {
27+
28+
static Map<String,?> of(Method method, String type) {
29+
final name = method.getName()
30+
final returnType = fromType(method.getReturnType())
31+
final parameters = method.getParameters().collect { param ->
32+
[
33+
name: param.getName(),
34+
type: fromType(param.getType())
35+
]
36+
}
37+
38+
return [
39+
type: type,
40+
spec: [
41+
name: name,
42+
returnType: returnType,
43+
parameters: parameters
44+
]
45+
]
46+
}
47+
48+
private static Object fromType(Class c) {
49+
return fromType(new ClassNode(c))
50+
}
51+
52+
private static Object fromType(ClassNode cn) {
53+
final name = Types.getName(cn.getTypeClass())
54+
if( cn.isUsingGenerics() ) {
55+
final typeArguments = cn.getGenericsTypes().collect { gt -> fromType(gt.getType()) }
56+
return [ name: name, typeArguments: typeArguments ]
57+
}
58+
else {
59+
return name
60+
}
61+
}
62+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright 2024-2025, Seqera Labs
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package nextflow.plugin.index
17+
18+
import groovy.json.JsonOutput
19+
import groovy.transform.CompileStatic
20+
import nextflow.config.schema.ConfigScope
21+
import nextflow.config.schema.SchemaNode
22+
import nextflow.config.schema.ScopeName
23+
import nextflow.plugin.extension.Factory
24+
import nextflow.plugin.extension.Function
25+
import nextflow.plugin.extension.Operator
26+
import nextflow.plugin.extension.PluginExtensionPoint
27+
import nextflow.script.dsl.Description
28+
29+
/**
30+
* Build an index of plugin definitions.
31+
*
32+
* @author Ben Sherman <[email protected]>
33+
*/
34+
@CompileStatic
35+
class PluginIndex {
36+
37+
private List<String> extensionPoints
38+
39+
PluginIndex(List<String> extensionPoints) {
40+
this.extensionPoints = extensionPoints
41+
}
42+
43+
List build() {
44+
final classLoader = Thread.currentThread().getContextClassLoader()
45+
46+
// extract schema for each plugin definition
47+
final definitions = []
48+
49+
for( final className : extensionPoints ) {
50+
final clazz = classLoader.loadClass(className)
51+
52+
if( ConfigScope.class.isAssignableFrom(clazz) ) {
53+
final scopeName = clazz.getAnnotation(ScopeName)?.value()
54+
final description = clazz.getAnnotation(Description)?.value()
55+
if( !scopeName )
56+
continue
57+
final node = SchemaNode.Scope.of(clazz, description)
58+
59+
definitions.add(ConfigSchema.of(node, scopeName))
60+
}
61+
62+
if( PluginExtensionPoint.class.isAssignableFrom(clazz) ) {
63+
final methods = clazz.getDeclaredMethods()
64+
for( final method : methods ) {
65+
if( method.getAnnotation(Factory) )
66+
definitions.add(FunctionSchema.of(method, 'Factory'))
67+
else if( method.getAnnotation(Function) )
68+
definitions.add(FunctionSchema.of(method, 'Function'))
69+
else if( method.getAnnotation(Operator) )
70+
definitions.add(FunctionSchema.of(method, 'Operator'))
71+
}
72+
}
73+
}
74+
75+
return definitions
76+
}
77+
}
78+
79+
80+
@CompileStatic
81+
class PluginIndexWriter {
82+
83+
static void main(String[] args) {
84+
if (args.length < 2) {
85+
System.err.println("Usage: PluginIndexWriter <output-path> <class1> [class2] ...")
86+
System.exit(1)
87+
}
88+
89+
final outputPath = args[0]
90+
final extensionPoints = args[1..-1]
91+
92+
// build index
93+
final index = new PluginIndex(extensionPoints).build()
94+
95+
// write index file
96+
final file = new File(outputPath)
97+
file.parentFile.mkdirs()
98+
file.text = JsonOutput.toJson(index)
99+
100+
println "Saved plugin index to $file"
101+
}
102+
}

0 commit comments

Comments
 (0)