<commit_before>import * as R from 'ramda';
import os from 'os';
import fse from 'fs-extra';
import { resolve, join as pjoin } from 'path';

import { app } from 'electron';
import { writeFile } from 'xod-fs';
import { tapP, rejectWithCode, delay } from 'xod-func-tools';
import * as xad from 'xod-arduino-deploy';
import * as xd from 'xod-deploy';

import * as settings from './settings';
import * as MESSAGES from '../shared/messages';
import formatError from '../shared/errorFormatter';
import * as ERROR_CODES from '../shared/errorCodes';
import * as EVENTS from '../shared/events';
import {
  createSystemMessage,
  parseDebuggerMessage,
  createErrorMessage,
} from '../shared/debuggerMessages';
import { errorToPlainObject, IS_DEV } from './utils';
import { getArdulibsPath, getBundledArdulibsPath } from './arduinoDependencies';

// =============================================================================
//
// Computed paths
//
// =============================================================================
const arduinoPackagesPath = resolve(app.getPath('userData'), 'packages');
const arduinoBuilderPlatformMap = {
  win32: 'win',
  linux: 'linux',
  darwin: 'mac',
};

const arduinoBuilderPath = IS_DEV
  ? resolve(
      app.getAppPath(),
      'arduino-builders',
      arduinoBuilderPlatformMap[os.platform()]
    )
  : resolve(process.resourcesPath, 'arduino-builder');

const artifactTmpDir = resolve(app.getPath('userData'), 'upload-temp');

// Another place to store uploader tools
// Will be used by xod-deploy (cloud compiler)
const uploadToolsPath = resolve(app.getPath('userData'), 'tools');

// =============================================================================
//
// Utils
//
// =============================================================================

/**
 * Returns a list of Boards, that was bundled into `xod-client-electron`.
 */
// :: () -> [Board]
export const getListOfBoards = () => xad.listBoardsFromIndex(xad.packageIndex);

/**
 * Returns a target Board.
 * It tries to take a value from Settings, if it doesn't exist
 * it will just return Null
 */
// :: () -> Nullable Board
export const loadTargetBoard = () =>
  R.compose(
    R.when(R.either(R.isNil, R.isEmpty), R.always(null)),
    settings.getUploadTarget,
    settings.load
  )();

/**
 * Saves a specified Board into Settings and returns itself.
 */
export const saveTargetBoard = board =>
  R.compose(
    R.always(board),
    settings.save,
    settings.setUploadTarget(board),
    settings.load
  )();

// =============================================================================
//
// Upload actions
//
// =============================================================================
/**
 * Install hardware data and tools by fqbn
 */
export const installPackage = fqbn =>
  xad
    .installArchitecture(fqbn, arduinoPackagesPath, xad.packageIndex)
    .catch(rejectWithCode(ERROR_CODES.CANT_INSTALL_ARCHITECTURE));

/**
 * Gets list of all serial ports
 * @returns {Promise<Object, Error>} Promise with Port object or Error
 * @see {@link https://www.npmjs.com/package/serialport#listing-ports}
 */
export const listPorts = () =>
  xad
    .listPorts()
    .then(R.sort(R.descend(R.prop('comName'))))
    .catch(rejectWithCode(ERROR_CODES.NO_PORTS_FOUND));

// :: Port -> [Port] -> Boolean
const hasPort = R.curry((port, ports) =>
  R.compose(R.gt(R.__, -1), R.findIndex(R.propEq('comName', port.comName)))(
    ports
  )
);

/**
 * Validates that port is exist and returns Promise with the same Port object
 * or Rejected Promise with Error Code and object, that contain port
 * and list of available ports.
 */
export const checkPort = port =>
  listPorts()
    .then(
      R.ifElse(hasPort(port), R.always(port), ports => {
        throw Object.assign(new Error(`Port ${port.comName} not found`), {
          port,
          ports,
        });
      })
    )
    .catch(rejectWithCode(ERROR_CODES.PORT_NOT_FOUND));

/**
 * Upload transpiled code to specified device at specified port
 * @param {Object} pab
 * @param {Object} port
 * @param {String} code
 * @returns {Promise<String, Error>} Promise with Stdout or Error
 */
export const uploadToArduino = (
  pab,
  port,
  code,
  ardulibsPath,
  onBuildStdout = () => {}
) => {
  // Create tmpDir in userData instead of os.tmpdir() to avoid error "readdirent: result too large
  const sketchFile = pjoin(artifactTmpDir, 'xod-arduino-sketch.cpp');
  const buildDir = pjoin(artifactTmpDir, 'build');
  const clearTmp = () => fse.remove(artifactTmpDir);

  let libPaths = [getBundledArdulibsPath()];

  return writeFile(sketchFile, code, 'utf8')
    .then(
      tapP(() =>
        fse.pathExists(ardulibsPath).then(exists => {
          if (exists) {
            libPaths = R.append(ardulibsPath, libPaths);
          }
        })
      )
    )
    .then(({ path }) =>
      xad.buildAndUpload(
        path,
        pab,
        arduinoPackagesPath,
        libPaths,
        buildDir,
        port,
        arduinoBuilderPath,
        onBuildStdout
      )
    )
    .then(tapP(clearTmp))
    .catch(err =>
      clearTmp().then(() => rejectWithCode(ERROR_CODES.UPLOAD_ERROR, err))
    );
};

// :: String -> [Board] -> Board
const findBoardById = R.curry((id, boards) =>
  R.find(R.propEq('boardIdentifier', id), boards)
);

// =============================================================================
//
// Local compiling and uploading case
//
// =============================================================================

const deployToArduino = ardulibsPath => ({
  payload,
  sendProgress,
  sendSuccess,
}) => {
  const boardId = payload.board.boardsTxtId;
  const boardCpuId = payload.board.cpuId;
  const { package: pkg, architecture } = payload.board;
  const fqbn = `${pkg}:${architecture}:unknown`;

  return R.pipeP(
    sendProgress(MESSAGES.CODE_TRANSPILED, 10),
    () => checkPort(payload.port).then(port => ({ port: port.comName })),
    sendProgress(MESSAGES.PORT_FOUND, 15),
    tapP(() => installPackage(fqbn)),
    sendProgress(MESSAGES.TOOLCHAIN_INSTALLED, 30),
    ({ port }) =>
      xad.loadPABs(fqbn, arduinoPackagesPath).then(boards => ({
        port,
        pab: R.compose(R.assoc('cpu', boardCpuId), findBoardById(boardId))(
          boards
        ),
      })),
    ({ port, pab }) =>
      uploadToArduino(
        xad.strigifyFQBN(pab),
        port,
        payload.code,
        ardulibsPath,
        buildStdout => {
          sendProgress(buildStdout, 60)();
        }
      ),
    result => {
      if (result.exitCode !== 0) {
        return Promise.reject(
          Object.assign(
            new Error(`Upload tool exited with error code: ${result.exitCode}`),
            result
          )
        );
      }
      sendSuccess([result.stdout, result.stderr].join('\n\n'), 100)();

      return result;
    }
  )(Promise.resolve());
};

// =============================================================================
//
// Cloud compiling and uploading case
//
// =============================================================================

// :: UploadConfig -> Promise Path Error
const installTool = R.converge(xad.installTool(uploadToolsPath), [
  xd.getToolVersionPath,
  xd.getToolUrl,
]);

const deployToArduinoThroughCloud = ({
  payload,
  sendProgress,
  sendSuccess,
}) => {
  const { code } = payload;
  const pio = payload.board.pio;

  return R.pipeP(
    sendProgress(MESSAGES.CODE_TRANSPILED, 10),
    () =>
      checkPort(payload.port).then(port => ({
        port: port.comName,
      })),
    sendProgress(MESSAGES.PORT_FOUND, 15),
    ({ port }) =>
      xd
        .getUploadConfig(pio)
        .then(uploadConfig => ({ port, uploadConfig }))
        .catch(err => {
          const errCode = R.cond([
            [
              R.propEq('status', 404),
              R.always(ERROR_CODES.BOARD_NOT_SUPPORTED),
            ],
            [R.has('status'), R.always(ERROR_CODES.CANT_GET_UPLOAD_CONFIG)],
            [R.T, R.always(ERROR_CODES.CLOUD_NETWORK_ERROR)],
          ])(err);

          return rejectWithCode(errCode, err);
        }),
    data =>
      installTool(data.uploadConfig).then(res =>
        R.assoc('toolPath', res.path, data)
      ),
    sendProgress(MESSAGES.CLOUD_TOOLCHAIN_INSTALLED, 30),
    data =>
      xd
        .compile(pio, code)
        .then(xd.saveCompiledBinary(artifactTmpDir))
        .then(artifactPath => R.assoc('artifactPath', artifactPath, data)),
    sendProgress(MESSAGES.CODE_COMPILED, 75),
    ({ uploadConfig, toolPath, artifactPath, port }) =>
      xd.upload(uploadConfig, { toolPath, artifactPath, port }),
    result => {
      if (result.exitCode !== 0) {
        return Promise.reject(
          Object.assign(
            new Error(`Upload tool exited with error code: ${result.exitCode}`),
            result
          )
        );
      }
      sendSuccess([result.stderr, result.stdout].join('\n\n'), 100)();

      return result;
    }
  )(Promise.resolve());
};

// =============================================================================
//
// Debug
//
// =============================================================================

const debug = async (port, onData, onClose) => {
  const ports = await xad.listPorts();
  const newPort = R.find(
    R.allPass([
      R.propEq('manufacturer', port.manufacturer),
      R.propEq('vendorId', port.vendorId),
      R.propEq('serialNumber', port.serialNumber),
      R.propEq('productId', port.productId),
    ]),
    ports
  );

  if (!newPort) {
    return rejectWithCode(
      ERROR_CODES.DEVICE_NOT_FOUND_FOR_DEBUG,
      new Error('Device for debug is not found')
    );
  }

  const portName = R.prop('comName', port);

  return delay(400).then(() => xad.openAndReadPort(portName, onData, onClose));
};

const isDeviceNotFound = R.propEq(
  'errorCode',
  ERROR_CODES.DEVICE_NOT_FOUND_FOR_DEBUG
);

// =============================================================================
//
// IPC handlers (for main process)
//
// =============================================================================

/**
 * Handler for starting debug session.
 * - `storeFn`   - is a function, that used to store reference to port,
 *                 to close connection with it on next upload or on stop
 *                 debug session
 * - `onCloseCb` - is a function, that called on any "close" event occured
 *                 on SerialPort object. It called one argument:
 *                 - `sendErr` function, that send error "Lost connection",
 *                   only main process knows is connection closed by user or
 *                   really error occured, so it's passed as argument and
 *                   called in the main process.
 */
