passport: some code reorg and way to list of supported strategies

--- salvus/hub.coffee
+++ salvus/hub.coffee
@@ -366,6 +366,7 @@
         done(null, user)

     settings = database.key_value_store(name:'passport_settings')
+    strategies = ['local']   # configured strategies listed here.
     get_conf = (strategy, cb) ->
         database.select
             table  : 'passport_settings'
@@ -381,291 +382,307 @@
                         dbg("WARNING: passport strategy #{strategy} not configured")
                         cb(undefined, undefined)
                     else
-
-                        cb(undefined, results[0][0])
+                        conf = results[0][0]
+                        if strategy != 'site_conf' and conf?
+                            strategies.push(strategy)
+                        cb(undefined, conf)
+
+    # Return the configured and supported authentication strategies.
+    app.get '/auth/strategies', (req, res) ->
+        res.json(strategies)

     # Set the site conf via cassandra like this:
     #    update passport_settings set conf['auth']='https://cloud.sagemath.com/auth' where strategy='site_conf';
-    get_conf 'site_conf', (err, site_conf) ->
-        if err or not site_conf?
-            cb(err)
-            return
-        auth_url = site_conf.auth
-        dbg("auth_url=#{auth_url}")
-
-        init_local = (cb) ->
-            dbg("init_local")
-            # Strategy: local email address / password login
-            PassportStrategy = require('passport-local').Strategy
-
-            verify = (username, password, done) ->
-                if username == 'a'
-                    return done(null, false, { message: 'Incorrect password.' })
-                console.log("local strategy validating user #{username}")
-                done(null, {username:username})
-
-            passport.use(new PassportStrategy(verify))
-
-            app.get '/auth/local', (req, res) ->
-                console.log('get login form')
-                res.send("""<form action="/auth/local" method="post">
-                                Email
-                                <input type="text" name="username">
-                                Password
-                                <input type="password" name="password">
-                                <button type="submit" value="Log In"/>Login</button>
-                            </form>""")
-
-            app.post '/auth/local', passport.authenticate('local'), (req, res) ->
-                console.log("authenticated... ")
+
+    auth_url = undefined # gets set below
+
+    init_local = (cb) ->
+        dbg("init_local")
+        # Strategy: local email address / password login
+        PassportStrategy = require('passport-local').Strategy
+
+        verify = (username, password, done) ->
+            if username == 'a'
+                return done(null, false, { message: 'Incorrect password.' })
+            console.log("local strategy validating user #{username}")
+            done(null, {username:username})
+
+        passport.use(new PassportStrategy(verify))
+
+        app.get '/auth/local', (req, res) ->
+            res.send("""<form action="/auth/local" method="post">
+                            Email
+                            <input type="text" name="username">
+                            Password
+                            <input type="password" name="password">
+                            <button type="submit" value="Log In"/>Login</button>
+                        </form>""")
+
+        app.post '/auth/local', passport.authenticate('local'), (req, res) ->
+            console.log("authenticated... ")
+            res.json(req.user)
+
+        cb()
+
+    init_google = (cb) ->
+        dbg("init_google")
+        # Strategy: Google OAuth 2 -- https://github.com/jaredhanson/passport-google-oauth
+        #
+        # NOTE: The passport-recommend library passport-google uses openid2, which
+        # is deprecated in a few days!   So instead, I have to use oauth2, which
+        # is in https://github.com/jaredhanson/passport-google-oauth, which I found by luck!?!
+        #
+        PassportStrategy = require('passport-google-oauth').OAuth2Strategy
+        strategy = 'google'
+        get_conf strategy, (err, conf) ->
+            if err or not conf?
+                cb(err)
+                return
+            # docs for getting these for your app
+            # https://developers.google.com/accounts/docs/OpenIDConnect#appsetup
+            #
+            # You must then put them in the database, via
+            #   update passport_settings set conf['clientID']='...'     where strategy='google';
+            #   update passport_settings set conf['clientSecret']='...' where strategy='google';
+            #
+            opts =
+                clientID     : conf.clientID
+                clientSecret : conf.clientSecret
+                callbackURL  : "#{auth_url}/#{strategy}/return"
+
+            verify = (accessToken, refreshToken, profile, done) ->
+                done(undefined, {profile:profile})
+            passport.use(new PassportStrategy(opts, verify))
+
+            # Enabling "profile" below I think required that I explicitly go to Google Developer Console for the project,
+            # then select API&Auth, then API's, then Google+, then explicitly enable it.  Otherwise, stuff just mysteriously
+            # didn't work.  To figure out that this was the problem, I had to grep the source code of the passport-google-oauth
+            # library and put in print statements to see what the *REAL* errors were, since that
+            # library hid the errors (**WHY**!!?).
+            app.get "/auth/#{strategy}", passport.authenticate(strategy, {'scope': 'openid email profile'})
+
+            app.get "/auth/#{strategy}/return", passport.authenticate(strategy, {failureRedirect: '/auth/local'}), (req, res) ->
                 res.json(req.user)

             cb()

-        init_google = (cb) ->
-            dbg("init_google")
-            # Strategy: Google OAuth 2 -- https://github.com/jaredhanson/passport-google-oauth
+    init_github = (cb) ->
+        dbg("init_github")
+        # Strategy: Github OAuth2 -- https://github.com/jaredhanson/passport-github
+        PassportStrategy = require('passport-github').Strategy
+        strategy = 'github'
+        get_conf strategy, (err, conf) ->
+            if err or not conf?
+                cb(err)
+                return
+            # Get these here:
+            #      https://github.com/settings/applications/new
+            # You must then put them in the database, via
+            #   update passport_settings set conf['clientID']='...'     where strategy='github';
+            #   update passport_settings set conf['clientSecret']='...' where strategy='github';
+
+            opts =
+                clientID     : conf.clientID
+                clientSecret : conf.clientSecret
+                callbackURL  : "#{auth_url}/#{strategy}/return"
+
+            verify = (accessToken, refreshToken, profile, done) ->
+                done(undefined, {profile:profile})
+            passport.use(new PassportStrategy(opts, verify))
+
+            app.get "/auth/#{strategy}", passport.authenticate(strategy)
+
+            app.get "/auth/#{strategy}/return", passport.authenticate(strategy, {failureRedirect: '/auth/local'}), (req, res) ->
+                res.json(req.user)
+
+            cb()
+
+    init_facebook = (cb) ->
+        dbg("init_facebook")
+        # Strategy: Facebook OAuth2 --
+        PassportStrategy = require('passport-facebook').Strategy
+        strategy = 'facebook'
+        get_conf strategy, (err, conf) ->
+            if err or not conf?
+                cb(err)
+                return
+            # Get these by going to https://developers.facebook.com/ and creating a new application.
+            # For that application, set the url to the site SMC will be served from.
+            # The Facebook "App ID" and is clientID and the Facebook "App Secret" is the clientSecret
+            # for oauth2, as I discovered by a lucky guess... (sigh).
             #
-            # NOTE: The passport-recommend library passport-google uses openid2, which
-            # is deprecated in a few days!   So instead, I have to use oauth2, which
-            # is in https://github.com/jaredhanson/passport-google-oauth, which I found by luck!?!
+            # You must then put them in the database, via
+            #   update passport_settings set conf['clientID']='...'     where strategy='facebook';
+            #   update passport_settings set conf['clientSecret']='...' where strategy='facebook';
+
+            opts =
+                clientID     : conf.clientID
+                clientSecret : conf.clientSecret
+                callbackURL  : "#{auth_url}/#{strategy}/return"
+                enableProof  : false
+
+            verify = (accessToken, refreshToken, profile, done) ->
+                done(undefined, {profile:profile})
+            passport.use(new PassportStrategy(opts, verify))
+
+            app.get "/auth/#{strategy}", passport.authenticate(strategy)
+
+            app.get "/auth/#{strategy}/return", passport.authenticate(strategy, {failureRedirect: '/auth/local'}), (req, res) ->
+                res.json(req.user)
+
+            cb()
+
+    init_dropbox = (cb) ->
+        dbg("init_dropbox")
+        PassportStrategy = require('passport-dropbox-oauth2').Strategy
+        strategy = 'dropbox'
+        get_conf strategy, (err, conf) ->
+            if err or not conf?
+                cb(err)
+                return
+            # Get these by:
+            #   (1) creating a dropbox account, then going to this url: https://www.dropbox.com/developers/apps
+            #   (2) make a dropbox api app that only access the datastore (not user files -- for now, since we're just doing auth!).
+            #   (3) You'll see an "App key" and an "App secret".
+            #   (4) Add the redirect URL on the dropbox page as well, which will be like https://cloud.sagemath.com/auth/dropbox/return
+            # This might (or might not) be relevant when we support dropbox sync: https://github.com/dropbox/dropbox-js
             #
