<commit_before>var fs     = require('fs-extra');
var path   = require('path');
var xml2js = require('xml2js');
var ig     = require('imagemagick');
var colors = require('colors');
var _      = require('underscore');
var Q      = require('q');
var argv   = require('minimist')(process.argv.slice(2));

/**
 * @var {Object} settings - names of the config file and of the splash image
 */
var settings = {};
settings.CONFIG_FILE = argv.config || 'config.xml';
settings.SPLASH_FILE = argv.splash || 'splash.png';
settings.OLD_XCODE_PATH = argv['xcode-old'] || false;

/**
 * Check which platforms are added to the project and return their splash screen names and sizes
 *
 * @param  {String} projectName
 * @return {Promise} resolves with an array of platforms
 */
var getPlatforms = function (projectName) {
  var deferred = Q.defer();
  var platforms = [];
  var xcodeFolder = '/Images.xcassets/LaunchImage.launchimage/';

  if (settings.OLD_XCODE_PATH) {
    xcodeFolder = '/Resources/splash/';
  }

  platforms.push({
    name : 'ios',
    // TODO: use async fs.exists
    isAdded : fs.existsSync('platforms/ios'),
    splashPath : 'platforms/ios/' + projectName + xcodeFolder,
    splash : [
      // iPhone
      { name: 'Default~iphone.png',            width: 320,  height: 480  },
      { name: 'Default@2x~iphone.png',         width: 640,  height: 960  },
      { name: 'Default-568h@2x~iphone.png',    width: 640,  height: 1136 },
      { name: 'Default-667h.png',              width: 750,  height: 1334 },
      { name: 'Default-736h.png',              width: 1242, height: 2208 },
      { name: 'Default-Landscape-736h.png',    width: 2208, height: 1242 },
      // iPad
      { name: 'Default-Portrait~ipad.png',     width: 768,  height: 1024 },
      { name: 'Default-Portrait@2x~ipad.png',  width: 1536, height: 2048 },
      { name: 'Default-Landscape~ipad.png',    width: 1024, height: 768  },
      { name: 'Default-Landscape@2x~ipad.png', width: 2048, height: 1536 }
    ]
  });
  platforms.push({
    name : 'android',
    isAdded : fs.existsSync('platforms/android'),
    splashPath : 'platforms/android/res/',
    splash : [
      // Landscape
      { name: 'drawable-land-ldpi/screen.png',  width: 320,  height: 200  },
      { name: 'drawable-land-mdpi/screen.png',  width: 480,  height: 320  },
      { name: 'drawable-land-hdpi/screen.png',  width: 800,  height: 480  },
      { name: 'drawable-land-xhdpi/screen.png', width: 1280, height: 720  },
      { name: 'drawable-land-xxhdpi/screen.png', width: 1600, height: 960  },
      { name: 'drawable-land-xxxhdpi/screen.png', width: 1920, height: 1280  },
      // Portrait
      { name: 'drawable-port-ldpi/screen.png',  width: 200,  height: 320  },
      { name: 'drawable-port-mdpi/screen.png',  width: 320,  height: 480  },
      { name: 'drawable-port-hdpi/screen.png',  width: 480,  height: 800  },
      { name: 'drawable-port-xhdpi/screen.png', width: 720,  height: 1280 },
      { name: 'drawable-port-xxhdpi/screen.png', width: 960, height: 1600  },
      { name: 'drawable-port-xxxhdpi/screen.png', width: 1280, height: 1920  }
    ]
  });
  platforms.push({
    name : 'windows',
    isAdded : fs.existsSync('platforms/windows'),
    splashPath : 'platforms/windows/images/',
    splash : [
      // Landscape
      { name: 'SplashScreen.scale-100.png', width: 620,  height: 300  },
      { name: 'SplashScreen.scale-125.png', width: 775,  height: 375  },
      { name: 'SplashScreen.scale-150.png', width: 930,  height: 450  },
      { name: 'SplashScreen.scale-200.png', width: 1240, height: 600  },
      { name: 'SplashScreen.scale-400.png', width: 2480, height: 1200 },
      // Portrait
      { name: 'SplashScreenPhone.scale-240.png', width: 1152,  height: 1920  },
      { name: 'SplashScreenPhone.scale-140.png', width: 672,  height: 1120  },
      { name: 'SplashScreenPhone.scale-100.png', width: 480,  height: 800  }
    ]
  });
  deferred.resolve(platforms);
  return deferred.promise;
};

/**
 * @var {Object} console utils
 */
var display = {};
display.success = function (str) {
  str = '✓  '.green + str;
  console.log('  ' + str);
};
display.error = function (str) {
  str = '✗  '.red + str;
  console.log('  ' + str);
};
display.header = function (str) {
  console.log('');
  console.log(' ' + str.cyan.underline);
  console.log('');
};

/**
 * read the config file and get the project name
 *
 * @return {Promise} resolves to a string - the project's name
 */
var getProjectName = function () {
  var deferred = Q.defer();
  var parser = new xml2js.Parser();
  fs.readFile(settings.CONFIG_FILE, function (err, data) {
    if (err) {
      deferred.reject(err);
    }
    parser.parseString(data, function (err, result) {
      if (err) {
        deferred.reject(err);
      }
      var projectName = result.widget.name[0];
      deferred.resolve(projectName);
    });
  });
  return deferred.promise;
};

/**
 * Crops and creates a new splash in the platform's folder.
 *
 * @param  {Object} platform
 * @param  {Object} splash
 * @return {Promise}
 */
var generateSplash = function (platform, splash) {
  var deferred = Q.defer();
  var srcPath = settings.SPLASH_FILE;
  var platformPath = srcPath.replace(/\.png$/, '-' + platform.name + '.png');
  if (fs.existsSync(platformPath)) {
    srcPath = platformPath;
  }
  var dstPath = platform.splashPath + splash.name;
  var dst = path.dirname(dstPath);
  if (!fs.existsSync(dst)) {
    fs.mkdirsSync(dst);
  }
  ig.crop({
    srcPath: srcPath,
    dstPath: dstPath,
    quality: 1,
    format: 'png',
    width: splash.width,
    height: splash.height
  } , function(err, stdout, stderr){
    if (err) {
      deferred.reject(err);
    } else {
      deferred.resolve();
      display.success(splash.name + ' created');
    }
  });
  return deferred.promise;
};

/**
 * Generates splash based on the platform object
 *
 * @param  {Object} platform
 * @return {Promise}
 */
var generateSplashForPlatform = function (platform) {
  var deferred = Q.defer();
  display.header('Generating splash screen for ' + platform.name);
  var all = [];
  var splashes = platform.splash;
  splashes.forEach(function (splash) {
    all.push(generateSplash(platform, splash));
  });
  Q.all(all).then(function () {
    deferred.resolve();
  }).catch(function (err) {
    console.log(err);
  });
  return deferred.promise;
};

/**
 * Goes over all the platforms and triggers splash screen generation
 *
 * @param  {Array} platforms
 * @return {Promise}
 */
var generateSplashes = function (platforms) {
  var deferred = Q.defer();
  var sequence = Q();
  var all = [];
  _(platforms).where({ isAdded : true }).forEach(function (platform) {
    sequence = sequence.then(function () {
      return generateSplashForPlatform(platform);
    });
    all.push(sequence);
  });
  Q.all(all).then(function () {
    deferred.resolve();
  });
  return deferred.promise;
};

/**
 * Checks if at least one platform was added to the project
 *
 * @return {Promise} resolves if at least one platform was found, rejects otherwise
 */
var atLeastOnePlatformFound = function () {
  var deferred = Q.defer();
  getPlatforms().then(function (platforms) {
    var activePlatforms = _(platforms).where({ isAdded : true });
    if (activePlatforms.length > 0) {
      display.success('platforms found: ' + _(activePlatforms).pluck('name').join(', '));
      deferred.resolve();
    } else {
      display.error(
        'No cordova platforms found. ' +
        'Make sure you are in the root folder of your Cordova project ' +
        'and add platforms with \'cordova platform add\''
      );
      deferred.reject();
    }
  });
  return deferred.promise;
};

/**
 * Checks if a valid splash file exists
 *
 * @return {Promise} resolves if exists, rejects otherwise
 */
var validSplashExists = function () {
  var deferred = Q.defer();
  fs.exists(settings.SPLASH_FILE, function (exists) {
    if (exists) {
      display.success(settings.SPLASH_FILE + ' exists');
      deferred.resolve();
    } else {
      display.error(settings.SPLASH_FILE + ' does not exist');
      deferred.reject();
    }
  });
  return deferred.promise;
};

/**
 * Checks if a config.xml file exists
 *
 * @return {Promise} resolves if exists, rejects otherwise
 */
var configFileExists = function () {
  var deferred = Q.defer();
  fs.exists(settings.CONFIG_FILE, function (exists) {
    if (exists) {
      display.success(settings.CONFIG_FILE + ' exists');
      deferred.resolve();
    } else {
      display.error('cordova\'s ' + settings.CONFIG_FILE + ' does not exist');
      deferred.reject();
    }
  });
  return deferred.promise;
};

display.header('Checking Project & Splash');

atLeastOnePlatformFound()
.then(validSplashExists)
.then(configFileExists)
.then(getProjectName)
.then(getPlatforms)
.then(generateSplashes)
.catch(function (err) {
  if (err) {
    console.log(err);
  }
}).then(function () {
  console.log('');
});
<commit_msg>add missing windows 8.1 splashes<commit_after>var fs     = require('fs-extra');
var path   = require('path');
var xml2js = require('xml2js');
var ig     = require('imagemagick');
var colors = require('colors');
var _      = require('underscore');
var Q      = require('q');
var argv   = require('minimist')(process.argv.slice(2));

/**
 * @var {Object} settings - names of the config file and of the splash image
 */
var settings = {};
settings.CONFIG_FILE = argv.config || 'config.xml';
settings.SPLASH_FILE = argv.splash || 'splash.png';
settings.OLD_XCODE_PATH = argv['xcode-old'] || false;

/**
 * Check which platforms are added to the project and return their splash screen names and sizes
 *
 * @param  {String} projectName
 * @return {Promise} resolves with an array of platforms
 */