export const startDebugSessionHandler = (storeFn, onCloseCb) => (
  event,
  { port }
) => {
  let triesToSearchDevice = 0;
  const maxTriesToSearch = 7;
  const searchDelay = 300;

  let messageCollector = [];
  const throttleDelay = 100; // ms

  const intervalId = setInterval(() => {
    if (messageCollector.length > 0) {
      event.sender.send(EVENTS.DEBUG_SESSION, messageCollector);
      messageCollector = [];
    }
  }, throttleDelay);

  const onData = data => {
    messageCollector = R.append(parseDebuggerMessage(data), messageCollector);
  };
  const onClose = () => {
    clearInterval(intervalId);
    onCloseCb(() => {
      const errorMessage = R.compose(
        R.omit('stack'), // Lost connection is not a big deal, we don't want a stacktrace here
        createErrorMessage
      )(new Error(MESSAGES.DEBUG_LOST_CONNECTION));
      event.sender.send(EVENTS.DEBUG_SESSION, [errorMessage]);
    });
    event.sender.send(
      EVENTS.STOP_DEBUG_SESSION,
      createSystemMessage('Debug session stopped')
    );
  };

  const runDebug = () =>
    debug(port, onData, onClose).catch(async err => {
      if (triesToSearchDevice >= maxTriesToSearch || !isDeviceNotFound(err)) {
        return err;
      }

      triesToSearchDevice += 1;
      await delay(searchDelay);
      return await runDebug();
    });

  return runDebug()
    .then(R.tap(debugPort => storeFn(debugPort, intervalId)))
    .catch(err => {
      clearInterval(intervalId);
      event.sender.send(EVENTS.DEBUG_SESSION, [createErrorMessage(err)]);
    });
};

export const stopDebugSessionHandler = (event, port) => xad.closePort(port);

export const uploadToArduinoHandler = (event, payload, wsPath) => {
  let lastPercentage = 0;
  // Messages
  const send = status =>
    R.compose(
      data => arg => {
        event.sender.send('UPLOAD_TO_ARDUINO', data);
        return arg;
      },
      R.assoc(status, true),
      (message, percentage, err = null) => {
        lastPercentage = percentage;
        return {
          success: false,
          progress: false,
          failure: false,
          error: err,
          message,
          percentage,
        };
      }
    );

  // :: (message, percentage, error) -> a -> a
  const sendSuccess = send('success');
  const sendProgress = send('progress');
  const sendFailure = send('failure');
  const convertAndSendError = err =>
    R.compose(
      msg => sendFailure(msg, lastPercentage, errorToPlainObject(err))(),
      formatError
    )(err);

  const ardulibsPath = getArdulibsPath(wsPath);

  const deployFn = payload.cloud
    ? deployToArduinoThroughCloud
    : deployToArduino(ardulibsPath);

  return deployFn({
    payload,
    sendProgress,
    sendSuccess,
  }).catch(convertAndSendError);
};

export const listPortsHandler = event =>
  listPorts()
    .then(ports =>
      event.sender.send(EVENTS.LIST_PORTS, {
        err: false,
        data: ports,
      })
    )
    .catch(err =>
      event.sender.send(EVENTS.LIST_PORTS, {
        err: true,
        data: err,
      })
    );

export const listBoardsHandler = event =>
  event.sender.send(EVENTS.LIST_BOARDS, {
    err: false,
    data: getListOfBoards(),
  });

export const loadTargetBoardHandler = event =>
  event.sender.send(EVENTS.GET_SELECTED_BOARD, {
    err: false,
    data: loadTargetBoard(),
  });

export const saveTargetBoardHandler = (event, payload) =>
  event.sender.send(EVENTS.SET_SELECTED_BOARD, {
    err: false,
    data: saveTargetBoard(payload),
  });
<commit_msg>refactor(xod-client-electron): interact with serialport using `xod-deploy` instead of `xod-arduino-deploy`<commit_after>import * as R from 'ramda';
import os from 'os';
import fse from 'fs-extra';
import { resolve, join as pjoin } from 'path';

import { app } from 'electron';
import { writeFile } from 'xod-fs';
import { tapP, rejectWithCode, delay } from 'xod-func-tools';
import * as xad from 'xod-arduino-deploy';
import * as xd from 'xod-deploy';

import * as settings from './settings';
import * as MESSAGES from '../shared/messages';
import formatError from '../shared/errorFormatter';
import * as ERROR_CODES from '../shared/errorCodes';
import * as EVENTS from '../shared/events';
import {
  createSystemMessage,
  parseDebuggerMessage,
  createErrorMessage,
} from '../shared/debuggerMessages';
import { errorToPlainObject, IS_DEV } from './utils';
import { getArdulibsPath, getBundledArdulibsPath } from './arduinoDependencies';

// =============================================================================
//
// Computed paths
//
// =============================================================================
const arduinoPackagesPath = resolve(app.getPath('userData'), 'packages');
const arduinoBuilderPlatformMap = {
  win32: 'win',
  linux: 'linux',
  darwin: 'mac',
};

const arduinoBuilderPath = IS_DEV
  ? resolve(
      app.getAppPath(),
      'arduino-builders',
      arduinoBuilderPlatformMap[os.platform()]
    )
  : resolve(process.resourcesPath, 'arduino-builder');

const artifactTmpDir = resolve(app.getPath('userData'), 'upload-temp');

// Another place to store uploader tools
// Will be used by xod-deploy (cloud compiler)
const uploadToolsPath = resolve(app.getPath('userData'), 'tools');

// =============================================================================
//
// Utils
//
// =============================================================================

/**
 * Returns a list of Boards, that was bundled into `xod-client-electron`.
 */
// :: () -> [Board]
export const getListOfBoards = () => xad.listBoardsFromIndex(xad.packageIndex);

/**
 * Returns a target Board.
 * It tries to take a value from Settings, if it doesn't exist
 * it will just return Null
 */
// :: () -> Nullable Board
export const loadTargetBoard = () =>
  R.compose(
    R.when(R.either(R.isNil, R.isEmpty), R.always(null)),
    settings.getUploadTarget,
    settings.load
  )();

/**
 * Saves a specified Board into Settings and returns itself.
 */
export const saveTargetBoard = board =>
  R.compose(
    R.always(board),
    settings.save,
    settings.setUploadTarget(board),
    settings.load
  )();

// =============================================================================
//
// Upload actions
//
// =============================================================================
/**
 * Install hardware data and tools by fqbn
 */
export const installPackage = fqbn =>
  xad
    .installArchitecture(fqbn, arduinoPackagesPath, xad.packageIndex)
    .catch(rejectWithCode(ERROR_CODES.CANT_INSTALL_ARCHITECTURE));

/**
 * Gets list of all serial ports
 * @returns {Promise<Object, Error>} Promise with Port object or Error
 * @see {@link https://www.npmjs.com/package/serialport#listing-ports}
 */
export const listPorts = () =>
  xd
    .listPorts()
    .then(R.sort(R.descend(R.prop('comName'))))
    .catch(rejectWithCode(ERROR_CODES.NO_PORTS_FOUND));

// :: Port -> [Port] -> Boolean
const hasPort = R.curry((port, ports) =>
  R.compose(R.gt(R.__, -1), R.findIndex(R.propEq('comName', port.comName)))(
    ports
  )
);

/**
 * Validates that port is exist and returns Promise with the same Port object
 * or Rejected Promise with Error Code and object, that contain port
 * and list of available ports.
 */
export const checkPort = port =>
  listPorts()
    .then(
      R.ifElse(hasPort(port), R.always(port), ports => {
        throw Object.assign(new Error(`Port ${port.comName} not found`), {
          port,
          ports,
        });
      })
    )
    .catch(rejectWithCode(ERROR_CODES.PORT_NOT_FOUND));

/**
 * Upload transpiled code to specified device at specified port
 * @param {Object} pab
 * @param {Object} port
 * @param {String} code
 * @returns {Promise<String, Error>} Promise with Stdout or Error
 */
export const uploadToArduino = (
  pab,
  port,
  code,
  ardulibsPath,
  onBuildStdout = () => {}
) => {
  // Create tmpDir in userData instead of os.tmpdir() to avoid error "readdirent: result too large
  const sketchFile = pjoin(artifactTmpDir, 'xod-arduino-sketch.cpp');
  const buildDir = pjoin(artifactTmpDir, 'build');
  const clearTmp = () => fse.remove(artifactTmpDir);

  let libPaths = [getBundledArdulibsPath()];

  return writeFile(sketchFile, code, 'utf8')
    .then(
      tapP(() =>
        fse.pathExists(ardulibsPath).then(exists => {
          if (exists) {
            libPaths = R.append(ardulibsPath, libPaths);
          }
        })
      )
    )
    .then(({ path }) =>
      xad.buildAndUpload(
        path,
        pab,
        arduinoPackagesPath,
        libPaths,
        buildDir,
        port,
        arduinoBuilderPath,
        onBuildStdout
      )
    )
    .then(tapP(clearTmp))
    .catch(err =>
      clearTmp().then(() => rejectWithCode(ERROR_CODES.UPLOAD_ERROR, err))
    );
};

// :: String -> [Board] -> Board
const findBoardById = R.curry((id, boards) =>
  R.find(R.propEq('boardIdentifier', id), boards)
);

// =============================================================================
//
// Local compiling and uploading case
//
// =============================================================================

const deployToArduino = ardulibsPath => ({
  payload,
  sendProgress,
  sendSuccess,
}) => {
  const boardId = payload.board.boardsTxtId;
  const boardCpuId = payload.board.cpuId;
  const { package: pkg, architecture } = payload.board;
  const fqbn = `${pkg}:${architecture}:unknown`;

  return R.pipeP(
    sendProgress(MESSAGES.CODE_TRANSPILED, 10),
    () => checkPort(payload.port).then(port => ({ port: port.comName })),
    sendProgress(MESSAGES.PORT_FOUND, 15),
    tapP(() => installPackage(fqbn)),
    sendProgress(MESSAGES.TOOLCHAIN_INSTALLED, 30),
    ({ port }) =>
      xad.loadPABs(fqbn, arduinoPackagesPath).then(boards => ({
        port,
        pab: R.compose(R.assoc('cpu', boardCpuId), findBoardById(boardId))(
          boards
        ),
      })),
    ({ port, pab }) =>
      uploadToArduino(
        xad.strigifyFQBN(pab),
        port,
        payload.code,
        ardulibsPath,
        buildStdout => {
          sendProgress(buildStdout, 60)();
        }
      ),
    result => {
      if (result.exitCode !== 0) {
        return Promise.reject(
          Object.assign(
            new Error(`Upload tool exited with error code: ${result.exitCode}`),
            result
          )
        );
      }
      sendSuccess([result.stdout, result.stderr].join('\n\n'), 100)();

      return result;
    }
  )(Promise.resolve());
};

// =============================================================================
//
// Cloud compiling and uploading case
//
// =============================================================================

// :: UploadConfig -> Promise Path Error
const installTool = R.converge(xad.installTool(uploadToolsPath), [
  xd.getToolVersionPath,
  xd.getToolUrl,
]);

const deployToArduinoThroughCloud = ({
  payload,
  sendProgress,
  sendSuccess,
}) => {
  const { code } = payload;
  const pio = payload.board.pio;

  return R.pipeP(
    sendProgress(MESSAGES.CODE_TRANSPILED, 10),
    () =>
      checkPort(payload.port).then(port => ({
        port: port.comName,
      })),
    sendProgress(MESSAGES.PORT_FOUND, 15),
    ({ port }) =>
      xd
        .getUploadConfig(pio)
        .then(uploadConfig => ({ port, uploadConfig }))
        .catch(err => {
          const errCode = R.cond([
            [
              R.propEq('status', 404),
              R.always(ERROR_CODES.BOARD_NOT_SUPPORTED),
            ],
            [R.has('status'), R.always(ERROR_CODES.CANT_GET_UPLOAD_CONFIG)],
            [R.T, R.always(ERROR_CODES.CLOUD_NETWORK_ERROR)],
          ])(err);

          return rejectWithCode(errCode, err);
        }),
    data =>
      installTool(data.uploadConfig).then(res =>
        R.assoc('toolPath', res.path, data)
      ),
    sendProgress(MESSAGES.CLOUD_TOOLCHAIN_INSTALLED, 30),
    data =>
      xd
        .compile(pio, code)
        .then(xd.saveCompiledBinary(artifactTmpDir))
        .then(artifactPath => R.assoc('artifactPath', artifactPath, data)),
    sendProgress(MESSAGES.CODE_COMPILED, 75),
    ({ uploadConfig, toolPath, artifactPath, port }) =>
      xd.upload(uploadConfig, { toolPath, artifactPath, port }),
    result => {
      if (result.exitCode !== 0) {
        return Promise.reject(
          Object.assign(
            new Error(`Upload tool exited with error code: ${result.exitCode}`),
            result
          )
        );
      }
      sendSuccess([result.stderr, result.stdout].join('\n\n'), 100)();

      return result;
    }
  )(Promise.resolve());
};

