From 3fda8fd4110b28f143bebaacd32a071a1fc893fc Mon Sep 17 00:00:00 2001 From: Seb Aebischer Date: Sun, 7 May 2023 12:42:22 +0100 Subject: [PATCH] feat(text): `deleteBlock`, `deleteEveryBlock` --- src/text.ts | 65 ++++++++++++++++++++++++++++++++++++++++++++++ tests/text.spec.ts | 41 +++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/src/text.ts b/src/text.ts index da6dfd1..0967e3b 100644 --- a/src/text.ts +++ b/src/text.ts @@ -160,4 +160,69 @@ export class Text { }); this.content = lines.join('\n'); } + + /** + * Deletes the first group of lines that match `block`, or throws if no lines + * match. + * + * @param block Strings or RegExps that identify the lines. + */ + deleteBlock(block: (string | RegExp)[]) { + if (block.length === 0) { + return; + } + + const lines = this.lines(); + const patterns = block.map((it) => regex(it)); + const maxIndex = lines.length - block.length; + const row = lines.findIndex(( + (_, index) => index <= maxIndex && patterns.every(( + (pattern, offset) => lines[index + offset].match(pattern) + )) + )); + if (row === -1) { + throw new Error(`No block found matching\n\n${patterns.join('\n')}`); + } + lines.splice(row, block.length); + this.content = lines.join('\n'); + } + + /** + * Deletes every group of lines that matches `block`. + * + * @param block Strings or RegExps that identify the lines. + */ + deleteEveryBlock(block: (string | RegExp)[]) { + if (block.length === 0) { + return; + } + + const lines = this.lines(); + const patterns = block.map((it) => regex(it)); + const rows: number[] = []; + let matchRow = -1; + let matches = 0; + lines.forEach((line, index) => { + if (line.match(patterns[matches])) { + if (matches === 0) { + matchRow = index; + } + matches += 1; + if (matches === block.length) { + rows.push(matchRow); + matches = 0; + } + } else if (matches > 0) { + if (line.match(patterns[0])) { + matches = 1; + } else { + matches = 0; + } + } + }); + rows.forEach((row, index) => { + lines.splice(row - index * block.length, block.length); + }); + this.content = lines.join('\n'); + } } diff --git a/tests/text.spec.ts b/tests/text.spec.ts index bc49785..4289492 100644 --- a/tests/text.spec.ts +++ b/tests/text.spec.ts @@ -213,4 +213,45 @@ describe('Text', () => { expect(text.content).to.equal('one'); }); }); + + describe('deleteBlock', () => { + it('should delete the first matching block (strings)', () => { + const text = new Text('one\ntwo\n\none\nthree\n\none\ntwo'); + text.deleteBlock(['one', 'two']); + expect(text.content).to.equal('\none\nthree\n\none\ntwo'); + }); + + it('should delete the first matching block (regexps)', () => { + const text = new Text('one\ntwo\n\none\nthree\n\none\ntwo'); + text.deleteBlock([/one/, /two/]); + expect(text.content).to.equal('\none\nthree\n\none\ntwo'); + }); + + it('should throw if no block matches', () => { + const text = new Text('one'); + expect( + () => text.deleteBlock(['one', 'two']), + ).to.throw('No block found matching\n\n/one/\n/two/'); + }); + }); + + describe('deleteEveryBlock', () => { + it('should delete all matching blocks (strings)', () => { + const text = new Text('one\ntwo\n\none\nthree\n\none\ntwo'); + text.deleteEveryBlock(['one', 'two']); + expect(text.content).to.equal('\none\nthree\n'); + }); + + it('should delete all matching blocks (regexps)', () => { + const text = new Text('one\ntwo\n\none\nthree\n\none\ntwo'); + text.deleteEveryBlock([/one/, /two/]); + expect(text.content).to.equal('\none\nthree\n'); + }); + + it('should not consider lines overlapping in blocks that match', () => { + const text = new Text('one\none\none\ntwo'); + text.deleteEveryBlock(['one', 'one']); + expect(text.content).to.equal('one\ntwo'); + }); + }); });