<commit_before>import React, { useMemo, useCallback, useContext } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { createSelector } from 'redux-orm/src/index.js';
import {
    pathToAction,
    getOptions,
    selectLocationState
} from 'redux-first-router';
import { paramCase } from 'param-case';
import { capitalCase } from 'capital-case';
import { useHtmlInput } from './components/inputs/Input';

const isAction = path => path && path.type;

export function useRoutesMap() {
    return useSelector(state => selectLocationState(state).routesMap);
}

export function toNavAction(path, routesMap) {
    const { querySerializer } = getOptions(),
        baseUrl = routesMap.INDEX ? routesMap.INDEX.path : '/';
    return isAction(path)
        ? path
        : pathToAction(
              path.indexOf('/') === 0 ? path : baseUrl + path,
              routesMap,
              querySerializer
          );
}

export function useNavAction() {
    const routesMap = useRoutesMap();
    return useCallback(path => toNavAction(path, routesMap), [routesMap]);
}

export function useNav(to) {
    const dispatch = useDispatch(),
        navAction = useNavAction();
    return useMemo(() => {
        function nav(path) {
            dispatch(navAction(path));
        }
        return to ? nav.bind(null, to) : nav;
    }, [dispatch, navAction, to]);
}

export const RouteContext = React.createContext({
    name: '@@CURRENT'
});

function useCurrentRoute() {
    return useContext(RouteContext).name;
}

export function useRenderContext() {
    const context = useSelector(state => state.context),
        currentRoute = useCurrentRoute();
    return (context && context[currentRoute]) || {};
}

export function useRouteInfo() {
    const currentRoute = useCurrentRoute(),
        routeInfos = useSelector(state => state.routeInfo),
        routeInfo = routeInfos && routeInfos[currentRoute],
        context = useRenderContext(),
        { router_info: ctxRouteInfo } = context;
    if (routeInfo) {
        if (
            !ctxRouteInfo ||
            ['name', 'mode', 'variant', 'item_id'].some(
                key => ctxRouteInfo[key] != routeInfo[key]
            )
        ) {
            return {
                ...routeInfo,
                pending: true
            };
        } else {
            return ctxRouteInfo;
        }
    } else {
        return { pending: true };
    }
}

export function useContextTitle() {
    const context = useRenderContext(),
        routeInfo = useRouteInfo();

    var title;
    if (routeInfo && !routeInfo.pending) {
        title = context.title || context.label;
    }

    if (!title && routeInfo) {
        title = getRouteTitle(routeInfo);
    }

    if (!title) {
        title = 'Loading...';
    }

    return title;
}

export function useRouteTitle(routeName) {
    const app = useApp();

    function routeTitle(routeName) {
        const [name, mode, variant] = app.splitRoute(routeName);
        const page_config = app.config.pages[name] || {
            name
        };
        return getRouteTitle({
            page_config,
            mode,
            variant
        });
    }

    if (routeName) {
        return routeTitle(routeName);
    } else {
        return routeTitle;
    }
}

function getRouteTitle(routeInfo) {
    const { page_config, mode, variant } = routeInfo,
        verbose_name = page_config.verbose_name || page_config.name,
        verbose_name_plural =
            page_config.verbose_name_plural ||
            page_config.url ||
            `${verbose_name}s`;

    let title;
    if (mode === 'list' && verbose_name === 'outbox') {
        title = 'outbox';
    } else if (mode === 'list') {
        title = verbose_name_plural;
    } else if (mode === 'edit') {
        if (variant === 'new') {
            title = `New ${verbose_name}`;
        } else {
            title = `Edit ${verbose_name}`;
        }
    } else if (mode && mode !== 'detail') {
        title = `${verbose_name} - ${mode}`;
    } else {
        title = verbose_name;
    }

    return capitalCase(title);
}

export function useReverse() {
    const location = useSelector(selectLocationState);
    return useCallback(
        (name, payload, query) => {
            const action = {
                type: name.toUpperCase()
            };
            if (!location.routesMap[action.type]) {
                throw new Error(`Unknown route: ${action.type}`);
            }
            if (payload) {
                if (typeof payload === 'object') {
                    action.payload = payload;
                } else {
                    action.payload = { slug: payload };
                }
            }
            if (query) {
                action.meta = { query };
            }
            return action;
        },
        [location]
    );
}

export function useIndexRoute() {
    // FIXME: This should be read from the configuration
    return 'index';
}

export function useBreadcrumbs() {
    const title = useContextTitle(),
        {
            name,
            page,
            page_config,
            item_id,
            mode,
            full_path,
            parent_id,
            parent_label,
            parent_conf
        } = useRouteInfo(),
        reverse = useReverse(),
        index = useIndexRoute();

    if (name === index) {
        return null;
    }

    const links = [],
        addLink = (url, label) => links.push({ url, label }),
        addCurrentPage = label => addLink(full_path, label);

    addLink(reverse(index), 'Home');

    if (parent_id && parent_conf) {
        addLink(
            reverse(`${parent_conf.page}_list`),
            getRouteTitle({
                page_config: parent_conf,
                mode: 'list'
            })
        );
        addLink(reverse(`${parent_conf.page}_detail`, parent_id), parent_label);
    }

    if (item_id) {
        addLink(
            reverse(`${page_config.name}_list`),
            getRouteTitle({
                page_config,
                mode: 'list'
            })
        );
        if (mode !== 'detail') {
            const currentTitle = getRouteTitle({ page_config, mode }),
                detailTitle = getRouteTitle({ page_config, mode: 'detail' });
            addLink(
                reverse(`${page}_detail`, item_id),
                title === currentTitle ? detailTitle : title
            );
            addCurrentPage(mode);
        } else {
            addCurrentPage(title);
        }
    } else {
        addCurrentPage(title);
    }

    return links;
}

export function useSitemap() {
    const app = useApp(),
        pages = Object.values(app.config.pages),
        options = pages.filter(page => !page.list),
        models = pages.filter(page => page.list);

    return { options, models };
}

export function useSpinner() {
    const spinner = useSelector(state => state.spinner);
    return spinner;
}

export const AppContext = React.createContext({
    app: {
        plugins: {},
        models: {}
    }
});

export function useComponents() {
    return usePluginComponentMap('react', 'components');
}

export function useInputComponents() {
    return usePluginComponentMap('react', 'inputs');
}

export { useHtmlInput };

export function useIconComponents() {
    return usePluginComponentMap('react', 'icons');
}

export function useIcon(icon) {
    const icons = useIconComponents();
    if (typeof icon === 'string') {
        if (icons[icon]) {
            return icons[icon];
        } else {
            return null;
        }
    } else if (typeof icon === 'function') {
        return icon;
    } else {
        return null;
    }
}

export function useViewComponents() {
    return usePluginComponentMap('react', 'views');
}

export function useApp() {
    return useContext(AppContext).app;
}

export function useModel(name, filter) {
    const app = useApp(),
        model = app.models[name];

    if (!model) {
        throw new Error(`Unknown model name ${name}`);
    }

    let selector;
    if (
        typeof filter === 'function' ||
        (typeof filter === 'object' && !Array.isArray(filter))
    ) {
        // Filter by query
        selector = createSelector(model.orm, session =>
            model.getQuerySet(session).filter(filter).toRefArray()
        );
    } else if (filter) {
        // Filter by id (default ModelSelectorSpec behavior)
        selector = state => createSelector(model.orm[name])(state, filter);
    } else {
        // All objects (use getQuerySet() to leverage config.ordering)
        selector = createSelector(model.orm, session =>
            model.getQuerySet(session).toRefArray()
        );
    }

    return useSelector(selector);
}

export function useUnsynced(modelConf) {
    const outbox = useSelector(state => state.offline.outbox) || [],
        {
            outbox: { filterUnsynced, parseOutbox }
        } = useApp();

    return filterUnsynced(parseOutbox(outbox), modelConf);
}

export function useList() {
    const { list: contextList = [], show_unsynced } = useRenderContext(),
        { page_config } = useRouteInfo(),
        modelList = useModel(page_config.page),
        unsynced = useUnsynced(page_config);

    let list;
    if (show_unsynced) {
        // Context list should generally already equal model list, unless
        // there has been a sync or other model update since last RENDER.
        const seen = {};
        modelList.forEach(row => (seen[row.id] = true));
        list = modelList.concat(contextList.filter(row => !seen[row.id]));
    } else {
        // Context list probably came directly from server, ignore local model
        list = contextList;
    }
    const empty = !list || !list.length;

    return {
        page_config,
        list,
        unsynced: show_unsynced ? unsynced : [],
        empty
    };
}

export function usePlugin(name) {
    const { plugins } = useApp();
    return plugins[name];
}

export function usePluginComponentMap(pluginName, mapName) {
    const plugin = usePlugin(pluginName) || {},
        { config = {} } = plugin,
        { [mapName]: componentMap = {} } = config;
    Object.entries(componentMap).forEach(([key, val]) => {
        componentMap[paramCase(key)] = val;
    });
    return componentMap;
}

export function usePluginState(name) {
    const plugin = usePlugin(name),
        pluginState = useSelector(state => state[name]);

    if (plugin) {
        return pluginState;
    } else {
        return null;
    }
}

export function usePluginReducer(name) {
    const plugin = usePlugin(name),
        pluginState = useSelector(state => state[name]);

    if (plugin) {
        return [pluginState, plugin];
    } else {
        return [null, null];
    }
}

export function usePluginContent() {
    const app = useApp(),
        components = useComponents(),
        routeInfo = useRouteInfo();

    const content = useMemo(
        () =>
            app
                .callPlugins('runComponent', [routeInfo])
                .map(name => (name ? components[name] : null))
                .filter(component => !!component),
        [routeInfo]
    );

    return useCallback(
        function PluginContent() {
            if (!content.length) {
                return null;
            } else {
                return (
                    <>
                        {content.map((Component, i) => (
                            <Component key={i} />
                        ))}
                    </>
                );
            }
        },
        [content]
    );
}

export function useMessages() {
    const { config } = usePlugin('react');
    return config.messages;
}
<commit_msg>accept non-current routeName in useRouteInfo<commit_after>import React, { useMemo, useCallback, useContext } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { createSelector } from 'redux-orm/src/index.js';
import {
    pathToAction,
    getOptions,
    selectLocationState
} from 'redux-first-router';
import { paramCase } from 'param-case';
import { capitalCase } from 'capital-case';
import { useHtmlInput } from './components/inputs/Input';

const isAction = path => path && path.type;

export function useRoutesMap() {
    return useSelector(state => selectLocationState(state).routesMap);
}

export function toNavAction(path, routesMap) {
    const { querySerializer } = getOptions(),
        baseUrl = routesMap.INDEX ? routesMap.INDEX.path : '/';
    return isAction(path)
        ? path
        : pathToAction(
              path.indexOf('/') === 0 ? path : baseUrl + path,
              routesMap,
              querySerializer
          );
}

export function useNavAction() {
    const routesMap = useRoutesMap();
    return useCallback(path => toNavAction(path, routesMap), [routesMap]);
}

export function useNav(to) {
    const dispatch = useDispatch(),
        navAction = useNavAction();
    return useMemo(() => {
        function nav(path) {
            dispatch(navAction(path));
        }
        return to ? nav.bind(null, to) : nav;
    }, [dispatch, navAction, to]);
}

export const RouteContext = React.createContext({
    name: '@@CURRENT'
});

function useCurrentRoute() {
    return useContext(RouteContext).name;
}

export function useRenderContext(routeName) {
    const context = useSelector(state => state.context),
        currentRoute = useCurrentRoute();
    return (context && context[routeName || currentRoute]) || {};
}

export function useRouteInfo(routeName) {
    const currentRoute = useCurrentRoute(),
        routeInfos = useSelector(state => state.routeInfo),
        routeInfo = routeInfos && routeInfos[routeName || currentRoute],
        context = useRenderContext(routeName),
        { router_info: ctxRouteInfo } = context;
    if (routeInfo) {
        if (
            !ctxRouteInfo ||
            ['name', 'mode', 'variant', 'item_id'].some(
                key => ctxRouteInfo[key] != routeInfo[key]
            )
        ) {
            return {
                ...routeInfo,
                pending: true
            };
        } else {
            return ctxRouteInfo;
        }
    } else {
        return { pending: true };
    }
}

export function useContextTitle() {
    const context = useRenderContext(),
        routeInfo = useRouteInfo();

    var title;
    if (routeInfo && !routeInfo.pending) {
        title = context.title || context.label;
    }

    if (!title && routeInfo) {
        title = getRouteTitle(routeInfo);
    }

    if (!title) {
        title = 'Loading...';
    }

    return title;
}

export function useRouteTitle(routeName) {
    const app = useApp();

    function routeTitle(routeName) {
        const [name, mode, variant] = app.splitRoute(routeName);
        const page_config = app.config.pages[name] || {
            name
        };
        return getRouteTitle({
            page_config,
            mode,
            variant
        });
    }

    if (routeName) {
        return routeTitle(routeName);
    } else {
        return routeTitle;
    }
}

function getRouteTitle(routeInfo) {
    const { page_config, mode, variant } = routeInfo,
        verbose_name = page_config.verbose_name || page_config.name,
        verbose_name_plural =
            page_config.verbose_name_plural ||
            page_config.url ||
            `${verbose_name}s`;

    let title;
    if (mode === 'list' && verbose_name === 'outbox') {
        title = 'outbox';
    } else if (mode === 'list') {
        title = verbose_name_plural;
    } else if (mode === 'edit') {
        if (variant === 'new') {
            title = `New ${verbose_name}`;
        } else {
            title = `Edit ${verbose_name}`;
        }
    } else if (mode && mode !== 'detail') {
        title = `${verbose_name} - ${mode}`;
    } else {
        title = verbose_name;
    }

    return capitalCase(title);
}

export function useReverse() {
    const location = useSelector(selectLocationState);
    return useCallback(
        (name, payload, query) => {
            const action = {
                type: name.toUpperCase()
            };
            if (!location.routesMap[action.type]) {
                throw new Error(`Unknown route: ${action.type}`);
            }
            if (payload) {
                if (typeof payload === 'object') {
                    action.payload = payload;
                } else {
                    action.payload = { slug: payload };
                }
            }
            if (query) {
                action.meta = { query };
            }
            return action;
        },
        [location]
    );
}

export function useIndexRoute() {
    // FIXME: This should be read from the configuration
    return 'index';
}

export function useBreadcrumbs() {
    const title = useContextTitle(),
        {
            name,
            page,
            page_config,
            item_id,
            mode,
            full_path,
            parent_id,
            parent_label,
            parent_conf
        } = useRouteInfo(),
        reverse = useReverse(),
        index = useIndexRoute();

    if (name === index) {
        return null;
    }

    const links = [],
        addLink = (url, label) => links.push({ url, label }),
        addCurrentPage = label => addLink(full_path, label);

    addLink(reverse(index), 'Home');

    if (parent_id && parent_conf) {
        addLink(
            reverse(`${parent_conf.page}_list`),
            getRouteTitle({
                page_config: parent_conf,
                mode: 'list'
            })
        );
        addLink(reverse(`${parent_conf.page}_detail`, parent_id), parent_label);
    }

    if (item_id) {
        addLink(
            reverse(`${page_config.name}_list`),
            getRouteTitle({
                page_config,
                mode: 'list'
            })
        );
        if (mode !== 'detail') {
            const currentTitle = getRouteTitle({ page_config, mode }),
                detailTitle = getRouteTitle({ page_config, mode: 'detail' });
            addLink(
                reverse(`${page}_detail`, item_id),
                title === currentTitle ? detailTitle : title
            );
            addCurrentPage(mode);
        } else {
            addCurrentPage(title);
        }
    } else {
        addCurrentPage(title);
    }

    return links;
}

export function useSitemap() {
    const app = useApp(),
        pages = Object.values(app.config.pages),
        options = pages.filter(page => !page.list),
        models = pages.filter(page => page.list);

    return { options, models };
}

export function useSpinner() {
    const spinner = useSelector(state => state.spinner);
    return spinner;
}

export const AppContext = React.createContext({
    app: {
        plugins: {},
        models: {}
    }
});

export function useComponents() {
    return usePluginComponentMap('react', 'components');
}

export function useInputComponents() {
    return usePluginComponentMap('react', 'inputs');
}

export { useHtmlInput };

export function useIconComponents() {
    return usePluginComponentMap('react', 'icons');
}

export function useIcon(icon) {
    const icons = useIconComponents();
    if (typeof icon === 'string') {
        if (icons[icon]) {
            return icons[icon];
        } else {
            return null;
        }
    } else if (typeof icon === 'function') {
        return icon;
    } else {
        return null;
    }
}

export function useViewComponents() {
    return usePluginComponentMap('react', 'views');
}

export function useApp() {
    return useContext(AppContext).app;
}

export function useModel(name, filter) {
    const app = useApp(),
        model = app.models[name];

    if (!model) {
        throw new Error(`Unknown model name ${name}`);
    }

    let selector;
    if (
        typeof filter === 'function' ||
        (typeof filter === 'object' && !Array.isArray(filter))
    ) {
        // Filter by query
        selector = createSelector(model.orm, session =>
            model.getQuerySet(session).filter(filter).toRefArray()
        );
    } else if (filter) {
        // Filter by id (default ModelSelectorSpec behavior)
        selector = state => createSelector(model.orm[name])(state, filter);
    } else {
        // All objects (use getQuerySet() to leverage config.ordering)
        selector = createSelector(model.orm, session =>
            model.getQuerySet(session).toRefArray()
        );
    }

    return useSelector(selector);
}

export function useUnsynced(modelConf) {
    const outbox = useSelector(state => state.offline.outbox) || [],
        {
            outbox: { filterUnsynced, parseOutbox }
        } = useApp();

    return filterUnsynced(parseOutbox(outbox), modelConf);
}

export function useList() {
    const { list: contextList = [], show_unsynced } = useRenderContext(),
        { page_config } = useRouteInfo(),
        modelList = useModel(page_config.page),
        unsynced = useUnsynced(page_config);

    let list;
    if (show_unsynced) {
        // Context list should generally already equal model list, unless
        // there has been a sync or other model update since last RENDER.
        const seen = {};
        modelList.forEach(row => (seen[row.id] = true));
        list = modelList.concat(contextList.filter(row => !seen[row.id]));
    } else {
        // Context list probably came directly from server, ignore local model
        list = contextList;
    }
    const empty = !list || !list.length;

    return {
        page_config,
        list,
        unsynced: show_unsynced ? unsynced : [],
        empty
    };
}

export function usePlugin(name) {
    const { plugins } = useApp();
    return plugins[name];
}

export function usePluginComponentMap(pluginName, mapName) {
    const plugin = usePlugin(pluginName) || {},
        { config = {} } = plugin,
        { [mapName]: componentMap = {} } = config;
    Object.entries(componentMap).forEach(([key, val]) => {
        componentMap[paramCase(key)] = val;
    });
    return componentMap;
}

export function usePluginState(name) {
    const plugin = usePlugin(name),
        pluginState = useSelector(state => state[name]);

    if (plugin) {
        return pluginState;
    } else {
        return null;
    }
}

export function usePluginReducer(name) {
    const plugin = usePlugin(name),
        pluginState = useSelector(state => state[name]);

    if (plugin) {
        return [pluginState, plugin];
    } else {
        return [null, null];
    }
}

export function usePluginContent() {
    const app = useApp(),
        components = useComponents(),
        routeInfo = useRouteInfo();

    const content = useMemo(
        () =>
            app
                .callPlugins('runComponent', [routeInfo])
                .map(name => (name ? components[name] : null))
                .filter(component => !!component),
        [routeInfo]
    );

    return useCallback(
        function PluginContent() {
            if (!content.length) {
                return null;
            } else {
                return (
                    <>
                        {content.map((Component, i) => (
                            <Component key={i} />
                        ))}
                    </>
                );
            }
        },
        [content]
    );
}

export function useMessages() {
    const { config } = usePlugin('react');
    return config.messages;
}
<|endoftext|>


<commit_before>/*
* @module AwsSpotter-webtask
*/
const https = require('https');
const Util = require('util');
const vm = require('vm');
const mods = require('module');
const concat = require('concat-stream');

/**
* Proof of concept, require a remote module
* Since webtask.io does not have this Node module in their private npm cache,
* it must be manually loaded at run time. Probably not very efficient.
*/
var remoteRequire = function (callback) {
  var url = 'https://raw.githubusercontent.com/glennschler/aws-spotter-node/master/index.js';

  https.get(url, function(res) {
    res.setEncoding('utf8');
    res.pipe(concat({ encoding: 'string' }, function(remoteSrc) {
      var wrappedSrc = mods.wrap(remoteSrc);
      vm.runInThisContext(wrappedSrc,
        'AwsSpotter.js')(null, require, module, __filename, __dirname);

      var moduleClass = module.exports;
      callback(moduleClass);
    }));
  });
}

// The webtask.io entry-point
module.exports = function(context, cb) {
  var awsCreds = context.data.wtData; // Secure data set during the 'wt create'
  awsCreds = parseJson(awsCreds);

  // These run time execution arguments are sent as JSON postdata
  var runArgs = context.body;

  // EC2 IAM user credentials must exist
  if (!awsCreds || !awsCreds.accessKeyId || !awsCreds.secretAccessKey) {
    return cb(new Error('EC2 secrets are missing'));
  }

  if (typeof runArgs === 'undefined') {
    return cb(new Error('AwsSpotter arguments are missing'));
  }

  logInfo(typeof runArgs, runArgs);

  // The EC2 region is sent at execution time. Merge with the EC2 credentials
  awsCreds.region = !runArgs ? '' : runArgs.region;
  var instanceType = !runArgs ? '' : runArgs.type;

  // Fetch the AwsSpotter module and then make an AwsSpotter method call
  remoteRequire(function (requiredClass) {
    const AwsSpotter = requiredClass;

    var spotter = null;
    try {
      var isLogging = true;
      spotter = new AwsSpotter(awsCreds, isLogging);
    }
    catch (errMsg) {
      returnError('Error instantiating AwsSpotter:', errMsg);
    }

    if (spotter !== null) {
      spotPrices(spotter);
    }
  });

  // called back after the remote module is loaded
  var spotPrices = function (spotter) {
    var priceOpts = {
      type: runArgs.type || 'm3.medium',
      product: runArgs.product || 'Linux/UNIX',
      dryRun: runArgs.dryRun
    };

    // Listen for price history results
    spotter.on('prices', onPrices);

    // Make the price history request
    spotter.spotPrices(priceOpts);
  }

  // fired when the spotPrices request is complete
  var onPrices = function onPrices(pricesData, err) {
    if (pricesData === null) {
      returnError(pricesData);
    }
    else {
      return cb(null, pricesData);
    }
  };

  // wrap the error
  var returnError = function() {
    errMsg = Util.format.apply(this, arguments);
    logInfo('Error:', Util.format.apply(this, arguments));
    return cb(errMsg);
  }
};