// =============================================================================
//
// Debug
//
// =============================================================================

const debug = async (port, onData, onClose) => {
  const ports = await xd.listPorts();
  const newPort = R.find(
    R.allPass([
      R.propEq('manufacturer', port.manufacturer),
      R.propEq('vendorId', port.vendorId),
      R.propEq('serialNumber', port.serialNumber),
      R.propEq('productId', port.productId),
    ]),
    ports
  );

  if (!newPort) {
    return rejectWithCode(
      ERROR_CODES.DEVICE_NOT_FOUND_FOR_DEBUG,
      new Error('Device for debug is not found')
    );
  }

  const portName = R.prop('comName', port);

  return delay(400).then(() => xd.openAndReadPort(portName, onData, onClose));
};

const isDeviceNotFound = R.propEq(
  'errorCode',
  ERROR_CODES.DEVICE_NOT_FOUND_FOR_DEBUG
);

// =============================================================================
//
// IPC handlers (for main process)
//
// =============================================================================

/**
 * Handler for starting debug session.
 * - `storeFn`   - is a function, that used to store reference to port,
 *                 to close connection with it on next upload or on stop
 *                 debug session
 * - `onCloseCb` - is a function, that called on any "close" event occured
 *                 on SerialPort object. It called one argument:
 *                 - `sendErr` function, that send error "Lost connection",
 *                   only main process knows is connection closed by user or
 *                   really error occured, so it's passed as argument and
 *                   called in the main process.
 */
export const startDebugSessionHandler = (storeFn, onCloseCb) => (
  event,
  { port }
) => {
  let triesToSearchDevice = 0;
  const maxTriesToSearch = 7;
  const searchDelay = 300;

  let messageCollector = [];
  const throttleDelay = 100; // ms

  const intervalId = setInterval(() => {
    if (messageCollector.length > 0) {
      event.sender.send(EVENTS.DEBUG_SESSION, messageCollector);
      messageCollector = [];
    }
  }, throttleDelay);

  const onData = data => {
    messageCollector = R.append(parseDebuggerMessage(data), messageCollector);
  };
  const onClose = () => {
    clearInterval(intervalId);
    onCloseCb(() => {
      const errorMessage = R.compose(
        R.omit('stack'), // Lost connection is not a big deal, we don't want a stacktrace here
        createErrorMessage
      )(new Error(MESSAGES.DEBUG_LOST_CONNECTION));
      event.sender.send(EVENTS.DEBUG_SESSION, [errorMessage]);
    });
    event.sender.send(
      EVENTS.STOP_DEBUG_SESSION,
      createSystemMessage('Debug session stopped')
    );
  };

  const runDebug = () =>
    debug(port, onData, onClose).catch(async err => {
      if (triesToSearchDevice >= maxTriesToSearch || !isDeviceNotFound(err)) {
        return err;
      }

      triesToSearchDevice += 1;
      await delay(searchDelay);
      return await runDebug();
    });

  return runDebug()
    .then(R.tap(debugPort => storeFn(debugPort, intervalId)))
    .catch(err => {
      clearInterval(intervalId);
      event.sender.send(EVENTS.DEBUG_SESSION, [createErrorMessage(err)]);
    });
};

export const stopDebugSessionHandler = (event, port) => xd.closePort(port);

export const uploadToArduinoHandler = (event, payload, wsPath) => {
  let lastPercentage = 0;
  // Messages
  const send = status =>
    R.compose(
      data => arg => {
        event.sender.send('UPLOAD_TO_ARDUINO', data);
        return arg;
      },
      R.assoc(status, true),
      (message, percentage, err = null) => {
        lastPercentage = percentage;
        return {
          success: false,
          progress: false,
          failure: false,
          error: err,
          message,
          percentage,
        };
      }
    );

  // :: (message, percentage, error) -> a -> a
  const sendSuccess = send('success');
  const sendProgress = send('progress');
  const sendFailure = send('failure');
  const convertAndSendError = err =>
    R.compose(
      msg => sendFailure(msg, lastPercentage, errorToPlainObject(err))(),
      formatError
    )(err);

  const ardulibsPath = getArdulibsPath(wsPath);

  const deployFn = payload.cloud
    ? deployToArduinoThroughCloud
    : deployToArduino(ardulibsPath);

  return deployFn({
    payload,
    sendProgress,
    sendSuccess,
  }).catch(convertAndSendError);
};

export const listPortsHandler = event =>
  listPorts()
    .then(ports =>
      event.sender.send(EVENTS.LIST_PORTS, {
        err: false,
        data: ports,
      })
    )
    .catch(err =>
      event.sender.send(EVENTS.LIST_PORTS, {
        err: true,
        data: err,
      })
    );

export const listBoardsHandler = event =>
  event.sender.send(EVENTS.LIST_BOARDS, {
    err: false,
    data: getListOfBoards(),
  });

export const loadTargetBoardHandler = event =>
  event.sender.send(EVENTS.GET_SELECTED_BOARD, {
    err: false,
    data: loadTargetBoard(),
  });

export const saveTargetBoardHandler = (event, payload) =>
  event.sender.send(EVENTS.SET_SELECTED_BOARD, {
    err: false,
    data: saveTargetBoard(payload),
  });
<|endoftext|>


<commit_before>/*global module,define, require, console, process*/
/*jshint
 camelcase: false
 */
(function () {
	'use strict';
	var util = require( 'util' );
	var path = require( 'path' );
	var yeoman = require( 'yeoman-generator' );
	var chalk = require( 'chalk' );
	var _ = require( 'underscore' );
	var moment = require( 'moment' );
	var utils = require( './utils' );
	var promptDef = require( './prompts' );

	var qsExtension = yeoman.generators.Base.extend( {

		constructor: function () {
			yeoman.generators.Base.apply( this, arguments );
			this.pkg = require( "../package.json" );

			if ( this.options['grunt'] ) {
				this.mode = 'grunt';
			} else {
				this.mode = 'default';
			}

		},

		init: {

			/**
			 * Retrieve the local extension folder
			 *
			 * @example
			 * e.g. %USERPROFILE%\My Documents\Qlik\Sense\Extensions\
			 */
			getLocalExtensionDir: function () {

				this.log( 'getLocalExtensionDir' );

				var done = this.async();
				var that = this;

				utils.getExtensionPath().then( function ( result ) {
					that.localExtensionDir = result;
					done();
				} ).catch( function ( err ) {
					console.error( 'Could  not retrieve the local extension path', err );
					done();
				} );
			}

		},

		askFor: function () {

			if ( this.mode === 'default' ) {
				var done = this.async();
				this.log( chalk.magenta( 'You\'re using qsextension generator.' ) );

				this.prompt( promptDef, function ( props ) {

					this.prompts = {};

					this.prompts.advancedMode = props.advancedMode;
					this.prompts.extensionName = props.extensionName;
					this.prompts.extensionNameSafe = props.extensionName.replace( /\s/g, "" );
					this.prompts.extensionType = props.extensionType;
					this.prompts.extensionNamespace = _.isEmpty( props.extensionNamespace ) ? '' : props.extensionNamespace + '-';
					this.prompts.extensionDescription = props.extensionDescription;
					this.prompts.authorName = props.authorName;
					this.prompts.lessSupport = props.lessSupport;
					this.prompts.license = props.license || 'mit';

					var d = new Date();
					this.prompts.publishingYear = d.getFullYear();
					this.prompts.creationDate = moment( d ).format( 'YYYY-MM-DD' );

					this.prompts.licenceGenerated = utils.getLicense( this.prompts );

					// Debug
					// this.log( 'prompts', this.prompts );

					done();

				}.bind( this ) );
			} else {

				// do nothing right now ...
				// later on get the values from the config file

			}
		},

		writing: function () {

			switch ( this.mode ) {
				case 'default':
					this._root();
					this._dirs();
					this._src();
					this._srcLib();
					this._styles();
					this._grunt();
					break;
				case 'grunt':
					// not implemented, yet
					//this._grunt();
					break;
				default:
					break;
			}

		},

		end: function () {

			if ( !this.options['skip-install'] ) {
				var npmdir = process.cwd() + '/grunt';
				process.chdir( npmdir );

				this.installDependencies( {
					bower: false,
					npm: true,
					skipMessage: true
				} );
			}

		},

		_root: function () {

			// root
			this.copy( 'gitattributes.txt', '.gitattributes' );
			this.copy( 'gitignore.txt', '.gitignore' );
			this.template( 'readme.md', 'README.md' );
			this.template( 'license.md', 'LICENSE.md' );
			this.template( 'changelog.md', 'CHANGELOG.md' );

		},

		_dirs: function () {

			// Build Dir
			this.mkdir( 'build' );
			this.mkdir( 'dist' );

		},

		_src: function () {

			// src dir
			this.mkdir( 'src' );
			this.copy( '.jshintrc', 'src/.jshintrc' );
			this.template( 'extension.js', 'src/' + this.prompts.extensionNamespace.toLowerCase() + this.prompts.extensionNameSafe.toLowerCase() + '.js' );
			this.template( 'extension.qext', 'src/' + this.prompts.extensionNamespace.toLowerCase() + this.prompts.extensionNameSafe.toLowerCase() + '.qext' );
			this.copy( 'extension.png', 'src/' + this.prompts.extensionNameSafe.toLowerCase() + '.png' );
			this.template( 'extension-properties.js', 'src/properties.js' );
			this.template( 'extension-initialproperties.js', 'src/initialproperties.js' );

		},

		_srcLib: function () {

			// src/lib
			this.mkdir( 'src/lib' );
			this.mkdir( 'src/lib/css' );
			this.mkdir( 'src/lib/js' );
			this.mkdir( 'src/lib/external' );
			this.mkdir( 'src/lib/images' );
			this.mkdir( 'src/lib/fonts' );
			this.mkdir( 'src/lib/icons' );
			this.mkdir( 'src/lib/data' );
			this.mkdir( 'src/lib/partials' );

			// src/lib/content
			this.copy( 'lib/js/extensionUtils.js', 'src/lib/js/extensionUtils.js' );

		},

		_grunt: function () {

			// Grunt
			this.mkdir( 'grunt' );
			this.copy( 'grunt/.jshintrc-dev', 'grunt/.jshintrc-dev' );
			this.copy( 'grunt/.jshintrc-release', 'grunt/.jshintrc-release' );
			this.template( 'grunt/grunt-config.yml', 'grunt/grunt-config.yml' );
			this.copy( 'grunt/_package.json', 'grunt/package.json' );
			this.copy( 'grunt/Gruntfile.projectconfig.js', 'grunt/Gruntfile.projectconfig.js' );
			this.copy( 'grunt/Gruntfile.clean.js', 'grunt/Gruntfile.clean.js' );
			this.copy( 'grunt/Gruntfile.cleanempty.js', 'grunt/Gruntfile.cleanempty.js' );
			this.copy( 'grunt/Gruntfile.compress.js', 'grunt/Gruntfile.compress.js' );
			this.copy( 'grunt/Gruntfile.copy.js', 'grunt/Gruntfile.copy.js' );

			// Gruntfile.Less will be added in the createStyles task
			this.template( 'grunt/Gruntfile.js', 'grunt/Gruntfile.js' );
			this.copy( 'grunt/Gruntfile.jshint.js', 'grunt/Gruntfile.jshint.js' );
			this.copy( 'grunt/Gruntfile.replace.js', 'grunt/Gruntfile.replace.js' );
			this.copy( 'grunt/Gruntfile.uglify.js', 'grunt/Gruntfile.uglify.js' );
			this.copy( 'grunt/gruntReplacements.yml', 'grunt/gruntReplacements.yml' );
			this.copy( 'grunt/gruntReplacements_dev.yml', 'grunt/gruntReplacements_dev.yml' );
			this.copy( 'grunt/gruntReplacements_release.yml', 'grunt/gruntReplacements_release.yml' );

			//var done = this.async();
			//this.composeWith( 'gengrunt', {options: {}} );
			//done();

		},

		_styles: function () {

			if ( this.prompts.lessSupport === true ) {
				this.mkdir( 'src/lib/less' );
				this.template( '_root.less', 'src/lib/less/_root.less' );
				this.template( 'styles.less', 'src/lib/less/styles.less' );
				this.template( 'variables.less', 'src/lib/less/variables.less' );
				this.template( 'style_Less.css', 'src/lib/css/style.css' );
				this.copy( 'grunt/Gruntfile.less.js', 'grunt/Gruntfile.less.js' );

			} else {
				this.template( 'style_noLess.css', 'src/lib/css/style.css' );
			}
		}
	} );

	module.exports = qsExtension;
}());
<commit_msg>option --grunt for updating the grunt deployment based on the latest generator-qsextension<commit_after>/*global module,define, require, console, process*/
/*jshint
 camelcase: false
 */