-            PassportStrategy = require('passport-google-oauth').OAuth2Strategy
-            strategy = 'google'
-            get_conf strategy, (err, conf) ->
-                if err or not conf?
+            # You must then put them in the database, via
+            #   update passport_settings set conf['clientID']='...'     where strategy='dropbox';
+            #   update passport_settings set conf['clientSecret']='...' where strategy='dropbox';
+
+            opts =
+                clientID     : conf.clientID
+                clientSecret : conf.clientSecret
+                callbackURL  : "#{auth_url}/#{strategy}/return"
+
+            verify = (accessToken, refreshToken, profile, done) ->
+                done(undefined, {profile:profile})
+            passport.use(new PassportStrategy(opts, verify))
+
+            app.get "/auth/#{strategy}", passport.authenticate("dropbox-oauth2")
+
+            app.get "/auth/#{strategy}/return", passport.authenticate("dropbox-oauth2", {failureRedirect: '/auth/local'}), (req, res) ->
+                res.json(req.user)
+
+            cb()
+
+    init_bitbucket = (cb) ->
+        dbg("init_bitbucket")
+        PassportStrategy = require('passport-bitbucket').Strategy
+        strategy = 'bitbucket'
+        get_conf strategy, (err, conf) ->
+            if err or not conf?
+                cb(err)
+                return
+            # Get these by:
+            #      (1) make a bitbucket account
+            #      (2) Go to https://bitbucket.org/account/user/[your username]/api
+            #      (3) Click add consumer and enter the URL of your SMC instance.
+            #
+            # You must then put them in the database, via
+            #   update passport_settings set conf['clientID']='...'     where strategy='bitbucket';
+            #   update passport_settings set conf['clientSecret']='...' where strategy='bitbucket';
+
+            opts =
+                consumerKey    : conf.clientID
+                consumerSecret : conf.clientSecret
+                callbackURL    : "#{auth_url}/#{strategy}/return"
+
+            verify = (accessToken, refreshToken, profile, done) ->
+                done(undefined, {profile:profile})
+            passport.use(new PassportStrategy(opts, verify))
+
+            app.get "/auth/#{strategy}", passport.authenticate(strategy)
+
+            app.get "/auth/#{strategy}/return", passport.authenticate(strategy, {failureRedirect: '/auth/local'}), (req, res) ->
+                res.json(req.user)
+
+            cb()
+
+    init_wordpress = (cb) ->
+        dbg("init_wordpress")
+        PassportStrategy = require('passport-wordpress').Strategy
+        strategy = 'wordpress'
+        get_conf strategy, (err, conf) ->
+            if err or not conf?
+                cb(err)
+                return
+            # Get these by:
+            #    (1) Make a wordpress account
+            #    (2) Go to https://developer.wordpress.com/apps/
+            #    (3) Click "Create a New Application"
+            #    (4) Fill the form as usual and eventual get the id and secret.
+            #
+            # You must then put them in the database, via
+            #   update passport_settings set conf['clientID']='...'     where strategy='wordpress';
+            #   update passport_settings set conf['clientSecret']='...' where strategy='wordpress';
+
+            opts =
+                clientID     : conf.clientID
+                clientSecret : conf.clientSecret
+                callbackURL  : "#{auth_url}/#{strategy}/return"
+
+            verify = (accessToken, refreshToken, profile, done) ->
+                done(undefined, {profile:profile})
+            passport.use(new PassportStrategy(opts, verify))
+
+            app.get "/auth/#{strategy}", passport.authenticate(strategy)
+
+            app.get "/auth/#{strategy}/return", passport.authenticate(strategy, {failureRedirect: '/auth/local'}), (req, res) ->
+                res.json(req.user)
+
+            cb()
+
+    init_twitter = (cb) ->
+        dbg("init_twitter")
+        PassportStrategy = require('passport-twitter').Strategy
+        strategy = 'twitter'
+        get_conf strategy, (err, conf) ->
+            if err or not conf?
+                cb(err)
+                return
+            # Get these by:
+            #    (1) Go to https://apps.twitter.com/ and create a new application.
+            #    (2) Click on Keys and Access Tokens
+            #
+            # You must then put them in the database, via
+            #   update passport_settings set conf['clientID']='...'     where strategy='twitter';
+            #   update passport_settings set conf['clientSecret']='...' where strategy='twitter';
+
+            opts =
+                consumerKey    : conf.clientID
+                consumerSecret : conf.clientSecret
+                callbackURL    : "#{auth_url}/#{strategy}/return"
+
+            verify = (accessToken, refreshToken, profile, done) ->
+                done(undefined, {profile:profile})
+            passport.use(new PassportStrategy(opts, verify))
+
+            app.get "/auth/#{strategy}", passport.authenticate(strategy)
+
+            app.get "/auth/#{strategy}/return", passport.authenticate(strategy, {failureRedirect: '/auth/local'}), (req, res) ->
+                res.json(req.user)
+
+            cb()
+
+    async.series([
+        (cb) ->
+            get_conf 'site_conf', (err, site_conf) ->
+                if err
                     cb(err)
-                    return
-                # docs for getting these for your app
-                # https://developers.google.com/accounts/docs/OpenIDConnect#appsetup
-                #
-                # You must then put them in the database, via
-                #   update passport_settings set conf['clientID']='...'     where strategy='google';
-                #   update passport_settings set conf['clientSecret']='...' where strategy='google';
-                #
-                opts =
-                    clientID     : conf.clientID
-                    clientSecret : conf.clientSecret
-                    callbackURL  : "#{auth_url}/#{strategy}/return"
-
-                verify = (accessToken, refreshToken, profile, done) ->
-                    done(undefined, {profile:profile})
-                passport.use(new PassportStrategy(opts, verify))
-
-                # Enabling "profile" below I think required that I explicitly go to Google Developer Console for the project,
-                # then select API&Auth, then API's, then Google+, then explicitly enable it.  Otherwise, stuff just mysteriously
-                # didn't work.  To figure out that this was the problem, I had to grep the source code of the passport-google-oauth
-                # library and put in print statements to see what the *REAL* errors were, since that
-                # library hid the errors (**WHY**!!?).
-                app.get "/auth/#{strategy}", passport.authenticate(strategy, {'scope': 'openid email profile'})
-
-                app.get "/auth/#{strategy}/return", passport.authenticate(strategy, {failureRedirect: '/auth/local'}), (req, res) ->
-                    res.json(req.user)
-
+                else
+                    if site_conf?
+                        auth_url = site_conf.auth
+                        dbg("auth_url=#{auth_url}")
+                    cb()
+        (cb) ->
+            if not auth_url?
                 cb()
-
-        init_github = (cb) ->
-            dbg("init_github")
-            # Strategy: Github OAuth2 -- https://github.com/jaredhanson/passport-github
-            PassportStrategy = require('passport-github').Strategy
-            strategy = 'github'
-            get_conf strategy, (err, conf) ->
-                if err or not conf?
-                    cb(err)
-                    return
-                # Get these here:
-                #      https://github.com/settings/applications/new
-                # You must then put them in the database, via
-                #   update passport_settings set conf['clientID']='...'     where strategy='github';
-                #   update passport_settings set conf['clientSecret']='...' where strategy='github';
-
-                opts =
-                    clientID     : conf.clientID
-                    clientSecret : conf.clientSecret
-                    callbackURL  : "#{auth_url}/#{strategy}/return"
-
-                verify = (accessToken, refreshToken, profile, done) ->
-                    done(undefined, {profile:profile})
-                passport.use(new PassportStrategy(opts, verify))
-
-                app.get "/auth/#{strategy}", passport.authenticate(strategy)
-
-                app.get "/auth/#{strategy}/return", passport.authenticate(strategy, {failureRedirect: '/auth/local'}), (req, res) ->
-                    res.json(req.user)
-
-                cb()
-
-        init_facebook = (cb) ->
-            dbg("init_facebook")
-            # Strategy: Facebook OAuth2 --
-            PassportStrategy = require('passport-facebook').Strategy
-            strategy = 'facebook'
-            get_conf strategy, (err, conf) ->
-                if err or not conf?
-                    cb(err)
-                    return
-                # Get these by going to https://developers.facebook.com/ and creating a new application.
-                # For that application, set the url to the site SMC will be served from.
-                # The Facebook "App ID" and is clientID and the Facebook "App Secret" is the clientSecret
-                # for oauth2, as I discovered by a lucky guess... (sigh).
-                #
-                # You must then put them in the database, via
-                #   update passport_settings set conf['clientID']='...'     where strategy='facebook';
-                #   update passport_settings set conf['clientSecret']='...' where strategy='facebook';
-
-                opts =
-                    clientID     : conf.clientID
-                    clientSecret : conf.clientSecret
-                    callbackURL  : "#{auth_url}/#{strategy}/return"
-                    enableProof  : false
-
-                verify = (accessToken, refreshToken, profile, done) ->
-                    done(undefined, {profile:profile})
-                passport.use(new PassportStrategy(opts, verify))
-
-                app.get "/auth/#{strategy}", passport.authenticate(strategy)
-
-                app.get "/auth/#{strategy}/return", passport.authenticate(strategy, {failureRedirect: '/auth/local'}), (req, res) ->
-                    res.json(req.user)
-
-                cb()
-
-        init_dropbox = (cb) ->
-            dbg("init_dropbox")
-            PassportStrategy = require('passport-dropbox-oauth2').Strategy
-            strategy = 'dropbox'
-            get_conf strategy, (err, conf) ->
-                if err or not conf?
-                    cb(err)
-                    return
-                # Get these by:
-                #   (1) creating a dropbox account, then going to this url: https://www.dropbox.com/developers/apps
-                #   (2) make a dropbox api app that only access the datastore (not user files -- for now, since we're just doing auth!).
-                #   (3) You'll see an "App key" and an "App secret".
-                #   (4) Add the redirect URL on the dropbox page as well, which will be like https://cloud.sagemath.com/auth/dropbox/return
-                # This might (or might not) be relevant when we support dropbox sync: https://github.com/dropbox/dropbox-js
-                #
-                # You must then put them in the database, via
-                #   update passport_settings set conf['clientID']='...'     where strategy='dropbox';
-                #   update passport_settings set conf['clientSecret']='...' where strategy='dropbox';
-
-                opts =
-                    clientID     : conf.clientID
-                    clientSecret : conf.clientSecret
-                    callbackURL  : "#{auth_url}/#{strategy}/return"
-
-                verify = (accessToken, refreshToken, profile, done) ->
-                    done(undefined, {profile:profile})
-                passport.use(new PassportStrategy(opts, verify))
-
-                app.get "/auth/#{strategy}", passport.authenticate("dropbox-oauth2")
-
-                app.get "/auth/#{strategy}/return", passport.authenticate("dropbox-oauth2", {failureRedirect: '/auth/local'}), (req, res) ->
-                    res.json(req.user)
-
-                cb()
-
-        init_bitbucket = (cb) ->
-            dbg("init_bitbucket")
-            PassportStrategy = require('passport-bitbucket').Strategy
-            strategy = 'bitbucket'
-            get_conf strategy, (err, conf) ->
-                if err or not conf?
-                    cb(err)
-                    return
-                # Get these by:
-                #      (1) make a bitbucket account
-                #      (2) Go to https://bitbucket.org/account/user/[your username]/api
-                #      (3) Click add consumer and enter the URL of your SMC instance.
-                #
-                # You must then put them in the database, via
-                #   update passport_settings set conf['clientID']='...'     where strategy='bitbucket';
-                #   update passport_settings set conf['clientSecret']='...' where strategy='bitbucket';
-
-                opts =
-                    consumerKey    : conf.clientID
-                    consumerSecret : conf.clientSecret
-                    callbackURL    : "#{auth_url}/#{strategy}/return"
-
-                verify = (accessToken, refreshToken, profile, done) ->
-                    done(undefined, {profile:profile})
-                passport.use(new PassportStrategy(opts, verify))
-
-                app.get "/auth/#{strategy}", passport.authenticate(strategy)
-
-                app.get "/auth/#{strategy}/return", passport.authenticate(strategy, {failureRedirect: '/auth/local'}), (req, res) ->
-                    res.json(req.user)
-
-                cb()
-
-        init_wordpress = (cb) ->
-            dbg("init_wordpress")
-            PassportStrategy = require('passport-wordpress').Strategy
-            strategy = 'wordpress'
-            get_conf strategy, (err, conf) ->
-                if err or not conf?
-                    cb(err)
-                    return
-                # Get these by:
-                #    (1) Make a wordpress account
-                #    (2) Go to https://developer.wordpress.com/apps/
-                #    (3) Click "Create a New Application"
-                #    (4) Fill the form as usual and eventual get the id and secret.
-                #
-                # You must then put them in the database, via
-                #   update passport_settings set conf['clientID']='...'     where strategy='wordpress';
-                #   update passport_settings set conf['clientSecret']='...' where strategy='wordpress';
-
-                opts =
-                    clientID     : conf.clientID
-                    clientSecret : conf.clientSecret
-                    callbackURL  : "#{auth_url}/#{strategy}/return"
-
-                verify = (accessToken, refreshToken, profile, done) ->
-                    done(undefined, {profile:profile})
-                passport.use(new PassportStrategy(opts, verify))
-
-                app.get "/auth/#{strategy}", passport.authenticate(strategy)
-
-                app.get "/auth/#{strategy}/return", passport.authenticate(strategy, {failureRedirect: '/auth/local'}), (req, res) ->
-                    res.json(req.user)
-
-                cb()
-
-        init_twitter = (cb) ->
-            dbg("init_twitter")
-            PassportStrategy = require('passport-twitter').Strategy
-            strategy = 'twitter'
-            get_conf strategy, (err, conf) ->
-                if err or not conf?
-                    cb(err)
-                    return
-                # Get these by:
-                #    (1) Go to https://apps.twitter.com/ and create a new application.
-                #    (2) Click on Keys and Access Tokens
-                #
-                # You must then put them in the database, via
-                #   update passport_settings set conf['clientID']='...'     where strategy='twitter';
-                #   update passport_settings set conf['clientSecret']='...' where strategy='twitter';
-
-                opts =
-                    consumerKey    : conf.clientID
-                    consumerSecret : conf.clientSecret
-                    callbackURL    : "#{auth_url}/#{strategy}/return"
-
-                verify = (accessToken, refreshToken, profile, done) ->
-                    done(undefined, {profile:profile})
-                passport.use(new PassportStrategy(opts, verify))
-
-                app.get "/auth/#{strategy}", passport.authenticate(strategy)
-
-                app.get "/auth/#{strategy}/return", passport.authenticate(strategy, {failureRedirect: '/auth/local'}), (req, res) ->
-                    res.json(req.user)
-
-                cb()
-
-        async.parallel([init_local, init_google, init_github, init_facebook,
-                        init_dropbox, init_bitbucket, init_wordpress, init_twitter], cb)
+            else
+                async.parallel([init_local, init_google, init_github, init_facebook,
+                                init_dropbox, init_bitbucket, init_wordpress, init_twitter], cb)
+    ], cb)


