whole_func_string: function walk(dir, opts, done, rootDir, level) { // done(err, result)
      if(arguments.length === 2) {
          done = arguments[1];
          opts = { levels: null };
      }
      
      if(typeof done !== 'function') throw new Error('Wrong arguments');
      if(!rootDir) dir = resolve(dir).fullPath;
      level = level || 1;
      
      var onError = opts.onError; // if onerror callback defined, just emit error and go ahead
      var maxRepeat = opts.maxRepeat || 5;
      var repeatInterval = opts.repeatInterval || 200;
      var skipSymlinks = opts.skipSymlinks === false ? false : true;
      
      //if(dir.substring(dir.length-1, dir.length) === '/')
      //    dir = dir.substring(0, dir.length-1); // remove last '/'
      
      var include = opts.include || [];
      //var exclude = opts.exclude || []; - not implemented
      
      var results = {};
      var pending = 0;
      
      fs.readdir(dir, function(err, list) {
          if(err && err.code === 'ENOENT') return done(null, {}); // directory removed, just return empty result 
          else if(err) {
              err = new Error('fsExt.walkdirRecursive: readdir failed').cause(err);
              if(onError) {
                  onError(err);
                  return done(null, results);
              }
              else {
                  pending = 0;
                  return done(err);
              }
          }
          
          if(!rootDir && opts.includeRootDir) { // undefined rootDir means this is parent dir
              list.unshift(dir);
          }
          pending = list.length;
          if(!pending) return done(null, results);
          
          rootDir = rootDir || dir;
          list.forEach(function(file) {
              if(file!==rootDir){
                  file = dir + '/' + file;
                  file = file.replace(/\\/g, '/');
              }
              
              var baseDir = rootDir;
              if(opts.includeRootDir) { 
                  baseDir = baseDir.split('/');
                  baseDir.pop();
                  baseDir = baseDir.join('/');
              }
              var fileId = file.replace(baseDir + '/', '');
              getStats(file, fileId, done);
          });
      });
      
      function getStats(file, fileId, done, repeated){
          repeated = repeated || 0;
          fs.lstat(file, function(err, stat) {
              if(pending <= 0) return;
              
              if(err && err.code === 'ENOENT') { // directory or file removed, just skip it
                  if(pending===1) return done(null, results);
              }
              else if(err && repeated >= maxRepeat) {
                  if(onError) {
                      onError(err);
                      if(!--pending) return done(null, results);
                  }
                  else {
                      pending = 0;
                      return done(err);
                  }
              }
              else if(err) {
                  return setTimeout(function(){
                      getStats(file, fileId, done, repeated+1);
                  }, repeatInterval);
              }
              
              if(skipSymlinks && stat && stat.isSymbolicLink()) {
                  if(!--pending) done(null, results);
                  return;
              }
              
              if(!stat){
                  if(!--pending) done(null, results);
              }
              else if(stat.isDirectory()) {
                  results[fileId] = getFileItem(fileId, stat, file);
                  if(file!==rootDir && (!opts.levels || opts.levels>=level+1)) walk(file, opts, function(err, res) {
                      if(err) {
                          res = {};
                          if(onError) onError(err);
                      }
                      
                      for(var key in res) {
                          results[key] = res[key];
                      }
                      if (!--pending) done(null, results);
                  }, rootDir, level+1);
                  else if(!--pending) done(null, results); 
              }
              else {
                  var fileExt = getExt(fileId);
                  if(include.length > 0 && include.indexOf(fileExt) !== -1)
                      results[fileId] = getFileItem(fileId, stat, file);
                  else if(include.length === 0)
                      results[fileId] = getFileItem(fileId, stat, file);
                  if(!--pending) done(null, results);
              }
              
          });
      }
      
      return dir;
  }
