Skip to content

Instantly share code, notes, and snippets.

@sirpengi
Last active August 29, 2015 14:05
Show Gist options
  • Save sirpengi/e862b9e9c0af70fe42c2 to your computer and use it in GitHub Desktop.
Save sirpengi/e862b9e9c0af70fe42c2 to your computer and use it in GitHub Desktop.
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