<commit_before>/*!
* deja
* Copyright(c) 2011 Mike Cantelon
* MIT Licensed
*/

var path = require('path'),
  fs = require('fs'),
  child_process = require('child_process'),
  spawn = child_process.spawn,
  wrench = require('./wrench'),
  helpers = require('./deja_helpers')

exports.ignore_files = [
  '.git',
  '.gitmodules',
  '.dejaignore_local'
]

exports.usage = function() {

  return "Usage:\n\
  deja clone <GIT REPO URL>\n\
  deja ls [REPO NAME][/SUBDIRECTORY]\n\
  deja pull <REPO NAME>\n\
  deja rm <REPO NAME>\n\
  deja diff <REPO NAME>\n\
  deja link <REPO NAME>[/SUBDIRECTORY]\n\
  deja unlink <REPO NAME>[/SUBDIRECTORY]\n\
  deja edit <REPO NAME><PATH/TO/FILE>\n\
  deja update\n\
  deja version"
}

exports.getHomeEnvVarOrDie = function() {

  var home = process.env.HOME

  if (home === undefined) {
    console.log('Error: HOME environmental variable not defined.')
    process.exit(1)
  }

  return home
}

exports.whenDirectoryExists = function(directory, callback) {

  // attempt to make directory if it doesn't already exist
  try {
    fs.mkdirSync(directory, 0700)
  } catch(e) {}

  // if directory exists, run callback or die
  path.exists(directory, function(exists) {

    if (exists) {
      callback()
    }
    else {
      console.log('Error: Could not create $HOME/.deja.')
    }
  })
}

exports.ls = function(home, dejaHome, repo) {

  var pathToRepo = dejaHome,
      subDir

  pathToRepo += (repo) ? '/' + repo : ''

  if (repo) {
    var repoAndSubDir = parseOutRepoAndSubdir(repo)
    subDir = repoAndSubDir.subDir
    subDir = (subDir != '')
      ? '/' + subDir
      : ''
  }

  path.exists(pathToRepo, function(exists) {

    if (exists) {
      helpers.traverseDir(pathToRepo, function(entry) {

        if (exports.ignore_files.indexOf(entry) == -1) {

          // if listing files in a repo, output status info for each file
          (repo)
            ? helpers.outputEntryAndInfo(entry, pathToRepo, home + subDir + '/' + entry)
            : console.log(entry)
        }
      })
    }
    else {
      console.log('Error: directory not found.')
    }
  })
}

exports.cloneRepo = function(home, dejaHome, repoUrl, config) {

  config = config || {}

  // allow user to leave off .git or .git/
  if (repoUrl.indexOf('.git') == -1) {
    repoUrl = repoUrl + '.git'
  }

  // default to Github if protocal not specified
  if (repoUrl.indexOf(':') == -1) {
    // if "username/repo" format not used, get from config
    if (repoUrl.indexOf('/') == -1) {

      if (config.github && config.github.user) {

        repoUrl = 'git@github.com:' + config.github.user + '/' + repoUrl
      }
      else {

        console.log('Error: Github repo name only specified, but no github user defined in $HOME/.gitconfig.')
        console.log('Set one with "git config --global github.user YOUR_USERNAME".')
        process.exit(1)
      }
    }
    else {

      repoUrl = 'git://github.com/' + repoUrl
    }
  }

  var basename = helpers.findUnusedBasename(dejaHome, repoUrl, function(basename) {

    var pathToRepo = dejaHome + '/' + basename

    path.exists(pathToRepo, function(exists) {

      if (exists) {

        console.log('Error: Repo already exists.')
        process.exit(1)
      }
      else {

        console.log('Cloning repository...')

        var clone = spawn('git', ['clone', repoUrl, pathToRepo])

        clone.on('exit', function(code) {

          if (code != 0) {

            console.log('Error: could not clone.')
            process.exit(code)
          }
          else {

            // check if the repo has submodules
            path.exists(pathToRepo + '/.gitmodules', function(exists) {

              // if submodules found, initialize and update them
              if (exists) {

                console.log('Initializing and updating submodules...')
                process.chdir(pathToRepo)

                var submodules = spawn('git', ['submodule', 'update', '--init'])

                submodules.on('exit', function(code) {
                  // create symlinks to items in first level of repo directory
                  exports.linkRepo(home, dejaHome, basename)
                })
              }
              else {

                // create symlinks to items in first level of repo directory
                exports.linkRepo(home, dejaHome, basename)
              }
            })
          }
        })
      }
    })
  })
}

function parseOutRepoAndSubdir(repo) {

  var pathData,
      subDir = ''

  // if repo contains slashes, then user wants to link a repo subdirectory
  if (repo.indexOf('/') != -1) {

    pathData = repo.split('/')
    repo = pathData.shift()
    subDir = pathData.join('/')
  }

  return {
    repo:   repo,
    subDir: subDir
  }
}

exports.linkRepo = function(home, dejaHome, repo) {

  var pathToRepo = dejaHome + '/' + repo,
      subDir

  var repoAndSubDir = parseOutRepoAndSubdir(repo)
  repo = repoAndSubDir.repo
  subDir = repoAndSubDir.subDir
  subDir += (subDir != '')
    ? '/'
    : ''

  var symlinkRootPath = home + '/' + subDir

  exports.getIgnoreList(home, repo, function(ignoreList) {

    path.exists(symlinkRootPath, function(exists) {

      if (exists) {

        console.log('Creating symbolic links for "' + repo + '"...')

        // cycle through cloned repo making symlinks to home
        helpers.traverseDir(pathToRepo, function(file) {

          if (exports.ignore_files.indexOf(file) == -1) {

            var filePath     = pathToRepo + '/' + file
            var symlinkPath  = symlinkRootPath + file

            exports.checkIfFileShouldBeIgnored(file, ignoreList)
              ? console.log('  Ignored ' + file + '.')
              : helpers.createSymlinkIfPathExists(symlinkPath, filePath)
          }
        })
      }
      else {

        console.log('Error: Not a valid repo.')
      }
    })
  })
}

exports.updateRepos = function(home, dejaHome) {

  // cycle through repos
  path.exists(dejaHome, function(exists) {

    if (exists) {
      helpers.traverseDir(dejaHome, function(repo) {

        // pull repo then link files
        exports.pullRepo(dejaHome, repo, function(repo) {
          exports.linkRepo(home, dejaHome, repo)
        })
      })
    }
  })

  // pull repo
  // link repo
}

exports.editRepoFile = function(dejaHome, repoFilePath) {

  var repoFilePathAbsolute = dejaHome + '/' + repoFilePath

  path.exists(repoFilePathAbsolute, function(exists) {

    if (exists) {

      child_process.exec('mktemp', function(err, stdout, stderr) {

        if (err) {

          console.log(err)
          return
        }

        var editor = (process.env.EDITOR) ? process.env.EDITOR : '/usr/bin/vim'

        var edit = spawn(editor, [repoFilePathAbsolute], {
          customFds: [
            process.stdin,
            process.stdout,
            process.stderr
          ]
        })

        edit.on('exit', function(code) {

          console.log((code == 0)
            ? 'Edit of "' + repoFilePath + '" complete.'
            : 'Error: Could not edit.'
          )
        })
      })
    }
    else {

      console.log('Error: File not found.')
    }
  })
}

exports.editCheat = function(dejaHome, cheat) {

  // get relative path from npm config, error if not set
  var getCheatPath = spawn('npm', ['get', 'dejaCheatPath'])

  var cheatPath = ''

  getCheatPath.stdout.on('data', function(data) {

    cheatPath += data.toString()
  })

  getCheatPath.stdout.on('end', function() {

    cheatPath = helpers.trim(cheatPath)

    if (cheatPath == 'undefined') {

      console.log('Error: issue the command "npm set dejaCheatPath <repo name[/path/to/cheats]>" to set the path to your cheats.')
    }
    else {

      var cheatPathAbsolute = dejaHome + '/' + cheatPath

      path.exists(cheatPathAbsolute, function(exists) {

        if (exists) {

          cheatFilePath = cheatPath + '/' + cheat + '.txt'

          exports.editRepoFile(dejaHome, cheatFilePath)
        }
        else {

          console.log('Error: path to cheat files does not exist.')
        }
      })
    }
  })
}

exports.pullRepo = function (dejaHome, repo, callback) {

  var pathToRepo = dejaHome + '/' + repo

  path.exists(pathToRepo, function(exists) {

    if (exists) {

      console.log('Pulling "' + repo + '"...')

      process.chdir(pathToRepo)

      var pull = spawn('git', ['pull'])

      pull.on('exit', function(code) {

        console.log((code == 0)
          ? 'Pull of "' + repo + '" complete.'
          : 'Error: Could not pull.'
        )

        if (callback) {
          callback(repo)
        }
        else {
          process.exit(code)
        }
      })
    }
    else {

      console.log('Error: Not a valid repo.')
    }
  })
}

exports.rmRepo = function(home, dejaHome, repo) {

  var pathToRepo = dejaHome + '/' + repo

  path.exists(pathToRepo, function(exists) {

    if (exists) {

      helpers.deleteSymlinksToRepo(home, pathToRepo)
      wrench.rmdirSyncRecursive(pathToRepo)
      console.log('Repo removed.')
    }
    else {

      console.log('Error: Not a valid repo.')
    }
  })
}

exports.unlinkRepo = function(home, dejaHome, repo) {

  var pathToRepo = dejaHome + '/' + repo,
      subDir

  var repoAndSubDir = parseOutRepoAndSubdir(repo)
  subDir = repoAndSubDir.subDir
  subDir = (subDir != '')
    ? '/' + subDir
    : ''

  path.exists(pathToRepo, function(exists) {

    if (exists) {
      helpers.deleteSymlinksToRepo(home + subDir, pathToRepo)
    }
    else {
      console.log('Error: Not a valid repo.')
    }
  })
}

exports.diffRepo = function (home, dejaHome, repo) {

  var pathToRepo = dejaHome + '/' + repo,
      subDir

  var repoAndSubDir = parseOutRepoAndSubdir(repo)
  repo = repoAndSubDir.repo
  subDir = repoAndSubDir.subDir
  subDir = (subDir != '')
    ? '/' + subDir
    : ''

  path.exists(pathToRepo, function(exists) {

    if (exists) {

      path.exists(home + subDir, function(exists) {

        if (exists) {

          var diff = spawn('diff', [home + subDir, pathToRepo])

          diff.stdout.on('data', function (data) {
            console.log(data.toString())
          })
        }
        else {

          console.log('Error: Not a valid home directory.')
        }
      })
    }
    else {

      console.log('Error: Not a valid repo.')
    }
  })
}

exports.version = function() {

  var package_json = fs.readFileSync(__dirname + '/../package.json')
  var package_data = JSON.parse(package_json)
  return 'deja version ' + package_data.version
}

exports.checkIfFileShouldBeIgnored = function(file, ignoreList) {

  var ignoreItem

  for(var index in ignoreList) {
    ignoreItem = new RegExp(ignoreList[index])
    if (file.match(ignoreItem)) {
      return true
    }
  }

  return false
}

exports.getIgnoreList = function(home, repo, callback) {

  var ignoreFilename = '.dejaignore'
    , pathToGlobalIgnore = home + '/' + ignoreFilename
    , pathToRepoIgnore = home + '/.deja/' + repo + '/' + ignoreFilename + '_local'
    , homeIgnoreList
    , repoIgnoreList
    , ignoreList

  path.exists(pathToGlobalIgnore, function(exists) {

    if (exists) {

      homeIgnoreList = exports.readIgnoreListFromFile(pathToGlobalIgnore)
    }

    path.exists(pathToRepoIgnore, function(exists) {

      if (exists) {

        repoIgnoreList = exports.readIgnoreListFromFile(pathToRepoIgnore)
      }

      // merge two ignore lists
      ignoreList = homeIgnoreList || []

      if (repoIgnoreList) {
        for(var index in repoIgnoreList) {
          var ignoreItem = repoIgnoreList[index]
          if (ignoreList.indexOf(ignoreItem) == -1) {
            ignoreList.push(ignoreItem)
          }
        }
      }

      callback(ignoreList)
    })
  })
}

exports.readIgnoreListFromFile = function(file) {

  var lines = fs.readFileSync(file).toString().split("\n")
    , ignoreList = []
    , line

  for (var index in lines) {

    line = lines[index]

    // if line contains a comment, trim it off
    if (line.indexOf('#') != -1) {
      line = line.slice(0, line.indexOf('#'))
    }

    // trim blank space from start/end of line
    line = helpers.trim(line)

    // change file pattern to regex
    line = '^' + line.replace(/\*/g, '(.*)') + '$'

    // if pattern doesn't exist in ignore list, add it
    if (line != '' && ignoreList.indexOf(line) == -1) {
      ignoreList.push(line)
    }
  }

  return ignoreList
}
<commit_msg>Made it so deja edit can work on files not yet created.<commit_after>/*!
* deja
* Copyright(c) 2011 Mike Cantelon
* MIT Licensed
*/

var path = require('path'),
  fs = require('fs'),
  child_process = require('child_process'),
  spawn = child_process.spawn,
  wrench = require('./wrench'),
  helpers = require('./deja_helpers')

exports.ignore_files = [
  '.git',
  '.gitmodules',
  '.dejaignore_local'
]

exports.usage = function() {

  return "Usage:\n\
  deja clone <GIT REPO URL>\n\
  deja ls [REPO NAME][/SUBDIRECTORY]\n\
  deja pull <REPO NAME>\n\
  deja rm <REPO NAME>\n\
  deja diff <REPO NAME>\n\
  deja link <REPO NAME>[/SUBDIRECTORY]\n\
  deja unlink <REPO NAME>[/SUBDIRECTORY]\n\
  deja edit <REPO NAME><PATH/TO/FILE>\n\
  deja update\n\
  deja version"
}

exports.getHomeEnvVarOrDie = function() {

  var home = process.env.HOME

  if (home === undefined) {
    console.log('Error: HOME environmental variable not defined.')
    process.exit(1)
  }

  return home
}

exports.whenDirectoryExists = function(directory, callback) {

  // attempt to make directory if it doesn't already exist
  try {
    fs.mkdirSync(directory, 0700)
  } catch(e) {}

  // if directory exists, run callback or die
  path.exists(directory, function(exists) {

    if (exists) {
      callback()
    }
    else {
      console.log('Error: Could not create $HOME/.deja.')
    }
  })
}

exports.ls = function(home, dejaHome, repo) {

  var pathToRepo = dejaHome,
      subDir

  pathToRepo += (repo) ? '/' + repo : ''

  if (repo) {
    var repoAndSubDir = parseOutRepoAndSubdir(repo)
    subDir = repoAndSubDir.subDir
    subDir = (subDir != '')
      ? '/' + subDir
      : ''
  }

  path.exists(pathToRepo, function(exists) {

    if (exists) {
      helpers.traverseDir(pathToRepo, function(entry) {

        if (exports.ignore_files.indexOf(entry) == -1) {

          // if listing files in a repo, output status info for each file
          (repo)
            ? helpers.outputEntryAndInfo(entry, pathToRepo, home + subDir + '/' + entry)
            : console.log(entry)
        }
      })
    }
    else {
      console.log('Error: directory not found.')
    }
  })
}

exports.cloneRepo = function(home, dejaHome, repoUrl, config) {

  config = config || {}

  // allow user to leave off .git or .git/
  if (repoUrl.indexOf('.git') == -1) {
    repoUrl = repoUrl + '.git'
  }

  // default to Github if protocal not specified
  if (repoUrl.indexOf(':') == -1) {
    // if "username/repo" format not used, get from config
    if (repoUrl.indexOf('/') == -1) {

      if (config.github && config.github.user) {

        repoUrl = 'git@github.com:' + config.github.user + '/' + repoUrl
      }
      else {

        console.log('Error: Github repo name only specified, but no github user defined in $HOME/.gitconfig.')
        console.log('Set one with "git config --global github.user YOUR_USERNAME".')
        process.exit(1)
      }
    }
    else {

      repoUrl = 'git://github.com/' + repoUrl
    }
  }

  var basename = helpers.findUnusedBasename(dejaHome, repoUrl, function(basename) {

    var pathToRepo = dejaHome + '/' + basename

    path.exists(pathToRepo, function(exists) {

      if (exists) {

        console.log('Error: Repo already exists.')
        process.exit(1)
      }
      else {

        console.log('Cloning repository...')

        var clone = spawn('git', ['clone', repoUrl, pathToRepo])

        clone.on('exit', function(code) {

          if (code != 0) {

            console.log('Error: could not clone.')
            process.exit(code)
          }
          else {

            // check if the repo has submodules
            path.exists(pathToRepo + '/.gitmodules', function(exists) {

              // if submodules found, initialize and update them
              if (exists) {

                console.log('Initializing and updating submodules...')
                process.chdir(pathToRepo)

                var submodules = spawn('git', ['submodule', 'update', '--init'])

                submodules.on('exit', function(code) {
                  // create symlinks to items in first level of repo directory
                  exports.linkRepo(home, dejaHome, basename)
                })
              }
              else {

                // create symlinks to items in first level of repo directory
                exports.linkRepo(home, dejaHome, basename)
              }
            })
          }
        })
      }
    })
  })
}

function parseOutRepoAndSubdir(repo) {

  var pathData,
      subDir = ''

  // if repo contains slashes, then user wants to link a repo subdirectory
  if (repo.indexOf('/') != -1) {

    pathData = repo.split('/')
    repo = pathData.shift()
    subDir = pathData.join('/')
  }

  return {
    repo:   repo,
    subDir: subDir
  }
}

exports.linkRepo = function(home, dejaHome, repo) {

  var pathToRepo = dejaHome + '/' + repo,
      subDir

  var repoAndSubDir = parseOutRepoAndSubdir(repo)
  repo = repoAndSubDir.repo
  subDir = repoAndSubDir.subDir
  subDir += (subDir != '')
    ? '/'
    : ''

  var symlinkRootPath = home + '/' + subDir

  exports.getIgnoreList(home, repo, function(ignoreList) {

    path.exists(symlinkRootPath, function(exists) {

      if (exists) {

        console.log('Creating symbolic links for "' + repo + '"...')

        // cycle through cloned repo making symlinks to home
        helpers.traverseDir(pathToRepo, function(file) {

          if (exports.ignore_files.indexOf(file) == -1) {

            var filePath     = pathToRepo + '/' + file
            var symlinkPath  = symlinkRootPath + file

            exports.checkIfFileShouldBeIgnored(file, ignoreList)
              ? console.log('  Ignored ' + file + '.')
              : helpers.createSymlinkIfPathExists(symlinkPath, filePath)
          }
        })
      }
      else {

        console.log('Error: Not a valid repo.')
      }
    })
  })
}

exports.updateRepos = function(home, dejaHome) {

  // cycle through repos
  path.exists(dejaHome, function(exists) {

    if (exists) {
      helpers.traverseDir(dejaHome, function(repo) {

        // pull repo then link files
        exports.pullRepo(dejaHome, repo, function(repo) {
          exports.linkRepo(home, dejaHome, repo)
        })
      })
    }
  })

  // pull repo
  // link repo
}

exports.editRepoFile = function(dejaHome, repoFilePath) {

  var repoFilePathAbsolute = dejaHome + '/' + repoFilePath
    , repoFilePathAbsoluteDir = path.dirname(repoFilePathAbsolute)

  path.exists(repoFilePathAbsoluteDir, function(exists) {

    if (exists) {

      child_process.exec('mktemp', function(err, stdout, stderr) {

        if (err) {

          console.log(err)
          return
        }

        var editor = (process.env.EDITOR) ? process.env.EDITOR : '/usr/bin/vim'

        var edit = spawn(editor, [repoFilePathAbsolute], {
          customFds: [
            process.stdin,
            process.stdout,
            process.stderr
          ]
        })

        edit.on('exit', function(code) {

          console.log((code == 0)
            ? 'Edit of "' + repoFilePath + '" complete.'
            : 'Error: Could not edit.'
          )
        })
      })
    }
    else {

      console.log('Error: invalid directory.')
    }
  })
}

exports.editCheat = function(dejaHome, cheat) {

  // get relative path from npm config, error if not set
  var getCheatPath = spawn('npm', ['get', 'dejaCheatPath'])

  var cheatPath = ''

  getCheatPath.stdout.on('data', function(data) {

    cheatPath += data.toString()
  })

  getCheatPath.stdout.on('end', function() {

    cheatPath = helpers.trim(cheatPath)

    if (cheatPath == 'undefined') {

      console.log('Error: issue the command "npm set dejaCheatPath <repo name[/path/to/cheats]>" to set the path to your cheats.')
    }
    else {

      var cheatPathAbsolute = dejaHome + '/' + cheatPath

      path.exists(cheatPathAbsolute, function(exists) {

        if (exists) {

          cheatFilePath = cheatPath + '/' + cheat + '.txt'

          exports.editRepoFile(dejaHome, cheatFilePath)
        }
        else {

          console.log('Error: path to cheat files does not exist.')
        }
      })
    }
  })
}

exports.pullRepo = function (dejaHome, repo, callback) {

  var pathToRepo = dejaHome + '/' + repo

  path.exists(pathToRepo, function(exists) {

    if (exists) {

      console.log('Pulling "' + repo + '"...')

      process.chdir(pathToRepo)

      var pull = spawn('git', ['pull'])

      pull.on('exit', function(code) {

        console.log((code == 0)
          ? 'Pull of "' + repo + '" complete.'
          : 'Error: Could not pull.'
        )

        if (callback) {
          callback(repo)
        }
        else {
          process.exit(code)
        }
      })
    }
    else {

      console.log('Error: Not a valid repo.')
    }
  })
}

exports.rmRepo = function(home, dejaHome, repo) {

  var pathToRepo = dejaHome + '/' + repo

  path.exists(pathToRepo, function(exists) {

    if (exists) {

      helpers.deleteSymlinksToRepo(home, pathToRepo)
      wrench.rmdirSyncRecursive(pathToRepo)
      console.log('Repo removed.')
    }
    else {

      console.log('Error: Not a valid repo.')
    }
  })
}

exports.unlinkRepo = function(home, dejaHome, repo) {

  var pathToRepo = dejaHome + '/' + repo,
      subDir

  var repoAndSubDir = parseOutRepoAndSubdir(repo)
  subDir = repoAndSubDir.subDir
  subDir = (subDir != '')
    ? '/' + subDir
    : ''

  path.exists(pathToRepo, function(exists) {

    if (exists) {
      helpers.deleteSymlinksToRepo(home + subDir, pathToRepo)
    }
    else {
      console.log('Error: Not a valid repo.')
    }
  })
}

exports.diffRepo = function (home, dejaHome, repo) {

  var pathToRepo = dejaHome + '/' + repo,
      subDir

  var repoAndSubDir = parseOutRepoAndSubdir(repo)
  repo = repoAndSubDir.repo
  subDir = repoAndSubDir.subDir
  subDir = (subDir != '')
    ? '/' + subDir
    : ''

  path.exists(pathToRepo, function(exists) {

    if (exists) {

      path.exists(home + subDir, function(exists) {

        if (exists) {

          var diff = spawn('diff', [home + subDir, pathToRepo])

          diff.stdout.on('data', function (data) {
            console.log(data.toString())
          })
        }
        else {

          console.log('Error: Not a valid home directory.')
        }
      })
    }
    else {

      console.log('Error: Not a valid repo.')
    }
  })
}

exports.version = function() {

  var package_json = fs.readFileSync(__dirname + '/../package.json')
  var package_data = JSON.parse(package_json)
  return 'deja version ' + package_data.version
}

exports.checkIfFileShouldBeIgnored = function(file, ignoreList) {

  var ignoreItem

  for(var index in ignoreList) {
    ignoreItem = new RegExp(ignoreList[index])
    if (file.match(ignoreItem)) {
      return true
    }
  }

  return false
}

exports.getIgnoreList = function(home, repo, callback) {

  var ignoreFilename = '.dejaignore'
    , pathToGlobalIgnore = home + '/' + ignoreFilename
    , pathToRepoIgnore = home + '/.deja/' + repo + '/' + ignoreFilename + '_local'
    , homeIgnoreList
    , repoIgnoreList
    , ignoreList

  path.exists(pathToGlobalIgnore, function(exists) {

    if (exists) {

      homeIgnoreList = exports.readIgnoreListFromFile(pathToGlobalIgnore)
    }

    path.exists(pathToRepoIgnore, function(exists) {

      if (exists) {

        repoIgnoreList = exports.readIgnoreListFromFile(pathToRepoIgnore)
      }

      // merge two ignore lists
      ignoreList = homeIgnoreList || []

      if (repoIgnoreList) {
        for(var index in repoIgnoreList) {
          var ignoreItem = repoIgnoreList[index]
          if (ignoreList.indexOf(ignoreItem) == -1) {
            ignoreList.push(ignoreItem)
          }
        }
      }

      callback(ignoreList)
    })
  })
}

exports.readIgnoreListFromFile = function(file) {

  var lines = fs.readFileSync(file).toString().split("\n")
    , ignoreList = []
    , line

  for (var index in lines) {

    line = lines[index]

    // if line contains a comment, trim it off
    if (line.indexOf('#') != -1) {
      line = line.slice(0, line.indexOf('#'))
    }

    // trim blank space from start/end of line
    line = helpers.trim(line)

    // change file pattern to regex
    line = '^' + line.replace(/\*/g, '(.*)') + '$'

    // if pattern doesn't exist in ignore list, add it
    if (line != '' && ignoreList.indexOf(line) == -1) {
      ignoreList.push(line)
    }
  }

  return ignoreList
}
<|endoftext|>


<commit_before>var h = require('highland');
var elasticsearch = require('elasticsearch');

module.exports = function dest (glob, opt) {
  var stream = h();
  var _stream = stream;
  opt = opt || {};

  var client = new elasticsearch.Client(opt);

  if (opt.rateLimit) {
    _stream = stream.ratelimit(1, opt.rateLimit);
  }

  _stream
    .consume(function(error, file, push, next) {
      if (error) {
        push(error);
        next();
      } else if (file === h.nil) {
        push(null, file);
      } else {
        if (!(file.data || file.contents)) {
          console.log('No content to index!', file);
          push(null, file);
          next();
        } else {
          var req = {
            index: glob.index || opt.index,
            type: glob.type || opt.type || file.data.type || file.base,
            id: file.id || file.relative || glob.id || opt.id,
            body: file.data || JSON.parse(file.contents.toString())
          };

          client.index(req, function(error, response) {
            if (error) {
              console.log('Error writing to ES:', file, error);
              push(error);
            } else {
              push(null, file);
            }
            next();
          });
        }
      }
    })

    .resume()
    ;

  return stream;
};

<commit_msg>log errors to error stream<commit_after>var h = require('highland');
var elasticsearch = require('elasticsearch');

module.exports = function dest (glob, opt) {
  var stream = h();
  var _stream = stream;
  opt = opt || {};

  var client = new elasticsearch.Client(opt);

  if (opt.rateLimit) {
    _stream = stream.ratelimit(1, opt.rateLimit);
  }

  _stream
    .consume(function(error, file, push, next) {
      if (error) {
        push(error);
        next();
      } else if (file === h.nil) {
        push(null, file);
      } else {
        if (!(file.data || file.contents)) {
          console.error('No content to index!', file);
          push(null, file);
          next();
        } else {
          var req = {
            index: glob.index || opt.index,
            type: glob.type || opt.type || file.data.type || file.base,
            id: file.id || file.relative || glob.id || opt.id,
            body: file.data || JSON.parse(file.contents.toString())
          };

          client.index(req, function(error, response) {
            if (error) {
              console.error('Error writing to ES:', file, error);
              push(error);
            } else {
              push(null, file);
            }
            next();
          });
        }
      }
    })

    .resume()
    ;

  return stream;
};

<|endoftext|>


<commit_before>'use strict';
var util = require('util');
var _ = require('lodash');

var h = require('highland');
var elasticsearch = require('elasticsearch');

let manageTemplate = require('./manageTemplate');

module.exports = function dest (glob, _opt) {
  glob = glob || {};

  /**
   * Clone the options because ElasticSearch overwrites them:
   */

  var opt = _.clone(_opt || {});

  /**
   * If the caller is hoping to use AWS ElasticSearch rather than a
   * direct instance then insert the signing module:
   */

  if (opt.amazonES) {
    opt.connectionClass = require('http-aws-es');
  }

  /**
   * Return a pipeline for use in non-Highland situations:
   */

  return h.pipeline(function(stream) {
    var _stream = (opt.rateLimit) ? stream.ratelimit(1, opt.rateLimit) : stream;
    var client = new elasticsearch.Client(_.clone(opt));

    return _stream
      .through(manageTemplate.stream(_.clone(opt)))

      /**
       * Ensure there is some data to index:
       */

      .filter(function(file) {
        return file.data || file.contents;
      })

      /**
       * Change the layout and use default values where necessary:
       */

      .map(function(file) {
        return {
          index: glob.index || file.index || opt.index,
          type: glob.type || (file.data && file.data.type) ||
            opt.type || file.base,
          id: glob.id || file.id || file.path || opt.id,
          body: file.data || JSON.parse(file.contents.toString())
        };
      })

      /**
       * Batch up the items to be indexed, either by size (say, 5000
       * of them) or by time (however many have arrived in a certain
       * amount of time, say 5s):
       */

      .batchWithTimeOrCount(opt.batchTimeout || 5000, opt.batchSize || 5000)

      /**
       * Map each insert to two records for the bulk API:
       */

      .consume((err, batch, push, next) => {
        if (err) {
            push(err);
            next();
            return;
        }

        if (batch === h.nil) {
          push(null, batch);
          return;
        }

        let bulkActions = [];

        batch.forEach(file => {
          bulkActions.push({
            index: {
              _index: file.index,
              _type: file.type,
              _id: file.id
            }
          });
          bulkActions.push(file.body);
        });

        let timeKey = `[vinyl-elasticsearch]: written ${bulkActions.length / 2} records on ${_opt.host}: took`;

        console.time(timeKey);
        client.bulk({body: bulkActions}, function(error, response) {
          if (error) {
            if (error.message.indexOf('Request Timeout after') > -1) {
              console.warn(util.format('[vinyl-elasticsearch]: %s', error));
            } else {
              throw new Error(util.format('[vinyl-elasticsearch]: %s %s %s', bulkActions,
                error, response));
            }
          }

          /**
           * Log the time taken:
           */

          console.timeEnd(timeKey);
          push(null, bulkActions);
          next();
        });
      })
      ;
  });
};
<commit_msg>split bulk update into two steps to make it easier to manage<commit_after>'use strict';
var util = require('util');
var _ = require('lodash');

var h = require('highland');
var elasticsearch = require('elasticsearch');

let manageTemplate = require('./manageTemplate');