var getPlatforms = function (projectName) {
  var deferred = Q.defer();
  var platforms = [];
  var xcodeFolder = '/Images.xcassets/LaunchImage.launchimage/';

  if (settings.OLD_XCODE_PATH) {
    xcodeFolder = '/Resources/splash/';
  }

  platforms.push({
    name : 'ios',
    // TODO: use async fs.exists
    isAdded : fs.existsSync('platforms/ios'),
    splashPath : 'platforms/ios/' + projectName + xcodeFolder,
    splash : [
      // iPhone
      { name: 'Default~iphone.png',            width: 320,  height: 480  },
      { name: 'Default@2x~iphone.png',         width: 640,  height: 960  },
      { name: 'Default-568h@2x~iphone.png',    width: 640,  height: 1136 },
      { name: 'Default-667h.png',              width: 750,  height: 1334 },
      { name: 'Default-736h.png',              width: 1242, height: 2208 },
      { name: 'Default-Landscape-736h.png',    width: 2208, height: 1242 },
      // iPad
      { name: 'Default-Portrait~ipad.png',     width: 768,  height: 1024 },
      { name: 'Default-Portrait@2x~ipad.png',  width: 1536, height: 2048 },
      { name: 'Default-Landscape~ipad.png',    width: 1024, height: 768  },
      { name: 'Default-Landscape@2x~ipad.png', width: 2048, height: 1536 }
    ]
  });
  platforms.push({
    name : 'android',
    isAdded : fs.existsSync('platforms/android'),
    splashPath : 'platforms/android/res/',
    splash : [
      // Landscape
      { name: 'drawable-land-ldpi/screen.png',  width: 320,  height: 200  },
      { name: 'drawable-land-mdpi/screen.png',  width: 480,  height: 320  },
      { name: 'drawable-land-hdpi/screen.png',  width: 800,  height: 480  },
      { name: 'drawable-land-xhdpi/screen.png', width: 1280, height: 720  },
      { name: 'drawable-land-xxhdpi/screen.png', width: 1600, height: 960  },
      { name: 'drawable-land-xxxhdpi/screen.png', width: 1920, height: 1280  },
      // Portrait
      { name: 'drawable-port-ldpi/screen.png',  width: 200,  height: 320  },
      { name: 'drawable-port-mdpi/screen.png',  width: 320,  height: 480  },
      { name: 'drawable-port-hdpi/screen.png',  width: 480,  height: 800  },
      { name: 'drawable-port-xhdpi/screen.png', width: 720,  height: 1280 },
      { name: 'drawable-port-xxhdpi/screen.png', width: 960, height: 1600  },
      { name: 'drawable-port-xxxhdpi/screen.png', width: 1280, height: 1920  }
    ]
  });
  platforms.push({
    name : 'windows',
    isAdded : fs.existsSync('platforms/windows'),
    splashPath : 'platforms/windows/images/',
    splash : [
      // Landscape
      { name: 'SplashScreen.scale-100.png', width: 620,  height: 300  },
      { name: 'SplashScreen.scale-125.png', width: 775,  height: 375  },
      { name: 'SplashScreen.scale-140.png', width: 868,  height: 420  },
      { name: 'SplashScreen.scale-150.png', width: 930,  height: 450  },
      { name: 'SplashScreen.scale-180.png', width: 1116,  height: 540  },
      { name: 'SplashScreen.scale-200.png', width: 1240, height: 600  },
      { name: 'SplashScreen.scale-400.png', width: 2480, height: 1200 },
      // Portrait
      { name: 'SplashScreenPhone.scale-240.png', width: 1152,  height: 1920  },
      { name: 'SplashScreenPhone.scale-140.png', width: 672,  height: 1120  },
      { name: 'SplashScreenPhone.scale-100.png', width: 480,  height: 800  }
    ]
  });
  deferred.resolve(platforms);
  return deferred.promise;
};

/**
 * @var {Object} console utils
 */
var display = {};
display.success = function (str) {
  str = '✓  '.green + str;
  console.log('  ' + str);
};
display.error = function (str) {
  str = '✗  '.red + str;
  console.log('  ' + str);
};
display.header = function (str) {
  console.log('');
  console.log(' ' + str.cyan.underline);
  console.log('');
};

/**
 * read the config file and get the project name
 *
 * @return {Promise} resolves to a string - the project's name
 */
var getProjectName = function () {
  var deferred = Q.defer();
  var parser = new xml2js.Parser();
  fs.readFile(settings.CONFIG_FILE, function (err, data) {
    if (err) {
      deferred.reject(err);
    }
    parser.parseString(data, function (err, result) {
      if (err) {
        deferred.reject(err);
      }
      var projectName = result.widget.name[0];
      deferred.resolve(projectName);
    });
  });
  return deferred.promise;
};

/**
 * Crops and creates a new splash in the platform's folder.
 *
 * @param  {Object} platform
 * @param  {Object} splash
 * @return {Promise}
 */
var generateSplash = function (platform, splash) {
  var deferred = Q.defer();
  var srcPath = settings.SPLASH_FILE;
  var platformPath = srcPath.replace(/\.png$/, '-' + platform.name + '.png');
  if (fs.existsSync(platformPath)) {
    srcPath = platformPath;
  }
  var dstPath = platform.splashPath + splash.name;
  var dst = path.dirname(dstPath);
  if (!fs.existsSync(dst)) {
    fs.mkdirsSync(dst);
  }
  ig.crop({
    srcPath: srcPath,
    dstPath: dstPath,
    quality: 1,
    format: 'png',
    width: splash.width,
    height: splash.height
  } , function(err, stdout, stderr){
    if (err) {
      deferred.reject(err);
    } else {
      deferred.resolve();
      display.success(splash.name + ' created');
    }
  });
  return deferred.promise;
};

/**
 * Generates splash based on the platform object
 *
 * @param  {Object} platform
 * @return {Promise}
 */
var generateSplashForPlatform = function (platform) {
  var deferred = Q.defer();
  display.header('Generating splash screen for ' + platform.name);
  var all = [];
  var splashes = platform.splash;
  splashes.forEach(function (splash) {
    all.push(generateSplash(platform, splash));
  });
  Q.all(all).then(function () {
    deferred.resolve();
  }).catch(function (err) {
    console.log(err);
  });
  return deferred.promise;
};

/**
 * Goes over all the platforms and triggers splash screen generation
 *
 * @param  {Array} platforms
 * @return {Promise}
 */
var generateSplashes = function (platforms) {
  var deferred = Q.defer();
  var sequence = Q();
  var all = [];
  _(platforms).where({ isAdded : true }).forEach(function (platform) {
    sequence = sequence.then(function () {
      return generateSplashForPlatform(platform);
    });
    all.push(sequence);
  });
  Q.all(all).then(function () {
    deferred.resolve();
  });
  return deferred.promise;
};

/**
 * Checks if at least one platform was added to the project
 *
 * @return {Promise} resolves if at least one platform was found, rejects otherwise
 */
var atLeastOnePlatformFound = function () {
  var deferred = Q.defer();
  getPlatforms().then(function (platforms) {
    var activePlatforms = _(platforms).where({ isAdded : true });
    if (activePlatforms.length > 0) {
      display.success('platforms found: ' + _(activePlatforms).pluck('name').join(', '));
      deferred.resolve();
    } else {
      display.error(
        'No cordova platforms found. ' +
        'Make sure you are in the root folder of your Cordova project ' +
        'and add platforms with \'cordova platform add\''
      );
      deferred.reject();
    }
  });
  return deferred.promise;
};

/**
 * Checks if a valid splash file exists
 *
 * @return {Promise} resolves if exists, rejects otherwise
 */
var validSplashExists = function () {
  var deferred = Q.defer();
  fs.exists(settings.SPLASH_FILE, function (exists) {
    if (exists) {
      display.success(settings.SPLASH_FILE + ' exists');
      deferred.resolve();
    } else {
      display.error(settings.SPLASH_FILE + ' does not exist');
      deferred.reject();
    }
  });
  return deferred.promise;
};

/**
 * Checks if a config.xml file exists
 *
 * @return {Promise} resolves if exists, rejects otherwise
 */
var configFileExists = function () {
  var deferred = Q.defer();
  fs.exists(settings.CONFIG_FILE, function (exists) {
    if (exists) {
      display.success(settings.CONFIG_FILE + ' exists');
      deferred.resolve();
    } else {
      display.error('cordova\'s ' + settings.CONFIG_FILE + ' does not exist');
      deferred.reject();
    }
  });
  return deferred.promise;
};

display.header('Checking Project & Splash');

atLeastOnePlatformFound()
.then(validSplashExists)
.then(configFileExists)
.then(getProjectName)
.then(getPlatforms)
.then(generateSplashes)
.catch(function (err) {
  if (err) {
    console.log(err);
  }
}).then(function () {
  console.log('');
});
<|endoftext|>


<commit_before>require('dotenv').config();
var body_parser = require('body-parser');
var express = require('express');
var flip = require('flip');
var qs = require('qs');
var request = require('request');

if (!process.env.APP_CLIENT_ID || !process.env.APP_CLIENT_SECRET || !process.env.PORT) {
  console.log('Error: Specify APP_CLIENT_ID, APP_CLIENT_SECRET, and PORT in environment');
  process.exit(1);
}

var app = express();
app.use(body_parser.json());
app.use(body_parser.urlencoded({ extended: true }));

// Handle oauth flow
app.get('/oauth', function(req, res) {
  var url = 'https://slack.com/api/oauth.access?' + qs.stringify({
    client_id: process.env.APP_CLIENT_ID,
    client_secret: process.env.APP_CLIENT_SECRET,
    code: req.query.code,
  });

  request.post(url, function(err, http, body) {
    if (err) {
      res.status(500).send('ERROR: ' + err);
    } else {
      res.send('Success!');
    }
  });
});

// Respond to webhooks
app.post('/webhook', function(req, res) {
  if (!req.body.token === process.env.APP_COMMAND_TOKEN) return res.sendStatus(403);
  if (!req.body.command || !req.body.command.startsWith('/tableflip')) return res.sendStatus(200);

  var flipped_text = flip(req.body.text) || '┻━┻';
  var response_text =  '(╯°□°）╯︵ ' + flipped_text;

  // Send our actual response async
  request({
    body: {
      response_type: 'in_channel',
      text: response_text,
    },
    json: true,
    method: 'post',
    uri: req.body.response_url,
  });

  // Let Slack know we didn't timeout
  res.send();
});

app.listen(process.env.PORT, function() {
  console.log('Server up...', host, port);
});
<commit_msg>Add ping url<commit_after>require('dotenv').config();
var body_parser = require('body-parser');
var express = require('express');
var flip = require('flip');
var qs = require('qs');
var request = require('request');

if (!process.env.APP_CLIENT_ID || !process.env.APP_CLIENT_SECRET || !process.env.PORT) {
  console.log('Error: Specify APP_CLIENT_ID, APP_CLIENT_SECRET, and PORT in environment');
  process.exit(1);
}

var app = express();
app.use(body_parser.json());
app.use(body_parser.urlencoded({ extended: true }));

// Handle oauth flow
app.get('/oauth', function(req, res) {
  var url = 'https://slack.com/api/oauth.access?' + qs.stringify({
    client_id: process.env.APP_CLIENT_ID,
    client_secret: process.env.APP_CLIENT_SECRET,
    code: req.query.code,
  });

  request.post(url, function(err, http, body) {
    if (err) {
      res.status(500).send('ERROR: ' + err);
    } else {
      res.send('Success!');
    }
  });
});

