<commit_before>// This websocket client runs in the browser and talks to the
// websocket server that's defined in server.js.
class Client {
  // game is a GameState and should start at the beginning of the game.
  constructor(name, game) {
    this.name = name
    this.game = game
    this.makeSocket()

    // The next op id we expect.
    // This is only useful after a "start" specifies which channel you
    // are listening to.
    // We skip 0 to avoid bugs. Each actual move for a game has an id,
    // starting at 1.
    this.nextID = 1
  }

  makeSocket() {
    let url = document.URL.replace("http", "ws").replace(/\/$/, "").replace(":8080", "") + ":9090"
    console.log(`connecting to ${url}`)

    // TODO: needs a more aggressive timeout
    this.ws = new WebSocket(url)

    this.ws.onmessage = (event => this.receive(event.data))
    this.ws.onerror = (event => this.handleError())
    this.ws.onclose = (event => this.handleClose())
  }

  // When data is received from the server
  receive(messageData) {
    let message = JSON.parse(messageData)

    if (message.op == "hello") {
      // We don't do anything on a hello right now.
      // It isn't technically necessary, it's just handy for debugging
      // because you expect a hello to come in.
      return
    }

    message.id = this.nextID

    if (message.op == "start") {
      this.handleStart(message)
    } else if (message.op == "draw") {
      // in spacetime, we keep on drawing
      this.handleRemoteMove(message)
    } else if (message.id != this.nextID) {
      console.log("out of order, returning: " + message)
      // This is a dupe, or out-of-order. Ignore it
    } else if (this.handleRemoteMove(message)) {
      // It was a remote move
    } else {
      console.log("don't know how to handle this message. dropping it")
      return
    }
  }

  // Sends a message object upstream.
  send(message) {
    this.ws.send(JSON.stringify(message))
  }

  // Send a looking-for-game message.
  register() {
    this.registered = true; // UI checks this to refresh on game seek
    console.log("registering as " + this.name)
    if (this.game) {
      this.send({op: "register", name: this.name, seeking: !this.game.started()})
    } else {
      this.send({op: "register", name: this.name, seeking: true})
    }
  }

  forceUpdate() {
    console.log("forceUpdate on the client was not overridden")
  }

  // Handles a move being reported from the server.
  // Returns whether we knew what to do with it.
  handleRemoteMove(move) {
    if (!move.op || !move.player) {
      return false
    }

    if (this.game.makeMove(move)) {
      this.buffer = []
      this.forceUpdate()
      this.nextID++
      return true
    }
    return false
  }

  // Sends a move to the server.
  // This does *not* display the move until the server confirms it.
  makeLocalMove(move) {
    move.player = this.name
    move.gameID = this.gameID

    this.send(move)
  }

  // This is called locally when the server decides remotely that a
  // game should start.
  handleStart(message) {
    this.buffer = []
    let players = message.players
    this.gameID = message.gameID

    console.log(`starting game: ${players[0]} vs ${players[1]}`)
    window.track("startGame")

    // Just use the gameID as the seed
    this.game.startGame(players, this.gameID)

    this.forceUpdate()
  }

  handleError() {
    this.ws.close()
  }

  // Whenever the socket closes we just make a new one
  handleClose() {
    console.log("the socket closed")

    // Wait 2 seconds to reconnect
    setTimeout(() => this.makeSocket(), 2000)
  }
}

module.exports = Client
<commit_msg>logging in client<commit_after>// This websocket client runs in the browser and talks to the
// websocket server that's defined in server.js.
class Client {
  // game is a GameState and should start at the beginning of the game.
  constructor(name, game) {
    this.name = name
    this.game = game
    this.makeSocket()

    // The next op id we expect.
    // This is only useful after a "start" specifies which channel you
    // are listening to.
    // We skip 0 to avoid bugs. Each actual move for a game has an id,
    // starting at 1.
    this.nextID = 1
  }

  makeSocket() {
    let url = document.URL.replace("http", "ws").replace(/\/$/, "").replace(":8080", "") + ":9090"
    console.log(`connecting to ${url}`)

    // TODO: needs a more aggressive timeout
    this.ws = new WebSocket(url)

    this.ws.onmessage = (event => this.receive(event.data))
    this.ws.onerror = (event => this.handleError())
    this.ws.onclose = (event => this.handleClose())
  }

  // When data is received from the server
  receive(messageData) {
    let message = JSON.parse(messageData)

    if (message.op == "hello") {
      // We don't do anything on a hello right now.
      // It isn't technically necessary, it's just handy for debugging
      // because you expect a hello to come in.
      return
    }

    message.id = this.nextID

    if (message.op == "start") {
      this.handleStart(message)
    } else if (message.op == "draw") {
      // in spacetime, we keep on drawing
      this.handleRemoteMove(message)
    } else if (message.id != this.nextID) {
      console.log("out of order, returning: " + message)
      // This is a dupe, or out-of-order. Ignore it
    } else if (this.handleRemoteMove(message)) {
      // It was a remote move
    } else {
      console.log("don't know how to handle this message. dropping it")
      return
    }
  }

  // Sends a message object upstream.
  send(message) {
    this.ws.send(JSON.stringify(message))
  }

  // Send a looking-for-game message.
  register() {
    this.registered = true; // UI checks this to refresh on game seek
    console.log("registering as " + this.name)
    if (this.game) {
      this.send({op: "register", name: this.name, seeking: !this.game.started()})
    } else {
      this.send({op: "register", name: this.name, seeking: true})
    }
  }

  forceUpdate() {
    console.log("forceUpdate on the client was not overridden")
  }

  // Handles a move being reported from the server.
  // Returns whether we knew what to do with it.
  handleRemoteMove(move) {
    if (!move.op || !move.player) {
      return false
    }

    console.log("making move: " + JSON.stringify(move))
    if (this.game.makeMove(move)) {
      console.log("new state: " + JSON.stringify(this.game))
      this.buffer = []
      this.forceUpdate()
      this.nextID++
      return true
    }
    return false
  }

  // Sends a move to the server.
  // This does *not* display the move until the server confirms it.
  makeLocalMove(move) {
    move.player = this.name
    move.gameID = this.gameID

    this.send(move)
  }

  // This is called locally when the server decides remotely that a
  // game should start.
  handleStart(message) {
    this.buffer = []
    let players = message.players
    this.gameID = message.gameID

    console.log(`starting game: ${players[0]} vs ${players[1]}`)
    window.track("startGame")

    // Just use the gameID as the seed
    this.game.startGame(players, this.gameID)

    this.forceUpdate()
  }

  handleError() {
    this.ws.close()
  }

  // Whenever the socket closes we just make a new one
  handleClose() {
    console.log("the socket closed")

    // Wait 2 seconds to reconnect
    setTimeout(() => this.makeSocket(), 2000)
  }
}

module.exports = Client
<|endoftext|>


<commit_before>import {Schemas} from './SchemasHelpers'
import {TimeSlotService} from "../../../both/service/TimeSlotService"

//order matters !
Schemas.UserAvailabilities = new SimpleSchema({
    start: {
        type: Date,
        label: "User Availabilities Start Date",
        custom: function () {
            if (new moment(this.value).isAfter(new moment(this.field(this.key.replace("start", "") + "end").value)))
                return "startAfterEnd";

            var userTeams = Users.findOne(this.docId).teams;
            var start = this.value;

            if (!AssignmentTerms.findOne({
                    teams: {
                        $elemMatch: {
                            $in: userTeams
                        }
                    },
                        start: {
                            $lte: start
                        }
                })
            )
                return "availabilitiesNoInTerm"

        },
        autoform: {
            type: "datetime-local",
        }
    },
    end: {
        type: Date,
        label: "User Availabilities End Date",
        custom: function () {
            if (new moment(this.value).isSame(new moment(this.field(this.key.replace("end", "") + "start").value)))
                return "endBeforeStart"

            var userTeams = Users.findOne(this.docId).teams;
            var end = this.value;

            if (!AssignmentTerms.findOne({
                    teams: {
                        $elemMatch: {
                            $in: userTeams
                        }
                    },
                    end: {
                        $gte: end
                    }
                })
            )
                return "availabilitiesNoInTerm"
        },
        autoform: {
            type: "datetime-local",
        }
    }
});

Schemas.UserAssignment = new SimpleSchema({
    taskName: {
        type: String,
        label: "User assignment User Name"
    },
    start: {
        type: Date,
        label: "User Assignment Start Date"
    },
    end: {
        type: Date,
        label: "User Assignment End Date"
    },
    assignmentId: {
        type: SimpleSchema.RegEx.Id,
        label: "User assignment assignment id",
        custom: function () { //validate data is same as the real assignment
            var assignment = Assignments.findOne(this.value);
            if (!assignment)
                return "unknownId"
            var timeSlot = TimeSlotService.getTimeSlot(assignment.taskId,assignment.timeSlotId);
            if (Tasks.findOne(assignment.taskId).name !== this.field(this.key.replace("assignmentId", "") + "taskName").value
                || !new moment(timeSlot.start).isSame(new moment(this.field(this.key.replace("assignmentId", "") + "start").value))
                || !new moment(timeSlot.end).isSame(new moment(this.field(this.key.replace("assignmentId", "") + "end").value)))
                return "userAssignmentNotMatching"
        }
    }
});

