<commit_before>// HTTP server for desktop editors

// Reads .tern-project files, wraps a Tern server in an HTTP wrapper
// so that editor plug-ins can talk to it.

// FIXME auto-shutdown after a given period of inactivity (unless
// given a command-line switch)

var tern = require("./tern");
var fs = require("fs"), path = require("path");

var projectFileName = ".tern-project", portFileName = ".tern-port";

function findProjectDir() {
  var dir = process.cwd();
  for (;;) {
    try {
      if (fs.statSync(path.resolve(dir, projectFileName)).isFile()) return dir;
    } catch(e) {}
    var end = dir.lastIndexOf("/");
    if (end <= 0) return null;
    dir = dir.slice(0, end);
  }
}

var defaultConfig = {
  environment: ["ecma5"],
  loadLibraries: []
};
var knownEnvironments = {
  ecma5: JSON.parse(fs.readFileSync(path.resolve(__dirname, "ecma5.json"), "utf8")),
  browser: JSON.parse(fs.readFileSync(path.resolve(__dirname, "browser.json"), "utf8"))
};

function readProjectFile(dir) {
  var data = JSON.parse(fs.readFileSync(path.resolve(dir, projectFileName), "utf8"));
  for (var option in defaultConfig) if (!data.hasOwnProperty(option))
    data[option] = defaultConfig[option];
  return data;
}

var dir = findProjectDir(), config;
if (dir) {
  config = readProjectFile(dir);
} else {
  dir = process.cwd();
  config = defaultConfig;
}
var server = startServer(dir, config);

function startServer(dir, config) {
  function getFile(name, c) {
    fs.readFile(path.resolve(dir, name), "utf8", c);
  }
  var env = [];
  config.environment.forEach(function(name) {
    env.push(knownEnvironments[name] || JSON.parse(fs.readFileSync(path.resolve(dir, name), "utf8")));
  });

  return new tern.Server({getFile: getFile, environment: env, debug: true});
}

var url = require("url");

var httpServer = require("http").createServer(function(req, resp) {
  var target = url.parse(req.url, true);
  if (target.path == "/ping") return respondSimple(resp, 200, "pong"); // FIXME update ttl
  if (target.path != "/") return respondSimple(resp, 404, "No service at " + target.path);

  if (req.method == "POST") {
    var body = "";
    req.on("data", function (data) { body += data; });
    req.on("end", function() { respond(resp, body); });
  } else if (req.method == "GET") {
    if (target.query.doc) respond(resp, target.query.doc);
    else respondSimple(resp, 400, "Missing query document");
  }
});
httpServer.listen(0, "localhost", function() {
  var portFile = path.resolve(dir, portFileName);
  fs.writeFileSync(portFile, String(httpServer.address().port), "utf8");
  process.on("exit", function() { try { fs.unlinkSync(portFile); } catch(e) {} });
  process.on("SIGINT", function() { process.exit(); });
  console.log("Listening on port " + httpServer.address().port);
});

function respondSimple(resp, status, text) {
  resp.writeHead(status, {"content-type": "text/plain"});
  resp.end(text);
}

function respond(resp, doc) {
  try { var doc = JSON.parse(doc); }
  catch(e) { return respondSimple(resp, 400, "JSON parse error: " + e.message); }

  server.request(doc, function(err, data) {
    if (err) return respondSimple(resp, 400, String(err));
    resp.writeHead(200, {"content-type": "application/json"});
    resp.end(JSON.stringify(data));
  });
}
<commit_msg>Add auto-shutdown behavior to the desktop server<commit_after>// HTTP server for desktop editors

// Reads .tern-project files, wraps a Tern server in an HTTP wrapper
// so that editor plug-ins can talk to it.

var tern = require("./tern");
var fs = require("fs"), path = require("path"), url = require("url");

var projectFileName = ".tern-project", portFileName = ".tern-port";
var maxIdleTime = 6e4 * 5; // Shut down after five minutes of inactivity

