docs: function parse(opts, cb) {
      assert.object(opts, 'opts');
      assert.optionalString(opts.configPath, 'opts.configPath');
      assert.optionalString(opts.basePath, 'opts.basePath');
      assert.optionalObject(opts.config, 'opts.config');
      assert.func(cb, 'cb');
  
  
      if (!opts.configPath && !opts.config) {
          throw new verror.VError('must specify either opts.configPath or ' +
                                  'opts.config');
      }
  
      if (!opts.configPath && !opts.basePath) {
          assert.fail('must specify opts.basePath');
      }
  
      var validatedConfig;
      var basePath;
  
      vasync.pipeline({arg: {}, funcs: [
          function readInput(it, cb1) {
              if (opts.config) {
                  it.config = opts.config;
                  basePath = opts.basePath;
                  cb1();
              } else {
                  // set basePath from `configPath` opts
                  basePath = path.dirname(opts.configPath);
                  fs.readFile(opts.configPath, 'utf8', function (err, config) {
                      if (err) {
                          cb1(new verror.VError({
                              cause: err,
                              info: {
                                  fileName: opts.configPath
                              }
                          }, 'problem reading input from disk'));
                      } else {
                          try {
                              it.config = JSON.parse(config);
                              cb1();
                          } catch (e) {
                              cb1(new verror.VError({
                                  cause: err,
                                  info: {
                                      fileName: opts.configPath,
                                      fileContents: config
                                  }
                              },'problem parsing JSON'));
                          }
                      }
                  });
              }
          },
          function validateSchemaVersion(it, cb1) {
              // this is a little funky, the reason we have to validate the
              // schema version manually is because we need to know which schema
              // to compile and use against the config. Therefore we have to
              // manually parse it out of the input and verify so we can choose
              // the schema version.
              try {
                  assert.number(it.config.schemaVersion,
                                'input does not contain schema version');
              } catch (e) {
                  return cb1(e);
              }
  
              it.schema = SCHEMAS[it.config.schemaVersion];
  
              if (!it.schema) {
                  return cb1(new verror.VError({
                      info: {
                          parsedVersion: it.config.schemaVersion,
                          supportedVersions: VERSIONS
                      }
                  }, 'schema version not supported'));
              } else {
                  return cb1();
              }
          },
          function validateInput(it, cb1) {
              _validateInput(it.schema, it.config, function (err, res) {
                  validatedConfig = res;
                  return cb1(err);
              });
          }
      ]}, function (err, res) {
          return cb(err, {
              validatedConfig: validatedConfig,
              basePath: basePath
          });
      });
  }