sample usage of backbone-fsm
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