<commit_before>'use strict';

//TODO: on initial scan, check for the latest timestamp so that re-initialization of the server doesn't needlessly bump the cache.manifest version?

var path    = require('path');
var fs      = require('fs');

var watchr  = require('watchr');
var scanner = require('scandirectory');

function sortedSet() {
  var arr = [];

  function indexOf(val) {
    var min = 0, max = arr.length - 1;
    while (min <= max) {
      var cur = (min + max) / 2 | 0;
      if (arr[cur] < val) {
        min = cur + 1;
      } else if (arr[cur] > val) {
        max = cur - 1;
      } else {
        return cur;
      }
    }
    return ~max;
  }

  arr.insert = function(val) {
    var index = Math.abs(indexOf(val));
    if (arr.length <= index || arr[index] !== val) {
      arr.splice(index, 0, val);
    }
    return arr;
  };

  arr.remove = function(val) {
    var index = indexOf(val);
    if (index >= 0 && arr[index] === val) {
      arr.splice(index, 1);
    }
    return arr;
  };

  return arr;
}

//Paths = array of paths to watch for changes
//Each path is an object with a "file" property and any of the following optional properties: url, ignore, recurse, rewrite
//Last argument can optionally be an "options" object
function serveManifest(paths, opts, callback) {
  if (! Array.isArray(paths)) {
    throw new Error('First argument must be array of paths to watch');
  }
  if (paths.length === 0) {
    throw new Error('Must provide at least one path to watch');
  }
  if (! opts) {
    opts = {};
  }
  if (typeof callback !== 'function') {
    callback = function() {};
  }

  var watchers = [];
  var allFiles = sortedSet();
  var completedScans = 0;
  var manifestVersion = new Date().toISOString();

  function checkReady() {
    if (completedScans === paths.length && watchers.length === paths.length) {
      callback(serveResponse);
    }
  }

  function usePath(p) {
    if (typeof p === 'string') {
      p = { file: p, url: p };
    }
    if (! ('file' in p)) {
      throw new Error('Path object must contain a "file" property');
    }
    var filePath = p['file'];
    var urlPath = p['url'] || p['file'];
    if (! urlPath.startsWith('/')) {
      urlPath = '/' + urlPath;
    }

    filePath = path.format(path.parse(filePath));
    //TODO: maybe turn all paths into absolute ones?

    function toUrl(orig) {
      //console.log('converting ' + orig);
      var relPath = orig.substr(filePath.length);
      if (relPath.startsWith(path.sep)) {
        relPath = relPath.substr(path.sep.length);
      }
      //Convert to /'s for URL in case the filePath has \ separators
      return urlPath + '/' + path.posix.format(path.parse(relPath));
      //TODO: will this work for the case where the whole path is a single file instead of a directory?
    }

    function listener(evt, evtPath) {
      console.log('listen event for ' + evtPath);
      if (! evtPath.startsWith(filePath)) {
        throw new Error('!!!!!!!!!!!!');
      }
      fs.stat(evtPath, function(err, stat) {
        if (stat.isFile()) {
          var url = toUrl(evtPath);
          if (evt === 'delete') {
            allFiles.remove(url);
          } else if (evt === 'create') {
            allFiles.insert(url);
          }
          manifestVersion = new Date().toISOString(); //TODO: use the time from stat? thanks to "catchupDelay" I may not have the right time anymore
          console.log('cache updated');
        }
      });
    }

    fs.stat(filePath, function(err, stat) {
      //TODO: keep track of the max 'mtime' (and maybe drop to second-level accuracy)
      if (stat.isDirectory()) {
        scanner.scandir(filePath, {
          fileAction: function(filePath, filename, next, stat) {
            allFiles.insert(toUrl(filePath));
            next();
          },
          next: function() {
            completedScans++;
            checkReady();
          }
        });
      } else { //!isDirectory
        allFiles.insert(urlPath);
        completedScans++;
        checkReady();
      }

      console.log('gonna watch ' + filePath);
      watchr.watch({
        path: filePath,
        listener: listener,
        next: function(err, watcher) {
          watchers.push(watcher);
          checkReady();
        },
        catchupDelay: 500
      });
    });
  }

  //Initialize the list of cache.manifest files
  for(var i = 0; i < paths.length; i++) {
    usePath(paths[i]);
  }

  function serveResponse(req, res) {
    //TODO: take a template of some kind? (nah, just read network/fallback/etc from opts
    res.set('Cache-Control', 'no-cache');
    res.set('Content-Type', 'text/cache-manifest');
    res.write('CACHE MANIFEST\n');
    //res.write('/json/lists.json\n');  //TODO: this
    for (var i = 0, len = allFiles.length; i < len; i++) {
      res.write(allFiles[i] + '\n');
    }
    res.write('\nNETWORK:\n*\n\n');
    //TODO: maybe just NETWORK block the JSON blobs instead of *?
    res.write('#Updated: ' + manifestVersion);
    res.end();
  }

  serveResponse['stop'] = function() {
    console.log('Stopping manifest generator filesystem watches');
    for (var i = 0; i < watchers.length; i++) {
      watchers[i].close();
    }
    watchers = [];
  };

  return serveResponse;
}

function nocache(req, res, next) {
  res.setHeader('Cache-Control', 'no-cache');
  next();
}

module.exports = {
  nocache: nocache,
  generator: serveManifest
};
<commit_msg>Throw any filesystem errors when checking initial paths<commit_after>'use strict';

//TODO: on initial scan, check for the latest timestamp so that re-initialization of the server doesn't needlessly bump the cache.manifest version?

var path    = require('path');
var fs      = require('fs');

var watchr  = require('watchr');
var scanner = require('scandirectory');

function sortedSet() {
  var arr = [];

  function indexOf(val) {
    var min = 0, max = arr.length - 1;
    while (min <= max) {
      var cur = (min + max) / 2 | 0;
      if (arr[cur] < val) {
        min = cur + 1;
      } else if (arr[cur] > val) {
        max = cur - 1;
      } else {
        return cur;
      }
    }
    return ~max;
  }

  arr.insert = function(val) {
    var index = Math.abs(indexOf(val));
    if (arr.length <= index || arr[index] !== val) {
      arr.splice(index, 0, val);
    }
    return arr;
  };

  arr.remove = function(val) {
    var index = indexOf(val);
    if (index >= 0 && arr[index] === val) {
      arr.splice(index, 1);
    }
    return arr;
  };

  return arr;
}

//Paths = array of paths to watch for changes
//Each path is an object with a "file" property and any of the following optional properties: url, ignore, recurse, rewrite
//Last argument can optionally be an "options" object
function serveManifest(paths, opts, callback) {
  if (! Array.isArray(paths)) {
    throw new Error('First argument must be array of paths to watch');
  }
  if (paths.length === 0) {
    throw new Error('Must provide at least one path to watch');
  }
  if (! opts) {
    opts = {};
  }
  if (typeof callback !== 'function') {
    callback = function() {};
  }

  var watchers = [];
  var allFiles = sortedSet();
  var completedScans = 0;
  var manifestVersion = new Date().toISOString();

  function checkReady() {
    if (completedScans === paths.length && watchers.length === paths.length) {
      callback(serveResponse);
    }
  }

  function usePath(p) {
    if (typeof p === 'string') {
      p = { file: p, url: p };
    }
    if (! ('file' in p)) {
      throw new Error('Path object must contain a "file" property');
    }
    var filePath = p['file'];
    var urlPath = p['url'] || p['file'];
    if (! urlPath.startsWith('/')) {
      urlPath = '/' + urlPath;
    }

    filePath = path.format(path.parse(filePath));
    //TODO: maybe turn all paths into absolute ones?

    function toUrl(orig) {
      //console.log('converting ' + orig);
      var relPath = orig.substr(filePath.length);
      if (relPath.startsWith(path.sep)) {
        relPath = relPath.substr(path.sep.length);
      }
      //Convert to /'s for URL in case the filePath has \ separators
      return urlPath + '/' + path.posix.format(path.parse(relPath));
      //TODO: will this work for the case where the whole path is a single file instead of a directory?
    }

    function listener(evt, evtPath) {
      console.log('listen event for ' + evtPath);
      if (! evtPath.startsWith(filePath)) {
        throw new Error('!!!!!!!!!!!!');
      }
      fs.stat(evtPath, function(err, stat) {
        if (stat.isFile()) {
          var url = toUrl(evtPath);
          if (evt === 'delete') {
            allFiles.remove(url);
          } else if (evt === 'create') {
            allFiles.insert(url);
          }
          manifestVersion = new Date().toISOString(); //TODO: use the time from stat? thanks to "catchupDelay" I may not have the right time anymore
          console.log('cache updated');
        }
      });
    }

    fs.stat(filePath, function(err, stat) {
      if (err) { throw err; }
      //TODO: keep track of the max 'mtime' (and maybe drop to second-level accuracy)
      if (stat.isDirectory()) {
        scanner.scandir(filePath, {
          fileAction: function(filePath, filename, next, stat) {
            allFiles.insert(toUrl(filePath));
            next();
          },
          next: function() {
            completedScans++;
            checkReady();
          }
        });
      } else { //!isDirectory
        allFiles.insert(urlPath);
        completedScans++;
        checkReady();
      }

      console.log('gonna watch ' + filePath);
      watchr.watch({
        path: filePath,
        listener: listener,
        next: function(err, watcher) {
          watchers.push(watcher);
          checkReady();
        },
        catchupDelay: 500
      });
    });
  }

  //Initialize the list of cache.manifest files
  for(var i = 0; i < paths.length; i++) {
    usePath(paths[i]);
  }

  function serveResponse(req, res) {
    //TODO: take a template of some kind? (nah, just read network/fallback/etc from opts
    res.set('Cache-Control', 'no-cache');
    res.set('Content-Type', 'text/cache-manifest');
    res.write('CACHE MANIFEST\n');
    //res.write('/json/lists.json\n');  //TODO: this
    for (var i = 0, len = allFiles.length; i < len; i++) {
      res.write(allFiles[i] + '\n');
    }
    res.write('\nNETWORK:\n*\n\n');
    //TODO: maybe just NETWORK block the JSON blobs instead of *?
    res.write('#Updated: ' + manifestVersion);
    res.end();
  }

  serveResponse['stop'] = function() {
    console.log('Stopping manifest generator filesystem watches');
    for (var i = 0; i < watchers.length; i++) {
      watchers[i].close();
    }
    watchers = [];
  };

  return serveResponse;
}

function nocache(req, res, next) {
  res.setHeader('Cache-Control', 'no-cache');
  next();
}

module.exports = {
  nocache: nocache,
  generator: serveManifest
};
<|endoftext|>


<commit_before>var util = require('util')
// var FolderCapper = require('folder-capper')
var async = require('async')
var WebTorrent = require('webtorrent')
var events = require('events')
var parseTorrent = require('parse-torrent')
var fs = require('graceful-fs')
var createTorrent = require('create-torrent')
var hash = require('crypto-hashing')
var _ = require('lodash')

var FILEEXTENSION = '.ccm'

var MetadataHandler = function (properties) {
  // Torrent setup
  this.announce = properties.client.announce
  this.urlList = properties.client.urlList

  // File system propertie
  this.dataDir = properties.folders.data
  this.spvFolder = this.dataDir + properties.folders.spvData
  this.fullNodeFolder = this.dataDir + properties.folders.fullNodeData
  this.torrentDir = properties.folders.torrents

  // Folder Capper Settings
  // var options = {
  //   folderToClear: this.fullNodeFolder,
  //   ignores: properties.folders.ignores,
  //   folderToCap: this.dataDir,
  //   capSize: properties.folders.capSize,
  //   retryTime: properties.folders.retryTime
  // }

  // Start the torrent Client
  this.client = new WebTorrent(properties.client)

  // // Start the floder capper
  // this.capper = new FolderCapper(options)
  // var self = this
  // this.capper.on('full', function (amountToClear) {
  //   self.emit('full', amountToClear)
  //   self.capper.clear(function (err, results) {
  //     if (err) return self.emit('error', err)
  //     return self.emit('cleanup', results)
  //   })
  // })
  // this.capper.startWatchMode(properties.folders.autoWatchInterval)
}

