]> git.basschouten.com Git - openhab-addons.git/commitdiff
[jrubyscripting] Add auto-require scripts option (#12381)
authorjimtng <2554958+jimtng@users.noreply.github.com>
Mon, 28 Feb 2022 08:04:52 +0000 (18:04 +1000)
committerGitHub <noreply@github.com>
Mon, 28 Feb 2022 08:04:52 +0000 (09:04 +0100)
Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>
bundles/org.openhab.automation.jrubyscripting/README.md
bundles/org.openhab.automation.jrubyscripting/src/main/java/org/openhab/automation/jrubyscripting/internal/JRubyScriptEngineConfiguration.java
bundles/org.openhab.automation.jrubyscripting/src/main/java/org/openhab/automation/jrubyscripting/internal/JRubyScriptEngineFactory.java
bundles/org.openhab.automation.jrubyscripting/src/main/resources/OH-INF/config/config.xml
bundles/org.openhab.automation.jrubyscripting/src/main/resources/OH-INF/i18n/jruby.properties

index 6a459af2833260656845a42c2649267d330bd151..13bab639702f4d70497481ea35b4fd63a881169e 100644 (file)
@@ -14,7 +14,8 @@ Alternatively, JRuby configuration parameters may be set by creating a `jruby.cf
 | org.openhab.automation.jrubyscripting:rubylib         | $OPENHAB_CONF/automation/lib/ruby/      | Search path for user libraries. Separate each path with a colon (semicolon in Windows).                                                                                                                     |
 | org.openhab.automation.jrubyscripting:local_context   | singlethread                            | The local context holds Ruby runtime, name-value pairs for sharing variables between Java and Ruby. See [this](https://github.com/jruby/jruby/wiki/RedBridge#Context_Instance_Type) for options and details |
 | org.openhab.automation.jrubyscripting:local_variables | transient                               | Defines how variables are shared between Ruby and Java. See [this](https://github.com/jruby/jruby/wiki/RedBridge#local-variable-behavior-options) for options and details                                   |
-| org.openhab.automation.jrubyscripting:gems            |                                         | Comma separated list of [Ruby Gems](https://rubygems.org/) to install.                                                                                                                                      |
+| org.openhab.automation.jrubyscripting:gems            |                                         | A comma separated list of [Ruby Gems](https://rubygems.org/) to install.                                                                                                                                      |
+| org.openhab.automation.jrubyscripting:require            |                                         | A comma separated list of script names to be required by the JRuby Scripting Engine at the beginning of user scripts.                                                                                                                                      |
 
 ## Ruby Gems
 
@@ -22,10 +23,11 @@ This automation add-on will install user specified gems and make them available
 Gem versions may be specified using the standard ruby gem_name=version format.
 The version number follows the [pessimistic version constraint](https://guides.rubygems.org/patterns/#pessimistic-version-constraint) syntax.
 
-For example this configuration will install version 4 or higher of the [openHAB JRuby Scripting Library](https://boc-tothefuture.github.io/openhab-jruby/).
+For example this configuration will install the latest version of the [openHAB JRuby Scripting Library](https://boc-tothefuture.github.io/openhab-jruby/), and instruct the scripting engine to automatically insert `require 'openhab'` at the start of the script. 
 
 ```text
-org.openhab.automation.jrubyscripting:gems=openhab-scripting=~>4.0
+org.openhab.automation.jrubyscripting:gems=openhab-scripting
+org.openhab.automation.jrubyscripting:require=openhab
 ```
 
 ## Creating JRuby Scripts
@@ -51,8 +53,8 @@ log:set DEBUG org.openhab.automation.jrubyscripting
 
 All [ScriptExtensions]({{base}}/configuration/jsr223.html#scriptextension-objects-all-jsr223-languages) are available in JRuby with the following exceptions/modifications:
 
-- The File variable, referencing java.io.File is not available as it conflicts with Ruby's File class preventing Ruby from initializing
-- Globals scriptExtension, automationManager, ruleRegistry, items, voice, rules, things, events, itemRegistry, ir, actions, se, audio, lifecycleTracker are prepended with a $ (e.g. $automationManager) making them available as a global objects in Ruby.
+- The `File` variable, referencing `java.io.File` is not available as it conflicts with Ruby's File class preventing Ruby from initializing
+- Globals `scriptExtension`, `automationManager`, `ruleRegistry`, `items`, `voice`, `rules`, `things`, `events`, `itemRegistry`, `ir`, `actions`, `se`, `audio`, `lifecycleTracker` are prepended with a `$` (e.g. `$automationManager`) making them available as global objects in Ruby.
 
 ## Script Examples
 
@@ -65,8 +67,10 @@ The openHAB server uses the [SLF4J](https://www.slf4j.org/) library for logging.
 require 'java'
 java_import org.slf4j.LoggerFactory
 
-LoggerFactory.getLogger("org.openhab.core.automation.examples").info("Hello world!")
+LoggerFactory.getLogger("org.openhab.automation.examples").info("Hello world!")
 ```
 
 JRuby can [import Java classes](https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby).
-Depending on the openHAB logging configuration, you may need to prefix logger names with `org.openhab.core.automation` for them to show up in the log file (or you modify the logging configuration).
+Depending on the openHAB logging configuration, you may need to prefix logger names with `org.openhab.automation` for them to show up in the log file (or you modify the logging configuration).
+
+**Note**: Installing the [JRuby Scripting Library](https://boc-tothefuture.github.io/openhab-jruby/installation/) will provide enhanced capabilities with simpler rule syntax.
index 5a38184e3d1c88e6c51fabaff6c16f8101825828..9e8b8ea1ff59da956ca3627eb3b9827cc3ca2360 100644 (file)
@@ -20,6 +20,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import javax.script.ScriptEngine;
 import javax.script.ScriptEngineFactory;
@@ -35,6 +36,7 @@ import org.slf4j.LoggerFactory;
  * Processes JRuby Configuration Parameters.
  *
  * @author Brian O'Connell - Initial contribution
+ * @author Jimmy Tanagra - Add $LOAD_PATH, require injection
  */
 @NonNullByDefault
 public class JRubyScriptEngineConfiguration {
@@ -48,6 +50,8 @@ public class JRubyScriptEngineConfiguration {
 
     private static final String GEM_HOME = "gem_home";
     private static final String RUBYLIB = "rubylib";
+    private static final String GEMS = "gems";
+    private static final String REQUIRE = "require";
 
     // Map of configuration parameters
     private static final Map<String, OptionalConfigurationElement> CONFIGURATION_PARAMETERS = Map.ofEntries(
@@ -67,7 +71,10 @@ public class JRubyScriptEngineConfiguration {
                     new OptionalConfigurationElement.Builder(OptionalConfigurationElement.Type.RUBY_ENVIRONMENT)
                             .mappedTo("RUBYLIB").defaultValue(DEFAULT_RUBYLIB.toString()).build()),
 
-            Map.entry("gems", new OptionalConfigurationElement.Builder(OptionalConfigurationElement.Type.GEM).build()));
+            Map.entry(GEMS, new OptionalConfigurationElement.Builder(OptionalConfigurationElement.Type.GEM).build()),
+
+            Map.entry(REQUIRE,
+                    new OptionalConfigurationElement.Builder(OptionalConfigurationElement.Type.REQUIRE).build()));
 
     private static final Map<OptionalConfigurationElement.Type, List<OptionalConfigurationElement>> CONFIGURATION_TYPE_MAP = CONFIGURATION_PARAMETERS
             .values().stream().collect(Collectors.groupingBy(v -> v.type));
@@ -105,16 +112,11 @@ public class JRubyScriptEngineConfiguration {
      * @param factory Script Engine to configure
      */
     void configureScriptEngine(ScriptEngineFactory factory) {
-        configureSystemProperties(CONFIGURATION_TYPE_MAP.getOrDefault(OptionalConfigurationElement.Type.SYSTEM_PROPERTY,
-                Collections.<OptionalConfigurationElement> emptyList()));
+        configureSystemProperties();
 
         ScriptEngine engine = factory.getScriptEngine();
-
-        configureRubyEnvironment(CONFIGURATION_TYPE_MAP.getOrDefault(OptionalConfigurationElement.Type.RUBY_ENVIRONMENT,
-                Collections.<OptionalConfigurationElement> emptyList()), engine);
-
-        configureGems(CONFIGURATION_TYPE_MAP.getOrDefault(OptionalConfigurationElement.Type.GEM,
-                Collections.<OptionalConfigurationElement> emptyList()), engine);
+        configureRubyEnvironment(engine);
+        configureGems(engine);
     }
 
     /**
@@ -122,96 +124,111 @@ public class JRubyScriptEngineConfiguration {
      */
     private void ensureGemHomeExists() {
         OptionalConfigurationElement gemHomeConfigElement = CONFIGURATION_PARAMETERS.get(GEM_HOME);
-        if (gemHomeConfigElement != null) {
-            Optional<String> gemHome = gemHomeConfigElement.getValue();
-            if (gemHome.isPresent()) {
-                File gemHomeDirectory = new File(gemHome.get());
-                if (!gemHomeDirectory.exists()) {
-                    logger.debug("gem_home directory does not exist, creating");
-                    if (!gemHomeDirectory.mkdirs()) {
-                        logger.debug("Error creating gem_home direcotry");
-                    }
+        if (gemHomeConfigElement == null) {
+            return;
+        }
+        Optional<String> gemHome = gemHomeConfigElement.getValue();
+        if (gemHome.isPresent()) {
+            File gemHomeDirectory = new File(gemHome.get());
+            if (!gemHomeDirectory.exists()) {
+                logger.debug("gem_home directory does not exist, creating");
+                if (!gemHomeDirectory.mkdirs()) {
+                    logger.warn("Error creating gem_home directory");
                 }
-            } else {
-                logger.debug("Gem install requested without gem_home specified, not ensuring gem_home path exists");
             }
+        } else {
+            logger.debug("Gem install requested without gem_home specified, not ensuring gem_home path exists");
         }
     }
 
     /**
      * Install a gems in ScriptEngine
      * 
-     * @param gemsDirectives List of gems to install
      * @param engine Engine to install gems
      */
-    private synchronized void configureGems(List<OptionalConfigurationElement> gemDirectives, ScriptEngine engine) {
-        for (OptionalConfigurationElement gemDirective : gemDirectives) {
-            if (gemDirective.getValue().isPresent()) {
-                ensureGemHomeExists();
-
-                String[] gems = gemDirective.getValue().get().split(",");
-                for (String gem : gems) {
-                    gem = gem.trim();
-                    String gemCommand;
-                    if (gem.contains("=")) {
-                        String[] gemParts = gem.split("=");
-                        gem = gemParts[0];
-                        String version = gemParts[1];
-                        gemCommand = "Gem.install('" + gem + "',version='" + version + "')\n";
-                    } else {
-                        gemCommand = "Gem.install('" + gem + "')\n";
-                    }
-
-                    try {
-                        logger.debug("Installing Gem: {} ", gem);
-                        logger.trace("Gem install code:\n{}\n", gemCommand);
-                        engine.eval(gemCommand);
-                    } catch (Exception e) {
-                        logger.error("Error installing Gem", e);
-                    }
-                }
+    private synchronized void configureGems(ScriptEngine engine) {
+        ensureGemHomeExists();
+
+        OptionalConfigurationElement gemsConfigElement = CONFIGURATION_PARAMETERS.get(GEMS);
+        if (gemsConfigElement == null || !gemsConfigElement.getValue().isPresent()) {
+            return;
+        }
+
+        String[] gems = gemsConfigElement.getValue().get().split(",");
+        for (String gem : gems) {
+            gem = gem.trim();
+            String version = "";
+            String gemCommand;
+            if (gem.contains("=")) {
+                String[] gemParts = gem.split("=");
+                gem = gemParts[0].trim();
+                version = gemParts[1].trim();
+            }
+
+            if (gem.isEmpty()) {
+                continue;
+            } else if (version.isEmpty()) {
+                gemCommand = "Gem.install('" + gem + "')\n";
             } else {
-                logger.debug("Ruby gem property has no value");
+                gemCommand = "Gem.install('" + gem + "', '" + version + "')\n";
+            }
+
+            try {
+                logger.debug("Installing Gem: {}", gem);
+                logger.trace("Gem install code:\n{}\n", gemCommand);
+                engine.eval(gemCommand);
+            } catch (ScriptException e) {
+                logger.warn("Error installing Gem: {}", e.getMessage());
+            } catch (BootstrapMethodError e) {
+                logger.warn("Error while checking/installing gems: {}. You may need to restart OpenHAB",
+                        e.getMessage());
+                logger.debug("Error in configureGems", e);
             }
         }
     }
 
     /**
-     * Configure the base Ruby Environment
+     * Execute ruby require statement in the ScriptEngine
      * 
-     * @param engine Engine to configure
+     * @param engine Engine to insert the require statements
      */
-    public ScriptEngine configureRubyEnvironment(ScriptEngine engine) {
-        configureRubyEnvironment(CONFIGURATION_TYPE_MAP.getOrDefault(OptionalConfigurationElement.Type.RUBY_ENVIRONMENT,
-                Collections.<OptionalConfigurationElement> emptyList()), engine);
-        return engine;
+    public void injectRequire(ScriptEngine engine) {
+        OptionalConfigurationElement requireConfigElement = CONFIGURATION_PARAMETERS.get(REQUIRE);
+        if (requireConfigElement == null || !requireConfigElement.getValue().isPresent()) {
+            return;
+        }
+
+        String[] scripts = requireConfigElement.getValue().get().split(",");
+        for (String script : scripts) {
+            final String requireStatement = String.format("require '%s'", script.trim());
+            try {
+                logger.trace("Injecting require statement: {}", requireStatement);
+                engine.eval(requireStatement);
+            } catch (ScriptException e) {
+                logger.warn("Error evaluating statement {}: {}", requireStatement, e.getMessage());
+            }
+        }
     }
 
     /**
      * Configure the optional elements of the Ruby Environment
      * 
-     * @param optionalConfigurationElements Optional elements to configure in the ruby environment
      * @param engine Engine in which to configure environment
      */
-    private void configureRubyEnvironment(List<OptionalConfigurationElement> optionalConfigurationElements,
-            ScriptEngine engine) {
-        for (OptionalConfigurationElement configElement : optionalConfigurationElements) {
-            String environmentProperty = configElement.mappedTo().get();
-            if (configElement.getValue().isPresent()) {
-                String environmentSetting = "ENV['" + environmentProperty + "']='" + configElement.getValue().get()
-                        + "'";
-                try {
-                    logger.trace("Setting Ruby environment with code: {} ", environmentSetting);
-                    engine.eval(environmentSetting);
-                } catch (ScriptException e) {
-                    logger.error("Error setting ruby environment", e);
-                }
-            } else {
-                logger.debug("Ruby environment property ({}) has no value", environmentProperty);
+    public ScriptEngine configureRubyEnvironment(ScriptEngine engine) {
+        getConfigurationElements(OptionalConfigurationElement.Type.RUBY_ENVIRONMENT).forEach(configElement -> {
+            final String environmentSetting = String.format("ENV['%s']='%s'", configElement.mappedTo().get(),
+                    configElement.getValue().get());
+            try {
+                logger.trace("Setting Ruby environment with code: {} ", environmentSetting);
+                engine.eval(environmentSetting);
+            } catch (ScriptException e) {
+                logger.warn("Error setting ruby environment", e);
             }
-        }
+        });
 
         configureRubyLib(engine);
+        return engine;
     }
 
     /**
@@ -244,17 +261,20 @@ public class JRubyScriptEngineConfiguration {
      * 
      * @param optionalConfigurationElements Optional system properties to configure
      */
-    private void configureSystemProperties(List<OptionalConfigurationElement> optionalConfigurationElements) {
-        for (OptionalConfigurationElement configElement : optionalConfigurationElements) {
+    private void configureSystemProperties() {
+        getConfigurationElements(OptionalConfigurationElement.Type.SYSTEM_PROPERTY).forEach(configElement -> {
             String systemProperty = configElement.mappedTo().get();
-            if (configElement.getValue().isPresent()) {
-                String propertyValue = configElement.getValue().get();
-                logger.trace("Setting system property ({}) to ({})", systemProperty, propertyValue);
-                System.setProperty(systemProperty, propertyValue);
-            } else {
-                logger.warn("System property ({}) has no value", systemProperty);
-            }
-        }
+            String propertyValue = configElement.getValue().get();
+            logger.trace("Setting system property ({}) to ({})", systemProperty, propertyValue);
+            System.setProperty(systemProperty, propertyValue);
+        });
+    }
+
+    private Stream<OptionalConfigurationElement> getConfigurationElements(
+            OptionalConfigurationElement.Type configurationType) {
+        return CONFIGURATION_TYPE_MAP
+                .getOrDefault(configurationType, Collections.<OptionalConfigurationElement> emptyList()).stream()
+                .filter(element -> element.getValue().isPresent());
     }
 
     /**
@@ -289,7 +309,8 @@ public class JRubyScriptEngineConfiguration {
         private enum Type {
             SYSTEM_PROPERTY,
             RUBY_ENVIRONMENT,
-            GEM
+            GEM,
+            REQUIRE
         }
 
         private static class Builder {
index 51ad71983e3297f930478ee1d12c8805f306c4bc..c903ab78e6c549c51bcac5da992763827dcb2e90 100644 (file)
@@ -35,6 +35,7 @@ import org.osgi.service.component.annotations.Modified;
  * This is an implementation of a {@link ScriptEngineFactory} for Ruby.
  *
  * @author Brian O'Connell - Initial contribution
+ * @author Jimmy Tanagra - Add require injection
  */
 @NonNullByDefault
 @Component(service = ScriptEngineFactory.class, configurationPid = "org.openhab.automation.jrubyscripting")
@@ -45,7 +46,7 @@ public class JRubyScriptEngineFactory extends AbstractScriptEngineFactory {
 
     // Filter out the File entry to prevent shadowing the Ruby File class which breaks Ruby in spectacularly
     // difficult ways to debug.
-    private static final Set<String> FILTERED_PRESETS = Set.of("File");
+    private static final Set<String> FILTERED_PRESETS = Set.of("File", "Files", "Path", "Paths");
     private static final Set<String> INSTANCE_PRESETS = Set.of();
     private static final Set<String> GLOBAL_PRESETS = Set.of("scriptExtension", "automationManager", "ruleRegistry",
             "items", "voice", "rules", "things", "events", "itemRegistry", "ir", "actions", "se", "audio",
@@ -113,6 +114,12 @@ public class JRubyScriptEngineFactory extends AbstractScriptEngineFactory {
 
         importClassesToRuby(scriptEngine, partitionedMap.getOrDefault(true, new HashMap<>()));
         super.scopeValues(scriptEngine, partitionedMap.getOrDefault(false, new HashMap<>()));
+
+        // scopeValues is called twice. The first call only passed 'se'. The second call passed the rest of the
+        // presets, including 'ir'. We wait for the second call before running the require statements.
+        if (scopeValues.containsKey("ir")) {
+            configuration.injectRequire(scriptEngine);
+        }
     }
 
     private void importClassesToRuby(ScriptEngine scriptEngine, Map<String, Object> objects) {
index b0605b4d2fedd29597c7cb17b974ed6dd5954073..4d9427e17d071101ef50c309f381e8c9f6cf403c 100644 (file)
@@ -6,24 +6,44 @@
                https://openhab.org/schemas/config-description-1.0.0.xsd">
        <config-description uri="automation:jruby">
 
-               <parameter-group name="system">
-                       <label>System Properties</label>
-                       <description>This group defines JRuby system properties.</description>
-                       <advanced>true</advanced>
+               <parameter-group name="gems">
+                       <label>Ruby Gems</label>
+                       <description>This group defines the list of Ruby Gems to install.</description>
                </parameter-group>
 
                <parameter-group name="environment">
                        <label>Ruby Environment</label>
                        <description>This group defines Ruby's environment.</description>
-                       <advanced>false</advanced>
                </parameter-group>
 
-               <parameter-group name="gems">
-                       <label>Ruby Gems</label>
-                       <description>This group defines the list of Ruby Gems to install.</description>
-                       <advanced>false</advanced>
+               <parameter-group name="system">
+                       <label>System Properties</label>
+                       <description>This group defines JRuby system properties.</description>
                </parameter-group>
 
+               <parameter name="gems" type="text" required="false" groupName="gems">
+                       <label>Ruby Gems</label>
+                       <description>A comma separated list of Ruby Gems to install.</description>
+               </parameter>
+
+               <parameter name="require" type="text" required="false" groupName="gems">
+                       <label>Require Scripts</label>
+                       <description>A comma separated list of script names to be required by the JRuby Scripting Engine before running user
+                               scripts.</description>
+               </parameter>
+
+               <parameter name="gem_home" type="text" required="false" groupName="environment">
+                       <label>GEM_HOME</label>
+                       <description>Location Ruby Gems will be installed and loaded, directory will be created if missing and gem installs
+                               are specified. Defaults to "OPENHAB_CONF/scripts/lib/ruby/gem_home" when not specified.</description>
+               </parameter>
+
+               <parameter name="rubylib" type="text" required="false" groupName="environment">
+                       <label>RUBYLIB</label>
+                       <description>Search path for user libraries. Separate each path with a colon (semicolon in Windows). Defaults to
+                               "OPENHAB_CONF/automation/lib/ruby" when not specified.</description>
+               </parameter>
+
                <parameter name="local_context" type="text" required="false" groupName="system">
                        <label>Context Instance Type</label>
                        <description>The local context holds Ruby runtime, name-value pairs for sharing variables between Java and Ruby. See
@@ -35,6 +55,7 @@
                                <option value="singlethread">SingleThread</option>
                                <option value="concurrent">Concurrent</option>
                        </options>
+                       <advanced>true</advanced>
                </parameter>
 
                <parameter name="local_variable" type="text" required="false" groupName="system">
                                <option value="persistent">Persistent</option>
                                <option value="global">Global</option>
                        </options>
+                       <advanced>true</advanced>
                </parameter>
 
-               <parameter name="gem_home" type="text" required="false" groupName="environment">
-                       <label>GEM_HOME</label>
-                       <description>Location Ruby Gems will be installed and loaded, directory will be created if missing and gem installs
-                               are specified</description>
-                       <default></default>
-               </parameter>
-
-               <parameter name="rubylib" type="text" required="false" groupName="environment">
-                       <label>RUBYLIB</label>
-                       <description>Search path for user libraries. Separate each path with a colon (semicolon in Windows).</description>
-               </parameter>
-
-               <parameter name="gems" type="text" required="false" groupName="gems">
-                       <label>Ruby Gems</label>
-                       <description>Comma separated list of Ruby Gems to install.</description>
-               </parameter>
 
        </config-description>
 </config-description:config-descriptions>
index 97e140816a72bebd3f54f1b074798d04c2fdabcd..989c8e5e9c793e55013f8ed37fcd7aa7c42e040d 100644 (file)
@@ -1,14 +1,7 @@
-
-# service
-
-service.automation.jrubyscripting.label = JRuby Scripting
-
-# bundle config
-
 automation.config.jruby.gem_home.label = GEM_HOME
-automation.config.jruby.gem_home.description = Location Ruby Gems will be installed and loaded, directory will be created if missing and gem installs are specified
+automation.config.jruby.gem_home.description = Location Ruby Gems will be installed and loaded, directory will be created if missing and gem installs are specified. Defaults to "OPENHAB_CONF/scripts/lib/ruby/gem_home" when not specified.
 automation.config.jruby.gems.label = Ruby Gems
-automation.config.jruby.gems.description = Comma separated list of Ruby Gems to install.
+automation.config.jruby.gems.description = A comma separated list of Ruby Gems to install.
 automation.config.jruby.group.environment.label = Ruby Environment
 automation.config.jruby.group.environment.description = This group defines Ruby's environment.
 automation.config.jruby.group.gems.label = Ruby Gems
@@ -26,5 +19,11 @@ automation.config.jruby.local_variable.description = Defines how variables are s
 automation.config.jruby.local_variable.option.transient = Transient
 automation.config.jruby.local_variable.option.persistent = Persistent
 automation.config.jruby.local_variable.option.global = Global
+automation.config.jruby.require.label = Require Scripts
+automation.config.jruby.require.description = A comma separated list of script names to be required by the JRuby Scripting Engine before running user scripts.
 automation.config.jruby.rubylib.label = RUBYLIB
-automation.config.jruby.rubylib.description = Search path for user libraries. Separate each path with a colon (semicolon in Windows).
+automation.config.jruby.rubylib.description = Search path for user libraries. Separate each path with a colon (semicolon in Windows). Defaults to "OPENHAB_CONF/automation/lib/ruby" when not specified.
+
+# service
+
+service.automation.jrubyscripting.label = JRuby Scripting