7. Cake and Cakefiles

CoffeeScript offers a simple build tool, called Cake, that is very similar in nature to Ruby’s Rake1 tool. Cake enables you to define simple tasks to help with your CoffeeScript projects. Perhaps you need a task to run your tests or a task to build your files. Cake lets you define those tasks easily in a file called a Cakefile.

In this chapter you’ll learn how to define and execute Cake tasks, and of course, we’ll be doing it all using CoffeeScript.


Tip

“Wait a minute, how do we install Cake?” You don’t have to! When you installed CoffeeScript, the installer also installed Cake and its command-line tool, aptly named cake. You don’t have to do anything extra to get access to this tool.


Getting Started

Before we write our first Cake task, it’s important that we understand a few things about how Cake works. The first thing we need to know about Cake is that all tasks must live in a file named Cakefile and that this file must live in the directory in which you want to run your Cake tasks. Typically, this is the root directory of your project.

The only other thing you need to know about Cake and Cakefiles is that the Cakefile must be written in CoffeeScript. Cakefiles also have a few special functions already available to help you write your tasks. We’ll see those in action as we build some Cake tasks.

Creating Cake Tasks

Let’s build our first Cake task—a simple “hello world” task:

Example: (source: example1/Cakefile)


task "greet", "Say hi to the nice people", ->
  console.log "Hello, World!"


Example: (source: example1/Cakefile.js)


(function() {

  task("greet", "Say hi to the nice people", function() {
    return console.log("Hello, World!");
  });

}).call(this);



Tip

Although you will never see the JavaScript that is generated under the covers for Cakefiles, I decided to still include its output in this chapter to better help you further grasp what your CoffeeScript is doing.


To define our Cake task, we have to call the task function that gets automatically added to every Cakefile. The first argument is the name of the task; this is what we will use on the command line to execute the task. The second argument, which is optional, is a description of the task. If supplied, the description will appear when we print out a list of tasks that are available to us. The last argument we pass into the task function is a function that will be executed when we execute the task. This function is where the heavy lifting of our task is done.

To print out which tasks are available, we can use the cake command-line tool:

> cake

If we were to run that against our Cakefile, we would get the following output:

Output: (source: example1/Cakefile)


Cakefile defines the following tasks:

cake greet                # Say hi to the nice people


As you can see in the output, we see the name of the task we defined, preceded with the cake command, as well as the description we gave to the task.


Tip

Being able to get a list of available tasks is incredibly useful if you start requiring libraries that may have their own built-in Cake tasks.


Running Cake Tasks

Now that we’ve created our first task, how do we run it? Easy. When I ran the cake command against our example, it told us to run our task. Simply type in cake followed by the name of the task you would like to run, like this:

> cake greet

That will run our greet Cake task.

Output:


Hello, World!


Using Options

We have our first task written, and we know how to execute it, but what if we want to pass some arguments into it? For example, what if we want our greet task to accept an option so we can customize who we greet? Let’s look at how Cake lets us do that.

The first step in being able to pass options to our tasks is to define the option. We do this using the special option function that Cake gives us. This function takes three arguments. The first argument is the “short” form of the option, the second argument is the “long” form of the option, and the final argument is a simple description of what the option does. Let’s take a look at our greet task again, and this time let’s define an option so we can customize the greeting.


Tip

When I talk about the “long” or “short” form of an option, I really am talking about how much typing the user has to do. For example, -n is a short option and --name is a long option.


Example: (source: example2/Cakefile)


option '-n', '--name [NAME]', 'name you want to greet'
task "greet", "Say hi to someone", (options)->
  message = "Hello, "
  if options.name?
    message += options.name
  else
    message += "World"
  console.log message


Example: (source: example2/Cakefile.js)


(function() {

  option('-n', '--name [NAME]', 'name you want to greet'),

  task("greet", "Say hi to someone", function(options) {
    var message;
    message = "Hello, ";
    if (options.name != null) {
      message += options.name;
    } else {
      message += "World";
    }
    return console.log(message);
  });

}).call(this);