Schemas.Users = new SimpleSchema({
    name: {
        type: String,
        label: "Users Name",
        max: 100
    },
    firstName: {
        type: String,
        label: "User first name",
        max: 100,
        optional: true,
        defaultValue: null
    },
    familyName: {
        type: String,
        label: "User family name",
        max: 100,
        optional: true,
        defaultValue: null
    },
    phoneNumber:{
        type: SimpleSchema.RegEx.Phone,
        label: "User phone",
        optional: true,
        defaultValue: null
    },
    birthDate: {
        type: Date,
        label: "User birth date",
        optional: true,
        defaultValue: null
    },
    loginUserId: {
        label: "User login link to collection managed by Account package",
        type: SimpleSchema.RegEx.Id,
        optional: true
    },
    isReadyForAssignment: {
        label: "User assignment ready state",
        type: Boolean,
        defaultValue: false,
        custom: function () {
            if(this.isUpdate)
            if(this.value === false){
                if(Users.findOne(this.docId).assignments && Users.findOne(this.docId).assignments.length !== 0){
                    return "userHasAssignments"
                }
            }
        }
    },
    groupRoles: {
        label: "User roles to gain a set of less or more data and features",
        type: [SimpleSchema.RegEx.Id],
        optional: true,
        custom: function () {
            this.value = _.compact(this.value);
            if(GroupRoles.find({_id:{$in:this.value}}).fetch().length !== this.value.length)
                return "unknownIdOrDuplicateId"
        }
    },
    'groupRoles.$': {
        autoform: {
            afFieldInput: {
                options: Schemas.helpers.allGroupRolesOptions
            }
        }
    },
    teams: {
        label: "User teams",
        type: [SimpleSchema.RegEx.Id],
        optional: true,
        custom: function () {
            this.value = _.compact(this.value);
            if(Teams.find({_id:{$in:this.value}}).fetch().length !== this.value.length)
                return "unknownIdOrDuplicateId"
        },
        autoValue: function () {
            if (this.isInsert) {
                //trick pour les filtres, tous les users appartiennement au moins à l'équipe ASSIGNMENTREADYTEAM
                var assignmentReadyTeam = Teams.findOne({name: ASSIGNMENTREADYTEAM});
                if(!this.value)
                    this.value = [];
                else if (!_.contains(this.value,assignmentReadyTeam._id)) //we don't add it we it already have it (when autoform do the check several times)
                    this.value.push(assignmentReadyTeam._id);
                return this.value;
            }

            if(this.isUpdate){
                if(this.field("teams").isSet){
                    if(this.field("teams").operator === "$pull"){
                        var teamRemoved = this.field("teams").value;

                        var user = Users.findOne(this.docId);
                        console.log(teamRemoved, "has been removed from",user);

                        var userTeams = user.teams;
                        userTeams.splice(userTeams.indexOf(teamRemoved),1);

                        //assignmentTerms that the user will actually lose (because he doesn't have another team covering it)
                        var assignmentTerms = AssignmentTerms.find({
                            $and : [
                                {
                                    teams: teamRemoved
                                },
                                {
                                    teams: {
                                        $not: {$in: userTeams}
                                    }
                                }
                            ]
                        }).fetch();

                        //all periods that the user will lose
                        var allPeriods = [];
                        assignmentTerms.forEach(assignmentTerm => {
                            if(assignmentTerm.assignmentTermPeriods.length !== 0){
                                allPeriods.concat(assignmentTerm.assignmentTermPeriods);
                            } else {
                                allPeriods.push({
                                    start: assignmentTerm.start,
                                    end: assignmentTerm.end
                                })
                            }
                        });

                        //check if user has one availability included in one of the periods he will lose
                        var availabilities = user.availabilities;
                        var oneAvailabilityIsIncluded = false;
                        user.availabilities.forEach(availability => {
                            if(oneAvailabilityIsIncluded) return; //break
                            var includeInAPeriod = false;
                            allPeriods.forEach(period => {
                                if(includeInAPeriod)
                                    return; //breack

                                if(TimeSlotService.isOverlapping(availability.start,availability.end,period.start,period.end)){
                                    includeInAPeriod = true;
                                }
                            });

                            if(includeInAPeriod){
                                oneAvailabilityIsIncluded = true;
                            }

                        });

                        if(oneAvailabilityIsIncluded){
                            if(Meteor.isClient)
                                sAlert.error(`You can not remove this team, user has already add availabilities in one a the team assignment term.
                                You can try to add another team whose assignment terms cover the existing user's availabilities.`,{timeout:7000});

                            throw new Meteor.Error("403", `Forbidden, user still have availability in at least one term that he will lose if team is removed`);
                            this.unset();
                        }

                    }
                }

            }
        },
    },
    'teams.$': {
        autoform: {
            afFieldInput: {
                options: Schemas.helpers.allTeamsOptions
            }
        }
    },
    skills: {
        label: "User Skills",
        type: [SimpleSchema.RegEx.Id],
        optional: true,
        defaultValue: [],
        custom: function () {
            this.value = _.compact(this.value);
            if(Skills.find({_id:{$in:this.value}}).fetch().length !== this.value.length)
                return "unknownIdOrDuplicateId"

            if(this.isUpdate && Users.findOne(this.docId).isReadyForAssignment && Users.findOne(this.docId).isReadyForAssignment === true)
                return "userHasBeenValidatedNoSkillsUpdate"
        }
    },
    'skills.$': {
        autoform: {
            afFieldInput: {
                options: Schemas.helpers.allSkillsOptions
            }
        }
    },
    availabilities: {
        type: [Schemas.UserAvailabilities],
        label: "User availabilities",
        optional: true,
        defaultValue: []
    },
    assignments: {
        type: [Schemas.UserAssignment],
        label: "User assignments",
        defaultValue: [],
        optional: true,
        autoform: {
            type: "hidden",
        }
    }
});<commit_msg>[RPI] feat (user) : user's team cannot be removed if user has at least one assignment that neeeded the deleted team<commit_after>import {Schemas} from './SchemasHelpers'
import {TimeSlotService} from "../../../both/service/TimeSlotService"
import {PeopleNeedService} from "../../../both/service/PeopleNeedService"

//order matters !
Schemas.UserAvailabilities = new SimpleSchema({
    start: {
        type: Date,
        label: "User Availabilities Start Date",
        custom: function () {
            if (new moment(this.value).isAfter(new moment(this.field(this.key.replace("start", "") + "end").value)))
                return "startAfterEnd";

            var userTeams = Users.findOne(this.docId).teams;
            var start = this.value;

            if (!AssignmentTerms.findOne({
                    teams: {
                        $elemMatch: {
                            $in: userTeams
                        }
                    },
                        start: {
                            $lte: start
                        }
                })
            )
                return "availabilitiesNoInTerm"

        },
        autoform: {
            type: "datetime-local",
        }
    },
    end: {
        type: Date,
        label: "User Availabilities End Date",
        custom: function () {
            if (new moment(this.value).isSame(new moment(this.field(this.key.replace("end", "") + "start").value)))
                return "endBeforeStart"

            var userTeams = Users.findOne(this.docId).teams;
            var end = this.value;

            if (!AssignmentTerms.findOne({
                    teams: {
                        $elemMatch: {
                            $in: userTeams
                        }
                    },
                    end: {
                        $gte: end
                    }
                })
            )
                return "availabilitiesNoInTerm"
        },
        autoform: {
            type: "datetime-local",
        }
    }
});

Schemas.UserAssignment = new SimpleSchema({
    taskName: {
        type: String,
        label: "User assignment User Name"
    },
    start: {
        type: Date,
        label: "User Assignment Start Date"
    },
    end: {
        type: Date,
        label: "User Assignment End Date"
    },
    assignmentId: {
        type: SimpleSchema.RegEx.Id,
        label: "User assignment assignment id",
        custom: function () { //validate data is same as the real assignment
            var assignment = Assignments.findOne(this.value);
            if (!assignment)
                return "unknownId"
            var timeSlot = TimeSlotService.getTimeSlot(assignment.taskId,assignment.timeSlotId);
            if (Tasks.findOne(assignment.taskId).name !== this.field(this.key.replace("assignmentId", "") + "taskName").value
                || !new moment(timeSlot.start).isSame(new moment(this.field(this.key.replace("assignmentId", "") + "start").value))
                || !new moment(timeSlot.end).isSame(new moment(this.field(this.key.replace("assignmentId", "") + "end").value)))
                return "userAssignmentNotMatching"
        }
    }
});

Schemas.Users = new SimpleSchema({
    name: {
        type: String,
        label: "Users Name",
        max: 100
    },
    firstName: {
        type: String,
        label: "User first name",
        max: 100,
        optional: true,
        defaultValue: null
    },
    familyName: {
        type: String,
        label: "User family name",
        max: 100,
        optional: true,
        defaultValue: null
    },
    phoneNumber:{
        type: SimpleSchema.RegEx.Phone,
        label: "User phone",
        optional: true,
        defaultValue: null
    },
    birthDate: {
        type: Date,
        label: "User birth date",
        optional: true,
        defaultValue: null
    },
    loginUserId: {
        label: "User login link to collection managed by Account package",
        type: SimpleSchema.RegEx.Id,
        optional: true
    },
    isReadyForAssignment: {
        label: "User assignment ready state",
        type: Boolean,
        defaultValue: false,
        custom: function () {
            if(this.isUpdate)
            if(this.value === false){
                if(Users.findOne(this.docId).assignments && Users.findOne(this.docId).assignments.length !== 0){
                    return "userHasAssignments"
                }
            }
        }
    },
    groupRoles: {
        label: "User roles to gain a set of less or more data and features",
        type: [SimpleSchema.RegEx.Id],
        optional: true,
        custom: function () {
            this.value = _.compact(this.value);
            if(GroupRoles.find({_id:{$in:this.value}}).fetch().length !== this.value.length)
                return "unknownIdOrDuplicateId"
        }
    },
    'groupRoles.$': {
        autoform: {
            afFieldInput: {
                options: Schemas.helpers.allGroupRolesOptions
            }
        }
    },
    teams: {
        label: "User teams",
        type: [SimpleSchema.RegEx.Id],
        optional: true,
        custom: function () {
            this.value = _.compact(this.value);
            if(Teams.find({_id:{$in:this.value}}).fetch().length !== this.value.length)
                return "unknownIdOrDuplicateId"
        },
        autoValue: function () {
            if (this.isInsert) {
                //trick pour les filtres, tous les users appartiennement au moins à l'équipe ASSIGNMENTREADYTEAM
                var assignmentReadyTeam = Teams.findOne({name: ASSIGNMENTREADYTEAM});
                if(!this.value)
                    this.value = [];
                else if (!_.contains(this.value,assignmentReadyTeam._id)) //we don't add it we it already have it (when autoform do the check several times)
                    this.value.push(assignmentReadyTeam._id);
                return this.value;
            }

            if(this.isUpdate){
                if(this.field("teams").isSet){
                    if(this.field("teams").operator === "$pull"){
                        var teamRemoved = this.field("teams").value;

                        //check if the user has one assignment that needed the deleted team
                        var userAssignments = Assignments.find({
                            userId:this.docId
                        }).fetch();

                        userAssignments.forEach(userAssignment => {
                            var peopleNeedAssigned = PeopleNeedService.getPeopleNeedByIdAndTask(userAssignment.peopleNeedId,Tasks.findOne(userAssignment.taskId)).peopleNeed;

                            if(peopleNeedAssigned.teamId === teamRemoved){
                                if(Meteor.isClient)
                                    sAlert.error(`You can not remove this team, user has already assignments that needed the removed team.
                                You can try to add remove user's assignment that need the deleted team`,{timeout:7000});

                                this.unset();
                                throw new Meteor.Error("403", `Forbidden, user has already assignments that needed the removed team.`);
                            }
                        });

                        //check if the user has one availability that are within the deleted team's assignment term
                        var user = Users.findOne(this.docId);
                        console.log(teamRemoved, "has been removed from",user);

                        var userTeams = user.teams;
                        userTeams.splice(userTeams.indexOf(teamRemoved),1);

                        //assignmentTerms that the user will actually lose (because he doesn't have another team covering it)
                        var assignmentTerms = AssignmentTerms.find({
                            $and : [
                                {
                                    teams: teamRemoved
                                },
                                {
                                    teams: {
                                        $not: {$in: userTeams}
                                    }
                                }
                            ]
                        }).fetch();

                        //all periods that the user will lose
                        var allPeriods = [];
                        assignmentTerms.forEach(assignmentTerm => {
                            if(assignmentTerm.assignmentTermPeriods.length !== 0){
                                allPeriods.concat(assignmentTerm.assignmentTermPeriods);
                            } else {
                                allPeriods.push({
                                    start: assignmentTerm.start,
                                    end: assignmentTerm.end
                                })
                            }
                        });

                        //check if user has one availability included in one of the periods he will lose
                        var availabilities = user.availabilities;
                        user.availabilities.forEach(availability => {
                            allPeriods.forEach(period => {
                                if(TimeSlotService.isOverlapping(availability.start,availability.end,period.start,period.end)){
                                    //includeInAPeriod = true;
                                    if(Meteor.isClient)
                                        sAlert.error(`You can not remove this team, user has already add availabilities in one a the team assignment term.
                                You can try to add another team whose assignment terms cover the existing user's availabilities.`,{timeout:7000});

                                    this.unset();
                                    throw new Meteor.Error("403", `Forbidden, user still have availability in at least one term that he will lose if team is removed`);
                                }
                            });
                        });
                    }
                }

            }
        },
    },
    'teams.$': {
        autoform: {
            afFieldInput: {
                options: Schemas.helpers.allTeamsOptions
            }
        }
    },
    skills: {
        label: "User Skills",
        type: [SimpleSchema.RegEx.Id],
        optional: true,
        defaultValue: [],
        custom: function () {
            this.value = _.compact(this.value);
            if(Skills.find({_id:{$in:this.value}}).fetch().length !== this.value.length)
                return "unknownIdOrDuplicateId"

            if(this.isUpdate && Users.findOne(this.docId).isReadyForAssignment && Users.findOne(this.docId).isReadyForAssignment === true)
                return "userHasBeenValidatedNoSkillsUpdate"
        }
    },
    'skills.$': {
        autoform: {
            afFieldInput: {
                options: Schemas.helpers.allSkillsOptions
            }
        }
    },
    availabilities: {
        type: [Schemas.UserAvailabilities],
        label: "User availabilities",
        optional: true,
        defaultValue: []
    },
    assignments: {
        type: [Schemas.UserAssignment],
        label: "User assignments",
        defaultValue: [],
        optional: true,
        autoform: {
            type: "hidden",
        }
    }
});<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';