(function () {
	'use strict';
	var util = require( 'util' );
	var path = require( 'path' );
	var fs = require( 'fs' );
	var yeoman = require( 'yeoman-generator' );
	var chalk = require( 'chalk' );
	var _ = require( 'underscore' );
	var moment = require( 'moment' );
	var utils = require( './utils' );
	var promptDef = require( './prompts' );

	var qsExtension = yeoman.generators.Base.extend( {

		constructor: function () {
			yeoman.generators.Base.apply( this, arguments );
			this.pkg = require( "../package.json" );

			if ( this.options['grunt'] ) {
				this.mode = 'grunt';
			} else {
				this.mode = 'default';
			}

		},

		init: {

			/**
			 * Retrieve the local extension folder
			 *
			 * @example
			 * e.g. %USERPROFILE%\My Documents\Qlik\Sense\Extensions\
			 */
			getLocalExtensionDir: function () {

				this.log( 'getLocalExtensionDir' );

				var done = this.async();
				var that = this;

				utils.getExtensionPath().then( function ( result ) {
					that.localExtensionDir = result;
					done();
				} ).catch( function ( err ) {
					console.error( 'Could  not retrieve the local extension path', err );
					done();
				} );
			}

		},

		askFor: function () {

			if ( this.mode === 'default' ) {
				var done = this.async();
				this.log( chalk.magenta( 'You\'re using qsextension generator.' ) );

				this.prompt( promptDef, function ( props ) {

					this.prompts = {};

					this.prompts.advancedMode = props.advancedMode;
					this.prompts.extensionName = props.extensionName;
					this.prompts.extensionNameSafe = props.extensionName.replace( /\s/g, "" );
					this.prompts.extensionType = props.extensionType;
					this.prompts.extensionNamespace = _.isEmpty( props.extensionNamespace ) ? '' : props.extensionNamespace + '-';
					this.prompts.extensionDescription = props.extensionDescription;
					this.prompts.authorName = props.authorName;
					this.prompts.lessSupport = props.lessSupport;
					this.prompts.license = props.license || 'mit';

					var d = new Date();
					this.prompts.publishingYear = d.getFullYear();
					this.prompts.creationDate = moment( d ).format( 'YYYY-MM-DD' );

					this.prompts.licenceGenerated = utils.getLicense( this.prompts );

					// Debug
					// this.log( 'prompts', this.prompts );

					done();

				}.bind( this ) );
			} else {

				// do nothing right now ...
				// later on get the values from the config file

			}
		},

		writing: function () {

			switch ( this.mode ) {
				case 'default':
					this._root();
					this._dirs();
					this._src();
					this._srcLib();
					this._styles();
					this._grunt();
					this._saveConfig();
					break;
				case 'grunt':
					// not implemented, yet
					var existingConfig = this.config.getAll();
					if ( !_.isEmpty( existingConfig ) ) {
						this.prompts = existingConfig;
						this._grunt();
					} else {
						this.log.error( chalk.red( 'Grunt update not possible, cannot load existing configuration!' ) + ' .yo-rc.json is missing.' );
					}
					break;
				default:
					break;
			}

		},

		end: function () {

			if ( !this.options['skip-install'] ) {

				// for future changes check if grunt based deployment is existing
				if ( fs.existsSync( path.join( process.cwd() + '/grunt' ) ) ) {

					var npmdir = process.cwd() + '/grunt';
					process.chdir( npmdir );

					this.installDependencies( {
						bower: false,
						npm: true,
						skipMessage: true
					} );

				}
			}

		},

		_root: function () {

			// root
			this.copy( 'gitattributes.txt', '.gitattributes' );
			this.copy( 'gitignore.txt', '.gitignore' );
			this.template( 'readme.md', 'README.md' );
			this.template( 'license.md', 'LICENSE.md' );
			this.template( 'changelog.md', 'CHANGELOG.md' );

		},

		_dirs: function () {

			// Build Dir
			this.mkdir( 'build' );
			this.mkdir( 'dist' );

		},

		_src: function () {

			// src dir
			this.mkdir( 'src' );
			this.copy( '.jshintrc', 'src/.jshintrc' );
			this.template( 'extension.js', 'src/' + this.prompts.extensionNamespace.toLowerCase() + this.prompts.extensionNameSafe.toLowerCase() + '.js' );
			this.template( 'extension.qext', 'src/' + this.prompts.extensionNamespace.toLowerCase() + this.prompts.extensionNameSafe.toLowerCase() + '.qext' );
			this.copy( 'extension.png', 'src/' + this.prompts.extensionNameSafe.toLowerCase() + '.png' );
			this.template( 'extension-properties.js', 'src/properties.js' );
			this.template( 'extension-initialproperties.js', 'src/initialproperties.js' );

		},

		_srcLib: function () {

			// src/lib
			this.mkdir( 'src/lib' );
			this.mkdir( 'src/lib/css' );
			this.mkdir( 'src/lib/js' );
			this.mkdir( 'src/lib/external' );
			this.mkdir( 'src/lib/images' );
			this.mkdir( 'src/lib/fonts' );
			this.mkdir( 'src/lib/icons' );
			this.mkdir( 'src/lib/data' );
			this.mkdir( 'src/lib/partials' );

			// src/lib/content
			this.copy( 'lib/js/extensionUtils.js', 'src/lib/js/extensionUtils.js' );

		},

		_grunt: function () {

			// Grunt
			this.mkdir( 'grunt' );
			this.copy( 'grunt/.jshintrc-dev', 'grunt/.jshintrc-dev' );
			this.copy( 'grunt/.jshintrc-release', 'grunt/.jshintrc-release' );
			this.template( 'grunt/grunt-config.yml', 'grunt/grunt-config.yml' );
			this.copy( 'grunt/_package.json', 'grunt/package.json' );
			this.copy( 'grunt/Gruntfile.projectconfig.js', 'grunt/Gruntfile.projectconfig.js' );
			this.copy( 'grunt/Gruntfile.clean.js', 'grunt/Gruntfile.clean.js' );
			this.copy( 'grunt/Gruntfile.cleanempty.js', 'grunt/Gruntfile.cleanempty.js' );
			this.copy( 'grunt/Gruntfile.compress.js', 'grunt/Gruntfile.compress.js' );
			this.copy( 'grunt/Gruntfile.copy.js', 'grunt/Gruntfile.copy.js' );

			// Gruntfile.Less will be added in the createStyles task
			this.template( 'grunt/Gruntfile.js', 'grunt/Gruntfile.js' );
			this.copy( 'grunt/Gruntfile.jshint.js', 'grunt/Gruntfile.jshint.js' );
			this.copy( 'grunt/Gruntfile.replace.js', 'grunt/Gruntfile.replace.js' );
			this.copy( 'grunt/Gruntfile.uglify.js', 'grunt/Gruntfile.uglify.js' );
			this.copy( 'grunt/gruntReplacements.yml', 'grunt/gruntReplacements.yml' );
			this.copy( 'grunt/gruntReplacements_dev.yml', 'grunt/gruntReplacements_dev.yml' );
			this.copy( 'grunt/gruntReplacements_release.yml', 'grunt/gruntReplacements_release.yml' );

		},

		_styles: function () {

			if ( this.prompts.lessSupport === true ) {
				this.mkdir( 'src/lib/less' );
				this.template( '_root.less', 'src/lib/less/_root.less' );
				this.template( 'styles.less', 'src/lib/less/styles.less' );
				this.template( 'variables.less', 'src/lib/less/variables.less' );
				this.template( 'style_Less.css', 'src/lib/css/style.css' );
				this.copy( 'grunt/Gruntfile.less.js', 'grunt/Gruntfile.less.js' );

			} else {
				this.template( 'style_noLess.css', 'src/lib/css/style.css' );
			}
		},

		_saveConfig: function () {
			this.config.set( this.prompts );
			this.config.save();
		}
	} );

	module.exports = qsExtension;
}());
<|endoftext|>


<commit_before>'use strict';
const path = require('path');
const Generator = require('yeoman-generator');
const yosay = require('yosay');
const chalk = require('chalk');
const isWin = process.platform === 'win32';
const homedir = (isWin) ? process.env.HOMEPATH : process.env.HOME;
const fs = require('fs');
const mkdirp = require('mkdirp');
const GruntfileEditor = require('gruntfile-editor');

function getDirectories(srcpath) {
  return fs.readdirSync(srcpath).filter(function(file) {
    return fs.statSync(path.join(srcpath, file)).isDirectory();
  });
}

