<commit_before>/*

    This module is designed to wrap the EC2 metadata gathering process.  AWS provides
    an endpoint that you can hit to get specific metadata values.

    You can see the AWS metadata documentation here:
    http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AESDG-chapter-instancedata.html

 */
var Q = require('q');
var _ = require('lodash');
var dns = require('dns');
var http = require('http');
var url = require('url');

/*
    Endpoint Configuration
 */
var BASE_URL = 'http://169.254.169.254/latest/';
var METADATA_ENDPOINT = 'meta-data/';
var DYNAMIC_ENDPOINT = 'dynamic/';

/*
    Allowed data types for the metadata endpoint
 */
var ALLOWED_METADATA_VALUES = [
    'ami-id',
    'ami-launch-index',
    'ami-manifest-path',
    'ancestor-ami-ids',
    'block-device-mapping/ami',
    'block-device-mapping/ebsN',
    'block-device-mapping/ephemeralN',
    'block-device-mapping/root',
    'block-device-mapping/swap',
    'hostname',
    'iam/info',
    'iam/security-credentials/role-name',
    'instance-action',
    'instance-id',
    'instance-type',
    'kernel-id',
    'local-hostname',
    'local-ipv4',
    'mac',
    'network/interfaces/macs/mac/device-number',
    'network/interfaces/macs/mac/ipv4-associations/public-ip',
    'network/interfaces/macs/mac/local-hostname',
    'network/interfaces/macs/mac/local-ipv4s',
    'network/interfaces/macs/mac/mac',
    'network/interfaces/macs/mac/owner-id',
    'network/interfaces/macs/mac/public-hostname',
    'network/interfaces/macs/mac/public-ipv4s',
    'network/interfaces/macs/mac/security-groups',
    'network/interfaces/macs/mac/security-group-ids',
    'network/interfaces/macs/mac/subnet-id',
    'network/interfaces/macs/mac/subnet-ipv4-cidr-block',
    'network/interfaces/macs/mac/vpc-id',
    'network/interfaces/macs/mac/vpc-ipv4-cidr-block',
    'placement/availability-zone',
    'product-codes',
    'public-hostname',
    'public-ipv4',
    'public-keys/0/openssh-key',
    'ramdisk-id',
    'reservation-id',
    'security-groups',
    'services/domain'
];

/*
    All allowed data types
 */
var ALLOWED_TYPES = _.union(ALLOWED_METADATA_VALUES, ALLOWED_DYNAMIC_VALUES);

/*
    Allowed data types for the dynamic endpoint
 */
var ALLOWED_DYNAMIC_VALUES = [
    'fws/instance-monitoring',
    'instance-identity/document',
    'instance-identity/pkcs7',
    'instance-identity/signature'
];

/*
    Argument count for data types which require arguments
 */
var REQUIRED_ARGUMENT_COUNT = {
    'block-device-mapping/ebsN' : 1,
    'block-device-mapping/ephemeralN' : 1,
    'iam/security-credentials/role-name' : 1,
    'network/interfaces/macs/mac/device-number' : 1,
    'network/interfaces/macs/mac/ipv4-associations/public-ip' : 2,
    'network/interfaces/macs/mac/local-hostname' : 1,
    'network/interfaces/macs/mac/local-ipv4s' : 1,
    'network/interfaces/macs/mac/mac' : 1,
    'network/interfaces/macs/mac/owner-id' : 1,
    'network/interfaces/macs/mac/public-hostname' : 1,
    'network/interfaces/macs/mac/public-ipv4s' : 1,
    'network/interfaces/macs/mac/security-groups' : 1,
    'network/interfaces/macs/mac/security-group-ids' : 1,
    'network/interfaces/macs/mac/subnet-id' : 1,
    'network/interfaces/macs/mac/subnet-ipv4-cidr-block' : 1,
    'network/interfaces/macs/mac/vpc-id' : 1,
    'network/interfaces/macs/mac/vpc-ipv4-cidr-block' : 1
};

/*
    The replacement strings used to populate the URL value for data types
    which require one or more arguments.
 */
var REPLACEMENT_STRINGS = {
    'block-device-mapping/ebsN' : 'block-device-mapping/ebs%1',
    'block-device-mapping/ephemeralN' : 'block-device-mapping/ephemeral%1',
    'iam/security-credentials/role-name' : 'iam/security-credentials/%1',
    'network/interfaces/macs/mac/device-number' : 'network/interfaces/macs/%1/device-number',
    'network/interfaces/macs/mac/ipv4-associations/public-ip' : 'network/interfaces/macs/%1/ipv4-associations/%2',
    'network/interfaces/macs/mac/local-hostname' : 'network/interfaces/macs/%1/local-hostname',
    'network/interfaces/macs/mac/local-ipv4s' : 'network/interfaces/macs/%1/local-ipv4s',
    'network/interfaces/macs/mac/mac' : 'network/interfaces/macs/%1/mac',
    'network/interfaces/macs/mac/owner-id' : 'network/interfaces/macs/%1/owner-id',
    'network/interfaces/macs/mac/public-hostname' : 'network/interfaces/macs/%1/public-hostname',
    'network/interfaces/macs/mac/public-ipv4s' : 'network/interfaces/macs/%1/public-ipv4s',
    'network/interfaces/macs/mac/security-groups' : 'network/interfaces/macs/%1/security-groups',
    'network/interfaces/macs/mac/security-group-ids' : 'network/interfaces/macs/%1/security-group-ids',
    'network/interfaces/macs/mac/subnet-id' : 'network/interfaces/macs/%1/subnet-id',
    'network/interfaces/macs/mac/subnet-ipv4-cidr-block' : 'network/interfaces/macs/%1/subnet-ipv4-cidr-block',
    'network/interfaces/macs/mac/vpc-id' : 'network/interfaces/macs/%1/vpc-id',
    'network/interfaces/macs/mac/vpc-ipv4-cidr-block' : 'network/interfaces/macs/%1/vpc-ipv4-cidr-block'
};

var isAllowedType = function(type) {
    return _.contains(ALLOWED_TYPES, type);
};

var fetchDataForURL = function(url) {
    var deferred = Q.defer();
    var req = http.get(url, function(res) {
        res.setEncoding('utf8');
        var result = "";
        res.on('data', function(chunk) {
            result += chunk;
        });
        res.on('end', function() {
            if (res.statusCode >= 200 && res.statusCode < 300) {
                deferred.resolve(result);
            } else if (res.statusCode == 404) {
                // return null for 404 responses - we already know it's a valid request
                // eg. public-ipv4 of a VPC instance without one
                deferred.resolve(null);
            } else {
                var e = new Error(result);
                e.code = res.statusCode;
                deferred.reject(e);
            }
        });
    });
    req.setTimeout( 2500, function() {
        deferred.reject(new Error('EC2-Metadata Fetch Timeout.'));
        req.abort();
    });
    req.on('error', function(e) {
        deferred.reject(e);
    });
    return deferred.promise;
};

var urlForType = function(type, args) {
    if(!isAllowedType(type)) {
        throw new Error('Not a valid EC2 metadata type');
        return null;
    }

    var url = "";
    if(_.contains(ALLOWED_METADATA_VALUES, type)) {
        url = BASE_URL + METADATA_ENDPOINT + replaceValuesForType(type, args);
    } else if(_.contains(ALLOWED_DYNAMIC_VALUES, type)) {
        url = BASE_URL + DYNAMIC_ENDPOINT + replaceValuesForType(type, args);
    }

    return url;
};

var replaceValuesForType = function(type, args) {
    if(REPLACEMENT_STRINGS.hasOwnProperty(type)) {
        var requiredArgCount = REQUIRED_ARGUMENT_COUNT[type] || 0;
        if(((requiredArgCount > 0) && !args) || args.length != requiredArgCount) {
            throw new Error('Incorrect Number of Arguments for Type');
            return null;
        }
        var replacementString = REPLACEMENT_STRINGS[type];
        var numArgs = REQUIRED_ARGUMENT_COUNT[type];
        for(var i = 0; i < numArgs; i++) {
            var argReplacementString = "%" + (i + 1);
            replacementString = replacementString.replace(argReplacementString, args[i]);
        }
        return replacementString;
    } else {
        return type;
    }
};

var getMetadataForInstance = function(type, args) {
    if(_.indexOf(ALLOWED_TYPES,type) < 0) {
        var deferred = Q.defer();
        deferred.reject("Not a valid metadata type");
        return deferred.promise;
    }

    var url = urlForType(type, args);
    return fetchDataForURL(url);
};

var isEC2 = function() {
    // the host running the process won't change, so we can cache this value
    if (isEC2._cached !== undefined) {
        return Q(isEC2._cached);
    }

    var deferred = Q.defer();
    var promise = deferred.promise.then(function setCache(value) {
        isEC2._cached = value;
        return value;
    });

    // first try to resolve ec2 internal metadata. This doesn't work with VPC or custom DNS though.
    dns.lookup('instance-data.ec2.internal.', function dnsResult(err, result) {
        if (result == '169.254.169.254') {
            deferred.resolve(true);
        } else {
            // next do a HEAD query to 'http://169.254.169.254/latest/' with a short timeout
            var reqOpts = _.assign(url.parse(BASE_URL), {method: 'HEAD'});
            var req = http.request(reqOpts, function httpRes(res) {
                deferred.resolve(true);
                req.abort();
            });
            req.setTimeout( 500, function httpTimeout() {
                deferred.resolve(false);
                req.abort();
            });
            req.on('error', function httpErr(e) {
                deferred.resolve(false);
            });
            req.end();
        }
    });
    return promise;
}

/*
    EXPORTS
 */

exports.urlForType = urlForType;
exports.replaceValuesForType = replaceValuesForType;
exports.getMetadataForInstance = getMetadataForInstance;
exports.isEC2 = isEC2;
<commit_msg>Modified the order of the variable definitions<commit_after>/*

    This module is designed to wrap the EC2 metadata gathering process.  AWS provides
    an endpoint that you can hit to get specific metadata values.

    You can see the AWS metadata documentation here:
    http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AESDG-chapter-instancedata.html

 */
var Q = require('q');
var _ = require('lodash');
var dns = require('dns');
var http = require('http');
var url = require('url');

/*
    Endpoint Configuration
 */
var BASE_URL = 'http://169.254.169.254/latest/';
var METADATA_ENDPOINT = 'meta-data/';
var DYNAMIC_ENDPOINT = 'dynamic/';

/*
    Allowed data types for the metadata endpoint
 */
var ALLOWED_METADATA_VALUES = [
    'ami-id',
    'ami-launch-index',
    'ami-manifest-path',
    'ancestor-ami-ids',
    'block-device-mapping/ami',
    'block-device-mapping/ebsN',
    'block-device-mapping/ephemeralN',
    'block-device-mapping/root',
    'block-device-mapping/swap',
    'hostname',
    'iam/info',
    'iam/security-credentials/role-name',
    'instance-action',
    'instance-id',
    'instance-type',
    'kernel-id',
    'local-hostname',
    'local-ipv4',
    'mac',
    'network/interfaces/macs/mac/device-number',
    'network/interfaces/macs/mac/ipv4-associations/public-ip',
    'network/interfaces/macs/mac/local-hostname',
    'network/interfaces/macs/mac/local-ipv4s',
    'network/interfaces/macs/mac/mac',
    'network/interfaces/macs/mac/owner-id',
    'network/interfaces/macs/mac/public-hostname',
    'network/interfaces/macs/mac/public-ipv4s',
    'network/interfaces/macs/mac/security-groups',
    'network/interfaces/macs/mac/security-group-ids',
    'network/interfaces/macs/mac/subnet-id',
    'network/interfaces/macs/mac/subnet-ipv4-cidr-block',
    'network/interfaces/macs/mac/vpc-id',
    'network/interfaces/macs/mac/vpc-ipv4-cidr-block',
    'placement/availability-zone',
    'product-codes',
    'public-hostname',
    'public-ipv4',
    'public-keys/0/openssh-key',
    'ramdisk-id',
    'reservation-id',
    'security-groups',
    'services/domain'
];

/*
    Allowed data types for the dynamic endpoint
 */
var ALLOWED_DYNAMIC_VALUES = [
    'fws/instance-monitoring',
    'instance-identity/document',
    'instance-identity/pkcs7',
    'instance-identity/signature'
];

/*
    All allowed data types
 */
var ALLOWED_TYPES = _.union(ALLOWED_METADATA_VALUES, ALLOWED_DYNAMIC_VALUES);

/*
    Argument count for data types which require arguments
 */
var REQUIRED_ARGUMENT_COUNT = {
    'block-device-mapping/ebsN' : 1,
    'block-device-mapping/ephemeralN' : 1,
    'iam/security-credentials/role-name' : 1,
    'network/interfaces/macs/mac/device-number' : 1,
    'network/interfaces/macs/mac/ipv4-associations/public-ip' : 2,
    'network/interfaces/macs/mac/local-hostname' : 1,
    'network/interfaces/macs/mac/local-ipv4s' : 1,
    'network/interfaces/macs/mac/mac' : 1,
    'network/interfaces/macs/mac/owner-id' : 1,
    'network/interfaces/macs/mac/public-hostname' : 1,
    'network/interfaces/macs/mac/public-ipv4s' : 1,
    'network/interfaces/macs/mac/security-groups' : 1,
    'network/interfaces/macs/mac/security-group-ids' : 1,
    'network/interfaces/macs/mac/subnet-id' : 1,
    'network/interfaces/macs/mac/subnet-ipv4-cidr-block' : 1,
    'network/interfaces/macs/mac/vpc-id' : 1,
    'network/interfaces/macs/mac/vpc-ipv4-cidr-block' : 1
};

/*
    The replacement strings used to populate the URL value for data types
    which require one or more arguments.
 */
var REPLACEMENT_STRINGS = {
    'block-device-mapping/ebsN' : 'block-device-mapping/ebs%1',
    'block-device-mapping/ephemeralN' : 'block-device-mapping/ephemeral%1',
    'iam/security-credentials/role-name' : 'iam/security-credentials/%1',
    'network/interfaces/macs/mac/device-number' : 'network/interfaces/macs/%1/device-number',
    'network/interfaces/macs/mac/ipv4-associations/public-ip' : 'network/interfaces/macs/%1/ipv4-associations/%2',
    'network/interfaces/macs/mac/local-hostname' : 'network/interfaces/macs/%1/local-hostname',
    'network/interfaces/macs/mac/local-ipv4s' : 'network/interfaces/macs/%1/local-ipv4s',
    'network/interfaces/macs/mac/mac' : 'network/interfaces/macs/%1/mac',
    'network/interfaces/macs/mac/owner-id' : 'network/interfaces/macs/%1/owner-id',
    'network/interfaces/macs/mac/public-hostname' : 'network/interfaces/macs/%1/public-hostname',
    'network/interfaces/macs/mac/public-ipv4s' : 'network/interfaces/macs/%1/public-ipv4s',
    'network/interfaces/macs/mac/security-groups' : 'network/interfaces/macs/%1/security-groups',
    'network/interfaces/macs/mac/security-group-ids' : 'network/interfaces/macs/%1/security-group-ids',
    'network/interfaces/macs/mac/subnet-id' : 'network/interfaces/macs/%1/subnet-id',
    'network/interfaces/macs/mac/subnet-ipv4-cidr-block' : 'network/interfaces/macs/%1/subnet-ipv4-cidr-block',
    'network/interfaces/macs/mac/vpc-id' : 'network/interfaces/macs/%1/vpc-id',
    'network/interfaces/macs/mac/vpc-ipv4-cidr-block' : 'network/interfaces/macs/%1/vpc-ipv4-cidr-block'
};

var isAllowedType = function(type) {
    return _.contains(ALLOWED_TYPES, type);
};

var fetchDataForURL = function(url) {
    var deferred = Q.defer();
    var req = http.get(url, function(res) {
        res.setEncoding('utf8');
        var result = "";
        res.on('data', function(chunk) {
            result += chunk;
        });
        res.on('end', function() {
            if (res.statusCode >= 200 && res.statusCode < 300) {
                deferred.resolve(result);
            } else if (res.statusCode == 404) {
                // return null for 404 responses - we already know it's a valid request
                // eg. public-ipv4 of a VPC instance without one
                deferred.resolve(null);
            } else {
                var e = new Error(result);
                e.code = res.statusCode;
                deferred.reject(e);
            }
        });
    });
    req.setTimeout( 2500, function() {
        deferred.reject(new Error('EC2-Metadata Fetch Timeout.'));
        req.abort();
    });
    req.on('error', function(e) {
        deferred.reject(e);
    });
    return deferred.promise;
};

var urlForType = function(type, args) {
    if(!isAllowedType(type)) {
        throw new Error('Not a valid EC2 metadata type');
        return null;
    }

    var url = "";
    if(_.contains(ALLOWED_METADATA_VALUES, type)) {
        url = BASE_URL + METADATA_ENDPOINT + replaceValuesForType(type, args);
    } else if(_.contains(ALLOWED_DYNAMIC_VALUES, type)) {
        url = BASE_URL + DYNAMIC_ENDPOINT + replaceValuesForType(type, args);
    }

    return url;
};

var replaceValuesForType = function(type, args) {
    if(REPLACEMENT_STRINGS.hasOwnProperty(type)) {
        var requiredArgCount = REQUIRED_ARGUMENT_COUNT[type] || 0;
        if(((requiredArgCount > 0) && !args) || args.length != requiredArgCount) {
            throw new Error('Incorrect Number of Arguments for Type');
            return null;
        }
        var replacementString = REPLACEMENT_STRINGS[type];
        var numArgs = REQUIRED_ARGUMENT_COUNT[type];
        for(var i = 0; i < numArgs; i++) {
            var argReplacementString = "%" + (i + 1);
            replacementString = replacementString.replace(argReplacementString, args[i]);
        }
        return replacementString;
    } else {
        return type;
    }
};

var getMetadataForInstance = function(type, args) {
    if(_.indexOf(ALLOWED_TYPES,type) < 0) {
        var deferred = Q.defer();
        deferred.reject("Not a valid metadata type");
        return deferred.promise;
    }

    var url = urlForType(type, args);
    return fetchDataForURL(url);
};

var isEC2 = function() {
    // the host running the process won't change, so we can cache this value
    if (isEC2._cached !== undefined) {
        return Q(isEC2._cached);
    }

    var deferred = Q.defer();
    var promise = deferred.promise.then(function setCache(value) {
        isEC2._cached = value;
        return value;
    });

    // first try to resolve ec2 internal metadata. This doesn't work with VPC or custom DNS though.
    dns.lookup('instance-data.ec2.internal.', function dnsResult(err, result) {
        if (result == '169.254.169.254') {
            deferred.resolve(true);
        } else {
            // next do a HEAD query to 'http://169.254.169.254/latest/' with a short timeout
            var reqOpts = _.assign(url.parse(BASE_URL), {method: 'HEAD'});
            var req = http.request(reqOpts, function httpRes(res) {
                deferred.resolve(true);
                req.abort();
            });
            req.setTimeout( 500, function httpTimeout() {
                deferred.resolve(false);
                req.abort();
            });
            req.on('error', function httpErr(e) {
                deferred.resolve(false);
            });
            req.end();
        }
    });
    return promise;
}

/*
    EXPORTS
 */

exports.urlForType = urlForType;
exports.replaceValuesForType = replaceValuesForType;
exports.getMetadataForInstance = getMetadataForInstance;
exports.isEC2 = isEC2;
<|endoftext|>


<commit_before>// @flow

import type yargs from 'yargs';

import outdent from 'outdent';
import ora from 'ora';
import chalk from 'chalk';
import sum from 'ramda/src/sum';
import sortBy from 'ramda/src/sortBy';
import reverse from 'ramda/src/reverse';
import { bardot, template } from 'bardot';

import type { RIW, AppStatusResult } from '../../..';
import log from '../../../lib/log';
import { createHandlerWithRIW } from '../../utils';

export const command = 'status';
export const desc = 'Show information about translations in your app';

const here = `app ${command}`;

export const builder = (yyargs: yargs.Argv) => yyargs
    .usage(outdent`
        $0 ${here}

        Shows information about the translations in your app.
        Indicates if you should run "riw app translate".
    `)
    .options({
        missing: {
            alias: 'm',
            boolean: true,
            group: 'Command options',
            desc: outdent`
                Show missing translations, grouped by locale
            `,
        },
    });

const sAbsent = chalk.red('(absent)');
const sTotal = 'TOTAL';

const bold = x => chalk.bold(x);
const sFromLidAr = arlid => arlid.map(bold).join(', ');

const absent = (ct: ?number) => (
    ct == null
        ? ` ${sAbsent}`
        : ''
);

const createPadder = (...ars: string[]) => {
    const ctChar = Math.max.apply(null, ars.map(s => s.length)) + 2;

    return (sLabel: string) => sLabel.padEnd(ctChar);
};

export const handler = createHandlerWithRIW((riw: RIW) => {
    const spinner = ora('Gathering information...').start();

    // keep flow happy
    const {
        default: ctDefault,
        target,
        todo,
        dateInputNewest,
        dateTodo,
        dateDB,
        dateConfig,
    }: AppStatusResult = riw.app.status();

    if (ctDefault == null) {
        spinner.fail(outdent`
            No locale output file(s) found. Run "riw app translate".
        `);
        return;
    }

    if (todo == null) {
        spinner.fail(outdent`
            No todo file found. Run "riw app translate".
        `);
        return;
    }

    const isTargetAbsent = lid => target[lid] === null;

    const arlidTarget = reverse(sortBy(lid => target[lid] || 0, Object.keys(target)));
    const arlidTargetAbsent = arlidTarget.filter(isTargetAbsent);
    let isShowingWarning = false;

    if (arlidTargetAbsent.length > 0) {
        isShowingWarning = true;
        spinner.fail(outdent`
            Missing file or key for ${sFromLidAr(arlidTargetAbsent)}.
        `);
    }

    if (dateInputNewest > dateTodo) {
        isShowingWarning = true;
        spinner.fail(outdent`
            Message descriptors in your source may have changed since you last built riw's output files.
        `);
    }

    if (dateDB > dateTodo) {
        isShowingWarning = true;
        spinner.fail(outdent`
            Translations may have changed since you last built riw's output files.
        `);
    }

    if (dateConfig > dateTodo) {
        isShowingWarning = true;
        spinner.fail(outdent`
            riw configuration may have changed since you last built riw's output files.
        `);
    }

    if (isShowingWarning) {
        spinner.fail(outdent`
            Run ${chalk.bold('riw app translate')} to regenerate output files.
        `);
    }

    spinner.stop();

    const padder = createPadder(sTotal, riw.config.defaultLocale, ...arlidTarget);

    const ctMessageHave = ctDefault + sum(arlidTarget.map(lid => target[lid] || 0));
    const ctMessageWant = ctDefault * (1 + riw.config.targetLocales.length);

    // fill the line.
    // can't use bardot's widthFill as we want bars with different numbers to line up.
    // $FlowFixMe flow moans at process.stdout.columns, no idea how to fix
    const ctCharBar = process.stdout.columns
        - padder('').length // space for locale
        - (ctMessageWant.toString().length * 2) // space for TOTAL numbers
        - 19; // space derived from remaining spaces and the "% complete" suffix
    const barTarget = bardot
        .maximum(ctDefault)
        .widthBar(ctCharBar);
    const barTotal = bardot
        .maximum(ctMessageWant)
        .widthBar(ctCharBar)
        .template(template.barCurMaxPct);

    const arlidTodo = Object.keys(todo || {});
    const ctTodo = todo == null ? 0 : sum(arlidTodo.map(lid => todo[lid].length));

    const summaryDefault = `- ${
        chalk.bold(padder(riw.config.defaultLocale))
    }${
        barTarget.current(ctDefault).toString()
    } ${
        chalk.dim('total messages')
    }`;

    const summaryTarget = lid => `- ${
        chalk.bold(padder(lid))
    }${
        barTarget.current(target[lid] || 0).toString()
    }${
        absent(target[lid])
    }`;

    const summaryTotal = `- ${
        chalk.bold(padder(sTotal))
    }${
        barTotal.current(ctMessageHave).toString()
    } complete`;

    log(outdent`

        App settings:
        - Default locale: ${sFromLidAr([riw.config.defaultLocale])}
        - Target locales: ${sFromLidAr(riw.config.targetLocales)}

        Current output file(s) for these locales:
        ${summaryDefault}
        ${arlidTarget.map(summaryTarget).join('\n')}
        ${summaryTotal}

    `);

    if (ctTodo > 0) {
        log(outdent`
            Todo file contains:
            - ${chalk.bold(ctTodo.toString())} messages to translate
            - ${chalk.bold(arlidTodo.length.toString())} locales – ${sFromLidAr(arlidTodo)}

        `);
    }
});
<commit_msg>fix: remove nonexistent `--missing` option from riw app status usage<commit_after>// @flow

import type yargs from 'yargs';

import outdent from 'outdent';
import ora from 'ora';
import chalk from 'chalk';
import sum from 'ramda/src/sum';
import sortBy from 'ramda/src/sortBy';
import reverse from 'ramda/src/reverse';
import { bardot, template } from 'bardot';

import type { RIW, AppStatusResult } from '../../..';
import log from '../../../lib/log';
import { createHandlerWithRIW } from '../../utils';

export const command = 'status';
export const desc = 'Show information about translations in your app';

const here = `app ${command}`;

export const builder = (yyargs: yargs.Argv) => yyargs
    .usage(outdent`
        $0 ${here}

        Shows information about the translations in your app.
        Indicates if you should run "riw app translate".
    `);

const sAbsent = chalk.red('(absent)');
const sTotal = 'TOTAL';

const bold = x => chalk.bold(x);
const sFromLidAr = arlid => arlid.map(bold).join(', ');

const absent = (ct: ?number) => (
    ct == null
        ? ` ${sAbsent}`
        : ''
);

const createPadder = (...ars: string[]) => {
    const ctChar = Math.max.apply(null, ars.map(s => s.length)) + 2;

    return (sLabel: string) => sLabel.padEnd(ctChar);
};

export const handler = createHandlerWithRIW((riw: RIW) => {
    const spinner = ora('Gathering information...').start();

    // keep flow happy
    const {
        default: ctDefault,
        target,
        todo,
        dateInputNewest,
        dateTodo,
        dateDB,
        dateConfig,
    }: AppStatusResult = riw.app.status();

    if (ctDefault == null) {
        spinner.fail(outdent`
            No locale output file(s) found. Run "riw app translate".
        `);
        return;
    }

    if (todo == null) {
        spinner.fail(outdent`
            No todo file found. Run "riw app translate".
        `);
        return;
    }

    const isTargetAbsent = lid => target[lid] === null;

    const arlidTarget = reverse(sortBy(lid => target[lid] || 0, Object.keys(target)));
    const arlidTargetAbsent = arlidTarget.filter(isTargetAbsent);
    let isShowingWarning = false;

    if (arlidTargetAbsent.length > 0) {
        isShowingWarning = true;
        spinner.fail(outdent`
            Missing file or key for ${sFromLidAr(arlidTargetAbsent)}.
        `);
    }

    if (dateInputNewest > dateTodo) {
        isShowingWarning = true;
        spinner.fail(outdent`
            Message descriptors in your source may have changed since you last built riw's output files.
        `);
    }

    if (dateDB > dateTodo) {
        isShowingWarning = true;
        spinner.fail(outdent`
            Translations may have changed since you last built riw's output files.
        `);
    }

    if (dateConfig > dateTodo) {
        isShowingWarning = true;
        spinner.fail(outdent`
            riw configuration may have changed since you last built riw's output files.
        `);
    }

    if (isShowingWarning) {
        spinner.fail(outdent`
            Run ${chalk.bold('riw app translate')} to regenerate output files.
        `);
    }

    spinner.stop();

    const padder = createPadder(sTotal, riw.config.defaultLocale, ...arlidTarget);

    const ctMessageHave = ctDefault + sum(arlidTarget.map(lid => target[lid] || 0));
    const ctMessageWant = ctDefault * (1 + riw.config.targetLocales.length);

    // fill the line.
    // can't use bardot's widthFill as we want bars with different numbers to line up.
    // $FlowFixMe flow moans at process.stdout.columns, no idea how to fix
    const ctCharBar = process.stdout.columns
        - padder('').length // space for locale
        - (ctMessageWant.toString().length * 2) // space for TOTAL numbers
        - 19; // space derived from remaining spaces and the "% complete" suffix
    const barTarget = bardot
        .maximum(ctDefault)
        .widthBar(ctCharBar);
    const barTotal = bardot
        .maximum(ctMessageWant)
        .widthBar(ctCharBar)
        .template(template.barCurMaxPct);

    const arlidTodo = Object.keys(todo || {});
    const ctTodo = todo == null ? 0 : sum(arlidTodo.map(lid => todo[lid].length));

    const summaryDefault = `- ${
        chalk.bold(padder(riw.config.defaultLocale))
    }${
        barTarget.current(ctDefault).toString()
    } ${
        chalk.dim('total messages')
    }`;

    const summaryTarget = lid => `- ${
        chalk.bold(padder(lid))
    }${
        barTarget.current(target[lid] || 0).toString()
    }${
        absent(target[lid])
    }`;

    const summaryTotal = `- ${
        chalk.bold(padder(sTotal))
    }${
        barTotal.current(ctMessageHave).toString()
    } complete`;

    log(outdent`

        App settings:
        - Default locale: ${sFromLidAr([riw.config.defaultLocale])}
        - Target locales: ${sFromLidAr(riw.config.targetLocales)}

        Current output file(s) for these locales:
        ${summaryDefault}
        ${arlidTarget.map(summaryTarget).join('\n')}
        ${summaryTotal}

    `);

    if (ctTodo > 0) {
        log(outdent`
            Todo file contains:
            - ${chalk.bold(ctTodo.toString())} messages to translate
            - ${chalk.bold(arlidTodo.length.toString())} locales – ${sFromLidAr(arlidTodo)}

        `);
    }
});
<|endoftext|>


<commit_before>'use strict';

const Store = require('jfs');
const fs = require('fs');
const waterfall = require('promise-waterfall');
const DESKTOP_ENV = process.env.DESKTOP_SESSION;
const DEFAULT_CFG_FILE_PATH = __dirname + '/default-config.json';
const CFG_DATA_DIR = getUserHome() + '/.lwsm';
const CFG_FILE_PATH = CFG_DATA_DIR + '/config.json';
const SESSION_DATA_DIR = CFG_DATA_DIR + '/sessionData';

let metaW;

let db;
let CFG;

// INIT
// ------------
const mkdirSync = (dirPath) => {
  try {
    fs.mkdirSync(dirPath);
  } catch (err) {
    if (err.code !== 'EEXIST') {
      throw err;
    }
  }
};

const copySync = (src, dest) => {
  if (!fs.existsSync(src)) {
    return false;
  }
  const data = fs.readFileSync(src, 'utf-8');
  fs.writeFileSync(dest, data);
};

try {
  // if config is already in place
  CFG = JSON.parse(fs.readFileSync(CFG_FILE_PATH, 'utf8'));
} catch (e) {
  // if there is no config yet load default cfg and create files and dirs
  CFG = JSON.parse(fs.readFileSync(DEFAULT_CFG_FILE_PATH, 'utf8'));
  mkdirSync(CFG_DATA_DIR);
  mkdirSync(SESSION_DATA_DIR);

  // copy files
  copySync(DEFAULT_CFG_FILE_PATH, CFG_FILE_PATH);
}

// create data store
db = new Store(SESSION_DATA_DIR);

// also make data dirs accessible to the outside
CFG.DATA_DIR = CFG_DATA_DIR;
CFG.SESSION_DATA_DIR = SESSION_DATA_DIR;

// setup meta wrapper
metaW = require('./metaWrapper')(CFG);

// EXPORT
// ------
module.exports = {
  saveSession,
  removeSession,
  restoreSession,
  x11Helper: metaW.x11Helper,
  getConnectedDisplaysId: metaW.getConnectedDisplaysId,
  resetCfg: () => {
    const configFilePath = CFG.DATA_DIR + '/config.json';
    console.log(CFG);

    if (fs.existsSync(configFilePath)) {
      fs.unlinkSync(configFilePath);
    } else {
      console.error('No Config present in ' + configFilePath);
    }
  },
  getCfg: () => {
    return CFG;
  },
  getDb: () => {
    return db;
  }
};

// HELPER
// --------
function getUserHome() {
  return process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'];
}

function catchGenericErr(err) {
  console.error('Generic Error in Main Handler', err);
  throw err;
}

// MAIN FUNCTIONS
// --------------
function saveSession(sessionName, inputHandlers) {
  const sessionToHandle = sessionName || 'DEFAULT';

  return metaW.getActiveWindowList()
    .then((windowList) => {
      return Promise.all([
        guessAndSetDesktopFilePaths(windowList, inputHandlers.desktopFilePath),
        metaW.getConnectedDisplaysId(),
      ]);
    })
    .then((results) => {
      const windowList = results[0];
      const connectedDisplaysId = results[1];
      return saveSessionForDisplayToDb(sessionToHandle, connectedDisplaysId, windowList);
    })
    .catch((err) => {
      console.error('An error occurred', err);
      throw err;
    });
}

function saveSessionForDisplayToDb(sessionToHandle, connectedDisplaysId, windowList) {
  return new Promise((fulfill, reject) => {
    // check if entry exists and update
    db.get(sessionToHandle, (err, sessionData) => {
      if (!sessionData) {
        // create new object
        sessionData = {
          name: sessionToHandle,
        };
      }
      if (!sessionData.displaysCombinations || !Array.isArray(sessionData.displaysCombinations)) {
        // create new array
        sessionData.displaysCombinations = [];
      }

      const existingDisplayEntry = sessionData.displaysCombinations.find((entry) => entry.id === connectedDisplaysId);
      if (existingDisplayEntry) {
        existingDisplayEntry.windowList = windowList;
      } else {
        sessionData.displaysCombinations.push({
          id: connectedDisplaysId,
          windowList,
        });
      }

      db.save(sessionToHandle, sessionData, (err) => {
        if (err) {
          reject(err);
        } else {
          console.log('SAVED SESSION: ' + sessionToHandle);
          fulfill();
        }
      });
    });
  });
}

function restoreSession(sessionName, isCloseAllOpenWindows) {
  const sessionToHandle = sessionName || 'DEFAULT';

  return new Promise((fulfill, reject) => {
    db.get(sessionToHandle || 'DEFAULT', (err, sessionData) => {
      if (err) {
        reject(err);
        return;
      }

      let savedWindowList;

      closeAllWindowsIfSet(isCloseAllOpenWindows)
        .then(metaW.goToFirstWorkspace)
        .then(metaW.getConnectedDisplaysId)
        .then((connectedDisplaysId) => {
          if (!sessionData.displaysCombinations) {
            console.error(`no display combinations saved yet`);
            return;
          }

          const displayEntry = sessionData.displaysCombinations.find((entry) => entry.id === connectedDisplaysId);

          if (displayEntry) {
            savedWindowList = displayEntry.windowList;
          } else {
            console.error(`no data for current display id '${connectedDisplaysId}' saved yet`);
            return;
          }
          return metaW.getActiveWindowList();
        })
        .then((currentWindowList) => {
          return startSessionPrograms(savedWindowList, currentWindowList);
        })
        .then(() => {
          // gets current window list by itself and returns the updated variant
          return waitForAllAppsToStart(savedWindowList)
        })
        .then((updatedCurrentWindowList) => {
          updateWindowIds(savedWindowList, updatedCurrentWindowList);
          return restoreWindowPositions(savedWindowList);
        })
        .then(() => {
          console.log('RESTORED SESSION: ' + sessionToHandle);
        })
        .catch((err) => {
          console.error('An error occurred', err);
          reject(err);
        })
        .then(fulfill);
    });
  }).catch(catchGenericErr);
}

function removeSession(sessionName) {
  return new Promise((fulfill, reject) => {
    fs.unlink(CFG.SESSION_DATA_DIR + '/' + sessionName + '.json', (error) => {
      if (error) {
        console.error(error);
        reject(error);
      } else {
        fulfill();
      }
    });
  }).catch(catchGenericErr);
}

function closeAllWindowsIfSet(isCloseAll) {
  return new Promise((fulfill, reject) => {
    if (isCloseAll) {
      metaW.getActiveWindowList()
        .then((currentWindowList) => {
          currentWindowList.forEach((win) => {
            metaW.closeWindow(win.windowId);
          });

          waitForAllAppsToClose()
            .then(fulfill)
            .catch(reject);
        });
    } else {
      fulfill();
    }
  }).catch(catchGenericErr);
}

function waitForAllAppsToClose() {
  let totalTimeWaited = 0;
  return new Promise((fulfill, reject) => {
    function pollAllAppsClosed() {
      setTimeout(() => {
        metaW.getActiveWindowList()
          .then((currentWindowList) => {
            totalTimeWaited += CFG.POLL_ALL_APPS_STARTED_TIMEOUT;
            console.log(currentWindowList.length);
            if (currentWindowList.length !== 0) {
              if (totalTimeWaited > CFG.POLL_ALL_MAX_TIMEOUT) {
                console.error('POLL_ALL_MAX_TIMEOUT reached');
                reject('POLL_ALL_MAX_TIMEOUT reached');
              } else {
                // call recursively
                pollAllAppsClosed();
              }
            } else {
              fulfill(currentWindowList);
            }
          });
      }, CFG.POLL_ALL_APPS_STARTED_TIMEOUT);
    }

    // start once initially
    pollAllAppsClosed();
  }).catch(catchGenericErr);
}

