Skip to content

Commit

Permalink
Extract regular expression
Browse files Browse the repository at this point in the history
  • Loading branch information
Ken Browning committed Feb 9, 2014
1 parent 72c066b commit 26d4ba1
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 97 deletions.
8 changes: 8 additions & 0 deletions lib/INewlineFinder.ts
@@ -0,0 +1,8 @@
///<reference path='../bower_components/dt-node/node.d.ts'/>
import INewlineFinderResult = require('./INewlineFinderResult');

interface INewlineFinder {
find(s: string): INewlineFinderResult[];
}

export = INewlineFinder;
8 changes: 8 additions & 0 deletions lib/INewlineFinderResult.ts
@@ -0,0 +1,8 @@
///<reference path='../bower_components/dt-node/node.d.ts'/>

interface INewlineFinderResult {
text: string;
index: number;
}

export = INewlineFinderResult;
33 changes: 8 additions & 25 deletions lib/LineEmitter.ts
@@ -1,49 +1,32 @@
///<reference path='../bower_components/dt-node/node.d.ts'/>
import ILine = require('./ILine');
import _events = require('./events');
import INewlineFinder = require('./INewlineFinder');


class LineEmitter extends _events.EventEmitter {

private newlinesPattern: RegExp;
private newlineFinder: INewlineFinder;
private buffer = '';
private lineNumber = 0;
private offset = 0;

constructor(newlines?: string[]) {
constructor(newlineFinder: INewlineFinder) {
super();
this.newlinesPattern = this.createNewlinesPattern(newlines || ['\r\n', '\n']);
}

private createNewlinesPattern(newlines: string[]) {
newlines = newlines.map((sequence: string) => {
return '\\' + sequence.split('').join('\\');
});
return new RegExp('(' + newlines.join('|') + ')', 'g');
this.newlineFinder = newlineFinder;
}

public pushLines(lines: string) {
this.buffer += lines;
this.newlinesPattern.lastIndex = 0;
var offset = 0;
this.findAllMatches(this.buffer, this.newlinesPattern).forEach((match) => {
var text = this.buffer.substring(offset, match.index);
var newline = match[0];
this.emitLine(this.createLine(text, newline));
offset += text.length + newline.length;
this.newlineFinder.find(this.buffer).forEach((newline) => {
var text = this.buffer.substring(offset, newline.index);
this.emitLine(this.createLine(text, newline.text));
offset += text.length + newline.text.length;
});
this.buffer = this.buffer.substr(offset);
}

private findAllMatches(s: string, re: RegExp) {
var results = [];
var result;
while(result = re.exec(s)) {
results.push(result);
}
return results;
}

private createLine(text: string, newline?: string) {
var line: ILine = {
number: ++this.lineNumber,
Expand Down
26 changes: 26 additions & 0 deletions lib/RegExpNewlineFinder.ts
@@ -0,0 +1,26 @@
///<reference path='../bower_components/dt-node/node.d.ts'/>
import INewlineFinder = require('./INewlineFinder');
import INewlineFinderResult = require('./INewlineFinderResult');

class RegExpNewlineFinder implements INewlineFinder {
private re: RegExp;

constructor(re: RegExp) {
this.re = re;
}

public find(s: string) {
var results: INewlineFinderResult[] = [];
this.re.lastIndex = 0;
var result;
while(result = this.re.exec(s)) {
results.push({
text: <string>result[0],
index: <number>result.index
});
}
return results;
}
}

export = RegExpNewlineFinder;
13 changes: 13 additions & 0 deletions lib/StringArrayNewlineFinder.ts
@@ -0,0 +1,13 @@
///<reference path='../bower_components/dt-node/node.d.ts'/>
import RegExpNewlineFinder = require('./RegExpNewlineFinder');

class StringArrayNewlineFinder extends RegExpNewlineFinder {
constructor(newlines: string[]) {
newlines = newlines.map((sequence: string) => {
return '\\' + sequence.split('').join('\\');
});
super(new RegExp('(' + newlines.join('|') + ')', 'g'));
}
}

export = StringArrayNewlineFinder;
87 changes: 15 additions & 72 deletions test/lib/LineEmitter.ts
Expand Up @@ -4,16 +4,16 @@ import sinonChai = require('../sinon-chai');
var expect = sinonChai.expect;
import ILine = require('../../lib/ILine');
import LineEmitter = require('../../lib/LineEmitter');
import events = require('../../lib/events');
import stream = require('stream');
import RegExpNewlineFinder = require('../../lib/RegExpNewlineFinder');


// ReSharper disable WrongExpressionStatement
describe('LineEmitter', () => {

it('supports a stream as input', done => {
var lines = [];
var emitter = new LineEmitter();
var emitter = new LineEmitter(new RegExpNewlineFinder(/(\r?\n)/g));
emitter.on('line', (line: ILine) => {
lines.push(line);
});
Expand All @@ -31,84 +31,27 @@ describe('LineEmitter', () => {
stream.resume();
});

it('supports a string as input', () => {
var emitter = new LineEmitter();
var fn = sinon.spy();
emitter.on('line', fn);
emitter.pushLines('foo');
emitter.flushLines();
expect(fn).to.have.been.calledOnce;
});

it('emits correct number of lines', () => {
var emitter = new LineEmitter();
var fn = sinon.spy();
emitter.on('line', fn);
emitter.pushLines('foo\nbar\r\nbaz\n');
emitter.flushLines();
expect(fn).to.have.been.calledThrice;
});

it('emits the correct line number for each line', () => {
var emitter = new LineEmitter();
it('emits the correct ILine data for each line', () => {
var emitter = new LineEmitter(new RegExpNewlineFinder(/(\r?\n)/g));
var expectedLineNumbers = [1, 2, 3];
emitter.on('line', (line: ILine) => {
expect(line.number).to.eq(expectedLineNumbers.shift());
});
emitter.pushLines('foo\nbar\nbaz');
emitter.flushLines();
});

it('emits the correct offset for each line', () => {
var emitter = new LineEmitter();
var expectedOffsets = [0, 4, 8];
emitter.on('line', (line: ILine) => {
expect(line.offset).to.eq(expectedOffsets.shift());
});
emitter.pushLines('foo\nbar\nbaz');
emitter.flushLines();
});

it('emits the correct text for each line', () => {
var emitter = new LineEmitter();
var expectedOffsets = [0, 4, 9];
var expectedTexts = ['foo', 'bar', 'baz'];
emitter.on('line', (line: ILine) => {
expect(line.text).to.eq(expectedTexts.shift());
});
emitter.pushLines('foo\nbar\nbaz');
emitter.flushLines();
});

it('emits the correct newline for each line', () => {
var emitter = new LineEmitter();
var expectedNewlines = ['\n', '\r\n'];
var lines = [];
emitter.on('line', (line: ILine) => {
expect(line.newline).to.eq(expectedNewlines.shift());
});
emitter.pushLines('foo\nbar\r\nbaz');
emitter.flushLines();
});

it('supports custom newline sequences', () => {
var emitter = new LineEmitter(['*+', '{.', '|', '\t']);
var expectedTexts = ['foo\n', 'bar\r\n', 'baz', 'qux'];
var expectedNewlines = ['\t', '|', '{.', '*+'];
emitter.on('line', (line: ILine) => {
lines.push(line);
expect(line.number).to.eq(expectedLineNumbers.shift());
expect(line.offset).to.eq(expectedOffsets.shift());
expect(line.text).to.eq(expectedTexts.shift());
expect(line.newline).to.eq(expectedNewlines.shift());
});
emitter.pushLines('foo\n\tbar\r\n|baz{.qux*+');
emitter.flushLines();
});

it('allows a single line to span over 2 stream chunks', () => {
var emitter = new LineEmitter();
emitter.on('line', (line: ILine) => {
expect(line.text).to.eq('foobar');
});
emitter.pushLines('foo');
emitter.pushLines('bar');
emitter.pushLines('fo');
emitter.pushLines('o\n');
emitter.pushLines('bar\r');
emitter.pushLines('\nba')
emitter.pushLines('z');
emitter.flushLines();
expect(lines.length).to.eq(3);
});

});
Expand Down
21 changes: 21 additions & 0 deletions test/lib/RegExpNewlineFinder.ts
@@ -0,0 +1,21 @@
import fs = require('fs');
import sinon = require('sinon');
import sinonChai = require('../sinon-chai');
var expect = sinonChai.expect;
import RegExpNewlineFinder = require('../../lib/RegExpNewlineFinder');


// ReSharper disable WrongExpressionStatement
describe ('RegExpNewlineFinder', () => {

it('allows newlines to be specified by a RegExp', () => {
var expectedTexts = ['\t', '|', '{.', '*+'];
var expectedIndexes = [4, 10, 14, 19];
var finder = new RegExpNewlineFinder(/(\*\+|\{\.|\||\t)/g);
finder.find('foo\n\tbar\r\n|baz{.qux*+').forEach((newline) => {
expect(newline.text).to.eq(expectedTexts.shift());
expect(newline.index).to.eq(expectedIndexes.shift());
});
});

});
21 changes: 21 additions & 0 deletions test/lib/StringArrayNewlineFinder.ts
@@ -0,0 +1,21 @@
import fs = require('fs');
import sinon = require('sinon');
import sinonChai = require('../sinon-chai');
var expect = sinonChai.expect;
import StringArrayNewlineFinder = require('../../lib/StringArrayNewlineFinder');


// ReSharper disable WrongExpressionStatement
describe('StringArrayNewlineFinder', () => {

it('allows newlines to be specified by a string[]', () => {
var expectedTexts = ['\t', '|', '{.', '*+'];
var expectedIndexes = [4, 10, 14, 19];
var finder = new StringArrayNewlineFinder(['*+', '{.', '|', '\t']);
finder.find('foo\n\tbar\r\n|baz{.qux*+').forEach((newline) => {
expect(newline.text).to.eq(expectedTexts.shift());
expect(newline.index).to.eq(expectedIndexes.shift());
});
});

});

0 comments on commit 26d4ba1

Please sign in to comment.