<commit_before>'use strict';

const _ = require('lodash');
const Bacon = require('baconjs');
const colors = require('colors/safe');
const https = require('https');
const request = require('request');
const url = require('url');

const Application = require('../models/application.js');
const Event = require('../models/events.js');
const Logger = require('../logger.js');
const WsStream = require('./ws-stream.js');
const { conf } = require('./configuration.js');

function getWsLogUrl (appId, timestamp, search, deploymentId) {
  const baseUrl = _.template(conf.LOG_WS_URL)({ appId, timestamp });

  const logsUrl = new url.URL(baseUrl);
  if (search != null) {
    logsUrl.searchParams.set('filter', search);
  }
  if (deploymentId != null) {
    logsUrl.searchParams.set('deployment_id', deploymentId);
  }

  return logsUrl.toString();
};

function getAppLogsUrl (appId) {
  return _.template(conf.LOG_HTTP_URL)({ appId });
}

/** Get logs as they arrive from a web socket.
 * Automatically reconnect if the connexion is closed.
 *
 * api: The API object
 * appId: The appId of the application
 * before (Date): only display log lines that happened before this date
 * after  (Date): only display log lines that happened after this date
 * deploymentId: Only display log lines corresponding to this deployment
 */
function getContinuousLogs (api, appId, before, after, search, deploymentId) {
  function makeUrl (retryTimestamp) {
    const newAfter = retryTimestamp === null || after.getTime() > retryTimestamp.getTime() ? after : retryTimestamp;
    return getWsLogUrl(appId, newAfter.toISOString(), search, deploymentId);
  };

  return WsStream
    .openStream(makeUrl, api.session.getAuthorization('GET', getAppLogsUrl(appId), {}))
    .filter((line) => {
      const lineDate = Date.parse(line._source['@timestamp']);
      const isBefore = !before || lineDate < before.getTime();
      const isAfter = !after || lineDate > after.getTime();
      return isBefore && isAfter;
    });
};

function getNewLogs (api, appId, before, after, search, deploymentId) {
  Logger.println('Waiting for application logs…');
  Logger.debug('Opening a websocket in order to fetch logs…');
  return getContinuousLogs(api, appId, before, after, search, deploymentId);
};

function getOldLogs (api, app_id, before, after, search, deploymentId) {
  const query = {};

  if (before == null && after == null) {
    query.limit = 300;
  }
  if (before != null) {
    query.before = before.toISOString();
  }
  if (after != null) {
    query.after = after.toISOString();
  }
  if (search != null) {
    query.filter = search;
  }
  if (deploymentId != null) {
    query.deployment_id = deploymentId;
  }

  const appLogsUrl = getAppLogsUrl(app_id);
  console.log(appLogsUrl);

  return Bacon
    .fromNodeCallback(request, {
      agent: appLogsUrl.startsWith('https://') ? new https.Agent({ keepAlive: true }) : undefined,
      url: appLogsUrl,
      qs: query,
      headers: {
        authorization: api.session.getAuthorization('GET', appLogsUrl, {}),
        Accept: 'application/json',
      },
    })
    .flatMapLatest((res) => {
      Logger.debug('Received old logs');
      const jsonBody = _.attempt(JSON.parse, res.body);
      if (_.isError(jsonBody)) {
        return new Bacon.Error('Received invalid JSON');
      }
      if (_.isArray(jsonBody)) {
        return Bacon.fromArray(jsonBody.reverse());
      }
      if (jsonBody['type'] === 'error') {
        return new Bacon.Error(jsonBody);
      }
    });
};

function isCleverMessage (line) {
  return line._source.syslog_program === '/home/bas/rubydeployer/deployer.rb';
};

function isDeploymentSuccessMessage (line) {
  return isCleverMessage(line)
    && _.startsWith(line._source['@message'].toLowerCase(), 'successfully deployed in');
};

function isDeploymentFailedMessage (line) {
  return isCleverMessage(line)
    && _.startsWith(line._source['@message'].toLowerCase(), 'deploy failed in');
};

function isBuildSucessMessage (line) {
  return _.startsWith(line._source['@message'].toLowerCase(), 'build succeeded in');
};

function getAppLogs (api, appId, instances, before, after, search, deploymentId) {
  const now = new Date();
  const fetchOldLogs = !after || after < now;

  const s_newLogs = getNewLogs(api, appId, before, after || now, search, deploymentId);
  const s_logs = fetchOldLogs
    ? getOldLogs(api, appId, before, after, search, deploymentId).merge(s_newLogs)
    : s_newLogs;

  return s_logs
    .filter((line) => _.isEmpty(instances) || _.includes(instances, line._source['@source_host']))
    .map((line) => {
      const { '@timestamp': timestamp, '@message': message } = line._source;
      if (isDeploymentSuccessMessage(line)) {
        return `${timestamp}: ${colors.bold.green(message)}`;
      }
      else if (isDeploymentFailedMessage(line)) {
        return `${timestamp}: ${colors.bold.red(message)}`;
      }
      else if (isBuildSucessMessage(line)) {
        return `${timestamp}: ${colors.bold.blue(message)}`;
      }
      return `${timestamp}: ${message}`;
    });
};

function getAllLogs (api, push, appData, commitId, quiet) {

  const deploymentId = _.get(push, 'deploymentId');
  const s_deploymentEvents = Event
    .getEvents(api, appData.app_id)
    .filter((e) => {
      if (deploymentId != null) {
        return _.get(e, 'data.uuid') === deploymentId;
      }
      return _.get(e, 'data.commit') === commitId;
    });

  const s_deploymentStart = s_deploymentEvents
    .filter((e) => e.event === 'DEPLOYMENT_ACTION_BEGIN')
    .first()
    .toProperty();

  // We ignore cancellation events triggered by a git push,
  // as they are always generated even though a deployment is not in progress.
  // Since we match events with commit id in a case of a git push,
  // it makes the program stop before the actual deployment.
  const s_deploymentEnd = s_deploymentEvents
    .filter((e) => e.event === 'DEPLOYMENT_ACTION_END')
    .filter((e) => (deploymentId != null) || (e.data.state !== 'CANCELLED') || (e.data.cause !== 'Git'))
    .first()
    .toProperty();

  const s_appLogs = Application.get(api, appData.app_id)
    .flatMapLatest((app) => {
      Logger.debug('Fetch application logs…');
      if (deploymentId != null) {
        return getAppLogs(api, app.id, null, null, new Date(), null, deploymentId);
      }
      return s_deploymentStart.flatMapLatest((deploymentStartEvent) => {
        const deploymentId = deploymentStartEvent.data.uuid;
        return getAppLogs(api, app.id, null, null, new Date(), null, deploymentId);
      });
    });

  // TODO, could be done without a Bus (with merged streams)
  const s_allLogs = new Bacon.Bus();

  s_deploymentStart.onValue(() => {
    s_allLogs.push(colors.bold.blue('Deployment started'));
  });

  s_deploymentEnd.onValue((e) => {
    if (e.data.state === 'OK') {
      s_allLogs.push(colors.bold.green('Deployment successful'));
    }
    else {
      s_allLogs.error('Deployment failed. Please check the logs');
    }
    s_allLogs.end();
  });

  if (!quiet) {
    s_allLogs.push('Fetching application information…');
    s_allLogs.plug(s_appLogs);
  }

  return s_allLogs;
}

module.exports = { getAppLogs, getAllLogs };
<commit_msg>Use @clevercloud/client directly for GET /logs/{appId}<commit_after>'use strict';

const _ = require('lodash');
const Bacon = require('baconjs');
const colors = require('colors/safe');
const url = require('url');

const Application = require('../models/application.js');
const Event = require('../models/events.js');
const Logger = require('../logger.js');
const WsStream = require('./ws-stream.js');
const { conf } = require('./configuration.js');

const { sendToApi } = require('../models/send-to-api.js');
const { getOldLogs: fetchOldLogs } = require('@clevercloud/client/cjs/api/log.js');

function getWsLogUrl (appId, timestamp, search, deploymentId) {
  const baseUrl = _.template(conf.LOG_WS_URL)({ appId, timestamp });

  const logsUrl = new url.URL(baseUrl);
  if (search != null) {
    logsUrl.searchParams.set('filter', search);
  }
  if (deploymentId != null) {
    logsUrl.searchParams.set('deployment_id', deploymentId);
  }

  return logsUrl.toString();
};

function getAppLogsUrl (appId) {
  return _.template(conf.LOG_HTTP_URL)({ appId });
}

/** Get logs as they arrive from a web socket.
 * Automatically reconnect if the connexion is closed.
 *
 * api: The API object
 * appId: The appId of the application
 * before (Date): only display log lines that happened before this date
 * after  (Date): only display log lines that happened after this date
 * deploymentId: Only display log lines corresponding to this deployment
 */
function getContinuousLogs (api, appId, before, after, search, deploymentId) {
  function makeUrl (retryTimestamp) {
    const newAfter = retryTimestamp === null || after.getTime() > retryTimestamp.getTime() ? after : retryTimestamp;
    return getWsLogUrl(appId, newAfter.toISOString(), search, deploymentId);
  };

  return WsStream
    .openStream(makeUrl, api.session.getAuthorization('GET', getAppLogsUrl(appId), {}))
    .filter((line) => {
      const lineDate = Date.parse(line._source['@timestamp']);
      const isBefore = !before || lineDate < before.getTime();
      const isAfter = !after || lineDate > after.getTime();
      return isBefore && isAfter;
    });
};

function getOldLogs (appId, beforeDate, afterDate, filter, deployment_id) {

  const limit = (beforeDate == null && afterDate == null) ? 300 : null;
  const before = (beforeDate != null) ? beforeDate.toISOString() : null;
  const after = (afterDate != null) ? afterDate.toISOString() : null;

  const logsProm = fetchOldLogs({ appId, limit, before, after, filter, deployment_id }).then(sendToApi);

  return Bacon.fromPromise(logsProm)
    .flatMapLatest((logs) => Bacon.fromArray(logs.reverse()));
};

function isCleverMessage (line) {
  return line._source.syslog_program === '/home/bas/rubydeployer/deployer.rb';
};

function isDeploymentSuccessMessage (line) {
  return isCleverMessage(line)
    && _.startsWith(line._source['@message'].toLowerCase(), 'successfully deployed in');
};

function isDeploymentFailedMessage (line) {
  return isCleverMessage(line)
    && _.startsWith(line._source['@message'].toLowerCase(), 'deploy failed in');
};

function isBuildSucessMessage (line) {
  return _.startsWith(line._source['@message'].toLowerCase(), 'build succeeded in');
};

function getAppLogs (api, appId, instances, before, after, search, deploymentId) {
  const now = new Date();
  const fetchOldLogs = !after || after < now;

  const s_newLogs = getNewLogs(api, appId, before, after || now, search, deploymentId);
  const s_logs = fetchOldLogs
    ? getOldLogs(appId, before, after, search, deploymentId).merge(s_newLogs)
    : s_newLogs;

  return s_logs
    .filter((line) => _.isEmpty(instances) || _.includes(instances, line._source['@source_host']))
    .map((line) => {
      const { '@timestamp': timestamp, '@message': message } = line._source;
      if (isDeploymentSuccessMessage(line)) {
        return `${timestamp}: ${colors.bold.green(message)}`;
      }
      else if (isDeploymentFailedMessage(line)) {
        return `${timestamp}: ${colors.bold.red(message)}`;
      }
      else if (isBuildSucessMessage(line)) {
        return `${timestamp}: ${colors.bold.blue(message)}`;
      }
      return `${timestamp}: ${message}`;
    });
};