module.exports = function dest (glob, _opt) {
  glob = glob || {};

  /**
   * Clone the options because ElasticSearch overwrites them:
   */

  var opt = _.clone(_opt || {});

  /**
   * If the caller is hoping to use AWS ElasticSearch rather than a
   * direct instance then insert the signing module:
   */

  if (opt.amazonES) {
    opt.connectionClass = require('http-aws-es');
  }

  /**
   * Return a pipeline for use in non-Highland situations:
   */

  return h.pipeline(function(stream) {
    var _stream = (opt.rateLimit) ? stream.ratelimit(1, opt.rateLimit) : stream;
    var client = new elasticsearch.Client(_.clone(opt));

    return _stream
      .through(manageTemplate.stream(_.clone(opt)))

      /**
       * Ensure there is some data to index:
       */

      .filter(function(file) {
        return file.data || file.contents;
      })

      /**
       * Change the layout and use default values where necessary:
       */

      .map(function(file) {
        return {
          index: glob.index || file.index || opt.index,
          type: glob.type || (file.data && file.data.type) ||
            opt.type || file.base,
          id: glob.id || file.id || file.path || opt.id,
          body: file.data || JSON.parse(file.contents.toString())
        };
      })

      /**
       * Batch up the items to be indexed, either by size (say, 5000
       * of them) or by time (however many have arrived in a certain
       * amount of time, say 5s):
       */

      .batchWithTimeOrCount(opt.batchTimeout || 5000, opt.batchSize || 5000)

      /**
       * Map each insert to two records for the bulk API:
       */

      .map(batch => {
        let bulkActions = [];

        batch.forEach(file => {
          bulkActions.push({
            index: {
              _index: file.index,
              _type: file.type,
              _id: file.id
            }
          });
          bulkActions.push(file.body);
        });

        return bulkActions;
      })
      .consume((err, bulkActions, push, next) => {
        if (err) {
          push(err);
          next();
          return;
        }

        if (bulkActions === h.nil) {
          push(null, h.nil);
          return;
        }

        let timeKey = `[vinyl-elasticsearch]: written ${bulkActions.length / 2} records on ${_opt.host}: took`;

        console.time(timeKey);
        client.bulk({body: bulkActions}, function(error, response) {
          if (error) {
            if (error.message.indexOf('Request Timeout after') > -1) {
              console.warn(util.format('[vinyl-elasticsearch]: %s', error));
            } else {
              throw new Error(util.format('[vinyl-elasticsearch]: %s %s %s', bulkActions,
                error, response));
            }
          }

          /**
           * Log the time taken:
           */

          console.timeEnd(timeKey);
          push(null, bulkActions);
          next();
        });
      })
      ;
  });
};
<|endoftext|>


<commit_before>/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://yuilibrary.com/license/
*/
"use strict";

var parse = require('url').parse;
var path  = require('path');
var fs    = require('fs');
var qs    = require('querystring');

/*

EchoEcho({
    paths: [] //Array of paths
    all: true //Turn on all paths with 'echo' in the url
})

*/

var EchoEcho = function(options) {
    this.BASE = {};
    this.options = options || {};

    this.scheme = require('./scheme');

    if (this.options.paths && !this.options.all) {
        this.paths(this.options.paths);
    }
};

EchoEcho.prototype = {
    load: function (obj) {
        var self = this;
        Object.keys(obj).forEach(function (key) {
            self.scheme[key] = obj[key];
        });
    },
    validate: function(req, lax) {
        if (typeof req === 'object') {
            req = req.url;
        }

        var url = req.split('?')[0],
            ret = false,
            route,
            base;

        if (this.options.all) {
            //Catch all (non-registered) echo urls.
            if (!ret && url.indexOf('/echo/') > -1) {
                route = url.split('/echo/')[1];
                ret = true;
            }
        } else {
            Object.keys(this.BASE).forEach(function (b) {
                if (url.indexOf(b) === 0) {
                    route = url.replace((b + '/echo/'), '');
                    ret = true;
                }
            });
        }

        if (route && !lax) {
            ret = false;
            base = route.split('/');
            if (this.scheme[base[0]]) {
                ret = base[0];
                if (base[0] === 'status') {
                    ret = base[1];
                }
            }
        }
        return ret;
    },
    handle: function (req) {
        return !!this.validate(req, true);
    },
    serve: function (req, res) {
        var self = this,
            base = this.validate(req),
            filepath,
            parsed,
            query,
            data;

        if (base && base !== true) {
            if (this.scheme[base]) {
                query  = parse(req.url).query,
                parsed = qs.parse(query);

                if (parsed.response) {
                    this.scheme[base](req, res, parsed.response);
                } else if (parsed.file) {
                    filepath = path.join(__dirname, parsed.file);
                    fs.readFile(filepath, function (err, data) {
                        if (err) {
                            self.scheme.status(404, res);
                        }
                        self.scheme[base](req, res, data);
                    });
                } else {
                    this.scheme[base](req, res, data);
                }
            } else {
                this.scheme.status(base, res);
            }
        } else {
            this.scheme.status(404, res);
        }

    },
    paths: function (p) {
        p.forEach(function (dir) {
            var base = dir;
            if (path.basename(dir).indexOf('.') > -1) {
                base = path.dirname(dir);
            }
            if (base.substr(-1) === '/') {
                base = base.substring(0, base.length - 1);
            }
            this.BASE[parse(base).path] = true;
        }, this);
    }
};

module.exports = new EchoEcho();

module.exports.EchoEcho = EchoEcho;
<commit_msg>Allow specification of an alternate directory root when serving files<commit_after>/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://yuilibrary.com/license/
*/
"use strict";

var parse = require('url').parse;
var path  = require('path');
var fs    = require('fs');
var qs    = require('querystring');

/*

EchoEcho({
    paths: [] //Array of paths
    all: true //Turn on all paths with 'echo' in the url
})

*/

var EchoEcho = function(options) {
    this.BASE = {};
    this.options = options || {};

    this.scheme = require('./scheme');

    if (this.options.paths && !this.options.all) {
        this.paths(this.options.paths);
    }
};

EchoEcho.prototype = {
    load: function (obj) {
        var self = this;
        Object.keys(obj).forEach(function (key) {
            self.scheme[key] = obj[key];
        });
    },
    validate: function(req, lax) {
        if (typeof req === 'object') {
            req = req.url;
        }

        var url = req.split('?')[0],
            ret = false,
            route,
            base;

        if (this.options.all) {
            //Catch all (non-registered) echo urls.
            if (!ret && url.indexOf('/echo/') > -1) {
                route = url.split('/echo/')[1];
                ret = true;
            }
        } else {
            Object.keys(this.BASE).forEach(function (b) {
                if (url.indexOf(b) === 0) {
                    route = url.replace((b + '/echo/'), '');
                    ret = true;
                }
            });
        }

        if (route && !lax) {
            ret = false;
            base = route.split('/');
            if (this.scheme[base[0]]) {
                ret = base[0];
                if (base[0] === 'status') {
                    ret = base[1];
                }
            }
        }
        return ret;
    },
    handle: function (req) {
        return !!this.validate(req, true);
    },
    serve: function (req, res, config) {
        var self = this,
            base = this.validate(req),
            filepath,
            parsed,
            query,
            data,
            dirroot = __dirname;

        config = config || {};

        if (config.dirroot) {
            dirroot = config.dirroot;
        }

        if (base && base !== true) {
            if (this.scheme[base]) {
                query  = parse(req.url).query;
                parsed = qs.parse(query);

                if (parsed.response) {
                    this.scheme[base](req, res, parsed.response);
                } else if (parsed.file) {
                    filepath = path.join(dirroot, parsed.file);
                    fs.readFile(filepath, function (err, data) {
                        if (err) {
                            self.scheme.status(404, res);
                        }
                        self.scheme[base](req, res, data);
                    });
                } else {
                    this.scheme[base](req, res, data);
                }
            } else {
                this.scheme.status(base, res);
            }
        } else {
            this.scheme.status(404, res);
        }

    },
    paths: function (p) {
        p.forEach(function (dir) {
            var base = dir;
            if (path.basename(dir).indexOf('.') > -1) {
                base = path.dirname(dir);
            }
            if (base.substr(-1) === '/') {
                base = base.substring(0, base.length - 1);
            }
            this.BASE[parse(base).path] = true;
        }, this);
    }
};

module.exports = new EchoEcho();

module.exports.EchoEcho = EchoEcho;
<|endoftext|>


<commit_before>var exec = require('exec-sync');
var editor = require('editor');
var fs = require('fs');
var path = require('path');

module.exports = function (deck, argv) {

  // if (!/-deck$/.test(deck)) {
  //   deck += '-deck';
  // }

  var ed = exec('npm get editor')
  var f = argv._.length  < 3 ?
    path.join(process.cwd(), 'deck.md') :
    path.join(exec('npm get prefix'), 'deck.md');

  if (!fs.existsSync(f)) {
    return console.error('Cannot find deck at', f)
  }

  editor(f, {
    editor: ed || process.env.EDITOR
  });

};
<commit_msg>cleanup<commit_after>var exec = require('exec-sync');
var editor = require('editor');
var fs = require('fs');
var path = require('path');

function edit (deck, argv) {

  var ed = exec('npm get editor');
  var f = argv._.length < 3 ?
    path.join(process.cwd(), 'deck.md') :
    path.join(exec('npm get prefix'), 'deck.md');

  if (!fs.existsSync(f)) {
    return console.error('Cannot find deck at', f)
  }

  editor(f, {
    editor: ed || process.env.EDITOR
  });
}

module.exports = edit;
<|endoftext|>


<commit_before>require('dotenv').config({silent: true});

var spawn = require('child_process').spawn;
var objectAssign = require('object-assign');

module.exports = function exec(script) {

	var argv = process.argv.splice(3);
	var command = script.command + ' ' + argv.join(' ');

	script.env = script.env || {};

	var env = objectAssign(process.env, script.env);

	var sh = 'sh', shFlag = '-c';
	if (process.platform === 'win32') {
		sh = 'cmd';
		shFlag = '/c';
	}

	console.log('to be executed:', command);
	spawn(sh, [shFlag, command], {
		env: env,
		stdio: ['pipe', process.stdout, process.stderr]
	}).on('close', function(code) {
		process.exit(code);
	});

}
<commit_msg>fix #36 'is not recognized as an internal or external command'<commit_after>require('dotenv').config({silent: true});

var spawn = require('child_process').spawn;
var objectAssign = require('object-assign');

module.exports = function exec(script) {

	var argv = process.argv.splice(3);
	var command = script.command + ' ' + argv.join(' ');

	script.env = script.env || {};

	var env = objectAssign(process.env, script.env);

	var sh = 'sh', shFlag = '-c';
	if (process.platform === 'win32') {
		sh = 'cmd';
		shFlag = '/c';
		command = '"' + command.trim() + '"';
	}

	console.log('to be executed:', command);
	spawn(sh, [shFlag, command], {
		env: env,
		windowsVerbatimArguments: process.platform === 'win32',
		stdio: ['pipe', process.stdout, process.stderr]
	}).on('close', function(code) {
		process.exit(code);
	});

}
<|endoftext|>


<commit_before>function Face(context, center) {
  this.context = context;
  this.center = { x: center.x, y: center.y };
  this.size = 30;
}

Face.prototype.draw = function () {
  this.context.fillStyle = 'yellow';
  this.context.beginPath();
  this.context.arc(this.center.x, this.center.y, this.size, 0, Math.PI * 2, true);
  this.context.closePath();
  this.context.fill();
  return this;
};

Face.prototype.checkEdges = function () {
  //return (!!(this.center.x < 0 && this.center.x > 800) && !!(this.center.y < 0 && this.center.y > 600));
  return true
};

Face.prototype.moveDown = function () {
  if ( this.checkEdges() ) {
    this.center.y++;
  }
  console.log(this.center);
  return this;
};