import Icon from '../ui/Icon';

import styles from './Breadcrumb.css';

import * as ProjectActions from './ProjectActions';
import * as ContentActions from './content/ContentActions';

const {dialog} = require('electron').remote;

export class Breadcrumb extends Component {
  handleOpenProject() {
    const openProject = this.props.openProject.bind(this);
    dialog.showOpenDialog({
      title: "Select a project file",
      filters: [{name: "JSON", extensions: ['json']}],
      properties: ['openFile'],
    },
    (filenames) => {
      if (filenames) {
        openProject(filenames[0]);
      }
    }
    );
  }

  handleNewContentClick(e) {
    this.props.createContent(this.props.project);
  }

  handleClearProject(e) {
    this.props.clearProject();
  }

  handleClearContentType(e) {
    this.props.clearContentType();
  }

  handleClearContent(e) {
    this.props.clearContent();
  }

  render() {
    const {
      project,
      projectList,
      contentType,
      content
    } = this.props;

    const title = [];
    const single = contentType && !contentType.settings.plural;

    const items = [(

        Projects

    )];

    function add(node, type = styles.link) {
      let cn = styles.item + ' ' + type;
      items.push(

          {node}

      );
    }

    if (project && project.title) {
      title.push(project.title);

      if (project.contentTypes.length !== 1) {
        add(
          {project.title}
        );
      }

      if (contentType) {
        title.push(single ? contentType.settings.title : contentType.settings.plural);
        add(

            {single ? contentType.settings.title : contentType.settings.plural}

        );

        if (content) {
          title.push(content.title);
          add(
            {content.title}
          );
        } else if (!single) {
          add(
            <span onClick={this.handleNewContentClick.bind(this)} className={styles.btn} title={`Add a new ${contentType.settings.title}`}>
              <Icon className={styles.icon} name="add"/>

          );
        }
      }
    } else {
        title.push("Pallium");
        add(

            <Icon className={styles.icon} name="folder_open"/>

        );
    }

    document.title = title.join(' / ');

    return (

        {items}

    );
  }

}

function mapStateToProps(state) {
  const project = state.project;
  const contentType = project && project.contentType;
  const content = contentType && contentType.content;
  const contentFilename = content && content.filename;
  return {
    projectList: state.projectList,
    project,
    contentType,
    content,
    contentFilename,
  };
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators({
    openProject: ProjectActions.open,
    clearProject: ProjectActions.clear,
    clearContentType: ContentActions.clearContentType,
    clearContent: ContentActions.clearContent,
    createContent: ContentActions.createContent,
  }, dispatch);
}

export default connect(mapStateToProps, mapDispatchToProps)(Breadcrumb);
<commit_msg>Added open shortcut<commit_after>import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';

import Icon from '../ui/Icon';
import Shortcuts from '../util/Shortcuts';

import styles from './Breadcrumb.css';

import * as ProjectActions from './ProjectActions';
import * as ContentActions from './content/ContentActions';

const {dialog} = require('electron').remote;

export class Breadcrumb extends Component {
  constructor(props) {
    super(props);
    this.shortcuts = new Shortcuts();
    this.shortcuts.register('ctrl o', this.handleOpenProject.bind(this));
  }

  componentDidMount() {
    this.shortcuts.start();
  }

  componentWillUnmount() {
    this.shortcuts.stop();
  }

  handleOpenProject() {
    const openProject = this.props.openProject.bind(this);
    dialog.showOpenDialog({
      title: "Select a project file",
      filters: [{name: "JSON", extensions: ['json']}],
      properties: ['openFile'],
    },
    (filenames) => {
      if (filenames) {
        openProject(filenames[0]);
      }
    });
  }

  handleNewContentClick(e) {
    this.props.createContent(this.props.project);
  }

  handleClearProject(e) {
    this.props.clearProject();
  }

  handleClearContentType(e) {
    this.props.clearContentType();
  }

  handleClearContent(e) {
    this.props.clearContent();
  }

  render() {
    const {
      project,
      projectList,
      contentType,
      content
    } = this.props;

    const title = [];
    const single = contentType && !contentType.settings.plural;

    const items = [(

        Projects

    )];

    function add(node, type = styles.link) {
      let cn = styles.item + ' ' + type;
      items.push(

          {node}

      );
    }

    if (project && project.title) {
      title.push(project.title);

      if (project.contentTypes.length !== 1) {
        add(
          {project.title}
        );
      }

      if (contentType) {
        title.push(single ? contentType.settings.title : contentType.settings.plural);
        add(

            {single ? contentType.settings.title : contentType.settings.plural}

        );

        if (content) {
          title.push(content.title);
          add(
            {content.title}
          );
        } else if (!single) {
          add(
            <span onClick={this.handleNewContentClick.bind(this)} className={styles.btn} title={`Add a new ${contentType.settings.title}`}>
              <Icon className={styles.icon} name="add"/>

          );
        }
      }
    } else {
        title.push("Pallium");
        add(

            <Icon className={styles.icon} name="folder_open"/>

        );
    }

    document.title = title.join(' / ');

    return (

        {items}

    );
  }

}

function mapStateToProps(state) {
  const project = state.project;
  const contentType = project && project.contentType;
  const content = contentType && contentType.content;
  const contentFilename = content && content.filename;
  return {
    projectList: state.projectList,
    project,
    contentType,
    content,
    contentFilename,
  };
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators({
    openProject: ProjectActions.open,
    clearProject: ProjectActions.clear,
    clearContentType: ContentActions.clearContentType,
    clearContent: ContentActions.clearContent,
    createContent: ContentActions.createContent,
  }, dispatch);
}

export default connect(mapStateToProps, mapDispatchToProps)(Breadcrumb);
<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');

module.exports = function (root, opts, pluginOpts) {
    var params = {};
    var cwd = process.cwd();

    if (!opts.src) {
        params.src = root.toString();
    } else {
        var src = path.resolve(cwd, opts.src);
        params.src = fs.readFileSync(src, 'utf8');
    }

    if (opts.dest) {
        params.dest = path.resolve(cwd, opts.dest);
    } else {
        var from = (pluginOpts || {}).from; // for the gulp and grunt
        var output = from ? path.basename(from, '.css') : 'index.html'
        params.dest = path.resolve(cwd, 'styleguide', output);
    }

    params.project = opts.project || 'Style Guide';
    if (opts.showCode === undefined) {
        params.showCode = true;
    } else {
        params.showCode = false;
    }

    var theme;
    if (opts.theme) {
        theme = 'psg-theme-' + opts.theme;
    } else {
        theme = 'psg-theme-default';
    }

    var themePath;
    if (opts.themePath) {
      themePath = opts.themePath;
    } else {
      var isFind = module.paths.some(function (m) {
          var p = path.resolve(m, theme);
          if (!isExists(p)) {
              return false;
          }
          themePath = p;
          return true;
      });
      if (!isFind) {
          throw new Error('specify theme is not found');
      }
    }

    try {
      var templateFile = path.resolve(themePath, 'template.ejs');
      params.template = fs.readFileSync(templateFile, 'utf-8');
    } catch (err) {
      throw err;
    }
    try {
      var templateStyle = path.resolve(themePath, 'style.css');
      params.style = fs.readFileSync(templateStyle, 'utf-8');
    } catch (err) {
      throw err;
    }
    return params;
}

function isExists(dirPath) {
    try {
        fs.statSync(dirPath);
    } catch (err) {
        return false;
    }
    return true;
}
<commit_msg>Fix showCode option<commit_after>var fs = require('fs');
var path = require('path');

module.exports = function (root, opts, pluginOpts) {
    var params = {};
    var cwd = process.cwd();

    if (!opts.src) {
        params.src = root.toString();
    } else {
        var src = path.resolve(cwd, opts.src);
        params.src = fs.readFileSync(src, 'utf8');
    }

    if (opts.dest) {
        params.dest = path.resolve(cwd, opts.dest);
    } else {
        var from = (pluginOpts || {}).from; // for the gulp and grunt
        var output = from ? path.basename(from, '.css') : 'index.html'
        params.dest = path.resolve(cwd, 'styleguide', output);
    }

    params.project = opts.project || 'Style Guide';
    if (opts.showCode === undefined || opts.showCode == true) {
        params.showCode = true;
    } else {
        params.showCode = false;
    }

    var theme;
    if (opts.theme) {
        theme = 'psg-theme-' + opts.theme;
    } else {
        theme = 'psg-theme-default';
    }

    var themePath;
    if (opts.themePath) {
      themePath = opts.themePath;
    } else {
      var isFind = module.paths.some(function (m) {
          var p = path.resolve(m, theme);
          if (!isExists(p)) {
              return false;
          }
          themePath = p;
          return true;
      });
      if (!isFind) {
          throw new Error('specify theme is not found');
      }
    }

    try {
      var templateFile = path.resolve(themePath, 'template.ejs');
      params.template = fs.readFileSync(templateFile, 'utf-8');
    } catch (err) {
      throw err;
    }
    try {
      var templateStyle = path.resolve(themePath, 'style.css');
      params.style = fs.readFileSync(templateStyle, 'utf-8');
    } catch (err) {
      throw err;
    }
    return params;
}

function isExists(dirPath) {
    try {
        fs.statSync(dirPath);
    } catch (err) {
        return false;
    }
    return true;
}
<|endoftext|>


<commit_before>"use strict";