util.inherits(MetadataHandler, events.EventEmitter)

var merge = function (torrent, cb) {
  var folderPath = torrent.path
  var fileArray = torrent.files
  var result = {}
  async.map(fileArray
    , function (file, callback) {
      var filePath = folderPath + '/' + file.path
      fs.readFile(filePath, function (err, data) {
        if (err) return callback(err)
        try {
          var jsonData = JSON.parse(data)
          return callback(null, jsonData)
        } catch (e) {
          return callback(e)
        }
      })
    }
    , function (err, results) {
      if (err) return cb(err)
      cb(null, _.merge(result, results))
    }
  )
}

var getHash = function (data) {
  if (!Buffer.isBuffer(data)) {
    data = new Buffer(data)
  }
  var sha2 = hash.sha256(data)
  return sha2
}

var createNewMetaDataFile = function (data, name, folder, cb) {
  data = JSON.stringify(data)
  var fileName = name + FILEEXTENSION
  var filePath = folder + '/' + fileName
  fs.writeFile(filePath, data, function (err) {
    if (err) return cb(err)
    cb(null, {filePath: filePath, fileName: fileName})
  })
}

var createTorrentFromMetaData = function (params, cb) {
  var opts = {
    name: params.fileName,              // name of the torrent (default = basename of `path`)
    comment: 'Colored Coins Metadata',  // free-form textual comments of the author
    createdBy: 'ColoredCoins-1.0.0',    // name and version of program used to create torrent
    announceList: params.announce,      // custom trackers (array of arrays of strings) (see [bep12](http://www.bittorrent.org/beps/bep_0012.html))
    creationDate: params.creationDate,  // creation time in UNIX epoch format (default = now)
    private: params.private,            // is this a private .torrent? (default = false)
    urlList: params.urlList,            // web seed urls (see [bep19](http://www.bittorrent.org/beps/bep_0019.html))
    pieceLength: params.pieceLength     // force a custom piece length (number of bytes)
  }

  createTorrent(params.filePath, opts, function (err, torrent) {
    if (err) return cb(err)
    var torrentObject = parseTorrent(torrent)
    var fileName = torrentObject.infoHash + '.torrent'
    var torrentPath = params.torrentDir + '/' + fileName
    var magnetURI = parseTorrent.toMagnetURI(torrentObject)
    fs.writeFile(torrentPath, torrent, function (err) {
      if (err) return cb(err)
      return cb(err, {torrent: torrentObject, fileName: fileName, filePath: torrentPath, magnetURI: magnetURI})
    })
  })
}

var getFilePathFromTorrent = function (torrentFileName, cb) {
  fs.readFile(torrentFileName, function (err, data) {
    if (err) return cb(err)
    return cb(null, parseTorrent(data).name)
  })
}

MetadataHandler.prototype.getMetadata = function (input, sha2, spv, cb) {
  var folderToSave = spv ? this.spvFolder : this.fullNodeFolder
  var opts = {
    announce: this.announce, // List of additional trackers to use (added to list in .torrent or magnet uri)
    path: folderToSave,      // Folder where files will be downloaded (default=`/tmp/webtorrent/`)
    verify: true             // Verify previously stored data before starting (default=false)
  }
  var self = this
  this.client.add(input, opts, function (torrent) {
    torrent.on('done', function () {
      merge(torrent, function (err, metadata) {
        if (err) {
          self.emit('error', err)
          if (cb) cb(err)
        }
        if (sha2 && getHash(metadata) !== sha2) {
          err = new Error(input + ' has failed hash test')
          self.emit('error', err)
          if (cb) cb(err)
          return
        }
        self.emit('downloads/' + input, metadata)
        self.emit('downloads', metadata)
        if (cb) cb(null, metadata)
      })
    })
  })
}

MetadataHandler.prototype.addMetadata = function (metadata, cb) {
  var sha2
  if (Buffer.isBuffer(metadata)) {
    sha2 = getHash(metadata)
  } else {
    try {
      sha2 = getHash(JSON.stringify(metadata))
    } catch (e) {
      return cb(e)
    }
  }
  var fileName = sha2.toString('hex')
  var self = this
  async.waterfall([
    function (callback) {
      createNewMetaDataFile(metadata, fileName, self.spvFolder, callback)
    },
    function (result, callback) {
      result.torrentDir = self.torrentDir
      result.announce = self.announce
      result.urlList = self.urlList
      createTorrentFromMetaData(result, callback)
    }
  ], function (err, result) {
    if (err) return cb(err)
    cb(null, {torrentHash: new Buffer(result.torrent.infoHash, 'hex'), sha2: sha2})
  })
}

MetadataHandler.prototype.shareMetadata = function (infoHash, spv, cb) {
  if (typeof spv === 'function') {
    cb = spv
    spv = true
  }
  if (typeof spv === 'undefined') spv = true
  var torrentFilePath = this.torrentDir + '/' + infoHash + '.torrent'
  var self = this
  getFilePathFromTorrent(torrentFilePath, function (err, dataFileName) {
    if (err) {
      self.emit('error', err)
      if (cb) cb(err)
      return
    }
    var folderToLook = spv ? self.spvFolder : self.fullNodeFolder
    var dataFilePath = folderToLook + '/' + dataFileName
    var opts = {
      name: dataFileName,                 // name of the torrent (default = basename of `path`)
      comment: 'Colored Coins Metadata',  // free-form textual comments of the author
      createdBy: 'ColoredCoins-1.0.0',    // name and version of program used to create torrent
      announceList: self.announce,        // custom trackers (array of arrays of strings) (see [bep12](http://www.bittorrent.org/beps/bep_0012.html))
      urlList: self.urlList               // web seed urls (see [bep19](http://www.bittorrent.org/beps/bep_0019.html))
    }
    self.client.on('error', function (err) {console.error(err)})
    self.client.seed(dataFilePath, opts, function (torrent) {
      self.emit('uploads/' + infoHash, torrent)
      self.emit('uploads', torrent)
      if (cb) cb(null, torrent)
    })
  })
}

module.exports = MetadataHandler
<commit_msg>serial seeding<commit_after>var util = require('util')
// var FolderCapper = require('folder-capper')
var async = require('async')
var WebTorrent = require('webtorrent')
var events = require('events')
var parseTorrent = require('parse-torrent')
var fs = require('graceful-fs')
var createTorrent = require('create-torrent')
var hash = require('crypto-hashing')
var _ = require('lodash')

var FILEEXTENSION = '.ccm'

var MetadataHandler = function (properties) {
  // Torrent setup
  this.announce = properties.client.announce
  this.urlList = properties.client.urlList

  // File system propertie
  this.dataDir = properties.folders.data
  this.spvFolder = this.dataDir + properties.folders.spvData
  this.fullNodeFolder = this.dataDir + properties.folders.fullNodeData
  this.torrentDir = properties.folders.torrents

  // Folder Capper Settings
  // var options = {
  //   folderToClear: this.fullNodeFolder,
  //   ignores: properties.folders.ignores,
  //   folderToCap: this.dataDir,
  //   capSize: properties.folders.capSize,
  //   retryTime: properties.folders.retryTime
  // }

  // Start the torrent Client
  this.client = new WebTorrent(properties.client)

  // // Start the floder capper
  // this.capper = new FolderCapper(options)
  // var self = this
  // this.capper.on('full', function (amountToClear) {
  //   self.emit('full', amountToClear)
  //   self.capper.clear(function (err, results) {
  //     if (err) return self.emit('error', err)
  //     return self.emit('cleanup', results)
  //   })
  // })
  // this.capper.startWatchMode(properties.folders.autoWatchInterval)
}

util.inherits(MetadataHandler, events.EventEmitter)

var merge = function (torrent, cb) {
  var folderPath = torrent.path
  var fileArray = torrent.files
  var result = {}
  async.map(fileArray
    , function (file, callback) {
      var filePath = folderPath + '/' + file.path
      fs.readFile(filePath, function (err, data) {
        if (err) return callback(err)
        try {
          var jsonData = JSON.parse(data)
          return callback(null, jsonData)
        } catch (e) {
          return callback(e)
        }
      })
    }
    , function (err, results) {
      if (err) return cb(err)
      cb(null, _.merge(result, results))
    }
  )
}

var getHash = function (data) {
  if (!Buffer.isBuffer(data)) {
    data = new Buffer(data)
  }
  var sha2 = hash.sha256(data)
  return sha2
}

var createNewMetaDataFile = function (data, name, folder, cb) {
  data = JSON.stringify(data)
  var fileName = name + FILEEXTENSION
  var filePath = folder + '/' + fileName
  fs.writeFile(filePath, data, function (err) {
    if (err) return cb(err)
    cb(null, {filePath: filePath, fileName: fileName})
  })
}

var createTorrentFromMetaData = function (params, cb) {
  var opts = {
    name: params.fileName,              // name of the torrent (default = basename of `path`)
    comment: 'Colored Coins Metadata',  // free-form textual comments of the author
    createdBy: 'ColoredCoins-1.0.0',    // name and version of program used to create torrent
    announceList: params.announce,      // custom trackers (array of arrays of strings) (see [bep12](http://www.bittorrent.org/beps/bep_0012.html))
    creationDate: params.creationDate,  // creation time in UNIX epoch format (default = now)
    private: params.private,            // is this a private .torrent? (default = false)
    urlList: params.urlList,            // web seed urls (see [bep19](http://www.bittorrent.org/beps/bep_0019.html))
    pieceLength: params.pieceLength     // force a custom piece length (number of bytes)
  }

  createTorrent(params.filePath, opts, function (err, torrent) {
    if (err) return cb(err)
    var torrentObject = parseTorrent(torrent)
    var fileName = torrentObject.infoHash + '.torrent'
    var torrentPath = params.torrentDir + '/' + fileName
    var magnetURI = parseTorrent.toMagnetURI(torrentObject)
    fs.writeFile(torrentPath, torrent, function (err) {
      if (err) return cb(err)
      return cb(err, {torrent: torrentObject, fileName: fileName, filePath: torrentPath, magnetURI: magnetURI})
    })
  })
}

var getFilePathFromTorrent = function (torrentFileName, cb) {
  fs.readFile(torrentFileName, function (err, data) {
    if (err) return cb(err)
    return cb(null, parseTorrent(data).name)
  })
}

MetadataHandler.prototype.getMetadata = function (input, sha2, spv, cb) {
  var folderToSave = spv ? this.spvFolder : this.fullNodeFolder
  var opts = {
    announce: this.announce, // List of additional trackers to use (added to list in .torrent or magnet uri)
    path: folderToSave,      // Folder where files will be downloaded (default=`/tmp/webtorrent/`)
    verify: true             // Verify previously stored data before starting (default=false)
  }
  var self = this
  this.client.add(input, opts, function (torrent) {
    torrent.on('done', function () {
      merge(torrent, function (err, metadata) {
        if (err) {
          self.emit('error', err)
          if (cb) cb(err)
        }
        if (sha2 && getHash(metadata) !== sha2) {
          err = new Error(input + ' has failed hash test')
          self.emit('error', err)
          if (cb) cb(err)
          return
        }
        self.emit('downloads/' + input, metadata)
        self.emit('downloads', metadata)
        if (cb) cb(null, metadata)
      })
    })
  })
}

MetadataHandler.prototype.addMetadata = function (metadata, cb) {
  var sha2
  if (Buffer.isBuffer(metadata)) {
    sha2 = getHash(metadata)
  } else {
    try {
      sha2 = getHash(JSON.stringify(metadata))
    } catch (e) {
      return cb(e)
    }
  }
  var fileName = sha2.toString('hex')
  var self = this
  async.waterfall([
    function (callback) {
      createNewMetaDataFile(metadata, fileName, self.spvFolder, callback)
    },
    function (result, callback) {
      result.torrentDir = self.torrentDir
      result.announce = self.announce
      result.urlList = self.urlList
      createTorrentFromMetaData(result, callback)
    }
  ], function (err, result) {
    if (err) return cb(err)
    cb(null, {torrentHash: new Buffer(result.torrent.infoHash, 'hex'), sha2: sha2})
  })
}

