103103import java .util .jar .JarEntry ;
104104import java .util .jar .JarOutputStream ;
105105import java .util .zip .ZipException ;
106+ import java .util .regex .Matcher ;
107+ import java .util .regex .Pattern ;
106108
107109import javax .script .ScriptEngine ;
108110import 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