Custom Macros

Rose Advanced Tutorial: Custom Macros

Custom Macros

Introduction

This tutorial walks you through developing custom macros - Python plugin code that provides extra checking or applies changes to a configuration.

This covers the development of checking (validator), changing (transformer) and reporting (reporter) macros.

Warning

Macros should only be written if there is a genuine need that is not covered by other metadata - make sure you are familiar with metadata capabilities before you write your own (real-life) macros.

For example, fail-if and warn-if metadata options can perform complex inter-setting validation.

Purpose

Macros are used in Rose to report problems with a configuration, and to change it. Nearly all metadata mechanics (checking vs metadata settings, and changing - e.g. trigger) are performed within Rose by Rose built-in macros.

Custom macros are user-defined, but follow exactly the same API - they are just in a different filespace location. They can be invoked via the command line (rose macro) or from within the Metadata menu in the config editor.

Example

These examples use the example suite from the brief tour which you should have familiarised yourself with.

Change directory to that example suite directory, or recreate it.

Location

We are going to develop a macro for the app fred_hello_world/. Change directory to app/fred_hello_world/.

The metadata for the app lives under the meta/ sub directory. Our new macro will live with the metadata.

Explanation

For this example, we want to check the value of the option env=WORLD in our fred_hello_world application. Specifically, for this example, we want our macro to give us an error if the 'world' is too far away from Earth.

Create the directories meta/lib/python/macros/ by running:

mkdir -p meta/lib/python/macros

File Creation

Create an empty file called __init__.py in the directory:

touch meta/lib/python/macros/__init__.py

Finally, create a file called planet.py in the directory:

touch meta/lib/python/macros/planet.py

Validator macro

Open planet.py in a text editor and paste in this text.

This is the bare bones of a rose macro - a bit of Python that is a subclass of rose.macro.MacroBase. At the moment, it doesn't do anything.

Explanation

We need to check the value of the option (env=WORLD) in our app configuration. To do this, we'll generate a list of allowed 'planet' choices that aren't too far away from Earth at the moment.

File Editing

Call a method to get the choices by adding the line

        allowed_planets = self._get_allowed_planets()

at the top of the validate method, so it looks like this:

    def validate(self, config, meta_config=None):
        """Return a list of errors, if any."""
        allowed_planets = self._get_allowed_planets()

Adding a method

Now add the method _get_allowed_planets to the class:

    def _get_allowed_planets(self):
        # Retrieve planets less than a certain distance away.
        cmd_strings = ["curl", "-s",
                       "http://www.heavens-above.com/planetsummary.aspx"]
        p = subprocess.Popen(cmd_strings, stdout=subprocess.PIPE)
        text = p.communicate()[0]
        planets = re.findall("(\w+)</td>",
                             re.sub('(?s)^.*(tablehead.*?ascension).*$',
                                    r"\1", text))
        distances = re.findall("([\d.]+)</td>",
                               re.sub('(?s)^.*(Range.*?Brightness).*$',
                                      r"\1", text))
        for planet, distance in zip(planets, distances):
            if float(distance) > 5.0:
                # The planet is more than 5 AU away.
                planets.remove(planet)
        planets += ["Earth"]  # Distance ~ 0
        return planets

Error text

This will give us a list of valid (nearby) solar system planets which our configuration option should be in. If it isn't, we need to send a message explaining the problem. Add:

    error_text = "planet is too far away."

at the top of the class, like this:

Error text example

class PlanetChecker(rose.macro.MacroBase):

    """Checks option values that refer to planets."""

    error_text = "planet is too far away."
    opts_to_check = [("env", "WORLD")]

    def validate(self, config, meta_config=None):
    """Return a list of errors, if any."""
        allowed_planets = self._get_allowed_planets()

Check

Finally, we need to check if the configuration option is in the list, by replacing

            # Check the option value (node.value) here

with

            if node.value not in allowed_planets:
                self.add_report(section, option, node.value, self.error_text)

Check explanation

The self.add_report call is invoked when the planet choice the user has made is not in the allowed planets. It adds the error information about the section and option (env and WORLD) to the self.reports list, which is returned to the rest of Rose to see if the macro reports any problems.

Check final macro

Your final macro should look like this.

Results

Your validator macro is now ready to use.

Run the config editor by typing:

rose edit

in the application directory. Navigate to the env page, and change the option env=WORLD to Jupiter.

To run the macro, select the top menu Metadata, then the item fred_hello_world, then the item planet.PlanetChecker.validate.

Errors

It should either return an "OK" dialog, or give an error dialog using the error text we wrote - it will depend on the current Earth-Jupiter distance.

If there is an error, the variable should display an error icon on the env page, which you can hover-over to get the error text. You can remove the error by fixing the value and re-running your macro.

Re-running

Try changing the value of env=WORLD to other solar system planets and re-running the macro.

You can also run your macro from the command line in the application directory by invoking:

rose macro planet.PlanetChecker

Transformer macro

We'll now make a macro that changes the configuration. Our example will change the value of env=WORLD to something else.

Open planet.py in a text editor and append this text.

Explanation

This is another bare-bones macro class, although this time it supplies a transform method instead of a validate method.

You can see that it returns a configuration object (config) as well as self.reports. This means that you can modify the configuration e.g. by adding or deleting a variable and then returning the changed config object.

Editing

We need to add some code to make some changes to the configuration.

Replace the line

            # Do something to the configuration.

with:

Editing - text

            if node is None or node.is_ignored():
                continue
            old_planet = node.value
            try:
                index = self.planets.index(old_planet)
            except (IndexError, ValueError):
                new_planet = self.planets[0]
            else:
                new_planet = self.planets[(index + 1) % len(self.planets)]
            config.set([section, option], new_planet)

Editing explanation

This changes the option env=WORLD to the next planet on the list. It will set it to the first planet on the list if it is something else. It will skip it if it is missing or ignored.

We also need to add a change message to flag what we've changed.

Change report

Beneath

            config.set([section, option], new_planet)

add

            message = self.change_text.format(old_planet, new_planet)
            self.add_report(section, option, new_planet, message)

Change report explanation

This makes use of the template self.change_text at the top of the class. The message will be used to provide more information to the user about the change.

Results

Your class should now look like this.

Running it

Your transform macro is now ready to use.

You can run it by running:

rose edit

in the application directory. Select the top menu Metadata, then the item fred_hello_world, then the item planet.PlanetChanger.transform.

Results explanation

It should give a dialog explaining the changes it's made and asking for permission to apply them. If you click OK, the changes will be applied and the value of env=WORLD will be changed. You can Undo and Redo macro changes.

Try running the macro once or twice more to see it change the configuration.

Command line

You can also run your macro from the command line in the application directory by invoking rose macro planet.PlanetChanger.

Reporter macro

Along with validator and transformer macros there are also reporter macros. These are used when you want to output information about a configuration but do not want to make any changes to it.

Next we will write a reporter macro which produces a horoscope entry based on the value of env=WORLD.

Running the reporter macro

Open planet.py and paste in this text.

You will need to add the following line with the other imports at the top of the file.

import random

Next run this macro from the command line by invoking:

rose macro planet.PlanetReporter

Macro Arguments

From time to time, we may want to change some macro settings. Rather than altering the macro each time or creating a separate macro for every possible setting, we can make use of Python keyword arguments.

We will alter the transformer macro to allow us to specify the name of the planet we want to use.

Editing the transformer macro

Open planet.py and alter the PlanetChanger class to look like this.

This adds the planet_name argument to the transform method with a default value of None. On running the macro it will give you the option to specify a value for planet_name. If you do, then that will be used as the new planet.

Running the transformer macro

Save your changes and run the transformer macro either from the command line or rose edit. You should be prompted to provide a value for planet_name. At the command line this will take the form of a prompt while in rose edit you will be presented with a dialog to enter values in, with defaults already entered for you.

Specify a value to use for planet_name using a quoted string, e.g. "Vulcan" and accept the proposed changes. The WORLD variable should now be set to Vulcan. Check your configuration to confirm this.

Metadata Option

If a macro addresses particular sections, namespaces, or options, then it makes sense to write the relationship down in the metadata for the particular settings. You can do this using the macro metadata option.

Example Metadata Option

For example, our validator and transformer macros above are both specific to env=WORLD. Open the file app/fred_hello_world/meta/rose-meta.conf in a text editor, and make sure the file contains the following text:

[env=WORLD]
macro=planet.PlanetChecker, planet.PlanetChanger

Close the config editor if it is still open, and open the app in the config editor again. The env page should now contain a dropdown menu at the top of the page for launching the two macros.

Further Reading

For more information, see the Rose API reference and the Rose configuration metadata macro option reference.