remove language-chef... all ruby files become Chef files

--- atom/packages.cson
+++ atom/packages.cson
@@ -23,7 +23,6 @@
   "gruvbox"
   "jshint"
   "jumpy"
-  "language-chef"
   "language-diff"
   "language-docker"
   "language-elixir"


Removed JSON formatter and 2nd toolbar

Removed JSON formatter and 2nd toolbar

--- atom/packages.cson
+++ atom/packages.cson
@@ -20,10 +20,8 @@
   "minimap"
   "minimap-bookmarks"
   "package-sync"
-  "pretty-json"
   "project-manager"
   "todo-show"
   "tool-bar"
   "tool-bar-main"
-  "toolbar-iconshortcuts"
 ]


:new: Add a spec about not showing objects in HTML attributes

--- spec/custom-element-spec.coffee
+++ spec/custom-element-spec.coffee
@@ -55,6 +55,15 @@
     expect(element.wow).not.toBe(defaultProp)
     expect(element.wow.__proto__).toBe(defaultProp)

+  it 'does not show object configs in html attributes', ->
+    element = new (registerCustomElement(getCustomElement({
+      name: 'x-spec-doesnt-show-object-config-html-attributes'
+      config: {wow: {type: Object}}
+    })))
+    expect(element.hasAttribute('wow')).toBe(false)
+    element.wow = {}
+    expect(element.hasAttribute('wow')).toBe(false)
+
   it 'automatically uses the existing html attributes in config', ->
     name = 'x-spec-automatically-resume-config-attributes'
     registerCustomElement(getCustomElement({


:fire: Delete obsolete test

:fire: Delete obsolete test

--- spec/keymap-manager-spec.coffee
+++ spec/keymap-manager-spec.coffee
@@ -713,13 +713,6 @@
         assert.equal(keymapManager.keystrokeForKeyboardEvent(buildKeydownEvent({key: '@', code: 'KeyG', metaKey: true, modifierState: {AltGraph: true}})), 'alt-cmd-g')
         assert.equal(keymapManager.keystrokeForKeyboardEvent(buildKeydownEvent({key: '@', code: 'KeyG', altKey: true, modifierState: {AltGraph: true}})), 'alt-g')

-      it "uses the keymap to fix incorrect KeyboardEvent.key values when ctrlKey is true on Linux", ->
-        mockProcessPlatform('linux')
-        currentKeymap = require('./helpers/keymaps/linux-dvorak')
-        assert.equal(keymapManager.keystrokeForKeyboardEvent(buildKeydownEvent({key: 'f', code: 'KeyF', ctrlKey: true})), 'ctrl-u')
-        assert.equal(keymapManager.keystrokeForKeyboardEvent(buildKeydownEvent({key: 'F', code: 'KeyF', ctrlKey: true, shiftKey: true})), 'ctrl-shift-U')
-        assert.equal(keymapManager.keystrokeForKeyboardEvent(buildKeydownEvent({key: 'F', code: 'KeyF', ctrlKey: true, altKey: true, shiftKey: true})), 'ctrl-alt-shift-U')
-
       it "resolves events with a key value of Unknown and a code of IntlRo to '/' (this occurs on a Brazillian Portuguese keyboard layout on Mint Linux)", ->
         mockProcessPlatform('linux')
         assert.equal(keymapManager.keystrokeForKeyboardEvent(buildKeydownEvent({key: 'Unidentified', code: 'IntlRo', ctrlKey: true})), 'ctrl-/')


Removed unused alias

Removed unused alias

--- framer/Layer.coffee
+++ framer/Layer.coffee
@@ -912,7 +912,6 @@
 	subLayersBelow: (point, originX=0, originY=0) -> @childrenBelow(point, originX, originY)
 	subLayersLeft: (point, originX=0, originY=0) -> @childrenLeft(point, originX, originY)
 	subLayersRight: (point, originX=0, originY=0) -> @childrenRight(point, originX, originY)
-	_superOrParentLayer: -> @_parentOrContext()

 	##############################################################
 	## ANIMATION


Tests for up.form.config.observeOptions()

--- spec/unpoly/form_spec.js.coffee
+++ spec/unpoly/form_spec.js.coffee
@@ -95,7 +95,7 @@
         result = up.form.submitButtons(form)
         expect(result).toEqual(jasmine.arrayWithExactContents([submitButton, submitInput]))

-    describe 'up.observe', ->
+    describe 'up.observe()', ->

       beforeEach ->
         up.form.config.observeDelay = 0
@@ -476,8 +476,39 @@
               'input2': 'input2-c'
             }

-
-    describe 'up.submit', ->
+    describe 'up.form.observeOptions()', ->
+
+      it 'parses the closest [up-observe-event] attribute into an { event } option', ->
+        form = fixture('form')
+        container = e.affix(form, 'div[up-observe-event="my:event"]')
+        field = e.affix(container, 'input[type="text"][name="foo"]')
+
+        options = {}
+        up.form.observeOptions(field, options)
+
+        expect(options.event).toBe('my:event')
+
+      it 'expands [up-observe-event="change"] into up.form.config.changeEvent', ->
+        form = fixture('form')
+        field = e.affix(form, 'input[type="text"][name="foo"][up-observe-event="change"]')
+        up.form.config.changeEvent = 'change blur'
+
+        options = {}
+        up.form.observeOptions(field, options)
+
+        expect(options.event).toBe('change blur')
+
+      it 'expands [up-observe-event="input"] into up.form.config.inputEvent', ->
+        form = fixture('form')
+        field = e.affix(form, 'input[type="text"][name="foo"][up-observe-event="input"]')
+        up.form.config.inputEvent = 'custom:event'
+
+        options = {}
+        up.form.observeOptions(field, options)
+
+        expect(options.event).toBe('custom:event')
+
+    describe 'up.submit()', ->

       it 'emits a preventable up:form:submit event', asyncSpec (next) ->
         $form = $fixture('form[action="/form-target"][up-target=".response"]')


Account for pane axis class name changes in pane-container-spec

--- spec/workspace-view-spec.coffee
+++ spec/workspace-view-spec.coffee
@@ -58,10 +58,10 @@
         simulateReload()

         expect(atom.workspaceView.getEditorViews().length).toBe 4
-        editor1 = atom.workspaceView.panes.find('.row > .pane .editor:eq(0)').view()
-        editor3 = atom.workspaceView.panes.find('.row > .pane .editor:eq(1)').view()
-        editor2 = atom.workspaceView.panes.find('.row > .column > .pane .editor:eq(0)').view()
-        editor4 = atom.workspaceView.panes.find('.row > .column > .pane .editor:eq(1)').view()
+        editor1 = atom.workspaceView.panes.find('.pane-row > .pane .editor:eq(0)').view()
+        editor3 = atom.workspaceView.panes.find('.pane-row > .pane .editor:eq(1)').view()
+        editor2 = atom.workspaceView.panes.find('.pane-row > .pane-column > .pane .editor:eq(0)').view()
+        editor4 = atom.workspaceView.panes.find('.pane-row > .pane-column > .pane .editor:eq(1)').view()

         expect(editor1.getPath()).toBe atom.project.resolve('a')
         expect(editor2.getPath()).toBe atom.project.resolve('b')
@@ -298,14 +298,14 @@
           expect(pane2[0]).not.toBe pane1[0]
           expect(editor.getPath()).toBe require.resolve('./fixtures/dir/b')

-          expect(atom.workspaceView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
+          expect(atom.workspaceView.panes.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]]

           editor = atom.workspaceView.openSync('file1', split: 'right')
           pane3 = atom.workspaceView.getActivePane()
           expect(pane3[0]).toBe pane2[0]
           expect(editor.getPath()).toBe require.resolve('./fixtures/dir/file1')

-          expect(atom.workspaceView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
+          expect(atom.workspaceView.panes.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]]

   describe ".openSingletonSync(filePath, options)", ->
     describe "when there is an active pane", ->
@@ -320,7 +320,7 @@
         expect(pane2[0]).not.toBe pane1[0]
         expect(pane1.itemForUri('b')).toBeFalsy()
         expect(pane2.itemForUri('b')).not.toBeFalsy()
-        expect(atom.workspaceView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
+        expect(atom.workspaceView.panes.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]]

         pane1.focus()
         expect(atom.workspaceView.getActivePane()[0]).toBe pane1[0]
@@ -330,7 +330,7 @@
         expect(pane3[0]).toBe pane2[0]
         expect(pane1.itemForUri('b')).toBeFalsy()
         expect(pane2.itemForUri('b')).not.toBeFalsy()
-        expect(atom.workspaceView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
+        expect(atom.workspaceView.panes.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]]

       it "handles split: left by opening to the left pane when necessary", ->
         atom.workspaceView.openSingletonSync('b', split: 'right')
@@ -344,7 +344,7 @@

         expect(pane1.itemForUri('file1')).toBeTruthy()
         expect(pane2.itemForUri('file1')).toBeFalsy()
-        expect(atom.workspaceView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
+        expect(atom.workspaceView.panes.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]]

         pane2.focus()
         expect(atom.workspaceView.getActivePane()[0]).toBe pane2[0]
@@ -352,7 +352,7 @@
         atom.workspaceView.openSingletonSync('file1', split: 'left')
         activePane = atom.workspaceView.getActivePane()
         expect(activePane[0]).toBe pane1[0]
-        expect(atom.workspaceView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
+        expect(atom.workspaceView.panes.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]]

       it "reuses the file when already open", ->
         atom.workspaceView.openSync('b')


email: password resets are categorized and HTMLed to work better with the sendgrid template

--- salvus/hub.coffee
+++ salvus/hub.coffee
@@ -5587,35 +5587,32 @@
         (cb) ->
             # send an email to mesg.email_address that has a password reset link
             body = """
-                Hello,
-
-                Somebody just requested to change the password on your SageMathCloud account.
-                If you requested this password change, please click this link:
-
-                     https://cloud.sagemath.com#forgot-#{id}
-
-                If you don't want to change your password, ignore this message.
-
-                In case of problems, email help@sagemath.com immediately (or just reply to this email).
-
-
-
-
-
-
-
-
-
-
-
-                ---
+
Hello,

+

+

+                Somebody just requested to change the password of your SageMathCloud account.
+                If you requested this password change, please click this link:

+

+

+
+                  https://cloud.sagemath.com#forgot-#{id}
+
+

+

+
If you don't want to change your password, ignore this message.

+

+
In case of problems, email
+                help@sagemath.com immediately
+                (or just reply to this email).
+

                 """

             send_email
-                subject : 'SageMathCloud password reset confirmation'
+                subject : 'SageMathCloud Password Reset'
                 body    : body
                 from    : 'SageMath Help <help@sagemath.com>'
                 to      : mesg.email_address
+                category: "password_reset"
                 cb      : cb
     ], (err) ->
         if err


Teamwork: Fixed teamwork opening for /Develop/Teamwork?playground=PlaygroundName urls.

--- client/landing/app/Applications/Teamwork.kdapplication/Views/teamworkapp.coffee
+++ client/landing/app/Applications/Teamwork.kdapplication/Views/teamworkapp.coffee
@@ -7,9 +7,21 @@

     super options, data

+    @createTeamwork()
+
+    if options.playground
+      @doCurlRequest playgroundsManifest, (err, manifests) =>
+        for manifest in manifests when manifest.name is options.playground
+          url = manifest.manifestUrl
+        @handlePlaygroundSelection options.playground, url
+    else
+      @teamwork.on "PanelCreated", =>
+        @doCurlRequest playgroundsManifest, (err, manifest) =>
+          @populatePlaygroundsButton manifest
+
+  createTeamwork: ->
     options               = @getOptions()
     instanceName          = if location.hostname is "localhost" then "teamwork-local" else "kd-prod-1"
-
     @teamwork             = new TeamworkWorkspace
       name                : options.name                or "Teamwork"
       joinModalTitle      : options.joinModalTitle      or "Join a coding session"
@@ -50,13 +62,6 @@
           ]
       ]

