require "../constants/token_abbrevs.cr" require "../formatter" require "html" module Tartrazine def self.to_html(text : String, language : String, theme : String = "default-dark", standalone : Bool = true, line_numbers : Bool = false) : String Tartrazine::Html.new( theme: Tartrazine.theme(theme), standalone: standalone, line_numbers: line_numbers ).format(text, Tartrazine.lexer(name: language)) end class Html < Formatter # property line_number_in_table : Bool = false # property with_classes : Bool = true property class_prefix : String = "" property highlight_lines : Array(Range(Int32, Int32)) = [] of Range(Int32, Int32) property line_number_id_prefix : String = "line-" property line_number_start : Int32 = 1 property tab_width = 8 property? line_numbers : Bool = false property? linkable_line_numbers : Bool = true property? standalone : Bool = false property? surrounding_pre : Bool = true property? wrap_long_lines : Bool = false property weight_of_bold : Int32 = 600 property theme : Theme def initialize(@theme : Theme = Tartrazine.theme("default-dark"), *, @highlight_lines = [] of Range(Int32, Int32), @class_prefix : String = "", @line_number_id_prefix = "line-", @line_number_start = 1, @tab_width = 8, @line_numbers : Bool = false, @linkable_line_numbers : Bool = true, @standalone : Bool = false, @surrounding_pre : Bool = true, @wrap_long_lines : Bool = false, @weight_of_bold : Int32 = 600) end def format(text : String, lexer : Lexer) : String outp = String::Builder.new("") format(text, lexer, outp) outp.to_s end def format(text : String, lexer : BaseLexer, io : IO) : Nil pre, post = wrap_standalone io << pre if standalone? format_text(text, lexer, io) io << post if standalone? end # Wrap text into a full HTML document, including the CSS for the theme def wrap_standalone output = String.build do |outp| outp << "" end {output.to_s, ""} end private def line_label(i : Int32) : String line_label = "#{i + 1}".rjust(4).ljust(5) line_class = highlighted?(i + 1) ? "class=\"#{get_css_class("LineHighlight")}\"" : "" line_id = linkable_line_numbers? ? "id=\"#{line_number_id_prefix}#{i + 1}\"" : "" "#{line_label} " end def format_text(text : String, lexer : BaseLexer, outp : IO) tokenizer = lexer.tokenizer(text) i = 0 if surrounding_pre? pre_style = wrap_long_lines? ? "style=\"white-space: pre-wrap; word-break: break-word;\"" : "" outp << "
"
      end
      outp << ""
      outp << line_label(i) if line_numbers?
      tokenizer.each do |token|
        outp << "#{HTML.escape(token[:value])}"
        if token[:value].ends_with? "\n"
          i += 1
          outp << line_label(i) if line_numbers?
        end
      end
      outp << "
" end # ameba:disable Metrics/CyclomaticComplexity def style_defs : String output = String.build do |outp| theme.styles.each do |token, style| outp << ".#{get_css_class(token)} {" # These are set or nil outp << "color: ##{style.color.try &.hex};" if style.color outp << "background-color: ##{style.background.try &.hex};" if style.background outp << "border: 1px solid ##{style.border.try &.hex};" if style.border # These are true/false/nil outp << "border: none;" if style.border == false outp << "font-weight: #{@weight_of_bold};" if style.bold outp << "font-style: italic;" if style.italic outp << "font-style: normal;" if style.italic == false outp << "text-decoration: underline;" if style.underline outp << "text-decoration: none;" if style.underline == false outp << "tab-size: #{tab_width};" if token == "Background" outp << "}" end end output end # Given a token type, return the CSS class to use. def get_css_class(token : String) : String if !theme.styles.has_key? token # Themes don't contain information for each specific # token type. However, they may contain information # for a parent style. Worst case, we go to the root # (Background) style. parent = theme.style_parents(token).reverse.find { |dad| theme.styles.has_key?(dad) } theme.styles[token] = theme.styles[parent] end class_prefix + Abbreviations[token] end end end