From bf40c4c61e76ec00b52fd40b9d0138dbb2a93f2a Mon Sep 17 00:00:00 2001 From: Michael Camilleri Date: Tue, 24 Dec 2019 00:04:07 +0900 Subject: [PATCH] Fix comment parsing in Console lexer (#1379) The Console lexer would not correctly parse comments if only the `comments` option was enabled. This was because of a bug in the way the prompt's regular expression pattern worked. This commit fixes that bug and adds a series of tests to confirm the behaviour of the lexer is as intended. --- lib/rouge/lexers/console.rb | 37 ++++++++++++++++++++++++++++++ spec/lexers/console_spec.rb | 45 +++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 spec/lexers/console_spec.rb diff --git a/lib/rouge/lexers/console.rb b/lib/rouge/lexers/console.rb index b5c3d3efd5..e047accbde 100644 --- a/lib/rouge/lexers/console.rb +++ b/lib/rouge/lexers/console.rb @@ -3,6 +3,38 @@ module Rouge module Lexers + # The {ConsoleLexer} class is intended to lex content that represents the + # text that would display in a console/terminal. As distinct from the + # {Shell} lexer, {ConsoleLexer} will try to parse out the prompt from each + # line before passing the remainder of the line to the language lexer for + # the shell (by default, the {Shell} lexer). + # + # The {ConsoleLexer} class accepts four options: + # 1. **lang**: the shell language to lex (default: `shell`); + # 2. **output**: the output language (default: `plaintext?token=Generic.Output`); + # 3. **prompt**: comma-separated list of strings that indicate the end of a + # prompt (default: `$,#,>,;`); + # 4. **comments**: whether to enable comments. + # + # The comments option, if enabled, will lex lines that begin with a `#` as a + # comment. Please note that this option will only work if the prompt is + # either not manually specified or, if manually specified, does not include + # the `#` character. + # + # Most Markdown lexers that recognise GitHub-Flavored Markdown syntax, will + # pass the language string to Rouge as written in the original document. + # This allows an end user to pass options to {ConsoleLexer} by passing them + # as CGI-style parameters as in the example below. + # + # @example + #
Here's some regular text.
+    #
+    # ```console?comments=true
+    # # This is a comment
+    # $ cp foo bar
+    # ```
+    #
+    # Some more regular text.
class ConsoleLexer < Lexer tag 'console' aliases 'terminal', 'shell_session', 'shell-session' @@ -31,6 +63,8 @@ def prompt_regex def end_chars @end_chars ||= if @prompt.any? @prompt.reject { |c| c.empty? } + elsif allow_comments? + %w($ > ;) else %w($ # > ;) end @@ -100,6 +134,9 @@ def stream_tokens(input, &output) def process_line(input, &output) input.scan(line_regex) + # As a nicety, support the use of elisions in input text. A user can + # write a line with only `<...>` or one or more `.` characters and + # Rouge will treat it as a comment. if input[0] =~ /\A\s*(?:<[.]+>|[.]+)\s*\z/ puts "console: matched snip #{input[0].inspect}" if @debug output_lexer.reset! diff --git a/spec/lexers/console_spec.rb b/spec/lexers/console_spec.rb new file mode 100644 index 0000000000..a3bfc37c20 --- /dev/null +++ b/spec/lexers/console_spec.rb @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- # +# frozen_string_literal: true + +describe Rouge::Lexers::ConsoleLexer do + let(:subject) { Rouge::Lexers::ConsoleLexer.new } + let(:klass) { Rouge::Lexers::ConsoleLexer } + + include Support::Lexing + + it 'parses a basic prompt' do + assert_tokens_equal '$ foo', + ['Generic.Prompt', '$'], + ['Text.Whitespace', ' '], + ['Text', 'foo'] + end + + it 'parses a custom prompt' do + subject_with_options = klass.new({ prompt: '%' }) + assert_tokens_equal '% foo', subject_with_options, + ['Generic.Prompt', '%'], + ['Text.Whitespace', ' '], + ['Text', 'foo'] + end + + it 'parses single-line comments' do + subject_with_options = klass.new({ comments: true }) + assert_tokens_equal '# this is a comment', subject_with_options, + ['Comment', '# this is a comment'] + end + + it 'ignores single-line comments' do + assert_tokens_equal '# this is not a comment', + ['Generic.Prompt', '#'], + ['Text.Whitespace', ' '], + ['Text', 'this is not a comment'] + end + + describe 'guessing' do + include Support::Guessing + + it 'guesses by filename' do + assert_guess :filename => 'foo.cap' + end + end +end