function waitForAllAppsToStart(savedWindowList) {
  let totalTimeWaited = 0;
  return new Promise((fulfill, reject) => {
    function pollAllAppsStarted(savedWindowList) {
      setTimeout(() => {
        metaW.getActiveWindowList().then((currentWindowList) => {
          totalTimeWaited += CFG.POLL_ALL_APPS_STARTED_TIMEOUT;
          if (!isAllAppsStarted(savedWindowList, currentWindowList)) {
            if (totalTimeWaited > CFG.POLL_ALL_MAX_TIMEOUT) {
              console.error('POLL_ALL_MAX_TIMEOUT reached');
              console.error('Unable to start the following apps', getNonStartedApps(savedWindowList, currentWindowList));
              reject('POLL_ALL_MAX_TIMEOUT reached');
            } else {
              // call recursively
              pollAllAppsStarted(savedWindowList);
            }
          } else {
            fulfill(currentWindowList);
          }
        });
      }, CFG.POLL_ALL_APPS_STARTED_TIMEOUT);
    }

    // start once initially
    pollAllAppsStarted(savedWindowList);
  }).catch(catchGenericErr);
}

function getNonStartedApps(savedWindowList, currentWindowList) {
  let nonStartedApps = [];
  savedWindowList.forEach((win) => {
    if (!getMatchingWindowId(win, currentWindowList)) {
      nonStartedApps.push(win);
    }
  });
  return nonStartedApps;
}

function isAllAppsStarted(savedWindowList, currentWindowList) {
  let isAllStarted = true;
  savedWindowList.forEach((win) => {
    if (!getMatchingWindowId(win, currentWindowList)) {
      isAllStarted = false;
    }
  });
  return isAllStarted;
}

function guessAndSetDesktopFilePaths(windowList, inputHandler) {
  const promises = [];
  windowList.forEach((win) => {
    promises.push(() => {
      return guessFilePath(win, inputHandler);
    });
  });

  return new Promise((fulfill, reject) => {
    waterfall(promises)
      .then(() => {
        fulfill(windowList);
      })
      .catch(reject);
  }).catch(catchGenericErr);
}

function guessFilePath(win, inputHandler) {
  return new Promise((fulfill, reject) => {
    function callInputHandler(error, stdout) {
      inputHandler(error, win, stdout)
        .then((input) => {
          if (isDesktopFile(win.executableFile)) {
            win.desktopFilePath = input;
            fulfill(win.desktopFilePath);
          } else {
            win.executableFile = input;
            fulfill(win.executableFile);
          }
        })
        .catch(reject);
    }

    if (isDesktopFile(win.executableFile)) {
      metaW.locate(win.executableFile)
        .then((stdout) => {
          callInputHandler(null, stdout)
        })
        .catch(callInputHandler);
    } else {
      callInputHandler(true, win.executableFile);
    }
  }).catch(catchGenericErr);
}

// TODO check for how many instances there should be running of a program
function startSessionPrograms(windowList, currentWindowList) {
  const promises = [];

  // set instances started to 0
  windowList.forEach((win) => win.instancesStarted = 0);
  windowList.forEach((win) => {
    const numberOfInstancesOfWin = getNumberOfInstancesToRun(win, windowList);
    if (!isProgramAlreadyRunning(win.wmClassName, currentWindowList, numberOfInstancesOfWin, win.instancesStarted)) {
      promises.push(metaW.startProgram(win.executableFile, win.desktopFilePath));
      win.instancesStarted += 1;
    }
  });

  return new Promise((fulfill, reject) => {
    Promise.all(promises)
      .then((results) => {
        fulfill(results);
      })
      .catch(reject);
  }).catch(catchGenericErr);
}

function getNumberOfInstancesToRun(windowToMatch, windowList) {
  return windowList.filter((win) => {
    return win.wmClassName === windowToMatch.wmClassName;
  }).length;
}

function isProgramAlreadyRunning(wmClassName, currentWindowList, numberOfInstancesToRun, instancesStarted) {
  if (!numberOfInstancesToRun) {
    numberOfInstancesToRun = 1;
  }

  if (!instancesStarted) {
    instancesStarted = 0;
  }

  let instancesRunning = 0;
  currentWindowList.forEach((win) => {
    if (win.wmClassName === wmClassName) {
      instancesRunning++;
    }
  });
  console.log(wmClassName + ' is running: ', instancesRunning + instancesStarted >= numberOfInstancesToRun, numberOfInstancesToRun, instancesStarted);
  return instancesRunning + instancesStarted >= numberOfInstancesToRun;
}

function isDesktopFile(executableFile) {
  return executableFile && executableFile.match(/desktop$/);
}

function updateWindowIds(savedWindowList, currentWindowList) {
  const wmClassNameMap = {};
  savedWindowList.forEach((win) => {
    if (!wmClassNameMap[win.wmClassName]) {
      wmClassNameMap[win.wmClassName] = getMatchingWindows(win, currentWindowList);
    }
    win.windowId = wmClassNameMap[win.wmClassName][0].windowId;
    win.windowIdDec = parseInt(win.windowId, 16);
    wmClassNameMap[win.wmClassName].shift();
  });
}

function getMatchingWindowId(win, currentWindowList) {
  const currentWindow = currentWindowList.find((winFromCurrent) => win.wmClassName === winFromCurrent.wmClassName);
  return currentWindow && currentWindow.windowId;
}

function getMatchingWindows(win, currentWindowList) {
  return currentWindowList.filter((winFromCurrent) => win.wmClassName === winFromCurrent.wmClassName);
}

function restoreWindowPositions(savedWindowList) {
  const promises = [];
  savedWindowList.forEach((win) => {
    promises.push(() => {
      return metaW.restoreWindowPosition(win);
    });
    promises.push(() => {
      return metaW.goToWorkspace(win.windowId, win.wmCurrentDesktopNr);
    });
  });

  return new Promise((fulfill, reject) => {
    waterfall(promises)
      .then((results) => {
        fulfill(results);
      })
      .catch(reject);
  }).catch(catchGenericErr);
}
<commit_msg>save lib paths to config file to use it with gnome shell and the like<commit_after>'use strict';

const Store = require('jfs');
const fs = require('fs');
const waterfall = require('promise-waterfall');
const DESKTOP_ENV = process.env.DESKTOP_SESSION;
const DEFAULT_CFG_FILE_PATH = __dirname + '/default-config.json';
const CFG_DATA_DIR = getUserHome() + '/.lwsm';
const CFG_FILE_PATH = CFG_DATA_DIR + '/config.json';
const SESSION_DATA_DIR = CFG_DATA_DIR + '/sessionData';

let metaW;

let db;
let CFG;

// INIT
// ------------
const mkdirSync = (dirPath) => {
  try {
    fs.mkdirSync(dirPath);
  } catch (err) {
    if (err.code !== 'EEXIST') {
      throw err;
    }
  }
};

const copySync = (src, dest) => {
  if (!fs.existsSync(src)) {
    return false;
  }
  const data = fs.readFileSync(src, 'utf-8');
  fs.writeFileSync(dest, data);
};

try {
  // if config is already in place
  CFG = JSON.parse(fs.readFileSync(CFG_FILE_PATH, 'utf8'));
} catch (e) {
  // if there is no config yet load default cfg and create files and dirs
  CFG = JSON.parse(fs.readFileSync(DEFAULT_CFG_FILE_PATH, 'utf8'));

  // save executable paths to cfg
  CFG.CMD_JSFILE_PATH = __dirname + '/../cmd.js';
  CFG.JSFILE_INDEX_PATH = __dirname + '/index.js';

  mkdirSync(CFG_DATA_DIR);
  mkdirSync(SESSION_DATA_DIR);

  // write config to user dir
  fs.writeFileSync(CFG_FILE_PATH, JSON.stringify(CFG), 'utf8');
}

// create data store
db = new Store(SESSION_DATA_DIR);

// also make data dirs accessible to the outside
CFG.DATA_DIR = CFG_DATA_DIR;
CFG.SESSION_DATA_DIR = SESSION_DATA_DIR;

// setup meta wrapper
metaW = require('./metaWrapper')(CFG);

// EXPORT
// ------
module.exports = {
  saveSession,
  removeSession,
  restoreSession,
  x11Helper: metaW.x11Helper,
  getConnectedDisplaysId: metaW.getConnectedDisplaysId,
  resetCfg: () => {
    const configFilePath = CFG.DATA_DIR + '/config.json';
    console.log(CFG);

    if (fs.existsSync(configFilePath)) {
      fs.unlinkSync(configFilePath);
    } else {
      console.error('No Config present in ' + configFilePath);
    }
  },
  getCfg: () => {
    return CFG;
  },
  getDb: () => {
    return db;
  }
};

// HELPER
// --------
function getUserHome() {
  return process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'];
}

function catchGenericErr(err) {
  console.error('Generic Error in Main Handler', err);
  throw err;
}

// MAIN FUNCTIONS
// --------------
function saveSession(sessionName, inputHandlers) {
  const sessionToHandle = sessionName || 'DEFAULT';

  return metaW.getActiveWindowList()
    .then((windowList) => {
      return Promise.all([
        guessAndSetDesktopFilePaths(windowList, inputHandlers.desktopFilePath),
        metaW.getConnectedDisplaysId(),
      ]);
    })
    .then((results) => {
      const windowList = results[0];
      const connectedDisplaysId = results[1];
      return saveSessionForDisplayToDb(sessionToHandle, connectedDisplaysId, windowList);
    })
    .catch((err) => {
      console.error('An error occurred', err);
      throw err;
    });
}

function saveSessionForDisplayToDb(sessionToHandle, connectedDisplaysId, windowList) {
  return new Promise((fulfill, reject) => {
    // check if entry exists and update
    db.get(sessionToHandle, (err, sessionData) => {
      if (!sessionData) {
        // create new object
        sessionData = {
          name: sessionToHandle,
        };
      }
      if (!sessionData.displaysCombinations || !Array.isArray(sessionData.displaysCombinations)) {
        // create new array
        sessionData.displaysCombinations = [];
      }

      const existingDisplayEntry = sessionData.displaysCombinations.find((entry) => entry.id === connectedDisplaysId);
      if (existingDisplayEntry) {
        existingDisplayEntry.windowList = windowList;
      } else {
        sessionData.displaysCombinations.push({
          id: connectedDisplaysId,
          windowList,
        });
      }

      db.save(sessionToHandle, sessionData, (err) => {
        if (err) {
          reject(err);
        } else {
          console.log('SAVED SESSION: ' + sessionToHandle);
          fulfill();
        }
      });
    });
  });
}

function restoreSession(sessionName, isCloseAllOpenWindows) {
  const sessionToHandle = sessionName || 'DEFAULT';

  return new Promise((fulfill, reject) => {
    db.get(sessionToHandle || 'DEFAULT', (err, sessionData) => {
      if (err) {
        reject(err);
        return;
      }

      let savedWindowList;

      closeAllWindowsIfSet(isCloseAllOpenWindows)
        .then(metaW.goToFirstWorkspace)
        .then(metaW.getConnectedDisplaysId)
        .then((connectedDisplaysId) => {
          if (!sessionData.displaysCombinations) {
            console.error(`no display combinations saved yet`);
            return;
          }

          const displayEntry = sessionData.displaysCombinations.find((entry) => entry.id === connectedDisplaysId);

          if (displayEntry) {
            savedWindowList = displayEntry.windowList;
          } else {
            console.error(`no data for current display id '${connectedDisplaysId}' saved yet`);
            return;
          }
          return metaW.getActiveWindowList();
        })
        .then((currentWindowList) => {
          return startSessionPrograms(savedWindowList, currentWindowList);
        })
        .then(() => {
          // gets current window list by itself and returns the updated variant
          return waitForAllAppsToStart(savedWindowList)
        })
        .then((updatedCurrentWindowList) => {
          updateWindowIds(savedWindowList, updatedCurrentWindowList);
          return restoreWindowPositions(savedWindowList);
        })
        .then(() => {
          console.log('RESTORED SESSION: ' + sessionToHandle);
        })
        .catch((err) => {
          console.error('An error occurred', err);
          reject(err);
        })
        .then(fulfill);
    });
  }).catch(catchGenericErr);
}

function removeSession(sessionName) {
  return new Promise((fulfill, reject) => {
    fs.unlink(CFG.SESSION_DATA_DIR + '/' + sessionName + '.json', (error) => {
      if (error) {
        console.error(error);
        reject(error);
      } else {
        fulfill();
      }
    });
  }).catch(catchGenericErr);
}

function closeAllWindowsIfSet(isCloseAll) {
  return new Promise((fulfill, reject) => {
    if (isCloseAll) {
      metaW.getActiveWindowList()
        .then((currentWindowList) => {
          currentWindowList.forEach((win) => {
            metaW.closeWindow(win.windowId);
          });

          waitForAllAppsToClose()
            .then(fulfill)
            .catch(reject);
        });
    } else {
      fulfill();
    }
  }).catch(catchGenericErr);
}