var Coffee = {
  debug: 0,
  debugString: "200\n1. March 14:28:371",

  msgNoPots: 'Ingen kanner i dag',
  msgNoCoffee: 'Kaffen har ikke blitt satt på',
  msgFormatError: 'Feil i kaffeformat',
  msgConnectionError: 'Frakoblet fra kaffekanna',
  msgComforting: 'Så så, det er sikkert kaffe :)',
  msgNotification: 'Kaffen er satt på, straks klar :)',

  get: function(pretty, callback) {
    if (callback == undefined) {
      console.log('ERROR: Callback is required. In the callback you should insert the results into the DOM.');
      return;
    }

    var api = Affiliation.org[localStorage.affiliationKey1].hw.apis.coffee;

    // Receives the status for the coffee pot
    var self = this;
    Ajaxer.getPlainText({
      url: api,
      success: function(data) {

        // If coffee debugging is enabled
        if (self.debug) {
          data = self.debugString;
        }

        try {
          // Split into pot number and age of last pot
          var pieces = data.split("\n");
          var pots = Number(pieces[0]);
          var ageString = pieces[1];

          // Coffee made today?
          if (self.isMadeToday(ageString)) {

            // Calculate minute difference from right now
            var now = new Date();
            var coffeeTime = String(ageString.match(/\d+:\d+:\d+/)).split(':');
            var then = new Date(now.getFullYear(), now.getMonth(), now.getDate(), coffeeTime[0], coffeeTime[1]);
            var age = self.minuteDiff(then);

            // If pretty strings are requested
            if (pretty) {
              age = self.prettyAgeString(age, coffeeTime);
              pots = self.prettyPotsString(pots);
            }

            // Call it back
            callback(pots, age);
          }
          else {
            // Coffee was not made today
            if (pretty) {
              callback(self.msgNoPots, self.msgNoCoffee);
            }
            else {
              callback(0, 0);
            }
          }
        } catch (err) {
          if (self.debug) console.log('ERROR: Coffee format is wrong:', err);
          callback(self.msgFormatError, self.msgComforting);
        }
      },
      error: function(jqXHR, text, err) {
        if (self.debug) console.log('ERROR: Failed to get coffee pot status.');
        callback(self.msgConnectionError, self.msgComforting);
      },
    });
  },

  isMadeToday: function(ageString) {
    // Get now
    var now = new Date();
    // Figure out which date the coffee was made
    var dateObject = ageString.match(/\d+\. [a-zA-Z]+/);
    var dateString = String(dateObject);
    dateString = dateString.replace('.', ''); // Remove period
    dateString = dateString + ', ' + now.getFullYear();
    var coffeeDate = new Date(dateString);
    // Check if the coffee pots were made today
    return coffeeDate.getDate() == now.getDate();
  },

  minuteDiff: function(then) {
    // Get now
    var now = new Date();
    var one_minute = 1000 * 60;
    // Calculate difference between the two dates, and convert to minutes
    return Math.floor(( now.getTime() - then.getTime() ) / one_minute);
  },

  prettyAgeString: function(diff, coffeeTime) {
    // Create a proper time string from all the minutes
    if (0 <= diff && diff <= 9)
      return 'Kaffen ble nettopp laget';
    else if (10 <= diff && diff <= 59)
      return 'Kaffen ble laget for '+diff+' min siden';
    else if (60 <= diff)
      return 'Kaffen ble laget kl '+coffeeTime[0]+':'+coffeeTime[1];
    else
      // time is negative, computer is likely in a timezone between GMT -12 and +1
      return 'God reise! Håper de har kaffe! :)';
  },

  prettyPotsString: function(pots) {
    return (pots=='0'?'Ingen kanner':pots=='1'?'1 kanne':pots+' kanner') + ' i dag';
  },

  showNotification: function(pots, age) { // Parameters 'pots' and 'age' not in use yet.
    var demo = (typeof pots == 'undefined' && typeof age == 'undefined');
    // If the computer has slept for a while and there are
    // suddenly four new coffeepots then they will all be
    // lined up for notifications, giving the user four
    // notifications at once. This is prevented here by not
    // allowing two consecutive notifications within 4 minutes
    // of each other.
    var showIt = true;
    try {
      var then = JSON.parse(localStorage.lastSubscriptionTime);
      if (this.minuteDiff(then) <= 4) {
        showIt = false;
      }
    }
    catch (err) {
      if (this.debug) console.log('ERROR: failed to calculate coffee subscription time difference');
    }
    if (showIt || demo) {

      // Save timestamp if this was a real coffee notification
      if (!demo)
        localStorage.lastSubscriptionTime = JSON.stringify(new Date());

      var key = localStorage.affiliationKey1;
      var memes = [];

      // Add regular memes
      var amount = MEME_AMOUNT; // Number of memes, in regular human numbers, not zero-indexed
      for (var i = 1; i <= amount; i++) {
        memes.push('./meme/'+i+'.jpg');
      }

      // Add affiliation memes
      if (Affiliation.org[key].hw.memePath) {
        var amount = Affiliation.getMemeCount(key);
        var path = Affiliation.org[key].hw.memePath;
        for (var i = 1; i <= amount; i++) {
          memes.push(path+i+'.jpg');
        }
      }

      // Randomize image
      var random = 1 + (Math.floor(Math.random() * memes.length));
      if (this.debug) console.log('memes['+(random-1)+'] of '+0+'-'+(memes.length-1)+' is "'+memes[random-1]+'"');
      var image = memes[random - 1]; // the list is zero-indexed

      // Create the notification
      var item = {
        title: Affiliation.org[key].name + ' Notifier',
        description: this.msgNotification,
        image: image,
        link: 'options.html',
        feedKey: key,
      }
      if (!demo)
        Browser.createNotification(item);
      else
        // Need to run it by the background process because the event listeners are there
        Browser.getBackgroundProcess().Browser.createNotification(item);
    }
    else {
      if (this.debug) console.log('ERROR: coffee notification displayed less than four minutes ago');
    }
  },

}
<commit_msg>coffee: minor refactoring<commit_after>"use strict";

var Coffee = {
  debug: 0,
  debugString: "200\n1. March 14:28:371",

  msgNoPots: 'Ingen kanner i dag',
  msgNoCoffee: 'Kaffen har ikke blitt satt på',
  msgFormatError: 'Feil i kaffeformat',
  msgConnectionError: 'Frakoblet fra kaffekanna',
  msgComforting: 'Så så, det er sikkert kaffe :)',
  msgNotification: 'Kaffen er satt på, straks klar :)',

  get: function(pretty, callback) {
    if (callback == undefined) {
      console.log('ERROR: Callback is required. In the callback you should insert the results into the DOM.');
      return;
    }

    var api = Affiliation.org[localStorage.affiliationKey1].hw.apis.coffee;

    // Receives the status for the coffee pot
    var self = this;
    Ajaxer.getPlainText({
      url: api,
      success: function(data) {

        // If coffee debugging is enabled
        if (self.debug) {
          data = self.debugString;
        }

        try {
          // Split into pot number and age of last pot
          var pieces = data.split("\n");
          var pots = Number(pieces[0]);
          var ageString = pieces[1];

          // Coffee made today?
          if (self.isMadeToday(ageString)) {

            // Calculate minute difference from right now
            var now = new Date();
            var coffeeTime = String(ageString.match(/\d+:\d+:\d+/)).split(':');
            var then = new Date(now.getFullYear(), now.getMonth(), now.getDate(), coffeeTime[0], coffeeTime[1]);
            var age = self.minuteDiff(then);

            // If pretty strings are requested
            if (pretty) {
              age = self.prettyAgeString(age, coffeeTime);
              pots = self.prettyPotsString(pots);
            }

            // Call it back
            callback(pots, age);
          }
          else {
            // Coffee was not made today
            if (pretty) {
              callback(self.msgNoPots, self.msgNoCoffee);
            }
            else {
              callback(0, 0);
            }
          }
        } catch (err) {
          if (self.debug) console.log('ERROR: Coffee format is wrong:', err);
          callback(self.msgFormatError, self.msgComforting);
        }
      },
      error: function(jqXHR, text, err) {
        if (self.debug) console.log('ERROR: Failed to get coffee pot status.');
        callback(self.msgConnectionError, self.msgComforting);
      },
    });
  },

  isMadeToday: function(ageString) {
    // Get now
    var now = new Date();
    // Figure out which date the coffee was made
    var dateObject = ageString.match(/\d+\. [a-zA-Z]+/);
    var dateString = String(dateObject);
    dateString = dateString.replace('.', ''); // Remove period
    dateString = dateString + ', ' + now.getFullYear();
    var coffeeDate = new Date(dateString);
    // Check if the coffee pots were made today
    return coffeeDate.getDate() == now.getDate();
  },

  minuteDiff: function(then) {
    // Get now
    var now = new Date();
    var one_minute = 1000 * 60;
    // Calculate difference between the two dates, and convert to minutes
    return Math.floor(( now.getTime() - then.getTime() ) / one_minute);
  },

  prettyAgeString: function(diff, coffeeTime) {
    // Create a proper time string from all the minutes
    if (0 <= diff && diff <= 9)
      return 'Kaffen ble nettopp laget';
    else if (10 <= diff && diff <= 59)
      return 'Kaffen ble laget for '+diff+' min siden';
    else if (60 <= diff)
      return 'Kaffen ble laget kl '+coffeeTime[0]+':'+coffeeTime[1];
    else
      // time is negative, computer is likely in a timezone between GMT -12 and +1
      return 'God reise! Håper de har kaffe! :)';
  },

  prettyPotsString: function(pots) {
    return (pots=='0'?'Ingen kanner':pots=='1'?'1 kanne':pots+' kanner') + ' i dag';
  },

  showNotification: function(pots, age) { // Parameters 'pots' and 'age' not in use yet.
    var demo = (typeof pots == 'undefined' && typeof age == 'undefined');
    // If the computer has slept for a while and there are
    // suddenly four new coffeepots then they will all be
    // lined up for notifications, giving the user four
    // notifications at once. This is prevented here by not
    // allowing two consecutive notifications within 4 minutes
    // of each other.
    var showIt = true;
    try {
      var then = JSON.parse(localStorage.lastSubscriptionTime);
      if (this.minuteDiff(then) < 5) {
        showIt = false;
      }
    }
    catch (err) {
      if (this.debug) console.log('ERROR: failed to calculate coffee subscription time difference');
    }

    if (showIt || demo) {

      // Save timestamp if this was a real coffee notification
      if (!demo)
        localStorage.lastSubscriptionTime = JSON.stringify(new Date());

      var key = localStorage.affiliationKey1;
      var memes = [];

      // Add regular memes
      var amount = MEME_AMOUNT; // Number of memes, in regular human numbers, not zero-indexed
      for (var i = 1; i <= amount; i++) {
        memes.push('./meme/'+i+'.jpg');
      }

      // Add affiliation memes
      if (Affiliation.org[key].hw.memePath) {
        var amount = Affiliation.getMemeCount(key);
        var path = Affiliation.org[key].hw.memePath;
        for (var i = 1; i <= amount; i++) {
          memes.push(path+i+'.jpg');
        }
      }

      // Randomize image
      var random = 1 + (Math.floor(Math.random() * memes.length));
      if (this.debug) console.log('memes['+(random-1)+'] of '+0+'-'+(memes.length-1)+' is "'+memes[random-1]+'"');
      var image = memes[random - 1]; // the list is zero-indexed

      // Create the notification
      var item = {
        title: Affiliation.org[key].name + ' Notifier',
        description: this.msgNotification,
        image: image,
        link: 'options.html',
        feedKey: key,
      }
      if (!demo) {
        Browser.createNotification(item);
      }
      else {
        // Need to run it by the background process because the event listeners are there
        Browser.getBackgroundProcess().Browser.createNotification(item);
      }
    }
    else {
      if (this.debug) console.log('ERROR: coffee notification displayed less than four minutes ago');
    }
  },

}
<|endoftext|>


