whole_func_string: function watchRecursive(dir, opts, onChange, cb, repeated){ // cb(err, files)
      if(arguments.length === 3) {
          cb = arguments[2];
          onChange = arguments[1];
          opts = {};
      }
      opts = object.extend({}, opts || {}, { includeRootDir:true });
      if(typeof cb !== 'function') throw new Error('Wrong arguments');
      
      var maxRepeats = 5;
      repeated = repeated || 0;
      
      // walk dir and setup watchers
      dir = walk(dir, opts, function(err, items){
          // sometimes, when setuping watch right after moving, or renaming "EPERM" error may occur, just wait and try again
          if(err && err.code === 'EPERM'){
              if(repeated < maxRepeats) setTimeout(function(){
                  watchRecursive(dir, opts, onChange, cb, repeated+1);
              }, 100);
              else cb(new Error('fsExt.watchRecursive: exploring folder failed').cause(err));
          }
          else if(err) {
              cb(new Error('fsExt.watchRecursive: exploring folder failed').cause(err));
          }
          else {
              watchDir(dir, items, cb);
          }
      }, opts.rootDir);
      
      function watchDir(dir, items, done){
          for(var key in items){
              if(items[key].isDir && !_dirWatchers[ items[key].fullPath ]) { // avoid duplicite watchers
                  // set dir watch
                  try {
                      var item = items[key];
                      
                      _dirWatchers[ item.fullPath ] = fs.watch(item.fullPath, { persistent: opts.watchPersistent || false })
                      .on('change', function(op, fileName){
                          var onChangeOpts = object.extend({}, opts, { rootDir: opts.rootDir||dir }),
                              watcher = this;
                              
                          watchRecursive(watcher.fullPath, onChangeOpts, onChange, function(err, descendants){
                              if(err && err.code==='ENOENT') return;
                              else if(err) onChange(new Error('fsExt.watchRecursive onChange: setup nested watch failed').cause(err));
                              else {
                                  var changes = compareDirFiles(watcher.id, watcher.children, descendants);
                                  
                                  // remove watchers if whole folder is removed
                                  for(var i=0;i<changes.length;i++){
                                      if(changes[i].event === 'removed' && changes[i].file.isDir){
                                          delete _dirWatchers[changes[i].file.fullPath];
                                      }
                                  }
                                  
                                  if(changes.length) onChange(null, changes);
                              }
                          });
                          
                      }).on('error', function(err){
                          this.close();
                          _dirWatchers[ this.fullPath ] = null;
                      });
                      
                      object.extend(_dirWatchers[ item.fullPath ], { children: getDirChildren(item.id, items) }, item);
                  }
                  catch(err){
                      return done(new Error('fsExt.watchRecursive: setup watcher failed').cause(err));
                  }
              }
          }
          done(null, items);
      }
  }
func_code_string: function watchRecursive(dir, opts, onChange, cb, repeated){ // cb(err, files)
      if(arguments.length === 3) {
          cb = arguments[2];
          onChange = arguments[1];
          opts = {};
      }
      opts = object.extend({}, opts || {}, { includeRootDir:true });
      if(typeof cb !== 'function') throw new Error('Wrong arguments');
      
      var maxRepeats = 5;
      repeated = repeated || 0;
      
      // walk dir and setup watchers
      dir = walk(dir, opts, function(err, items){
          // sometimes, when setuping watch right after moving, or renaming "EPERM" error may occur, just wait and try again
          if(err && err.code === 'EPERM'){
              if(repeated < maxRepeats) setTimeout(function(){
                  watchRecursive(dir, opts, onChange, cb, repeated+1);
              }, 100);
              else cb(new Error('fsExt.watchRecursive: exploring folder failed').cause(err));
          }
          else if(err) {
              cb(new Error('fsExt.watchRecursive: exploring folder failed').cause(err));
          }
          else {
              watchDir(dir, items, cb);
          }
      }, opts.rootDir);
      
      function watchDir(dir, items, done){
          for(var key in items){
              if(items[key].isDir && !_dirWatchers[ items[key].fullPath ]) { // avoid duplicite watchers
                  // set dir watch
                  try {
                      var item = items[key];
                      
                      _dirWatchers[ item.fullPath ] = fs.watch(item.fullPath, { persistent: opts.watchPersistent || false })
                      .on('change', function(op, fileName){
                          var onChangeOpts = object.extend({}, opts, { rootDir: opts.rootDir||dir }),
                              watcher = this;
                              
                          watchRecursive(watcher.fullPath, onChangeOpts, onChange, function(err, descendants){
                              if(err && err.code==='ENOENT') return;
                              else if(err) onChange(new Error('fsExt.watchRecursive onChange: setup nested watch failed').cause(err));
                              else {
                                  var changes = compareDirFiles(watcher.id, watcher.children, descendants);
                                  
                                  // remove watchers if whole folder is removed
                                  for(var i=0;i<changes.length;i++){
                                      if(changes[i].event === 'removed' && changes[i].file.isDir){
                                          delete _dirWatchers[changes[i].file.fullPath];
                                      }
                                  }
                                  
                                  if(changes.length) onChange(null, changes);
                              }
                          });
                          
                      }).on('error', function(err){
                          this.close();
                          _dirWatchers[ this.fullPath ] = null;
                      });
                      
                      object.extend(_dirWatchers[ item.fullPath ], { children: getDirChildren(item.id, items) }, item);
                  }
                  catch(err){
                      return done(new Error('fsExt.watchRecursive: setup watcher failed').cause(err));
                  }
              }
          }
          done(null, items);
      }
  }
func_documentation_string: watching directory recursive
  @param {String} dir root dir to watch
  @param {Object} opts watch / walk options
  @param {Function} onChange fired when change
  @param {Function} cb executed when all watchers are set