Skip to content

Instantly share code, notes, and snippets.

@lionel-rowe
Created March 22, 2019 18:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lionel-rowe/72a19bf346858ede6f406ad20e7c157a to your computer and use it in GitHub Desktop.
Save lionel-rowe/72a19bf346858ede6f406ad20e7c157a to your computer and use it in GitHub Desktop.
/*
Concepts covered:
concept | syntax
---------------------------------|--------------------------
testing for a match | test
replacing matches | replace
...with a string | replace(re, '...')
...with a function | replace(re, m => ...)
character classes | []
non-capturing groups | (?:)
capturing groups | ()
numeric reference | \1
alternation | |
0 or more operator | *
1 or more operator | +
quantifiers with minimum bounds | {n,}
space and non-space | \s and \S
case-insensitive flag | i
global flag | g
start | ^
end | $
*/
// test generation
const spamPhrases = [
'viagra', 'free money', 'work from home', 'stock alert', 'dear friend'
];
const mangleMap = {
a: [ 'a', '@', '4' ],
b: [ 'b', '8' ],
c: [ 'c', '{', '[', '(' ],
e: [ 'e', '3' ],
g: [ 'g', '9' ],
i: [ 'i', '1', '|' ],
o: [ 'o', '0' ],
s: [ 's', '5' ],
t: [ 't' , '7' ],
z: [ 'z', '2' ]
};
const tests = [
[ 'Hi Michelle,', false ],
[ 'The PPT from the meeting is attached', false ],
[ 'Are you free on Wednesday 19th?', false ],
[ 'I\'ll see you at work tomorrow.', false ],
[ 'This may be gibberish, but it\'s harmless gibberish', false ],
[ '', false ]
];
const randInt = max => Math.floor(Math.random() * (max + 1));
const manglePhrase = phrase => {
const mangledPhrase = phrase.split('').map(char => {
const mangledChars = mangleMap[char];
if (!mangledChars) return char;
else {
const len = mangledChars.length;
const c = mangleMap[char][randInt(len - 1)];
return c[randInt(1) === 1 ? 'toUpperCase' : 'toLowerCase']();
}
});
return mangledPhrase.join(' '.repeat(randInt(2)));
};
spamPhrases.forEach(phrase => {
for (let i = 0; i < 5; i++) {
const mangled = manglePhrase(phrase);
tests.push([mangled, true]);
}
});
// model answer
// - student will produce something functionally equivalent
// but using regex literals exclusively (no `makeRegex`)
const wordCondenser = /(?:^|\s)\S(?:(\s+)\S)(?:\1\S)*(?:$|\s)/g;
const spaceCondenser = /\s{2,}/g;
const condenseSpaces = msg => {
return msg
.replace(wordCondenser, m => m.replace(/\s+/g, ''))
.replace(spaceCondenser, ' ');
};
const makeRegex = str => {
const content = str.split('').map(char => {
const mangledChars = mangleMap[char];
if (!mangledChars || mangledChars.length === 1) {
return char;
} else {
return `[${mangledChars.join('')}]`;
}
});
return new RegExp(`(?:^|\\s)${content.join('')}(?:$|\\s)`, 'i');
};
const blacklistRegexps = spamPhrases.map(phrase => makeRegex(phrase));
/*
blacklistRegexps:
/(?:^|\s)v[i1|][a@4][g9]r[a@4](?:$|\s)/i,
/(?:^|\s)fr[e3][e3] m[o0]n[e3]y(?:$|\s)/i,
/(?:^|\s)w[o0]rk fr[o0]m h[o0]m[e3](?:$|\s)/i,
/(?:^|\s)[s5][t7][o0][c{[(]k [a@4]l[e3]r[t7](?:$|\s)/i,
/(?:^|\s)d[e3][a@4]r fr[i1|][e3]nd(?:$|\s)/i
*/
const isSpam = msg => {
const spacesCondensed = condenseSpaces(msg);
return blacklistRegexps.some(re => re.test(spacesCondensed));
};
// run tests
tests.forEach(test => {
if (isSpam(test[0]) !== test[1]) console.log(test);
});
// documentation generation
const spamPhrasesMD = '**Spam phrases:**\n\n'
+ spamPhrases.map(phrase => `* ${phrase}`).join('\n');
/*
**Spam phrases:**
* viagra
* free money
* work from home
* stock alert
* dear friend
*/
const mangleMapMD = `
Character | Mangled versions
----------|-----------------
${Object.keys(mangleMap).map(key => `${key.padEnd('Character'.length, ' ')} | ${mangleMap[key].join(', ')}`).join('\n')}
`;
/*
Character | Mangled versions
----------|-----------------
a | a, @, 4
b | b, 8
c | c, {, [, (
e | e, 3
g | g, 9
i | i, 1, |
o | o, 0
s | s, 5
t | t, 7
z | z, 2
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment