[
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 Josh Katz\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "R/launch.R",
    "content": "source(file.path(Sys.getenv(\"DIRNAME\"), \"needs.R\"))\nneeds(jsonlite)\n\nrun <- function(dataIn) {\n\n  # set up environment\n  input <- unname(dataIn[[1]])\n  .e <- as.environment(list(\n    path = dataIn[[2]],\n    out = modifyList(list(x = NULL, auto_unbox = T),\n      dataIn[[3]], keep.null = T)\n  ))\n  lockBinding(\".e\", environment())\n\n  # run source, capture output\n  captured <- tryCatch(capture.output({\n    temp <- source(.e$path, local = T)$value\n  }), error = function(err) err)\n  unlockBinding(\".e\", environment())\n\n  # process and return\n  if (inherits(captured, \"error\")) {\n    msg <- conditionMessage(captured)\n    cat(\"Error in R script\", .e$path, \"\\n\", sQuote(msg), file = stderr())\n    return(invisible(F))\n  }\n  .e$out$x <- if (is.null(temp)) {\n     \"\"\n  } else {\n    temp\n  }\n  do.call(toJSON, .e$out)\n\n}\n\nsuppressWarnings(\n  run(fromJSON(Sys.getenv(\"input\")))\n)\n"
  },
  {
    "path": "README.md",
    "content": "# r-script\n\nA simple little module for passing data from NodeJS to R (and back again).\n\nData passed from node is converted into a list and loaded into the R environment as the variable `input`. No special syntax in R is needed. For better portability/reliability, it's recommended to load packages with [`needs`](https://github.com/joshkatz/needs) (comes packaged inside the module — no installation required).\n\n### Installation\n```\nnpm install r-script\n```\n\n### Example\n\n```js\nvar R = require(\"r-script\");\n```\n\n##### Synchronous\n```javascript\n// example.js\n\nvar out = R(\"ex-sync.R\")\n  .data(\"hello world\", 20)\n  .callSync();\n  \nconsole.log(out);\n\n// [ 'oedorlwlh l', 'oldlrhelwo ', 'erllol dhow', ' lwrellodoh', 'holdlerw ol',\n//   'lrlewdhol o', 'lll wohdeor', 'hwrlledl oo', 'elrooh lwld', 'ewrlo lhdlo',\n//   'hlloroelwd', 'h eodollwlr', 'wr ldleohlo', 'or ohldlwel', 'lohe lowlrd',\n//   'rhdwoelllo ', 'owhorldell ', 'rlle ohdolw', 'rhlwolle od', 'woro helldl' ]\n```\n\n```r\n# ex-sync.R\nneeds(magrittr)\nset.seed(512)\ndo.call(rep, input) %>% \n  strsplit(NULL) %>% \n  sapply(sample) %>% \n  apply(2, paste, collapse = \"\")\n```\n\n\n##### Asynchronous\n\n```javascript\n// example.js\n\nvar attitude = JSON.parse(\n  require(\"fs\").readFileSync(\"example/attitude.json\", \"utf8\"));\n\nR(\"example/ex-async.R\")\n  .data({df: attitude, nGroups: 3, fxn: \"mean\" })\n  .call(function(err, d) {\n    if (err) throw err;\n    console.log(d);\n  });\n  \n// [ { group: '(40,55]', rating: 46.7143, advance: 41.1429 },\n//   { group: '(55,70]', rating: 64.6154, advance: 41.9231 },\n//   { group: '(70,85]', rating: 77.2, advance: 45.5 } ]\n```\n\n```r\n# ex-async.R\nneeds(dplyr)\nattach(input[[1]])\n\nreturn(\"early returns are ignored\")\ncat(\"so are undirected calls to cat\")\nprint(\"or print\")\ncat(\"unless directed to a file\", file = \"out.Rout\")\n\n# output of final expression is returned to node\ndf %>% \n  mutate(group = cut(rating, nGroups, ordered = T)) %>% \n  group_by(group) %>% \n  summarize_each(funs_(fxn)) %>%\n  select(group, rating, advance) %>%\n  mutate(group = as.character(group))\n```\n\n### Syntax\n\n**R**(_path_)\n\nCreates a new object that will source the R script specified by _path_.\n\nR.**data**(...)\n\nAdds data to the object and returns itself. You can give any number of arguments of different types. \n\nR.**call**([_options_], _callback_)\n\nCalls R. Any previously supplied _data_ is stringified into JSON and passed to R, where it's converted into a list and loaded into the R environment as the variable `input`. On completion, the _callback_ is invoked with two arguments: any error and the output from R, parsed back into a Javascript object.\n\nAdditional arguments for the conversion from R to JSON can be specified as _options_ (see documentation for [```toJSON```](https://github.com/jeroenooms/jsonlite/blob/master/R/toJSON.R) from the R package `jsonlite` for defaults).\n\nR.**callSync**([_options_])\n\nThe same as above, but calls R synchronously.\n"
  },
  {
    "path": "example/attitude.json",
    "content": "[{\"rating\":43,\"complaints\":51,\"privileges\":30,\"learning\":39,\"raises\":61,\"critical\":92,\"advance\":45},{\"rating\":63,\"complaints\":64,\"privileges\":51,\"learning\":54,\"raises\":63,\"critical\":73,\"advance\":47},{\"rating\":71,\"complaints\":70,\"privileges\":68,\"learning\":69,\"raises\":76,\"critical\":86,\"advance\":48},{\"rating\":61,\"complaints\":63,\"privileges\":45,\"learning\":47,\"raises\":54,\"critical\":84,\"advance\":35},{\"rating\":81,\"complaints\":78,\"privileges\":56,\"learning\":66,\"raises\":71,\"critical\":83,\"advance\":47},{\"rating\":43,\"complaints\":55,\"privileges\":49,\"learning\":44,\"raises\":54,\"critical\":49,\"advance\":34},{\"rating\":58,\"complaints\":67,\"privileges\":42,\"learning\":56,\"raises\":66,\"critical\":68,\"advance\":35},{\"rating\":71,\"complaints\":75,\"privileges\":50,\"learning\":55,\"raises\":70,\"critical\":66,\"advance\":41},{\"rating\":72,\"complaints\":82,\"privileges\":72,\"learning\":67,\"raises\":71,\"critical\":83,\"advance\":31},{\"rating\":67,\"complaints\":61,\"privileges\":45,\"learning\":47,\"raises\":62,\"critical\":80,\"advance\":41},{\"rating\":64,\"complaints\":53,\"privileges\":53,\"learning\":58,\"raises\":58,\"critical\":67,\"advance\":34},{\"rating\":67,\"complaints\":60,\"privileges\":47,\"learning\":39,\"raises\":59,\"critical\":74,\"advance\":41},{\"rating\":69,\"complaints\":62,\"privileges\":57,\"learning\":42,\"raises\":55,\"critical\":63,\"advance\":25},{\"rating\":68,\"complaints\":83,\"privileges\":83,\"learning\":45,\"raises\":59,\"critical\":77,\"advance\":35},{\"rating\":77,\"complaints\":77,\"privileges\":54,\"learning\":72,\"raises\":79,\"critical\":77,\"advance\":46},{\"rating\":81,\"complaints\":90,\"privileges\":50,\"learning\":72,\"raises\":60,\"critical\":54,\"advance\":36},{\"rating\":74,\"complaints\":85,\"privileges\":64,\"learning\":69,\"raises\":79,\"critical\":79,\"advance\":63},{\"rating\":65,\"complaints\":60,\"privileges\":65,\"learning\":75,\"raises\":55,\"critical\":80,\"advance\":60},{\"rating\":65,\"complaints\":70,\"privileges\":46,\"learning\":57,\"raises\":75,\"critical\":85,\"advance\":46},{\"rating\":50,\"complaints\":58,\"privileges\":68,\"learning\":54,\"raises\":64,\"critical\":78,\"advance\":52},{\"rating\":50,\"complaints\":40,\"privileges\":33,\"learning\":34,\"raises\":43,\"critical\":64,\"advance\":33},{\"rating\":64,\"complaints\":61,\"privileges\":52,\"learning\":62,\"raises\":66,\"critical\":80,\"advance\":41},{\"rating\":53,\"complaints\":66,\"privileges\":52,\"learning\":50,\"raises\":63,\"critical\":80,\"advance\":37},{\"rating\":40,\"complaints\":37,\"privileges\":42,\"learning\":58,\"raises\":50,\"critical\":57,\"advance\":49},{\"rating\":63,\"complaints\":54,\"privileges\":42,\"learning\":48,\"raises\":66,\"critical\":75,\"advance\":33},{\"rating\":66,\"complaints\":77,\"privileges\":66,\"learning\":63,\"raises\":88,\"critical\":76,\"advance\":72},{\"rating\":78,\"complaints\":75,\"privileges\":58,\"learning\":74,\"raises\":80,\"critical\":78,\"advance\":49},{\"rating\":48,\"complaints\":57,\"privileges\":44,\"learning\":45,\"raises\":51,\"critical\":83,\"advance\":38},{\"rating\":85,\"complaints\":85,\"privileges\":71,\"learning\":71,\"raises\":77,\"critical\":74,\"advance\":55},{\"rating\":82,\"complaints\":82,\"privileges\":39,\"learning\":59,\"raises\":64,\"critical\":78,\"advance\":39}]\n"
  },
  {
    "path": "example/ex-async.R",
    "content": "# ex-async.R\nneeds(dplyr)\nattach(input[[1]])\n\nreturn(\"early returns are ignored\")\ncat(\"so are undirected calls to cat\")\nprint(\"or print\")\ncat(\"unless directed to a file\", file = \"out.Rout\")\n\n# output of final expression is returned to parent\ndf %>% \n  mutate(group = cut(rating, nGroups, ordered = T)) %>% \n  group_by(group) %>% \n  summarize_all(funs_(fxn)) %>%\n  select(group, rating, advance) %>%\n  mutate(group = as.character(group))\n"
  },
  {
    "path": "example/ex-sync.R",
    "content": "# example/ex-sync.R\nneeds(magrittr)\nset.seed(512)\ndo.call(rep, input) %>% \n  strsplit(NULL) %>% \n  sapply(sample) %>% \n  apply(2, paste, collapse = \"\")"
  },
  {
    "path": "example/ex.js",
    "content": "#!/usr/bin/env node\nvar R = require(\"r-script\");\n\n// sync\nvar out = R(\"example/ex-sync.R\")\n  .data(\"hello world\", 20)\n  .callSync();\nconsole.log(out);\n\n// async\nvar attitude = JSON.parse(\n  require(\"fs\").readFileSync(\"example/attitude.json\", \"utf8\"));\n\nR(\"example/ex-async.R\")\n  .data({df: attitude, nGroups: 3, fxn: \"mean\" })\n  .call(function(err, d) {\n    if (err) throw err;\n    console.log(d);\n  });"
  },
  {
    "path": "index.js",
    "content": "var _ = require(\"underscore\"),\n    child_process = require(\"child_process\");\n\nfunction init(path) {\n  var obj = new R(path);\n  _.bindAll(obj, \"data\", \"call\", \"callSync\");\n  return obj;\n}\n\nfunction R(path) {\n  this.d = {};\n  this.path = path;\n  this.options = {\n    env: _.extend({DIRNAME: __dirname}, process.env),\n    encoding: \"utf8\"\n  };\n  this.idCounter = 0;\n  this.args = [\"--vanilla\", __dirname + \"/R/launch.R\"];\n}\n\nR.prototype.data = function() {\n  for (var i = 0; i < arguments.length; i++) {\n    this.d[++this.idCounter] = arguments[i];\n  }\n  return this;\n};\n\nR.prototype.call = function(_opts, _callback) {\n  var callback = _callback || _opts;\n  var opts = _.isFunction(_opts) ? {} : _opts;\n  this.options.env.input = JSON.stringify([this.d, this.path, opts]);\n  var child = child_process.spawn(\"Rscript\", this.args, this.options);\n  var body = \"\";\n  child.stderr.on(\"data\", callback);\n  child.stdout.on(\"data\", function(d) {\n     body += d;\n  });\n  child.on(\"close\", function(code) {\n    callback(null, JSON.parse(body));\n  });\n};\n\nR.prototype.callSync = function(_opts) {\n  var opts = _opts || {};\n  this.options.env.input = JSON.stringify([this.d, this.path, opts]);\n  var child = child_process.spawnSync(\"Rscript\", this.args, this.options);\n  if (child.stderr) throw child.stderr;\n  return(JSON.parse(child.stdout));\n};\n\nmodule.exports = init;\n"
  },
  {
    "path": "needs.R",
    "content": "tryCatch(needs(), error = function(e) {\n  while (\".needs\" %in% search()) detach(.needs)\n  .needs <- new.env(parent = .GlobalEnv)\n  .needs$needs <- function(...) \n{\n    needs_ <- function(...) {\n        pkgs <- unlist(...)\n        if (length(pkgs)) {\n            loaded <- suppressMessages(suppressWarnings(sapply(pkgs, \n                library, character = T, logical = T)))\n            if (any(!loaded)) {\n                missing <- pkgs[!loaded]\n                cat(\"installing packages:n\")\n                cat(missing, sep = \"n\")\n                utils::install.packages(missing, repos = \"http://cran.rstudio.com/\", \n                  quiet = T)\n            }\n            suppressMessages(suppressWarnings(sapply(pkgs, library, \n                character = T)))\n        }\n    }\n    packageInfo <- utils::installed.packages()\n    if (missing(...)) \n        return(invisible())\n    pkgs <- match.call()[-1]\n    parsed <- if (is.null(names(pkgs))) {\n        as.character(pkgs)\n    }\n    else {\n        mapply(paste, names(pkgs), as.character(pkgs), MoreArgs = list(sep = \":\"))\n    }\n    parts <- lapply(strsplit(parsed, \"[:=(, ]+\"), function(d) {\n        d[d != \"\"]\n    })\n    grouped <- split(parts, sapply(parts, length))\n    needs_(grouped$`1`)\n    toCheck <- grouped$`2`\n    if (length(toCheck)) {\n        installedPackages <- packageInfo[, \"Package\"]\n        needsPackage <- sapply(toCheck, `[`, 1)\n        needsVersion <- sapply(toCheck, function(x) {\n            gsub(\"[^0-9.-]+\", \"\", x[2])\n        })\n        installed <- needsPackage %in% installedPackages\n        needs_(needsPackage[!installed])\n        compared <- mapply(utils::compareVersion, needsVersion[installed], \n            packageInfo[needsPackage[installed], \"Version\"])\n        if (any(compared == 1)) {\n            toUpdate <- needsPackage[installed][compared == 1]\n            cat(\"updating packages:n\")\n            cat(toUpdate, sep = \"n\")\n            utils::update.packages(oldPkgs = toUpdate, ask = F)\n        }\n        needs_(needsPackage[installed])\n    }\n    invisible()\n}\n\n  # attach to the search path\n  attach(.needs)\n})\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"r-script\",\n  \"version\": \"0.0.4\",\n  \"description\": \"A simple little module for passing data from NodeJS to R (and back again).\",\n  \"keywords\": [\n    \"R\",\n    \"rstats\",\n    \"statistics\"\n  ],\n  \"homepage\": \"http://github.com/joshkatz/r-script\",\n  \"bugs\": \"http://github.com/joshkatz/r-script/issues\",\n  \"license\": \"MIT\",\n  \"author\": {\n    \"name\": \"Josh Katz\"\n  },\n  \"main\": \"index.js\",\n  \"repository\": \"joshkatz/r-script\",\n  \"dependencies\": {\n    \"underscore\": \"^1.8.3\"\n  },\n  \"devDependencies\": {\n    \"queue-async\": \"^1.0.7\"\n  }\n}\n"
  }
]