Name: js-handler/node_modules/nodeunit/node_modules/tap/lib/tap-consumer.js 
1:
module.exports = TapConsumer
2:
 
3:
// pipe a stream into this that's emitting tap-formatted data,
4:
// and it'll emit "data" events with test objects or comment strings
5:
// and an "end" event with the final results.
6:
 
7:
var yamlish = require("yamlish")
8:
  , Results = require("./tap-results")
9:
  , inherits = require("inherits")
10:
 
11:
TapConsumer.decode = TapConsumer.parse = function (str) {
12:
  var tc = new TapConsumer
13:
    , list = []
14:
  tc.on("data", function (res) {
15:
    list.push(res)
16:
  })
17:
  tc.end(str)
18:
  tc.results.list = list
19:
  return tc.results
20:
}
21:
 
22:
var Stream = require("stream").Stream
23:
inherits(TapConsumer, Stream)
24:
function TapConsumer () {
25:
  if (!(this instanceof TapConsumer)) {
26:
    return new TapConsumer
27:
  }
28:
 
29:
  Stream.call(this)
30:
  this.results = new Results
31:
  this.readable = this.writable = true
32:
 
33:
  this.on("data", function (res) {
34:
    if (typeof res === "object") this.results.add(res)
35:
  })
36:
 
37:
  this._plan = null
38:
  this._buffer = ""
39:
  this._indent = []
40:
  this._current = null
41:
  this._actualCount = 0
42:
  this._passed = []
43:
  this._failed = []
44:
  //console.error("TapConsumer ctor done")
45:
}
46:
 
47:
TapConsumer.prototype.bailedOut = false
48:
 
49:
TapConsumer.prototype.write = function (chunk) {
50:
  if (!this.writable) this.emit("error", new Error("not writable"))
51:
  if (this.bailedOut) return true
52:
 
53:
  this._buffer = this._buffer + chunk
54:
  // split it up into lines.
55:
  var lines = this._buffer.split(/\r?\n/)
56:
  // ignore the last line, since it might be incomplete.
57:
  this._buffer = lines.pop()
58:
 
59:
  for (var i = 0, l = lines.length; i < l; i ++) {
60:
    //console.error([i, lines[i]])
61:
    // see if it's indented.
62:
    var line = lines[i]
63:
      , spaces = (this._indent.length && !line.trim())
64:
               || line.match(/^\s/)
65:
    // at this level, only interested in fully undented stuff.
66:
    if (spaces) {
67:
      var c = i
68:
      while (c < l && (!lines[c].trim() || lines[c].match(/^\s/))) {
69:
        this._indent.push(lines[c++])
70:
      }
71:
      //console.error(c-i, "indented", this._indent, this._current)
72:
      i = c - 1
73:
      continue
74:
    }
75:
    // some kind of line.  summary, ok, notok, comment, or garbage.
76:
    // this also finishes parsing any of the indented lines from before
77:
    this._parseLine(line)
78:
  }
79:
  return true
80:
}
81:
 
82:
TapConsumer.prototype.end = function () {
83:
  // finish up any hanging indented sections or final buffer
84:
  if (this._buffer.match(/^\s/)) this._indent.push(this.buffer)
85:
  else this._parseLine(this._buffer)
86:
 
87:
  if (!this.bailedOut &&
88:
      this._plan !== null &&
89:
      this.results.testsTotal !== this._plan) {
90:
    while (this._actualCount < this._plan) {
91:
      this.emit("data", {ok: false, name:"MISSING TEST",
92:
                         id:this._actualCount ++ })
93:
    }
94:
  }
95:
 
96:
  this._parseLine("")
97:
  this._buffer = ""
98:
  this.writable = false
99:
  this.emit("end", null, this._actualCount, this._passed)
100:
}
101:
 