func_code_string: function walk(dir, opts, done, rootDir, level) { // done(err, result)
      if(arguments.length === 2) {
          done = arguments[1];
          opts = { levels: null };
      }
      
      if(typeof done !== 'function') throw new Error('Wrong arguments');
      if(!rootDir) dir = resolve(dir).fullPath;
      level = level || 1;
      
      var onError = opts.onError; // if onerror callback defined, just emit error and go ahead
      var maxRepeat = opts.maxRepeat || 5;
      var repeatInterval = opts.repeatInterval || 200;
      var skipSymlinks = opts.skipSymlinks === false ? false : true;
      
      //if(dir.substring(dir.length-1, dir.length) === '/')
      //    dir = dir.substring(0, dir.length-1); // remove last '/'
      
      var include = opts.include || [];
      //var exclude = opts.exclude || []; - not implemented
      
      var results = {};
      var pending = 0;
      
      fs.readdir(dir, function(err, list) {
          if(err && err.code === 'ENOENT') return done(null, {}); // directory removed, just return empty result 
          else if(err) {
              err = new Error('fsExt.walkdirRecursive: readdir failed').cause(err);
              if(onError) {
                  onError(err);
                  return done(null, results);
              }
              else {
                  pending = 0;
                  return done(err);
              }
          }
          
          if(!rootDir && opts.includeRootDir) { // undefined rootDir means this is parent dir
              list.unshift(dir);
          }
          pending = list.length;
          if(!pending) return done(null, results);
          
          rootDir = rootDir || dir;
          list.forEach(function(file) {
              if(file!==rootDir){
                  file = dir + '/' + file;
                  file = file.replace(/\\/g, '/');
              }
              
              var baseDir = rootDir;
              if(opts.includeRootDir) { 
                  baseDir = baseDir.split('/');
                  baseDir.pop();
                  baseDir = baseDir.join('/');
              }
              var fileId = file.replace(baseDir + '/', '');
              getStats(file, fileId, done);
          });
      });
      
      function getStats(file, fileId, done, repeated){
          repeated = repeated || 0;
          fs.lstat(file, function(err, stat) {
              if(pending <= 0) return;
              
              if(err && err.code === 'ENOENT') { // directory or file removed, just skip it
                  if(pending===1) return done(null, results);
              }
              else if(err && repeated >= maxRepeat) {
                  if(onError) {
                      onError(err);
                      if(!--pending) return done(null, results);
                  }
                  else {
                      pending = 0;
                      return done(err);
                  }
              }
              else if(err) {
                  return setTimeout(function(){
                      getStats(file, fileId, done, repeated+1);
                  }, repeatInterval);
              }
              
              if(skipSymlinks && stat && stat.isSymbolicLink()) {
                  if(!--pending) done(null, results);
                  return;
              }
              
              if(!stat){
                  if(!--pending) done(null, results);
              }
              else if(stat.isDirectory()) {
                  results[fileId] = getFileItem(fileId, stat, file);
                  if(file!==rootDir && (!opts.levels || opts.levels>=level+1)) walk(file, opts, function(err, res) {
                      if(err) {
                          res = {};
                          if(onError) onError(err);
                      }
                      
                      for(var key in res) {
                          results[key] = res[key];
                      }
                      if (!--pending) done(null, results);
                  }, rootDir, level+1);
                  else if(!--pending) done(null, results); 
              }
              else {
                  var fileExt = getExt(fileId);
                  if(include.length > 0 && include.indexOf(fileExt) !== -1)
                      results[fileId] = getFileItem(fileId, stat, file);
                  else if(include.length === 0)
                      results[fileId] = getFileItem(fileId, stat, file);
                  if(!--pending) done(null, results);
              }
              
          });
      }
      
      return dir;
  }
func_documentation_string: recursive walk folder
  @param {String} dir directory
  @param {Object} opts walk options - include/exclude file extensions
  @param {Function} done callback(err, array)
  @param {String} rootDir used when walking recursively, do not use this argument
  @param {Integer} level actual level in tree
  @returns {String} resolved dir