Last active
August 29, 2015 14:05
-
-
Save sirpengi/e862b9e9c0af70fe42c2 to your computer and use it in GitHub Desktop.
sample usage of backbone-fsm
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module.paths.push("."); | |
var assert = require("assert"); | |
var _ = require("underscore"); | |
var Backbone = require("backbone"); | |
var FSM = require("backbone-fsm").BackboneFSM; | |
/* Implementation of a turnstile (i.e., at train station). Turnstile is | |
initially locked and won't let people through. Once a coin is | |
inserted, the turnstile becomes unlocked, and lets one person through before | |
locking again. */ | |
var TurnstileFSM = FSM.define( | |
{ | |
action: {person_entered: true}, | |
guard: {locked: false}, | |
result: {locked: true} | |
}, | |
{ | |
action: {coin_inserted: true}, | |
result: {locked: false} | |
} | |
); | |
var Turnstile = TurnstileFSM( | |
Backbone.Model.extend({ | |
defaults: { | |
locked: true | |
} | |
}) | |
); | |
var myTurnstile = new Turnstile(); | |
assert.equal(myTurnstile.can({person_entered: true}), false); | |
assert.equal(myTurnstile.can({coin_inserted: true}), true); | |
assert.equal(myTurnstile.get("locked"), true); | |
myTurnstile.transition({coin_inserted: true}); | |
assert.equal(myTurnstile.can({person_entered: true}), true); | |
assert.equal(myTurnstile.can({coin_inserted: true}), true); | |
assert.equal(myTurnstile.get("locked"), false); | |
myTurnstile.transition({person_entered: true}); | |
assert.equal(myTurnstile.get("locked"), true); | |
/* Implementation of a HTTP/1.0 response parser. | |
This implementation works on lines. Further down, | |
I show how you can use another FSM to convert from | |
character-by-character stream (or chunked streams) | |
into lines. Chaining multiple FSMs together works wonders | |
and is easy with Backbone.Event mixin.*/ | |
var HTTPFSM = FSM.define( | |
{ | |
action: {read: /^.+$/m}, | |
guard: {mode: null}, | |
result: function(obj) { | |
var code = obj.read.split(/\s/)[1]; | |
this.set({ | |
status_code: code, | |
mode: "headers" | |
}); | |
} | |
}, | |
{ | |
action: {read: /^.+$/m}, | |
guard: {mode: "headers"}, | |
result: function(obj) { | |
var parts = obj.read.split(":"); | |
var headers = this.get("headers"); | |
headers[parts[0].toLowerCase()] = parts[1].trim(); | |
} | |
}, | |
{ | |
action: {read: /^$\n/m}, | |
guard: {mode: "headers"}, | |
result: {mode: "body"} | |
}, | |
{ | |
action: {read: /^.*$/m}, | |
guard: {mode: "body"}, | |
result: function(obj) { | |
var body = this.get("body") || ""; | |
this.set({body: body + obj.read}); | |
} | |
} | |
); | |
var http_stream = [ | |
"HTTP/1.0 200 OK", | |
"Date: Fri, 31 Dec 1999 23:59:59 GMT", | |
"Content-Type: text/html", | |
"Content-Length: 1354", | |
"", | |
"<html>", | |
"<body>", | |
"<h1>Hello World</h1>", | |
"</body>" | |
]; | |
var check_http = function(obj) { | |
assert.equal(obj.get("status_code"), "200"); | |
assert.equal(obj.get("body"), "<html>\n<body>\n<h1>Hello World</h1>\n</body>"); | |
assert.equal(obj.get("headers")["date"], "Fri, 31 Dec 1999 23"); | |
assert.equal(obj.get("headers")["content-type"], "text/html"); | |
assert.equal(obj.get("headers")["content-length"], "1354"); | |
} | |
var HTTPResponseParser = HTTPFSM( | |
Backbone.Model.extend({ | |
defaults: { | |
status_code: null, | |
body: null, | |
headers: null | |
}, | |
initialize: function() { | |
this.set("headers", {}); | |
} | |
}) | |
); | |
// Here we feed the parser just the raw lines | |
var myHTTP = new HTTPResponseParser(); | |
_.each(_.initial(http_stream), function(v) { | |
myHTTP.transition({read: v + "\n"}); | |
}); | |
myHTTP.transition({read: _.last(http_stream)}); | |
check_http(myHTTP); | |
/* Implementation of a FSM that takes string chunks | |
and converts them into lines. Listeners will listen | |
for the emit_line event and act accordingly.*/ | |
var Stream2LineFSM = FSM.define( | |
{ | |
action: {read: FSM.ANY}, | |
result: function(obj) { | |
var input = obj.read || ""; | |
var s = (this.get("buffer") || "") + input; | |
var chunks = s.split("\n"); | |
if (chunks.length === 1) { | |
this.set("buffer", chunks[0]); | |
} else { | |
_.each(_.initial(chunks), function(v) { | |
this.trigger("emit_line", v + "\n"); | |
}, this); | |
this.set("buffer", _.last(chunks)); | |
} | |
} | |
}, | |
{ | |
action: {EOF: FSM.ANY}, | |
result: function(obj) { | |
this.trigger("emit_line", this.get("buffer") || ""); | |
this.set("buffer", null); | |
} | |
} | |
) | |
var Stream2Line = Stream2LineFSM(Backbone.Model); | |
var stream = http_stream.join("\n"); | |
// Test by sending the entire response as one chunk | |
var streamer = new Stream2Line(); | |
myHTTP = new HTTPResponseParser(); | |
myHTTP.listenTo(streamer, "emit_line", function(v) { | |
this.transition({read: v}); | |
}); | |
streamer.transition({read: stream}); | |
streamer.transition({EOF: true}); | |
check_http(myHTTP); | |
// Test by sending the entire response char-by-char | |
streamer = new Stream2Line(); | |
myHTTP = new HTTPResponseParser(); | |
myHTTP.listenTo(streamer, "emit_line", function(v) { | |
this.transition({read: v}); | |
}); | |
for (var i=0; i<stream.length; i++) { | |
streamer.transition({read: stream[i]}); | |
} | |
streamer.transition({EOF: true}); | |
check_http(myHTTP); | |
// Test by sending the entire response 2 characters at a time | |
streamer = new Stream2Line(); | |
myHTTP = new HTTPResponseParser(); | |
myHTTP.listenTo(streamer, "emit_line", function(v) { | |
this.transition({read: v}); | |
}); | |
for (var i=0; i<stream.length; i+=2) { | |
var c = (stream[i] || "") + (stream[i+1] || ""); | |
streamer.transition({read: c}); | |
} | |
streamer.transition({EOF: true}); | |
check_http(myHTTP); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment