<commit_before>/**
 * General Transitive utilities library
 */

import isNumeric from 'isnumeric/isNumeric'
import SphericalMercator from 'sphericalmercator'

const TOLERANCE = 0.000001

function fuzzyEquals (a, b, tolerance = TOLERANCE) {
  return Math.abs(a - b) < tolerance
}

function distance (x1, y1, x2, y2) {
  return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1))
}

function getRadiusFromAngleChord (angleR, chordLen) {
  return (chordLen / 2) / Math.sin(angleR / 2)
}

/*
 * CCW utility function. Accepts 3 coord pairs; result is positive if points
 * have counterclockwise orientation, negative if clockwise, 0 if collinear.
 */

function ccw (ax, ay, bx, by, cx, cy) {
  var raw = ccwRaw(ax, ay, bx, by, cx, cy)
  return (raw === 0) ? 0 : raw / Math.abs(raw)
}

function ccwRaw (ax, ay, bx, by, cx, cy) {
  return (bx - ax) * (cy - ay) - (cx - ax) * (by - ay)
}

/*
 * Compute angle formed by three points in cartesian plane using law of cosines
 */

function angleFromThreePoints (ax, ay, bx, by, cx, cy) {
  var c = distance(ax, ay, bx, by)
  var a = distance(bx, by, cx, cy)
  var b = distance(ax, ay, cx, cy)
  return Math.acos((a * a + c * c - b * b) / (2 * a * c))
}

function pointAlongArc (x1, y1, x2, y2, r, theta, ccw, t) {
  ccw = Math.abs(ccw) / ccw // convert to 1 or -1

  var rot = Math.PI / 2 - Math.abs(theta) / 2
  var vectToCenter = normalizeVector(rotateVector({
    x: x2 - x1,
    y: y2 - y1
  }, ccw * rot))

  // calculate the center of the arc circle
  var cx = x1 + r * vectToCenter.x
  var cy = y1 + r * vectToCenter.y

  var vectFromCenter = negateVector(vectToCenter)
  rot = Math.abs(theta) * t * ccw
  vectFromCenter = normalizeVector(rotateVector(
    vectFromCenter, rot))

  return {
    x: cx + r * vectFromCenter.x,
    y: cy + r * vectFromCenter.y
  }
}

function getVectorAngle (x, y) {
  var t = Math.atan(y / x)

  if (x < 0 && t <= 0) t += Math.PI
  else if (x < 0 && t >= 0) t -= Math.PI

  return t
}

function rayIntersection (ax, ay, avx, avy, bx, by, bvx, bvy) {
  var u = ((by - ay) * bvx - (bx - ax) * bvy) / (bvx * avy - bvy * avx)
  var v = ((by - ay) * avx - (bx - ax) * avy) / (bvx * avy - bvy * avx)

  return {
    u: u,
    v: v,
    intersect: (u > -TOLERANCE && v > -TOLERANCE)
  }
}

function lineIntersection (x1, y1, x2, y2, x3, y3, x4, y4) {
  var d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)

  if (d === 0) { // lines are parallel
    return {
      intersect: false
    }
  }

  return {
    x: ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / d,
    y: ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / d,
    intersect: true
  }
}

/**
 * Parse a pixel-based style descriptor, returning an number.
 *
 * @param {String/Number}
 */
function parsePixelStyle (descriptor) {
  if (typeof descriptor === 'number') return descriptor
  return parseFloat(descriptor.substring(0, descriptor.length - 2), 10)
}

/**
 * Whether vector is projected into positive xy quadrant.
 */
function isOutwardVector (vector) {
  return !fuzzyEquals(vector.x, 0)
    ? vector.x > 0
    : vector.y > 0
}

/**
 * vector utilities
 */

function normalizeVector (v) {
  var d = Math.sqrt(v.x * v.x + v.y * v.y)
  return {
    x: v.x / d,
    y: v.y / d
  }
}

function rotateVector (v, theta) {
  return {
    x: v.x * Math.cos(theta) - v.y * Math.sin(theta),
    y: v.x * Math.sin(theta) + v.y * Math.cos(theta)
  }
}

function negateVector (v) {
  return {
    x: -v.x,
    y: -v.y
  }
}

function addVectors (v1, v2) {
  return {
    x: v1.x + v2.x,
    y: v1.y + v2.y
  }
}

/**
 * GTFS utilities
 */

function otpModeToGtfsType (otpMode) {
  switch (otpMode) {
    case 'TRAM':
      return 0
    case 'SUBWAY':
      return 1
    case 'RAIL':
      return 2
    case 'BUS':
      return 3
    case 'FERRY':
      return 4
    case 'CABLE_CAR':
      return 5
    case 'GONDOLA':
      return 6
    case 'FUNICULAR':
      return 7
  }
}

// Rendering utilties

function renderDataToSvgPath (renderData) {
  return renderData.map((d, k) => {
    if (k === 0) return `M${d.x} ${d.y}`
    if (d.arc) return `A${d.radius} ${d.radius} ${d.arc} 0 ${d.arc > 0 ? 0 : 1} ${d.x} ${d.y}`
    return `L${d.x} ${d.y}`
  }).join(' ')
}

// An instance of the SphericalMercator converter
const sm = new SphericalMercator()

/**
 * @param {*} fontSize A CSS font size or a numerical (pixel) font size.
 * @returns A CSS font size ending with the provided CSS unit or 'px' if none provided.
 */
function getFontSizeWithUnit (fontSize) {
  return fontSize + (isNumeric(fontSize) ? 'px' : '')
}

export {
  fuzzyEquals,
  distance,
  getRadiusFromAngleChord,
  ccw,
  ccwRaw,
  angleFromThreePoints,
  pointAlongArc,
  getVectorAngle,
  rayIntersection,
  lineIntersection,
  parsePixelStyle,
  isOutwardVector,
  normalizeVector,
  rotateVector,
  negateVector,
  addVectors,
  otpModeToGtfsType,
  renderDataToSvgPath,
  sm,
  getFontSizeWithUnit
}
<commit_msg>style(util/index.js): Fix lint<commit_after>/**
 * General Transitive utilities library
 */

import isNumeric from 'isnumeric/isNumeric'
import SphericalMercator from 'sphericalmercator'

const TOLERANCE = 0.000001

function fuzzyEquals (a, b, tolerance = TOLERANCE) {
  return Math.abs(a - b) < tolerance
}

function distance (x1, y1, x2, y2) {
  return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1))
}

function getRadiusFromAngleChord (angleR, chordLen) {
  return (chordLen / 2) / Math.sin(angleR / 2)
}

/*
 * CCW utility function. Accepts 3 coord pairs; result is positive if points
 * have counterclockwise orientation, negative if clockwise, 0 if collinear.
 */

function ccw (ax, ay, bx, by, cx, cy) {
  var raw = ccwRaw(ax, ay, bx, by, cx, cy)
  return (raw === 0) ? 0 : raw / Math.abs(raw)
}

function ccwRaw (ax, ay, bx, by, cx, cy) {
  return (bx - ax) * (cy - ay) - (cx - ax) * (by - ay)
}

/*
 * Compute angle formed by three points in cartesian plane using law of cosines
 */

function angleFromThreePoints (ax, ay, bx, by, cx, cy) {
  var c = distance(ax, ay, bx, by)
  var a = distance(bx, by, cx, cy)
  var b = distance(ax, ay, cx, cy)
  return Math.acos((a * a + c * c - b * b) / (2 * a * c))
}

function pointAlongArc (x1, y1, x2, y2, r, theta, ccw, t) {
  ccw = Math.abs(ccw) / ccw // convert to 1 or -1

  var rot = Math.PI / 2 - Math.abs(theta) / 2
  var vectToCenter = normalizeVector(rotateVector({
    x: x2 - x1,
    y: y2 - y1
  }, ccw * rot))

  // calculate the center of the arc circle
  var cx = x1 + r * vectToCenter.x
  var cy = y1 + r * vectToCenter.y

  var vectFromCenter = negateVector(vectToCenter)
  rot = Math.abs(theta) * t * ccw
  vectFromCenter = normalizeVector(rotateVector(
    vectFromCenter, rot))

  return {
    x: cx + r * vectFromCenter.x,
    y: cy + r * vectFromCenter.y
  }
}

function getVectorAngle (x, y) {
  var t = Math.atan(y / x)

  if (x < 0 && t <= 0) t += Math.PI
  else if (x < 0 && t >= 0) t -= Math.PI

  return t
}

function rayIntersection (ax, ay, avx, avy, bx, by, bvx, bvy) {
  var u = ((by - ay) * bvx - (bx - ax) * bvy) / (bvx * avy - bvy * avx)
  var v = ((by - ay) * avx - (bx - ax) * avy) / (bvx * avy - bvy * avx)

  return {
    u: u,
    v: v,
    intersect: (u > -TOLERANCE && v > -TOLERANCE)
  }
}

function lineIntersection (x1, y1, x2, y2, x3, y3, x4, y4) {
  var d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)

  if (d === 0) { // lines are parallel
    return {
      intersect: false
    }
  }

  return {
    x: ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / d,
    y: ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / d,
    intersect: true
  }
}

/**
 * Parse a pixel-based style descriptor, returning an number.
 *
 * @param {String/Number}
 */
function parsePixelStyle (descriptor) {
  if (typeof descriptor === 'number') return descriptor
  return parseFloat(descriptor.substring(0, descriptor.length - 2), 10)
}

/**
 * Whether vector is projected into positive xy quadrant.
 */
function isOutwardVector (vector) {
  return !fuzzyEquals(vector.x, 0)
    ? vector.x > 0
    : vector.y > 0
}

/**
 * vector utilities
 */

function normalizeVector (v) {
  var d = Math.sqrt(v.x * v.x + v.y * v.y)
  return {
    x: v.x / d,
    y: v.y / d
  }
}

function rotateVector (v, theta) {
  return {
    x: v.x * Math.cos(theta) - v.y * Math.sin(theta),
    y: v.x * Math.sin(theta) + v.y * Math.cos(theta)
  }
}

function negateVector (v) {
  return {
    x: -v.x,
    y: -v.y
  }
}

function addVectors (v1, v2) {
  return {
    x: v1.x + v2.x,
    y: v1.y + v2.y
  }
}

/**
 * GTFS utilities
 */

function otpModeToGtfsType (otpMode) {
  switch (otpMode) {
    case 'TRAM':
      return 0
    case 'SUBWAY':
      return 1
    case 'RAIL':
      return 2
    case 'BUS':
      return 3
    case 'FERRY':
      return 4
    case 'CABLE_CAR':
      return 5
    case 'GONDOLA':
      return 6
    case 'FUNICULAR':
      return 7
  }
}

// Rendering utilties

function renderDataToSvgPath (renderData) {
  return renderData.map((d, k) => {
    if (k === 0) return `M${d.x} ${d.y}`
    if (d.arc) return `A${d.radius} ${d.radius} ${d.arc} 0 ${d.arc > 0 ? 0 : 1} ${d.x} ${d.y}`
    return `L${d.x} ${d.y}`
  }).join(' ')
}

// An instance of the SphericalMercator converter
const sm = new SphericalMercator()

/**
 * @param {*} fontSize A CSS font size or a numerical (pixel) font size.
 * @returns A CSS font size ending with the provided CSS unit or 'px' if none provided.
 */
function getFontSizeWithUnit (fontSize) {
  return fontSize + (isNumeric(fontSize) ? 'px' : '')
}

export {
  fuzzyEquals,
  distance,
  getRadiusFromAngleChord,
  ccw,
  ccwRaw,
  angleFromThreePoints,
  pointAlongArc,
  getVectorAngle,
  rayIntersection,
  lineIntersection,
  parsePixelStyle,
  isOutwardVector,
  normalizeVector,
  rotateVector,
  negateVector,
  addVectors,
  otpModeToGtfsType,
  renderDataToSvgPath,
  sm,
  getFontSizeWithUnit
}
<|endoftext|>


<commit_before>
var spawn = require('child_process').spawn

    //runs a command with args,
    //returning a promise that will resolve with
    //{ out:..., err:..., code:... }
    //the promise does not reject.
    //pass the ondataout and ondataerr handlers
    //that will pass incremental changes to you
    //but this doesn't change the above behavior.

module.exports = function(cmd, args, ondataout, ondataerr) {

    return new Promise(function(resolve, reject) {

            //OSX El Capitan launchctl PATH related solution, for now
        if(process.platform == 'darwin') {
            process.env.PATH = ["/usr/local/bin", process.env.PATH].join(":");
        }

        var total_err = '';
        var total_out = '';
        var proc = spawn(cmd, args);

        proc.stdout.on('data', function(data){
            var s = data.toString('utf-8');
            total_out += s;
            if(ondataout) ondataout(s);
        });

        proc.stderr.on('data', function(data){
            var s = data.toString('utf-8');
            total_err += s;
            if(ondataerr) ondataerr(s);
        });

        proc.on('close', function(code) {
            resolve({
                out:total_out,
                err:total_err,
                code:code
            });
        }); //on close

    }); //Promise

} //module.exports
<commit_msg>minor fix for previous path fix<commit_after>
var spawn = require('child_process').spawn

    //runs a command with args,
    //returning a promise that will resolve with
    //{ out:..., err:..., code:... }
    //the promise does not reject.
    //pass the ondataout and ondataerr handlers
    //that will pass incremental changes to you
    //but this doesn't change the above behavior.

var path_set = false;
module.exports = function(cmd, args, ondataout, ondataerr) {

    return new Promise(function(resolve, reject) {

            //OSX El Capitan launchctl PATH related solution, for now
        if(process.platform == 'darwin' && !path_set) {
            process.env.PATH = ["/usr/local/bin", process.env.PATH].join(":");
        }

        var total_err = '';
        var total_out = '';
        var proc = spawn(cmd, args);

        proc.stdout.on('data', function(data){
            var s = data.toString('utf-8');
            total_out += s;
            if(ondataout) ondataout(s);
        });

        proc.stderr.on('data', function(data){
            var s = data.toString('utf-8');
            total_err += s;
            if(ondataerr) ondataerr(s);
        });

        proc.on('close', function(code) {
            resolve({
                out:total_out,
                err:total_err,
                code:code
            });
        }); //on close

    }); //Promise

} //module.exports
<|endoftext|>


<commit_before>var CHANGES = require('../constants/changeType');

var arrayBuffer = require('./arrayBuffer');
var error = require('./error');
var RepoUtils = require('./repo');
var ChangeUtils = require('./change');

// Read content of a file, returns an ArrayBuffer
function readFile(repoState, filename) {
    var workingState = repoState.getCurrentState();
    var caches = repoState.getCache();
    var changes = workingState.getChanges();
    var change = changes.getEntry(filename);

    // File has been modified?
    if (change) {
        if (change.getType() == CHANGES.REMOVE) {
            throw error.fileNotFound(filename);
        }

        return change.getContent();
    }

    var tree = workingState.getTree();
    var treeEntry = tree.getEntry(filename);

    // File entry does not exists
    if (!treeEntry) {
        throw error.fileNotFound(filename);
    }

    // Get content from cache
    return caches.getBlob(treeEntry.getSHA());
}

// Read content of a file, returns a String
function readFileAsString(repoState, filename, encoding) {
    encoding = encoding || 'utf8';
    var buffer = readFile(repoState, filename);
    return arrayBuffer.enforceString(buffer, encoding);
}

// Push a new change
function pushChange(repoState, filename, type, content) {
    var workingState = repoState.getCurrentState();

    // Push changes to list
    var changes = workingState.getChanges();
    changes = ChangeUtils.pushChange(changes, filename, type, content);

    // Update workingState and repoState
    workingState = workingState.set('changes', changes);

    return RepoUtils.updateCurrentWorkingState(repoState, workingState);
}

// Create a new file
function createFile(repoState, filename, content) {
    return pushChange(repoState, filename, {
        type: CHANGES.CREATE,
        content: content
    });
}

// Write a file
function writeFile(repoState, filename, content) {
    return pushChange(repoState, filename, {
        type: CHANGES.UPDATE,
        content: content
    });
}

// Remove a file
function removeFile(repoState, filename) {
    return pushChange(repoState, filename, {
        type: CHANGES.REMOVE
    });
}

// Rename a file
function moveFile(repoState, filename, newFilename) {
    var workingState = repoState.getCurrentState();
    var changes = workingState.getChanges();
    var tree = workingState.getTree();
    var treeEntry = tree.get(filename);

    // File entry does not exists
    if (!treeEntry) {
        throw error.fileNotFound(filename);
    }

    // New file exists?
    if (tree.has(newFilename)) {
        throw error.fileAlreadyExist(newFilename);
    }

    // Create new file
    changes = ChangeUtils.pushChange(changes, newFilename, {
        type: CHANGES.CREATE,
        sha: treeEntry.getSHA()
    });

    // Remove old one
    changes = ChangeUtils.pushChange(changes, filename, {
        type: CHANGES.REMOVE
    });

    workingState = workingState.set('changes', changes);
    return RepoUtils.updateCurrentWorkingState(repoState, workingState);
}

module.exports = {
    read: readFile,
    readAsString: readFileAsString,
    create: createFile,
    write: writeFile,
    remove: removeFile,
    move: moveFile
};
<commit_msg>Add method to test if file exists<commit_after>var CHANGES = require('../constants/changeType');

var arrayBuffer = require('./arrayBuffer');
var error = require('./error');
var RepoUtils = require('./repo');
var ChangeUtils = require('./change');
var WorkingUtils = require('./working');

// Read content of a file, returns an ArrayBuffer
function readFile(repoState, filename) {
    var workingState = repoState.getCurrentState();
    var caches = repoState.getCache();
    var changes = workingState.getChanges();
    var change = changes.getEntry(filename);

    // File has been modified?
    if (change) {
        if (change.getType() == CHANGES.REMOVE) {
            throw error.fileNotFound(filename);
        }

        return change.getContent();
    }

    var tree = workingState.getTree();
    var treeEntry = tree.getEntry(filename);

    // File entry does not exists
    if (!treeEntry) {
        throw error.fileNotFound(filename);
    }

    // Get content from cache
    return caches.getBlob(treeEntry.getSHA());
}

// Read content of a file, returns a String
function readFileAsString(repoState, filename, encoding) {
    encoding = encoding || 'utf8';
    var buffer = readFile(repoState, filename);
    return arrayBuffer.enforceString(buffer, encoding);
}

// Push a new change
function pushChange(repoState, filename, type, content) {
    var workingState = repoState.getCurrentState();

    // Push changes to list
    var changes = workingState.getChanges();
    changes = ChangeUtils.pushChange(changes, filename, type, content);

    // Update workingState and repoState
    workingState = workingState.set('changes', changes);

    return RepoUtils.updateCurrentWorkingState(repoState, workingState);
}

// Return true if file exists, false otherwise
function fileExists(repoState, filename) {
    var workingState = repoState.getCurrentState();
    var tree = WorkingUtils.getWorkingTree(workingState);

    return tree.has(filename);
}

// Create a new file
function createFile(repoState, filename, content) {
    if (fileExists(repoState, filename)) {
        throw error.fileAlreadyExist(filename);
    }

    return pushChange(repoState, filename, {
        type: CHANGES.CREATE,
        content: content
    });
}

// Write a file
function writeFile(repoState, filename, content) {
    if (!fileExists(repoState, filename)) {
        throw error.fileAlreadyExist(filename);
    }

    return pushChange(repoState, filename, {
        type: CHANGES.UPDATE,
        content: content
    });
}

// Remove a file
function removeFile(repoState, filename) {
    if (!fileExists(repoState, filename)) {
        throw error.fileAlreadyExist(filename);
    }

    return pushChange(repoState, filename, {
        type: CHANGES.REMOVE
    });
}

// Rename a file
function moveFile(repoState, filename, newFilename) {
    var workingState = repoState.getCurrentState();
    var changes = workingState.getChanges();
    var tree = WorkingUtils.getWorkingTree(workingState);
    var treeEntry = tree.get(filename);

    // File entry does not exists
    if (!treeEntry) {
        throw error.fileNotFound(filename);
    }

    // New file exists?
    if (tree.has(newFilename)) {
        throw error.fileAlreadyExist(newFilename);
    }

    // Create new file
    changes = ChangeUtils.pushChange(changes, newFilename, {
        type: CHANGES.CREATE,
        sha: treeEntry.getSHA()
    });

    // Remove old one
    changes = ChangeUtils.pushChange(changes, filename, {
        type: CHANGES.REMOVE
    });

    workingState = workingState.set('changes', changes);
    return RepoUtils.updateCurrentWorkingState(repoState, workingState);
}

module.exports = {
    exists: fileExists,
    read: readFile,
    readAsString: readFileAsString,
    create: createFile,
    write: writeFile,
    remove: removeFile,
    move: moveFile
};
<|endoftext|>


<commit_before>var Q = require('q');
var _ = require('lodash');
var path = require('path');
var cheerio = require('cheerio');
var domSerializer = require('dom-serializer');
var request = require('request');
var crc = require("crc");

var links = require('./links');
var imgUtils = require('./images');
var fs = require('./fs');

// Render a cheerio dom as html
var renderDom = function($, dom, options) {
    if (!dom && $._root && $._root.children) {
        dom = $._root.children;
    }

    options = options|| dom.options || $._options;
    return domSerializer(dom, options);
};

// Map of images that have been converted
var imgConversionCache = {};

function replaceText($, el, search, replace, text_only ) {
    return $(el).each(function(){
        var node = this.firstChild,
            val,
            new_val,

            // Elements to be removed at the end.
            remove = [];

        // Only continue if firstChild exists.
        if ( node ) {

            // Loop over all childNodes.
            do {
                // Only process text nodes.
                if ( node.nodeType === 3 ) {

                    // The original node value.
                    val = node.nodeValue;

                    // The new value.
                    new_val = val.replace( search, replace );

                    // Only replace text if the new value is actually different!
                    if ( new_val !== val ) {

                        if ( !text_only && /</.test( new_val ) ) {
                            // The new value contains HTML, set it in a slower but far more
                            // robust way.
                            $(node).before( new_val );

                            // Don't remove the node yet, or the loop will lose its place.
                            remove.push( node );
                        } else {
                            // The new value contains no HTML, so it can be set in this
                            // very fast, simple way.
                            node.nodeValue = new_val;
                        }
                    }
                }

            } while ( node = node.nextSibling );
        }

        // Time to remove those elements!
        remove.length && $(remove).remove();
    });
};

function pregQuote( str ) {
    return (str+'').replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, "\\$1");
};

// Adapt an html snippet to be relative to a base folder
function normalizeHtml(src, options) {
    var $ = cheerio.load(src, {
        // We should parse html without trying to normalize too much
        xmlMode: false,

        // SVG need some attributes to use uppercases
        lowerCaseAttributeNames: false,
        lowerCaseTags: false
    });
    var toConvert = [];
    var svgContent = {};
    var outputRoot = options.book.options.output;

    imgConversionCache[outputRoot] = imgConversionCache[outputRoot] || {};

    // Find svg images to extract and process
    if (options.convertImages) {
        $("svg").each(function() {
            var content = renderDom($, $(this));
            var svgId = _.uniqueId("svg");
            var dest = svgId+".svg";

            // Generate filename
            dest = "/"+fs.getUniqueFilename(outputRoot, dest);

            svgContent[dest] = content;
            $(this).replaceWith($("").attr("src", dest));
        });
    }

    // Find images to normalize
    $("img").each(function() {
        var origin = undefined;
        var src = $(this).attr("src");
        var isExternal = links.isExternal(src);

        // Transform as relative to the bases
        if (links.isRelative(src)) {
            src = links.toAbsolute(src, options.base, options.output);
        }

        // Convert if needed
        if (options.convertImages) {
            // If image is external and ebook, then downlaod the images
            if (isExternal) {
                origin = src;
                src = "/"+crc.crc32(origin).toString(16)+path.extname(origin);
                src = links.toAbsolute(src, options.base, options.output);
                isExternal = false;
            }

            var ext = path.extname(src);
            var srcAbs = path.join("/", options.base, src);

            // Test image extension
            if (_.contains(imgUtils.INVALID, ext)) {
                if (imgConversionCache[outputRoot][srcAbs]) {
                    // Already converted
                    src = imgConversionCache[outputRoot][srcAbs];
                } else {
                    // Not converted yet
                    var dest = "";

                    // Replace extension
                    dest = path.join(path.dirname(srcAbs), path.basename(srcAbs, ext)+".png");
                    dest = dest[0] == "/"? dest.slice(1) : dest;

                    // Get a name that doesn't exists
                    dest = fs.getUniqueFilename(outputRoot, dest);

                    options.book.log.debug.ln("detect invalid image (will be converted to png):", srcAbs);

                    // Add to cache
                    imgConversionCache[outputRoot][srcAbs] = "/"+dest;

                    // Push to convert
                    toConvert.push({
                        origin: origin,
                        content: svgContent[srcAbs],
                        source: isExternal? srcAbs : path.join("./", srcAbs),
                        dest: path.join("./", dest)
                    });

                    src = path.join("/", dest);
                }

                // Reset as relative to output
                src = links.toAbsolute(src, options.base, options.output);
            }

            else if (origin) {
                // Need to downlaod image
                toConvert.push({
                    origin: origin,
                    source: path.join("./", srcAbs)
                });
            }
        }

        $(this).attr("src", src);
    });

    $("a").each(function() {
        var href = $(this).attr("href");

        if (links.isRelative(href)) {
            var absolutePath = path.join(options.base, href);

            // If is in navigation relative: change extsnio nto be .html
            if (options.navigation[absolutePath]) href = links.changeExtension(href, ".html");

            // Transform as absolute
            href = links.toAbsolute(href, options.base, options.output);
        } else {
            $(this).attr("target", "_blank");
        }

        // Transform extension
        $(this).attr("href", href);
    });

    // Replace glossayr terms
    _.each(options.glossary, function(term) {
        var r =  new RegExp( "\\b(" + pregQuote(term.name.toLowerCase()) + ")\\b" , 'gi' );
        var includedInFiles = false;

        $("*").each(function() {
            replaceText($, this, r, function(match) {
                // Add to files index in glossary
                if (!includedInFiles) {
                    includedInFiles = true;
                    term.files = term.files || [];
                    term.files.push(options.navigation[options.input]);
                }
                return ""+match+"";
            });
        });
    });

    return {
        html: renderDom($),
        images: toConvert
    };
};

// Convert svg images to png
function convertImages(images, options) {
    if (!options.convertImages) return Q();

    var downloaded = [];
    options.book.log.debug.ln("convert ", images.length, "images to png");

    return _.reduce(images, function(prev, image) {
        var imgin = path.resolve(options.book.options.output, image.source);

        return prev

        // Write image if need to be download
        .then(function() {
            if (!image.origin && !_.contains(downloaded, image.origin)) return;
            options.book.log.debug("download image", image.origin, "...");
            downloaded.push(image.origin);
            return options.book.log.debug.promise(fs.writeStream(imgin, request(image.origin)));
        })

        // Write svg if content
        .then(function() {
            if (!image.content) return;
            return fs.writeFile(imgin, image.content);
        })

        // Convert
        .then(function() {
            if (!image.dest) return;
            var imgout = path.resolve(options.book.options.output, image.dest);
            options.book.log.debug("convert image", image.source, "to", image.dest, "...");
            return options.book.log.debug.promise(imgUtils.convertSVG(imgin, imgout));
        });
    }, Q())
    .then(function() {
        options.book.log.debug.ok(images.length+" images converted with success");
    });
};

// Adapt page content to be relative to a base folder
function normalizePage(sections, options) {
    options = _.defaults(options || {}, {
        // Current book
        book: null,

        // Do we need to convert svg?
        convertImages: false,

        // Current file path
        input: ".",

        // Navigation to use to transform path
        navigation: {},

        // Directory parent of the file currently in rendering process
        base: "./",

        // Directory parent from the html output
        output: "./",

        // Glossary terms
        glossary: []
    });

    // List of images to convert
    var toConvert = [];

    sections = _.map(sections, function(section) {
        if (section.type != "normal") return section;

        var out = normalizeHtml(section.content, options);;

        toConvert = toConvert.concat(out.images);
        section.content = out.html;
        return section;
    });

    return Q()
    .then(function() {
        toConvert = _.uniq(toConvert, 'source');
        return convertImages(toConvert, options);
    })
    .thenResolve(sections);
};

// Extract text from sections
function extractText(sections) {
    return _.reduce(sections, function(prev, section) {
        if (section.type != "normal") return prev;

        var $ = cheerio.load(section.content);
        $("*").each(function() {
            prev = prev+" "+$(this).text();
        });

        return prev;
    }, "");
};

module.exports = {
    normalize: normalizePage,
    extractText: extractText
};
<commit_msg>Fix page normalization with empty links or images<commit_after>var Q = require('q');
var _ = require('lodash');
var path = require('path');
var cheerio = require('cheerio');
var domSerializer = require('dom-serializer');
var request = require('request');
var crc = require("crc");

var links = require('./links');
var imgUtils = require('./images');
var fs = require('./fs');

// Render a cheerio dom as html
var renderDom = function($, dom, options) {
    if (!dom && $._root && $._root.children) {
        dom = $._root.children;
    }

    options = options|| dom.options || $._options;
    return domSerializer(dom, options);
};

// Map of images that have been converted
var imgConversionCache = {};

function replaceText($, el, search, replace, text_only ) {
    return $(el).each(function(){
        var node = this.firstChild,
            val,
            new_val,

            // Elements to be removed at the end.
            remove = [];

        // Only continue if firstChild exists.
        if ( node ) {

            // Loop over all childNodes.
            do {
                // Only process text nodes.
                if ( node.nodeType === 3 ) {

                    // The original node value.
                    val = node.nodeValue;

                    // The new value.
                    new_val = val.replace( search, replace );

                    // Only replace text if the new value is actually different!
                    if ( new_val !== val ) {

                        if ( !text_only && /</.test( new_val ) ) {
                            // The new value contains HTML, set it in a slower but far more
                            // robust way.
                            $(node).before( new_val );

                            // Don't remove the node yet, or the loop will lose its place.
                            remove.push( node );
                        } else {
                            // The new value contains no HTML, so it can be set in this
                            // very fast, simple way.
                            node.nodeValue = new_val;
                        }
                    }
                }

            } while ( node = node.nextSibling );
        }

        // Time to remove those elements!
        remove.length && $(remove).remove();
    });
};

function pregQuote( str ) {
    return (str+'').replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, "\\$1");
};

// Adapt an html snippet to be relative to a base folder
function normalizeHtml(src, options) {
    var $ = cheerio.load(src, {
        // We should parse html without trying to normalize too much
        xmlMode: false,

        // SVG need some attributes to use uppercases
        lowerCaseAttributeNames: false,
        lowerCaseTags: false
    });
    var toConvert = [];
    var svgContent = {};
    var outputRoot = options.book.options.output;

    imgConversionCache[outputRoot] = imgConversionCache[outputRoot] || {};

    // Find svg images to extract and process
    if (options.convertImages) {
        $("svg").each(function() {
            var content = renderDom($, $(this));
            var svgId = _.uniqueId("svg");
            var dest = svgId+".svg";

            // Generate filename
            dest = "/"+fs.getUniqueFilename(outputRoot, dest);

            svgContent[dest] = content;
            $(this).replaceWith($("").attr("src", dest));
        });
    }

    // Find images to normalize
    $("img").each(function() {
        var origin = undefined;
        var src = $(this).attr("src");
        if (!src) return;
        var isExternal = links.isExternal(src);

        // Transform as relative to the bases
        if (links.isRelative(src)) {
            src = links.toAbsolute(src, options.base, options.output);
        }

        // Convert if needed
        if (options.convertImages) {
            // If image is external and ebook, then downlaod the images
            if (isExternal) {
                origin = src;
                src = "/"+crc.crc32(origin).toString(16)+path.extname(origin);
                src = links.toAbsolute(src, options.base, options.output);
                isExternal = false;
            }

            var ext = path.extname(src);
            var srcAbs = path.join("/", options.base, src);

            // Test image extension
            if (_.contains(imgUtils.INVALID, ext)) {
                if (imgConversionCache[outputRoot][srcAbs]) {
                    // Already converted
                    src = imgConversionCache[outputRoot][srcAbs];
                } else {
                    // Not converted yet
                    var dest = "";

                    // Replace extension
                    dest = path.join(path.dirname(srcAbs), path.basename(srcAbs, ext)+".png");
                    dest = dest[0] == "/"? dest.slice(1) : dest;

                    // Get a name that doesn't exists
                    dest = fs.getUniqueFilename(outputRoot, dest);

                    options.book.log.debug.ln("detect invalid image (will be converted to png):", srcAbs);

                    // Add to cache
                    imgConversionCache[outputRoot][srcAbs] = "/"+dest;

                    // Push to convert
                    toConvert.push({
                        origin: origin,
                        content: svgContent[srcAbs],
                        source: isExternal? srcAbs : path.join("./", srcAbs),
                        dest: path.join("./", dest)
                    });

                    src = path.join("/", dest);
                }

                // Reset as relative to output
                src = links.toAbsolute(src, options.base, options.output);
            }

            else if (origin) {
                // Need to downlaod image
                toConvert.push({
                    origin: origin,
                    source: path.join("./", srcAbs)
                });
            }
        }

        $(this).attr("src", src);
    });

    $("a").each(function() {
        var href = $(this).attr("href");
        if (!href) return;

        if (links.isRelative(href)) {
            var absolutePath = path.join(options.base, href);

            // If is in navigation relative: change extsnio nto be .html
            if (options.navigation[absolutePath]) href = links.changeExtension(href, ".html");

            // Transform as absolute
            href = links.toAbsolute(href, options.base, options.output);
        } else {
            $(this).attr("target", "_blank");
        }

        // Transform extension
        $(this).attr("href", href);
    });

    // Replace glossayr terms
    _.each(options.glossary, function(term) {
        var r =  new RegExp( "\\b(" + pregQuote(term.name.toLowerCase()) + ")\\b" , 'gi' );
        var includedInFiles = false;

        $("*").each(function() {
            replaceText($, this, r, function(match) {
                // Add to files index in glossary
                if (!includedInFiles) {
                    includedInFiles = true;
                    term.files = term.files || [];
                    term.files.push(options.navigation[options.input]);
                }
                return ""+match+"";
            });
        });
    });

    return {
        html: renderDom($),
        images: toConvert
    };
};

// Convert svg images to png
function convertImages(images, options) {
    if (!options.convertImages) return Q();

    var downloaded = [];
    options.book.log.debug.ln("convert ", images.length, "images to png");

    return _.reduce(images, function(prev, image) {
        var imgin = path.resolve(options.book.options.output, image.source);

        return prev

        // Write image if need to be download
        .then(function() {
            if (!image.origin && !_.contains(downloaded, image.origin)) return;
            options.book.log.debug("download image", image.origin, "...");
            downloaded.push(image.origin);
            return options.book.log.debug.promise(fs.writeStream(imgin, request(image.origin)));
        })

        // Write svg if content
        .then(function() {
            if (!image.content) return;
            return fs.writeFile(imgin, image.content);
        })

        // Convert
        .then(function() {
            if (!image.dest) return;
            var imgout = path.resolve(options.book.options.output, image.dest);
            options.book.log.debug("convert image", image.source, "to", image.dest, "...");
            return options.book.log.debug.promise(imgUtils.convertSVG(imgin, imgout));
        });
    }, Q())
    .then(function() {
        options.book.log.debug.ok(images.length+" images converted with success");
    });
};

// Adapt page content to be relative to a base folder
function normalizePage(sections, options) {
    options = _.defaults(options || {}, {
        // Current book
        book: null,

        // Do we need to convert svg?
        convertImages: false,

        // Current file path
        input: ".",

        // Navigation to use to transform path
        navigation: {},

        // Directory parent of the file currently in rendering process
        base: "./",

        // Directory parent from the html output
        output: "./",

        // Glossary terms
        glossary: []
    });

    // List of images to convert
    var toConvert = [];

    sections = _.map(sections, function(section) {
        if (section.type != "normal") return section;

        var out = normalizeHtml(section.content, options);;

        toConvert = toConvert.concat(out.images);
        section.content = out.html;
        return section;
    });

    return Q()
    .then(function() {
        toConvert = _.uniq(toConvert, 'source');
        return convertImages(toConvert, options);
    })
    .thenResolve(sections);
};

// Extract text from sections
function extractText(sections) {
    return _.reduce(sections, function(prev, section) {
        if (section.type != "normal") return prev;

        var $ = cheerio.load(section.content);
        $("*").each(function() {
            prev = prev+" "+$(this).text();
        });

        return prev;
    }, "");
};

module.exports = {
    normalize: normalizePage,
    extractText: extractText
};
<|endoftext|>


<commit_before>var Q = require('q');
var _ = require('lodash');
var url = require('url');
var path = require('path');
var cheerio = require('cheerio');
var domSerializer = require('dom-serializer');
var request = require('request');
var crc = require("crc");

var links = require('./links');
var imgUtils = require('./images');
var fs = require('./fs');
var batch = require('./batch');
var code = require('./code');

// Render a cheerio dom as html
var renderDom = function($, dom, options) {
    if (!dom && $._root && $._root.children) {
        dom = $._root.children;
    }

    options = options|| dom.options || $._options;
    return domSerializer(dom, options);
};

// Map of images that have been converted
var imgConversionCache = {};

function replaceText($, el, search, replace, text_only ) {
    return $(el).each(function(){
        var node = this.firstChild,
            val,
            new_val,

            // Elements to be removed at the end.
            remove = [];

        // Only continue if firstChild exists.
        if ( node ) {

            // Loop over all childNodes.
            do {
                // Only process text nodes.
                if ( node.nodeType === 3 ) {

                    // The original node value.
                    val = node.nodeValue;

                    // The new value.
                    new_val = val.replace( search, replace );

                    // Only replace text if the new value is actually different!
                    if ( new_val !== val ) {

                        if ( !text_only && /</.test( new_val ) ) {
                            // The new value contains HTML, set it in a slower but far more
                            // robust way.
                            $(node).before( new_val );

                            // Don't remove the node yet, or the loop will lose its place.
                            remove.push( node );
                        } else {
                            // The new value contains no HTML, so it can be set in this
                            // very fast, simple way.
                            node.nodeValue = new_val;
                        }
                    }
                }

            } while ( node = node.nextSibling );
        }

        // Time to remove those elements!
        remove.length && $(remove).remove();
    });
};

function pregQuote( str ) {
    return (str+'').replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, "\\$1");
};

// Adapt an html snippet to be relative to a base folder
function normalizeHtml(src, options) {
    var $ = cheerio.load(src, {
        // We should parse html without trying to normalize too much
        xmlMode: false,

        // SVG need some attributes to use uppercases
        lowerCaseAttributeNames: false,
        lowerCaseTags: false
    });
    var toConvert = [];
    var svgContent = {};
    var outputRoot = options.book.options.output;

    imgConversionCache[outputRoot] = imgConversionCache[outputRoot] || {};

    // Find svg images to extract and process
    if (options.convertImages) {
        $("svg").each(function() {
            var content = renderDom($, $(this));
            var svgId = _.uniqueId("svg");
            var dest = svgId+".svg";

            // Generate filename
            dest = "/"+fs.getUniqueFilename(outputRoot, dest);

            svgContent[dest] = '<?xml version="1.0" encoding="UTF-8"?>'+content;
            $(this).replaceWith($("").attr("src", dest));
        });
    }

    // Find images to normalize
    $("img").each(function() {
        var origin = undefined;
        var src = $(this).attr("src");
        if (!src) return;
        var isExternal = links.isExternal(src);

        // Transform as relative to the bases
        if (links.isRelative(src)) {
            src = links.toAbsolute(src, options.base, options.output);
        }

        // Convert if needed
        if (options.convertImages) {
            // If image is external and ebook, then downlaod the images
            if (isExternal) {
                origin = src;
                src = "/"+crc.crc32(origin).toString(16)+path.extname(origin);
                src = links.toAbsolute(src, options.base, options.output);
                isExternal = false;
            }

            var ext = path.extname(src);
            var srcAbs = links.join("/", options.base, src);

            // Test image extension
            if (_.contains(imgUtils.INVALID, ext)) {
                if (imgConversionCache[outputRoot][srcAbs]) {
                    // Already converted
                    src = imgConversionCache[outputRoot][srcAbs];
                } else {
                    // Not converted yet
                    var dest = "";

                    // Replace extension
                    dest = links.join(path.dirname(srcAbs), path.basename(srcAbs, ext)+".png");
                    dest = dest[0] == "/"? dest.slice(1) : dest;

                    // Get a name that doesn't exists
                    dest = fs.getUniqueFilename(outputRoot, dest);

                    options.book.log.debug.ln("detect invalid image (will be converted to png):", srcAbs);

                    // Add to cache
                    imgConversionCache[outputRoot][srcAbs] = "/"+dest;

                    // Push to convert
                    toConvert.push({
                        origin: origin,
                        content: svgContent[srcAbs],
                        source: isExternal? srcAbs : path.join("./", srcAbs),
                        dest: path.join("./", dest)
                    });

                    src = links.join("/", dest);
                }

                // Reset as relative to output
                src = links.toAbsolute(src, options.base, options.output);
            }

            else if (origin) {
                // Need to downlaod image
                toConvert.push({
                    origin: origin,
                    source: path.join("./", srcAbs)
                });
            }
        }

        $(this).attr("src", src);
    });

    // Normalize links
    $("a").each(function() {
        var href = $(this).attr("href");
        if (!href) return;

        if (links.isAnchor(href)) {
            // Keep it as it is
        } else if (links.isRelative(href)) {
            var parts = url.parse(href);
            var absolutePath = links.join(options.base, parts.pathname);
            var anchor = parts.hash || "";

            // If is in navigation relative: transform as content
            if (options.navigation[absolutePath]) {
                absolutePath = options.book.contentLink(absolutePath);
            }

            // Transform as absolute
            href = links.toAbsolute("/"+absolutePath, options.base, options.output)+anchor;
        } else {
            // External links
            $(this).attr("target", "_blank");
        }

        // Transform extension
        $(this).attr("href", href);
    });

    // Highlight code blocks
    $("code").each(function() {
        // Extract language
        var lang = _.chain(
                ($(this).attr("class") || "").split(" ")
            )
            .map(function(cl) {
                if (cl.search("lang-") === 0) return cl.slice("lang-".length);
                return null;
            })
            .compact()
            .first()
            .value();

        if (lang) {
            var html = code.highlight(
                lang,
                $(this).text()
            );

            $(this).html(html);
        }
    });

    // Replace glossary terms
    var glossary = _.sortBy(options.glossary, function(term) {
        return -term.name.length;
    });
    _.each(glossary, function(term) {
        var r =  new RegExp( "\\b(" + pregQuote(term.name.toLowerCase()) + ")\\b" , 'gi' );
        var includedInFiles = false;

        $("*").each(function() {
            // Ignore codeblocks
            if (_.contains(["code", "pre", "a"], this.name.toLowerCase())) return;

            replaceText($, this, r, function(match) {
                // Add to files index in glossary
                if (!includedInFiles) {
                    includedInFiles = true;
                    term.files = term.files || [];
                    term.files.push(options.navigation[options.input]);
                }
                return ""+match+"";
            });
        });
    });

    return {
        html: renderDom($),
        images: toConvert
    };
};

// Convert svg images to png
function convertImages(images, options) {
    if (!options.convertImages) return Q();

    var downloaded = [];
    options.book.log.debug.ln("convert ", images.length, "images to png");

    return batch.execEach(images, {
        max: 100,
        fn: function(image) {
            var imgin = path.resolve(options.book.options.output, image.source);

            return Q()

            // Write image if need to be download
            .then(function() {
                if (!image.origin && !_.contains(downloaded, image.origin)) return;
                options.book.log.debug("download image", image.origin, "...");
                downloaded.push(image.origin);
                return options.book.log.debug.promise(fs.writeStream(imgin, request(image.origin)))
                .fail(function(err) {
                    if (!_.isError(err)) err = new Error(err);

                    err.message = 'Fail downloading '+image.origin+': '+err.message;
                    throw err;
                });
            })

            // Write svg if content
            .then(function() {
                if (!image.content) return;
                return fs.writeFile(imgin, image.content);
            })

            // Convert
            .then(function() {
                if (!image.dest) return;
                var imgout = path.resolve(options.book.options.output, image.dest);
                options.book.log.debug("convert image", image.source, "to", image.dest, "...");
                return options.book.log.debug.promise(imgUtils.convertSVG(imgin, imgout));
            });
        }
    })
    .then(function() {
        options.book.log.debug.ok(images.length+" images converted with success");
    });
};

// Adapt page content to be relative to a base folder
function normalizePage(sections, options) {
    options = _.defaults(options || {}, {
        // Current book
        book: null,

        // Do we need to convert svg?
        convertImages: false,

        // Current file path
        input: ".",

        // Navigation to use to transform path
        navigation: {},

        // Directory parent of the file currently in rendering process
        base: "./",

        // Directory parent from the html output
        output: "./",

        // Glossary terms
        glossary: []
    });

    // List of images to convert
    var toConvert = [];

    sections = _.map(sections, function(section) {
        if (section.type != "normal") return section;

        var out = normalizeHtml(section.content, options);;

        toConvert = toConvert.concat(out.images);
        section.content = out.html;
        return section;
    });

    return Q()
    .then(function() {
        toConvert = _.uniq(toConvert, 'source');
        return convertImages(toConvert, options);
    })
    .thenResolve(sections);
};

// Extract text from sections
function extractText(sections) {
    return _.reduce(sections, function(prev, section) {
        if (section.type != "normal") return prev;

        var $ = cheerio.load(section.content);
        $("*").each(function() {
            prev = prev+" "+$(this).text();
        });

        return prev;
    }, "");
};

module.exports = {
    normalize: normalizePage,
    extractText: extractText
};
<commit_msg>Add warning for file outside spine<commit_after>var Q = require('q');
var _ = require('lodash');
var url = require('url');
var path = require('path');
var cheerio = require('cheerio');
var domSerializer = require('dom-serializer');
var request = require('request');
var crc = require("crc");

var links = require('./links');
var imgUtils = require('./images');
var fs = require('./fs');
var batch = require('./batch');
var code = require('./code');

var parsableExtensions = require('gitbook-parsers').extensions;

// Render a cheerio dom as html
var renderDom = function($, dom, options) {
    if (!dom && $._root && $._root.children) {
        dom = $._root.children;
    }

    options = options|| dom.options || $._options;
    return domSerializer(dom, options);
};

// Map of images that have been converted
var imgConversionCache = {};

function replaceText($, el, search, replace, text_only ) {
    return $(el).each(function(){
        var node = this.firstChild,
            val,
            new_val,

            // Elements to be removed at the end.
            remove = [];

        // Only continue if firstChild exists.
        if ( node ) {

            // Loop over all childNodes.
            do {
                // Only process text nodes.
                if ( node.nodeType === 3 ) {

                    // The original node value.
                    val = node.nodeValue;

                    // The new value.
                    new_val = val.replace( search, replace );

                    // Only replace text if the new value is actually different!
                    if ( new_val !== val ) {

                        if ( !text_only && /</.test( new_val ) ) {
                            // The new value contains HTML, set it in a slower but far more
                            // robust way.
                            $(node).before( new_val );

                            // Don't remove the node yet, or the loop will lose its place.
                            remove.push( node );
                        } else {
                            // The new value contains no HTML, so it can be set in this
                            // very fast, simple way.
                            node.nodeValue = new_val;
                        }
                    }
                }

            } while ( node = node.nextSibling );
        }

        // Time to remove those elements!
        remove.length && $(remove).remove();
    });
};

function pregQuote( str ) {
    return (str+'').replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, "\\$1");
};

// Adapt an html snippet to be relative to a base folder
function normalizeHtml(src, options) {
    var $ = cheerio.load(src, {
        // We should parse html without trying to normalize too much
        xmlMode: false,

        // SVG need some attributes to use uppercases
        lowerCaseAttributeNames: false,
        lowerCaseTags: false
    });
    var toConvert = [];
    var svgContent = {};
    var outputRoot = options.book.options.output;

    imgConversionCache[outputRoot] = imgConversionCache[outputRoot] || {};

    // Find svg images to extract and process
    if (options.convertImages) {
        $("svg").each(function() {
            var content = renderDom($, $(this));
            var svgId = _.uniqueId("svg");
            var dest = svgId+".svg";

            // Generate filename
            dest = "/"+fs.getUniqueFilename(outputRoot, dest);

            svgContent[dest] = '<?xml version="1.0" encoding="UTF-8"?>'+content;
            $(this).replaceWith($("").attr("src", dest));
        });
    }

    // Find images to normalize
    $("img").each(function() {
        var origin = undefined;
        var src = $(this).attr("src");
        if (!src) return;
        var isExternal = links.isExternal(src);

        // Transform as relative to the bases
        if (links.isRelative(src)) {
            src = links.toAbsolute(src, options.base, options.output);
        }

        // Convert if needed
        if (options.convertImages) {
            // If image is external and ebook, then downlaod the images
            if (isExternal) {
                origin = src;
                src = "/"+crc.crc32(origin).toString(16)+path.extname(origin);
                src = links.toAbsolute(src, options.base, options.output);
                isExternal = false;
            }

            var ext = path.extname(src);
            var srcAbs = links.join("/", options.base, src);

            // Test image extension
            if (_.contains(imgUtils.INVALID, ext)) {
                if (imgConversionCache[outputRoot][srcAbs]) {
                    // Already converted
                    src = imgConversionCache[outputRoot][srcAbs];
                } else {
                    // Not converted yet
                    var dest = "";

                    // Replace extension
                    dest = links.join(path.dirname(srcAbs), path.basename(srcAbs, ext)+".png");
                    dest = dest[0] == "/"? dest.slice(1) : dest;

                    // Get a name that doesn't exists
                    dest = fs.getUniqueFilename(outputRoot, dest);

                    options.book.log.debug.ln("detect invalid image (will be converted to png):", srcAbs);

                    // Add to cache
                    imgConversionCache[outputRoot][srcAbs] = "/"+dest;

                    // Push to convert
                    toConvert.push({
                        origin: origin,
                        content: svgContent[srcAbs],
                        source: isExternal? srcAbs : path.join("./", srcAbs),
                        dest: path.join("./", dest)
                    });

                    src = links.join("/", dest);
                }

                // Reset as relative to output
                src = links.toAbsolute(src, options.base, options.output);
            }

            else if (origin) {
                // Need to downlaod image
                toConvert.push({
                    origin: origin,
                    source: path.join("./", srcAbs)
                });
            }
        }

        $(this).attr("src", src);
    });

    // Normalize links
    $("a").each(function() {
        var href = $(this).attr("href");
        if (!href) return;

        if (links.isAnchor(href)) {
            // Keep it as it is
        } else if (links.isRelative(href)) {
            var parts = url.parse(href);
            var absolutePath = links.join(options.base, parts.pathname);
            var anchor = parts.hash || "";

            // If is in navigation relative: transform as content
            if (options.navigation[absolutePath]) {
                absolutePath = options.book.contentLink(absolutePath);
            }

            // If md/adoc/rst files is not in summary
            // or for ebook, signal all files that are outside the summary
            else if (_.contains(parsableExtensions, path.extname(absolutePath))
            || _.contains(['epub', 'pdf', 'mobi'], options.book.options.generator)) {
                options.book.log.warn.ln("page", options.input, "contains an hyperlink to resource outside spine '"+href+"'");
            }

            // Transform as absolute
            href = links.toAbsolute("/"+absolutePath, options.base, options.output)+anchor;
        } else {
            // External links
            $(this).attr("target", "_blank");
        }

        // Transform extension
        $(this).attr("href", href);
    });

    // Highlight code blocks
    $("code").each(function() {
        // Extract language
        var lang = _.chain(
                ($(this).attr("class") || "").split(" ")
            )
            .map(function(cl) {
                if (cl.search("lang-") === 0) return cl.slice("lang-".length);
                return null;
            })
            .compact()
            .first()
            .value();

        if (lang) {
            var html = code.highlight(
                lang,
                $(this).text()
            );

            $(this).html(html);
        }
    });

    // Replace glossary terms
    var glossary = _.sortBy(options.glossary, function(term) {
        return -term.name.length;
    });
    _.each(glossary, function(term) {
        var r =  new RegExp( "\\b(" + pregQuote(term.name.toLowerCase()) + ")\\b" , 'gi' );
        var includedInFiles = false;

        $("*").each(function() {
            // Ignore codeblocks
            if (_.contains(["code", "pre", "a"], this.name.toLowerCase())) return;

            replaceText($, this, r, function(match) {
                // Add to files index in glossary
                if (!includedInFiles) {
                    includedInFiles = true;
                    term.files = term.files || [];
                    term.files.push(options.navigation[options.input]);
                }
                return ""+match+"";
            });
        });
    });

    return {
        html: renderDom($),
        images: toConvert
    };
};

// Convert svg images to png
function convertImages(images, options) {
    if (!options.convertImages) return Q();

    var downloaded = [];
    options.book.log.debug.ln("convert ", images.length, "images to png");

    return batch.execEach(images, {
        max: 100,
        fn: function(image) {
            var imgin = path.resolve(options.book.options.output, image.source);

            return Q()

            // Write image if need to be download
            .then(function() {
                if (!image.origin && !_.contains(downloaded, image.origin)) return;
                options.book.log.debug("download image", image.origin, "...");
                downloaded.push(image.origin);
                return options.book.log.debug.promise(fs.writeStream(imgin, request(image.origin)))
                .fail(function(err) {
                    if (!_.isError(err)) err = new Error(err);

                    err.message = 'Fail downloading '+image.origin+': '+err.message;
                    throw err;
                });
            })

            // Write svg if content
            .then(function() {
                if (!image.content) return;
                return fs.writeFile(imgin, image.content);
            })

            // Convert
            .then(function() {
                if (!image.dest) return;
                var imgout = path.resolve(options.book.options.output, image.dest);
                options.book.log.debug("convert image", image.source, "to", image.dest, "...");
                return options.book.log.debug.promise(imgUtils.convertSVG(imgin, imgout));
            });
        }
    })
    .then(function() {
        options.book.log.debug.ok(images.length+" images converted with success");
    });
};

// Adapt page content to be relative to a base folder
function normalizePage(sections, options) {
    options = _.defaults(options || {}, {
        // Current book
        book: null,

        // Do we need to convert svg?
        convertImages: false,

        // Current file path
        input: ".",

        // Navigation to use to transform path
        navigation: {},

        // Directory parent of the file currently in rendering process
        base: "./",

        // Directory parent from the html output
        output: "./",

        // Glossary terms
        glossary: []
    });

    // List of images to convert
    var toConvert = [];

    sections = _.map(sections, function(section) {
        if (section.type != "normal") return section;

        var out = normalizeHtml(section.content, options);;

        toConvert = toConvert.concat(out.images);
        section.content = out.html;
        return section;
    });

    return Q()
    .then(function() {
        toConvert = _.uniq(toConvert, 'source');
        return convertImages(toConvert, options);
    })
    .thenResolve(sections);
};

// Extract text from sections
function extractText(sections) {
    return _.reduce(sections, function(prev, section) {
        if (section.type != "normal") return prev;

        var $ = cheerio.load(section.content);
        $("*").each(function() {
            prev = prev+" "+$(this).text();
        });

        return prev;
    }, "");
};

module.exports = {
    normalize: normalizePage,
    extractText: extractText
};
<|endoftext|>


<commit_before>const Language = require('../models/Language');
const Submission = require('../models/Submission');
const docker = require('../engines/docker');
const io = require('./socket-io');
const slack = require('./slack');
const { generateInput, isValidAnswer } = require('./secret.js');

const markError = (submission, error) => {
  console.error(error);
  submission.status = 'error';
  submission.save();
};

module.exports.validate = (user, submission, language, solution) => {
  submission.input = generateInput();
  submission.save((error, newSubmission) => {
    if (error) {
      return markError(submission, error);
    }

    const params = {
      id: language.slug,
      code: newSubmission.code,
      stdin: newSubmission.input,
    };

    docker(params).then((info) => {
      console.log('info:', info);

      if (typeof info !== 'object') {
        return markError(newSubmission, 'info is not object');
      }

      const { stdout, stderr } = info;
      newSubmission.stdout = stdout;
      newSubmission.stderr = stderr;

      if (isValidAnswer(newSubmission.input, stdout)) {
        newSubmission.status = 'success';

        Language.update({ slug: language.slug }, { $set: { solution: newSubmission._id } }, (error) => {
          if (error) {
            return markError(newSubmission, error);
          }

          io.emit('update-languages', {});
        });
      } else {
        newSubmission.status = 'failed';
      }

      newSubmission.save((error, submission) => {
        if (error) {
          return markError(submission, error);
        }

        Submission.populate(submission, { path: 'user language' }, (error, submission) => {
          io.emit('update-submission', { _id: submission._id, });

          if (submission.status === 'success') {
            const bytesInfo = (() => {
              if (solution) {
                return `${
                  solution.user.team === 0 ? ':red_circle:' : ':large_blue_circle:'
                } *${solution.size} bytes* => ${
                  submission.user.team === 0 ? ':red_circle:' : ':large_blue_circle:'
                } *${submission.size} bytes*`;
              }

              return `:new: ${
                submission.user.team === 0 ? ':red_circle:' : ':large_blue_circle:'
              } *${submission.size} bytes*`;
            })();

            slack.send({
              text: `*${submission.user.name()}* won the language *${language.name}*!! (${bytesInfo}) Congrats!!! <https://esolang.hakatashi.com/submissions/${submission._id}|[Details]>`,
            });
          }
        });
      });
    }).catch((error) => {
      markError(submission, error);
    });
  });
};
<commit_msg>Fix slack notification<commit_after>const Language = require('../models/Language');
const Submission = require('../models/Submission');
const docker = require('../engines/docker');
const io = require('./socket-io');
const slack = require('./slack');
const { generateInput, isValidAnswer } = require('./secret.js');

const markError = (submission, error) => {
  console.error(error);
  submission.status = 'error';
  submission.save();
};

module.exports.validate = (user, submission, language, solution) => {
  submission.input = generateInput();
  submission.save((error, newSubmission) => {
    if (error) {
      return markError(submission, error);
    }

    const params = {
      id: language.slug,
      code: newSubmission.code,
      stdin: newSubmission.input,
    };

    docker(params).then((info) => {
      console.log('info:', info);

      if (typeof info !== 'object') {
        return markError(newSubmission, 'info is not object');
      }

      const { stdout, stderr } = info;
      newSubmission.stdout = stdout;
      newSubmission.stderr = stderr;

      if (isValidAnswer(newSubmission.input, stdout)) {
        newSubmission.status = 'success';

        Language.update({ slug: language.slug }, { $set: { solution: newSubmission._id } }, (error) => {
          if (error) {
            return markError(newSubmission, error);
          }

          io.emit('update-languages', {});
        });
      } else {
        newSubmission.status = 'failed';
      }

      newSubmission.save((error, submission) => {
        if (error) {
          return markError(submission, error);
        }

        Submission.populate(submission, { path: 'user language' }, (error, submission) => {
          io.emit('update-submission', { _id: submission._id, });

          if (submission.status === 'success') {
            const bytesInfo = (() => {
              if (solution) {
                return `${
                  [':heart:', ':blue_heart:', ':green_heart:'][solution.user.team]
                } *${solution.size} bytes* => ${
                  [':heart:', ':blue_heart:', ':green_heart:'][submission.user.team]
                } *${submission.size} bytes*`;
              }

              return `:new: ${
                [':heart:', ':blue_heart:', ':green_heart:'][submission.user.team]
              } *${submission.size} bytes*`;
            })();

            slack.send({
              text: `*${submission.user.name()}* won the language *${language.name}*!! (${bytesInfo}) Congrats!!! <https://esolang.hakatashi.com/submissions/${submission._id}|[Details]>`,
            });
          }
        });
      });
    }).catch((error) => {
      markError(submission, error);
    });
  });
};
<|endoftext|>


<commit_before>import React from 'react';
import cx from 'classnames';
import {autobind} from 'core-decorators';

function getClasses(staticClasses, active) {
  return cx(...staticClasses, {
    inactive: !active,
    active,
  });
}

class TabsNavigationItem extends React.Component {
  static propTypes = {
    title: React.PropTypes.node,
    tabIndex: React.PropTypes.number.isRequired,
    active: React.PropTypes.bool,
    onClick: React.PropTypes.func,
  }

  render() {
    const classes = getClasses(['github-Tabs-NavigationItem'], this.props.active);
    return (

        {this.props.title}

    );
  }

  @autobind
  handleClick() {
    this.props.onClick(this.props.tabIndex);
  }
}

export default class Tabs extends React.Component {
  static propTypes = {
    className: React.PropTypes.string,
    activeIndex: React.PropTypes.number.isRequired,
    children: React.PropTypes.node,
    onChange: React.PropTypes.func,
  }

  static defaultProps = {
    className: '',
    onChange: () => {},
  }

  render() {
    const children = React.Children.toArray(this.props.children).filter(v => !!v);
    return (

          {children.map((child, i) => (
            child && <TabsNavigationItem
              key={i}
              title={child.props.title}
              active={this.props.activeIndex === i}
              tabIndex={i}
              onClick={this.props.onChange}
                     />
          ))}

          {children.map((child, i) => (
            child && React.cloneElement(child, {active: i === this.props.activeIndex})
          ))}

    );
  }
}

Tabs.Panel = class TabPanel extends React.Component {
  static propTypes = {
    className: React.PropTypes.string,
    active: React.PropTypes.bool,
    children: React.PropTypes.node,
    title: React.PropTypes.node,
  }

  static defaultProps = {
    className: '',
    active: false,
  }

  render() {
    // eslint-disable-next-line no-unused-vars
    const {active, children, className, title, ...others} = this.props;
    return (

        {this.props.children}

    );
  }
};
<commit_msg>Change Tab conditional rendering to fix linter format<commit_after>import React from 'react';
import cx from 'classnames';
import {autobind} from 'core-decorators';

function getClasses(staticClasses, active) {
  return cx(...staticClasses, {
    inactive: !active,
    active,
  });
}

class TabsNavigationItem extends React.Component {
  static propTypes = {
    title: React.PropTypes.node,
    tabIndex: React.PropTypes.number.isRequired,
    active: React.PropTypes.bool,
    onClick: React.PropTypes.func,
  }

  render() {
    const classes = getClasses(['github-Tabs-NavigationItem'], this.props.active);
    return (

        {this.props.title}

    );
  }

  @autobind
  handleClick() {
    this.props.onClick(this.props.tabIndex);
  }
}

export default class Tabs extends React.Component {
  static propTypes = {
    className: React.PropTypes.string,
    activeIndex: React.PropTypes.number.isRequired,
    children: React.PropTypes.node,
    onChange: React.PropTypes.func,
  }

  static defaultProps = {
    className: '',
    onChange: () => {},
  }

  render() {
    const children = React.Children.toArray(this.props.children).filter(v => !!v);
    return (

          {children.map((child, i) => child && (
            <TabsNavigationItem
              key={i}
              title={child.props.title}
              active={this.props.activeIndex === i}
              tabIndex={i}
              onClick={this.props.onChange}
            />
          ))}

          {children.map((child, i) => child && (
            React.cloneElement(child, {active: i === this.props.activeIndex})
          ))}

    );
  }
}

Tabs.Panel = class TabPanel extends React.Component {
  static propTypes = {
    className: React.PropTypes.string,
    active: React.PropTypes.bool,
    children: React.PropTypes.node,
    title: React.PropTypes.node,
  }

  static defaultProps = {
    className: '',
    active: false,
  }

  render() {
    // eslint-disable-next-line no-unused-vars
    const {active, children, className, title, ...others} = this.props;
    return (

        {this.props.children}

    );
  }
};
<|endoftext|>


