Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Initial push

  • Loading branch information...
commit 717d136fd251a893d5c1b781a97903385337c166 0 parents
brandon authored

Showing 3 changed files with 328 additions and 0 deletions. Show diff stats Hide diff stats

  1. +114 0 Readme.md
  2. +201 0 lib/index.js
  3. +13 0 package.json
114 Readme.md
Source Rendered
... ... @@ -0,0 +1,114 @@
  1 +# Mikronode
  2 +
  3 + A stream based Expect utility for interacting with streams.
  4 +
  5 + var Expect = require('node-expect');
  6 + var socket = new require('net').Socket({type:'tcp4'});
  7 + var parser = new Expect();
  8 +
  9 + parser.conversation(/connect/i) // Start a conversation matching the connection text from the telnet server in the stream as the trigger.
  10 + .sync() // The expect rules are synchronous, and run in order.
  11 + .expect(/login/i) // First expect rule matches the login prompt.
  12 + .send("myuser\n") // Send user login ID.
  13 + .expect(/password/) // We are expecting a password prompt next.
  14 + .send("mypassword\n") // Now send the password.
  15 + .branch(/denied/i) // Branch is a special expect rule, where it will test all consecutive branch definitions to decide which match to follow.
  16 + // This is handy when you want to run rules synchronously but have a section of async rules run as one synchronous rule.
  17 + .reset() // Reset will reset the current conversation thread so that it is as if the first expect in this conversation never started.
  18 + // This allows you to restart conversation matching on a synchronous rule set without needing to match the conversation trigger.
  19 + .branch(/\$ /) // Another branch to match.
  20 + .push() // Push match back into the stream. The end result is that the next conversation will pick up the match since we are ending this one.
  21 + .end() // End this conversation.
  22 + .conversation(/\$ /) // Start this conversation on a shell prompt. This conversation won't be synchronous.
  23 + .expect(null,true) // Don't expect anything. The conversation trigger is enough. Just do the action. Then consume this rule, not to run again.
  24 + .send("echo 'I win!'\n")
  25 + .expect(/\$ /) // Expecting the prompt again.
  26 + .send("exit")
  27 + .expect(/closed/i)
  28 + .end() // End this conversation.
  29 +
  30 +
  31 +## Installation
  32 +
  33 + Clone this repository into your node_modules directory.
  34 + - or -
  35 + $ npm install node-expect
  36 +
  37 +
  38 +## Features
  39 +
  40 + * Follows the builder pattern for simple script construction.
  41 + * Acts like a WriteableStream
  42 + * Simple attaching input to a stream with pipes, or using built-in monitor.
  43 + * Large set of commands to react to matched patterns in stream.
  44 +
  45 +## TODO
  46 + * Write tests to make sure everything keeps working while making above changes.
  47 +
  48 +## API
  49 +
  50 +### Construction
  51 +
  52 + Expect = require('node-expect');
  53 + parser = new Expect();
  54 +
  55 + Constructing a new Expect() object returns a parser object that is used to start building a set of conversations.
  56 +
  57 + * conversation = parser.conversation(TRIGGER PATTERN)
  58 + Starts a new conversation. A conversation allows narrowing to a set of expect rules with their own conversational path.
  59 + * conversation.synch()
  60 + Turns on synchronous matching of expect rules. Otherwise all rules are considered each time something new arrives in the stream.
  61 + * expect = conversation.expect(PATTERN,mark)
  62 + Sets an expect rule to match content in the stream. If mark==true then when this rule matches, do not allow subsequent matches. This is default in synchronous conversations.
  63 + * expect = conversation.branch(PATTERN,mark)
  64 + Exactly like creating an expect rule, except this will roll up all consecutive branch calls as one expect rule. Only the first will have its commands evaluated.
  65 + * expect.send(String)
  66 + Sends a string out the monitored stream. This is handy for socket streams that are two ways. An error will occur if the monitored stream is read only, closed, or unavailable.
  67 + * expect.emit(name)
  68 + This will emit the named event with the results of the expect pattern match as the parameter.
  69 + * expect.handler(callbackFunction)
  70 + This will call the callback function with the results of the expect pattern match as the first parameter, and the parser object as the second.
  71 + * expect.reset()
  72 + Causes the current conversation to be reset and act as if no expect rules have yet been matched.
  73 + * expect.end()
  74 + End the current conversation. The parser will match on conversation patterns after this action is executed.
  75 + * expect.push()
  76 + Push the results of the last match back to the buffer. This allows another rule, or conversation to match this text again.
  77 +
  78 +
  79 +### Interaction
  80 +
  81 + The following methods are available for channels:
  82 +
  83 + * parser.monitor(stream)
  84 + Sets the stream to be monitored. All conversations are reset when this is called.
  85 + * parser.reset()
  86 + Resets all conversations.
  87 + * parser.write(string)
  88 + Write to the parser like a stream.
  89 +
  90 +## License
  91 +
  92 +(The MIT License)
  93 +
  94 +Copyright (c) 2011 Brandon Myers <trakkasure@gmail.com>
  95 +
  96 +Permission is hereby granted, free of charge, to any person obtaining
  97 +a copy of this software and associated documentation files (the
  98 +'Software'), to deal in the Software without restriction, including
  99 +without limitation the rights to use, copy, modify, merge, publish,
  100 +distribute, sublicense, and/or sell copies of the Software, and to
  101 +permit persons to whom the Software is furnished to do so, subject to
  102 +the following conditions:
  103 +
  104 +The above copyright notice and this permission notice shall be
  105 +included in all copies or substantial portions of the Software.
  106 +
  107 +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
  108 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  109 +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  110 +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  111 +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  112 +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  113 +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  114 +