// Respond to webhooks
app.post('/webhook', function(req, res) {
  if (!req.body.token === process.env.APP_COMMAND_TOKEN) return res.sendStatus(403);
  if (!req.body.command || !req.body.command.startsWith('/tableflip')) return res.sendStatus(200);

  var flipped_text = flip(req.body.text) || '┻━┻';
  var response_text =  '(╯°□°）╯︵ ' + flipped_text;

  // Send our actual response async
  request({
    body: {
      response_type: 'in_channel',
      text: response_text,
    },
    json: true,
    method: 'post',
    uri: req.body.response_url,
  });

  // Let Slack know we didn't timeout
  res.send();
});

app.listen(process.env.PORT, function() {
  console.log('Server up...', host, port);
});

app.get('health', function(req, res) {
  res.send('Ok');
});
<|endoftext|>


<commit_before>(function() {
    'use strict';
    var moduleName = 'ngCepDb';
    angular.module(moduleName, []);
    angular.module(moduleName).constant('ngCepDb', new CepDb());
})();
<commit_msg>wrong name meh<commit_after>(function() {
    'use strict';
    var moduleName = 'ngCepDb';
    angular.module(moduleName, []);
    angular.module(moduleName).constant('cepDb', new CepDb());
})();
<|endoftext|>


<commit_before>(function () {
  'use strict';

  /**
   * Function interface - Provides differences between a local and remove view.
   *
   * @external resolver
   * @function
   * @return {external:Promise.<external:Difference.<T>>} a promise resolving to a view of the difference.
   */

  var q = require('q');
  var iterator = require('./iterator');

  function deserializeDifference(deserialize, difference) {
    var deserialized = { added : [], removed : [] };
    var i;
    for (i = 0; i < difference.added.length; i++) {
      deserialized.added.push(deserialize(difference.added[i]));
    }
    for (i = 0; i < difference.removed.length; i++) {
      deserialized.removed.push(deserialize(difference.removed[i]));
    }
    return deserialized;
  }

  function fromItems(array, remote, serialize, deserialize) {
    function fetchDifference(level) {
      return remote(level).then(function (summary) {
        var it = iterator.map(iterator.fromArray(array), serialize);
        return summary.minusIterator(it);
      }).then(function (summary) {
        return summary.toDifference();
      }).then(function (diff) {
        if (diff === null) {
          return fetchDifference(level + 1);
        } else {
          return diff;
        }
      });
    }

    return function () {
      return fetchDifference(0).then(deserializeDifference.bind(null, deserialize));
    };
  }

  function fromSummarizers(local, remote, deserialize) {

    function fetchDifference(level) {
      return q.all([local(level), remote(level)]).then(function (arr) {
        return arr[1].minus(arr[0]).toDifference();
      }).then(function (diff) {
        if (diff === null) {
          return fetchDifference(level + 1);
        } else {
          return diff;
        }
      });
    }

    return function () {
      return fetchDifference(0).then(deserializeDifference.bind(null, deserialize));
    };
  }

  /**
   * @module resolver
   */
  module.exports = {

    /**
     * Creates a resolver giving the difference between two summaries.
     *
     * @function
     * @param {summarizer} local - a summarizer of the local content.
     * @param {summarizer} remote - a summarizer of the remote content.
     * @param {deserialize} deserialize - how to deserialize byte arrays to objects.
     * @return {resolver} a resolver returning differences between the provided summarizers.
     * @deprecated
     */
    fromSummarizers : fromSummarizers,

    /**
     * Creates a resolver giving the difference between a remote summarizer and local items.
     *
     * @function
     * @param {Object[]} array - the array of items in the current state.
     * @param {summarizer} remote - a summarizer of the remote content.
     * @param {serialize} serialize - a serializer for items in the array.
     * @param {deserialize} deserialize - how to deserialize byte arrays to objects.
     * @return {resolver} a resolver returning differences between the summarizer and local items.
     * @deprecated
     */
    fromItems : fromItems
  };
})();
<commit_msg>Factorized resolver algorithm in javascript<commit_after>(function () {
  'use strict';

  /**
   * Function interface - Provides differences between a local and remove view.
   *
   * @external resolver
   * @function
   * @return {external:Promise.<external:Difference.<T>>} a promise resolving to a view of the difference.
   */

  var q = require('q');
  var iterator = require('./iterator');

  function iterateOnLevelAndDeserialize(compute, deserialize) {

    function fetchDifference(level) {
      return compute(level).then(function (diff) {
        if (diff === null) {
          return fetchDifference(level + 1);
        } else {
          return diff;
        }
      });
    }

    function deserializeDifference(difference) {
      var deserialized = { added : [], removed : [] };
      var i;
      for (i = 0; i < difference.added.length; i++) {
        deserialized.added.push(deserialize(difference.added[i]));
      }
      for (i = 0; i < difference.removed.length; i++) {
        deserialized.removed.push(deserialize(difference.removed[i]));
      }
      return deserialized;
    }

    function resolve() {
      return fetchDifference(0).then(deserializeDifference);
    }

    return resolve;
  }

  function fromItems(array, remote, serialize, deserialize) {
    return iterateOnLevelAndDeserialize(function (level) {
      return remote(level).then(function (summary) {
        var it = iterator.map(iterator.fromArray(array), serialize);
        return summary.minusIterator(it);
      }).then(function (summary) {
        return summary.toDifference();
      });
    }, deserialize);
  }

  function fromSummarizers(local, remote, deserialize) {
    return iterateOnLevelAndDeserialize(function (level) {
      return q.all([local(level), remote(level)]).then(function (arr) {
        return arr[1].minus(arr[0]).toDifference();
      });
    }, deserialize);
  }

  /**
   * @module resolver
   */
  module.exports = {

    /**
     * Creates a resolver giving the difference between two summaries.
     *
     * @function
     * @param {summarizer} local - a summarizer of the local content.
     * @param {summarizer} remote - a summarizer of the remote content.
     * @param {deserialize} deserialize - how to deserialize byte arrays to objects.
     * @return {resolver} a resolver returning differences between the provided summarizers.
     * @deprecated
     */
    fromSummarizers : fromSummarizers,

    /**
     * Creates a resolver giving the difference between a remote summarizer and local items.
     *
     * @function
     * @param {Object[]} array - the array of items in the current state.
     * @param {summarizer} remote - a summarizer of the remote content.
     * @param {serialize} serialize - a serializer for items in the array.
     * @param {deserialize} deserialize - how to deserialize byte arrays to objects.
     * @return {resolver} a resolver returning differences between the summarizer and local items.
     */
    fromItems : fromItems
  };
})();
<|endoftext|>


<commit_before>const fs = require('fs');
const os = require('os');
const beep = require('beeper');
const c = require('ansi-colors');
const flog = require('fancy-log');
const through2 = require('through2');
const PluginError = require('plugin-error');
const stripJsonComments = require('strip-json-comments');
const HTMLHint = require('htmlhint').HTMLHint;

const formatOutput = function (report, file, options) {
  'use strict';
  if (report.length === 0) {
    return {
      success: true
    };
  }

  const filePath = (file.path || 'stdin');

  // Handle errors
  const messages = report.filter(err => {
    return err;
  }).map(err => {
    return {
      file: filePath,
      error: err
    };
  });

  const output = {
    errorCount: messages.length,
    success: false,
    messages,
    options
  };

  return output;
};

const htmlhintPlugin = function (options, customRules) {
  'use strict';

  const ruleset = {};

  if (!customRules && options && Array.isArray(options) && options.length > 0) {
    customRules = options;
    options = {};
  }

  if (!options) {
    options = {};
  }

  // Read Htmlhint options from a specified htmlhintrc file.
  if (typeof options === 'string') {
    // Don't catch readFile errors, let them bubble up
    options = {
      htmlhintrc: './' + options
    };
	}

	// If necessary check for required param(s), e.g. options hash, etc.
	// read config file for htmlhint if available
	if (options.htmlhintrc) {
		try {
			const externalOptions = fs.readFileSync(options.htmlhintrc, 'utf-8');
			options = JSON.parse(stripJsonComments(externalOptions));
		} catch (err) {
			throw new Error('gulp-htmlhint: Cannot parse .htmlhintrc');
		}
	}

  if (Object.keys(options).length > 0) {
    // Build a list of all available rules
    for (const key in HTMLHint.defaultRuleset) {
      if (HTMLHint.defaultRuleset.hasOwnProperty(key)) { // eslint-disable-line no-prototype-builtins
        ruleset[key] = 1;
      }
    }

    // Normalize htmlhint options
    // htmlhint only checks for rulekey, so remove rule if set to false
    for (const rule in options) {
      if (options[rule]) {
        ruleset[rule] = options[rule];
      } else {
        delete ruleset[rule];
      }
    }
  }

  // Add the defined custom rules
  // This will not require adding the costume rule id to the .htmlhintrc file
  if (customRules !== null && Array.isArray(customRules) && customRules.length > 0) {
    const has = Object.prototype.hasOwnProperty;
    for (const rule of customRules) {
      if (typeof rule === 'object') {
        HTMLHint.addRule(rule);
        if (has.call(rule, 'id')) {
          ruleset[rule.id] = true;
        }
      }
    }
  }

  return through2.obj((file, enc, cb) => {
    const report = HTMLHint.verify(file.contents.toString(), ruleset);

  // Send status down-stream
    file.htmlhint = formatOutput(report, file, options);
    cb(null, file);
  });
};

function getMessagesForFile(file) {
  'use strict';
  return file.htmlhint.messages.map(msg => {
    const message = msg.error;
    let evidence = message.evidence;
    const line = message.line;
    const col = message.col;
    let detail;

    if (line) {
      detail = c.yellow('L' + line) + c.red(':') + c.yellow('C' + col);
    } else {
      detail = c.yellow('GENERAL');
    }

    if (col === 0) {
      evidence = c.red('?') + evidence;
    } else if (col > evidence.length) {
      evidence = c.red(evidence + ' ');
    } else {
      evidence = evidence.slice(0, col - 1) + c.red(evidence[col - 1]) + evidence.slice(col);
    }

    return {
      message: c.red('[') + detail + c.red(']') + c.yellow(' ' + message.message) + ' (' + message.rule.id + ')',
      evidence
    };
  });
}

const defaultReporter = function (file) {
  'use strict';
  const errorCount = file.htmlhint.errorCount;
  const plural = errorCount === 1 ? '' : 's';

  beep();

  flog(c.cyan(errorCount) + ' error' + plural + ' found in ' + c.magenta(file.path));

  getMessagesForFile(file).forEach(data => {
    flog(data.message);
    flog(data.evidence);
  });
};

htmlhintPlugin.addRule = function (rule) {
  'use strict';
  return HTMLHint.addRule(rule);
};

