Skip to content

Commit

Permalink
Adds group extraction and named groups
Browse files Browse the repository at this point in the history
  • Loading branch information
ljdavies committed Apr 26, 2020
1 parent 8078f77 commit 88bbbc0
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 17 deletions.
1 change: 1 addition & 0 deletions __tests__/builder/followedBy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ describe('followedBy test', () => {
)
.followedBy('goodbye');

expect(builder.matchesString('2helloyessmile2!goodbye')).toBeTruthy();
expect(builder.matchesString('2helloyessmile2!goodbye')).toBeTruthy();
expect(builder.matchesString('hellogoodbye')).toBeFalsy();
});
Expand Down
9 changes: 9 additions & 0 deletions __tests__/builder/getMatches.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ExpressionBuilder from '../../src/exceptional-expressions';
import { group } from '../../src/utils';
import Sequences from '../../src/sequences';

describe('getMatches test', () => {
Expand All @@ -22,4 +23,12 @@ describe('getMatches test', () => {
expect(builder.getMatches('##hello I $$$$ a test hello****')).toEqual(['$$$$', '****']);
expect(builder.getMatches('##hello I $$$ a test hello***')).toEqual([]);
});

it('duplicated functionality given groups', () => {
builder.contains(group(Sequences.symbols(4)));

expect(builder.getMatches('I am a test string!!!!')).toEqual(['!!!!']);
expect(builder.getMatches('##hello I $$$$ a test hello****')).toEqual(['$$$$', '****']);
expect(builder.getMatches('##hello I $$$ a test hello***')).toEqual([]);
});
});
56 changes: 56 additions & 0 deletions __tests__/builder/getMatchesWithGroups.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import ExpressionBuilder from '../../src/exceptional-expressions';
import { group } from '../../src/utils';
import Sequences from '../../src/sequences';