-    if options.playground
-      @handlePlaygroundSelection options.playground
-    else
-      @teamwork.on "PanelCreated", =>
-        @doCurlRequest "https://raw.github.com/koding/Teamwork/master/Playgrounds/manifest.json", (err, manifest) =>
-          @populatePlaygroundsButton manifest
-
   populatePlaygroundsButton: (playgrounds) ->
     button   = @teamwork.getActivePanel().headerButtons.Playgrounds
     menu     = []


Add language-diff

Add language-diff

--- atom/packages.cson
+++ atom/packages.cson
@@ -21,6 +21,7 @@
   "git-history"
   "jshint"
   "jumpy"
+  "language-diff"
   "language-docker"
   "language-elixir"
   "language-lisp"


Add PHP try..catch snippets

Add PHP try..catch snippets

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -398,6 +398,14 @@
     'prefix': 'dowhile'
     'body': 'do {\n\t$1\n} while ( ${2:condition} );'

+  'try':
+    'prefix': 'try'
+    'body': 'try {\n\t$1\n} $2'
+
+  'catch':
+    'prefix': 'catch'
+    'body': 'catch ( ${1:Exception} \\$${2:e} ) {\n\t$3\n} $4'
+
   'array (one-line)':
     'prefix': 'ar'
     'body': 'array( $1 )'


Add SELECT DISTINCT snippet

Add SELECT DISTINCT snippet

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -613,6 +613,10 @@
     'prefix': 'select'
     'body': 'SELECT ${1:*} FROM ${2:tname} WHERE ${3:condition};'

+  'select distinct':
+    'prefix': 'selectdistinct'
+    'body': 'SELECT DISTINCT ${1:*} FROM ${2:tname} WHERE ${3:condition};'
+
   'where':
     'prefix': 'where'
     'body': 'WHERE '


make the logging function not completely messed up

--- gameState.coffee
+++ gameState.coffee
@@ -679,15 +679,16 @@
     newState.phase = @phase
     newState

-    # Games can provide output using the `log` function. For now it just
-    # prints to the console.
-    log = (obj) ->
-      if console?
-        console.log(obj)
-
-    # A warning has a similar effect to a log message, but indicates that
-    # something has gone wrong with the gameplay.
-    warn = (obj) ->
+  # Games can provide output using the `log` function. For now it just
+  # prints to the console.
+  log: (obj) ->
+    if console?
+      console.log(obj)
+
+  # A warning has a similar effect to a log message, but indicates that
+  # something has gone wrong with the gameplay.
+  warn: (obj) ->
+    if console?
       console.log("WARNING: ", obj)


Fixed copy paste errors in log statments.

--- src/api/transactions.coffee
+++ src/api/transactions.coffee
@@ -60,8 +60,8 @@

 	// Test if the user is authorised
 	if (authorisation.inGroup('admin', this.authenticated) === false) {
-		logger.info('User ' +this.authenticated.email+ ' is not an admin, API access to findClientById denied.')
-		this.body = 'User ' +this.authenticated.email+ ' is not an admin, API access to findClientById denied.'
+		logger.info('User ' +this.authenticated.email+ ' is not an admin, API access to addTransaction denied.')
+		this.body = 'User ' +this.authenticated.email+ ' is not an admin, API access to addTransaction denied.'
 		this.status = 401;
 		return;
 	}
@@ -152,8 +152,8 @@

 	// Test if the user is authorised
 	if (authorisation.inGroup('admin', this.authenticated) === false) {
-		logger.info('User ' +this.authenticated.email+ ' is not an admin, API access to findClientById denied.')
-		this.body = 'User ' +this.authenticated.email+ ' is not an admin, API access to findClientById denied.'
+		logger.info('User ' +this.authenticated.email+ ' is not an admin, API access to updateTransaction denied.')
+		this.body = 'User ' +this.authenticated.email+ ' is not an admin, API access to updateTransaction denied.'
 		this.status = 401;
 		return;
 	}
@@ -180,8 +180,8 @@

 	// Test if the user is authorised
 	if (authorisation.inGroup('admin', this.authenticated) === false) {
-		logger.info('User ' +this.authenticated.email+ ' is not an admin, API access to findClientById denied.')
-		this.body = 'User ' +this.authenticated.email+ ' is not an admin, API access to findClientById denied.'
+		logger.info('User ' +this.authenticated.email+ ' is not an admin, API access to removeTransaction denied.')
+		this.body = 'User ' +this.authenticated.email+ ' is not an admin, API access to removeTransaction denied.'
 		this.status = 401;
 		return;
 	}


fix noddy job progress check

fix noddy job progress check

--- server/job.coffee
+++ server/job.coffee
@@ -570,7 +570,7 @@
     job_process.update {job: proc.job, order: proc.order+1}, {available:true}
   try
     for jb in proc.job
-      if not job_process.find(job:proc.job)?
+      if not job_process.find(job:jb)?
         # note this only lets us know when the last one was run, so if we run progress on first process, then cap for 15 minutes,
         # and the whole job finishes within 15 minutes, no other progress check would run
         # need a way to run this IN 15 minutes, but only once - don't need to run multiples if already waiting to run


Better spacing

Better spacing

--- js/inner/action_log.coffee
+++ js/inner/action_log.coffee
@@ -93,6 +93,8 @@
   report_build: () =>
     VM.raiseIntercomDialog('I think I found a bug in Circle at ' + window.location + '\n\n')

+
+
 class Step extends CI.inner.Obj

   observables: =>


Pull line length out into its own variable.

--- src/buffered-process.coffee
+++ src/buffered-process.coffee
@@ -115,8 +115,9 @@
       buffered += data
       lastNewlineIndex = data.lastIndexOf('\n')
       if lastNewlineIndex isnt -1
-        onLines(buffered.substring(0, lastNewlineIndex + bufferedLength + 1))
-        buffered = buffered.substring(lastNewlineIndex + bufferedLength + 1)
+        lineLength = lastNewlineIndex + bufferedLength + 1
+        onLines(buffered.substring(0, lineLength))
+        buffered = buffered.substring(lineLength)

     stream.on 'close', =>
       return if @killed


Update app.coffee

Update app.coffee

--- backend/app.coffee
+++ backend/app.coffee
@@ -54,4 +54,5 @@
 # Check whether user exists or not
 app.get '/api/checkuser/:username', routes.checkUser

+# here and in similar places to use winston or something like this instead of using console.log
 console.log "API is starting on port #{PORT}"


let the user know when the order update is done

--- client/landing/app/Applications/Environments.kdapplication/views/FirewallMapperView.coffee
+++ client/landing/app/Applications/Environments.kdapplication/views/FirewallMapperView.coffee
@@ -79,6 +79,9 @@
     newRulesList = (item.getData() for item in @ruleListController.itemsOrdered)
     domain.updateRuleOrders newRulesList, (err, response)->
       return console.log err if err?
+      new KDNotificationView
+        title : "Order of your rule list has been successfully updated."
+        type  : "top"

 class FirewallFilterListItemView extends KDListItemView


removed some parens

removed some parens

--- game_of_life.coffee
+++ game_of_life.coffee
@@ -13,14 +13,14 @@
       @alive_color = alive_color
       @grid = []
       @fillGrid()
-      @createCells(canvas_width, canvas_height)
+      @createCells canvas_width, canvas_height

     fillGrid: ->
       for i in [0...@num_rows]
         row = []
         for [0...@num_columns]
-          row.push({ alive : false })
-        @grid.push(row)
+          row.push { alive : false }
+        @grid.push row

     createCells: (canvas_width, canvas_height) ->
       paper = Raphael("canvas", canvas_width, canvas_height)
@@ -33,7 +33,7 @@
         cell.attr(stroke: "#d8d8d8").data("row", row).data("column", column).click ->
           cell_data = world.grid[@data("row")][@data("column")]
           cell_data.alive = !cell_data.alive
-          world.colorCell(@, cell_data.alive)
+          world.colorCell @, cell_data.alive

       for j in [0...@num_rows]
         for k in [0...@num_columns]
@@ -47,7 +47,7 @@
         for k in [0...@num_columns]
           cell_id_string = "cell_" + j + "_" + k
           cell = $("#" + cell_id_string)
-          @colorCell(cell, @grid[j][k].alive)
+          @colorCell cell, @grid[j][k].alive

     updateGrid: (results_grid) ->
       for j in [0...results_grid.length]


Also call callback

Also call callback

--- src/components/Graph.coffee
+++ src/components/Graph.coffee
@@ -74,6 +74,7 @@
     @network.start (err) =>
       return callback err if err
       super()
+      do callback

   checkComponent: (name, process) ->
     unless process.component.isReady()


Anchor to the end

Anchor to the end