module.exports = class extends Generator {
  initializing() {
    this.gruntfile = new GruntfileEditor();
    // check for existence of package.json
    try {
      fs.accessSync('./package.json', fs.F_OK);
      this.hasPackageJson = true;
    } catch (e) {
      this.hasPackageJson = false;
    }
  }

  prompting() {
    var done = this.async();
    var self = this;

    // Have Yeoman greet the user.
    this.log(yosay('Welcome to the ArcGIS Web AppBuilder generator!'));
    this.log(chalk.yellow('These generators should be run in the root folder of your project.'));

    var prompts = [{
      name: 'abort',
      type: 'confirm',
      default: true,
      message: 'No package.json found. Would you like to abort and run npm init first?',
      when: function() {
        return !self.hasPackageJson;
      }
    }, {
      type: 'list',
      choices: [
        {
        value: 'is2d',
        name: '2D'
        },
        {
        value: 'is3d',
        name: '3D'
        }
      ],
      name: 'widgetsType',
      message: 'Type of widget(s) to be generated:',
      when: function(currentAnswers) {
        return !currentAnswers.abort;
      }
    }, {
      when: function(currentAnswers) {
        return !currentAnswers.abort;
      },
      name: 'wabRoot',
      message: 'Web AppBuilder install root:',
      'default': function(currentAnswers) {
        return path.join(homedir, 'WebAppBuilderForArcGIS');
      },
      validate: function(wabPath) {
        // make sure input directory and the following paths exist:
        // * server
        // * client
        var paths = [wabPath, path.join(wabPath, 'server'), path.join(wabPath, 'client')];
        try {
          paths.forEach(function(path) {
            fs.accessSync(path, fs.F_OK);
          });
          return true;
        } catch (e) {
          // It isn't accessible
          return 'Invalid path. Please ensure this is a valid path to your WAB root.';
        }
      }
    }, {
      when: function(currentAnswers) {
        if (currentAnswers.abort) {
          return false;
        }
        try {
          var appsPath = path.join(currentAnswers.wabRoot, 'server', 'apps');
          var appsDirectories = getDirectories(appsPath);
          if (appsDirectories.length > 0) {
            return true;
          } else {
            this.log(chalk.red('You do not have any WAB apps setup yet. After you create an app, please see the Gruntfile for a todo, or run this generator again.'));
          }
        } catch (e) {
          this.log(chalk.red('You do not have any WAB apps setup yet. After you create an app, please see the Gruntfile for a todo, or run this generator again.'));
        }

      }.bind(this),
      name: 'appDirId',
      type: 'list',
      message: 'Web AppBuilder application:',
      choices: function(currentAnswers) {
        // Always include option for "None"
        var retArray = [{
          name: 'None',
          value: 'None',
          short: 'N'
        }];
        var appsPath = path.join(currentAnswers.wabRoot, 'server', 'apps');
        var appsDirectories = getDirectories(appsPath);
        appsDirectories.forEach(function(appDirectory) {
          try {
            // get the config file, convert to JSON, and read the title property
            var configPath = path.join(currentAnswers.wabRoot, 'server', 'apps', appDirectory, 'config.json');
            var configJson = JSON.parse(fs.readFileSync(configPath, 'utf8'));
            if (configJson.hasOwnProperty('title') && configJson.title !== '') {
              retArray.push({
                name: configJson.title,
                value: appDirectory,
                short: appDirectory
              });
            } else {
              // does not have title property or is empty. Use the app folder name (number) as the name.
              retArray.push({
                name: appDirectory,
                value: appDirectory,
                short: appDirectory
              });
            }
          } catch (e) {
            // Cannot open the config file. Just use the app folder name (number) as the name
            retArray.push({
              name: appDirectory,
              value: appDirectory,
              short: appDirectory
            });
          }
        });
        return retArray;
      }
    }, {
      when: function(currentAnswers) {
        return !currentAnswers.abort;
      },
      type: 'confirm',
      message: 'Would you like to use SASS for CSS preprocessing?',
      name: 'useSass'
    }];

    this.prompt(prompts).then(function(props) {
      this.abort = props.abort;
      this.wabRoot = props.wabRoot;
      this.widgetsType = props.widgetsType;
      this.useSass = props.useSass;
      if (props.appDirId && props.appDirId !== 'None') {
        this.appDirId = props.appDirId;
      } else {
        this.appDirId = false;
      }
      done();
    }.bind(this));
  }

  writing() {
    if (this.abort) {
      return;
    }
    mkdirp('widgets');
    this.config.set('widgetsType', this.widgetsType);
    this.config.set('useSass', this.useSass);

    // gruntConfig:
    if (this.abort) {
      return;
    }

    // Setting up the stemappDir and appDir Gruntfile variables:
    var stemappDir;
    if (this.widgetsType === 'is3d') {
      stemappDir = path.join(this.wabRoot, 'client', 'stemapp3d');
    } else {
      stemappDir = path.join(this.wabRoot, 'client', 'stemapp');
    }
    var appDir = false;
    if (this.appDirId) {
      appDir = path.join(this.wabRoot, 'server', 'apps', this.appDirId);
    }
    if (isWin) {
      // this hack is needed to ensure paths are not escaped when injected into Gruntfile
      stemappDir = stemappDir.replace(/\\/g, '/');
      if (appDir) {
        appDir = appDir.replace(/\\/g, '/');
      }
    }
    this.gruntfile.insertVariable('stemappDir', '"' + stemappDir + '"');
    if (appDir) {
      this.gruntfile.insertVariable('appDir', '"' + appDir + '"');
    } else {
      this.gruntfile.insertVariable('appDir', '"TODO - AFTER CREATING AN APP, PLEASE PUT PATH HERE AND INSERT ENTRY IN SYNC.MAIN.FILES BELOW."');
    }

    // SYNC CONFIG
    var syncConfig = '{ main: { verbose: true, files: [';
    var filesPrefix = '{cwd: \'dist/\', src: \'**\', dest: ';
    syncConfig = syncConfig + filesPrefix + 'stemappDir }';
    if (appDir) {
      syncConfig = syncConfig + ',' + filesPrefix + 'appDir }';
    }
    syncConfig = syncConfig + ']';
    syncConfig = syncConfig + '} }';

    this.gruntfile.insertConfig('sync', syncConfig);

    // BABEL CONFIG
    var babelConfig = {
      main: {
        files: [{
          expand: true,
          src: [
              'widgets/*.js',
      'widgets/**/*.js',
      'widgets/**/**/*.js',
      'widgets/!**/**/nls/*.js',
      'themes/*.js',
      'themes/**/*.js',
      'themes/**/**/*.js',
      'themes/!**/**/nls/*.js'
          ],
          dest: 'dist/'
        }]
      }
    };
    this.gruntfile.insertConfig('babel', JSON.stringify(babelConfig));

    // WATCH CONFIG
    this.gruntfile.insertConfig('watch', `{
      main: {
        files: ['widgets/**', 'themes/**'],
        tasks: ['clean', ${(this.useSass ? '\'sass\', ' : '')}'babel', 'copy', 'sync'],
        options: {
          spawn: false,
          atBegin: true
        }
      }
    }`);

    // COPY CONFIG
    this.gruntfile.insertConfig('copy', JSON.stringify({
      main: {
        src: [
          'widgets/**/**.html',
          'widgets/**/**.json',
          'widgets/**/**.css',
          'widgets/**/images/**',
          'widgets/**/nls/**',
    'themes/**/**.html',
    'themes/**/**.json',
    'themes/**/**.css',
    'themes/**/images/**',
    'themes/**/nls/**'
        ],
        dest: 'dist/',
        expand: true
      }
    }));

    // CLEAN CONFIG
    this.gruntfile.insertConfig('clean', JSON.stringify({
      dist: {
        src: 'dist/**'
      }
    }));

    // SASS CONFIG
    if(this.useSass) {
      this.gruntfile.insertConfig('sass', `{
        dist: {
          options: {
            sourceMap: true,
          },

          files: [{
            expand: true,
            src: ['widgets/**/*.scss'],
            rename: function(dest, src) {
              return src.replace('scss', 'css')
            }
          }]
        }
      }`);
      this.gruntfile.loadNpmTasks('grunt-sass');
    }

    // load tasks
    this.gruntfile.loadNpmTasks('grunt-babel');
    this.gruntfile.loadNpmTasks('grunt-contrib-clean');
    this.gruntfile.loadNpmTasks('grunt-contrib-copy');
    this.gruntfile.loadNpmTasks('grunt-contrib-watch');
    this.gruntfile.loadNpmTasks('grunt-sync');

    // register tasks
    this.gruntfile.registerTask('default', ['watch']);

    // projectFiles:
    if (this.abort) {
      return;
    }
    this.fs.copyTpl(
      this.templatePath('editorconfig'),
      this.destinationPath('.editorconfig')
    );
    this.fs.copyTpl(
      this.templatePath('babelrc'),
      this.destinationPath('.babelrc')
    );
    fs.writeFileSync('Gruntfile.js', this.gruntfile.toString());
  }

  install() {
    if (this.abort || this.options['skip-install']) {
      return;
    }
    this.npmInstall([
      'babel-plugin-transform-es2015-modules-simple-amd',
      'babel-preset-es2015-without-strict',
      'babel-preset-stage-0',
      'grunt',
      'grunt-contrib-watch',
      'grunt-sync',
      'grunt-babel',
      'babel-core',
      'grunt-contrib-clean',
      'grunt-contrib-copy',
      'grunt-sass'
    ], {
      'saveDev': true
    });
  }
}
<commit_msg>re-added changes from bad merge<commit_after>'use strict';
const path = require('path');
const Generator = require('yeoman-generator');
const yosay = require('yosay');
const chalk = require('chalk');
const isWin = process.platform === 'win32';
const homedir = (isWin) ? process.env.HOMEPATH : process.env.HOME;
const fs = require('fs');
const mkdirp = require('mkdirp');
const GruntfileEditor = require('gruntfile-editor');

function getDirectories(srcpath) {
  return fs.readdirSync(srcpath).filter(function(file) {
    return fs.statSync(path.join(srcpath, file)).isDirectory();
  });
}

module.exports = class extends Generator {
  initializing() {
    this.gruntfile = new GruntfileEditor();
    // check for existence of package.json
    try {
      fs.accessSync('./package.json', fs.F_OK);
      this.hasPackageJson = true;
    } catch (e) {
      this.hasPackageJson = false;
    }
  }

  prompting() {
    var done = this.async();
    var self = this;

    // Have Yeoman greet the user.
    this.log(yosay('Welcome to the ArcGIS Web AppBuilder generator!'));
    this.log(chalk.yellow('These generators should be run in the root folder of your project.'));

    var prompts = [{
      name: 'abort',
      type: 'confirm',
      default: true,
      message: 'No package.json found. Would you like to abort and run npm init first?',
      when: function() {
        return !self.hasPackageJson;
      }
    }, {
      type: 'list',
      choices: [
        {
        value: 'is2d',
        name: '2D'
        },
        {
        value: 'is3d',
        name: '3D'
        }
      ],
      name: 'widgetsType',
      message: 'Type of widget(s) to be generated:',
      when: function(currentAnswers) {
        return !currentAnswers.abort;
      }
    }, {
      when: function(currentAnswers) {
        return !currentAnswers.abort;
      },
      name: 'wabRoot',
      message: 'Web AppBuilder install root:',
      'default': function(currentAnswers) {
        return path.join(homedir, 'WebAppBuilderForArcGIS');
      },
      validate: function(wabPath) {
        // make sure input directory and the following paths exist:
        // * server
        // * client
        var paths = [wabPath, path.join(wabPath, 'server'), path.join(wabPath, 'client')];
        try {
          paths.forEach(function(path) {
            fs.accessSync(path, fs.F_OK);
          });
          return true;
        } catch (e) {
          // It isn't accessible
          return 'Invalid path. Please ensure this is a valid path to your WAB root.';
        }
      }
    }, {
      when: function(currentAnswers) {
        if (currentAnswers.abort) {
          return false;
        }
        try {
          var appsPath = path.join(currentAnswers.wabRoot, 'server', 'apps');
          var appsDirectories = getDirectories(appsPath);
          if (appsDirectories.length > 0) {
            return true;
          } else {
            this.log(chalk.red('You do not have any WAB apps setup yet. After you create an app, please see the Gruntfile for a todo, or run this generator again.'));
          }
        } catch (e) {
          this.log(chalk.red('You do not have any WAB apps setup yet. After you create an app, please see the Gruntfile for a todo, or run this generator again.'));
        }

      }.bind(this),
      name: 'appDirId',
      type: 'list',
      message: 'Web AppBuilder application:',
      choices: function(currentAnswers) {
        // Always include option for "None"
        var retArray = [{
          name: 'None',
          value: 'None',
          short: 'N'
        }];
        var appsPath = path.join(currentAnswers.wabRoot, 'server', 'apps');
        var appsDirectories = getDirectories(appsPath);
        appsDirectories.forEach(function(appDirectory) {
          try {
            // get the config file, convert to JSON, and read the title property
            var configPath = path.join(currentAnswers.wabRoot, 'server', 'apps', appDirectory, 'config.json');
            var configJson = JSON.parse(fs.readFileSync(configPath, 'utf8'));
            if (configJson.hasOwnProperty('title') && configJson.title !== '') {
              retArray.push({
                name: configJson.title,
                value: appDirectory,
                short: appDirectory
              });
            } else {
              // does not have title property or is empty. Use the app folder name (number) as the name.
              retArray.push({
                name: appDirectory,
                value: appDirectory,
                short: appDirectory
              });
            }
          } catch (e) {
            // Cannot open the config file. Just use the app folder name (number) as the name
            retArray.push({
              name: appDirectory,
              value: appDirectory,
              short: appDirectory
            });
          }
        });
        return retArray;
      }
    }, {
      when: function(currentAnswers) {
        return !currentAnswers.abort;
      },
      type: 'confirm',
      message: 'Would you like to use SASS for CSS preprocessing?',
      name: 'useSass'
    }];

    this.prompt(prompts).then(function(props) {
      this.abort = props.abort;
      this.wabRoot = props.wabRoot;
      this.widgetsType = props.widgetsType;
      this.useSass = props.useSass;
      if (props.appDirId && props.appDirId !== 'None') {
        this.appDirId = props.appDirId;
      } else {
        this.appDirId = false;
      }
      done();
    }.bind(this));
  }

  writing() {
    if (this.abort) {
      return;
    }
    mkdirp('widgets');
    this.config.set('widgetsType', this.widgetsType);
    this.config.set('useSass', this.useSass);

    // gruntConfig:
    if (this.abort) {
      return;
    }

    // Setting up the stemappDir and appDir Gruntfile variables:
    var stemappDir;
    if (this.widgetsType === 'is3d') {
      stemappDir = path.join(this.wabRoot, 'client', 'stemapp3d');
    } else {
      stemappDir = path.join(this.wabRoot, 'client', 'stemapp');
    }
    var appDir = false;
    if (this.appDirId) {
      appDir = path.join(this.wabRoot, 'server', 'apps', this.appDirId);
    }
    if (isWin) {
      // this hack is needed to ensure paths are not escaped when injected into Gruntfile
      stemappDir = stemappDir.replace(/\\/g, '/');
      if (appDir) {
        appDir = appDir.replace(/\\/g, '/');
      }
    }
    this.gruntfile.insertVariable('stemappDir', '"' + stemappDir + '"');
    if (appDir) {
      this.gruntfile.insertVariable('appDir', '"' + appDir + '"');
    } else {
      this.gruntfile.insertVariable('appDir', '"TODO - AFTER CREATING AN APP, PLEASE PUT PATH HERE AND INSERT ENTRY IN SYNC.MAIN.FILES BELOW."');
    }

    // SYNC CONFIG
    var syncConfig = '{ main: { verbose: true, files: [';
    var filesPrefix = '{cwd: \'dist/\', src: \'**\', dest: ';
    syncConfig = syncConfig + filesPrefix + 'stemappDir }';
    if (appDir) {
      syncConfig = syncConfig + ',' + filesPrefix + 'appDir }';
    }
    syncConfig = syncConfig + ']';
    syncConfig = syncConfig + '} }';

    this.gruntfile.insertConfig('sync', syncConfig);

    // BABEL CONFIG
    var babelConfig = {
      main: {
        files: [{
          expand: true,
          src: [
              'widgets/*.js',
              'widgets/**/*.js',
              'widgets/**/**/*.js',
              'widgets/!**/**/nls/*.js',
              'themes/*.js',
              'themes/**/*.js',
              'themes/**/**/*.js',
              'themes/!**/**/nls/*.js'
          ],
          dest: 'dist/'
        }]
      }
    };
    this.gruntfile.insertConfig('babel', JSON.stringify(babelConfig));

    // WATCH CONFIG
    this.gruntfile.insertConfig('watch', `{
      main: {
        files: ['widgets/**', 'themes/**'],
        tasks: ['clean', ${(this.useSass ? '\'sass\', ' : '')}'babel', 'copy', 'sync'],
        options: {
          spawn: false,
          atBegin: true,
          livereload: true
        }
      }
    }`);

    // COPY CONFIG
    this.gruntfile.insertConfig('copy', JSON.stringify({
      main: {
        src: [
          'widgets/**/**.html',
          'widgets/**/**.json',
          'widgets/**/**.css',
          'widgets/**/images/**',
          'widgets/**/nls/**',
          'themes/**/**.html',
          'themes/**/**.json',
          'themes/**/**.css',
          'themes/**/images/**',
          'themes/**/nls/**'
        ],
        dest: 'dist/',
        expand: true
      }
    }));

    // CLEAN CONFIG
    this.gruntfile.insertConfig('clean', JSON.stringify({
      dist: {
        src: 'dist/**'
      }
    }));

    // SASS CONFIG
    if(this.useSass) {
      this.gruntfile.insertConfig('sass', `{
        dist: {
          options: {
            sourceMap: true,
          },

          files: [{
            expand: true,
            src: ['widgets/**/*.scss'],
            rename: function(dest, src) {
              return src.replace('scss', 'css')
            }
          }]
        }
      }`);
      this.gruntfile.loadNpmTasks('grunt-sass');
    }

    // load tasks
    this.gruntfile.loadNpmTasks('grunt-babel');
    this.gruntfile.loadNpmTasks('grunt-contrib-clean');
    this.gruntfile.loadNpmTasks('grunt-contrib-copy');
    this.gruntfile.loadNpmTasks('grunt-contrib-watch');
    this.gruntfile.loadNpmTasks('grunt-sync');

    // register tasks
    this.gruntfile.registerTask('default', ['watch']);

    // projectFiles:
    if (this.abort) {
      return;
    }
    this.fs.copyTpl(
      this.templatePath('editorconfig'),
      this.destinationPath('.editorconfig')
    );
    this.fs.copyTpl(
      this.templatePath('babelrc'),
      this.destinationPath('.babelrc')
    );
    fs.writeFileSync('Gruntfile.js', this.gruntfile.toString());
  }

  install() {
    if (this.abort || this.options['skip-install']) {
      return;
    }
    this.npmInstall([
      'babel-plugin-transform-es2015-modules-simple-amd',
      'babel-preset-es2015-without-strict',
      'babel-preset-stage-0',
      'grunt',
      'grunt-contrib-watch',
      'grunt-sync',
      'grunt-babel',
      'babel-core',
      'grunt-contrib-clean',
      'grunt-contrib-copy',
      'grunt-sass'
    ], {
      'saveDev': true
    });
  }
}
<|endoftext|>


<commit_before>'use strict';
var util    = require('util');
var path    = require('path');
var yeoman  = require('yeoman-generator');
var fs      = require('fs-extra');
var chalk   = require('chalk');

var EvolutionGenerator = yeoman.generators.Base.extend({
  init: function () {
    this.prompts  = [];
    this.pkg      = require('../package.json');

    this.on('end', function () {
      this.config.save();
      this.log.info('Running ' + chalk.yellow('bower install') + ' & ' + chalk.yellow('npm install') + ' for you to install the required dependencies. If this fails, try running the command yourself.');
      if (!this.options['skip-install']) {
        this.installDependencies({
          bower:        true,
          npm:          true,
          skipMessage:  true,
          callback:     function() {
            this.log.ok('All done! Run ' + chalk.yellow('grunt build:dev') + ' and ' + chalk.yellow('grunt watch') + ' to get started!');
          }.bind(this)
        });
      }
    });
  },
  promptForName: function() {
    var existing = function() {
      try {
        var bower    = require(path.join(this.env.cwd, 'bower.json'));

        return bower.name;
      } catch(e) {};
    }.bind(this);

    this.prompts.push({
      required: true,
      type:     'text',
      name:     'projName',
      message:  'Project Name (e.g. MySite)',
      default:  function() {
        return existing() || path.basename(this.env.cwd);
      }.bind(this)
    });
  },
  promptForShortName: function() {
    var existing = function() {
      try {
        var bower    = require(path.join(this.env.cwd, 'bower.json'));

        return bower.shortName;
      } catch(e) {};
    }.bind(this);

    this.prompts.push({
      required: true,
      type:     'text',
      name:     'projShortName',
      message:  'Project short name (e.g. ms)',
      default:  function() {
        return existing() || path.basename(this.env.cwd).replace(/[^A-Z]/g, '').toLowerCase();
      }.bind(this)
    });
  },
  promptForAuthorName: function() {
    var existing = function() {
      try {
        var bower    = require(path.join(this.env.cwd, 'bower.json'));

        return bower.author.name;
      } catch(e) {};
    }.bind(this);

    this.prompts.push({
      type:     'text',
      name:     'authorName',
      message:  'Author Name (e.g. John Smith)',
      default:  function() {
        return existing() || '';
      }.bind(this)
    });
  },
  promptForAuthorURI: function() {
    var existing = function() {
      try {
        var bower    = require(path.join(this.env.cwd, 'bower.json'));

        return bower.author.url
      } catch(e) {};
    }.bind(this);

    this.prompts.push({
      type:     'text',
      name:     'authorURI',
      message:  'Author URI (e.g. www.johnsmith.com)',
      default:  function() {
        return existing() || '';
      }.bind(this)
    });
  },
  promptForDescription: function() {
    var existing = function() {
      try {
        var bower    = require(path.join(this.env.cwd, 'bower.json'));

        return bower.description;
      } catch(e) {};
    }.bind(this);

    this.prompts.push({
      type:     'text',
      name:     'projDescription',
      message:  'Project Description',
      default:  function() {
        return existing() || '';
      }.bind(this)
    });
  },
  promptForVersion: function() {
    var existing = function() {
      try {
        var bower    = require(path.join(this.env.cwd, 'bower.json'));

        return bower.version;
      } catch(e) {};
    }.bind(this);

    this.prompts.push({
      type:     'text',
      name:     'projVersion',
      message:  'Project version',
      default:  function() {
        return existing() || '0.1.0';
      }.bind(this)
    });
  },
  promptForWeb: function() {
    this.prompts.push({
      type:     'text',
      name:     'web',
      message:  'WordPress directory',
      default:  'web'
    });
  },
  promptForChild: function() {

    var existing = function(response) {
      var childLoc = path.join(response.web, 'wp-content/themes/', response.projShortName + '-theme');

      try {
        var style = this.readFileAsString(path.join(childLoc, 'style.css'));

        if (style.length) {
          return true;
        }
      } catch(e) {}
    }.bind(this);

    this.prompts.push({
      when:     function(response) {
        return existing(response);
      },
      type:     'confirm',
      name:     'writeChild',
      message:  'Overwrite existing child theme?',
      default:  'no'
    });
  },
  ask: function() {
    var done = this.async();

    // have Yeoman greet the user.
    console.log(this.yeoman);

    this.prompt(this.prompts, function(props) {
      this.props = props;

      done();
    }.bind(this));
  },
  ready: function() {
    this.log.write('\n');
    this.log.info( chalk.green('Here we go!') );
  },
  writeProjectFiles: function() {
    this.log.info('Writing project files...');

    this.copy('Gruntfile.js', 'Gruntfile.js');

    this.template('editorconfig', '.editorconfig');
    this.template('gitignore',    '.gitignore');
    this.template('jshintrc',     '.jshintrc');
    this.template('bower.json',   'bower.json');
    this.template('package.json', 'package.json');
    //this.template('README.md',    'README.md');
  },
  writeThemeFiles:  function() {
    this.log.info('Writing theme files...');

    var existing = function(location) {
      try {
        var style = this.readFileAsString(path.join(location, 'style.css'));

        if (style.length) {
          return true;
        } else {
          return false;
        }
      } catch(e) {}
    }.bind(this);

    var parLoc      = path.join(this.props.web, 'wp-content/themes/evolution-parent-theme');
    var childLoc    = path.join(this.props.web, 'wp-content/themes/', this.props.projShortName + '-theme');
    var writeChild  = !existing(childLoc) || this.props.writeChild;

    this.directory('themes/evolution-parent-theme', parLoc);

    if (writeChild) {
      this.directory('themes/evolution-child-theme', childLoc);
    }
  },
  cleanUp: function() {
    this.log.info('Cleaning up...');

    var root  = path.join(this.props.web, 'wp-content/themes/', this.props.projShortName + '-theme');
    var files = this.expandFiles('**/.gitkeep', {dot: true, cwd: root});

    for (var i = 0; i < files.length; i++) {
      var file = path.join(root, files[i]);

      fs.remove(file, function(err) {
        if (err) return this.log.info( chalk.red(err) );
      });
    }
  }
});