<commit_before>var events = require('events'),
    path = require('path');

var vows = require(path.join(__dirname, '..', 'vows'));
var Context = require('vows/context').Context;
var tryEnd = vows.tryEnd;

this.Suite = function () {
    this.results = {
        honored: 0,
        broken:  0,
        errored: 0,
        total:   0,
        time:  null
    };
    this.batches = [];
};

this.Suite.prototype = new(function () {
    this.addVows = function (tests) {
        var batch = {
            tests: tests,
            suite:  this,
            remaining: 0,
            honored:   0,
            broken:    0,
            errored:   0,
            total:     0
        };

        this.batches.push(batch);

        if (typeof(tests) === 'object') {
            if ('topic' in tests) {
                throw new(Error)("missing top-level context.");
            }
            // Count the number of vows/promises expected to fire,
            // so we know when the tests are over.
            // We match the keys against `matcher`, to decide
            // whether or not they should be included in the test.
            (function count(tests) {
                var match = false;
                batch.remaining ++;
                Object.keys(tests).filter(function (k) {
                    return k !== 'topic';
                }).forEach(function (key) {
                    if (typeof(tests[key]) === "object") {
                        if (! (match = count(tests[key]) ||
                               match || vows.options.matcher.test(key))) {
                            delete tests[key];
                            batch.remaining --;
                        }
                    }
                });
                return match;
            })(tests);
        }

        return this;
    };

    this.runVows = function (batch) {
        var topic,
            tests   = batch.tests,
            promise = batch.promise = new(events.EventEmitter);

        if (typeof(tests) === 'function') {
            return tests.call(null);
        } else {
            // The test runner, it calls itself recursively, passing the
            // previous context to the inner contexts. This is so the `topic`
            // functions have access to all the previous context topics in their
            // arguments list.
            // It is defined and invoked at the same time.
            // If it encounters a `topic` function, it waits for the returned
            // promise to emit (the topic), at which point it runs the functions under it,
            // passing the topic as an argument.
            (function run(ctx, lastTopic) {
                var old = false;
                topic = ctx.tests.topic;

                if (typeof(topic) === 'function') {
                    // Run the topic, passing the previous context topics
                    topic = topic.apply(ctx.env, ctx.topics);
                }

                // If this context has a topic, store it in `lastTopic`,
                // if not, use the last topic, passed down by a parent
                // context.
                if (topic) {
                    lastTopic = topic;
                } else {
                    old   = true;
                    topic = lastTopic;
                }

                // If the topic doesn't return an event emitter (such as a promise),
                // we create it ourselves, and emit the value on the next tick.
                if (! (topic instanceof vows.options.Emitter)) {
                    ctx.emitter = new(vows.options.Emitter);

                    if (! ctx._callback) {
                        process.nextTick(function (val) {
                            return function () { ctx.emitter.emit("success", val) };
                        }(topic));
                    } else if (typeof(topic) !== "undefined" && !old) {
                        throw new(Error)("topic must not return anything when using `this.callback`.");
                    }
                    topic = ctx.emitter;
                }

                topic.addListener('success', function (val) {
                    // Once the topic fires, add the return value
                    // to the beginning of the topics list, so it
                    // becomes the first argument for the next topic.
                    // If we're using the parent topic, no need to
                    // prepend it to the topics list, or we'll get
                    // duplicates.
                    if (! old) ctx.topics.unshift(val);
                });

                // Now run the tests, or sub-contexts
                Object.keys(ctx.tests).filter(function (k) {
                    return ctx.tests[k] && k !== 'topic';
                }).forEach(function (item) {
                    // Create a new evaluation context,
                    // inheriting from the parent one.
                    var env = Object.create(ctx.env);

                    // Holds the current test or context
                    var vow = Object.create({
                        callback: ctx.tests[item],
                        context: ctx.name,
                        description: item,
                        binding: ctx.env,
                        batch: batch
                    });

                    // If we encounter a function, add it to the callbacks
                    // of the `topic` function, so it'll get called once the
                    // topic fires.
                    // If we encounter an object literal, we recurse, sending it
                    // our current context.
                    if (typeof(vow.callback) === 'function') {
                        topic.addVow(vow);
                    } else if (typeof(vow.callback) === 'object' && ! Array.isArray(vow.callback)) {
                        // If there's a setup stage, we have to wait for it to fire,
                        // before calling the inner context. Else, just run the inner context
                        // synchronously.
                        if (topic) {
                            topic.addListener("success", function (ctx) {
                                return function (val) {
                                    return run(new(Context)(vow, ctx, env), lastTopic);
                                };
                            }(ctx));
                        } else {
                            run(new(Context)(vow, ctx, env), lastTopic);
                        }
                    }
                });
                // Check if we're done running the tests
                batch.remaining --;
                tryEnd(batch);
            // This is our initial, empty context
            })(new(Context)({ callback: tests, context: null, description: null }, {}));
        }
        return promise;
    };

    this.run = function (callback) {
        var that = this;
        var start = new(Date);

        (function run(batches) {
            var batch;

            if (batch = batches.shift()) {
                that.runVows(batch).addListener('end', function () {
                    run(batches);
                });
            } else {
                that.results.time = (new(Date) - start) / 1000;

                vows.reporter.report(['finish', that.results]);

                if (callback) { callback(that.results) }

                // Don't exit until stdout is empty
                process.stdout.addListener('drain', function () {
                    process.exit(that.results.broken || that.results.errored ? 1 : 0);
                });
            }
        })(this.batches.slice(0));
    };

    this.runParallel = function () {};
});
<commit_msg>ability to reset batch/suite<commit_after>var events = require('events'),
    path = require('path');

require.paths.unshift(path.join(__dirname, '..'));

var vows = require('vows');
var Context = require('vows/context').Context;
var tryEnd = vows.tryEnd;

this.Suite = function (subject) {
    this.subject = subject;
    this.batches = [];
    this.reset();
};

this.Suite.prototype = new(function () {
    this.reset = function () {
        this.results = {
            honored: 0,
            broken:  0,
            errored: 0,
            total:   0,
            time:  null
        };
        this.batches.forEach(function (b) {
            b.lastContext = null;
            b.remaining = b._remaining;
            b.honored = b.broken = b.errored = b.total = 0;
        });
    };

    this.addVows = function (tests) {
        var batch = {
            tests: tests,
            suite:  this,
            remaining: 0,
           _remaining: 0,
            honored:   0,
            broken:    0,
            errored:   0,
            total:     0
        };

        this.batches.push(batch);

        if (typeof(tests) === 'object') {
            if ('topic' in tests) {
                throw new(Error)("missing top-level context.");
            }
            // Count the number of vows/promises expected to fire,
            // so we know when the tests are over.
            // We match the keys against `matcher`, to decide
            // whether or not they should be included in the test.
            (function count(tests) {
                var match = false;
                batch.remaining ++;
                Object.keys(tests).filter(function (k) {
                    return k !== 'topic';
                }).forEach(function (key) {
                    if (typeof(tests[key]) === "object") {
                        if (! (match = count(tests[key]) ||
                               match || vows.options.matcher.test(key))) {
                            delete tests[key];
                            batch.remaining --;
                        }
                    }
                });
                return match;
            })(tests);
        }
        batch._remaining = batch.remaining;

        return this;
    };

    this.runVows = function (batch) {
        var topic,
            tests   = batch.tests,
            promise = batch.promise = new(events.EventEmitter);

        if (typeof(tests) === 'function') {
            return tests.call(null);
        } else {
            // The test runner, it calls itself recursively, passing the
            // previous context to the inner contexts. This is so the `topic`
            // functions have access to all the previous context topics in their
            // arguments list.
            // It is defined and invoked at the same time.
            // If it encounters a `topic` function, it waits for the returned
            // promise to emit (the topic), at which point it runs the functions under it,
            // passing the topic as an argument.
            (function run(ctx, lastTopic) {
                var old = false;
                topic = ctx.tests.topic;

                if (typeof(topic) === 'function') {
                    // Run the topic, passing the previous context topics
                    topic = topic.apply(ctx.env, ctx.topics);
                }

                // If this context has a topic, store it in `lastTopic`,
                // if not, use the last topic, passed down by a parent
                // context.
                if (topic) {
                    lastTopic = topic;
                } else {
                    old   = true;
                    topic = lastTopic;
                }

                // If the topic doesn't return an event emitter (such as a promise),
                // we create it ourselves, and emit the value on the next tick.
                if (! (topic instanceof vows.options.Emitter)) {
                    ctx.emitter = new(vows.options.Emitter);

                    if (! ctx._callback) {
                        process.nextTick(function (val) {
                            return function () { ctx.emitter.emit("success", val) };
                        }(topic));
                    } else if (typeof(topic) !== "undefined" && !old) {
                        throw new(Error)("topic must not return anything when using `this.callback`.");
                    }
                    topic = ctx.emitter;
                }

                topic.addListener('success', function (val) {
                    // Once the topic fires, add the return value
                    // to the beginning of the topics list, so it
                    // becomes the first argument for the next topic.
                    // If we're using the parent topic, no need to
                    // prepend it to the topics list, or we'll get
                    // duplicates.
                    if (! old) ctx.topics.unshift(val);
                });

                // Now run the tests, or sub-contexts
                Object.keys(ctx.tests).filter(function (k) {
                    return ctx.tests[k] && k !== 'topic';
                }).forEach(function (item) {
                    // Create a new evaluation context,
                    // inheriting from the parent one.
                    var env = Object.create(ctx.env);

                    // Holds the current test or context
                    var vow = Object.create({
                        callback: ctx.tests[item],
                        context: ctx.name,
                        description: item,
                        binding: ctx.env,
                        batch: batch
                    });

                    // If we encounter a function, add it to the callbacks
                    // of the `topic` function, so it'll get called once the
                    // topic fires.
                    // If we encounter an object literal, we recurse, sending it
                    // our current context.
                    if (typeof(vow.callback) === 'function') {
                        topic.addVow(vow);
                    } else if (typeof(vow.callback) === 'object' && ! Array.isArray(vow.callback)) {
                        // If there's a setup stage, we have to wait for it to fire,
                        // before calling the inner context. Else, just run the inner context
                        // synchronously.
                        if (topic) {
                            topic.addListener("success", function (ctx) {
                                return function (val) {
                                    return run(new(Context)(vow, ctx, env), lastTopic);
                                };
                            }(ctx));
                        } else {
                            run(new(Context)(vow, ctx, env), lastTopic);
                        }
                    }
                });
                // Check if we're done running the tests
                batch.remaining --;
                tryEnd(batch);
            // This is our initial, empty context
            })(new(Context)({ callback: tests, context: null, description: null }, {}));
        }
        return promise;
    };

    this.run = function (callback) {
        var that = this;
        var start = new(Date);

        (function run(batches) {
            var batch;

            if (batch = batches.shift()) {
                that.runVows(batch).addListener('end', function () {
                    run(batches);
                });
            } else {
                that.results.time = (new(Date) - start) / 1000;

                vows.reporter.report(['finish', that.results]);

                if (callback) { callback(that.results) }

                // Don't exit until stdout is empty
                process.stdout.addListener('drain', function () {
                    process.exit(that.results.broken || that.results.errored ? 1 : 0);
                });
                that.reset();
            }
        })(this.batches.slice(0));
    };

    this.runParallel = function () {};
});
<|endoftext|>