function getAllLogs (api, push, appData, commitId, quiet) {

  const deploymentId = _.get(push, 'deploymentId');
  const s_deploymentEvents = Event
    .getEvents(api, appData.app_id)
    .filter((e) => {
      if (deploymentId != null) {
        return _.get(e, 'data.uuid') === deploymentId;
      }
      return _.get(e, 'data.commit') === commitId;
    });

  const s_deploymentStart = s_deploymentEvents
    .filter((e) => e.event === 'DEPLOYMENT_ACTION_BEGIN')
    .first()
    .toProperty();

  // We ignore cancellation events triggered by a git push,
  // as they are always generated even though a deployment is not in progress.
  // Since we match events with commit id in a case of a git push,
  // it makes the program stop before the actual deployment.
  const s_deploymentEnd = s_deploymentEvents
    .filter((e) => e.event === 'DEPLOYMENT_ACTION_END')
    .filter((e) => (deploymentId != null) || (e.data.state !== 'CANCELLED') || (e.data.cause !== 'Git'))
    .first()
    .toProperty();

  const s_appLogs = Application.get(api, appData.app_id)
    .flatMapLatest((app) => {
      Logger.debug('Fetch application logs…');
      if (deploymentId != null) {
        return getAppLogs(api, app.id, null, null, new Date(), null, deploymentId);
      }
      return s_deploymentStart.flatMapLatest((deploymentStartEvent) => {
        const deploymentId = deploymentStartEvent.data.uuid;
        return getAppLogs(api, app.id, null, null, new Date(), null, deploymentId);
      });
    });

  // TODO, could be done without a Bus (with merged streams)
  const s_allLogs = new Bacon.Bus();

  s_deploymentStart.onValue(() => {
    s_allLogs.push(colors.bold.blue('Deployment started'));
  });

  s_deploymentEnd.onValue((e) => {
    if (e.data.state === 'OK') {
      s_allLogs.push(colors.bold.green('Deployment successful'));
    }
    else {
      s_allLogs.error('Deployment failed. Please check the logs');
    }
    s_allLogs.end();
  });

  if (!quiet) {
    s_allLogs.push('Fetching application information…');
    s_allLogs.plug(s_appLogs);
  }

  return s_allLogs;
}

module.exports = { getAppLogs, getAllLogs };
<|endoftext|>


<commit_before>  // なでしこの字句解析を行う
// 既に全角半角を揃えたコードに対して字句解析を行う
const {opPriority} = require('./nako_parser_const')

// 予約語句
// (memo)「回」「間」「繰返」「反復」「抜」「続」「戻」「代入」などは replaceWord で word から変換
const reservedWords = require('./nako_reserved_words')

// 助詞の一覧
const josi = require('./nako_josi_list')
const josiRE = josi.josiRE

// 字句解析ルールの一覧
const lexRules = require('./nako_lex_rules')
const rules = lexRules.rules
const trimOkurigana = lexRules.trimOkurigana

class NakoLexer {
  constructor () {
    this.funclist = {}
    this.result = []
  }

  // プラグインの取り込みを行う
  static checkRequire (tokens) {
    let i = 0
    while ((i + 2) < tokens.length) {
      const tNot = tokens[i]
      const tFile = tokens[i + 1]
      const tTorikomu = tokens[i + 2]
      if (tNot.type === 'not' && tTorikomu.value === '取込') {
        tNot.type = 'require'
        if (tFile.type === 'string' || tFile.type === 'string_ex')
          {tFile.type = 'string'}
         else
          {throw new Error('[字句解析エラー] 『!「ファイル」を取り込む』の書式で記述してください。')}

        i += 3
        continue
      }
      i++
    }
  }

  setFuncList (listObj) {
    this.funclist = listObj
  }

  setInput (code, isFirst, line) {
    // 最初に全部を区切ってしまう
    this.tokenize(code, line)
    // 関数の定義があれば funclist を更新
    NakoLexer.checkRequire(this.result)
    this.preDefineFunc(this.result)
    this.replaceWord(this.result)

    if (isFirst) {
      const eofLine = (this.result.length > 0) ? this.result[this.result.length - 1].line : 0
      this.result.push({type: 'eol', line: eofLine, josi: '', value: '---'}) // 改行
      this.result.push({type: 'eof', line: eofLine, josi: '', value: ''}) // ファイル末尾
    }
    return this.result
  }

  preDefineFunc (tokens) {
    // 関数を先読みして定義
    let i = 0
    let isFuncPointer = false
    const readArgs = () => {
      const args = []
      const keys = {}
      if (tokens[i].type !== '(') {return []}
      i++
      while (tokens[i]) {
        const t = tokens[i]
        i++
        if (t.type === ')')
          {break}
         else if (t.type === 'func')
          {isFuncPointer = true}
         else if (t.type !== '|' && t.type !== 'comma') {
          if (isFuncPointer) {
            t.funcPointer = true
            isFuncPointer = false
          }
          args.push(t)
          if (!keys[t.value])
            {keys[t.value] = []}

          keys[t.value].push(t.josi)
        }
      }
      const varnames = []
      const funcPointers = []
      const result = []
      const already = {}
      for (const arg of args)
        {if (!already[arg.value]) {
          const josi = keys[arg.value]
          result.push(josi)
          varnames.push(arg.value)
          if (arg.funcPointer)
            {funcPointers.push(arg.value)}
           else
            {funcPointers.push(null)}

          already[arg.value] = true
        }}

      return [result, varnames, funcPointers]
    }
    // トークンを一つずつ確認
    while (i < tokens.length) {
      // タイプの置換
      const t = tokens[i]
      // 無名関数の定義：「xxには**」があった場合 ... 暗黙的な関数定義とする
      if ((t.type === 'word' && t.josi === 'には') || (t.type === 'word' && t.josi === 'は~')) {
        t.josi = 'には'
        tokens.splice(i + 1, 0, {type: 'def_func', value: '関数', line: t.line, josi: ''})
        i++
        continue
      }
      // 予約語の置換
      if (t.type === 'word' && reservedWords[t.value]) {
        t.type = reservedWords[t.value]
        if (t.value === 'そう') {t.value = 'それ'}
      }
      // 関数定義の確認
      if (t.type !== 'def_func') {
        i++
        continue
      }
      const defToken = t
      i++ // skip "●"
      let josi = []
      let varnames = []
      let funcPointers = []
      let funcName = ''
      // 関数名の前に引数定義
      if (tokens[i] && tokens[i].type === '(')
        {[josi, varnames, funcPointers] = readArgs()}

      // 関数名
      if (tokens[i] && tokens[i].type === 'word')
        {funcName = tokens[i++].value}

      // 関数名の後で引数定義
      if (josi.length === 0 && tokens[i] && tokens[i].type === '(')
        {[josi, varnames, funcPointers] = readArgs()}

      // 関数定義か？
      if (funcName !== '')
        {this.funclist[funcName] = {
          type: 'func',
          josi,
          fn: null,
          varnames,
          funcPointers
        }}

      // 無名関数のために
      defToken.meta = {josi, varnames, funcPointers}
    }
  }

  replaceWord (tokens) {
    let comment = []
    let i = 0
    const getLastType = () => {
      if (i <= 0) {return 'eol'}
      return tokens[i - 1].type
    }
    while (i < tokens.length) {
      const t = tokens[i]
      if (t.type === 'word' && t.value !== 'それ') {
        // 関数を変換
        let fo = this.funclist[t.value]
        if (fo && fo.type === 'func') {
          t.type = 'func'
          t.meta = fo
          continue
        }
      }
      // 数字につくマイナス記号を判定
      // (ng) 5 - 3 || word - 3
      // (ok) (行頭)-3 || 1 * -3 || Aに -3を 足す
      if (t.type === '-' && tokens[i + 1] && tokens[i + 1].type === 'number') {
        // 一つ前の語句が、(行頭|演算子|助詞付きの語句)なら 負数である
        const ltype = getLastType()
        if (ltype === 'eol' || opPriority[ltype] || tokens[i - 1].josi !== '') {
          tokens.splice(i, 1) // remove '-'
          tokens[i].value *= -1
        }
      }
      // 助詞の「は」を = に展開
      if (t.josi === undefined) {t.josi = ''}
      if (t.josi === 'は') {
        tokens.splice(i + 1, 0, {type: 'eq', line: t.line})
        i += 2
        t.josi = ''
        continue
      }
      // 「とは」を一つの単語にする
      if (t.josi === 'とは') {
        tokens.splice(i + 1, 0, {type: t.josi, line: t.line})
        t.josi = ''
        i += 2
        continue
      }
      // 助詞のならばをトークンとする
      if (josi.tarareba[t.josi]) {
        const josi = (t.josi !== 'でなければ') ? 'ならば' : 'でなければ'
        t.josi = ''
        tokens.splice(i + 1, 0, {type: 'ならば', value: josi, line: t.line})
        i += 2
        continue
      }
      // '_' + 改行 を飛ばす (演算子直後に改行を入れたい場合に使う)
      if (t.type === '_eol') {
        tokens.splice(i, 1)
        continue
      }
      // コメントを飛ばす
      if (t.type === 'line_comment' || t.type === 'range_comment') {
        comment.push(t.value)
        tokens.splice(i, 1)
        continue
      }
      // 改行にコメントを埋め込む
      if (t.type === 'eol') {
        t.value = comment.join('/')
        comment = []
      }
      i++
    }
  }

  tokenize (src, line) {
    this.result = []
    let isDefTest = false
    while (src !== '') {
      let ok = false
      for (const rule of rules) {
        const m = rule.pattern.exec(src)
        if (!m) {continue}
        ok = true
        if (rule.name === 'space') {
          src = src.substr(m[0].length)
          continue
        }
        // 特別なパーサを通すか？
        if (rule.cbParser) {
          if (isDefTest && rule.name === 'word' && !isDefTestFuncName) {
            isDefTestFuncName = true
            continue
          }

          const rp = rule.cbParser(src)

          if (isDefTestFuncName) {
            isDefTestFuncName = false
          }

          if (rule.name === 'string_ex') {
            // 展開あり文字列 → aaa{x}bbb{x}cccc
            const list = rp.res.split(/[{}｛｝]/)
            if (list.length >= 1 && list.length % 2 === 0)
              {throw new Error('字句解析エラー(' + (line + 1) + '): 展開あり文字列で値の埋め込み{...}が対応していません。')}

            for (let i = 0; i < list.length; i++) {
              const josi = (i === list.length - 1) ? rp.josi : ''
              if (i % 2 === 0) {
                const rr = {type: 'string', value: list[i], josi, line}
                this.result.push(rr)
              } else {
                list[i] = trimOkurigana(list[i])
                this.result.push({type: '&', value: '&', josi: '', line})
                this.result.push({type: 'code', value: list[i], josi: '', line})
                this.result.push({type: '&', value: '&', josi: '', line})
              }
            }
            line += rp.numEOL
            src = rp.src
            break
          }
          src = rp.src
          const rr = {type: rule.name, value: rp.res, josi: rp.josi, line: line}
          this.result.push(rr)
          line += rp.numEOL
          break
        }
        // 値を変換する必要があるか？
        let value = m[0]
        if (rule.cb) {value = rule.cb(value)}
        // ソースを進める
        src = src.substr(m[0].length)
        if (rule.name === 'eol' && value === '\n')
          {value = line++}

        let josi = ''
        if (rule.readJosi) {
          const j = josiRE.exec(src)
          if (j) {
            josi = j[0]
            src = src.substr(j[0].length)
          }
        }

        switch (rule.name) {
          case 'def_test': {
            isDefTest = true
            break
          }
          case 'eol': {
            isDefTest = false
            break
          }
          default: {
            break
          }
        }

        this.result.push({
          type: rule.name,
          value: value,
          line: line,
          josi: josi
        })
        break
      }
      if (!ok) {throw new Error('字句解析で未知の語句(' + (line + 1) + '): ' + src.substr(0, 3) + '...')}
    }
  }
}