As you can see, before our task definition we called the option function and passed it the three arguments it expects. The first argument is -n, the short form of the option. The second is --name, the long form of the option. Notice that in the example, as part of the long form option, I have also put [NAME], which tells Cake that you are expecting a value there. If you do not put something like [NAME], Cake will raise an error if you try to pass in a value with the option, the last argument being the description.


Tip

Although all three arguments are required for the option function, only the last two arguments are truly necessary. You have to provide both the long form option as well as the description, but the short form of the option is not required. If you don’t want to have a short form for the option, either pass in an empty string or null as the first parameter.


Now when we run the cake command to see the list of available tasks, we should see this:

Output: (source: example2/Cakefile)


Cakefile defines the following tasks:

cake greet                # Say hi to someone

  -n, --name         name you want to greet


At the bottom of the output we can see the list of available options for the tasks.


Tip

I want to point out what I feel to be a shortcoming of Cake options. Options are not defined for a specific task; instead, they are available for all tasks. If, in addition to our greet task, we were to have a second task, both tasks would accept the name option we defined. Although this isn’t the end of the world, it does mean that some care does need to be taken when naming the options and providing their descriptions.


If we look at our greet task again, we can see that we are passing an object, options, into the function that will be executed when the task is executed:

Example: (source: example2/Cakefile)


option '-n', '--name [NAME]', 'name you want to greet'
task "greet", "Say hi to someone", (options)->
  message = "Hello, "
  if options.name?
    message += options.name
  else
    message += "World"
  console.log message


Example: (source: example2/Cakefile.js)


(function() {

  option('-n', '--name [NAME]', 'name you want to greet'),

  task("greet", "Say hi to someone", function(options) {
    var message;
    message = "Hello, ";
    if (options.name != null) {
      message += options.name;
    } else {
      message += "World";
    }
    return console.log(message);
  });

}).call(this);


With the options object, we can determine if someone has called the task with the name option. If the task is run with the name option, we will greet that name; if not, we will use a generic greeting.

We can run our greet task with the name option like this:

> cake -n Mark greet

Output: (source: example2a/Cakefile)


Hello, Mark


If you want an option to be required, you would have to do that manually in the task definition itself by checking the existence of the option and throwing an error if it doesn’t exist:

Example: (source: example2a/Cakefile)


option '-n', '--name [NAME]', 'name you want to greet'
task "greet", "Say hi to someone", (options)->
  throw new Error("[NAME] is required") unless options.name?
  console.log "Hello, #{options.name}"


Example: (source: example2a/Cakefile.js)


(function() {

  option('-n', '--name [NAME]', 'name you want to greet'),

  task("greet", "Say hi to someone", function(options) {
    if (options.name == null) throw new Error("[NAME] is required");
    return console.log("Hello, " + options.name);
  });

}).call(this);


Output: (source: example2a/Cakefile)


node.js:201
        throw e; // process.nextTick error, or 'error' event on first tick
              ^
Error: [NAME] is required
    at Object.action (.../cake/example2a/Cakefile:6:37)
    at /usr/local/lib/node_modules/coffee-script/lib/coffee-script/cake.js:39:26
    at Object.run (/usr/local/lib/node_modules/coffee-script/lib/coffee-script/ cake.js:62:21)
    at Object.<anonymous> (/usr/local/lib/node_modules/coffee-script/bin/cake:7:38)
    at Module._compile (module.js:432:26)
    at Object..js (module.js:450:10)
    at Module.load (module.js:351:31)
    at Function._load (module.js:310:12)
    at Array.0 (module.js:470:10)
    at EventEmitter._tickCallback (node.js:192:40)



Tip

When running a Cake task with options, it is important to note that all options must be placed before the name of the task. If the options are placed after the task, you will be greeted with a rather unfriendly error. To me this seems backward. I would prefer cake greet -n Mark, but, unfortunately, at the time of writing that’s not possible.