<commit_before>//This is the shop. Pretty self explanatory :P
'use strict';

const fs = require('fs');
const request = require('request');

let shopList = {
	//Adding true as the last element of an array over here notifies admins when that item has been bought. This is useful for items like trainer cards that
	//require an admin's assistance
	potd: ['POTD', 'Buys the ability to set the Pokémon of the Day. Not purchasable if there is already a POTD.', 2],
	poof: ['Poof', 'Buy a poof message to be added into the pool of possible poofs.', 10, true],
	avatar: ['Avatar', 'Set your own custom avatar.', 35, true],
	card: ['Card', 'Buys a trainer card which can show information through a command.', 50, true],
	song: ['Song', 'Buys a song that can be played on a declarable card. (You must supply the song as a RAW MP3/OGG URL) <button name="send", value="/feelingit">Example</button>', 25, true],
	customize: ['Customise', 'This allows you to stylise your Trainer Card with HTML5 Elements such as Audio/Mouse Cursor/Background Image/etc.', 15, true],
	fix: ['Fix', 'Buys the ability to edit your custom avatar or trainer card.', 10, true],
	room: ['Room', 'Buys a chatroom for you to own (within reasons, can be refused).', 100, true],
	rng: ['RNG', 'Buys Floatzel\'s and Tempest\'s RNG/Cloning Services for Pokemon X&Y/ORAS. <button name ="send", value="/Powersaves">More Info</button>', 2]
};

function getShop () {
	let status = (!global.shopclosed) ? 'Shop status: Open
To buy an item, type in /buy [item] in the chat, or simply click on one of the buttons.' : 'Shop status: Closed';
	let text = '
Sora\'s Shop

Item	Description	Price
';

	for (let i in shopList) {
		text = text + '
' + shopList[i][0] + '	' + shopList[i][1] + '	' + shopList[i][2] + '	<button name = "send", value="/buy ' + i + '">Buy!</button>
';
	}
	text = text + '

' + status + '';
	return text; //escaping quotation marks
}

function addLog(message) {
	if (!global.moneyLog) global.moneyLog = '';
	let d = new Date();
	global.moneyLog += '[' + d.format('{yyyy}-{MM}-{dd} {hh}:{mm}:{ss} {tt}') + '] ';
	global.moneyLog += message + '
';
}

