Skip to content

Commit cc8f97e

Browse files
ctruedenclaude
andcommitted
Detect the language of each script template
Templates were categorized by the language of their file extension, so e.g. an appose-python template ended up under Jython. Now honor a #! line or #@script(language=...) directive at the head of the template, falling back to the extension. Also label the [by language] sections and item suffixes using the language's display name. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 parent 19bdebe commit cc8f97e

1 file changed

Lines changed: 61 additions & 4 deletions

File tree

‎src/main/java/org/scijava/ui/swing/script/TextEditor.java‎

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@
103103
import java.util.jar.JarEntry;
104104
import java.util.jar.JarOutputStream;
105105
import java.util.zip.ZipException;
106+
import java.util.regex.Matcher;
107+
import java.util.regex.Pattern;
106108

107109
import javax.script.ScriptEngine;
108110
import javax.script.ScriptException;
@@ -1411,6 +1413,61 @@ private static String languageDisplayName(final ScriptLanguage language) {
14111413
return label == null || label.trim().isEmpty() ? language.getLanguageName() : label;
14121414
}
14131415

1416+
/** Matches a shebang line declaring the script language. */
1417+
private static final Pattern SHEBANG = Pattern.compile("^#!\\s*(.+?)\\s*$");
1418+
1419+
/** Matches the language attribute of a {@code #@script} directive. */
1420+
private static final Pattern SCRIPT_LANGUAGE = Pattern.compile(
1421+
"^\\s*#@script\\s*\\(.*\\blanguage\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')");
1422+
1423+
/**
1424+
* Determines the language of a script template: a language declared by a
1425+
* {@code #!} line or {@code #@script(language="...")} directive in the
1426+
* template's leading lines wins; otherwise, the language associated with the
1427+
* file extension is used.
1428+
* <p>
1429+
* TODO: Ideally, this would use scijava-common API rather than a bespoke
1430+
* mini-parser. {@code ScriptInfo#parseParameters()} does understand these
1431+
* declarations, but also registers the script as a module (see
1432+
* {@code ScriptDirectiveScriptProcessor}), which is unwanted for templates.
1433+
* What we would like there is a side-effect-free way to ask about a script
1434+
* without registering it, e.g. {@code ScriptService#getLanguage(URL)} or a
1435+
* {@code ScriptInfo} flag/subclass which skips {@code addModule}.
1436+
* </p>
1437+
*/
1438+
private ScriptLanguage templateLanguage(final URL url, final String ext) {
1439+
final String declared = declaredLanguage(url);
1440+
if (declared != null) {
1441+
ScriptLanguage lang = scriptService.getLanguageByName(declared);
1442+
if (lang == null) lang = scriptService.getLanguageByExtension(declared);
1443+
if (lang != null) return lang;
1444+
log.warn("Unknown script language '" + declared + "' in template: " + url);
1445+
}
1446+
return ext.isEmpty() ? null : scriptService.getLanguageByExtension(ext);
1447+
}
1448+
1449+
/** Scans the head of a template for a language declaration. */
1450+
private String declaredLanguage(final URL url) {
1451+
try (final BufferedReader in = new BufferedReader(new InputStreamReader(
1452+
url.openStream(), StandardCharsets.UTF_8)))
1453+
{
1454+
for (int i = 0; i < 50; i++) {
1455+
final String line = in.readLine();
1456+
if (line == null) break;
1457+
if (i == 0) {
1458+
final Matcher m = SHEBANG.matcher(line);
1459+
if (m.matches()) return m.group(1);
1460+
}
1461+
final Matcher m = SCRIPT_LANGUAGE.matcher(line);
1462+
if (m.find()) return m.group(1) != null ? m.group(1) : m.group(2);
1463+
}
1464+
}
1465+
catch (final IOException exc) {
1466+
log.debug(exc);
1467+
}
1468+
return null;
1469+
}
1470+
14141471
private void addTemplates(final JMenu templatesMenu) {
14151472
final File baseDir = appService.getApp().getBaseDirectory();
14161473

@@ -1422,10 +1479,10 @@ private void addTemplates(final JMenu templatesMenu) {
14221479
final String ext = FileUtils.getExtension(key);
14231480

14241481
// try to determine the scripting language
1425-
final ScriptLanguage lang = ext.isEmpty() ? null :
1426-
scriptService.getLanguageByExtension(ext);
1482+
final ScriptLanguage lang = templateLanguage(entry.getValue(), ext);
14271483
final String langName = lang == null ? null : lang.getLanguageName();
1428-
final String langSuffix = lang == null ? null : " (" + langName + ")";
1484+
final String langDisplayName = lang == null ? null : languageDisplayName(lang);
1485+
final String langSuffix = lang == null ? null : " (" + langDisplayName + ")";
14291486

14301487
final String path = adjustPath(key, langName);
14311488

@@ -1438,7 +1495,7 @@ private void addTemplates(final JMenu templatesMenu) {
14381495

14391496
// add script to the secondary language-sorted menu structure
14401497
if (langName != null) {
1441-
final String langPath = "[by language]/" + langName + "/" + path;
1498+
final String langPath = "[by language]/" + langDisplayName + "/" + path;
14421499
final JMenu langMenu = getMenu(templatesMenu, langPath, true);
14431500
final JMenuItem langItem = new JMenuItem(label);
14441501
langMenu.add(langItem);

0 commit comments

Comments
 (0)