| 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