<commit_before>import { createSelector } from "@reduxjs/toolkit";

const getUsers = state => state.users;
export const getLocalUser = state => state.localUser;
const getRooms = state => state.rooms;
const getLocalRoomMembersByRoom = state => state.localRoomMembers.byRoom;
const getMessagesByRoom = state => state.messages.byRoom;
const getLocalUserPresent = state => state.localUser.present;
export const getAuthorUser = (state, props) => state.users[props.authorId];
export const getMessageAuthor = (state, props) => state.users[props.message.author];
export const getLastMessage = state => state.messages.lastMessage;
export const getNick = state => state.localUser.nick;
export const getDesktopMentionNotifications = state => state.userSettings?.desktopMentionNotifications;
export const getShowDesktopNotification = state => state.notifications.showDesktop;

export const getRoomIds = createSelector([getRooms], (rooms = {}) => Object.keys(rooms));

export const getActiveRoom = createSelector([getRooms], rooms => _.find(rooms, { current: true }));

export const getActiveRoomId = createSelector([getActiveRoom], (activeRoom = {}) => activeRoom._id);

export const getTotalUnreadMessageCount = createSelector([getLocalRoomMembersByRoom], localRoomMembersByRoom =>
	_.reduce(localRoomMembersByRoom, (count, roomMember) => count + (roomMember.unreadMessageCount || 0), 0)
);

export const hasAnyUnreadMention = createSelector([getLocalRoomMembersByRoom], localRoomMembersByRoom =>
	_.some(localRoomMembersByRoom, roomMember => roomMember.unreadMessageCount > 0 && roomMember.unreadMention)
);

export const getRoomMembers = createSelector(
	[getActiveRoomId, getRooms],
	(activeRoomId, rooms = {}) => (rooms[activeRoomId] || {}).$members
);

export const getRoomTopic = createSelector([getActiveRoom], room => {
	if (!room) return;
	return {
		tokens: room.topicTokens,
		text: room.topic
	};
});

const getChatByRoom = state => state.chatInput.byRoom;

export const getLocalMessages = createSelector(
	[getActiveRoomId, getMessagesByRoom, getLocalUser],
	(activeRoomId, messagesByRoom, localUser) => {
		if (!activeRoomId) return [];
		return _.filter(messagesByRoom[activeRoomId], { author: localUser._id });
	}
);

const getChatForCurrentRoom = createSelector(
	[getActiveRoomId, getChatByRoom],
	(activeRoomId, chatByRoomId) => chatByRoomId[activeRoomId]
);

export const getAppendTextForCurrentRoom = createSelector(
	[getChatForCurrentRoom],
	(getChatForCurrentRoom = {}) => getChatForCurrentRoom.appendText
);

export const getCurrentRoomTextEmpty = createSelector([getAppendTextForCurrentRoom], (text = "") => text.length === 0);

export const getEditedMessageForCurrentRoom = createSelector(
	[getChatForCurrentRoom],
	(chatForCurrentRoom = {}) => chatForCurrentRoom.editedMessage
);

export const getSection = state => {
	const sectionMatch = /2\/(\w+)/.exec(state.router.location.pathname);
	return sectionMatch ? sectionMatch[1] : null;
};

export const getDocumentTitle = createSelector(
	[getTotalUnreadMessageCount, hasAnyUnreadMention, getSection, getActiveRoom],
	(totalUnreadMessageCount, anyUnreadMention, section, activeRoom = {}) => {
		const unread = totalUnreadMessageCount > 0 ? `${anyUnreadMention ? "*" : ""}(${totalUnreadMessageCount}) ` : "";
		const roomName = activeRoom.name || (section === "settings" && "Settings") || "";
		const leading = `${unread}${roomName}`;
		const leadingBreak = leading ? " - " : "";
		return `${leading}${leadingBreak}Bunker`;
	}
);

export const getSortedRoomMemberUsers = createSelector([getUsers, getRoomMembers], (users, roomMembers) => {
	if (!roomMembers) return;

	const emptyUser = { connected: false, present: false, nick: "", empty: true };

	return _.orderBy(
		roomMembers,
		[
			roomMember => (users[roomMember.user] || emptyUser).connected,
			roomMember => (users[roomMember.user] || emptyUser).present,
			roomMember => (users[roomMember.user] || emptyUser).nick.toLowerCase()
		],
		["desc", "desc", "asc"]
	);
});

export const getSortedRoomMemberUserIds = createSelector([getSortedRoomMemberUsers], roomMemberUsers =>
	roomMemberUsers.map(roomMemberUser => roomMemberUser.user)
);

export const getRoomMembersForCurrentRoomHash = createSelector([getRoomMembers], roomMembers =>
	_.keyBy(roomMembers, "user")
);

export const getRoomMemberRoleForCurrentRoomByUserId = userId =>
	createSelector([getRoomMembersForCurrentRoomHash], roomMembersHash => roomMembersHash[userId]?.role);

export const getLastMessageContainsMention = createSelector([getLastMessage, getNick], (lastMessage, nick) => {
	if (!lastMessage) return;

	const hasMention = lastMessage.tokens.some(token => {
		if (token.type !== "word") return false;
		return token.value.toUpperCase().includes(nick.toUpperCase()) || token.value.includes("@all");
	});

	if (hasMention) return lastMessage;
});

export const getShowNotification = createSelector(
	[getLastMessageContainsMention, getLocalUserPresent],
	(lastMessage, localUserPresent) => !!lastMessage && !localUserPresent
);

export const getLastMentionRoomName = createSelector(
	[getLastMessageContainsMention, getRooms],
	(lastMessageMention = {}, rooms = {}) => rooms[lastMessageMention.room]?.name
);

export const getLastMentionAuthorNick = createSelector(
	[getLastMessageContainsMention, getUsers],
	(lastMessageMention = {}, users = {}) => users[lastMessageMention.author]?.nick
);

export const getLastMentionText = createSelector(
	[getLastMessageContainsMention],
	(lastMessageMention = {}) => lastMessageMention.text
);

export const getRoomIsCurrent = roomId => createSelector([getActiveRoomId], activeRoomId => activeRoomId === roomId);

export const getRoomName = roomId => state => getRooms(state)[roomId]?.name;

export const getUnreadMention = roomId => state => getLocalRoomMembersByRoom(state)[roomId]?.unreadMention;

export const getUnreadMessageCount = roomId => state => getLocalRoomMembersByRoom(state)[roomId]?.unreadMessageCount;

export const getUnreadRoomIds = createSelector([getLocalRoomMembersByRoom], localRoomMembersByRoom =>
	_(localRoomMembersByRoom)
		.filter(roomMember => roomMember.unreadMessageCount > 0)
		.orderBy(["unreadStart"])
		.map("room")
		.value()
);

export const getReadRoomIds = createSelector([getLocalRoomMembersByRoom], localRoomMembersByRoom =>
	_(localRoomMembersByRoom)
		.filter(roomMember => !roomMember.unreadMessageCount)
		.sortBy("roomOrder")
		.map("room")
		.value()
);

export const getIsDisconnected = state => !state.socket.connected;

export const newIsVersionV2Deployed = ({ version }) =>
	version.clientVersionV2New && version.clientVersionV2 !== version.clientVersionV2New;
<commit_msg>fixed topic explosion<commit_after>import { createSelector } from "@reduxjs/toolkit";

const getUsers = state => state.users;
export const getLocalUser = state => state.localUser;
const getRooms = state => state.rooms;
const getLocalRoomMembersByRoom = state => state.localRoomMembers.byRoom;
const getMessagesByRoom = state => state.messages.byRoom;
const getLocalUserPresent = state => state.localUser.present;
export const getAuthorUser = (state, props) => state.users[props.authorId];
export const getMessageAuthor = (state, props) => state.users[props.message.author];
export const getLastMessage = state => state.messages.lastMessage;
export const getNick = state => state.localUser.nick;
export const getDesktopMentionNotifications = state => state.userSettings?.desktopMentionNotifications;
export const getShowDesktopNotification = state => state.notifications.showDesktop;

export const getRoomIds = createSelector([getRooms], (rooms = {}) => Object.keys(rooms));

export const getActiveRoom = createSelector([getRooms], rooms => _.find(rooms, { current: true }));

export const getActiveRoomId = createSelector([getActiveRoom], (activeRoom = {}) => activeRoom._id);

export const getTotalUnreadMessageCount = createSelector([getLocalRoomMembersByRoom], localRoomMembersByRoom =>
	_.reduce(localRoomMembersByRoom, (count, roomMember) => count + (roomMember.unreadMessageCount || 0), 0)
);

export const hasAnyUnreadMention = createSelector([getLocalRoomMembersByRoom], localRoomMembersByRoom =>
	_.some(localRoomMembersByRoom, roomMember => roomMember.unreadMessageCount > 0 && roomMember.unreadMention)
);

export const getRoomMembers = createSelector(
	[getActiveRoomId, getRooms],
	(activeRoomId, rooms = {}) => (rooms[activeRoomId] || {}).$members
);

export const getRoomTopic = createSelector([getActiveRoom], room => {
	if (!room) return;
	return {
		tokens: room.topicTokens,
		text: room.topic
	};
});

const getChatByRoom = state => state.chatInput.byRoom;

export const getLocalMessages = createSelector(
	[getActiveRoomId, getMessagesByRoom, getLocalUser],
	(activeRoomId, messagesByRoom, localUser) => {
		if (!activeRoomId) return [];
		return _.filter(messagesByRoom[activeRoomId], { author: localUser._id });
	}
);

const getChatForCurrentRoom = createSelector(
	[getActiveRoomId, getChatByRoom],
	(activeRoomId, chatByRoomId) => chatByRoomId[activeRoomId]
);

export const getAppendTextForCurrentRoom = createSelector(
	[getChatForCurrentRoom],
	(getChatForCurrentRoom = {}) => getChatForCurrentRoom.appendText
);

export const getCurrentRoomTextEmpty = createSelector([getAppendTextForCurrentRoom], (text = "") => text.length === 0);

export const getEditedMessageForCurrentRoom = createSelector(
	[getChatForCurrentRoom],
	(chatForCurrentRoom = {}) => chatForCurrentRoom.editedMessage
);

export const getSection = state => {
	const sectionMatch = /2\/(\w+)/.exec(state.router.location.pathname);
	return sectionMatch ? sectionMatch[1] : null;
};

export const getDocumentTitle = createSelector(
	[getTotalUnreadMessageCount, hasAnyUnreadMention, getSection, getActiveRoom],
	(totalUnreadMessageCount, anyUnreadMention, section, activeRoom = {}) => {
		const unread = totalUnreadMessageCount > 0 ? `${anyUnreadMention ? "*" : ""}(${totalUnreadMessageCount}) ` : "";
		const roomName = activeRoom.name || (section === "settings" && "Settings") || "";
		const leading = `${unread}${roomName}`;
		const leadingBreak = leading ? " - " : "";
		return `${leading}${leadingBreak}Bunker`;
	}
);

export const getSortedRoomMemberUsers = createSelector([getUsers, getRoomMembers], (users, roomMembers) => {
	if (!roomMembers) return;

	const emptyUser = { connected: false, present: false, nick: "", empty: true };

	return _.orderBy(
		roomMembers,
		[
			roomMember => (users[roomMember.user] || emptyUser).connected,
			roomMember => (users[roomMember.user] || emptyUser).present,
			roomMember => (users[roomMember.user] || emptyUser).nick.toLowerCase()
		],
		["desc", "desc", "asc"]
	);
});

export const getSortedRoomMemberUserIds = createSelector([getSortedRoomMemberUsers], roomMemberUsers =>
	roomMemberUsers.map(roomMemberUser => roomMemberUser.user)
);

export const getRoomMembersForCurrentRoomHash = createSelector([getRoomMembers], roomMembers =>
	_.keyBy(roomMembers, "user")
);

export const getRoomMemberRoleForCurrentRoomByUserId = userId =>
	createSelector([getRoomMembersForCurrentRoomHash], roomMembersHash => roomMembersHash[userId]?.role);

export const getLastMessageContainsMention = createSelector([getLastMessage, getNick], (lastMessage, nick) => {
	if (!lastMessage || !lastMessage.tokens) return;

	const hasMention = lastMessage.tokens.some(token => {
		if (token.type !== "word") return false;
		return token.value.toUpperCase().includes(nick.toUpperCase()) || token.value.includes("@all");
	});

	if (hasMention) return lastMessage;
});

export const getShowNotification = createSelector(
	[getLastMessageContainsMention, getLocalUserPresent],
	(lastMessage, localUserPresent) => !!lastMessage && !localUserPresent
);

export const getLastMentionRoomName = createSelector(
	[getLastMessageContainsMention, getRooms],
	(lastMessageMention = {}, rooms = {}) => rooms[lastMessageMention.room]?.name
);