Face.prototype.moveUp = function () {
  if ( this.checkEdges() ) {
    this.center.y--;
  }
  return this;
};

Face.prototype.moveRight = function () {
  if ( this.checkEdges() ) {
    this.center.x++;
  }
  return this;
};

Face.prototype.moveLeft = function () {
  if ( this.checkEdges() ) {
    this.center.x--;
  }
  return this;
};

module.exports = Face;
<commit_msg>make face move faster by increasing x and y additions on keydown<commit_after>function Face(context, center) {
  this.context = context;
  this.center = { x: center.x, y: center.y };
  this.size = 30;
}

Face.prototype.draw = function () {
  this.context.fillStyle = 'yellow';
  this.context.beginPath();
  this.context.arc(this.center.x, this.center.y, this.size, 0, Math.PI * 2, true);
  this.context.closePath();
  this.context.fill();
  return this;
};

Face.prototype.checkEdges = function () {
  //return (!!(this.center.x < 0 && this.center.x > 800) && !!(this.center.y < 0 && this.center.y > 600));
  return true
};

Face.prototype.moveDown = function () {
  if ( this.checkEdges() ) {
    this.center.y += 5;
  }
  console.log(this.center);
  return this;
};

Face.prototype.moveUp = function () {
  if ( this.checkEdges() ) {
    this.center.y -= 5;
  }
  return this;
};

Face.prototype.moveRight = function () {
  if ( this.checkEdges() ) {
    this.center.x += 5;
  }
  return this;
};

Face.prototype.moveLeft = function () {
  if ( this.checkEdges() ) {
    this.center.x -= 5;
  }
  return this;
};

module.exports = Face;
<|endoftext|>


<commit_before>var xml = require('xml');
var generatorName = 'Feed for Node.js';

// ============================================================================
// Feed Main Module
// ============================================================================
function Feed(options) {
    this.title       = options.title;
    this.description = options.description;
    this.link        = options.link;
    this.image       = options.image;
    this.copyright   = options.copyright;

    this.author      = options.author;

    this.items       = [];
    this.categories  = [];

    this.addItem = function (options) {
        options = options || {};
        var item = {
            title:          options.title,
            link:           options.link,
            description:    options.description,
            date:           options.date,
            image:          options.image,
            author:         options.author
        };

        this.items.push(item);
        return this;
    }

    this.addCategory = function (category) {
        this.categories.push(category);
        return this;
    }

    this.render = function (format) {
        switch(format) {
            case 'rss-2.0':
                return rss_2_0(this);
            break;

            case 'atom-1.0':
                return atom_1_0(this);
            break;

            default:
                return rss_2_0(this);
        }
    };

    /**
     * DEPRECATED METHODS
     */
    this.item = function (options)  {
        console.warn('DEPRECATED: use addItem() instead of item()');
        return this.addItem(options);
    }

    this.category = function (category) {
        console.warn('DEPRECATED: use addCategory() instead of category()');
        return this.addCategory(category);
    }
};

var urlPattern = new RegExp('(http|https)://[\w-]+(\.[\w-]+)+([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?');

// ============================================================================
// Atom 1.0 [application/atom+xml]
//
// - Reference: http://www.atomenabled.org/developers/syndication/
// ============================================================================

function atom_1_0(options) {
    var items       = [],
        container   = [],
        feed        = [],

        entries     = options.items,
        categories  = options.categories,
        author      = options.author,
        d           = new Date();

    var doctype = '<?xml version="1.0" encoding="utf-8"?>\n';

    container.push({ feed: feed });
    feed.push({ _attr: { xmlns: 'http://www.w3.org/2005/Atom' }});

    /**************************************************************************
     * "feed" node: REQUIRED elements
     *************************************************************************/
    feed.push({ title:      options.title });
    feed.push({ id:         options.link });
    feed.push({ updated:    RFC3339(d) });      // Auto-magic :)

    /**************************************************************************
     * "feed" node: recommended elements
     *************************************************************************/
    if(author)
    {
        var authorDetails = [];

        if(author.name)
            authorDetails.push({ name:     author.name });

        if(author.email)
            authorDetails.push({ email:     author.email });

        if(author.link)
            authorDetails.push({ uri:     author.link });

        feed.push({ author: authorDetails });
    }

    // link (relative link to feed) - will be added in a future version

    /**************************************************************************
     * "feed" node: optional elements
     *************************************************************************/
    if(options.description)
        feed.push({ subtitle:   options.description });

    if(options.image)
        feed.push({ logo:       options.image });

    if(options.copyright)
        feed.push({ rights:     options.copyright });

    feed.push({ generator:      generatorName });

    // Looping through categories (if any set!)
    for(var i=0; i<categories.length; i++)
        feed.push({ category: [{ _attr: { term: categories[i] } }] });

    // contributor

    // icon

    /**************************************************************************
     * "entry" nodes
     *************************************************************************/
    for(var i=0; i<entries.length; i++) {
        //
        // entry: required elements
        //
        var entry = [
            { title:        { _attr: { type: 'html' }, _cdata: entries[i].title }},
            { id:           entries[i].link },
            { link:         [{ _attr: { href: entries[i].link } }]},
            { updated:      RFC3339(entries[i].date) }
        ];

        //
        // entry: recommended elements
        //
        if(entries[i].description)
            entry.push({ summary: { _attr: { type: 'html' }, _cdata: entries[i].description }});

        // entry author(s)
        if(entries[i].author) {
            for(var y=0; y<entries[i].author.length; y++) {
                var entryAuthor = [];

                if(entries[i].author[y].name)
                    entryAuthor.push({ name: entries[i].author[y].name });

                if(entries[i].author[y].email)
                    entryAuthor.push({ email: entries[i].author[y].email });

                if(entries[i].author[y].link)
                    entryAuthor.push({ uri: entries[i].author[y].link });

                entry.push({ author: entryAuthor });
            }
        }

        // content

        // link - relative link to article

        //
        // entry: optional elements
        //

        // category

        // contributor

        // published

        // source

        // rights

        feed.push({ entry: entry });
    }

    return doctype + xml(container, true);
}

// ============================================================================
// Real Simple Syndication 2.0 (RSS 2.0)
//
// - Reference: http://feed2.w3.org/docs/rss2.html
// ============================================================================
function rss_2_0(options) {
    var items       = [],
        channel     = [],
        container   = [];

    var items = options.items;
    var categories = options.categories;
    var author = options.author;
    var d = new Date();

    var doctype = '<?xml version="1.0" encoding="utf-8"?>\n';

    container.push({ rss: [] });
    container[0].rss.push({ _attr: { version: '2.0' } });
    container[0].rss.push({ channel: channel });

    /**
     * Required Channel Elements
     * http://cyber.law.harvard.edu/rss/rss.html#requiredChannelElements
     */
    if(options.title == undefined)
        throw new Error('channel title is mandatory');
    else
        channel.push({ title: options.title });

    if(options.description == undefined)
        throw new Error('channel description is mandatory');
    else
        channel.push({ description: options.description });

    if(options.link == undefined)
        throw new Error('channel link is mandatory');
    else
        if(!urlPattern.test(options.link))
            throw new Error('invalid channel link');
        else
            channel.push({ link: options.link });

    /**
     * Optional Channel Elements
     * http://cyber.law.harvard.edu/rss/rss.html#optionalChannelElements
     */
    if(author != undefined) {
        if(author.email && author.name)
            channel.push({ author: author.email + ' (' + author.name + ')' });

        else if(author.email && !author.name)
            channel.push({ author: author.email });

        else if(!author.email && author.name)
            channel.push({ author: author.name });
    }

    channel.push({ lastBuildDate: d.toUTCString() });
    channel.push({ docs: 'http://blogs.law.harvard.edu/tech/rss'});

    /**
     * Channel Image
     * http://cyber.law.harvard.edu/rss/rss.html#ltimagegtSubelementOfLtchannelgt
     */
    if(options.image)
        channel.push({ image: options.image });

    /**
     * Channel Copyright
     * http://cyber.law.harvard.edu/rss/rss.html#optionalChannelElements
     */
    if(options.copyright)
        channel.push({ copyright: options.copyright });

    channel.push({ generator: generatorName });

    /**
     * Channel Categories
     * http://cyber.law.harvard.edu/rss/rss.html#comments
     */
    for(var i = 0; i < categories.length; i++)
        channel.push({ category: categories[i] });

    /**
     * Channel Categories
     * http://cyber.law.harvard.edu/rss/rss.html#hrelementsOfLtitemgt
     */
    for(var i = 0; i < items.length; i++) {

        // You need to have at least a title OR a description
        if(!items[i].title && !items[i].description)
            throw new Error('item must have at least a title or a description');

        var item = [];

        if(items[i].title)
            item.push({ title: { _cdata: items[i].title }});

        if(items[i].link)
            item.push({ link: items[i].link });

        if(items[i].date)
            item.push({ pubDate: items[i].date.toUTCString() });

        if(items[i].description)
            item.push({ description: { _cdata: items[i].description }});

        /**
         * Item Author
         * http://cyber.law.harvard.edu/rss/rss.html#ltauthorgtSubelementOfLtitemgt
         */
        if(items[i].author) {
            for(var y = 0; y < items[i].author.length; y++) {
                var itemAuthor = [];

                if(items[i].author[y].name)
                    itemAuthor.push({ name: items[i].author[y].name });

                if(items[i].author[y].email)
                    itemAuthor.push({ email: items[i].author[y].email });

                if(items[i].author[y].link)
                    itemAuthor.push({ link: items[i].author[y].link });

                item.push({ author: itemAuthor });
            }
        }

        if(items[i].image)
            item.push({ enclosure: [{ _attr: { url: items[i].image } }] });

        channel.push({ item: item });
    }

    return doctype + xml(container, true);
}

function RFC3339(d) {
    function pad(n) { return n < 10 ? '0' + n : n }

    return d.getUTCFullYear()+'-'
        + pad(d.getUTCMonth()+1)+'-'
        + pad(d.getUTCDate())+'T'
        + pad(d.getUTCHours())+':'
        + pad(d.getUTCMinutes())+':'
        + pad(d.getUTCSeconds())+'Z'
}

module.exports = Feed;<commit_msg>Removed author from being output in the RSS feed (it remains in the ATOM feed).<commit_after>var xml = require('xml');
var generatorName = 'Feed for Node.js';