var persistent = process.argv.indexOf("--persistent") > -1;

function findProjectDir() {
  var dir = process.cwd();
  for (;;) {
    try {
      if (fs.statSync(path.resolve(dir, projectFileName)).isFile()) return dir;
    } catch(e) {}
    var end = dir.lastIndexOf("/");
    if (end <= 0) return null;
    dir = dir.slice(0, end);
  }
}

var defaultConfig = {
  environment: ["ecma5"],
  loadLibraries: []
};
var knownEnvironments = {
  ecma5: JSON.parse(fs.readFileSync(path.resolve(__dirname, "ecma5.json"), "utf8")),
  browser: JSON.parse(fs.readFileSync(path.resolve(__dirname, "browser.json"), "utf8"))
};

function readProjectFile(dir) {
  var data = JSON.parse(fs.readFileSync(path.resolve(dir, projectFileName), "utf8"));
  for (var option in defaultConfig) if (!data.hasOwnProperty(option))
    data[option] = defaultConfig[option];
  return data;
}

var dir = findProjectDir(), config;
if (dir) {
  config = readProjectFile(dir);
} else {
  dir = process.cwd();
  config = defaultConfig;
}
var server = startServer(dir, config);

function startServer(dir, config) {
  function getFile(name, c) {
    fs.readFile(path.resolve(dir, name), "utf8", c);
  }
  var env = [];
  config.environment.forEach(function(name) {
    env.push(knownEnvironments[name] || JSON.parse(fs.readFileSync(path.resolve(dir, name), "utf8")));
  });

  return new tern.Server({getFile: getFile, environment: env, debug: true});
}

function doShutdown() {
  if (persistent) return;
  console.log("Was idle for " + Math.floor(maxIdleTime / 6e4) + " minutes. Shutting down.");
  process.exit();
}

var shutdown = setTimeout(doShutdown, maxIdleTime);

var httpServer = require("http").createServer(function(req, resp) {
  clearTimeout(shutdown);
  shutdown = setTimeout(doShutdown, maxIdleTime);

  var target = url.parse(req.url, true);
  if (target.path == "/ping") return respondSimple(resp, 200, "pong");
  if (target.path != "/") return respondSimple(resp, 404, "No service at " + target.path);

  if (req.method == "POST") {
    var body = "";
    req.on("data", function (data) { body += data; });
    req.on("end", function() { respond(resp, body); });
  } else if (req.method == "GET") {
    if (target.query.doc) respond(resp, target.query.doc);
    else respondSimple(resp, 400, "Missing query document");
  }
});
httpServer.listen(0, "localhost", function() {
  var portFile = path.resolve(dir, portFileName);
  fs.writeFileSync(portFile, String(httpServer.address().port), "utf8");
  process.on("exit", function() { try { fs.unlinkSync(portFile); } catch(e) {} });
  process.on("SIGINT", function() { process.exit(); });
  console.log("Listening on port " + httpServer.address().port);
});

function respondSimple(resp, status, text) {
  resp.writeHead(status, {"content-type": "text/plain"});
  resp.end(text);
}

function respond(resp, doc) {
  try { var doc = JSON.parse(doc); }
  catch(e) { return respondSimple(resp, 400, "JSON parse error: " + e.message); }

  server.request(doc, function(err, data) {
    if (err) return respondSimple(resp, 400, String(err));
    resp.writeHead(200, {"content-type": "application/json"});
    resp.end(JSON.stringify(data));
  });
}

{'fraction_non_alphanumeric': 0.1762917933130699, 'fraction_numerical': 0.009552757273122015, 'mean_word_length': 4.364906832298137, 'pattern_counts': {'":': 4, '<': 5, '<?xml version=': 0, '>': 4, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6106025', 'n_tokens_mistral': 2316, 'n_tokens_neox': 2257, 'n_words': 558}