Invoking Other Tasks

There will come a time when you want to execute other tasks from inside another task. Here’s an example: Two common tasks you might have in a project would be a task to clean up your build directories and another task to compile and build your project. Let’s write these two tasks:

Example: (source: example3/Cakefile)


task "clean", "Clean up build directories", ->
  console.log "cleaning up..."

task "build", "Build the project files", ->
  console.log "building..."


Example: (source: example3/Cakefile.js)


(function() {

  task("clean", "Clean up build directories", function() {
    return console.log("cleaning up...");
  });

  task("build", "Build the project files", function() {
    return console.log("building...");
  });

}).call(this);


Output: (source: example3/Cakefile)


Cakefile defines the following tasks:

cake clean                # Clean up build directories
cake build                # Build the project files


After using these tasks for a while, you realize that you are always running the clean task and then the build task. You can run both tasks with one command, like this:

> cake clean build

That will run both tasks for you. But what if want to have a third task, package, that will package up your project for you? Before you can neatly package up your project, you want to first make sure you build it—and before you build it, you want to make sure that the build directories are clean. You could do it like this:

> cake clean build package

The problem is that this approach is error prone. What if you forget to call the build or clean tasks first? What happens? Fortunately, Cake lets us invoke other tasks from inside a task. To do that, we can call the invoke function that Cake provides for us and give it the name of the task we want to call:

Example: (source: example4/Cakefile)


task "clean", "Clean up build directories", ->
  console.log "cleaning up..."

task "build", "Build the project files", ->
  console.log "building..."

task "package", "Clean, build, and package the project", ->
  invoke "clean"
  invoke "build"
  console.log "packaging..."


Example: (source: example4/Cakefile.js)


(function() {

  task("clean", "Clean up build directories", function() {
    return console.log("cleaning up...");
  });

  task("build", "Build the project files", function() {
    return console.log("building...");
  });

  task("package", "Clean, build, and package the project", function() {
    invoke("clean");
    invoke("build");
    return console.log("packaging...");
  });

}).call(this);


Output: (source: example4/Cakefile)


Cakefile defines the following tasks:

cake clean                # Clean up build directories
cake build                # Build the project files
cake package              # Clean, build, and package the project


Now we can call the package task and see that both the clean and build tasks are also being executed:

> cake package

Output: (source: example4/Cakefile)


cleaning up...
building...
packaging...



Tip

It’s important to note that when invoking other tasks, they will be run asynchronously. So in our example, there is no guarantee that the clean task will be finished before the build task is run. This could potentially cause issues, so be careful. The same is true of executing them by chaining them to a single cake command on the command line. Caveat emptor.


Wrapping Up

In this chapter we dug through the build tool that ships with CoffeeScript, called Cake. You learned how to define new tasks and see which tasks are available. You saw how to execute tasks, give them options, and run multiple tasks at once. You might feel as though you didn’t really learn much about writing useful Cake tasks. The reason is that this is all that Cake offers. It is up to you to fill those tasks with what you need. The Node.js2 project, which I talk about in Chapter 9, “Intro to Node.js,” is a great place to start when looking for useful modules you can use to read and write files and directories, compile CoffeeScript files, make HTTP requests, and more. Other chapters will include Cakefiles to do things such as run tests, so be on the lookout for those examples.

Although Cake is a good tool, and it’s nice to have it automatically installed with CoffeeScript, I do find it a bit too weak with too many awkward idiosyncrasies for it to be useful for most tasks I want to write. When combined with modules from Node3 and other available modules, it can be quite powerful, but I still find myself falling back to my Ruby roots and using Rake because it’s much more polished than Cake.

However, I highly encourage you to give it a shot. It might be exactly the right tool for you, especially if you’re limited in what languages you can use and have installed; then it’s a no-brainer.

Notes

1. https://github.com/jimweirich/rake

2. http://nodejs.org/

3. http://nodejs.org/

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.9.148