MetadataHandler.prototype.shareMetadata = function (infoHash, spv, cb) {
  var self = this
  var torrentFilePath = this.torrentDir + '/' + infoHash + '.torrent'
  var torrent = fs.readFileSync(torrentFilePath)
  getFilePathFromTorrent(torrentFilePath, function (err, dataFileName) {
    if (err) {
      self.emit('error', err)
      if (cb) cb(err)
      return
    }
    var folderToLook = spv ? self.spvFolder : self.fullNodeFolder
    var dataFilePath = folderToLook + '/' + dataFileName
    var opts = {
      name: dataFileName,                 // name of the torrent (default = basename of `path`)
      comment: 'Colored Coins Metadata',  // free-form textual comments of the author
      createdBy: 'ColoredCoins-1.0.0',    // name and version of program used to create torrent
      announceList: self.announce,        // custom trackers (array of arrays of strings) (see [bep12](http://www.bittorrent.org/beps/bep_0012.html))
      urlList: self.urlList               // web seed urls (see [bep19](http://www.bittorrent.org/beps/bep_0019.html))
    }
    self.client.on('error', function (err) {console.error(err)})
    self.client.seed(dataFilePath, opts, function (torrent) {
      console.log('onseed() - torrent.infoHash = ', torrent.infoHash)
      self.emit('uploads/' + infoHash, torrent)
      self.emit('uploads', torrent)
      if (cb) cb(null, torrent)
    })
  })
}

MetadataHandler.prototype.shareMetadataBulks = function (infoHashes, spv, cb) {
  if (typeof spv === 'function') {
    cb = spv
    spv = true
  }
  if (typeof spv === 'undefined') spv = true

  var self = this
  var i = 1
  async.eachSeries(infoHashes, function (infoHash, callback) {
    console.log('Start seeding : ' + infoHash + ', # ' + i++)
    self.shareMetadata(infoHash, spv, callback)
  }, cb)
}

module.exports = MetadataHandler
<|endoftext|>


<commit_before>const styles = require('./styles');
const React = require('react-native');
const { Dimensions, Animated, } = React;
const deviceScreen = Dimensions.get('window');

const {
  PanResponder,
  View,
  TouchableWithoutFeedback,
  Component,
} = React;

/**
 * Default open menu offset. Describes a size of the amount you can
 * move content view from the left and release without opening it
 * @type {Number}
 */
const openMenuOffset = deviceScreen.width * 2 / 3;

/**
 * Content view offset in the `hidden` state
 * @type {Number}
 */
const hiddenMenuOffset = 0;

/**
 * Size of the amount you can move content view in the opened menu state and
 * release without menu closing
 * @type {Number}
 */
const barrierForward = deviceScreen.width / 4;

/**
 * Check if the current gesture offset bigger than allowed one
 * before opening menu
 * @param  {Number} dx Gesture offset from the left side of the window
 * @return {Boolean}
 */
function shouldOpenMenu(dx: Number) {
  return dx > barrierForward;
}

class SideMenu extends Component {
  constructor(props) {
    super(props);
    /**
     * Current state of the menu, whether it is open or not
     * @type {Boolean}
     */
    this.isOpen = false;

    /**
     * Default left offset for content view
     * @todo Check if it's possible to avoid using `prevLeft`
     * @type {Number}
     */
    this.prevLeft = 0;

    this.state = {
      left: new Animated.Value(0),
    };
  }

  /**
   * Set the initial responders
   * @return {Void}
   */
  componentWillMount() {
    this.responder = PanResponder.create({
      onStartShouldSetResponderCapture: this.props.onStartShouldSetResponderCapture.bind(this),
      onMoveShouldSetPanResponder: this.handleMoveShouldSetPanResponder.bind(this),
      onPanResponderMove: this.handlePanResponderMove.bind(this),
      onPanResponderRelease: this.handlePanResponderEnd.bind(this),
    });
  }

  /**
   * Determines if gestures are enabled, based off of disableGestures prop
   * @return {Boolean}
   */
  gesturesAreEnabled() {
    let { disableGestures, } = this.props;

    if (typeof disableGestures === 'function') {
      return !disableGestures();
    }

    return !disableGestures;
  }

  /**
   * Permission to use responder
   * @return {Boolean}
   */
  handleMoveShouldSetPanResponder(e: Object, gestureState: Object) {
    if (this.gesturesAreEnabled()) {
      const x = Math.round(Math.abs(gestureState.dx));
      const y = Math.round(Math.abs(gestureState.dy));

      return x > this.props.toleranceX && y < this.props.toleranceY;
    }

    return false;
  }

  /**
   * Handler on responder move
   * @param  {Synthetic Event} e
   * @param  {Object} gestureState
   * @return {Void}
   */
  handlePanResponderMove(e: Object, gestureState: Object) {
    if (this.menuPositionMultiplier() *
      this.state.left.__getValue() + gestureState.dx >= 0) {
      this.state.left.setValue(this.prevLeft + gestureState.dx);
    }
  }

  /**
   * Handler on responder move ending
   * @param  {Synthetic Event} e
   * @param  {Object} gestureState
   * @return {Void}
   */
  handlePanResponderEnd(e: Object, gestureState: Object) {
    const currentLeft = this.state.left.__getValue();

    const shouldOpen = this.menuPositionMultiplier() *
      (currentLeft + gestureState.dx);

    if (shouldOpenMenu(shouldOpen)) {
      this.openMenu();
    } else {
      this.closeMenu();
    }
  }

  /**
   * Returns 1 or -1 depending on the menuPosition
   * @return {Number}
   */
  menuPositionMultiplier() {
    return this.props.menuPosition === 'right' ? -1 : 1;
  }

  /**
   * Open menu
   * @return {Void}
   */
  openMenu() {
    const openOffset = this.menuPositionMultiplier() *
      (this.props.openMenuOffset || openMenuOffset);

    this.props
      .animationFunction(this.state.left, openOffset)
      .start();

    this.prevLeft = openOffset;

    if (!this.isOpen) {
      this.props.onChange(this.isOpen);

      this.isOpen = true;

      // Force update to make the overlay appear (if touchToClose is set)
      if (this.props.touchToClose) {
        this.forceUpdate();
      }
    }
  }

  /**
   * Close menu
   * @return {Void}
   */
  closeMenu() {
    const closeOffset = this.menuPositionMultiplier() *
      (this.props.hiddenMenuOffset || hiddenMenuOffset);

    this.props
      .animationFunction(this.state.left, closeOffset)
      .start();

    this.prevLeft = closeOffset;

    if (this.isOpen) {
      this.props.onChange(this.isOpen);

      this.isOpen = false;
      // Force update to make the overlay disappear (if touchToClose is set)
      if (this.props.touchToClose) {
        this.forceUpdate();
      }
    }
  }

  /**
   * Toggle menu
   * @return {Void}
   */
  toggleMenu() {
    if (this.isOpen) {
      this.closeMenu();
    } else {
      this.openMenu();
    }
  }

  /**
   * Get content view. This view will be rendered over menu
   * @return {React.Component}
   */
  getContentView() {
    const menuActions = this.getMenuActions();

    let overlay = null;

    if (this.isOpen && this.props.touchToClose) {
      overlay = (
        <TouchableWithoutFeedback onPress={this.closeMenu.bind(this)}>
          <View style={styles.overlay} />
        </TouchableWithoutFeedback>
      );
    }

    const children = React.Children.map(this.props.children,
      (child) => React.cloneElement(child, { menuActions, }));

    return (
      <Animated.View
        style={[styles.frontView, this.props.animationStyle(this.state.left), ]}
        ref={(sideMenu) => this.sideMenu = sideMenu}
        {...this.responder.panHandlers}>
        {children}
        {overlay}
      </Animated.View>
    );
  }

  /**
   * Get menu actions to expose it to
   * menu and children components
   * @return {Object} Public API methods
   */
  getMenuActions() {
    return {
      close: this.closeMenu.bind(this),
      toggle: this.toggleMenu.bind(this),
      open: this.openMenu.bind(this),
    };
  }

  /**
   * Get menu view. This view will be rendered under
   * content view. Also, this function will decorate
   * passed `menu` component with side menu API
   * @return {React.Component}
   */
  getMenuView() {
    const menuActions = this.getMenuActions();

    return (
      <View style={styles.menu}>
        {React.addons.cloneWithProps(this.props.menu, { menuActions, })}
      </View>
    );
  }

  /**
   * Compose and render menu and content view
   * @return {React.Component}
   */
  render() {
    return (
      <View style={styles.container}>
        {this.getMenuView()}
        {this.getContentView()}
      </View>
    );
  }
}

SideMenu.propTypes = {
  toleranceX: React.PropTypes.number,
  toleranceY: React.PropTypes.number,
  onChange: React.PropTypes.func,
  touchToClose: React.PropTypes.bool,
  disableGestures: React.PropTypes.oneOfType([React.PropTypes.func, React.PropTypes.bool, ]),
  animationFunction: React.PropTypes.func,
  onStartShouldSetResponderCapture: React.PropTypes.func,
};

SideMenu.defaultProps = {
  toleranceY: 10,
  toleranceX: 10,
  touchToClose: false,
  onStartShouldSetResponderCapture: () => true,
  onChange: () => {},
  animationStyle: (value) => {
    return {
      transform: [{
        translateX: value,
      }, ],
    };
  },
  animationFunction: (prop, value) => {
    return Animated.spring(
      prop,
      {
        toValue: value,
        friction: 8,
      }
    );
  },
};

module.exports = SideMenu;
<commit_msg>Use context instead of props to pass menuActions thru<commit_after>const styles = require('./styles');
const React = require('react-native');
const { Dimensions, Animated, } = React;
const deviceScreen = Dimensions.get('window');

const {
  PanResponder,
  View,
  TouchableWithoutFeedback,
  Component,
} = React;

/**
 * Default open menu offset. Describes a size of the amount you can
 * move content view from the left and release without opening it
 * @type {Number}
 */
const openMenuOffset = deviceScreen.width * 2 / 3;

/**
 * Content view offset in the `hidden` state
 * @type {Number}
 */
const hiddenMenuOffset = 0;

/**
 * Size of the amount you can move content view in the opened menu state and
 * release without menu closing
 * @type {Number}
 */
const barrierForward = deviceScreen.width / 4;

/**
 * Check if the current gesture offset bigger than allowed one
 * before opening menu
 * @param  {Number} dx Gesture offset from the left side of the window
 * @return {Boolean}
 */
function shouldOpenMenu(dx: Number) {
  return dx > barrierForward;
}

class SideMenu extends Component {
  constructor(props) {
    super(props);
    /**
     * Current state of the menu, whether it is open or not
     * @type {Boolean}
     */
    this.isOpen = false;

    /**
     * Default left offset for content view
     * @todo Check if it's possible to avoid using `prevLeft`
     * @type {Number}
     */
    this.prevLeft = 0;

    this.state = {
      left: new Animated.Value(0),
    };
  }

  getChildContext() {
    return {
      menuActions: this.getMenuActions(),
    };
  }

  /**
   * Set the initial responders
   * @return {Void}
   */
  componentWillMount() {
    this.responder = PanResponder.create({
      onStartShouldSetResponderCapture: this.props.onStartShouldSetResponderCapture.bind(this),
      onMoveShouldSetPanResponder: this.handleMoveShouldSetPanResponder.bind(this),
      onPanResponderMove: this.handlePanResponderMove.bind(this),
      onPanResponderRelease: this.handlePanResponderEnd.bind(this),
    });
  }

  /**
   * Determines if gestures are enabled, based off of disableGestures prop
   * @return {Boolean}
   */
  gesturesAreEnabled() {
    let { disableGestures, } = this.props;

    if (typeof disableGestures === 'function') {
      return !disableGestures();
    }

    return !disableGestures;
  }

  /**
   * Permission to use responder
   * @return {Boolean}
   */
  handleMoveShouldSetPanResponder(e: Object, gestureState: Object) {
    if (this.gesturesAreEnabled()) {
      const x = Math.round(Math.abs(gestureState.dx));
      const y = Math.round(Math.abs(gestureState.dy));

      return x > this.props.toleranceX && y < this.props.toleranceY;
    }

    return false;
  }

  /**
   * Handler on responder move
   * @param  {Synthetic Event} e
   * @param  {Object} gestureState
   * @return {Void}
   */
  handlePanResponderMove(e: Object, gestureState: Object) {
    if (this.menuPositionMultiplier() *
      this.state.left.__getValue() + gestureState.dx >= 0) {
      this.state.left.setValue(this.prevLeft + gestureState.dx);
    }
  }

  /**
   * Handler on responder move ending
   * @param  {Synthetic Event} e
   * @param  {Object} gestureState
   * @return {Void}
   */
  handlePanResponderEnd(e: Object, gestureState: Object) {
    const currentLeft = this.state.left.__getValue();

    const shouldOpen = this.menuPositionMultiplier() *
      (currentLeft + gestureState.dx);

    if (shouldOpenMenu(shouldOpen)) {
      this.openMenu();
    } else {
      this.closeMenu();
    }
  }

  /**
   * Returns 1 or -1 depending on the menuPosition
   * @return {Number}
   */
  menuPositionMultiplier() {
    return this.props.menuPosition === 'right' ? -1 : 1;
  }

  /**
   * Open menu
   * @return {Void}
   */
  openMenu() {
    const openOffset = this.menuPositionMultiplier() *
      (this.props.openMenuOffset || openMenuOffset);

    this.props
      .animationFunction(this.state.left, openOffset)
      .start();

    this.prevLeft = openOffset;

    if (!this.isOpen) {
      this.props.onChange(this.isOpen);

      this.isOpen = true;

      // Force update to make the overlay appear (if touchToClose is set)
      if (this.props.touchToClose) {
        this.forceUpdate();
      }
    }
  }

  /**
   * Close menu
   * @return {Void}
   */
  closeMenu() {
    const closeOffset = this.menuPositionMultiplier() *
      (this.props.hiddenMenuOffset || hiddenMenuOffset);

    this.props
      .animationFunction(this.state.left, closeOffset)
      .start();

    this.prevLeft = closeOffset;

    if (this.isOpen) {
      this.props.onChange(this.isOpen);

      this.isOpen = false;
      // Force update to make the overlay disappear (if touchToClose is set)
      if (this.props.touchToClose) {
        this.forceUpdate();
      }
    }
  }

  /**
   * Toggle menu
   * @return {Void}
   */
  toggleMenu() {
    if (this.isOpen) {
      this.closeMenu();
    } else {
      this.openMenu();
    }
  }

  /**
   * Get content view. This view will be rendered over menu
   * @return {React.Component}
   */
  getContentView() {
    let overlay = null;

    if (this.isOpen && this.props.touchToClose) {
      overlay = (
        <TouchableWithoutFeedback onPress={this.closeMenu.bind(this)}>
          <View style={styles.overlay} />
        </TouchableWithoutFeedback>
      );
    }

    return (
      <Animated.View
        style={[styles.frontView, this.props.animationStyle(this.state.left), ]}
        ref={(sideMenu) => this.sideMenu = sideMenu}
        {...this.responder.panHandlers}>
        {this.props.children}
        {overlay}
      </Animated.View>
    );
  }

  /**
   * Get menu actions to expose it to
   * menu and children components
   * @return {Object} Public API methods
   */
  getMenuActions() {
    return {
      close: this.closeMenu.bind(this),
      toggle: this.toggleMenu.bind(this),
      open: this.openMenu.bind(this),
    };
  }

  /**
   * Compose and render menu and content view
   * @return {React.Component}
   */
  render() {
    return (
      <View style={styles.container}>
        <View style={styles.menu}>
          {this.props.menu}
        </View>
        {this.getContentView()}
      </View>
    );
  }
}

SideMenu.childContextTypes = {
  menuActions: React.PropTypes.object.isRequired,
};

SideMenu.propTypes = {
  toleranceX: React.PropTypes.number,
  toleranceY: React.PropTypes.number,
  onChange: React.PropTypes.func,
  touchToClose: React.PropTypes.bool,
  disableGestures: React.PropTypes.oneOfType([React.PropTypes.func, React.PropTypes.bool, ]),
  animationFunction: React.PropTypes.func,
  onStartShouldSetResponderCapture: React.PropTypes.func,
};

SideMenu.defaultProps = {
  toleranceY: 10,
  toleranceX: 10,
  touchToClose: false,
  onStartShouldSetResponderCapture: () => true,
  onChange: () => {},
  animationStyle: (value) => {
    return {
      transform: [{
        translateX: value,
      }, ],
    };
  },
  animationFunction: (prop, value) => {
    return Animated.spring(
      prop,
      {
        toValue: value,
        friction: 8,
      }
    );
  },
};

module.exports = SideMenu;
<|endoftext|>


<commit_before>"use strict";

var katex = require('katex');

function setup(md, options) {
  var defaultRender = md.renderer.rules.fence;

  md.renderer.rules.fence = function (tokens, idx, options, env, self) {
    var token = tokens[idx];

    if(token.info === "math") {
      return RenderMath.render(token.content, true);
    }

    // pass token to default renderer.
    return defaultRender(tokens, idx, options, env, self);
  };
}

module.exports = setup;

String.prototype.trim = function() {
  return this.replace(/^\s+|\s+$/g, "");
};

(function(global) {
  var RenderMath = (function(){

    return {
      render: function(str, disp) {
        // split content
        var arr = str.trim().split("\n");
        var result = "";

        // render each line
        for(var i = 0; i < arr.length; i++) {
          result += "<p>" + this.renderElement(this.preprocessMath(arr[i]), disp) + "<p>";
        }

        return result;
      },

      renderElement: function(str, disp) {
        return katex.renderToString(str, { displayMode: disp });
      },

      preprocessMath: function(str) {
        var newstr;

        // correct index-texts
        newstr = str.replace(/_(.*?)(\s|$|=|\(|\)|\*|\/|\^)/g, '_($1)$2');

        // parse to TeX
        newstr = AMTparseAMtoTeX(newstr);

        return newstr;
      }
    };
  }());

  // expose module to global object
  global.RenderMath = RenderMath;
})( this );
<commit_msg>Clean up code<commit_after>"use strict";

String.prototype.trim = function() {
  return this.replace(/^\s+|\s+$/g, "");
};

var katex = require('katex');

function setup(md, options) {
  var defaultRender = md.renderer.rules.fence;

  md.renderer.rules.fence = function (tokens, idx, options, env, self) {
    var token = tokens[idx];

    if(token.info === "math") {
      return render(token.content, true);
    }

    // pass token to default renderer.
    return defaultRender(tokens, idx, options, env, self);
  };
}

function render(str, disp) {
  // split content
  var arr = str.trim().split("\n");
  var result = "";

  // render each line
  for(var i = 0; i < arr.length; i++) {
    result += "<p>" + renderElement(preprocessMath(arr[i]), disp) + "<p>";
  }

  return result;
}

function renderElement(str, disp) {
  return katex.renderToString(str, { displayMode: disp });
}

function preprocessMath(str) {
  var newstr;

  // correct index-texts
  newstr = str.replace(/_(.*?)(\s|$|=|\(|\)|\*|\/|\^)/g, '_($1)$2');

  // parse to TeX
  newstr = AMTparseAMtoTeX(newstr);

  return newstr;
}
//
//   // expose module to global object
//   global.RenderMath = RenderMath;
// })( this );

module.exports = setup;
<|endoftext|>


<commit_before>/* jslint node: true, esnext: true */

"use strict";

const fs = require("fs"),
  path = require("path");

const RootUTI = {
  toString() {
      return this.name;
    },

    /**
     * Deliver JSON representation of the UTI.
     * Sample result
     * {
     *   "name": "myUTI",
     *   "conformsTo": [ "uti1", "uti2"]
     * }
     * @return json representation of the UTI
     */
    toJSON() {
      return {
        name: this.name,
        conformsTo: this.conformsTo
      };
    }
};

/**
 * Initialized the uti api.
 * @param options {object} options.definitionFileName is given then additional UTIs will be loaded from the given file name
 * @return {Promise} a promise that is fullfilled when the initialization is done
 */