export const getLastMentionAuthorNick = createSelector(
	[getLastMessageContainsMention, getUsers],
	(lastMessageMention = {}, users = {}) => users[lastMessageMention.author]?.nick
);

export const getLastMentionText = createSelector(
	[getLastMessageContainsMention],
	(lastMessageMention = {}) => lastMessageMention.text
);

export const getRoomIsCurrent = roomId => createSelector([getActiveRoomId], activeRoomId => activeRoomId === roomId);

export const getRoomName = roomId => state => getRooms(state)[roomId]?.name;

export const getUnreadMention = roomId => state => getLocalRoomMembersByRoom(state)[roomId]?.unreadMention;

export const getUnreadMessageCount = roomId => state => getLocalRoomMembersByRoom(state)[roomId]?.unreadMessageCount;

export const getUnreadRoomIds = createSelector([getLocalRoomMembersByRoom], localRoomMembersByRoom =>
	_(localRoomMembersByRoom)
		.filter(roomMember => roomMember.unreadMessageCount > 0)
		.orderBy(["unreadStart"])
		.map("room")
		.value()
);

export const getReadRoomIds = createSelector([getLocalRoomMembersByRoom], localRoomMembersByRoom =>
	_(localRoomMembersByRoom)
		.filter(roomMember => !roomMember.unreadMessageCount)
		.sortBy("roomOrder")
		.map("room")
		.value()
);

export const getIsDisconnected = state => !state.socket.connected;

export const newIsVersionV2Deployed = ({ version }) =>
	version.clientVersionV2New && version.clientVersionV2 !== version.clientVersionV2New;
<|endoftext|>


<commit_before>"use strict";

import debug from 'debug'
import { join }  from 'path'
import { existsSync as exists, readFileSync as read } from 'fs'
import chokidar from 'chokidar'
import {EventEmitter} from 'events'
const ____ = debug('faster-titanium:FileWatcher')
const ___x = debug('faster-titanium:FileWatcher:error')

/**
 * Watch files
 */
export default class FileWatcher extends EventEmitter {

    /**
     * @param {string} projDir
     */
    constructor(projDir) {
        super()

        this.projDir = projDir

        const watcher = chokidar.watch(this.dirs, { persistent: true, ignoreInitial: true })

        watcher.on('change', ::this.onChange)
        watcher.on('error', path => ___x(path) || this.emit('error', path))
    }

    /**
     * @param {string} path
     */
    onChange(path) {
        if (this.isAlloyPath(path)) {
            this.emit('change:alloy', path)
        }
        else {
            this.emit('change', path)
        }
    }

    isAlloyPath(path) {
        return path.indexOf(join(this.projDir, 'app/')) === 0
    }

    /** @type {string[]} */
    get dirs() {
        return [
            'Resources',
            'app',
            'i18n'
        ]
        .map( name => join(this.projDir, name) )
        .filter (exists)
    }
}
<commit_msg>FileWatcher#close()<commit_after>"use strict";

import debug from 'debug'
import { join }  from 'path'
import { existsSync as exists, readFileSync as read } from 'fs'
import chokidar from 'chokidar'
import {EventEmitter} from 'events'
const ____ = debug('faster-titanium:FileWatcher')
const ___x = debug('faster-titanium:FileWatcher:error')

/**
 * Watch files
 */
export default class FileWatcher extends EventEmitter {

    /**
     * @param {string} projDir
     */
    constructor(projDir) {
        super()

        this.projDir = projDir

        this.watcher = chokidar.watch(this.dirs, { persistent: true, ignoreInitial: true })

        this.watcher.on('change', ::this.onChange)
        this.watcher.on('error', path => ___x(path) || this.emit('error', path))
    }

    /**
     * stop watching
     */
    close() {
        this.watcher.close()
        ____(`watch stopped`)
    }

    /**
     * @param {string} path
     */
    onChange(path) {
        if (this.isAlloyPath(path)) {
            this.emit('change:alloy', path)
        }
        else {
            this.emit('change', path)
        }
    }

    isAlloyPath(path) {
        return path.indexOf(join(this.projDir, 'app/')) === 0
    }

    /** @type {string[]} */
    get dirs() {
        return [
            'Resources',
            'app',
            'i18n'
        ]
        .map( name => join(this.projDir, name) )
        .filter (exists)
    }
}
<|endoftext|>


<commit_before>import fs from 'fs'
import Delogger from 'delogger'
import EventEmitter from 'events'
import { expect } from 'chai'

import template from './config.template'

export const ConfigLocation = `config.json`

export default class Config extends EventEmitter {
  constructor (props) {
    super()
    props = props || {}
    this.location = process.env.CONFIG_PATH || ConfigLocation
    this.log = new Delogger('Config')

    if (props.sync) {
      try {
        var data = fs.readFileSync(this.location)
        this.parseConfig(data)
      } catch (err) {
        if (err.code === 'ENOENT') {
          this.generateConfig()
        } else {
          this.log.error(err)
        }
      }
    } else {
      fs.readFile(this.location, (err, data) => {
        if (err && err.code === 'ENOENT') {
          this.generateConfig()
        } else if (!err) {
          this.parseConfig(data)
        } else {
          this.log.error(err)
        }
      })
    }
  }

  generateConfig () {
    Object.assign(this, template)

    fs.writeFile(this.location, JSON.stringify(template, 'undefined', 2), (err) => {
      if (err) {
        this.log.error(err)
      }
      this.emit('ready')
    })
  }

  parseConfig (string) {
    let config = JSON.parse(string)

    expect(config).to.have.property('log').to.be.a('object')
    expect(config.log).to.have.property('path').to.be.a('string').not.empty

    expect(config).to.have.property('server').to.be.a('object')
    expect(config.server).to.have.property('port').to.be.a('number').within(0, 65535)
    expect(config.server).to.have.property('masterKey').to.be.a('string').to.have.lengthOf.above(5)
    expect(config.server).to.have.property('https')

    if (config.server.https) {
      expect(config.server.https).to.be.a('number')
      expect(config.server).to.have.property('hostname').to.be.a('string').not.empty

      expect(config.server).to.have.property('certs').to.be.a('object')
      expect(config.server.certs).to.have.property('privatekey').to.be.a('string').not.empty
      expect(config.server.certs).to.have.property('certificate').to.be.a('string').not.empty
      expect(config.server.certs).to.have.property('chain').to.be.a('string').not.empty
    } else {
      expect(config.server.https).to.be.a('boolean')
    }

    expect(config).to.have.property('database').to.be.a('object')
    expect(config.database).to.have.property('path').to.be.a('string').not.empty

    expect(config).to.have.property('authentification').to.be.a('boolean')
    expect(config).to.have.property('registration').to.be.a('boolean')

    expect(config).to.have.property('files').to.be.a('object')
    expect(config.files).to.have.property('path').to.be.a('string').not.empty

    expect(config).to.have.property('torrent').to.be.a('object')
    expect(config.torrent).to.have.property('providers').to.be.a('array')

    Object.assign(this, config)

    this.emit('ready')
  }
}<commit_msg>change masterkey via env var<commit_after>import fs from 'fs'
import Delogger from 'delogger'
import EventEmitter from 'events'
import { expect } from 'chai'

import template from './config.template'

export const ConfigLocation = `config.json`

export default class Config extends EventEmitter {
  constructor (props) {
    super()
    props = props || {}
    this.location = process.env.CONFIG_PATH || ConfigLocation
    this.server.masterKey = process.env.MASTER_KEY || this.server.masterKey
    this.log = new Delogger('Config')

    if (props.sync) {
      try {
        var data = fs.readFileSync(this.location)
        this.parseConfig(data)
      } catch (err) {
        if (err.code === 'ENOENT') {
          this.generateConfig()
        } else {
          this.log.error(err)
        }
      }
    } else {
      fs.readFile(this.location, (err, data) => {
        if (err && err.code === 'ENOENT') {
          this.generateConfig()
        } else if (!err) {
          this.parseConfig(data)
        } else {
          this.log.error(err)
        }
      })
    }
  }

  generateConfig () {
    Object.assign(this, template)

    fs.writeFile(this.location, JSON.stringify(template, 'undefined', 2), (err) => {
      if (err) {
        this.log.error(err)
      }
      this.emit('ready')
    })
  }

  parseConfig (string) {
    let config = JSON.parse(string)

    expect(config).to.have.property('log').to.be.a('object')
    expect(config.log).to.have.property('path').to.be.a('string').not.empty

    expect(config).to.have.property('server').to.be.a('object')
    expect(config.server).to.have.property('port').to.be.a('number').within(0, 65535)
    expect(config.server).to.have.property('masterKey').to.be.a('string').to.have.lengthOf.above(5)
    expect(config.server).to.have.property('https')

    if (config.server.https) {
      expect(config.server.https).to.be.a('number')
      expect(config.server).to.have.property('hostname').to.be.a('string').not.empty

      expect(config.server).to.have.property('certs').to.be.a('object')
      expect(config.server.certs).to.have.property('privatekey').to.be.a('string').not.empty
      expect(config.server.certs).to.have.property('certificate').to.be.a('string').not.empty
      expect(config.server.certs).to.have.property('chain').to.be.a('string').not.empty
    } else {
      expect(config.server.https).to.be.a('boolean')
    }

    expect(config).to.have.property('database').to.be.a('object')
    expect(config.database).to.have.property('path').to.be.a('string').not.empty

    expect(config).to.have.property('authentification').to.be.a('boolean')
    expect(config).to.have.property('registration').to.be.a('boolean')

    expect(config).to.have.property('files').to.be.a('object')
    expect(config.files).to.have.property('path').to.be.a('string').not.empty

    expect(config).to.have.property('torrent').to.be.a('object')
    expect(config.torrent).to.have.property('providers').to.be.a('array')

    Object.assign(this, config)

    this.emit('ready')
  }
}<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const database = require('./database');
const Reading = require('./Reading');

const sqlFile = database.sqlFile;

class Meter {
	/**
	 * @param id This meter's ID. Should be undefined if the meter is being newly created
	 * @param name This meter's name
	 * @param ipAddress This meter's IP Address
	 * @param enabled This meter is being actively read from
	 * @param displayable This meters is available to users for charting
	 * @param type What kind of meter this is
	 * @param gps location in format of GIS coordinates
	 * @param meterTimezone Default timezone for meter
	 * @param identifier Another way to identify a meter
	 * @param note Note about the meter
	 * @param area Area of the meter default null
	 * @param cumulative Identifies if meter readings that are stored are the sum of usage or the particular reading, default false
	 * @param cumulativeReset True if cumulative values can reset back to zero., default false
	 * @param cumulativeResetStart The earliest time of day that a reset can occur, default '00:00:00'
	 * @param cumulativeResetEnd The latest time of day that a reset can occur, default '23:59:59'
	 * @param readingLength Specifies the time range on every reading in the CSV file, default '00:00:00'
	 * @param readingVariation +/- time allowed on length to consider within allowed length, default '23:59:59'
	 * @param reading The value of reading, default 0.0
	 * @param startTimestamp Start timestamp of reading, default '0001-01-01 00:00:00'
	 * @param endTimestamp  End timestamp of reading, '0001-01-01 00:00:00'
	 */
	constructor(id, name, ipAddress, enabled, displayable, type, meterTimezone, gps = undefined, identifier = name, note, area,
		cumulative, cumulativeReset, cumulativeResetStart, cumulativeResetEnd, readingLength, readingVariation, reading,
		startTimestamp, endTimestamp) {
		this.id = id;
		this.name = name;
		this.ipAddress = ipAddress;
		this.enabled = enabled;
		this.displayable = displayable;
		this.type = type;
		this.gps = gps;
		this.meterTimezone = meterTimezone;
		this.identifier = identifier;
		this.note = note;
		this.area = area;
		this.cumulative = cumulative;
		this.cumulativeReset = cumulativeReset;
		this.cumulativeResetStart = cumulativeResetStart;
		this.cumulativeResetEnd = cumulativeResetEnd;
		this.readingLength = readingLength;
		this.readingVariation = readingVariation;
		this.reading = reading;
		this.startTimestamp = startTimestamp;
		this.endTimestamp = endTimestamp;
	}

	/**
	 * Returns a promise to create the meters table.
	 * @param conn the connection to use
	 * @return {Promise.<>}
	 */
	static createTable(conn) {
		return conn.none(sqlFile('meter/create_meters_table.sql'));
	}

	/**
	 * Returns a promise to create the meter_type type.
	 * This needs to be run before Meter.createTable().
	 * @param conn the connection to use
	 * @return {Promise<void>}
	 */
	static createMeterTypesEnum(conn) {
		return conn.none(sqlFile('meter/create_meter_types_enum.sql'));
	}

	/**
	 * Returns a promise to retrieve the meter with the given name from the database.
	 * @param name the meter's name
	 * @param conn the connection to be used.
	 * @returns {Promise.<Meter>}
	 */
	static async getByName(name, conn) {
		const row = await conn.one(sqlFile('meter/get_meter_by_name.sql'), { name: name });
		return Meter.mapRow(row);
	}

	/**
	 * Check if a meter with the same name is already in the database.
	 * @param conn the connection to be used.
	 * @returns {boolean}
	 */
	async existsByName(conn) {
		const row = await conn.oneOrNone(sqlFile('meter/get_meter_by_name.sql'), { name: this.name });
		return row !== null;
	}

	/**
	 * Creates a new meter from the data in a row.
	 * @param row the row from which the meter is to be created
	 * @returns Meter from row
	 */
	static mapRow(row) {
		return new Meter(row.id, row.name, row.ipaddress, row.enabled, row.displayable, row.meter_type,
			row.default_timezone_meter, row.gps, row.identifier, row.note, row.area, row.cumulative, row.cumulative_reset,
			row.cumulative_reset_start, row.cumulative_reset_end, row.reading_length, row.reading_variation,
			row.reading, row.start_timestamp, row.end_timestamp);
	}

	/**
	 * Returns a promise to retrieve the meter with the given id from the database.
	 * @param id the id of the meter to retrieve
	 * @param conn the connection to be used.
	 * @returns {Promise.<Meter>}
	 */
	static async getByID(id, conn) {
		const row = await conn.one(sqlFile('meter/get_meter_by_id.sql'), { id: id });
		return Meter.mapRow(row);
	}

	/**
	 * Returns a promise to get all of the meters from the database
	 * @param conn the connection to be used.
	 * @returns {Promise.<array.<Meter>>}
	 */
	static async getAll(conn) {
		const rows = await conn.any(sqlFile('meter/get_all_meters.sql'));
		return rows.map(Meter.mapRow);
	}

	/**
	 * Returns a promise to get all of the displayable meters from the database
	 * @param conn the connection to use. Defaults to the default database connection.
	 * @returns {Promise.<array.<Meter>>}
	 */
	static async getDisplayable(conn) {
		const rows = await conn.any(sqlFile('meter/get_displayable_meters.sql'));
		return rows.map(Meter.mapRow);
	}

	/**
	 * Returns a promise to get all of the updatable meters from the database
	 * @param conn the connection to use. Defaults to the default database connection.
	 * @returns {Promise.<array.<Meter>>}
	 */
	static async getEnabled(conn) {
		const rows = await conn.any(sqlFile('meter/get_enabled_meters.sql'));
		return rows.map(Meter.mapRow);
	}

	/**
	 * Returns a promise to insert this meter into the database
	 * @param conn the connection to be used.
	 * @returns {Promise.<>}
	 */
	async insert(conn) {
		const meter = this;
		if (meter.id !== undefined) {
			throw new Error('Attempt to insert a meter that already has an ID');
		}
		const resp = await conn.one(sqlFile('meter/insert_new_meter.sql'), meter);
		this.id = resp.id;
	}

	/**
	 * Returns a promise to update an existing meter in the database
	 * @param conn the connection to use.
	 * @returns {Promise.<>}
	 */
	async update(conn) {
		const meter = this;
		if (meter.id === undefined) {
			throw new Error('Attempt to update a meter with no ID');
		}
		await conn.none(sqlFile('meter/update_meter.sql'), meter);
	}

	/**
	 * Returns a promise to get all of the readings for this meter from the database.
	 * @param conn the connection to be used.
	 * @returns {Promise.<Array.<Reading>>}
	 */
	readings(conn) {
		return Reading.getAllByMeterID(this.id, conn);
	}
}

// Enum of meter types
Meter.type = {
	MAMAC: 'mamac',
	METASYS: 'metasys',
	OBVIUS: 'obvius'
};

module.exports = Meter;
<commit_msg>fix comments<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const database = require('./database');
const Reading = require('./Reading');

const sqlFile = database.sqlFile;

class Meter {
	/**
	 * @param id This meter's ID. Should be undefined if the meter is being newly created
	 * @param name This meter's name
	 * @param ipAddress This meter's IP Address
	 * @param enabled This meter is being actively read from
	 * @param displayable This meters is available to users for charting
	 * @param type What kind of meter this is
	 * @param gps location in format of GIS coordinates
	 * @param meterTimezone Default timezone for meter
	 * @param identifier Another way to identify a meter
	 * @param note Note about the meter
	 * @param area Area of the meter default null
	 * @param cumulative Identifies if meter readings that are stored are the sum of usage or the particular reading, default false
	 * @param cumulativeReset True if cumulative values can reset back to zero., default false
	 * @param cumulativeResetStart The earliest time of day that a reset can occur, default '00:00:00'
	 * @param cumulativeResetEnd The latest time of day that a reset can occur, default '23:59:59.999999'
	 * @param readingLength Specifies the time range on every reading in the CSV file, default '00:00:00'
	 * @param readingVariation +/- time allowed on length to consider within allowed length, default '23:59:59.999999'
	 * @param reading The value of reading, default 0.0
	 * @param startTimestamp Start timestamp of last reading input for this meter, default '01-01-01 00:00:00'
	 * @param endTimestamp  End timestamp of last reading input for this meter, '01-01-01 00:00:00'
	 */
	constructor(id, name, ipAddress, enabled, displayable, type, meterTimezone, gps = undefined, identifier = name, note, area,
		cumulative, cumulativeReset, cumulativeResetStart, cumulativeResetEnd, readingLength, readingVariation, reading,
		startTimestamp, endTimestamp) {
		this.id = id;
		this.name = name;
		this.ipAddress = ipAddress;
		this.enabled = enabled;
		this.displayable = displayable;
		this.type = type;
		this.gps = gps;
		this.meterTimezone = meterTimezone;
		this.identifier = identifier;
		this.note = note;
		this.area = area;
		this.cumulative = cumulative;
		this.cumulativeReset = cumulativeReset;
		this.cumulativeResetStart = cumulativeResetStart;
		this.cumulativeResetEnd = cumulativeResetEnd;
		this.readingLength = readingLength;
		this.readingVariation = readingVariation;
		this.reading = reading;
		this.startTimestamp = startTimestamp;
		this.endTimestamp = endTimestamp;
	}

	/**
	 * Returns a promise to create the meters table.
	 * @param conn the connection to use
	 * @return {Promise.<>}
	 */
	static createTable(conn) {
		return conn.none(sqlFile('meter/create_meters_table.sql'));
	}

	/**
	 * Returns a promise to create the meter_type type.
	 * This needs to be run before Meter.createTable().
	 * @param conn the connection to use
	 * @return {Promise<void>}
	 */
	static createMeterTypesEnum(conn) {
		return conn.none(sqlFile('meter/create_meter_types_enum.sql'));
	}

	/**
	 * Returns a promise to retrieve the meter with the given name from the database.
	 * @param name the meter's name
	 * @param conn the connection to be used.
	 * @returns {Promise.<Meter>}
	 */
	static async getByName(name, conn) {
		const row = await conn.one(sqlFile('meter/get_meter_by_name.sql'), { name: name });
		return Meter.mapRow(row);
	}

	/**
	 * Check if a meter with the same name is already in the database.
	 * @param conn the connection to be used.
	 * @returns {boolean}
	 */
	async existsByName(conn) {
		const row = await conn.oneOrNone(sqlFile('meter/get_meter_by_name.sql'), { name: this.name });
		return row !== null;
	}

	/**
	 * Creates a new meter from the data in a row.
	 * @param row the row from which the meter is to be created
	 * @returns Meter from row
	 */
	static mapRow(row) {
		return new Meter(row.id, row.name, row.ipaddress, row.enabled, row.displayable, row.meter_type,
			row.default_timezone_meter, row.gps, row.identifier, row.note, row.area, row.cumulative, row.cumulative_reset,
			row.cumulative_reset_start, row.cumulative_reset_end, row.reading_length, row.reading_variation,
			row.reading, row.start_timestamp, row.end_timestamp);
	}

	/**
	 * Returns a promise to retrieve the meter with the given id from the database.
	 * @param id the id of the meter to retrieve
	 * @param conn the connection to be used.
	 * @returns {Promise.<Meter>}
	 */
	static async getByID(id, conn) {
		const row = await conn.one(sqlFile('meter/get_meter_by_id.sql'), { id: id });
		return Meter.mapRow(row);
	}

	/**
	 * Returns a promise to get all of the meters from the database
	 * @param conn the connection to be used.
	 * @returns {Promise.<array.<Meter>>}
	 */
	static async getAll(conn) {
		const rows = await conn.any(sqlFile('meter/get_all_meters.sql'));
		return rows.map(Meter.mapRow);
	}

	/**
	 * Returns a promise to get all of the displayable meters from the database
	 * @param conn the connection to use. Defaults to the default database connection.
	 * @returns {Promise.<array.<Meter>>}
	 */
	static async getDisplayable(conn) {
		const rows = await conn.any(sqlFile('meter/get_displayable_meters.sql'));
		return rows.map(Meter.mapRow);
	}

	/**
	 * Returns a promise to get all of the updatable meters from the database
	 * @param conn the connection to use. Defaults to the default database connection.
	 * @returns {Promise.<array.<Meter>>}
	 */
	static async getEnabled(conn) {
		const rows = await conn.any(sqlFile('meter/get_enabled_meters.sql'));
		return rows.map(Meter.mapRow);
	}

	/**
	 * Returns a promise to insert this meter into the database
	 * @param conn the connection to be used.
	 * @returns {Promise.<>}
	 */
	async insert(conn) {
		const meter = this;
		if (meter.id !== undefined) {
			throw new Error('Attempt to insert a meter that already has an ID');
		}
		const resp = await conn.one(sqlFile('meter/insert_new_meter.sql'), meter);
		this.id = resp.id;
	}

	/**
	 * Returns a promise to update an existing meter in the database
	 * @param conn the connection to use.
	 * @returns {Promise.<>}
	 */
	async update(conn) {
		const meter = this;
		if (meter.id === undefined) {
			throw new Error('Attempt to update a meter with no ID');
		}
		await conn.none(sqlFile('meter/update_meter.sql'), meter);
	}

	/**
	 * Returns a promise to get all of the readings for this meter from the database.
	 * @param conn the connection to be used.
	 * @returns {Promise.<Array.<Reading>>}
	 */
	readings(conn) {
		return Reading.getAllByMeterID(this.id, conn);
	}
}

// Enum of meter types
Meter.type = {
	MAMAC: 'mamac',
	METASYS: 'metasys',
	OBVIUS: 'obvius'
};

module.exports = Meter;
<|endoftext|>


<commit_before>/*
 * vocab.js
 * Vocabulary words to be used in sentence patterns (patterns.js)
 *
 * New Age Bullshit Generator
 * © 2014-15 Seb Pearce (sebpearce.com)
 * Licensed under the MIT License.
 *
 */