describe('getMatches test', () => {
let builder: ExpressionBuilder = new ExpressionBuilder('g');
let match: string | any[];

beforeEach(() => {
builder.reset();
});

it('extracts single group match', () => {
builder
.contains('hello')
.followedBy(Sequences.whitespace({ min: 1 }))
.followedBy(group(Sequences.numbers(4), 'numbers'));

match = builder.getMatchesWithGroups('hello 1234');
expect(match.length).toEqual(1);
expect(match[0].match).toEqual('hello 1234');
expect(match[0].groups['numbers']).toEqual('1234');
});

it('extracts multi group match', () => {
builder
.contains('hello')
.followedBy(Sequences.whitespace({ min: 1 }))
.followedBy(group(Sequences.numbers(4), 'numbers'));

match = builder.getMatchesWithGroups('hello 1234 hello 4567');

expect(match.length).toEqual(2);
expect(match[0].match).toEqual('hello 1234');
expect(match[1].match).toEqual('hello 4567');
expect(match[0].groups['numbers']).toEqual('1234');
expect(match[1].groups['numbers']).toEqual('4567');
});

it('extracts nested groups match', () => {
builder
.contains('hello')
.followedBy(Sequences.whitespace({ min: 1 }))
.followedBy(group(['(', group(Sequences.numbers(4), 'numbers'), ')'], 'container'));

match = builder.getMatchesWithGroups('hello (1234) hello (4567)');

expect(match.length).toEqual(2);
expect(match[0].match).toEqual('hello (1234)');
expect(match[1].match).toEqual('hello (4567)');
expect(match[0].groups['numbers']).toEqual('1234');
expect(match[1].groups['numbers']).toEqual('4567');
expect(match[0].groups['container']).toEqual('(1234)');
expect(match[1].groups['container']).toEqual('(4567)');
});
});
49 changes: 34 additions & 15 deletions src/exceptional-expressions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
assertDoesntExist,
validateFlags,
extractMatches,
extractMatchesWithGroup,
IGroupings,
} from './utils';
export default class ExpressionBuilder {
private beginsWithExpression: string | null = null;
Expand All @@ -28,11 +30,15 @@ export default class ExpressionBuilder {
return extractMatches(string, this.buildExpression()).length;
}

public getMatches(string: string): RegExpMatchArray {
public getMatches(string: string): Array<string> {
return extractMatches(string, this.buildExpression());
}

public getCaptureGroups() {
public getMatchesWithGroups(string: string): IGroupings[] {
return extractMatchesWithGroup(string, this.buildExpression(), this.getCaptureGroups());
}

public getCaptureGroups(): Array<string | number> {
return this.groups.map((group, index) => (group.startsWith('__') ? index + 1 : group));
}

Expand All @@ -48,6 +54,7 @@ export default class ExpressionBuilder {
this.beginsWithExpression = null;
this.endsWithExpression = null;
this.internal = [];
this.groups = [];
}

private buildExpression(): RegExp {
Expand All @@ -67,7 +74,7 @@ export default class ExpressionBuilder {
);

const validated: string = validateExpression(expression);
this.internal.push(validated);
this.internal.push(this.handleInternalTransformation(validated));

return this;
}
Expand All @@ -80,9 +87,8 @@ export default class ExpressionBuilder {

const validated: string = validateExpression(expression);

this.internal[this.internal.length - 1] = wrapOrExpression(
this.internal[this.internal.length - 1],
validated
this.internal[this.internal.length - 1] = this.handleInternalTransformation(
wrapOrExpression(this.internal[this.internal.length - 1], validated)
);

return this;
Expand All @@ -99,7 +105,9 @@ export default class ExpressionBuilder {
);

const validated: string = validateExpression(expression);
this.internal.push(this.extractInternalGroups(handleOptionalWrapping(validated, optional)));
this.internal.push(
this.handleInternalTransformation(handleOptionalWrapping(validated, optional))
);

return this;
}
Expand All @@ -112,9 +120,8 @@ export default class ExpressionBuilder {

const validated: string = validateExpression(expression);

this.internal[this.internal.length - 1] = wrapOrExpression(
this.internal[this.internal.length - 1],
validated
this.internal[this.internal.length - 1] = this.handleInternalTransformation(
wrapOrExpression(this.internal[this.internal.length - 1], validated)
);

return this;
Expand All @@ -135,7 +142,9 @@ export default class ExpressionBuilder {
public endsWith(expression: any, optional: boolean = false): ExpressionBuilder {
const validated: string = validateExpression(expression);

this.endsWithExpression = handleOptionalWrapping(validated, optional);
this.endsWithExpression = this.handleInternalTransformation(
handleOptionalWrapping(validated, optional)
);

return this;
}
Expand All @@ -149,7 +158,9 @@ export default class ExpressionBuilder {
const validated: string = validateExpression(expression);

//TODO extract the optional wrapping and reapply?
this.endsWithExpression = wrapOrExpression(this.endsWithExpression, validated);
this.endsWithExpression = this.handleInternalTransformation(
wrapOrExpression(this.endsWithExpression, validated)
);

return this;
}
Expand All @@ -161,7 +172,9 @@ export default class ExpressionBuilder {
public beginsWith(expression: any, optional: boolean = false): ExpressionBuilder {
const validated: string = validateExpression(expression);

this.beginsWithExpression = handleOptionalWrapping(validated, optional);
this.beginsWithExpression = this.handleInternalTransformation(
handleOptionalWrapping(validated, optional)
);

return this;
}
Expand All @@ -172,14 +185,20 @@ export default class ExpressionBuilder {
'orBeginsWith must be preceeded by a beginsWith statement'
);

const validated: string = validateExpression(expression);
const validated: string = this.handleInternalTransformation(validateExpression(expression));

//TODO extract the optional wrapping and reapply?
this.beginsWithExpression = wrapOrExpression(this.beginsWithExpression, validated);
this.beginsWithExpression = this.handleInternalTransformation(
wrapOrExpression(this.beginsWithExpression, validated)
);

return this;
}

private handleInternalTransformation(expression: any): string {
return this.extractInternalGroups(expression);
}

private extractInternalGroups(expression: string): string {
const groupRx: RegExp = /(\[\|([a-z0-9A-Z_-]+)\|\])(.*)(\[\|\2\|\])/;
let transformed: string = expression;
Expand Down
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import ExpBuilder from './exceptional-expressions';
import { or } from './utils';
import { or, anythingBut, group } from './utils';

export { ExpBuilder, or };
export { ExpBuilder, or, anythingBut, group };
49 changes: 49 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export const group: (expression: any, group?: string) => string = (
return `~~[|${group}|](${validateExpression(expression)})[|${group}|]`;
};

/**
* Generate a random string of length 4
*/
const randomString = (): string => {
return Math.random().toString(36).substring(2, 4) + Math.random().toString(36).substring(2, 4);
};
Expand Down Expand Up @@ -64,6 +67,12 @@ export const anythingBut: (expression: any) => string = (expression: any): strin
return `~~(?:(?:(?!${validateExpression(expression)}).)*)`;
};

/**
* Assert that a value passed equals either null or undefined
*
* @param {any} val
* @param {string} message
*/
export const assertDoesntExist: (val: any, message?: string) => asserts val is null | undefined = (
val: any,
message?: string
Expand Down Expand Up @@ -183,6 +192,46 @@ export const extractMatches: (string: string, regex: RegExp) => Array<string> =
return getGroupsByIndex(string, regex, 0);
};

export interface IGroupings {
match: string;
groups: IGroup;
}

interface IGroup {
[key: string]: string;
}

export const extractMatchesWithGroup = (
string: string,
regex: RegExp,
groups: Array<string | number>
): IGroupings[] => {
let matchCount: number = 0;
const extractions: Array<string>[] = [];
const groupings: IGroupings[] = [];

for (let index = 0; index < groups.length + 1; index++) {
const matches = getGroupsByIndex(string, regex, index);
if (index === 0) {
matchCount = matches.length;
}
extractions.push(matches);
}

for (let i = 0; i < matchCount; i++) {
const group: IGroupings = {
match: extractions[0][i],
groups: {},
};
for (let j = 1; j < extractions.length; j++) {
group.groups[groups[j - 1]] = extractions[j][i];
}
groupings.push(group);
}

return groupings;
};

export const getGroupsByIndex = (
string: string,
regex: RegExp,
Expand Down

0 comments on commit 88bbbc0

Please sign in to comment.