Skip to content

Commit

Permalink
Proposal for escaping within lexed content
Browse files Browse the repository at this point in the history
  • Loading branch information
http://jneen.net/ committed Jun 3, 2019
1 parent 5030571 commit 931a5f9
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 0 deletions.
3 changes: 3 additions & 0 deletions lib/rouge/demos/escape
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
If Formatter.enable_escape! is called, this allows escaping into html
or the parent format with a special delimiter. For example:
<!<span style="text-decoration: underline">!>underlined text!<!</span>!>
36 changes: 36 additions & 0 deletions lib/rouge/formatter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,26 @@ def self.find(tag)
REGISTRY[tag]
end

def self.with_escape
Thread.current[:'rouge/with-escape'] = true
yield
ensure
Thread.current[:'rouge/with-escape'] = false
end

def self.escape_enabled?
!!(@escape_enabled || !!Thread.current[:'rouge/with-escape'])
end

def self.enable_escape!
@escape_enabled = true
end

def self.disable_escape!
@escape_enabled = false
Thread.current[:'rouge/with-escape'] = false
end

# Format a token stream. Delegates to {#format}.
def self.format(tokens, *a, &b)
new(*a).format(tokens, &b)
Expand All @@ -30,8 +50,24 @@ def initialize(opts={})
# pass
end

def escape?(tok)
tok == Token::Tokens::Escape
end

def filter_escapes(tokens)
tokens.each do |t, v|
if t == Token::Tokens::Escape
yield Token::Tokens::Error, v
else
yield t, v
end
end
end

# Format a token stream.
def format(tokens, &b)
tokens = enum_for(:filter_escapes, tokens) unless Formatter.escape_enabled?

return stream(tokens, &b) if block_given?

out = String.new('')
Expand Down
2 changes: 2 additions & 0 deletions lib/rouge/formatters/html.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ def stream(tokens, &b)
end

def span(tok, val)
return val if escape?(tok)

safe_span(tok, val.gsub(/[&<>]/, TABLE_FOR_ESCAPE_HTML))
end

Expand Down
53 changes: 53 additions & 0 deletions lib/rouge/lexers/escape.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
module Rouge
module Lexers
class Escape < Lexer
tag 'escape'
aliases 'esc'

desc 'A generic lexer for including escaped content - see Formatter.enable_escape!'

option :start, 'the beginning of the escaped section, default "<!"'
option :end, 'the end of the escaped section, e.g. "!>"'
option :lang, 'the language to lex in unescaped sections'

def initialize(*)
super
@start = string_option(:start) { '<!' }
@end = string_option(:end) { '!>' }
@lang = lexer_option(:lang) { PlainText.new }
end

def to_start_regex
@to_start_regex ||= /(.*?)(#{Regexp.escape(@start)})/m
end

def to_end_regex
@to_end_regex ||= /(.*?)(#{Regexp.escape(@end)})/m
end

def stream_tokens(str, &b)
stream = StringScanner.new(str)
p :to_start_regex => to_start_regex
p :to_end_regex => to_end_regex

loop do
if stream.scan(to_start_regex)
puts "pre-escape: #{stream[1].inspect}" if @debug
@lang.continue_lex(stream[1], &b)
else
# no more start delimiters, scan til the end
@lang.continue_lex(stream.rest, &b)
return
end

if stream.scan(to_end_regex)
yield Token::Tokens::Escape, stream[1]
else
yield Token::Tokens::Escape, stream.rest
return
end
end
end
end
end
end
24 changes: 24 additions & 0 deletions spec/formatter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,28 @@
it 'is found by Rouge.highlight' do
assert { Rouge.highlight('puts "Hello"', 'ruby', 'terminal256') }
end

it 'does not escape by default' do
assert { not Rouge::Formatter.escape_enabled? }
end

it 'escapes in all threads with #enable_escape!' do
begin
Rouge::Formatter.enable_escape!
assert { Rouge::Formatter.escape_enabled? }
ensure
Rouge::Formatter.disable_escape!
end
end

it 'escapes locally with #with_escape' do
Rouge::Formatter.with_escape do
assert { Rouge::Formatter.escape_enabled? }
assert { not Thread.new { Rouge::Formatter.escape_enabled? }.value }
Rouge::Formatter.disable_escape!
assert { not Rouge::Formatter.escape_enabled? }
end

assert { not Rouge::Formatter.escape_enabled? }
end
end
3 changes: 3 additions & 0 deletions spec/lexers_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
out_buf << value
end

# Escape is allowed to drop characters from its input
next if lexer_class == Rouge::Lexers::Escape

if out_buf != sample
out_file = "tmp/mismatch.#{subject.tag}"
puts "mismatch with #{samples_dir.join(lexer_class.tag)}! logged to #{out_file}"
Expand Down
1 change: 1 addition & 0 deletions spec/visual/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def query_string
reload_source!

Rouge::Lexer.enable_debug!
Rouge::Formatter.enable_escape! if params[:escape]

theme_class = Rouge::Theme.find(params[:theme] || 'thankful_eyes')
halt 404 unless theme_class
Expand Down
8 changes: 8 additions & 0 deletions spec/visual/samples/escape
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
If Formatter.enable_escape! is called, this allows escaping into html
or the parent format with a special delimiter. For example, here is
some <!<span style="text-decoration: underline">!>underlined text<!</span>!> that flows with the rest of the document.

When visually speccing this, you should see the escaped portions rendered as an
error. This is because for safety reasons we don't allow escaping to html without
explicit opting in. If you provide the ?escape=1 option in the url, you should
see underlined text.

0 comments on commit 931a5f9

Please sign in to comment.