Skip to content


Subversion checkout URL

You can clone with
Download ZIP


Callback #167

merged 4 commits into from

2 participants


Added a callback argument to the document function that will be called once all asynchronous tasks have been completed.

Due to the current run-and-forget approach (which is understandable for optimizing run time), this wasn't as clean as I would have liked. This could easily be improved/simplified if all asynchronous jobs (or maybe just the directory copies) were made synchronous or the flow depended on their completed (i.e. nested callbacks - yuck!).

However, I hope it meets your standards. This should fix #166.


You're right, that is pretty icky (and costs ~10% of the total codebase size!). What's the fundamental problem with the programmatic interface being fire-and-forget as well?


Well, I started with the obvious approach;

if files.length then nextFile() else callback?()

However, as it's the programmatic approach the callback would be getting fired when docco hasn't actually finished yet.

Imagine a scenario where someone's code runs docco programmatically and then copies the output directory somewhere else upon completed. That developer would be relying on the callback function being called once docco's done. However, maybe not all assets had been completely copied over yet, and therefore their final documentation directory would be incomplete and possibly unusable.

The reason for the fancier error handling as well is to support general practise with callback functions, where developers generally expect errors to be handed back as the first argument to their callback function and not thrown where it can't be handled.

As I've stated, nested callbacks or synchronous copies would resolved this but perhaps a library such as async would produce cleaner code. I intentionally avoided this as it added a new dependency and I'm not sure how you'd have felt about that. I have no problems trying again with async (perhaps creating a separate PR for comparisons) if you want?

This was referenced

As this is the main component to enabling docco to be accessible programmatically, I'm not sure code size is a huge concern (my opinion only). Also, even though the alternative (#169) is slightly bigger than this solution it is definitely cleaner and more maintainable.

Again, the final option is to remove asynchronous aspects entirely (i.e use only synchronous functions). This may not be as optimal since processing will have to wait for the copies and each file read before proceeding. However, if you're keen on keeping code size down this might be the best approach as it shouldn't really add any lines. That said; you'd probably have to implement #155 as well since exec doesn't have a synchronous option.

Please let me know if I can do anything else to help with this as I'd really like to see this functionality soon so I can use the latest stuff :wink:


Scratch last comment, I found another way (I had a doh! moment) and thought of a much simpler way to handle the callback. It's much smaller (only 4 additional LOC - in coffeescript at least) and much more readable. Assets are copied last before calling the callback, but I don't see how this could cause any issues.

Let me know what you think.


Ah, there you go. Much better. Small tweaks before I merge:

  • Move the error default callback out of the parameter list, and into the function body.
  • Use && instead of & for the join.

Ah yes, I did forget the extra & and the only reason I added the default argument was to save LOC :wink:

Just pushed these changes so you should be able to pick them up now.


Sorry, but did you test this?

  • It looks like there's no spaces surrounding the &&, which would cause the lines to be munged together.

  • You don't need to use ?= as callback is defined. or= will do nicely.


I did test this and it worked for me (tested on RHEL 6.4).

I'll make the assignment operator change just now along with the padded && if you'd prefer it that way.