// ============================================================================
// Feed Main Module
// ============================================================================
function Feed(options) {
    this.title       = options.title;
    this.description = options.description;
    this.link        = options.link;
    this.image       = options.image;
    this.copyright   = options.copyright;

    this.author      = options.author;          // Atom only (at channel level)

    this.items       = [];
    this.categories  = [];

    this.addItem = function (options) {
        options = options || {};
        var item = {
            title:          options.title,
            link:           options.link,
            description:    options.description,
            date:           options.date,
            image:          options.image,
            author:         options.author
        };

        this.items.push(item);
        return this;
    }

    this.addCategory = function (category) {
        this.categories.push(category);
        return this;
    }

    this.render = function (format) {
        switch(format) {
            case 'rss-2.0':
                return rss_2_0(this);
            break;

            case 'atom-1.0':
                return atom_1_0(this);
            break;

            default:
                return rss_2_0(this);
        }
    };

    /**
     * DEPRECATED METHODS
     */
    this.item = function (options)  {
        console.warn('DEPRECATED: use addItem() instead of item()');
        return this.addItem(options);
    }

    this.category = function (category) {
        console.warn('DEPRECATED: use addCategory() instead of category()');
        return this.addCategory(category);
    }
};

var urlPattern = new RegExp('(http|https)://[\w-]+(\.[\w-]+)+([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?');

// ============================================================================
// Atom 1.0 [application/atom+xml]
//
// - Reference: http://www.atomenabled.org/developers/syndication/
// ============================================================================

function atom_1_0(options) {
    var items       = [],
        container   = [],
        feed        = [],

        entries     = options.items,
        categories  = options.categories,
        author      = options.author,
        d           = new Date();

    var doctype = '<?xml version="1.0" encoding="utf-8"?>\n';

    container.push({ feed: feed });
    feed.push({ _attr: { xmlns: 'http://www.w3.org/2005/Atom' }});

    /**************************************************************************
     * "feed" node: REQUIRED elements
     *************************************************************************/
    feed.push({ title:      options.title });
    feed.push({ id:         options.link });
    feed.push({ updated:    RFC3339(d) });      // Auto-magic :)

    /**************************************************************************
     * "feed" node: recommended elements
     *************************************************************************/
    if(author)
    {
        var authorDetails = [];

        if(author.name)
            authorDetails.push({ name:     author.name });

        if(author.email)
            authorDetails.push({ email:     author.email });

        if(author.link)
            authorDetails.push({ uri:     author.link });

        feed.push({ author: authorDetails });
    }

    // link (relative link to feed) - will be added in a future version

    /**************************************************************************
     * "feed" node: optional elements
     *************************************************************************/
    if(options.description)
        feed.push({ subtitle:   options.description });

    if(options.image)
        feed.push({ logo:       options.image });

    if(options.copyright)
        feed.push({ rights:     options.copyright });

    feed.push({ generator:      generatorName });

    // Looping through categories (if any set!)
    for(var i=0; i<categories.length; i++)
        feed.push({ category: [{ _attr: { term: categories[i] } }] });

    // contributor

    // icon

    /**************************************************************************
     * "entry" nodes
     *************************************************************************/
    for(var i=0; i<entries.length; i++) {
        //
        // entry: required elements
        //
        var entry = [
            { title:        { _attr: { type: 'html' }, _cdata: entries[i].title }},
            { id:           entries[i].link },
            { link:         [{ _attr: { href: entries[i].link } }]},
            { updated:      RFC3339(entries[i].date) }
        ];

        //
        // entry: recommended elements
        //
        if(entries[i].description)
            entry.push({ summary: { _attr: { type: 'html' }, _cdata: entries[i].description }});

        // entry author(s)
        if(entries[i].author) {
            for(var y=0; y<entries[i].author.length; y++) {
                var entryAuthor = [];

                if(entries[i].author[y].name)
                    entryAuthor.push({ name: entries[i].author[y].name });

                if(entries[i].author[y].email)
                    entryAuthor.push({ email: entries[i].author[y].email });

                if(entries[i].author[y].link)
                    entryAuthor.push({ uri: entries[i].author[y].link });

                entry.push({ author: entryAuthor });
            }
        }

        // content

        // link - relative link to article

        //
        // entry: optional elements
        //

        // category

        // contributor

        // published

        // source

        // rights

        feed.push({ entry: entry });
    }

    return doctype + xml(container, true);
}

// ============================================================================
// Real Simple Syndication 2.0 (RSS 2.0)
//
// - Reference: http://feed2.w3.org/docs/rss2.html
// ============================================================================
function rss_2_0(options) {
    var items       = [],
        channel     = [],
        container   = [];

    var items = options.items;
    var categories = options.categories;
    var d = new Date();

    var doctype = '<?xml version="1.0" encoding="utf-8"?>\n';

    container.push({ rss: [] });
    container[0].rss.push({ _attr: { version: '2.0' } });
    container[0].rss.push({ channel: channel });

    /**
     * Required Channel Elements
     * http://cyber.law.harvard.edu/rss/rss.html#requiredChannelElements
     */
    if(options.title == undefined)
        throw new Error('channel title is mandatory');
    else
        channel.push({ title: options.title });

    if(options.description == undefined)
        throw new Error('channel description is mandatory');
    else
        channel.push({ description: options.description });

    if(options.link == undefined)
        throw new Error('channel link is mandatory');
    else
        if(!urlPattern.test(options.link))
            throw new Error('invalid channel link');
        else
            channel.push({ link: options.link });

    channel.push({ lastBuildDate: d.toUTCString() });
    channel.push({ docs: 'http://blogs.law.harvard.edu/tech/rss'});

    /**
     * Channel Image
     * http://cyber.law.harvard.edu/rss/rss.html#ltimagegtSubelementOfLtchannelgt
     */
    if(options.image)
        channel.push({ image: options.image });

    /**
     * Channel Copyright
     * http://cyber.law.harvard.edu/rss/rss.html#optionalChannelElements
     */
    if(options.copyright)
        channel.push({ copyright: options.copyright });

    channel.push({ generator: generatorName });

    /**
     * Channel Categories
     * http://cyber.law.harvard.edu/rss/rss.html#comments
     */
    for(var i = 0; i < categories.length; i++)
        channel.push({ category: categories[i] });

    /**
     * Channel Categories
     * http://cyber.law.harvard.edu/rss/rss.html#hrelementsOfLtitemgt
     */
    for(var i = 0; i < items.length; i++) {

        // You need to have at least a title OR a description
        if(!items[i].title && !items[i].description)
            throw new Error('item must have at least a title or a description');

        var item = [];

        if(items[i].title)
            item.push({ title: { _cdata: items[i].title }});

        if(items[i].link)
            item.push({ link: items[i].link });

        if(items[i].date)
            item.push({ pubDate: items[i].date.toUTCString() });

        if(items[i].description)
            item.push({ description: { _cdata: items[i].description }});

        /**
         * Item Author
         * http://cyber.law.harvard.edu/rss/rss.html#ltauthorgtSubelementOfLtitemgt
         */
        if(items[i].author) {
            for(var y = 0; y < items[i].author.length; y++) {
                var itemAuthor = [];

                if(items[i].author[y].name)
                    itemAuthor.push({ name: items[i].author[y].name });

                if(items[i].author[y].email)
                    itemAuthor.push({ email: items[i].author[y].email });

                if(items[i].author[y].link)
                    itemAuthor.push({ link: items[i].author[y].link });

                item.push({ author: itemAuthor });
            }
        }

        if(items[i].image)
            item.push({ enclosure: [{ _attr: { url: items[i].image } }] });

        channel.push({ item: item });
    }

    return doctype + xml(container, true);
}

function RFC3339(d) {
    function pad(n) { return n < 10 ? '0' + n : n }

    return d.getUTCFullYear()+'-'
        + pad(d.getUTCMonth()+1)+'-'
        + pad(d.getUTCDate())+'T'
        + pad(d.getUTCHours())+':'
        + pad(d.getUTCMinutes())+':'
        + pad(d.getUTCSeconds())+'Z'
}

module.exports = Feed;<|endoftext|>


<commit_before>var xml = require('xml');
var generatorName = 'Feed for Node.js';

// ============================================================================
// Feed Main Module
// ============================================================================
function Feed(options) {
    this.id          = options.id;
    this.title       = options.title;
    this.description = options.description;
    this.link        = options.link;
    this.image       = options.image;
    this.copyright   = options.copyright;
    this.feed        = options.feed;
    this.hub         = options.hub;
    this.updated     = options.updated;

    this.author      = options.author;          // Atom only (at channel level)

    this.items       = [];
    this.categories  = [];
    this.contributors = [];     				// Atom only

    this.addItem = function (options) {
        options = options || {};
        var item = {
            title:          options.title,
            link:           options.link,
            description:    options.description,
            date:           options.date,
            image:          options.image,
            author:         options.author,
            contributor:    options.contributor,	// Atom only
            guid:           options.guid,
            content:        options.content,
            copyright:		options.copyright	// Atom only
        };

        this.items.push(item);
        return this;
    }

    this.addCategory = function (category) {
        this.categories.push(category);
        return this;
    }

    this.addContributor = function (contributor) {
        this.contributors.push(contributor);
        return this;
    }

    this.render = function (format) {
        switch(format) {
            case 'rss-2.0':
                return rss_2_0(this);
            break;

            case 'atom-1.0':
                return atom_1_0(this);
            break;

            default:
                return rss_2_0(this);
        }
    };

    /**
     * DEPRECATED METHODS
     */
    this.item = function (options)  {
        console.warn('DEPRECATED: use addItem() instead of item()');
        return this.addItem(options);
    }

    this.category = function (category) {
        console.warn('DEPRECATED: use addCategory() instead of category()');
        return this.addCategory(category);
    }
};

// ============================================================================
// Atom 1.0 [application/atom+xml]
//
// - Reference: http://www.atomenabled.org/developers/syndication/
// ============================================================================