htmlhintPlugin.reporter = function (customReporter, options) {
  'use strict';
  let reporter = defaultReporter;

  if (typeof customReporter === 'function') {
    reporter = customReporter;
  }

  if (typeof customReporter === 'string') {
    if (customReporter === 'fail' || customReporter === 'failOn') {
      return htmlhintPlugin.failOnError();
    } else if (customReporter === 'failAfter') {
      return htmlhintPlugin.failAfterError();
    }

    reporter = require(customReporter);
  }

  if (typeof reporter === 'undefined') {
    throw new TypeError('Invalid reporter');
  }

  return through2.obj((file, enc, cb) => {
    // Only report if HTMLHint ran and errors were found
    if (file.htmlhint && !file.htmlhint.success) {
      reporter(file, file.htmlhint.messages, options);
    }

    cb(null, file);
  });
};

htmlhintPlugin.failOnError = function (opts) {
  'use strict';
  opts = opts || {};
  return through2.obj((file, enc, cb) => {
    // Something to report and has errors
    let error;
    if (file.htmlhint && !file.htmlhint.success) {
      if (opts.suppress === true) {
        error = new PluginError('gulp-htmlhint', {
          message: 'HTMLHint failed.',
          showStack: false
        });
      } else {
        const errorCount = file.htmlhint.errorCount;
        const plural = errorCount === 1 ? '' : 's';
        const msg = c.cyan(errorCount) + ' error' + plural + ' found in ' + c.magenta(file.path);
        const messages = [msg].concat(getMessagesForFile(file).map(m => {
          return m.message;
        }));

        error = new PluginError('gulp-htmlhint', {
          message: messages.join(os.EOL),
          showStack: false
        });
      }
    }
    cb(error, file);
  });
};

htmlhintPlugin.failAfterError = function (opts) {
  'use strict';
  opts = opts || {};
  let globalErrorCount = 0;
  let globalErrorMessage = '';
  return through2.obj(check, summarize);

  function check(file, enc, cb) {
    if (file.htmlhint && !file.htmlhint.success) {
      if (opts.suppress === true) {
        globalErrorCount += file.htmlhint.errorCount;
      } else {
        globalErrorCount += file.htmlhint.errorCount;
        const plural = file.htmlhint.errorCount === 1 ? '' : 's';
        const msg = c.cyan(file.htmlhint.errorCount) + ' error' + plural + ' found in ' + c.magenta(file.path);
        const messages = [msg].concat(getMessagesForFile(file).map(m => {
          return m.message;
        }));

        globalErrorMessage += messages.join(os.EOL) + os.EOL;
      }
    }
    cb(null, file);
  }

  function summarize(cb) {
    if (!globalErrorCount) {
      cb();
      return;
    }

    const plural = globalErrorCount === 1 ? '' : 's';
    const message = globalErrorMessage ?
      c.cyan(globalErrorCount) + ' error' + plural + ' overall:' + os.EOL + globalErrorMessage :
      c.cyan(globalErrorCount) + ' error' + plural + ' overall.';

    const error = new PluginError('gulp-htmlhint', {
      message: 'HTMLHint failed. ' + message,
      showStack: false
    });
    cb(error);
  }
};

// Backward compatibility
htmlhintPlugin.failReporter = htmlhintPlugin.failOnError;

module.exports = htmlhintPlugin;
<commit_msg>linter<commit_after>const fs = require('fs');
const os = require('os');
const beep = require('beeper');
const c = require('ansi-colors');
const flog = require('fancy-log');
const through2 = require('through2');
const PluginError = require('plugin-error');
const stripJsonComments = require('strip-json-comments');
const HTMLHint = require('htmlhint').HTMLHint;

const formatOutput = function (report, file, options) {
  'use strict';
  if (report.length === 0) {
    return {
      success: true
    };
  }

  const filePath = (file.path || 'stdin');

  // Handle errors
  const messages = report.filter(err => {
    return err;
  }).map(err => {
    return {
      file: filePath,
      error: err
    };
  });

  const output = {
    errorCount: messages.length,
    success: false,
    messages,
    options
  };

  return output;
};

const htmlhintPlugin = function (options, customRules) {
  'use strict';

  const ruleset = {};

  if (!customRules && options && Array.isArray(options) && options.length > 0) {
    customRules = options;
    options = {};
  }

  if (!options) {
    options = {};
  }

  // Read Htmlhint options from a specified htmlhintrc file.
  if (typeof options === 'string') {
    // Don't catch readFile errors, let them bubble up
    options = {
      htmlhintrc: './' + options
    };
  }

	// If necessary check for required param(s), e.g. options hash, etc.
	// read config file for htmlhint if available
  if (options.htmlhintrc) {
    try {
      const externalOptions = fs.readFileSync(options.htmlhintrc, 'utf-8');
      options = JSON.parse(stripJsonComments(externalOptions));
    } catch (err) {
      throw new Error('gulp-htmlhint: Cannot parse .htmlhintrc');
    }
  }

  if (Object.keys(options).length > 0) {
    // Build a list of all available rules
    for (const key in HTMLHint.defaultRuleset) {
      if (HTMLHint.defaultRuleset.hasOwnProperty(key)) { // eslint-disable-line no-prototype-builtins
        ruleset[key] = 1;
      }
    }

    // Normalize htmlhint options
    // htmlhint only checks for rulekey, so remove rule if set to false
    for (const rule in options) {
      if (options[rule]) {
        ruleset[rule] = options[rule];
      } else {
        delete ruleset[rule];
      }
    }
  }

  // Add the defined custom rules
  // This will not require adding the costume rule id to the .htmlhintrc file
  if (customRules !== null && Array.isArray(customRules) && customRules.length > 0) {
    const has = Object.prototype.hasOwnProperty;
    for (const rule of customRules) {
      if (typeof rule === 'object') {
        HTMLHint.addRule(rule);
        if (has.call(rule, 'id')) {
          ruleset[rule.id] = true;
        }
      }
    }
  }

  return through2.obj((file, enc, cb) => {
    const report = HTMLHint.verify(file.contents.toString(), ruleset);

  // Send status down-stream
    file.htmlhint = formatOutput(report, file, options);
    cb(null, file);
  });
};

function getMessagesForFile(file) {
  'use strict';
  return file.htmlhint.messages.map(msg => {
    const message = msg.error;
    let evidence = message.evidence;
    const line = message.line;
    const col = message.col;
    let detail;

    if (line) {
      detail = c.yellow('L' + line) + c.red(':') + c.yellow('C' + col);
    } else {
      detail = c.yellow('GENERAL');
    }

    if (col === 0) {
      evidence = c.red('?') + evidence;
    } else if (col > evidence.length) {
      evidence = c.red(evidence + ' ');
    } else {
      evidence = evidence.slice(0, col - 1) + c.red(evidence[col - 1]) + evidence.slice(col);
    }

    return {
      message: c.red('[') + detail + c.red(']') + c.yellow(' ' + message.message) + ' (' + message.rule.id + ')',
      evidence
    };
  });
}

const defaultReporter = function (file) {
  'use strict';
  const errorCount = file.htmlhint.errorCount;
  const plural = errorCount === 1 ? '' : 's';

  beep();

  flog(c.cyan(errorCount) + ' error' + plural + ' found in ' + c.magenta(file.path));

  getMessagesForFile(file).forEach(data => {
    flog(data.message);
    flog(data.evidence);
  });
};

htmlhintPlugin.addRule = function (rule) {
  'use strict';
  return HTMLHint.addRule(rule);
};

htmlhintPlugin.reporter = function (customReporter, options) {
  'use strict';
  let reporter = defaultReporter;

  if (typeof customReporter === 'function') {
    reporter = customReporter;
  }

  if (typeof customReporter === 'string') {
    if (customReporter === 'fail' || customReporter === 'failOn') {
      return htmlhintPlugin.failOnError();
    } else if (customReporter === 'failAfter') {
      return htmlhintPlugin.failAfterError();
    }

    reporter = require(customReporter);
  }

  if (typeof reporter === 'undefined') {
    throw new TypeError('Invalid reporter');
  }

  return through2.obj((file, enc, cb) => {
    // Only report if HTMLHint ran and errors were found
    if (file.htmlhint && !file.htmlhint.success) {
      reporter(file, file.htmlhint.messages, options);
    }

    cb(null, file);
  });
};

htmlhintPlugin.failOnError = function (opts) {
  'use strict';
  opts = opts || {};
  return through2.obj((file, enc, cb) => {
    // Something to report and has errors
    let error;
    if (file.htmlhint && !file.htmlhint.success) {
      if (opts.suppress === true) {
        error = new PluginError('gulp-htmlhint', {
          message: 'HTMLHint failed.',
          showStack: false
        });
      } else {
        const errorCount = file.htmlhint.errorCount;
        const plural = errorCount === 1 ? '' : 's';
        const msg = c.cyan(errorCount) + ' error' + plural + ' found in ' + c.magenta(file.path);
        const messages = [msg].concat(getMessagesForFile(file).map(m => {
          return m.message;
        }));

        error = new PluginError('gulp-htmlhint', {
          message: messages.join(os.EOL),
          showStack: false
        });
      }
    }
    cb(error, file);
  });
};

htmlhintPlugin.failAfterError = function (opts) {
  'use strict';
  opts = opts || {};
  let globalErrorCount = 0;
  let globalErrorMessage = '';
  return through2.obj(check, summarize);

  function check(file, enc, cb) {
    if (file.htmlhint && !file.htmlhint.success) {
      if (opts.suppress === true) {
        globalErrorCount += file.htmlhint.errorCount;
      } else {
        globalErrorCount += file.htmlhint.errorCount;
        const plural = file.htmlhint.errorCount === 1 ? '' : 's';
        const msg = c.cyan(file.htmlhint.errorCount) + ' error' + plural + ' found in ' + c.magenta(file.path);
        const messages = [msg].concat(getMessagesForFile(file).map(m => {
          return m.message;
        }));

        globalErrorMessage += messages.join(os.EOL) + os.EOL;
      }
    }
    cb(null, file);
  }

  function summarize(cb) {
    if (!globalErrorCount) {
      cb();
      return;
    }

    const plural = globalErrorCount === 1 ? '' : 's';
    const message = globalErrorMessage ?
      c.cyan(globalErrorCount) + ' error' + plural + ' overall:' + os.EOL + globalErrorMessage :
      c.cyan(globalErrorCount) + ' error' + plural + ' overall.';

    const error = new PluginError('gulp-htmlhint', {
      message: 'HTMLHint failed. ' + message,
      showStack: false
    });
    cb(error);
  }
};

// Backward compatibility
htmlhintPlugin.failReporter = htmlhintPlugin.failOnError;

module.exports = htmlhintPlugin;
<|endoftext|>


<commit_before>// Credit to:
// https://coderwall.com/p/fhgu_q/inlining-images-with-gulp-sass

var fs    = require('fs');
var path  = require('path');
var types = require('node-sass').types;