--- src/bgg.coffee
+++ src/bgg.coffee
@@ -25,7 +25,7 @@
 bgg = require('bgg')(options)

 module.exports = (robot) ->
-  robot.respond /bgg search (.*)/, (msg) ->
+  robot.respond /bgg search (.*)$/, (msg) ->
     params =
       query: encodeURI msg.match[1]
       type: 'boardgame'


added key prop

added key prop

--- imports/ui/instance/steps.cjsx
+++ imports/ui/instance/steps.cjsx
@@ -22,7 +22,7 @@
   render: ->

       {@props.items.map (step) ->
-

+

           {renderStep step}


little refactoring

little refactoring

--- src/bot.coffee
+++ src/bot.coffee
@@ -72,38 +72,33 @@
   connect: (roomId) ->
     if not /^[0-9a-f]{24}$/.test(roomId)
       throw new Error "Invalid roomId: cannot connect to '#{roomId}'"
-    @which_server roomId, (host, port) ->
+    @whichServer roomId, (host, port) ->
       url  = "ws://#{host}:#{port}/socket.io/websocket"
       @ws = new WebSocket(url)
       @ws.onmessage = @onMessage.bind(@)
       @ws.onclose = @onClose.bind(@)

-  listen: (port, address) ->
+  whichServer: (roomid, callback) ->
     self = @
-    http.createServer((req, res) ->
+    options =
+      host: 'turntable.fm'
+      port: 80
+      path: "/api/room.which_chatserver?roomid=#{roomid}"
+    http.get options, (res) ->
       dataStr = ''
-      req.on 'data', (chunk) ->
+      res.on 'data', (chunk) ->
         dataStr += chunk.toString()
-      req.on 'end', ->
-        data = querystring.parse(dataStr)
-        req._POST = data
-        self.emit('httpRequest', req, res)
-    ).listen(port, address)
-
-
-  tcpListen: (port, address) ->
-    self = @
-    net.createServer((socket) ->
-      socket.on 'connect', ->
-        self.emit('tcpConnect', socket)
-      socket.on 'data', (data) ->
-        msg = data.toString()
-        if msg[msg.length - 1] == '\n'
-          self.emit('tcpMessage', socket, msg.substr(0, msg.length-1))
-      socket.on 'end', ->
-        self.emit('tcpEnd', socket)
-    ).listen(port, address)
+      res.on 'end', ->
+        try
+          data = JSON.parse dataStr
+        catch err
+          data = []
+        if data[0]
+          [host, port] = data[1].chatserver
+          callback.call(self, host, port)
+        else
+          @log "Failed to determine which server to use: #{dataStr}"

   setTmpSong: (data) ->
@@ -263,29 +258,35 @@
     @_msgId++

-  which_server: (roomid, callback) ->
-    self = @
-    options =
-      host: 'turntable.fm'
-      port: 80
-      path: "/api/room.which_chatserver?roomid=#{roomid}"
-    http.get options, (res) ->
-      dataStr = ''
-      res.on 'data', (chunk) ->
-        dataStr += chunk.toString()
-      res.on 'end', ->
-        try
-          data = JSON.parse dataStr
-        catch err
-          data = []
-        if data[0]
-          callback.call(self, data[1].chatserver[0], data[1].chatserver[1])
-        else
-          @log "Failed to determine which server to use: #{dataStr}"
-
-
   close: ->
     @ws.close()
+
+
+  listen: (port, address) ->
+    self = @
+    http.createServer((req, res) ->
+      dataStr = ''
+      req.on 'data', (chunk) ->
+        dataStr += chunk.toString()
+      req.on 'end', ->
+        data = querystring.parse(dataStr)
+        req._POST = data
+        self.emit('httpRequest', req, res)
+    ).listen(port, address)
+
+
+  tcpListen: (port, address) ->
+    self = @
+    net.createServer((socket) ->
+      socket.on 'connect', ->
+        self.emit('tcpConnect', socket)
+      socket.on 'data', (data) ->
+        msg = data.toString()
+        if msg[msg.length - 1] == '\n'
+          self.emit('tcpMessage', socket, msg.substr(0, msg.length-1))
+      socket.on 'end', ->
+        self.emit('tcpEnd', socket)
+    ).listen(port, address)

   roomNow: (callback) ->
@@ -618,13 +619,13 @@

   modifyProfile: (profile, callback) ->
     rq = api: 'user.modify_profile'
-    if profile.name       then rq.name       = profile.name
-    if profile.twitter    then rq.twitter    = profile.twitter
-    if profile.facebook   then rq.facebook   = profile.facebook
-    if profile.website    then rq.website    = profile.website
-    if profile.about      then rq.about      = profile.about
-    if profile.topartists then rq.topartists = profile.topartists
-    if profile.hangout    then rq.hangout    = profile.hangout
+    rq.name       = profile.name if profile.name
+    rq.twitter    = profile.twitter if profile.twitter
+    rq.facebook   = profile.facebook if profile.facebook
+    rq.website    = profile.website if profile.website
+    rq.about      = profile.about if profile.about
+    rq.topartists = profile.topartists if profile.topartists
+    rq.hangout    = profile.hangout if profile.hangout
     @_send rq, callback


Fix typo.

Fix typo.

--- src/cli.coffee
+++ src/cli.coffee
@@ -25,7 +25,7 @@
     migrate.error(err)

   usage = ->
-    console.error 'Usage: migrate <generate|one|down|pending|all|test [--name <migration_name>]'
+    console.error 'Usage: migrate <generate|one|down|pending|all|test [--name <migration_name>]>'
     process.exit 0

   command = argv._[0]


fix messages with new adapter format

fix messages with new adapter format

--- src/irc.coffee
+++ src/irc.coffee
@@ -5,34 +5,34 @@
 Irc = require 'irc'

 class IrcBot extends Adapter
-  send: (user, strings...) ->
+  send: (envelope, strings...) ->
     for str in strings
       if not str?
         continue
-      if user.room
-        console.log "#{user.room} #{str}"
-        @bot.say(user.room, str)
-      else if user.name
-        console.log "#{user.name} #{str}"
-        @bot.say(user.name, str)
+      if envelope.user.room
+        console.log "#{envelope.user.room} #{str}"
+        @bot.say(envelope.user.room, str)
+      else if envelope.user.name
+        console.log "#{envelope.user.name} #{str}"
+        @bot.say(envelope.user.name, str)
       else
-        console.log "#{user} #{str}"
-        @bot.say(user, str)
+        console.log "#{envelope.user} #{str}"
+        @bot.say(envelope.user, str)

-  notice: (user, strings...) ->
+  notice: (envelope, strings...) ->
     for str in strings
       if not str?
         continue
-      if user.room
-        console.log "notice #{user.room} #{str}"
-        @bot.notice(user.room, str)
+      if envelope.user.room
+        console.log "notice #{envelope.user.room} #{str}"
+        @bot.notice(envelope.user.room, str)
       else
-        console.log "notice #{user.name} #{str}"
-        @bot.notice(user.name, str)
+        console.log "notice #{envelope.user.name} #{str}"
+        @bot.notice(envelope.user.name, str)

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

   join: (channel) ->
     self = @


get going

get going

--- src/prime.iced
+++ src/prime.iced
@@ -67,7 +67,7 @@
 fermat2_test = (n) ->
   #y = nbv(2).modPow(n.subtract(nbv(1)),n)
   #ret = (y.compareTo(BigInteger.ONE) is 0)
-  #fta.stop()
+  #fta.start()
   #return ret
   #console.log(y.toString())
   t = nbv(1)
@@ -86,6 +86,7 @@
     t = t.mod(n)
   #console.log t.toString()
   ret = (t.compareTo(nbv(2)) is 0)
+  #fta.stop()
   ret

 #--------------
@@ -118,7 +119,7 @@
 # @param {number} iter Get 1 - 4^{-iter} satisfaction
 # @return {Boolean} T/F depending on whether it passes or not
 #
-miller_rabin = (n, iter) ->
+miller_rabin = (n, iter, progress_hook) ->
   return false if n.compareTo(BigInteger.ZERO) <= 0
   if n.compareTo(nbv(7) <= 0)
     iv = n.intValue()
@@ -132,6 +133,7 @@
   msrf = new MS_RandomFountain()

   for i in [0...iter]
+    progress_hook? { what : "mr", i, total : iter, p : n }
     a = ms_random_zn msrf, n
     y = a.modPow(r,n)
     if y.compareTo(BigInteger.ONE) isnt 0
@@ -140,6 +142,7 @@
         y = y.square().mod(n)
         return false if y.compareTo(BigInteger.ONE) is 0

+  progress_hook? { what : "mr", i : iter, total : iter, p : n }
   return true

 #=================================================================
@@ -176,7 +179,8 @@
     for sp,i in small_primes
       while (@mods[i] + @inc >= sp)
         @mods[i] -= sp
-        return true if (@mods[i] + @inc) is 0
+        if (@mods[i] + @inc) is 0
+          return true
     return false

   #-----------------------
@@ -224,7 +228,7 @@
 # Use the sieve for primality testing, which in the case of regular odd
 # primes is [1,2], and for strong primes is more interesting...
 #
-prime_search = (start, range, sieve, iters=32) ->
+prime_search = (start, range, sieve, progress_hook, iters=20) ->
   pf = new PrimeFinder start, sieve
   pf.setmax range
   pvec = (pp while ((pp = pf.next_weak()).compareTo(BigInteger.ZERO) > 0))
@@ -232,7 +236,15 @@
   while pvec.length
     i = ms_random_word() % pvec.length
     p = pvec[i]
-    return p if (ft = fermat2_test(p)) and miller_rabin(p, iters)
+
+    progress_hook? { what : "fermat", p }
+    if not fermat2_test(p) then # noop
+    else if miller_rabin(p, iters, progress_hook)
+      progress_hook? { what : "passed_mr", p }
+      return p
+    else
+      progress_hook? { what : "failed_mr", p }
+
     tmp = pvec.pop()
     pvec[i] = tmp if i < pvec.length

@@ -248,8 +260,6 @@
   recharge : (cb) ->
     await prng.generate Math.floor(7000/8), defer tmp
     @buf = tmp.to_buffer()
-    console.log "Generate ->"
-    console.log @buf
     cb()
   nextBytes : (v) ->
     throw new Error "need a recharge!" unless @buf?
@@ -267,8 +277,9 @@
 # @param {number} nbits The number of bits in the prime
 # @param {number} iters The number of time to run Miller-Rabin
 # @param {callback} Callback with the {BigInteger} result
-#
-random_prime = (nbits, iters, cb) ->
+# @param {function} progress_hook A hook to call to update progress
+#
+random_prime = (nbits, iters, cb, progress_hook) ->
   srf = new StrongRandomFountain()
   sieve = [1,2]
   go = true
@@ -277,10 +288,10 @@
     await srf.recharge defer()
     p = new BigInteger nbits, srf
     p = p.setBit(0).setBit(nbits-1)
-    console.log "Starting from #{p.toString()}"
-    p = prime_search p, nbits/2, sieve, iters
+    progress_hook? { what : "guess", p }
+    p = prime_search p, nbits/4, sieve, progress_hook, iters
     go = (p.compareTo(BigInteger.ZERO) is 0)
-    console.log "iter #{i++} -> #{go}"
+  progress_hook? { what : "found", p }
   cb p

 #=================================================================
@@ -291,8 +302,13 @@
 exports.miller_rabin = miller_rabin
 exports.random_prime = random_prime

-await random_prime 4096, 10, defer p
+#=================================================================
+
+progress_hook = (obj) ->
+  s = obj.p.toString()
+  interval = if obj.total? and obj.i? then "(#{obj.i} of #{obj.total})" else ""
+  console.log "+ #{obj.what} #{interval} #{s[0...3]}....#{s[(s.length-6)...]}"
+await random_prime 3072, 10, defer(p), progress_hook
 console.log p.toString()
-console.log "avg -> #{fta.avg()}"
 process.exit -1


fixed wrong x,x return error

fixed wrong x,x return error

--- js/msa/stage/tileStage.coffee
+++ js/msa/stage/tileStage.coffee
@@ -78,9 +78,13 @@
       mouseX = e.offsetX
       mouseY = e.offsetY

-      unless e.offsetX?
+      unless mouseX?
         mouseX = e.layerX
         mouseY = e.layerY
+
+      unless mouseX?
+        mouseX = e.pageX
+        mouseY = e.pageY

       unless mouseX?
         console.log e
@@ -88,7 +92,7 @@
         return

       # TODO: else
-      return [mouseX,mouseX]
+      return [mouseX,mouseY]

     _onDblClick: (e) ->
       #if not @rect? or @rect?.left is 0


update mewbot

update mewbot

--- core/mewbot.coffee
+++ core/mewbot.coffee
@@ -130,7 +130,8 @@
             else
                 if profileName is "default"
                     if callback
-                        callback("config profile [#{profileName}] not found")
+                        @logger.debug "default config profile not found"
+                        callback()
                 else
                     @logger.debug "config profile [#{profileName}] not found, using default profile ..."
                     @changeProfile "default",callback


Synchronous WirePattern is deprecated

Synchronous WirePattern is deprecated


[grunt] uncomment SCSS Lint in `test` task

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -147,8 +147,7 @@
   A task for scss and js linting
   ###
   grunt.registerTask 'test', [
-    # @todo: fix all errors in scss or change linting rules
-    # 'scsslint:build'
+    'scsslint:build'
     'eslint:build'
   ]


Remove missed console.log debugging lines in AuthenticationController.coffee

--- services/web/app/coffee/Features/Authentication/AuthenticationController.coffee
+++ services/web/app/coffee/Features/Authentication/AuthenticationController.coffee
@@ -99,7 +99,6 @@

 	_redirectToLoginPage: (req, res) ->
 		logger.log url: req.url, "user not logged in so redirecting to login page"
-		console.log req.session
 		req.query.redir = req.path
 		url = "/login?#{querystring.stringify(req.query)}"
 		res.redirect url
@@ -142,5 +141,4 @@
 			req.session[key] = value

 		req.session.user = lightUser
-		console.log "LOGGED IN", req.session
 		callback()


add logo name

add logo name

--- lib/_config/_config.coffee
+++ lib/_config/_config.coffee
@@ -4,7 +4,7 @@
 @Config =

 	# Basic Details
-	name: 'My App'
+	name: 'Emblem Conference 2016'
 	title: ->
 			TAPi18n.__ 'configTitle'
 	subtitle: ->


No skipOverExistingClosingBracket when cursor at escape sequence No bracket deletion when pressing backspace at escape sequence

--- lib/bracket-matcher.coffee
+++ lib/bracket-matcher.coffee
@@ -59,11 +59,12 @@
     hasWordAfterCursor = /\w/.test(nextCharacter)
     hasWordBeforeCursor = /\w/.test(previousCharacter)
     hasQuoteBeforeCursor = previousCharacter is text[0]
-    hasEscapeSequenceBeforeCursor = previousCharacter is '\\' or prepreviousCharacter is '\\'
+    hasBackslashBeforeCursor = previousCharacter is '\\'
+    hasEscapeSequenceBeforeCursor = hasBackslashBeforeCursor or prepreviousCharacter is '\\'

     autoCompleteOpeningBracket = atom.config.get('bracket-matcher.autocompleteBrackets') and @isOpeningBracket(text) and not hasWordAfterCursor and not (@isQuote(text) and (hasWordBeforeCursor or hasQuoteBeforeCursor)) and not hasEscapeSequenceBeforeCursor
     skipOverExistingClosingBracket = false
-    if @isClosingBracket(text) and nextCharacter == text
+    if @isClosingBracket(text) and nextCharacter == text and not hasBackslashBeforeCursor
       if bracketMarker = _.find(@bracketMarkers, (marker) => marker.isValid() and marker.getBufferRange().end.isEqual(cursorBufferPosition))
         skipOverExistingClosingBracket = true

@@ -99,9 +100,19 @@
     return unless @editor.getSelection().isEmpty()

     cursorBufferPosition = @editor.getCursorBufferPosition()
-    previousCharacter = @editor.getTextInBufferRange([cursorBufferPosition.add([0, -1]), cursorBufferPosition])
+    previousCharacters = @editor.getTextInBufferRange([cursorBufferPosition.add([0, -2]), cursorBufferPosition]).split('')
     nextCharacter = @editor.getTextInBufferRange([cursorBufferPosition, cursorBufferPosition.add([0,1])])
-    if @pairedCharacters[previousCharacter] is nextCharacter
+
+    previousCharacter = ''
+    prepreviousCharacter = ''
+
+    previousCharacter = previousCharacters.pop() if previousCharacters.length > 0
+    prepreviousCharacter = previousCharacters.pop()  if previousCharacters.length > 0
+
+    hasBackslashBeforeCursor = previousCharacter is '\\'
+    hasEscapeSequenceBeforeCursor = hasBackslashBeforeCursor or prepreviousCharacter is '\\'
+
+    if (@pairedCharacters[previousCharacter] is nextCharacter) and not hasEscapeSequenceBeforeCursor
       @editor.transact =>
         @editor.moveCursorLeft()
         @editor.delete()


Remove old set_complement implementation.

(Was commented out)

--- src/set/set_complement.coffee
+++ src/set/set_complement.coffee
@@ -19,23 +19,3 @@
   _addComplement: (items, opposite) ->
     itemsToAdd = (item for item in items when opposite.has(item))
     @add(itemsToAdd...) if itemsToAdd.length > 0
-
-#= require ./binary_set_operation
-
-#class Batman.SetComplement extends Batman.BinarySetOperation
-  #_itemsWereAddedToSource: (source, opposite, items...) ->
-    #if source == @left
-      #@_addComplement(items, opposite)
-    #else
-      #itemsToRemove = (item for item in items when opposite.has(item))
-      #@remove itemsToRemove...
-
-  #_itemsWereRemovedFromSource: (source, opposite, items...) ->
-    #if source == @left
-      #@remove items...
-    #else
-      #@_addComplement(items, opposite)
-
-
-
-


fix a new race condition related to sessions, caused by fixing a different one.

--- src/smc-webapp/session.coffee
+++ src/smc-webapp/session.coffee
@@ -32,9 +32,10 @@
             timeout : 0
             cb      : =>
                 @restore()
+                @_initialized = true

     save: =>
-        if @_ignore
+        if @_ignore or not @_initialized
             return
         @_state = get_session_state(@redux)
         @_save_to_local_storage()


Guard against null elements

Guard against null elements

--- src/trix/utilities/dom.coffee
+++ src/trix/utilities/dom.coffee
@@ -42,6 +42,7 @@
     document.createTreeWalker(root, whatToShow, filter, entityReferenceExpansion)

   findNodeForContainerAtOffset: (container, offset) ->
+    return unless container
     if container.nodeType is Node.TEXT_NODE
       container
     else if offset is 0


Some permission fixes fro vm.*().

Some permission fixes fro vm.*().

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -673,6 +673,8 @@
 backup = $coroutine ({vm, remoteId, file, compress, onlyMetadata}) ->
   yield @backupVm({vm, remoteId, file, compress, onlyMetadata})

+backup.permission = 'admin'
+
 backup.params = {
   id: {type: 'string'}
   remoteId: { type: 'string' }
@@ -722,6 +724,8 @@
     compress,
     onlyMetadata
   })