function atom_1_0(options) {
    var items       = [],
        container   = [],
        feed        = [],

        entries     = options.items,
        categories  = options.categories,
        author      = options.author,
        contributors = options.contributors,
        d           = new Date();

    var doctype = '<?xml version="1.0" encoding="utf-8"?>\n';

    container.push({ feed: feed });
    feed.push({ _attr: { xmlns: 'http://www.w3.org/2005/Atom' }});

    /**************************************************************************
     * "feed" node: REQUIRED elements
     *************************************************************************/
    if(options.id == undefined)
        throw new Error('feed id is mandatory');
    else
        feed.push({ id: options.id });

    if(options.title == undefined)
        throw new Error('feed title is mandatory');
    else
        feed.push({ title: options.title });

    if(options.link == undefined)
        throw new Error('feed link is mandatory');
    else
        if(!validateURL(options.link))
            throw new Error('invalid feed link');
        else
            feed.push({ link: options.link });

    if(Object.prototype.toString.call(options.updated) === '[object Date]')
        feed.push({ updated: RFC3339(options.updated) });
    else
       feed.push({ updated: RFC3339(d) });

    /**************************************************************************
     * "feed" node: recommended elements
     *************************************************************************/

    if(author) {
        var authorDetails = [];

        if(author.name)
            authorDetails.push({ name:     author.name });

        if(author.email)
            authorDetails.push({ email:     author.email });

        if(author.link)
            authorDetails.push({ uri:     author.link });

        feed.push({ author: authorDetails });
    }

    // link (rel="alternate")
    if(options.link)
        feed.push({ link: { _attr: { rel: 'alternate', href: options.link }}});

    // link (rel="self")
    if(options.feed)
        feed.push({ link: { _attr: { rel: 'self', href: options.feed }}});

    // link (rel="hub")
    if(options.hub)
        feed.push({ link: { _attr: { rel:'hub', href: options.hub }}});

    /**************************************************************************
     * "feed" node: optional elements
     *************************************************************************/

    if(options.description)
        feed.push({ subtitle:   options.description });

    if(options.image)
        feed.push({ logo:       options.image });

    if(options.copyright)
        feed.push({ rights:     options.copyright });

    feed.push({ generator:      generatorName });

    // Looping through categories (if any set!)
    for(var i=0; i<categories.length; i++)
        feed.push({ category: [{ _attr: { term: categories[i] } }] });

    if(contributors) {
    	for(var y = 0; y < contributors.length; y++) {
	        var contributorDetails = [];

	        if(contributors[y].name)
	        	contributorDetails.push({ name:     contributors[y].name });

	        if(contributors[y].email)
	        	contributorDetails.push({ email:     contributors[y].email });

	        if(contributors[y].link)
	            authorDetails.push({ uri:     contributors[y].link });

	        feed.push({ contributor: contributorDetails });
    	}
    }

    // icon

    /**************************************************************************
     * "entry" nodes
     *************************************************************************/
    for(var i = 0; i < entries.length; i++) {
        //
        // entry: required elements
        //
        if(entries[i].title == undefined)
            throw new Error('entry need a title');

        if(entries[i].link == undefined)
            throw new Error('entry need a link');
        else
            if(!validateURL(entries[i].link))
                throw new Error('invalid entry link');

        if(entries[i].date == undefined || !(entries[i].date instanceof Date))
            throw new Error('entry need a date');

        var entry = [
            { title:        { _attr: { type: 'html' }, _cdata: entries[i].title }},
            { id:           entries[i].link },
            { link:         [{ _attr: { href: entries[i].link } }]},
            { updated:      RFC3339(entries[i].date) }
        ];

        //
        // entry: recommended elements
        //
        if(entries[i].description)
            entry.push({ summary: { _attr: { type: 'html' }, _cdata: entries[i].description }});

        if(entries[i].content)
            entry.push({ content: { _attr: { type: 'html' }, _cdata: entries[i].content }});

        // entry author(s)
        if(entries[i].author) {
            for(var y = 0; y < entries[i].author.length; y++) {
                var entryAuthor = [];

                if(entries[i].author[y].name)
                    entryAuthor.push({ name: entries[i].author[y].name });

                if(entries[i].author[y].email)
                    entryAuthor.push({ email: entries[i].author[y].email });

                if(entries[i].author[y].link)
                    entryAuthor.push({ uri: entries[i].author[y].link });

                entry.push({ author: entryAuthor });
            }
        }

        // content

        // link - relative link to article

        //
        // entry: optional elements
        //

        // category

        // contributor
        if(entries[i].contributor) {
            for(var y = 0; y < entries[i].contributor.length; y++) {
                var entryContributor = [];

                if(entries[i].contributor[y].name)
                    entryContributor.push({ name: entries[i].contributor[y].name });

                if(entries[i].contributor[y].email)
                    entryContributor.push({ email: entries[i].contributor[y].email });

                if(entries[i].contributor[y].link)
                    entryContributor.push({ uri: entries[i].contributor[y].link });

                entry.push({ contributor: entryContributor });
            }
        }

        // published

        // source

        // rights
        if(entries[i].copyright)
        	entry.push({ rights: entries[i].copyright });

        feed.push({ entry: entry });
    }

    return doctype + xml(container, true);
}

// ============================================================================
// Real Simple Syndication 2.0 (RSS 2.0)
//
// - Reference: http://feed2.w3.org/docs/rss2.html
// ============================================================================
function rss_2_0(options) {
    var items       = [],
        channel     = [],
        container   = [];

    var items = options.items;
    var categories = options.categories;
    var d = new Date();
    var bContent = false;
    var bAtom = false;

    var doctype = '<?xml version="1.0" encoding="utf-8"?>\n';

    container.push({ rss: [] });
    container[0].rss.push({ _attr: { version: '2.0' } });
    container[0].rss.push({ channel: channel });

    /**
     * Required Channel Elements
     * http://cyber.law.harvard.edu/rss/rss.html#requiredChannelElements
     */
    if(options.title == undefined)
        throw new Error('channel title is mandatory');
    else
        channel.push({ title: options.title });

    if(options.description == undefined)
        throw new Error('channel description is mandatory');
    else
        channel.push({ description: options.description });

    if(options.link == undefined)
        throw new Error('channel link is mandatory');
    else
        if(!validateURL(options.link))
            throw new Error('invalid channel link');
        else
            channel.push({ link: options.link });

    if(Object.prototype.toString.call(options.updated) === '[object Date]')
        channel.push({ lastBuildDate: options.updated.toUTCString() });
    else
       channel.push({ lastBuildDate: d.toUTCString() });

    channel.push({ docs: 'http://blogs.law.harvard.edu/tech/rss'});

    /**
     * Channel Image
     * http://cyber.law.harvard.edu/rss/rss.html#ltimagegtSubelementOfLtchannelgt
     */
    if(options.image)
        channel.push({
            image: [
                { title: options.title },
                { url: options.image },
                { link: options.link }
            ]
        });

    /**
     * Channel Copyright
     * http://cyber.law.harvard.edu/rss/rss.html#optionalChannelElements
     */
    if(options.copyright)
        channel.push({ copyright: options.copyright });

    channel.push({ generator: generatorName });

    /**
     * Channel Categories
     * http://cyber.law.harvard.edu/rss/rss.html#comments
     */
    for(var i = 0; i < categories.length; i++)
        channel.push({ category: categories[i] });

    /**
     * Feed URL
     * http://validator.w3.org/feed/docs/warning/MissingAtomSelfLink.html
     */
    if(options.feed) {
        bAtom = true;
        channel.push({ "atom:link": { _attr: { href: options.feed, rel: 'self', type: 'application/rss+xml' }}});
    }

    /**
     * Hub for PubSubHubbub
     * https://code.google.com/p/pubsubhubbub/
     */
    if(options.hub) {
        bAtom = true;
        channel.push({ "atom:link": { _attr: { href: options.hub, rel: 'hub' }}});
    }

    /**
     * Channel Categories
     * http://cyber.law.harvard.edu/rss/rss.html#hrelementsOfLtitemgt
     */
    for(var i = 0; i < items.length; i++) {

        // You need to have at least a title OR a description
        if(!items[i].title && !items[i].description)
            throw new Error('item must have at least a title or a description');

        var item = [];

        if(items[i].title)
            item.push({ title: { _cdata: items[i].title }});

        if(items[i].link)
            item.push({ link: items[i].link });

        if(items[i].guid)
            item.push({ guid: items[i].guid });
        else if (items[i].link)
            item.push({ guid: items[i].link });

        if(items[i].date)
            item.push({ pubDate: items[i].date.toUTCString() });

        if(items[i].description)
            item.push({ description: { _cdata: items[i].description }});

        if(items[i].content) {
            bContent = true;
            item.push({ 'content:encoded': { _cdata: items[i].content }});
        }
        /**
         * Item Author
         * http://cyber.law.harvard.edu/rss/rss.html#ltauthorgtSubelementOfLtitemgt
         */
        if(items[i].author) {
            for(var y = 0; y < items[i].author.length; y++) {
                var itemAuthor = [];

                if(items[i].author[y].name)
                    itemAuthor.push({ name: items[i].author[y].name });

                if(items[i].author[y].email)
                    itemAuthor.push({ email: items[i].author[y].email });

                if(items[i].author[y].link)
                    itemAuthor.push({ link: items[i].author[y].link });

                item.push({ author: itemAuthor });
            }
        }

        if(items[i].image)
            item.push({ enclosure: [{ _attr: { url: items[i].image } }] });

        channel.push({ item: item });
    }

    if(bContent)
        container[0].rss[0]._attr['xmlns:content'] = 'http://purl.org/rss/1.0/modules/content/';

    if(bAtom)
        container[0].rss[0]._attr['xmlns:atom'] = 'http://www.w3.org/2005/Atom';

    return doctype + xml(container, true);
}

function RFC3339(d) {
    function pad(n) { return n < 10 ? '0' + n : n }

    return d.getUTCFullYear()+'-'
        + pad(d.getUTCMonth()+1)+'-'
        + pad(d.getUTCDate())+'T'
        + pad(d.getUTCHours())+':'
        + pad(d.getUTCMinutes())+':'
        + pad(d.getUTCSeconds())+'Z'
}

function validateURL(str) {
    var urlPattern = /^(?:(?:https?):\/\/)?(?:\S+(?::\S*)?@)?(?:(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))|localhost)(?::\d{2,5})?(?:\/[^\s]*)?$/i;

    return urlPattern.test(str);
}

module.exports = Feed;
<commit_msg>Removed extraneous link element<commit_after>var xml = require('xml');
var generatorName = 'Feed for Node.js';

// ============================================================================
// Feed Main Module
// ============================================================================
function Feed(options) {
    this.id          = options.id;
    this.title       = options.title;
    this.description = options.description;
    this.link        = options.link;
    this.image       = options.image;
    this.copyright   = options.copyright;
    this.feed        = options.feed;
    this.hub         = options.hub;
    this.updated     = options.updated;

    this.author      = options.author;          // Atom only (at channel level)

    this.items       = [];
    this.categories  = [];
    this.contributors = [];     				// Atom only

    this.addItem = function (options) {
        options = options || {};
        var item = {
            title:          options.title,
            link:           options.link,
            description:    options.description,
            date:           options.date,
            image:          options.image,
            author:         options.author,
            contributor:    options.contributor,	// Atom only
            guid:           options.guid,
            content:        options.content,
            copyright:		options.copyright	// Atom only
        };

        this.items.push(item);
        return this;
    }

    this.addCategory = function (category) {
        this.categories.push(category);
        return this;
    }

    this.addContributor = function (contributor) {
        this.contributors.push(contributor);
        return this;
    }

    this.render = function (format) {
        switch(format) {
            case 'rss-2.0':
                return rss_2_0(this);
            break;

            case 'atom-1.0':
                return atom_1_0(this);
            break;

            default:
                return rss_2_0(this);
        }
    };

    /**
     * DEPRECATED METHODS
     */
    this.item = function (options)  {
        console.warn('DEPRECATED: use addItem() instead of item()');
        return this.addItem(options);
    }

    this.category = function (category) {
        console.warn('DEPRECATED: use addCategory() instead of category()');
        return this.addCategory(category);
    }
};