exports.commands = {

	getbucks: function(target, room, user) {
		if (!this.canBroadcast()) return;
		this.sendReplyBox('Please check out the Shop page in the link below to see methods of earning money:
' +
			'- Shop

');
	},

	buckslog: 'moneylog',
	moneylog: function(target, room, user) {
		if (!this.can('lock')) return false;
		this.popupReply('|html|Transaction Log (Time Zone:)
' + global.moneyLog);
	},

	atm: 'wallet',
	money: 'wallet',
	cash: 'wallet',
	bucks: 'wallet',
	purse: 'wallet',
	wallet: function(target, room, user) {
		if (!this.canBroadcast()) return;
		if (!toId(target)) target = user.name;
		else target = Users.getExact(target) ? Users.getExact(target).name : target;
		let money = Number(Core.read('money', toId(target))) || 'no';
		this.sendReplyBox(target + ' has ' + money + ' buck' + (money === 1 ? '' : 's') + '.');
	},

	shop: function(target, room, user) {
		if (!this.canBroadcast()) return;
		let shop = getShop();
		if (this.broadcasting) {
			return this.sendReply('|uhtml|shop|
Click <button name = "receive" value = "|uhtmlchange|shop|
' + shop.replace(/"/g, '"') + '
">here</button> to enter our shop!
');
		}
		this.sendReplyBox(shop);
	},

	toggleshop: 'adjustshop',
	adjustshop: function(target, room, user) {
		if (!this.can('hotpatch')) return false;
		global.shopclosed = !global.shopclosed;
		addLog(user.name + ' ' + (global.shopclosed ? 'closed' : 'opened') + ' the shop.');
		this.sendReply('The shop is now ' + (global.shopclosed ? 'closed' : 'open') + '.');
	},

	givebucks: 'award',
	givebucks: 'award',
	gb: 'award',
	award: function(target, room, user, connection, cmd) {
		if (!this.can('hotpatch')) return false;
		if (!target) return this.sendReply('The correct syntax is /' + cmd + ' [user], [amount]');
		target = target.split(',');
		if (target.length < 2) return this.sendReply('/' + cmd + ' [user], [amount] - Gives a user the specified number of bucks.')
		let targetUser = Users.getExact(target[0]) ? Users.getExact(target[0]).name : target[0];
		let amt = Number(toId(target[1])) || target[1];
		if (!amt) return this.sendReply('You need to mention the number of bucks you want to give ' + targetUser);
		if (isNaN(amt)) return this.sendReply(amt + " is not a valid number.");
		if (amt < 1) return this.sendReply('You cannot give ' + targetUser + ' anything less than 1 buck!');
		if (~String(amt).indexOf('.')) return this.sendReply('You cannot give ' + targetUser + ' fractions of bucks.');

		Core.write('money', toId(targetUser), amt, '+');
		let giveFormat = (amt == 1) ? 'buck' : 'bucks';
		let hasFormat = (Core.read('money', toId(targetUser)) === 1) ? 'buck' : 'bucks';
		if (Users.getExact(targetUser)) Users.getExact(targetUser).send('|popup|' + user.name + ' has given you ' + amt + ' ' + giveFormat + '. You now have ' + Core.read('money', toId(targetUser)) + ' ' + hasFormat + '.');
		addLog(user.name + ' has given ' + targetUser + ' ' + amt + ' ' + giveFormat + '. This user now has ' + Core.read('money', toId(targetUser)) + ' ' + hasFormat + '.');
		return this.sendReply(targetUser + ' was given ' + amt + ' ' + giveFormat + '. This user now has ' + Core.read('money', toId(targetUser)) + ' ' + hasFormat + '.');
	},

	removebucks: 'remove',
	rb: 'remove',
	tb: 'remove',
	takebucks: 'remove',
	take: 'remove',
	remove: function(target, room, user, connection, cmd) {
		if (!this.can('hotpatch')) return false;
		if (!target) return this.sendReply('/' + cmd + ' [user], [amount] - Removes the specified number of bucks from a user.');
		target = target.split(',');
		if (target.length < 2) return this.sendReply('/' + cmd + ' [user], [amount] - Removes the specified number of bucks from a user.')
		let targetUser = Users.getExact(target[0]) ? Users.getExact(target[0]).name : target[0];
		let amt = Number(toId(target[1])) || target[1];
		if (!amt) return this.sendReply('You need to mention the number of bucks you want to remove from ' + targetUser + '.');
		if (isNaN(amt)) return this.sendReply(amt + " is not a valid number.");
		if (amt < 1) return this.sendReply('You cannot take away anything less than 1 buck!');
		if (~String(amt).indexOf('.')) return this.sendReply('You cannot take away fractions.');
		if (Core.read('money', toId(targetUser)) < amt) return this.sendReply('You can\'t take away more than what ' + targetUser + ' already has!');

		Core.write('money', toId(targetUser), amt, '-');
		let takeFormat = (amt === 1) ? 'buck' : 'bucks';
		let hasFormat = (Core.read('money', toId(targetUser)) === 1) ? 'buck' : 'bucks';
		if (Users.getExact(targetUser)) Users.getExact(targetUser).send('|popup|' + user.name + ' has taken away ' + amt + ' ' + takeFormat + ' from you. You now have ' + Core.read('money', toId(targetUser)) + ' ' + hasFormat + ' left.');
		addLog(user.name + ' has taken away ' + amt + ' ' + takeFormat + ' from ' + targetUser + '. This user now has ' + Core.read('money', toId(targetUser)) + ' ' + hasFormat + ' left.');
		return this.sendReply('You have taken away ' + amt + ' ' + takeFormat + ' from ' + targetUser + '. This user now has ' + Core.read('money', toId(targetUser)) + ' ' + hasFormat + ' left.');
	},

	transfermoney: 'transferbucks',
	transferbucks: function(target, room, user, connection, cmd) {
		if (!this.canBroadcast()) return false;
		if (!target) return this.parse('/help transferbucks');
		target = this.splitTarget(target, true);
		let targetUser = this.targetUsername;
		targetUser = Users.getExact(targetUser) ? Users.getExact(targetUser).name : targetUser;
		if (!targetUser || !targetUser.trim()) return this.parse('/help transferbucks');
		if (!toId(targetUser)) return this.sendReply('"' + targetUser + '" is not a valid username.')
		if (toId(targetUser) === user.userid) return this.sendReply('You can\'t transfer bucks to yourself!');
		if (!toId(target)) return this.sendReply('You need to specify the number of bucks you want to transfer to ' + targetUser);
		if (isNaN(target)) return this.sendReply(target + " isn't a valid number.");
		if (Core.read('money', user.userid) < target) return this.sendReply('You can\'t give ' + targetUser + ' more than what you have!');

		Core.write('money', toId(targetUser), Number(target), '+');
		Core.write('money', user.userid, Number(target), '-');
		let amt = (Core.read('money', toId(targetUser)) == 1) ? 'buck' : 'bucks';
		let userAmt = (Core.read('money', user.userid) == 1) ? 'buck' : 'bucks';
		let bucks = (target == 1) ? 'buck' : 'bucks';
		if (Users.getExact(targetUser)) Users.getExact(targetUser).send('|popup|' + user.name + ' has transferred ' + target + ' ' + bucks + ' to you. You now have ' + Core.read('money', toId(targetUser)) + ' ' + amt + '.');
		addLog(user.name + ' has transferred ' + target + ' ' + bucks + ' to ' + targetUser + '. This user now has ' + Core.read('money', toId(targetUser)) + ' ' + amt + '. ' + user.name + ' has ' + Core.read('money', user.userid) + ' ' + userAmt + ' left.');
		return this.sendReply('You have transferred ' + target + ' ' + bucks + ' to ' + targetUser + '. You have ' + Core.read('money', user.userid) + ' ' + userAmt + ' left.');
	},
	transferbuckshelp: ['/transferbucks or /transfermoney [user], [amount] - Transfers the specified number of bucks to a user.'],

	buy: function(target, room, user) {
		if (global.shopclosed) return this.sendReply("The shop is closed for now. Wait until it re-opens shortly.");
		target = toId(target);
		if (!shopList[target]) return this.sendReply('That item isn\'t in the shop.');
		let price = shopList[target][2];
		if (Core.read('money', user.userid) < price) return this.sendReply("You don't have enough money to buy a " + shopList[target][0].toLowerCase() + ".");

		//these items have their own specifics
		if (target === 'avatar') {
			if (!Number(user.avatar) && fs.existsSync('config/avatars/' + user.avatar)) return this.sendReply('You already have a custom avatar. Buy a fix if you want to change it.');
		} else if (target === 'potd') {
			if (Config.potd) return this.sendReply('The Pokémon of the Day has already been set.');
			this.sendReply("|html|Use /setpotd Pokémon to set the Pokémon of the day.");
			user.setpotd = true;
		}

		if (shopList[target][3]) {
			Users.users.forEach((u) => {
				if (u.can('hotpatch')) u.send('|pm|~Server-Kun [Do Not Reply]|' + u.userid + '|' + user.name + ' has bought a ' + target + ".")
			});
			this.sendReply('PM the details of your ' + shopList[target][0].toLowerCase() + ' to an Admin.');
		}

		room.add('|html|' + user.name + ' bought a ' + target + '!');
		Core.write("money", user.userid, price, '-');
		addLog(user.name + ' bought a ' + target + ' from the shop.');
	},

	setpotd: function (target, room, user) {
		if (!user.setpotd) return this.sendReply("You need to buy the ability to set the Pokemon of the Day!");
		if (user.alreadysetpotd) return this.sendReply("You've already set the POTD!");

		Config.potd = target;
		Simulator.SimulatorProcess.eval('Config.potd = \'' + toId(target) + '\'');
		if (!target) return this.sendRepply("You need to choose a Pokémon to set as the POTD.");
		if (Rooms.lobby) Rooms.lobby.addRaw('
The Pokémon of the Day is now ' + target + '!
This Pokemon will be guaranteed to show up in random battles.
');
		this.logModCommand('The Pokemon of the Day was changed to ' + target + ' by ' + user.name + '.');
		user.setpotd = false;
		user.alreadysetpotd = true;
	}
};
<commit_msg>Fix money log in shop<commit_after>//This is the shop. Pretty self explanatory :P
'use strict';

const fs = require('fs');
const request = require('request');

let shopList = {
	//Adding true as the last element of an array over here notifies admins when that item has been bought. This is useful for items like trainer cards that
	//require an admin's assistance
	potd: ['POTD', 'Buys the ability to set the Pokémon of the Day. Not purchasable if there is already a POTD.', 2],
	poof: ['Poof', 'Buy a poof message to be added into the pool of possible poofs.', 10, true],
	avatar: ['Avatar', 'Set your own custom avatar.', 35, true],
	card: ['Card', 'Buys a trainer card which can show information through a command.', 50, true],
	song: ['Song', 'Buys a song that can be played on a declarable card. (You must supply the song as a RAW MP3/OGG URL) <button name="send", value="/feelingit">Example</button>', 25, true],
	customize: ['Customise', 'This allows you to stylise your Trainer Card with HTML5 Elements such as Audio/Mouse Cursor/Background Image/etc.', 15, true],
	fix: ['Fix', 'Buys the ability to edit your custom avatar or trainer card.', 10, true],
	room: ['Room', 'Buys a chatroom for you to own (within reasons, can be refused).', 100, true],
	rng: ['RNG', 'Buys Floatzel\'s and Tempest\'s RNG/Cloning Services for Pokemon X&Y/ORAS. <button name ="send", value="/Powersaves">More Info</button>', 2]
};

function getShop () {
	let status = (!global.shopclosed) ? 'Shop status: Open
To buy an item, type in /buy [item] in the chat, or simply click on one of the buttons.' : 'Shop status: Closed';
	let text = '
Sora\'s Shop

Item	Description	Price
';

	for (let i in shopList) {
		text = text + '
' + shopList[i][0] + '	' + shopList[i][1] + '	' + shopList[i][2] + '	<button name = "send", value="/buy ' + i + '">Buy!</button>
';
	}
	text = text + '

' + status + '';
	return text; //escaping quotation marks
}

function addLog(message) {
	if (!global.moneyLog) global.moneyLog = '';
	global.moneyLog += '[' + require('dateformat')(new Date(), 'yyyy-mm-dd HH:MM:ss') + '] ';
	global.moneyLog += message + '
';
}

exports.commands = {

	getbucks: function(target, room, user) {
		if (!this.canBroadcast()) return;
		this.sendReplyBox('Please check out the Shop page in the link below to see methods of earning money:
' +
			'- Shop

');
	},

	buckslog: 'moneylog',
	moneylog: function(target, room, user) {
		if (!this.can('lock')) return false;
		this.popupReply('|html|Transaction Log (Time Zone:)
' + global.moneyLog);
	},

	atm: 'wallet',
	money: 'wallet',
	cash: 'wallet',
	purse: 'wallet',
	wallet: function(target, room, user) {
		if (!this.canBroadcast()) return;
		if (!toId(target)) target = user.name;
		else target = Users.getExact(target) ? Users.getExact(target).name : target;
		let money = Number(Core.read('money', toId(target))) || 'no';
		this.sendReplyBox(target + ' has ' + money + ' buck' + (money === 1 ? '' : 's') + '.');
	},

	shop: function(target, room, user) {
		if (!this.canBroadcast()) return;
		let shop = getShop();
		if (this.broadcasting) {
			return this.sendReply('|uhtml|shop|
Click <button name = "receive" value = "|uhtmlchange|shop|
' + shop.replace(/"/g, '"') + '
">here</button> to enter our shop!
');
		}
		this.sendReplyBox(shop);
	},

	toggleshop: 'adjustshop',
	adjustshop: function(target, room, user) {
		if (!this.can('hotpatch')) return false;
		global.shopclosed = !global.shopclosed;
		addLog(user.name + ' ' + (global.shopclosed ? 'closed' : 'opened') + ' the shop.');
		this.sendReply('The shop is now ' + (global.shopclosed ? 'closed' : 'open') + '.');
	},

	givebucks: 'award',
	givebucks: 'award',
	gb: 'award',
	award: function(target, room, user, connection, cmd) {
		if (!this.can('hotpatch')) return false;
		if (!target) return this.sendReply('The correct syntax is /' + cmd + ' [user], [amount]');
		target = target.split(',');
		if (target.length < 2) return this.sendReply('/' + cmd + ' [user], [amount] - Gives a user the specified number of bucks.')
		let targetUser = Users.getExact(target[0]) ? Users.getExact(target[0]).name : target[0];
		let amt = Number(toId(target[1])) || target[1];
		if (!amt) return this.sendReply('You need to mention the number of bucks you want to give ' + targetUser);
		if (isNaN(amt)) return this.sendReply(amt + " is not a valid number.");
		if (amt < 1) return this.sendReply('You cannot give ' + targetUser + ' anything less than 1 buck!');
		if (~String(amt).indexOf('.')) return this.sendReply('You cannot give ' + targetUser + ' fractions of bucks.');

		Core.write('money', toId(targetUser), amt, '+');
		let giveFormat = (amt == 1) ? 'buck' : 'bucks';
		let hasFormat = (Core.read('money', toId(targetUser)) === 1) ? 'buck' : 'bucks';
		if (Users.getExact(targetUser)) Users.getExact(targetUser).send('|popup|' + user.name + ' has given you ' + amt + ' ' + giveFormat + '. You now have ' + Core.read('money', toId(targetUser)) + ' ' + hasFormat + '.');
		addLog(user.name + ' has given ' + targetUser + ' ' + amt + ' ' + giveFormat + '. This user now has ' + Core.read('money', toId(targetUser)) + ' ' + hasFormat + '.');
		return this.sendReply(targetUser + ' was given ' + amt + ' ' + giveFormat + '. This user now has ' + Core.read('money', toId(targetUser)) + ' ' + hasFormat + '.');
	},

	removebucks: 'remove',
	rb: 'remove',
	tb: 'remove',
	takebucks: 'remove',
	take: 'remove',
	remove: function(target, room, user, connection, cmd) {
		if (!this.can('hotpatch')) return false;
		if (!target) return this.sendReply('/' + cmd + ' [user], [amount] - Removes the specified number of bucks from a user.');
		target = target.split(',');
		if (target.length < 2) return this.sendReply('/' + cmd + ' [user], [amount] - Removes the specified number of bucks from a user.')
		let targetUser = Users.getExact(target[0]) ? Users.getExact(target[0]).name : target[0];
		let amt = Number(toId(target[1])) || target[1];
		if (!amt) return this.sendReply('You need to mention the number of bucks you want to remove from ' + targetUser + '.');
		if (isNaN(amt)) return this.sendReply(amt + " is not a valid number.");
		if (amt < 1) return this.sendReply('You cannot take away anything less than 1 buck!');
		if (~String(amt).indexOf('.')) return this.sendReply('You cannot take away fractions.');
		if (Core.read('money', toId(targetUser)) < amt) return this.sendReply('You can\'t take away more than what ' + targetUser + ' already has!');

		Core.write('money', toId(targetUser), amt, '-');
		let takeFormat = (amt === 1) ? 'buck' : 'bucks';
		let hasFormat = (Core.read('money', toId(targetUser)) === 1) ? 'buck' : 'bucks';
		if (Users.getExact(targetUser)) Users.getExact(targetUser).send('|popup|' + user.name + ' has taken away ' + amt + ' ' + takeFormat + ' from you. You now have ' + Core.read('money', toId(targetUser)) + ' ' + hasFormat + ' left.');
		addLog(user.name + ' has taken away ' + amt + ' ' + takeFormat + ' from ' + targetUser + '. This user now has ' + Core.read('money', toId(targetUser)) + ' ' + hasFormat + ' left.');
		return this.sendReply('You have taken away ' + amt + ' ' + takeFormat + ' from ' + targetUser + '. This user now has ' + Core.read('money', toId(targetUser)) + ' ' + hasFormat + ' left.');
	},

	transfermoney: 'transferbucks',
	transferbucks: function(target, room, user, connection, cmd) {
		if (!this.canBroadcast()) return false;
		if (!target) return this.parse('/help transferbucks');
		target = this.splitTarget(target, true);
		let targetUser = this.targetUsername;
		targetUser = Users.getExact(targetUser) ? Users.getExact(targetUser).name : targetUser;
		if (!targetUser || !targetUser.trim()) return this.parse('/help transferbucks');
		if (!toId(targetUser)) return this.sendReply('"' + targetUser + '" is not a valid username.')
		if (toId(targetUser) === user.userid) return this.sendReply('You can\'t transfer bucks to yourself!');
		if (!toId(target)) return this.sendReply('You need to specify the number of bucks you want to transfer to ' + targetUser);
		if (isNaN(target)) return this.sendReply(target + " isn't a valid number.");
		if (Core.read('money', user.userid) < target) return this.sendReply('You can\'t give ' + targetUser + ' more than what you have!');

		Core.write('money', toId(targetUser), Number(target), '+');
		Core.write('money', user.userid, Number(target), '-');
		let amt = (Core.read('money', toId(targetUser)) == 1) ? 'buck' : 'bucks';
		let userAmt = (Core.read('money', user.userid) == 1) ? 'buck' : 'bucks';
		let bucks = (target == 1) ? 'buck' : 'bucks';
		if (Users.getExact(targetUser)) Users.getExact(targetUser).send('|popup|' + user.name + ' has transferred ' + target + ' ' + bucks + ' to you. You now have ' + Core.read('money', toId(targetUser)) + ' ' + amt + '.');
		addLog(user.name + ' has transferred ' + target + ' ' + bucks + ' to ' + targetUser + '. This user now has ' + Core.read('money', toId(targetUser)) + ' ' + amt + '. ' + user.name + ' has ' + Core.read('money', user.userid) + ' ' + userAmt + ' left.');
		return this.sendReply('You have transferred ' + target + ' ' + bucks + ' to ' + targetUser + '. You have ' + Core.read('money', user.userid) + ' ' + userAmt + ' left.');
	},
	transferbuckshelp: ['/transferbucks or /transfermoney [user], [amount] - Transfers the specified number of bucks to a user.'],

	buy: function(target, room, user) {
		if (global.shopclosed) return this.sendReply("The shop is closed for now. Wait until it re-opens shortly.");
		target = toId(target);
		if (!shopList[target]) return this.sendReply('That item isn\'t in the shop.');
		let price = shopList[target][2];
		if (Core.read('money', user.userid) < price) return this.sendReply("You don't have enough money to buy a " + shopList[target][0].toLowerCase() + ".");

		//these items have their own specifics
		if (target === 'avatar') {
			if (!Number(user.avatar) && fs.existsSync('config/avatars/' + user.avatar)) return this.sendReply('You already have a custom avatar. Buy a fix if you want to change it.');
		} else if (target === 'potd') {
			if (Config.potd) return this.sendReply('The Pokémon of the Day has already been set.');
			this.sendReply("|html|Use /setpotd Pokémon to set the Pokémon of the day.");
			user.setpotd = true;
		}

		if (shopList[target][3]) {
			Users.users.forEach((u) => {
				if (u.can('hotpatch')) u.send('|pm|~Server-Kun [Do Not Reply]|' + u.userid + '|' + user.name + ' has bought a ' + target + ".")
			});
			this.sendReply('PM the details of your ' + shopList[target][0].toLowerCase() + ' to an Admin.');
		}

		room.add('|html|' + user.name + ' bought a ' + target + '!');
		Core.write("money", user.userid, price, '-');
		addLog(user.name + ' bought a ' + target + ' from the shop.');
	},

	setpotd: function (target, room, user) {
		if (!user.setpotd) return this.sendReply("You need to buy the ability to set the Pokemon of the Day!");
		if (user.alreadysetpotd) return this.sendReply("You've already set the POTD!");

		Config.potd = target;
		Simulator.SimulatorProcess.eval('Config.potd = \'' + toId(target) + '\'');
		if (!target) return this.sendRepply("You need to choose a Pokémon to set as the POTD.");
		if (Rooms.lobby) Rooms.lobby.addRaw('
The Pokémon of the Day is now ' + target + '!
This Pokemon will be guaranteed to show up in random battles.
');
		this.logModCommand('The Pokemon of the Day was changed to ' + target + ' by ' + user.name + '.');
		user.setpotd = false;
		user.alreadysetpotd = true;
	}
};
<|endoftext|>


<commit_before>
var fs = require('fs'),
  path = require('path'),
  util = require('util'),
  vm = require('vm'),
  stream = require('stream'),
  Lexer = require('gherkin').Lexer('en'),
  templates = require('../template'),
  sandbox = require('./sandbox');

//
// Basic gherkin feature -> mocha bdd test suite
//
//    fs.createReadStream(something).pipe(new Parser).pipe(process.stdout);
//
// Basic gherkin to mocha mapping:
//
// - feature      -> top-level describe()
// - scenario(s)  -> inner describe(), might have one or more
// - step(s)      -> it()
//

module.exports = Parser;

function Parser(opts) {
  opts = opts || {};
  this.readable = this.writable = true;

  this.steps = [];
  this.features = [];
  this.scenarios = [];

  this.stepsDefinition = opts.step || '';
  this.sandbox = opts.sandbox || sandbox;
  this.sandbox.console = console;

  this.missing = opts.missing;

  this.chunks = [];

  this.lexer = new Lexer(this);
  this.template = templates.body;

  stream.Stream.call(this);

  this.init();
}

util.inherits(Parser, stream.Stream);

Parser.prototype.init = function(feature) {
  var steps = this.stepsDefinition;
  if(!steps) return;
  vm.runInNewContext(steps, this.sandbox);
};

Parser.prototype.parse = function(feature) {
  this.lexer.scan(feature);
};

//
// Stream api
//

Parser.prototype.write = function(chunk) {
  // collect all the data before streaming out.
  // should probably be done better by pausing / resuming as necessary
  this.chunks = this.chunks.concat(chunk);
};

Parser.prototype.end = function() {
  var feature = this.chunks.join('');
  this.parse(feature);
  this.emit('data', this.mocha());
  this.emit('close');
};

Parser.prototype.destroy = function() {};

//
// Mocha output
//
// - feature      -> top-level describe()
// - scenario(s)  -> inner describe(), might have one or more
// - step(s)      -> it()

Parser.prototype.mocha = function() {
  var steps = this.steps;

  var data = {};
  data.feature = this.features[0];
  data.feature.desc = data.feature.desc.replace(/\n/g, ' ');
  data.scenarios = this.scenarios.map(function(scenario, i, arr) {
    var ln = scenario.line,
      last = i === arr.length - 1,
      next = arr[i + 1] ? arr[i + 1].line : 0;

    scenario.steps = steps.filter(function(step) {
      if(last) return step.line > ln;
      return step.line > ln && step.line < next;
    });

    return scenario;
  });

  if(this.missing) {
    // add missing step here
    return this.scenarios.map(function(scenario) {
      var steps = scenario.steps;
      return steps.map(function(step) {
        // clean step type from name
        var name = step.name.replace(new RegExp('^' + step.step), '');

        // cleanup name for some specific stuff like escaping "/"
        name = name.replace(/([^\\])\//g, '$1\\\/');

        // step fn handler
        var fn = step.step.trim() + '($reg, function() {\n'.replace('$reg', new RegExp(name));
        fn += '  // add code for your definition here, regexp captured paramaters can be used\n';
        fn += '  // in the function body with simple placeholders like $1, $2, ...\n';
        fn += '});\n';
        return fn;
      }).join('\n');
    }).join('');
  }

  return this.template.render(data);
};

//
// Gherkin parser api
//

Parser.prototype.background = function(keyword, name, desc, line) {
  this.emit('background', name, desc, line);
};

Parser.prototype.comment = function(comment, line) {
  this.emit('comment', comment, line);
};

Parser.prototype.doc_string = function(cotentType, string, line) {
  this.emit('doc_string', contentType, string, line);
};

Parser.prototype.eof = function() {
  this.emit('eof');
};

Parser.prototype.feature = function(keyword, name, desc, line) {
  this.emit('feature', name, desc, line);
  desc = desc.replace(/"/g, "'");
  this.features.push({ name: name, desc: desc, line: line });
};

Parser.prototype.row = function(cells, line) {
  // console.log('row>>', arguments);
};

Parser.prototype.scenario = function(keyword, name, desc, line) {
  this.emit('scenario', name, desc, line);

  desc = desc.replace(/"/g, "'");
  this.scenarios.push({ name: name, desc: desc, line: line });
};

Parser.prototype.step = function(keyword, name, line) {
  this.emit('step', keyword, name, line);
  this.emit(keyword, name, line);

  var data = { step: keyword, name: keyword + name, line: line, args: '' };
  name = data.name = data.name.replace(/"/g, "'");

  var match = this.sandbox.match(name);

  if(!match) return this.steps.push(data);

  // a step is macthing
  var src = match.fn + '',
    args = (name.match(match.reg) || []).slice(1);

  // remove first / last line of our toString'd fn
  src = src.split(/\r\n|\n/g);
  src = src.slice(1, src.length - 1).map(function(l) {
    return l ? '      ' + l : l;
  }).join('\n');

  data.body = src.replace(/\$([\d]+)/g, function(m, index) {
    return args[parseInt(index, 10) - 1];
  });

  data.args = 'done';
  this.steps.push(data);
};

Parser.prototype.tag = function(tag, line) {
  this.emit('tag', tag, line);
};
<commit_msg>make sure to pass modules options to template<commit_after>
var fs = require('fs'),
  path = require('path'),
  util = require('util'),
  vm = require('vm'),
  stream = require('stream'),
  Lexer = require('gherkin').Lexer('en'),
  templates = require('../template'),
  sandbox = require('./sandbox');

//
// Basic gherkin feature -> mocha bdd test suite
//
//    fs.createReadStream(something).pipe(new Parser).pipe(process.stdout);
//
// Basic gherkin to mocha mapping:
//
// - feature      -> top-level describe()
// - scenario(s)  -> inner describe(), might have one or more
// - step(s)      -> it()
//

module.exports = Parser;

function Parser(opts) {
  opts = opts || {};
  this.readable = this.writable = true;

  this.steps = [];
  this.features = [];
  this.scenarios = [];

  this.stepsDefinition = opts.step || '';
  this.sandbox = opts.sandbox || sandbox;
  this.sandbox.console = console;

  this.missing = opts.missing;

  this.modules = opts.modules || [];

  this.chunks = [];

  this.lexer = new Lexer(this);
  this.template = templates.body;

  stream.Stream.call(this);

  this.init();
}

util.inherits(Parser, stream.Stream);

Parser.prototype.init = function(feature) {
  var steps = this.stepsDefinition;
  if(!steps) return;
  vm.runInNewContext(steps, this.sandbox);
};

Parser.prototype.parse = function(feature) {
  this.lexer.scan(feature);
};

//
// Stream api
//

Parser.prototype.write = function(chunk) {
  // collect all the data before streaming out.
  // should probably be done better by pausing / resuming as necessary
  this.chunks = this.chunks.concat(chunk);
};

Parser.prototype.end = function() {
  var feature = this.chunks.join('');
  this.parse(feature);
  this.emit('data', this.mocha());
  this.emit('close');
};

Parser.prototype.destroy = function() {};

//
// Mocha output
//
// - feature      -> top-level describe()
// - scenario(s)  -> inner describe(), might have one or more
// - step(s)      -> it()

Parser.prototype.mocha = function() {
  var steps = this.steps;

  var data = {};
  data.modules = this.modules;
  data.feature = this.features[0];
  data.feature.desc = data.feature.desc.replace(/\n/g, ' ');
  data.scenarios = this.scenarios.map(function(scenario, i, arr) {
    var ln = scenario.line,
      last = i === arr.length - 1,
      next = arr[i + 1] ? arr[i + 1].line : 0;

    scenario.steps = steps.filter(function(step) {
      if(last) return step.line > ln;
      return step.line > ln && step.line < next;
    });

    return scenario;
  });

  if(this.missing) {
    // add missing step here
    return this.scenarios.map(function(scenario) {
      var steps = scenario.steps;
      return steps.map(function(step) {
        // clean step type from name
        var name = step.name.replace(new RegExp('^' + step.step), '');

        // cleanup name for some specific stuff like escaping "/"
        name = name.replace(/([^\\])\//g, '$1\\\/');

        // step fn handler
        var fn = step.step.trim() + '($reg, function() {\n'.replace('$reg', new RegExp(name));
        fn += '  // add code for your definition here, regexp captured paramaters can be used\n';
        fn += '  // in the function body with simple placeholders like $1, $2, ...\n';
        fn += '});\n';
        return fn;
      }).join('\n');
    }).join('');
  }

  return this.template.render(data);
};

//
// Gherkin parser api
//

Parser.prototype.background = function(keyword, name, desc, line) {
  this.emit('background', name, desc, line);
};

Parser.prototype.comment = function(comment, line) {
  this.emit('comment', comment, line);
};

Parser.prototype.doc_string = function(cotentType, string, line) {
  this.emit('doc_string', contentType, string, line);
};

Parser.prototype.eof = function() {
  this.emit('eof');
};

Parser.prototype.feature = function(keyword, name, desc, line) {
  this.emit('feature', name, desc, line);
  desc = desc.replace(/"/g, "'");
  this.features.push({ name: name, desc: desc, line: line });
};

Parser.prototype.row = function(cells, line) {
  // console.log('row>>', arguments);
};

Parser.prototype.scenario = function(keyword, name, desc, line) {
  this.emit('scenario', name, desc, line);

  desc = desc.replace(/"/g, "'");
  this.scenarios.push({ name: name, desc: desc, line: line });
};

Parser.prototype.step = function(keyword, name, line) {
  this.emit('step', keyword, name, line);
  this.emit(keyword, name, line);

  var data = { step: keyword, name: keyword + name, line: line, args: '' };
  name = data.name = data.name.replace(/"/g, "'");

  var match = this.sandbox.match(name);

  if(!match) return this.steps.push(data);

  // a step is macthing
  var src = match.fn + '',
    args = (name.match(match.reg) || []).slice(1);

  // remove first / last line of our toString'd fn
  src = src.split(/\r\n|\n/g);
  src = src.slice(1, src.length - 1).map(function(l) {
    return l ? '      ' + l : l;
  }).join('\n');

  data.body = src.replace(/\$([\d]+)/g, function(m, index) {
    return args[parseInt(index, 10) - 1];
  });

  data.args = 'done';
  this.steps.push(data);
};

Parser.prototype.tag = function(tag, line) {
  this.emit('tag', tag, line);
};
<|endoftext|>


<commit_before>/* global Travis */
import TravisRoute from 'travis/routes/basic';
import config from 'travis/config/environment';
import BuildFaviconMixin from 'travis/mixins/build-favicon';
import Ember from 'ember';

import KeyboardShortcuts from 'ember-keyboard-shortcuts/mixins/route';

let { service } = Ember.inject;

export default TravisRoute.extend(BuildFaviconMixin, KeyboardShortcuts, {
  flashes: service(),
  auth: service(),
  featureFlags: service(),

  needsAuth: false,

  renderTemplate: function () {
    if (this.get('config').pro) {
      Ember.$('body').addClass('pro');
    }
    return this._super(...arguments);
  },

  model() {
    if (this.get('auth.signedIn')) {
      return this.get('featureFlags.fetchTask').perform();
    }
  },

  activate() {
    var repos;
    if (!this.get('features.proVersion')) {
      repos = this.get('store').peekAll('repo');
      repos.forEach((repo) => {
        return this.subscribeToRepo(repo);
      });
      return repos.addArrayObserver(this, {
        willChange: 'reposWillChange',
        didChange: 'reposDidChange'
      });
    }
  },

  reposWillChange() {},

  reposDidChange(array, start, removedCount, addedCount) {
    var addedRepos;
    addedRepos = array.slice(start, start + addedCount);
    return addedRepos.forEach((repo) => {
      return this.subscribeToRepo(repo);
    });
  },

  subscribeToRepo: function (repo) {
    if (this.pusher) {
      return this.pusher.subscribe('repo-' + (repo.get('id')));
    }
  },

  title(titleParts) {
    if (titleParts.length) {
      titleParts = titleParts.reverse();
      titleParts.push('Travis CI');
      return titleParts.join(' - ');
    } else {
      return config.defaultTitle;
    }
  },

  keyboardShortcuts: {
    'up': {
      action: 'disableTailing',
      preventDefault: false
    },
    'down': {
      action: 'disableTailing',
      preventDefault: false
    }
  },

  actions: {
    signIn() {
      this.get('auth').signIn();
      this.afterSignIn();
    },

    signOut() {
      this.get('auth').signOut();
      this.afterSignOut();
    },

    disableTailing() {
      Travis.tailing.stop();
    },

    redirectToGettingStarted() {
      // keep as a no-op as this bubbles from other routes
    },

    error(error) {
      if (error === 'needs-auth') {
        this.set('auth.redirected', true);
        return this.transitionTo('auth');
      } else {
        return true;
      }
    },

    showRepositories() {
      this.transitionTo('index');
    },

    viewSearchResults(query) {
      this.transitionTo('search', query);
    },
  },

  afterSignIn() {
    this.get('flashes').clear();
    let transition = this.auth.get('afterSignInTransition');
    if (transition) {
      this.auth.set('afterSignInTransition', null);
      return transition.retry();
    } else {
      return this.transitionTo('index');
    }
  },

  afterSignOut() {
    this.set('repositories.accessible', []);
    this.setDefault();
    this.get('featureFlags').reset();
    if (this.get('config.enterprise')) {
      return this.transitionTo('auth');
    }
    return this.transitionTo('index');
  },
});
<commit_msg>Add missing service definition<commit_after>/* global Travis */
import TravisRoute from 'travis/routes/basic';
import config from 'travis/config/environment';
import BuildFaviconMixin from 'travis/mixins/build-favicon';
import Ember from 'ember';

import KeyboardShortcuts from 'ember-keyboard-shortcuts/mixins/route';

let { service } = Ember.inject;

export default TravisRoute.extend(BuildFaviconMixin, KeyboardShortcuts, {
  flashes: service(),
  auth: service(),
  featureFlags: service(),
  repositories: service(),

  needsAuth: false,

  renderTemplate: function () {
    if (this.get('config').pro) {
      Ember.$('body').addClass('pro');
    }
    return this._super(...arguments);
  },

  model() {
    if (this.get('auth.signedIn')) {
      return this.get('featureFlags.fetchTask').perform();
    }
  },

  activate() {
    var repos;
    if (!this.get('features.proVersion')) {
      repos = this.get('store').peekAll('repo');
      repos.forEach((repo) => {
        return this.subscribeToRepo(repo);
      });
      return repos.addArrayObserver(this, {
        willChange: 'reposWillChange',
        didChange: 'reposDidChange'
      });
    }
  },

  reposWillChange() {},

  reposDidChange(array, start, removedCount, addedCount) {
    var addedRepos;
    addedRepos = array.slice(start, start + addedCount);
    return addedRepos.forEach((repo) => {
      return this.subscribeToRepo(repo);
    });
  },

  subscribeToRepo: function (repo) {
    if (this.pusher) {
      return this.pusher.subscribe('repo-' + (repo.get('id')));
    }
  },

  title(titleParts) {
    if (titleParts.length) {
      titleParts = titleParts.reverse();
      titleParts.push('Travis CI');
      return titleParts.join(' - ');
    } else {
      return config.defaultTitle;
    }
  },

  keyboardShortcuts: {
    'up': {
      action: 'disableTailing',
      preventDefault: false
    },
    'down': {
      action: 'disableTailing',
      preventDefault: false
    }
  },

  actions: {
    signIn() {
      this.get('auth').signIn();
      this.afterSignIn();
    },

    signOut() {
      this.get('auth').signOut();
      this.afterSignOut();
    },

    disableTailing() {
      Travis.tailing.stop();
    },

    redirectToGettingStarted() {
      // keep as a no-op as this bubbles from other routes
    },

    error(error) {
      if (error === 'needs-auth') {
        this.set('auth.redirected', true);
        return this.transitionTo('auth');
      } else {
        return true;
      }
    },

    showRepositories() {
      this.transitionTo('index');
    },

    viewSearchResults(query) {
      this.transitionTo('search', query);
    },
  },

  afterSignIn() {
    this.get('flashes').clear();
    let transition = this.auth.get('afterSignInTransition');
    if (transition) {
      this.auth.set('afterSignInTransition', null);
      return transition.retry();
    } else {
      return this.transitionTo('index');
    }
  },

  afterSignOut() {
    this.set('repositories.accessible', []);
    this.setDefault();
    this.get('featureFlags').reset();
    if (this.get('config.enterprise')) {
      return this.transitionTo('auth');
    }
    return this.transitionTo('index');
  },
});
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Text, ListView, View, TouchableOpacity } from 'react-native'
import { Actions } from 'react-native-router-flux'

import styles from '../styles/stylesheet'
import strings from '../resources/translations'

class StopRequestView extends Component{
  constructor(props) {
    super(props)

    this.state = {
    }
  }

  render() {
    const goBack = () => Actions.departures();

    const vehicleTypes = 'tram metro train bus ferry'.split(' ')

    return (
      <View style={styles.stopRequestStyle}>
      <View>
        <Text style={{fontSize: 20, textAlign: 'center'}}>{strings[vehicleTypes[this.props.vehicle.vehicle_type]]} {this.props.vehicle.line} to {this.props.vehicle.destination} {strings.stopsAt} {this.props.stop.stopName} ({this.props.stop.stopId})</Text>
        <TouchableOpacity onPress={goBack}>
          <Text style={{color: '#ff0000', textAlign: 'center'}}>{strings.back}</Text>
        </TouchableOpacity>
        </View>
      </View>
    )
  }
}

export default StopRequestView;
<commit_msg>removed unnecessary semicolons<commit_after>import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Text, ListView, View, TouchableOpacity } from 'react-native'
import { Actions } from 'react-native-router-flux'

import styles from '../styles/stylesheet'
import strings from '../resources/translations'

class StopRequestView extends Component{
  constructor(props) {
    super(props)

    this.state = {
    }
  }

  render() {
    const goBack = () => Actions.departures()

    const vehicleTypes = 'tram metro train bus ferry'.split(' ')

    return (
      <View style={styles.stopRequestStyle}>
      <View>
        <Text style={{fontSize: 20, textAlign: 'center'}}>{strings[vehicleTypes[this.props.vehicle.vehicle_type]]} {this.props.vehicle.line} to {this.props.vehicle.destination} {strings.stopsAt} {this.props.stop.stopName} ({this.props.stop.stopId})</Text>
        <TouchableOpacity onPress={goBack}>
          <Text style={{color: '#ff0000', textAlign: 'center'}}>{strings.back}</Text>
        </TouchableOpacity>
        </View>
      </View>
    )
  }
}

export default StopRequestView
<|endoftext|>