// helper to parse json
var parseJson = function(jsonStr) {
  try {
    jsonStr = JSON.parse(jsonStr);
  }
  catch (parseError) {
    jsonStr = null;
  }
  return jsonStr;
}

// wrap for future 3rd party logger
var logInfo = function () {
  console.info(Util.format.apply(this, arguments));
}
<commit_msg>log error correctly from the webtask<commit_after>/*
* @module AwsSpotter-webtask
*/
const https = require('https');
const Util = require('util');
const vm = require('vm');
const mods = require('module');
const concat = require('concat-stream');

/**
* Proof of concept, require a remote module
* Since webtask.io does not have this Node module in their private npm cache,
* it must be manually loaded at run time. Probably not very efficient.
*/
var remoteRequire = function (callback) {
  var url = 'https://raw.githubusercontent.com/glennschler/aws-spotter-node/master/index.js';

  https.get(url, function(res) {
    res.setEncoding('utf8');
    res.pipe(concat({ encoding: 'string' }, function(remoteSrc) {
      var wrappedSrc = mods.wrap(remoteSrc);
      vm.runInThisContext(wrappedSrc,
        'AwsSpotter.js')(null, require, module, __filename, __dirname);

      var moduleClass = module.exports;
      callback(moduleClass);
    }));
  });
}

// The webtask.io entry-point
module.exports = function(context, cb) {
  var awsCreds = context.data.wtData; // Secure data set during the 'wt create'
  awsCreds = parseJson(awsCreds);

  // These run time execution arguments are sent as JSON postdata
  var runArgs = context.body;

  // EC2 IAM user credentials must exist
  if (!awsCreds || !awsCreds.accessKeyId || !awsCreds.secretAccessKey) {
    cb(new Error('EC2 secrets are missing'));
    return;
  }

  if (typeof runArgs === 'undefined') {
    cb(new Error('AwsSpotter arguments are missing'));
    return;
  }

  // The EC2 region is sent at execution time. Merge with the EC2 credentials
  awsCreds.region = !runArgs ? '' : runArgs.region;
  var instanceType = !runArgs ? '' : runArgs.type;

  // Fetch the AwsSpotter module and then make an AwsSpotter method call
  remoteRequire(function (requiredClass) {
    const AwsSpotter = requiredClass;

    var spotter = null;
    try {
      var isLogging = true;
      spotter = new AwsSpotter(awsCreds, isLogging);
    }
    catch (errMsg) {
      returnError('Error instantiating AwsSpotter:', errMsg);
    }

    if (spotter !== null) {
      spotPrices(spotter, runArgs);
    }
  });

  // called back after the remote module is loaded
  var spotPrices = function (spotter, runArgs) {

    console.log ('args', runArgs);

    var priceOpts = {
      type: runArgs.type || 'm3.medium',
      product: runArgs.product || 'Linux/UNIX',
      DryRun: runArgs.dryRun
    };

    // Listen for price history results
    spotter.on('prices', onPrices);

    // Make the price history request
    spotter.spotPrices(priceOpts);
  }

  // handler when spotPrices request is complete
  var onPrices = function (pricesData, err) {
    if (pricesData === null) {
      returnError('price err:\n', err);
    }
    else {
      logInfo('prices event fired:\n', pricesData);
      cb(pricesData);
    }
  };

  // wrap the error
  var returnError = function () {
    errMsg = Util.format.apply(this, arguments);
    logInfo('Error:', Util.format.apply(this, arguments));
    cb(errMsg);
  }
};

// helper to parse json
var parseJson = function (jsonStr) {
  try {
    jsonStr = JSON.parse(jsonStr);
  }
  catch (parseError) {
    jsonStr = null;
  }
  return jsonStr;
}

// wrap for future 3rd party logger
var logInfo = function () {
  console.info(Util.format.apply(this, arguments));
}
<|endoftext|>


<commit_before>define([
	'jquery', 'cascade', 'bootstrap'
], function ($, $cascade) {
	// Global handler for better bootstrap implicit experience
	$('body').popover({selector: '[data-toggle="popover"]:not([data-trigger="manual"])', html: 'true', trigger: 'hover focus', container: 'body'});
	$('body').tooltip({selector: '[data-toggle="tooltip"]:not([data-trigger="manual"])', html: 'true', trigger: 'hover focus', container: 'body'});

	var closeTooltips = function() {
		$('[data-toggle="tooltip"][aria-describedby]').each(function() {
			$(this).tooltip('hide');
		});
		// Destroy orphan previous tooltips
		$('.tooltip.in').empty().remove();
	};
	$(document).on('click', '.toggle-visibility', function () {
		$(this).toggleClass('active');
	}).on('click', '.disabled,[disabled]', function (event) {
		event.preventDefault();
	}).on('click', '[data-toggle="popover"][data-trigger="manual"]', function () {
		$(this).popover('toggle');
	}).on('mousedown', function (e) {
		// Auto hide popover on click
		if ($('.popover.in').length && $(e.target).closest('[data-toggle="popover"],.popover').length === 0) {
			$('.popover.in').each(function () {
				$('[aria-describedby="' + $(this).attr('id') + '"]').popover('hide');
			});
		}
	}).on('show.bs.tooltip', null, function() {
		// Close nicely previous tooltips when a new one is displayed
		closeTooltips();
	}).on('show.bs.dropdown', null, function(e) {
		// Close previous dropdowns
		$('.dropdown-menu.detached').closeDropdown();

		// Handle the dropdown inside an absolute/overflow-hidden container, move the dropdown to body
		var $trigger = $(e.relatedTarget);
		if (!$trigger.hasClass('detached') && $trigger.closest('.dropdown-overflow').length && $trigger.closest('.dropdown').length) {
			var flag = 'dropdown-' + Math.random();
			var $dropdown = $trigger.closest('.dropdown');
			var $menu = $dropdown.find('.dropdown-menu').addClass('detached').attr('data-previous-container',flag);
			$trigger.addClass('detached').attr('data-dropdown-container',flag);
			$('body').append($menu.css({
				position: 'absolute',
				display: 'initial',
				left: $menu.offset().left + 'px',
				width : $menu.width() + 'px',
				top: ($menu.offset().top + $(e.target).outerHeight()) + 'px'
			}).data('open', true).detach());
		}
	}).on('hidden.bs.dropdown', null, function(e) {
		var $trigger = $(e.relatedTarget);
		if ($trigger.hasClass('detached')) {
			$('[data-previous-container="' + $trigger.attr('data-dropdown-container') + '"]').closeDropdown();
		}
	}).on('change', '.btn-file :file', function () {
		var $input = $(this);
		var label = $input.val().replace(/\\/g, '/').replace(/.*\//, '');
		$input.parents('.input-group').find(':text').val(label);
		// FIX : #19192 ->
	}).off('click.bs.button.data-api', '[data-toggle^="button"]').on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) {
		var $btn = $(e.target).closest('.btn').button('toggle');
		if (!($(e.target).is('input[type="radio"]') || $(e.target).is('input[type="checkbox"]'))) {
			// Prevent double click on radios, and the double selections (so cancellation) on checkboxes
			e.preventDefault();
			// The target component still receive the focus
			if ($btn.is('input,button')) {
				$btn.trigger('focus');
			} else {
				$btn.find('input:visible,button:visible').first().trigger('focus');
			}
		}
	}).on('click', '.retract', function () {
		$(this).closest('.retractable').toggleClass('retracted');
	}).on('keypress', 'form:not([method]),[method="get"],form[method="GET"]', function (e) {
		// Some browser (like Chrome) the first submit is used (hidden or not) and receive a click
		if ((e.which === 13 || e.keyCode === 13) && !$(e.target).is('textarea')) {
			// Determines the right input
			var $button = $(this).find('input:visible[type=submit]:not(.disabled):not(.hide):not(.hidden):not([disabled]),button:visible[type=submit]:not(.disabled):not(.hide):not(.hidden):not([disabled])').first();
			if ($button.length) {
				$button.trigger('click');
				e.preventDefault();
				return false;
			}
		}
	}).on('submit', 'form:not([method]),[method="get"],form[method="GET"]', function (e) {
		e.preventDefault();
	}).on('show.bs.tab', 'a[data-toggle="tab"]', function () {
		// Load active partials for tab
		var $target = $($(this).attr('href'));
		if ($target.is('[data-ajax]')) {
			$.proxy($cascade.loadPartial, $target)();
		}
	})
	// Load active partials for collapse
	.on('show.bs.collapse', '.collapse[data-ajax]', $cascade.loadPartial)
	// Load active partials for popover
	.on('show.bs.popover', '[data-ajax]', $cascade.loadPartial)
	// Load active partials for modal
	.on('click.bs.modal.data-api.ajax', '[data-toggle="modal-ajax"][data-ajax]:not([cascade-loaded])', function (e) {
		var $link = $(this).attr('cascade-loaded','true');
		$.proxy($cascade.loadPartial, $link)(function() {
			$link.attr('data-toggle', 'modal').trigger('click.bs.modal.data-api');
		});
		e.preventDefault();
	}).on('show.bs.modal', '.modal', closeTooltips)
	.on('hide.bs.modal', '.modal', closeTooltips)
	.on('show.bs.modal', '.modal[data-ajax]', $cascade.loadPartial);

	$.fn.hideGroup = function () {
		$(this).closest('.form-group').addClass('hidden');
		return this;
	};
	$.fn.closeDropdown = function () {
		this.each(function() {
			var $dropdown = $(this);
			var flag = $dropdown.attr('data-previous-container') || $dropdown.attr('data-dropdown-container');
			var $trigger = $();
			var $container = $();
			if (flag) {
				$trigger = $('[data-dropdown-container="' + flag + '"]');
				$dropdown = $('[data-previous-container="' + flag + '"]');
				$container = $trigger.parent();
			} else if ($dropdown.is('.dropdown-menu')) {
				$container = $dropdown.parent();
				$trigger = $container.find('[data-toggle="dropdown"]');
			} else if ($dropdown.is('.dropdown')) {
				$container = $dropdown;
				$trigger = $container.find('[data-toggle="dropdown"]');
				$dropdown = $container.find('.dropdown-menu');
			} else if ($dropdown.is('[data-toggle="dropdown"]')) {
				$container = $dropdown.parent();
				$trigger = $dropdown;
				$dropdown = $container.find('.dropdown-menu');
			}
			if ($trigger.hasClass('detached')) {
				$container.removeClass('open').append($dropdown.removeClass('detached').css({
					position: '',
					left: '',
					width: '',
					display: '',
					top: ''
				}).detach());
				$trigger.removeClass('detached');
			}
			$container.removeAttr('data-open');
			$trigger.removeAttr('data-dropdown-container');
			$dropdown.data('open', false).removeAttr('data-previous-container');
		});
	};
	/**
	 * $ shortcut to apply disabled mode.
	 */
	$.fn.disable = function () {
		var yes = (arguments.length === 0 || arguments[0]) && 'disabled';
		$(this)[yes ? 'attr' : 'removeAttr']('disabled', 'disabled').prop('disabled', yes)[yes ? 'addClass' : 'removeClass']('disabled');
		return this;
	};
	/**
	 * $ shortcut to apply enabled mode.
	 */
	$.fn.enable = function () {
		$(this).disable(!(arguments.length === 0 || arguments[0]));
		return this;
	};
	$.fn.showGroup = function () {
		$(this).closest('.form-group').removeClass('hidden');
		return this;
	};
	$.fn.rawText = function (rawText) {
		// Get text mode
		if (typeof rawText === 'undefined') {
			var text = this.first().contents().filter(function () {
				return this.nodeType === 3;
			});
			return text.length ? text[0].textContent || '' : '';
		}
		// Set text
		this.each(function () {
			var text = $(this).contents().filter(function () {
				return this.nodeType === 3;
			});
			if (rawText && text.length === 0) {
				// Create a new text node at the end
				$(this).append(document.createTextNode(rawText));
			} else {
				// Replace the content of existing node
				text.each(function () {
					this.textContent = rawText;
				});
			}
		});
		return this;
	};

	function selectMenu() {
		var selector = $(this).closest('li');
		// Check there is not yet selected child
		if (selector.parents('.navbar,.nav-pills.nav-stacked').find('li.active').length === 0) {
			selector.addClass('active');
			$.fn.collapse && selector.parents('.navbar').length === 0 && selector.parents('.collapse').collapse('show');
			selector.parents('.dropdown').addClass('active');
		}
	}

	/**
	 * Synchronize the menu with the URL of the context url
	 * @param  {string} url Current url to synchronize with menus.
	 */
	function synchronizeMenu(url) {
		var patterns = [];
		var fragments = url.split('/');
		do {
			patterns.push('a[href^="' + fragments.join('/').replace(/"/g, '\\"') + '"]');
			fragments.pop();
		} while (fragments.length);
		var base = _('_main').find('.navbar,.nav-pills.nav-stacked');

		// Remove all highlights
		base.find('li.active').removeClass('active');

		// Highlight tool bar menu
		for (var index = 0; index < patterns.length; index++) {
			base.find(patterns[index]).each(selectMenu);
		}
	}

	// Transformation
	$cascade.register('html', function (selector) {
		// Add popover feature
		selector.find('.tab-pane.active[data-ajax]').each($cascade.loadPartial);
	});
	$cascade.register('hash', function (url) {
		$('.modal-backdrop').remove();
		$('.tooltip').remove();
		$('.popover').remove();
		$('.dropdown-menu.detached').closeDropdown();
		synchronizeMenu(url);
	});

	/**
	 * Full screen management : enter full screen
	 * @param {Object} $selector Optional selector.
	 */
	$.fn.fullscreen = function() {
		var el = $(this)[0] || document.documentElement;

		// use necessary prefixed versions
		if (el.webkitRequestFullscreen) {
			el.webkitRequestFullscreen();
			return;
		}
		if (el.mozRequestFullScreen) {
			el.mozRequestFullScreen();
			return;
		}
		if (el.msRequestFullscreen) {
			el.msRequestFullscreen();
			return;
		}
		// finally the standard version
		el.requestFullscreen && el.requestFullscreen();
	};

	/**
	 * Exit full screen management
	 */
	$.fn.exitFullscreen = function() {
		// use necessary prefixed versions
		if (document.webkitExitFullscreen) {
			document.webkitCancelFullScreen();
			return;
		}
		if (document.mozCancelFullScreen) {
			document.mozCancelFullScreen();
			return;
		}
		if (document.msExitFullscreen) {
			document.msExitFullscreen();
			return;
		}
		// finally the standard version
		document.exitFullscreen && document.exitFullscreen();
	};

});
<commit_msg>Remove tooltip animation causing overlaps<commit_after>define([
	'jquery', 'cascade', 'bootstrap'
], function ($, $cascade) {
	// Global handler for better bootstrap implicit experience
	$('body').popover({selector: '[data-toggle="popover"]:not([data-trigger="manual"])', html: 'true', trigger: 'hover focus', container: 'body'});
	$('body').tooltip({selector: '[data-toggle="tooltip"]:not([data-trigger="manual"])', html: 'true', trigger: 'hover focus', container: 'body', animation: false});

	var closeTooltips = function() {
		$('[data-toggle="tooltip"][aria-describedby]').each(function() {
			$(this).tooltip('hide');
		});
		// Destroy orphan previous tooltips
		$('.tooltip.in').empty().remove();
	};
	$(document).on('click', '.toggle-visibility', function () {
		$(this).toggleClass('active');
	}).on('click', '.disabled,[disabled]', function (event) {
		event.preventDefault();
	}).on('click', '[data-toggle="popover"][data-trigger="manual"]', function () {
		$(this).popover('toggle');
	}).on('mousedown', function (e) {
		// Auto hide popover on click
		if ($('.popover.in').length && $(e.target).closest('[data-toggle="popover"],.popover').length === 0) {
			$('.popover.in').each(function () {
				$('[aria-describedby="' + $(this).attr('id') + '"]').popover('hide');
			});
		}
	}).on('show.bs.tooltip', null, function() {
		// Close nicely previous tooltips when a new one is displayed
		closeTooltips();
	}).on('show.bs.dropdown', null, function(e) {
		// Close previous dropdowns
		$('.dropdown-menu.detached').closeDropdown();

		// Handle the dropdown inside an absolute/overflow-hidden container, move the dropdown to body
		var $trigger = $(e.relatedTarget);
		if (!$trigger.hasClass('detached') && $trigger.closest('.dropdown-overflow').length && $trigger.closest('.dropdown').length) {
			var flag = 'dropdown-' + Math.random();
			var $dropdown = $trigger.closest('.dropdown');
			var $menu = $dropdown.find('.dropdown-menu').addClass('detached').attr('data-previous-container',flag);
			$trigger.addClass('detached').attr('data-dropdown-container',flag);
			$('body').append($menu.css({
				position: 'absolute',
				display: 'initial',
				left: $menu.offset().left + 'px',
				width : $menu.width() + 'px',
				top: ($menu.offset().top + $(e.target).outerHeight()) + 'px'
			}).data('open', true).detach());
		}
	}).on('hidden.bs.dropdown', null, function(e) {
		var $trigger = $(e.relatedTarget);
		if ($trigger.hasClass('detached')) {
			$('[data-previous-container="' + $trigger.attr('data-dropdown-container') + '"]').closeDropdown();
		}
	}).on('change', '.btn-file :file', function () {
		var $input = $(this);
		var label = $input.val().replace(/\\/g, '/').replace(/.*\//, '');
		$input.parents('.input-group').find(':text').val(label);
		// FIX : #19192 ->
	}).off('click.bs.button.data-api', '[data-toggle^="button"]').on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) {
		var $btn = $(e.target).closest('.btn').button('toggle');
		if (!($(e.target).is('input[type="radio"]') || $(e.target).is('input[type="checkbox"]'))) {
			// Prevent double click on radios, and the double selections (so cancellation) on checkboxes
			e.preventDefault();
			// The target component still receive the focus
			if ($btn.is('input,button')) {
				$btn.trigger('focus');
			} else {
				$btn.find('input:visible,button:visible').first().trigger('focus');
			}
		}
	}).on('click', '.retract', function () {
		$(this).closest('.retractable').toggleClass('retracted');
	}).on('keypress', 'form:not([method]),[method="get"],form[method="GET"]', function (e) {
		// Some browser (like Chrome) the first submit is used (hidden or not) and receive a click
		if ((e.which === 13 || e.keyCode === 13) && !$(e.target).is('textarea')) {
			// Determines the right input
			var $button = $(this).find('input:visible[type=submit]:not(.disabled):not(.hide):not(.hidden):not([disabled]),button:visible[type=submit]:not(.disabled):not(.hide):not(.hidden):not([disabled])').first();
			if ($button.length) {
				$button.trigger('click');
				e.preventDefault();
				return false;
			}
		}
	}).on('submit', 'form:not([method]),[method="get"],form[method="GET"]', function (e) {
		e.preventDefault();
	}).on('show.bs.tab', 'a[data-toggle="tab"]', function () {
		// Load active partials for tab
		var $target = $($(this).attr('href'));
		if ($target.is('[data-ajax]')) {
			$.proxy($cascade.loadPartial, $target)();
		}
	})
	// Load active partials for collapse
	.on('show.bs.collapse', '.collapse[data-ajax]', $cascade.loadPartial)
	// Load active partials for popover
	.on('show.bs.popover', '[data-ajax]', $cascade.loadPartial)
	// Load active partials for modal
	.on('click.bs.modal.data-api.ajax', '[data-toggle="modal-ajax"][data-ajax]:not([cascade-loaded])', function (e) {
		var $link = $(this).attr('cascade-loaded','true');
		$.proxy($cascade.loadPartial, $link)(function() {
			$link.attr('data-toggle', 'modal').trigger('click.bs.modal.data-api');
		});
		e.preventDefault();
	}).on('show.bs.modal', '.modal', closeTooltips)
	.on('hide.bs.modal', '.modal', closeTooltips)
	.on('show.bs.modal', '.modal[data-ajax]', $cascade.loadPartial);

	$.fn.hideGroup = function () {
		$(this).closest('.form-group').addClass('hidden');
		return this;
	};
	$.fn.closeDropdown = function () {
		this.each(function() {
			var $dropdown = $(this);
			var flag = $dropdown.attr('data-previous-container') || $dropdown.attr('data-dropdown-container');
			var $trigger = $();
			var $container = $();
			if (flag) {
				$trigger = $('[data-dropdown-container="' + flag + '"]');
				$dropdown = $('[data-previous-container="' + flag + '"]');
				$container = $trigger.parent();
			} else if ($dropdown.is('.dropdown-menu')) {
				$container = $dropdown.parent();
				$trigger = $container.find('[data-toggle="dropdown"]');
			} else if ($dropdown.is('.dropdown')) {
				$container = $dropdown;
				$trigger = $container.find('[data-toggle="dropdown"]');
				$dropdown = $container.find('.dropdown-menu');
			} else if ($dropdown.is('[data-toggle="dropdown"]')) {
				$container = $dropdown.parent();
				$trigger = $dropdown;
				$dropdown = $container.find('.dropdown-menu');
			}
			if ($trigger.hasClass('detached')) {
				$container.removeClass('open').append($dropdown.removeClass('detached').css({
					position: '',
					left: '',
					width: '',
					display: '',
					top: ''
				}).detach());
				$trigger.removeClass('detached');
			}
			$container.removeAttr('data-open');
			$trigger.removeAttr('data-dropdown-container');
			$dropdown.data('open', false).removeAttr('data-previous-container');
		});
	};
	/**
	 * $ shortcut to apply disabled mode.
	 */
	$.fn.disable = function () {
		var yes = (arguments.length === 0 || arguments[0]) && 'disabled';
		$(this)[yes ? 'attr' : 'removeAttr']('disabled', 'disabled').prop('disabled', yes)[yes ? 'addClass' : 'removeClass']('disabled');
		return this;
	};
	/**
	 * $ shortcut to apply enabled mode.
	 */
	$.fn.enable = function () {
		$(this).disable(!(arguments.length === 0 || arguments[0]));
		return this;
	};
	$.fn.showGroup = function () {
		$(this).closest('.form-group').removeClass('hidden');
		return this;
	};
	$.fn.rawText = function (rawText) {
		// Get text mode
		if (typeof rawText === 'undefined') {
			var text = this.first().contents().filter(function () {
				return this.nodeType === 3;
			});
			return text.length ? text[0].textContent || '' : '';
		}
		// Set text
		this.each(function () {
			var text = $(this).contents().filter(function () {
				return this.nodeType === 3;
			});
			if (rawText && text.length === 0) {
				// Create a new text node at the end
				$(this).append(document.createTextNode(rawText));
			} else {
				// Replace the content of existing node
				text.each(function () {
					this.textContent = rawText;
				});
			}
		});
		return this;
	};

	function selectMenu() {
		var selector = $(this).closest('li');
		// Check there is not yet selected child
		if (selector.parents('.navbar,.nav-pills.nav-stacked').find('li.active').length === 0) {
			selector.addClass('active');
			$.fn.collapse && selector.parents('.navbar').length === 0 && selector.parents('.collapse').collapse('show');
			selector.parents('.dropdown').addClass('active');
		}
	}

	/**
	 * Synchronize the menu with the URL of the context url
	 * @param  {string} url Current url to synchronize with menus.
	 */
	function synchronizeMenu(url) {
		var patterns = [];
		var fragments = url.split('/');
		do {
			patterns.push('a[href^="' + fragments.join('/').replace(/"/g, '\\"') + '"]');
			fragments.pop();
		} while (fragments.length);
		var base = _('_main').find('.navbar,.nav-pills.nav-stacked');

		// Remove all highlights
		base.find('li.active').removeClass('active');

		// Highlight tool bar menu
		for (var index = 0; index < patterns.length; index++) {
			base.find(patterns[index]).each(selectMenu);
		}
	}

	// Transformation
	$cascade.register('html', function (selector) {
		// Add popover feature
		selector.find('.tab-pane.active[data-ajax]').each($cascade.loadPartial);
	});
	$cascade.register('hash', function (url) {
		$('.modal-backdrop').remove();
		$('.tooltip').remove();
		$('.popover').remove();
		$('.dropdown-menu.detached').closeDropdown();
		synchronizeMenu(url);
	});

	/**
	 * Full screen management : enter full screen
	 * @param {Object} $selector Optional selector.
	 */
	$.fn.fullscreen = function() {
		var el = $(this)[0] || document.documentElement;

		// use necessary prefixed versions
		if (el.webkitRequestFullscreen) {
			el.webkitRequestFullscreen();
			return;
		}
		if (el.mozRequestFullScreen) {
			el.mozRequestFullScreen();
			return;
		}
		if (el.msRequestFullscreen) {
			el.msRequestFullscreen();
			return;
		}
		// finally the standard version
		el.requestFullscreen && el.requestFullscreen();
	};

	/**
	 * Exit full screen management
	 */
	$.fn.exitFullscreen = function() {
		// use necessary prefixed versions
		if (document.webkitExitFullscreen) {
			document.webkitCancelFullScreen();
			return;
		}
		if (document.mozCancelFullScreen) {
			document.mozCancelFullScreen();
			return;
		}
		if (document.msExitFullscreen) {
			document.msExitFullscreen();
			return;
		}
		// finally the standard version
		document.exitFullscreen && document.exitFullscreen();
	};

});
<|endoftext|>


<commit_before>$(document).on('turbolinks:load', function() {
  var $input = $('input#gobierto_search');
  var $resultsContainer = $('#search_results');

  function truncateOnWord(str, limit) {
    var trimmable = '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u2028\u2029\u3000\uFEFF';
    var reg = new RegExp('(?=[' + trimmable + '])');
    var words = str.split(reg);
    var count = 0;
    return words.filter(function(word) {
      count += word.length;
      return count <= limit;
    }).join('');
  }

  function itemUpdatedAt(d){
    if(d['updated_at'] === undefined)
      return;

    return I18n.t("layouts.search.updated") + ": " + I18n.l("date.formats.short", d['updated_at']);
  }

  function itemDescription(d){
    var maxLenght = 200;
    var description = (d['bio'] || d['description'] || d['body'] || d['bio_' + I18n.locale] || d['description_' + I18n.locale] || d['body_' + I18n.locale] || '');
    if(description === '' || description.length < maxLenght)
      return description;
    return truncateOnWord(description, maxLenght) + '...';
  }

  function itemType(d){
    if(d['class_name'] === undefined)
      return;

    switch(d['class_name']){
      case 'GobiertoPeople::Person':
        return I18n.t("layouts.search.person_item");
      case 'GobiertoPeople::PersonPost':
        return I18n.t("layouts.search.person_post_item");
      case 'GobiertoPeople::PersonEvent':
        return I18n.t("layouts.search.person_event_item");
      case 'GobiertoPeople::PersonStatement':
        return I18n.t("layouts.search.person_statement_item");
      case 'GobiertoCms::Page':
        return I18n.t("layouts.search.page_item");
    }
  }

  function searchCallback(err, content) {
    if (err) {
      console.error(err);
      return;
    }

    $resultsContainer.html('');
    content.results.forEach(function(indexResults){
      indexResults.hits.forEach(function(d){
        var result = '
' +
					'
' + (d['title'] || d['name'] || d['title_' + I18n.locale] || d['name_' + I18n.locale]) + '
' +
					'
' +
            '
' + itemDescription(d) + '
' +
						'' + itemType(d) + ' · ' +
						'' + itemUpdatedAt(d) + '' +
					'
' +
				'
';

        var div = $(result);
        div.appendTo($resultsContainer);
      });
    });

    $('
'+I18n.t("layouts.search.powered_by")+'
').appendTo($resultsContainer);
  }

  $input.on('keyup', function(e){
    var q = $(this).val();
    var queries = [];
    window.searchClient.indexes.forEach(function(index){
      queries.push({
        indexName: index,
        query: q,
        params: {
          hitsPerPage: 10,
          filters: window.searchClient.filters
          }
      });
    });

    if(q.length > 2){
      window.searchClient.client.search(queries, searchCallback);
    }
  });

  $input.focusin(function() {
    $resultsContainer.show();
  });

  // Hide resultsContainer if clicked outside the search input and results
  $('body').click(function(e) {
    if ( !$(e.target).is('#search_results *') && !$(e.target).is('input#gobierto_search') ) {
      $resultsContainer.hide();
    }
  });
});
<commit_msg>Improved small details:<commit_after>$(document).on('turbolinks:load', function() {
  var $input = $('input#gobierto_search');
  var $resultsContainer = $('#search_results');

  function truncateOnWord(str, limit) {
    var trimmable = '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u2028\u2029\u3000\uFEFF';
    var reg = new RegExp('(?=[' + trimmable + '])');
    var words = str.split(reg);
    var count = 0;
    return words.filter(function(word) {
      count += word.length;
      return count <= limit;
    }).join('');
  }

  function itemUpdatedAt(d){
    if(d['updated_at'] === undefined)
      return;

    return I18n.t("layouts.search.updated") + ": " + I18n.l("date.formats.short", d['updated_at']);
  }

  function itemDescription(d){
    var maxLenght = 200;
    var description = (d['bio'] || d['description'] || d['body'] || d['bio_' + I18n.locale] || d['description_' + I18n.locale] || d['body_' + I18n.locale] || '');
    if(description === '' || description.length < maxLenght)
      return description;
    return truncateOnWord(description, maxLenght) + '...';
  }

  function itemType(d){
    if(d['class_name'] === undefined)
      return;

    switch(d['class_name']){
      case 'GobiertoPeople::Person':
        return I18n.t("layouts.search.person_item");
      case 'GobiertoPeople::PersonPost':
        return I18n.t("layouts.search.person_post_item");
      case 'GobiertoPeople::PersonEvent':
        return I18n.t("layouts.search.person_event_item");
      case 'GobiertoPeople::PersonStatement':
        return I18n.t("layouts.search.person_statement_item");
      case 'GobiertoCms::Page':
        return I18n.t("layouts.search.page_item");
    }
  }

  function searchCallback(err, content) {
    if (err) {
      console.error(err);
      return;
    }

    $resultsContainer.html('');
    var sum = 0;
    content.results.forEach(function(indexResults){
      sum += indexResults.nbHits;
    });
    if(sum > 0) {
      content.results.forEach(function(indexResults){
        indexResults.hits.forEach(function(d){
          var result = '
' +
            '
' + (d['title'] || d['name'] || d['title_' + I18n.locale] || d['name_' + I18n.locale]) + '
' +
            '
' +
              '
' + itemDescription(d) + '
' +
              '' + itemType(d) + ' · ' +
              '' + itemUpdatedAt(d) + '' +
            '
' +
          '
';

          var div = $(result);
          div.appendTo($resultsContainer);
        });
      });
    } else {
      $('

No hay resultados

').appendTo($resultsContainer);
    }

    $('
'+I18n.t("layouts.search.powered_by")+'
').appendTo($resultsContainer);
  }

  $input.on('keyup', function(e){
    var q = $(this).val();
    var queries = [];
    window.searchClient.indexes.forEach(function(index){
      queries.push({
        indexName: index,
        query: q,
        params: {
          hitsPerPage: 10,
          filters: window.searchClient.filters
          }
      });
    });

    if(q.length > 2){
      window.searchClient.client.search(queries, searchCallback);
    } else {
      $resultsContainer.html('');
    }
  });

  $input.focusin(function() {
    $resultsContainer.show();
  });

  // Hide resultsContainer if clicked outside the search input and results
  $('body').click(function(e) {
    if ( !$(e.target).is('#search_results *') && !$(e.target).is('input#gobierto_search') ) {
      $resultsContainer.hide();
    }
  });
});
<|endoftext|>


<commit_before>import seedrandom from "seedrandom";

const isType = (type) => {
    return (arg) => Object.prototype.toString.call(arg) == "[object #{ type }]"
};

const isFunc = isType("Function");

const isObject = isType("Object");

const range = (start, end) => [for (i of Array(end - start).keys()) i + start];

const randomBoundedFloat = (prng, min = 0, max = 1) => {
    const spread = max - min;
    return prng() * spread + min;
};

const randomBoundedInteger = (prng, min = 0, max = 1) => {
    const spread = 1 + max - min;
    return Math.floor(prng() * spread) + min;
};

const randomColor = (prng) => {
    const byte = {kind: "integer", min: 0, max: 255, prng};
    const mutator = (bytes) => {
        const [red, green, blue] = bytes;
        return { red, green, blue };
    };

    return new Stochator(byte, byte, byte, mutator).next;
};

const randomNormallyDistributedFloat = (prng, mean, stdev, min, max) => {
    const seed = randomBoundedFloat(prng);
    const float = inverseNormalCumulativeDistribution(seed) * stdev + mean;
    return min != null && max != null ?
        Math.min(max, Math.max(min, float)) : float;
};

const randomCharacter = (prng, lowercase) => {
    const [min, max] = lowercase ? [97, 122] : [65, 90];
    const mutator = (charCode) => String.fromCharCode(charCode);
    return new Stochator({ kind: "integer", min, max, prng }, mutator).next;
};

const randomSetMember = (prng, set) => {
    const max = set.length - 1;
    return set.get(randomBoundedInteger(prng, 0, max));
};

const randomSetMemberWithoutReplacement = (prng, set) => {
    if (set.get(0)) {
        set.length -= 1;
        return set.pop(randomBoundedInteger(prng, 0, set.length));
    }
};

const randomWeightedSetMember = (prng, set, weights) => {
    let [member, weightSum, float] = [undefined, 0, randomBoundedFloat(prng)];
    set.each((value, index) => {
        if (member) {
            return;
        }
        const weight = weights.get(index);
        if (float <= weightSum + weight && float >= weightSum) {
            member = value;
        }
        weightSum += weight;
    });

    return member;
};

const inverseNormalCumulativeDistribution = (probability) => {
    const high = probability > 0.97575;
    const low = probability < 0.02425;
    let numCoefficients, denomCoeffcients, numMaxExponent, denomMaxExponent, coefficient, base;

    if (low || high) {
        numCoefficients = new Set([
            -7.784894002430293e-3, -3.223964580411365e-1, -2.400758277161838,
            -2.549732539343734, 4.374664141464968
        ]);
        denomCoeffcients = new Set([
            7.784695709041462e-3, 3.224671290700398e-1,
            2.445134137142996, 3.754408661907416
        ]);

        [numMaxExponent, denomMaxExponent] = [5, 4];
        coefficient = low ? 1 : -1;
        base = Math.sqrt(
            -2 * Math.log(low ? probability : 1 - probability)
        );
    } else {
        numCoefficients = new Set([
            -3.969683028665376e1, 2.209460984245205e2, -2.759285104469687e2,
            1.383577518672690e2, -3.066479806614716e1, 2.506628277459239
        ]);
        denomCoeffcients = new Set([
            -5.447609879822406e1, 1.615858368580409e2, -1.556989798598866e2,
            6.680131188771972e1, -1.328068155288572e1
        ]);

        [numMaxExponent, denomMaxExponent] = [5, 5];
        coefficient = probability - 0.5;
        base = Math.pow(coefficient, 2);
    }

    const mapMaxExp = (maxExp) => {
        return (value, index) => value * Math.pow(base, maxExp - index);
    };

    const numerator = numCoefficients.map(mapMaxExp(numMaxExponent)).sum();
    const denominator = denomCoeffcients.map(mapMaxExp(denomMaxExponent)).sum() + 1;

    return coefficient * numerator / denominator;
};

const shuffleSet = (prng, set) => {
    let values = set.copy();
    for (index of range(0, values.length)) {
        randomIndex = randomBoundedInteger(prng, 0, index);

        tmp = values[index];
        values[index] = values[randomIndex];
        values[randomIndex] = tmp;
    }
    return values;
};

const floatGenerator = (prng, min, max, mean, stdev) => {
    if (mean && stdev) {
        return () => randomNormallyDistributedFloat(prng, mean, stdev, min, max);
    } else {
        return () => randomBoundedFloat(prng, min, max);
    }
};

const integerGenerator = (prng, min = 0, max = 1) => {
    return () => randomBoundedInteger(prng, min, max);
};

const setGenerator = (prng, values, replacement = true, shuffle = false, weights = null) => {
    if (!values || !values.length) {
        throw Error("Must provide a 'values' array for a set generator.")
    }

    const set = new Set(values);
    if (shuffle) {
        return () => shuffleSet(prng, set);
    } else if (replacement) {
        if (weights) {
            weightsSet = new Set(weights);
            return () => randomWeightedSetMember(prng, set, weightsSet);
        } else {
            return () => randomSetMember(prng, set);
        }
    } else {
        return () => randomSetMemberWithoutReplacement(prng, set);
    }
};

const createGenerator = (config) => {
    const kind = config.kind || "float";

    const defaultPrng = config.seed ? seedrandom : Math.random;
    const basePrng = config.prng || defaultPrng;
    const prng = config.seed ? basePrng(config.seed) : basePrng;

    let generator = null;
    switch (kind) {
        case "float":
            let { min, max, mean, stdev } = config;
            generator = floatGenerator(prng, min, max, mean, stdev);
        case "integer":
            generator = integerGenerator(prng, config.min, config.max);
        case "set":
            let { values, replacement, shuffle, weights } = config;
            generator = setGenerator(prng, values, replacement, shuffle, weights);
        case "color":
        case "rgb":
            generator = randomColor(prng);
        case "a-z":
        case "A-Z":
            generator = randomCharacter(prng, kind === "a-z")
        default:
            break;
    }

    if (!generator) {
        throw Error("#{ kind } not a recognized kind.");
    } else {
        return generator;
    }
};

const getNextValueGenerator = (configs) => {
    configs[0] = configs[0] ? configs[0] : {};
    const generators = [for (config of configs) createGenerator(config)];
    if (generators.length === 1) {
        return () => generators[0]();
    } else {
        return () => [for (generator of generators) generator()];
    }
};

export default class Stochator {

    VERSION = "0.4"

    constructor(...args) {
        let configs = args.slice(0, -2);
        let [mutator, name] = args.slice(-2);
        // If the last arg is an object, all args are config args.
        // If the penultimate arg is an object, check whether the last arg
        // is a string (hence, the name) || a function (hence, the mutator).
        if (isObject(name)) {
            configs = args;
            [mutator, name] = [null, "next"];
        } else if (isObject(mutator)) {
            configs = args.slice(0, -1);
            [mutator, name] = isFunc(name) ? [name, "next"] : [null, name];
        }

        // If the mutator is provided, override the default identity func.
        if (mutator) {
            this.mutate = (nextValue) => mutator(nextValue, this.getValue());
        }

        // Transform the configs to a func to get the next value.
        const getNext = getNextValueGenerator(configs);

        // Assign `name` to the next mutated value(s), after `times` iterations.
        // If `times` is 1, just return the value, otherwise return an array.
        this[name] = (times=1) => {
            const values = [
                for (time of range(1, times + 1))
                this.setValue(this.mutate(getNext()))
            ];
            return times == 1 ? values[0] : values;
        }
    }

    getValue() {
        return this._value;
    }

    mutate(value) {
        return value;
    }

    setValue(value) {
        this._value = value;
        return this._value;
    }

    toString() {
        return "[object Stochator]";
    }

    _value = 0
}
<commit_msg>Use plain-old JS arrays instead of home-rolled Set class.<commit_after>import seedrandom from "seedrandom";

const isType = (type) => {
    return (arg) => Object.prototype.toString.call(arg) == "[object #{ type }]"
};

const isFunc = isType("Function");

const isObject = isType("Object");

const range = (start, end) => [for (i of Array(end - start).keys()) i + start];

const randomBoundedFloat = (prng, min = 0, max = 1) => {
    const spread = max - min;
    return prng() * spread + min;
};

const randomBoundedInteger = (prng, min = 0, max = 1) => {
    const spread = 1 + max - min;
    return Math.floor(prng() * spread) + min;
};

const randomColor = (prng) => {
    const byte = {kind: "integer", min: 0, max: 255, prng};
    const mutator = (bytes) => {
        const [red, green, blue] = bytes;
        return { red, green, blue };
    };

    return new Stochator(byte, byte, byte, mutator).next;
};

const randomNormallyDistributedFloat = (prng, mean, stdev, min, max) => {
    const seed = randomBoundedFloat(prng);
    const float = inverseNormalCumulativeDistribution(seed) * stdev + mean;
    return min != null && max != null ?
        Math.min(max, Math.max(min, float)) : float;
};

const randomCharacter = (prng, lowercase) => {
    const [min, max] = lowercase ? [97, 122] : [65, 90];
    const mutator = (charCode) => String.fromCharCode(charCode);
    return new Stochator({ kind: "integer", min, max, prng }, mutator).next;
};

const randomSetMember = (prng, values) => {
    const max = values.length - 1;
    return values[randomBoundedInteger(prng, 0, max)];
};

const randomSetMemberWithoutReplacement = (prng, values) => {
    if (values.length > 0) {
        const index = randomBoundedInteger(prng, 0, values.length - 1);
        const value = values[index];
        values.splice(index, 1);
        return value;
    }
};

const randomWeightedSetMember = (prng, values, weights) => {
    let [member, weightSum, float] = [undefined, 0, randomBoundedFloat(prng)];
    values.forEach((value, index) => {
        if (member) {
            return;
        }
        const weight = weights[index];
        if (float <= weightSum + weight && float >= weightSum) {
            member = value;
        }
        weightSum += weight;
    });

    return member;
};

const inverseNormalCumulativeDistribution = (probability) => {
    const high = probability > 0.97575;
    const low = probability < 0.02425;
    let numCoefficients, denomCoeffcients, numMaxExponent, denomMaxExponent, coefficient, base;

    if (low || high) {
        numCoefficients = [
            -7.784894002430293e-3, -3.223964580411365e-1, -2.400758277161838,
            -2.549732539343734, 4.374664141464968
        ];
        denomCoeffcients = [
            7.784695709041462e-3, 3.224671290700398e-1,
            2.445134137142996, 3.754408661907416
        ];

        [numMaxExponent, denomMaxExponent] = [5, 4];
        coefficient = low ? 1 : -1;
        base = Math.sqrt(
            -2 * Math.log(low ? probability : 1 - probability)
        );
    } else {
        numCoefficients = [
            -3.969683028665376e1, 2.209460984245205e2, -2.759285104469687e2,
            1.383577518672690e2, -3.066479806614716e1, 2.506628277459239
        ];
        denomCoeffcients = [
            -5.447609879822406e1, 1.615858368580409e2, -1.556989798598866e2,
            6.680131188771972e1, -1.328068155288572e1
        ];

        [numMaxExponent, denomMaxExponent] = [5, 5];
        coefficient = probability - 0.5;
        base = Math.pow(coefficient, 2);
    }

    const mapMaxExp = (maxExp) => {
        return (value, index) => value * Math.pow(base, maxExp - index);
    };

    const sum = (arr) => arr.reduce((result, value) => result + value, 0);

    const numerator = sum(numCoefficients.map(mapMaxExp(numMaxExponent)));
    const denominator = sum(denomCoeffcients.map(mapMaxExp(denomMaxExponent))) + 1;

    return coefficient * numerator / denominator;
};

const shuffleSet = (prng, values) => {
    let valuesRef = [...values];
    for (index of range(0, valuesRef.length)) {
        randomIndex = randomBoundedInteger(prng, 0, index);

        tmp = valuesRef[index];
        valuesRef[index] = valuesRef[randomIndex];
        valuesRef[randomIndex] = tmp;
    }
    return valuesRef;
};

const floatGenerator = (prng, min, max, mean, stdev) => {
    if (mean && stdev) {
        return () => randomNormallyDistributedFloat(prng, mean, stdev, min, max);
    } else {
        return () => randomBoundedFloat(prng, min, max);
    }
};

const integerGenerator = (prng, min = 0, max = 1) => {
    return () => randomBoundedInteger(prng, min, max);
};

const setGenerator = (prng, values, replacement = true, shuffle = false, weights = null) => {
    if (!values || !values.length) {
        throw Error("Must provide a 'values' array for a set generator.")
    }

    if (shuffle) {
        return () => shuffleSet(prng, values);
    } else if (replacement) {
        if (weights) {
            return () => randomWeightedSetMember(prng, values, weights);
        } else {
            return () => randomSetMember(prng, values);
        }
    } else {
        return () => randomSetMemberWithoutReplacement(prng, values);
    }
};

const createGenerator = (config) => {
    const kind = config.kind || "float";

    const defaultPrng = config.seed ? seedrandom : Math.random;
    const basePrng = config.prng || defaultPrng;
    const prng = config.seed ? basePrng(config.seed) : basePrng;

    let generator = null;
    switch (kind) {
        case "float":
            let { min, max, mean, stdev } = config;
            generator = floatGenerator(prng, min, max, mean, stdev);
        case "integer":
            generator = integerGenerator(prng, config.min, config.max);
        case "set":
            let { values, replacement, shuffle, weights } = config;
            generator = setGenerator(prng, values, replacement, shuffle, weights);
        case "color":
        case "rgb":
            generator = randomColor(prng);
        case "a-z":
        case "A-Z":
            generator = randomCharacter(prng, kind === "a-z")
        default:
            break;
    }

    if (!generator) {
        throw Error("#{ kind } not a recognized kind.");
    } else {
        return generator;
    }
};

const getNextValueGenerator = (configs) => {
    configs[0] = configs[0] ? configs[0] : {};
    const generators = [for (config of configs) createGenerator(config)];
    if (generators.length === 1) {
        return () => generators[0]();
    } else {
        return () => [for (generator of generators) generator()];
    }
};

export default class Stochator {

    VERSION = "0.4"

    constructor(...args) {
        let configs = args.slice(0, -2);
        let [mutator, name] = args.slice(-2);
        // If the last arg is an object, all args are config args.
        // If the penultimate arg is an object, check whether the last arg
        // is a string (hence, the name) || a function (hence, the mutator).
        if (isObject(name)) {
            configs = args;
            [mutator, name] = [null, "next"];
        } else if (isObject(mutator)) {
            configs = args.slice(0, -1);
            [mutator, name] = isFunc(name) ? [name, "next"] : [null, name];
        }

        // If the mutator is provided, override the default identity func.
        if (mutator) {
            this.mutate = (nextValue) => mutator(nextValue, this.getValue());
        }

        // Transform the configs to a func to get the next value.
        const getNext = getNextValueGenerator(configs);

        // Assign `name` to the next mutated value(s), after `times` iterations.
        // If `times` is 1, just return the value, otherwise return an array.
        this[name] = (times=1) => {
            const values = [
                for (time of range(1, times + 1))
                this.setValue(this.mutate(getNext()))
            ];
            return times == 1 ? values[0] : values;
        }
    }

    getValue() {
        return this._value;
    }

    mutate(value) {
        return value;
    }

    setValue(value) {
        this._value = value;
        return this._value;
    }

    toString() {
        return "[object Stochator]";
    }

    _value = 0
}
<|endoftext|>


<commit_before>'use strict'
/*
 * App config for production.
 */

module.exports = {
  environment: 'production',
  APIBaseURL: 'https://TBD',
  dataHubURL: 'https://TBD',
  dataHubOrganization: 'offgrid-market-opportunities'
}
<commit_msg>Add production urls (staging urls)<commit_after>'use strict'
/*
 * App config for production.
 */

module.exports = {
  environment: 'production',
  APIBaseURL: 'https://ifc.ds.io/',
  dataHubURL: 'https://ifc.ds.io/',
  dataHubOrganization: 'offgrid-market-opportunities'
}
<|endoftext|>


<commit_before>'use strict';

var React = require('react-native');
var {
  AppRegistry,
  View,
} = React;

var iTunesBrowser = React.createClass({
  render: function() {
    return (
      <View>
      </View>
    );
  }
});

AppRegistry.registerComponent('iTunesBrowser', () => iTunesBrowser);
<commit_msg>Add toolbar and dummy content<commit_after>'use strict';

var React = require('react-native');
var {
  AppRegistry,
  View,
  Text,
  StyleSheet,
  StatusBarIOS,
} = React;

StatusBarIOS.setStyle('light-content');

var iTunesBrowser = React.createClass({
  render: function() {
    return (
      <View style={styles.mainContainer}>
        <View style={styles.navbar}>
          <View style={styles.navbarButton}></View>
          <Text style={styles.navbarTitle}>iTunesBrowser</Text>
          <Text style={styles.navbarButton}>Search</Text>
        </View>
        <View style={styles.content}>
          <Text>
            Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
          </Text>
        </View>
      </View>
    );
  }
});

var styles = StyleSheet.create({
  mainContainer: {
    flex: 1,
  },
  content: {
    flex: 1,
    backgroundColor: '#CCC',
  },
  navbar: {
    backgroundColor: '#2A3744',
    paddingTop: 30,
    paddingBottom: 10,
    flexDirection: 'row'
  },
  navbarTitle: {
    color: '#FEFEFE',
    textAlign: 'center',
    fontWeight: 'bold',
    flex: 1
  },
  navbarButton: {
    width: 50,
    color: '#FEFEFE',
    textAlign: 'center'
  }
});

AppRegistry.registerComponent('iTunesBrowser', () => iTunesBrowser);
<|endoftext|>


<commit_before>(function($) {
  $(function() {
    $(".js-edition-select-filter select").change(function() {
      $(this).parent().submit();
    });
    $(".js-btn-enter").each(function() {
      var btn = $(this);
      btn.siblings('input').on('change paste keydown', function() {
        btn.show();
      });
    })
  })
})(jQuery);
<commit_msg>Add some missing semicolons.<commit_after>(function($) {
  $(function() {
    $(".js-edition-select-filter select").change(function() {
      $(this).parent().submit();
    });
    $(".js-btn-enter").each(function() {
      var btn = $(this);
      btn.siblings('input').on('change paste keydown', function() {
        btn.show();
      });
    });
  });
})(jQuery);
<|endoftext|>


<commit_before>/*
 * Copyright 2016 Teppo Kurki <teppo.kurki@iki.fi>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const Transform = require('stream').Transform

const SignalK = require('@signalk/client')

const debug = require('debug')('signalk-server:providers:mdns-ws')

const WebSocket = require('ws')

function MdnsWs (options) {
  Transform.call(this, {
    objectMode: true
  })
  this.options = options
  this.selfHost = options.app.config.getExternalHostname() + '.'
  this.selfPort = options.app.config.getExternalPort()
  this.remoteServers = {}
  this.remoteServers[this.selfHost + ':' + this.selfPort] = {}
  if (options.ignoreServers) {
    options.ignoreServers.forEach(s => {
      this.remoteServers[s] = {}
    })
  }
  if (options.host && options.port) {
    this.signalkClient = new SignalK.Client({
      hostname: options.host,
      port: options.port,
      useTLS: options.type === 'wss',
      reconnect: true,
      autoConnect: false,
    })
    this.connect(this.signalkClient)
  } else {
    try {
      let mdns

      try {
        mdns = require('mdns')
      } catch ( ex ) {
        this.options.app.setProviderError(this.options.providerId, 'mdns module not installed')
        console.error(ex)
        return
      }

      const discovery = new SignalK.Discovery(mdns, 60000)

      debug('Starting discovery')

      discovery.on('timeout', () => console.log('No SK servers found'))

      discovery.on('found', server => {
        if (server.isMain() && server.isMaster()) {
          if ( !this.remoteServers[server.hostname + ':' + server.port] ) {
            client = server.createClient({
              useTLS: false,
              reconnect: true,
              autoConnect: false
            })
            this.connect(client)
          }
        }
      })
    } catch (ex) {
      this.options.app.setProviderError(this.options.providerId, ex.message)
      console.error(ex)
      return
    }
  }
}

require('util').inherits(MdnsWs, Transform)

function setProviderStatus (that, providerId, message, isError) {
  if (!isError) {
    that.options.app.setProviderStatus(providerId, message)
    console.log(message)
  } else {
    that.options.app.setProviderError(providerId, message)
    console.error(message)
  }
}

MdnsWs.prototype.connect = function (client) {
  const that = this
  const providerId = `${that.options.providerId}.${client.options.hostname}:${client.options.port}`

  client
    .connect()
    .then(() => {
      setProviderStatus(that, providerId, `ws connection connected to ${client.options.hostname}:${client.options.port}`)
      that.remoteServers[client.options.hostname + ':' + client.options.port] = client
      if ( that.options.subscription ) {
        let parsed
        try {
          parsed = JSON.parse(that.options.subscription)
        } catch ( ex ) {
          setProviderStatus(that, providerId, `unable to parse subscription json: ${that.options.subscription}: ${ex.message}`, true)
        }
        if ( !Array.isArray(parsed) ) {
          parsed = [ parsed ]
        }
        parsed.forEach((sub, idx) => {
          debug('sending subscription %j', sub)
          client.subscribe(sub, String(idx))
        })
      } else {
        debug('subscribing to all')
        client.subscribe()
      }
    })
    .catch(err => {
      setProviderStatus(that, providerId, err.message, true)
    })

  client.on('delta', (data) => {
    if (data && data.updates) {
      data.updates.forEach(function (update) {
        update['$source'] = providerId
      })
    }

    that.push(data)
  })
}

MdnsWs.prototype._transform = function (chunk, encoding, done) {}

module.exports = MdnsWs
<commit_msg>fix: use correct protocol with websocket (#968)<commit_after>/*
 * Copyright 2016 Teppo Kurki <teppo.kurki@iki.fi>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const Transform = require('stream').Transform

const SignalK = require('@signalk/client')

const debug = require('debug')('signalk-server:providers:mdns-ws')

const WebSocket = require('ws')

function MdnsWs (options) {
  Transform.call(this, {
    objectMode: true
  })
  this.options = options
  this.selfHost = options.app.config.getExternalHostname() + '.'
  this.selfPort = options.app.config.getExternalPort()
  this.remoteServers = {}
  this.remoteServers[this.selfHost + ':' + this.selfPort] = {}
  if (options.ignoreServers) {
    options.ignoreServers.forEach(s => {
      this.remoteServers[s] = {}
    })
  }
  if (options.host && options.port) {
    this.signalkClient = new SignalK.Client({
      hostname: options.host,
      port: options.port,
      useTLS: options.protocol === 'wss',
      reconnect: true,
      autoConnect: false,
    })
    this.connect(this.signalkClient)
  } else {
    try {
      let mdns

      try {
        mdns = require('mdns')
      } catch ( ex ) {
        this.options.app.setProviderError(this.options.providerId, 'mdns module not installed')
        console.error(ex)
        return
      }

      const discovery = new SignalK.Discovery(mdns, 60000)

      debug('Starting discovery')

      discovery.on('timeout', () => console.log('No SK servers found'))

      discovery.on('found', server => {
        if (server.isMain() && server.isMaster()) {
          if ( !this.remoteServers[server.hostname + ':' + server.port] ) {
            client = server.createClient({
              useTLS: false,
              reconnect: true,
              autoConnect: false
            })
            this.connect(client)
          }
        }
      })
    } catch (ex) {
      this.options.app.setProviderError(this.options.providerId, ex.message)
      console.error(ex)
      return
    }
  }
}

require('util').inherits(MdnsWs, Transform)

function setProviderStatus (that, providerId, message, isError) {
  if (!isError) {
    that.options.app.setProviderStatus(providerId, message)
    console.log(message)
  } else {
    that.options.app.setProviderError(providerId, message)
    console.error(message)
  }
}

MdnsWs.prototype.connect = function (client) {
  const that = this
  const providerId = `${that.options.providerId}.${client.options.hostname}:${client.options.port}`

  client
    .connect()
    .then(() => {
      setProviderStatus(that, providerId, `ws connection connected to ${client.options.hostname}:${client.options.port}`)
      that.remoteServers[client.options.hostname + ':' + client.options.port] = client
      if ( that.options.subscription ) {
        let parsed
        try {
          parsed = JSON.parse(that.options.subscription)
        } catch ( ex ) {
          setProviderStatus(that, providerId, `unable to parse subscription json: ${that.options.subscription}: ${ex.message}`, true)
        }
        if ( !Array.isArray(parsed) ) {
          parsed = [ parsed ]
        }
        parsed.forEach((sub, idx) => {
          debug('sending subscription %j', sub)
          client.subscribe(sub, String(idx))
        })
      } else {
        debug('subscribing to all')
        client.subscribe()
      }
    })
    .catch(err => {
      setProviderStatus(that, providerId, err.message, true)
    })

  client.on('delta', (data) => {
    if (data && data.updates) {
      data.updates.forEach(function (update) {
        update['$source'] = providerId
      })
    }

    that.push(data)
  })
}

MdnsWs.prototype._transform = function (chunk, encoding, done) {}

module.exports = MdnsWs
<|endoftext|>


<commit_before>var cliSteps = function cliSteps() {
  var fs              = require('fs');
  var rimraf          = require('rimraf');
  var mkdirp          = require('mkdirp');
  var exec            = require('child_process').exec;

  var baseDir         = fs.realpathSync(__dirname + "/../..");
  var tmpDir          = baseDir + "/tmp/cucumber-js-sandbox";
  var cleansingNeeded = true;

  var lastRun = { error: null, stdout: "", stderr: "" };

  function tmpPath(path) {
    return (tmpDir + "/" + path);
  };

  function cleanseIfNeeded() {
    if (cleansingNeeded) {
      try { rimraf.sync(tmpDir); } catch(e) {}
      lastRun         = { error: null, stdout: "", stderr: "" };
      cleansingNeeded = false;
    }
  };

  function isWindowsPlatform() {
    return (process.platform == 'win32' || process.platform == 'win64');
  };

  function joinPathSegments(segments) {
    var pathJoiner = isWindowsPlatform() ? "\\" : '/';
    return segments.join(pathJoiner);
  }

  this.Given(/^a file named "(.*)" with:$/, function(filePath, fileContent, callback) {
    cleanseIfNeeded();
    var absoluteFilePath = tmpPath(filePath);
    var filePathSegments = absoluteFilePath.split('/');
    var fileName         = filePathSegments.pop();
    var dirName          = joinPathSegments(filePathSegments);
    mkdirp(dirName, 0755, function(err) {
      if (err) { throw new Error(err); }
      fs.writeFile(absoluteFilePath, fileContent, function(err) {
        if (err) { throw new Error(err); }
        callback();
      });
    });
  });

  this.When(/^I run `cucumber.js(| .+)`$/, function(args, callback) {
    var initialCwd = process.cwd();
    process.chdir(tmpDir);
    var runtimePath = joinPathSegments([baseDir, 'bin', 'cucumber.js']);
    var command     = "node \"" + runtimePath + "\"" + args;
    exec(command,
         function (error, stdout, stderr) {
           lastRun['error']  = error;
           lastRun['stdout'] = stdout;
           lastRun['stderr'] = stderr;
           process.chdir(initialCwd);
           cleansingNeeded = true;
           callback();
         });
  });

  this.Then(/^it passes with:$/, function(expectedOutput, callback) {
    var actualOutput = lastRun['stdout'];
    var actualError  = lastRun['error'];
    var actualStderr = lastRun['stderr'];

    if (actualOutput.indexOf(expectedOutput) == -1)
      throw new Error("Expected output to match the following:\n'" + expectedOutput + "'\nGot:\n'" + actualOutput + "'.\n" +
                      "Error:\n'" + actualError + "'.\n" +
                      "stderr:\n'" + actualStderr  +"'.");
    callback();
  });

  this.Then(/^it outputs this json:$/, function(expectedOutput, callback) {
    var actualOutput = lastRun['stdout'];
    var actualError =  lastRun['error'];
    var actualStderr =  lastRun['stderr'];

    expectedOutput = expectedOutput.replace(/<current-directory>/g, tmpDir.replace(/\\/g,'/'));

    try { var actualJson = JSON.parse(actualOutput.replace(/\\\\/g,'/')); }
    catch(err) { throw new Error("Error parsing actual JSON:\n" + actualOutput); }

    try { var expectedJson = JSON.parse(expectedOutput); }
    catch(err) { throw new Error("Error parsing expected JSON:\n" + expectedOutput); }

    neutraliseVariableValuesInJson(actualJson);
    neutraliseVariableValuesInJson(expectedJson);

    var actualJsonString = JSON.stringify(actualJson, null, 2);
    var expectedJsonString = JSON.stringify(expectedJson, null, 2);

    if (actualJsonString != expectedJsonString)
      throw new Error("Expected output to match the following:\n'" + expectedJsonString + "'\nGot:\n'" + actualJsonString + "'.\n" +
                      "Error:\n'" + actualError + "'.\n" +
                      "stderr:\n'" + actualStderr  +"'.");
    callback();
  });

  this.Then(/^I see the version of Cucumber$/, function(callback) {
    var Cucumber       = require('../../lib/cucumber');
    var actualOutput   = lastRun['stdout'];
    var expectedOutput = Cucumber.VERSION + "\n";
    if (actualOutput.indexOf(expectedOutput) == -1)
      throw new Error("Expected output to match the following:\n'" + expectedOutput + "'\nGot:\n'" + actualOutput + "'.");
    callback();
  });

  this.Then(/^I see the help of Cucumber$/, function(callback) {
    var actualOutput   = lastRun['stdout'];
    var expectedOutput = "Usage: cucumber.js ";
    if (actualOutput.indexOf(expectedOutput) == -1)
      throw new Error("Expected output to match the following:\n'" + expectedOutput + "'\nGot:\n'" + actualOutput + "'.");
    callback();
  });
};

var neutraliseVariableValuesInJson = function neutraliseVariableValuesInJson(report) {
  report.forEach(function (item) {
    (item.elements || []).forEach(function (element) {
      (element['steps'] || []).forEach(function (step) {
        if ('result' in step) {
          if ('error_message' in step.result) {
            step.result.error_message = "<error-message>";
          }

          if ('duration' in step.result) {
            step.result.duration = "<duration>";
          }
        }
      });
    });
  });
};

module.exports = cliSteps;
<commit_msg>Fix the cli step that checks expected output to use indexOf again, and correctly strip color codes and normalize new line characters before checking the two strings<commit_after>var cliSteps = function cliSteps() {
  var fs              = require('fs');
  var rimraf          = require('rimraf');
  var mkdirp          = require('mkdirp');
  var exec            = require('child_process').exec;

  var baseDir         = fs.realpathSync(__dirname + "/../..");
  var tmpDir          = baseDir + "/tmp/cucumber-js-sandbox";
  var cleansingNeeded = true;

  var lastRun = { error: null, stdout: "", stderr: "" };

  function tmpPath(path) {
    return (tmpDir + "/" + path);
  };

  function cleanseIfNeeded() {
    if (cleansingNeeded) {
      try { rimraf.sync(tmpDir); } catch(e) {}
      lastRun         = { error: null, stdout: "", stderr: "" };
      cleansingNeeded = false;
    }
  };

  function isWindowsPlatform() {
    return (process.platform == 'win32' || process.platform == 'win64');
  };

  function joinPathSegments(segments) {
    var pathJoiner = isWindowsPlatform() ? "\\" : '/';
    return segments.join(pathJoiner);
  }

  this.Given(/^a file named "(.*)" with:$/, function(filePath, fileContent, callback) {
    cleanseIfNeeded();
    var absoluteFilePath = tmpPath(filePath);
    var filePathSegments = absoluteFilePath.split('/');
    var fileName         = filePathSegments.pop();
    var dirName          = joinPathSegments(filePathSegments);
    mkdirp(dirName, 0755, function(err) {
      if (err) { throw new Error(err); }
      fs.writeFile(absoluteFilePath, fileContent, function(err) {
        if (err) { throw new Error(err); }
        callback();
      });
    });
  });

  this.When(/^I run `cucumber.js(| .+)`$/, function(args, callback) {
    var initialCwd = process.cwd();
    process.chdir(tmpDir);
    var runtimePath = joinPathSegments([baseDir, 'bin', 'cucumber.js']);
    var command     = "node \"" + runtimePath + "\"" + args;
    exec(command,
         function (error, stdout, stderr) {
           lastRun['error']  = error;
           lastRun['stdout'] = stdout;
           lastRun['stderr'] = stderr;
           process.chdir(initialCwd);
           cleansingNeeded = true;
           callback();
         });
  });

  this.Then(/^it should (pass|fail) with:$/, function (passOrFail, expectedOutput, callback) {
    var actualOutput = lastRun['stdout'];
    var actualError = lastRun['error'];
    var actualStderr = lastRun['stderr'];

    var cleanString = function (str) {
      //Strips colour codes and normalise line endings

      return str
      .replace(/\033\[[0-9;]*m/g, '')
      .replace(/\r\n|\r/g, "\n");
    };

    actualOutput = cleanString(actualOutput);
    expectedOutput = cleanString(expectedOutput);

    if (actualOutput.indexOf(expectedOutput) === -1)
      throw new Error("Expected output to match the following:\n'" + expectedOutput + "'\nGot:\n'" + actualOutput + "'.\n" +
      "Error:\n'" + actualError + "'.\n" +
      "stderr:\n'" + actualStderr + "'.");

    callback();
  });

  this.Then(/^it outputs this json:$/, function(expectedOutput, callback) {
    var actualOutput = lastRun['stdout'];
    var actualError =  lastRun['error'];
    var actualStderr =  lastRun['stderr'];

    expectedOutput = expectedOutput.replace(/<current-directory>/g, tmpDir.replace(/\\/g,'/'));

    try { var actualJson = JSON.parse(actualOutput.replace(/\\\\/g,'/')); }
    catch(err) { throw new Error("Error parsing actual JSON:\n" + actualOutput); }

    try { var expectedJson = JSON.parse(expectedOutput); }
    catch(err) { throw new Error("Error parsing expected JSON:\n" + expectedOutput); }

    neutraliseVariableValuesInJson(actualJson);
    neutraliseVariableValuesInJson(expectedJson);

    var actualJsonString = JSON.stringify(actualJson, null, 2);
    var expectedJsonString = JSON.stringify(expectedJson, null, 2);

    if (actualJsonString != expectedJsonString)
      throw new Error("Expected output to match the following:\n'" + expectedJsonString + "'\nGot:\n'" + actualJsonString + "'.\n" +
                      "Error:\n'" + actualError + "'.\n" +
                      "stderr:\n'" + actualStderr  +"'.");
    callback();
  });

  this.Then(/^I see the version of Cucumber$/, function(callback) {
    var Cucumber       = require('../../lib/cucumber');
    var actualOutput   = lastRun['stdout'];
    var expectedOutput = Cucumber.VERSION + "\n";
    if (actualOutput.indexOf(expectedOutput) == -1)
      throw new Error("Expected output to match the following:\n'" + expectedOutput + "'\nGot:\n'" + actualOutput + "'.");
    callback();
  });

  this.Then(/^I see the help of Cucumber$/, function(callback) {
    var actualOutput   = lastRun['stdout'];
    var expectedOutput = "Usage: cucumber.js ";
    if (actualOutput.indexOf(expectedOutput) == -1)
      throw new Error("Expected output to match the following:\n'" + expectedOutput + "'\nGot:\n'" + actualOutput + "'.");
    callback();
  });
};

var neutraliseVariableValuesInJson = function neutraliseVariableValuesInJson(report) {
  report.forEach(function (item) {
    (item.elements || []).forEach(function (element) {
      (element['steps'] || []).forEach(function (step) {
        if ('result' in step) {
          if ('error_message' in step.result) {
            step.result.error_message = "<error-message>";
          }

          if ('duration' in step.result) {
            step.result.duration = "<duration>";
          }
        }
      });
    });
  });
};

module.exports = cliSteps;
<|endoftext|>