102:
TapConsumer.prototype._parseLine = function (line) {
103:
  if (this.bailedOut) return
104:
  //console.error("_parseLine", [line])
105:
  // if there are any indented lines, and there is a
106:
  // current object already, then they belong to it.
107:
  // if there is not a current object, then they're garbage.
108:
  if (this._current && this._indent.length) {
109:
    this._parseIndented()
110:
  }
111:
  this._indent.length = 0
112:
  if (this._current) {
113:
    if (this._current.ok) this._passed.push(this._current.id)
114:
    else this._failed.push(this._current.id)
115:
    this.emit("data", this._current)
116:
  }
117:
  this._current = null
118:
  line = line.trim()
119:
  if (!line) return
120:
  // try to see what kind of line this is.
121:
 
122:
  var bo
123:
  if (bo = line.match(/^bail out!\s*(.*)$/i)) {
124:
    this.bailedOut = true
125:
    // this.emit("error", new Error(line))
126:
    this.emit("bailout", bo[1])
127:
    return
128:
  }
129:
 
130:
  if (line.match(/^#/)) { // just a comment
131:
    line = line.replace(/^#+/, "").trim()
132:
    // console.error("outputting comment", [line])
133:
    if (line) this.emit("data", line)
134:
    return
135:
  }
136:
 
137:
  var plan = line.match(/^([0-9]+)\.\.([0-9]+)(?:\s+#(.*))?$/)
138:
  if (plan) {
139:
    var start = +(plan[1])
140:
      , end = +(plan[2])
141:
      , comment = plan[3]
142:
 
143:
    // TODO: maybe do something else with this?
144:
    // it might be something like: "1..0 #Skip because of reasons"
145:
    this._plan = end
146:
    this.emit("plan", end, comment)
147:
    // plan must come before or after all tests.
148:
    if (this._actualCount !== 0) {
149:
      this._sawPlan = true
150:
    }
151:
    return
152:
  }
153:
 
154:
  if (line.match(/^(not )?ok(?:\s+([0-9]+))?/)) {
155:
    this._parseResultLine(line)
156:
    return
157:
  }
158:
 
159:
  // garbage.  emit as a comment.
160:
  //console.error("emitting", [line.trim()])
161:
  if (line.trim()) this.emit("data", line.trim())
162:
}
163:
 
164:
TapConsumer.prototype._parseDirective = function (line) {
165:
  line = line.trim()
166:
  if (line.match(/^TODO\b/i)) {
167:
    return { todo:true, explanation: line.replace(/^TODO\s*/i, "") }
168:
  } else if (line.match(/^SKIP\b/i)) {
169:
    return { skip:true, explanation: line.replace(/^SKIP\s*/i, "") }
170:
  }
171:
}
172:
 
173:
TapConsumer.prototype._parseResultLine = function (line) {
174:
  this._actualCount ++
175:
  if (this._sawPlan) {
176:
    this.emit("data", {ok: false, name:"plan in the middle of tests"
177:
                      ,id:this._actualCount ++})
178:
  }
179:
  var parsed = line.match(/^(not )?ok(?: ([0-9]+))?(?:(?: - )?(.*))?$/)
180:
    , ok = !parsed[1]
181:
    , id = +(parsed[2] || this._actualCount)
182:
    , rest = parsed[3] || ""
183:
    , name
184:
    , res = { id:id, ok:ok }
185:
 
186:
  // split on un-escaped # characters
187:
 
188:
  //console.log("# "+JSON.stringify([name, rest]))
189:
  rest = rest.replace(/([^\\])((?:\\\\)*)#/g, "$1\n$2").split("\n")
190:
  name = rest.shift()
191:
  rest = rest.filter(function (r) { return r.trim() }).join("#")
192:
  //console.log("# "+JSON.stringify([name, rest]))
193:
 
194:
  // now, let's see if there's a directive in there.
195:
  var dir = this._parseDirective(rest.trim())
196:
  if (!dir) name += rest ? "#" + rest : ""
197:
  else {
198:
    res.ok = true
199:
    if (dir.skip) res.skip = true
200:
    else if (dir.todo) res.todo = true
201:
    if (dir.explanation) res.explanation = dir.explanation
202:
  }
203:
  res.name = name
204:
 
205:
  //console.error(line, [ok, id, name])
206:
  this._current = res
207:
}
208:
 
209:
TapConsumer.prototype._parseIndented = function () {
210:
  // pull yamlish block out
211:
  var ind = this._indent
212:
    , ys
213:
    , ye
214:
    , yind
215:
    , diag
216:
  //console.error(ind, this._indent)
217:
  for (var i = 0, l = ind.length; i < l; i ++) {
218:
    var line = ind[i]
219:
    if (line === undefined) continue
220:
    var lt = line.trim()
221:
 
222:
    if (!ys) {
223:
      ys = line.match(/^(\s*)---(.*)$/)
224:
      if (ys) {
225:
        yind = ys[1]
226:
        diag = [ys[2]]
227:
        //console.error([line,ys, diag])
228:
        continue
229:
      } else if (lt) this.emit("data", lt)
230:
    } else if (ys && !ye) {
231:
      if (line === yind + "...") ye = true
232:
      else {
233:
        diag.push(line.substr(yind.length))
234:
      }
235:
    } else if (ys && ye && lt) this.emit("data", lt)
236:
  }
237:
  if (diag) {
238:
    //console.error('about to parse', diag)
239:
    diag = yamlish.decode(diag.join("\n"))
240:
    //console.error('parsed', diag)
241:
    Object.keys(diag).forEach(function (k) {
242:
      //console.error(this._current, k)
243:
      if (!this._current.hasOwnProperty(k)) this._current[k] = diag[k]
244:
    }, this)
245:
  }
246:
}