<commit_before>/* global console */
/* global decodeURIComponent */

var tfsExec = require('./utils/tfsExec'),
    utils   = require('./utils/common'),
    vscode  = require('vscode');

// Let's check if vscode-tfs is outdated
utils.checkLastRelease();

var globalCommands = [
  {
    label: 'Get',
    detail: 'Get the latest version of files for the entire workspace.',
    isGlobal: true
  },
  {
    label: 'Status',
    detail: 'Show pending changes for the entire workspace.',
    isGlobal: true
  }
];

var fullCommands = globalCommands.concat([
  {
    label: 'Add',
    detail: 'Adds current file to TFS.',
    isGlobal: false
  },
  {
    label: 'CheckIn',
    detail: 'Adds current file to TFS.',
    isGlobal: false
  },
  {
    label: 'Undo',
    detail: 'Undo current file pending changes.',
    isGlobal: false
  }
]);

// We sort the commands array by their label property
fullCommands.sort(function (commandA, commandB) {
  return (commandA.label < commandB.label) ? -1 : 1;
});

/**
 * Show a selectable list of available commands.
 *
 * @version 0.5.0
 */
function tfsList() {
  var isGlobal = !utils.getCurrentFilePath(),
      commands = isGlobal ? globalCommands : fullCommands,
      promise = vscode.window.showQuickPick(commands);

  promise.then(function(command) {
    if (!command) {
      return;
    }

    tfsExec(command.label.toLowerCase(), command.isGlobal);
  });
}

/**
 * @todo Add a description
 *
 * @version 0.5.0
 */
exports.activate = function(context) {
  vscode.workspace.onDidChangeTextDocument(function() {
    if (vscode.window.activeTextEditor.document.isDirty) {
      return;
    }

     tfsExec('checkout');
  });

  var disposables = [
    vscode.commands.registerCommand('vscode-tfs.list', tfsList)
  ];

  context.subscriptions.concat(disposables);
}

exports.deactivate = function() {};
<commit_msg>Dev: better output for status command<commit_after>/* global console */
/* global decodeURIComponent */

var tfsExec = require('./utils/tfsExec'),
    utils   = require('./utils/common'),
    vscode  = require('vscode');

// Let's check if vscode-tfs is outdated
utils.checkLastRelease();

var globalCommands = [
  {
    label: 'Get',
    detail: 'Get the latest version of files for the entire workspace.',
    isGlobal: true
  },
  {
    label: 'Status',
    detail: 'Show pending changes for the entire workspace.',
    isGlobal: true
  }
];

var fullCommands = globalCommands.concat([
  {
    label: 'Add',
    detail: 'Adds current file to TFS.',
    isGlobal: false
  },
  {
    label: 'CheckIn',
    detail: 'Adds current file to TFS.',
    isGlobal: false
  },
  {
    label: 'Undo',
    detail: 'Undo current file pending changes.',
    isGlobal: false
  }
]);

// We sort the commands array by their label property
fullCommands.sort(function (commandA, commandB) {
  return (commandA.label < commandB.label) ? -1 : 1;
});

/**
 * Show a selectable list of available commands.
 *
 * @version 0.5.0
 */
function tfsList() {
  var isGlobal = !utils.getCurrentFilePath(),
      commands = isGlobal ? globalCommands : fullCommands,
      promise = vscode.window.showQuickPick(commands);

  promise.then(function(command) {
    if (!command) {
      return;
    }

    tfsExec(command.label.toLowerCase(), command.isGlobal);
  });
}

/**
 * Extension activation
 * - Checkout the active file when it's modified
 * - Link commands to functions
 *
 * @version 0.5.3
 */
exports.activate = function(context) {
  vscode.workspace.onDidChangeTextDocument(function() {
    if (vscode.window.activeTextEditor.document.isDirty) {
      return;
    }

    tfsExec('checkout');
  });

  var disposables = [
    vscode.commands.registerCommand('vscode-tfs.get',    function() { tfsExec('get', true); }),
    vscode.commands.registerCommand('vscode-tfs.list',   tfsList),
    vscode.commands.registerCommand('vscode-tfs.status', function() { tfsExec('status', true); })
  ];

  context.subscriptions.concat(disposables);
}

exports.deactivate = function() {};
<|endoftext|>


<commit_before>'use strict'
var pick = require('lodash.pick')
var url = require('url')
var connect = require('pull-ws/client')
var util = require('../util')

module.exports = function (params, cb) {
  params = pick(params, 'host', 'port', 'path', 'protocol')

  var address = {
    protocol: params.protocol,
    port: params.port,
    pathname: params.path,
    slashes: true
  }

  if (params.path == null) {
    if(util.isWindows()){
      if(params.host ==='0.0.0.0')params.host='localhost'
      if(params.host ==='::')params.host='localhost'
    }

    address.hostname = params.host
    address.port = params.port

  } else {
    address = 'ws+unix://' + params.path
  }

  var c = connect(url.format(address), function (err) {
    if (err) return cb(err)
    c.type = 'connection'
    delete c.remoteAddress
    if (cb) {
      cb(null, c)
      cb = null
    }
  })

}<commit_msg>Add binaryType for ws protocol<commit_after>'use strict'
var pick = require('lodash.pick')
var url = require('url')
var connect = require('pull-ws/client')
var util = require('../util')

module.exports = function (params, cb) {
  params = pick(params, 'host', 'port', 'path', 'protocol',"binaryType")

  var address = {
    protocol: params.protocol,
    port: params.port,
    pathname: params.path,
    slashes: true
  }
  if (params.path == null) {
    if(util.isWindows()){
      if(params.host ==='0.0.0.0')params.host='localhost'
      if(params.host ==='::')params.host='localhost'
    }

    address.hostname = params.host
    address.port = params.port

  } else {
    address = 'ws+unix://' + params.path
  }

  var c = connect(url.format(address),{
    binaryType:params.binaryType,
    onConnect: function (err) {
      if (err) return cb(err)
      c.type = 'connection'
      delete c.remoteAddress
      if (cb) {
        cb(null, c)
        cb = null
      }
    }
  })

}
<|endoftext|>


<commit_before>const Promise = require('bluebird').Promise;
const request = require('request-promise');
const config = require('config');

const FILTER_PREFIX = 'api/json?tree=';
const BUILD_DETAILS_FILTER = 'id,timestamp,building,duration,result,changeSet[items[rev]],actions';

function getJobData(jobId) {
  const url = createBuildsUrl(jobId);
  return request.get({ url, json: true })
    .then(result => getJobDetails(result))
    .catch(error => ({ error }));
}

function getJobDetails(job) {
  return Promise.map(job.builds, build => {
    const actionFilter = createActionFilter(job.name);
    const detailsUrl = createBuildDetailsUrl(build.url, actionFilter);
    return getBuildDetails(detailsUrl);
  });
}

function getBuildDetails(url) {
  return request.get({ url, json: true })
    .then(result => normalize(result))
    .catch(error => ({ error }));
}

function normalize(details) {
  if (details.building) {
    return {};
  }
  const actions = details.actions.filter(isEmpty);
  const normalized = Object.assign(details, { actions });
  delete normalized.building;
  return normalized;
}

function isEmpty(obj) {
  return obj !== null && Object.keys(obj).length !== 0;
}

function createBuildsUrl(jobId) {
  return `${config.get('jenkins.url')}/job/${jobId}/${FILTER_PREFIX}name,builds[url]`;
}

function createActionFilter(jobId) {
  const filterName = config.get(`jenkins.jobs.${jobId}`);
  return config.get(`jenkins.filters.${filterName}`);
}

function createBuildDetailsUrl(url, actionFilter) {
  return `${url}${FILTER_PREFIX}${BUILD_DETAILS_FILTER}${actionFilter}`;
}

module.exports = {
  getBuildData: () => {
    const jobs = config.get('jenkins.jobs');
    return Promise.map(Object.keys(jobs), job => getJobData(job))
      .then(builds => builds.filter(isEmpty));
  }
};
<commit_msg>Change jobId to jobName (as Jenkins calls it). Add jobName to result data.<commit_after>const Promise = require('bluebird').Promise;
const request = require('request-promise');
const config = require('config');

const FILTER_PREFIX = 'api/json?tree=';
const BUILD_DETAILS_FILTER = 'id,timestamp,building,duration,result,changeSet[items[rev]],actions';

function getJobData(jobName) {
  const url = createBuildsUrl(jobName);
  return request.get({ url, json: true })
    .then(result => getJobDetails(result))
    .catch(error => ({ error }));
}

function getJobDetails(job) {
  return Promise.map(job.builds, build => {
    const actionFilter = createActionFilter(job.name);
    const detailsUrl = createBuildDetailsUrl(build.url, actionFilter);
    return getBuildDetails(job.name, detailsUrl);
  });
}

function getBuildDetails(jobName, url) {
  return request.get({ url, json: true })
    .then(result => normalize(jobName, result))
    .catch(error => ({ error }));
}

function normalize(jobName, details) {
  if (details.building) {
    return {};
  }
  const actions = details.actions.filter(isEmpty);
  const normalized = Object.assign(details, { actions });
  delete normalized.building;
  normalized.jobName = jobName;
  return normalized;
}

function isEmpty(obj) {
  return obj !== null && Object.keys(obj).length !== 0;
}

function createBuildsUrl(jobName) {
  return `${config.get('jenkins.url')}/job/${jobName}/${FILTER_PREFIX}name,builds[url]`;
}

function createActionFilter(jobName) {
  const filterName = config.get(`jenkins.jobs.${jobName}`);
  return config.get(`jenkins.filters.${filterName}`);
}

function createBuildDetailsUrl(url, actionFilter) {
  return `${url}${FILTER_PREFIX}${BUILD_DETAILS_FILTER}${actionFilter}`;
}

module.exports = {
  getBuildData: () => {
    const jobs = config.get('jenkins.jobs');
    return Promise.map(Object.keys(jobs), jobName => getJobData(jobName))
      .then(builds => builds.filter(isEmpty));
  }
};
<|endoftext|>