exports.initialize = function (options) {
  if (!options) {
    options = {};
  }

  const registry = {};
  const utiByFileNameExtension = {};
  const utiByMimeType = {};

  function assignExtensions(extensions, name) {
    extensions.forEach(ext => {
      if (utiByFileNameExtension[ext]) {
        utiByFileNameExtension[ext].push(name);
      } else {
        //console.log(`${ext} : ${name}`);

        utiByFileNameExtension[ext] = [name];
      }
    });
  }

  function assignMimeTypes(mimTypes, name) {
    mimTypes.forEach(type => {
      if (utiByMimeType[type]) {
        utiByMimeType[type].push(name);
      } else {
        utiByMimeType[type] = [name];
      }
    });
  }

  function _conformsTo(a, b) {
    for (let i in a.conformsTo) {
      const u = a.conformsTo[i];

      if (u === b) {
        return true;
      }

      if (_conformsTo(u, b)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Check whenever two UTI are conformant.
   * If a conforms to b and b conforms to c then a also conforms to c.
   * @param a {string} first UTI
   * @param b {string} second UTI
   * @return {boolean} true if UTI a conforms to UTI b.
   */
  exports.conformsTo = function (a, b) {
    a = registry[a];
    if (!a) {
      return false;
    }

    return _conformsTo(a, registry[b]);
  };

  /*
    TODO find a better name
    exports.allConformingTo = function *(a) {
      a = registry[a];
      if (!a) {
        return;
      }

      for (let i in a.conformsTo) {
        yield i;

        const u = a.conformsTo[i];
      }
    };
  */

  /**
   * Lookup a given UTI.
   * @param name {string} UTI
   * @return {string} UTI for the given name or undefined if UTI is not present.
   */
  exports.getUTI = function (name) {
    return registry[name];
  };

  /**
   * Lookup a UTI for a file name.
   * First the file name extension is extracted.
   * Then a lookup in the reistered UTIs for file name extions is executed.
   * @param fileName {string} file to detect UTI for
   * @return {string} UTI for the given fileName or undefined if no UTI is registerd for the file names extension
   */
  exports.getUTIsForFileName = function (fileName) {
    const m = fileName.match(/(\.[a-zA-Z_0-9]+)$/);

    if (m) {
      return utiByFileNameExtension[m[1]];
    }
    return undefined;
  };

  /**
   * Lookup a UTIs for a mime type.
   * @param mimeType {string} mime type to get UTIs for
   * @return {string} UTI for the given mime type or undefined if no UTI is registerd for the mime type
   */
  exports.getUTIsForMimeType = function (mimeType) {
    return utiByMimeType[mimeType];
  };

  /**
   * Load additional UTIs form a file.
   * @param fileName {string} file containing UTI definitions
   * @return {Promise} a promise that resolves after the UTIs have been registered.
   */
  exports.loadDefinitionsFromFile = function (fileName) {
    // not using promisify-node any longer since it has some side-effects into the plain fs module
    return new Promise(function (resolve, reject) {
      fs.readFile(fileName, {
        encoding: "utf-8"
      }, function (error, data) {
        if (error) {
          reject(error);
          return;
        }
        try {
          resolve(loadDefinitions(data));
        } catch (error) {
          reject(error);
        }
      });
    });
  };

  function loadDefinitions(data) {
    for (let u of JSON.parse(data)) {
      const properties = {
        name: {
          value: u.name
        }
      };

      if (u.fileNameExtension) {
        assignExtensions(Array.isArray(u.fileNameExtension) ? u.fileNameExtension : [u.fileNameExtension],
          u.name);
      }

      if (u.mimeType) {
        assignMimeTypes(Array.isArray(u.mimeType) ? u.mimeType : [u.mimeType],
          u.name);
      }

      const conformsTo = {};

      if (u.conformsTo) {
        const ct = Array.isArray(u.conformsTo) ? u.conformsTo : [u.conformsTo];

        ct.forEach(name => {
          const aUTI = registry[name];
          if (aUTI) {
            conformsTo[name] = aUTI;
          } else {
            throw new Error(`Referenced UTI not known: ${name}`);
          }
        });
      }

      properties.conformsTo = {
        value: conformsTo
      };

      const nu = Object.create(RootUTI, properties);

      registry[nu.name] = nu;
    }

    return "ok";
  }

  exports.loadDefinitions = loadDefinitions;

  const loadPromise = exports.loadDefinitionsFromFile(path.join(__dirname, 'publicUTI.json'));

  if (options.definitionFileName) {
    return loadPromise.then(resolved => {
      return exports.loadDefinitionsFromFile(options.definitionFileName);
    });
  }

  return loadPromise;

  /*
    const fileNames = [path.join(__dirname, 'publicUTI.json')];

    if (options.definitionFileName) {
      fileNames.push(options.definitionFileName);
    }

    return Promise.all(fileNames.map(function (f) {
      return exports.loadDefinitionsFromFile(f);
    }));
  */
};
<commit_msg>docs: loadDefinitions()<commit_after>/* jslint node: true, esnext: true */

"use strict";

const fs = require("fs"),
  path = require("path");

const RootUTI = {
  toString() {
      return this.name;
    },

    /**
     * Deliver JSON representation of the UTI.
     * Sample result
     * {
     *   "name": "myUTI",
     *   "conformsTo": [ "uti1", "uti2"]
     * }
     * @return json representation of the UTI
     */
    toJSON() {
      return {
        name: this.name,
        conformsTo: this.conformsTo
      };
    }
};

/**
 * Initialized the uti api.
 * @param options {object} options.definitionFileName is given then additional UTIs will be loaded from the given file name
 * @return {Promise} a promise that is fullfilled when the initialization is done
 */
exports.initialize = function (options) {
  if (!options) {
    options = {};
  }

  const registry = {};
  const utiByFileNameExtension = {};
  const utiByMimeType = {};

  function assignExtensions(extensions, name) {
    extensions.forEach(ext => {
      if (utiByFileNameExtension[ext]) {
        utiByFileNameExtension[ext].push(name);
      } else {
        //console.log(`${ext} : ${name}`);

        utiByFileNameExtension[ext] = [name];
      }
    });
  }

  function assignMimeTypes(mimTypes, name) {
    mimTypes.forEach(type => {
      if (utiByMimeType[type]) {
        utiByMimeType[type].push(name);
      } else {
        utiByMimeType[type] = [name];
      }
    });
  }

  function _conformsTo(a, b) {
    for (let i in a.conformsTo) {
      const u = a.conformsTo[i];

      if (u === b) {
        return true;
      }

      if (_conformsTo(u, b)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Check whenever two UTI are conformant.
   * If a conforms to b and b conforms to c then a also conforms to c.
   * @param a {string} first UTI
   * @param b {string} second UTI
   * @return {boolean} true if UTI a conforms to UTI b.
   */
  exports.conformsTo = function (a, b) {
    a = registry[a];
    if (!a) {
      return false;
    }

    return _conformsTo(a, registry[b]);
  };

  /*
    TODO find a better name
    exports.allConformingTo = function *(a) {
      a = registry[a];
      if (!a) {
        return;
      }

      for (let i in a.conformsTo) {
        yield i;

        const u = a.conformsTo[i];
      }
    };
  */

  /**
   * Lookup a given UTI.
   * @param name {string} UTI
   * @return {string} UTI for the given name or undefined if UTI is not present.
   */
  exports.getUTI = function (name) {
    return registry[name];
  };

  /**
   * Lookup a UTI for a file name.
   * First the file name extension is extracted.
   * Then a lookup in the reistered UTIs for file name extions is executed.
   * @param fileName {string} file to detect UTI for
   * @return {string} UTI for the given fileName or undefined if no UTI is registerd for the file names extension
   */
  exports.getUTIsForFileName = function (fileName) {
    const m = fileName.match(/(\.[a-zA-Z_0-9]+)$/);

    if (m) {
      return utiByFileNameExtension[m[1]];
    }
    return undefined;
  };

  /**
   * Lookup a UTIs for a mime type.
   * @param mimeType {string} mime type to get UTIs for
   * @return {string} UTI for the given mime type or undefined if no UTI is registerd for the mime type
   */
  exports.getUTIsForMimeType = function (mimeType) {
    return utiByMimeType[mimeType];
  };

  /**
   * Load additional UTIs form a file.
   * @param fileName {string} file containing UTI definitions
   * @return {Promise} a promise that resolves after the UTIs have been registered.
   */
  exports.loadDefinitionsFromFile = function (fileName) {
    // not using promisify-node any longer since it has some side-effects into the plain fs module
    return new Promise(function (resolve, reject) {
      fs.readFile(fileName, {
        encoding: "utf-8"
      }, function (error, data) {
        if (error) {
          reject(error);
          return;
        }
        try {
          resolve(loadDefinitions(data));
        } catch (error) {
          reject(error);
        }
      });
    });
  };

  /**
   * Loads additionnal uti defintions from a (json) string
   * @param {String} data
   */
  function loadDefinitions(data) {
    for (let u of JSON.parse(data)) {
      const properties = {
        name: {
          value: u.name
        }
      };

      if (u.fileNameExtension) {
        assignExtensions(Array.isArray(u.fileNameExtension) ? u.fileNameExtension : [u.fileNameExtension],
          u.name);
      }

      if (u.mimeType) {
        assignMimeTypes(Array.isArray(u.mimeType) ? u.mimeType : [u.mimeType],
          u.name);
      }

      const conformsTo = {};

      if (u.conformsTo) {
        const ct = Array.isArray(u.conformsTo) ? u.conformsTo : [u.conformsTo];

        ct.forEach(name => {
          const aUTI = registry[name];
          if (aUTI) {
            conformsTo[name] = aUTI;
          } else {
            throw new Error(`Referenced UTI not known: ${name}`);
          }
        });
      }

      properties.conformsTo = {
        value: conformsTo
      };

      const nu = Object.create(RootUTI, properties);

      registry[nu.name] = nu;
    }

    return "ok";
  }

  exports.loadDefinitions = loadDefinitions;

  const loadPromise = exports.loadDefinitionsFromFile(path.join(__dirname, 'publicUTI.json'));

  if (options.definitionFileName) {
    return loadPromise.then(resolved => {
      return exports.loadDefinitionsFromFile(options.definitionFileName);
    });
  }

  return loadPromise;

  /*
    const fileNames = [path.join(__dirname, 'publicUTI.json')];

    if (options.definitionFileName) {
      fileNames.push(options.definitionFileName);
    }

    return Promise.all(fileNames.map(function (f) {
      return exports.loadDefinitionsFromFile(f);
    }));
  */
};
<|endoftext|>


<commit_before>'use strict'

/**
 * Collection of expressions and utilities for the Club of Expressions :
 *
 * * `parse` : from Lisp source to nested JS arrays
 *
 * @summary Expressions and utilities for the Club of Expressions.
 */

/**
 * @summary Parses Lisp source which represents an expression.
 *
 * Adapted from <https://www.recurse.com/blog/21-little-lisp-interpreter>
 * Just added «input.» and skipped `categorize`.
 *
 * @param src The Lisp source
 * @return A nested JS array
 */
exports.parse = function(input) {
  return parenthesize(tokenize(input));
};

var tokenize = function(input) {
  return input.replace(/\(/g, ' ( ')
        .replace(/\)/g, ' ) ')
        .trim()
        .split(/\s+/);
};

var parenthesize = function(input, list) {
  if (list === undefined) {
    return parenthesize(input, []);
  } else {
    var token = input.shift();
    if (token === undefined) {
      return list.pop();
    } else if (token === "(") {
      list.push(parenthesize(input, []));
      return parenthesize(input, list);
    } else if (token === ")") {
      return list;
    } else {
      return parenthesize(input, list.concat(token));
    }
  }
};

/**
 * @summary Renders an expression Lisp source.
 *
 * @param expr An expression
 * @return Lisp source, aka Code Club
 */
exports.renderExprAsLisp = function (expr) {
    if (typeof expr === 'object') {
        var cmd = expr[0];
        var args = expr.slice(1).map(exports.renderExprAsLisp);
        return '(' + cmd + ' ' + args.join(' ') + ')';
    } else {
        return expr;
    }
}

/**
 * @summary Renders an expression LaTex source.
 *
 * @param expr An expression
 * @param parentCmd An optional type of expr, aka command
 * @return LaTex source
 */
exports.renderExprAsLaTeX = function (expr, parentCmd) {
  if (typeof expr === 'object') {
    var cmd = expr[0];
    var args = expr.slice(1).map(function (expr) {
        return exports.renderExprAsLaTeX(expr, cmd);
    });
    var latex = '';
    if (cmd === 'Somme'    ) latex = args.join('+');
    if (cmd === 'Diff'     ) latex = args[0] + "-" + args[1];
    if (cmd === 'Produit'  ) {
        var lastArg = args[0];
        latex = args[0];
        for (var i = 1; i < args.length; i++) {
            var arg = args[i];
            if (isNaN(parseInt(arg)) && (!isNaN(parseInt(lastArg)) || arg != lastArg))
                latex = latex + arg;
            else
                latex = latex + '×' + arg;
            lastArg = arg;
        }
    }
    if (cmd === 'Quotient' ) latex = "\\frac{" + args[0] + "}{" + args[1] + "}";
    if (cmd === 'Opposé'   ) latex = "-" + args[0];
    if (cmd === 'Inverse'  ) latex = "\\frac{1}{" + args[0] + "}";
    if (cmd === 'Carré'    ) latex = args[0] + "^2";
    if (cmd === 'Puissance') latex = args[0] + "^" + args[1];
    if (cmd === 'Racine'   ) latex = "\\sqrt{" + args[0] + "}";
    if (latex === '') return "Unknown cmd: " + cmd;
    if (parens(cmd, parentCmd)) latex = '\\left(' + latex + '\\right)';
    return latex;
  } else {
      return expr;
  }
}

/**
 * @summary Tests if a value is in an array.
 *
 * @param obj The value
 * @param arr The array
 * @return a boolean
 */
var belongsTo = function (obj, arr) {
    return arr.indexOf(obj) !== -1;
}

/**
 * @summary Tests if the sub-expr should be surrounded with parens.
 *
 * @param cmd The current type of expr, aka command
 * @param parentCmd The parent command
 * @return a boolean
 */
var parens = function (cmd, parentCmd) {
    var S  = 'Somme';
    var D  = 'Diff';
    var P  = 'Produit';
    var Q  = 'Quotient';
    var O  = 'Opposé';
    var I  = 'Inverse';
    var C  = 'Carré';
    var Pu = 'Puissance';
    var R  = 'Racine';
    if (belongsTo(cmd, [S,D])) {
        return belongsTo(parentCmd, [D,P,O,C,Pu,R]);
    }
    if (belongsTo(cmd, [P,Q,O,I])) {
        return belongsTo(parentCmd, [C,Pu]);
    }
    return false;
}

/**
 * @summary Inspects an expression.
 *
 * @param expr The expression to inspect
 * @param parentCmd An optional type of expr, aka command
 * @return An object with all the information
 */
exports.properties = function (expr, parentCmd) {
  if (typeof expr === 'object') {
    // Init of the returned object
    var newProps = {
      depth: 0,
      leaves: 0,
      letters: 0,
      numbers: 0
    };
    // Recursion
    var cmd = expr[0];
    var args = expr.slice(1);
    var propsArray = args.map(function (expr) {
      return exports.properties(expr, cmd);
    });
    // Process children
    for (var i = 0; i < propsArray.length; i += 1) {
      var props = propsArray[i];
      if (props.depth > newProps.depth) newProps.depth = props.depth;
      newProps.leaves += props.leaves;
      newProps.letters += props.letters;
      newProps.numbers += props.numbers;
    }
    newProps.depth += 1;
    // Return
    return newProps;
  } else {
    // A leaf
    var aLetter = isNaN(parseInt(expr));
    return {
      depth: 0,
      leaves: 1,
      letters:  aLetter? 1 : 0,
      numbers: !aLetter? 1 : 0
    };
  }
}

/**
 * @summary Builds an expression with specified letters and numbers.
 *
 * @param expr The expression template to use
 * @param obj The elements used to replace the old ones
 */
exports.replaceValuesWith = function (expr, obj) {
  if (typeof expr === 'object') {
    var cmd = expr[0];
    var args = expr.slice(1).map(function (expr) {
      return exports.replaceValuesWith(expr, obj);
    });
    args.unshift(cmd);
    return args;
  } else {
      var newLeaf = obj[expr];
      if (typeof newLeaf !== 'undefined') return newLeaf;
      return expr;
  }
}

/**
 * @summary Builds a random integers array (from 2 to `max`, shuffled).
 *
 * @param max The max integer to include
 * @return The array of integers
 */
exports.randomNumbers = function (max) {
    // http://stackoverflow.com/questions/3895478
    var ints = Array.apply(null, Array(max-1)).map(function (_, i) {return i+2;});
    var counter = ints.length;
    while (counter > 0) {
        var index = Math.floor(Math.random() * counter);
        counter--;
        // swap
        var temp = ints[counter];
        ints[counter] = ints[index];
        ints[index] = temp;
    }
    ints.unshift(0);  // The first number in an expression is 1.
    return ints;
}

exports.expressions = function () {
  // For convenience, definitions are done with variables (avoid quotes).
  var a = 'a';
  var b = 'b';
  var c = 'c';
  var d = 'd';
  var e = 'e';
  var f = 'f';
  var g = 'g';
  // Opérations:
  var S  = 'Somme';
  var D  = 'Diff';
  var P  = 'Produit';
  var Q  = 'Quotient';
  var O  = 'Opposé';
  var I  = 'Inverse';
  var C  = 'Carré';
  var Pu = 'Puissance';
  var R  = 'Racine';
  // Les calculs entre parenthèses sont prioritaires.
  var Pa = 'parenthèses';
  // Dans une expression sans parenthèses avec uniquement des additions et des
  // soustractions, on effectue les calculs de gauche à droite.
  var GD = 'gauche-droite';
  // Dans une expression sans parenthèses, les multiplications et les divisions
  // ont priorité sur les additions, les soustractions et les passages à l'opposé.
  var MD = 'mult-div';
  // Dans une expression sans parenthèses, l'élévation à une puissance a priorité
  // sur les quatre opérations usuelles et sur le passage à l'opposé.
  var El = 'élévation';
  // Dans une expression sans parenthèses, le passage à l'opposé a priorité sur
  // l'addition et la soustraction.
  var Op = 'opposé';
  // Devant une lettre ou une parenthèse, le signe « × » de la multiplication est
  // facultatif.
  var X = 'signe ×';
  // Lorsque la division est indiquée par un trait de fraction, les parenthèses
  // autour des expressions au numérateur et au dénominateur sont facultatives.
  var F = 'fraction';

  // The expressions (from the boss himself)
  return [
  {"nom" : "Somme de deux nombres",
   "conv": [],
   "expr": [S,1,2]},
  {"nom" : "Somme d’une lettre et d’un nombre",
   "conv": [],
   "expr": [S,a,1]},
  {"nom" : "Somme d’un nombre et d’une lettre",
   "conv": [],
   "expr": [S,1,a]},
  {"nom" : "Somme de deux lettres",
   "conv": [],
   "expr": [S,a,b]},
  {"nom" : "Diff de deux nombres",
   "conv": [],
   "expr": [D,1,2]},
  {"nom" : "Diff d’une lettre et d’un nombre",
   "conv": [],
   "expr": [D,a,1]},
  {"nom" : "Diff d’un nombre et d’une lettre",
   "conv": [],
   "expr": [D,1,a]},
  {"nom" : "Diff de deux lettres",
   "conv": [],
   "expr": [D,a,b]},
  {"nom" : "Produit de deux nombres",
   "conv": [],
   "expr": [P,1,2]},
  {"nom" : "Multiple",
   "conv": [],
   "expr": [P,1,a]},
  {"nom" : "Produit d’une lettre par un nombre",
   "conv": [],
   "expr": [P,a,1]},
  {"nom" : "Produit de deux lettres",
   "conv": [],
   "expr": [P,a,b]},
  {"nom" : "Produit de deux lettres identiques",
   "conv": [],
   "expr": [P,a,a]},
  {"nom" : "Division par un nombre",
   "conv": [],
   "expr": [Q,a,1]},
  {"nom" : "Coeff divisé par un nombre",
   "conv": [],
   "expr": [Q,1,a]},
  {"nom" : "Carré d’un nombre",
   "conv": [],
   "expr": [C,1]},
  {"nom" : "Carré d’une lettre",
   "conv": [],
   "expr": [C,a]},
  {"nom" : "Puissance",
   "conv": [],
   "expr": [Pu,a,1]},
  {"nom" : "Inverse",
   "conv": [],
   "expr": [I,a]},
  {"nom" : "Opposé",
   "conv": [],
   "expr": [O,a]},
  {"nom" : "Produit d’un nombre avec une somme",
   "conv": [Pa],
   "expr": [P,1,[S,a,2]]},
  {"nom" : "Produit d’un nombre avec une différence",
   "conv": [Pa],
   "expr": [P,1,[D,a,2]]},
  {"nom" : "Somme d’un nombre avec un produit",
   "conv": [MD,X],
   "expr": [S,1,[P,2,a]]},
  {"nom" : "Différence entre multiple et nombre",
   "conv": [MD,X],
   "expr": [D,[P,1,a],2]},
  {"nom" : "Somme d’un nombre avec un quotient",
   "conv": [MD],
   "expr": [S,1,[Q,a,2]]},
  {"nom" : "Différence entre un opposé et un nombre",
   "conv": [O],
   "expr": [D,[O,a],1]},
  {"nom" : "Opposé d’une différence",
   "conv": [Pa],
   "expr": [O,[D,a,1]]},
  {"nom" : "Différence entre un nombre et une somme",
   "conv": [Pa],
   "expr": [D,1,[S,2,a]]},
  {"nom" : "Somme d’une différence avec un nombre",
   "conv": [GD],
   "expr": [S,[D,a,1],2]},
  {"nom" : "Somme divisée par un nombre",
   "conv": [F],
   "expr": [Q,[S,a,b],1]},
  {"nom" : "Inverse d’une somme",
   "conv": [F],
   "expr": [I,[S,a,1]]},
  {"nom" : "Multiple divisé par un nombre",
   "conv": [X,F],
   "expr": [Q,[P,1,a],2]},
  {"nom" : "Opposé d’un multiple",
   "conv": [MD,X],
   "expr": [O,[P,1,a]]},
  {"nom" : "Homographique séparée",
   "conv": [F,MD],
   "expr": [S,1,[Q,2,[S,[P,3,a],4]]]},
  ];
}();

<commit_msg>Factor out skipMultSign<commit_after>'use strict'

/**
 * Collection of expressions and utilities for the Club of Expressions :
 *
 * * `parse` : from Lisp source to nested JS arrays
 *
 * @summary Expressions and utilities for the Club of Expressions.
 */

/**
 * @summary Parses Lisp source which represents an expression.
 *
 * Adapted from <https://www.recurse.com/blog/21-little-lisp-interpreter>
 * Just added «input.» and skipped `categorize`.
 *
 * @param src The Lisp source
 * @return A nested JS array
 */
exports.parse = function(input) {
  return parenthesize(tokenize(input));
};

var tokenize = function(input) {
  return input.replace(/\(/g, ' ( ')
        .replace(/\)/g, ' ) ')
        .trim()
        .split(/\s+/);
};

var parenthesize = function(input, list) {
  if (list === undefined) {
    return parenthesize(input, []);
  } else {
    var token = input.shift();
    if (token === undefined) {
      return list.pop();
    } else if (token === "(") {
      list.push(parenthesize(input, []));
      return parenthesize(input, list);
    } else if (token === ")") {
      return list;
    } else {
      return parenthesize(input, list.concat(token));
    }
  }
};

/**
 * @summary Renders an expression Lisp source.
 *
 * @param expr An expression
 * @return Lisp source, aka Code Club
 */
exports.renderExprAsLisp = function (expr) {
    if (typeof expr === 'object') {
        var cmd = expr[0];
        var args = expr.slice(1).map(exports.renderExprAsLisp);
        return '(' + cmd + ' ' + args.join(' ') + ')';
    } else {
        return expr;
    }
}

var skipMultSign = function (lastArg, arg) {
    return isNaN(parseInt(arg)) && (!isNaN(parseInt(lastArg)) || arg != lastArg);
}

/**
 * @summary Renders an expression LaTex source.
 *
 * @param expr An expression
 * @param parentCmd An optional type of expr, aka command
 * @return LaTex source
 */
exports.renderExprAsLaTeX = function (expr, parentCmd) {
  if (typeof expr === 'object') {
    var cmd = expr[0];
    var args = expr.slice(1).map(function (expr) {
        return exports.renderExprAsLaTeX(expr, cmd);
    });
    var latex = '';
    if (cmd === 'Somme'    ) latex = args.join('+');
    if (cmd === 'Diff'     ) latex = args[0] + "-" + args[1];
    if (cmd === 'Produit'  ) {
        var lastArg = args[0];
        latex = args[0];
        for (var i = 1; i < args.length; i++) {
            var arg = args[i];
            if (skipMultSign(lastArg, arg))
                latex = latex + arg;
            else
                latex = latex + '×' + arg;
            lastArg = arg;
        }
    }
    if (cmd === 'Quotient' ) latex = "\\frac{" + args[0] + "}{" + args[1] + "}";
    if (cmd === 'Opposé'   ) latex = "-" + args[0];
    if (cmd === 'Inverse'  ) latex = "\\frac{1}{" + args[0] + "}";
    if (cmd === 'Carré'    ) latex = args[0] + "^2";
    if (cmd === 'Puissance') latex = args[0] + "^" + args[1];
    if (cmd === 'Racine'   ) latex = "\\sqrt{" + args[0] + "}";
    if (latex === '') return "Unknown cmd: " + cmd;
    if (parens(cmd, parentCmd)) latex = '\\left(' + latex + '\\right)';
    return latex;
  } else {
      return expr;
  }
}

/**
 * @summary Tests if a value is in an array.
 *
 * @param obj The value
 * @param arr The array
 * @return a boolean
 */
var belongsTo = function (obj, arr) {
    return arr.indexOf(obj) !== -1;
}

/**
 * @summary Tests if the sub-expr should be surrounded with parens.
 *
 * @param cmd The current type of expr, aka command
 * @param parentCmd The parent command
 * @return a boolean
 */
var parens = function (cmd, parentCmd) {
    var S  = 'Somme';
    var D  = 'Diff';
    var P  = 'Produit';
    var Q  = 'Quotient';
    var O  = 'Opposé';
    var I  = 'Inverse';
    var C  = 'Carré';
    var Pu = 'Puissance';
    var R  = 'Racine';
    if (belongsTo(cmd, [S,D])) {
        return belongsTo(parentCmd, [D,P,O,C,Pu,R]);
    }
    if (belongsTo(cmd, [P,Q,O,I])) {
        return belongsTo(parentCmd, [C,Pu]);
    }
    return false;
}

/**
 * @summary Inspects an expression.
 *
 * @param expr The expression to inspect
 * @param parentCmd An optional type of expr, aka command
 * @return An object with all the information
 */
exports.properties = function (expr, parentCmd) {
  if (typeof expr === 'object') {
    // Init of the returned object
    var newProps = {
      depth: 0,
      leaves: 0,
      letters: 0,
      numbers: 0
    };
    // Recursion
    var cmd = expr[0];
    var args = expr.slice(1);
    var propsArray = args.map(function (expr) {
      return exports.properties(expr, cmd);
    });
    // Process children
    for (var i = 0; i < propsArray.length; i += 1) {
      var props = propsArray[i];
      if (props.depth > newProps.depth) newProps.depth = props.depth;
      newProps.leaves += props.leaves;
      newProps.letters += props.letters;
      newProps.numbers += props.numbers;
    }
    newProps.depth += 1;
    // Return
    return newProps;
  } else {
    // A leaf
    var aLetter = isNaN(parseInt(expr));
    return {
      depth: 0,
      leaves: 1,
      letters:  aLetter? 1 : 0,
      numbers: !aLetter? 1 : 0
    };
  }
}

/**
 * @summary Builds an expression with specified letters and numbers.
 *
 * @param expr The expression template to use
 * @param obj The elements used to replace the old ones
 */
exports.replaceValuesWith = function (expr, obj) {
  if (typeof expr === 'object') {
    var cmd = expr[0];
    var args = expr.slice(1).map(function (expr) {
      return exports.replaceValuesWith(expr, obj);
    });
    args.unshift(cmd);
    return args;
  } else {
      var newLeaf = obj[expr];
      if (typeof newLeaf !== 'undefined') return newLeaf;
      return expr;
  }
}

/**
 * @summary Builds a random integers array (from 2 to `max`, shuffled).
 *
 * @param max The max integer to include
 * @return The array of integers
 */
exports.randomNumbers = function (max) {
    // http://stackoverflow.com/questions/3895478
    var ints = Array.apply(null, Array(max-1)).map(function (_, i) {return i+2;});
    var counter = ints.length;
    while (counter > 0) {
        var index = Math.floor(Math.random() * counter);
        counter--;
        // swap
        var temp = ints[counter];
        ints[counter] = ints[index];
        ints[index] = temp;
    }
    ints.unshift(0);  // The first number in an expression is 1.
    return ints;
}

exports.expressions = function () {
  // For convenience, definitions are done with variables (avoid quotes).
  var a = 'a';
  var b = 'b';
  var c = 'c';
  var d = 'd';
  var e = 'e';
  var f = 'f';
  var g = 'g';
  // Opérations:
  var S  = 'Somme';
  var D  = 'Diff';
  var P  = 'Produit';
  var Q  = 'Quotient';
  var O  = 'Opposé';
  var I  = 'Inverse';
  var C  = 'Carré';
  var Pu = 'Puissance';
  var R  = 'Racine';
  // Les calculs entre parenthèses sont prioritaires.
  var Pa = 'parenthèses';
  // Dans une expression sans parenthèses avec uniquement des additions et des
  // soustractions, on effectue les calculs de gauche à droite.
  var GD = 'gauche-droite';
  // Dans une expression sans parenthèses, les multiplications et les divisions
  // ont priorité sur les additions, les soustractions et les passages à l'opposé.
  var MD = 'mult-div';
  // Dans une expression sans parenthèses, l'élévation à une puissance a priorité
  // sur les quatre opérations usuelles et sur le passage à l'opposé.
  var El = 'élévation';
  // Dans une expression sans parenthèses, le passage à l'opposé a priorité sur
  // l'addition et la soustraction.
  var Op = 'opposé';
  // Devant une lettre ou une parenthèse, le signe « × » de la multiplication est
  // facultatif.
  var X = 'signe ×';
  // Lorsque la division est indiquée par un trait de fraction, les parenthèses
  // autour des expressions au numérateur et au dénominateur sont facultatives.
  var F = 'fraction';

  // The expressions (from the boss himself)
  return [
  {"nom" : "Somme de deux nombres",
   "conv": [],
   "expr": [S,1,2]},
  {"nom" : "Somme d’une lettre et d’un nombre",
   "conv": [],
   "expr": [S,a,1]},
  {"nom" : "Somme d’un nombre et d’une lettre",
   "conv": [],
   "expr": [S,1,a]},
  {"nom" : "Somme de deux lettres",
   "conv": [],
   "expr": [S,a,b]},
  {"nom" : "Diff de deux nombres",
   "conv": [],
   "expr": [D,1,2]},
  {"nom" : "Diff d’une lettre et d’un nombre",
   "conv": [],
   "expr": [D,a,1]},
  {"nom" : "Diff d’un nombre et d’une lettre",
   "conv": [],
   "expr": [D,1,a]},
  {"nom" : "Diff de deux lettres",
   "conv": [],
   "expr": [D,a,b]},
  {"nom" : "Produit de deux nombres",
   "conv": [],
   "expr": [P,1,2]},
  {"nom" : "Multiple",
   "conv": [],
   "expr": [P,1,a]},
  {"nom" : "Produit d’une lettre par un nombre",
   "conv": [],
   "expr": [P,a,1]},
  {"nom" : "Produit de deux lettres",
   "conv": [],
   "expr": [P,a,b]},
  {"nom" : "Produit de deux lettres identiques",
   "conv": [],
   "expr": [P,a,a]},
  {"nom" : "Division par un nombre",
   "conv": [],
   "expr": [Q,a,1]},
  {"nom" : "Coeff divisé par un nombre",
   "conv": [],
   "expr": [Q,1,a]},
  {"nom" : "Carré d’un nombre",
   "conv": [],
   "expr": [C,1]},
  {"nom" : "Carré d’une lettre",
   "conv": [],
   "expr": [C,a]},
  {"nom" : "Puissance",
   "conv": [],
   "expr": [Pu,a,1]},
  {"nom" : "Inverse",
   "conv": [],
   "expr": [I,a]},
  {"nom" : "Opposé",
   "conv": [],
   "expr": [O,a]},
  {"nom" : "Produit d’un nombre avec une somme",
   "conv": [Pa],
   "expr": [P,1,[S,a,2]]},
  {"nom" : "Produit d’un nombre avec une différence",
   "conv": [Pa],
   "expr": [P,1,[D,a,2]]},
  {"nom" : "Somme d’un nombre avec un produit",
   "conv": [MD,X],
   "expr": [S,1,[P,2,a]]},
  {"nom" : "Différence entre multiple et nombre",
   "conv": [MD,X],
   "expr": [D,[P,1,a],2]},
  {"nom" : "Somme d’un nombre avec un quotient",
   "conv": [MD],
   "expr": [S,1,[Q,a,2]]},
  {"nom" : "Différence entre un opposé et un nombre",
   "conv": [O],
   "expr": [D,[O,a],1]},
  {"nom" : "Opposé d’une différence",
   "conv": [Pa],
   "expr": [O,[D,a,1]]},
  {"nom" : "Différence entre un nombre et une somme",
   "conv": [Pa],
   "expr": [D,1,[S,2,a]]},
  {"nom" : "Somme d’une différence avec un nombre",
   "conv": [GD],
   "expr": [S,[D,a,1],2]},
  {"nom" : "Somme divisée par un nombre",
   "conv": [F],
   "expr": [Q,[S,a,b],1]},
  {"nom" : "Inverse d’une somme",
   "conv": [F],
   "expr": [I,[S,a,1]]},
  {"nom" : "Multiple divisé par un nombre",
   "conv": [X,F],
   "expr": [Q,[P,1,a],2]},
  {"nom" : "Opposé d’un multiple",
   "conv": [MD,X],
   "expr": [O,[P,1,a]]},
  {"nom" : "Homographique séparée",
   "conv": [F,MD],
   "expr": [S,1,[Q,2,[S,[P,3,a],4]]]},
  ];
}();

<|endoftext|>


<commit_before>#!/usr/bin/env node
'use strict';

const colors = require("colors");
const fs = require('fs');

const program = require("commander");
const _version_ = require("./package.json").version;
const Odot = require('./src/odot.js');

var json;

var odot = new Odot();

program
    .version(_version_)
    .usage('<command> <item>');

program
    .command("list").alias("=")
    .description("List your current list")
    .action(function() {
        odot.equals();
});

program
    .command("add [item...]").alias("+")
    .description("Add an item to your list")
    .action(function(item) {
        item = Odot.makeText(item);
        odot.plus(item);
});

program
    .command("remove [item...]").alias("-")
    .description("Remove an item to your list")
    .action(function(item) {
        item = Odot.makeText(item);
        odot.minus(item);
});

program
    .command("filter").alias("*")
    .description("Remove finished items from your list")
    .action(function() {
        odot.filter();
});

program
    .command("check [item...]").alias("!")
    .description("Check an item on your list")
    .action(function(item) {
        item = Odot.makeText(item);
        odot.check(item);
});

program
    .command("stats").alias("s")
    .description("Print your stats")
    .action(function() {
        odot.stats();
});

program
    .command("clear").alias("0")
    .description("Clear your list")
    .action(function() {
        odot.zero();
});

program
    .command("idea [item...]").alias("?")
    .description("Adds an idea to the list")
    .action(function(item) {
        item = Odot.makeText(item);
        odot.question(item);
});

program
    .command("yes [item...]").alias("y")
    .description("Adds an idea to the todo list")
    .action(function(item) {
        item = Odot.makeText(item);
        odot.yes(item);
});

program
    .command("no [item...]").alias("n")
    .description("Removes an idea from the list")
    .action(function(item) {
        item = Odot.makeText(item);
        odot.no(item);
})

// remote
program
    .command("connect <secret>").alias("c")
    .description("Connect to remote")
    .action(function(secret) {
        odot.connect(secret);
});

program
    .command("push")
    .description("Push to remote")
    .action(function() {
        odot.push();
});

program
    .command("pull")
    .description("Pull from remote")
    .action(function() {
        odot.pull();
});

program.parse(process.argv);

// Output help by default
if (!process.argv.slice(2).length) {
  program.outputHelp();
}
<commit_msg>Commented remote stuff<commit_after>#!/usr/bin/env node
'use strict';

const colors = require("colors");
const fs = require('fs');

const program = require("commander");
const _version_ = require("./package.json").version;
const Odot = require('./src/odot.js');

var json;

var odot = new Odot();

program
    .version(_version_)
    .usage('<command> <item>');

program
    .command("list").alias("=")
    .description("List your current list")
    .action(function() {
        odot.equals();
});

program
    .command("add [item...]").alias("+")
    .description("Add an item to your list")
    .action(function(item) {
        item = Odot.makeText(item);
        odot.plus(item);
});

program
    .command("remove [item...]").alias("-")
    .description("Remove an item to your list")
    .action(function(item) {
        item = Odot.makeText(item);
        odot.minus(item);
});

program
    .command("filter").alias("*")
    .description("Remove finished items from your list")
    .action(function() {
        odot.filter();
});

program
    .command("check [item...]").alias("!")
    .description("Check an item on your list")
    .action(function(item) {
        item = Odot.makeText(item);
        odot.check(item);
});

program
    .command("stats").alias("s")
    .description("Print your stats")
    .action(function() {
        odot.stats();
});

program
    .command("clear").alias("0")
    .description("Clear your list")
    .action(function() {
        odot.zero();
});

program
    .command("idea [item...]").alias("?")
    .description("Adds an idea to the list")
    .action(function(item) {
        item = Odot.makeText(item);
        odot.question(item);
});

program
    .command("yes [item...]").alias("y")
    .description("Adds an idea to the todo list")
    .action(function(item) {
        item = Odot.makeText(item);
        odot.yes(item);
});

program
    .command("no [item...]").alias("n")
    .description("Removes an idea from the list")
    .action(function(item) {
        item = Odot.makeText(item);
        odot.no(item);
})

// remote
/* program
    .command("connect <secret>").alias("c")
    .description("Connect to remote")
    .action(function(secret) {
        odot.connect(secret);
});

program
    .command("push")
    .description("Push to remote")
    .action(function() {
        odot.push();
});

program
    .command("pull")
    .description("Pull from remote")
    .action(function() {
        odot.pull();
});
*/

program.parse(process.argv);

// Output help by default
if (!process.argv.slice(2).length) {
  program.outputHelp();
}
<|endoftext|>


<commit_before>var Event = require('./event-base.js');
require('./event-emitter.js'); // will extent Event
require('./event-listener.js'); // will extent the exported object

module.exports = Event;
<commit_msg>update for travis-build<commit_after>var Event = require('./event-base.js');
require('./event-emitter.js'); // will extent Event
require('./event-listener.js'); // will extent the exported object

module.exports = Event;<|endoftext|>


<commit_before>var _ = require('underscore');
var winston = require('winston');
var mongoose = require('mongoose');
var Slack = require('slack-client');
var nconf = require('nconf');
var winston = require('winston');

function AddonSlack (app, server, io, passport){
    var self = this;
	this.name = 'Slack';
	this.description = 'Slack integration addon';
    this.listDependencies = ['slack-robot'];
    var Testcase = mongoose.model('Testcase')
    var Result = mongoose.model('Result');

    var allowAllUsers = true;
    var allowedUsers = [/jussi/, /wirkus/];

    var allowAllChannels = false;
    var allowedChannels = [/jussiva/, /wirkus/, /thread/, /6lowpan/, /clitest/];

  if( !nconf.get('slack')) {
    winston.error('slack configuration missing!');
    return;
  }

	this.register = function(){
    var slackOpts = {
      token: nconf.get('slack').token,
      autoReconnect: true,
      //autoMark: false
    }

    var allowedChannel = function(channel) {
        if( allowAllChannels ) return true;
        if( !channel || !channel.name ) return false;
        winston.log('check if channel is valid: '+channel.name);
        return allowedChannels.some( function(ch) {
            if( channel.name.toLowerCase().match(ch) ){
                return true;
            }
        });
    }
    var allowedUser = function(user) {
        if( allowAllUsers ) return true;
        if( !user || !user.real_name ) return false;
        winston.log('check if user is valid: '+user.real_name);
        return allowedUsers.some( function(usr) {
            if( user.real_name.toLowerCase().match(usr) ){
                return true;
            }
        });
    }

    var allowMessage = function(channel, user) {
        if( !allowedChannel(channel) ) return false;
        if( !allowedUser(user) ) return false;
        return true;
    }

    var help = function(message, channel, user, m) {
        channel.send("```Test Management Service\n:list testcases\tList all available cases\n:list results\tList 10 latest results\n:run [testcase]\t:run individual case\n:help\tThis help```");
    }
    var list = function(message, channel, user, m) {
        q = {f: 'tcid', t: 'distinct'};
        if( m=message.text.match(/^\:list\Wtestcases\W(.*)/)  ) {
            try {
                _.extend(q, JSON.parse(m[1]));
            } catch(e) {
                console.log(e);
                channel.send('Invalid format');
                return;
            }
        }
        Testcase.query(q, function(error, list){
          channel.send('```'+JSON.stringify(list, ' ', '\n')+'```');
        })
    }
    var results = function(message, channel, user, m) {
        q = {l: 10, s: '{"cre.time": -1}', q: {}};
        if( m=message.text.match(/thread/) || channel.name.match(/thread/) ) {
            if(!q.q['$or']) q.q['$or'] = [];
            q.q['$or'].push({'exec.sut.cut': 'thread'});
        }
        else if( m=message.text.match(/6lowpan/) || channel.name.match(/6lowpan/)  ) {
            if(!q.q['$or']) q.q['$or'] = [];
            q.q['$or'].push({'exec.sut.cut': '6LoWPAN'});
        }
        else if( m=message.text.match(/^\:list\Wresults\W(.*)/)  ) {
            try {
                _.extend(q, JSON.parse(m[1]));
            } catch(e) {
                console.log(e);
                channel.send('Invalid format');
                return;
            }
        }
        q.q = JSON.stringify(q.q);
        console.log('result query: '+JSON.stringify(q));
        Result.query(q, function(error, list){
            if( error ) {
                channel.send('Error while query');
                return;
            }
            var str = 'TC\t\tResult\n';
            list.forEach( function (result){
                if( result.exec && result.exec.verdict ) {
                    str += result.tcid+'\t'+result.exec.verdict+'\n';
                }
            });
            channel.send('```'+str+'```');
        })
    }
    var run = function(message, channel, user, m) {
        channel.send('Starting execute TC: '+m[1]+'...');
    }

    var commands = [
        { m: /^\:help/, f: help },
        { m: /^\:list\Wtestcases/, f: list},
        { m: /^\:list\Wresults/, f: results },
        { m: /^\:run\W(.*)/, f: run }
    ]

    var execute = function(message, channel, user) {
        if( !message.text.match(/^\:/) ) return false;
        console.log('check if cmd is valid: '+message.text);
        return commands.some( function(cmd) {
            m =  message.text.match(cmd.m);
            if(m) {
                cmd.f(message, channel, user, m);
                return true;
            }
        });
    }

    var handleMessage = function(message) {
        try {
            channel = slack.getChannelGroupOrDMByID(message.channel)
            user = slack.getUserByID(message.user)
            if( !allowMessage(channel, user) ) {
                winston.warn('message not allowed');
                return;
            }
            if( !user.hasOwnProperty("real_name") ) {
                console.log(user);
                console.log(message.text);
                return;
            }
            winston.log('SLACK: '+channel.name+'|'+user.real_name+''+message.text);
            if( !execute(message, channel, user) ) {
                console('Unknown command: '+message.text);
            }
        } catch( e ) {
            console.log(e);
            console.log(message.text);
        }
    }

    var slack = new Slack(slackOpts.token, slackOpts.autoReconnect, slackOpts.automark);

    slack
      .on('open', function(){
      channels = []
      groups = []
      unreads = slack.getUnreadCount()
      winston.info('Slack -connection open');
      // Get all the channels that bot is a member of
      /*channels = {}
      for( var id, channel in slack.channels ) {
         if( channel.is_member )
          channels[channel.id] = channel.name;
      }*/
      // Get all groups that are open and not archived
      //groups = (group.name for id, group of slack.groups when group.is_open and not group.is_archived)
    })
    .on('message', handleMessage);
    slack.login();
    /*
    var robotOpts = {
      ignoreMessageInGeneral: true,
      //mentionToRespond: true,
      //skipDMMention: false
    }
    var robot = new Robot(slackOpts, robotOpts);

    robot.listen('hello') //:results([a-z\-]+)
      .handler(function(req, res) {
      // you can get named-param inside Request object
      console.log(req.param);
      console.log("hello received");
      res.sendText("Hi There");
      // {animal: 'sheep', year: '2010'}
      // note that req.param always returns Map<string, string>
    });

    app.get('/api/v0/slack', function(req, res){
		  res.json({ok: 1});
		});*/
	}

  return this;
}

exports = module.exports = AddonSlack;<commit_msg>added error handling for slack<commit_after>var _ = require('underscore');
var winston = require('winston');
var mongoose = require('mongoose');
var Slack = require('slack-client');
var nconf = require('nconf');
var winston = require('winston');

function AddonSlack (app, server, io, passport){
    var self = this;
	this.name = 'Slack';
	this.description = 'Slack integration addon';
    this.listDependencies = ['slack-robot'];
    var Testcase = mongoose.model('Testcase')
    var Result = mongoose.model('Result');

    var allowAllUsers = true;
    var allowedUsers = [/jussi/, /wirkus/];

    var allowAllChannels = false;
    var allowedChannels = [/jussiva/, /wirkus/, /thread/, /6lowpan/, /clitest/];

  if( !nconf.get('slack')) {
    winston.error('slack configuration missing!');
    return;
  }

	this.register = function(){
    var slackOpts = {
      token: nconf.get('slack').token,
      autoReconnect: true,
      //autoMark: false
    }

    var allowedChannel = function(channel) {
        if( allowAllChannels ) return true;
        if( !channel || !channel.name ) return false;
        winston.log('SLACK: check if channel is valid: '+channel.name);
        return allowedChannels.some( function(ch) {
            if( channel.name.toLowerCase().match(ch) ){
                return true;
            }
        });
    }
    var allowedUser = function(user) {
        if( allowAllUsers ) return true;
        if( !user || !user.real_name ) return false;
        winston.log('SLACK: check if user is valid: '+user.real_name);
        return allowedUsers.some( function(usr) {
            if( user.real_name.toLowerCase().match(usr) ){
                return true;
            }
        });
    }

    var allowMessage = function(channel, user) {
        if( !allowedChannel(channel) ) return false;
        if( !allowedUser(user) ) return false;
        return true;
    }

    var help = function(message, channel, user, m) {
        channel.send("```Test Management Service\n:list testcases\tList all available cases\n:list results\tList 10 latest results\n:run [testcase]\t:run individual case\n:help\tThis help```");
    }
    var list = function(message, channel, user, m) {
        q = {f: 'tcid', t: 'distinct'};
        if( m=message.text.match(/^\:list\Wtestcases\W(.*)/)  ) {
            try {
                _.extend(q, JSON.parse(m[1]));
            } catch(e) {
                console.log(e);
                channel.send('Invalid format');
                return;
            }
        }
        Testcase.query(q, function(error, list){
          channel.send('```'+JSON.stringify(list, ' ', '\n')+'```');
        })
    }
    var results = function(message, channel, user, m) {
        q = {l: 10, s: '{"cre.time": -1}', q: {}};
        if( m=message.text.match(/thread/) || channel.name.match(/thread/) ) {
            if(!q.q['$or']) q.q['$or'] = [];
            q.q['$or'].push({'exec.sut.cut': 'thread'});
        }
        else if( m=message.text.match(/6lowpan/) || channel.name.match(/6lowpan/)  ) {
            if(!q.q['$or']) q.q['$or'] = [];
            q.q['$or'].push({'exec.sut.cut': '6LoWPAN'});
        }
        else if( m=message.text.match(/^\:list\Wresults\W(.*)/)  ) {
            try {
                _.extend(q, JSON.parse(m[1]));
            } catch(e) {
                console.log(e);
                channel.send('Invalid format');
                return;
            }
        }
        q.q = JSON.stringify(q.q);
        console.log('SLACK: result query: '+JSON.stringify(q));
        Result.query(q, function(error, list){
            if( error ) {
                channel.send('Error while query');
                return;
            }
            var str = 'TC\t\tResult\n';
            list.forEach( function (result){
                if( result.exec && result.exec.verdict ) {
                    str += result.tcid+'\t'+result.exec.verdict+'\n';
                }
            });
            channel.send('```'+str+'```');
        })
    }
    var run = function(message, channel, user, m) {
        channel.send('Starting execute TC: '+m[1]+'...');
    }

    var commands = [
        { m: /^\:help/, f: help },
        { m: /^\:list\Wtestcases/, f: list},
        { m: /^\:list\Wresults/, f: results },
        { m: /^\:run\W(.*)/, f: run }
    ]

    var execute = function(message, channel, user) {
        if( !message.text.match(/^\:/) ) return false;
        console.log('SLACK: check if cmd is valid: '+message.text);
        return commands.some( function(cmd) {
            m =  message.text.match(cmd.m);
            if(m) {
                cmd.f(message, channel, user, m);
                return true;
            }
        });
    }

    var handleMessage = function(message) {
        try {
            channel = slack.getChannelGroupOrDMByID(message.channel)
            user = slack.getUserByID(message.user)
            if( !allowMessage(channel, user) ) {
                winston.warn('SLACK: message not allowed');
                return;
            }
            if( !user.hasOwnProperty("real_name") ) {
                console.log(user);
                console.log(message.text);
                return;
            }
            winston.log('SLACK: '+channel.name+'|'+user.real_name+''+message.text);
            if( !execute(message, channel, user) ) {
                console('SLACK: Unknown command: '+message.text);
            }
        } catch( e ) {
            console.log(e);
            console.log(message.text);
        }
    }

    var slack = new Slack(slackOpts.token, slackOpts.autoReconnect, slackOpts.automark);

    slack
      .on('open', function(){
      channels = []
      groups = []
      unreads = slack.getUnreadCount()
      winston.info('Slack -connection open');
      // Get all the channels that bot is a member of
      /*channels = {}
      for( var id, channel in slack.channels ) {
         if( channel.is_member )
          channels[channel.id] = channel.name;
      }*/
      // Get all groups that are open and not archived
      //groups = (group.name for id, group of slack.groups when group.is_open and not group.is_archived)
    })
    .on('error', winston.error);
    .on('message', handleMessage);
    slack.login();
    /*
    var robotOpts = {
      ignoreMessageInGeneral: true,
      //mentionToRespond: true,
      //skipDMMention: false
    }
    var robot = new Robot(slackOpts, robotOpts);

    robot.listen('hello') //:results([a-z\-]+)
      .handler(function(req, res) {
      // you can get named-param inside Request object
      console.log(req.param);
      console.log("hello received");
      res.sendText("Hi There");
      // {animal: 'sheep', year: '2010'}
      // note that req.param always returns Map<string, string>
    });

    app.get('/api/v0/slack', function(req, res){
		  res.json({ok: 1});
		});*/
	}

  return this;
}

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