module.exports = NakoLexer
<commit_msg>変数定義を追加<commit_after>  // なでしこの字句解析を行う
// 既に全角半角を揃えたコードに対して字句解析を行う
const {opPriority} = require('./nako_parser_const')

// 予約語句
// (memo)「回」「間」「繰返」「反復」「抜」「続」「戻」「代入」などは replaceWord で word から変換
const reservedWords = require('./nako_reserved_words')

// 助詞の一覧
const josi = require('./nako_josi_list')
const josiRE = josi.josiRE

// 字句解析ルールの一覧
const lexRules = require('./nako_lex_rules')
const rules = lexRules.rules
const trimOkurigana = lexRules.trimOkurigana

class NakoLexer {
  constructor () {
    this.funclist = {}
    this.result = []
  }

  // プラグインの取り込みを行う
  static checkRequire (tokens) {
    let i = 0
    while ((i + 2) < tokens.length) {
      const tNot = tokens[i]
      const tFile = tokens[i + 1]
      const tTorikomu = tokens[i + 2]
      if (tNot.type === 'not' && tTorikomu.value === '取込') {
        tNot.type = 'require'
        if (tFile.type === 'string' || tFile.type === 'string_ex')
          {tFile.type = 'string'}
         else
          {throw new Error('[字句解析エラー] 『!「ファイル」を取り込む』の書式で記述してください。')}

        i += 3
        continue
      }
      i++
    }
  }

  setFuncList (listObj) {
    this.funclist = listObj
  }

  setInput (code, isFirst, line) {
    // 最初に全部を区切ってしまう
    this.tokenize(code, line)
    // 関数の定義があれば funclist を更新
    NakoLexer.checkRequire(this.result)
    this.preDefineFunc(this.result)
    this.replaceWord(this.result)

    if (isFirst) {
      const eofLine = (this.result.length > 0) ? this.result[this.result.length - 1].line : 0
      this.result.push({type: 'eol', line: eofLine, josi: '', value: '---'}) // 改行
      this.result.push({type: 'eof', line: eofLine, josi: '', value: ''}) // ファイル末尾
    }
    return this.result
  }

  preDefineFunc (tokens) {
    // 関数を先読みして定義
    let i = 0
    let isFuncPointer = false
    const readArgs = () => {
      const args = []
      const keys = {}
      if (tokens[i].type !== '(') {return []}
      i++
      while (tokens[i]) {
        const t = tokens[i]
        i++
        if (t.type === ')')
          {break}
         else if (t.type === 'func')
          {isFuncPointer = true}
         else if (t.type !== '|' && t.type !== 'comma') {
          if (isFuncPointer) {
            t.funcPointer = true
            isFuncPointer = false
          }
          args.push(t)
          if (!keys[t.value])
            {keys[t.value] = []}

          keys[t.value].push(t.josi)
        }
      }
      const varnames = []
      const funcPointers = []
      const result = []
      const already = {}
      for (const arg of args)
        {if (!already[arg.value]) {
          const josi = keys[arg.value]
          result.push(josi)
          varnames.push(arg.value)
          if (arg.funcPointer)
            {funcPointers.push(arg.value)}
           else
            {funcPointers.push(null)}

          already[arg.value] = true
        }}

      return [result, varnames, funcPointers]
    }
    // トークンを一つずつ確認
    while (i < tokens.length) {
      // タイプの置換
      const t = tokens[i]
      // 無名関数の定義：「xxには**」があった場合 ... 暗黙的な関数定義とする
      if ((t.type === 'word' && t.josi === 'には') || (t.type === 'word' && t.josi === 'は~')) {
        t.josi = 'には'
        tokens.splice(i + 1, 0, {type: 'def_func', value: '関数', line: t.line, josi: ''})
        i++
        continue
      }
      // 予約語の置換
      if (t.type === 'word' && reservedWords[t.value]) {
        t.type = reservedWords[t.value]
        if (t.value === 'そう') {t.value = 'それ'}
      }
      // 関数定義の確認
      if (t.type !== 'def_func') {
        i++
        continue
      }
      const defToken = t
      i++ // skip "●"
      let josi = []
      let varnames = []
      let funcPointers = []
      let funcName = ''
      // 関数名の前に引数定義
      if (tokens[i] && tokens[i].type === '(')
        {[josi, varnames, funcPointers] = readArgs()}

      // 関数名
      if (tokens[i] && tokens[i].type === 'word')
        {funcName = tokens[i++].value}

      // 関数名の後で引数定義
      if (josi.length === 0 && tokens[i] && tokens[i].type === '(')
        {[josi, varnames, funcPointers] = readArgs()}

      // 関数定義か？
      if (funcName !== '')
        {this.funclist[funcName] = {
          type: 'func',
          josi,
          fn: null,
          varnames,
          funcPointers
        }}

      // 無名関数のために
      defToken.meta = {josi, varnames, funcPointers}
    }
  }

  replaceWord (tokens) {
    let comment = []
    let i = 0
    const getLastType = () => {
      if (i <= 0) {return 'eol'}
      return tokens[i - 1].type
    }
    while (i < tokens.length) {
      const t = tokens[i]
      if (t.type === 'word' && t.value !== 'それ') {
        // 関数を変換
        let fo = this.funclist[t.value]
        if (fo && fo.type === 'func') {
          t.type = 'func'
          t.meta = fo
          continue
        }
      }
      // 数字につくマイナス記号を判定
      // (ng) 5 - 3 || word - 3
      // (ok) (行頭)-3 || 1 * -3 || Aに -3を 足す
      if (t.type === '-' && tokens[i + 1] && tokens[i + 1].type === 'number') {
        // 一つ前の語句が、(行頭|演算子|助詞付きの語句)なら 負数である
        const ltype = getLastType()
        if (ltype === 'eol' || opPriority[ltype] || tokens[i - 1].josi !== '') {
          tokens.splice(i, 1) // remove '-'
          tokens[i].value *= -1
        }
      }
      // 助詞の「は」を = に展開
      if (t.josi === undefined) {t.josi = ''}
      if (t.josi === 'は') {
        tokens.splice(i + 1, 0, {type: 'eq', line: t.line})
        i += 2
        t.josi = ''
        continue
      }
      // 「とは」を一つの単語にする
      if (t.josi === 'とは') {
        tokens.splice(i + 1, 0, {type: t.josi, line: t.line})
        t.josi = ''
        i += 2
        continue
      }
      // 助詞のならばをトークンとする
      if (josi.tarareba[t.josi]) {
        const josi = (t.josi !== 'でなければ') ? 'ならば' : 'でなければ'
        t.josi = ''
        tokens.splice(i + 1, 0, {type: 'ならば', value: josi, line: t.line})
        i += 2
        continue
      }
      // '_' + 改行 を飛ばす (演算子直後に改行を入れたい場合に使う)
      if (t.type === '_eol') {
        tokens.splice(i, 1)
        continue
      }
      // コメントを飛ばす
      if (t.type === 'line_comment' || t.type === 'range_comment') {
        comment.push(t.value)
        tokens.splice(i, 1)
        continue
      }
      // 改行にコメントを埋め込む
      if (t.type === 'eol') {
        t.value = comment.join('/')
        comment = []
      }
      i++
    }
  }

  tokenize (src, line) {
    this.result = []
    let isDefTest = false
    let isDefTestFuncName = false
    while (src !== '') {
      let ok = false
      for (const rule of rules) {
        const m = rule.pattern.exec(src)
        if (!m) {continue}
        ok = true
        if (rule.name === 'space') {
          src = src.substr(m[0].length)
          continue
        }
        // 特別なパーサを通すか？
        if (rule.cbParser) {
          if (isDefTest && rule.name === 'word' && !isDefTestFuncName) {
            isDefTestFuncName = true
            continue
          }

          const rp = rule.cbParser(src)

          if (isDefTestFuncName) {
            isDefTestFuncName = false
          }

          if (rule.name === 'string_ex') {
            // 展開あり文字列 → aaa{x}bbb{x}cccc
            const list = rp.res.split(/[{}｛｝]/)
            if (list.length >= 1 && list.length % 2 === 0)
              {throw new Error('字句解析エラー(' + (line + 1) + '): 展開あり文字列で値の埋め込み{...}が対応していません。')}

            for (let i = 0; i < list.length; i++) {
              const josi = (i === list.length - 1) ? rp.josi : ''
              if (i % 2 === 0) {
                const rr = {type: 'string', value: list[i], josi, line}
                this.result.push(rr)
              } else {
                list[i] = trimOkurigana(list[i])
                this.result.push({type: '&', value: '&', josi: '', line})
                this.result.push({type: 'code', value: list[i], josi: '', line})
                this.result.push({type: '&', value: '&', josi: '', line})
              }
            }
            line += rp.numEOL
            src = rp.src
            break
          }
          src = rp.src
          const rr = {type: rule.name, value: rp.res, josi: rp.josi, line: line}
          this.result.push(rr)
          line += rp.numEOL
          break
        }
        // 値を変換する必要があるか？
        let value = m[0]
        if (rule.cb) {value = rule.cb(value)}
        // ソースを進める
        src = src.substr(m[0].length)
        if (rule.name === 'eol' && value === '\n')
          {value = line++}

        let josi = ''
        if (rule.readJosi) {
          const j = josiRE.exec(src)
          if (j) {
            josi = j[0]
            src = src.substr(j[0].length)
          }
        }

        switch (rule.name) {
          case 'def_test': {
            isDefTest = true
            break
          }
          case 'eol': {
            isDefTest = false
            break
          }
          default: {
            break
          }
        }

        this.result.push({
          type: rule.name,
          value: value,
          line: line,
          josi: josi
        })
        break
      }
      if (!ok) {throw new Error('字句解析で未知の語句(' + (line + 1) + '): ' + src.substr(0, 3) + '...')}
    }
  }
}

module.exports = NakoLexer
<|endoftext|>