+
+rollingBackup.permission = 'admin'

 rollingBackup.params = {
   id: { type: 'string' }
@@ -945,6 +949,8 @@
     sr = xapi.pool.$default_SR
     if not sr
       throw new InvalidParameters('there is not default SR in this pool')
+
+    # FIXME: must have administrate permission on default SR.
   else
     xapi = @getXapi(sr)


Fix missing function

Fix missing function

--- hammurabi.coffee
+++ hammurabi.coffee
@@ -20,6 +20,7 @@
   }

 has_plague = ()-> _.random(1, 100) <= 15
+has_rat_problems = ()-> _.random(1, 100) <= 40

 too_many_people_died = (game)-> game.total_starved / (game.population + game.starved - game.newcomers) >= 0.45


Wrap type checks in String(...)

Wrap type checks in String(...)

--- scripts/home-files.coffee
+++ scripts/home-files.coffee
@@ -159,16 +159,16 @@
     preload: ->
         return if not @blob?
         for file in @blob.files
-            if file.type.startsWith('image/') and file.type != 'image/gif'
+            if String(file.type).startsWith('image/') and String(file.type) != 'image/gif'
                 _ = document.createElement('img')
-                _.src = file.file
+                _.src = file.url
             else if file.type.startsWith('video/')
                 _ = document.createElement('video')
                 _.preload = 'auto'
-                _.src = file.file
+                _.src = file.url
             else if file.type.startsWith('audio/')
                 _ = document.createElement('audio')
                 _.preload = 'auto'
-                _.src = file.file
+                _.src = file.url

 window.MediaFile = MediaFile


Fix for commodity.

Fix for commodity.

--- snippets/php.cson
+++ snippets/php.cson
@@ -47,7 +47,7 @@
     'prefix': 'php:Contract'
     'leftLabelHTML': 'Ⓢ'
     'rightLabelHTML': 'PHP Interface'
-    'body': """<?php namespace App\\\\Contracts\\\\${1:Subfolder};
+    'body': """<?php namespace App\\\\Contracts\\\\${1:Repositories};

       /**
        * Contract ${2:ContractName}...


modules refactor

modules refactor

--- src/camera.coffee
+++ src/camera.coffee
@@ -137,5 +137,6 @@
 Camera.defaultModules = [
   "ZSort"
   "Zoom"
-  "R"
+  "Rotate"
+  "Shake"
 ]


Typo in Help

Typo in Help

--- help/this.coffee
+++ help/this.coffee
@@ -1,7 +1,7 @@
 #
 # # This Help
 #
-module.export = """
+module.exports = """
 This is a Terminal, in your Browser. Think of it like a text interface to
 your Virtual Machine, provided by Koding.


dumb error

dumb error

--- src/chrome.coffee
+++ src/chrome.coffee
@@ -13,7 +13,7 @@
     if panel.runModal isnt OSX.NSFileHandlingPanelOKButton
       return null
     filename = panel.filenames.lastObject
-    localStorage.lastOpenedFilename = filename
+    localStorage.lastOpenedPath = filename
     filename

   @openURL: (url) ->
@@ -28,7 +28,7 @@
       return null
     panel.filenames.lastObject

-  writeToPasteboard: (text) ->
+  @writeToPasteboard: (text) ->
     pb = OSX.NSPasteboard.generalPasteboard
     pb.declareTypes_owner [OSX.NSStringPboardType], null
     pb.setString_forType text, OSX.NSStringPboardType


Version bump

Version bump

--- src/client.coffee
+++ src/client.coffee
@@ -2,7 +2,7 @@
 Postie.js
 Copyright 2013 Paul Springett

-Version: 0.1.0
+Version: 0.1.1
 Author: Paul Springett
 URL: http://github.com/paulspringett/postie.js

@@ -81,7 +81,6 @@
     document.body.appendChild frame

     @_frame = frame.contentWindow
-

   # Create methods on this client from the list of endpoints from the server
   #


improve docs about grouped settings

improve docs about grouped settings

See https://github.com/atom/settings-view/pull/698#issuecomment-171725215

--- src/config.coffee
+++ src/config.coffee
@@ -203,10 +203,26 @@
 #       maximum: 11.5
 # ```
 #
-# #### object
-#
-# Value must be an object. This allows you to nest config options. Sub options
-# must be under a `properties key`
+# #### color
+#
+# Values will be coerced into a {Color} with `red`, `green`, `blue`, and `alpha`
+# properties that all have numeric values. `red`, `green`, `blue` will be in
+# the range 0 to 255 and `value` will be in the range 0 to 1. Values can be any
+# valid CSS color format such as `#abc`, `#abcdef`, `white`,
+# `rgb(50, 100, 150)`, and `rgba(25, 75, 125, .75)`.
+#
+# ```coffee
+# config:
+#   someSetting:
+#     type: 'color'
+#     default: 'white'
+# ```
+#
+# ### Grouping
+#
+# A config setting with the type `object` allows grouping a set of config
+# settings. The group will be visualy separated and has its own group headline.
+# The sub options must be listed under a `properties` key.
 #
 # ```coffee
 # config:
@@ -219,21 +235,6 @@
 #         maximum: 11.5
 # ```
 #
-# #### color
-#
-# Values will be coerced into a {Color} with `red`, `green`, `blue`, and `alpha`
-# properties that all have numeric values. `red`, `green`, `blue` will be in
-# the range 0 to 255 and `value` will be in the range 0 to 1. Values can be any
-# valid CSS color format such as `#abc`, `#abcdef`, `white`,
-# `rgb(50, 100, 150)`, and `rgba(25, 75, 125, .75)`.
-#
-# ```coffee
-# config:
-#   someSetting:
-#     type: 'color'
-#     default: 'white'
-# ```
-#
 # ### Other Supported Keys
 #
 # #### enum
@@ -273,6 +274,9 @@
 # confusing for users, and a more descriptive title is useful.
 #
 # Descriptions will be displayed below the title in the settings view.
+#
+# For a group of config settings the humanized key or the title and the
+# description are used for the group headline.
 #
 # ```coffee
 # config:


new config schema

new config schema

--- src/config.coffee
+++ src/config.coffee
@@ -1,90 +1,63 @@
 Joi = require('joi')
-Person =
-  type: "foaf:person"
-  name: {
-    context: "foaf:name"
-    schema: Joi.string().alphanum().required()
-  }
-
-
-
-module.exports = {
-  entity: {
-    type: {
-      context: "foaf:Group"
-
+Agent =
+  prefixes: [
+    {
+      id: "http://xmlns.com/foaf/0.1/",
+      key: "foaf"
     }
-    name: {
-      context: "foaf:name",
+  ]
+  properties: [
+    {
+      id: "foaf:name",
+      key: "name",
       schema: Joi.string().alphanum().required()
     }
-    members: {
-      context: "relations:members"
-      entity: Person
-    }
-    sup: {
-      context: "foaf:Group"
-      entity: "self"
+  ]

-    }
-    sub: {
-      context: "foaf:Group"
-      entity: "self"
-    }
-    relations: "http://open.app/relations"
-  }
+module.exports =
+  entity:
+    prefixes: [
+      {
+        id: "http://xmlns.com/foaf/0.1/",
+        key: "foaf"
+      },
+      {
+        id: "http://open.app/relations/"
+        key: "relations"
+      },
+      {
+        id: "https://schema.org/"
+        key: "schema"
+      }
+    ]
+    properties: [
+      {
+        id: "foaf:name",
+        key: "name",
+        schema: Joi.string().alphanum().required()
+      },
+      {
+        id: "schema:description",
+        key: "description",
+        schema: Joi.string().alphanum().optional()
+      },
+      {
+        id: "relations:member",
+        key: "members"
+        schema: Joi.array().optional(),
+        type: Agent

-  url: {
+      }
+    ]
+    type: "foaf:group"
+  url:
     protocol: "http:"
     host: "open.app"
     port: 5000
     pathname: "/circles"
-  }
-}

-module.exports =
-  "@language": "en"
-  type:
-    "@id": "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
-    "@type":  "http://tools.ietf.org/html/rfc3987"

-  # prefixes
-  circles: "http://circles.app.enspiral.com/"
-  foaf: "http://xmlns.com/foaf/0.1/"
-  people:
-    "@id": "http://people.app.enspiral.com/"
-    "@type": "foaf:Person"
-  relations: "http://relations.app.enspiral.com/"
-  schema: "https://schema.org/"
-
-  #properties
-  description: "schema:description"
-  group:
-    "@id": "foaf:Group"
-    "defaultPrefix": "circles"
-  image: "foaf:Image"
-  name: "foaf:name"
-  person: "foaf:Person"

-  createdAt:
-    "@id": "relations:createdAt"
-    "@type": "schema:DateTime"

-  modifiedAt:
-    "@id": "relations:createdAt"
-    "@type": "schema:DateTime"
-
-  #relations
-  members:
-    "@id": "relations:members"
-    "@type": "person"
-    "defaultPrefix": "people"
-  subgroups:
-    "@id": "relations:subgroup"
-    "@type": "group"

-  supergroups:
-    "@id": "relations:supergroup"
-    "@type": "group"
-


Complement config/alias

Complement config/alias

--- src/config.coffee
+++ src/config.coffee
@@ -186,7 +186,9 @@
         id = _id_ if _id_?
         configData = _configData_
         break
-    configData = configArr_rev[0] unless _id_?
+    unless _id_?
+      configData = configArr_rev[0]
+      configArr[configArr.length - 1].alias[id] = id
     id = parsePaths id, configData
     id = parseVars id, configData
     id = normalize id


Adding a run toggle to the engine.

Adding a run toggle to the engine.

--- src/engine.coffee
+++ src/engine.coffee
@@ -32,11 +32,18 @@
         @isRunning = true
         @timestamp = new Date()
         @step()
+        return this

     # Stop the engine's time simulation.
     stop : () ->
         @isRunning = false
         @timestamp = null
+        return this
+
+    # Start the engine if it's stopped, stop it if it's running.
+    toggle : () ->
+        if @isRunning then @stop() else @start()
+        return this

     # Add the given elements to the engine.
     add : (elements...) ->


Added framerate class

Added framerate class

--- src/engine.coffee
+++ src/engine.coffee
@@ -8,6 +8,7 @@
     excludedModules: []
     includedModules: []
     paused: false
+    showFPS: false

   ###*
   The Engine controls the game world and manages game state. Once you
@@ -47,6 +48,9 @@

     queuedObjects = []

+    framerate = Framerate
+      noDOM: true
+
     update = ->
       self.trigger "update"

@@ -66,6 +70,12 @@
         I.objects.invoke("draw", canvas)

       self.trigger "draw", canvas
+
+      if I.showFPS
+        canvas.fillColor("#FFF")
+        canvas.fillText("fps: " + framerate.fps)
+
+      framerate.rendered()

     step = ->
       if !I.paused || frameAdvance


Fix boolean guard error

Fix boolean guard error

--- src/entity.coffee
+++ src/entity.coffee
@@ -56,8 +56,8 @@

   constructor: (opts={}) ->
     @group   = opts.group or fail ("entity must have a group")
-    @solid   = opts.solid   || true
-    @gravity = opts.gravity || true
+    @solid   = if opts.solid?   then opts.solid   else true
+    @gravity = if opts.gravity? then opts.gravity else true
     @speed   = opts.speed   || 0
     @facing  = opts.facing  || ''


modify

modify

--- src/5.test.coffee
+++ src/5.test.coffee
@@ -37,6 +37,7 @@
             quote = null
             slashQuoteReady = true
             wordStarted = false
+            dotAffected = false
             i = 0
             while i < s.length
                 c = s[i]
@@ -53,9 +54,15 @@
                     if "a" <= c <= "z" or "A" <= c <= "Z" or "0" <= c <= "9" or
                             c == "_" or c == "$" or c == "."
                         wordStarted = true
+                    else
+                        wordStarted = false
+                oldDotAffected = dotAffected
+                if quote == null
+                    if c == "."
+                        dotAffected = true
                     else if c == " " or c == "\t" or c == "\n" or c == "\r"
                     else
-                        wordStarted = false
+                        dotAffected = false
                 if c == "\"" and quote == null
                     quote = "double"
                     i++
@@ -72,7 +79,7 @@
                     i++
                 else if c == "\\" and quote != null
                     i += 2
-                else if quote == null and not oldWordStarted and "a" <= c <= "z"
+                else if quote == null and not oldWordStarted and not oldDotAffected and "a" <= c <= "z"
                     t = s.substr(i, 10)
                     if t.indexOf("end") == 0 or t.indexOf("equal") == 0 or t.indexOf("unit") == 0
                         positions.push(i)


Fix async test bug.

Fix async test bug.

--- src/5.test.coffee
+++ src/5.test.coffee
@@ -176,6 +176,9 @@
         @_children.push(newChild)
         @
     addAsync: (description, fun) ->
+        if not fun?
+            fun = description
+            description = ""
         @add(description, fun, true)
         @
     getChildren: ->


fixed the missing quote

fixed the missing quote

--- src/gitlab.coffee
+++ src/gitlab.coffee
@@ -166,7 +166,7 @@
                   splitted = strip_content(hook.object_attributes.description).split  "\r\n"
                   for i in [0...splitted.length]
                     splitted[i] = "> " + splitted[i]
-                  text += "\r\n" + splitted.join "\r\n
+                  text += "\r\n" + splitted.join "\r\n"
                   robot.send user, text
                 else
                   robot.send user, "Merge Request #{bold(hook.object_attributes.iid)}: #{hook.user.username} #{hook.object_attributes.action}  #{hook.object_attributes.title} (#{hook.object_attributes.state}) between #{bold(hook.object_attributes.source_branch)} and #{bold(hook.object_attributes.target_branch)} at #{bold(hook.object_attributes.url)}"


Add functional code

Add functional code

--- src/jeolok.coffee
+++ src/jeolok.coffee
@@ -12,7 +12,53 @@
     root = this,
     factory = ->

-        # TODO
+        class Jeolok
+
+            _oGeolocation = null
+
+            constructor: ->
+                _oGeolocation = window.navigator.geolocation if window?.navigator?.geolocation?
+
+                @position = null
+
+                return
+
+            getCurrentPosition: ( oOptions, fNext ) ->
+                _perform.call this, "getCurrentPosition", oOptions, fNext
+
+            get: ( oOptions, fNext ) ->
+                _perform.call this, "getCurrentPosition", oOptions, fNext
+
+            watchPosition: ( oOptions, fNext ) ->
+                _perform.call this, "watchPosition", oOptions, fNext
+
+            watch: ( oOptions, fNext ) ->
+                _perform.call this, "watchPosition", oOptions, fNext
+
+            clearWatch: ( iWatcherID ) ->
+                _oGeolocation.clearWatch iWatcherID
+
+            _perform = ( sOperation, oOptions = {}, fNext = null ) ->
+                return fNext? new Error "navigator.geolocation isn't available!" unless _oGeolocation
+
+                if ( oOptions instanceof Function ) and fNext is null
+                    fNext = oOptions
+                    oOptions = {}
+
+                fPositionSuccess = ( oPosition ) =>
+                    _fill.call this, oPosition
+                    fNext? null, @position
+
+                fPositionError = ( oError ) ->
+                    fNext? oError
+
+                _oGeolocation[ sOperation ] fPositionSuccess, fPositionError, oOptions
+
+            _fill = ( oPosition ) ->
+                @position = oPosition
+                @position.date = new Date oPosition.timestamp
+
+        return new Jeolok()

 ) ->
     if typeof define is "function" and define.amd


Used a method for retrieving lodash method to prevent occasional startup issues.

--- src/Setter.coffee
+++ src/Setter.coffee
@@ -1,5 +1,5 @@
 deep = (value, other) -> lodash.merge(value, other, deep)
-lodashDefaults = lodash.partialRight(lodash.merge, deep)
+getLodashDefaults = _.once -> lodash.partialRight(lodash.merge, deep)

 Setter =

@@ -40,7 +40,7 @@
   defaults: (obj, defaults) ->
     # Ensure the object is defined and default to an empty object to prevent returning undefined.
     obj ?= {}
-    lodashDefaults.call(lodash, obj, defaults)
+    getLodashDefaults().call(lodash, obj, defaults)

   # @param {*} value
   # @returns Whether the given value is not an non-empty string, null, undefined or NaN.


`vm.delete()` ok.

`vm.delete()` ok.

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -243,15 +243,16 @@

   xapi = @getXAPI VM

-  $each VM.$VBDs, (ref) ->
-    try
-      VBD = @getObject ref
-    catch
-      return
-
-    return if VBD.read_only or not VBD.VDI?
-
-    xapi.call 'VDI.destroy', VBD.VDI
+  if deleteDisks
+    $each VM.$VBDs, (ref) =>
+      try
+        VBD = @getObject ref
+      catch e
+        return
+
+      return if VBD.read_only or not VBD.VDI?
+
+      xapi.call 'VDI.destroy', VBD.VDI

   xapi.call 'VM.destroy', VM.ref


More correctly replace greater-than inequality.

This now inverts the terms' coefficients instead of swapping left and
right.

--- js/inequality.coffee
+++ js/inequality.coffee
@@ -123,9 +123,10 @@
     else
       throw "Unexpected token found: #{tokens[0]}"

-# Validate and convert the inequality to less than
+# Validate and convert the inequality to less than, if necessary
 convert_inequality = (terms) ->
   inequalities = terms.filter (t) -> t.constructor == Inequality
+
   if inequalities.length == 0
     throw "No inequalities in expression: #{terms}"

@@ -138,14 +139,18 @@
   if index == terms.length - 1
     throw "Inequality at the end of expression: #{terms}"

+  # Inequality is present, valid, and already less-than, so we're done
   if inequality.sign == "<"
     return terms

+  # Inequality is greater-than, so we need to invert all terms and replace it
+  invert = (term) -> new Term term.coefficient * -1, term.variable
+
   inequality = new Inequality "<"
-  left = terms.slice(0, index)
-  right = terms.slice(index + 1)
+  left = terms.slice(0, index).map invert
+  right = terms.slice(index + 1).map invert

-  return right.concat(inequality, left)
+  return left.concat(inequality, right)

 # These inputs should all be parseable. Not a maximally reduced set,
 # but I think this covers everything


Remove bug that make the room change to null

--- src/web/js/coffee/Room.coffee
+++ src/web/js/coffee/Room.coffee
@@ -67,7 +67,6 @@

 	refreshTrackQueue: () ->
 		p = @webService.query("room/#{@name}");
-		p.catch () => @_clear();
 		return p.then (data) =>
 			@player = @buildPlayer(data);
 			@trackQueue = @buildTrackQueue(data);


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera.coffee
+++ src/camera.coffee
@@ -38,7 +38,7 @@
     if objectCenter.x > centerRect.right
       I.scroll.x = I.scroll.x.approach(centerRect.right, I.maxVelocity.x)
     else if objectCenter.x < centerRect.left
-      I.scroll.x = I.scroll.x.approach(centerRect.right, I.maxVelocity.x)
+      I.scroll.x = I.scroll.x.approach(centerRect.left, I.maxVelocity.x)

     I.scroll = Point(
       I.scroll.x.clamp(centerRect.left, centerRect.right)


Basic drawRect and drawCircle methods, because why not

--- src/canvas.coffee
+++ src/canvas.coffee
@@ -10,12 +10,14 @@
   # * __id__ (String) - the id of the HTML element
   #
   # Ex:
+  #
   #   `Kona.Canvas.init('gameCanvas')`
   init: (id) ->
     @elem    = document.getElementById(id) or fail("Can't find element with id: #{id}")
     @ctx     = @elem.getContext('2d')
     @width   = @elem.width  || @defaults.width
     @height  = @elem.height || @defaults.height
+

   # Safely perform a draw that may change the ctx fillStyle or other properties
   # (will not affect subsequent drawings)
@@ -30,8 +32,39 @@
     fxn()
     @ctx.restore()

-  # Clear the entire canvas. Used internally between frames to prevent blurring on redraw
+
+  # Wipe the canvas clean.
+  # Used internally between frames to prevent blurring on redraw
   clear: ->
     @safe =>
       @ctx.fillStyle = 'white'
       @ctx.fillRect(0, 0, @width, @height)
+
+
+  # Draw a basic rectangle
+  #
+  # Ex:
+  #
+  #   `Kona.Canvas.drawRect(@position, @box, { color: 'red' })`
+  #
+  drawRect: (position, box, opts={}) ->
+    @safe =>
+      @ctx.fillStyle = opts.color || 'black'
+      @ctx.fillRect(position.x, position.y, box.width, box.height)
+
+
+  # Wrapper for the fillRect method
+  #
+  # Ex:
+  #
+  #   `Kona.Canvas.drawCircle(@position, { radius: 20, color: 'blue' })`
+  #
+  drawCircle: (position, opts={}) ->
+    radius = opts.radius or fail("Must specify a radius")
+    @safe =>
+      @ctx.fillStyle = opts.color || 'black'
+      @ctx.beginPath()
+      @ctx.arc(position.x, position.y, radius, 0, 2*Math.PI, false)
+      @ctx.closePath()
+      @ctx.fill()
+


Fix type in parser

Fix type in parser

--- src/parser.coffee
+++ src/parser.coffee
@@ -32,8 +32,8 @@
     l:name '<--<|>' r:name { return {type:'owns_a', left:r, right:l}; }

   dependency =
-    l:name '- ->' r:name { return {type:'use_a', left:l, right:r}; } /
-    l:name '<- -' r:name { return {type:'use_a', left:r, right:l}; }
+    l:name '- ->' r:name { return {type:'uses_a', left:l, right:r}; } /
+    l:name '<- -' r:name { return {type:'uses_a', left:r, right:l}; }

   name =
     space letters:[A-Za-z0-9:_]+ space { return letters.join(''); }


Tank: unit test code cleanup

Tank: unit test code cleanup

Throw assertions were refactored for better readability.

--- spec/Tank.coffee
+++ spec/Tank.coffee
@@ -18,9 +18,9 @@
       tank = new Tank
         size: 1
       tank.fill('test')
-      callFillAgain = ->
+      ( ->
         tank.fill('again')
-      callFillAgain.should.throw 'Tank is full'
+      ).should.throw 'Tank is full'

     it 'should emit full event when the tank becomes full', (done) ->
       tank = new Tank
@@ -70,28 +70,26 @@
     it 'should throw error when tank is empty', ->
       tank = new Tank

-      callRelease = ->
+      ( ->
         tank.release()
-      callRelease.should.throw 'Tank is empty'
+      ).should.throw 'Tank is empty'

   describe 'having a drain option', ->
     it 'should not have size option specified', ->
-      invalidInstantiation = ->
+      ( ->
         new Tank
           drain: (data, cb) ->
           size: 5
-
-      invalidInstantiation.should.throw 'Cannot specify size option for a tank with drain option'
+      ).should.throw 'Cannot specify size option for a tank with drain option'

     it 'should not be able to release data manually', ->
       tank = new Tank
         drain: (data, cb) ->
           do cb

-      invalidCall = ->
+      ( ->
         do tank.release
-
-      invalidCall.should.throw 'Content is automatically released through the callback given in drain option'
+      ).should.throw 'Content is automatically released through the callback given in drain option'

     it 'should release any filled item using the promisifiable drain function', (done) ->
       tank = new Tank
@@ -109,10 +107,9 @@
       tank.fill 'test'

       do tank.seal
-      invalidAction = ->
+      ( ->
         tank.fill 'test2'
-
-      invalidAction.should.throw 'Cannot fill sealed tanks'
+      ).should.throw 'Cannot fill sealed tanks'

     it 'should emit end event if becomes empty when sealed', (done) ->
       tank = new Tank


preflop madness

preflop madness

--- src/player.coffee
+++ src/player.coffee
@@ -52,9 +52,9 @@

     if communityCards.length == 0
       if value > 0.5 and me.bet < 200
-        return sendBet 200
+        return sendBet 200 - (me?.bet ? 0)
       unless value < 0.1
-        return sendBet minBet
+        return sendBet maxBet

     if communityCards.length >= 3 and value < 0.2
       return sendBet 0


Use AudioContext from window.

Use AudioContext from window.

--- src/player.coffee
+++ src/player.coffee
@@ -8,9 +8,8 @@

 module.exports = (window, core, game, settings) ->
   if not fs.existsSync(core)
-    getCore(core, (path) ->
+    getCore core, (path) ->
       module.exports(window, path, game, settings)
-    )
     return

   @settings = settings
@@ -154,7 +153,7 @@
         @joypad[i][@settings.gp2joy[j]] = button.pressed
   @core.on 'inputpoll', @inputpoll

-  @audio = new AudioContext()
+  @audio = new window.AudioContext()

   @audiosamplebatch = (buffer, frames) =>
     source = @audio.createBufferSource()