Skip to content

Commit d9dce68

Browse files
committed
Merge branch 'script-imports'
This adds initial support for the #@script directive in scripts. It also enriches the behavior of ScriptInfo#getIdentifier().
2 parents 702baa9 + 13231ff commit d9dce68

File tree

9 files changed

+447
-49
lines changed

9 files changed

+447
-49
lines changed

src/main/java/org/scijava/MenuPath.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public MenuPath(final Collection<? extends MenuEntry> menuEntries) {
6262

6363
/**
6464
* Creates a menu path with entries parsed from the given string. Assumes
65-
* "&gt;" as the separator (e.g., "File&gt;New&gt;Image").
65+
* {@code >} as the separator (e.g., {@code File>New>Image}).
6666
*
6767
* @see #PATH_SEPARATOR
6868
*/

src/main/java/org/scijava/script/ScriptInfo.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,11 @@ public ScriptModule createModule() throws ModuleException {
332332
return new ScriptModule(this);
333333
}
334334

335+
@Override
336+
public boolean canRunHeadless() {
337+
return is("headless");
338+
}
339+
335340
// -- Contextual methods --
336341

337342
@Override
@@ -354,7 +359,12 @@ public void setContext(final Context context) {
354359

355360
@Override
356361
public String getIdentifier() {
357-
return "script:" + (path == null ? "<inline>" : path);
362+
final String name = getName();
363+
final String prefix = "script:";
364+
if (name != null) return prefix + name;
365+
if (path != null) return prefix + path;
366+
if (script != null) return prefix + "<" + DigestUtils.bestHex(script) + ">";
367+
return prefix + "<unknown>";
358368
}
359369

360370
// -- Locatable methods --

src/main/java/org/scijava/script/process/DefaultScriptProcessorService.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232

3333
package org.scijava.script.process;
3434

35-
import org.scijava.plugin.AbstractSingletonService;
35+
import org.scijava.plugin.AbstractPTService;
3636
import org.scijava.plugin.Plugin;
3737
import org.scijava.service.Service;
3838

@@ -43,8 +43,7 @@
4343
*/
4444
@Plugin(type = Service.class)
4545
public class DefaultScriptProcessorService extends
46-
AbstractSingletonService<ScriptProcessor> implements
47-
ScriptProcessorService
46+
AbstractPTService<ScriptProcessor> implements ScriptProcessorService
4847
{
4948
// NB: No implementation needed.
5049
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* #%L
3+
* SciJava Common shared library for SciJava software.
4+
* %%
5+
* Copyright (C) 2009 - 2017 Board of Regents of the University of
6+
* Wisconsin-Madison, Broad Institute of MIT and Harvard, Max Planck
7+
* Institute of Molecular Cell Biology and Genetics, University of
8+
* Konstanz, and KNIME GmbH.
9+
* %%
10+
* Redistribution and use in source and binary forms, with or without
11+
* modification, are permitted provided that the following conditions are met:
12+
*
13+
* 1. Redistributions of source code must retain the above copyright notice,
14+
* this list of conditions and the following disclaimer.
15+
* 2. Redistributions in binary form must reproduce the above copyright notice,
16+
* this list of conditions and the following disclaimer in the documentation
17+
* and/or other materials provided with the distribution.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
23+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29+
* POSSIBILITY OF SUCH DAMAGE.
30+
* #L%
31+
*/
32+
33+
package org.scijava.script.process;
34+
35+
import java.util.ArrayList;
36+
import java.util.Collections;
37+
import java.util.List;
38+
import java.util.Map;
39+
import java.util.function.Predicate;
40+
import java.util.regex.Matcher;
41+
import java.util.regex.Pattern;
42+
43+
import org.scijava.convert.ConvertService;
44+
import org.scijava.parse.ParseService;
45+
import org.scijava.plugin.Parameter;
46+
import org.scijava.script.ScriptInfo;
47+
48+
/**
49+
* Abstract base class for {@link ScriptProcessor} plugins that parse lines
50+
* of the form {@code #@directive(...) ...}.
51+
*
52+
* @author Curtis Rueden
53+
*/
54+
public abstract class DirectiveScriptProcessor implements ScriptProcessor {
55+
56+
private final Pattern p = //
57+
Pattern.compile("^#@(\\w*)\\s*(\\((.*)\\))?\\s*(.*)$");
58+
59+
@Parameter
60+
private ConvertService convertService;
61+
62+
@Parameter
63+
private ParseService parser;
64+
65+
private ScriptInfo info;
66+
67+
private Predicate<String> directivesToMatch;
68+
69+
public DirectiveScriptProcessor(final Predicate<String> directivesToMatch) {
70+
this.directivesToMatch = directivesToMatch;
71+
}
72+
73+
// -- ScriptProcessor methods --
74+
75+
@Override
76+
public void begin(final ScriptInfo scriptInfo) {
77+
info = scriptInfo;
78+
}
79+
80+
@Override
81+
public String process(final String line) {
82+
// as quickly as possible, verify that this line is a directive
83+
if (!line.startsWith("#@")) return line;
84+
85+
// parse the directive, and ensure it is well-formed
86+
final Matcher m = p.matcher(line);
87+
if (!m.matches()) return line;
88+
89+
// ensure directive is relevant
90+
final String directive = m.group(1);
91+
if (!directivesToMatch.test(directive)) return line;
92+
93+
// parse attributes (inner match without parentheses)
94+
final String attrString = m.group(3);
95+
final Map<String, Object> attrs = attrString == null ? //
96+
Collections.emptyMap() : parser.parse(attrString, false).asMap();
97+
98+
// retain the rest of the string
99+
final String theRest = m.group(4);
100+
101+
return process(directive, attrs, theRest);
102+
}
103+
104+
// -- Internal methods --
105+
106+
/** Processes the given directive. */
107+
protected abstract String process(final String directive,
108+
final Map<String, Object> attrs, final String theRest);
109+
110+
/** Gets the active {@link ScriptInfo} instance. */
111+
protected ScriptInfo info() {
112+
return info;
113+
}
114+
115+
/** Checks whether some key matches the desired value, ignoring case. */
116+
protected boolean is(final String key, final String desired) {
117+
return desired.equalsIgnoreCase(key);
118+
}
119+
120+
/** Coerces some object into another object of the given type. */
121+
protected <T> T as(final Object v, final Class<T> type) {
122+
final T converted = convertService.convert(v, type);
123+
if (converted != null) return converted;
124+
// NB: Attempt to convert via string.
125+
// This is useful in cases where a weird type of object came back
126+
// (e.g., org.scijava.parse.eval.Unresolved), but which happens to have a
127+
// nice string representation which ultimately is expressible as the type.
128+
return convertService.convert(v.toString(), type);
129+
}
130+
131+
/** Coerces some object into a list of objects of the given type. */
132+
protected <T> List<T> asList(final Object v, final Class<T> type) {
133+
final ArrayList<T> result = new ArrayList<>();
134+
final List<?> list = as(v, List.class);
135+
for (final Object item : list) {
136+
result.add(as(item, type));
137+
}
138+
return result;
139+
}
140+
}

src/main/java/org/scijava/script/process/ParameterScriptProcessor.java

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,9 @@
6262
* supported:
6363
* </p>
6464
* <ul>
65-
* <li>{@code #@<type> <varName>}</li>
66-
* <li>{@code #@<type>(<attr1>=<value1>, ..., <attrN>=<valueN>) <varName>}</li>
65+
* <li>{@code #@ <type> <varName>}</li>
66+
* <li>{@code #@ <type>(<attr1>=<value1>, ..., <attrN>=<valueN>) <varName>}</li>
67+
* <li>{@code #@<IOType> <varName>}</li>
6768
* <li>{@code #@<IOType> <type> <varName>}</li>
6869
* <li>{@code #@<IOType>(<attr1>=<value1>, ..., <attrN>=<valueN>) <type>
6970
* <varName>}</li>
@@ -77,7 +78,8 @@
7778
* <li>{@code <IOType>} - one of {@code INPUT}, {@code OUTPUT}, or {@code BOTH}.
7879
* </li>
7980
* <li>{@code <varName>} - the name of the input or output variable.</li>
80-
* <li>{@code <type>} - the Java {@link Class} of the variable.</li>
81+
* <li>{@code <type>} - the Java {@link Class} of the variable, or
82+
* {@link Object} if none specified.</li>
8183
* <li>{@code <attr*>} - an attribute key.</li>
8284
* <li>{@code <value*>} - an attribute value.</li>
8385
* </ul>
@@ -90,9 +92,9 @@
9092
* <ul>
9193
* <li>{@code #@Dataset dataset}</li>
9294
* <li>{@code #@double(type=OUTPUT) result}</li>
93-
* <li>{@code #@BOTH ImageDisplay display}</li>
94-
* <li>{@code #@INPUT(persist=false, visibility=INVISIBLE) boolean verbose}
95-
* </li>
95+
* <li>{@code #@both ImageDisplay display}</li>
96+
* <li>{@code #@input(persist=false, visibility=INVISIBLE) boolean verbose}</li>
97+
* <li>{@code #@output thing}</li>
9698
* </ul>
9799
* <p>
98100
* Parameters will be parsed and filled just like @{@link Parameter}-annotated
@@ -117,14 +119,15 @@ public class ParameterScriptProcessor implements ScriptProcessor {
117119
private LogService log;
118120

119121
private ScriptInfo info;
120-
private boolean header = true;
122+
private boolean header;
121123

122124
// -- ScriptProcessor methods --
123125

124126
@Override
125127
public void begin(final ScriptInfo scriptInfo) {
126128
info = scriptInfo;
127129
info.setReturnValueAppended(true);
130+
header = true;
128131
}
129132

130133
@Override
@@ -187,11 +190,18 @@ private boolean parseParam(final String param,
187190
final String typeName, varName;
188191
final String maybeIOType = tokens[0].toUpperCase();
189192
if (isIOType(maybeIOType)) {
190-
// assume syntax: <IOType> <type> <varName>
191-
if (tokens.length < 3) return false;
193+
if (tokens.length == 2) {
194+
// <IOType> <varName>
195+
typeName = "Object";
196+
varName = tokens[1];
197+
}
198+
else if (tokens.length == 3) {
199+
// <IOType> <type> <varName>
200+
typeName = tokens[1];
201+
varName = tokens[2];
202+
}
203+
else return false;
192204
attrs.put("type", maybeIOType);
193-
typeName = tokens[1];
194-
varName = tokens[2];
195205
}
196206
else {
197207
// assume syntax: <type> <varName>

0 commit comments

Comments
 (0)