/*
 * Copyright (c) 2012 Frank Hellwig
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

var fs = require('fs');
var path = require('path');
var findpkg = require('findpkg');
var jsvutil = require('jsvutil');
var strformat = require('strformat');

var DEFAULT_SCHEMA = path.join('config', 'schema');
var CONFIG_DIR = process.env['NODE_CONFIG_DIR'] || 'config'
var CONFIG_ENV = process.env['NODE_ENV'] || 'config'
var DEFAULT_CONFIG = path.join(CONFIG_DIR, CONFIG_ENV);

var OPTIONS_SCHEMA = {
    type: 'object',
    properties: {
        schema: {
            type: ['object', 'string'],
            default: DEFAULT_SCHEMA
        },
        config: {
            type: ['object', 'string'],
            default: DEFAULT_CONFIG
        }
    }
};

/**
 * Returns true if the pathname identifies a file.
 */
function isFile(pathname) {
    return fs.existsSync(pathname) && fs.statSync(pathname).isFile();
}

/**
 * Reads and parses JSON file. If a syntax error occurs, the pathname is
 * included in the error message.
 */
function readJsonFile(pathname) {
    var json = fs.readFileSync(pathname, 'utf8');
    try {
        return JSON.parse(json);
    } catch (err) {
        if (err instanceof SyntaxError) {
            err.message = strformat("{0} in '{1}'", err.message, pathname);
        }
        throw err;
    }
}

/**
 * Reads the file specified by the pathname. If the file does not exist,
 * then .js and .json extensions are added. Returns a JavaScript object.
 * Throws an error if the file is not found.
 */
function readFile(pathname) {
    var msg = ''; // additional error message info
    var ext = path.extname(pathname).toLowerCase();
    if (ext === '.js') {
        if (isFile(pathname)) {
            return require(pathname);
        }
    } else if (ext === '.json') {
        if (isFile(pathname)) {
            return readJsonFile(pathname);
        }
    } else {
        if (isFile(pathname + '.js')) {
            return require(pathname + '.js');
        } else if (isFile(pathname + '.json')) {
            return readJsonFile(pathname + '.json');
        } else {
            msg = ' (tried .js and .json)'; // note leading space
        }
    }
    throw new Error(strformat("File not found '{0}'{1}", pathname, msg));
}

/**
 * The function exported by this module.
 */
function pkgconfig(options) {
    if (typeof options === 'undefined') {
        options = {};
    } else if (typeof options === 'string') {
        options = {config: options};
    } else if (typeof options !== 'object') {
        throw new TypeError('options must be an object or a string');
    }
    options = jsvutil.validate(options, OPTIONS_SCHEMA);
    var pkginfo = findpkg(module.parent);
    if (typeof options.schema === 'string') {
        options.schema = readFile(pkginfo.resolve(options.schema));
    }
    if (typeof options.config === 'string') {
        options.config = readFile(pkginfo.resolve(options.config));
    }
    jsvutil.check(options.schema);
    return jsvutil.validate(options.config, options.schema);
}

module.exports = pkgconfig;

@@ -3148,54 +3148,21 @@
  = '
- (tried .js and .json)'; // note leading space
+(.js%7C.json)';
 %0A   
@@ -3224,20 +3224,20 @@
 und '%7B0%7D
-'
 %7B1%7D
+'
 %22, pathn
@@ -3262,44 +3262,71 @@
 %0A * 
-The function exported by this module
+Process the options parameter and return a valid options object
 .%0A *
@@ -3337,24 +3337,29 @@
 nction p
-kgconfig
+rocessOptions
 (options
@@ -3358,24 +3358,37 @@
 (options) %7B%0A
+    var opt;%0A
     if (type
@@ -3407,17 +3407,14 @@
 == '
-undefined
+object
 ') %7B
@@ -3417,32 +3417,38 @@
 ') %7B%0A       
+ opt =
  options
  = %7B%7D;%0A    %7D
@@ -3439,13 +3439,8 @@
 ions
- = %7B%7D
 ;%0A  
@@ -3494,20 +3494,16 @@
      opt
-ions
  = %7Bconf
@@ -3550,24 +3550,98 @@
 ons 
-!
+=
 == '
-object
+undefined
 ') %7B%0A
+        opt = %7B%7D; // use default values in options schema%0A    %7D else %7B%0A
     
@@ -3716,25 +3716,22 @@
   %7D%0A    
-options =
+return
  jsvutil
@@ -3743,20 +3743,16 @@
 date(opt
-ions
 , OPTION
@@ -3762,16 +3762,137 @@
 CHEMA);%0A
+%7D%0A%0A/**%0A * The function exported by this module.%0A */%0Afunction pkgconfig(options) %7B%0A    var opt = processOptions(options);%0A
     var 
@@ -3939,28 +3939,24 @@
  (typeof opt
-ions
 .schema === 
@@ -3974,28 +3974,24 @@
 %0A        opt
-ions
 .schema = re
@@ -4012,28 +4012,24 @@
 .resolve(opt
-ions
 .schema));%0A 
@@ -4047,28 +4047,24 @@
  (typeof opt
-ions
 .config === 
@@ -4086,20 +4086,16 @@
      opt
-ions
 .config 
@@ -4120,28 +4120,24 @@
 .resolve(opt
-ions
 .config));%0A 
@@ -4162,20 +4162,16 @@
 heck(opt
-ions
 .schema)
@@ -4203,20 +4203,16 @@
 date(opt
-ions
 .config,
@@ -4215,20 +4215,16 @@
 fig, opt
-ions
 .schema)

Add tests