Find :: outside of markdown code formatting

Viewed 61

I have a bunch of markdown files, where I want to search for Ruby's double colon :: outside of some code formatting (e.g. where I forgot to apply proper markdown). For example

`foo::bar`

hello `foo::bar` test
`  example::with::whitespace  `

```
Proper::Formatted
```

```
  Module::WithIndendation
```

```
Some::Nested::Modules
```

```ruby
CodeBlock::WithSyntax
```

# Some::Class

## Another::Class Heading
some text

The regex only should match Some::Class and Another::Class, because they miss the surrounding backticks, and are also not within a multiline code fence block.

I have this regex, but it also matches the multi line block

[\s]+[^`]+(::)[^`]+[\s]?

Any idea, how to exclude this?

EDIT: It would be great, if the regex would work in Ruby, JS and on the command line for grep.

2 Answers

For the original input, you may use this regex in ruby to match :: string

  1. not preceded by a ` and

  2. not preceded by ` followed a white-space:

Regex:

(?<!`\s)(?<!`)\b\w+::\w+

RegEx Demo 1

RegEx Breakup:

  • (?<!\s): Negative lookbehind to assert that <code> and whitespace is not at preceding position
  • (?<!): Negative lookbehind to assert that <code> is not at preceding position
  • \b: Match word boundary
  • \w+: Match 1+ word characters
  • ::: Match a ::
  • \w+: Match 1+ word characters

You can use this regex in Javascript:

(?<!`\w*\s*|::)\b\w+(?:::\w+)+

RegEx Demo 2


For gnu-grep, consider this command:

grep -ZzoP '`\w*\s*\b\w+::\w+(*SKIP)(*F)|\b\w+::\w+' file |
xargs -0 printf '%s\n'

Some::Class
Another::Class

RegEx Demo 3

One can use the regular expression

rgx = /`[^`]*`|([^`\r\n]*::[^`\r\n]*)/

with the form of String#gsub that takes one argument and no block, and therefore returns an enumerator (str holding the example string given in the question):

  str.gsub(rgx).select { $1 }
  #=> ["# Some::Class", "## Another::Class Heading"]

The idea is that the first part of the regex's alternation, `[^`]*`, matches, but does not capture, all strings delimited by backtics (including ``), whereas the second part, ([^`\r\n]*::[^`\r\n]*), matches and captures all strings on a single line that contain '::' but no backtics. We therefore concern ourselves with captures only, by invoking select { $1 } on the enumerator returned by gsub.


The regular expression can be made self-documenting by writing it in free-spacing mode.

rgx = /
        `            # match a backtic
        [^`]*        # match zero of more characters other than backtics 
        `            # match a backtic
      |              # or
        (            # begin capture group 1
          [^`\r\n]*  # match zero of more characters other than backtics and
                     # line terminators
          ::         # match two colons
          [^`\r\n]*  # ditto line before previous
        )            # end capture group 1
      /x             # invoke free-spacing regex definition mode

[^`\r\n] contains \r (carriage return) in the event that the file was created with Windows. If desired, [^`]* can be replaced with .*? (match zero or more characters, as few as possible).

Related