module.exports = EvolutionGenerator;<commit_msg>Updated shortname to include extension<commit_after>'use strict';
var util    = require('util');
var path    = require('path');
var yeoman  = require('yeoman-generator');
var fs      = require('fs-extra');
var chalk   = require('chalk');

var EvolutionGenerator = yeoman.generators.Base.extend({
  init: function () {
    this.prompts  = [];
    this.pkg      = require('../package.json');

    this.on('end', function () {
      this.config.save();
      this.log.info('Running ' + chalk.yellow('bower install') + ' & ' + chalk.yellow('npm install') + ' for you to install the required dependencies. If this fails, try running the command yourself.');
      if (!this.options['skip-install']) {
        this.installDependencies({
          bower:        true,
          npm:          true,
          skipMessage:  true,
          callback:     function() {
            this.log.ok('All done! Run ' + chalk.yellow('grunt build:dev') + ' and ' + chalk.yellow('grunt watch') + ' to get started!');
          }.bind(this)
        });
      }
    });
  },
  promptForName: function() {
    var existing = function() {
      try {
        var bower    = require(path.join(this.env.cwd, 'bower.json'));

        return bower.name;
      } catch(e) {};
    }.bind(this);

    this.prompts.push({
      required: true,
      type:     'text',
      name:     'projName',
      message:  'Project Name (e.g. MySite)',
      default:  function() {
        return existing() || path.basename(this.env.cwd);
      }.bind(this)
    });
  },
  promptForShortName: function() {
    var existing = function() {
      try {
        var bower    = require(path.join(this.env.cwd, 'bower.json'));

        return bower.shortName;
      } catch(e) {};
    }.bind(this);

    this.prompts.push({
      required: true,
      type:     'text',
      name:     'projShortName',
      message:  'Project short name (e.g. mscom)',
      default:  function() {
        var ext = path.extname(this.env.cwd);
        var extname = ext.replace('.','');
        var base = path.basename(this.env.cwd).replace(ext, '');
        var basename = ( /[A-Z]/.test(base) ) ? base.replace(/[^A-Z]/g, '') : base.charAt(0);

        return existing() || ( basename + extname ).toLowerCase();
      }.bind(this)
    });
  },
  promptForAuthorName: function() {
    var existing = function() {
      try {
        var bower    = require(path.join(this.env.cwd, 'bower.json'));

        return bower.author.name;
      } catch(e) {};
    }.bind(this);

    this.prompts.push({
      type:     'text',
      name:     'authorName',
      message:  'Author Name (e.g. John Smith)',
      default:  function() {
        return existing() || '';
      }.bind(this)
    });
  },
  promptForAuthorURI: function() {
    var existing = function() {
      try {
        var bower    = require(path.join(this.env.cwd, 'bower.json'));

        return bower.author.url
      } catch(e) {};
    }.bind(this);

    this.prompts.push({
      type:     'text',
      name:     'authorURI',
      message:  'Author URI (e.g. www.johnsmith.com)',
      default:  function() {
        return existing() || '';
      }.bind(this)
    });
  },
  promptForDescription: function() {
    var existing = function() {
      try {
        var bower    = require(path.join(this.env.cwd, 'bower.json'));

        return bower.description;
      } catch(e) {};
    }.bind(this);

    this.prompts.push({
      type:     'text',
      name:     'projDescription',
      message:  'Project Description',
      default:  function() {
        return existing() || '';
      }.bind(this)
    });
  },
  promptForVersion: function() {
    var existing = function() {
      try {
        var bower    = require(path.join(this.env.cwd, 'bower.json'));

        return bower.version;
      } catch(e) {};
    }.bind(this);

    this.prompts.push({
      type:     'text',
      name:     'projVersion',
      message:  'Project version',
      default:  function() {
        return existing() || '0.1.0';
      }.bind(this)
    });
  },
  promptForWeb: function() {
    this.prompts.push({
      type:     'text',
      name:     'web',
      message:  'WordPress directory',
      default:  'web'
    });
  },
  promptForChild: function() {

    var existing = function(response) {
      var childLoc = path.join(response.web, 'wp-content/themes/', response.projShortName + '-theme');

      try {
        var style = this.readFileAsString(path.join(childLoc, 'style.css'));

        if (style.length) {
          return true;
        }
      } catch(e) {}
    }.bind(this);

    this.prompts.push({
      when:     function(response) {
        return existing(response);
      },
      type:     'confirm',
      name:     'writeChild',
      message:  'Overwrite existing child theme?',
      default:  'no'
    });
  },
  ask: function() {
    var done = this.async();

    // have Yeoman greet the user.
    console.log(this.yeoman);

    this.prompt(this.prompts, function(props) {
      this.props = props;

      done();
    }.bind(this));
  },
  ready: function() {
    this.log.write('\n');
    this.log.info( chalk.green('Here we go!') );
  },
  writeProjectFiles: function() {
    this.log.info('Writing project files...');

    this.copy('Gruntfile.js', 'Gruntfile.js');

    this.template('editorconfig', '.editorconfig');
    this.template('gitignore',    '.gitignore');
    this.template('jshintrc',     '.jshintrc');
    this.template('bower.json',   'bower.json');
    this.template('package.json', 'package.json');
    //this.template('README.md',    'README.md');
  },
  writeThemeFiles:  function() {
    this.log.info('Writing theme files...');

    var existing = function(location) {
      try {
        var style = this.readFileAsString(path.join(location, 'style.css'));

        if (style.length) {
          return true;
        } else {
          return false;
        }
      } catch(e) {}
    }.bind(this);

    var parLoc      = path.join(this.props.web, 'wp-content/themes/evolution-parent-theme');
    var childLoc    = path.join(this.props.web, 'wp-content/themes/', this.props.projShortName + '-theme');
    var writeChild  = !existing(childLoc) || this.props.writeChild;

    this.directory('themes/evolution-parent-theme', parLoc);

    if (writeChild) {
      this.directory('themes/evolution-child-theme', childLoc);
    }
  },
  cleanUp: function() {
    this.log.info('Cleaning up...');

    var root  = path.join(this.props.web, 'wp-content/themes/', this.props.projShortName + '-theme');
    var files = this.expandFiles('**/.gitkeep', {dot: true, cwd: root});

    for (var i = 0; i < files.length; i++) {
      var file = path.join(root, files[i]);

      fs.remove(file, function(err) {
        if (err) return this.log.info( chalk.red(err) );
      });
    }
  }
});

module.exports = EvolutionGenerator;<|endoftext|>


<commit_before>$(function() {
    $('#field-format').change( function() {
        $( "#datadict_post" ).trigger( "click" );
    });
});
<commit_msg>format selector triggers btn without datadict reference<commit_after>$(function() {
    $('#field-format').change( function() {
        $( "#resource_save" ).trigger( "click" );
    });
});
<|endoftext|>


<commit_before>/*
 * This controller will take care of parsing the data from json file
 */

// var jsonFileUrl = drupalSettings.path.baseUrl + 'modules/custom/dashpage/angular/mildderjson.json';
var jsonFileUrl = drupalSettings.path.baseUrl + 'manage/term/client/json';

var pageInfoBaseControllers = angular.module('pageInfoBase', ['flexxiaChartsnewjs', 'ngMaterial', 'datatables', 'ngResource', 'datatables.buttons']);

pageInfoBaseControllers.controller('PageInfoBaseController', ['$scope', '$http', '$mdDialog', '$mdMedia', '$filter', '$sce',
  function($scope, $http, $mdDialog, $mdMedia, $filter, $sce) {
    $scope.$sce = $sce;
    $scope.loading = true;

    angular.element(document).ready(function() {
      $http.get(jsonFileUrl).success(function(data) {

        // when manageinfo call table page, $scope.pageData use drupalSettings variable
        if ((drupalSettings.path.currentPath.indexOf("manageinfo/") > -1) && (typeof drupalSettings.manageinfo.manageinfoTable.jsonContentData !== 'undefined')) {
          $scope.pageData = drupalSettings.manageinfo.manageinfoTable.jsonContentData;
        }
        else {
          $scope.pageData = data;
        }
      }).catch(function(err) {
        // Log error somehow.
      }).finally(function() {
        // Hide loading spinner whether our call succeeded or failed.
        $scope.loading = false;
      });
    });

  }
]);

/* Datatables Controller */
pageInfoBaseControllers.controller('AngularDataTables', DataTablesCtrl);
function DataTablesCtrl($http, $scope, $resource, DTOptionsBuilder, DTColumnDefBuilder, DTDefaultOptions) {
  angular.forEach($scope.pageData.contentSection, function(value) {
    if(value.type == "commonTable") {
      $scope.tableSettings = value.middle.middleMiddle.middleMiddleMiddle.tableSettings;
    }
  });
  $scope.dtOptionsCommonTables = {
    paginationType: $scope.tableSettings.paginationType,
    bInfo: false,
    bPaginate: $scope.tableSettings.pagination,
    bFilter: $scope.tableSettings.searchFilter,
    language: {
      "searchPlaceholder": $scope.tableSettings.searchPlaceholder,
      "sSearch": "",
      "oPaginate": {
        "sFirst": "",
        "sLast": "",
        "sNext": "",
        "sPrevious": "",
      },
      "sLengthMenu": '<select>' + '<option value="10">SHOW 10</option>' + '<option value="20">SHOW 20</option>' + '<option value="30">SHOW 30</option>' + '<option value="40">SHOW 40</option>' + '<option value="50">SHOW 50</option>' + '<option value="-1">SHOW All</option>' + '</select> '
    },
  };
}

// code to load 2 apps
jQuery(document).ready(function() {
  if(document.getElementById('navInfoBase') !== null) {
    angular.bootstrap(document.getElementById("pageInfoBase"), ['pageInfoBase']);
  }
});
<commit_msg>clean code<commit_after>/*
 * This controller will take care of parsing the data from json file
 */

// var jsonFileUrl = drupalSettings.path.baseUrl + 'modules/custom/dashpage/angular/mildderjson.json';
var jsonFileUrl = drupalSettings.path.baseUrl + 'manage/term/client/json';

var pageInfoBaseControllers = angular.module('pageInfoBase', ['flexxiaChartsnewjs', 'ngMaterial', 'datatables', 'ngResource', 'datatables.buttons']);

pageInfoBaseControllers.controller('PageInfoBaseController', ['$scope', '$http', '$mdDialog', '$mdMedia', '$filter', '$sce',
  function($scope, $http, $mdDialog, $mdMedia, $filter, $sce) {
    $scope.$sce = $sce;

    angular.element(document).ready(function() {
      $http.get(jsonFileUrl).success(function(data) {

        // when manageinfo call table page, $scope.pageData use drupalSettings variable
        if ((drupalSettings.path.currentPath.indexOf("manageinfo/") > -1) && (typeof drupalSettings.manageinfo.manageinfoTable.jsonContentData !== 'undefined')) {
          $scope.pageData = drupalSettings.manageinfo.manageinfoTable.jsonContentData;
        }
        else {
          $scope.pageData = data;
        }
      }).catch(function(err) {
        // Log error somehow.
      }).finally(function() {

      });
    });

  }
]);

/* Datatables Controller */
pageInfoBaseControllers.controller('AngularDataTables', DataTablesCtrl);
function DataTablesCtrl($http, $scope, $resource, DTOptionsBuilder, DTColumnDefBuilder, DTDefaultOptions) {
  angular.forEach($scope.pageData.contentSection, function(value) {
    if(value.type == "commonTable") {
      $scope.tableSettings = value.middle.middleMiddle.middleMiddleMiddle.tableSettings;
    }
  });
  $scope.dtOptionsCommonTables = {
    paginationType: $scope.tableSettings.paginationType,
    bInfo: false,
    bPaginate: $scope.tableSettings.pagination,
    bFilter: $scope.tableSettings.searchFilter,
    language: {
      "searchPlaceholder": $scope.tableSettings.searchPlaceholder,
      "sSearch": "",
      "oPaginate": {
        "sFirst": "",
        "sLast": "",
        "sNext": "",
        "sPrevious": "",
      },
      "sLengthMenu": '<select>' + '<option value="10">SHOW 10</option>' + '<option value="20">SHOW 20</option>' + '<option value="30">SHOW 30</option>' + '<option value="40">SHOW 40</option>' + '<option value="50">SHOW 50</option>' + '<option value="-1">SHOW All</option>' + '</select> '
    },
  };
}

// code to load 2 apps
jQuery(document).ready(function() {
  if(document.getElementById('navInfoBase') !== null) {
    angular.bootstrap(document.getElementById("pageInfoBase"), ['pageInfoBase']);
  }
});
<|endoftext|>


<commit_before>'use strict';
var util = require('util');
var path = require('path');
var yeoman = require('yeoman-generator');
var yosay = require('yosay');
var chalk = require('chalk');
var compact = require('lodash.compact');
var camelize = require('camelize');

var NpmModuleGenerator = yeoman.generators.Base.extend({
  init: function () {

    // set instance variables for templating
    this.pkg = require('../package.json');
    this.username = 'metaraine';
    this.camelize = camelize;

    this.on('end', function () {
      if (!this.options['skip-install']) {
        this.installDependencies();
      }
    });
  },

  askFor: function () {
    var done = this.async();

    // Have Yeoman greet the user.
    this.log(yosay('Welcome to the marvelous NpmModule generator!'));

    var prompts = [{
      type: 'text',
      name: 'project',
      message: 'project name',
      default: path.basename(this.env.cwd)
    },{
      type: 'text',
      name: 'description',
      message: 'project description'
    },{
      type: 'text',
      name: 'keywords',
      message: 'keywords (comma-separated)'
    }];

    this.prompt(prompts, function (props) {
      // convert the keywords string into an array
      props.keywords = compact( // remove empty values
        props.keywords.split(',')
        .map(function(s) { return s.trim(); }) // trim
      );

      // prettify the keywords to display each one on a separate line with correct indentation in the package.json
      props.prettyKeywords = JSON.stringify(props.keywords)
        .replace(/([[,])/g, '$1\n    ') // add '    \n' after each item
        .replace(/]/g, '\n  ]') // add a newline before the closing ]

      // assign props to instance for use in templating
      this.props = props;

      done();
    }.bind(this));
  },

  app: function () {
    this.copy('_editorconfig', '.editorconfig');
    this.copy('_gitattributes', '.gitattributes');
    this.copy('_gitignore', '.gitignore');
    this.copy('_jshintrc', '.jshintrc');
    this.copy('_package.json', 'package.json');
    this.copy('_travis.yml', 'travis.yml');
    this.copy('index.js', 'index.js');
    this.copy('license', 'license');
    this.copy('readme.md', 'readme.md');
    this.copy('test.js', 'test.js');
  }
});

module.exports = NpmModuleGenerator;
<commit_msg>Copy _travis.yml to .travis.yml (fix)<commit_after>'use strict';
var util = require('util');
var path = require('path');
var yeoman = require('yeoman-generator');
var yosay = require('yosay');
var chalk = require('chalk');
var compact = require('lodash.compact');
var camelize = require('camelize');

var NpmModuleGenerator = yeoman.generators.Base.extend({
  init: function () {

    // set instance variables for templating
    this.pkg = require('../package.json');
    this.username = 'metaraine';
    this.camelize = camelize;

    this.on('end', function () {
      if (!this.options['skip-install']) {
        this.installDependencies();
      }
    });
  },

  askFor: function () {
    var done = this.async();

    // Have Yeoman greet the user.
    this.log(yosay('Welcome to the marvelous NpmModule generator!'));

    var prompts = [{
      type: 'text',
      name: 'project',
      message: 'project name',
      default: path.basename(this.env.cwd)
    },{
      type: 'text',
      name: 'description',
      message: 'project description'
    },{
      type: 'text',
      name: 'keywords',
      message: 'keywords (comma-separated)'
    }];

    this.prompt(prompts, function (props) {
      // convert the keywords string into an array
      props.keywords = compact( // remove empty values
        props.keywords.split(',')
        .map(function(s) { return s.trim(); }) // trim
      );

      // prettify the keywords to display each one on a separate line with correct indentation in the package.json
      props.prettyKeywords = JSON.stringify(props.keywords)
        .replace(/([[,])/g, '$1\n    ') // add '    \n' after each item
        .replace(/]/g, '\n  ]') // add a newline before the closing ]

      // assign props to instance for use in templating
      this.props = props;

      done();
    }.bind(this));
  },

  app: function () {
    this.copy('_editorconfig', '.editorconfig');
    this.copy('_gitattributes', '.gitattributes');
    this.copy('_gitignore', '.gitignore');
    this.copy('_jshintrc', '.jshintrc');
    this.copy('_travis.yml', '.travis.yml');
    this.copy('_package.json', 'package.json');
    this.copy('index.js', 'index.js');
    this.copy('license', 'license');
    this.copy('readme.md', 'readme.md');
    this.copy('test.js', 'test.js');
  }
});

module.exports = NpmModuleGenerator;
<|endoftext|>


<commit_before>'use strict';
var util = require('util');
var path = require('path');
var fs = require('fs-extra');
var yeoman = require('yeoman-generator');

var NopejsGenerator = module.exports = function NopejsGenerator(args, options, config) {
  yeoman.generators.Base.apply(this, arguments);

  this.on('end', function () {
    this.installDependencies({ skipInstall: options['skip-install'] });
  });

  this.pkg = JSON.parse(this.readFileAsString(path.join(__dirname, '../package.json')));
};

util.inherits(NopejsGenerator, yeoman.generators.Base);

NopejsGenerator.prototype.askFor = function askFor() {
  var cb = this.async();

  // have Yeoman greet the user.
  console.log(this.yeoman);

  var prompts = [
  //]; /*
    {
      name: 'packageName',
      message: 'What would you like to name this package? (this will be slugified)'
    },
    {
      name: 'siteName',
      message: 'What would you like to call your site?'
    },
    {
      name: 'siteAuthor',
      message: 'Who is the site author?'
    }
  ];
//*/
  this.prompt(prompts, function (props) {
    ///*
    this.packageName = props.packageName;
    this.siteName = props.siteName;
    this.siteAuthor = props.siteAuthor;
    //*/

    cb();
  }.bind(this));
};

NopejsGenerator.prototype.app = function app() {
  this.copy('_package.json', 'package.json');
  this.copy('_bower.json', 'bower.json');
  this.copy('.bowerrc');
};

NopejsGenerator.prototype.gruntfile = function gruntfile() {
  this.template('Gruntfile.js');
};

NopejsGenerator.prototype.projectfiles = function projectfiles() {
  this.copy('editorconfig', '.editorconfig');
  this.copy('jshintrc', '.jshintrc');
};

NopejsGenerator.prototype.nopejsFiles = function nopejsFiles() {
    this.bowerInstall("shakeelmohamed/nopejs", { save: true }, function(){
      fs.copy('./bower_components/nopejs', './', function (err) {
        if (err) {
          console.error(err);
        } else {
          console.log("success!");
        }
      });
    });
};<commit_msg>We can just remove the bower components afterwards :)<commit_after>'use strict';
var util = require('util');
var path = require('path');
var fs = require('fs-extra');
var yeoman = require('yeoman-generator');

var NopejsGenerator = module.exports = function NopejsGenerator(args, options, config) {
  yeoman.generators.Base.apply(this, arguments);

  this.on('end', function () {
    this.installDependencies({ skipInstall: options['skip-install'] });
  });

  this.pkg = JSON.parse(this.readFileAsString(path.join(__dirname, '../package.json')));
};

util.inherits(NopejsGenerator, yeoman.generators.Base);

NopejsGenerator.prototype.askFor = function askFor() {
  var cb = this.async();

  // have Yeoman greet the user.
  console.log(this.yeoman);

  var prompts = [
  //]; /*
    {
      name: 'packageName',
      message: 'What would you like to name this package? (this will be slugified)'
    },
    {
      name: 'siteName',
      message: 'What would you like to call your site?'
    },
    {
      name: 'siteAuthor',
      message: 'Who is the site author?'
    }
  ];
//*/
  this.prompt(prompts, function (props) {
    ///*
    this.packageName = props.packageName;
    this.siteName = props.siteName;
    this.siteAuthor = props.siteAuthor;
    //*/

    cb();
  }.bind(this));
};

NopejsGenerator.prototype.app = function app() {
  this.copy('_package.json', 'package.json');
  this.copy('_bower.json', 'bower.json');
  this.copy('.bowerrc');
};

NopejsGenerator.prototype.gruntfile = function gruntfile() {
  this.template('Gruntfile.js');
};

NopejsGenerator.prototype.projectfiles = function projectfiles() {
  this.copy('editorconfig', '.editorconfig');
  this.copy('jshintrc', '.jshintrc');
};

NopejsGenerator.prototype.nopejsFiles = function nopejsFiles() {
    this.bowerInstall("shakeelmohamed/nopejs", { save: true }, function(){
      fs.copy('./bower_components/nopejs', './', function (err) {
        if (err) {
          console.error(err);
        } else {
          console.log("success!");
          fs.remove('./bower_components', function(err){
            if (err) return console.error(err);

            console.log("success!")
          });
        }
      });
    });
};<|endoftext|>