<commit_before>/* Upload pattern.
 *
 * Options:
 *    showTitle(boolean): show/hide the h1 title (true)
 *    url(string): If not used with a form, this option must provide the URL to submit to or baseUrl with relativePath needs to be used (null)
 *    baseUrl(string): to be used in conjunction with relativePath to generate submission urls based on related items (null)
 *    relativePath(string): again, to be used with baseUrl to create upload url (null)
 *    initialFolder(string): UID of initial folder related items widget should have selected (null)
 *    currentPath(string): Current path related items is starting with (null)
 *    clickable(boolean): If you can click on container to also upload (false)
 *    className(string): value for class attribute in the form element ('upload')
 *    paramName(string): value for name attribute in the file input element ('file')
 *    ajaxUpload(boolean): true or false for letting the widget upload the files via ajax. If false the form will act like a normal form. (true)
 *    wrap(boolean): true or false for wrapping this element using the value of wrapperTemplate. (false)
 *    wrapperTemplate(string): HTML template for wrapping around with this element. ('
')
 *    resultTemplate(string): HTML template for the element that will contain file information. ('

Drop files here...

')
 *    autoCleanResults(boolean): condition value for the file preview in div element to fadeout after file upload is completed. (true)
 *    previewsContainer(selector): JavaScript selector for file preview in div element. (.upload-previews)
 *    container(selector): JavaScript selector for where to put upload stuff into in case of form. If not provided it will be place before the first submit button. ('')
 *    relatedItems(object): Related items pattern options. Will only use only if relativePath is used to use correct upload destination ({ attributes: ["UID", "Title", "Description", "getURL", "Type", "path", "ModificationDate"], batchSize: 20, basePath: "/", vocabularyUrl: null, width: 500, maximumSelectionSize: 1, placeholder: "Search for item on site..." })
 *
 * Documentation:
 *
 *    # On a div element
 *
 *    {{ example-1 }}
 *
 * Example: example-1
 *

 *

 *
Something here that is useful

 *
Something else here that is useful

 *
Another thing here that is useful

 *

 *

 *
 */