// ============================================================================
// Atom 1.0 [application/atom+xml]
//
// - Reference: http://www.atomenabled.org/developers/syndication/
// ============================================================================

function atom_1_0(options) {
    var items       = [],
        container   = [],
        feed        = [],

        entries     = options.items,
        categories  = options.categories,
        author      = options.author,
        contributors = options.contributors,
        d           = new Date();

    var doctype = '<?xml version="1.0" encoding="utf-8"?>\n';

    container.push({ feed: feed });
    feed.push({ _attr: { xmlns: 'http://www.w3.org/2005/Atom' }});

    /**************************************************************************
     * "feed" node: REQUIRED elements
     *************************************************************************/
    if(options.id == undefined)
        throw new Error('feed id is mandatory');
    else
        feed.push({ id: options.id });

    if(options.title == undefined)
        throw new Error('feed title is mandatory');
    else
        feed.push({ title: options.title });

    if(options.link == undefined)
        throw new Error('feed link is mandatory');
    else
        if(!validateURL(options.link))
            throw new Error('invalid feed link');

    if(Object.prototype.toString.call(options.updated) === '[object Date]')
        feed.push({ updated: RFC3339(options.updated) });
    else
       feed.push({ updated: RFC3339(d) });

    /**************************************************************************
     * "feed" node: recommended elements
     *************************************************************************/

    if(author) {
        var authorDetails = [];

        if(author.name)
            authorDetails.push({ name:     author.name });

        if(author.email)
            authorDetails.push({ email:     author.email });

        if(author.link)
            authorDetails.push({ uri:     author.link });

        feed.push({ author: authorDetails });
    }

    // link (rel="alternate")
    if(options.link)
        feed.push({ link: { _attr: { rel: 'alternate', href: options.link }}});

    // link (rel="self")
    if(options.feed)
        feed.push({ link: { _attr: { rel: 'self', href: options.feed }}});

    // link (rel="hub")
    if(options.hub)
        feed.push({ link: { _attr: { rel:'hub', href: options.hub }}});

    /**************************************************************************
     * "feed" node: optional elements
     *************************************************************************/

    if(options.description)
        feed.push({ subtitle:   options.description });

    if(options.image)
        feed.push({ logo:       options.image });

    if(options.copyright)
        feed.push({ rights:     options.copyright });

    feed.push({ generator:      generatorName });

    // Looping through categories (if any set!)
    for(var i=0; i<categories.length; i++)
        feed.push({ category: [{ _attr: { term: categories[i] } }] });

    if(contributors) {
    	for(var y = 0; y < contributors.length; y++) {
	        var contributorDetails = [];

	        if(contributors[y].name)
	        	contributorDetails.push({ name:     contributors[y].name });

	        if(contributors[y].email)
	        	contributorDetails.push({ email:     contributors[y].email });

	        if(contributors[y].link)
	            authorDetails.push({ uri:     contributors[y].link });

	        feed.push({ contributor: contributorDetails });
    	}
    }

    // icon

    /**************************************************************************
     * "entry" nodes
     *************************************************************************/
    for(var i = 0; i < entries.length; i++) {
        //
        // entry: required elements
        //
        if(entries[i].title == undefined)
            throw new Error('entry need a title');

        if(entries[i].link == undefined)
            throw new Error('entry need a link');
        else
            if(!validateURL(entries[i].link))
                throw new Error('invalid entry link');

        if(entries[i].date == undefined || !(entries[i].date instanceof Date))
            throw new Error('entry need a date');

        var entry = [
            { title:        { _attr: { type: 'html' }, _cdata: entries[i].title }},
            { id:           entries[i].link },
            { link:         [{ _attr: { href: entries[i].link } }]},
            { updated:      RFC3339(entries[i].date) }
        ];

        //
        // entry: recommended elements
        //
        if(entries[i].description)
            entry.push({ summary: { _attr: { type: 'html' }, _cdata: entries[i].description }});

        if(entries[i].content)
            entry.push({ content: { _attr: { type: 'html' }, _cdata: entries[i].content }});

        // entry author(s)
        if(entries[i].author) {
            for(var y = 0; y < entries[i].author.length; y++) {
                var entryAuthor = [];

                if(entries[i].author[y].name)
                    entryAuthor.push({ name: entries[i].author[y].name });

                if(entries[i].author[y].email)
                    entryAuthor.push({ email: entries[i].author[y].email });

                if(entries[i].author[y].link)
                    entryAuthor.push({ uri: entries[i].author[y].link });

                entry.push({ author: entryAuthor });
            }
        }

        // content

        // link - relative link to article

        //
        // entry: optional elements
        //

        // category

        // contributor
        if(entries[i].contributor) {
            for(var y = 0; y < entries[i].contributor.length; y++) {
                var entryContributor = [];

                if(entries[i].contributor[y].name)
                    entryContributor.push({ name: entries[i].contributor[y].name });

                if(entries[i].contributor[y].email)
                    entryContributor.push({ email: entries[i].contributor[y].email });

                if(entries[i].contributor[y].link)
                    entryContributor.push({ uri: entries[i].contributor[y].link });

                entry.push({ contributor: entryContributor });
            }
        }

        // published

        // source

        // rights
        if(entries[i].copyright)
        	entry.push({ rights: entries[i].copyright });

        feed.push({ entry: entry });
    }

    return doctype + xml(container, true);
}

// ============================================================================
// Real Simple Syndication 2.0 (RSS 2.0)
//
// - Reference: http://feed2.w3.org/docs/rss2.html
// ============================================================================
function rss_2_0(options) {
    var items       = [],
        channel     = [],
        container   = [];

    var items = options.items;
    var categories = options.categories;
    var d = new Date();
    var bContent = false;
    var bAtom = false;

    var doctype = '<?xml version="1.0" encoding="utf-8"?>\n';

    container.push({ rss: [] });
    container[0].rss.push({ _attr: { version: '2.0' } });
    container[0].rss.push({ channel: channel });

    /**
     * Required Channel Elements
     * http://cyber.law.harvard.edu/rss/rss.html#requiredChannelElements
     */
    if(options.title == undefined)
        throw new Error('channel title is mandatory');
    else
        channel.push({ title: options.title });

    if(options.description == undefined)
        throw new Error('channel description is mandatory');
    else
        channel.push({ description: options.description });

    if(options.link == undefined)
        throw new Error('channel link is mandatory');
    else
        if(!validateURL(options.link))
            throw new Error('invalid channel link');
        else
            channel.push({ link: options.link });

    if(Object.prototype.toString.call(options.updated) === '[object Date]')
        channel.push({ lastBuildDate: options.updated.toUTCString() });
    else
       channel.push({ lastBuildDate: d.toUTCString() });

    channel.push({ docs: 'http://blogs.law.harvard.edu/tech/rss'});

    /**
     * Channel Image
     * http://cyber.law.harvard.edu/rss/rss.html#ltimagegtSubelementOfLtchannelgt
     */
    if(options.image)
        channel.push({
            image: [
                { title: options.title },
                { url: options.image },
                { link: options.link }
            ]
        });

    /**
     * Channel Copyright
     * http://cyber.law.harvard.edu/rss/rss.html#optionalChannelElements
     */
    if(options.copyright)
        channel.push({ copyright: options.copyright });

    channel.push({ generator: generatorName });

    /**
     * Channel Categories
     * http://cyber.law.harvard.edu/rss/rss.html#comments
     */
    for(var i = 0; i < categories.length; i++)
        channel.push({ category: categories[i] });

    /**
     * Feed URL
     * http://validator.w3.org/feed/docs/warning/MissingAtomSelfLink.html
     */
    if(options.feed) {
        bAtom = true;
        channel.push({ "atom:link": { _attr: { href: options.feed, rel: 'self', type: 'application/rss+xml' }}});
    }

    /**
     * Hub for PubSubHubbub
     * https://code.google.com/p/pubsubhubbub/
     */
    if(options.hub) {
        bAtom = true;
        channel.push({ "atom:link": { _attr: { href: options.hub, rel: 'hub' }}});
    }

    /**
     * Channel Categories
     * http://cyber.law.harvard.edu/rss/rss.html#hrelementsOfLtitemgt
     */
    for(var i = 0; i < items.length; i++) {

        // You need to have at least a title OR a description
        if(!items[i].title && !items[i].description)
            throw new Error('item must have at least a title or a description');

        var item = [];

        if(items[i].title)
            item.push({ title: { _cdata: items[i].title }});

        if(items[i].link)
            item.push({ link: items[i].link });

        if(items[i].guid)
            item.push({ guid: items[i].guid });
        else if (items[i].link)
            item.push({ guid: items[i].link });

        if(items[i].date)
            item.push({ pubDate: items[i].date.toUTCString() });

        if(items[i].description)
            item.push({ description: { _cdata: items[i].description }});

        if(items[i].content) {
            bContent = true;
            item.push({ 'content:encoded': { _cdata: items[i].content }});
        }
        /**
         * Item Author
         * http://cyber.law.harvard.edu/rss/rss.html#ltauthorgtSubelementOfLtitemgt
         */
        if(items[i].author) {
            for(var y = 0; y < items[i].author.length; y++) {
                var itemAuthor = [];

                if(items[i].author[y].name)
                    itemAuthor.push({ name: items[i].author[y].name });

                if(items[i].author[y].email)
                    itemAuthor.push({ email: items[i].author[y].email });

                if(items[i].author[y].link)
                    itemAuthor.push({ link: items[i].author[y].link });

                item.push({ author: itemAuthor });
            }
        }

        if(items[i].image)
            item.push({ enclosure: [{ _attr: { url: items[i].image } }] });

        channel.push({ item: item });
    }

    if(bContent)
        container[0].rss[0]._attr['xmlns:content'] = 'http://purl.org/rss/1.0/modules/content/';

    if(bAtom)
        container[0].rss[0]._attr['xmlns:atom'] = 'http://www.w3.org/2005/Atom';

    return doctype + xml(container, true);
}

function RFC3339(d) {
    function pad(n) { return n < 10 ? '0' + n : n }

    return d.getUTCFullYear()+'-'
        + pad(d.getUTCMonth()+1)+'-'
        + pad(d.getUTCDate())+'T'
        + pad(d.getUTCHours())+':'
        + pad(d.getUTCMinutes())+':'
        + pad(d.getUTCSeconds())+'Z'
}

function validateURL(str) {
    var urlPattern = /^(?:(?:https?):\/\/)?(?:\S+(?::\S*)?@)?(?:(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))|localhost)(?::\d{2,5})?(?:\/[^\s]*)?$/i;

    return urlPattern.test(str);
}

module.exports = Feed;
<|endoftext|>