201 lib/index.js
... ... @@ -0,0 +1,201 @@
  1 +
  2 +module.exports=(function(){
  3 + var _NA = 0x00;
  4 + var _TRIGGER = 0x01;
  5 + var _CONVERSATION = 0x02;
  6 + var _COMMAND = 0x04;
  7 + var _END = 0x08;
  8 + function parser(s) {
  9 + this.mode=_TRIGGER;
  10 + this.conv=null; // this is the current conversation being processed.
  11 + this.triggers=[];
  12 + this.buffer='';
  13 + if (s) {this.monitor(s)}
  14 + }
  15 + var util=require('util');
  16 + util.inherits(parser,require('events').EventEmitter);
  17 + parser.prototype.monitor=function(stream) {
  18 + this.stream=stream;
  19 + var self=this;
  20 + this.conv=null;
  21 + this.buffer='';
  22 + this.reset();
  23 + this.stream.on('data',function(buf) {
  24 + self.write(buf);
  25 + });
  26 + }
  27 + parser.prototype.reset=function() {
  28 + this.triggers.forEach(function(t) {t.used=false;t.last=0;t.conversation.forEach(function(c){c.used=false})});
  29 + }
  30 + function handleDecision(m,self) {
  31 + m.forEach(function(i) {
  32 + if (i[0].eat)
  33 + i[0].used=true;
  34 + check=true;
  35 + var e=i[0];
  36 + if (!e.push&&i[1].length)
  37 + self.buffer=self.buffer.substr(i[1].index+i[1][0].length);
  38 + if (e.send) {
  39 + if (self.debug>1) console.log("Sending "+e.send);
  40 + self.stream.write(e.send);
  41 + //self.mode=_COMMAND;
  42 + }
  43 + if (e.emit) {
  44 + if (self.debug>1) console.log("Emiting event "+e.emit[0]);
  45 + self.emit(e.emit[0],e.emit[1],self);
  46 + }
  47 + if (e.handler) {
  48 + if (self.debug>1)
  49 + console.log("Executing handler "+e.handler.name);
  50 + e.handler(i[1],i,self);
  51 + }
  52 + if (e.end) {
  53 + if (self.debug>1) console.log("Ending conversation");
  54 + self.mode=_END;
  55 + check=false;
  56 + }
  57 + if (e.reset) {
  58 + self.conv.conversation.forEach(function(i){i.used=false});
  59 + self.conf.last=0;
  60 + }
  61 + });
  62 + }
  63 + parser.prototype.write=function(buf){
  64 + var self=this;
  65 + self.buffer+=buf.toString();
  66 + if (self.debug>4) console.log('Data:"'+buf.toString()+'"');
  67 + do {
  68 + var check=false;
  69 + if (self.mode&_TRIGGER) {
  70 + if (self.debug>2) console.log('MODE TRIGGER: ');
  71 + m=self.triggers.map(function(item,idx,ary) {
  72 + return item.sync?(item.used?[0,null]:[item,self.buffer.match(item.trigger)]):[item,self.buffer.match(item.trigger)];
  73 + });
  74 + m=m.filter(function(i){return i[1]!=null});
  75 + if (m.length) {
  76 + self.mode=_CONVERSATION;
  77 + self.conv=m[0][0]; // only using the first matched trigger.
  78 + if (self.debug>3) console.log(self.conv);
  79 + self.conv.used=true;
  80 + if (self.debug>0) console.log('Starting conversation: '+m[0][1][0]);
  81 + self.buffer=self.buffer.substr(m[0][1].index+m[0][1][0].length);
  82 + check=true;
  83 + }
  84 + }
  85 + if (self.mode&_CONVERSATION) {
  86 + if (self.debug>2) console.log('MODE CONVERSATION: ');
  87 + if (self.conv.sync) {
  88 + var m=self.conv.conversation;
  89 + var idx=self.conv.last||0;
  90 + var i=m[idx];// all synchronous items are consumed and not reused.
  91 + //while(m[idx]&&(i.eat&&i.used)) i=m[++idx];
  92 + var match=null;
  93 + if (i&&!i.used) {
  94 + if (i.branch) { // next item is a branch, test all next branches.
  95 + for (item=m[idx];item;idx++) {
  96 + if (!item.branch) break; // no branches matched.
  97 + if (self.debug>2) console.log('BRANCH: '+item.expect);
  98 + match=item.expect==null?[item,'']:[item,self.buffer.match(item.expect)];
  99 + if (match[1]) break; // only first branch match counts.
  100 + }
  101 + if (match) { // we have a match!
  102 + while(self.conv.conversation[self.conv.last].branch)self.conv.conversation[(++self.conv.last)].used=true; // mark all consecutive branch commands used.
  103 + if (self.debug>2) console.log('BRANCH: match '+match[1][0]);
  104 + check=true;
  105 + handleDecision([match],self);
  106 + }
  107 + } else {
  108 + if (self.debug>2) console.log('SYNC MATCH: '+i.expect);
  109 + if (i.expect==null) m=['']; // this is an open execute.
  110 + else
  111 + if (!(m=self.buffer.match(i.expect))) continue; // no match on the next item.
  112 + else
  113 + if (self.debug>3) console.log('SYNC MATCHED:'+m[0]);
  114 + self.conv.last++;
  115 + check=true;
  116 + i.used=true;
  117 + handleDecision([[i,m]],self);
  118 + }
  119 + } else
  120 + console.log("Error. We only have used expects. This shouldn't happen. ("+idx+") "+i);
  121 + } else { // Async matching. All matches have their handlers executed.
  122 + if (self.debug>2) console.log('ASYNC MATCH');
  123 + m=self.conv.conversation.map(function(item,idx,ary) {
  124 + return item.eat?// if we are suppose to not re-use this expect after it is matched, then check if it's used.
  125 + (item.used?[item,null]:item.expect==null?[item,'']:[item,self.buffer.match(item.expect)])
  126 + :item.expect==null?[item,'']:[item,self.buffer.match(item.expect)];
  127 + });
  128 + m=m.filter(function(i){return i[1]!=null}); // Filter out non-matches
  129 + if (self.debug>3)
  130 + console.log('ASYNC MATCH: '+m);
  131 + handleDecision(m,self);
  132 + }
  133 + }
  134 + /*
  135 + if (self.mode&_COMMAND) {
  136 + console.log("COMMAND:"+self.command);
  137 + console.log('"'+self.buffer+'"');
  138 + console.log(self.buffer.match(self.command));
  139 + if (m=self.buffer.match(self.command)) { // eat the echo'd command.
  140 + self.buffer=self.buffer.substr(self.buffer.indexOf(m[0])+m[0].length);
  141 + self.mode=_CONVERSATION;
  142 + }
  143 + }
  144 + */
  145 + if (self.mode&_END) {
  146 + if (self.debug>3) console.log("END");
  147 + self.mode=_TRIGGER; // restart
  148 + check=false;
  149 + }
  150 + } while (check);
  151 + }
  152 + parser.prototype.conversation=function(trigger) { // if eat==true then when it's matched, remove it.
  153 + var self=this;
  154 + var c={
  155 + parser: self,
  156 + data: {trigger: trigger,conversation:[],sync: false,used:false,last: 0},
  157 + sync: function() { this.data.sync = true; return this }, // if true then match the expect rules synchronously.
  158 + branch: function(e) {
  159 + d=this.expect(e);
  160 + d.p.d.branch=true;
  161 + return d;
  162 + },
  163 + expect: function(e,eat) {
  164 + var slf=this; // this should be c
  165 + if (this.data.sync) eat=true;
  166 + var d={expect:(typeof e=='string')?new RegExp(e):e,used:false,eat: eat};
  167 + this.data.conversation.push(d);
  168 + return { // this should be anonymous.
  169 + d:d,
  170 + p:slf,
  171 + send: function(s) { // send text out when expect
  172 + this.d.send=s;
  173 + return this;
  174 + },
  175 + emit: function(e,p) { // emit an event when expect
  176 + this.d.emit=[e,p];
  177 + return this;
  178 + },
  179 + handler: function(c) { // callback when expect
  180 + this.d.handler=c;
  181 + return this;
  182 + },
  183 + end: function(){this.d.end=true;return this}, // End this conversation when expect matches
  184 + reset: function(){this.d.reset=true;return this},
  185 + push: function(){this.d.push=true;return this},
  186 + branch: function(e){return this.p.branch(e)},
  187 + expect: function(e) {return this.p.expect(e)}, // start a new expect with new actions.
  188 + conversation: function(t) {return this.p.parser.conversation(t)}, // start up a new conversation with a new trigger.
  189 + monitor:function(m) {this.p.parser.monitor(m);return self}
  190 + }
  191 + },
  192 + monitor:function(m) {this.parser.monitor(m);return self},
  193 + conversation: function(t) {return self.conversation(t)}
  194 + }
  195 + this.triggers.push(c.data);
  196 + return c;
  197 + }
  198 +
  199 + return parser;
  200 +
  201 +})()
13 package.json
... ... @@ -0,0 +1,13 @@
  1 +{
  2 + "name": "node-expect",
  3 + "description": "Expect style stream parser for Node",
  4 + "version": "0.1.0",
  5 + "author": "Brandon Myers <trakkasure@gmail.com>",
  6 + "contributors": [
  7 + { "name": "Brandon Myers", "email": "trakkasure@gmail.com" }
  8 + ],
  9 + "keywords": ["expect", "parser", "stream","buffer"],
  10 + "repository": "git://github.com/trakkasure/node-expect",
  11 + "main": "lib/index",
  12 + "engines": { "node": ">= 0.4.0" }
  13 +}

0 comments on commit 717d136

Please sign in to comment.
Something went wrong with that request. Please try again.