1

I'm trying to create a JS regex that matches !next either at the beginning or end of the line.

Currently I'm using /(^!next|!next$)/i which works. However that's obviously kind of ugly to read and not DRY, so I'm looking to improve.

The closest I've come is this:

/(?=!next)[^$]/i

But that actually just matches everything (and it occurs to me now that I misremembered the way that ^ works inside character classes, so this solution is garbage anyway).

I've searched the web and SO, and I'm totally stumped.

strugee
  • 2,752
  • 4
  • 19
  • 30
  • You could leave off those parentheses, but your first expression is just fine. `^` inside `[]` means not any one character. – StackSlave Oct 08 '16 at 05:30
  • 1
    The original way is much easier to understand at a glance. If there are other developers involved I'd stick with that. – K Scandrett Oct 08 '16 at 05:36
  • 2
    You could built the regexp dynamically with `function beginningOrEnd(str) { return new Regexp(\`^${str}|${str}\$\`, 'i'); }`, then call it with `beginningOrEnd("!next")`. –  Oct 08 '16 at 05:50
  • Why are you escaping the exclamation mark? –  Oct 08 '16 at 05:54
  • @torazaburo good question. it's based on a regexp that someone else authored in a PR and I copypasta'd without noticing. – strugee Oct 08 '16 at 05:57

2 Answers2

4

Here's a fun one, using regex conditionals:

/(^)?!next(?(1)|$)/gm

Demo on Regex101

How it works is it captures the beginning-of-line anchor, and, if it is not present, matches the end-of-line anchor instead.

Personally, though, I'd still argue in favour of your solution over mine because it's more readable to someone who doesn't have an extremely thorough knowledge of regexes (and is more portable).

For added fun, here's another version (that's even uglier than your initial variant):

/!next(?:(?<=^.{5})|(?=$))/gm

Demo on Regex101

I'd still recommend sticking with the classic alternation, though.

And, finally, one that works in JS (no, really):

/(?:^|(?=.{5}$))!next/gm

Demo on Regex101

Sebastian Lenartowicz
  • 4,695
  • 4
  • 28
  • 39
  • I haven't tested it, but I'm willing to bet OP's initial way is technologically faster. – StackSlave Oct 08 '16 at 05:33
  • 1
    @PHPglue: [According to Regex101, you're right (58 steps vs. 86)](https://regex101.com/r/54mXuy/2), but the OP asked for a DRY-ish regex, not a fast one. Note that I argue in favour of his solution as opposed to my own. I'm actually interested why the alternation variant has less steps now... – Sebastian Lenartowicz Oct 08 '16 at 05:35
  • I can't get that to work in regexp101 specifying the JS flavor. –  Oct 08 '16 at 05:53
  • @SebastianLenartowicz the second one is... truly horrifying. but nice :P – strugee Oct 08 '16 at 05:57
  • @torazaburo. No. JavaScript doesn't support conditionals. The second version also doesn't work because JS doesn't support lookbehind assertions, either. – Tim Pietzcker Oct 08 '16 at 05:59
  • Plus one for extreme creativity, however misplaced. –  Oct 08 '16 at 06:03
  • @torazaburo: I came up with a JS-compatible one, just for you :P – Sebastian Lenartowicz Oct 08 '16 at 06:09
3

You could try

function beginningOrEnd(input, search) {
  const pos = input.search(search);
  return pos === 0 || pos === input.length - search.length;
}

beginningOrEnd("hey, !help", "!help");

This uses search.length, so obviously search must be a string.

Or, you could construct the regexp yourself:

function beginningOrEnd(search) {
  return new RegExp(`^${search}|${search}\$`, "i");
}

beginningOrEnd("!help").test(input)

In the real world, you'd want to escape special regexp characters appropriately, of course.