# Exports an object that defines
#  all of the configuration needed by the projects'
#  depended-on grunt tasks.
#
# You can familiarize yourself with all of Lineman's defaults by checking out the parent file:
# https://github.com/testdouble/lineman/blob/master/config/application.coffee
#

module.exports = require(process.env['LINEMAN_MAIN']).config.extend('application', {
  removeTasks:
    common: [ "webfonts:dev", "images:dev"]
    dist: ["images:dist", "webfonts:dist", "pages:dist"]

  server:
    apiProxy:
      enabled: true
      host: 'localhost'
      port: 4567

  # enableSass: true

  # configure lineman to load additional angular related npm tasks
  loadNpmTasks: [ "grunt-ngmin"]

  # task override configuration
  prependTasks:
    dist: ["ngmin"]         # ngmin should run in dist only

  watch:
    scripts:
      files: ["generated/**"],
      tasks: ['copy:dev']

  copy:
    dev:
      files: [expand: true, cwd: 'generated', src: ['css/**', 'js/**', '!**/spec.js',
              '!**/*.less*', '!**/*.coffee*', '!**/spec.js.map'], dest: '../public' ]

  # configuration for grunt-ngmin, this happens _after_ concat once, which is the ngmin ideal :)
  ngmin: {
    js: {
      src: "<%= files.js.concatenated %>",
      dest: "<%= files.js.concatenated %>"
    }
  },
})

# Exports an object that defines
#  all of the configuration needed by the projects'
#  depended-on grunt tasks.
#
# You can familiarize yourself with all of Lineman's defaults by checking out the parent file:
# https://github.com/testdouble/lineman/blob/master/config/application.coffee
#

module.exports = require(process.env['LINEMAN_MAIN']).config.extend('application', {
  removeTasks:
    common: [ "webfonts:dev", "images:dev"]
    dist: ["images:dist", "webfonts:dist", "pages:dist"]

  server:
    apiProxy:
      enabled: true
      host: 'localhost'
      port: 4567

  # enableSass: true

  # configure lineman to load additional angular related npm tasks
  loadNpmTasks: [ "grunt-ngmin"]

  # task override configuration
  prependTasks:
    dist: ["ngmin"]         # ngmin should run in dist only

  watch:
    scripts:
      files: ["generated/**"],
      tasks: ['copy:dev']

  copy:
    dev:
      files: [expand: true, cwd: 'generated', src: ['css/**', 'js/**', '!**/spec.js',
              '!**/*.less*', '!**/*.coffee*', '!**/spec.js.map'], dest: '../public' ]
    dist:
      files: [expand: true, cwd: 'dist', src: ['js/**'], dest: '../public']

  # configuration for grunt-ngmin, this happens _after_ concat once, which is the ngmin ideal :)
  ngmin: {
    js: {
      src: "<%= files.js.concatenated %>",
      dest: "<%= files.js.concatenated %>"
    }
  },
})

Copy JS files to public dir when building assets

By default this copies to the /frontend/dist directory which meant the
uglified JS was not being served.

By default lineman runs copy:dev as part of it's common tasks
(confusingly really given it's *:dev name) which copies the non
minified version over which is how this problem didn't have more drastic
effects.


React = require 'react'
BS = require 'react-bootstrap'
_ = require 'underscore'

{History, Link} = require 'react-router'
{TransitionActions, TransitionStore} = require '../../flux/transition'

BackButton = React.createClass
  displayName: 'BackButton'

  propTypes:
    fallbackLink: React.PropTypes.shape(
      to: React.PropTypes.string
      params: React.PropTypes.object
      text: React.PropTypes.string
    ).isRequired

  contextTypes:
    router: React.PropTypes.func

  navigate: ->
    historyInfo = TransitionStore.getPrevious(@context.router)
    if historyInfo.path
      @context.router.transitionTo(historyInfo.path)
    else
      @context.router.transitionTo(@props.fallbackLink.to, @props.fallbackLink.params)

  render: ->
    # Gets route to last path that was not the same as the current one
    # See TransitionStore for more detail.
    historyInfo = TransitionStore.getPrevious(@context.router)
    {fallbackLink, className} = @props
    {text} = fallbackLink

    backText = if historyInfo.name then "Back to #{historyInfo.name}"  else fallbackLink.text

    <BS.Button className={className} {...@props}
      onClick={@navigate}>
      {backText}
    </BS.Button>

module.exports = BackButton

React = require 'react'
BS = require 'react-bootstrap'
_ = require 'underscore'

{History, Link} = require 'react-router'
{TransitionActions, TransitionStore} = require '../../flux/transition'

BackButton = React.createClass
  displayName: 'BackButton'

  propTypes:
    fallbackLink: React.PropTypes.shape(
      to: React.PropTypes.string
      params: React.PropTypes.object
      text: React.PropTypes.string
    ).isRequired

  contextTypes:
    router: React.PropTypes.func

  render: ->
    # Gets route to last path that was not the same as the current one
    # See TransitionStore for more detail.
    historyInfo = TransitionStore.getPrevious(@context.router)
    {fallbackLink, className} = @props
    {text} = fallbackLink

    backText = if historyInfo.name then "Back to #{historyInfo.name}" else fallbackLink.text

    href =  historyInfo.path or @context.router.makeHref(
      @props.fallbackLink.to, @props.fallbackLink.params
    )

    <BS.Button href={href} className={className} {...@props}>
      {backText}
    </BS.Button>

module.exports = BackButton

Use anchor link form of button

Use anchor link form of button

This way it's a real link and can be opened in new tab


module.exports =
  findMisspellings: (text) ->
    wordRegex = /(?:^|\s)([a-zA-Z']+)(?=\s|\.|$)/g
    row = 0
    misspellings = []
    for line in text.split('\n')
      while matches = wordRegex.exec(line)
        word = matches[1]
        continue unless $native.isMisspelled(word)
        startColumn = matches.index + matches[0].length - word.length
        endColumn = startColumn + word.length
        misspellings.push([[row, startColumn], [row, endColumn]])
      row++
    callTaskMethod('misspellingsFound', misspellings)

module.exports =
  findMisspellings: (text) ->
    wordRegex = /(?:^|[\s\[\]])([a-zA-Z']+)(?=[\s\.\[\]]|$)/g
    row = 0
    misspellings = []
    for line in text.split('\n')
      while matches = wordRegex.exec(line)
        word = matches[1]
        continue unless $native.isMisspelled(word)
        startColumn = matches.index + matches[0].length - word.length
        endColumn = startColumn + word.length
        misspellings.push([[row, startColumn], [row, endColumn]])
      row++
    callTaskMethod('misspellingsFound', misspellings)

Add [] as word boundaries

Add [] as word boundaries

This is a common boundary used in markdown link text


Impromptu = require '../impromptu'

class Instance extends Impromptu.Cache
  run: (fn) ->
    return get fn if @_cached

    @set (err, results) =>
      if err then fn err else @get fn

  get: (fn) ->
    fn null, @_cached ? @options.fallback

  set: (fn) ->
    @options.update.call @options.context, (err, value) =>
      unless err
        @_cached = value
      fn err, !err

  unset: (fn) ->
    @_cached = null
    fn null, true

# Expose `Instance`.
exports = module.exports = Instance

Impromptu = require '../impromptu'

class Instance extends Impromptu.Cache
  run: (fn) ->
    return @get fn if @_cached

    @set (err, results) =>
      if err then fn err else @get fn

  get: (fn) ->
    fn null, @_cached ? @options.fallback

  set: (fn) ->
    @options.update.call @options.context, (err, value) =>
      unless err
        @_cached = value
      fn err, !err

  unset: (fn) ->
    @_cached = null
    fn null, true

# Expose `Instance`.
exports = module.exports = Instance

Add missing reference to this.

Add missing reference to this.


Promise = require 'bluebird'
async = Promise.coroutine
request = Promise.promisifyAll require 'request'
requestAsync = Promise.promisify request, multiArgs: true

{SERVER_HOSTNAME, POI_VERSION, ROOT} = global
{log, warn, error} = require './utils'

module.exports =
  checkUpdate: async (callback) ->
    try
      [response, body] = yield requestAsync "http://#{SERVER_HOSTNAME}/update/latest.json",
        method: 'GET'
        json: true
        headers:
          'User-Agent': "poi v#{POI_VERSION}"
      if response.statusCode == 200
        callback body
      else
        callback 'error'
    catch e
      error e
      callback 'error'

Promise = require 'bluebird'
async = Promise.coroutine
request = Promise.promisifyAll require 'request'
requestAsync = Promise.promisify request, multiArgs: true

{POI_VERSION, ROOT} = global
{log, warn, error} = require './utils'

module.exports =
  checkUpdate: async (callback) ->
    try
      [response, body] = yield requestAsync "http://#{global.SERVER_HOSTNAME}/update/latest.json",
        method: 'GET'
        json: true
        headers:
          'User-Agent': "poi v#{POI_VERSION}"
      if response.statusCode == 200
        callback body
      else
        callback 'error'
    catch e
      error e
      callback 'error'

Fix an error that may lead to [ERROR] Error: getaddrinfo ENOTFOUND undefined undefined:80


describe 'Test of core.coffee', ->
  elements = null

  beforeEach ->
    elements = [null, null, null]
    for i,_ of elements
      elements[i] =  window.document.createElement('section')
      elements[i].innerHTML = '''
      <animation>
        <keyframe target='h1'>
        <keyframe target='h2'>
      </animation>
      '''
      window.document.body.appendChild(elements[i])

  afterEach ->
    for i,_ of elements
      window.document.body.removeChild(elements[i])
    elements = null

  it "initializes a object", ->
    core = new Scena.Core
    expect(window.Scena.core).toBe(core)

    core.initialize()
    expect(core.pages.length).toBe(3)

  it "skips to aspecified page", ->
    core = new Scena.Core
    core.initialize()
    core.skipToPage(2)

    expect(core.getCurrentIndex()).toBe(2)

  it "fires a keyframe if the page has keyframes", ->
    core = new Scena.Core
    core.initialize()
    core.skipToPage(0)

    expect(core.currentAnimation.keyframes.length).toBe(2)
    core.nextStep()
    expect(core.currentAnimation.keyframes.length).toBe(1)
    core.nextStep()
    expect(core.currentAnimation.keyframes.length).toBe(0)

  it "moves to a next page if the page has no-keyframes", ->
    core = new Scena.Core
    core.initialize()
    core.skipToPage(0)

    core.nextStep()
    core.nextStep()
    core.nextStep()
    expect(core.currentAnimation.keyframes.length).toBe(2)
    expect(core.getCurrentIndex()).toBe(1)

  it "skips to aspecified page and remove a keyframe if keyframes remain", ->
    core = new Scena.Core
    core.initialize()
    core.skipToPage(0)
    core.nextStep()    # of remaining keyframes is 1
    core.skipToPage(2)

    expect(core.getCurrentIndex()).toBe(2)
    expect(core.currentAnimation.keyframes.length).toBe(2)

  xit "animates to aspecified page", ->

  it "skips to aspecified page", ->
    core = new Scena.Core
    core.initialize()
    core.skipToPage(2)

    expect(core.getCurrentIndex()).toBe(2)

  it "skips to next page", ->
    core = new Scena.Core
    core.initialize()
    core.skipToPage(1)

    core.skipToNextPage()
    expect(core.getCurrentIndex()).toBe(2)

  it "skips to previous page", ->
    core = new Scena.Core
    core.initialize()
    core.skipToPage(1)

    core.skipToPrevPage()
    expect(core.getCurrentIndex()).toBe(0)

  it "skips to first page", ->
    core = new Scena.Core
    core.initialize()
    core.skipToFirstPage()
    expect(core.getCurrentIndex()).toBe(0)

  it "skips to last page", ->
    core = new Scena.Core
    core.initialize()
    core.skipToLastPage()
    expect(core.getCurrentIndex()).toBe(2)

Append a test of the core.coffee

Append a test of the core.coffee


define [
    'underscore'
    'marionette'
    '../core'
    '../base'
    '../query'
    'tpl!templates/workflows/workspace.html'
], (_, Marionette, c, base, query, templates...) ->

    templates = _.object ['workspace'], templates

    class WorkspaceWorkflow extends Marionette.Layout
        className: 'workspace-workflow'

        template: templates.workspace

        regions:
            queries: '.query-region'
            queryModal: '.create-query-modal'

        regionViews:
            queries: query.QueryList

        initialize: ->
            @data = {}
            if (@data.queries = @options.queries)
                throw new Error 'queries collection required'

        onRender: ->
            @queries.show new @regionViews.queries
                collection: @data.queries
                queryModalRegion: @queryModal

    { WorkspaceWorkflow }

define [
    'underscore'
    'marionette'
    '../core'
    '../base'
    '../query'
    'tpl!templates/workflows/workspace.html'
], (_, Marionette, c, base, query, templates...) ->

    templates = _.object ['workspace'], templates

    class WorkspaceWorkflow extends Marionette.Layout
        className: 'workspace-workflow'

        template: templates.workspace

        regions:
            queries: '.query-region'
            queryModal: '.create-query-modal'

        regionViews:
            queries: query.QueryList

        initialize: ->
            @data = {}
            if not (@data.queries = @options.queries)
                throw new Error 'queries collection required'

        onRender: ->
            @queries.show new @regionViews.queries
                collection: @data.queries
                queryModalRegion: @queryModal

    { WorkspaceWorkflow }

Add missing negation to data assertion in WorkspaceWorkflow


# Grab a few objects that we'll need
popen = require('child_process').exec
path = require('path')
platform = require('os').platform

# Open a terminal and run dub
dub_build = ->
  # Run the terminal app
  cmdline = "osascript -e \'tell application \"Terminal\" to do script \"cd #{atom.project.path}; dub build\"\'"
  popen cmdline

# Open a terminal and run the app with dub
dub_run = ->
  # Run the terminal app
  cmdline = "osascript -e \'tell application \"Terminal\" to do script \"cd #{atom.project.path}; dub run\"\'"
  popen cmdline

module.exports =
  # Bind workspace command to run_dub
  activate: (state) ->
    atom.workspaceView.command "d-struct:dub-build", => @dubbuild()
    atom.workspaceView.command "d-struct:dub-run", => @dubrun()
  dubbuild: ->
    dub_build()
  dubrun: ->
    dub_run()

# Grab a few objects that we'll need
popen = require('child_process').exec
platform = require('os').platform

# TODO: linux support still needs to be added
commands =
    build:
        win32: "start cmd /k \"cd #{atom.project.getPaths().shift()} && dub run\""
        darwin: "osascript -e \'tell application \"Terminal\" to do script \"cd #{atom.project.getPaths().shift()}; dub run\"\'"
    run:
        win32: "start cmd /k \"cd \"#{atom.project.getPaths().shift()}\" && dub build\""
        darwin: "osascript -e \'tell application \"Terminal\" to do script \"cd #{atom.project.getPaths().shift()}; dub build\"\'"

# Open a terminal and run dub
dub_build = ->
  # Run the terminal app
  popen commands.build[platform()] if platform() of commands.build

# Open a terminal and run the app with dub
dub_run = ->
  # Run the terminal app
  popen commands.run[platform()] if platform() of commands.run

module.exports =
  # Bind workspace command to run_dub
  activate: (state) ->
    atom.workspaceView.command "d-struct:dub-build", => @dubbuild()
    atom.workspaceView.command "d-struct:dub-run", => @dubrun()
  dubbuild: ->
    dub_build()
  dubrun: ->
    dub_run()

Make the dub commands play nice with Windows.

* The dub build/run commands are now compatible with Windows as well as OS X.
* Fix code to get project path.
* Remove the path import since it's not needed.


'menu': [
  {
    'label': 'Packages'
    'submenu': [
      'label': 'Symbols'
      'submenu': [
        { 'label': 'File Symbols', 'command': 'symbols-view:toggle-file-symbols' }
        { 'label': 'Project Symbols', 'command': 'symbols-view:toggle-project-symbols' }
      ]
    ]
  }
]

'context-menu':
  '.overlayer': [
    { 'label': 'Go to Declaration', 'command': 'symbols-view:go-to-declaration' }
  ]

'menu': [
  {
    'label': 'Packages'
    'submenu': [
      'label': 'Symbols'
      'submenu': [
        { 'label': 'File Symbols', 'command': 'symbols-view:toggle-file-symbols' }
        { 'label': 'Project Symbols', 'command': 'symbols-view:toggle-project-symbols' }
      ]
    ]
  }
]

'context-menu':
  'atom-text-editor': [
    { 'label': 'Go to Declaration', 'command': 'symbols-view:go-to-declaration' }
  ]

Add context menu to atom-text-editor selector


MANAGED_VMS = 'Managed VMs'

module.exports = (stacks = []) ->

  stacks.sort (a, b) ->

    return  1  if a.title is MANAGED_VMS
    return -1  if b.title is MANAGED_VMS

    return new Date(a.meta.createdAt) - new Date(b.meta.createdAt)

isManagedVMStack = require './isManagedVMStack'

module.exports = (stacks = []) ->

  stacks.sort (a, b) ->

    return  1  if isManagedVMStack a
    return -1  if isManagedVMStack b

    return new Date(a.meta.createdAt) - new Date(b.meta.createdAt)

Refactor sort environment stacks utility


ua = require('universal-analytics')

visitor = ua('UA-49535937-3')

module.exports = (req, res, next) ->
  eventParams =
    ec: 'API Request'                        # category
    ea: req.get('Referrer') || 'no referrer' # action
    el: req.ip                               # label
    dp: req.url                              # page path

  visitor.event(eventParams).send()
  next()

ua = require('universal-analytics')

module.exports = (req, res, next) ->
  visitor = ua('UA-49535937-3')

  eventParams =
    ec: 'API Request'                        # category
    ea: req.get('Referrer') || 'no referrer' # action
    el: req.ip                               # label
    dp: req.url                              # page path

  visitor.event(eventParams).send()
  next()

Use different visitor per event

Use different visitor per event

Instantiate a 'visitor' on every API request, since GA only tracks 500 events per visitor/session per day.


fsUtils = require 'fs-utils'

### Internal ###
module.exports =
class Package
  @build: (path) ->
    TextMatePackage = require 'text-mate-package'
    AtomPackage = require 'atom-package'

    if TextMatePackage.testName(path)
      new TextMatePackage(path)
    else
      new AtomPackage(path)

  @load: (path, options) ->
    pack = @build(path)
    pack.load(options)
    pack

  name: null
  path: null

  constructor: (@path) ->
    @name = fsUtils.base(@path)

{basename} = require 'path'

### Internal ###
module.exports =
class Package
  @build: (path) ->
    TextMatePackage = require 'text-mate-package'
    AtomPackage = require 'atom-package'

    if TextMatePackage.testName(path)
      new TextMatePackage(path)
    else
      new AtomPackage(path)

  @load: (path, options) ->
    pack = @build(path)
    pack.load(options)
    pack

  name: null
  path: null

  constructor: (@path) ->
    @name = basename(@path)

Use path.basename() instead of fsUtils.base()


define [
  "chaplin"
  "models/posts"
  "views/posts"
], (Chaplin, Posts, PostsView) ->
  "use strict"

  class PostsController extends Chaplin.Controller

    show: (params) ->
      # Unescape matched parts of url.
      # Why the hell chaplin doesn't do it by himself?
      # XXX: Seems like what we should use encodeURIComponent
      # and then decode it for each url parameter like user or id
      # but we hope that they will not contain "bad" characters
      # in future.
      params.tag = decodeURIComponent params.tag if params.tag?
      params.club = decodeURIComponent params.club if params.club?

      query = use_bl: 1
      if params.user?
        query.user = params.user
        query.tag = params.tag if params.tag?
        params.title = "@#{params.user}"
      else if params.club?
        query.club = params.club
        params.title = "!#{params.club}"
      else if params.tag?
        query.tag = params.tag
        params.title = "*#{params.tag}"

      @collection = new Posts [], id: params.id, query: query
      @view = new PostsView collection: @collection, pageble: params.pageble
      @adjustTitle params.title

define [
  "chaplin"
  "models/posts"
  "views/posts"
], (Chaplin, Posts, PostsView) ->
  "use strict"

  class PostsController extends Chaplin.Controller

    show: (params) ->
      # Unescape matched parts of url.
      # Why the hell chaplin doesn't do it by himself?
      # XXX: Seems like what we should use encodeURIComponent
      # and then decode it for each url parameter like user or id
      # but we hope that they will not contain "bad" characters
      # in future.
      params.tag = decodeURIComponent params.tag if params.tag?
      params.club = decodeURIComponent params.club if params.club?

      query = use_bl: 1
      if params.user?
        query.user = params.user
        query.tag = params.tag if params.tag?
        params.title = "@#{params.user}"
      else if params.club?
        query.club = params.club
        params.title = "!#{params.club}"
      else if params.tag?
        query.tag = params.tag
        params.title = "*#{params.tag}"
      else
        query = {}

      @collection = new Posts [], id: params.id, query: query
      @view = new PostsView collection: @collection, pageble: params.pageble
      @adjustTitle params.title

Disable use_bl for /top request

Disable use_bl for /top request


should = require 'should'
environment = require './shared/environment'
Impromptu = require '../lib/impromptu'
path = require 'path'
exec = require('child_process').exec

describe 'Config File', ->
  impromptu = new Impromptu

  after (done) ->
    tempDir = path.dirname Impromptu::compiledPrompt
    exec "rm -rf #{tempDir}", ->
      done()

  tu = null

  it 'should create an instance of Impromptu', ->
    tu = new Impromptu()
    should.exist tu

  it 'should load the config file', ->
    impromptu.load()
    impromptu.prompt._orderedSections.length.should.be.ok

  it 'should build the prompt', (done) ->
    impromptu.prompt.build (err, prompt) ->
      prompt.should.equal '\u001b[42m\u001b[37m user@host \u001b[0m\u001b[44m\u001b[37m ~/path/to/impromptu \u001b[0m'
      done()

should = require 'should'
environment = require './shared/environment'
Impromptu = require '../lib/impromptu'
path = require 'path'
exec = require('child_process').exec

describe 'Config File', ->
  impromptu = new Impromptu

  after (done) ->
    tempDir = path.dirname impromptu.path.compiled
    exec "rm -rf #{tempDir}", ->
      done()

  tu = null

  it 'should create an instance of Impromptu', ->
    tu = new Impromptu()
    should.exist tu

  it 'should load the config file', ->
    impromptu.load()
    impromptu.prompt._orderedSections.length.should.be.ok

  it 'should build the prompt', (done) ->
    impromptu.prompt.build (err, prompt) ->
      prompt.should.equal '\u001b[42m\u001b[37m user@host \u001b[0m\u001b[44m\u001b[37m ~/path/to/impromptu \u001b[0m'
      done()

Update compiled prompt path to use new variable name.


should = chai.should()

describe 'MyThing', ->
  it 'should do stuff', ->
    'this thing'.should.not.equal('this thing')

should = chai.should()

describe 'MyThing', ->
  it 'should do stuff', ->
    'this thing'.should.equal('this thing')

Revert "Purposefully fail test - TravisCI integration test"

This reverts commit c0d68c398d40c19d2dee9fb7aa50626ee2d79545.


_ = require 'underscore'

module.exports = class BunyanMongo
  queue: []

  collection: null

  options:
    collection_name: 'logs'
    capped: true
    size: 32 * 1024 * 1024

  constructor: (options = {}) ->
    _.extend @, options

  setDB: (db) ->
    options = _.pick @options, 'capped', 'size'

    db.createCollection @options.collection_name, options, (err, collection) =>
      throw err if err
      @collection = collection
      @dequeueRecords()

  dequeueRecords: ->
    while @queue.length
      @write @queue.shift()

  write: (data) ->
    if @collection
      @collection.insert data, (err) ->
        console.error err if err
    else
      @queue.push data

_ = require 'underscore'

module.exports = class BunyanMongo
  queue: []

  collection: null

  options:
    collection_name: 'logs'
    capped: true
    size: 32 * 1024 * 1024

  constructor: (options = {}) ->
    _.extend @options, options

  setDB: (db) ->
    options = _.pick @options, 'capped', 'size'

    db.createCollection @options.collection_name, options, (err, collection) =>
      throw err if err
      @collection = collection
      @dequeueRecords()

  dequeueRecords: ->
    while @queue.length
      @write @queue.shift()

  write: (data) ->
    if @collection
      @collection.insert data, (err) ->
        console.error err if err
    else
      @queue.push data

Use options passed in constructor

Use options passed in constructor


# Keybindings require three things to be fully defined: A selector that is
# matched against the focused element, the keystroke and the command to
# execute.
#
# Below is a basic keybinding which registers on all platforms by applying to
# the root workspace element.

# For more detailed documentation see
# https://atom.io/docs/latest/behind-atom-keymaps-in-depth
'atom-workspace':
  'cmd-shift-j': 'import-js:import'
  'cmd-shift-k': 'import-js:goto'
  'cmd-shift-i': 'import-js:fix-imports'

# TODO(Joe): I have no idea if these keybindings are any good and didn't give
# them much thought at all, so we should definitely improve this before
# shipping.

Clean up atom keymaps file

Clean up atom keymaps file

This had a bunch of comments in it that are no longer needed.


jQuery ->
  $('.modal .validate').on 'click', ->
    $modal = $(this).closest('.modal')
    $form  = $modal.find('form')
    if $form.length > 0
      $form.first().submit()
    else
      $modal.modal('hide')

  #display 'loading...' before some data-remote=true links
  $('#top-menu [data-remote="true"], .postit-link [data-remote="true"]').on 'click', ->
    $('#loading').fadeIn(150)
  #and hide it..
  $('.modal').on 'shown', ->
    $('#loading').fadeOut(150)

jQuery ->
  $('.modal .validate').on 'click', ->
    $modal = $(this).closest('.modal')
    $form  = $modal.find('form')
    if $form.length > 0
      $form.first().submit()
    else
      $modal.modal('hide')

  #display 'loading...' before some data-remote=true links
  $('#top-menu [data-remote="true"], .postit-link[data-remote="true"]').on 'click', ->
    $('#loading').fadeIn(150)
  #and hide it..
  $('.modal').on 'shown', ->
    $('#loading').fadeOut(150)

Fix typo in previous commit

Fix typo in previous commit


class RateLimiter
  constructor: (@expiry=10*60)->
    @data = {}

  okay: (key)=>
    check_time = Math.round(new Date().getTime() / 1000)
    @clearExpiredKeys(check_time) # Calling here so we don't leak ram by holding expired keys forever
    check = @data[key]

    if @data[key]
      return false
    else
      @data[key] = check_time + @expiry
      return true

  clearExpiredKeys: (check_time)=>
    for key, time of @data
      if time < check_time
        console.log "removing #{key} since #{time} < #{check_time}"
        #delete @data[key]

module.exports = RateLimiter

class RateLimiter
  constructor: (@expiry=10*60)->
    @data = {}

  okay: (key)=>
    check_time = Math.round(new Date().getTime() / 1000)
    @clearExpiredKeys(check_time) # Calling here so we don't leak ram by holding expired keys forever
    check = @data[key]

    if @data[key]
      return false
    else
      @data[key] = check_time + @expiry
      return true

  clearExpiredKeys: (check_time)=>
    for key, time of @data
      if time < check_time
        #console.log "removing #{key} since #{time} < #{check_time}"
        delete @data[key]

module.exports = RateLimiter

Fix bug with rate limiter not clearing keys properly.


redis = require 'redis'

# Connect to redis
if process.env.REDIS_DB_URL
  parts = require("url").parse(process.env.REDIS_DB_URL)
  db = redis.createClient(parts.port, parts.hostname)
  db.auth(parts.auth.split(":")[1])  if parts.auth
else
  db = redis.createClient()

if process.env.NODE_ENV == 'test'
  # Select test database
  db.select(1)

determineShardSize = (next) ->
  return next(determineShardSize.SHARD_SIZE)  if determineShardSize.SHARD_SIZE
  db.config 'get', 'hash-max-zipmap-entries', (err, results) ->
    throw err  if err
    [key, size] = results  if results
    determineShardSize.SHARD_SIZE = (if size then Number(size) else 512)
    next(determineShardSize.SHARD_SIZE)

determineShardSize.SHARD_SIZE = null

db.shard = (command, key, id, args...) ->
  if command[0] != 'h'
    throw new Error('Sharding does not work with non-hashes.')
  id = Number(id)
  determineShardSize (size) ->
    [division, remainder] = [ Math.floor(id / size), (id % size) ]
    key = [ key, division ].join(':')
    db[command](key, remainder, args...)

# Export database variable
module.exports = db

redis = require 'redis'

# Connect to redis
if process.env.REDIS_DB_URL
  parts = require("url").parse(process.env.REDIS_DB_URL)
  db = redis.createClient(parts.port, parts.hostname)
  db.auth(parts.auth.split(":")[1])  if parts.auth
else
  db = redis.createClient()

if process.env.NODE_ENV == 'test'
  # Select test database
  db.select(1)

db.shard = (command, key, id, args...) ->
  if command[0] != 'h'
    throw new Error('Sharding does not work with non-hashes.')
  [id, size] = [Number(id), 512]
  [division, remainder] = [ Math.floor(id / size), (id % size) ]
  key = [ key, division ].join(':')
  db[command](key, remainder, args...)

# Export database variable
module.exports = db

Use hardcoded number for shards.

Use hardcoded number for shards.


# Your snippets
#
# Atom snippets allow you to enter a simple prefix in the editor and hit tab to
# expand the prefix into a larger code block with templated values.
#
# You can create a new snippet in this file by typing "snip" and then hitting
# tab.
#
# An example CoffeeScript snippet to expand log to console.log:
#
# '.source.coffee':
#   'Console log':
#     'prefix': 'log'
#     'body': 'console.log $1'
#
# This file uses CoffeeScript Object Notation (CSON).
# If you are unfamiliar with CSON, you can read more about it here:
# https://github.com/bevry/cson#what-is-cson

'.source.elm':
  'Doc Comment':
    'prefix': 'doc'
    'body': """
      {-| $1
      -}
    """

Add Elm Doc Comment Snippet

Add Elm Doc Comment Snippet


gulp = require('gulp')
gutil = require('gulp-util')

coffee = require('gulp-coffee')
gulp.task 'coffee', ->
  gulp.src('src/*.coffee')
    .pipe(coffee({bare: true}).on('error', gutil.log))
    .pipe(gulp.dest('./dist/'))

webserver = require('gulp-webserver')
gulp.task 'serve', ->
  gulp.src([
    'bower_components'
    'demo'
    'dist'
  ])
    .pipe webserver
      livereload: true
      open: true

gulp.task('default', ['coffee'])

gulp = require('gulp')
gutil = require('gulp-util')

coffee = require('gulp-coffee')
gulp.task 'coffee', ->
  gulp.src('src/*.coffee')
    .pipe(coffee({bare: true}).on('error', gutil.log))
    .pipe(gulp.dest('./dist/'))

webserver = require('gulp-webserver')
gulp.task 'serve', ->
  gulp.src([
    'bower_components'
    'demo'
    'dist'
  ])
    .pipe webserver
      livereload: true
      open: true

gulp.task 'watch', ->
  gulp.watch('src/*.coffee', ['coffee'])

gulp.task('default', ['coffee', 'serve', 'watch']);

Add source file watch task

Add source file watch task


#= require templates/index
#= require_tree templates
#= require_tree models
#= require_tree collections
#= require_tree views

socket = io.connect('http://localhost')
socket.on 'connect', ->
  socket.emit 'adduser', prompt("What's your name?"), ->
    socket.on 'updatechat', (username, data) ->
      $("#messages").append("<p>#{username}: #{data}</p>")

    # Attach events to DOM
    $(document).on 'keyup', '#chat', (e) ->
      if e.which == 13
        socket.emit 'sendchat', $(this).val()
        $(this).val('')

    socket.on 'start battle', (battleId) ->
      $(document).on 'click', 'button', ->
        socket.emit 'send move', battleId, $(this).text()

$ ->
  $builder = $('.builder')
  pokemon = new Team(({name: "Bulbasaur"}  for x in [0...6]))
  builderView = new TeamBuilderView(el: $builder, collection: pokemon)
  builderView.render()

#= require templates/index
#= require_tree templates
#= require_tree models
#= require_tree collections
#= require_tree views

socket = io.connect(window.location.origin)
socket.on 'connect', ->
  socket.emit 'adduser', prompt("What's your name?"), ->
    socket.on 'updatechat', (username, data) ->
      $("#messages").append("<p>#{username}: #{data}</p>")

    # Attach events to DOM
    $(document).on 'keyup', '#chat', (e) ->
      if e.which == 13
        socket.emit 'sendchat', $(this).val()
        $(this).val('')

    socket.on 'start battle', (battleId) ->
      $(document).on 'click', 'button', ->
        socket.emit 'send move', battleId, $(this).text()

$ ->
  $builder = $('.builder')
  pokemon = new Team(({name: "Bulbasaur"}  for x in [0...6]))
  builderView = new TeamBuilderView(el: $builder, collection: pokemon)
  builderView.render()

Use window's origin for connecting socket.io.


{div, table, thead, tbody, tr, th, td} = React.DOM

table_headers = [
	"Team"
	"Win %"
	"Wins"
	"Losses"
	"Ties"
]

@TeamTable = React.createClass
	propTypes:
		teams: React.PropTypes.array

	calculateWinPercentage: (team) ->
		"#{Math.round((team.wins.value / (team.wins.value + team.losses.value + team.ties.value)) * 10000) / 100} %"

	render: ->
		div className: "league--table-container",
			table className: "league--table",
				thead className: "league--table-head",
					tr className: "league--table-headers",
						for header in table_headers
							th
								key: header
								className: "league--table-header"
								header

				tbody className: "league--table-body",
					for team in @props.teams
						tr
							key: team["name"]
							className: "league--table-row"

							td className: "league--table-cell", team["name"]
							td className: "league--table-cell",
								@calculateWinPercentage(team)
							td className: "league--table-cell", team["wins"]["value"]
							td className: "league--table-cell", team["losses"]["value"]
							td className: "league--table-cell", team["ties"]["value"]

{div, table, thead, tbody, tr, th, td, p} = React.DOM

table_headers = [
	"Team"
	"Win %"
	"Wins"
	"Losses"
	"Ties"
]

@TeamTable = React.createClass
	propTypes:
		teams: React.PropTypes.array

	calculateWinPercentage: (team) ->
		"#{Math.round((team.wins.value / (team.wins.value + team.losses.value + team.ties.value)) * 10000) / 100} %"

	render: ->
		div className: "league--table-container",
			p className: "league--table-disclaimer",
				"Standings from 2011 through 2015."
			table className: "league--table",
				thead className: "league--table-head",
					tr className: "league--table-headers",
						for header in table_headers
							th
								key: header
								className: "league--table-header"
								header

				tbody className: "league--table-body",
					for team in @props.teams
						tr
							key: team["name"]
							className: "league--table-row"

							td className: "league--table-cell", team["name"]
							td className: "league--table-cell",
								@calculateWinPercentage(team)
							td className: "league--table-cell", team["wins"]["value"]
							td className: "league--table-cell", team["losses"]["value"]
							td className: "league--table-cell", team["ties"]["value"]

Add seasons accounted for disclaimer above team table


exports.findMainGraph = (project) ->
  return null unless project.graphs.length
  if project.main
    # Ensure currently set main graph exists
    for graph in project.graphs
      return project.main if graph.properties.id is project.main
  # No 'main' graph sent, see if we can make a smart choice
  for graph in project.graphs
    return graph.properties.id if graph.name is 'main'
    return graph.properties.id if graph.properties.main
  return null

exports.getProjectHash = (project, callback) ->
  unless project.graphs.length
    if project.components.length
      # No graphs in this project, but there are components
      callback null, [
        'project'
        project.id
        'component'
        project.components[0].name
      ]
      return
    setTimeout ->
      # Wait for graphs to be populated
      if not project.graphs.length and not project.components.length
        return callback new Error "Project #{project.id} has no graphs or components"
      exports.getProjectHash project, callback
    , 100
    return
  # Open main graph, or the first graph
  main = project.main or project.graphs[0].properties.id
  unless main
    return callback new Error "Unable find a main graph for project #{project.id}"
  callback null, [
    'project'
    project.id
    main
  ]
  return

exports.findMainGraph = (project) ->
  return null unless project.graphs.length
  if project.main
    # Ensure currently set main graph exists
    for graph in project.graphs
      return project.main if graph.properties.id is project.main
  # No 'main' graph sent, see if we can make a smart choice
  for graph in project.graphs
    return graph.properties.id if graph.name is 'main'
    return graph.properties.id if graph.properties.main
  # No suitable graph found, use first
  return project.graphs[0].properties.id

exports.getProjectHash = (project, callback) ->
  unless project.graphs.length
    if project.components.length
      # No graphs in this project, but there are components
      callback null, [
        'project'
        project.id
        'component'
        project.components[0].name
      ]
      return
    setTimeout ->
      # Wait for graphs to be populated
      if not project.graphs.length and not project.components.length
        return callback new Error "Project #{project.id} has no graphs or components"
      exports.getProjectHash project, callback
    , 100
    return
  # Open main graph, or the first graph
  main = project.main or project.graphs[0].properties.id
  unless main
    return callback new Error "Unable find a main graph for project #{project.id}"
  callback null, [
    'project'
    project.id
    main
  ]
  return

Set first graph as main if no other suitable is found


window.coviolations ?=
    views: {}
    models: {}

$ ->
    NProgress.start()
    NProgress.inc()

    app = window.coviolations
    waitRendering = 2

    renderFinished = =>
        NProgress.inc()
        waitRendering -= 1
        if waitRendering == 0
            NProgress.done()

    renderProjects = =>
        projectCollection = new app.models.UserProjectCollection
        projectCollection.fetch
            data:
                limit: 0
            success: (collection) =>
                if collection.meta.total_count
                    projectView = new app.views.ManageProjectsView
                        el: $('.js-enabled-projects')
                        collection: collection
                    projectView.on 'renderFinished', $.proxy renderFinished, @

                    projectView.render()
                else
                    $('.js-enabled-projects td').html 'No projects found'

    renderProjects()
    window.push.on 'project', =>
        renderProjects()

    renderTasks = =>
        taskCollection = new app.models.TaskCollection()
        taskCollection.fetch
            data:
                limit: 20
                with_violations: true
                self: true
            success: (collection) ->
                taskView = new app.views.TaskLineListView
                    el: $('.js-last-tasks')
                    collection: collection
                    showProjectName: true
                taskView.on 'renderFinished', $.proxy renderFinished, @
                taskView.render()
    renderTasks()
    window.push.on 'task', =>
        renderTasks()

    prettyPrint()

window.coviolations ?=
    views: {}
    models: {}

$ ->
    NProgress.start()
    NProgress.inc()

    app = window.coviolations
    waitRendering = 2

    renderFinished = =>
        NProgress.inc()
        waitRendering -= 1
        if waitRendering <= 0
            NProgress.done()

    renderProjects = =>
        projectCollection = new app.models.UserProjectCollection
        projectCollection.fetch
            data:
                limit: 0
            success: (collection) =>
                if collection.meta.total_count
                    projectView = new app.views.ManageProjectsView
                        el: $('.js-enabled-projects')
                        collection: collection
                    projectView.on 'renderFinished', $.proxy renderFinished, @

                    projectView.render()
                else
                    $('.js-enabled-projects td').html 'No projects found'

    renderProjects()
    window.push.on 'project', =>
        renderProjects()

    renderTasks = =>
        taskCollection = new app.models.TaskCollection()
        taskCollection.fetch
            data:
                limit: 20
                with_violations: true
                self: true
            success: (collection) ->
                taskView = new app.views.TaskLineListView
                    el: $('.js-last-tasks')
                    collection: collection
                    showProjectName: true
                taskView.on 'renderFinished', $.proxy renderFinished, @
                taskView.render()
    renderTasks()
    window.push.on 'task', =>
        renderTasks()

    prettyPrint()

Fix progress bar with push

Fix progress bar with push


Pencil = require './pencil'
{createShape} = require '../core/shapes'

module.exports = class Eraser extends Pencil

  name: 'Eraser'
  iconName: 'eraser'

  constructor: () ->
    @strokeWidth = 10

  makePoint: (x, y, lc) ->
    createShape('Point', {x, y, size: @strokeWidth, color: '#000'})
  makeShape: -> createShape('ErasedLinePath')

Pencil = require './Pencil'
{createShape} = require '../core/shapes'

module.exports = class Eraser extends Pencil

  name: 'Eraser'
  iconName: 'eraser'

  constructor: () ->
    @strokeWidth = 10

  makePoint: (x, y, lc) ->
    createShape('Point', {x, y, size: @strokeWidth, color: '#000'})
  makeShape: -> createShape('ErasedLinePath')

Fix Pencil import on case sensitive filesystems


# use the first element that is "scrollable"
# http://css-tricks.com/snippets/jquery/smooth-scrolling/
scrollableElement = (els) ->
  for el in arguments
    $scrollElement = $(el)

    if $scrollElement.scrollTop() > 0
      return el
    else
      $scrollElement.scrollTop(1)
      isScrollable = $scrollElement.scrollTop() > 0
      $scrollElement.scrollTop(0)
      if isScrollable
        return el

  return [];

$(document).ready ->
  $('.footer-navigation a').tipsy
    gravity: 'w'
    fade: true
    opacity: 0.9

  $('body').on 'click', '.get-started-now', ->
    $targetElem = $("section.plans-and-pricing")
    targetTop = $targetElem.offset().top
    scrollableElem = scrollableElement('html', 'body')

    $(scrollableElem).animate({scrollTop: targetTop}, 600)

# use the first element that is "scrollable"
# http://css-tricks.com/snippets/jquery/smooth-scrolling/
scrollableElement = (els) ->
  for el in arguments
    $scrollElement = $(el)

    if $scrollElement.scrollTop() > 0
      return el
    else
      $scrollElement.scrollTop(1)
      isScrollable = $scrollElement.scrollTop() > 0
      $scrollElement.scrollTop(0)
      if isScrollable
        return el

  return [];

$(document).ready ->
  $('.footer-navigation a')
    .click(false)
    .tipsy
      gravity: 'w'
      fade: true
      opacity: 0.9

  $('body').on 'click', '.get-started-now', ->
    $targetElem = $("section.plans-and-pricing")
    targetTop = $targetElem.offset().top
    scrollableElem = scrollableElement('html', 'body')

    $(scrollableElem).animate({scrollTop: targetTop}, 600)

Disable footer links that don't work.

Disable footer links that don't work.


Backbone.Factlink ||= {}

class Backbone.Factlink.LocalStorageTextModel extends Backbone.Model
  constructor: (attributes, options) ->
    super

    @set 'text', localStorage?[options.key] ? ''
    @on 'change:text', (model, value) ->
      localStorage[options.key] = value if localStorage?

Backbone.Factlink ||= {}

class Backbone.Factlink.LocalStorageTextModel extends Backbone.Model
  constructor: (attributes, options) ->
    super

    @set 'text', sessionStorage?[options.key] ? ''
    @on 'change:text', (model, value) ->
      sessionStorage[options.key] = value if sessionStorage?

Use session storage instead of local storage

untested


kd = require 'kd'
KDObject = kd.Object
remote = require('app/remote').getInstance()

module.exports = class DatadogMetrics extends KDObject

  @buffer = kd.utils.dict()

  @collect = (name, state, count = 1) ->

    key = "#{name}:#{state}"
    @buffer[key] ?= 0
    @buffer[key] += count

  @send = ->

    return  if @inProgress

    keys = Object.keys @buffer
    return  unless keys.length

    @inProgress = yes

    metrics = kd.utils.dict()
    data = []

    for key in keys when (count = @buffer[key]) > 0
      metrics[key] = count
      data.push "#{key}:#{count}"

    return  unless data.length

    remote.api.DataDog.sendMetrics data, (err) =>
      return console.error 'Metrics:', err  if err

      for key in Object.keys metrics
        @buffer[key] -= metrics[key]

      @inProgress = no

  remote.once 'ready', ->

    do ->

      kd.utils.repeat 5 * 1000, -> DatadogMetrics.send()

kd = require 'kd'
KDObject = kd.Object
remote = require('app/remote').getInstance()

module.exports = class DatadogMetrics extends KDObject

  @buffer = kd.utils.dict()

  @collect = (name, state, count = 1) ->

    key = "#{name}:#{state}"
    @buffer[key] ?= 0
    @buffer[key] += count

  @send = ->

    return  if @inProgress

    keys = Object.keys @buffer
    return  unless keys.length

    @inProgress = yes

    metrics = kd.utils.dict()
    data = []

    for key in keys when (count = @buffer[key]) > 0
      metrics[key] = count
      data.push "#{key}:#{count}"

    return  unless data.length

    remote.api.DataDog.sendMetrics data, (err) =>
      return console.error 'Metrics:', err  if err

      for key in Object.keys metrics
        @buffer[key] -= metrics[key]

      @inProgress = no

  interval = null

  startInterval = ->

    return  if interval
    interval = kd.utils.repeat 5 * 1000, -> DatadogMetrics.send()

  stopInterval = ->

    kd.utils.killRepeat interval
    interval = null

  remote.on 'connected', ->

    return  if interval

    { mainController } = kd.singletons
    mainController.on 'AccountChanged', (account) ->

      if account.type is 'registered'
      then startInterval()
      else stopInterval()

  remote.on 'disconnected', stopInterval

Send metric data once client is logged in


Steam.FrameView = (_, _frame) ->
  createCompatibleModelItem = (model) ->
    key: model.key
    algorithm: model.model_algorithm
    category: model.model_category
    responseColumnName: model.response_column_name

  createColumnItem = (name) -> name: name

  loadCompatibleModels = ->
    _.switchToModels type: 'compatibleWithFrame', frameKey: _frame.key

  data: _frame
  key: _frame.key
  timestamp: _frame.creation_epoch_time_millis
  title: _frame.key
  columns: map _frame.column_names, createColumnItem
  columnCount: "(#{_frame.column_names.length})"
  compatibleModels: map _frame.compatible_models, createCompatibleModelItem
  compatibleModelsCount: _frame.compatible_models.length
  hasCompatibleModels: _frame.compatible_models.length > 0
  loadCompatibleModels: loadCompatibleModels
  isRawFrame: _frame.is_raw_frame
  parseUrl: "/2/Parse2.query?source_key=#{encodeURIComponent _frame.key}"
  dispose: ->
  template: 'frame-view'

Steam.FrameView = (_, _frame) ->
  createCompatibleModelItem = (model) ->
    key: model.key
    algorithm: model.model_algorithm
    category: model.model_category
    responseColumnName: model.response_column_name

  createColumnItem = (name) -> name: name

  loadCompatibleModels = ->
    _.switchToModels type: 'compatibleWithFrame', frameKey: _frame.key

  data: _frame
  key: _frame.key
  timestamp: _frame.creation_epoch_time_millis
  title: _frame.key
  columns: map _frame.column_names, createColumnItem
  columnCount: "(#{_frame.column_names.length})"
  compatibleModels: map _frame.compatible_models, createCompatibleModelItem
  compatibleModelsCount: "(#{_frame.compatible_models.length})"
  hasCompatibleModels: _frame.compatible_models.length > 0
  loadCompatibleModels: loadCompatibleModels
  isRawFrame: _frame.is_raw_frame
  parseUrl: "/2/Parse2.query?source_key=#{encodeURIComponent _frame.key}"
  dispose: ->
  template: 'frame-view'

Add parens for model counts

Add parens for model counts


"*":
  "exception-reporting":
    userId: "41cbedc9-3477-568d-cd05-8df535ad8564"
  welcome:
    showOnStartup: false
  core:
    projectHome: "/Users/joseangel/Workspace"
    themes: [
      "one-dark-ui"
      "monokai"
    ]
  editor:
    invisibles: {}
    tabLength: 4
  "one-dark-ui": {}
  "atom-beautify":
    _analyticsUserId: "b64fba17-eb1c-422a-9a39-23a43a36c424"
  emmet: {}
  "tree-view":
    hideIgnoredNames: true

Add ignore files to tree view

Add ignore files to tree view


show_download_menu = ->
  $("#download-menu").slideDown duration: 200
  setTimeout hide_download_menu, 20000
  $("#download-link").html "cancel"
  return
hide_download_menu = ->
  $("#download-menu").slideUp duration: 200
  $("#download-link").html "download"
  return
toggle_download_menu = ->
  if $("#download-link").html() is "download"
    show_download_menu()
  else
    hide_download_menu()
  return

@show_download_menu = ->
  $("#download-menu").slideDown duration: 200
  setTimeout hide_download_menu, 20000
  $("#download-link").html "cancel"
  return
@hide_download_menu = ->
  $("#download-menu").slideUp duration: 200
  $("#download-link").html "download"
  return
@toggle_download_menu = ->
  if $("#download-link").html() is "download"
    show_download_menu()
  else
    hide_download_menu()
  return

Fix a bug where the download menu wouldn't appear


NotificationPlugin = require "../../notification-plugin"

class PivotalTracker extends NotificationPlugin
  stacktraceLines = (stacktrace) ->
    ("#{line.file}:#{line.lineNumber} - #{line.method}" for line in stacktrace when line.inProject)

  @receiveEvent: (config, event) ->
    # Build the request
    params =
      "story[name]": "#{event.error.exceptionClass} in #{event.error.context}"
      "story[story_type]": "bug"
      "story[labels]": "bugsnag"
      "story[description]":
        """
        *#{event.error.exceptionClass}* in *#{event.error.context}*
        #{event.error.message if event.error.message}
        #{event.error.url}

        *Stacktrace:*
        #{stacktraceLines(event.error.stacktrace).join("\n")}
        """

    # Send the request to the url
    @request
      .post("http://www.pivotaltracker.com/services/v3/projects/#{config.projectId}/stories")
      .set("X-TrackerToken", config.apiToken)
      .type("form")
      .send(params)
      .buffer(true)
      .end((res) ->
        if res?.text
          console.log res.text
        else
          console.log "No response from pivotal! Status #{res.status}"
      );

module.exports = PivotalTracker

NotificationPlugin = require "../../notification-plugin"

class PivotalTracker extends NotificationPlugin
  stacktraceLines = (stacktrace) ->
    ("#{line.file}:#{line.lineNumber} - #{line.method}" for line in stacktrace when line.inProject)

  @receiveEvent: (config, event) ->
    # Build the request
    params =
      "story[name]": "#{event.error.exceptionClass} in #{event.error.context}"
      "story[story_type]": "bug"
      "story[labels]": "bugsnag"
      "story[description]":
        """
        *#{event.error.exceptionClass}* in *#{event.error.context}*
        #{event.error.message if event.error.message}
        #{event.error.url}

        *Stacktrace:*
        #{stacktraceLines(event.error.stacktrace).join("\n")}
        """

    # Send the request to the url
    @request
      .post("https://www.pivotaltracker.com/services/v3/projects/#{config.projectId}/stories")
      .set("X-TrackerToken", config.apiToken)
      .type("form")
      .send(params)
      .buffer(true)
      .end((res) ->
        if res && res.status != 200
          console.log "Status code: #{res.status}"
          console.log res.text || "No response from pivotal!"
      );

module.exports = PivotalTracker

Use https for pivotal plugin

Use https for pivotal plugin


# # Kissmetrics Batch

class BatchKissmetricsClient
  @HOST: 'api.kissmetrics.com'
  @HTTP_METHOD: 'POST'

  constructor: (@options) ->
    @queue = options.queue
    @_validate_queue @queue

  add: (timestamp, data) ->
    data.timestamp = timestamp
    @_transformData data

    @queue.add data

  get: ->
    @queue.get()

  process: ->
    queue = @get()

  _transformData: (data) ->
    data.identity = data._p
    delete data._p

    if data.type is 'record'
      data.event = data._n
      delete data._n

    if data.type is 'alias'
      data.alias = data._n
      delete data._n

    delete data.type

  _validate_queue: ->
    for required_method in ['add', 'get']
      unless typeof @queue[required_method] is 'function'
        throw new Error "Missing method: #{required_method}"

module.exports = BatchKissmetricsClient

# # Kissmetrics Batch

class BatchKissmetricsClient
  @HOST: 'api.kissmetrics.com'
  @HTTP_METHOD: 'POST'

  @process: (apiKey) ->
    queue = @get()

  constructor: (@options) ->
    @queue = options.queue
    @_validate_queue @queue

  add: (timestamp, data) ->
    data.timestamp = timestamp
    @_transformData data

    @queue.add data

  get: ->
    @queue.get()

  _transformData: (data) ->
    data.identity = data._p
    delete data._p

    if data.type is 'record'
      data.event = data._n
      delete data._n

    if data.type is 'alias'
      data.alias = data._n
      delete data._n

    delete data.type

  _validate_queue: ->
    for required_method in ['add', 'get']
      unless typeof @queue[required_method] is 'function'
        throw new Error "Missing method: #{required_method}"

module.exports = BatchKissmetricsClient

Make process() a static method on batch client

Accept api key argument


{expect} = require "chai"
utils = require "../../utils"

{Collections} = utils.require "base"

describe "step_interpolator_transform module", ->
  source = {start: 0, end: 10}
  target = {start: 20, end: 80}

  generate_jitter = ->
    Collections("Jitter").create
      width: 1
      center: 0
      distribution: 'uniform'

  describe "Jitter with uniform", ->
    transform = generate_jitter()
    transform.set('distribution', 'uniform')

    it "should average the fixed values", ->
      vals =  Array.apply(null, Array(10)).map(function(){return 5})
      rets = transform.v_compute(vals)

      add(a, b) ->
        return a+b

      thesum = rets.reduce(add, 0)
      thediff = (thesum/1000) - 5
      expect(thediff).to.be.below 0.01

  describe "Jitter with normal", ->
    transform = generate_jitter()
    transform.set('distribution', 'normal')

    it "should average the fixed values", ->
      vals =  Array.apply(null, Array(10)).map(function(){return 5})
      rets = transform.v_compute(vals)

      add(a, b) ->
        return a+b

      thesum = rets.reduce(add, 0)
      thediff = (thesum/1000) - 5
      expect(thediff).to.be.below 0.01

{expect} = require "chai"
utils = require "../../utils"

{Collections} = utils.require "base"

describe "step_interpolator_transform module", ->
  source = {start: 0, end: 10}
  target = {start: 20, end: 80}

  generate_jitter = ->
    Collections("Jitter").create
      width: 1
      center: 0
      distribution: 'uniform'

  describe "Jitter with uniform", ->
    transform = generate_jitter()
    transform.set('distribution', 'uniform')

    it "should average the fixed values", ->
      vals =  Array.apply(null, Array(10)).map ->
                5
      rets = transform.v_compute(vals)

      add(a, b) ->
        return a+b

      thesum = rets.reduce(add, 0)
      thediff = (thesum/1000) - 5
      expect(thediff).to.be.below 0.01

  describe "Jitter with normal", ->
    transform = generate_jitter()
    transform.set('distribution', 'normal')

    it "should average the fixed values", ->
      vals =  Array.apply(null, Array(10)).map ->
                5
      rets = transform.v_compute(vals)

      add(a, b) ->
        return a+b

      thesum = rets.reduce(add, 0)
      thediff = (thesum/1000) - 5
      expect(thediff).to.be.below 0.01

Put in correct form for the CoffeeScript to make JS


git = require '../git'
StatusView = require '../views/status-view'
Path = require 'path'

gitCheckoutCurrentFile = ->
  currentFile = atom.project.getRepo().relativize atom.workspace.getActiveEditor()?.getPath()
  git(
    ['checkout', currentFile],
    (data) -> new StatusView(type: 'success', message: data.toString())
  )

module.exports = gitCheckoutCurrentFile

git = require '../git'
StatusView = require '../views/status-view'
Path = require 'path'

gitCheckoutCurrentFile = ->
  currentFile = atom.project.getRepo().relativize atom.workspace.getActiveEditor()?.getPath()
  git(
    ['checkout', '--', currentFile],
    (data) -> new StatusView(type: 'success', message: data.toString())
  )

module.exports = gitCheckoutCurrentFile

Fix error if filename matches an existing branch


scaffolt = require 'scaffolt'
_s = require 'underscore.string'

module.exports = class Scaffold

  @generate: ->
    scaffold = new Scaffold
    scaffold.generate arguments...

  @destroy: ->
    scaffold = new Scaffold
    scaffold.destroy arguments...

  generate: (name = '', callback = ->) ->
    scaffolt @className(), name, callback

  destroy: (name = '', callback = ->) ->
    scaffolt @className(), name, {revert: yes}, callback

  className: ->
    _s.dasherize(@constructor.name).replace(/^-/, '')

scaffolt = require 'scaffolt'
_s = require 'underscore.string'

module.exports = class Scaffold

  @generate: ->
    scaffold = new @constructor
    scaffold.generate arguments...

  @destroy: ->
    scaffold = new @constructor
    scaffold.destroy arguments...

  generate: (name = '', callback = ->) ->
    scaffolt @className(), name, callback

  destroy: (name = '', callback = ->) ->
    scaffolt @className(), name, {revert: yes}, callback

  className: ->
    _s.dasherize(@constructor.name).replace(/^-/, '')

Fix issue in class methods for Scaffold

Fix issue in class methods for Scaffold


angular.module('doubtfire.admin.states.teachingperiods', [])

#
# Convenors of a unit(s) can see a list of all the units they convene
# in this view and make changes to those units.
#
# Users with an Administrator system role can create new units.
#
.config((headerServiceProvider) ->
  teachingPeriodsAdminViewStateData =
    url: "/admin/teachingperiods"
    views:
      main:
        controller: "AdministerTeachingPeriodsState"
        templateUrl: "admin/states/teachingperiods/teachingperiods.tpl.html"
    data:
      pageTitle: "_Teaching-Period Administration_"
      roleWhitelist: ['Admin']
  headerServiceProvider.state "admin/teachingperiods", teachingPeriodsAdminViewStateData
)
.controller("AdministerTeachingPeriodsState", ($scope, $state, $modal, ExternalName, currentUser, alertService, TeachingPeriod, TeachingPeriodSettingsModal) ->

  $scope.teachingPeriods = TeachingPeriod.query()

  # Table sort details
  $scope.sortOrder = "start_date"
  $scope.reverse = true

  # Pagination details
  $scope.currentPage = 1
  $scope.maxSize = 5
  $scope.pageSize = 15

  # Get the confugurable, external name of Doubtfire
  $scope.externalName = ExternalName

  # User settings/create modal
  $scope.showTeachingPeriodModal = (teachingPeriod) ->
    # If we're given a user, show that user, else create a new one
    teachingPeriodToShow = if teachingPeriod? then teachingPeriod else { }
    TeachingPeriodSettingsModal.show teachingPeriodToShow
)

angular.module('doubtfire.admin.states.teachingperiods', [])

#
# Users with an Administrator system role can create new Teaching Periods.
#
.config((headerServiceProvider) ->
  teachingPeriodsAdminViewStateData =
    url: "/admin/teachingperiods"
    views:
      main:
        controller: "AdministerTeachingPeriodsState"
        templateUrl: "admin/states/teachingperiods/teachingperiods.tpl.html"
    data:
      pageTitle: "_Teaching-Period Administration_"
      roleWhitelist: ['Admin']
  headerServiceProvider.state "admin/teachingperiods", teachingPeriodsAdminViewStateData
)
.controller("AdministerTeachingPeriodsState", ($scope, $state, $modal, ExternalName, currentUser, alertService, TeachingPeriod, TeachingPeriodSettingsModal) ->

  $scope.teachingPeriods = TeachingPeriod.query()

  # Table sort details
  $scope.sortOrder = "start_date"
  $scope.reverse = true

  # Pagination details
  $scope.currentPage = 1
  $scope.maxSize = 5
  $scope.pageSize = 15

  # Get the confugurable, external name of Doubtfire
  $scope.externalName = ExternalName

  # User settings/create modal
  $scope.showTeachingPeriodModal = (teachingPeriod) ->
    # If we're given a user, show that user, else create a new one
    teachingPeriodToShow = if teachingPeriod? then teachingPeriod else { }
    TeachingPeriodSettingsModal.show teachingPeriodToShow
)

FIX: Comment explaining teaching period state


{$$$, View} = require 'atom'

# Displays the keybindings for a package namespace
module.exports =
class PackageKeymapView extends View
  @content: ->
    @section =>
      @div class: 'section-heading icon icon-keyboard', 'Keybindings'
      @table outlet: 'keymapTable', class: 'package-keymap-table table native-key-bindings text', tabindex: -1, =>
        @thead =>
          @tr =>
            @th 'Keystroke'
            @th 'Command'
            @th 'Selector'
        @tbody outlet: 'keybindingItems'

  initialize: (namespace) ->
    for {command, keystroke, selector} in atom.keymap.getKeyBindings()
      continue unless command.indexOf("#{namespace}:") is 0

      @keybindingItems.append $$$ ->
        @tr class: 'package-keymap-item', =>
          @td class: 'keystroke', keystroke
          @td class: 'command', command
          @td class: 'selector', selector

    @hide() unless @keybindingItems.children().length > 0

{$$$, View} = require 'atom'

# Displays the keybindings for a package namespace
module.exports =
class PackageKeymapView extends View
  @content: ->
    @section =>
      @div class: 'section-heading icon icon-keyboard', 'Keybindings'
      @table class: 'package-keymap-table table native-key-bindings text', tabindex: -1, =>
        @thead =>
          @tr =>
            @th 'Keystroke'
            @th 'Command'
            @th 'Selector'
        @tbody outlet: 'keybindingItems'

  initialize: (namespace) ->
    for {command, keystroke, selector} in atom.keymap.getKeyBindings()
      continue unless command.indexOf("#{namespace}:") is 0

      @keybindingItems.append $$$ ->
        @tr =>
          @td keystroke
          @td command
          @td selector

    @hide() unless @keybindingItems.children().length > 0

Remove unused classes and outlets

Remove unused classes and outlets


[
	{
		title: ".atom"
		paths: [
			"~/.atom"
		]
		devMode: true
	}
	{
		title: ".files"
		paths: [
			"~/.files"
		]
		devMode: true
	}
	{
		title: "language-roff"
		paths: [
			"~/Labs/language-roff"
		]
		devMode: true
	}
	{
		title: "file-icons"
		paths: [
			"~/Labs/file-icons"
		]
		devMode: true
	}
	{
		title: "Atom-FS"
		paths: [
			"~/Labs/Atom-FS"
			"~/Labs/Utils"
		]
		devMode: true
	}
	{
		title: "Utils"
		paths: [
			"~/Labs/Utils"
		]
		devMode: true
	}
]

[
	{
		title: ".atom"
		paths: [
			"~/.atom"
		]
		devMode: true
	}
	{
		title: ".files"
		paths: [
			"~/.files"
		]
		devMode: true
	}
	{
		title: "language-roff"
		paths: [
			"~/Labs/language-roff"
		]
		devMode: true
	}
	{
		title: "file-icons"
		paths: [
			"~/Labs/file-icons"
		]
		devMode: true
	}
	{
		title: "Atom-FS"
		paths: [
			"~/Labs/Atom-FS"
		]
		devMode: true
	}
	{
		title: "Utils"
		paths: [
			"~/Labs/Utils"
		]
		devMode: true
	}
]

Remove `~/Labs/Utils` from Atom-FS's project entry


define [
  "jquery"
  "chaplin"
  "views/base/view"
  "lib/utils"
  "templates/menu"
], ($, Chaplin, View, utils, template) ->
  "use strict"

  class MenuView extends View

    el: "#menu"
    template: template
    autoRender: true
    events:
      "click #common_menu a": "navigate"
      "click #logout": "logout"

    initialize: ->
      super
      Chaplin.mediator.subscribe "!router:changeURL", (url) =>
        $("#common_menu>li").removeClass("active")
        a = $("#common_menu a[href='/#{url}']")
        if a.length
          a.parent().addClass("active")

    # Reload controller even if url was not changed
    navigate: (e) ->
      url = $(e.currentTarget).attr("href")
      utils.gotoUrl url
      # For some reason jQuery's event.preventDefault() doesn't work here
      false

    logout: (e) ->
      e.preventDefault()
      utils.clearAuth()
      @render()
      utils.gotoUrl "/"

    afterRender: ->
      super
      @$(".dropdown-toggle").dropdown()

define [
  "jquery"
  "chaplin"
  "views/base/view"
  "lib/utils"
  "templates/menu"
], ($, Chaplin, View, utils, template) ->
  "use strict"

  class MenuView extends View

    el: "#menu"
    template: template
    autoRender: true
    events:
      "click #common_menu a": "navigate"
      "click #logout": "logout"

    initialize: ->
      super
      @subscribeEvent "!router:changeURL", (url) ->
        $("#common_menu>li").removeClass("active")
        a = $("#common_menu a[href='/#{url}']")
        if a.length
          a.parent().addClass("active")

    # Reload controller even if url was not changed
    navigate: (e) ->
      url = $(e.currentTarget).attr("href")
      utils.gotoUrl url
      # For some reason jQuery's event.preventDefault() doesn't work here
      false

    logout: (e) ->
      e.preventDefault()
      utils.clearAuth()
      @render()
      utils.gotoUrl "/"

    afterRender: ->
      super
      @$(".dropdown-toggle").dropdown()

Use View's native subscribeEvent method

Use View's native subscribeEvent method

I will always read documentation
I will always read documentation
I will always read documentation


_ = require 'underscore'
Collection = require 'exercise/collection'
step = require '../../api/steps/4571/GET'

describe 'Exercise Collection', ->

  beforeEach ->
    @stepId = 4571
    @step = _.clone(step)
    Collection.quickLoad(@stepId, @step)

  describe 'getCurrentPanel', ->
    it 'returns free-response by default', ->
      expect(Collection.getCurrentPanel(@stepId)).equal('free-response')

    it 'returns review if correct answer is present', ->
      @step.correct_answer_id = 99
      Collection.quickLoad(@stepId, @step)
      expect(Collection.getCurrentPanel(@stepId)).equal('review')

    it 'returns multiple-choice if free response is present', ->
      @step.free_response = 'my best guess'
      Collection.quickLoad(@stepId, @step)
      expect(Collection.getCurrentPanel(@stepId)).equal('multiple-choice')

_ = require 'underscore'
Collection = require 'exercise/collection'
step = require '../../api/steps/4571/GET'

describe 'Exercise Collection', ->

  beforeEach ->
    @stepId = 4571
    @step = _.clone(step)
    Collection.quickLoad(@stepId, @step)

  describe 'getCurrentPanel', ->
    it 'returns free-response by default', ->
      expect(Collection.getCurrentPanel(@stepId)).equal('free-response')

    it 'returns review if content.questions is missing', ->
      @step.content = {}
      Collection.quickLoad(@stepId, @step)
      expect(Collection.getCurrentPanel(@stepId)).equal('review')

    it 'returns multiple-choice if free_response is present', ->
      @step.free_response = 'my best guess'
      Collection.quickLoad(@stepId, @step)
      expect(Collection.getCurrentPanel(@stepId)).equal('multiple-choice')

Fix testing for different states

Fix testing for different states


# See https://atom.io/docs/latest/hacking-atom-package-word-count#menus for more details
'context-menu':
  '.tree-view .directory > .header': [
    {
      label: 'SysAngular',
      submenu: [
        {
          label: 'Create Module',
          command: 'atom-sys-angular:generate-module'
        },
        {
          label: 'Create Controller',
          command: 'atom-sys-angular:generate-controller'
        },
        {
          label: 'Create Directive',
          command: 'atom-sys-angular:generate-directive'
        },
        {
          label: 'Create Service',
          command: 'atom-sys-angular:generate-service'
        }
      ]
    }
  ]

# See https://atom.io/docs/latest/hacking-atom-package-word-count#menus for more details
'context-menu':
  '.tree-view .directory > .header': [
    {
      label: 'SysAngular',
      submenu: [
        {
          label: 'Create Module',
          command: 'atom-sys-angular:generate-module'
        },
        {
          label: 'Create Controller',
          command: 'atom-sys-angular:generate-controller'
        },
        {
          label: 'Create Directive',
          command: 'atom-sys-angular:generate-directive'
        },
        {
          label: 'Create Route',
          command: 'atom-sys-angular:generate-route'
        },
        {
          label: 'Create Service',
          command: 'atom-sys-angular:generate-service'
        }
      ]
    }
  ]

Add 'Create Route' to menu

Add 'Create Route' to menu


React    = require 'react'
BS       = require 'react-bootstrap'

module.exports = React.createClass
  displayName: 'Icon'
  propTypes:
    type: React.PropTypes.string
    className: React.PropTypes.string
    tooltip: React.PropTypes.string

  render: ->
    classes = ['tutor-icon', 'fa', "fa-#{@props.type}"]
    classes.push(@props.className) if @props.className
    icon = <i className={classes.join(' ')} />

    if @props.tooltip
      tooltip = <BS.Tooltip>Useful for talking securely about students over email.</BS.Tooltip>
      <BS.OverlayTrigger placement='bottom' overlay={tooltip}>{icon}</BS.OverlayTrigger>
    else
      icon

React    = require 'react'
BS       = require 'react-bootstrap'

module.exports = React.createClass
  displayName: 'Icon'
  propTypes:
    type: React.PropTypes.string
    className: React.PropTypes.string
    tooltip: React.PropTypes.string
    tooltipProps: React.PropTypes.object

  getDefaultProps: ->
    tooltipProps: { placement: 'bottom' }

  render: ->
    classes = ['tutor-icon', 'fa', "fa-#{@props.type}"]
    classes.push(@props.className) if @props.className
    icon = <i className={classes.join(' ')} />

    if @props.tooltip
      tooltip = <BS.Tooltip>{@props.tooltip}</BS.Tooltip>
      <BS.OverlayTrigger {...@props.tooltipProps} overlay={tooltip}>{icon}</BS.OverlayTrigger>
    else
      icon

Fix so tooltip property is actually displayed


'atom-text-editor':
  'ctrl-y': 'editor:delete-line'
  'alt-j': 'find-and-replace:select-next'
  'alt-shift-J': 'find-and-replace:select-all'

Add some shortcuts to atom

Add some shortcuts to atom


{View} = require 'atom-space-pen-views'
SettingsPanel = require './settings-panel'

module.exports =
class GeneralPanel extends View
  @content: ->
    @div =>
      @form class: 'general-panel section', =>
        @div outlet: "loadingElement", class: 'alert alert-info loading-area icon icon-hourglass', "Loading settings"

  initialize: ->
    @loadingElement.remove()

    @subPanels = [
      new SettingsPanel('core', note: '''

These are Atom's core settings which affect behavior unrelated to text editing. Individual packages might have additional config settings of their own. Check individual package settings by clicking its package card in the Packages list.

      ''')

      new SettingsPanel('editor', note: '''

These config settings are related to text editing. Some of these settings can be overriden on a per-language basis. Check language settings by clicking the language's package card in the Packages list.

      ''')
    ]

    for subPanel in @subPanels
      @append(subPanel)

    @on 'click', '.packages-open', ->
      atom.workspace.open('atom://config/packages')

    return

  dispose: ->
    for subPanel in @subPanels
      subPanel.dispose()
    return

Use text note instead of alert

Use text note instead of alert


{CompositeDisposable} = require 'event-kit'

class PanelElement extends HTMLElement
  createdCallback: ->
    @subscriptions = new CompositeDisposable

  getModel: -> @model

  setModel: (@model) ->
    @appendChild(@model.getItemView())
    @subscriptions.add @model.onDidChangeVisible(@visibleChanged.bind(this))
    @subscriptions.add @model.onDidDestroy(@destroyed.bind(this))

  attachedCallback: ->
    @visibleChanged(@model.isVisible())

  visibleChanged: (visible) ->
    if visible
      @style.display = null
    else
      @style.display = 'none'

  destroyed: ->
    @subscriptions.dispose()
    @parentNode?.removeChild(this)

module.exports = PanelElement = document.registerElement 'atom-panel', prototype: PanelElement.prototype

{CompositeDisposable} = require 'event-kit'
{callAttachHooks} = require './space-pen-extensions'

class PanelElement extends HTMLElement
  createdCallback: ->
    @subscriptions = new CompositeDisposable

  getModel: -> @model

  setModel: (@model) ->
    view = @model.getItemView()
    @appendChild(view)
    callAttachHooks(view) # for backward compatibility with SpacePen views

    @subscriptions.add @model.onDidChangeVisible(@visibleChanged.bind(this))
    @subscriptions.add @model.onDidDestroy(@destroyed.bind(this))

  attachedCallback: ->
    @visibleChanged(@model.isVisible())

  visibleChanged: (visible) ->
    if visible
      @style.display = null
    else
      @style.display = 'none'

  destroyed: ->
    @subscriptions.dispose()
    @parentNode?.removeChild(this)

module.exports = PanelElement = document.registerElement 'atom-panel', prototype: PanelElement.prototype

Call the attach hooks after adding a view to a panel.


expect = require('chai').expect
parser = require '../dist/parser'

describe 'TagDataParser', ->
  describe '#parse()', ->
    it 'returns a tag data object', ->
      expect(parser.parse('ruby:2.1.2 rails:3.x,4.x')).to.deep.equal([
        {
          name: 'ruby'
          versions: ['2.1.2']
        }
        {
          name: 'rails'
          versions: ['3.x', '4.x']
        }
      ])

    it 'accepts version with spaces', ->
      expect(parser.parse('tag:version,\\ with\\ space')).to.deep.equal([
        {
          name: 'tag'
          versions: ['version', ' with space']
        }
      ])

    it 'accepts version with backslashes', ->
      expect(parser.parse('tag:back\\\\slash')).to.deep.equal([
        {
          name: 'tag'
          versions: ['back\\slash']
        }
      ])

expect = require('chai').expect
parser = require '../dist/parser'

describe 'TagDataParser', ->
  describe '#parse()', ->
    it 'returns a tag data object', ->
      expect(parser.parse('ruby:2.1.2 rails:3.x,4.x')).to.deep.equal([
        {
          name: 'ruby'
          versions: ['2.1.2']
        }
        {
          name: 'rails'
          versions: ['3.x', '4.x']
        }
      ])

    it 'accepts multi bytes tag name and versions', ->
      expect(parser.parse('タグ1 タグ2:バージョン1,バージョン2')).to.deep.equal([
        {
          name: 'タグ1'
          versions: []
        }
        {
          name: 'タグ2'
          versions: ['バージョン1', 'バージョン2']
        }
      ])

    it 'accepts version with spaces', ->
      expect(parser.parse('tag:version,\\ with\\ space')).to.deep.equal([
        {
          name: 'tag'
          versions: ['version', ' with space']
        }
      ])

    it 'accepts version with backslashes', ->
      expect(parser.parse('tag:back\\\\slash')).to.deep.equal([
        {
          name: 'tag'
          versions: ['back\\slash']
        }
      ])

Add test case for multi bytes chars

Add test case for multi bytes chars


fs = Npm.require 'fs'
path = Npm.require 'path'
sass = Npm.require 'node-sass'

share.MailerUtils =

  joinUrl: (base, path) ->
    # Remove any trailing slashes
    base = base.replace(/\/$/, '')

    # Add front slash if not exist already
    unless /^\//.test(path)
      path = '/' + path

    return base + path

  addDoctype: (html) ->
    '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n' + html

  readFile: (relativePathFromApp) ->
    file = path.join(process.env.PWD, relativePathFromApp)

    try
      return fs.readFileSync(file, encoding: 'utf8')
    catch ex
      throw new Meteor.Error 500, 'Could not find file: ' + file, ex.message

  toCSS: (scss) ->
    file = path.join(process.env.PWD, scss)

    try
      return sass.renderSync(file: file, sourceMap: false).css
    catch ex
      # ex is somehow a JSON string.
      e = JSON.parse(ex)
      console.error 'Sass failed to compile: ' + e.message
      console.error 'In ' + (e.file or scss)

fs = Npm.require 'fs'
path = Npm.require 'path'
sass = Npm.require 'node-sass'

if process.env.BUNDLE_PATH
  # BUNDLE_PATH = /var/www/app/bundle
  ROOT = path.join(process.env.BUNDLE_PATH, 'programs', 'server', 'assets', 'app')
else
  # PWD = /lookback-emailjobs/app
  ROOT = path.join(process.env.PWD, 'private')

share.MailerUtils =

  joinUrl: (base, path) ->
    # Remove any trailing slashes
    base = base.replace(/\/$/, '')

    # Add front slash if not exist already
    unless /^\//.test(path)
      path = '/' + path

    return base + path

  addDoctype: (html) ->
    '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n' + html

  readFile: (relativePathFromApp) ->
    file = path.join(ROOT, relativePathFromApp)

    try
      return fs.readFileSync(file, encoding: 'utf8')
    catch ex
      throw new Meteor.Error 500, 'Could not find file: ' + file, ex.message

  toCSS: (scss) ->
    file = path.join(ROOT, scss)

    try
      return sass.renderSync(file: file, sourceMap: false).css
    catch ex
      # ex is somehow a JSON string.
      e = JSON.parse(ex)
      console.error 'Sass failed to compile: ' + e.message
      console.error 'In ' + (e.file or scss)

Add support for reading BUNDLE_PATH env var, for Meteor app bundles


class FSFolder extends FSFile

  fetchContents:(callback)->

    # @emit "fs.fetchContents.started"
    # @kiteController.run
    #   toDo      : "ls"
    #   withArgs  :
    #     command : @path
    # , (err, response)=>
    #   if err
    #     warn err
    #     @emit "fs.fetchContents.finished", err
    #   else
    #     files = FSHelper.parseLsOutput [@path], response
    #     @emit "fs.fetchContents.finished", files
    #     callback? files

    @emit "fs.fetchContents.started"
    # a = Date.now()
    @kiteController.run
      withArgs  :
        command : "ls #{FSHelper.escapeFilePath @path} -LHlpva --group-directories-first --time-style=full-iso"
    , (err, response)=>
      # log "------------------------------------------------------------------"
      # log "l flag response in: #{Date.now()-a} msec."
      if err
        warn err
        @emit "fs.fetchContents.finished", err
      else
        files = FSHelper.parseLsOutput [@path], response
        @emit "fs.fetchContents.finished", files
        callback? files

  # forkRepoCommandMap = ->

  #   git : "git clone"
  #   svn : "svn checkout"
  #   hg  : "hg clone"

  # cloneRepo:(options, callback)->

  #   @kiteController.run "#{forkRepoCommandMap()[repoType]} #{repo} #{escapeFilePath getAppPath manifest}", (err, response)->

class FSFolder extends FSFile

  fetchContents:(callback)->

    # @emit "fs.fetchContents.started"
    # @kiteController.run
    #   toDo      : "ls"
    #   withArgs  :
    #     command : @path
    # , (err, response)=>
    #   if err
    #     warn err
    #     @emit "fs.fetchContents.finished", err
    #   else
    #     files = FSHelper.parseLsOutput [@path], response
    #     @emit "fs.fetchContents.finished", files
    #     callback? files

    @emit "fs.fetchContents.started"
    # a = Date.now()
    @kiteController.run
      withArgs  :
        command : "ls #{FSHelper.escapeFilePath @path} -Llpva --group-directories-first --time-style=full-iso"
    , (err, response)=>
      # log "------------------------------------------------------------------"
      # log "l flag response in: #{Date.now()-a} msec."
      if err
        warn err
        @emit "fs.fetchContents.finished", err
      else
        files = FSHelper.parseLsOutput [@path], response
        @emit "fs.fetchContents.finished", files
        callback? files

  # forkRepoCommandMap = ->

  #   git : "git clone"
  #   svn : "svn checkout"
  #   hg  : "hg clone"

  # cloneRepo:(options, callback)->

  #   @kiteController.run "#{forkRepoCommandMap()[repoType]} #{repo} #{escapeFilePath getAppPath manifest}", (err, response)->

Revert "Add -H option to fix broken symbolic links error"

This reverts commit 6b5b68deb6cebff22cbbcef16819ca8c71620bdd.


class @FlexibilityOrder
  constructor: (@element) ->
    # pass

  url: (path) ->
    "#{ App.scenario.url_path() }/flexibility_order/#{ path }"

  update: (order) =>
    $.ajax
      url: @url('set'),
      type: 'POST',
      data:
        flexibility_order:
          order: order,
          scenario_id: App.scenario.api_session_id()
      success: ->
        App.call_api()
      error: (e,f) ->
        console.log('Throw error')

  render: =>
    $.ajax
      url: @url('get')
      type: 'GET'
      success: (data) =>
        Sortable.create @element,
          ghostClass: 'ghost'
          animation: 150
          store:
            get: (sortable) ->
              data.order

            set: (sortable) =>
              @update(sortable.toArray().concat(['curtailment']))

class @FlexibilityOrder
  constructor: (@element) ->
    @lastGood = null

  url: (path) ->
    "#{ App.scenario.url_path() }/flexibility_order/#{ path }"

  update: (sortable) =>
    options = sortable.toArray()

    $.ajax
      url: @url('set'),
      type: 'POST',
      data:
        flexibility_order:
          order: options
      success: =>
        App.call_api()
        @lastGood = options
      error: (e,f) =>
        if @lastGood
          sortable.sort(@lastGood)

  render: =>
    $.ajax
      url: @url('get')
      type: 'GET'
      success: (data) =>
        Sortable.create @element,
          ghostClass: 'ghost'
          animation: 150
          store:
            get: (_sortable) =>
              @lastGood = data.order
              data.order

            set: (sortable) =>
              @update(sortable)

Improve error handling in FlexibilityOrder

Restores the order to the last known good settings when an error is
returned by ETEngine. The user is most likely not able to correct the
error.


"*":
  "atom-beautify":
    css:
      indent_size: 2
    general:
      _analyticsUserId: "8bfc3dc2-e55f-4b45-93b6-8b516d108dab"
    html:
      indent_size: 2
    js:
      indent_size: 2
  "atom-package-deps":
    ignored: []
  core:
    disabledPackages: [
      "metrics"
      "welcome"
      "language-javascript"
      "markdown-preview"
    ]
    telemetryConsent: "no"
  editor:
    fontSize: 13
    showIndentGuide: true
  "exception-reporting":
    userId: "e2c17e7a-6562-482a-b326-cd43e0f43b75"
  linter:
    disabledProviders: [
      "ESLint"
    ]
  "linter-eslint":
    disableWhenNoEslintConfig: true
    globalNodePath: "/usr/local"
    useGlobalEslint: false
  "linter-ui-default":
    panelHeight: 69
  "markdown-preview":
    useGitHubStyle: true
  "spell-check":
    locales: [
      "es-ES"
    ]
  tabs:
    enableVcsColoring: true
  "tree-view":
    hideVcsIgnoredFiles: true
  welcome:
    showOnStartup: false

"*":
  "atom-beautify":
    css:
      indent_size: 2
    general:
      _analyticsUserId: "8bfc3dc2-e55f-4b45-93b6-8b516d108dab"
    html:
      indent_size: 2
    js:
      indent_size: 2
  "atom-package-deps":
    ignored: []
  core:
    disabledPackages: [
      "metrics"
      "welcome"
      "language-javascript"
      "markdown-preview"
    ]
    telemetryConsent: "no"
  editor:
    showIndentGuide: true
  "exception-reporting":
    userId: "e2c17e7a-6562-482a-b326-cd43e0f43b75"
  linter:
    disabledProviders: [
      "ESLint"
    ]
  "linter-eslint":
    disableWhenNoEslintConfig: true
    globalNodePath: "/usr/local"
    useGlobalEslint: false
  "linter-ui-default":
    panelHeight: 69
  "markdown-preview":
    useGitHubStyle: true
  "spell-check":
    locales: [
      "es-ES"
    ]
  tabs:
    enableVcsColoring: true
  "tree-view":
    hideVcsIgnoredFiles: true
  welcome:
    showOnStartup: false

Increase font size in Atom

Increase font size in Atom


window.addBackboneHistoryCallbacksForDiscussionModal = ->

  old_navigate = Backbone.History.prototype.navigate
  Backbone.History.prototype.navigate = (fragment) ->
    fragment = @getFragment(fragment || '') # copied from Backbone

    FactlinkApp.DiscussionModalOnFrontend.setBackgroundPageUrl(fragment)
    old_navigate.apply this, arguments

  old_loadUrl = Backbone.History.prototype.loadUrl
  Backbone.History.prototype.loadUrl = (fragmentOverride) ->
    fragment = @getFragment(fragmentOverride) # copied from Backbone
    return if FactlinkApp.DiscussionModalOnFrontend.closeDiscussionAndAlreadyOnBackgroundPage(fragment)

    FactlinkApp.DiscussionModalOnFrontend.setBackgroundPageUrl(fragment)
    old_loadUrl.apply this, arguments

window.addBackboneHistoryCallbacksForDiscussionModal = ->

  old_navigate = Backbone.History.prototype.navigate
  Backbone.History.prototype.navigate = (fragment) ->
    fragment = @getFragment(fragment || '') # copied from Backbone

    FactlinkApp.DiscussionModalOnFrontend.setBackgroundPageUrl(fragment)
    old_navigate.apply this, arguments

  old_loadUrl = Backbone.History.prototype.loadUrl
  Backbone.History.prototype.loadUrl = (fragmentOverride) ->
    fragment = @getFragment(fragmentOverride) # copied from Backbone

    if FactlinkApp.DiscussionModalOnFrontend.closeDiscussionAndAlreadyOnBackgroundPage(fragment)
      @fragment = fragment
      return true

    FactlinkApp.DiscussionModalOnFrontend.setBackgroundPageUrl(fragment)
    old_loadUrl.apply this, arguments

Return true when aborting, since we are pretending that we loaded a route.

Set the fragment, because earlier that was being set by a behaviour in checkUrl that wasn't intended


Organization = require '../../models/organization'
crypto = require 'crypto'
hexDigest = (string)->
  sha = crypto.createHash('sha256');
  sha.update('awesome')
  sha.digest('hex')

routes = (app) ->

  app.get '/login', (req, res) ->
    res.render "#{__dirname}/views/login",
      title: 'Login',
      stylesheet: 'login'

  app.post '/sessions', (req, res, next) ->
    errMsg = "Org Name or Password is invalid. Try again"
    if req.body.name and req.body.password
      Organization.findOne name: req.body.name, (err, org) ->
        if org and org.hashed_password == hexDigest(req.body.password)
          req.session.org_id = org.id
          req.flash 'info',
            "You are logged in as #{req.session.currentOrg}"
          res.redirect '/dashboard'
        else
          req.flash 'error', errMsg
          res.redirect '/login'
    else
      req.flash 'error', errMsg
      res.redirect '/login'

  app.del '/sessions', (req, res) ->
    req.session.regenerate (err) ->
      req.flash 'info', 'You have been logged out.'
      res.redirect '/login'

module.exports = routes

Organization = require '../../models/organization'
crypto = require 'crypto'
hexDigest = (string)->
  sha = crypto.createHash('sha256');
  sha.update('awesome')
  sha.digest('hex')

routes = (app) ->

  app.get '/login', (req, res) ->
    res.render "#{__dirname}/views/login",
      title: 'Login',
      stylesheet: 'login'

  app.post '/sessions', (req, res, next) ->
    errMsg = "Org Name or Password is invalid. Try again"
    if req.body.name and req.body.password
      Organization.findOne name: req.body.name, (err, org) ->
        if org and org.hashed_password == hexDigest(req.body.password)
          req.session.org_id = org.id
          req.flash 'info', "You are logged in as #{org.name}"
          res.redirect '/dashboard'
        else
          req.flash 'error', errMsg
          res.redirect '/login'
    else
      req.flash 'error', errMsg
      res.redirect '/login'

  app.del '/sessions', (req, res) ->
    req.session.regenerate (err) ->
      req.flash 'info', 'You have been logged out.'
      res.redirect '/login'

module.exports = routes

Fix the flash when logging in

Fix the flash when logging in


gulp = require 'gulp'
babel = require 'gulp-babel'
uglify = require 'gulp-uglify'

gulp.task 'build', (done)->
  gulp.src 'src/dfa.js'
    .pipe babel({presets:['env']})
    .pipe uglify()
    .pipe gulp.dest('.')

gulp = require 'gulp'
babel = require 'gulp-babel'
uglify = require 'gulp-uglify'

gulp.task 'build', (done)->
  gulp.src 'src/dfa.js'
    .pipe babel({presets:['env']})
    .pipe uglify({ keep_fnames: true })
    .pipe gulp.dest('.')

Set uglify option 'keep_fname' true

Set uglify option 'keep_fname' true


if Meteor.isServer
	url = Npm.require('url')

RocketChat.settings.get 'Site_Url', (key, value) ->
	if value?.trim() isnt ''
		__meteor_runtime_config__.ROOT_URL = value
		__meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL = value
		if Meteor.absoluteUrl.defaultOptions?.rootUrl?
			Meteor.absoluteUrl.defaultOptions.rootUrl = value

		if Meteor.isServer
			RocketChat.hostname = url.parse(value).hostname

			process.env.MOBILE_ROOT_URL = value
			process.env.MOBILE_DDP_URL = value
			if WebAppInternals?.generateBoilerplate
				WebAppInternals.generateBoilerplate()

if Meteor.isServer
	url = Npm.require('url')

RocketChat.settings.get 'Site_Url', (key, value) ->
	if value?.trim() isnt ''
		__meteor_runtime_config__.ROOT_URL = value

		if Meteor.absoluteUrl.defaultOptions?.rootUrl?
			Meteor.absoluteUrl.defaultOptions.rootUrl = value

		if Meteor.isServer
			RocketChat.hostname = url.parse(value).hostname

			process.env.MOBILE_ROOT_URL = value
			process.env.MOBILE_DDP_URL = value
			if WebAppInternals?.generateBoilerplate
				WebAppInternals.generateBoilerplate()

Fix problem with ddp connection from some urls


{Robot, Adapter, TextMessage, EnterMessage, LeaveMessage, Response} = require 'hubot'

DotNetServer = require './dotnet'

class DotNet extends Adapter

  send: (envelope, strings...) ->
    @server.sendChat envelope.message.client, str for str in strings

  emote: (envelope, strings...) ->
    @server.sendEmote envelope.message.client, str for str in strings

  reply: (envelope, strings...) ->
    @send envelope, "#{envelope.user.name}: #{str}" for str in strings

  run: ->
    @server = new DotNetServer 8880

    @server.on 'chat', (user, message, client) =>
      console.log user, ':', message

      msg = new TextMessage user, message
      # attach the client so we know who to reply to
      msg.client = client

      # pipe the incoming chat into hubot
      @receive msg

    @server.start()

    # tell hubot we're ready
    @emit 'connected'

exports.use = (robot) ->
  new DotNet robot

{Robot, Adapter, TextMessage, EnterMessage, LeaveMessage, Response} = require 'hubot'

DotNetServer = require './server'

class DotNet extends Adapter

  constructor: ->
    @port = process.env.HUBOT_DOTNET_PORT || 8880

  send: (envelope, strings...) ->
    @server.sendChat envelope.message.client, str for str in strings

  emote: (envelope, strings...) ->
    @server.sendEmote envelope.message.client, str for str in strings

  reply: (envelope, strings...) ->
    @send envelope, "#{envelope.user.name}: #{str}" for str in strings

  run: ->
    @server = new DotNetServer @port

    @server.on 'chat', (user, message, client) =>
      console.log user, ':', message

      msg = new TextMessage user, message
      # attach the client so we know who to reply to
      msg.client = client

      # pipe the incoming chat into hubot
      @receive msg

    @server.start()

    # tell hubot we're ready
    @emit 'connected'

exports.use = (robot) ->
  new DotNet robot

Fix a few adapter issues.

Fix a few adapter issues.


# Description:
#   Hubot will handle your bug reports now
#
# Dependencies:
#   "scraper": "0.0.9"
#
# Configuration:
#   None
#
# Commands:
#
# Author:
#   capoferro (shamelessly copied from rrix's archer.coffee)

scraper = require 'scraper'
action = (msg) ->
    options = {
      'uri': 'http://programmerexcuses.com',
      'headers': {
        'User-Agent': 'User-Agent: Programmer Excuses Bot for Hubot'
      }
    }
    scraper options, (err, jQuery) ->
      throw err if err
      quote = jQuery('.wrapper a').toArray()[0]
      dialog = '' + jQuery(quote).text().trim()
      msg.send dialog

module.exports = (robot) ->
  robot.hear /\sbug[\s?.!]/i, action
  robot.hear /\sexcuse\s/i, action
  robot.hear /what (is|was) the (problem|bug|issue)/i, action

# Description:
#   Hubot will handle your bug reports now
#
# Dependencies:
#   "scraper": "0.0.9"
#
# Configuration:
#   None
#
# Commands:
#
# Author:
#   capoferro (shamelessly copied from rrix's archer.coffee)

scraper = require 'scraper'
action = (msg) ->
    options = {
      'uri': 'http://programmerexcuses.com',
      'headers': {
        'User-Agent': 'User-Agent: Programmer Excuses Bot for Hubot'
      }
    }
    scraper options, (err, jQuery) ->
      throw err if err
      quote = jQuery('.wrapper a').toArray()[0]
      dialog = '' + jQuery(quote).text().trim()
      msg.send dialog

module.exports = (robot) ->
  robot.respond /\sbug[\s?.!]/i, action
  robot.respond /\sexcuse\s/i, action
  robot.respond /what (is|was) the (problem|bug|issue)/i, action

Reduce verbosity of programmer excuses

Reduce verbosity of programmer excuses


mediator = require 'mediator'

module.exports = class DialogManager
  constructor: ->
    @source = document.getElementById 'dialog-template'
    @template = Handlebars.compile(@source.innerText)
    mediator.dialogManager = @

  showDialog: (data, callback) =>
    result = $ @template(data)
    that = this

    result.children('.dialog-option').click (event) ->
      that.hideDialog()
      id = parseInt( $(this).prop('id') )
      callback(id + 1) if callback

    $('#dialog').append(result).
    css('left', 0).
    css('top', homepageview.canvas.height + 2).
    css('width', homepageview.canvas.width).
    fadeIn()

  hideDialog: () =>
    $('#dialog').empty()

mediator = require 'mediator'

module.exports = class DialogManager
  constructor: ->
    @source = document.getElementById 'dialog-template'
    @template = Handlebars.compile(@source.innerText)
    @canvas = document.getElementById 'game-canvas'
    mediator.dialogManager = @

  showDialog: (data, callback) =>
    result = $ @template(data)
    that = this

    result.children('.dialog-option').click (event) ->
      that.hideDialog()
      id = parseInt( $(this).prop('id') )
      callback(id + 1) if callback

    $('#dialog').append(result).
    css('left', 0).
    css('top', @canvas.height + 2).
    css('width', @canvas.width).
    fadeIn()

  hideDialog: () =>
    $('#dialog').empty()

Fix dialogmanager only working in debug mode


# dom.d.ts: https://github.com/Microsoft/TypeScript/blob/master/src/lib/dom.generated.d.ts

# this fix is needed because these definitions are absent from typescript's official lib.d.ts and are used in dom.d.ts.
fix =
"""
interface ArrayBufferView {}
declare var ArrayBufferView: {}

interface ArrayBuffer {}
declare var ArrayBuffer: {}

interface Uint8Array {}
declare var Uint8Array: {}

interface Int32Array {}
declare var Int32Array: {}

interface Float32Array {}
declare var Float32Array: {}
"""

fs = require 'fs'
SupAPI.addPlugin 'typescript', 'dom', {
  defs: fs.readFileSync(__dirname + '/dom.d.ts', encoding: 'utf8') + fix
}

# dom.d.ts: https://github.com/Microsoft/TypeScript/blob/master/src/lib/dom.generated.d.ts

# This fix is needed because these definitions are absent from typescript's official lib.d.ts and are used in dom.d.ts.

# 06 march 2015: these definitions can actually be found in :
# https://github.com/Microsoft/TypeScript/blob/master/src/lib/extensions.d.ts
# and also in
# https://github.com/Microsoft/TypeScript/blob/master/src/lib/es6.d.ts
fix =
"""
interface ArrayBufferView {}
declare var ArrayBufferView: {}

interface ArrayBuffer {}
declare var ArrayBuffer: {}

interface Uint8Array {}
declare var Uint8Array: {}

interface Int32Array {}
declare var Int32Array: {}

interface Float32Array {}
declare var Float32Array: {}
"""

fs = require 'fs'
SupAPI.addPlugin 'typescript', 'dom', {
  defs: fs.readFileSync(__dirname + '/dom.d.ts', encoding: 'utf8') + fix
}

Add comments on missing definitions.

Add comments on missing definitions.


'body':
  'meta-F': 'project-find:show'
  'meta-f': 'find-and-replace:show'

'.editor':
  'alt-meta-f': 'find-and-replace:show-replace'

  'meta-g': 'find-and-replace:find-next'
  'meta-G': 'find-and-replace:find-previous'

  'meta-e': 'find-and-replace:use-selection-as-find-pattern'

'.find-and-replace':
  'meta-enter': 'find-and-replace:replace-all'
  'alt-meta-/': 'find-and-replace:toggle-regex-option'
  'alt-meta-c': 'find-and-replace:toggle-case-option'
  'alt-meta-s': 'find-and-replace:toggle-selection-option'

'.find-and-replace, .project-find, .project-find .results-view':
  'escape': 'core:cancel'
  'tab': 'find-and-replace:focus-next'
  'shift-tab': 'find-and-replace:focus-previous'

'.project-find':
  'meta-enter': 'project-find:replace-all'

'body':
  'meta-F': 'project-find:show'
  'meta-f': 'find-and-replace:show'
  'alt-meta-f': 'find-and-replace:show-replace'

'.editor':
  'meta-g': 'find-and-replace:find-next'
  'meta-G': 'find-and-replace:find-previous'

  'meta-e': 'find-and-replace:use-selection-as-find-pattern'

'.find-and-replace':
  'meta-enter': 'find-and-replace:replace-all'
  'alt-meta-/': 'find-and-replace:toggle-regex-option'
  'alt-meta-c': 'find-and-replace:toggle-case-option'
  'alt-meta-s': 'find-and-replace:toggle-selection-option'

'.find-and-replace, .project-find, .project-find .results-view':
  'escape': 'core:cancel'
  'tab': 'find-and-replace:focus-next'
  'shift-tab': 'find-and-replace:focus-previous'

'.project-find':
  'meta-enter': 'project-find:replace-all'

Make replace keymap work in all contexts