-
-
Save czak/94f8b98e3f6789b38a4f7414ca703380 to your computer and use it in GitHub Desktop.
module Czak | |
module Tags | |
class HighlightCaptionBlock < Liquid::Block | |
include Liquid::StandardFilters | |
# The regular expression syntax checker. Start with the language specifier. | |
# Follow that by zero or more space separated options that take one of three | |
# forms: name, name=value, or name="<quoted list>" | |
# | |
# <quoted list> is a space-separated list of numbers | |
SYNTAX = %r!^([a-zA-Z0-9.+#-]+)((\s+\w+(=([\w\.\/-]+|"([0-9]+\s)*[0-9]+"))?)*)$! | |
def initialize(tag_name, markup, tokens) | |
super | |
if markup.strip =~ SYNTAX | |
@lang = Regexp.last_match(1).downcase | |
@highlight_options = parse_options(Regexp.last_match(2)) | |
else | |
raise SyntaxError, <<-eos | |
Syntax Error in tag 'highlight' while parsing the following markup: | |
#{markup} | |
Valid syntax: highlight <lang> [linenos] | |
eos | |
end | |
end | |
def render(context) | |
prefix = context["highlighter_prefix"] || "" | |
suffix = context["highlighter_suffix"] || "" | |
code = super.to_s.gsub(%r!\A(\n|\r)+|(\n|\r)+\z!, "") | |
is_safe = !!context.registers[:site].safe | |
output = | |
case context.registers[:site].highlighter | |
when "pygments" | |
render_pygments(code, is_safe) | |
when "rouge" | |
render_rouge(code) | |
else | |
render_codehighlighter(code) | |
end | |
rendered_output = add_code_tag(output) | |
prefix + rendered_output + suffix | |
end | |
def sanitized_opts(opts, is_safe) | |
if is_safe | |
Hash[[ | |
[:startinline, opts.fetch(:startinline, nil)], | |
[:hl_lines, opts.fetch(:hl_lines, nil)], | |
[:linenos, opts.fetch(:linenos, nil)], | |
[:encoding, opts.fetch(:encoding, "utf-8")], | |
[:cssclass, opts.fetch(:cssclass, nil)], | |
[:caption, opts.fetch(:caption, nil)] | |
].reject { |f| f.last.nil? }] | |
else | |
opts | |
end | |
end | |
private | |
def parse_options(input) | |
options = {} | |
unless input.empty? | |
# Split along 3 possible forms -- key="<quoted list>", key=value, or key | |
input.scan(%r!(?:\w+="[^"]*"|\w+=[\w\.\/-]+|\w+)!) do |opt| | |
key, value = opt.split("=") | |
# If a quoted list, convert to array | |
if value && value.include?("\"") | |
value.delete!('"') | |
value = value.split | |
end | |
options[key.to_sym] = value || true | |
end | |
end | |
if options.key?(:linenos) && options[:linenos] == true | |
options[:linenos] = "inline" | |
end | |
options | |
end | |
def render_pygments(code, is_safe) | |
Jekyll::External.require_with_graceful_fail("pygments") | |
highlighted_code = Pygments.highlight( | |
code, | |
:lexer => @lang, | |
:options => sanitized_opts(@highlight_options, is_safe) | |
) | |
if highlighted_code.nil? | |
Jekyll.logger.error <<eos | |
There was an error highlighting your code: | |
#{code} | |
While attempting to convert the above code, Pygments.rb returned an unacceptable value. | |
This is usually a timeout problem solved by running `jekyll build` again. | |
eos | |
raise ArgumentError, "Pygments.rb returned an unacceptable value "\ | |
"when attempting to highlight some code." | |
end | |
highlighted_code.sub('<div class="highlight"><pre>', "").sub("</pre></div>", "") | |
end | |
def render_rouge(code) | |
Jekyll::External.require_with_graceful_fail("rouge") | |
formatter = Rouge::Formatters::HTML.new( | |
:line_numbers => @highlight_options[:linenos], | |
:wrap => false | |
) | |
lexer = Rouge::Lexer.find_fancy(@lang, code) || Rouge::Lexers::PlainText | |
formatter.format(lexer.lex(code)) | |
end | |
def render_codehighlighter(code) | |
h(code).strip | |
end | |
def add_code_tag(code) | |
code_attributes = [ | |
"class=\"language-#{@lang.to_s.tr("+", "-")}\"", | |
"data-lang=\"#{@lang}\"" | |
].join(" ") | |
res = "<figure class=\"highlight\">" | |
res += "<figcaption>#{@highlight_options[:caption]}</figcaption>" if @highlight_options[:caption] | |
res += "<pre><code #{code_attributes}>#{code.chomp}</code></pre></figure>" | |
res | |
end | |
end | |
end | |
end | |
Liquid::Template.register_tag("highlight2", Czak::Tags::HighlightCaptionBlock) |
Wow, I got annoyed by this very same thing today. I looked into tbjers/jekyll-highlight which claims to be a better highlighter, and for all intense and purposes it is (because it offers this feature), but I couldn't get over how it forces the caption to include a File: prefix (couldn't see an option to disable it). I considered making a pull request to allow the user to dictate what (or rather if they event want a) prefix for the caption, but it hasn't been updated in 3 years and I feared just waiting forever to get the feature to my blog. Then I found this and for all intense and purposes, this is exactly what I was looking for.
Though, if I'm allowed to be a little critical, I don't like the idea of copying and pasting a class and then changing the code to get some desired features. Now if there are any upstream changes to the original file, ours will be left behind π’ π’ π’. So I tried to replicate what you did, but had my class inherit from the default highlight tag. This'll maximise code reuse and maintain consistency with upstream updates.
# src: https://gist.github.com/czak/94f8b98e3f6789b38a4f7414ca703380
# see also: https://github.com/jekyll/jekyll/blob/master/lib/jekyll/tags/highlight.rb
module Mohkale
class HighlightCaptionBlock < Jekyll::Tags::HighlightBlock
@@markup_parse_regexp = %r!^([a-zA-Z0-9.+#-]+)((\s+\w+(=([\w\.\/-]+|"([0-9]+\s)*[0-9]+"))?)*)$!.freeze
# also uses a different regex to the parent class, so
# the method body needs to be defined basically twice :(
def initialize(tag_name, markup, tokens)
begin
super(tag_name, markup, tokens)
rescue SyntaxError
if markup.strip =~ @@markup_parse_regexp
@lang = Regexp.last_match(1).downcase
@highlight_options = parse_options(Regexp.last_match(2))
else
raise SyntaxError, <<~MSG
Syntax Error in tag 'highlight' while parsing the following markup:
#{markup}
Valid syntax: highlight <lang> [caption] [linenos] [option=OPTION]
WARN: values for options must be a series of characters ending in
a space. You cannot delimit them with speech marks.
MSG
end
end
end
def render(context)
@context = context
super
end
def add_code_tag(code)
tag = super # without a figure caption
if caption = @highlight_options[:caption]
# find the end of the beginning figure tag
end_of_fig = /class="highlight"[^>]*>/
if match = end_of_fig.match(tag)
end_of_tag = match.end(0) # end of zeroeth group, meaning of entire match string
tag = tag.slice(0, end_of_tag) + get_caption_tag(caption) + tag.slice(end_of_tag, tag.length)
else
# can't find end of figure, so can't append figure caption to tag
raise "unable to find end of figure tag in code\n#{tag}"
end
end
tag
end
private
# Split along 3 possible forms -- key="<quoted list>", key=value, or key
@@options_parse_regexp = %r!(?:\w+="[^"]*"|\w+=[\w\.\/-]+|\w+)!
# uses a different regexp, so has to be redefined here :(
def parse_options(input)
options = {}
unless input.empty?
input.scan(@@options_parse_regexp) do |opt|
key, value = opt.split("=")
# If a quoted list, convert to array
if value && value.include?("\"")
value.delete!('"')
value = value.split
end
options[key.to_sym] = value || true
end
end
if options.key?(:linenos) && options[:linenos] == true
options[:linenos] = "inline"
end
options
end
def get_caption_tag(caption)
tag = "<figcaption>"
if caption_prefix
tag += "<span class=\"prefix\">#{caption_prefix} </span>"
end
tag += caption
tag += "</figcaption>"
tag
end
def _site_config()
@context.registers[:site].config
end
def _highlight_config()
_site_config["highlight"]
end
def caption_prefix
_highlight_config && _highlight_config["caption_prefix"]
end
end
end
Liquid::Template.register_tag("highlight", Mohkale::HighlightCaptionBlock)
seeing as I'm extending the existing highlight tag, I've taken liberty to just redirect all highlight tags through my class instead of the default π. I've also tried to replicate the prefix feature from tbjers/jekyll-highlight so if you want a prefix for figure captions, simply add the following to your _config.yml
file.
highlight:
caption_prefix: "File:"
Update: oops, forgot to allow file names with full stops like you did. embarassing π my bad. Added it now :)
I got annoyed by the fact that the highlight
tag doesn't follow my kramdown settings for line numbers so I added a fallback to that when unspecified. I also got annoyed that I couldn't declare prefixes for figure captions in the tag itself, so I added that. Then I got annoyed that you can't send values with multiple spaces to the tag, noticed that you can for some reason do so with commer delimeted numbers in speech marks, also noticed that doing so is meaningless because @highlight_options
is only used for line numbers, literally nothing else; so I changed it so that you can delimit multi space strings using speech marks (doesn't support speech marks within strings sadly).
Anyways, here's what I've got now:
# src: https://gist.github.com/czak/94f8b98e3f6789b38a4f7414ca703380
# see also: https://github.com/jekyll/jekyll/blob/master/lib/jekyll/tags/highlight.rb
module Mohkale
class HighlightCaptionBlock < Jekyll::Tags::HighlightBlock
@@markup_parse_regexp = %r!^([\w\d.+#-]+)((\s+\w+(=([\w\./\\]+|"[^"]+"))?)*)$!.freeze
# also uses a different regex to the parent class, so
# the method body needs to be defined basically twice :(
def initialize(tag_name, markup, tokens)
begin
super(tag_name, markup, tokens)
rescue SyntaxError
if markup.strip =~ @@markup_parse_regexp
@lang = Regexp.last_match(1).downcase
@highlight_options = parse_options(Regexp.last_match(2))
else
raise SyntaxError, <<~MSG
Syntax Error in tag 'highlight' while parsing the following markup:
#{markup}
Valid syntax: highlight <lang> [caption] [linenos] [option=OPTION]
WARN: values for options must be a series of characters ending in
a space. You cannot delimit them with speech marks.
MSG
end
end
end
def render(context)
@context = context
# make highlight tag conform to kramdown
linenos = @highlight_options[:linenos]
@highlight_options[:linenos] =
if linenos.nil?
_kramdown_linenos
elsif linenos == "no"
false
else
linenos
end
super
end
def add_code_tag(code)
tag = super # without a figure caption
if caption = @highlight_options[:caption]
# find the end of the beginning figure tag
end_of_fig = /class="highlight"[^>]*>/
if match = end_of_fig.match(tag)
end_of_tag = match.end(0) # end of zeroeth group, meaning of entire match string
tag = tag.slice(0, end_of_tag) + get_caption_tag(caption) + tag.slice(end_of_tag, tag.length)
else
# can't find end of figure, so can't append figure caption to tag
raise "unable to find end of figure tag in code\n#{tag}"
end
end
tag
end
private
# Split along 3 possible forms -- key="<quoted list>", key=value, or key
@@options_parse_regexp = %r!(?:\w+="[^"]*"|\w+=[\w\.\/-]+|\w+)!
# uses a different regexp, so has to be redefined here :(
def parse_options(input)
options = {}
return options if input.empty?
# Split along 3 possible forms -- key="<quoted list>", key=value, or key
input.scan(@@options_parse_regexp) do |opt|
key, value = opt.split("=")
# If a quoted value, remove
if value&.include?('"')
value.delete!('"')
end
options[key.to_sym] = value || true
end
options[:linenos] = "inline" if options[:linenos] == true
options
end
def get_caption_tag(caption)
tag = "<figcaption>"
if caption_prefix
tag += "<span class=\"prefix\">#{caption_prefix}</span>"
end
tag += caption
tag += "</figcaption>"
tag
end
def _site_config()
@context.registers[:site].config
end
def _highlight_config()
_site_config["highlight"]
end
def caption_prefix
@highlight_options[:caption_prefix] || (_highlight_config && _highlight_config["caption_prefix"])
end
# there's probably a better way todo this, but what ever
def _kramdown_linenos
begin
_site_config["kramdown"][:syntax_highlighter_opts][:block][:line_numbers]
rescue NoMethodError
nil # key not found
end
end
end
end
Liquid::Template.register_tag("highlight", Mohkale::HighlightCaptionBlock)
@mohkale Are there other means to add caption other than to a separate file?
I'm not sure what you're asking. Either way it's been nearly two years since I wrote this and well over a year since I stopped using jekyll so I'm not sure I'll be of much help.
The plugin registers a
highlight2
tag with Liquid.To use the plugin on your site, place
highlight_caption_block.rb
inside_plugins
folder and use the following:This will result in the following markup: