Skip to content

Commit

Permalink
Do not escape underscored username in code or codeblocks
Browse files Browse the repository at this point in the history
Skip escaping underscore when content is in code or codeblock. It works by
going through all lines of a markdown content. Find underscored username.
Escape if we are not in the codeblock.

The (?<!) is negative lookbehind which rules out the case
when a underscored username is present in code, e.g., `@_dev_` will not be escaped.
  • Loading branch information
JuanitoFatas committed Feb 9, 2020
1 parent 41d812b commit 432885f
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 2 deletions.
20 changes: 19 additions & 1 deletion app/labor/markdown_fixer.rb
Expand Up @@ -47,11 +47,29 @@ def split_tags(markdown)
end

def underscores_in_usernames(markdown)
markdown.gsub(/(@)(_\w+)(_)/, '\1\\\\\2\\\\\3')
return markdown unless markdown.include?("@")

traverser = MarkdownTraverser.new(markdown)
traverser.each do |line|
unless traverser.in_codeblock?
escape_underscored_username_in_line!(line)
end
end.join
end

private

# Match @_username_ that is not preceded by backtick
USERNAME_WITH_UNDERSCORE_REGEXP = /(?<!`)@_\w+_/.freeze

# Escapes underscored username that is not in code
def escape_underscored_username_in_line!(line)
line.scan(USERNAME_WITH_UNDERSCORE_REGEXP).each do |to_escape|
line.sub!(to_escape, to_escape.gsub("_", "\\_"))
end
line
end

def add_quotes_to_section(markdown, section:)
# Only add quotes to front matter, or text between triple dashes
markdown.sub(FRONT_MATTER_DETECTOR) do |front_matter|
Expand Down
35 changes: 35 additions & 0 deletions app/labor/markdown_traverser.rb
@@ -0,0 +1,35 @@
# To go through a markdown document. Mainly used to decide if we are in
# code blocks to decide if we should escape underscored usernames.
class MarkdownTraverser
def initialize(markdown)
@lines = markdown.dup.lines
init_position
end

def each
lines.each do |line|
update_position(line.include?(CODEBLOCK_MARKER))
yield(line)
end
end

def in_codeblock?
prev == true && current == false
end

private

CODEBLOCK_MARKER = "```".freeze

attr_accessor :lines, :prev, :current

def init_position
self.prev = false
self.current = lines.first.include?(CODEBLOCK_MARKER)
end

def update_position(current)
self.current = current
self.prev = current ? !prev : prev
end
end
19 changes: 18 additions & 1 deletion spec/labor/markdown_fixer_spec.rb
Expand Up @@ -130,7 +130,7 @@ def front_matter(title: "", description: "")
test_string1 = "@_xy_"
expected_result1 = "@\\_xy\\_"
test_string2 = "@_x_y_"
expected_result2 = "@\\_x_y\\_"
expected_result2 = "@\\_x\\_y\\_"

expect(described_class.underscores_in_usernames(test_string1)).to eq(expected_result1)
expect(described_class.underscores_in_usernames(test_string2)).to eq(expected_result2)
Expand All @@ -141,5 +141,22 @@ def front_matter(title: "", description: "")
expected_result = "_make this cursive_"
expect(described_class.underscores_in_usernames(test_string)).to eq(expected_result)
end

it "escapes correctly and ignores underscored username in code and code block" do
input = <<~INPUT
@_dev_
```ruby
@_no_escape_codeblock
```
`@_no_escape_code`
INPUT

result = described_class.underscores_in_usernames(input)

expect(result).to include("@\\_dev\\_")
expect(result).to include("@_no_escape_codeblock", "@_no_escape_code")
end
end
end

0 comments on commit 432885f

Please sign in to comment.