((6 lines not shown))
configure options
exec "mkdir -p #{config.output}", ->
- exec "cp -f #{config.css} #{config.output}"
- exec "cp -fR #{config.public} #{config.output}" if fs.existsSync config.public
+ callback ?= (error) -> throw error if error
+ complete = ->
+ exec [
+ "cp -f #{config.css} #{config.output}"
+ "cp -fR #{config.public} #{config.output}" if fs.existsSync config.public

Might have to test what happens when if isn't passed through as the array (containing a string and undefined) will still be joined (i.e. "cp -f #{config.css} #{config.output} && "). I imagine exec will handle this well but should probably check.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Please see the latest changes which incorporate your suggestions.

@jashkenas jashkenas merged commit 0b4678f into jashkenas:master
@neocotic neocotic deleted the neocotic:async-support branch
@neocotic neocotic referenced this pull request from a commit in neocotic/docco
@neocotic neocotic cleaned merge of pull request #167 e2afa52

Fantastic! If/when you accept #168, would you be able to do another minor release so I can pick up this version in a dependency on another project (I'm currently adding docco 0.6 support to grunt-docco)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
Showing with 34 additions and 13 deletions.
  1. +24 −8 docco.js
  2. +10 −5 docco.litcoffee
32 docco.js
@@ -1,27 +1,34 @@
-// Generated by CoffeeScript 1.6.1
+// Generated by CoffeeScript 1.6.2
(function() {
var Docco, commander, config, configure, document, exec, ext, format, fs, getLanguage, highlight, l, languages, marked, parse, path, run, spawn, version, write, _, _ref,
__slice = [].slice;
- document = function(options) {
+ document = function(options, callback) {
if (options == null) {
options = {};
return exec("mkdir -p " + config.output, function() {
- var files, nextFile;
- exec("cp -f " + config.css + " " + config.output);
- if (fs.existsSync(config["public"])) {
- exec("cp -fR " + config["public"] + " " + config.output);
- }
+ var complete, files, nextFile;
+ callback || (callback = function(error) {
+ if (error) {
+ throw error;
+ }
+ });
+ complete = function() {
+ return exec(["cp -f " + config.css + " " + config.output, fs.existsSync(config["public"]) ? "cp -fR " + config["public"] + " " + config.output : void 0].join(' && '), callback);
+ };
files = config.sources.slice();
nextFile = function() {
var source;
source = files.shift();
return fs.readFile(source, function(error, buffer) {
var code, sections;
if (error) {
- throw error;
+ return callback(error);
code = buffer.toString();
sections = parse(source, code);
@@ -29,6 +36,8 @@
write(source, sections);
if (files.length) {
return nextFile();
+ } else {
+ return complete();
@@ -38,6 +47,7 @@
parse = function(source, code) {
var codeText, docsText, hasCode, i, lang, line, lines, match, prev, save, sections, _i, _j, _len, _len1;
lines = code.split('\n');
sections = [];
lang = getLanguage(source);
@@ -78,6 +88,7 @@
format = function(source, sections) {
var code, i, language, section, _i, _len, _results;
language = getLanguage(source);
_results = [];
for (i = _i = 0, _len = sections.length; _i < _len; i = ++_i) {
@@ -92,6 +103,7 @@
write = function(source, sections) {
var destination, first, hasTitle, html, title;
destination = function(file) {
return path.join(config.output, path.basename(file, path.extname(file)) + '.html');
@@ -121,6 +133,7 @@
configure = function(options) {
var dir;
_.extend(config, _.pick.apply(_, [options].concat(;
if (options.template) {
config.layout = null;
@@ -135,6 +148,7 @@
config.template = _.template(fs.readFileSync(config.template).toString());
return config.sources = options.args.filter(function(source) {
var lang;
lang = getLanguage(source, config);
if (!lang) {
console.warn("docco: skipped unknown type (" + m + ")");
@@ -167,6 +181,7 @@
getLanguage = function(source) {
var codeExt, codeLang, lang;
ext = config.extension || path.extname(source) || path.basename(source);
lang = languages[ext];
if (lang && === 'markdown') {
@@ -184,6 +199,7 @@
run = function(args) {
var c;
if (args == null) {
args = process.argv;
15 docco.litcoffee
@@ -77,25 +77,30 @@ assets, reading all the source files in, splitting them up into prose+code
sections, highlighting each file in the appropriate language, and printing them
out in an HTML template.
- document = (options = {}) ->
+ document = (options = {}, callback) ->
configure options
exec "mkdir -p #{config.output}", ->
- exec "cp -f #{config.css} #{config.output}"
- exec "cp -fR #{config.public} #{config.output}" if fs.existsSync config.public
+ callback or= (error) -> throw error if error
+ complete = ->
+ exec [
+ "cp -f #{config.css} #{config.output}"
+ "cp -fR #{config.public} #{config.output}" if fs.existsSync config.public
+ ].join(' && '), callback
files = config.sources.slice()
nextFile = ->
source = files.shift()
fs.readFile source, (error, buffer) ->
- throw error if error
+ return callback error if error
code = buffer.toString()
sections = parse source, code
format source, sections
write source, sections
- nextFile() if files.length
+ if files.length then nextFile() else complete()
Something went wrong with that request. Please try again.