docs: function() {
      var XML_GRAMMAR_SPEC = 'grammars/xml.peg';
      var NAMESPACE_SPEC = xml2js.opts.inputdir + '/namespace' + xml2js.opts.module + '.xml';
      var CLASS_SPEC = function(c) { return xml2js.opts.inputdir + '/' + c + '.xml'; }
      var TYPES_SPEC = xml2js.opts.inputdir + '/types_8h.xml';
      xml2js.MODULE = xml2js.opts.module;
      return Promise.join(createXmlParser(XML_GRAMMAR_SPEC),
                          xml2js.opts.typemaps ? initCustomPointerTypemaps(xml2js.opts.typemaps) : Promise.resolve(),
                          fs.readFileAsync(NAMESPACE_SPEC, 'utf8'),
                          fs.existsSync(TYPES_SPEC) ? fs.readFileAsync(TYPES_SPEC, 'utf8') : Promise.resolve(null),
                          function(xmlparser, ignore, xml, xml_types) {
        if (xml_types != null) {
          _.extend(xml2js.ENUMS, getEnums(xmlparser.parse(xml_types)[0], false));
          _.extend(xml2js.ENUMS_BY_GROUP, getEnums(xmlparser.parse(xml_types)[0], true));
        }
        var spec_c = xmlparser.parse(xml)[0];
        _.extend(xml2js.ENUMS, getEnums(spec_c, false));
        _.extend(xml2js.ENUMS_BY_GROUP, getEnums(spec_c, true));
        _.extend(xml2js.METHODS, getMethods(spec_c));
        _.each(getSubclassNames(spec_c), function(className) { xml2js.CLASSES[className] = {} });
        var parseClasses = _.map(getSubclasses(spec_c), function(c) {
          return fs.readFileAsync(CLASS_SPEC(c), 'utf8').then(function(xml) {
            try {
              var spec_c = xmlparser.parse(xml)[0];
              var className = getName(spec_c);
              _.extend(xml2js.CLASSES[className], {
                description: getDescription(spec_c),
                parent: getParent(spec_c, className),
                enums: getEnums(spec_c, false, className),
                enums_by_group: getEnums(spec_c, true, className),
                variables: getVariables(spec_c, className),
                methods: getMethods(spec_c, className)
              });
            } catch(e) {
              console.log(e.toString() + ': class ' + className + ' was not parsed correctly.');
            }
          });
        });
        var parseGroups = fs.readdirAsync(xml2js.opts.inputdir).then(function(files) {
          var groupxmlfiles = _.filter(files, function(fn) {
            return ((path.extname(fn) == '.xml') && (path.basename(fn).search(/^group/) != -1));
          });
          return Promise.all(_.map(groupxmlfiles, function(fn) {
            return fs.readFileAsync(xml2js.opts.inputdir + '/' + fn, 'utf8').then(function(xml) {
              var spec_c = xmlparser.parse(xml)[0];
              if (_.isEmpty(getSubmodules(spec_c))) {
                var group = getName(spec_c);
                var classes = getSubclassNames(spec_c);
                xml2js.CLASSGROUPS[group] = {
                  description: getDescription(spec_c),
                  classes: classes
                };
                _.each(classes, function(c) {
                  if (_.has(xml2js.CLASSES, c)) {
                    xml2js.CLASSES[c].group = group;
                  } else {
                    console.log('Warning: Group ' + group + ' has unknown class ' + c);
                  }
                });
              }
            });
          }));
        });
        return Promise.all(parseClasses.concat(parseGroups));
      }).then(function() {
        if (!_.isEmpty(xml2js.CLASSGROUPS)) {
          // try to categorize ungrouped classes, if any
          var grouped = _.flatten(_.pluck(_.values(xml2js.CLASSGROUPS), 'classes'));
          var ungrouped = _.difference(_.keys(xml2js.CLASSES), grouped);
          _.each(ungrouped, function(c) {
            _.each(findUsage(c), function(group) {
              xml2js.CLASSGROUPS[group].classes.push(c);
            });
          });
          grouped = _.flatten(_.pluck(_.values(xml2js.CLASSGROUPS), 'classes'));
          ungrouped = _.difference(_.keys(xml2js.CLASSES), grouped);
          // try to categorize ungrouped enums, if any
          _.each(xml2js.ENUMS_BY_GROUP, function(enumGroupSpec, enumGroupName) {
            _.each(findUsage(enumGroupName, true), function(c) {
              xml2js.CLASSES[c].enums_by_group[enumGroupName] = enumGroupSpec;            
              _.each(enumGroupSpec.members, function(enumName) {
                xml2js.CLASSES[c].enums[enumName] = xml2js.ENUMS[enumName];
                delete xml2js.ENUMS[enumName];
              });
              delete xml2js.ENUMS_BY_GROUP[enumGroupName];
            });
          });
        }
      }).then(function() {
        if (xml2js.opts.custom && fs.existsSync(xml2js.opts.custom)) {
          return fs.readFileAsync(xml2js.opts.custom, 'utf8').then(function(custom) {
            try {
              customizeMethods(JSON.parse(custom));
            } catch(e) {
              console.log('invalid custom.json, ignored. ' + e.toString());
            }
          });
        } else {
          console.log(xml2js.opts.custom ? ('Error: No such customization file exists: ' + xml2js.opts.custom) : 'No customizations given.');
        }
      }).then(function() {
        generateCustomPointerClasses();       
        validateMethods();
        validateVars();
        return _.pick(xml2js, 'MODULE', 'ENUMS', 'ENUMS_BY_GROUP', 'METHODS', 'CLASSES', 'CLASSGROUPS');
      });
    }