bs.bullshitWords = {

  nCosmos: [
		'cosmos',
		'quantum soup',
		'infinite',
		'universe',
		'galaxy',
		'multiverse',
		'grid',
		'quantum matrix',
		'totality',
		'quantum cycle',
		'nexus',
		'planet',
		'solar system',
		'world',
		'stratosphere',
		'dreamscape',
		'biosphere',
	],

  nPerson: [
		'being',
		'child',
		'traveller',
		'entity',
		'lifeform',
		'wanderer',
		'visitor',
		'prophet',
		'seeker',
	],

  nPersonPlural: [
		'beings',
		'travellers',
		'entities',
		'lifeforms',
		'dreamweavers',
		'adventurers',
		'pilgrims',
		'warriors',
		'messengers',
		'dreamers',
		'storytellers',
		'seekers',
	],

  nMass: [
		'consciousness',
		'nature',
		'beauty',
		'knowledge',
		'truth',
		'life',
		'healing',
		'potential',
		'freedom',
		'purpose',
		'coherence',
		'choice',
		'passion',
		'understanding',
		'balance',
		'growth',
		'inspiration',
		'conscious living',
		'energy',
		'health',
		'spacetime',
		'learning',
		'being',
		'wisdom',
		'stardust',
		'sharing',
		'science',
		'curiosity',
		'hope',
		'wonder',
		'faith',
		'fulfillment',
		'peace',
		'rebirth',
		'self-actualization',
		'presence',
		'power',
		'will',
		'flow',
		'potential',
		'potentiality',
		'chi',
		'intuition',
		'synchronicity',
		'wellbeing',
		'joy',
		'love',
		'karma',
		'life-force',
		'awareness',
		'guidance',
		'transformation',
		'grace',
		'divinity',
		'non-locality',
		'inseparability',
		'interconnectedness',
		'transcendence',
		'empathy',
		'insight',
		'rejuvenation',
		'ecstasy',
		'aspiration',
		'complexity',
		'serenity',
	],

  nMassBad: [
		'turbulence',
		'pain',
		'suffering',
		'stagnation',
		'desire',
		'bondage',
		'greed',
		'selfishness',
		'ego',
		'dogma',
		'illusion',
		'delusion',
		'yearning',
		'discontinuity',
		'materialism',
	],

  nOurPlural: [
		'souls',
		'lives',
		'dreams',
		'hopes',
		'bodies',
		'hearts',
		'brains',
		'third eyes',
		'essences',
		'chakras',
	],

  nPath: [
		'circuit',
		'mission',
		'journey',
		'path',
		'quest',
		'vision quest',
		'story',
		'myth',
	],

  nOf: [
		'quantum leap',
		'evolution',
		'spark',
		'lightning bolt',
		'reintegration',
		'vector',
		'rebirth',
		'revolution',
		'wellspring',
		'fount',
		'source',
		'fusion',
		'canopy',
		'flow',
		'network',
		'current',
		'transmission',
		'oasis',
		'quantum shift',
		'paradigm shift',
		'metamorphosis',
		'harmonizing',
		'reimagining',
		'rekindling',
		'unifying',
		'osmosis',
		'vision',
		'uprising',
		'explosion',
	],

  ing: [
		'flowering',
		'unfolding',
		'blossoming',
		'awakening',
		'deepening',
		'refining',
		'maturing',
		'evolving',
		'summoning',
		'unveiling',
		'redefining',
		'condensing',
		'ennobling',
	],

  adj: [
		'enlightened',
		'zero-point',
		'quantum',
		'high-frequency',
		'Vedic',
		'non-dual',
		'conscious',
		'sentient',
		'sacred',
		'infinite',
		'primordial',
		'ancient',
		'powerful',
		'spiritual',
		'higher',
		'advanced',
		'internal',
		'sublime',
		'technological',
		'dynamic',
		'life-affirming',
		'sensual',
		'unrestricted',
		'ever-present',
		'endless',
		'ethereal',
		'astral',
		'cosmic',
		'spatial',
		'transformative',
		'unified',
		'non-local',
		'mystical',
		'divine',
		'self-aware',
		'magical',
		'amazing',
		'interstellar',
		'unlimited',
		'authentic',
		'angelic',
		'karmic',
		'psychic',
		'pranic',
		'consciousness-expanding',
		'perennial',
		'heroic',
		'archetypal',
		'mythic',
		'intergalatic',
		'holistic',
		'joyous',
		'sublime',
	],

  adjBig: [
		'epic',
		'unimaginable',
		'colossal',
		'unfathomable',
		'magnificent',
		'enormous',
		'jaw-dropping',
		'ecstatic',
		'powerful',
		'untold',
		'astonishing',
		'incredible',
		'breathtaking',
		'staggering',
	],

  adjWith: [
		'aglow with',
		'buzzing with',
		'beaming with',
		'full of',
		'overflowing with',
		'radiating',
		'bursting with',
		'electrified with',
	],

  adjPrefix: [
		'ultra-',
		'supra-',
		'hyper-',
		'pseudo-',
	],

  vtMass: [
		'inspire',
		'integrate',
		'ignite',
		'discover',
		'rediscover',
		'foster',
		'release',
		'manifest',
		'harmonize',
		'engender',
		'bring forth',
		'bring about',
		'create',
		'spark',
		'reveal',
		'generate',
		'leverage',
	],

  vtPerson: [
		'enlighten',
		'inspire',
		'empower',
		'unify',
		'strengthen',
		'recreate',
		'fulfill',
		'change',
		'develop',
		'heal',
		'awaken',
		'synergize',
		'ground',
		'bless',
		'beckon',
	],

  viPerson: [
		'exist',
		'believe',
		'grow',
		'live',
		'dream',
		'reflect',
		'heal',
		'vibrate',
		'self-actualize',
	],

  vtDestroy: [
		'destroy',
		'eliminate',
		'shatter',
		'disrupt',
		'sabotage',
		'exterminate',
		'obliterate',
		'eradicate',
		'extinguish',
		'erase',
		'confront',
	],

  nTheXOf: [
		'richness',
		'truth',
		'growth',
		'nature',
		'healing',
		'knowledge',
	],

  ppPerson: [
		'awakened',
		're-energized',
		'recreated',
		'reborn',
		'guided',
		'aligned',
	],

  ppThingPrep: [
		'enveloped in',
		'transformed into',
		'nurtured by',
		'opened by',
		'immersed in',
		'engulfed in',
		'baptized in',
	],

  fixedAdvP: [
		'through non-local interactions',
		'inherent in nature',
		'at the quantum level',
		'at the speed of light',
		'of unfathomable proportions',
		'on a cosmic scale',
		'devoid of self',
		'of the creative act',
		,
	],

  fixedAdvPPlace: [
		'in this dimension',
		'outside time',
		'within the Godhead',
		'at home in the cosmos',
		,
	],

  fixedNP: [
		'expanding wave functions',
		'superpositions of possibilities',
		'electromagnetic forces',
		'electromagnetic resonance',
		'molecular structures',
		'atomic ionization',
		'electrical impulses',
		'a resonance cascade',
		'bio-electricity',
		'ultrasonic energy',
		'sonar energy',
		'vibrations',
		'frequencies',
		'four-dimensional superstructures',
		'ultra-sentient particles',
		'sub-atomic particles',
		'chaos-driven reactions',
		'supercharged electrons',
		'supercharged waveforms',
		'pulses',
		'transmissions',
		'morphogenetic fields',
		'bio-feedback',
		'meridians',
		'morphic resonance',
	],

  nSubject: [
		'alternative medicine',
		'astrology',
		'tarot',
		'crystal healing',
		'the akashic record',
		'feng shui',
		'acupuncture',
		'homeopathy',
		'aromatherapy',
		'ayurvedic medicine',
		'faith healing',
		'prayer',
		'astral projection',
		'Kabala',
		'reiki',
		'naturopathy',
		'numerology',
		'affirmations',
		'the Law of Attraction',
	],

  vOpenUp: [
		'open up',
		'give us access to',
		'enable us to access',
		'remove the barriers to',
		'clear a path toward',
		'let us access',
		'tap into',
		'align us with',
	],

  vTraverse: [
		'traverse',
		'walk',
		'follow',
		'engage with',
		'go along',
		'roam',
		'navigate',
		'wander',
		'embark on',
	],

};

<commit_msg>remove two commas creating undefined array elements; convert indentation to spaces<commit_after>/*
 * vocab.js
 * Vocabulary words to be used in sentence patterns (patterns.js)
 *
 * New Age Bullshit Generator
 * © 2014-15 Seb Pearce (sebpearce.com)
 * Licensed under the MIT License.
 *
 */

bs.bullshitWords = {

  nCosmos: [
    'cosmos',
    'quantum soup',
    'infinite',
    'universe',
    'galaxy',
    'multiverse',
    'grid',
    'quantum matrix',
    'totality',
    'quantum cycle',
    'nexus',
    'planet',
    'solar system',
    'world',
    'stratosphere',
    'dreamscape',
    'biosphere',
  ],

  nPerson: [
    'being',
    'child',
    'traveller',
    'entity',
    'lifeform',
    'wanderer',
    'visitor',
    'prophet',
    'seeker',
  ],

  nPersonPlural: [
    'beings',
    'travellers',
    'entities',
    'lifeforms',
    'dreamweavers',
    'adventurers',
    'pilgrims',
    'warriors',
    'messengers',
    'dreamers',
    'storytellers',
    'seekers',
  ],

  nMass: [
    'consciousness',
    'nature',
    'beauty',
    'knowledge',
    'truth',
    'life',
    'healing',
    'potential',
    'freedom',
    'purpose',
    'coherence',
    'choice',
    'passion',
    'understanding',
    'balance',
    'growth',
    'inspiration',
    'conscious living',
    'energy',
    'health',
    'spacetime',
    'learning',
    'being',
    'wisdom',
    'stardust',
    'sharing',
    'science',
    'curiosity',
    'hope',
    'wonder',
    'faith',
    'fulfillment',
    'peace',
    'rebirth',
    'self-actualization',
    'presence',
    'power',
    'will',
    'flow',
    'potential',
    'potentiality',
    'chi',
    'intuition',
    'synchronicity',
    'wellbeing',
    'joy',
    'love',
    'karma',
    'life-force',
    'awareness',
    'guidance',
    'transformation',
    'grace',
    'divinity',
    'non-locality',
    'inseparability',
    'interconnectedness',
    'transcendence',
    'empathy',
    'insight',
    'rejuvenation',
    'ecstasy',
    'aspiration',
    'complexity',
    'serenity',
  ],

  nMassBad: [
    'turbulence',
    'pain',
    'suffering',
    'stagnation',
    'desire',
    'bondage',
    'greed',
    'selfishness',
    'ego',
    'dogma',
    'illusion',
    'delusion',
    'yearning',
    'discontinuity',
    'materialism',
  ],

  nOurPlural: [
    'souls',
    'lives',
    'dreams',
    'hopes',
    'bodies',
    'hearts',
    'brains',
    'third eyes',
    'essences',
    'chakras',
  ],

  nPath: [
    'circuit',
    'mission',
    'journey',
    'path',
    'quest',
    'vision quest',
    'story',
    'myth',
  ],

  nOf: [
    'quantum leap',
    'evolution',
    'spark',
    'lightning bolt',
    'reintegration',
    'vector',
    'rebirth',
    'revolution',
    'wellspring',
    'fount',
    'source',
    'fusion',
    'canopy',
    'flow',
    'network',
    'current',
    'transmission',
    'oasis',
    'quantum shift',
    'paradigm shift',
    'metamorphosis',
    'harmonizing',
    'reimagining',
    'rekindling',
    'unifying',
    'osmosis',
    'vision',
    'uprising',
    'explosion',
  ],

  ing: [
    'flowering',
    'unfolding',
    'blossoming',
    'awakening',
    'deepening',
    'refining',
    'maturing',
    'evolving',
    'summoning',
    'unveiling',
    'redefining',
    'condensing',
    'ennobling',
  ],

  adj: [
    'enlightened',
    'zero-point',
    'quantum',
    'high-frequency',
    'Vedic',
    'non-dual',
    'conscious',
    'sentient',
    'sacred',
    'infinite',
    'primordial',
    'ancient',
    'powerful',
    'spiritual',
    'higher',
    'advanced',
    'internal',
    'sublime',
    'technological',
    'dynamic',
    'life-affirming',
    'sensual',
    'unrestricted',
    'ever-present',
    'endless',
    'ethereal',
    'astral',
    'cosmic',
    'spatial',
    'transformative',
    'unified',
    'non-local',
    'mystical',
    'divine',
    'self-aware',
    'magical',
    'amazing',
    'interstellar',
    'unlimited',
    'authentic',
    'angelic',
    'karmic',
    'psychic',
    'pranic',
    'consciousness-expanding',
    'perennial',
    'heroic',
    'archetypal',
    'mythic',
    'intergalatic',
    'holistic',
    'joyous',
    'sublime',
  ],

  adjBig: [
    'epic',
    'unimaginable',
    'colossal',
    'unfathomable',
    'magnificent',
    'enormous',
    'jaw-dropping',
    'ecstatic',
    'powerful',
    'untold',
    'astonishing',
    'incredible',
    'breathtaking',
    'staggering',
  ],

  adjWith: [
    'aglow with',
    'buzzing with',
    'beaming with',
    'full of',
    'overflowing with',
    'radiating',
    'bursting with',
    'electrified with',
  ],

  adjPrefix: [
    'ultra-',
    'supra-',
    'hyper-',
    'pseudo-',
  ],

  vtMass: [
    'inspire',
    'integrate',
    'ignite',
    'discover',
    'rediscover',
    'foster',
    'release',
    'manifest',
    'harmonize',
    'engender',
    'bring forth',
    'bring about',
    'create',
    'spark',
    'reveal',
    'generate',
    'leverage',
  ],

  vtPerson: [
    'enlighten',
    'inspire',
    'empower',
    'unify',
    'strengthen',
    'recreate',
    'fulfill',
    'change',
    'develop',
    'heal',
    'awaken',
    'synergize',
    'ground',
    'bless',
    'beckon',
  ],

  viPerson: [
    'exist',
    'believe',
    'grow',
    'live',
    'dream',
    'reflect',
    'heal',
    'vibrate',
    'self-actualize',
  ],

  vtDestroy: [
    'destroy',
    'eliminate',
    'shatter',
    'disrupt',
    'sabotage',
    'exterminate',
    'obliterate',
    'eradicate',
    'extinguish',
    'erase',
    'confront',
  ],

  nTheXOf: [
    'richness',
    'truth',
    'growth',
    'nature',
    'healing',
    'knowledge',
  ],

  ppPerson: [
    'awakened',
    're-energized',
    'recreated',
    'reborn',
    'guided',
    'aligned',
  ],

  ppThingPrep: [
    'enveloped in',
    'transformed into',
    'nurtured by',
    'opened by',
    'immersed in',
    'engulfed in',
    'baptized in',
  ],

  fixedAdvP: [
    'through non-local interactions',
    'inherent in nature',
    'at the quantum level',
    'at the speed of light',
    'of unfathomable proportions',
    'on a cosmic scale',
    'devoid of self',
    'of the creative act',
  ],

  fixedAdvPPlace: [
    'in this dimension',
    'outside time',
    'within the Godhead',
    'at home in the cosmos',
  ],

  fixedNP: [
    'expanding wave functions',
    'superpositions of possibilities',
    'electromagnetic forces',
    'electromagnetic resonance',
    'molecular structures',
    'atomic ionization',
    'electrical impulses',
    'a resonance cascade',
    'bio-electricity',
    'ultrasonic energy',
    'sonar energy',
    'vibrations',
    'frequencies',
    'four-dimensional superstructures',
    'ultra-sentient particles',
    'sub-atomic particles',
    'chaos-driven reactions',
    'supercharged electrons',
    'supercharged waveforms',
    'pulses',
    'transmissions',
    'morphogenetic fields',
    'bio-feedback',
    'meridians',
    'morphic resonance',
  ],

  nSubject: [
    'alternative medicine',
    'astrology',
    'tarot',
    'crystal healing',
    'the akashic record',
    'feng shui',
    'acupuncture',
    'homeopathy',
    'aromatherapy',
    'ayurvedic medicine',
    'faith healing',
    'prayer',
    'astral projection',
    'Kabala',
    'reiki',
    'naturopathy',
    'numerology',
    'affirmations',
    'the Law of Attraction',
  ],

  vOpenUp: [
    'open up',
    'give us access to',
    'enable us to access',
    'remove the barriers to',
    'clear a path toward',
    'let us access',
    'tap into',
    'align us with',
  ],

  vTraverse: [
    'traverse',
    'walk',
    'follow',
    'engage with',
    'go along',
    'roam',
    'navigate',
    'wander',
    'embark on',
  ],

};

<|endoftext|>


<commit_before>import { version as nodeVersion } from 'process'

import readPkgUp from 'read-pkg-up'

// Caches it.
// TODO: use async instead
const {
  package: { version: autoserverVersion },
} = readPkgUp.sync()

// Retrieve environment-specific versions
export const getVersionsInfo = function() {
  const autoserver = `v${autoserverVersion}`

  return { node: nodeVersion, autoserver }
}
<commit_msg>Fix new read-pkg-up major release<commit_after>import { version as nodeVersion } from 'process'

import readPkgUp from 'read-pkg-up'

// Caches it.
// TODO: use async instead
const {
  packageJson: { version: autoserverVersion },
} = readPkgUp.sync()

// Retrieve environment-specific versions
export const getVersionsInfo = function() {
  const autoserver = `v${autoserverVersion}`

  return { node: nodeVersion, autoserver }
}
<|endoftext|>


<commit_before>export default [
  {
    translations: {
      arabic: 'تفاحة',
      darija: 'Tefa7',
      english: 'Apple'
    },
    sound: '/assets/test.mp3'
  },
  {
    translations: {
      arabic: 'موز',
      darija: 'Banan',
      english: 'Banana'
    },
    sound: '/assets/test2.mp3'
  },
  {
    translations: {
      arabic: 'برتقال',
      darija: 'Limoun',
      english: 'Orange'
    },
    sound: '/assets/test.mp3'
  },
  {
    translations: {
      arabic: 'خوخ',
      darija: 'Khokh',
      english: 'Peach'
    },
    sound: '/assets/test2.mp3'
  },
  {
    translations: {
      arabic: 'بطيخ',
      darija: 'Dela7',
      english: 'Watermelon'
    },
    sound: '/assets/test.mp3'
  },
  {
    translations: {
      arabic: 'فراولة',
      darija: 'Fraise',
      english: 'Strawberry'
    },
    sound: '/assets/test2.mp3'
  },
  {
    translations: {
      arabic: 'خبز',
      darija: 'Khobz',
      english: 'Bread'
    },
    sound: '/assets/test.mp3'
  },
  {
    translations: {
      arabic: 'ماء',
      darija: 'Ma',
      english: 'Water'
    },
    sound: '/assets/test.mp3'
  },
  {
    translations: {
      arabic: 'شاي',
      darija: 'Atay',
      english: 'Tea'
    },
    sound: '/assets/test.mp3'
  },
  {
    translations: {
      arabic: 'قهوة',
      darija: '9ahwa',
      english: 'Coffee'
    },
    sound: '/assets/test.mp3'
  },
  {
    translations: {
      arabic: 'حليب',
      darija: '7alib',
      english: 'Milk'
    },
    sound: '/assets/test.mp3'
  },
  {
    translations: {
      arabic: 'عصير',
      darija: '3assir',
      english: 'Juice'
    },
    sound: '/assets/test.mp3'
  },
  {
    translations: {
      arabic: 'عصير البرتقال',
      darija: '3assir limoun',
      english: 'Otange juice'
    },
    sound: '/assets/test.mp3'
  },
  {
    translations: {
      arabic: 'سكر',
      darija: 'Sukkar',
      english: 'Sugar'
    },
    sound: '/assets/test.mp3'
  },
  {
    translations: {
      arabic: 'ملح',
      darija: 'Mel7a',
      english: 'Salt'
    },
    sound: '/assets/test.mp3'
  },
  {
    translations: {
      arabic: 'ابزار',
      darija: 'Ibzar',
      english: 'Pepper'
    },
    sound: '/assets/test.mp3'
  },
  {
    translations: {
      arabic: 'زيت',
      darija: 'Zit',
      english: 'Oil'
    },
    sound: '/assets/test.mp3'
  },
  {
    translations: {
      arabic: 'زيتون',
      darija: 'Zitoun',
      english: 'Olive'
    },
    sound: '/assets/test2.mp3'
  }
];
<commit_msg>Upate sound<commit_after>export default [
  {
    translations: {
      arabic: 'تفاحة',
      darija: 'Tefa7',
      english: 'Apple'
    },
    sound: '/assets/word-sounds/food/Tefa7.mp3'
  },
  {
    translations: {
      arabic: 'موز',
      darija: 'Banan',
      english: 'Banana'
    },
    sound: '/assets/word-sounds/food/Banan.mp3'
  },
  {
    translations: {
      arabic: 'برتقال',
      darija: 'Limoun',
      english: 'Orange'
    },
    sound: '/assets/word-sounds/food/Limoun.mp3'
  },
  {
    translations: {
      arabic: 'خوخ',
      darija: 'Khokh',
      english: 'Peach'
    },
    sound: '/assets/word-sounds/food/Khokh.mp3'
  },
  {
    translations: {
      arabic: 'بطيخ',
      darija: 'Dela7',
      english: 'Watermelon'
    },
    sound: '/assets/word-sounds/food/Dela7.mp3'
  },
  {
    translations: {
      arabic: 'فراولة',
      darija: 'Fraise',
      english: 'Strawberry'
    },
    sound: '/assets/word-sounds/food/Fraise.mp3'
  },
  {
    translations: {
      arabic: 'خبز',
      darija: 'Khobz',
      english: 'Bread'
    },
    sound: '/assets/word-sounds/food/Khoubz.mp3'
  },
  {
    translations: {
      arabic: 'ماء',
      darija: 'Ma',
      english: 'Water'
    },
    sound: '/assets/word-sounds/food/Ma.mp3'
  },
  {
    translations: {
      arabic: 'شاي',
      darija: 'Atay',
      english: 'Tea'
    },
    sound: '/assets/word-sounds/food/Atay.mp3'
  },
  {
    translations: {
      arabic: 'قهوة',
      darija: '9ahwa',
      english: 'Coffee'
    },
    sound: '/assets/word-sounds/food/9ahwa.mp3'
  },
  {
    translations: {
      arabic: 'حليب',
      darija: '7alib',
      english: 'Milk'
    },
    sound: '/assets/word-sounds/food/7alib.mp3'
  },
  {
    translations: {
      arabic: 'عصير',
      darija: '3assir',
      english: 'Juice'
    },
    sound: '/assets/word-sounds/food/3assir.mp3'
  },
  {
    translations: {
      arabic: 'عصير البرتقال',
      darija: '3assir limoun',
      english: 'Otange juice'
    },
    sound: '/assets/word-sounds/food/3assir limoun.mp3'
  },
  {
    translations: {
      arabic: 'سكر',
      darija: 'Sukkar',
      english: 'Sugar'
    },
    sound: '/assets/word-sounds/food/Sukkar.mp3'
  },
  {
    translations: {
      arabic: 'ملح',
      darija: 'Mel7a',
      english: 'Salt'
    },
    sound: '/assets/word-sounds/food/Mel7a.mp3'
  },
  {
    translations: {
      arabic: 'ابزار',
      darija: 'Ibzar',
      english: 'Pepper'
    },
    sound: '/assets/word-sounds/food/Ibzar.mp3'
  },
  {
    translations: {
      arabic: 'زيت',
      darija: 'Zit',
      english: 'Oil'
    },
    sound: '/assets/word-sounds/food/Zit.mp3'
  },
  {
    translations: {
      arabic: 'زيتون',
      darija: 'Zitoun',
      english: 'Olive'
    },
    sound: '/assets/word-sounds/food/Zitoun.mp3'
  }
];
<|endoftext|>


<commit_before>(function(global) {

  'use strict';

  var fabric  = global.fabric || (global.fabric = { }),
      piBy2   = Math.PI * 2,
      extend = fabric.util.object.extend;

  if (fabric.Circle) {
    fabric.warn('fabric.Circle is already defined.');
    return;
  }

  /**
   * Circle class
   * @class fabric.Circle
   * @extends fabric.Object
   * @see {@link fabric.Circle#initialize} for constructor definition
   */
  fabric.Circle = fabric.util.createClass(fabric.Object, /** @lends fabric.Circle.prototype */ {

    /**
     * Type of an object
     * @type String
     * @default
     */
    type: 'circle',

    /**
     * Radius of this circle
     * @type Number
     * @default
     */
    radius: 0,

    /**
     * Constructor
     * @param {Object} [options] Options object
     * @return {fabric.Circle} thisArg
     */
    initialize: function(options) {
      options = options || { };

      this.set('radius', options.radius || 0);
      this.callSuper('initialize', options);
    },

    /**
     * @private
     * @param {String} key
     * @param {Any} value
     * @return {fabric.Circle} thisArg
     */
    _set: function(key, value) {
      this.callSuper('_set', key, value);

      if (key === 'radius') {
        this.setRadius(value);
      }

      return this;
    },

    /**
     * Returns object representation of an instance
     * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
     * @return {Object} object representation of an instance
     */
    toObject: function(propertiesToInclude) {
      return extend(this.callSuper('toObject', propertiesToInclude), {
        radius: this.get('radius')
      });
    },

    /* _TO_SVG_START_ */
    /**
     * Returns svg representation of an instance
     * @param {Function} [reviver] Method for further parsing of svg representation.
     * @return {String} svg representation of an instance
     */
    toSVG: function(reviver) {
      var markup = this._createBaseSVGMarkup();

      markup.push(
        '<circle ',
          'cx="0" cy="0" ',
          'r="', this.radius,
          '" style="', this.getSvgStyles(),
          '" transform="', this.getSvgTransform(),
        '"/>'
      );

      return reviver ? reviver(markup.join('')) : markup.join('');
    },
    /* _TO_SVG_END_ */

    /**
     * @private
     * @param ctx {CanvasRenderingContext2D} context to render on
     */
    _render: function(ctx, noTransform) {
      ctx.beginPath();
      // multiply by currently set alpha (the one that was set by path group where this object is contained, for example)
      ctx.globalAlpha = this.group ? (ctx.globalAlpha * this.opacity) : this.opacity;
      ctx.arc(noTransform ? this.left : 0, noTransform ? this.top : 0, this.radius, 0, piBy2, false);
      ctx.closePath();

      this._renderFill(ctx);
      this.stroke && this._renderStroke(ctx);
    },

    /**
     * Returns horizontal radius of an object (according to how an object is scaled)
     * @return {Number}
     */
    getRadiusX: function() {
      return this.get('radius') * this.get('scaleX');
    },

    /**
     * Returns vertical radius of an object (according to how an object is scaled)
     * @return {Number}
     */
    getRadiusY: function() {
      return this.get('radius') * this.get('scaleY');
    },

    /**
     * Sets radius of an object (and updates width accordingly)
     * @return {Number}
     */
    setRadius: function(value) {
      this.radius = value;
      this.set('width', value * 2).set('height', value * 2);
    },

    /**
     * Returns complexity of an instance
     * @return {Number} complexity of this instance
     */
    complexity: function() {
      return 1;
    }
  });

  /* _FROM_SVG_START_ */
  /**
   * List of attribute names to account for when parsing SVG element (used by {@link fabric.Circle.fromElement})
   * @static
   * @memberOf fabric.Circle
   * @see: http://www.w3.org/TR/SVG/shapes.html#CircleElement
   */
  fabric.Circle.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy r'.split(' '));

  /**
   * Returns {@link fabric.Circle} instance from an SVG element
   * @static
   * @memberOf fabric.Circle
   * @param {SVGElement} element Element to parse
   * @param {Object} [options] Options object
   * @throws {Error} If value of `r` attribute is missing or invalid
   * @return {fabric.Circle} Instance of fabric.Circle
   */
  fabric.Circle.fromElement = function(element, options) {
    options || (options = { });
    var parsedAttributes = fabric.parseAttributes(element, fabric.Circle.ATTRIBUTE_NAMES);
    if (!isValidRadius(parsedAttributes)) {
      throw new Error('value of `r` attribute is required and can not be negative');
    }

    if (!('left' in parsedAttributes)) {
    	parsedAttributes.left = 0;
    }
	parsedAttributes.left -= (options.width / 2) || 0;

    if (!('top' in parsedAttributes)) {
    	parsedAttributes.top = 0
    }
	parsedAttributes.top -= (options.height / 2) || 0;

    var obj = new fabric.Circle(extend(parsedAttributes, options));

    obj.cx = parseFloat(element.getAttribute('cx')) || 0;
    obj.cy = parseFloat(element.getAttribute('cy')) || 0;

    return obj;
  };

  /**
   * @private
   */
  function isValidRadius(attributes) {
    return (('radius' in attributes) && (attributes.radius > 0));
  }
  /* _FROM_SVG_END_ */

  /**
   * Returns {@link fabric.Circle} instance from an object representation
   * @static
   * @memberOf fabric.Circle
   * @param {Object} object Object to create an instance from
   * @return {Object} Instance of fabric.Circle
   */
  fabric.Circle.fromObject = function(object) {
    return new fabric.Circle(object);
  };

})(typeof exports !== 'undefined' ? exports : this);
<commit_msg>Update circle.class.js<commit_after>(function(global) {

  'use strict';

  var fabric  = global.fabric || (global.fabric = { }),
      piBy2   = Math.PI * 2,
      extend = fabric.util.object.extend;

  if (fabric.Circle) {
    fabric.warn('fabric.Circle is already defined.');
    return;
  }

  /**
   * Circle class
   * @class fabric.Circle
   * @extends fabric.Object
   * @see {@link fabric.Circle#initialize} for constructor definition
   */
  fabric.Circle = fabric.util.createClass(fabric.Object, /** @lends fabric.Circle.prototype */ {

    /**
     * Type of an object
     * @type String
     * @default
     */
    type: 'circle',

    /**
     * Radius of this circle
     * @type Number
     * @default
     */
    radius: 0,

    /**
     * Constructor
     * @param {Object} [options] Options object
     * @return {fabric.Circle} thisArg
     */
    initialize: function(options) {
      options = options || { };

      this.set('radius', options.radius || 0);
      this.callSuper('initialize', options);
    },

    /**
     * @private
     * @param {String} key
     * @param {Any} value
     * @return {fabric.Circle} thisArg
     */
    _set: function(key, value) {
      this.callSuper('_set', key, value);

      if (key === 'radius') {
        this.setRadius(value);
      }

      return this;
    },

    /**
     * Returns object representation of an instance
     * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
     * @return {Object} object representation of an instance
     */
    toObject: function(propertiesToInclude) {
      return extend(this.callSuper('toObject', propertiesToInclude), {
        radius: this.get('radius')
      });
    },

    /* _TO_SVG_START_ */
    /**
     * Returns svg representation of an instance
     * @param {Function} [reviver] Method for further parsing of svg representation.
     * @return {String} svg representation of an instance
     */
    toSVG: function(reviver) {
      var markup = this._createBaseSVGMarkup();

      markup.push(
        '<circle ',
          'cx="0" cy="0" ',
          'r="', this.radius,
          '" style="', this.getSvgStyles(),
          '" transform="', this.getSvgTransform(),
        '"/>'
      );

      return reviver ? reviver(markup.join('')) : markup.join('');
    },
    /* _TO_SVG_END_ */

    /**
     * @private
     * @param ctx {CanvasRenderingContext2D} context to render on
     */
    _render: function(ctx, noTransform) {
      ctx.beginPath();
      // multiply by currently set alpha (the one that was set by path group where this object is contained, for example)
      ctx.globalAlpha = this.group ? (ctx.globalAlpha * this.opacity) : this.opacity;
      ctx.arc(noTransform ? this.left : 0, noTransform ? this.top : 0, this.radius, 0, piBy2, false);
      this._renderFill(ctx);
      this.stroke && this._renderStroke(ctx);
    },

    /**
     * Returns horizontal radius of an object (according to how an object is scaled)
     * @return {Number}
     */
    getRadiusX: function() {
      return this.get('radius') * this.get('scaleX');
    },

    /**
     * Returns vertical radius of an object (according to how an object is scaled)
     * @return {Number}
     */
    getRadiusY: function() {
      return this.get('radius') * this.get('scaleY');
    },

    /**
     * Sets radius of an object (and updates width accordingly)
     * @return {Number}
     */
    setRadius: function(value) {
      this.radius = value;
      this.set('width', value * 2).set('height', value * 2);
    },

    /**
     * Returns complexity of an instance
     * @return {Number} complexity of this instance
     */
    complexity: function() {
      return 1;
    }
  });

  /* _FROM_SVG_START_ */
  /**
   * List of attribute names to account for when parsing SVG element (used by {@link fabric.Circle.fromElement})
   * @static
   * @memberOf fabric.Circle
   * @see: http://www.w3.org/TR/SVG/shapes.html#CircleElement
   */
  fabric.Circle.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy r'.split(' '));

  /**
   * Returns {@link fabric.Circle} instance from an SVG element
   * @static
   * @memberOf fabric.Circle
   * @param {SVGElement} element Element to parse
   * @param {Object} [options] Options object
   * @throws {Error} If value of `r` attribute is missing or invalid
   * @return {fabric.Circle} Instance of fabric.Circle
   */
  fabric.Circle.fromElement = function(element, options) {
    options || (options = { });
    var parsedAttributes = fabric.parseAttributes(element, fabric.Circle.ATTRIBUTE_NAMES);
    if (!isValidRadius(parsedAttributes)) {
      throw new Error('value of `r` attribute is required and can not be negative');
    }

    if (!('left' in parsedAttributes)) {
    	parsedAttributes.left = 0;
    }
    if (!('top' in parsedAttributes)) {
    	parsedAttributes.top = 0
    }
    if (!('transformMatrix' in parsedAttributes)) {
      parsedAttributes.left -= (options.width / 2);
      parsedAttributes.top -= (options.height / 2);
    }
    var obj = new fabric.Circle(extend(parsedAttributes, options));

    obj.cx = parseFloat(element.getAttribute('cx')) || 0;
    obj.cy = parseFloat(element.getAttribute('cy')) || 0;

    return obj;
  };

  /**
   * @private
   */
  function isValidRadius(attributes) {
    return (('radius' in attributes) && (attributes.radius > 0));
  }
  /* _FROM_SVG_END_ */

  /**
   * Returns {@link fabric.Circle} instance from an object representation
   * @static
   * @memberOf fabric.Circle
   * @param {Object} object Object to create an instance from
   * @return {Object} Instance of fabric.Circle
   */
  fabric.Circle.fromObject = function(object) {
    return new fabric.Circle(object);
  };

})(typeof exports !== 'undefined' ? exports : this);
<|endoftext|>


<commit_before>import React from 'react';
import {
  ActivityIndicator,
  FlatList,
  StyleSheet,
  Text,
  View,
} from 'react-native';

import { List, ListItem } from 'react-native-elements'

import HubOnboarding, { WifiConnectionState } from '../HubOnboarding'
import Screen from './Screen';
import Settings from '../Settings'

export default class SetupWifi extends Screen {
  constructor(props) {
    super(props)

    this.state = {
      ssids: [],
      currentSsid: null,
    }

    this.setTitle('Wi-Fi')
  }

  didAppear() {
    let subscribeForWifiEvents = () => {
      HubOnboarding.hubDevice.onNetworksChanged((ssid) => {
        if (!this.state.ssids.find(s => s === ssid)) {
          this.setState({ssids: this.state.ssids.concat([ssid])})
        }
      })

      HubOnboarding.hubDevice.onConnectionStateChanged((connectionState, currentSsid) => {
        if (connectionState === WifiConnectionState.CONNECTION_STATE_CONNECTED) {
          this.setState({currentSsid: currentSsid})
        }
      })
    }

    HubOnboarding.hubDevice
      .connect()
      .then(() => {
        subscribeForWifiEvents()
      })
      .catch((error) => {
        alert("Could not connect to the Hub. Please try again.")
        //TODO: Present error message on screen with a "retry" button that connects again
      })
  }

  willDisappear() {
    //TODO: Setting an empty callback isn't clean – use proper approach.
    HubOnboarding.hubDevice.onNetworksChanged(() => {})
    HubOnboarding.hubDevice.onConnectionStateChanged(() => {})
  }

  onNetworkSelected(ssid) {
    console.log("Network selected: " + ssid)

    if (ssid == this.state.currentSsid) {
      Settings.setHubApiUrl(HubOnboarding.hubDevice.dnsName)
        .then(() => this.pushScreen('setup.nuimo'))
    }
    else {
      HubOnboarding.hubDevice.sendSsid(ssid)
      this.pushScreen('setup.wifiPassword') // TODO make it a modal
    }
  }

  render() {
    return (
      <View style={styles.container}>
        {this._renderContent()}
      </View>
    );
  }

  _renderContent() {
    if (this.state.ssids.length > 0) {
      return (
        <View>
          <Text style={styles.title}>
            Select your Wi-Fi network
          </Text>
          <List>
            <FlatList
              data={this.state.ssids}
              renderItem={({item}) => (
                <ListItem title={item}
                  onPress={() => this.onNetworkSelected(item)}
                  leftIcon={this.state.currentSsid == item ? {name: 'done'} : {name: 'wifi'}}
                />
              )}
              keyExtractor={(item) => item}
            />
          </List>
        </View>
      )
    }
    else {
      return (
        <View>
          <Text style={styles.title}>Searching for Wi-Fi networks...</Text>
          <ActivityIndicator size={"large"} />
        </View>
      )
    }
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  title: {
    fontSize: 18,
    textAlign: 'center',
    margin: 10,
  },
})
<commit_msg>Sort SSIDs alphabetically<commit_after>import React from 'react';
import {
  ActivityIndicator,
  FlatList,
  StyleSheet,
  Text,
  View,
} from 'react-native';

import { List, ListItem } from 'react-native-elements'

import HubOnboarding, { WifiConnectionState } from '../HubOnboarding'
import Screen from './Screen';
import Settings from '../Settings'

export default class SetupWifi extends Screen {
  constructor(props) {
    super(props)

    this.state = {
      ssids: [],
      currentSsid: null,
    }

    this.setTitle('Wi-Fi')
  }

  didAppear() {
    let subscribeForWifiEvents = () => {
      HubOnboarding.hubDevice.onNetworksChanged((ssid) => {
        if (!this.state.ssids.find(s => s === ssid)) {
          this.setState({ssids: this.state.ssids
            .concat([ssid])
            .sort(function (a, b) {
              return a.toLowerCase().localeCompare(b.toLowerCase());
            })
          })
        }
      })

      HubOnboarding.hubDevice.onConnectionStateChanged((connectionState, currentSsid) => {
        if (connectionState === WifiConnectionState.CONNECTION_STATE_CONNECTED) {
          this.setState({currentSsid: currentSsid})
        }
      })
    }

    HubOnboarding.hubDevice
      .connect()
      .then(() => {
        subscribeForWifiEvents()
      })
      .catch((error) => {
        alert("Could not connect to the Hub. Please try again.")
        //TODO: Present error message on screen with a "retry" button that connects again
      })
  }

  willDisappear() {
    //TODO: Setting an empty callback isn't clean – use proper approach.
    HubOnboarding.hubDevice.onNetworksChanged(() => {})
    HubOnboarding.hubDevice.onConnectionStateChanged(() => {})
  }

  onNetworkSelected(ssid) {
    console.log("Network selected: " + ssid)

    if (ssid == this.state.currentSsid) {
      Settings.setHubApiUrl(HubOnboarding.hubDevice.dnsName)
        .then(() => this.pushScreen('setup.nuimo'))
    }
    else {
      HubOnboarding.hubDevice.sendSsid(ssid)
      this.pushScreen('setup.wifiPassword') // TODO make it a modal
    }
  }

  render() {
    return (
      <View style={styles.container}>
        {this._renderContent()}
      </View>
    );
  }

  _renderContent() {
    if (this.state.ssids.length > 0) {
      return (
        <View>
          <Text style={styles.title}>
            Select your Wi-Fi network
          </Text>
          <List>
            <FlatList
              data={this.state.ssids}
              renderItem={({item}) => (
                <ListItem title={item}
                  onPress={() => this.onNetworkSelected(item)}
                  leftIcon={this.state.currentSsid == item ? {name: 'done'} : {name: 'wifi'}}
                />
              )}
              keyExtractor={(item) => item}
            />
          </List>
        </View>
      )
    }
    else {
      return (
        <View>
          <Text style={styles.title}>Searching for Wi-Fi networks...</Text>
          <ActivityIndicator size={"large"} />
        </View>
      )
    }
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  title: {
    fontSize: 18,
    textAlign: 'center',
    margin: 10,
  },
})
<|endoftext|>


<commit_before>/**
* NpolarBaseController is meant to be the parent of a safe Controller,
* ie. a controller dealing with only with presentation. See also NpolarEditController.
*
*
* Usage:
*
* angular.module('myApp').controller('MyApiController', function($scope, $routeParams, $controller, MyModel) {
*
* // 1. MyApiController -> NpolarBaseController
* $controller('NpolarBaseController', {$scope: $scope});
*
* // 2. Set resource for parent document operations
* $scope.resource = MyModel;
*
* // 3. Set document for resource (and view)
* MyModel.fetch($routeParams, function(document) {
*   $scope.document = document;
* }, function() error {
*   $scope.error = error;
* });
*
*/

'use strict';
var _ = require('lodash');

/**
 * @ngInject
 */
var BaseController = function($scope, $location, $route, $routeParams, $window, $controller, $http,
  npolarApiConfig, NpolarApiSecurity, NpolarApiUser, NpolarApiResource) {

  $scope.init = function() {
    $scope.base = npolarApiConfig.base;
    $scope.environment = npolarApiConfig.environment;
    $scope.lang = npolarApiConfig.lang;
    $scope.user = NpolarApiSecurity.getUser();
  };

  // back() click handler
  $scope.back = function() {
    $window.history.back();
  };

  $scope.isAuthenticated = function() {
    return (NpolarApiSecurity.isJwtValid());
  };

  $scope.locationBase = function() {
    console.log($routeParams.id);
    if ($routeParams.id === '__new') {
      $location.path('/');
    } else {
      $location.path($routeParams.id);
    }
  };

  // Show action, ie. fetch document and inject into scope
  $scope.show = function() {
    $scope.resource.fetch($routeParams, function(document) {
      $scope.document = document;

    }, function(error) {
      $scope.error = NpolarApiResource.error(error);
    });
  };

  $scope.search = function(query) {
    $scope.resource.feed(query, function(response) {
      $scope.feed = response.feed;
    }, function(error) {
      $scope.error = NpolarApiResource.error(error);
    });
  };

  $scope.getLang = function() {
    return $scope.lang;
  };

  $scope.setLang = function(lang) {
    $scope.lang = lang;
    $scope.title = $scope.getTitle(lang);
  };

  $scope.getTitle = function(lang) {
    return _.where($scope.document.titles,
      { lang: lang }
    )[0].text || $scope.document.titles[0].text;
  };

  $scope.isSuccess = function(status) {
    return (status >= 200 && status <= 299);
  };

  $scope.isError = function(status) {
    return (status <= 99 || status >= 400);
  };

  $scope.init();
};

module.exports = BaseController;<commit_msg>Added security to BaseController<commit_after>/**
* NpolarBaseController is meant to be the parent of a safe Controller,
* ie. a controller dealing with only with presentation. See also NpolarEditController.
*
*
* Usage:
*
* angular.module('myApp').controller('MyApiController', function($scope, $routeParams, $controller, MyModel) {
*
* // 1. MyApiController -> NpolarBaseController
* $controller('NpolarBaseController', {$scope: $scope});
*
* // 2. Set resource for parent document operations
* $scope.resource = MyModel;
*
* // 3. Set document for resource (and view)
* MyModel.fetch($routeParams, function(document) {
*   $scope.document = document;
* }, function() error {
*   $scope.error = error;
* });
*
*/

'use strict';
var _ = require('lodash');

/**
 * @ngInject
 */
var BaseController = function($scope, $location, $route, $routeParams, $window, $controller, $http,
  npolarApiConfig, NpolarApiSecurity, NpolarApiUser, NpolarApiResource) {

  $scope.init = function() {
    $scope.base = npolarApiConfig.base;
    $scope.environment = npolarApiConfig.environment;
    $scope.lang = npolarApiConfig.lang;
    $scope.user = NpolarApiSecurity.getUser();
    $scope.security = NpolarApiSecurity;
  };

  // back() click handler
  $scope.back = function() {
    $window.history.back();
  };

  $scope.isAuthenticated = function() {
    return (NpolarApiSecurity.isJwtValid());
  };

  $scope.locationBase = function() {
    console.log($routeParams.id);
    if ($routeParams.id === '__new') {
      $location.path('/');
    } else {
      $location.path($routeParams.id);
    }
  };

  // Show action, ie. fetch document and inject into scope
  $scope.show = function() {
    $scope.resource.fetch($routeParams, function(document) {
      $scope.document = document;

    }, function(error) {
      $scope.error = NpolarApiResource.error(error);
    });
  };

  $scope.search = function(query) {
    $scope.resource.feed(query, function(response) {
      $scope.feed = response.feed;
    }, function(error) {
      $scope.error = NpolarApiResource.error(error);
    });
  };

  $scope.getLang = function() {
    return $scope.lang;
  };

  $scope.setLang = function(lang) {
    $scope.lang = lang;
    $scope.title = $scope.getTitle(lang);
  };

  $scope.getTitle = function(lang) {
    return _.where($scope.document.titles,
      { lang: lang }
    )[0].text || $scope.document.titles[0].text;
  };

  $scope.isSuccess = function(status) {
    return (status >= 200 && status <= 299);
  };

  $scope.isError = function(status) {
    return (status <= 99 || status >= 400);
  };

  $scope.init();
};

module.exports = BaseController;<|endoftext|>


<commit_before>/** @module ShortcodeTokenizer */

/* tokens */
const TEXT = 'TEXT'
const ERROR = 'ERROR'
const OPEN = 'OPEN'
const CLOSE = 'CLOSE'
const SELF_CLOSING = 'SELF_CLOSING'

/* eslint-disable */

/* matches code name */
const RX_KEY = '[a-zA-Z][a-zA-Z0-9_-]*'

/* matches paramters */
const RX_PARAM =       RX_KEY + '=\\d+\\.\\d+' +    // floats
                 '|' + RX_KEY + '=\\d+' +           // ints
                 '|' + RX_KEY + '="[^\\]"]*"' +     // double-qouted strings
                 '|' + RX_KEY + '=\'[^\\]\']*\'' +  // single-qouted strings
                 '|' + RX_KEY                       // flags
const RX_PARAMS = '(?:(?:' + RX_PARAM + ')(?:(?!\\s+/?\\])\\s|))+'

/* matches all code token types, used for quickly
   finding potentia code tokens */
const RX_ENCLOSURE   = '\\[\\/?[a-zA-Z][^\\]]+\\]'
/* matches opening code tokens [row] */
const RX_OPEN        = '\\[(' + RX_KEY + ')(\\s' + RX_PARAMS + ')?\\]'
/* matches self-closing code tokens [row/] */
const RX_SELFCLOSING = '\\[(' + RX_KEY + ')(\\s' + RX_PARAMS + ')?\\s?\\/\\]'
/* matches close code tokens [/row] */
const RX_CLOSE       = '\\[\\/(' + RX_KEY + ')\\]'

/* case-insensitive regular expressions */
const rxParams      = new RegExp(RX_PARAMS.substring(0, RX_PARAMS.length - 1), 'ig')
const rxEnclosure   = new RegExp(RX_ENCLOSURE, 'i')
const rxOpen        = new RegExp(RX_OPEN, 'i')
const rxClose       = new RegExp(RX_CLOSE, 'i')
const rxSelfclosing = new RegExp(RX_SELFCLOSING, 'i')

/* eslint-enable */

/**
 * Get token type based on token-string.
 *
 * Note: assuming that this is not a TEXT token
 *
 * @param {string} str
 * @returns {string} token type
 */
function getTokenType(str) {
  if (str[1] === '/') {
    return CLOSE
  }
  if (str[str.length - 2] === '/') {
    return SELF_CLOSING
  }
  return OPEN
}

/**
 * Casts input string to native types.
 *
 * @param {string} value
 * @returns {*} mixed value
 */
function castValue(value) {
  value = value.replace(/(^['"]|['"]$)/g, '')
  if (/^\d+$/.test(value)) return +value
  if (/^\d+.\d+$/.test(value)) return parseFloat(value)
  if (/^['"]?(true|false|yes|no)['"]?$/i.test(value)) {
    value = value
      .replace(/(^['"]|['"]$)/g, '')
      .toLowerCase()
    return value === 'true' || value === 'yes'
  }
  if (value === 'undefined') return typeof thisIsNotDefined
  if (value === 'null') return null
  return value
}

/**
 * Token class is used both as a token during tokenization/lexing
 * and as a node in the resulting AST.
 *
 * @access private
 */
export class Token {
  constructor(type, body, pos = 0) {
    this.name = null
    this.type = type
    this.body = body
    this.pos = pos
    this.children = []
    this.params = {}
    this.isClosed = type === SELF_CLOSING
    this.init()
  }

  /**
   * @access private
   */
  init() {
    if (this.type !== TEXT && this.type !== ERROR) {
      const match = this.matchBody()
      this.initName(match)
      if (match[2]) {
        this.initParams(match[2])
      }
    }
  }

  /**
   * @access private
   */
  initName(match) {
    this.name = match[1]
  }

  /**
   * @access private
   */
  initParams(paramStr) {
    const match = paramStr.match(rxParams)
    this.params = match.reduce((params, paramToken) => {
      paramToken = paramToken.trim()
      let equal = paramToken.indexOf('=')
      if (!~equal) {
        params[paramToken] = true
      } else {
        params[paramToken.substring(0, equal)] = castValue(paramToken.substring(equal + 1))
      }
      return params
    }, {})
  }

  /**
   * @access private
   */
  matchBody() {
    let rx
    if (this.type === CLOSE) {
      rx = rxClose
    } else if (this.type === OPEN) {
      rx = rxOpen
    } else if (this.type === SELF_CLOSING) {
      rx = rxSelfclosing
    } else {
      throw new SyntaxError('Unknown token: ' + this.type)
    }

    let match = this.body.match(rx)
    if (match === null) {
      throw new SyntaxError('Invalid ' + this.type + ' token: ' + this.body)
    }
    return match
  }

  /**
   * Determines if this token can close the param token.
   *
   * @access public
   * @param {Token} token another token
   * @returns {boolean}
   */
  canClose(token) {
    return this.name === token.name
  }
}

/**
 * Creates a new tokenizer.
 *
 * Pass in input as first param or later using `input()`
 *
 * @param {string} [input=null] Optional input to tokenize
 * @param {Object} [options] options object
 * @param {boolean} [options.strict=true] strict mode
 * @param {boolean} [options.skipWhiteSpace=false] will ignore tokens containing only white space (basically all \s)
 */
export default class ShortcodeTokenizer {

  constructor(input = null, options = {strict: true, skipWhiteSpace: false}) {
    if (typeof options === 'boolean') {
      options = {strict: options, skipWhiteSpace: false}
    }
    this.options = options
    this.buf = null
    this.originalBuf = null
    this.pos = 0
    if (input) {
      this.input(input)
    }
  }

  /**
   * @deprecated use options.strict
   */
  get strict() {
    console.warn(`Deprecated: use options.strict instead`)
    return this.options.strict
  }

  /**
   * @deprecated use options.strict
   */
  set strict(value) {
    console.warn(`Deprecated: use options.strict = ${value} instead`)
    this.options.strict = value
  }

  /**
   * Sets input buffer with a new input string.
   *
   * @param {string} input template string
   * @throws {Error} Invalid input
   * @returns {this} returns this for chaining
   */
  input(input) {
    if (typeof input !== 'string') {
      throw new Error('Invalid input')
    }

    this.buf = this.originalBuf = input
    this.pos = 0
    return this
  }

  /**
   * Resets input buffer and position to their origial values.
   *
   * @returns {this} returns this for chaining
   */
  reset() {
    this.buf = this.originalBuf
    this.pos = 0
    return this
  }

  /**
   * Creates a token generator.
   *
   * @throws {Error} Invalid input
   * @returns {Token[]} An array of Token instances
   */
  tokens(input = null) {
    if (input) {
      this.input(input)
    }

    if (typeof this.buf !== 'string') {
      throw new Error('Invalid input')
    }

    let tokens = []
    let allTokens = []
    while ((tokens = this._next()) !== null) {
      tokens = Array.isArray(tokens) ? tokens : [tokens]
      allTokens.push(...tokens)
    }
    return allTokens
  }

  /**
   * Uses the tokens generator to build an AST from the tokens.
   *
   * @see tokens
   * @returns {array} an array of AST roots
   */
  ast(input = null) {
    let tokens = this.tokens(input)
    let stack = []
    let ast = []
    let parent = null
    let token
    for (token of tokens) {
      if (token.type === TEXT) {
        if (this.options.skipWhiteSpace && token.body.replace(/\s+/g, '').length === 0) {
          continue
        }
        if (!parent) {
          ast.push(token)
        } else {
          parent.children.push(token)
        }
      } else if (token.type === OPEN) {
        if (!parent) {
          parent = token
          ast.push(parent)
        } else {
          parent.children.push(token)
          stack.push(parent)
          parent = token
        }
      } else if (token.type === CLOSE) {
        if (!parent || !token.canClose(parent)) {
          if (this.options.strict) {
            throw new SyntaxError('Unmatched close token: ' + token.body)
          } else {
            let err = new Token(ERROR, token.body)
            if (!parent) {
              ast.push(err)
            } else {
              parent.children.push(err)
            }
          }
        } else {
          parent.isClosed = true
          parent = stack.pop()
        }
      } else if (token.type === SELF_CLOSING) {
        if (!parent) {
          ast.push(token)
        } else {
          parent.children.push(token)
        }
      } else {
        throw new SyntaxError('Unknown token: ' + token.type)
      }
    }
    if (parent) {
      if (this.options.strict) {
        throw new SyntaxError('Unmatched open token: ' + parent.body)
      } else {
        ast.push(new Token(ERROR, token.body))
      }
    }
    return ast
  }

  /**
   * Internal function used to retrieve the next token from the current
   * position in the input buffer.
   *
   * @private
   * @returns {Token} returns the next Token from the input buffer
   */
  _next() {
    if (!this.buf) {
      return null
    }

    let match = this.buf.match(rxEnclosure)

    // all text
    if (match === null) {
      let token = new Token(TEXT, this.buf, this.pos)
      this.pos += this.buf.length
      this.buf = null
      return token
    }

    let tokens = []

    // first part is text
    if (match.index !== 0) {
      tokens.push(new Token(
        TEXT,
        this.buf.substring(0, match.index),
        this.pos
      ))
    }

    // matching token
    tokens.push(new Token(
      getTokenType(match[0]),
      match[0],
      this.pos + match.index
    ))

    // shorten buffer
    this.buf = this.buf.substring(match.index + match[0].length)
    this.pos += match.index + match[0].length
    if (this.buf.length === 0) {
      this.buf = null
    }
    return tokens
  }
}

Object.assign(ShortcodeTokenizer, {
  TEXT,
  ERROR,
  OPEN,
  CLOSE,
  SELF_CLOSING,
  rxParams,
  rxEnclosure,
  rxOpen,
  rxClose,
  rxSelfclosing
})
<commit_msg>Added boolean regexp to tag match<commit_after>/** @module ShortcodeTokenizer */

/* tokens */
const TEXT = 'TEXT'
const ERROR = 'ERROR'
const OPEN = 'OPEN'
const CLOSE = 'CLOSE'
const SELF_CLOSING = 'SELF_CLOSING'

/* eslint-disable */

/* matches code name */
const RX_KEY = '[a-zA-Z][a-zA-Z0-9_-]*'

/* matches paramters */
const RX_PARAM =       RX_KEY + '=\\d+\\.\\d+' +    // floats
                 '|' + RX_KEY + '=\\d+' +           // ints
                 '|' + RX_KEY + '=(true|false)' +   // bools
                 '|' + RX_KEY + '="[^\\]"]*"' +     // double-qouted strings
                 '|' + RX_KEY + '=\'[^\\]\']*\'' +  // single-qouted strings
                 '|' + RX_KEY                       // flags
const RX_PARAMS = '(?:(?:' + RX_PARAM + ')(?:(?!\\s+/?\\])\\s|))+'

/* matches all code token types, used for quickly
   finding potentia code tokens */
const RX_ENCLOSURE   = '\\[\\/?[a-zA-Z][^\\]]+\\]'
/* matches opening code tokens [row] */
const RX_OPEN        = '\\[(' + RX_KEY + ')(\\s' + RX_PARAMS + ')?\\]'
/* matches self-closing code tokens [row/] */
const RX_SELFCLOSING = '\\[(' + RX_KEY + ')(\\s' + RX_PARAMS + ')?\\s?\\/\\]'
/* matches close code tokens [/row] */
const RX_CLOSE       = '\\[\\/(' + RX_KEY + ')\\]'

/* case-insensitive regular expressions */
const rxParams      = new RegExp(RX_PARAMS.substring(0, RX_PARAMS.length - 1), 'ig')
const rxEnclosure   = new RegExp(RX_ENCLOSURE, 'i')
const rxOpen        = new RegExp(RX_OPEN, 'i')
const rxClose       = new RegExp(RX_CLOSE, 'i')
const rxSelfclosing = new RegExp(RX_SELFCLOSING, 'i')

/* eslint-enable */

/**
 * Get token type based on token-string.
 *
 * Note: assuming that this is not a TEXT token
 *
 * @param {string} str
 * @returns {string} token type
 */
function getTokenType(str) {
  if (str[1] === '/') {
    return CLOSE
  }
  if (str[str.length - 2] === '/') {
    return SELF_CLOSING
  }
  return OPEN
}

/**
 * Casts input string to native types.
 *
 * @param {string} value
 * @returns {*} mixed value
 */
function castValue(value) {
  value = value.replace(/(^['"]|['"]$)/g, '')
  if (/^\d+$/.test(value)) return +value
  if (/^\d+.\d+$/.test(value)) return parseFloat(value)
  if (/^['"]?(true|false|yes|no)['"]?$/i.test(value)) {
    value = value
      .replace(/(^['"]|['"]$)/g, '')
      .toLowerCase()
    return value === 'true' || value === 'yes'
  }
  if (value === 'undefined') return typeof thisIsNotDefined
  if (value === 'null') return null
  return value
}

/**
 * Token class is used both as a token during tokenization/lexing
 * and as a node in the resulting AST.
 *
 * @access private
 */
export class Token {
  constructor(type, body, pos = 0) {
    this.name = null
    this.type = type
    this.body = body
    this.pos = pos
    this.children = []
    this.params = {}
    this.isClosed = type === SELF_CLOSING
    this.init()
  }

  /**
   * @access private
   */
  init() {
    if (this.type !== TEXT && this.type !== ERROR) {
      const match = this.matchBody()
      this.initName(match)
      if (match[2]) {
        this.initParams(match[2])
      }
    }
  }

  /**
   * @access private
   */
  initName(match) {
    this.name = match[1]
  }

  /**
   * @access private
   */
  initParams(paramStr) {
    const match = paramStr.match(rxParams)
    this.params = match.reduce((params, paramToken) => {
      paramToken = paramToken.trim()
      let equal = paramToken.indexOf('=')
      if (!~equal) {
        params[paramToken] = true
      } else {
        params[paramToken.substring(0, equal)] = castValue(paramToken.substring(equal + 1))
      }
      return params
    }, {})
  }

  /**
   * @access private
   */
  matchBody() {
    let rx
    if (this.type === CLOSE) {
      rx = rxClose
    } else if (this.type === OPEN) {
      rx = rxOpen
    } else if (this.type === SELF_CLOSING) {
      rx = rxSelfclosing
    } else {
      throw new SyntaxError('Unknown token: ' + this.type)
    }

    let match = this.body.match(rx)
    if (match === null) {
      throw new SyntaxError('Invalid ' + this.type + ' token: ' + this.body)
    }
    return match
  }

  /**
   * Determines if this token can close the param token.
   *
   * @access public
   * @param {Token} token another token
   * @returns {boolean}
   */
  canClose(token) {
    return this.name === token.name
  }
}

/**
 * Creates a new tokenizer.
 *
 * Pass in input as first param or later using `input()`
 *
 * @param {string} [input=null] Optional input to tokenize
 * @param {Object} [options] options object
 * @param {boolean} [options.strict=true] strict mode
 * @param {boolean} [options.skipWhiteSpace=false] will ignore tokens containing only white space (basically all \s)
 */
export default class ShortcodeTokenizer {

  constructor(input = null, options = {strict: true, skipWhiteSpace: false}) {
    if (typeof options === 'boolean') {
      options = {strict: options, skipWhiteSpace: false}
    }
    this.options = options
    this.buf = null
    this.originalBuf = null
    this.pos = 0
    if (input) {
      this.input(input)
    }
  }

  /**
   * @deprecated use options.strict
   */
  get strict() {
    console.warn(`Deprecated: use options.strict instead`)
    return this.options.strict
  }

  /**
   * @deprecated use options.strict
   */
  set strict(value) {
    console.warn(`Deprecated: use options.strict = ${value} instead`)
    this.options.strict = value
  }

  /**
   * Sets input buffer with a new input string.
   *
   * @param {string} input template string
   * @throws {Error} Invalid input
   * @returns {this} returns this for chaining
   */
  input(input) {
    if (typeof input !== 'string') {
      throw new Error('Invalid input')
    }

    this.buf = this.originalBuf = input
    this.pos = 0
    return this
  }

  /**
   * Resets input buffer and position to their origial values.
   *
   * @returns {this} returns this for chaining
   */
  reset() {
    this.buf = this.originalBuf
    this.pos = 0
    return this
  }

  /**
   * Creates a token generator.
   *
   * @throws {Error} Invalid input
   * @returns {Token[]} An array of Token instances
   */
  tokens(input = null) {
    if (input) {
      this.input(input)
    }

    if (typeof this.buf !== 'string') {
      throw new Error('Invalid input')
    }

    let tokens = []
    let allTokens = []
    while ((tokens = this._next()) !== null) {
      tokens = Array.isArray(tokens) ? tokens : [tokens]
      allTokens.push(...tokens)
    }
    return allTokens
  }

  /**
   * Uses the tokens generator to build an AST from the tokens.
   *
   * @see tokens
   * @returns {array} an array of AST roots
   */
  ast(input = null) {
    let tokens = this.tokens(input)
    let stack = []
    let ast = []
    let parent = null
    let token
    for (token of tokens) {
      if (token.type === TEXT) {
        if (this.options.skipWhiteSpace && token.body.replace(/\s+/g, '').length === 0) {
          continue
        }
        if (!parent) {
          ast.push(token)
        } else {
          parent.children.push(token)
        }
      } else if (token.type === OPEN) {
        if (!parent) {
          parent = token
          ast.push(parent)
        } else {
          parent.children.push(token)
          stack.push(parent)
          parent = token
        }
      } else if (token.type === CLOSE) {
        if (!parent || !token.canClose(parent)) {
          if (this.options.strict) {
            throw new SyntaxError('Unmatched close token: ' + token.body)
          } else {
            let err = new Token(ERROR, token.body)
            if (!parent) {
              ast.push(err)
            } else {
              parent.children.push(err)
            }
          }
        } else {
          parent.isClosed = true
          parent = stack.pop()
        }
      } else if (token.type === SELF_CLOSING) {
        if (!parent) {
          ast.push(token)
        } else {
          parent.children.push(token)
        }
      } else {
        throw new SyntaxError('Unknown token: ' + token.type)
      }
    }
    if (parent) {
      if (this.options.strict) {
        throw new SyntaxError('Unmatched open token: ' + parent.body)
      } else {
        ast.push(new Token(ERROR, token.body))
      }
    }
    return ast
  }

  /**
   * Internal function used to retrieve the next token from the current
   * position in the input buffer.
   *
   * @private
   * @returns {Token} returns the next Token from the input buffer
   */
  _next() {
    if (!this.buf) {
      return null
    }

    let match = this.buf.match(rxEnclosure)

    // all text
    if (match === null) {
      let token = new Token(TEXT, this.buf, this.pos)
      this.pos += this.buf.length
      this.buf = null
      return token
    }

    let tokens = []

    // first part is text
    if (match.index !== 0) {
      tokens.push(new Token(
        TEXT,
        this.buf.substring(0, match.index),
        this.pos
      ))
    }

    // matching token
    tokens.push(new Token(
      getTokenType(match[0]),
      match[0],
      this.pos + match.index
    ))

    // shorten buffer
    this.buf = this.buf.substring(match.index + match[0].length)
    this.pos += match.index + match[0].length
    if (this.buf.length === 0) {
      this.buf = null
    }
    return tokens
  }
}

Object.assign(ShortcodeTokenizer, {
  TEXT,
  ERROR,
  OPEN,
  CLOSE,
  SELF_CLOSING,
  rxParams,
  rxEnclosure,
  rxOpen,
  rxClose,
  rxSelfclosing
})
<|endoftext|>