define([
  'jquery',
  'underscore',
  'dropzone',
  'text!./templates/upload.xml',
  'text!./templates/preview.xml',
], function($, _, Dropzone, UploadTemplate, PreviewTemplate) {
  'use strict';

  /* we do not want this plugin to auto discover */
  Dropzone.autoDiscover = false;

  i18n.loadCatalog('widgets');
  // var _t = i18n.MessageFactory('widgets');
  var _t = function (str) {
      // XXX: Still need to sort out i18n. Will have to check whether we can
      // rely on mockup-i18n.
      return str;
  };

  var upload = {
    name: 'upload',
    defaults: {
      showTitle: true,
      url: null, // XXX MUST provide url to submit to OR be in a form
      className: 'upload',
      wrap: false,
      wrapperTemplate: '
',
      fileaddedClassName: 'dropping',
      useTus: false,
      container: '',
      ajaxUpload: true,

      paramName: 'file',
      clickable: true,
      addRemoveLinks: false,
      autoCleanResults: true,
      previewsContainer: '.previews',
      previewTemplate: null,
      maxFiles: null,
      maxFilesize: 99999999, // let's not have a max by default...

      relatedItems: {
        // UID attribute is required here since we're working with related items
        attributes: ['UID', 'Title', 'Description', 'getURL', 'Type', 'path', 'ModificationDate'],
        batchSize: 20,
        basePath: '/',
        vocabularyUrl: null,
        width: 500,
        maximumSelectionSize: 1,
        placeholder: _t('Search for item on site...')
      }
    },

    //placeholder: 'Search for item on site...'
    init: function() {
      var self = this,
          template = UploadTemplate;

      // values that will change current processing
      self.currentPath = self.options.currentPath;
      self.numFiles = 0;
      self.currentFile = 0;

      template = _.template(template, {_t: _t});
      self.$el.addClass(self.options.className);
      self.$el.append(template);

      self.$progress = $('.progress-bar-success', self.$el);

      if (!self.options.showTitle) {
        self.$el.find('h2.title').hide();
      }

      if (!self.options.ajaxUpload) {
        // no ajax upload, drop the fallback
        $('.fallback', this.$el).remove();
        if (this.$el.hasClass('.upload-container')) {
          this.$el.addClass('no-ajax-upload');
        } else {
          this.$el.closest('.upload-container').addClass('no-ajax-upload');
        }
      }

      if (self.options.wrap) {
        self.$el.wrap(self.options.wrapperTemplate);
        self.$el = self.$el.parent();
      }

      if (self.options.baseUrl && self.options.relativePath){
        // only use related items if we can generate paths based urls
        self.$pathInput = $('input[name="location"]', self.$el);
        self.relatedItems = self.setupRelatedItems(self.$pathInput);
      } else {
        $('input[name="location"]', self.$el).parent().remove();
        self.relatedItems = null;
      }

      self.$dropzone = $('.upload-area', self.$el);

      $('button.browse', self.$el).click(function(e) {
        e.preventDefault();
        e.stopPropagation();
        // we trigger the dropzone dialog!
        self.dropzone.hiddenFileInput.click();
      });

      var dzoneOptions = this.getDzoneOptions();

      try {
        // if init of Dropzone fails it says nothing and
        // it fails silently. Using this block we make sure
        // that if you break it w/ some weird or missing option
        // you can get a proper log of it
        //
        self.dropzone = new Dropzone(self.$dropzone[0], dzoneOptions);
      } catch (e) {
        if (window.DEBUG) {
          // log it!
          console.log(e);
        }
        throw e;
      }

      self.dropzone.on('addedfile', function(file) {
        self.showControls();
      });

      self.dropzone.on('removedfile', function() {
        if (self.dropzone.files.length < 1) {
          self.hideControls();
        }
      });

      if (self.options.autoCleanResults) {
        self.dropzone.on('complete', function(file) {
          setTimeout(function() {
            $(file.previewElement).fadeOut();
          }, 3000);
        });
      }

      self.dropzone.on('complete', function(file) {
        if (self.dropzone.files.length < 1) {
          self.hideControls();
        }
      });

      self.dropzone.on('totaluploadprogress', function(pct) {
        // need to caclulate total pct here in reality since we're manually
        // processing each file one at a time.
        pct = ((((self.currentFile - 1) * 100) + pct) / (self.numFiles * 100)) * 100;
        self.$progress.attr('aria-valuenow', pct).css('width', pct + '%');
      });

      $('.upload-all', self.$el).click(function (e) {
        e.preventDefault();
        e.stopPropagation();
        self.processUpload({
          finished: function() {
            self.$progress.attr('aria-valuenow', 0).css('width', '0%');
          }
        });
      });
    },

    showControls: function() {
      var self = this;
      $('.controls', self.$el).fadeIn('slow');
    },

    hideControls: function() {
      var self = this;
      $('.controls', self.$el).fadeOut('slow');
    },

    pathJoin: function() {
      var parts = [];
      _.each(arguments, function(part) {
        if (!part){
          return;
        }
        if (part[0] === '/'){
          part = part.substring(1);
        }
        if (part[part.length - 1] === '/'){
          part = part.substring(0, part.length - 1);
        }
        parts.push(part);
      });
      return parts.join('/');
    },

    getUrl: function() {

      var self = this;
      var url = self.options.url;
      if (!url) {
        if (self.options.baseUrl && self.options.relativePath){
          url = self.options.baseUrl;
          if (url[url.length - 1] !== '/'){
            url = url + '/';
          }
          url = url + self.pathJoin(self.currentPath, self.options.relativePath);
        } else {
          var $form = self.$el.parents('form');
          if ($form.length > 0){
            url = $form.attr('action');
          } else {
            url = window.location.href;
          }
        }
      }
      return url;
    },

    getDzoneOptions: function() {
      var self = this;

      // clickable option
      if (typeof(self.options.clickable) === 'string') {
        if (self.options.clickable === 'true') {
          self.options.clickable = true;
        } else {
          self.options.clickable = false;
        }
      }

      var options = $.extend({}, self.options);
      options.url = self.getUrl();
      // XXX force to only upload one at a time,
      // right now we don't support multiple for backends
      options.uploadMultiple = false;

      delete options.wrap;
      delete options.wrapperTemplate;
      delete options.resultTemplate;
      delete options.autoCleanResults;
      delete options.fileaddedClassName;
      delete options.useTus;

      if (self.options.previewsContainer) {
        /*
         * if they have a select but it's not an id, let's make an id selector
         * so we can target the correct container. dropzone is weird here...
         */
        var $preview = self.$el.find(self.options.previewsContainer);
        if ($preview.length > 0) {
          options.previewsContainer = $preview[0];
        }
      }

      // XXX: do we need to allow this?
      options.autoProcessQueue = false;
      // options.addRemoveLinks = true;  // we show them in the template
      options.previewTemplate = PreviewTemplate;

      // if our element is a form we should force some values
      // https://github.com/enyo/dropzone/wiki/Combine-normal-form-with-Dropzone
      return options;
    },

    processUpload: function(options) {
      if (!options){
        options = {};
      }

      var self = this,
          processing = false,
          useTus = self.options.useTus,
          fileaddedClassName = self.options.fileaddedClassName,
          finished = options.finished;

      self.numFiles = self.dropzone.files.length;
      self.currentFile = 0;

      function process() {
        processing = true;
        if (self.dropzone.files.length === 0) {
          processing = false;
          self.$el.removeClass(fileaddedClassName);
          if (finished !== undefined && typeof(finished) === 'function'){
            finished();
          }
          return;
        }
        var file = self.dropzone.files[0];
        if ([Dropzone.SUCCESS, Dropzone.ERROR, Dropzone.CANCELED]
            .indexOf(file.status) !== -1) {
          // remove it
          self.dropzone.removeFile(file);
          process();
        } else if (file.status !== Dropzone.UPLOADING) {
          // start processing file
          if (useTus && window.tus) {
            // use tus upload if installed
            self.handleTusUpload(file);
          } else {
            // otherwise, just use dropzone to process
            self.currentFile += 1;
            self.dropzone.processFile(file);
          }
          setTimeout(process, 100);
        } else {
          // currently processing
          setTimeout(process, 100);
        }
      }
      process();
    },

    handleTusUpload: function(file) {
      /* this needs fixing... */
      var self = this,
          $preview = $(file.previewElement),
          chunkSize = 1024 * 1024 * 5; // 5mb chunk size

      file.status = Dropzone.UPLOADING;

      window.tus.upload(file, {
        endpoint: self.dropzone.options.url,
        headers: {
          'FILENAME': file.name
        },
        chunkSize: chunkSize
      }).fail(function() {
        if(window.DEBUG){
          console.alert('Error uploading with TUS resumable uploads');
        }
        file.status = Dropzone.ERROR;
      }).progress(function(e, bytesUploaded, bytesTotal) {
        var percentage = (bytesUploaded / bytesTotal * 100);
        self.$progress.attr('aria-valuenow', percentage).css('width', percentage + '%');
        self.$progress.html(_t('uploading...
') +
                            self.formatBytes(bytesUploaded) +
                            ' / ' + self.formatBytes(bytesTotal));
      }).done(function(url, file) {
        file.status = Dropzone.SUCCESS;
        self.dropzone.emit('success', file);
        self.dropzone.emit('complete', file);
      });
    },

    formatBytes: function(bytes) {
      var kb = Math.round(bytes / 1024);
      if (kb < 1024) {
        return kb + ' KiB';
      }
      var mb = Math.round(kb / 1024);
      if (mb < 1024) {
        return mb + ' MB';
      }
      return Math.round(mb / 1024) + ' GB';
    },

    setupRelatedItems: function($input) {
      var self = this;
      var options = self.options.relatedItems;
      if (self.options.initialFolder){
        $input.attr('value', self.options.initialFolder);
      }
      var ri = new RelatedItems($input, options);
      ri.$el.on('change', function() {
        var result = $(this).select2('data');
        if (result.length > 0){
          self.currentPath = result[0].path;
        } else {
          self.currentPath = null;
        }
        self.options.url = self.dropzone.options.url = self.getUrl();
      });
      return ri;
    }

  };
  registry.register(upload);
});
<commit_msg>Add registry as dependency and add trigger<commit_after>/* Upload pattern.
 *
 * Options:
 *    showTitle(boolean): show/hide the h1 title (true)
 *    url(string): If not used with a form, this option must provide the URL to submit to or baseUrl with relativePath needs to be used (null)
 *    baseUrl(string): to be used in conjunction with relativePath to generate submission urls based on related items (null)
 *    relativePath(string): again, to be used with baseUrl to create upload url (null)
 *    initialFolder(string): UID of initial folder related items widget should have selected (null)
 *    currentPath(string): Current path related items is starting with (null)
 *    clickable(boolean): If you can click on container to also upload (false)
 *    className(string): value for class attribute in the form element ('upload')
 *    paramName(string): value for name attribute in the file input element ('file')
 *    ajaxUpload(boolean): true or false for letting the widget upload the files via ajax. If false the form will act like a normal form. (true)
 *    wrap(boolean): true or false for wrapping this element using the value of wrapperTemplate. (false)
 *    wrapperTemplate(string): HTML template for wrapping around with this element. ('
')
 *    resultTemplate(string): HTML template for the element that will contain file information. ('

Drop files here...

')
 *    autoCleanResults(boolean): condition value for the file preview in div element to fadeout after file upload is completed. (true)
 *    previewsContainer(selector): JavaScript selector for file preview in div element. (.upload-previews)
 *    container(selector): JavaScript selector for where to put upload stuff into in case of form. If not provided it will be place before the first submit button. ('')
 *    relatedItems(object): Related items pattern options. Will only use only if relativePath is used to use correct upload destination ({ attributes: ["UID", "Title", "Description", "getURL", "Type", "path", "ModificationDate"], batchSize: 20, basePath: "/", vocabularyUrl: null, width: 500, maximumSelectionSize: 1, placeholder: "Search for item on site..." })
 *
 * Documentation:
 *
 *    # On a div element
 *
 *    {{ example-1 }}
 *
 * Example: example-1
 *

 *

 *
Something here that is useful

 *
Something else here that is useful

 *
Another thing here that is useful

 *

 *

 *
 */

define([
  "jquery",
  "underscore",
  "dropzone",
  "pat-registry",
  "text!./templates/upload.xml",
  "text!./templates/preview.xml",
], function($, _, Dropzone, registry, UploadTemplate, PreviewTemplate) {
  'use strict';

  /* we do not want this plugin to auto discover */
  Dropzone.autoDiscover = false;

  // i18n.loadCatalog('widgets');
  // var _t = i18n.MessageFactory('widgets');
  var _t = function (str) {
      // XXX: Still need to sort out i18n. Will have to check whether we can
      // rely on mockup-i18n.
      return str;
  };

  var upload = {
    name: 'upload',
    trigger: '.pat-upload',
    defaults: {
      showTitle: true,
      url: null, // XXX MUST provide url to submit to OR be in a form
      className: 'upload',
      wrap: false,
      wrapperTemplate: '
',
      fileaddedClassName: 'dropping',
      useTus: false,
      container: '',
      ajaxUpload: true,

      paramName: 'file',
      clickable: true,
      addRemoveLinks: false,
      autoCleanResults: true,
      previewsContainer: '.previews',
      previewTemplate: null,
      maxFiles: null,
      maxFilesize: 99999999, // let's not have a max by default...

      relatedItems: {
        // UID attribute is required here since we're working with related items
        attributes: ['UID', 'Title', 'Description', 'getURL', 'Type', 'path', 'ModificationDate'],
        batchSize: 20,
        basePath: '/',
        vocabularyUrl: null,
        width: 500,
        maximumSelectionSize: 1,
        placeholder: _t('Search for item on site...')
      }
    },

    //placeholder: 'Search for item on site...'
    init: function() {
      var self = this,
          template = UploadTemplate;

      // values that will change current processing
      self.currentPath = self.options.currentPath;
      self.numFiles = 0;
      self.currentFile = 0;

      template = _.template(template, {_t: _t});
      self.$el.addClass(self.options.className);
      self.$el.append(template);

      self.$progress = $('.progress-bar-success', self.$el);

      if (!self.options.showTitle) {
        self.$el.find('h2.title').hide();
      }

      if (!self.options.ajaxUpload) {
        // no ajax upload, drop the fallback
        $('.fallback', this.$el).remove();
        if (this.$el.hasClass('.upload-container')) {
          this.$el.addClass('no-ajax-upload');
        } else {
          this.$el.closest('.upload-container').addClass('no-ajax-upload');
        }
      }

      if (self.options.wrap) {
        self.$el.wrap(self.options.wrapperTemplate);
        self.$el = self.$el.parent();
      }

      if (self.options.baseUrl && self.options.relativePath){
        // only use related items if we can generate paths based urls
        self.$pathInput = $('input[name="location"]', self.$el);
        self.relatedItems = self.setupRelatedItems(self.$pathInput);
      } else {
        $('input[name="location"]', self.$el).parent().remove();
        self.relatedItems = null;
      }

      self.$dropzone = $('.upload-area', self.$el);

      $('button.browse', self.$el).click(function(e) {
        e.preventDefault();
        e.stopPropagation();
        // we trigger the dropzone dialog!
        self.dropzone.hiddenFileInput.click();
      });

      var dzoneOptions = this.getDzoneOptions();

      try {
        // if init of Dropzone fails it says nothing and
        // it fails silently. Using this block we make sure
        // that if you break it w/ some weird or missing option
        // you can get a proper log of it
        //
        self.dropzone = new Dropzone(self.$dropzone[0], dzoneOptions);
      } catch (e) {
        if (window.DEBUG) {
          // log it!
          console.log(e);
        }
        throw e;
      }

      self.dropzone.on('addedfile', function(file) {
        self.showControls();
      });

      self.dropzone.on('removedfile', function() {
        if (self.dropzone.files.length < 1) {
          self.hideControls();
        }
      });

      if (self.options.autoCleanResults) {
        self.dropzone.on('complete', function(file) {
          setTimeout(function() {
            $(file.previewElement).fadeOut();
          }, 3000);
        });
      }

      self.dropzone.on('complete', function(file) {
        if (self.dropzone.files.length < 1) {
          self.hideControls();
        }
      });

      self.dropzone.on('totaluploadprogress', function(pct) {
        // need to caclulate total pct here in reality since we're manually
        // processing each file one at a time.
        pct = ((((self.currentFile - 1) * 100) + pct) / (self.numFiles * 100)) * 100;
        self.$progress.attr('aria-valuenow', pct).css('width', pct + '%');
      });

      $('.upload-all', self.$el).click(function (e) {
        e.preventDefault();
        e.stopPropagation();
        self.processUpload({
          finished: function() {
            self.$progress.attr('aria-valuenow', 0).css('width', '0%');
          }
        });
      });
    },

    showControls: function() {
      var self = this;
      $('.controls', self.$el).fadeIn('slow');
    },

    hideControls: function() {
      var self = this;
      $('.controls', self.$el).fadeOut('slow');
    },

    pathJoin: function() {
      var parts = [];
      _.each(arguments, function(part) {
        if (!part){
          return;
        }
        if (part[0] === '/'){
          part = part.substring(1);
        }
        if (part[part.length - 1] === '/'){
          part = part.substring(0, part.length - 1);
        }
        parts.push(part);
      });
      return parts.join('/');
    },

    getUrl: function() {

      var self = this;
      var url = self.options.url;
      if (!url) {
        if (self.options.baseUrl && self.options.relativePath){
          url = self.options.baseUrl;
          if (url[url.length - 1] !== '/'){
            url = url + '/';
          }
          url = url + self.pathJoin(self.currentPath, self.options.relativePath);
        } else {
          var $form = self.$el.parents('form');
          if ($form.length > 0){
            url = $form.attr('action');
          } else {
            url = window.location.href;
          }
        }
      }
      return url;
    },

    getDzoneOptions: function() {
      var self = this;

      // clickable option
      if (typeof(self.options.clickable) === 'string') {
        if (self.options.clickable === 'true') {
          self.options.clickable = true;
        } else {
          self.options.clickable = false;
        }
      }

      var options = $.extend({}, self.options);
      options.url = self.getUrl();
      // XXX force to only upload one at a time,
      // right now we don't support multiple for backends
      options.uploadMultiple = false;

      delete options.wrap;
      delete options.wrapperTemplate;
      delete options.resultTemplate;
      delete options.autoCleanResults;
      delete options.fileaddedClassName;
      delete options.useTus;

      if (self.options.previewsContainer) {
        /*
         * if they have a select but it's not an id, let's make an id selector
         * so we can target the correct container. dropzone is weird here...
         */
        var $preview = self.$el.find(self.options.previewsContainer);
        if ($preview.length > 0) {
          options.previewsContainer = $preview[0];
        }
      }

      // XXX: do we need to allow this?
      options.autoProcessQueue = false;
      // options.addRemoveLinks = true;  // we show them in the template
      options.previewTemplate = PreviewTemplate;

      // if our element is a form we should force some values
      // https://github.com/enyo/dropzone/wiki/Combine-normal-form-with-Dropzone
      return options;
    },

    processUpload: function(options) {
      if (!options){
        options = {};
      }

      var self = this,
          processing = false,
          useTus = self.options.useTus,
          fileaddedClassName = self.options.fileaddedClassName,
          finished = options.finished;

      self.numFiles = self.dropzone.files.length;
      self.currentFile = 0;

      function process() {
        processing = true;
        if (self.dropzone.files.length === 0) {
          processing = false;
          self.$el.removeClass(fileaddedClassName);
          if (finished !== undefined && typeof(finished) === 'function'){
            finished();
          }
          return;
        }
        var file = self.dropzone.files[0];
        if ([Dropzone.SUCCESS, Dropzone.ERROR, Dropzone.CANCELED]
            .indexOf(file.status) !== -1) {
          // remove it
          self.dropzone.removeFile(file);
          process();
        } else if (file.status !== Dropzone.UPLOADING) {
          // start processing file
          if (useTus && window.tus) {
            // use tus upload if installed
            self.handleTusUpload(file);
          } else {
            // otherwise, just use dropzone to process
            self.currentFile += 1;
            self.dropzone.processFile(file);
          }
          setTimeout(process, 100);
        } else {
          // currently processing
          setTimeout(process, 100);
        }
      }
      process();
    },

    handleTusUpload: function(file) {
      /* this needs fixing... */
      var self = this,
          $preview = $(file.previewElement),
          chunkSize = 1024 * 1024 * 5; // 5mb chunk size

      file.status = Dropzone.UPLOADING;

      window.tus.upload(file, {
        endpoint: self.dropzone.options.url,
        headers: {
          'FILENAME': file.name
        },
        chunkSize: chunkSize
      }).fail(function() {
        if(window.DEBUG){
          console.alert('Error uploading with TUS resumable uploads');
        }
        file.status = Dropzone.ERROR;
      }).progress(function(e, bytesUploaded, bytesTotal) {
        var percentage = (bytesUploaded / bytesTotal * 100);
        self.$progress.attr('aria-valuenow', percentage).css('width', percentage + '%');
        self.$progress.html(_t('uploading...
') +
                            self.formatBytes(bytesUploaded) +
                            ' / ' + self.formatBytes(bytesTotal));
      }).done(function(url, file) {
        file.status = Dropzone.SUCCESS;
        self.dropzone.emit('success', file);
        self.dropzone.emit('complete', file);
      });
    },

    formatBytes: function(bytes) {
      var kb = Math.round(bytes / 1024);
      if (kb < 1024) {
        return kb + ' KiB';
      }
      var mb = Math.round(kb / 1024);
      if (mb < 1024) {
        return mb + ' MB';
      }
      return Math.round(mb / 1024) + ' GB';
    },

    setupRelatedItems: function($input) {
      var self = this;
      var options = self.options.relatedItems;
      if (self.options.initialFolder){
        $input.attr('value', self.options.initialFolder);
      }
      var ri = new RelatedItems($input, options);
      ri.$el.on('change', function() {
        var result = $(this).select2('data');
        if (result.length > 0){
          self.currentPath = result[0].path;
        } else {
          self.currentPath = null;
        }
        self.options.url = self.dropzone.options.url = self.getUrl();
      });
      return ri;
    }

  };
  registry.register(upload);
});
<|endoftext|>


<commit_before><commit_msg>Making sure API exposes offset parameter for retrieving tweets in chunks<commit_after><|endoftext|>


<commit_before><commit_msg>Remove 404 handler - url input will be disabled for desktop app + causes bugs with webpack-dev-server<commit_after><|endoftext|>


<commit_before><commit_msg>Whitespace cleanup, tabs to spaces<commit_after><|endoftext|>


<commit_before>const JobsHelper = require('./JobsHelper.js');
const Job = require('../models/Job.js');

module.exports = {
  getAll: async function(req, res) {
    let jobs = await Job.find();

    if (!jobs) throw 'No jobs found!';

    res.status(200).json(jobs);
  },
  createNew: async function(req, res) {
    // Remember the form's data
    const {
      companyName,
      title,
      description,
      imgUrl,
      website,
      location,
      expDate,
    } = req.body;

    // Change the form's data as needed
    const tags = JobsHelper.cleanTags(req.body.tags);

    // Create and save the new job as specified by mongoose model
    const cleanedJob = new Job({
      companyName,
      info: {
        title,
        description,
        imgUrl,
        website,
      },
      location,
      tags,
      expDate: new Date(expDate),
    });

    await cleanedJob.save();

    res.status(201).json({
      SUCCESS: cleanedJob,
    });
  },
  /*
    Returns one job as specified by a parameter.
    Parameters: job id.
  */
  getOne: async function(req, res) {
    let job = await Job.findById(req.params.jobId);

    if (!job) throw 'No job found!';

    res.status(200).json(job);
  },
  /*
    Updates a job with any fields that have been sent as a request body.
    Parameters: job id.
  */
  updateJob: async function(req, res) {
    // Remember the request's data
    const {
      companyName,
      title,
      description,
      imgUrl,
      website,
      location,
      expDate,
      tags,
      isFeatured,
    } = req.body;

    // Find the job by an id that was passed in
    let job = await Job.findById(req.params.jobId);

    if (!job) throw 'No job has been found!';

    // Update the job
    job.companyName = companyName || job.companyName;
    job.info.title = title || job.info.title;
    job.info.description = description || job.info.description;
    job.info.imgUrl = imgUrl || job.info.imgUrl;
    job.info.website = website || job.info.website;
    job.location = location || job.location;
    job.tags = tags ? JobsHelper.cleanTags(tags) : job.tags;
    // If isFeature is either a 'true' or 'false', assign the boolean representation, else don't update
    job.isFeatured = ~['true', 'false'].indexOf(isFeatured)
      ? Boolean(isFeatured && isFeatured.replace(/false/, ''))
      : job.isFeatured;
    job.expDate = expDate ? new Date(expDate) : job.expDate;

    // Save the job
    await job.save();

    res.status(200).json({
      UPDATED: job,
    });
  },
  /*
    Deletes a job.
    Parameters: job id.
  */
  deleteJob: async function(req, res) {
    const jobId = req.params.jobId;
    let job = await Job.findByIdAndRemove(jobId);

    res.status(200).json({
      REMOVED: {
        name: job.info.title,
        _id: jobId,
      },
    });
  },
};
<commit_msg>Convert isFeatured from Boolean to String<commit_after>const JobsHelper = require('./JobsHelper.js');
const Job = require('../models/Job.js');

module.exports = {
  getAll: async function(req, res) {
    let jobs = await Job.find();

    if (!jobs) throw 'No jobs found!';

    res.status(200).json(jobs);
  },
  createNew: async function(req, res) {
    // Remember the form's data
    const {
      companyName,
      title,
      description,
      imgUrl,
      website,
      location,
      expDate,
    } = req.body;

    // Change the form's data as needed
    const tags = JobsHelper.cleanTags(req.body.tags);

    // Create and save the new job as specified by mongoose model
    const cleanedJob = new Job({
      companyName,
      info: {
        title,
        description,
        imgUrl,
        website,
      },
      location,
      tags,
      expDate: new Date(expDate),
    });

    await cleanedJob.save();

    res.status(201).json({
      SUCCESS: cleanedJob,
    });
  },
  /*
    Returns one job as specified by a parameter.
    Parameters: job id.
  */
  getOne: async function(req, res) {
    let job = await Job.findById(req.params.jobId);

    if (!job) throw 'No job found!';

    res.status(200).json(job);
  },
  /*
    Updates a job with any fields that have been sent as a request body.
    Parameters: job id.
  */
  updateJob: async function(req, res) {
    // Remember the request's data
    const {
      companyName,
      title,
      description,
      imgUrl,
      website,
      location,
      expDate,
      tags,
      // isFeatured,
    } = req.body;

    // HACK Data from front is sent as JSON,
    // so isFeatured is already Boolean
    const isFeatured = String(req.body.isFeatured);

    // Find the job by an id that was passed in
    let job = await Job.findById(req.params.jobId);

    if (!job) throw 'No job has been found!';

    // Update the job
    job.companyName = companyName || job.companyName;
    job.info.title = title || job.info.title;
    job.info.description = description || job.info.description;
    job.info.imgUrl = imgUrl || job.info.imgUrl;
    job.info.website = website || job.info.website;
    job.location = location || job.location;
    job.tags = tags ? JobsHelper.cleanTags(tags) : job.tags;
    // If isFeature is either a 'true' or 'false', assign the boolean representation, else don't update
    job.isFeatured = ~['true', 'false'].indexOf(isFeatured)
      ? Boolean(isFeatured && isFeatured.replace(/false/, ''))
      : job.isFeatured;
    job.expDate = expDate ? new Date(expDate) : job.expDate;

    // Save the job
    await job.save();

    res.status(200).json({
      UPDATED: job,
    });
  },
  /*
    Deletes a job.
    Parameters: job id.
  */
  deleteJob: async function(req, res) {
    const jobId = req.params.jobId;
    let job = await Job.findByIdAndRemove(jobId);

    res.status(200).json({
      REMOVED: {
        name: job.info.title,
        _id: jobId,
      },
    });
  },
};
<|endoftext|>


<commit_before>const r = require('./../../../db');
const config = require('config');
const mailgun = require('mailgun-js')(config.get('api').mailgun);
const request = require('request');

const regex = /([\w#]+@[\w.!#$%&'*+\-/=?^_`{|}~]+) *"(.*?)" *([\w\W]+)/;

module.exports.alias = [
	'send',
	'mail'
];

module.exports.command = (message) => {
	const name = message.author.username
		.replace(/ /g, '+')
		.replace(/\W/g, '=');

	const email = regex.exec(message.input);

	r.table('users')
		.get(message.author.id)
		.run(r.conn, (err1, res) => {
			if (err1) {
				message.channel.createMessage('An error occured while searching for registrations.');
			} else if (!res) {
				message.channel.createMessage('You have not registered yet. Please run `dmail register` to register an E-Mail with your account.');
			} else if (!email) {
				message.channel.createMessage(`Invalid use of command. Expected input: \`dmail ${message.command} email@example.com "subject" content\`\nThe "quotes" around the subject are required.`);
			} else {
				const data = {
					from: `${message.author.username}#${message.author.discriminator} <${name}#${message.author.discriminator}@discordmail.com>`,
					to: email[1],
					subject: email[2],
					text: email[3]
				};

				if (message.attachments) {
					data.attachment = [];

					message.attachments.forEach((file) => {
						request({ url: file.url, encoding: null }, (rerr, rres, rbody) => {
							data.attachment.push(new mailgun.Attachment({
								data: rbody,
								filename: file.filename,
								knownLength: file.size
							}));
						});
					});
				}

				mailgun.messages().send(data, (err2) => {
					if (err2) {
						message.channel.createMessage(`Failed to send E-Mail: ${err2.message}`);
					} else {
						message.channel.createMessage('Successfully sent E-Mail.');
					}
				});
			}
		});
};
<commit_msg>console log attatching<commit_after>const r = require('./../../../db');
const config = require('config');
const mailgun = require('mailgun-js')(config.get('api').mailgun);
const request = require('request');

const regex = /([\w#]+@[\w.!#$%&'*+\-/=?^_`{|}~]+) *"(.*?)" *([\w\W]+)/;

module.exports.alias = [
	'send',
	'mail'
];

module.exports.command = (message) => {
	const name = message.author.username
		.replace(/ /g, '+')
		.replace(/\W/g, '=');

	const email = regex.exec(message.input);

	r.table('users')
		.get(message.author.id)
		.run(r.conn, (err1, res) => {
			if (err1) {
				message.channel.createMessage('An error occured while searching for registrations.');
			} else if (!res) {
				message.channel.createMessage('You have not registered yet. Please run `dmail register` to register an E-Mail with your account.');
			} else if (!email) {
				message.channel.createMessage(`Invalid use of command. Expected input: \`dmail ${message.command} email@example.com "subject" content\`\nThe "quotes" around the subject are required.`);
			} else {
				const data = {
					from: `${message.author.username}#${message.author.discriminator} <${name}#${message.author.discriminator}@discordmail.com>`,
					to: email[1],
					subject: email[2],
					text: email[3]
				};

				if (message.attachments) {
					console.log('Attatching stuff!');
					data.attachment = [];

					message.attachments.forEach((file) => {
						request({ url: file.url, encoding: null }, (rerr, rres, rbody) => {
							data.attachment.push(new mailgun.Attachment({
								data: rbody,
								filename: file.filename,
								knownLength: file.size
							}));
						});
					});
				}

				mailgun.messages().send(data, (err2) => {
					if (err2) {
						message.channel.createMessage(`Failed to send E-Mail: ${err2.message}`);
					} else {
						message.channel.createMessage('Successfully sent E-Mail.');
					}
				});
			}
		});
};
<|endoftext|>


<commit_before>const r = require('./../../../db');
const config = require('config');
const mailgun = require('mailgun-js')(config.get('api').mailgun);
const request = require('request');

const regex = /([\w#]+@[\w.!#$%&'*+\-/=?^_`{|}~]+) *"(.*?)" *([\w\W]+)/;

module.exports.alias = [
	'send',
	'mail'
];

module.exports.command = (message) => {
	const name = message.author.username
		.replace(/ /g, '+')
		.replace(/\W/g, '=');

	const email = regex.exec(message.input);

	r.table('users')
		.get(message.author.id)
		.run(r.conn, (err1, res) => {
			if (err1) {
				message.channel.createMessage('An error occured while searching for registrations.');
			} else if (!res) {
				message.channel.createMessage('You have not registered yet. Please run `dmail register` to register an E-Mail with your account.');
			} else if (!email) {
				message.channel.createMessage(`Invalid use of command. Expected input: \`dmail ${message.command} email@example.com "subject" content\`\nThe "quotes" around the subject are required.`);
			} else {
				const data = {
					from: `${message.author.username}#${message.author.discriminator} <${name}#${message.author.discriminator}@discordmail.com>`,
					to: email[1],
					subject: email[2],
					text: email[3]
				};

				if (message.attachments) {
					console.log('Attatching stuff!');
					data.attachment = [];

					message.attachments.forEach((file) => {
						request(file.url, (rerr, rres, rbody) => {
							data.attachment.push(rbody);
						});
					});
				}

				mailgun.messages().send(data, (err2) => {
					if (err2) {
						message.channel.createMessage(`Failed to send E-Mail: ${err2.message}`);
					} else {
						message.channel.createMessage('Successfully sent E-Mail.');
					}
				});
			}
		});
};
<commit_msg>simple<commit_after>const r = require('./../../../db');
const config = require('config');
const mailgun = require('mailgun-js')(config.get('api').mailgun);
const request = require('request');

const regex = /([\w#]+@[\w.!#$%&'*+\-/=?^_`{|}~]+) *"(.*?)" *([\w\W]+)/;

module.exports.alias = [
	'send',
	'mail'
];

module.exports.command = (message) => {
	const name = message.author.username
		.replace(/ /g, '+')
		.replace(/\W/g, '=');

	const email = regex.exec(message.input);

	r.table('users')
		.get(message.author.id)
		.run(r.conn, (err1, res) => {
			if (err1) {
				message.channel.createMessage('An error occured while searching for registrations.');
			} else if (!res) {
				message.channel.createMessage('You have not registered yet. Please run `dmail register` to register an E-Mail with your account.');
			} else if (!email) {
				message.channel.createMessage(`Invalid use of command. Expected input: \`dmail ${message.command} email@example.com "subject" content\`\nThe "quotes" around the subject are required.`);
			} else {
				const data = {
					from: `${message.author.username}#${message.author.discriminator} <${name}#${message.author.discriminator}@discordmail.com>`,
					to: email[1],
					subject: email[2],
					text: email[3]
				};

				if (message.attachments && message.attachments[0]) {
					console.log('Attatching stuff!');

					data.attachment = request(message.attachments[0].url);
				}

				mailgun.messages().send(data, (err2) => {
					if (err2) {
						message.channel.createMessage(`Failed to send E-Mail: ${err2.message}`);
					} else {
						message.channel.createMessage('Successfully sent E-Mail.');
					}
				});
			}
		});
};
<|endoftext|>


<commit_before>// var accountKey = require('./bing-search-api-key.js')
var accountKey = process.env.BING_NEWS_API
var Bing = require('node-bing-api')({ accKey: accountKey });

module.exports = function(queryStr, callback){
	Bing.news(queryStr, {
	    top: 5,  // Number of results (max 15)
	    skip: 3,   // Skip first 3 results
	    newsSortBy: "Date", //Choices are: Date, Relevance
	    newsCategory: "rt_Business" // Choices are:
	                                //   rt_Business
	                                //   rt_Entertainment
	                                //   rt_Health
	                                //   rt_Politics
	                                //   rt_Sports
	                                //   rt_US
	                                //   rt_World
	                                //   rt_ScienceAndTechnology
	    // newsLocationOverride: "US.WA" // Only for en-US market
	  }, function(error, res, body){
	  	if(error){
	  		console.log('ERROR!')
	  	} else {
	  		callback(body);
	  	}
	  });
}

// ADD THIS FILE TO .GITIGNORE
<commit_msg>Makes fixto bing api helper<commit_after>// var accountKey = require('./bing-search-api-key.js')
var accountKey = process.env.BING_NEWS_API
var Bing = require('node-bing-api')({ accKey: accountKey });

module.exports = function(queryStr, callback){
	Bing.news(queryStr, {
	    top: 5,  // Number of results (max 15)
	    newsSortBy: "Date", //Choices are: Date, Relevance
	    newsCategory: "rt_Business" // Choices are:
	                                //   rt_Business
	                                //   rt_Entertainment
	                                //   rt_Health
	                                //   rt_Politics
	                                //   rt_Sports
	                                //   rt_US
	                                //   rt_World
	                                //   rt_ScienceAndTechnology
	    // newsLocationOverride: "US.WA" // Only for en-US market
	  }, function(error, res, body){
	  	if(error){
	  		console.log('ERROR!')
	  	} else {
	  		callback(body);
	  	}
	  });
}

// ADD THIS FILE TO .GITIGNORE
<|endoftext|>


<commit_before>// Current version turns on office light when there's motion
// this code has an adaptive algorithm that extends the time the
// light is on the more often it is turned on.

const hue = require("node-hue-api");
const moment = require("moment");
const constants = require('./constants');
const api = new hue.HueApi(constants.ip, constants.username);
const particle = require('./lib/Particle');
const _ = require('underscore');
const lightState = hue.lightState;
const huelib = require('./lib/Hue');
const lightName = require('./lib/LightName');

const lightIntervals = [10, 20, 60, 5*60, 10*60, 15*60]; // seconds
let lightIntervalIndex = 0;

let senseTimerHandle = null;
let lightTimerHandle = null;
let motionSticky = false;

// called whenever motion is seen
const onMotion = function() {
	console.log(`saw motion`);
	console.log(`setting light timer to ${getLightInterval()} seconds`);
	motionSticky = true;
	turnLightsOn();
	clearTimeout(lightTimerHandle);
	lightTimerHandle = setTimeout(turnLightsOff, getLightInterval() * 1000);

	// set a timer that determines next time to see if there's been motion
	// set twice as long as light is on, so that user can wave to turn light on
	// when it goes off, and process is still sensing
	if (!senseTimerHandle) {
		console.log(`setting sense timer to ${getLightInterval() *2} seconds`);
		senseTimerHandle = setTimeout(senseTimerExpired, getLightInterval() * 2 * 1000);
	}
}

const senseTimerExpired = function() {
	console.log(`sense timer expired`);
	senseTimerHandle = null;
	if (motionSticky) {
		console.log(`there was motion during sense period`);
		motionSticky = false;
		increaseLightInterval();
		// set timer again and wait another sense interval
		console.log(`setting sense timer for ${getLightInterval() * 2} seconds`);
		senseTimerHandle = setTimeout(senseTimerExpired, getLightInterval() * 2 * 1000);
	} else {
		// no motion in last sense interval
		// set sense interval back to default and turn lights off
		resetLightInterval()
		console.log(`no motion in sense interval, light interval is now ${getLightInterval()} seconds`);
	}
}

const getLightInterval = function() {
	return lightIntervals[lightIntervalIndex];
}

const increaseLightInterval = function() {
	lightIntervalIndex = Math.min(lightIntervalIndex + 1, lightIntervals.length);
}

const resetLightInterval = function() {
	lightIntervalIndex = 0;
}

// turn light to low on then fade to on. Turn light to low off, then fade to off
var lights = null; // set by main from argv
const lowOn = lightState.create().on(true).hsb(100, 30, 0);
const on = lightState.create().on(true).white(325, 100	).transition(3000);
const lowOff = lightState.create().on(true).hsb(250, 100, 0).transition(10000);
const off = lightState.create().on(false);

// used to transition between low on to on and low off to off states.
let lightTimer = null;
let _lightState = 'off'; // off, lowOff, on, lowOn

const turnLightsOn = function() {
	if (isDaytime()) { return }
	if (_lightState === 'off') {
		// set to lowOn, wait, On
		clearTimeout(lightTimer);
		setLights(lights, lowOn);
		_lightState = 'lowOn';
		lightTimer = setTimeout(function() {
			setLights(lights, on);
			_lightState = 'on';
		}, 1000)
	} else if (_lightState === 'lowOff') {
		// straight to on
		clearTimeout(lightTimer);
		setLights(lights, on);
		_lightState = 'on';
	}
	// if _lightState is lowOn or on, do nothing
}

const turnLightsOff = function() {
	if (_lightState === 'on' || _lightState === 'lowOn') {
		console.log(`turning lights off`);
		clearTimeout(lightTimer);
		setLights(lights, lowOff);
		_lightState = 'lowOff';
		lightTimer = setTimeout(function() {
			setLights(lights, off);
			_lightState = 'off';
		}, 10000)
	}
}

const setLights = function(lights, lightState) {
	lights.forEach(function(light) {
		api.setLightState(light.id, lightState);
	})
}

const isDaytime = function() {
	let h = moment().hour();
	return (h > 7 && h < 17);
}

const main = function() {
	if (process.argv.length < 3) {
		usage();
		process.exit(0);
	}

	console.log(`starting`);
	lights = lightName.lightsFromNamesOrExit(process.argv.slice(2));
	setLights(lights, off);

	let particleCallInProgress = false;
	setInterval(function() {
		if (!particleCallInProgress) {
			particleCallInProgress = true;
			particle.getMotion()
			.then(function(reply) {
				particleCallInProgress = false;
				if (reply.return_value !== 0) {
					onMotion();
				}
			})
		}
	}, 1000);
}

const usage = function() {
	console.log("usage: node " + __filename + " LIGHTNAME LIGHTNAME ...");
	console.log("Each LIGHTNAME is the name of a light defined in constants.js:");
	var lightNames = _.pluck(constants.lights, 'name').join(", ");
	console.log('"' + lightNames + '"');
}

main();

<commit_msg>added try catch<commit_after>// Current version turns on office light when there's motion
// this code has an adaptive algorithm that extends the time the
// light is on the more often it is turned on.

const hue = require("node-hue-api")
const moment = require("moment")
const constants = require('./constants')
const api = new hue.HueApi(constants.ip, constants.username)
const particle = require('./lib/Particle')
const _ = require('underscore')
const lightState = hue.lightState
const huelib = require('./lib/Hue')
const lightName = require('./lib/LightName')

const lightIntervals = [10, 20, 60, 5*60, 10*60, 15*60] // seconds
let lightIntervalIndex = 0

let senseTimerHandle = null
let lightTimerHandle = null
let motionSticky = false

// called whenever motion is seen
const onMotion = function() {
	console.log(`saw motion`)
	console.log(`setting light timer to ${getLightInterval()} seconds`)
	motionSticky = true
	turnLightsOn()
	clearTimeout(lightTimerHandle)
	lightTimerHandle = setTimeout(turnLightsOff, getLightInterval() * 1000)

	// set a timer that determines next time to see if there's been motion
	// set twice as long as light is on, so that user can wave to turn light on
	// when it goes off, and process is still sensing
	if (!senseTimerHandle) {
		console.log(`setting sense timer to ${getLightInterval() *2} seconds`)
		senseTimerHandle = setTimeout(senseTimerExpired, getLightInterval() * 2 * 1000)
	}
}

const senseTimerExpired = function() {
	console.log(`sense timer expired`)
	senseTimerHandle = null
	if (motionSticky) {
		console.log(`there was motion during sense period`)
		motionSticky = false
		increaseLightInterval()
		// set timer again and wait another sense interval
		console.log(`setting sense timer for ${getLightInterval() * 2} seconds`)
		senseTimerHandle = setTimeout(senseTimerExpired, getLightInterval() * 2 * 1000)
	} else {
		// no motion in last sense interval
		// set sense interval back to default and turn lights off
		resetLightInterval()
		console.log(`no motion in sense interval, light interval is now ${getLightInterval()} seconds`)
	}
}

const getLightInterval = function() {
	return lightIntervals[lightIntervalIndex]
}

const increaseLightInterval = function() {
	lightIntervalIndex = Math.min(lightIntervalIndex + 1, lightIntervals.length)
}

const resetLightInterval = function() {
	lightIntervalIndex = 0
}

// turn light to low on then fade to on. Turn light to low off, then fade to off
var lights = null // set by main from argv
const lowOn = lightState.create().on(true).hsb(100, 30, 0)
const on = lightState.create().on(true).white(325, 100	).transition(3000)
const lowOff = lightState.create().on(true).hsb(250, 100, 0).transition(10000)
const off = lightState.create().on(false)

// used to transition between low on to on and low off to off states.
let lightTimer = null
let _lightState = 'off' // off, lowOff, on, lowOn

const turnLightsOn = function() {
	if (isDaytime()) { return }
	if (_lightState === 'off') {
		// set to lowOn, wait, On
		clearTimeout(lightTimer)
		setLights(lights, lowOn)
		_lightState = 'lowOn'
		lightTimer = setTimeout(function() {
			setLights(lights, on)
			_lightState = 'on'
		}, 1000)
	} else if (_lightState === 'lowOff') {
		// straight to on
		clearTimeout(lightTimer)
		setLights(lights, on)
		_lightState = 'on'
	}
	// if _lightState is lowOn or on, do nothing
}

const turnLightsOff = function() {
	if (_lightState === 'on' || _lightState === 'lowOn') {
		console.log(`turning lights off`)
		clearTimeout(lightTimer)
		setLights(lights, lowOff)
		_lightState = 'lowOff'
		lightTimer = setTimeout(function() {
			setLights(lights, off)
			_lightState = 'off'
		}, 10000)
	}
}

const setLights = function(lights, lightState) {
	lights.forEach(function(light) {
		api.setLightState(light.id, lightState)
	})
}

const isDaytime = function() {
	let h = moment().hour()
	return (h > 7 && h < 17)
}

const main = function() {
	if (process.argv.length < 3) {
		usage()
		process.exit(0)
	}

	console.log(`starting`)
	lights = lightName.lightsFromNamesOrExit(process.argv.slice(2))
	setLights(lights, off)

	let particleCallInProgress = false
	setInterval(function() {
		if (!particleCallInProgress) {
			particleCallInProgress = true
			particle.getMotion()
			.then(function(reply) {
				particleCallInProgress = false
				if (reply.return_value !== 0) {
					onMotion()
				}
			})
			.catch(function(err) {
				console.log(`Caught error: ${err}`)
				particleCallInProgress = false
			})
		}
	}, 1000)
}

const usage = function() {
	console.log("usage: node " + __filename + " LIGHTNAME LIGHTNAME ...")
	console.log("Each LIGHTNAME is the name of a light defined in constants.js:")
	var lightNames = _.pluck(constants.lights, 'name').join(", ")
	console.log('"' + lightNames + '"')
}

main()

<|endoftext|>


<commit_before>import rp from 'request-promise';

import Logger from '../utils/Logger';
import GoPipelineParser from '../utils/GoPipelineParser';
import Util from '../utils/Util';

export default class GoBuildService {

  constructor(goConfig) {
    this.conf = goConfig;
  }

  /**
   * @returns {Promise<Array<string>>} All available pipelines from the go.cd server
   */
  getAllPipelines() {
    const options = Util.createRequestOptions(`${this.conf.serverUrl}/go/api/pipelines.xml`, this.conf);

    return rp(options)
      .then((res) => {
        // Parse response xml
        return GoPipelineParser.parsePipelineNames(res).then((pipelineNames) => {
          return pipelineNames;
        }, (error) => {
          Logger.error('Failed to parse pipelines.xml');
          throw error;
        });
      }).catch((err) => {
        Logger.error('Failed to retrieve pipeline names');
        throw err
      });
  }

  /**
   * @returns {Promise<Array<Object>>} All available pipeline groups from the go.cd server
   */
  getPipelineGroups() {
    const options = Util.createRequestOptions(`${this.conf.serverUrl}/go/api/config/pipeline_groups`, this.conf);

    return rp(options)
      .then((res) => {
        return JSON.parse(res);
      }).catch((err) => {
        Logger.error('Failed to retrieve pipeline groups');
        throw err
      });
  }

  /**
   * Retrive all pipelines paused info.
   *
   * @return {Promise<Object}
   * Example
   * {
   *   'pipeline1' : {
   *     paused : false,
   *     paused_by: null,
   *     pause_reason: null},
   *   'pipeline2' : {
   *     paused : true,
   *     paused_by : 'me',
   *     pause_reason : 'Under construction'
   *   }
   * }
   */
  getPipelinesPauseInfo() {
    const options = Util.createRequestOptions(`${this.conf.serverUrl}/go/api/dashboard`, this.conf, true, {'Accept' : 'application/vnd.go.cd.v1+json'});

    return rp(options)
      .then((res) => {
        // Return map with pipeline name as key.
        return res._embedded.pipeline_groups.reduce((acc, curr) => {
          curr._embedded.pipelines.forEach((cp) => {
            acc[cp.name] = cp.pause_info;
          });
          return acc;
        }, {});
      })
      .catch((err) => {
        Logger.error('Failed to retrieve pipeline pause information');
        return {};
      })
  }

  /**
   * @param   {string}          name  Name of the pipeline
   * @returns {Promise<Object>}       Pipeline instance.
   * Example
   * {
   *    name : 'id,
   *    buildtime : 1457085089646,
   *    author: 'Bobby Malone',
   *    counter: 255,
   *    paused: false,
   *    health: 2,
   *    stageresults: [
   *      {
   *        name: 'Build',
   *        status: 'passed',
   *        jobresults: [{
   *          name: 'build-job',
   *          result: 'passed',
   *          schedueld: 1457085089646
   *        }]
   *      },
   *      {
   *        name: 'Test',
   *        status: 'building'
   *        jobresults: []
   *      }]
   * }
   */
  getPipelineHistory(name) {
    const options = Util.createRequestOptions(`${this.conf.serverUrl}/go/api/pipelines/${name}/history/0`, this.conf, true);

    return rp(options)
      .then(res => GoPipelineParser.parsePipelineResult(res.pipelines.slice(0, 5)))
      .catch((err) => {
        Logger.error(`Failed to get pipeline history for pipeline "${name}" returning last result, ${err.statusCode}: ${err.message}`);
        return this.pipelines ? this.pipelines.filter((p) => p && p.name === name)[0] : null;
      });
  }

}
<commit_msg>Adapt to gocd v19.3.0<commit_after>import rp from 'request-promise';

import Logger from '../utils/Logger';
import GoPipelineParser from '../utils/GoPipelineParser';
import Util from '../utils/Util';

export default class GoBuildService {

  constructor(goConfig) {
    this.conf = goConfig;
  }

  /**
   * @returns {Promise<Array<string>>} All available pipelines from the go.cd server
   */
  getAllPipelines() {
    const options = Util.createRequestOptions(`${this.conf.serverUrl}/go/api/pipelines.xml`, this.conf);

    return rp(options)
      .then((res) => {
        // Parse response xml
        return GoPipelineParser.parsePipelineNames(res).then((pipelineNames) => {
          return pipelineNames;
        }, (error) => {
          Logger.error('Failed to parse pipelines.xml');
          throw error;
        });
      }).catch((err) => {
        Logger.error('Failed to retrieve pipeline names');
        throw err
      });
  }

  /**
   * @returns {Promise<Array<Object>>} All available pipeline groups from the go.cd server
   */
  getPipelineGroups() {
    const options = Util.createRequestOptions(`${this.conf.serverUrl}/go/api/config/pipeline_groups`, this.conf);

    return rp(options)
      .then((res) => {
        return JSON.parse(res);
      }).catch((err) => {
        Logger.error('Failed to retrieve pipeline groups');
        throw err
      });
  }

  /**
   * Retrive all pipelines paused info.
   *
   * @return {Promise<Object}
   * Example
   * {
   *   'pipeline1' : {
   *     paused : false,
   *     paused_by: null,
   *     pause_reason: null},
   *   'pipeline2' : {
   *     paused : true,
   *     paused_by : 'me',
   *     pause_reason : 'Under construction'
   *   }
   * }
   */
  getPipelinesPauseInfo() {
    const options = Util.createRequestOptions(`${this.conf.serverUrl}/go/api/dashboard`, this.conf, true, {'Accept' : 'application/vnd.go.cd.v3+json'});
    return rp(options)
      .then((res) => {
        // Return map with pipeline name as key.
        return res._embedded.pipeline_groups.reduce((acc, curr) => {
          curr.pipelines.forEach((cp) => {
            acc[cp.name] = cp.pause_info;
          });
          return acc;
        }, {});
      })
      .catch((err) => {
        Logger.error('Failed to retrieve pipeline pause information');
        return {};
      })
  }

  /**
   * @param   {string}          name  Name of the pipeline
   * @returns {Promise<Object>}       Pipeline instance.
   * Example
   * {
   *    name : 'id,
   *    buildtime : 1457085089646,
   *    author: 'Bobby Malone',
   *    counter: 255,
   *    paused: false,
   *    health: 2,
   *    stageresults: [
   *      {
   *        name: 'Build',
   *        status: 'passed',
   *        jobresults: [{
   *          name: 'build-job',
   *          result: 'passed',
   *          schedueld: 1457085089646
   *        }]
   *      },
   *      {
   *        name: 'Test',
   *        status: 'building'
   *        jobresults: []
   *      }]
   * }
   */
  getPipelineHistory(name) {
    const options = Util.createRequestOptions(`${this.conf.serverUrl}/go/api/pipelines/${name}/history/0`, this.conf, true);

    return rp(options)
      .then(res => GoPipelineParser.parsePipelineResult(res.pipelines.slice(0, 5)))
      .catch((err) => {
        Logger.error(`Failed to get pipeline history for pipeline "${name}" returning last result, ${err.statusCode}: ${err.message}`);
        return this.pipelines ? this.pipelines.filter((p) => p && p.name === name)[0] : null;
      });
  }

}
<|endoftext|>