Skip to content
RedWolves edited this page Jan 3, 2013 · 3 revisions

anvil.scaffold

anvil.scaffold is the generic scaffolding utility built into Anvil. anvil.scaffold allows you to configure scaffold definitions using a conventional format to generate templated files and directories.

--

Usage

Quick Look

With anvil.scaffold you can create scaffold definitions for generating files and directories. As a simple demo, here is an anvil plugin that will define how additional Anvil plugins could be scaffolded:

anvil.scaffold({
	type: 'plugin',
	description: 'Creates a new anvil plugin',
	prompt: [{
		name: 'name',
		description: 'Please choose a name for this plugin:',
		required: true
	}],
	output: {
		lib: {},
		src: {
			'index.js': anvil.scaffold.file( __dirname + '/plugin.template.js' )
		}
	}
});

The contents of the plugin.template.js is:

module.exports = function (_, anvil) {
	return anvil.plugin({
		name: '{{name}}',
		configure: function (config, command, done) {
			done();
		},
		run: function (done) {
			done()
		}
	});
};

Once this scaffold is executed, it will output lib and src directories and an index.js file with src that has the name populated from metadata supplied by the user at the command line.

API

anvil.scaffold( definition );

anvil.scaffold accepts a single object for defining the scaffold:

anvil.scaffold({
	...
});

Properties

The following is a list of acceptable properties that can be set on the scaffold definition object.

--

type

The unique identifier to reference this scaffold. This type will be used on the command line to specify which scaffold to invoke.

Examples

type: 'plugin'

type: 'jquery-plugin'

type: 'backbone:model'

--

description

A short description that will be shown on the command line when when a user runs anvil generate list

Examples

description: 'Create an empty Backbone project'

description: 'Create a new Anvil plugin'

--

prompt

This is a value used to solicit additional metadata from the user. This metadata will be passed as a model to all files and directories for templating. prompt accepts input in any schema defined by the prompt package.

Examples

// will ask for "name" at command line,
// creating a "name" property for all template models
prompt: ['name'] 
// will require description at command line,
// creating a "name" property for all template models
prompt: [{
	name: 'name',
	description: 'Please choose a name for this plugin:',
	required: true
}]
// will require 2 descriptions at command line,
// creating a "username" and "password" property for all template models,
// hiding the password entered
prompt: [{
	name: 'username',
	description: 'Enter your username:',
	required: true
}, {
	name: 'password',
	description: 'Enter your password:',
	required: true,
	hidden: true
}]

Please see prompt for more detailed usage of the prompt API.

--

output

Define the structure of directories and files to write to the file system. This accepts a map of properties to file and directory names and contents. If a property is an object, a directory will be generated. If a property is a string, a file will be generated. Take the following example:

output: {
	destination: {}
}

Since destination is an object, a directory with that name will be created. It should also be possible to generate directories using a more complex path style:

output: {
	'src/scripts/external': {}
}

This output would create an external directory within src/scripts, additionally generating those directories as well if they did not already exist. Since the value of the object of this destination is empty, the directory itself will also remain empty. Nesting any structures within the object will create a nested directory tree:

output: {
	src: {
		scripts: {
			external: {}
		}
	}
}

In order to generate files, the property's value must be a string. Continuing with our previous example:

output: {
	src: {
		scripts: {
			external: {
				'file.js': '(function () {})();'
			}
		}
	}
}

This would output the same directory structure as before, except now a file.js will be created in the external directory. The contents to use for the file are the string value of the property, or (function () {})();. You may also choose to store the content of the file in another file and read it as a string, possibly making maintenance of these files easier.

In addition, all keys and string values are passed through Handlebars for templating. This allows you to dynamically generate file contents or even file and directory names themselves:

output: {
	src: {
		scripts: {
			'{{dirType}}': {
				'{{scriptName}}.js': '(function ({{lib}}) {})({{lib}});'
			}
		}
	}
}

The data which is used as a model for the templates comes from data optionally supplied in the scaffold or from a user's inputs at the command prompts. This data is automatically merged together for you. The type will be passed to the view template automatically.

Finally, you can supply a function for any one of the values as long as your function passes a either a string (the contents of a file), or an object for a new directory level. The final merged user input and data from your scaffold will be passed into the method as its only argument. These functions are evaluated asynchronously and therefore must invoke their done callback to continue, passing the value into the callback.

This example would generate a different file based on theoretical user input:

output: function ( data, done ) {
	if ( data.short ) {
		done( { "short.js": fileContentsShort } );
	} else {
		done( { "normal.js": fileContentsNormal } );
	}
}

You can (and should) use this to load files just when you need them instead of loading them every time your plugin is fired up:

output: {
	"yourfile.js": function ( data, done ) {
		anvil.fs.read( "./templates/yourfile.js", done );
	}
}

In fact, having a file loaded and templated when the scaffold is invoked is such a common use case that there is also a helper method for handing this:

output: {
	"yourfile.js": anvil.scaffold.file( __dirname + "/templates/yourfile.js" )
}

Using this helper will return a function that will automatically load the file asynchronously and pass the contents back through the done method.

--

data

An object containing additional data to pass to scaffold templates (e.g. file contents and file and directory names). This data will be automatically merged with other data including properties provided by anvil.scaffold and from user input prompt responses. Please note that any properties provided in data that match names provided in prompt will have their values overwritten by user input.

Examples

data: {
	author: 'appendTo',
	status: 'l33t'
},
// Now usable within scaffold templates:
output: {
	scripts: {
		'{{author}}.js': 'console.log("This app is {{status}}");'
	}
}

Methods

The following is a list of methods that have default functionality, but you can override to provide a greater level of control to your scaffolds:

--

render

All keys and values on your output object are passed through this method. The default render method looks like this:

render: function ( data ) {
	var template = Handlebars.compile( data.template );
	return template( data.data );
}

The data argument contains relevant properties necessary for rendering a string for file output:

  • mode: Will either be "name" or "file" based on if it's rendering a file/directory name, or the contents of a file
  • template: The contents of either the file/directory name, or the contents of a file
  • filename: If in "file" mode this will be just the name and extension of the file, otherwise null

To disable templating entirely, pass false as the value for render:

render: false

--

processData

You can override this method to manipulate the data being passed to any templates right before templating occurs, e.g. right after user input has been retrieved. It receives the data as its only parameter, and only what you return will be used as the new template data.

By default it simply returns the data that is passed in.

Example

In this example, the user supplied name is cleaned and prepared to be used as a directory name and key:

prompt: [{
	name: 'name',
	description: 'Please choose a name for your theme:',
	required: true
}],
processData: function ( data ) {
	data.key = data.name
	               .toLowerCase()
	               .trim()
	               .replace( /[^a-z0-9_-]/g, '-' )
	               .replace( /-+/g, '-' );

	return data;
}

--

Command line

Running a scaffold

Invoking a scaffold from the command line:

anvil generate <type>

where type is the name of a scaffold definition. generate is also aliased to gen or new:

anvil gen backbone:model
anvil new jquery-plugin

Listing Available Scaffolds:

If you want to list available scaffolds, use the scaffolds command:

anvil scaffolds