docs: function bakeFile(source, dest, fromWatcher) {
    var args = createArgs(source, {fromWatcher: fromWatcher, watch: options.watch}, true),
        json = {};
        
    // remove any ignored paths from the dest
    args.dest = args.dest.split(path.sep).filter(function (file) {
      return file[0] !== '_';
    }).join(path.sep);
    
    wrapFunction('BakeFile', args, function () {
      var json = {},
          sourceTemplate;
      
      if (args.readOnly.sourceInfo.isIgnoredFile) {
        if (fromWatcher && (['.html', '.js', '.json', '.html.js', '.html.json'].indexOf(args.readOnly.sourceInfo.ext) !== -1)) {
          // redo everything under this directory
          wrapFunction('RerenderFolder', args, function () {
            clearLoaderCache();
            bakeFolder(path.dirname(args.source), path.dirname(args.dest));
          });
        }
      }
      else if (['.html.js', '.html.json'].indexOf(args.readOnly.sourceInfo.ext) !== -1) {
        if (fromWatcher) {
          // re-render the base template if it exists
          wrapFunction('RerenderFile', args, function () {
            
            sourceTemplate = path.join(path.dirname(args.source), args.readOnly.sourceInfo.basename + ".html");
            if (fs.existsSync(sourceTemplate)) {
              clearLoaderCache();
              return renderFile(sourceTemplate, path.join(path.dirname(args.dest), args.readOnly.sourceInfo.basename + ".html"));
            }
            else {
              return false;
            }
          });
        }
      }
      else if (args.readOnly.sourceInfo.ext === '.html') {
      
        // setup the template vars
        args.vars = {
          zoopinator: {
            filename: path.basename(args.source),
            url: args.source.substr(options.sourceRoot.length).replace('\\', '/'),
            scratchpad: scratchpad
          }
        };
        
        wrapFunction('RenderHTML', args, function () {
        
          // load the json down the tree
          args.json = createParents(args.source, '_.json').push(args.source + '.json');
          wrapFunction('LoadJSON', args, function () {
            args.json.forEach(function (file) {
              loadJSON(file, json);
            });
            if (json.vars) {
              args.vars = _.sourceExtend(args.vars, json.vars);
            }
          }); 
          
          // is this a multi-file (a template that expands into multiple dest files)?
          if (args.readOnly.sourceInfo.isMultiHTMLFile) {
          
            wrapFunction('RenderMultiDestHTML', args, function () {
              var oldDest = args.dest;
            
              // try to load the file map from the json first
              args.destMap = json.destMap || {};
              
              // then let the javascript load/change the map
              wrapFunction('GetMultiDestMap', args);
              
              // process each file in the map (dest -> vars)
              args.destMap.forEach(function (fileVars, fileDest) {
                args.dest = fileDest;
                args.vars = fileVars;
                
                wrapFunction('RenderFile', args, function () {
                  return renderFile(args.source, args.dest, args.vars);
                });
              });
              args.dest = oldDest;
            });
          }
          else {
            // render the file
            wrapFunction('RenderFile', args, function () {
              return renderFile(args.source, args.dest, args.vars);
            });
          }
        });
      }
      else {
        // copy the file if newer
        wrapFunction('CopyFile', args, function () {
          return copyFileIfNewer(args.source, args.dest);
        });
      }
      
      if (options.watch) {
        watchOnce(source, function () {
          clearLoaderCache();
          bakeFile(args.source, args.dest, true);
        }, fromWatcher ? 're-watching' : 'watching');
      }
    });
  }