14 Commits

Author SHA1 Message Date
8d7faf2098 0.3.0 2024-08-13 11:06:06 -03:00
2e87762f1b API changes to make it nicer
These are incompatible, tho.

* Theme is now a property of the formatter instead
  of passing it arounf
* get_style_defs is now style_defs
2024-08-13 10:57:02 -03:00
88f5674917 Tiny bug 2024-08-12 21:02:17 -03:00
ce6f3d29b5 Remove Re2 hack 2024-08-12 19:01:13 -03:00
46d6d3f467 Make how-heavy-is-bold configurable 2024-08-12 10:55:58 -03:00
78ddc69937 Merge branch 'main' of github.com:ralsina/tartrazine 2024-08-12 10:11:03 -03:00
b1ad7b64c0 oops 2024-08-12 10:10:51 -03:00
cbedf8a8db Bump to 0.2.0 2024-08-11 13:24:30 -03:00
ec8c53c823 Added --line-numbers for the terminal formatter 2024-08-11 13:21:47 -03:00
e3a1ce37b4 Support guessing lexer by filename 2024-08-11 13:04:35 -03:00
b4f38e00e1 Script to generate lexer metadata constants 2024-08-11 12:41:22 -03:00
08daabe1c3 Cleanup token abbreviation generation script 2024-08-11 12:06:02 -03:00
e8d405fc99 Implemented decent version of the CLI 2024-08-11 11:54:00 -03:00
e295256573 Implemented decent version of the CLI 2024-08-11 11:49:42 -03:00
18 changed files with 1571 additions and 121 deletions

View File

@@ -1,5 +1,5 @@
# This configuration file was generated by `ameba --gen-config` # This configuration file was generated by `ameba --gen-config`
# on 2024-08-04 23:09:09 UTC using Ameba version 1.6.1. # on 2024-08-12 22:00:49 UTC using Ameba version 1.6.1.
# The point is for the user to remove these configuration records # The point is for the user to remove these configuration records
# one by one as the reported problems are removed from the code base. # one by one as the reported problems are removed from the code base.
@@ -9,7 +9,7 @@ Documentation/DocumentationAdmonition:
Description: Reports documentation admonitions Description: Reports documentation admonitions
Timezone: UTC Timezone: UTC
Excluded: Excluded:
- src/tartrazine.cr - src/lexer.cr
- src/actions.cr - src/actions.cr
Admonitions: Admonitions:
- TODO - TODO
@@ -17,3 +17,105 @@ Documentation/DocumentationAdmonition:
- BUG - BUG
Enabled: true Enabled: true
Severity: Warning Severity: Warning
# Problems found: 22
# Run `ameba --only Lint/MissingBlockArgument` for details
Lint/MissingBlockArgument:
Description: Disallows yielding method definitions without block argument
Excluded:
- pygments/tests/examplefiles/cr/test.cr
Enabled: true
Severity: Warning
# Problems found: 1
# Run `ameba --only Lint/NotNil` for details
Lint/NotNil:
Description: Identifies usage of `not_nil!` calls
Excluded:
- pygments/tests/examplefiles/cr/test.cr
Enabled: true
Severity: Warning
# Problems found: 34
# Run `ameba --only Lint/ShadowingOuterLocalVar` for details
Lint/ShadowingOuterLocalVar:
Description: Disallows the usage of the same name as outer local variables for block
or proc arguments
Excluded:
- pygments/tests/examplefiles/cr/test.cr
Enabled: true
Severity: Warning
# Problems found: 1
# Run `ameba --only Lint/UnreachableCode` for details
Lint/UnreachableCode:
Description: Reports unreachable code
Excluded:
- pygments/tests/examplefiles/cr/test.cr
Enabled: true
Severity: Warning
# Problems found: 6
# Run `ameba --only Lint/UselessAssign` for details
Lint/UselessAssign:
Description: Disallows useless variable assignments
ExcludeTypeDeclarations: false
Excluded:
- pygments/tests/examplefiles/cr/test.cr
Enabled: true
Severity: Warning
# Problems found: 3
# Run `ameba --only Naming/BlockParameterName` for details
Naming/BlockParameterName:
Description: Disallows non-descriptive block parameter names
MinNameLength: 3
AllowNamesEndingInNumbers: true
Excluded:
- pygments/tests/examplefiles/cr/test.cr
AllowedNames:
- _
- e
- i
- j
- k
- v
- x
- y
- ex
- io
- ws
- op
- tx
- id
- ip
- k1
- k2
- v1
- v2
ForbiddenNames: []
Enabled: true
Severity: Convention
# Problems found: 1
# Run `ameba --only Naming/RescuedExceptionsVariableName` for details
Naming/RescuedExceptionsVariableName:
Description: Makes sure that rescued exceptions variables are named as expected
Excluded:
- pygments/tests/examplefiles/cr/test.cr
AllowedNames:
- e
- ex
- exception
- error
Enabled: true
Severity: Convention
# Problems found: 6
# Run `ameba --only Naming/TypeNames` for details
Naming/TypeNames:
Description: Enforces type names in camelcase manner
Excluded:
- pygments/tests/examplefiles/cr/test.cr
Enabled: true
Severity: Convention

View File

@@ -1,5 +1,5 @@
build: $(wildcard src/**/*.cr) $(wildcard lexers/*xml) $(wildcard styles/*xml) shard.yml build: $(wildcard src/**/*.cr) $(wildcard lexers/*xml) $(wildcard styles/*xml) shard.yml
shards build -Dstrict_multi_assign -Dno_number_autocast shards build -Dstrict_multi_assign -Dno_number_autocast -d --error-trace
release: $(wildcard src/**/*.cr) $(wildcard lexers/*xml) $(wildcard styles/*xml) shard.yml release: $(wildcard src/**/*.cr) $(wildcard lexers/*xml) $(wildcard styles/*xml) shard.yml
shards build --release shards build --release
static: $(wildcard src/**/*.cr) $(wildcard lexers/*xml) $(wildcard styles/*xml) shard.yml static: $(wildcard src/**/*.cr) $(wildcard lexers/*xml) $(wildcard styles/*xml) shard.yml

View File

@@ -31,12 +31,21 @@ is a subset of Pygments'.
Currently Tartrazine supports ... 241 languages. Currently Tartrazine supports ... 241 languages.
It has 332 themes (64 from Chroma, the rest are base16 themes via It has 331 themes (63 from Chroma, the rest are base16 themes via
[Sixteen](https://github.com/ralsina/sixteen) [Sixteen](https://github.com/ralsina/sixteen)
## Installation ## Installation
This has a CLI but it's not generally usable. From prebuilt binaries:
Each release provides statically-linked binaries that should
work on any Linux. Get them from the [releases page](https://github.com/ralsina/tartrazine/releases) and put them in your PATH.
To build from source:
1. Clone this repo
2. Run `make` to build the `tartrazine` binary
3. Copy the binary somewhere in your PATH.
## Usage ## Usage
@@ -60,4 +69,4 @@ puts Tartrazine::Html.new.format(File.read(ARGV[0]), lexer, theme)
## Contributors ## Contributors
- [Roberto Alsina](https://github.com/ralsina) - creator and maintainer - [Roberto Alsina](https://github.com/ralsina) - creator and maintainer

10
TODO.md
View File

@@ -2,6 +2,10 @@
## TODO ## TODO
* Implement styles * Implement styles
* Implement formatters * Implement formatters
* Implement lexer loader that respects aliases, etc * Implement CLI
* ✅ Implement lexer loader that respects aliases
* ✅ Implement lexer loader by file extension
* ✅ Add --line-numbers to terminal formatter
* Implement lexer loader by mime type

54
scripts/lexer_metadata.py Normal file
View File

@@ -0,0 +1,54 @@
# This script parses the metadata of all the lexers and generates
# a datafile with all the information so we don't have to instantiate
# all the lexers to get the information.
import glob
from collections import defaultdict
lexer_by_name = {}
lexer_by_mimetype = defaultdict(set)
lexer_by_filename = defaultdict(set)
for fname in glob.glob("lexers/*.xml"):
aliases = set([])
mimetypes = set([])
filenames = set([])
print(fname)
with open(fname) as f:
lexer_name = fname.split("/")[-1].split(".")[0]
for line in f:
if "</config" in line:
break
if "<filename>" in line:
filenames.add(line.split(">")[1].split("<")[0].lower())
if "<mime_type>" in line:
mimetypes.add(line.split(">")[1].split("<")[0].lower())
if "<alias>" in line:
aliases.add(line.split(">")[1].split("<")[0].lower())
if "<name>" in line:
aliases.add(line.split(">")[1].split("<")[0].lower())
for alias in aliases:
if alias in lexer_by_name and alias != lexer_by_name[alias]:
raise Exception(f"Alias {alias} already in use by {lexer_by_name[alias]}")
lexer_by_name[alias] = lexer_name
for mimetype in mimetypes:
lexer_by_mimetype[mimetype] = lexer_name
for filename in filenames:
lexer_by_filename[filename].add(lexer_name)
with open("src/constants/lexers.cr", "w") as f:
f.write("module Tartrazine\n")
f.write(" LEXERS_BY_NAME = {\n")
for k, v in lexer_by_name.items():
f.write(f'"{k}" => "{v}", \n')
f.write("}\n")
f.write(" LEXERS_BY_MIMETYPE = {\n")
for k, v in lexer_by_mimetype.items():
f.write(f'"{k}" => "{v}", \n')
f.write("}\n")
f.write(" LEXERS_BY_FILENAME = {\n")
for k, v in lexer_by_filename.items():
f.write(f'"{k}" => {str(list(v)).replace("'", "\"")}, \n')
f.write("}\n")
f.write("end\n")

View File

@@ -1,3 +1,10 @@
# Script to generate abbreviations for tokens. Parses all lexers
# and styles files to find all token names and generate a unique
# abbreviation for each one. The abbreviations are generated by
# taking the uppercase letters of the token name and converting
# them to lowercase. If the abbreviation is not unique, the script
# will print a warning and exit.
import sys import sys
import string import string
import glob import glob
@@ -40,7 +47,9 @@ for fname in glob.glob("styles/*.xml"):
tokens.add(line) tokens.add(line)
check_abbrevs() check_abbrevs()
print("Abbreviations = {") with open ("src/constants/token_abbrevs.cr", "w") as outf:
for k, v in abbrevs.items(): outf.write("module Tartrazine\n")
print(f' "{k}" => "{v}",') outf.write(" Abbreviations = {\n")
print("}") for k in sorted(abbrevs.keys()):
outf.write(f' "{k}" => "{abbrevs[k]}",\n')
outf.write(" }\nend\n")

View File

@@ -1,5 +1,5 @@
name: tartrazine name: tartrazine
version: 0.1.1 version: 0.3.0
authors: authors:
- Roberto Alsina <roberto.alsina@gmail.com> - Roberto Alsina <roberto.alsina@gmail.com>

View File

@@ -1,5 +1,4 @@
require "./actions" require "./actions"
require "./constants"
require "./formatter" require "./formatter"
require "./rules" require "./rules"
require "./styles" require "./styles"

1160
src/constants/lexers.cr Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -22,6 +22,7 @@ module Tartrazine
"GenericSubheading" => "gs", "GenericSubheading" => "gs",
"GenericTraceback" => "gt", "GenericTraceback" => "gt",
"GenericUnderline" => "gu", "GenericUnderline" => "gu",
"Highlight" => "hl",
"Keyword" => "k", "Keyword" => "k",
"KeywordConstant" => "kc", "KeywordConstant" => "kc",
"KeywordDeclaration" => "kd", "KeywordDeclaration" => "kd",

View File

@@ -1,5 +1,4 @@
require "./actions" require "./actions"
require "./constants"
require "./formatter" require "./formatter"
require "./rules" require "./rules"
require "./styles" require "./styles"
@@ -10,12 +9,15 @@ module Tartrazine
# This is the base class for all formatters. # This is the base class for all formatters.
abstract class Formatter abstract class Formatter
property name : String = "" property name : String = ""
property theme : Theme = Tartrazine.theme("default-dark")
def format(text : String, lexer : Lexer, theme : Theme) : String # Format the text using the given lexer.
def format(text : String, lexer : Lexer) : String
raise Exception.new("Not implemented") raise Exception.new("Not implemented")
end end
def get_style_defs(theme : Theme) : String # Return the styles, if the formatter supports it.
def style_defs : String
raise Exception.new("Not implemented") raise Exception.new("Not implemented")
end end
end end

View File

@@ -2,16 +2,25 @@ require "../formatter"
module Tartrazine module Tartrazine
class Ansi < Formatter class Ansi < Formatter
def format(text : String, lexer : Lexer, theme : Theme) : String property? line_numbers : Bool = false
def initialize(@theme : Theme = Tartrazine.theme("default-dark"), @line_numbers : Bool = false)
end
def format(text : String, lexer : Lexer) : String
output = String.build do |outp| output = String.build do |outp|
lexer.tokenize(text).each do |token| lexer.group_tokens_in_lines(lexer.tokenize(text)).each_with_index do |line, i|
outp << self.colorize(token[:value], token[:type], theme) label = line_numbers? ? "#{i + 1}".rjust(4).ljust(5) : ""
outp << label
line.each do |token|
outp << colorize(token[:value], token[:type])
end
end end
end end
output output
end end
def colorize(text : String, token : String, theme : Theme) : String def colorize(text : String, token : String) : String
style = theme.styles.fetch(token, nil) style = theme.styles.fetch(token, nil)
return text if style.nil? return text if style.nil?
if theme.styles.has_key?(token) if theme.styles.has_key?(token)

View File

@@ -1,3 +1,4 @@
require "../constants/token_abbrevs.cr"
require "../formatter" require "../formatter"
module Tartrazine module Tartrazine
@@ -14,20 +15,37 @@ module Tartrazine
property? standalone : Bool = false property? standalone : Bool = false
property? surrounding_pre : Bool = true property? surrounding_pre : Bool = true
property? wrap_long_lines : Bool = false property? wrap_long_lines : Bool = false
property weight_of_bold : Int32 = 600
def format(text : String, lexer : Lexer, theme : Theme) : String property theme : Theme
text = format_text(text, lexer, 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
text = format_text(text, lexer)
if standalone? if standalone?
text = wrap_standalone(text, theme) text = wrap_standalone(text)
end end
text text
end end
# Wrap text into a full HTML document, including the CSS for the theme # Wrap text into a full HTML document, including the CSS for the theme
def wrap_standalone(text, theme) : String def wrap_standalone(text) : String
output = String.build do |outp| output = String.build do |outp|
outp << "<!DOCTYPE html><html><head><style>" outp << "<!DOCTYPE html><html><head><style>"
outp << get_style_defs(theme) outp << style_defs
outp << "</style></head><body>" outp << "</style></head><body>"
outp << text outp << text
outp << "</body></html>" outp << "</body></html>"
@@ -35,21 +53,21 @@ module Tartrazine
output output
end end
def format_text(text : String, lexer : Lexer, theme : Theme) : String def format_text(text : String, lexer : Lexer) : String
lines = group_tokens_in_lines(lexer.tokenize(text)) lines = lexer.group_tokens_in_lines(lexer.tokenize(text))
output = String.build do |outp| output = String.build do |outp|
if surrounding_pre? if surrounding_pre?
pre_style = wrap_long_lines? ? "style=\"white-space: pre-wrap; word-break: break-word;\"" : "" pre_style = wrap_long_lines? ? "style=\"white-space: pre-wrap; word-break: break-word;\"" : ""
outp << "<pre class=\"#{get_css_class("Background", theme)}\" #{pre_style}>" outp << "<pre class=\"#{get_css_class("Background")}\" #{pre_style}>"
end end
"<code class=\"#{get_css_class("Background", theme)}\">" outp << "<code class=\"#{get_css_class("Background")}\">"
lines.each_with_index(offset: line_number_start - 1) do |line, i| lines.each_with_index(offset: line_number_start - 1) do |line, i|
line_label = line_numbers? ? "#{i + 1}".rjust(4).ljust(5) : "" line_label = line_numbers? ? "#{i + 1}".rjust(4).ljust(5) : ""
line_class = highlighted?(i + 1) ? "class=\"#{get_css_class("LineHighlight", theme)}\"" : "" line_class = highlighted?(i + 1) ? "class=\"#{get_css_class("LineHighlight")}\"" : ""
line_id = linkable_line_numbers? ? "id=\"#{line_number_id_prefix}#{i + 1}\"" : "" line_id = linkable_line_numbers? ? "id=\"#{line_number_id_prefix}#{i + 1}\"" : ""
outp << "<span #{line_id} #{line_class} style=\"user-select: none;\">#{line_label} </span>" outp << "<span #{line_id} #{line_class} style=\"user-select: none;\">#{line_label} </span>"
line.each do |token| line.each do |token|
fragment = "<span class=\"#{get_css_class(token[:type], theme)}\">#{token[:value]}</span>" fragment = "<span class=\"#{get_css_class(token[:type])}\">#{token[:value]}</span>"
outp << fragment outp << fragment
end end
end end
@@ -59,10 +77,10 @@ module Tartrazine
end end
# ameba:disable Metrics/CyclomaticComplexity # ameba:disable Metrics/CyclomaticComplexity
def get_style_defs(theme : Theme) : String def style_defs : String
output = String.build do |outp| output = String.build do |outp|
theme.styles.each do |token, style| theme.styles.each do |token, style|
outp << ".#{get_css_class(token, theme)} {" outp << ".#{get_css_class(token)} {"
# These are set or nil # These are set or nil
outp << "color: ##{style.color.try &.hex};" if style.color outp << "color: ##{style.color.try &.hex};" if style.color
outp << "background-color: ##{style.background.try &.hex};" if style.background outp << "background-color: ##{style.background.try &.hex};" if style.background
@@ -71,7 +89,7 @@ module Tartrazine
# These are true/false/nil # These are true/false/nil
outp << "border: none;" if style.border == false outp << "border: none;" if style.border == false
outp << "font-weight: bold;" if style.bold outp << "font-weight: bold;" if style.bold
outp << "font-weight: 400;" if style.bold == false outp << "font-weight: #{@weight_of_bold};" if style.bold == false
outp << "font-style: italic;" if style.italic outp << "font-style: italic;" if style.italic
outp << "font-style: normal;" if style.italic == false outp << "font-style: normal;" if style.italic == false
outp << "text-decoration: underline;" if style.underline outp << "text-decoration: underline;" if style.underline
@@ -85,7 +103,7 @@ module Tartrazine
end end
# Given a token type, return the CSS class to use. # Given a token type, return the CSS class to use.
def get_css_class(token, theme) def get_css_class(token : String) : String
return class_prefix + Abbreviations[token] if theme.styles.has_key?(token) return class_prefix + Abbreviations[token] if theme.styles.has_key?(token)
# Themes don't contain information for each specific # Themes don't contain information for each specific
@@ -97,31 +115,9 @@ module Tartrazine
}] }]
end end
# Is this line in the highlighted ranges?
def highlighted?(line : Int) : Bool def highlighted?(line : Int) : Bool
highlight_lines.any?(&.includes?(line)) highlight_lines.any?(&.includes?(line))
end end
def group_tokens_in_lines(tokens : Array(Token)) : Array(Array(Token))
split_tokens = [] of Token
tokens.each do |token|
if token[:value].includes?("\n")
values = token[:value].split("\n")
values.each_with_index do |value, index|
value += "\n" if index < values.size - 1
split_tokens << {type: token[:type], value: value}
end
else
split_tokens << token
end
end
lines = [Array(Token).new]
split_tokens.each do |token|
lines.last << token
if token[:value].includes?("\n")
lines << Array(Token).new
end
end
lines
end
end end
end end

View File

@@ -1,3 +1,5 @@
require "./constants/lexers"
module Tartrazine module Tartrazine
class LexerFiles class LexerFiles
extend BakedFileSystem extend BakedFileSystem
@@ -5,6 +7,36 @@ module Tartrazine
bake_folder "../lexers", __DIR__ bake_folder "../lexers", __DIR__
end end
# Get the lexer object for a language name
# FIXME: support mimetypes
def self.lexer(name : String? = nil, filename : String? = nil) : Lexer
if name.nil? && filename.nil?
lexer_file_name = LEXERS_BY_NAME["plaintext"]
elsif name && name != "autodetect"
lexer_file_name = LEXERS_BY_NAME[name.downcase]
else
# Guess by filename
candidates = Set(String).new
LEXERS_BY_FILENAME.each do |k, v|
candidates += v.to_set if File.match?(k, File.basename(filename.to_s))
end
case candidates.size
when 0
lexer_file_name = LEXERS_BY_NAME["plaintext"]
when 1
lexer_file_name = candidates.first
else
raise Exception.new("Multiple lexers match the filename: #{candidates.to_a.join(", ")}")
end
end
Lexer.from_xml(LexerFiles.get("/#{lexer_file_name}.xml").gets_to_end)
end
# Return a list of all lexers
def self.lexers : Array(String)
LEXERS_BY_NAME.keys.sort!
end
# This implements a lexer for Pygments RegexLexers as expressed # This implements a lexer for Pygments RegexLexers as expressed
# in Chroma's XML serialization. # in Chroma's XML serialization.
# #
@@ -92,6 +124,30 @@ module Tartrazine
result result
end end
# Group tokens into lines, splitting them when a newline is found
def group_tokens_in_lines(tokens : Array(Token)) : Array(Array(Token))
split_tokens = [] of Token
tokens.each do |token|
if token[:value].includes?("\n")
values = token[:value].split("\n")
values.each_with_index do |value, index|
value += "\n" if index < values.size - 1
split_tokens << {type: token[:type], value: value}
end
else
split_tokens << token
end
end
lines = [Array(Token).new]
split_tokens.each do |token|
lines.last << token
if token[:value].includes?("\n")
lines << Array(Token).new
end
end
lines
end
# ameba:disable Metrics/CyclomaticComplexity # ameba:disable Metrics/CyclomaticComplexity
def self.from_xml(xml : String) : Lexer def self.from_xml(xml : String) : Lexer
l = Lexer.new l = Lexer.new
@@ -173,8 +229,4 @@ module Tartrazine
# A token, the output of the tokenizer # A token, the output of the tokenizer
alias Token = NamedTuple(type: String, value: String) alias Token = NamedTuple(type: String, value: String)
def self.lexer(name : String) : Lexer
Lexer.from_xml(LexerFiles.get("/#{name}.xml").gets_to_end)
end
end end

View File

@@ -1,35 +1,97 @@
require "docopt"
require "./**" require "./**"
HELP = <<-HELP HELP = <<-HELP
tartrazine: a syntax highlighting tool tartrazine: a syntax highlighting tool
Usage: Usage:
tartrazine (-h, --help)
tartrazine FILE -f html [-t theme][--standalone][--line-numbers] tartrazine FILE -f html [-t theme][--standalone][--line-numbers]
[-l lexer] [-o output][--css] [-l lexer][-o output]
tartrazine FILE -f terminal [-t theme][-l lexer][-o output] tartrazine -f html -t theme --css
tartrazine FILE -f terminal [-t theme][-l lexer][--line-numbers]
[-o output]
tartrazine FILE -f json [-o output] tartrazine FILE -f json [-o output]
tartrazine --list-themes tartrazine --list-themes
tartrazine --list-lexers tartrazine --list-lexers
tartrazine --list-formatters
tartrazine --version
-f <formatter> Format to use (html, terminal, json) Options:
-t <theme> Theme to use (see --list-themes) -f <formatter> Format to use (html, terminal, json)
-l <lexer> Lexer (language) to use (see --list-lexers) -t <theme> Theme to use, see --list-themes [default: default-dark]
-o <output> Output file (default: stdout) -l <lexer> Lexer (language) to use, see --list-lexers [default: autodetect]
--standalone Generate a standalone HTML file -o <output> Output file. Default is stdout.
--css Generate a CSS file for the theme --standalone Generate a standalone HTML file, which includes
--line-numbers Include line numbers in the output all style information. If not given, it will generate just
a HTML fragment ready to include in your own page.
--css Generate a CSS file for the theme called <theme>.css
--line-numbers Include line numbers in the output
-h, --help Show this screen
-v, --version Show version number
HELP HELP
lexer = Tartrazine.lexer("crystal") options = Docopt.docopt(HELP, ARGV)
theme = Tartrazine.theme(ARGV[1])
# formatter = Tartrazine::Json.new # Handle version manually
formatter = Tartrazine::Html.new if options["--version"]
formatter.standalone = true puts "tartrazine #{Tartrazine::VERSION}"
formatter.class_prefix = "hl-" exit 0
formatter.line_number_id_prefix = "ln-" end
formatter.line_numbers = true
formatter.highlight_lines = [3..7, 20..30] if options["--list-themes"]
formatter.linkable_line_numbers = false puts Tartrazine.themes.join("\n")
formatter.wrap_long_lines = false exit 0
puts formatter.format(File.read(ARGV[0]), lexer, theme) end
if options["--list-lexers"]
puts Tartrazine.lexers.join("\n")
exit 0
end
if options["--list-formatters"]
puts "html\njson\nterminal"
exit 0
end
theme = Tartrazine.theme(options["-t"].as(String))
if options["-f"]
formatter = options["-f"].as(String)
case formatter
when "html"
formatter = Tartrazine::Html.new
formatter.standalone = options["--standalone"] != nil
formatter.line_numbers = options["--line-numbers"] != nil
formatter.theme = theme
when "terminal"
formatter = Tartrazine::Ansi.new
formatter.line_numbers = options["--line-numbers"] != nil
formatter.theme = theme
when "json"
formatter = Tartrazine::Json.new
else
puts "Invalid formatter: #{formatter}"
exit 1
end
if formatter.is_a?(Tartrazine::Html) && options["--css"]
File.open("#{options["-t"].as(String)}.css", "w") do |outf|
outf.puts formatter.style_defs
end
exit 0
end
lexer = Tartrazine.lexer(name: options["-l"].as(String), filename: options["FILE"].as(String))
input = File.open(options["FILE"].as(String)).gets_to_end
output = formatter.format(input, lexer)
if options["-o"].nil?
puts output
else
File.open(options["-o"].as(String), "w") do |outf|
outf.puts output
end
end
end

View File

@@ -1,5 +1,4 @@
require "./actions" require "./actions"
require "./constants"
require "./formatter" require "./formatter"
require "./rules" require "./rules"
require "./styles" require "./styles"
@@ -12,7 +11,7 @@ module Tartrazine
# This rule matches via a regex pattern # This rule matches via a regex pattern
class Rule class Rule
property pattern : Regex = Re2.new "" property pattern : Regex = Regex.new ""
property actions : Array(Action) = [] of Action property actions : Array(Action) = [] of Action
property xml : String = "foo" property xml : String = "foo"
@@ -34,12 +33,15 @@ module Tartrazine
def initialize(node : XML::Node, multiline, dotall, ignorecase) def initialize(node : XML::Node, multiline, dotall, ignorecase)
@xml = node.to_s @xml = node.to_s
@pattern = Re2.new( pattern = node["pattern"]
node["pattern"], flags = Regex::Options::ANCHORED
multiline, # MULTILINE implies DOTALL which we don't want, so we
dotall, # use in-pattern flag (?m) instead
ignorecase, # flags |= Regex::Options::MULTILINE if multiline
anchored: true) pattern = "(?m)" + pattern if multiline
flags |= Regex::Options::DOTALL if dotall
flags |= Regex::Options::IGNORE_CASE if ignorecase
@pattern = Regex.new(pattern, flags)
add_actions(node) add_actions(node)
end end
@@ -91,25 +93,4 @@ module Tartrazine
add_actions(node) add_actions(node)
end end
end end
# This is a hack to workaround that Crystal seems to disallow
# having regexes multiline but not dot_all
class Re2 < Regex
@source = "fa"
@options = Regex::Options::None
@jit = true
def initialize(pattern : String, multiline = false, dotall = false, ignorecase = false, anchored = false)
flags = LibPCRE2::UTF | LibPCRE2::DUPNAMES |
LibPCRE2::UCP
flags |= LibPCRE2::MULTILINE if multiline
flags |= LibPCRE2::DOTALL if dotall
flags |= LibPCRE2::CASELESS if ignorecase
flags |= LibPCRE2::ANCHORED if anchored
flags |= LibPCRE2::NO_UTF_CHECK
@re = Regex::PCRE2.compile(pattern, flags) do |error_message|
raise Exception.new(error_message)
end
end
end
end end

View File

@@ -1,5 +1,4 @@
require "./actions" require "./actions"
require "./constants"
require "./formatter" require "./formatter"
require "./rules" require "./rules"
require "./styles" require "./styles"
@@ -10,6 +9,11 @@ require "xml"
module Tartrazine module Tartrazine
alias Color = Sixteen::Color alias Color = Sixteen::Color
class ThemeFiles
extend BakedFileSystem
bake_folder "../styles", __DIR__
end
def self.theme(name : String) : Theme def self.theme(name : String) : Theme
begin begin
return Theme.from_base16(name) return Theme.from_base16(name)
@@ -23,9 +27,16 @@ module Tartrazine
end end
end end
class ThemeFiles # Return a list of all themes
extend BakedFileSystem def self.themes
bake_folder "../styles", __DIR__ themes = Set(String).new
ThemeFiles.files.each do |file|
themes << file.path.split("/").last.split(".").first
end
Sixteen::DataFiles.files.each do |file|
themes << file.path.split("/").last.split(".").first
end
themes.to_a.sort!
end end
class Style class Style

View File

@@ -1,5 +1,4 @@
require "./actions" require "./actions"
require "./constants"
require "./formatter" require "./formatter"
require "./rules" require "./rules"
require "./styles" require "./styles"
@@ -12,7 +11,7 @@ require "xml"
module Tartrazine module Tartrazine
extend self extend self
VERSION = "0.1.1" VERSION = {{ `shards version #{__DIR__}`.chomp.stringify }}
Log = ::Log.for("tartrazine") Log = ::Log.for("tartrazine")
end end