function waitForAllAppsToClose() {
  let totalTimeWaited = 0;
  return new Promise((fulfill, reject) => {
    function pollAllAppsClosed() {
      setTimeout(() => {
        metaW.getActiveWindowList()
          .then((currentWindowList) => {
            totalTimeWaited += CFG.POLL_ALL_APPS_STARTED_TIMEOUT;
            console.log(currentWindowList.length);
            if (currentWindowList.length !== 0) {
              if (totalTimeWaited > CFG.POLL_ALL_MAX_TIMEOUT) {
                console.error('POLL_ALL_MAX_TIMEOUT reached');
                reject('POLL_ALL_MAX_TIMEOUT reached');
              } else {
                // call recursively
                pollAllAppsClosed();
              }
            } else {
              fulfill(currentWindowList);
            }
          });
      }, CFG.POLL_ALL_APPS_STARTED_TIMEOUT);
    }

    // start once initially
    pollAllAppsClosed();
  }).catch(catchGenericErr);
}

function waitForAllAppsToStart(savedWindowList) {
  let totalTimeWaited = 0;
  return new Promise((fulfill, reject) => {
    function pollAllAppsStarted(savedWindowList) {
      setTimeout(() => {
        metaW.getActiveWindowList().then((currentWindowList) => {
          totalTimeWaited += CFG.POLL_ALL_APPS_STARTED_TIMEOUT;
          if (!isAllAppsStarted(savedWindowList, currentWindowList)) {
            if (totalTimeWaited > CFG.POLL_ALL_MAX_TIMEOUT) {
              console.error('POLL_ALL_MAX_TIMEOUT reached');
              console.error('Unable to start the following apps', getNonStartedApps(savedWindowList, currentWindowList));
              reject('POLL_ALL_MAX_TIMEOUT reached');
            } else {
              // call recursively
              pollAllAppsStarted(savedWindowList);
            }
          } else {
            fulfill(currentWindowList);
          }
        });
      }, CFG.POLL_ALL_APPS_STARTED_TIMEOUT);
    }

    // start once initially
    pollAllAppsStarted(savedWindowList);
  }).catch(catchGenericErr);
}

function getNonStartedApps(savedWindowList, currentWindowList) {
  let nonStartedApps = [];
  savedWindowList.forEach((win) => {
    if (!getMatchingWindowId(win, currentWindowList)) {
      nonStartedApps.push(win);
    }
  });
  return nonStartedApps;
}

function isAllAppsStarted(savedWindowList, currentWindowList) {
  let isAllStarted = true;
  savedWindowList.forEach((win) => {
    if (!getMatchingWindowId(win, currentWindowList)) {
      isAllStarted = false;
    }
  });
  return isAllStarted;
}

function guessAndSetDesktopFilePaths(windowList, inputHandler) {
  const promises = [];
  windowList.forEach((win) => {
    promises.push(() => {
      return guessFilePath(win, inputHandler);
    });
  });

  return new Promise((fulfill, reject) => {
    waterfall(promises)
      .then(() => {
        fulfill(windowList);
      })
      .catch(reject);
  }).catch(catchGenericErr);
}

function guessFilePath(win, inputHandler) {
  return new Promise((fulfill, reject) => {
    function callInputHandler(error, stdout) {
      inputHandler(error, win, stdout)
        .then((input) => {
          if (isDesktopFile(win.executableFile)) {
            win.desktopFilePath = input;
            fulfill(win.desktopFilePath);
          } else {
            win.executableFile = input;
            fulfill(win.executableFile);
          }
        })
        .catch(reject);
    }

    if (isDesktopFile(win.executableFile)) {
      metaW.locate(win.executableFile)
        .then((stdout) => {
          callInputHandler(null, stdout)
        })
        .catch(callInputHandler);
    } else {
      callInputHandler(true, win.executableFile);
    }
  }).catch(catchGenericErr);
}

// TODO check for how many instances there should be running of a program
function startSessionPrograms(windowList, currentWindowList) {
  const promises = [];

  // set instances started to 0
  windowList.forEach((win) => win.instancesStarted = 0);
  windowList.forEach((win) => {
    const numberOfInstancesOfWin = getNumberOfInstancesToRun(win, windowList);
    if (!isProgramAlreadyRunning(win.wmClassName, currentWindowList, numberOfInstancesOfWin, win.instancesStarted)) {
      promises.push(metaW.startProgram(win.executableFile, win.desktopFilePath));
      win.instancesStarted += 1;
    }
  });

  return new Promise((fulfill, reject) => {
    Promise.all(promises)
      .then((results) => {
        fulfill(results);
      })
      .catch(reject);
  }).catch(catchGenericErr);
}

function getNumberOfInstancesToRun(windowToMatch, windowList) {
  return windowList.filter((win) => {
    return win.wmClassName === windowToMatch.wmClassName;
  }).length;
}

function isProgramAlreadyRunning(wmClassName, currentWindowList, numberOfInstancesToRun, instancesStarted) {
  if (!numberOfInstancesToRun) {
    numberOfInstancesToRun = 1;
  }

  if (!instancesStarted) {
    instancesStarted = 0;
  }

  let instancesRunning = 0;
  currentWindowList.forEach((win) => {
    if (win.wmClassName === wmClassName) {
      instancesRunning++;
    }
  });
  console.log(wmClassName + ' is running: ', instancesRunning + instancesStarted >= numberOfInstancesToRun, numberOfInstancesToRun, instancesStarted);
  return instancesRunning + instancesStarted >= numberOfInstancesToRun;
}

function isDesktopFile(executableFile) {
  return executableFile && executableFile.match(/desktop$/);
}

function updateWindowIds(savedWindowList, currentWindowList) {
  const wmClassNameMap = {};
  savedWindowList.forEach((win) => {
    if (!wmClassNameMap[win.wmClassName]) {
      wmClassNameMap[win.wmClassName] = getMatchingWindows(win, currentWindowList);
    }
    win.windowId = wmClassNameMap[win.wmClassName][0].windowId;
    win.windowIdDec = parseInt(win.windowId, 16);
    wmClassNameMap[win.wmClassName].shift();
  });
}

function getMatchingWindowId(win, currentWindowList) {
  const currentWindow = currentWindowList.find((winFromCurrent) => win.wmClassName === winFromCurrent.wmClassName);
  return currentWindow && currentWindow.windowId;
}

function getMatchingWindows(win, currentWindowList) {
  return currentWindowList.filter((winFromCurrent) => win.wmClassName === winFromCurrent.wmClassName);
}

function restoreWindowPositions(savedWindowList) {
  const promises = [];
  savedWindowList.forEach((win) => {
    promises.push(() => {
      return metaW.restoreWindowPosition(win);
    });
    promises.push(() => {
      return metaW.goToWorkspace(win.windowId, win.wmCurrentDesktopNr);
    });
  });

  return new Promise((fulfill, reject) => {
    waterfall(promises)
      .then((results) => {
        fulfill(results);
      })
      .catch(reject);
  }).catch(catchGenericErr);
}
<|endoftext|>


<commit_before>const c = exports.c = {};
const t = exports.t = {};

const util = require('util');
const id = require('./id');

let assign = Object.assign;

let indent = str => str.replace(/\n/g, '\n  ');

t.id = id;

t.Null = assign(id.judge(util.isNullOrUndefined, 'is not Null'), {
  show: () => 'Null',
  sample: () => null,
});

t.Any = assign(id.judge(v => !util.isNullOrUndefined(v), 'is Null'), {
  show: () => 'Any',
  sample: () => 'anytype',
});

c.Or = (...Types) => assign(id.match(value => {
  let ret;
  let errors = [];
  let matchSome = Types.some(Type => {
    try {
      ret = Type(value);
    }
    catch(e) {
      errors.push(e.message);
      return false;
    }
    return true;
  });
  if(matchSome) return ret;
  throw new Error('Or: All' + indent('\n' + errors.join('\n')));
}), {
  show: () => `(${Types.map(Type => Type.show()).join('|')})`,
  sample: () => Types[0].sample(),
});

c.Optional = Type => assign(c.Or(t.Null, Type), {
  show: () => 'Optional ' + Type.show(),
  sample: () => Type.sample(),
});

c.Default = (Type, defaultValue) => assign(id.match(value => {
  if(util.isNullOrUndefined(value)) return Type(defaultValue);
  return Type(value);
}), {
  show: () => Type.show() + ' Default ' + defaultValue,
  sample: () => defaultValue,
});

c.Val = value => assign(id.judge(real => real == value, 'is not eq to ' + value), {
  show: () => 'Value ' + value,
  sample: () => value,
});

t.Num = assign(t.Any.judge(v => !isNaN(v) && v !== '', 'is not a Num').match(Number), {
  show: () => 'Num',
  sample: () => 1,
});

t.Str = assign(t.Any.match(String), {
  show: () => 'Str',
  sample: () => 'sample string',
});

t.Bool = assign(t.Any.match(value => {
  const boolStr = value.toString();
  if(boolStr === 'true') return true;
  if(boolStr === 'false') return false;
  throw new Error(`\n${value} is not a Bool`);
}), {
  show: () => 'Bool',
  sample: () => false,
});

t.Date = assign(t.Any
.match(value => new Date(value))
.judge(v => v.toString() != 'Invalid Date', 'is not a Date'), {
  show: () => 'Date',
  sample: () => new Date(),
});

t.Json = assign(c.Or(
  t.Any.judge(util.isObject, 'is not an Obj'),
  t.Str.match(JSON.parse).judge(v => !util.isNullOrUndefined(v), 'json is null')
), {
  show: () => 'Json',
  sample: () => ({}),
});

t.Obj = assign(t.Json.judge(v => !util.isArray(v), 'is an Arr not an Obj'), {
  show: () => 'Obj',
  sample: () => ({}),
});

t.Arr = assign(t.Json.judge(util.isArray, 'is an Obj not an Arr'), {
  show: () => 'Arr',
  sample: () => [],
});

c.Map = (ValueType) =>
assign(t.Obj.match(obj => Object.keys(obj).reduce((ret, key) => {
  let newvalue;
  try {
    newvalue = ValueType(obj[key]);
  }
  catch(e) {
    throw new Error(`Map field '${key}'` + indent('\n' + e.message));
  }
  ret[key] = newvalue;
  return ret;
}, {})), {
  show: () => {
    const fields = '  MapKey: ' + indent(ValueType.show());
    return util.format("{\n%s\n}", fields);
  },
  sample: () => ({key: ValueType.sample()}),
});

c.Obj = TypeMap =>
assign(t.Obj.match(obj => Object.keys(TypeMap).reduce((ret, key) => {
  let result;
  try {
    result = TypeMap[key](obj[key]);
    if(!util.isNullOrUndefined(result))
      ret[key] = result;
  }
  catch(e) {
    throw new Error(`Obj field '${key}'` + indent('\n' + e.message));
  }
  return ret;
}, {})), {
  show: () => {
    const fields = Object.keys(TypeMap)
      .map(key => '  ' + key + ': ' + indent(TypeMap[key].show()))
      .join(',\n');
    return util.format("{\n%s\n}", fields);
  },
  sample: () => Object.keys(TypeMap).reduce((ret, key) => {
    ret[key] = TypeMap[key].sample();
    return ret;
  }, {}),
});

c.Arr = Type => assign(t.Arr.match(arr => arr.map((item, i) => {
  let result;
  try {
    result = Type(item);
  }
  catch(e) {
    throw new Error(`Arr field '${i}'` + indent('\n' + e.message));
  }
  return result;
})), {
  show: () => util.format('[%s]', Type.show()),
  sample: () => [Type.sample()]
});
<commit_msg>add random sample<commit_after>const c = exports.c = {};
const t = exports.t = {};

const util = require('util');
const id = require('./id');

let assign = Object.assign;

let indent = str => str.replace(/\n/g, '\n  ');

let random = values => values[Math.floor(Math.random() * values.length)];

t.id = id;

t.Null = assign(id.judge(util.isNullOrUndefined, 'is not Null'), {
  show: () => 'Null',
  sample: () => random([null, undefined]),
});

t.Any = assign(id.judge(v => !util.isNullOrUndefined(v), 'is Null'), {
  show: () => 'Any',
  sample: () => random([t.Str, t.Num, t.Bool, t.Date, t.Json]).sample(),
});

c.Or = (...Types) => assign(id.match(value => {
  let ret;
  let errors = [];
  let matchSome = Types.some(Type => {
    try {
      ret = Type(value);
    }
    catch(e) {
      errors.push(e.message);
      return false;
    }
    return true;
  });
  if(matchSome) return ret;
  throw new Error('Or: All' + indent('\n' + errors.join('\n')));
}), {
  show: () => `(${Types.map(Type => Type.show()).join('|')})`,
  sample: () => random(Types).sample(),
});

c.Optional = Type => assign(c.Or(t.Null, Type), {
  show: () => 'Optional ' + Type.show(),
});

c.Default = (Type, defaultValue) => assign(id.match(value => {
  if(util.isNullOrUndefined(value)) return Type(defaultValue);
  return Type(value);
}), {
  show: () => Type.show() + ' Default ' + defaultValue,
  sample: () => random([true, false]) ? defaultValue : Type.sample(),
});

c.Val = value => assign(id.judge(real => real == value, 'is not eq to ' + value), {
  show: () => 'Value ' + value,
  sample: () => value,
});

t.Num = assign(t.Any.judge(v => !isNaN(v) && v !== '', 'is not a Num').match(Number), {
  show: () => 'Num',
  sample: () => random([0, 1, 2, 4, 7, 8, 9, 11.1]),
});

t.Str = assign(t.Any.match(String), {
  show: () => 'Str',
  sample: () => random(['sample string', 'hello world', 'random string']),
});

t.Bool = assign(t.Any.match(value => {
  const boolStr = value.toString();
  if(boolStr === 'true') return true;
  if(boolStr === 'false') return false;
  throw new Error(`\n${value} is not a Bool`);
}), {
  show: () => 'Bool',
  sample: () => random([true, false]),
});

t.Date = assign(t.Any
.match(value => new Date(value))
.judge(v => v.toString() != 'Invalid Date', 'is not a Date'), {
  show: () => 'Date',
  sample: () => new Date(),
});

t.Json = assign(c.Or(
  t.Any.judge(util.isObject, 'is not an Obj'),
  t.Str.match(JSON.parse).judge(v => !util.isNullOrUndefined(v), 'json is null')
), {
  show: () => 'Json',
  sample: () => random([{}, {name: 'wak', age: 18}, [1, 1, 2, 3, 5], []]),
});

t.Obj = assign(t.Json.judge(v => !util.isArray(v), 'is an Arr not an Obj'), {
  show: () => 'Obj',
  sample: () => random([{}, {name: 'object', age: 42}]),
});

t.Arr = assign(t.Json.judge(util.isArray, 'is an Obj not an Arr'), {
  show: () => 'Arr',
  sample: () => random([[1, 1, 2, 3, 5], [], ['a', 'b', 'c']]),
});

c.Map = (ValueType) =>
assign(t.Obj.match(obj => Object.keys(obj).reduce((ret, key) => {
  let newvalue;
  try {
    newvalue = ValueType(obj[key]);
  }
  catch(e) {
    throw new Error(`Map field '${key}'` + indent('\n' + e.message));
  }
  ret[key] = newvalue;
  return ret;
}, {})), {
  show: () => {
    const fields = '  MapKey: ' + indent(ValueType.show());
    return util.format("{\n%s\n}", fields);
  },
  sample: () => ({key: ValueType.sample()}),
});

c.Obj = TypeMap =>
assign(t.Obj.match(obj => Object.keys(TypeMap).reduce((ret, key) => {
  let result;
  try {
    result = TypeMap[key](obj[key]);
    if(!util.isNullOrUndefined(result))
      ret[key] = result;
  }
  catch(e) {
    throw new Error(`Obj field '${key}'` + indent('\n' + e.message));
  }
  return ret;
}, {})), {
  show: () => {
    const fields = Object.keys(TypeMap)
      .map(key => '  ' + key + ': ' + indent(TypeMap[key].show()))
      .join(',\n');
    return util.format("{\n%s\n}", fields);
  },
  sample: () => Object.keys(TypeMap).reduce((ret, key) => {
    ret[key] = TypeMap[key].sample();
    return ret;
  }, {}),
});

c.Arr = Type => assign(t.Arr.match(arr => arr.map((item, i) => {
  let result;
  try {
    result = Type(item);
  }
  catch(e) {
    throw new Error(`Arr field '${i}'` + indent('\n' + e.message));
  }
  return result;
})), {
  show: () => util.format('[%s]', Type.show()),
  sample: () => [Type.sample()]
});
<|endoftext|>


<commit_before>(function() {

    "use strict";

    /** This controller takes a camera target and provides animation using
     *  points of interest. POIs can be added and removed. One can navigate
     *  to next, previous or specific POIs.
     *
     *  @constructor
     */
    XMOT.CameraAnimationController = new XMOT.Class({

        /**
         *  @this {XMOT.CameraAnimationController}
         *  @param {Element|Transformable} targetViewGroup
         *  @param {Object=} options
         *
         *  Options:
         *      o moveToTime: time in milliseconds used for camera pose animation, default: 3000
         */
        initialize: function(targetViewGroup, options) {

            if(!options) {
                options = {};
            }
            if(!options.moveToTime) {
                options.moveToTime = 3000;
            }

            this.target = XMOT.util.getOrCreateTransformable(targetViewGroup);

            /** @private */
            this._idGenerator = new XMOT.util.IDGenerator();
            /** @private */
            this._pointOfInterests = new XMOT.util.Map();
            /** @private */
            this._moveToTime = options.moveToTime;

            /** @private
             *  @type {string}
             */
            this._currentPointOfInterestID = null;

            /** @private */
            this._movementInProgress = false;
        },

        /**
         *  @this {XMOT.CameraAnimationController}
         *  @param {window.XML3DVec3} position
         *  @param {window.XML3DRotation} orientation
         *  @param {number=} moveToTime. Default: class instance's moveToTime
         *  @return {string} id of the newly added point of interest
         */
        addPOI: function(position, orientation, moveToTime) {

            var id = this._idGenerator.newID();
            if(moveToTime === undefined)
                moveToTime = this._moveToTime;

            var poi = {
                id: id,
                position: position,
                orientation: orientation,
                moveToTime: moveToTime
            };
            this._pointOfInterests.add(id, poi);

            if(this._currentPointOfInterestID == null) {
                this._currentPointOfInterestID = id;
            }

            return id;
        },

        /**
         *  @this {XMOT.CameraAnimationController}
         *  @param {string} id of the point of interest to remove
         */
        removePOI: function(id) {
            this._pointOfInterests.remove(id);
        },

        /**
         *  @this {XMOT.CameraAnimationController}
         */
        clearPOIs: function() {
            this.stopMovementToPOI();

            this._pointOfInterests.clear();
            this._currentPointOfInterestID = null;
            this._idGenerator.reset();
        },

        /**
         *  @this {XMOT.CameraAnimationController}
         */
        moveToNextPOI: function() {

            var nextPOI = this._pointOfInterests.getNext(this._currentPointOfInterestID)[0].id;
            this.moveToPOI(nextPOI);
        },

        /**
         *  @this {XMOT.CameraAnimationController}
         */
        moveToPreviousPOI: function() {

            var previousPOI = this._pointOfInterests.getPrevious(this._currentPointOfInterestID)[0].id;
            this.moveToPOI(previousPOI);
        },

        /**
         *  @this {XMOT.CameraAnimationController}
         *  @param {string} id of the point of interest to move to
         *  @return {boolean} true if movement has actually been started
         */
        moveToPOI: function(id) {

            if(this._pointOfInterests.size() < 1 ||
               this._movementInProgress || this.target.movementInProgress()) {
                return false;
            }

            this._currentPointOfInterestID = id;
            this._movementInProgress = true;

            var nextPOI = this._pointOfInterests.get(id)[0];

            this.target.moveTo(nextPOI.position, nextPOI.orientation, nextPOI.moveToTime, {
                queueing: false,
                callback: this.callback("_moveToFinished")
            });

            return true;
        },

        /**
         *  @this {XMOT.CameraAnimationController}
         */
        stopMovementToPOI: function() {

            this.target.stop();
            this._moveToFinished();
        },

        _moveToFinished: function() {
            this._movementInProgress = false;
        }
    });
}());
<commit_msg>CameraAnimationCtrl: added movementInProgress()<commit_after>(function() {

    "use strict";

    /** This controller takes a camera target and provides animation using
     *  points of interest. POIs can be added and removed. One can navigate
     *  to next, previous or specific POIs.
     *
     *  @constructor
     */
    XMOT.CameraAnimationController = new XMOT.Class({

        /**
         *  @this {XMOT.CameraAnimationController}
         *  @param {Element|Transformable} targetViewGroup
         *  @param {Object=} options
         *
         *  Options:
         *      o moveToTime: time in milliseconds used for camera pose animation, default: 3000
         */
        initialize: function(targetViewGroup, options) {

            if(!options) {
                options = {};
            }
            if(!options.moveToTime) {
                options.moveToTime = 3000;
            }

            this.target = XMOT.util.getOrCreateTransformable(targetViewGroup);

            /** @private */
            this._idGenerator = new XMOT.util.IDGenerator();
            /** @private */
            this._pointOfInterests = new XMOT.util.Map();
            /** @private */
            this._moveToTime = options.moveToTime;

            /** @private
             *  @type {string}
             */
            this._currentPointOfInterestID = null;

            /** @private */
            this._movementInProgress = false;
        },

        /**
         *  @this {XMOT.CameraAnimationController}
         *  @return {boolean}
         */
        movementInProgress: function() {
            return this._movementInProgress;
        },

        /**
         *  @this {XMOT.CameraAnimationController}
         *  @param {window.XML3DVec3} position
         *  @param {window.XML3DRotation} orientation
         *  @param {number=} moveToTime. Default: class instance's moveToTime
         *  @return {string} id of the newly added point of interest
         */
        addPOI: function(position, orientation, moveToTime) {

            var id = this._idGenerator.newID();
            if(moveToTime === undefined)
                moveToTime = this._moveToTime;

            var poi = {
                id: id,
                position: position,
                orientation: orientation,
                moveToTime: moveToTime
            };
            this._pointOfInterests.add(id, poi);

            if(this._currentPointOfInterestID == null) {
                this._currentPointOfInterestID = id;
            }

            return id;
        },

        /**
         *  @this {XMOT.CameraAnimationController}
         *  @param {string} id of the point of interest to remove
         */
        removePOI: function(id) {
            this._pointOfInterests.remove(id);
        },

        /**
         *  @this {XMOT.CameraAnimationController}
         */
        clearPOIs: function() {
            this.stopMovementToPOI();

            this._pointOfInterests.clear();
            this._currentPointOfInterestID = null;
            this._idGenerator.reset();
        },

        /**
         *  @this {XMOT.CameraAnimationController}
         */
        moveToNextPOI: function() {

            var nextPOI = this._pointOfInterests.getNext(this._currentPointOfInterestID)[0].id;
            this.moveToPOI(nextPOI);
        },

        /**
         *  @this {XMOT.CameraAnimationController}
         */
        moveToPreviousPOI: function() {

            var previousPOI = this._pointOfInterests.getPrevious(this._currentPointOfInterestID)[0].id;
            this.moveToPOI(previousPOI);
        },

        /**
         *  @this {XMOT.CameraAnimationController}
         *  @param {string} id of the point of interest to move to
         *  @return {boolean} true if movement has actually been started
         */
        moveToPOI: function(id) {

            if(this._pointOfInterests.size() < 1 ||
               this._movementInProgress || this.target.movementInProgress()) {
                return false;
            }

            this._currentPointOfInterestID = id;
            this._movementInProgress = true;

            var nextPOI = this._pointOfInterests.get(id)[0];

            this.target.moveTo(nextPOI.position, nextPOI.orientation, nextPOI.moveToTime, {
                queueing: false,
                callback: this.callback("_moveToFinished")
            });

            return true;
        },

        /**
         *  @this {XMOT.CameraAnimationController}
         */
        stopMovementToPOI: function() {

            this.target.stop();
            this._moveToFinished();
        },

        _moveToFinished: function() {
            this._movementInProgress = false;
        }
    });
}());
<|endoftext|>


<commit_before>'use strict';

/**
 * @file Helper for iptc/exif metadata processing
 */

var ex = require('exiv2');

var hydrationError = require('anyfetch-file-hydrater').hydrationError;

/**
 * Extract the metadata of the specified iamge file
 *
 * @param {string} path Path of the specified file
 * @param {function} cb Callback, first parameter, is the error if any, then the processed data
 */
module.exports = function(path, document, changes, finalCb) {
  ex.getImageTags(path, function(err, tags) {
    if(err) {
      return finalCb(new hydrationError(err), changes);
    }
    if(!tags) {
      return finalCb(null, changes);
    }

    var selection = {
      author: 'Iptc.Application2.Byline',
      description: 'Iptc.Application2.Caption',
      keywords: 'Xmp.dc.subject'
    };

    for(var key in selection) {
      if(tags.hasOwnProperty(selection[key])) {
        changes.metadata[key] = tags[selection[key]];
      }
    }

    finalCb(err, changes);
  });
};<commit_msg>Fixed spelling error<commit_after>'use strict';

/**
 * @file Helper for iptc/exif metadata processing
 */

var ex = require('exiv2');

var hydrationError = require('anyfetch-file-hydrater').hydrationError;

/**
 * Extract the metadata of the specified image file
 *
 * @param {string} path Path of the specified file
 * @param {function} cb Callback, first parameter, is the error if any, then the processed data
 */
module.exports = function(path, document, changes, finalCb) {
  ex.getImageTags(path, function(err, tags) {
    if(err) {
      return finalCb(new hydrationError(err), changes);
    }
    if(!tags) {
      return finalCb(null, changes);
    }

    var selection = {
      author: 'Iptc.Application2.Byline',
      description: 'Iptc.Application2.Caption',
      keywords: 'Xmp.dc.subject'
    };

    for(var key in selection) {
      if(tags.hasOwnProperty(selection[key])) {
        changes.metadata[key] = tags[selection[key]];
      }
    }

    finalCb(err, changes);
  });
};<|endoftext|>


<commit_before>var admin = require('./admin'),
    buildBlocker = require('./build-blocker'),
    config = require('./config'),
    configService = require('./config-service.js'),
    express = require("express"),
    fs = require('fs'),
    mocks = require('./mocks'),
    path = require('path'),
    proxy = require('./proxy'),
    staticShim = require('./static-shim'),
    liveReload = require('./live-reload');

mocks.init();

function resourceRewrite(req, res, next) {
  // Rewrite requests to /r/phoenix
  var match = /^\/r\/phoenix(-v[\d]?)?(.*)/.exec(req.url);
  if (match) {
    req.url = match[2];
  }
  next();
}

function configure(app, secure) {
  app.set('views', path.dirname(__dirname) + '/views/');
  app.set('view engine', 'jade');

  app.configure(function() {
    app.use(express.logger());
    app.use(express.methodOverride());
    app.use(buildBlocker.middleware);
    app.use(configService);
    app.use(resourceRewrite);
    app.use(mocks.middleware);
    app.use(staticShim);
    app.use(app.router);
    app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
  });

  admin(app, repo);

  app.all('/m/x', function(req, res) {
    var data = '';
    req.on('data', function(chunk) {
      data += chunk;
    });
    req.on('end', function() {
      console.log('client:', data);
      res.send(200);
    });
  });

  app.all(/\/.*/, proxy.proxy(secure));
}

// Repo setup
var repo = require('./git-repo')(config.appDir, process.env.MOCK_REPO);
buildBlocker.init(repo);
repo.init(function() {
  if (config.isStandalone) {
    var watcher = require('./watcher')();
    watcher.init(repo);

    repo.build(function(){});
  }
});

console.log('Starting mock-server on port', config.port);
console.log('Config', config.toString());
var app = express.createServer();
configure(app, false);
app.listen(config.port);

if (!config.isStandalone) {
  console.log('Starting mock-server on secure port', config.securePort);
  try {
    var hostExists = fs.statSync(config.appDir + '/host.key').isFile();
  } catch (err) { /* NOP */ }

  var secureApp = express.createServer({
      key: fs.readFileSync((hostExists ? config.appDir + '/host.key' : path.dirname(__dirname) + '/local.key')),
      cert: fs.readFileSync((hostExists ? config.appDir + '/host.cert' : path.dirname(__dirname) + '/local.cert'))
    });
  configure(secureApp, true);
  secureApp.listen(config.securePort);
}

if (config.liveReload) {
  liveReload(app, secureApp);
}
<commit_msg>ensure only isHeroku servers do ssl rewriting<commit_after>var admin = require('./admin'),
    buildBlocker = require('./build-blocker'),
    config = require('./config'),
    configService = require('./config-service.js'),
    express = require("express"),
    fs = require('fs'),
    mocks = require('./mocks'),
    path = require('path'),
    proxy = require('./proxy'),
    staticShim = require('./static-shim'),
    liveReload = require('./live-reload');

mocks.init();

function resourceRewrite(req, res, next) {
  // Rewrite requests to /r/phoenix
  var match = /^\/r\/phoenix(-v[\d]?)?(.*)/.exec(req.url);
  if (match) {
    req.url = match[2];
  }
  next();
}

function configure(app, secure) {
  app.set('views', path.dirname(__dirname) + '/views/');
  app.set('view engine', 'jade');

  app.configure(function() {
    app.use(express.logger());
    app.use(express.methodOverride());
    app.use(buildBlocker.middleware);
    app.use(configService);
    app.use(resourceRewrite);
    app.use(mocks.middleware);
    app.use(staticShim);
    app.use(app.router);
    app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
  });

  admin(app, repo);

  app.all('/m/x', function(req, res) {
    var data = '';
    req.on('data', function(chunk) {
      data += chunk;
    });
    req.on('end', function() {
      console.log('client:', data);
      res.send(200);
    });
  });

  app.all(/\/.*/, proxy.proxy(secure));
}

// Repo setup
var repo = require('./git-repo')(config.appDir, process.env.MOCK_REPO);
buildBlocker.init(repo);
repo.init(function() {
  if (config.isStandalone) {
    var watcher = require('./watcher')();
    watcher.init(repo);

    repo.build(function(){});
  }
});

console.log('Starting mock-server on port', config.port);
console.log('Config', config.toString());
var app = express.createServer();
configure(app, false);
app.listen(config.port);

if (!config.isHeroku) {
  console.log('Starting mock-server on secure port', config.securePort);
  try {
    var hostExists = fs.statSync(config.appDir + '/host.key').isFile();
  } catch (err) { /* NOP */ }

  var secureApp = express.createServer({
      key: fs.readFileSync((hostExists ? config.appDir + '/host.key' : path.dirname(__dirname) + '/local.key')),
      cert: fs.readFileSync((hostExists ? config.appDir + '/host.cert' : path.dirname(__dirname) + '/local.cert'))
    });
  configure(secureApp, true);
  secureApp.listen(config.securePort);
}

if (config.liveReload) {
  liveReload(app, secureApp);
}
<|endoftext|>


<commit_before>var util = require('util'),
    fs   = require('fs'),
    path = require('path'),
    mocha = require('mocha'),
    _ = require('lodash'),
    handlebars = require('handlebars'),
    less = require('less'),
    moment = require('moment'),
    uuid = require('node-uuid');

var Base = mocha.reporters.Base,
    utils = mocha.utils;

var splitChar = process.platform === "win32" ? '\\' : '/',
    reportsDir = path.join(process.cwd(), 'reports'),
    reportJsonFile = path.join(reportsDir, 'reports.json'),
    reportHtmlFile = path.join(reportsDir, 'mochawesome.html');
    reportCssFile = path.join(reportsDir, 'mochawesome.css'),
    templatesDir = path.join(__dirname, '..', 'templates'),
    templates = {};

module.exports = Mochawesome;

createStylesheet();
loadTemplates();
initHandlebarsHelpers();

/**
 * Initialize a new reporter.
 *
 * @param {Runner} runner
 * @api public
 */

function Mochawesome (runner) {
  var self = this;
  Base.call(this, runner);

  var allSuites = {},
      allTests = [],
      allFailures = [],
      allPasses = [],
      endCalled = false;

  runner.on('test end', function (test) {
    allTests.push(test);
  });

  runner.on('pass', function (test) {
    allPasses.push(test);
  });

  runner.on('fail', function (test){
    allFailures.push(test);
  });

  runner.on('end', function () {
    if (!endCalled) {
      endCalled = true; // end gets called more than once for some reason so this ensures we only do this once

      allSuites = self.runner.suite;
      traverseSuites(allSuites);

      var obj = {
        reportTitle: process.cwd().split(splitChar).pop(),
        stats: self.stats,
        suites: allSuites,
        allTests: allTests.map(cleanTest),
        allPasses: allPasses.map(cleanTest),
        allFailures: allFailures.map(cleanTest)
      };

      var passPercentage = Math.round((obj.stats.passes / obj.stats.tests)*1000)/10;
      var percentClass;
      if (passPercentage <= 50) {
        percentClass = 'danger';
      } else if (passPercentage > 50 && passPercentage < 80) {
        percentClass = 'warning';
      } else {
        percentClass = 'success';
      }
      obj.stats.passPercent = passPercentage;
      obj.stats.percentClass = percentClass;

      saveToFile('json', obj);
      saveToFile('html', obj);
    }
  });
}

/**
 * HELPER FUNCTIONS
 */

/**
 * Do a breadth-first search to find
 * and format all nested 'suite' objects.
 *
 * @param {Object} suite
 * @api private
 */

function traverseSuites (suite) {
  var queue = [],
      next = suite;
  while (next) {
    if (next.root) {
      cleanSuite(next);
    }
    if (next.suites.length) {
      _.each(next.suites, function(suite, i) {
        cleanSuite(suite);
        queue.push(suite);
      });
    }
    next = queue.shift();
  }
}

/**
 * Modify the suite object to add properties needed to render
 * the template and remove properties we do not need.
 *
 * @param {Object} suite
 * @api private
 */

function cleanSuite (suite) {
  var cleanTests = _.map(suite.tests, cleanTest);
  var passingTests = _.where(cleanTests, {state: 'passed'});
  var failingTests = _.where(cleanTests, {state: 'failed'});

  suite.uuid = uuid.v4();
  suite.tests = cleanTests;
  suite.fullFile = suite.file || '';
  suite.file = suite.file ? suite.file.replace(process.cwd(), '') : '';
  suite.passes = passingTests;
  suite.failures = failingTests;
  suite.totalTests = suite.tests.length;
  suite.totalPasses = passingTests.length;
  suite.totalFailures = failingTests.length;

  removeAllPropsFromObjExcept(suite, [
    'title',
    'tests',
    'suites',
    'file',
    'fullFile',
    'passes',
    'failures',
    'totalTests',
    'totalPasses',
    'totalFailures',
    'root',
    'uuid'
  ]);
}

/**
 * Return a plain-object representation of `test`
 * free of cyclic properties etc.
 *
 * @param {Object} test
 * @return {Object}
 * @api private
 */

function cleanTest (test) {
  return {
    title: test.title,
    fullTitle: test.fullTitle(),
    duration: test.duration,
    state: test.state,
    pass: test.state === 'passed',
    fail: test.state === 'failed',
    code: utils.clean(test.fn.toString()),
    err: test.err,
    isRoot: test.parent.root
  }
}

/**
 * Remove all properties from an object except
 * those that are in the propsToKeep array.
 *
 * @param {Object} obj
 * @param {Array} propsToKeep
 * @api private
 */

function removeAllPropsFromObjExcept(obj, propsToKeep) {
  _.forOwn(obj, function(val, prop) {
    if (propsToKeep.indexOf(prop) === -1) {
      delete obj[prop];
    }
  });
}

/**
 * Compile LESS stylesheet into CSS and save to file.
 *
 * @api private
 */

function createStylesheet () {
  var stylesDir = path.join(templatesDir, 'styles');
  var bsPath = path.join(__dirname, '..', 'node_modules', 'bootstrap', 'less');
  var stylesheet = fs.readFileSync(path.join(stylesDir, 'mochawesome.less'), {encoding: 'utf8'});

  var parser = new(less.Parser)({
    paths: [stylesDir, bsPath], // Specify search paths for @import directives
  });

  parser.parse(stylesheet, function(e, tree) {
    if (e) throw e;
    var outCss = tree.toCSS({
      compress: true
    });
    saveToFile('css', outCss);
  });
}

/**
 * Load and compile template files from templates directory.
 * Register any partials found in the directory.
 *
 * @api private
 */

function loadTemplates() {
  fs.readdir(templatesDir, function (err, files) {
    if (err) throw err;
    files.forEach(function (file) {
      var filePath = path.join(templatesDir, file);
      fs.stat(filePath, function (err, stats) {
        if (err) throw err;
        if (!stats.isDirectory()) {
          var fileName = file.replace('.mu','');
          fs.readFile(filePath, {encoding:'utf8'}, function (err, data) {
            if (err) throw err;
            if (file.indexOf('_') === 0) {
              handlebars.registerPartial(fileName, data);
            } else {
              templates[fileName] = handlebars.compile(data);
            }
          });
        }
      });
    });
  });
}

function loadTemplatesSync() {
  var files = fs.readdirSync(templatesDir);
  files.forEach(function (file) {
    var filePath = path.join(templatesDir, file);
    var fileStats = fs.statSync(filePath);
    var fileName = file.replace('.mu','');
    if (!fileStats.isDirectory()) {
      var file = fs.readFileSync(filePath, {encoding:'utf8'});
      if (file.indexOf('_') === 0) {
        handlebars.registerPartial(fileName, file);
      } else {
        templates[fileName] = handlebars.compile(file);
      }
    }
  });
}

/**
 * Save data out to files
 *
 * @param {String} json, html
 * @param {Object}
 * @api private
 */

function saveToFile (filetype, inData) {
  var outData, outFile, writeFile;
  var outMsg = '';
  switch (filetype) {
  case 'json':
    outData = JSON.stringify(inData, null, 2);
    outFile = reportJsonFile;
    break;
  case 'html':
    outData = templates.mochawesome(inData);
    outFile = reportHtmlFile;
    outMsg = "\nopen " + outFile.replace(process.cwd(),'').replace('/', '') + "\n";
    break;
  case 'css':
    outData = inData;
    outFile = reportCssFile;
    break;
  }

  try {
    if (!fs.existsSync(reportsDir)) {
      fs.mkdirSync(reportsDir);
    };
    writeFile = fs.openSync(outFile, 'w');
    fs.writeSync(writeFile, outData);
    fs.close(writeFile);
    util.print("Saved " + outFile + "\n" + outMsg);

  } catch (err) {
    util.print("\nError: Unable to save " + outFile + "\n" + err);
  }
}

/**
 * Register Handlebars helpers used in template
 *
 * @api private
 */

function initHandlebarsHelpers () {
  function getDurationObj(durationInMilliseconds) {
    var dur = moment.duration(durationInMilliseconds, 'ms');
    return {
      duration: dur,
      hrs: dur.asHours(),
      min: dur.asMinutes(),
      sec: dur.asSeconds(),
      ms: durationInMilliseconds
    }
  }

  handlebars.registerHelper('formatSummaryDuration', function (context) {
    var dur = getDurationObj(context);
    if (dur.hrs  < 1) {
      if (dur.min < 1) {
        if (dur.sec < 1) {
          return context;
        }
        return dur.sec;
      }
      return dur.min;
    }
    return dur.hrs;
  });

  handlebars.registerHelper('getSummaryDurationUnits', function (context) {
    var dur = getDurationObj(context);
    if (dur.hrs  < 1) {
      if (dur.min < 1) {
        if (dur.sec < 1) {
          return 'Milliseconds';
        }
        return 'Seconds';
      }
      return 'Minutes';
    }
    return 'Hours';
  });

  handlebars.registerHelper('formatDuration', function (context) {
    var dur = getDurationObj(context);
    if (dur.hrs  < 1) {
      if (dur.min < 1) {
        if (dur.sec < 1) {
          return context + 'ms';
        }
        return dur.sec + 's';
      }
      return dur.duration.get('m') + 'm:' + dur.duration.get('s') + ':' + dur.duration.get('ms');
    }
    return dur.duration.get('h') + 'h:' + dur.duration.get('m') + 'm:' + dur.duration.get('s') + ':' + dur.duration.get('ms');
  });

  handlebars.registerHelper('dateFormat', function(context, format) {
    if (format === "fromNow") {
      return moment(context).fromNow();
    } else {
      return moment(context).format(format);
    }
  });
}<commit_msg>format durations<commit_after>var util = require('util'),
    fs   = require('fs'),
    path = require('path'),
    mocha = require('mocha'),
    _ = require('lodash'),
    handlebars = require('handlebars'),
    less = require('less'),
    moment = require('moment'),
    uuid = require('node-uuid');

var Base = mocha.reporters.Base,
    utils = mocha.utils;

var splitChar = process.platform === "win32" ? '\\' : '/',
    reportsDir = path.join(process.cwd(), 'reports'),
    reportJsonFile = path.join(reportsDir, 'reports.json'),
    reportHtmlFile = path.join(reportsDir, 'mochawesome.html');
    reportCssFile = path.join(reportsDir, 'mochawesome.css'),
    templatesDir = path.join(__dirname, '..', 'templates'),
    templates = {};

module.exports = Mochawesome;

createStylesheet();
loadTemplates();
initHandlebarsHelpers();

/**
 * Initialize a new reporter.
 *
 * @param {Runner} runner
 * @api public
 */

function Mochawesome (runner) {
  var self = this;
  Base.call(this, runner);

  var allSuites = {},
      allTests = [],
      allFailures = [],
      allPasses = [],
      endCalled = false;

  runner.on('test end', function (test) {
    allTests.push(test);
  });

  runner.on('pass', function (test) {
    allPasses.push(test);
  });

  runner.on('fail', function (test){
    allFailures.push(test);
  });

  runner.on('end', function () {
    if (!endCalled) {
      endCalled = true; // end gets called more than once for some reason so this ensures we only do this once

      allSuites = self.runner.suite;
      traverseSuites(allSuites);

      var obj = {
        reportTitle: process.cwd().split(splitChar).pop(),
        stats: self.stats,
        suites: allSuites,
        allTests: allTests.map(cleanTest),
        allPasses: allPasses.map(cleanTest),
        allFailures: allFailures.map(cleanTest)
      };

      var passPercentage = Math.round((obj.stats.passes / obj.stats.tests)*1000)/10;
      var percentClass;
      if (passPercentage <= 50) {
        percentClass = 'danger';
      } else if (passPercentage > 50 && passPercentage < 80) {
        percentClass = 'warning';
      } else {
        percentClass = 'success';
      }
      obj.stats.passPercent = passPercentage;
      obj.stats.percentClass = percentClass;

      saveToFile('json', obj);
      saveToFile('html', obj);
    }
  });
}

/**
 * HELPER FUNCTIONS
 */

/**
 * Do a breadth-first search to find
 * and format all nested 'suite' objects.
 *
 * @param {Object} suite
 * @api private
 */

function traverseSuites (suite) {
  var queue = [],
      next = suite;
  while (next) {
    if (next.root) {
      cleanSuite(next);
    }
    if (next.suites.length) {
      _.each(next.suites, function(suite, i) {
        cleanSuite(suite);
        queue.push(suite);
      });
    }
    next = queue.shift();
  }
}

/**
 * Modify the suite object to add properties needed to render
 * the template and remove properties we do not need.
 *
 * @param {Object} suite
 * @api private
 */

function cleanSuite (suite) {
  var cleanTests = _.map(suite.tests, cleanTest);
  var passingTests = _.where(cleanTests, {state: 'passed'});
  var failingTests = _.where(cleanTests, {state: 'failed'});

  suite.uuid = uuid.v4();
  suite.tests = cleanTests;
  suite.fullFile = suite.file || '';
  suite.file = suite.file ? suite.file.replace(process.cwd(), '') : '';
  suite.passes = passingTests;
  suite.failures = failingTests;
  suite.totalTests = suite.tests.length;
  suite.totalPasses = passingTests.length;
  suite.totalFailures = failingTests.length;

  removeAllPropsFromObjExcept(suite, [
    'title',
    'tests',
    'suites',
    'file',
    'fullFile',
    'passes',
    'failures',
    'totalTests',
    'totalPasses',
    'totalFailures',
    'root',
    'uuid'
  ]);
}

/**
 * Return a plain-object representation of `test`
 * free of cyclic properties etc.
 *
 * @param {Object} test
 * @return {Object}
 * @api private
 */

function cleanTest (test) {
  return {
    title: test.title,
    fullTitle: test.fullTitle(),
    duration: test.duration,
    state: test.state,
    pass: test.state === 'passed',
    fail: test.state === 'failed',
    code: utils.clean(test.fn.toString()),
    err: test.err,
    isRoot: test.parent.root
  }
}

/**
 * Remove all properties from an object except
 * those that are in the propsToKeep array.
 *
 * @param {Object} obj
 * @param {Array} propsToKeep
 * @api private
 */

function removeAllPropsFromObjExcept(obj, propsToKeep) {
  _.forOwn(obj, function(val, prop) {
    if (propsToKeep.indexOf(prop) === -1) {
      delete obj[prop];
    }
  });
}

/**
 * Compile LESS stylesheet into CSS and save to file.
 *
 * @api private
 */

function createStylesheet () {
  var stylesDir = path.join(templatesDir, 'styles');
  var bsPath = path.join(__dirname, '..', 'node_modules', 'bootstrap', 'less');
  var stylesheet = fs.readFileSync(path.join(stylesDir, 'mochawesome.less'), {encoding: 'utf8'});

  var parser = new(less.Parser)({
    paths: [stylesDir, bsPath], // Specify search paths for @import directives
  });

  parser.parse(stylesheet, function(e, tree) {
    if (e) throw e;
    var outCss = tree.toCSS({
      compress: true
    });
    saveToFile('css', outCss);
  });
}

/**
 * Load and compile template files from templates directory.
 * Register any partials found in the directory.
 *
 * @api private
 */

function loadTemplates() {
  fs.readdir(templatesDir, function (err, files) {
    if (err) throw err;
    files.forEach(function (file) {
      var filePath = path.join(templatesDir, file);
      fs.stat(filePath, function (err, stats) {
        if (err) throw err;
        if (!stats.isDirectory()) {
          var fileName = file.replace('.mu','');
          fs.readFile(filePath, {encoding:'utf8'}, function (err, data) {
            if (err) throw err;
            if (file.indexOf('_') === 0) {
              handlebars.registerPartial(fileName, data);
            } else {
              templates[fileName] = handlebars.compile(data);
            }
          });
        }
      });
    });
  });
}

function loadTemplatesSync() {
  var files = fs.readdirSync(templatesDir);
  files.forEach(function (file) {
    var filePath = path.join(templatesDir, file);
    var fileStats = fs.statSync(filePath);
    var fileName = file.replace('.mu','');
    if (!fileStats.isDirectory()) {
      var file = fs.readFileSync(filePath, {encoding:'utf8'});
      if (file.indexOf('_') === 0) {
        handlebars.registerPartial(fileName, file);
      } else {
        templates[fileName] = handlebars.compile(file);
      }
    }
  });
}

/**
 * Save data out to files
 *
 * @param {String} json, html
 * @param {Object}
 * @api private
 */

function saveToFile (filetype, inData) {
  var outData, outFile, writeFile;
  var outMsg = '';
  switch (filetype) {
  case 'json':
    outData = JSON.stringify(inData, null, 2);
    outFile = reportJsonFile;
    break;
  case 'html':
    outData = templates.mochawesome(inData);
    outFile = reportHtmlFile;
    outMsg = "\nopen " + outFile.replace(process.cwd(),'').replace('/', '') + "\n";
    break;
  case 'css':
    outData = inData;
    outFile = reportCssFile;
    break;
  }

  try {
    if (!fs.existsSync(reportsDir)) {
      fs.mkdirSync(reportsDir);
    };
    writeFile = fs.openSync(outFile, 'w');
    fs.writeSync(writeFile, outData);
    fs.close(writeFile);
    util.print("Saved " + outFile + "\n" + outMsg);

  } catch (err) {
    util.print("\nError: Unable to save " + outFile + "\n" + err);
  }
}

/**
 * Register Handlebars helpers used in template
 *
 * @api private
 */

function initHandlebarsHelpers () {
  function getDurationObj(durationInMilliseconds) {
    var dur = moment.duration(durationInMilliseconds, 'ms');
    return {
      duration: dur,
      hrs: dur.asHours(),
      min: dur.asMinutes(),
      sec: dur.asSeconds(),
      ms: durationInMilliseconds
    }
  }

  handlebars.registerHelper('formatSummaryDuration', function (context) {
    var dur = getDurationObj(context);
    if (dur.hrs  < 1) {
      if (dur.min < 1) {
        if (dur.sec < 1) {
          return context;
        }
        return dur.sec;
      }
      return dur.min;
    }
    return dur.duration.get('h') + ':' + dur.duration.get('m');
  });

  handlebars.registerHelper('getSummaryDurationUnits', function (context) {
    var dur = getDurationObj(context);
    if (dur.hrs  < 1) {
      if (dur.min < 1) {
        if (dur.sec < 1) {
          return 'Milliseconds';
        }
        return 'Seconds';
      }
      return 'Minutes';
    }
    return 'Hours';
  });

  handlebars.registerHelper('formatDuration', function (context) {
    var dur = getDurationObj(context);
    if (dur.hrs  < 1) {
      if (dur.min < 1) {
        if (dur.sec < 1) {
          return context + ' ms';
        }
        return dur.sec + ' s';
      }
      return dur.duration.get('m') + ':' + dur.duration.get('s') + '.' + dur.duration.get('ms') + ' m';
    }
    return dur.duration.get('h') + ':' + dur.duration.get('m') + ':' + dur.duration.get('s') + '.' + dur.duration.get('ms') + ' h';
  });

  handlebars.registerHelper('dateFormat', function(context, format) {
    if (format === "fromNow") {
      return moment(context).fromNow();
    } else {
      return moment(context).format(format);
    }
  });
}<|endoftext|>