Writing a Grunt Plugin that Uses other Plugins

I’ve been using Grunt for a while now, and wanted to package some reusable functionality from one project into a plugin, so that it could easily be used in other projects. For some reason, this turned out to be a lot less intuitive than I was expecting. I read the official page on Creating Plugins, but it doesn’t mention a variety of details you need to pay attention to if you want to encapsulate other plugins within your own plugin.

There are several gotchas. First, if you simply port the 3rd-party tasks’ configurations to the plugin’s Gruntfile.js, your plugin will work when you test it (from the plugin source), but it will fail when you use your plugin, since the plugin’s Gruntfile doesn’t get processed when it is run as a plugin. You also have to explicitly load tasks you want to use, and be mindful of how you use paths to reference them.

Here are my tips on writing plugins:

  1. Clone the grunt-init gruntplugin repository and run grunt-init gruntplugin to setup the plugin template, as described in the Grunt documentation on creating plugins. This will be your plugin source. You’ll be able to run Grunt in there to test it.
  2. Right away, add your plugin to the project you wish to use it in, so that you can test it as a plugin while you develop. You don’t need to publish it, instead, in your project package.json, add it as a devDependency with a URL to its GIT repository:
    myproject/package.json
      "devDependencies": {
        "grunt-myplugin": "git@bitbucket.org:antun/grunt-myplugin.git"
      }

    As you make updates, you’ll need to commit/push, then uninstall/reinstall the plugin from your project as follows:

    $ npm rm grunt-myplugin
    $ npm install
  3. Add your 3rd-party plugins to your plugin source’s package file as dependencies, rather than devDependencies. This will ensure they’re installed under your project when your plugin is installed.
    package.json
      "dependencies": {
        "grunt-shell" : "1.1.1"
      },
  4. Explicitly load the 3rd party task you want to use, from your plugin source’s task file. This is necessary since your plugin won’t automatically have access to its own dependencies, when it’s used as a plugin in another project. Do this near the top of the task file. e.g. If you rely on the grunt-shell module within your plugin, would do:
    tasks/buttonimage.js
    module.exports = function(grunt) {
      var path = require('path');
      grunt.task.loadTasks(path.resolve(__dirname, '../node_modules/grunt-shell/tasks'));
      // load any other tasks, and task registration
    }

    Note also that I used the Node.js path.resolve() method to handle loading the URL relative to the plugin, no matter where it’s run from. Since path is a core Node.js module, you can require it without any special dependency handling.

  5. Define your plugin’s custom task(s) in its source Gruntfile. Organize the options as you would like to see them used when it is used as a plugin. Remember, this configuration will only be used when you run your plugin’s tasks from within the source.
    Gruntfile.js
      grunt.initConfig({
        buttonimage: {
          testButton: { 
              output: 'dist/testButton.gif'
          }
        }
        /* Other configuration, test, stuff */
      }

    You can use this.options() inside of your

  6. Don’t bother adding configuration for your 3rd-party tasks in the plugin source Gruntfile. The source Gruntfile doesn’t get processed when you run your plugin in another project. Instead, set the configuration procedurally from within your task file using grunt.config.set() before running the task. e.g.
    tasks/buttonimage.js
      grunt.registerMultiTask('buttonimage', 'Generate localized buttons with text labels.', function() {
        var cmd = 'some shell command you built up';
        grunt.config.set('shell', {
            buttonImage: {
                command: cmd
            }
          }
        );
        grunt.task.run(['shell:buttonImage']);
      });
  7. Don’t change the working directory in your task, so work with absolute paths instead. Use path.resolve() to get an absolute path relative to the top-level Gruntfile for files that you will want to operate on. When you define file paths in the Gruntfile, whether that’s the one in your plugin source config or eventually the one in the project where you will use your plugin, you will want to convert them to absolute paths. To do this, use the Node.js path.resolve() method to convert the input path – which is relative to the root Gruntfile – to an absolute path:
    tasks/buttonimage.js
      grunt.registerMultiTask('buttonimage', 'Generate localized buttons with text labels.', function() {
        var outputPath = path.resolve(this.options().output);
        var cmd = "convert -some arguments " + outputPath
        /* Set the config and run your task */
      }

    If you need to get an absolute path relative to your task definition (e.g. to run a Node.js script that you have bundled inside your plugin), you can use path.resolve(__dirname, ‘path/relative/to/task/file.js’) to get that.

Hopefully these will save you some time!

Arduino Stopwatch Experiment

I’ve been working my way through the lessons in the SunFounder LCD Ultrasonic Relay Sensor Electronic Bricks Starter Kit. The kit comes with a manual, and the early experiments have pretty detailed steps with electronics diagrams.

But by the time you get to the stopwatch experiment (lesson 13) there are no more diagrams, so I was left to figure out how to connect the 4-digit 7-segment display. My display had the part number SMA420564, and it had 12 pins. I couldn’t find a spec sheet for it, to understand which pins referred to which segment/digit. After a bit of trial and error, I figured it out:

Pin Diagram for SMA420564

Pin Diagram for SMA420564

The way I figured it out which pin mapped to which was to adapt the example program that came with the kit to light all segments at once (i.e. “8.”) and then plug/unplug wires until I figured out which was which. I don’t really know the pin numbers, but as long as you know which digit/segment they light, that’s enough. The pins d1, d2, d3, and d4 correspond with each digit, with d1 being the first on the left. The pins with capital letters correspond with the segments A-G of the 7-segment character. The naming convention appears to be quite standardized. The diagram below shows which letter corresponds with which segment:

7-segment-display-labeled

 

Here’s a wiring schematic to show which pins of the Arduino each pin goes to. Note that in the diagram below, the LED display is rotated 90º clockwise:

Timer_Schematic

Schematic for Experiment 13 in Sunfounder Ultrasonic Kit for Arduino

 

I used Upverter to create the diagram, and you can access it directly on the Upverter site.

With everything wired, it was time to try the code. The kit I had came with one of those small CDs, so I actually downloaded it from the Sunfounder web site. It’s lesson #13 in the Ultrasonic Kit for Arduino here. When I tried to validate the stopwatch, it failed with the following error:

Timer1 was not declared in this scope

Turns out that there are some C++ libraries that the Sunfounder example code relies on, which weren’t in my environment. I found this blog post that talks about the error, and there’s a link to download the TimerOne library. After downloading it I wasn’t really sure what to do with the .h and .cpp files. I tried placing them in the same directory as my .ino file, but that didn’t help. Eventually I figured out that you have to import it via the Arduino IDE (Sketch>Import Library>Add Library… and then select the folder that contains the TimerOne.cpp and TimerOne.h files.

With that done, I was able to burn the program to my Arduino, and now have a happy stopwatch:

stopwatch

:requirements Method for Rails 3 Routes

In Rails 2, if you wanted to have a parameter on a URL that had a period (e.g. a decimal point in latitude/longitude coordinates), you needed to use the :requirements method to tell Rails to include everything in the URL.

e.g.if your URL was /spots/new_popup/37.77617617425586,-122.39735126495361

… then the Rails 2 route would look like this:

map.connect '/spots/new_popup/:coords', :requirements => {:coords => /.*/}, :controller => 'spots', :action => 'new_popup'

In Rails 3, there :requirements method has been replaced with :constraints. So the route above would be re-written like this:

match '/spots/new_popup/:coords', :constraints => {:coords => /.*/}, :controller => 'spots', :action => 'new_popup'