module.exports = function(options) {
    options = options || {};

    var base = options.base || process.cwd();
    return {
        'inline-image($file)': function(file, done) {
            // we want to file relative to the base
            var relativePath = './' + file.getValue();
            var filePath = path.resolve(base, relativePath);

            // get the file ext
            var ext = filePath.split('.').pop();

            // read the file
            fs.readFile(filePath, function(err, data) {
                if (err) { return done(err); }

                // base 64 encode the data
                var img = (new Buffer(data)).toString('base64');
                // format the string...
                var dataImg = 'data:image/' + ext + ';base64,' + img;
                // ...win
                done(types.String(dataImg));
            });
        }
    };
};
<commit_msg>support svgs<commit_after>// Credit to:
// https://coderwall.com/p/fhgu_q/inlining-images-with-gulp-sass
var fs    = require('fs');
var path  = require('path');
var types = require('node-sass').types;

var svg = function(buffer) {
    var svg = buffer.toString()
        .replace(/\n/g, '')
        .replace(/\r/g, '')
        .replace(/\#/g, '%23')
        .replace(/\"/g, "'");

    return '"data:image/svg+xml;utf8,' + svg + '"';
};

var img = function(buffer, ext) {
    return '"data:image/' + ext + ';base64,' + buffer.toString('base64') + '"';
};

module.exports = function(options) {
    options = options || {};

    var base = options.base || process.cwd();
    return {
        'inline-image($file)': function(file, done) {
            // we want to file relative to the base
            var relativePath = './' + file.getValue();
            var filePath = path.resolve(base, relativePath);

            // get the file ext
            var ext = filePath.split('.').pop();

            // read the file
            fs.readFile(filePath, function(err, data) {
                if (err) { return done(err); }
                var buffer = new Buffer(data);
                var str = ext === 'svg' ? svg(buffer, ext) : img(buffer, ext);
                done(types.String(str));
            });
        }
    };
};
<|endoftext|>


<commit_before>var creds = require(process.env.HOME + '/.googlePlayCreds.json');
var PlayMusic = require('playmusic');
var mkdirp = require('mkdirp');
var https = require('https');
var fs = require('fs');
var ffmpeg = require('fluent-ffmpeg');
var stream = require('stream');

var config, player;

var gmusicBackend = {};
gmusicBackend.name = 'gmusic';

// TODO: seeking
var encodeSong = function(origStream, seek, songID, progCallback, errCallback) {
    var incompletePath = config.songCachePath + '/gmusic/incomplete/' + songID + '.opus';
    var incompleteStream = fs.createWriteStream(incompletePath, {flags: 'w'});
    var encodedPath = config.songCachePath + '/gmusic/' + songID + '.opus';

    var command = ffmpeg(origStream)
        .noVideo()
        //.inputFormat('mp3')
        //.inputOption('-ac 2')
        .audioCodec('libopus')
        .audioBitrate('192')
        .format('opus')
        .on('error', function(err) {
            console.log('gmusic: error while transcoding ' + songID + ': ' + err);
            if(fs.existsSync(incompletePath))
                fs.unlinkSync(incompletePath);
            errCallback(err);
        })

    var opusStream = command.pipe(null, {end: true});
    opusStream.on('data', function(chunk) {
        incompleteStream.write(chunk, undefined, function() {
            progCallback(chunk.length, false);
        });
    });
    opusStream.on('end', function() {
        incompleteStream.end(undefined, undefined, function() {
            console.log('transcoding ended for ' + songID);

            // TODO: we don't know if transcoding ended successfully or not,
            // and there might be a race condition between errCallback deleting
            // the file and us trying to move it to the songCache

            // atomically move result to encodedPath
            if(fs.existsSync(incompletePath)) {
                fs.renameSync(incompletePath, encodedPath);
                progCallback(0, true);
            } else {
                progCallback(0, false);
            }
        });
    });

    console.log('transcoding ' + songID + '...');
    return function(err) {
        command.kill();
        console.log('gmusic: canceled preparing: ' + songID + ': ' + err);
        if(fs.existsSync(incompletePath))
            fs.unlinkSync(incompletePath);
        errCallback('canceled preparing: ' + songID + ': ' + err);
    };
};

var gmusicDownload = function(songID, progCallback, errCallback) {
    var req = null;
    var gmusicStream = new stream.PassThrough();

    var doDownload = function(streamUrl) {
        if(streamUrl) {
            console.log('gmusic: downloading song ' + songID);

            req = https.request(streamUrl, function(res) {
                res.pipe(gmusicStream, {end: false});

                res.on('end', function() {
                    if(res.statusCode === 302) { // redirect
                        console.log('gmusic: redirected. retrying with new URL');
                        res.unpipe();
                        doDownload(res.headers.location);
                    } else if (res.statusCode === 200) {
                        console.log('gmusic: download finished');
                        gmusicStream.end();
                    } else {
                        gmusicStream.end();
                        console.log('ERROR in gmusic: unknown status code ' + res.statusCode);
                        if(errCallback)
                            errCallback('unknown status code ' + res.statusCode);
                    }
                });
            });
            req.on('error', function(e) {
                console.log('ERROR in gmusic: ' + e + ' while fetching! reconnecting in 5s...');
                setTimeout(function() {
                    gmusicBackend.init(function() {
                        console.log('ERROR in gmusic while fetching! now reconnected to gmusic');
                        gmusicBackend.pm.getStreamUrl(songID, function(streamUrl) {
                            doDownload(streamUrl);
                        }, function(err) {
                            errCallback(err);
                        });
                    });
                }, 5000);
            });
            req.end();
        } else {
            gmusicBackend.pm.getStreamUrl(songID, function(streamUrl) {
                doDownload(streamUrl);
            }, function(err) {
                errCallback(err);
            });
        }
    };

    /*
    return function(err) {
        // TODO: this doesn't seem to work very well...
        if(command)
            command.kill();
        if(req)
            req.abort();

        if(fs.existsSync(filePath))
            fs.unlinkSync(filePath);
        if(fs.existsSync(incompleteFilePath))
            fs.unlinkSync(incompleteFilePath);

        console.log('gmusic: canceled preparing: ' + songID + ': ' + err);

        errCallback();
    };
    */

    doDownload();

    var cancelEncoding = encodeSong(gmusicStream, 0, songID, progCallback, errCallback);
    return function(err) {
        if(req)
            req.abort();
        cancelEncoding(err);
    };
};

// cache songID to disk.
// on success: progCallback must be called with true as argument
// on failure: errCallback must be called with error message
// returns a function that cancels preparing
gmusicBackend.prepareSong = function(songID, progCallback, errCallback) {
    var filePath = config.songCachePath + '/gmusic/' + songID + '.opus';

    if(fs.existsSync(filePath)) {
        // true as first argument because there is song data
        progCallback(true, true);
    } else {
        return gmusicDownload(songID, progCallback, errCallback);
    }
};

// search for music from the backend
// on success: callback must be called with a list of song objects
// on failure: errCallback must be called with error message
gmusicBackend.search = function(query, callback, errCallback) {
    gmusicBackend.pm.search(query.terms, config.searchResultCnt, function(data) {
        var songs;
        var results = {};
        results.songs = {};

        if(data.entries) {
            songs = data.entries.filter(function(entry) {
                return entry.type === '1'; // songs only, no albums/artists
            });

            for(var i = 0; i < songs.length; i++) {
                results.songs[songs[i].track.nid] = {
                    artist: songs[i].track.artist,
                    title: songs[i].track.title,
                    album: songs[i].track.album,
                    albumArt: null, // TODO: can we add this?
                    duration: songs[i].track.durationMillis,
                    songID: songs[i].track.nid,
                    score: songs[i].score,
                    backendName: 'gmusic',
                    format: 'opus'
                };
            }
        }

        callback(results);
    }, function(err) {
        errCallback('error while searching gmusic: ' + err);
    });
};

// called when partyplay is started to initialize the backend
// do any necessary initialization here
gmusicBackend.init = function(_player, callback) {
    player = _player;
    config = _player.config;

    mkdirp(config.songCachePath + '/gmusic/incomplete');

    // initialize google play music backend
    gmusicBackend.pm = new PlayMusic();
    gmusicBackend.pm.init(creds, callback);
};

module.exports = gmusicBackend;
<commit_msg>add isPrepared function for checking if a song is prepared<commit_after>var creds = require(process.env.HOME + '/.googlePlayCreds.json');
var PlayMusic = require('playmusic');
var mkdirp = require('mkdirp');
var https = require('https');
var fs = require('fs');
var ffmpeg = require('fluent-ffmpeg');
var stream = require('stream');

var config, player;

var gmusicBackend = {};
gmusicBackend.name = 'gmusic';

// TODO: seeking
var encodeSong = function(origStream, seek, songID, progCallback, errCallback) {
    var incompletePath = config.songCachePath + '/gmusic/incomplete/' + songID + '.opus';
    var incompleteStream = fs.createWriteStream(incompletePath, {flags: 'w'});
    var encodedPath = config.songCachePath + '/gmusic/' + songID + '.opus';

    var command = ffmpeg(origStream)
        .noVideo()
        //.inputFormat('mp3')
        //.inputOption('-ac 2')
        .audioCodec('libopus')
        .audioBitrate('192')
        .format('opus')
        .on('error', function(err) {
            console.log('gmusic: error while transcoding ' + songID + ': ' + err);
            if(fs.existsSync(incompletePath))
                fs.unlinkSync(incompletePath);
            errCallback(err);
        })

    var opusStream = command.pipe(null, {end: true});
    opusStream.on('data', function(chunk) {
        incompleteStream.write(chunk, undefined, function() {
            progCallback(chunk.length, false);
        });
    });
    opusStream.on('end', function() {
        incompleteStream.end(undefined, undefined, function() {
            console.log('transcoding ended for ' + songID);

            // TODO: we don't know if transcoding ended successfully or not,
            // and there might be a race condition between errCallback deleting
            // the file and us trying to move it to the songCache

            // atomically move result to encodedPath
            if(fs.existsSync(incompletePath)) {
                fs.renameSync(incompletePath, encodedPath);
                progCallback(0, true);
            } else {
                progCallback(0, false);
            }
        });
    });

    console.log('transcoding ' + songID + '...');
    return function(err) {
        command.kill();
        console.log('gmusic: canceled preparing: ' + songID + ': ' + err);
        if(fs.existsSync(incompletePath))
            fs.unlinkSync(incompletePath);
        errCallback('canceled preparing: ' + songID + ': ' + err);
    };
};

var gmusicDownload = function(songID, progCallback, errCallback) {
    var req = null;
    var gmusicStream = new stream.PassThrough();

    var doDownload = function(streamUrl) {
        if(streamUrl) {
            console.log('gmusic: downloading song ' + songID);

            req = https.request(streamUrl, function(res) {
                res.pipe(gmusicStream, {end: false});

                res.on('end', function() {
                    if(res.statusCode === 302) { // redirect
                        console.log('gmusic: redirected. retrying with new URL');
                        res.unpipe();
                        doDownload(res.headers.location);
                    } else if (res.statusCode === 200) {
                        console.log('gmusic: download finished');
                        gmusicStream.end();
                    } else {
                        gmusicStream.end();
                        console.log('ERROR in gmusic: unknown status code ' + res.statusCode);
                        if(errCallback)
                            errCallback('unknown status code ' + res.statusCode);
                    }
                });
            });
            req.on('error', function(e) {
                console.log('ERROR in gmusic: ' + e + ' while fetching! reconnecting in 5s...');
                setTimeout(function() {
                    gmusicBackend.init(function() {
                        console.log('ERROR in gmusic while fetching! now reconnected to gmusic');
                        gmusicBackend.pm.getStreamUrl(songID, function(streamUrl) {
                            doDownload(streamUrl);
                        }, function(err) {
                            errCallback(err);
                        });
                    });
                }, 5000);
            });
            req.end();
        } else {
            gmusicBackend.pm.getStreamUrl(songID, function(streamUrl) {
                doDownload(streamUrl);
            }, function(err) {
                errCallback(err);
            });
        }
    };

    /*
    return function(err) {
        // TODO: this doesn't seem to work very well...
        if(command)
            command.kill();
        if(req)
            req.abort();

        if(fs.existsSync(filePath))
            fs.unlinkSync(filePath);
        if(fs.existsSync(incompleteFilePath))
            fs.unlinkSync(incompleteFilePath);

        console.log('gmusic: canceled preparing: ' + songID + ': ' + err);

        errCallback();
    };
    */

    doDownload();

    var cancelEncoding = encodeSong(gmusicStream, 0, songID, progCallback, errCallback);
    return function(err) {
        if(req)
            req.abort();
        cancelEncoding(err);
    };
};

// cache songID to disk.
// on success: progCallback must be called with true as argument
// on failure: errCallback must be called with error message
// returns a function that cancels preparing
gmusicBackend.prepareSong = function(songID, progCallback, errCallback) {
    var filePath = config.songCachePath + '/gmusic/' + songID + '.opus';

    if(fs.existsSync(filePath)) {
        // true as first argument because there is song data
        progCallback(true, true);
    } else {
        return gmusicDownload(songID, progCallback, errCallback);
    }
};

gmusicBackend.isPrepared = function(songID) {
    var filePath = config.songCachePath + '/gmusic/' + songID + '.opus';
    return fs.existsSync(filePath);
};

// search for music from the backend
// on success: callback must be called with a list of song objects
// on failure: errCallback must be called with error message
gmusicBackend.search = function(query, callback, errCallback) {
    gmusicBackend.pm.search(query.terms, config.searchResultCnt, function(data) {
        var songs;
        var results = {};
        results.songs = {};

        if(data.entries) {
            songs = data.entries.filter(function(entry) {
                return entry.type === '1'; // songs only, no albums/artists
            });

            for(var i = 0; i < songs.length; i++) {
                results.songs[songs[i].track.nid] = {
                    artist: songs[i].track.artist,
                    title: songs[i].track.title,
                    album: songs[i].track.album,
                    albumArt: null, // TODO: can we add this?
                    duration: songs[i].track.durationMillis,
                    songID: songs[i].track.nid,
                    score: songs[i].score,
                    backendName: 'gmusic',
                    format: 'opus'
                };
            }
        }

        callback(results);
    }, function(err) {
        errCallback('error while searching gmusic: ' + err);
    });
};

// called when partyplay is started to initialize the backend
// do any necessary initialization here
gmusicBackend.init = function(_player, callback) {
    player = _player;
    config = _player.config;

    mkdirp(config.songCachePath + '/gmusic/incomplete');

    // initialize google play music backend
    gmusicBackend.pm = new PlayMusic();
    gmusicBackend.pm.init(creds, callback);
};

module.exports = gmusicBackend;
<|endoftext|>


<commit_before>'use strict';
module.exports = cachingStream;
var duplexer = require('duplexify');
var stream = require('readable-stream');

function cachingStream() {
	var ended = false;
	var caching = true;
	var passingThrough = true;

	var cache = [];
	var copyStream = null;

	var output = noOpReader();
	var input = new stream.Writable({write: handleData})
		.once('finish', handleFlush);
	var duplex = duplexer(input, output);

	duplex.endOutput = endOutput;
	duplex.makeCopyStream = makeCopyStream;
	duplex.dropCache = dropCache;

	return duplex;

	function handleData(buffer, enc, cb) {
		if (caching) {
			var cacheCopy = passingThrough ? new Buffer(buffer) : buffer;
			if (copyStream) {
				copyStream.push(cacheCopy);
			} else {
				cache.push(cacheCopy);
			}
		}
		if (passingThrough) {
			output.push(buffer);
		}
		cb();
	}

	function handleFlush() {
		ended = true;
		if (output) {
			output.push(null);
		}
		if (caching && copyStream) {
			dropCache(true);
		}
	}

	function makeCopyStream() {
		if (copyStream) {
			throw new Error('copyStream was already created');
		}
		if (!caching) {
			throw new Error('cache has been dropped');
		}
		try {
			copyStream = noOpReader();
			cache.forEach(function (buffer) {
				copyStream.push(buffer);
			});
			return copyStream;
		} finally {
			cache = null;
			if (ended) {
				dropCache(true);
			}
		}
	}

	function endOutput() {
		passingThrough = false;
		output.push(null);
		output = null;
	}

	function dropCache(endStream) {
		cache = null;
		if (copyStream && endStream) {
			copyStream.push(null);
			copyStream = null;
		}
		if (!copyStream) {
			caching = false;
		}
	}
}

function noOp() {}

function noOpReader() {
	return new stream.Readable({read: noOp});
}
<commit_msg>refactor a bit<commit_after>'use strict';
module.exports = cachingStream;
var duplexer = require('duplexify');
var stream = require('readable-stream');

function cachingStream() {
	var ended = false;
	var hasCache = true;

	var cache = [];
	var copyStream = null;

	var output = noOpReader();
	var input = new stream.Writable({write: handleData})
		.once('finish', inputEnded);
	var duplex = duplexer(input, output);

	duplex.endOutput = endOutputStream;
	duplex.makeCopyStream = makeCopyStream;
	duplex.dropCache = dropCache;

	return duplex;

	function handleData(buffer, enc, cb) {
		if (hasCache) {
			var cacheCopy = output ? new Buffer(buffer) : buffer;
			if (copyStream) {
				copyStream.push(cacheCopy);
			} else {
				cache.push(cacheCopy);
			}
		}
		if (output) {
			output.push(buffer);
		}
		cb();
	}

	function inputEnded() {
		ended = true;
		endOutputStream();
		endCacheStream();
	}

	function makeCopyStream() {
		if (copyStream) {
			throw new Error('copyStream was already created');
		}
		if (!hasCache) {
			throw new Error('cache has been dropped');
		}
		try {
			copyStream = noOpReader();
			cache.forEach(function (buffer) {
				copyStream.push(buffer);
			});
			return copyStream;
		} finally {
			cache = null;
			if (ended) {
				endCacheStream();
			}
		}
	}

	function endOutputStream() {
		if (output) {
			output.push(null);
			output = null;
		}
	}

	function endCacheStream() {
		if (copyStream) {
			hasCache = false;
			copyStream.push(null);
			copyStream = null;
		}
	}

	function dropCacheStore() {
		cache = null;
		if (!copyStream) {
			hasCache = false;
		}
	}

	function dropCache(endStream) {
		dropCacheStore();
		if (endStream) {
			endCacheStream();
		}
	}
}

function noOp() {}

function noOpReader() {
	return new stream.Readable({read: noOp});
}
<|endoftext|>


<commit_before>/*jslint node: true, indent: 2 */
'use strict';
var restify, bunyan, routes, log, server;

restify = require('restify');
bunyan  = require('bunyan');
routes  = require('./routes/');
var namespace = require('restify-namespace');
var restifyValidator = require('restify-validator');

log = bunyan.createLogger({
  name        : 'app',
  level       : process.env.LOG_LEVEL || 'info',
  stream      : process.stdout,
  serializers : bunyan.stdSerializers
});

server = restify.createServer({
  name : 'app',
  log  : log,
  formatters : {
    'application/json' : function (req, res, body) {
      res.setHeader('Cache-Control', 'must-revalidate');

      // Does the client *explicitly* accepts application/json?
      var acceptHeader = req.header('Accept');
      var sendPlainText = (typeof acceptHeader === 'undefined' || acceptHeader.split(/, */).indexOf('application/json') === -1);

      // Send as plain text
      if (sendPlainText) {
        res.setHeader('Content-Type', 'text/plain; charset=utf-8');
      }

      // Send as JSON
      if (!sendPlainText) {
        res.setHeader('Content-Type', 'application/json; charset=utf-8');
      }
      return JSON.stringify(body);
    }
  }
});

server.use(restify.bodyParser({ mapParams: true }));
server.use(restify.queryParser());
server.use(restify.gzipResponse());
server.pre(restify.pre.sanitizePath());
server.use(restifyValidator);
server.use(restify.CORS());
server.use(restify.fullResponse());

server.on('after', restify.auditLogger({ log: log }));
namespace(server, '/v1', function() {
  routes(server);
});

console.log('Server started.');
server.listen(8888, function () {
  log.info('%s listening at %s', server.name, server.url);
});

<commit_msg>refactoring<commit_after>/*jslint node: true, indent: 2 */
'use strict';
var restify, bunyan, routes, namespace, validator, log, server;

restify   = require('restify');
bunyan    = require('bunyan');
routes    = require('./routes/');
namespace = require('restify-namespace');
validator = require('restify-validator');

log = bunyan.createLogger({
  name        : 'app',
  level       : process.env.LOG_LEVEL || 'info',
  stream      : process.stdout,
  serializers : bunyan.stdSerializers
});

server = restify.createServer({
  name : 'app',
  log  : log,
  formatters : {
    'application/json' : function (req, res, body) {
      res.setHeader('Cache-Control', 'must-revalidate');

      // Does the client *explicitly* accepts application/json?
      var acceptHeader = req.header('Accept');
      var sendPlainText = (typeof acceptHeader === 'undefined' || acceptHeader.split(/, */).indexOf('application/json') === -1);

      // Send as plain text
      if (sendPlainText) {
        res.setHeader('Content-Type', 'text/plain; charset=utf-8');
      }

      // Send as JSON
      if (!sendPlainText) {
        res.setHeader('Content-Type', 'application/json; charset=utf-8');
      }
      return JSON.stringify(body);
    }
  }
});

server.use(restify.bodyParser({ mapParams: true }));
server.use(restify.queryParser());
server.use(restify.gzipResponse());
server.pre(restify.pre.sanitizePath());
server.use(validator);
server.use(restify.CORS());
server.use(restify.fullResponse());

server.on('after', restify.auditLogger({ log: log }));

// Set /v1 prefix for the routes
namespace(server, '/v1', function() {
  routes(server);
});

console.log('Server started.');
server.listen(8888, function () {
  log.info('%s listening at %s', server.name, server.url);
});
<|endoftext|>


<commit_before>// @flow

/**
 * `autocomplete` type prompt
 */

var ansiEscapes = require('ansi-escapes');
var chalk = require('chalk');
var figures = require('figures');
var Base = require('inquirer/lib/prompts/base');
var Choices = require('inquirer/lib/objects/choices');
var observe = require('inquirer/lib/utils/events');
var utils = require('inquirer/lib/utils/readline');
var Paginator = require('inquirer/lib/utils/paginator');
var runAsync = require('run-async');
var { takeWhile } = require('rxjs/operators');

const isSelectable = (choice) =>
  choice.type !== 'separator' && !choice.disabled;

class AutocompletePrompt extends Base {
  constructor(
    questions /*: Array<any> */,
    rl /*: readline$Interface */,
    answers /*: Array<any> */
  ) {
    super(questions, rl, answers);

    if (!this.opt.source) {
      this.throwParamError('source');
    }

    this.currentChoices = new Choices([]);

    this.firstRender = true;
    this.selected = 0;

    // Make sure no default is set (so it won't be printed)
    this.initialValue = this.opt.default;

    const shouldLoop = this.opt.loop === undefined ? true : this.opt.loop;
    this.paginator = new Paginator(this.screen, {
      isInfinite: shouldLoop
    });
  }

  /**
   * Start the Inquiry session
   * @param  {Function} cb      Callback when prompt is done
   * @return {this}
   */
  _run(cb /*: Function */) /*: this*/ {
    this.done = cb;

    if (this.rl.history instanceof Array) {
      this.rl.history = [];
    }

    var events = observe(this.rl);

    const dontHaveAnswer = () => this.answer === undefined;

    events.line
      .pipe(takeWhile(dontHaveAnswer))
      .forEach(this.onSubmit.bind(this));
    events.keypress
      .pipe(takeWhile(dontHaveAnswer))
      .forEach(this.onKeypress.bind(this));

    // Call once at init
    this.search(undefined);

    return this;
  }

  /**
   * Render the prompt to screen
   * @return {undefined}
   */
  render(error /*: ?string */) {
    // Render question
    var content = this.getQuestion();
    var bottomContent = '';

    if (this.firstRender) {
      var suggestText = this.opt.suggestOnly ? ', tab to autocomplete' : '';
      content += chalk.dim(
        '(Use arrow keys or type to search' + suggestText + ')'
      );
    }
    // Render choices or answer depending on the state
    if (this.status === 'answered') {
      content += chalk.cyan(this.shortAnswer || this.answerName || this.answer);
    } else if (this.searching) {
      content += this.rl.line;
      bottomContent += '  ' + chalk.dim(this.opt.searchText || 'Searching...');
    } else if (this.nbChoices) {
      var choicesStr = listRender(this.currentChoices, this.selected);
      content += this.rl.line;
      var indexPosition = this.selected;
      var realIndexPosition = 0;
      this.currentChoices.choices.every((choice, index) => {
        if (index > indexPosition) {
          return false;
        }
        const name = choice.name;
        realIndexPosition += name ? name.split('\n').length : 0;
        return true;
      });
      bottomContent += this.paginator.paginate(
        choicesStr,
        realIndexPosition,
        this.opt.pageSize
      );
    } else {
      content += this.rl.line;
      bottomContent +=
        '  ' + chalk.yellow(this.opt.emptyText || 'No results...');
    }

    if (error) {
      bottomContent += '\n' + chalk.red('>> ') + error;
    }

    this.firstRender = false;

    this.screen.render(content, bottomContent);
  }

  /**
   * When user press `enter` key
   */
  onSubmit(line /* : string */) {
    const lineOrRl = line || this.rl.line;

    if (typeof this.opt.validate === 'function') {
      const checkValidationResult = (validationResult) => {
        if (validationResult !== true) {
          this.render(
            validationResult || 'Enter something, tab to autocomplete!'
          );
        } else {
          this.onSubmitAfterValidation(lineOrRl);
        }
      };

      let validationResult;
      if (this.opt.suggestOnly) {
        validationResult = this.opt.validate(lineOrRl, this.answers);
      } else {
        const choice = this.currentChoices.getChoice(this.selected);
        validationResult = this.opt.validate(choice, this.answers);
      }

      if (isPromise(validationResult)) {
        validationResult.then(checkValidationResult);
      } else {
        checkValidationResult(validationResult);
      }
    } else {
      this.onSubmitAfterValidation(lineOrRl);
    }
  }

  onSubmitAfterValidation(line /* : string */) {
    var choice = {};
    if (this.nbChoices <= this.selected && !this.opt.suggestOnly) {
      this.rl.write(line);
      this.search(line);
      return;
    }

    if (this.opt.suggestOnly) {
      choice.value = line || this.rl.line;
      this.answer = line || this.rl.line;
      this.answerName = line || this.rl.line;
      this.shortAnswer = line || this.rl.line;
      this.rl.line = '';
    } else if (this.nbChoices) {
      choice = this.currentChoices.getChoice(this.selected);
      this.answer = choice.value;
      this.answerName = choice.name;
      this.shortAnswer = choice.short;
    } else {
      this.rl.write(line);
      this.search(line);
      return;
    }

    runAsync(this.opt.filter, (err, value) => {
      choice.value = value;
      this.answer = value;

      if (this.opt.suggestOnly) {
        this.shortAnswer = value;
      }

      this.status = 'answered';
      // Rerender prompt
      this.render();
      this.screen.done();
      this.done(choice.value);
    })(choice.value);
  }

  search(searchTerm /* : ?string */) {
    var self = this;
    self.selected = 0;

    // Only render searching state after first time
    if (self.searchedOnce) {
      self.searching = true;
      self.currentChoices = new Choices([]);
      self.render(); // Now render current searching state
    } else {
      self.searchedOnce = true;
    }

    self.lastSearchTerm = searchTerm;

    var thisPromise;
    try {
      var result = self.opt.source(self.answers, searchTerm);
      thisPromise = Promise.resolve(result);
    } catch (error) {
      thisPromise = Promise.reject(error);
    }

    // Store this promise for check in the callback
    self.lastPromise = thisPromise;

    return thisPromise.then(function inner(choices) {
      // If another search is triggered before the current search finishes, don't set results
      if (thisPromise !== self.lastPromise) return;

      self.currentChoices = new Choices(choices);

      var realChoices = choices.filter(isSelectable);
      self.nbChoices = realChoices.length;

      var selectedIndex = realChoices.findIndex(
        (choice) =>
          choice === self.initialValue || choice.value === self.initialValue
      );

      if (selectedIndex >= 0) {
        self.selected = selectedIndex;
      }

      self.searching = false;
      self.render();
    });
  }

  ensureSelectedInRange() {
    var selectedIndex = Math.min(this.selected, this.nbChoices); // Not above currentChoices length - 1
    this.selected = Math.max(selectedIndex, 0); // Not below 0
  }

  /**
   * When user type
   */

  onKeypress(e /* : {key: { name: string, ctrl: boolean }, value: string } */) {
    var len;
    var keyName = (e.key && e.key.name) || undefined;

    if (keyName === 'tab' && this.opt.suggestOnly) {
      if (this.currentChoices.getChoice(this.selected)) {
        this.rl.write(ansiEscapes.cursorLeft);
        var autoCompleted = this.currentChoices.getChoice(this.selected).value;
        this.rl.write(ansiEscapes.cursorForward(autoCompleted.length));
        this.rl.line = autoCompleted;
        this.render();
      }
    } else if (keyName === 'down' || (keyName === 'n' && e.key.ctrl)) {
      len = this.nbChoices;
      this.selected = this.selected < len - 1 ? this.selected + 1 : 0;
      this.ensureSelectedInRange();
      this.render();
      utils.up(this.rl, 2);
    } else if (keyName === 'up' || (keyName === 'p' && e.key.ctrl)) {
      len = this.nbChoices;
      this.selected = this.selected > 0 ? this.selected - 1 : len - 1;
      this.ensureSelectedInRange();
      this.render();
    } else {
      this.render(); // Render input automatically
      // Only search if input have actually changed, not because of other keypresses
      if (this.lastSearchTerm !== this.rl.line) {
        this.search(this.rl.line); // Trigger new search
      }
    }
  }
}

/**
 * Function for rendering list choices
 * @param  {Number} pointer Position of the pointer
 * @return {String}         Rendered content
 */
function listRender(choices, pointer /*: string */) /*: string */ {
  var output = '';
  var separatorOffset = 0;

  choices.forEach(function (choice, i) {
    if (choice.type === 'separator') {
      separatorOffset++;
      output += '  ' + choice + '\n';
      return;
    }

    if (choice.disabled) {
      separatorOffset++;
      output += '  - ' + choice.name;
      output +=
        ' (' +
        (typeof choice.disabled === 'string' ? choice.disabled : 'Disabled') +
        ')';
      output += '\n';
      return;
    }

    var isSelected = i - separatorOffset === pointer;
    var line = (isSelected ? figures.pointer + ' ' : '  ') + choice.name;

    if (isSelected) {
      line = chalk.cyan(line);
    }
    output += line + ' \n';
  });

  return output.replace(/\n$/, '');
}

function isPromise(value) {
  return typeof value === 'object' && typeof value.then === 'function';
}

module.exports = AutocompletePrompt;
<commit_msg>Hide default value from appearing next to the message text when in list(suggestOnly=false) mode. Keep showing it in input (suggestOnly=true) mode. See #121<commit_after>// @flow

/**
 * `autocomplete` type prompt
 */

var ansiEscapes = require('ansi-escapes');
var chalk = require('chalk');
var figures = require('figures');
var Base = require('inquirer/lib/prompts/base');
var Choices = require('inquirer/lib/objects/choices');
var observe = require('inquirer/lib/utils/events');
var utils = require('inquirer/lib/utils/readline');
var Paginator = require('inquirer/lib/utils/paginator');
var runAsync = require('run-async');
var { takeWhile } = require('rxjs/operators');

const isSelectable = (choice) =>
  choice.type !== 'separator' && !choice.disabled;

class AutocompletePrompt extends Base {
  constructor(
    questions /*: Array<any> */,
    rl /*: readline$Interface */,
    answers /*: Array<any> */
  ) {
    super(questions, rl, answers);

    if (!this.opt.source) {
      this.throwParamError('source');
    }

    this.currentChoices = new Choices([]);

    this.firstRender = true;
    this.selected = 0;

    // Make sure no default is set (so it won't be printed)
    this.initialValue = this.opt.default;
    if (!this.opt.suggestOnly) {
      this.opt.default = null;
    }

    const shouldLoop = this.opt.loop === undefined ? true : this.opt.loop;
    this.paginator = new Paginator(this.screen, {
      isInfinite: shouldLoop
    });
  }

  /**
   * Start the Inquiry session
   * @param  {Function} cb      Callback when prompt is done
   * @return {this}
   */
  _run(cb /*: Function */) /*: this*/ {
    this.done = cb;

    if (this.rl.history instanceof Array) {
      this.rl.history = [];
    }

    var events = observe(this.rl);

    const dontHaveAnswer = () => this.answer === undefined;

    events.line
      .pipe(takeWhile(dontHaveAnswer))
      .forEach(this.onSubmit.bind(this));
    events.keypress
      .pipe(takeWhile(dontHaveAnswer))
      .forEach(this.onKeypress.bind(this));

    // Call once at init
    this.search(undefined);

    return this;
  }

  /**
   * Render the prompt to screen
   * @return {undefined}
   */
  render(error /*: ?string */) {
    // Render question
    var content = this.getQuestion();
    var bottomContent = '';

    if (this.firstRender) {
      var suggestText = this.opt.suggestOnly ? ', tab to autocomplete' : '';
      content += chalk.dim(
        '(Use arrow keys or type to search' + suggestText + ')'
      );
    }
    // Render choices or answer depending on the state
    if (this.status === 'answered') {
      content += chalk.cyan(this.shortAnswer || this.answerName || this.answer);
    } else if (this.searching) {
      content += this.rl.line;
      bottomContent += '  ' + chalk.dim(this.opt.searchText || 'Searching...');
    } else if (this.nbChoices) {
      var choicesStr = listRender(this.currentChoices, this.selected);
      content += this.rl.line;
      var indexPosition = this.selected;
      var realIndexPosition = 0;
      this.currentChoices.choices.every((choice, index) => {
        if (index > indexPosition) {
          return false;
        }
        const name = choice.name;
        realIndexPosition += name ? name.split('\n').length : 0;
        return true;
      });
      bottomContent += this.paginator.paginate(
        choicesStr,
        realIndexPosition,
        this.opt.pageSize
      );
    } else {
      content += this.rl.line;
      bottomContent +=
        '  ' + chalk.yellow(this.opt.emptyText || 'No results...');
    }

    if (error) {
      bottomContent += '\n' + chalk.red('>> ') + error;
    }

    this.firstRender = false;

    this.screen.render(content, bottomContent);
  }

  /**
   * When user press `enter` key
   */
  onSubmit(line /* : string */) {
    const lineOrRl = line || this.rl.line;

    if (typeof this.opt.validate === 'function') {
      const checkValidationResult = (validationResult) => {
        if (validationResult !== true) {
          this.render(
            validationResult || 'Enter something, tab to autocomplete!'
          );
        } else {
          this.onSubmitAfterValidation(lineOrRl);
        }
      };

      let validationResult;
      if (this.opt.suggestOnly) {
        validationResult = this.opt.validate(lineOrRl, this.answers);
      } else {
        const choice = this.currentChoices.getChoice(this.selected);
        validationResult = this.opt.validate(choice, this.answers);
      }

      if (isPromise(validationResult)) {
        validationResult.then(checkValidationResult);
      } else {
        checkValidationResult(validationResult);
      }
    } else {
      this.onSubmitAfterValidation(lineOrRl);
    }
  }

  onSubmitAfterValidation(line /* : string */) {
    var choice = {};
    if (this.nbChoices <= this.selected && !this.opt.suggestOnly) {
      this.rl.write(line);
      this.search(line);
      return;
    }

    if (this.opt.suggestOnly) {
      choice.value = line || this.rl.line;
      this.answer = line || this.rl.line;
      this.answerName = line || this.rl.line;
      this.shortAnswer = line || this.rl.line;
      this.rl.line = '';
    } else if (this.nbChoices) {
      choice = this.currentChoices.getChoice(this.selected);
      this.answer = choice.value;
      this.answerName = choice.name;
      this.shortAnswer = choice.short;
    } else {
      this.rl.write(line);
      this.search(line);
      return;
    }

    runAsync(this.opt.filter, (err, value) => {
      choice.value = value;
      this.answer = value;

      if (this.opt.suggestOnly) {
        this.shortAnswer = value;
      }

      this.status = 'answered';
      // Rerender prompt
      this.render();
      this.screen.done();
      this.done(choice.value);
    })(choice.value);
  }

  search(searchTerm /* : ?string */) {
    var self = this;
    self.selected = 0;

    // Only render searching state after first time
    if (self.searchedOnce) {
      self.searching = true;
      self.currentChoices = new Choices([]);
      self.render(); // Now render current searching state
    } else {
      self.searchedOnce = true;
    }

    self.lastSearchTerm = searchTerm;

    var thisPromise;
    try {
      var result = self.opt.source(self.answers, searchTerm);
      thisPromise = Promise.resolve(result);
    } catch (error) {
      thisPromise = Promise.reject(error);
    }

    // Store this promise for check in the callback
    self.lastPromise = thisPromise;

    return thisPromise.then(function inner(choices) {
      // If another search is triggered before the current search finishes, don't set results
      if (thisPromise !== self.lastPromise) return;

      self.currentChoices = new Choices(choices);

      var realChoices = choices.filter(isSelectable);
      self.nbChoices = realChoices.length;

      var selectedIndex = realChoices.findIndex(
        (choice) =>
          choice === self.initialValue || choice.value === self.initialValue
      );

      if (selectedIndex >= 0) {
        self.selected = selectedIndex;
      }

      self.searching = false;
      self.render();
    });
  }

  ensureSelectedInRange() {
    var selectedIndex = Math.min(this.selected, this.nbChoices); // Not above currentChoices length - 1
    this.selected = Math.max(selectedIndex, 0); // Not below 0
  }

  /**
   * When user type
   */

  onKeypress(e /* : {key: { name: string, ctrl: boolean }, value: string } */) {
    var len;
    var keyName = (e.key && e.key.name) || undefined;

    if (keyName === 'tab' && this.opt.suggestOnly) {
      if (this.currentChoices.getChoice(this.selected)) {
        this.rl.write(ansiEscapes.cursorLeft);
        var autoCompleted = this.currentChoices.getChoice(this.selected).value;
        this.rl.write(ansiEscapes.cursorForward(autoCompleted.length));
        this.rl.line = autoCompleted;
        this.render();
      }
    } else if (keyName === 'down' || (keyName === 'n' && e.key.ctrl)) {
      len = this.nbChoices;
      this.selected = this.selected < len - 1 ? this.selected + 1 : 0;
      this.ensureSelectedInRange();
      this.render();
      utils.up(this.rl, 2);
    } else if (keyName === 'up' || (keyName === 'p' && e.key.ctrl)) {
      len = this.nbChoices;
      this.selected = this.selected > 0 ? this.selected - 1 : len - 1;
      this.ensureSelectedInRange();
      this.render();
    } else {
      this.render(); // Render input automatically
      // Only search if input have actually changed, not because of other keypresses
      if (this.lastSearchTerm !== this.rl.line) {
        this.search(this.rl.line); // Trigger new search
      }
    }
  }
}

/**
 * Function for rendering list choices
 * @param  {Number} pointer Position of the pointer
 * @return {String}         Rendered content
 */
function listRender(choices, pointer /*: string */) /*: string */ {
  var output = '';
  var separatorOffset = 0;

  choices.forEach(function (choice, i) {
    if (choice.type === 'separator') {
      separatorOffset++;
      output += '  ' + choice + '\n';
      return;
    }

    if (choice.disabled) {
      separatorOffset++;
      output += '  - ' + choice.name;
      output +=
        ' (' +
        (typeof choice.disabled === 'string' ? choice.disabled : 'Disabled') +
        ')';
      output += '\n';
      return;
    }

    var isSelected = i - separatorOffset === pointer;
    var line = (isSelected ? figures.pointer + ' ' : '  ') + choice.name;

    if (isSelected) {
      line = chalk.cyan(line);
    }
    output += line + ' \n';
  });

  return output.replace(/\n$/, '');
}

function isPromise(value) {
  return typeof value === 'object' && typeof value.then === 'function';
}

module.exports = AutocompletePrompt;
<|endoftext|>


<commit_before>'use strict';

var defaults = {
    ellipse: '…',
    chars: [' ', '-'],
    max: 140,
    truncate: true
};

function ellipsize(str, max, ellipse, chars, truncate) {
    if (str.length < max) return str;

    var last = 0,
        c = '',
        midMax = Math.floor(max / 2),
        computedMax = truncate === 'middle' ? midMax : max;

    for (var i = 0, len = str.length; i < len; i++) {
        c = str.charAt(i);

        if (chars.indexOf(c) !== -1 && truncate !== 'middle') {
            last = i;
        }

        if (i < computedMax) continue;
        if (last === 0) {
            return !truncate ?
                '' :
                str.substring(0, computedMax - 1) + ellipse + (
                    truncate === 'middle' ?
                    str.substring(str.length - midMax, str.length) :
                    ''
                );
        }

        return str.substring(0, last) + ellipse;
    }

    return str;
}

module.exports = function(str, max, opts) {
    if (typeof str !== 'string' || str.length === 0) return '';
    if (max === 0) return '';

    opts = opts || {};

    for (var key in defaults) {
        if (opts[key] === null || typeof opts[key] === 'undefined') {
            opts[key] = defaults[key];
        }
    }

    opts.max = max || opts.max;

    return ellipsize(str, opts.max, opts.ellipse, opts.chars, opts.truncate);
};
<commit_msg>Match the max length including the ellipsize<commit_after>'use strict';

var defaults = {
    ellipse: '…',
    chars: [' ', '-'],
    max: 140,
    truncate: true
};

function ellipsize(str, max, ellipse, chars, truncate) {
    if (str.length < max) return str;

    var last = 0,
        c = '',
        midMax = Math.floor(max / 2),
        computedMax = truncate === 'middle' ? midMax : max;

    for (var i = 0, len = str.length; i < len; i++) {
        c = str.charAt(i);

        if (chars.indexOf(c) !== -1 && truncate !== 'middle') {
            last = i;
        }

        if (i < computedMax) continue;
        if (last === 0) {
            return !truncate ?
                '' :
                str.substring(0, computedMax - 1) + ellipse + (
                    truncate === 'middle' ?
                    str.substring(str.length - midMax, str.length) :
                    ''
                );
        }

        return str.substring(0, last) + ellipse;
    }

    return str;
}

module.exports = function(str, max, opts) {
    if (typeof str !== 'string' || str.length === 0) return '';
    if (max === 0) return '';

    opts = opts || {};

    for (var key in defaults) {
        if (opts[key] === null || typeof opts[key] === 'undefined') {
            opts[key] = defaults[key];
        }
    }

    opts.max = max || opts.max;

    return ellipsize(str, opts.max - 1, opts.ellipse, opts.chars, opts.truncate);
};
<|endoftext|>