├── bin └── .keep ├── .gitignore ├── etnoir ├── .gitignore ├── src │ ├── etnoir │ │ ├── error.cr │ │ ├── themes.cr │ │ ├── exit.cr │ │ ├── lexers.cr │ │ ├── formatters.cr │ │ ├── command.cr │ │ └── commands │ │ │ ├── style.cr │ │ │ └── highlight.cr │ └── etnoir.cr ├── shard.lock └── shard.yml ├── src ├── noir │ ├── version.cr │ ├── formatters │ │ ├── formatters.cr │ │ ├── html.cr │ │ ├── html_inline.cr │ │ └── terminal_rgb.cr │ ├── lexers.cr │ ├── themes.cr │ ├── scanner.cr │ ├── lexers │ │ ├── json.cr │ │ ├── elm.cr │ │ ├── html.cr │ │ ├── python.cr │ │ ├── javascript.cr │ │ ├── crystal.cr │ │ ├── yaml.cr │ │ ├── css.cr │ │ └── ruby.cr │ ├── formatter.cr │ ├── themes │ │ ├── solarized.cr │ │ └── monokai.cr │ ├── theme.cr │ ├── token.cr │ └── lexer.cr └── noir.cr ├── spec ├── lexers │ ├── html │ │ ├── fixtures │ │ │ ├── comments.in │ │ │ ├── comments.out │ │ │ ├── script_style.in │ │ │ ├── syntax.in │ │ │ ├── script_style.out │ │ │ └── syntax.out │ │ └── html_spec.cr │ ├── javascript │ │ ├── fixtures │ │ │ ├── comment.in │ │ │ ├── literals.in │ │ │ ├── comment.out │ │ │ ├── syntax.in │ │ │ ├── literals.out │ │ │ └── syntax.out │ │ └── javascript_spec.cr │ ├── ruby │ │ ├── fixtures │ │ │ ├── comment.in │ │ │ ├── comment.out │ │ │ ├── literals.in │ │ │ ├── syntax.in │ │ │ ├── literals.out │ │ │ └── syntax.out │ │ └── ruby_spec.cr │ ├── json │ │ ├── fixtures │ │ │ ├── syntax.in │ │ │ └── syntax.out │ │ └── json_spec.cr │ ├── crystal │ │ ├── fixtures │ │ │ ├── keyword_like_method.in │ │ │ ├── literals.in │ │ │ ├── keyword_like_method.out │ │ │ ├── syntax.in │ │ │ ├── literals.out │ │ │ └── syntax.out │ │ └── crystal_spec.cr │ ├── css │ │ ├── fixtures │ │ │ ├── comments.in │ │ │ ├── at_rules.in │ │ │ ├── comments.out │ │ │ ├── selectors.in │ │ │ ├── at_rules.out │ │ │ └── selectors.out │ │ └── css_spec.cr │ ├── python │ │ ├── fixtures │ │ │ ├── fizzbuzz.in │ │ │ └── fizzbuzz.out │ │ └── python_spec.cr │ ├── elm │ │ ├── elm_spec.cr │ │ └── fixtures │ │ │ ├── syntax.in │ │ │ └── syntax.out │ └── yaml │ │ ├── yaml_spec.cr │ │ └── fixtures │ │ └── syntax.in └── spec_helper.cr ├── .editorconfig ├── shard.yml ├── LICENSE.🍣.md ├── Makefile ├── README.md └── LICENSE.MIT.md /bin/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /docs/ 2 | /bin/etnoir 3 | -------------------------------------------------------------------------------- /etnoir/.gitignore: -------------------------------------------------------------------------------- 1 | /lib/ 2 | /bin/etnoir* 3 | -------------------------------------------------------------------------------- /src/noir/version.cr: -------------------------------------------------------------------------------- 1 | module Noir 2 | VERSION = "0.1.0" 3 | end 4 | -------------------------------------------------------------------------------- /etnoir/src/etnoir/error.cr: -------------------------------------------------------------------------------- 1 | class Etnoir::Error < Exception 2 | end 3 | -------------------------------------------------------------------------------- /spec/lexers/html/fixtures/comments.in: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /spec/lexers/javascript/fixtures/comment.in: -------------------------------------------------------------------------------- 1 | // hello 2 | 3 | /* 4 | world 5 | */ 6 | -------------------------------------------------------------------------------- /spec/lexers/ruby/fixtures/comment.in: -------------------------------------------------------------------------------- 1 | # foo 2 | 3 | =begin 4 | hello world 5 | =end 6 | -------------------------------------------------------------------------------- /etnoir/shard.lock: -------------------------------------------------------------------------------- 1 | version: 2.0 2 | shards: 3 | noir: 4 | path: .. 5 | version: 0.1.0 6 | 7 | -------------------------------------------------------------------------------- /spec/lexers/json/fixtures/syntax.in: -------------------------------------------------------------------------------- 1 | { 2 | "key" : [6.02e-23, "\u3042いうえお かきくけこ\n",true,false,null] 3 | } 4 | -------------------------------------------------------------------------------- /src/noir/formatters/formatters.cr: -------------------------------------------------------------------------------- 1 | require "./html" 2 | require "./html_inline" 3 | require "./terminal_rgb" 4 | -------------------------------------------------------------------------------- /spec/lexers/html/fixtures/comments.out: -------------------------------------------------------------------------------- 1 | [#, ""] 2 | [#, "\n"] 3 | -------------------------------------------------------------------------------- /spec/lexers/html/fixtures/script_style.in: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /spec/lexers/crystal/fixtures/keyword_like_method.in: -------------------------------------------------------------------------------- 1 | "foo".is_a?(String) 2 | "foo".nil? 3 | "foo".as(String) 4 | "foo".as?(String) 5 | -------------------------------------------------------------------------------- /etnoir/src/etnoir/themes.cr: -------------------------------------------------------------------------------- 1 | require "noir/themes/monokai" 2 | require "noir/themes/solarized" 3 | 4 | module Etnoir 5 | DEFAULT_THEME = "monokai" 6 | end 7 | -------------------------------------------------------------------------------- /spec/lexers/javascript/fixtures/literals.in: -------------------------------------------------------------------------------- 1 | 42 2 | 3.14 3 | "foo" 4 | 'bar' 5 | `foo ${42} bar` 6 | ["foo", "bar"] 7 | {"foo": "bar"} 8 | {foo: "bar"} 9 | /foo|bar/ 10 | -------------------------------------------------------------------------------- /etnoir/src/etnoir/exit.cr: -------------------------------------------------------------------------------- 1 | class Etnoir::Exit < Exception 2 | getter status 3 | 4 | def initialize(@status = 0) 5 | super("BUG: etnoir exit status: #{status}") 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/lexers/javascript/fixtures/comment.out: -------------------------------------------------------------------------------- 1 | [#, "// hello"] 2 | [#, "\n\n"] 3 | [#, "/*\n world\n*/"] 4 | [#, "\n"] 5 | -------------------------------------------------------------------------------- /spec/lexers/ruby/fixtures/comment.out: -------------------------------------------------------------------------------- 1 | [#, "# foo"] 2 | [#, "\n\n"] 3 | [#, "=begin\nhello world\n=end"] 4 | [#, "\n"] 5 | -------------------------------------------------------------------------------- /spec/lexers/css/fixtures/comments.in: -------------------------------------------------------------------------------- 1 | /* This is a comment */ 2 | 3 | body { 4 | font-size: 1em; /* this also is a comment */ 5 | } 6 | 7 | /* 8 | this is a comment as well 9 | */ 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.cr] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 2 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /shard.yml: -------------------------------------------------------------------------------- 1 | name: noir 2 | version: 0.1.0 3 | authors: 4 | - TSUYUSATO Kitsune 5 | description: | 6 | Syntax Highlight Library for Crystal 7 | 8 | license: MIT 9 | 10 | crystal: 1.0.0 11 | -------------------------------------------------------------------------------- /etnoir/src/etnoir/lexers.cr: -------------------------------------------------------------------------------- 1 | require "noir/lexers/crystal" 2 | require "noir/lexers/css" 3 | require "noir/lexers/html" 4 | require "noir/lexers/javascript" 5 | require "noir/lexers/json" 6 | require "noir/lexers/python" 7 | require "noir/lexers/ruby" 8 | -------------------------------------------------------------------------------- /etnoir/shard.yml: -------------------------------------------------------------------------------- 1 | name: etnoir 2 | version: 0.1.0 3 | authors: 4 | - TSUYUSATO Kitsune 5 | description: | 6 | NOIR command-line tool 7 | 8 | dependencies: 9 | noir: 10 | path: .. 11 | 12 | targets: 13 | etnoir: 14 | main: src/etnoir.cr 15 | 16 | license: MIT 17 | -------------------------------------------------------------------------------- /src/noir/lexers.cr: -------------------------------------------------------------------------------- 1 | require "./lexer" 2 | 3 | module Noir::Lexers 4 | LEXERS = {} of String => Lexer.class 5 | 6 | def self.register(name : String, klass) 7 | LEXERS[name] = klass 8 | end 9 | 10 | def self.find(name : String) : Lexer? 11 | LEXERS[name]?.try &.new 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/lexers/python/fixtures/fizzbuzz.in: -------------------------------------------------------------------------------- 1 | # 2 | # fizzbuzz.py 3 | # 4 | 5 | from collections import defaultdict 6 | 7 | fizz, buzz = defaultdict(lambda: ''), defaultdict(lambda: '') 8 | fizz[0], buzz[0] = 'Fizz', 'Buzz' 9 | 10 | for i in range(1, 101): 11 | print(fizz[i % 3] + buzz[i % 5] or str(i)) 12 | -------------------------------------------------------------------------------- /spec/lexers/html/fixtures/syntax.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | <"A 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /spec/lexers/css/css_spec.cr: -------------------------------------------------------------------------------- 1 | require "../../spec_helper" 2 | 3 | require "../../../src/noir/lexers/css" 4 | 5 | describe Noir::Lexers::CSS do 6 | it "can find canonical name 'css'" do 7 | Noir.find_lexer("css").should be_a(Noir::Lexers::CSS) 8 | end 9 | 10 | it_lexes_fixtures "css", Noir::Lexers::CSS 11 | end 12 | -------------------------------------------------------------------------------- /spec/lexers/elm/elm_spec.cr: -------------------------------------------------------------------------------- 1 | require "../../spec_helper" 2 | 3 | require "../../../src/noir/lexers/elm" 4 | 5 | describe Noir::Lexers::Elm do 6 | it "can find canonical name 'elm'" do 7 | Noir.find_lexer("elm").should be_a(Noir::Lexers::Elm) 8 | end 9 | 10 | it_lexes_fixtures "elm", Noir::Lexers::Elm 11 | end 12 | -------------------------------------------------------------------------------- /spec/lexers/ruby/fixtures/literals.in: -------------------------------------------------------------------------------- 1 | 42 2 | 3.14 3 | ?x 4 | "foo" 5 | 'foo' 6 | `foo` 7 | "foo #{bar} baz" 8 | 'foo #{bar} baz' 9 | %(foo bar baz) 10 | %q(foo bar baz) 11 | %w(foo bar baz) 12 | /foo bar/ 13 | /foo bar/m 14 | %r( 15 | foo 16 | bar 17 | )x 18 | [] 19 | [1, 2, 3] 20 | {foo: "bar"} 21 | {"foo" => "bar"} 22 | -------------------------------------------------------------------------------- /spec/lexers/html/html_spec.cr: -------------------------------------------------------------------------------- 1 | require "../../spec_helper" 2 | 3 | require "../../../src/noir/lexers/html" 4 | 5 | describe Noir::Lexers::HTML do 6 | it "can find canonical name 'html'" do 7 | Noir.find_lexer("html").should be_a(Noir::Lexers::HTML) 8 | end 9 | 10 | it_lexes_fixtures "html", Noir::Lexers::HTML 11 | end 12 | -------------------------------------------------------------------------------- /spec/lexers/json/json_spec.cr: -------------------------------------------------------------------------------- 1 | require "../../spec_helper" 2 | 3 | require "../../../src/noir/lexers/json" 4 | 5 | describe Noir::Lexers::JSON do 6 | it "can find canonical name 'json'" do 7 | Noir.find_lexer("json").should be_a(Noir::Lexers::JSON) 8 | end 9 | 10 | it_lexes_fixtures "json", Noir::Lexers::JSON 11 | end 12 | -------------------------------------------------------------------------------- /spec/lexers/yaml/yaml_spec.cr: -------------------------------------------------------------------------------- 1 | require "../../spec_helper" 2 | 3 | require "../../../src/noir/lexers/yaml" 4 | 5 | describe Noir::Lexers::YAML do 6 | it "can find canonical name 'yaml'" do 7 | Noir.find_lexer("yaml").should be_a(Noir::Lexers::YAML) 8 | end 9 | 10 | it_lexes_fixtures "yaml", Noir::Lexers::YAML 11 | end 12 | -------------------------------------------------------------------------------- /spec/lexers/ruby/fixtures/syntax.in: -------------------------------------------------------------------------------- 1 | def foo(x, y, &z) 2 | begin 3 | yield y if x 4 | rescue => e 5 | puts :rescue 6 | ensure 7 | puts :ensure 8 | end 9 | end 10 | 11 | class Foo 12 | def self.foo 13 | end 14 | end 15 | 16 | module Bar 17 | extend self 18 | end 19 | 20 | class Baz < Foo 21 | include Bar 22 | end 23 | -------------------------------------------------------------------------------- /src/noir/themes.cr: -------------------------------------------------------------------------------- 1 | require "./themes" 2 | 3 | module Noir::Themes 4 | THEMES = {} of String => Theme.class 5 | 6 | def self.register(name : String, theme) : Nil 7 | THEMES[name] = theme 8 | end 9 | 10 | def self.find(name : String, scope = ".highlight") : Theme? 11 | THEMES[name]?.try &.new(scope: scope) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /etnoir/src/etnoir.cr: -------------------------------------------------------------------------------- 1 | require "colorize" 2 | 3 | module Etnoir 4 | end 5 | 6 | require "./etnoir/command" 7 | require "./etnoir/error" 8 | require "./etnoir/exit" 9 | 10 | begin 11 | Etnoir::Command.new(STDOUT).run 12 | exit 0 13 | rescue e : Etnoir::Exit 14 | exit e.status 15 | rescue e : Etnoir::Error 16 | STDERR.print "ERROR: ".colorize(:red) 17 | STDERR.puts e.message.colorize.bold 18 | exit 1 19 | end 20 | -------------------------------------------------------------------------------- /spec/lexers/crystal/fixtures/literals.in: -------------------------------------------------------------------------------- 1 | 42 2 | 3.14 3 | 1_i8 4 | 2_i16 5 | 3_i32 6 | 4_i64 7 | 5_i128 8 | 6u8 9 | 7u16 10 | 8u32 11 | 9u64 12 | 10u128 13 | 11f32 14 | 12f64 15 | 'x' 16 | '\n' 17 | :foo 18 | :"foo bar" 19 | :+ 20 | "foo bar" 21 | "foo #{ {:bar} } baz" 22 | %w(foo (bar) baz) 23 | <<-FOO 24 | foo bar 25 | #{foo bar} 26 | FOO 27 | <<-'FOO' 28 | foo bar 29 | #{foo bar} 30 | FOO 31 | %r{foo} 32 | %r[foo]mix 33 | -------------------------------------------------------------------------------- /spec/lexers/ruby/ruby_spec.cr: -------------------------------------------------------------------------------- 1 | require "../../spec_helper" 2 | 3 | require "../../../src/noir/lexers/ruby" 4 | 5 | describe Noir::Lexers::Ruby do 6 | it "can find canonical name 'ruby'" do 7 | Noir.find_lexer("ruby").should be_a(Noir::Lexers::Ruby) 8 | end 9 | 10 | it "can find alias name 'rb'" do 11 | Noir.find_lexer("rb").should be_a(Noir::Lexers::Ruby) 12 | end 13 | 14 | it_lexes_fixtures "ruby", Noir::Lexers::Ruby 15 | end 16 | -------------------------------------------------------------------------------- /spec/lexers/python/python_spec.cr: -------------------------------------------------------------------------------- 1 | require "../../spec_helper" 2 | 3 | require "../../../src/noir/lexers/python" 4 | 5 | describe Noir::Lexers::Python do 6 | it "can find canonical name 'python'" do 7 | Noir.find_lexer("python").should be_a(Noir::Lexers::Python) 8 | end 9 | 10 | it "can find alias name 'py'" do 11 | Noir.find_lexer("py").should be_a(Noir::Lexers::Python) 12 | end 13 | 14 | it_lexes_fixtures "python", Noir::Lexers::Python 15 | end 16 | -------------------------------------------------------------------------------- /spec/lexers/crystal/crystal_spec.cr: -------------------------------------------------------------------------------- 1 | require "../../spec_helper" 2 | 3 | require "../../../src/noir/lexers/crystal" 4 | 5 | describe Noir::Lexers::Crystal do 6 | it "can find canonical name 'crystal'" do 7 | Noir.find_lexer("crystal").should be_a(Noir::Lexers::Crystal) 8 | end 9 | 10 | it "can find alias name 'cr'" do 11 | Noir.find_lexer("cr").should be_a(Noir::Lexers::Crystal) 12 | end 13 | 14 | it_lexes_fixtures "crystal", Noir::Lexers::Crystal 15 | end 16 | -------------------------------------------------------------------------------- /LICENSE.🍣.md: -------------------------------------------------------------------------------- 1 | # "THE SUSHI-WARE LICENSE" 2 | 3 | wrote this file. 4 | 5 | As long as you retain this notice you can do whatever you want 6 | with this stuff. If we meet some day, and you think this stuff 7 | is worth it, you can buy me a **sushi 🍣** in return. 8 | 9 | (This license is based on ["THE BEER-WARE LICENSE" (Revision 42)]. 10 | Thanks a lot, Poul-Henning Kamp ;) 11 | 12 | ["THE BEER-WARE LICENSE" (Revision 42)]: https://people.freebsd.org/~phk/ 13 | -------------------------------------------------------------------------------- /spec/lexers/javascript/fixtures/syntax.in: -------------------------------------------------------------------------------- 1 | function foo() { 2 | return 42; 3 | } 4 | 5 | if (x === y) { 6 | console.log(x); 7 | } else { 8 | console.log(y); 9 | } 10 | 11 | var bar; 12 | 13 | for (let i = 0; i < 100; i++) { 14 | console.log(bar); 15 | } 16 | 17 | class Foo { 18 | constructor(x, y) { 19 | this.x = x; 20 | this.y = y; 21 | } 22 | } 23 | 24 | class Bar extends Foo { 25 | get z() { 26 | return this.x + this.y; 27 | } 28 | } 29 | 30 | const bar = new Bar(1, 2); 31 | -------------------------------------------------------------------------------- /spec/lexers/javascript/javascript_spec.cr: -------------------------------------------------------------------------------- 1 | require "../../spec_helper" 2 | 3 | require "../../../src/noir/lexers/javascript" 4 | 5 | describe Noir::Lexers::JavaScript do 6 | it "can find canonical name 'javascript'" do 7 | Noir.find_lexer("javascript").should be_a(Noir::Lexers::JavaScript) 8 | end 9 | 10 | it "can find alias name 'js'" do 11 | Noir.find_lexer("js").should be_a(Noir::Lexers::JavaScript) 12 | end 13 | 14 | it_lexes_fixtures "javascript", Noir::Lexers::JavaScript 15 | end 16 | -------------------------------------------------------------------------------- /src/noir/formatters/html.cr: -------------------------------------------------------------------------------- 1 | require "html" 2 | 3 | require "../formatter" 4 | require "../theme" 5 | 6 | class Noir::Formatters::HTML < Noir::Formatter 7 | def initialize(@out : IO) 8 | end 9 | 10 | def format(token, value) : Nil 11 | if token && token != Tokens::Text 12 | klass = token.short_name 13 | @out << %() 14 | ::HTML.escape value, @out 15 | @out << %() 16 | else 17 | ::HTML.escape value, @out 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /src/noir/formatters/html_inline.cr: -------------------------------------------------------------------------------- 1 | require "html" 2 | 3 | require "../formatter" 4 | require "../theme" 5 | 6 | class Noir::Formatters::HTMLInline < Noir::Formatter 7 | def initialize(@theme : Theme, @out : IO) 8 | end 9 | 10 | def format(token, value) : Nil 11 | if token && token != Tokens::Text 12 | style = @theme.style_for(token) 13 | @out << %() 14 | ::HTML.escape value, @out 15 | @out << %() 16 | else 17 | ::HTML.escape value, @out 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /src/noir/scanner.cr: -------------------------------------------------------------------------------- 1 | # :nodoc: 2 | class Noir::Scanner 3 | getter reader : Char::Reader 4 | 5 | def initialize(string : String) 6 | @reader = Char::Reader.new string 7 | end 8 | 9 | def scan(re : Regex) : Regex::MatchData? 10 | if m = re.match_at_byte_index(reader.string, reader.pos, Regex::Options::ANCHORED) 11 | @reader.pos = m.byte_end 0 12 | m 13 | end 14 | end 15 | 16 | def has_next? 17 | reader.has_next? 18 | end 19 | 20 | def current_char 21 | reader.current_char 22 | end 23 | 24 | def next_char 25 | reader.next_char 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NOIR_SRC = $(shell find src -name '*.cr') 2 | ETNOIR_SRC = $(shell find etnoir/src -name '*.cr') 3 | 4 | etnoir: bin/etnoir 5 | 6 | bin/etnoir: etnoir/bin/etnoir 7 | ln -sf ../etnoir/bin/etnoir bin/etnoir 8 | 9 | etnoir/bin/etnoir: $(NOIR_SRC) $(ETNOIR_SRC) 10 | cd etnoir && shards update && shards build 11 | 12 | spec: noir_spec etnoir_spec 13 | 14 | noir_spec: 15 | crystal spec 16 | 17 | etnoir_spec: 18 | cd etnoir && crystal spec 19 | 20 | update_fixture: 21 | UPDATE_FIXTURE=1 crystal spec 22 | 23 | clean: 24 | rm -rf etnoir/bin/etnoir 25 | 26 | .PHONY: etnoir spec noir_spec etnoir_spec clean 27 | -------------------------------------------------------------------------------- /spec/lexers/html/fixtures/script_style.out: -------------------------------------------------------------------------------- 1 | [#, ""] 7 | [#, "\n"] 8 | [#, ""] 18 | [#, "\n"] 19 | -------------------------------------------------------------------------------- /spec/lexers/css/fixtures/at_rules.in: -------------------------------------------------------------------------------- 1 | @media screen { 2 | body { 3 | background: #ccc; 4 | } 5 | } 6 | 7 | @namespace "http://www.w3.org/1999/xhtml"; 8 | 9 | @import url("mystyle.css"); 10 | 11 | @charset "ISO-8859-1"; 12 | 13 | @font-face { font-family: "Example Font"; src: url("http://www.example.com/fonts/example"); } 14 | 15 | @media screen { body { font-size: 16px } } @media print { body { font-size: 12pt } } 16 | 17 | 18 | @page { body { margin: 1in 1.5in; } } 19 | 20 | @page linke-seite:left { body { margin:20mm; margin-right:25mm; } } 21 | 22 | @-moz-document url-prefix(http://pygments.org) { a {font-style: normal !important;} } 23 | -------------------------------------------------------------------------------- /spec/lexers/css/fixtures/comments.out: -------------------------------------------------------------------------------- 1 | [#, "/* This is a comment */"] 2 | [#, "\n\n"] 3 | [#, "body"] 4 | [#, " "] 5 | [#, "{"] 6 | [#, "\n "] 7 | [#, "font-size"] 8 | [#, ":"] 9 | [#, " "] 10 | [#, "1em"] 11 | [#, ";"] 12 | [#, " "] 13 | [#, "/* this also is a comment */"] 14 | [#, "\n"] 15 | [#, "}"] 16 | [#, "\n\n"] 17 | [#, "/*\n this is a comment as well\n*/"] 18 | [#, "\n"] 19 | -------------------------------------------------------------------------------- /src/noir.cr: -------------------------------------------------------------------------------- 1 | # `Noir` is a syntax highlight library for Crystal. 2 | module Noir 3 | end 4 | 5 | require "./noir/formatter" 6 | require "./noir/formatters" 7 | require "./noir/lexer" 8 | require "./noir/lexers" 9 | require "./noir/theme" 10 | require "./noir/themes" 11 | require "./noir/version" 12 | 13 | module Noir 14 | def self.find_lexer(name : String) : Lexer? 15 | Lexers.find(name) 16 | end 17 | 18 | def self.find_theme(name : String, scope = ".highlight") : Theme? 19 | Themes.find(name, scope: scope) 20 | end 21 | 22 | def self.highlight(code : String, lexer, formatter) 23 | lexer.lex_all code, formatter 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /src/noir/lexers/json.cr: -------------------------------------------------------------------------------- 1 | require "../lexer" 2 | 3 | class Noir::Lexers::JSON < Noir::Lexer 4 | tag "json" 5 | filenames %w(*.json) 6 | mimetypes %w(application/json application/vnd.api+json application/hal+json) 7 | 8 | state :root do 9 | rule /\s+/, Text::Whitespace 10 | rule /"/, Str::Double, :string 11 | rule /\b(true|false|null)\b/, Keyword::Constant 12 | rule /[{},:\[\]]/, Punctuation 13 | rule /-?(0|[1-9]\d*)(\.\d+)?(e[+-]?\d+)?/i, Num 14 | end 15 | 16 | state :string do 17 | rule /[^\\"]+/, Str::Double 18 | rule /\\u[0-9a-fA-F]{4}|\\[\"\\\/bfnrt]/, Str::Escape 19 | rule /"/, Str::Double, :pop! 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /src/noir/formatter.cr: -------------------------------------------------------------------------------- 1 | require "./token" 2 | 3 | abstract class Noir::Formatter 4 | @last_token : Token? 5 | @value : IO::Memory = IO::Memory.new 6 | 7 | def yield_token(token : Token, value) : Nil 8 | return unless value 9 | return if value.is_a?(String) && value.empty? 10 | 11 | if token != @last_token 12 | if @last_token 13 | format @last_token, @value.to_s 14 | @value.clear 15 | end 16 | @last_token = token 17 | end 18 | 19 | @value << value 20 | end 21 | 22 | def finish 23 | value = @value.to_s 24 | format @last_token, value unless value.empty? 25 | end 26 | 27 | abstract def format(token : Token?, value : String) : Nil 28 | end 29 | -------------------------------------------------------------------------------- /etnoir/src/etnoir/formatters.cr: -------------------------------------------------------------------------------- 1 | module Etnoir 2 | module Formatters 3 | FORMATTERS = { 4 | "html", "html-inline", 5 | "terminal-rgb", 6 | } 7 | 8 | def self.valid_name?(name) 9 | FORMATTERS.includes? name 10 | end 11 | 12 | def self.instantiate(name, theme, io) 13 | case name 14 | when "html" 15 | Noir::Formatters::HTML.new io 16 | when "html-inline" 17 | Noir::Formatters::HTMLInline.new theme, io 18 | when "terminal-rgb" 19 | Noir::Formatters::TerminalRGB.new theme, io 20 | else 21 | raise "unknown formatter '#{name}'" 22 | end 23 | end 24 | end 25 | 26 | DEFAULT_FORMATTER = "terminal-rgb" 27 | end 28 | -------------------------------------------------------------------------------- /spec/lexers/css/fixtures/selectors.in: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: 12pt; 3 | background : #fff url(temp.png) top left no-repeat; 4 | width: 75%; 5 | } 6 | 7 | * html body { 8 | font-size: 14pt; 9 | } 10 | 11 | #nav .new { 12 | display: block; 13 | } 14 | 15 | ul#nav li.new { 16 | font-weight: bold; 17 | } 18 | 19 | :link { 20 | color: #f00; 21 | } 22 | 23 | :link:hover { 24 | color: #0f0; 25 | } 26 | 27 | *::before { 28 | content: "text"; 29 | } 30 | 31 | :first-child::after { 32 | display: none; 33 | } 34 | 35 | .class:first-child:focus::after { 36 | display: none; 37 | } 38 | 39 | #thing { 40 | font: normal 400 12px/35px Helvetica, Arial, sans-serif; 41 | } 42 | 43 | #foo { 44 | unrecognized-prop: 1; 45 | -moz-prefixed-prop: 2; 46 | } 47 | 48 | a[target="_blank"] { 49 | background-color: yellow; 50 | } 51 | -------------------------------------------------------------------------------- /spec/lexers/crystal/fixtures/keyword_like_method.out: -------------------------------------------------------------------------------- 1 | [#, "\"foo\""] 2 | [#, "."] 3 | [#, "is_a?"] 4 | [#, "("] 5 | [#, "String"] 6 | [#, ")"] 7 | [#, "\n"] 8 | [#, "\"foo\""] 9 | [#, "."] 10 | [#, "nil?"] 11 | [#, "\n"] 12 | [#, "\"foo\""] 13 | [#, "."] 14 | [#, "as"] 15 | [#, "("] 16 | [#, "String"] 17 | [#, ")"] 18 | [#, "\n"] 19 | [#, "\"foo\""] 20 | [#, "."] 21 | [#, "as?"] 22 | [#, "("] 23 | [#, "String"] 24 | [#, ")"] 25 | [#, "\n"] 26 | -------------------------------------------------------------------------------- /spec/lexers/json/fixtures/syntax.out: -------------------------------------------------------------------------------- 1 | [#, "{"] 2 | [#, "\n "] 3 | [#, "\"key\""] 4 | [#, " "] 5 | [#, ":"] 6 | [#, " "] 7 | [#, "["] 8 | [#, "6.02e-23"] 9 | [#, ","] 10 | [#, " "] 11 | [#, "\""] 12 | [#, "\\u3042"] 13 | [#, "いうえお かきくけこ"] 14 | [#, "\\n"] 15 | [#, "\""] 16 | [#, ","] 17 | [#, "true"] 18 | [#, ","] 19 | [#, "false"] 20 | [#, ","] 21 | [#, "null"] 22 | [#, "]"] 23 | [#, "\n"] 24 | [#, "}"] 25 | [#, "\n"] 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NOIR 2 | 3 | Syntax Highlight Library for [Crystal](https://crystal-lang.org) 4 | 5 | ## CLI 6 | 7 | [ET NOIR](etnoir/) is CLI tool for NOIR. It can be installed with these commands: 8 | 9 | ```console 10 | $ git clone https://github.com/MakeNowJust/noir 11 | $ cd noir 12 | $ make 13 | ``` 14 | 15 | Then `bin/etnoir` is available: 16 | 17 | ```console 18 | $ bin/etnoir 19 | ET NOIR - NOIR command-line tool 20 | 21 | Usage: 22 | etnoir highlight FILENAME 23 | etnoir style THEME 24 | 25 | Command: 26 | highlight highlight FILENAME content 27 | style print THEME style as CSS 28 | help show this help 29 | version show ET NOIR version 30 | ``` 31 | 32 | ## Note 33 | 34 | This project is heavily inspired by [jneen/rouge](https://github.com/jneen/rouge). 35 | 36 | ## License 37 | 38 | MIT and [:sushi:](https://github.com/MakeNowJust/sushi-ware) 39 | © TSUYUSATO "[MakeNowJust](https://quine.codes)" Kitsune <> 2016-2017 40 | -------------------------------------------------------------------------------- /LICENSE.MIT.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2016-2017 TSUYUSATO "MakeNowJust" Kitsune 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /etnoir/src/etnoir/command.cr: -------------------------------------------------------------------------------- 1 | require "./error" 2 | require "./exit" 3 | require "./commands/*" 4 | 5 | class Etnoir::Command 6 | include Commands::Highlight 7 | include Commands::Style 8 | 9 | HELP = <<-HELP 10 | ET NOIR - NOIR command-line tool 11 | 12 | Usage: 13 | etnoir highlight FILENAME 14 | etnoir style THEME 15 | 16 | Command: 17 | highlight highlight FILENAME content 18 | style print THEME style as CSS 19 | help show this help 20 | version show ET NOIR version 21 | HELP 22 | 23 | def initialize(@out : IO) 24 | end 25 | 26 | def run(args = ARGV.dup) 27 | case command = args.shift? 28 | when .nil?, "help", "-h", "--help" 29 | @out.puts HELP 30 | when "version", "-v", "--version" 31 | @out.puts Noir::VERSION 32 | when "highlight" 33 | highlight args 34 | when "style" 35 | style args 36 | else 37 | raise "unknown command '#{command}'" 38 | end 39 | end 40 | 41 | def raise(message) 42 | ::raise Error.new(message) 43 | end 44 | 45 | def exit(status) 46 | ::raise Exit.new(status) 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/spec_helper.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | 3 | require "../src/noir" 4 | 5 | UPDATE_FIXTURE = ENV["UPDATE_FIXTURE"]? == "1" 6 | 7 | class SpecFormatter < Noir::Formatter 8 | @out : IO::Memory 9 | 10 | def initialize 11 | @out = IO::Memory.new 12 | end 13 | 14 | def format(token, value) : Nil 15 | [token, value].inspect @out 16 | @out.puts 17 | end 18 | 19 | def to_s(io) 20 | @out.to_s io 21 | end 22 | end 23 | 24 | def it_lexes_fixtures(name, lexer_class, dir = __DIR__) 25 | Dir.glob("#{dir}/fixtures/*.in") do |in_file| 26 | in_text = File.read(in_file) 27 | 28 | out_file = in_file.gsub(/\.in$/, ".out") 29 | if File.exists?(out_file) && !UPDATE_FIXTURE 30 | out_text = File.read(out_file) 31 | elsif !UPDATE_FIXTURE 32 | raise "cannot find output file against '#{in_file}'" 33 | end 34 | 35 | lexer = lexer_class.new 36 | formatter = SpecFormatter.new 37 | 38 | Noir.highlight(in_text, lexer: lexer, formatter: formatter) 39 | if out_text 40 | it "should highlight #{in_text.inspect} to #{out_text.inspect}" do 41 | formatter.to_s.should eq(out_text) 42 | end 43 | else 44 | File.write(out_file, formatter.to_s) 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /src/noir/formatters/terminal_rgb.cr: -------------------------------------------------------------------------------- 1 | require "../formatter" 2 | require "../theme" 3 | 4 | class Noir::Formatters::TerminalRGB < Noir::Formatter 5 | def initialize(@theme : Theme, @out : IO) 6 | end 7 | 8 | def format(token, value) : Nil 9 | if token 10 | style = @theme.style_for token 11 | else 12 | style = @theme.base_style 13 | end 14 | 15 | value.each_line(chomp: false) do |line| 16 | wrote = false 17 | 18 | if c = style.fore 19 | @out << "\e[" unless wrote 20 | @out << "38;2;#{c.red};#{c.green};#{c.blue}" 21 | wrote = true 22 | end 23 | 24 | if style.bold 25 | @out << "\e[" unless wrote 26 | @out << ";" if wrote 27 | @out << "1" 28 | wrote = true 29 | end 30 | 31 | if style.italic 32 | @out << "\e[" unless wrote 33 | @out << ";" if wrote 34 | @out << "3" 35 | wrote = true 36 | end 37 | 38 | if style.underline 39 | @out << "\e[" unless wrote 40 | @out << ";" if wrote 41 | @out << "4" 42 | wrote = true 43 | end 44 | 45 | @out << "m" if wrote 46 | @out << line.chomp 47 | @out << "\e[0m" if wrote 48 | @out.puts if line.ends_with?("\n") 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/lexers/javascript/fixtures/literals.out: -------------------------------------------------------------------------------- 1 | [#, "42"] 2 | [#, "\n"] 3 | [#, "3.14"] 4 | [#, "\n"] 5 | [#, "\"foo\""] 6 | [#, "\n"] 7 | [#, "'bar'"] 8 | [#, "\n"] 9 | [#, "`foo "] 10 | [#, "${"] 11 | [#, "42"] 12 | [#, "}"] 13 | [#, " bar`"] 14 | [#, "\n"] 15 | [#, "["] 16 | [#, "\"foo\""] 17 | [#, ","] 18 | [#, " "] 19 | [#, "\"bar\""] 20 | [#, "]"] 21 | [#, "\n"] 22 | [#, "{"] 23 | [#, "\"foo\""] 24 | [#, ":"] 25 | [#, " "] 26 | [#, "\"bar\""] 27 | [#, "}"] 28 | [#, "\n"] 29 | [#, "{"] 30 | [#, "foo"] 31 | [#, ":"] 32 | [#, " "] 33 | [#, "\"bar\""] 34 | [#, "}"] 35 | [#, "\n"] 36 | [#, "/foo|bar/"] 37 | [#, "\n"] 38 | -------------------------------------------------------------------------------- /spec/lexers/html/fixtures/syntax.out: -------------------------------------------------------------------------------- 1 | [#, ""] 2 | [#, "\n"] 3 | [#, ", " "] 5 | [#, "lang="] 6 | [#, "\"en\""] 7 | [#, ">"] 8 | [#, "\n"] 9 | [#, ""] 10 | [#, "\n "] 11 | [#, ", " "] 13 | [#, "charset = "] 14 | [#, "\"UTF-8\""] 15 | [#, ">"] 16 | [#, "\n "] 17 | [#, ", " "] 19 | [#, "name="] 20 | [#, "'viewport'"] 21 | [#, " "] 22 | [#, "content="] 23 | [#, "\"width=device-width,\n initial-scale=1.0\""] 24 | [#, "/>"] 25 | [#, "\n "] 26 | [#, ""] 27 | [#<Token Name::Entity>, "<""] 28 | [#<Token Text>, "A"] 29 | [#<Token Name::Tag>, ""] 30 | [#, "\n"] 31 | [#, ""] 32 | [#, "\n"] 33 | [#, ""] 34 | [#, "\n "] 35 | [#, ""] 36 | [#, "\n"] 37 | [#, ""] 38 | [#, "\n"] 39 | [#, ""] 40 | [#, "\n"] 41 | -------------------------------------------------------------------------------- /etnoir/src/etnoir/commands/style.cr: -------------------------------------------------------------------------------- 1 | require "option_parser" 2 | 3 | require "noir" 4 | 5 | require "../themes" 6 | 7 | module Etnoir::Commands::Style 8 | @out : IO 9 | 10 | abstract def raise(message) 11 | abstract def exit(status) 12 | 13 | def style(args) 14 | option = parse_style_option(args) 15 | 16 | option.theme.to_s @out 17 | end 18 | 19 | record Option, 20 | theme : Noir::Theme 21 | 22 | def parse_style_option(args) 23 | theme = nil 24 | scope = ".highlight" 25 | 26 | OptionParser.parse(args) do |parser| 27 | parser.banner = <<-BANNER 28 | ET NOIR - NOIR command-line tool 29 | 30 | Usage: etnoir style [-s SCOPE] THEME 31 | 32 | Option: 33 | BANNER 34 | 35 | parser.on("-s SCOPE", "--scope=SCOPE", "specify the scope to apply theme CSS (default: .highlight)") do |selector| 36 | scope = selector 37 | end 38 | 39 | parser.on("-h", "--help", "show this help") do 40 | puts parser 41 | exit 0 42 | end 43 | 44 | parser.unknown_args do |ar, gs| 45 | args = ar + gs 46 | raise "THEME is not specified" if args.empty? 47 | raise "multiple THEMEs are not allowed" if args.size >= 2 48 | 49 | name = args.first 50 | unless theme = Noir.find_theme(name, scope: scope) 51 | raise "unknown theme '#{name}'" 52 | end 53 | end 54 | end 55 | 56 | Option.new(theme.not_nil!) 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/lexers/elm/fixtures/syntax.in: -------------------------------------------------------------------------------- 1 | {- Make a GET request to load a book called "Public Opinion" 2 | 3 | Read how it works: 4 | https://guide.elm-lang.org/effects/http.html 5 | 6 | -} 7 | 8 | import Browser 9 | import Html exposing (Html, text, pre) 10 | import Http 11 | 12 | -- MAIN 13 | 14 | main = 15 | Browser.element 16 | { init = init 17 | , update = update 18 | , subscriptions = subscriptions 19 | , view = view 20 | } 21 | 22 | -- MODEL 23 | 24 | type Model 25 | = Failure 26 | | Loading 27 | | Success String 28 | 29 | 30 | init : () -> (Model, Cmd Msg) 31 | init _ = 32 | ( Loading 33 | , Http.get 34 | { url = "https://elm-lang.org/assets/public-opinion.txt" 35 | , expect = Http.expectString GotText 36 | } 37 | ) 38 | 39 | 40 | -- UPDATE 41 | 42 | type Msg 43 | = GotText (Result Http.Error String) 44 | 45 | update : Msg -> Model -> (Model, Cmd Msg) 46 | update msg model = 47 | case msg of 48 | GotText result -> 49 | case result of 50 | Ok fullText -> 51 | (Success fullText, Cmd.none) 52 | 53 | Err _ -> 54 | (Failure, Cmd.none) 55 | 56 | -- SUBSCRIPTIONS 57 | 58 | subscriptions : Model -> Sub Msg 59 | subscriptions model = 60 | Sub.none 61 | 62 | -- VIEW 63 | 64 | view : Model -> Html Msg 65 | view model = 66 | case model of 67 | Failure -> 68 | text "I was unable to load your book." 69 | 70 | Loading -> 71 | text "Loading..." 72 | 73 | Success fullText -> 74 | pre [] [ text fullText ] 75 | -------------------------------------------------------------------------------- /spec/lexers/crystal/fixtures/syntax.in: -------------------------------------------------------------------------------- 1 | # types 2 | 3 | Foo 4 | Foo::Bar 5 | 6 | # builtin 7 | Int32 8 | String 9 | 10 | class Foo::Bar 11 | end 12 | 13 | struct Foo::Bar 14 | end 15 | 16 | enum Foo::Bar 17 | end 18 | 19 | module Foo::Bar 20 | end 21 | 22 | lib Foo 23 | struct Bar 24 | end 25 | 26 | enum Bar 27 | end 28 | 29 | union Bar 30 | end 31 | 32 | type Bar = Foo 33 | end 34 | 35 | 36 | # constants 37 | 38 | FOO 39 | Foo::BAR 40 | 41 | # builtins 42 | ARGV 43 | PROGRAM_NAME 44 | 45 | 46 | # methods 47 | 48 | def foo 49 | end 50 | 51 | fun foo 52 | end 53 | 54 | macro foo 55 | end 56 | 57 | def self.foo 58 | end 59 | 60 | def Foo.foo 61 | end 62 | 63 | def foo?; end 64 | def foo!; end 65 | 66 | def +; end 67 | def -; end 68 | def *; end 69 | def /; end 70 | def %; end 71 | def **; end 72 | def ~; end 73 | def |; end 74 | def &; end 75 | def ^; end 76 | def <<; end 77 | def >>; end 78 | def ==; end 79 | def ===; end 80 | def !=; end 81 | def <; end 82 | def <=; end 83 | def >; end 84 | def >=; end 85 | def <=>; end 86 | def =~; end 87 | def !~; end 88 | 89 | # variables 90 | 91 | foo 92 | @foo 93 | @@foo 94 | 95 | $? 96 | $~ 97 | $1 98 | $2? 99 | 100 | 101 | # method invocations 102 | 103 | foo.foo 104 | foo.foo? 105 | foo.foo! 106 | 107 | foo.foo do |bar| 108 | end 109 | foo.foo { |bar| } 110 | 111 | `foo #{foo} bar` 112 | 113 | +foo 114 | -foo 115 | foo + bar 116 | foo - bar 117 | foo * bar 118 | foo / bar 119 | foo % bar 120 | foo ** bar 121 | (foo + bar) * (foo - bar) 122 | 123 | ~foo 124 | foo | bar 125 | foo & bar 126 | foo ^ bar 127 | foo << bar 128 | foo >> bar 129 | 130 | foo == bar 131 | foo === bar 132 | foo != bar 133 | foo < bar 134 | foo <= bar 135 | foo > bar 136 | foo >= bar 137 | foo <=> bar 138 | foo =~ bar 139 | foo !~ bar 140 | 141 | !foo 142 | foo || bar 143 | foo && bar 144 | -------------------------------------------------------------------------------- /spec/lexers/ruby/fixtures/literals.out: -------------------------------------------------------------------------------- 1 | [#, "42"] 2 | [#, "\n"] 3 | [#, "3.14"] 4 | [#, "\n"] 5 | [#, "?x"] 6 | [#, "\n"] 7 | [#, "\"foo\""] 8 | [#, "\n"] 9 | [#, "'foo'"] 10 | [#, "\n"] 11 | [#, "`foo`"] 12 | [#, "\n"] 13 | [#, "\"foo "] 14 | [#, "\#{"] 15 | [#, "bar"] 16 | [#, "}"] 17 | [#, " baz\""] 18 | [#, "\n"] 19 | [#, "'foo \#{bar} baz'"] 20 | [#, "\n"] 21 | [#, "%(foo bar baz)"] 22 | [#, "\n"] 23 | [#, "%q(foo bar baz)"] 24 | [#, "\n"] 25 | [#, "%w(foo bar baz)"] 26 | [#, "\n"] 27 | [#, "/foo bar/"] 28 | [#, "\n"] 29 | [#, "/foo bar/m"] 30 | [#, "\n"] 31 | [#, "%r(\n foo\n bar\n)x"] 32 | [#, "\n"] 33 | [#, "[]"] 34 | [#, "\n"] 35 | [#, "["] 36 | [#, "1"] 37 | [#, ","] 38 | [#, " "] 39 | [#, "2"] 40 | [#, ","] 41 | [#, " "] 42 | [#, "3"] 43 | [#, "]"] 44 | [#, "\n"] 45 | [#, "{"] 46 | [#, "foo: "] 47 | [#, "\"bar\""] 48 | [#, "}"] 49 | [#, "\n"] 50 | [#, "{"] 51 | [#, "\"foo\""] 52 | [#, " "] 53 | [#, "=>"] 54 | [#, " "] 55 | [#, "\"bar\""] 56 | [#, "}"] 57 | [#, "\n"] 58 | -------------------------------------------------------------------------------- /spec/lexers/ruby/fixtures/syntax.out: -------------------------------------------------------------------------------- 1 | [#, "def"] 2 | [#, " "] 3 | [#, "foo"] 4 | [#, "("] 5 | [#, "x"] 6 | [#, ","] 7 | [#, " "] 8 | [#, "y"] 9 | [#, ","] 10 | [#, " "] 11 | [#, "&"] 12 | [#, "z"] 13 | [#, ")"] 14 | [#, "\n "] 15 | [#, "begin"] 16 | [#, "\n "] 17 | [#, "yield"] 18 | [#, " "] 19 | [#, "y"] 20 | [#, " "] 21 | [#, "if"] 22 | [#, " "] 23 | [#, "x"] 24 | [#, "\n "] 25 | [#, "rescue"] 26 | [#, " "] 27 | [#, "=>"] 28 | [#, " "] 29 | [#, "e"] 30 | [#, "\n "] 31 | [#, "puts"] 32 | [#, " "] 33 | [#, ":rescue"] 34 | [#, "\n "] 35 | [#, "ensure"] 36 | [#, "\n "] 37 | [#, "puts"] 38 | [#, " "] 39 | [#, ":ensure"] 40 | [#, "\n "] 41 | [#, "end"] 42 | [#, "\n"] 43 | [#, "end"] 44 | [#, "\n\n"] 45 | [#, "class"] 46 | [#, " "] 47 | [#, "Foo"] 48 | [#, "\n "] 49 | [#, "def"] 50 | [#, " "] 51 | [#, "self"] 52 | [#, "."] 53 | [#, "foo"] 54 | [#, "\n "] 55 | [#, "end"] 56 | [#, "\n"] 57 | [#, "end"] 58 | [#, "\n\n"] 59 | [#, "module"] 60 | [#, " "] 61 | [#, "Bar"] 62 | [#, "\n "] 63 | [#, "extend"] 64 | [#, " "] 65 | [#, "self"] 66 | [#, "\n"] 67 | [#, "end"] 68 | [#, "\n\n"] 69 | [#, "class"] 70 | [#, " "] 71 | [#, "Baz"] 72 | [#, " "] 73 | [#, "<"] 74 | [#, " "] 75 | [#, "Foo"] 76 | [#, "\n "] 77 | [#, "include"] 78 | [#, " "] 79 | [#, "Bar"] 80 | [#, "\n"] 81 | [#, "end"] 82 | [#, "\n"] 83 | -------------------------------------------------------------------------------- /src/noir/lexers/elm.cr: -------------------------------------------------------------------------------- 1 | require "../lexer" 2 | 3 | class Noir::Lexers::Elm < Noir::Lexer 4 | tag "elm" 5 | filenames %w(*.elm) 6 | mimetypes %w(text/x-elm) 7 | 8 | # Keywords are logically grouped by lines 9 | keywords = %w( 10 | module exposing port 11 | import as 12 | type alias 13 | if then else 14 | case of 15 | let in 16 | ) 17 | 18 | state :root do 19 | # Whitespaces 20 | rule /\s+/m, Text 21 | 22 | # Single line comments 23 | rule /--.*/, Comment::Single 24 | # Multiline comments 25 | rule /{-/, Comment::Multiline, :multiline_comment 26 | 27 | # Keywords 28 | rule /\b(#{keywords.join('|')})\b/, Keyword 29 | 30 | # Variable or a function 31 | rule /[a-z]\w*/, Name 32 | # Underscore is a name for a variable, when it won't be used later 33 | rule /_/, Name 34 | # Type 35 | rule /[A-Z]\w*/, Keyword::Type 36 | 37 | # Two symbol operators: -> :: // .. && || ++ |> <| << >> == /= <= >= 38 | rule /(->|::|\/\/|\.\.|&&|\|\||\+\+|\|>|<\||>>|<<|==|\/=|<=|>=)/, Operator 39 | # One symbol operators: + - / * % = < > ^ | ! 40 | rule /[+-\/*%=<>^\|!]/, Operator 41 | # Lambda operator 42 | rule /\\/, Operator 43 | # Not standard Elm operators, but these symbols can be used for custom inflix operators. We need to highlight them as operators as well. 44 | rule /[@\#$&~?]/, Operator 45 | 46 | # Single, double quotes, and triple double quotes 47 | rule /"""/, Str, :multiline_string 48 | rule /'(\\.|.)'/, Str::Char 49 | rule /"/, Str, :double_quote 50 | 51 | # Numbers 52 | rule /0x[\da-f]+/i, Num::Hex 53 | rule /\d+e[+-]?\d+/i, Num::Float 54 | rule /\d+\.\d+(e[+-]?\d+)?/i, Num::Float 55 | rule /\d+/, Num::Integer 56 | 57 | # Punctuation: [ ] ( ) , ; ` { } : 58 | rule /[\[\](),;`{}:]/, Punctuation 59 | end 60 | 61 | # Multiline and nested commenting 62 | state :multiline_comment do 63 | rule /-}/, Comment::Multiline, :pop! 64 | rule /{-/, Comment::Multiline, :multiline_comment 65 | rule /[^-{}]+/, Comment::Multiline 66 | rule /[-{}]/, Comment::Multiline 67 | end 68 | 69 | # Double quotes 70 | state :double_quote do 71 | rule /[^\\"]+/, Str::Double 72 | rule /\\"/, Str::Escape 73 | rule /"/, Str::Double, :pop! 74 | end 75 | 76 | # Multiple line string with triple double quotes, e.g. """ multi """ 77 | state :multiline_string do 78 | rule /\\"/, Str::Escape 79 | rule /"""/, Str, :pop! 80 | rule /[^"]+/, Str 81 | rule /"/, Str 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /spec/lexers/crystal/fixtures/literals.out: -------------------------------------------------------------------------------- 1 | [#, "42"] 2 | [#, "\n"] 3 | [#, "3.14"] 4 | [#, "\n"] 5 | [#, "1_i8"] 6 | [#, "\n"] 7 | [#, "2_i16"] 8 | [#, "\n"] 9 | [#, "3_i32"] 10 | [#, "\n"] 11 | [#, "4_i64"] 12 | [#, "\n"] 13 | [#, "5_i128"] 14 | [#, "\n"] 15 | [#, "6u8"] 16 | [#, "\n"] 17 | [#, "7u16"] 18 | [#, "\n"] 19 | [#, "8u32"] 20 | [#, "\n"] 21 | [#, "9u64"] 22 | [#, "\n"] 23 | [#, "10u128"] 24 | [#, "\n"] 25 | [#, "11f32"] 26 | [#, "\n"] 27 | [#, "12f64"] 28 | [#, "\n"] 29 | [#, "'x'"] 30 | [#, "\n"] 31 | [#, "'\\n'"] 32 | [#, "\n"] 33 | [#, ":foo"] 34 | [#, "\n"] 35 | [#, ":\"foo bar\""] 36 | [#, "\n"] 37 | [#, ":+"] 38 | [#, "\n"] 39 | [#, "\"foo bar\""] 40 | [#, "\n"] 41 | [#, "\"foo "] 42 | [#, "\#{"] 43 | [#, " "] 44 | [#, "{"] 45 | [#, ":bar"] 46 | [#, "}"] 47 | [#, " "] 48 | [#, "}"] 49 | [#, " baz\""] 50 | [#, "\n"] 51 | [#, "%w(foo (bar) baz)"] 52 | [#, "\n"] 53 | [#, "<<-"] 54 | [#, "FOO"] 55 | [#, "\nfoo bar\n"] 56 | [#, "\#{"] 57 | [#, "foo"] 58 | [#, " "] 59 | [#, "bar"] 60 | [#, "}"] 61 | [#, "\n"] 62 | [#, "FOO"] 63 | [#, "\n"] 64 | [#, "<<-"] 65 | [#, "'FOO'"] 66 | [#, "\nfoo bar\n\#{foo bar}\n"] 67 | [#, "FOO"] 68 | [#, "\n"] 69 | [#, "%r{foo}"] 70 | [#, "\n"] 71 | [#, "%r[foo]mix"] 72 | [#, "\n"] 73 | -------------------------------------------------------------------------------- /etnoir/src/etnoir/commands/highlight.cr: -------------------------------------------------------------------------------- 1 | require "option_parser" 2 | 3 | require "noir" 4 | 5 | require "../formatters" 6 | require "../lexers" 7 | require "../themes" 8 | 9 | module Etnoir::Commands::Highlight 10 | @out : IO 11 | 12 | abstract def raise(message) 13 | abstract def exit(status) 14 | 15 | def highlight(args) 16 | option = parse_highlight_option(args) 17 | 18 | code = File.read option.filename 19 | Noir.highlight code, 20 | lexer: option.lexer, 21 | formatter: option.formatter 22 | end 23 | 24 | record Option, 25 | filename : String, 26 | lexer : Noir::Lexer, 27 | formatter : Noir::Formatter 28 | 29 | def parse_highlight_option(args) 30 | filename = nil 31 | lexer = nil 32 | theme = Noir.find_theme(DEFAULT_THEME).not_nil! 33 | formatter = DEFAULT_FORMATTER 34 | 35 | OptionParser.parse(args) do |parser| 36 | parser.banner = <<-BANNER 37 | ET NOIR - NOIR command-line tool 38 | 39 | Usage: etnoir highlight [-l LEXER] [-t THEME] [-f FORMATTER] FILENAME 40 | 41 | Option: 42 | BANNER 43 | 44 | parser.on("-l LEXER", "--lexer=LEXER", "specify the lexer to use") do |name| 45 | unless lexer = Noir.find_lexer(name) 46 | raise "unknown lexer '#{name}'" 47 | end 48 | end 49 | 50 | parser.on("-t THEME", "--theme=THEME", "specify the theme to use for highlighting (default: #{DEFAULT_THEME}") do |name| 51 | unless theme = Noir.find_theme(name) 52 | raise "unknown theme '#{name}'" 53 | end 54 | end 55 | 56 | parser.on("-f FORMATTER", "--formatter=FORMATTER", "specify the output formatter to use (default: #{DEFAULT_FORMATTER})") do |name| 57 | unless Formatters.valid_name?(name) 58 | raise "unknown formatter '#{name}'" 59 | end 60 | formatter = name 61 | end 62 | 63 | parser.on("-h", "--help", "show this help") do 64 | puts parser 65 | exit 0 66 | end 67 | 68 | parser.unknown_args do |ar, gs| 69 | args = ar + gs 70 | raise "FILENAME is not specified" if args.empty? 71 | raise "multiple FILENAMEs are not allowed" if args.size >= 2 72 | 73 | filename = args.first 74 | end 75 | end 76 | 77 | # TODO: should use Lexer#.filenames, but Crystal doesn't have `File#fnmatch` currently. It uses filename extension as lexer name for now. 78 | # See https://github.com/crystal-lang/crystal/pull/5179 79 | if !lexer && /\.(\w+)\z/ =~ filename 80 | ext = $1 81 | lexer = Noir.find_lexer ext 82 | end 83 | 84 | raise "LEXER is not specified" unless lexer 85 | 86 | Option.new filename.not_nil!, 87 | lexer: lexer.not_nil!, 88 | formatter: Formatters.instantiate(formatter, theme.not_nil!, @out) 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /src/noir/themes/solarized.cr: -------------------------------------------------------------------------------- 1 | require "../theme" 2 | 3 | module Noir::Themes 4 | module Solarized 5 | macro included 6 | palette :yellow, "#B58900" 7 | palette :orange, "#CB4B16" 8 | palette :red, "#DC322F" 9 | palette :magenta, "#D33682" 10 | palette :violet, "#6C71C4" 11 | palette :blue, "#268BD2" 12 | palette :cyan, "#2AA198" 13 | palette :green, "#859900" 14 | 15 | style Text, fore: :base1, back: :base03 16 | 17 | style Keyword, fore: :green 18 | style Keyword::Constant, fore: :orange 19 | style Keyword::Reserved, fore: :blue 20 | style Keyword::Type, fore: :red 21 | 22 | style Name::Attribute, fore: :base1 23 | style Name::Builtin, fore: :yellow 24 | style Name::Builtin::Pseudo, 25 | Name::Class, 26 | Name::Decorator, 27 | Name::Function, 28 | Name::Tag, 29 | Name::Variable, fore: :blue 30 | style Name::Constant, 31 | Name::Entity, 32 | Name::Exception, fore: :orange 33 | 34 | style Literal::String, 35 | Literal::String::Single, 36 | Literal::String::Double, 37 | Literal::String::Char, fore: :cyan 38 | style Literal::String::Interpol, fore: :blue 39 | style Literal::String::Backtick, 40 | Literal::String::Doc, fore: :base1 41 | style Literal::String::Escape, fore: :orange 42 | style Literal::String::Heredoc, fore: :base1 43 | style Literal::String::Regex, fore: :red 44 | style Literal::Number, fore: :cyan 45 | 46 | style Operator, fore: :green 47 | style Punctuation, fore: :orange 48 | style Comment, fore: :base01 49 | style Comment::Preproc, Comment::Special, fore: :green 50 | 51 | style Generic::Deleted, fore: :cyan 52 | style Generic::Emph, italic: true 53 | style Generic::Error, fore: :red 54 | style Generic::Heading, fore: :orange 55 | style Generic::Inserted, fore: :green 56 | style Generic::Strong, bold: true 57 | style Generic::Subheading, fore: :orange 58 | end 59 | end 60 | 61 | class SolarizedDark < Noir::Theme 62 | name "solarized-dark" 63 | 64 | palette :base03, "#002B36" 65 | palette :base02, "#073642" 66 | palette :base01, "#586E75" 67 | palette :base00, "#657B83" 68 | palette :base0, "#839496" 69 | palette :base1, "#93A1A1" 70 | palette :base2, "#EEE8D5" 71 | palette :base3, "#FDF6E3" 72 | 73 | include Solarized 74 | end 75 | 76 | class SolarizedLight < Noir::Theme 77 | name "solarized-light" 78 | 79 | palette :base3, "#002B36" 80 | palette :base2, "#073642" 81 | palette :base1, "#586E75" 82 | palette :base0, "#657B83" 83 | palette :base00, "#839496" 84 | palette :base01, "#93A1A1" 85 | palette :base02, "#EEE8D5" 86 | palette :base03, "#FDF6E3" 87 | 88 | include Solarized 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /spec/lexers/python/fixtures/fizzbuzz.out: -------------------------------------------------------------------------------- 1 | [#, "#"] 2 | [#, "\n"] 3 | [#, "# fizzbuzz.py"] 4 | [#, "\n"] 5 | [#, "#"] 6 | [#, "\n\n"] 7 | [#, "from"] 8 | [#, " "] 9 | [#, "collections"] 10 | [#, " "] 11 | [#, "import"] 12 | [#, " "] 13 | [#, "defaultdict"] 14 | [#, "\n\n"] 15 | [#, "fizz"] 16 | [#, ","] 17 | [#, " "] 18 | [#, "buzz"] 19 | [#, " "] 20 | [#, "="] 21 | [#, " "] 22 | [#, "defaultdict"] 23 | [#, "("] 24 | [#, "lambda"] 25 | [#, ":"] 26 | [#, " "] 27 | [#, "''"] 28 | [#, "),"] 29 | [#, " "] 30 | [#, "defaultdict"] 31 | [#, "("] 32 | [#, "lambda"] 33 | [#, ":"] 34 | [#, " "] 35 | [#, "''"] 36 | [#, ")"] 37 | [#, "\n"] 38 | [#, "fizz"] 39 | [#, "["] 40 | [#, "0"] 41 | [#, "],"] 42 | [#, " "] 43 | [#, "buzz"] 44 | [#, "["] 45 | [#, "0"] 46 | [#, "]"] 47 | [#, " "] 48 | [#, "="] 49 | [#, " "] 50 | [#, "'Fizz'"] 51 | [#, ","] 52 | [#, " "] 53 | [#, "'Buzz'"] 54 | [#, "\n\n"] 55 | [#, "for"] 56 | [#, " "] 57 | [#, "i"] 58 | [#, " "] 59 | [#, "in"] 60 | [#, " "] 61 | [#, "range"] 62 | [#, "("] 63 | [#, "1"] 64 | [#, ","] 65 | [#, " "] 66 | [#, "101"] 67 | [#, "):"] 68 | [#, "\n "] 69 | [#, "print"] 70 | [#, "("] 71 | [#, "fizz"] 72 | [#, "["] 73 | [#, "i"] 74 | [#, " "] 75 | [#, "%"] 76 | [#, " "] 77 | [#, "3"] 78 | [#, "]"] 79 | [#, " "] 80 | [#, "+"] 81 | [#, " "] 82 | [#, "buzz"] 83 | [#, "["] 84 | [#, "i"] 85 | [#, " "] 86 | [#, "%"] 87 | [#, " "] 88 | [#, "5"] 89 | [#, "]"] 90 | [#, " "] 91 | [#, "or"] 92 | [#, " "] 93 | [#, "str"] 94 | [#, "("] 95 | [#, "i"] 96 | [#, "))"] 97 | [#, "\n"] 98 | -------------------------------------------------------------------------------- /src/noir/themes/monokai.cr: -------------------------------------------------------------------------------- 1 | require "../theme" 2 | 3 | class Noir::Themes::Monokai < Noir::Theme 4 | name "monokai" 5 | 6 | palette :black, "#000000" 7 | palette :bright_green, "#a6e22e" 8 | palette :bright_pink, "#f92672" 9 | palette :carmine, "#960050" 10 | palette :dark, "#49483e" 11 | palette :dark_grey, "#888888" 12 | palette :dark_red, "#aa0000" 13 | palette :dimgrey, "#75715e" 14 | palette :dimgreen, "#324932" 15 | palette :dimred, "#493131" 16 | palette :emperor, "#555555" 17 | palette :grey, "#999999" 18 | palette :light_grey, "#aaaaaa" 19 | palette :light_violet, "#ae81ff" 20 | palette :soft_cyan, "#66d9ef" 21 | palette :soft_yellow, "#e6db74" 22 | palette :very_dark, "#1e0010" 23 | palette :whitish, "#f8f8f2" 24 | palette :orange, "#f6aa11" 25 | palette :white, "#ffffff" 26 | 27 | style Comment, 28 | Comment::Multiline, 29 | Comment::Single, fore: :dimgrey, italic: true 30 | style Comment::Preproc, fore: :dimgrey, bold: true 31 | style Comment::Special, fore: :dimgrey, italic: true, bold: true 32 | style Error, fore: :carmine, back: :very_dark 33 | style Generic::Inserted, fore: :white, back: :dimgreen 34 | style Generic::Deleted, fore: :white, back: :dimred 35 | style Generic::Emph, fore: :black, italic: true 36 | style Generic::Error, 37 | Generic::Traceback, fore: :dark_red 38 | style Generic::Heading, fore: :grey 39 | style Generic::Output, fore: :dark_grey 40 | style Generic::Prompt, fore: :emperor 41 | style Generic::Strong, bold: true 42 | style Generic::Subheading, fore: :light_grey 43 | style Keyword, 44 | Keyword::Constant, 45 | Keyword::Declaration, 46 | Keyword::Pseudo, 47 | Keyword::Reserved, 48 | Keyword::Type, fore: :soft_cyan, bold: true 49 | style Keyword::Namespace, 50 | Operator::Word, 51 | Operator, fore: :bright_pink, bold: true 52 | style Literal::Number::Float, 53 | Literal::Number::Hex, 54 | Literal::Number::Integer::Long, 55 | Literal::Number::Integer, 56 | Literal::Number::Oct, 57 | Literal::Number, 58 | Literal::String::Escape, fore: :light_violet 59 | style Literal::String::Backtick, 60 | Literal::String::Char, 61 | Literal::String::Doc, 62 | Literal::String::Double, 63 | Literal::String::Heredoc, 64 | Literal::String::Interpol, 65 | Literal::String::Other, 66 | Literal::String::Regex, 67 | Literal::String::Single, 68 | Literal::String::Symbol, 69 | Literal::String, fore: :soft_yellow 70 | style Name::Attribute, fore: :bright_green 71 | style Name::Class, 72 | Name::Decorator, 73 | Name::Exception, 74 | Name::Function, fore: :bright_green, bold: true 75 | style Name::Constant, fore: :soft_cyan 76 | style Name::Builtin::Pseudo, 77 | Name::Builtin, 78 | Name::Entity, 79 | Name::Namespace, 80 | Name::Variable::Class, 81 | Name::Variable::Global, 82 | Name::Variable::Instance, 83 | Name::Variable, 84 | Text::Whitespace, fore: :whitish 85 | style Name::Label, fore: :whitish, bold: true 86 | style Name::Tag, fore: :bright_pink 87 | style Text, fore: :whitish, back: :dark 88 | end 89 | -------------------------------------------------------------------------------- /src/noir/lexers/html.cr: -------------------------------------------------------------------------------- 1 | require "../lexer" 2 | require "./javascript" 3 | require "./css" 4 | 5 | class Noir::Lexers::HTML < Noir::Lexer 6 | tag "html" 7 | filenames %w(*.html *.htm *.xhtml) 8 | mimetypes %w(text/html application/xhtml+xml) 9 | 10 | getter js_lexer : JavaScript 11 | getter css_lexer : CSS 12 | 13 | def initialize 14 | @js_lexer = JavaScript.new 15 | @css_lexer = CSS.new 16 | super 17 | end 18 | 19 | # :nodoc: 20 | def reset_js_lexer 21 | @js_lexer = JavaScript.new 22 | end 23 | 24 | # :nodoc: 25 | def reset_css_lexer 26 | @css_lexer = CSS.new 27 | end 28 | 29 | state :root do 30 | rule /[^<&]+/, Text 31 | rule /&\S*?;/, Name::Entity 32 | rule //im, Comment::Preproc 33 | rule //m, Comment::Preproc 34 | rule //, Comment, :pop! 85 | rule /-/, Comment 86 | end 87 | 88 | state :tag do 89 | rule /\s+/, Text 90 | rule /[a-zA-Z0-9_:-]+\s*=\s*/, Name::Attribute, :attr 91 | rule /[a-zA-Z0-9_:-]+/, Name::Attribute 92 | rule %r(/?\s*>), Name::Tag, :pop! 93 | end 94 | 95 | state :attr do 96 | rule /"/ do |m| 97 | m.token Str 98 | m.goto :dq 99 | end 100 | 101 | rule /'/ do |m| 102 | m.token Str 103 | m.goto :sq 104 | end 105 | 106 | rule /[^\s>]+/, Str, :pop! 107 | end 108 | 109 | state :dq do 110 | rule /"/, Str, :pop! 111 | rule /[^"]+/, Str 112 | end 113 | 114 | state :sq do 115 | rule /'/, Str, :pop! 116 | rule /[^']+/, Str 117 | end 118 | 119 | state :script_content do 120 | rule %r([^<]+) do |m| 121 | m.delegate m.lexer.as(HTML).js_lexer 122 | end 123 | 124 | rule %r(<\s*/\s*script\s*>), Name::Tag, :pop! 125 | 126 | rule %r(<) do |m| 127 | m.delegate m.lexer.as(HTML).js_lexer 128 | end 129 | end 130 | 131 | state :style_content do 132 | rule /[^<]+/ do |m| 133 | m.delegate m.lexer.as(HTML).css_lexer 134 | end 135 | 136 | rule %r(<\s*/\s*style\s*>), Name::Tag, :pop! 137 | 138 | rule / Color 72 | @@styles = {} of Token => Style 73 | 74 | def self.palette(color_name) 75 | @@palette.fetch(color_name) do 76 | if (klass = {{@type.superclass}}).responds_to?(:palette) 77 | klass.palette(color_name) 78 | else 79 | raise "undefined color: #{color_name}" 80 | end 81 | end 82 | end 83 | 84 | def self.palette(color_name, color) 85 | color = Color.parse(color) || raise "invalid color: #{color}" 86 | @@palette[color_name] = color 87 | end 88 | 89 | def self.style_for(token : Token) 90 | token.token_chain.each do |t| 91 | style = style?(t) 92 | return style if style 93 | end 94 | 95 | base_style 96 | end 97 | 98 | class_getter base_style : Style do 99 | style?(Tokens::Text) || raise("style for Text token must be defined") 100 | end 101 | 102 | # :nodoc: 103 | def self.style?(token) 104 | @@styles.fetch(token) do 105 | if (klass = {{@type.superclass}}).responds_to?(:style?) 106 | klass.style?(token) 107 | else 108 | nil 109 | end 110 | end 111 | end 112 | 113 | private def self.color(color : Symbol) 114 | palette(color) 115 | end 116 | 117 | private def self.color(color : String) 118 | Color.parse(color) || raise "invalid color: #{color}" 119 | end 120 | 121 | private def self.color(color : Nil) 122 | nil 123 | end 124 | 125 | def self.style(*tokens, fore = nil, back = nil, bold = false, italic = false, underline = false) 126 | style = Style.new(color(fore), color(back), bold, italic, underline) 127 | tokens.each do |t| 128 | @@styles[t] = style 129 | end 130 | end 131 | 132 | def initialize(@scope = ".highlight") 133 | end 134 | 135 | def style_for(token) 136 | self.class.style_for token 137 | end 138 | 139 | def base_style 140 | self.class.base_style 141 | end 142 | 143 | private def style?(token) 144 | self.class.style? token 145 | end 146 | 147 | def to_s(io) 148 | Tokens.each_token do |t| 149 | if style = style?(t) 150 | css_selectors(t).join(", ", io) { |s| io << s } 151 | io << " { " 152 | style.to_s io 153 | io << " }" 154 | io.puts 155 | end 156 | end 157 | end 158 | 159 | private def css_selectors(token) 160 | sub_tokens = [] of String 161 | 162 | token.each_sub_token do |t| 163 | next if style?(t) 164 | sub_tokens.concat css_selectors(t) 165 | end 166 | 167 | [css_selector(token)].concat sub_tokens 168 | end 169 | 170 | private def css_selector(token) 171 | if token == Tokens::Text 172 | @scope 173 | else 174 | "#{@scope} .#{token.short_name}" 175 | end 176 | end 177 | end 178 | -------------------------------------------------------------------------------- /spec/lexers/css/fixtures/at_rules.out: -------------------------------------------------------------------------------- 1 | [#, "@media"] 2 | [#, " "] 3 | [#, "screen"] 4 | [#, " "] 5 | [#, "{"] 6 | [#, "\n "] 7 | [#, "body"] 8 | [#, " "] 9 | [#, "{"] 10 | [#, "\n "] 11 | [#, "background"] 12 | [#, ":"] 13 | [#, " "] 14 | [#, "#ccc"] 15 | [#, ";"] 16 | [#, "\n "] 17 | [#, "}"] 18 | [#, "\n"] 19 | [#, "}"] 20 | [#, "\n\n"] 21 | [#, "@namespace"] 22 | [#, " "] 23 | [#, "\"http://www.w3.org/1999/xhtml\""] 24 | [#, ";"] 25 | [#, "\n\n"] 26 | [#, "@import"] 27 | [#, " "] 28 | [#, "url(\"mystyle.css\")"] 29 | [#, ";"] 30 | [#, "\n\n"] 31 | [#, "@charset"] 32 | [#, " "] 33 | [#, "\"ISO-8859-1\""] 34 | [#, ";"] 35 | [#, "\n\n"] 36 | [#, "@font-face"] 37 | [#, " "] 38 | [#, "{"] 39 | [#, " "] 40 | [#, "font-family"] 41 | [#, ":"] 42 | [#, " "] 43 | [#, "\"Example Font\""] 44 | [#, ";"] 45 | [#, " "] 46 | [#, "src"] 47 | [#, ":"] 48 | [#, " "] 49 | [#, "url(\"http://www.example.com/fonts/example\")"] 50 | [#, ";"] 51 | [#, " "] 52 | [#, "}"] 53 | [#, "\n\n"] 54 | [#, "@media"] 55 | [#, " "] 56 | [#, "screen"] 57 | [#, " "] 58 | [#, "{"] 59 | [#, " "] 60 | [#, "body"] 61 | [#, " "] 62 | [#, "{"] 63 | [#, " "] 64 | [#, "font-size"] 65 | [#, ":"] 66 | [#, " "] 67 | [#, "16px"] 68 | [#, " "] 69 | [#, "}"] 70 | [#, " "] 71 | [#, "}"] 72 | [#, " "] 73 | [#, "@media"] 74 | [#, " "] 75 | [#, "print"] 76 | [#, " "] 77 | [#, "{"] 78 | [#, " "] 79 | [#, "body"] 80 | [#, " "] 81 | [#, "{"] 82 | [#, " "] 83 | [#, "font-size"] 84 | [#, ":"] 85 | [#, " "] 86 | [#, "12pt"] 87 | [#, " "] 88 | [#, "}"] 89 | [#, " "] 90 | [#, "}"] 91 | [#, "\n\n\n"] 92 | [#, "@page"] 93 | [#, " "] 94 | [#, "{"] 95 | [#, " "] 96 | [#, "body"] 97 | [#, " "] 98 | [#, "{"] 99 | [#, " "] 100 | [#, "margin"] 101 | [#, ":"] 102 | [#, " "] 103 | [#, "1in"] 104 | [#, " "] 105 | [#, "1.5in"] 106 | [#, ";"] 107 | [#, " "] 108 | [#, "}"] 109 | [#, " "] 110 | [#, "}"] 111 | [#, "\n\n"] 112 | [#, "@page"] 113 | [#, " "] 114 | [#, "linke-seite"] 115 | [#, ":"] 116 | [#, "left"] 117 | [#, " "] 118 | [#, "{"] 119 | [#, " "] 120 | [#, "body"] 121 | [#, " "] 122 | [#, "{"] 123 | [#, " "] 124 | [#, "margin"] 125 | [#, ":"] 126 | [#, "20mm"] 127 | [#, ";"] 128 | [#, " "] 129 | [#, "margin-right"] 130 | [#, ":"] 131 | [#, "25mm"] 132 | [#, ";"] 133 | [#, " "] 134 | [#, "}"] 135 | [#, " "] 136 | [#, "}"] 137 | [#, "\n\n"] 138 | [#, "@-moz-document"] 139 | [#, " "] 140 | [#, "url-prefix"] 141 | [#, "("] 142 | [#, "http"] 143 | [#, "://"] 144 | [#, "pygments"] 145 | [#, "."] 146 | [#, "org"] 147 | [#, ")"] 148 | [#, " "] 149 | [#, "{"] 150 | [#, " "] 151 | [#, "a"] 152 | [#, " "] 153 | [#, "{"] 154 | [#, "font-style"] 155 | [#, ":"] 156 | [#, " "] 157 | [#, "normal"] 158 | [#, " "] 159 | [#, "!important"] 160 | [#, ";}"] 161 | [#, " "] 162 | [#, "}"] 163 | [#, "\n"] 164 | -------------------------------------------------------------------------------- /src/noir/token.cr: -------------------------------------------------------------------------------- 1 | module Noir 2 | # :nodoc: 3 | TOKEN_NAMES = [] of String 4 | 5 | macro finished 6 | alias Token = {{ TOKEN_NAMES.map { |t| "#{t.id}.class".id }.join(" | ").id }} 7 | end 8 | 9 | module Tokens 10 | def self.each_token 11 | {% for const in @type.constants %} 12 | yield {{const}} 13 | {{const}}.each_sub_token_all do |t| 14 | yield t 15 | end 16 | {% end %} 17 | end 18 | 19 | private macro def_token(name, short_name, parent = "", token_chain = [] of Token) 20 | def_token({{name}}, {{short_name}}, {{parent}}, {{token_chain}}) { } 21 | end 22 | 23 | # :nodoc: 24 | macro def_token(name, short_name, parent = "", token_chain = [] of Token, &block) 25 | {% qualified_name = parent + name.stringify %} 26 | {% ::Noir::TOKEN_NAMES.push "Tokens::#{qualified_name.id}" %} 27 | 28 | {% 29 | token_chain = token_chain 30 | .join(",") 31 | .split(",") 32 | .select { |x| !x.empty? } 33 | .map(&.id) 34 | %} 35 | {% token_chain.unshift "Tokens::#{qualified_name.id}.as(Token)".id %} 36 | 37 | module {{name}} 38 | def self.name 39 | {{name}} 40 | end 41 | 42 | def self.short_name 43 | {{short_name}} 44 | end 45 | 46 | def self.qualified_name 47 | {{qualified_name}} 48 | end 49 | 50 | def self.token_chain : Array(Token) 51 | {{token_chain}} 52 | end 53 | 54 | def self.each_sub_token 55 | \{% for const in @type.constants %} 56 | yield \{{const}} 57 | \{% end %} 58 | end 59 | 60 | def self.each_sub_token_all 61 | each_sub_token do |t| 62 | yield t 63 | t.each_sub_token_all do |t| 64 | yield t 65 | end 66 | end 67 | end 68 | 69 | def self.to_s(io) 70 | io << "#" 71 | end 72 | 73 | private macro def_token(name, short_name) 74 | def_token(\{{name}}, \{{short_name}}) { } 75 | end 76 | 77 | private macro def_token(name, short_name, &block) 78 | ::Noir::Tokens.def_token(\{{name}}, \{{short_name}}, {{"#{qualified_name.id}::"}}, {{token_chain}}) do 79 | \{{yield}} 80 | end 81 | end 82 | 83 | {{yield}} 84 | end 85 | end 86 | 87 | def_token Text, "" do 88 | def_token Whitespace, "w" 89 | end 90 | 91 | def_token Error, "err" 92 | def_token Other, "x" 93 | 94 | def_token Keyword, "k" do 95 | def_token Constant, "kc" 96 | def_token Declaration, "kd" 97 | def_token Namespace, "kn" 98 | def_token Pseudo, "kp" 99 | def_token Reserved, "kr" 100 | def_token Type, "kt" 101 | def_token Variable, "kv" 102 | end 103 | 104 | def_token Name, "n" do 105 | def_token Attribute, "na" 106 | def_token Builtin, "nb" do 107 | def_token Pseudo, "bp" 108 | end 109 | def_token Class, "nc" 110 | def_token Constant, "no" 111 | def_token Decorator, "nd" 112 | def_token Entity, "ni" 113 | def_token Exception, "ne" 114 | def_token Function, "nf" 115 | def_token Property, "py" 116 | def_token Label, "nl" 117 | def_token Namespace, "nn" 118 | def_token Other, "nx" 119 | def_token Tag, "nt" 120 | def_token Variable, "nv" do 121 | def_token Class, "vc" 122 | def_token Global, "vg" 123 | def_token Instance, "vi" 124 | end 125 | end 126 | 127 | def_token Literal, "l" do 128 | def_token Date, "ld" 129 | 130 | def_token String, "s" do 131 | def_token Backtick, "sb" 132 | def_token Char, "sc" 133 | def_token Doc, "sd" 134 | def_token Double, "s2" 135 | def_token Escape, "se" 136 | def_token Heredoc, "sh" 137 | def_token Interpol, "si" 138 | def_token Other, "sx" 139 | def_token Regex, "sr" 140 | def_token Single, "s1" 141 | def_token Symbol, "ss" 142 | end 143 | 144 | def_token Number, "m" do 145 | def_token Float, "mf" 146 | def_token Hex, "mh" 147 | def_token Integer, "mi" do 148 | def_token Long, "il" 149 | end 150 | def_token Oct, "mo" 151 | def_token Bin, "mb" 152 | def_token Other, "mx" 153 | end 154 | end 155 | 156 | def_token Operator, "o" do 157 | def_token Word, "ow" 158 | end 159 | 160 | def_token Punctuation, "p" do 161 | def_token Indicator, "pi" 162 | end 163 | 164 | def_token Comment, "c" do 165 | def_token Doc, "cd" 166 | def_token Multiline, "cm" 167 | def_token Preproc, "cp" 168 | def_token Single, "c1" 169 | def_token Special, "cs" 170 | end 171 | 172 | def_token Generic, "g" do 173 | def_token Deleted, "gd" 174 | def_token Emph, "ge" 175 | def_token Error, "gr" 176 | def_token Heading, "gh" 177 | def_token Inserted, "gi" 178 | def_token Output, "go" 179 | def_token Prompt, "gp" 180 | def_token Strong, "gs" 181 | def_token Subheading, "gu" 182 | def_token Traceback, "gt" 183 | def_token Lineno, "gl" 184 | end 185 | 186 | # convenience 187 | 188 | alias Num = Literal::Number 189 | alias Str = Literal::String 190 | end 191 | end 192 | -------------------------------------------------------------------------------- /spec/lexers/javascript/fixtures/syntax.out: -------------------------------------------------------------------------------- 1 | [#, "function"] 2 | [#, " "] 3 | [#, "foo"] 4 | [#, "()"] 5 | [#, " "] 6 | [#, "{"] 7 | [#, "\n "] 8 | [#, "return"] 9 | [#, " "] 10 | [#, "42"] 11 | [#, ";"] 12 | [#, "\n"] 13 | [#, "}"] 14 | [#, "\n\n"] 15 | [#, "if"] 16 | [#, " "] 17 | [#, "("] 18 | [#, "x"] 19 | [#, " "] 20 | [#, "==="] 21 | [#, " "] 22 | [#, "y"] 23 | [#, ")"] 24 | [#, " "] 25 | [#, "{"] 26 | [#, "\n "] 27 | [#, "console"] 28 | [#, "."] 29 | [#, "log"] 30 | [#, "("] 31 | [#, "x"] 32 | [#, ");"] 33 | [#, "\n"] 34 | [#, "}"] 35 | [#, " "] 36 | [#, "else"] 37 | [#, " "] 38 | [#, "{"] 39 | [#, "\n "] 40 | [#, "console"] 41 | [#, "."] 42 | [#, "log"] 43 | [#, "("] 44 | [#, "y"] 45 | [#, ");"] 46 | [#, "\n"] 47 | [#, "}"] 48 | [#, "\n\n"] 49 | [#, "var"] 50 | [#, " "] 51 | [#, "bar"] 52 | [#, ";"] 53 | [#, "\n\n"] 54 | [#, "for"] 55 | [#, " "] 56 | [#, "("] 57 | [#, "let"] 58 | [#, " "] 59 | [#, "i"] 60 | [#, " "] 61 | [#, "="] 62 | [#, " "] 63 | [#, "0"] 64 | [#, ";"] 65 | [#, " "] 66 | [#, "i"] 67 | [#, " "] 68 | [#, "<"] 69 | [#, " "] 70 | [#, "100"] 71 | [#, ";"] 72 | [#, " "] 73 | [#, "i"] 74 | [#, "++"] 75 | [#, ")"] 76 | [#, " "] 77 | [#, "{"] 78 | [#, "\n "] 79 | [#, "console"] 80 | [#, "."] 81 | [#, "log"] 82 | [#, "("] 83 | [#, "bar"] 84 | [#, ");"] 85 | [#, "\n"] 86 | [#, "}"] 87 | [#, "\n\n"] 88 | [#, "class"] 89 | [#, " "] 90 | [#, "Foo"] 91 | [#, " "] 92 | [#, "{"] 93 | [#, "\n "] 94 | [#, "constructor"] 95 | [#, "("] 96 | [#, "x"] 97 | [#, ","] 98 | [#, " "] 99 | [#, "y"] 100 | [#, ")"] 101 | [#, " "] 102 | [#, "{"] 103 | [#, "\n "] 104 | [#, "this"] 105 | [#, "."] 106 | [#, "x"] 107 | [#, " "] 108 | [#, "="] 109 | [#, " "] 110 | [#, "x"] 111 | [#, ";"] 112 | [#, "\n "] 113 | [#, "this"] 114 | [#, "."] 115 | [#, "y"] 116 | [#, " "] 117 | [#, "="] 118 | [#, " "] 119 | [#, "y"] 120 | [#, ";"] 121 | [#, "\n "] 122 | [#, "}"] 123 | [#, "\n"] 124 | [#, "}"] 125 | [#, "\n\n"] 126 | [#, "class"] 127 | [#, " "] 128 | [#, "Bar"] 129 | [#, " "] 130 | [#, "extends"] 131 | [#, " "] 132 | [#, "Foo"] 133 | [#, " "] 134 | [#, "{"] 135 | [#, "\n "] 136 | [#, "get"] 137 | [#, " "] 138 | [#, "z"] 139 | [#, "()"] 140 | [#, " "] 141 | [#, "{"] 142 | [#, "\n "] 143 | [#, "return"] 144 | [#, " "] 145 | [#, "this"] 146 | [#, "."] 147 | [#, "x"] 148 | [#, " "] 149 | [#, "+"] 150 | [#, " "] 151 | [#, "this"] 152 | [#, "."] 153 | [#, "y"] 154 | [#, ";"] 155 | [#, "\n "] 156 | [#, "}"] 157 | [#, "\n"] 158 | [#, "}"] 159 | [#, "\n\n"] 160 | [#, "const"] 161 | [#, " "] 162 | [#, "bar"] 163 | [#, " "] 164 | [#, "="] 165 | [#, " "] 166 | [#, "new"] 167 | [#, " "] 168 | [#, "Bar"] 169 | [#, "("] 170 | [#, "1"] 171 | [#, ","] 172 | [#, " "] 173 | [#, "2"] 174 | [#, ");"] 175 | [#, "\n"] 176 | -------------------------------------------------------------------------------- /src/noir/lexers/python.cr: -------------------------------------------------------------------------------- 1 | require "../lexer" 2 | 3 | class Noir::Lexers::Python < Noir::Lexer 4 | tag "python" 5 | aliases %w(py) 6 | filenames %w( 7 | *.py *.pyw 8 | *.sc SConstruct SConscript *.tac 9 | ) 10 | mimetypes %w( 11 | text/x-python application/x-python 12 | ) 13 | 14 | KEYWORDS = Set.new %w( 15 | assert break continue del elif else except exec 16 | finally for global if lambda pass print raise 17 | return try while yield as with from import yield 18 | async await 19 | ) 20 | 21 | BUILTINS = Set.new %w( 22 | __import__ abs all any apply basestring bin bool buffer 23 | bytearray bytes callable chr classmethod cmp coerce compile 24 | complex delattr dict dir divmod enumerate eval execfile exit 25 | file filter float frozenset getattr globals hasattr hash hex id 26 | input int intern isinstance issubclass iter len list locals 27 | long map max min next object oct open ord pow property range 28 | raw_input reduce reload repr reversed round set setattr slice 29 | sorted staticmethod str sum super tuple type unichr unicode 30 | vars xrange zip 31 | ) 32 | 33 | BUILTINS_PSEUDO = Set.new %w(self None Ellipsis NotImplemented False True) 34 | 35 | EXCEPTIONS = Set.new %w( 36 | ArithmeticError AssertionError AttributeError 37 | BaseException DeprecationWarning EOFError EnvironmentError 38 | Exception FloatingPointError FutureWarning GeneratorExit IOError 39 | ImportError ImportWarning IndentationError IndexError KeyError 40 | KeyboardInterrupt LookupError MemoryError NameError 41 | NotImplemented NotImplementedError OSError OverflowError 42 | OverflowWarning PendingDeprecationWarning ReferenceError 43 | RuntimeError RuntimeWarning StandardError StopIteration 44 | SyntaxError SyntaxWarning SystemError SystemExit TabError 45 | TypeError UnboundLocalError UnicodeDecodeError 46 | UnicodeEncodeError UnicodeError UnicodeTranslateError 47 | UnicodeWarning UserWarning ValueError VMSError Warning 48 | WindowsError ZeroDivisionError 49 | ) 50 | 51 | IDENTIFIER = /[a-z_][a-z0-9_]*/i 52 | DOTTED_IDENTIFIER = /[a-z_.][a-z0-9_.]*/i 53 | 54 | state :root do 55 | rule /\n+/m, Text 56 | rule /^(:)(\s*)([ru]{,2}""".*?""")/mi, &.groups Punctuation, Text, Str::Doc 57 | 58 | rule /[^\S\n]+/, Text 59 | rule /#.*?$/m, Comment 60 | rule /[\[\]{}:(),;]/, Punctuation 61 | rule /\\\n/, Text 62 | rule /\\/, Text 63 | 64 | rule /(in|is|and|or|not)\b/, Operator::Word 65 | rule /!=|==|<<|>>|[-~+\/*%=<>&^|.]/, Operator 66 | 67 | rule /(from)((?:\\\s|\s)+)(#{DOTTED_IDENTIFIER})((?:\\\s|\s)+)(import)/, &.groups Keyword::Namespace, 68 | Text, 69 | Name::Namespace, 70 | Text, 71 | Keyword::Namespace 72 | 73 | rule /(import)(\s+)(#{DOTTED_IDENTIFIER})/, &.groups Keyword::Namespace, Text, Name::Namespace 74 | 75 | rule /(def)((?:\s|\\\s)+)/ do |m| 76 | m.groups Keyword, Text 77 | m.push :funcname 78 | end 79 | 80 | rule /(class)((?:\s|\\\s)+)/ do |m| 81 | m.groups Keyword, Text 82 | m.push :classname 83 | end 84 | 85 | # TODO: not in python 3 86 | rule /`.*?`/, Str::Backtick 87 | rule /(?:r|ur|ru)"""/i, Str, :raw_tdqs 88 | rule /(?:r|ur|ru)'''/i, Str, :raw_tsqs 89 | rule /(?:r|ur|ru)"/i, Str, :raw_dqs 90 | rule /(?:r|ur|ru)'/i, Str, :raw_sqs 91 | rule /u?"""/i, Str, :tdqs 92 | rule /u?'''/i, Str, :tsqs 93 | rule /u?"/i, Str, :dqs 94 | rule /u?'/i, Str, :sqs 95 | 96 | rule /@#{DOTTED_IDENTIFIER}/i, Name::Decorator 97 | 98 | # using negative lookbehind so we don't match property names 99 | rule /(?, "body"] 2 | [#, " "] 3 | [#, "{"] 4 | [#, "\n "] 5 | [#, "font-size"] 6 | [#, ":"] 7 | [#, " "] 8 | [#, "12pt"] 9 | [#, ";"] 10 | [#, "\n "] 11 | [#, "background"] 12 | [#, " "] 13 | [#, ":"] 14 | [#, " "] 15 | [#, "#fff"] 16 | [#, " "] 17 | [#, "url(temp.png)"] 18 | [#, " "] 19 | [#, "top"] 20 | [#, " "] 21 | [#, "left"] 22 | [#, " "] 23 | [#, "no-repeat"] 24 | [#, ";"] 25 | [#, "\n "] 26 | [#, "width"] 27 | [#, ":"] 28 | [#, " "] 29 | [#, "75%"] 30 | [#, ";"] 31 | [#, "\n"] 32 | [#, "}"] 33 | [#, "\n\n"] 34 | [#, "*"] 35 | [#, " "] 36 | [#, "html"] 37 | [#, " "] 38 | [#, "body"] 39 | [#, " "] 40 | [#, "{"] 41 | [#, "\n "] 42 | [#, "font-size"] 43 | [#, ":"] 44 | [#, " "] 45 | [#, "14pt"] 46 | [#, ";"] 47 | [#, "\n"] 48 | [#, "}"] 49 | [#, "\n\n"] 50 | [#, "#nav"] 51 | [#, " "] 52 | [#, ".new"] 53 | [#, " "] 54 | [#, "{"] 55 | [#, "\n "] 56 | [#, "display"] 57 | [#, ":"] 58 | [#, " "] 59 | [#, "block"] 60 | [#, ";"] 61 | [#, "\n"] 62 | [#, "}"] 63 | [#, "\n\n"] 64 | [#, "ul"] 65 | [#, "#nav"] 66 | [#, " "] 67 | [#, "li"] 68 | [#, ".new"] 69 | [#, " "] 70 | [#, "{"] 71 | [#, "\n "] 72 | [#, "font-weight"] 73 | [#, ":"] 74 | [#, " "] 75 | [#, "bold"] 76 | [#, ";"] 77 | [#, "\n"] 78 | [#, "}"] 79 | [#, "\n\n"] 80 | [#, ":link"] 81 | [#, " "] 82 | [#, "{"] 83 | [#, "\n "] 84 | [#, "color"] 85 | [#, ":"] 86 | [#, " "] 87 | [#, "#f00"] 88 | [#, ";"] 89 | [#, "\n"] 90 | [#, "}"] 91 | [#, "\n\n"] 92 | [#, ":link:hover"] 93 | [#, " "] 94 | [#, "{"] 95 | [#, "\n "] 96 | [#, "color"] 97 | [#, ":"] 98 | [#, " "] 99 | [#, "#0f0"] 100 | [#, ";"] 101 | [#, "\n"] 102 | [#, "}"] 103 | [#, "\n\n"] 104 | [#, "*"] 105 | [#, "::before"] 106 | [#, " "] 107 | [#, "{"] 108 | [#, "\n "] 109 | [#, "content"] 110 | [#, ":"] 111 | [#, " "] 112 | [#, "\"text\""] 113 | [#, ";"] 114 | [#, "\n"] 115 | [#, "}"] 116 | [#, "\n\n"] 117 | [#, ":first-child::after"] 118 | [#, " "] 119 | [#, "{"] 120 | [#, "\n "] 121 | [#, "display"] 122 | [#, ":"] 123 | [#, " "] 124 | [#, "none"] 125 | [#, ";"] 126 | [#, "\n"] 127 | [#, "}"] 128 | [#, "\n\n"] 129 | [#, ".class"] 130 | [#, ":first-child:focus::after"] 131 | [#, " "] 132 | [#, "{"] 133 | [#, "\n "] 134 | [#, "display"] 135 | [#, ":"] 136 | [#, " "] 137 | [#, "none"] 138 | [#, ";"] 139 | [#, "\n"] 140 | [#, "}"] 141 | [#, "\n\n"] 142 | [#, "#thing"] 143 | [#, " "] 144 | [#, "{"] 145 | [#, "\n "] 146 | [#, "font"] 147 | [#, ":"] 148 | [#, " "] 149 | [#, "normal"] 150 | [#, " "] 151 | [#, "400"] 152 | [#, " "] 153 | [#, "12px"] 154 | [#, "/"] 155 | [#, "35px"] 156 | [#, " "] 157 | [#, "Helvetica"] 158 | [#, ","] 159 | [#, " "] 160 | [#, "Arial"] 161 | [#, ","] 162 | [#, " "] 163 | [#, "sans-serif"] 164 | [#, ";"] 165 | [#, "\n"] 166 | [#, "}"] 167 | [#, "\n\n"] 168 | [#, "#foo"] 169 | [#, " "] 170 | [#, "{"] 171 | [#, "\n "] 172 | [#, "unrecognized-prop"] 173 | [#, ":"] 174 | [#, " "] 175 | [#, "1"] 176 | [#, ";"] 177 | [#, "\n "] 178 | [#, "-moz-prefixed-prop"] 179 | [#, ":"] 180 | [#, " "] 181 | [#, "2"] 182 | [#, ";"] 183 | [#, "\n"] 184 | [#, "}"] 185 | [#, "\n\n"] 186 | [#, "a"] 187 | [#, "["] 188 | [#, "target"] 189 | [#, "="] 190 | [#, "\"_blank\""] 191 | [#, "]"] 192 | [#, " "] 193 | [#, "{"] 194 | [#, "\n "] 195 | [#, "background-color"] 196 | [#, ":"] 197 | [#, " "] 198 | [#, "yellow"] 199 | [#, ";"] 200 | [#, "\n"] 201 | [#, "}"] 202 | [#, "\n"] 203 | -------------------------------------------------------------------------------- /src/noir/lexers/javascript.cr: -------------------------------------------------------------------------------- 1 | require "../lexer" 2 | 3 | class Noir::Lexers::JavaScript < Noir::Lexer 4 | tag "javascript" 5 | aliases %w(js) 6 | filenames %w(*.js) 7 | mimetypes %w( 8 | application/javascript application/x-javascript 9 | text/javascript text/x-javascript 10 | ) 11 | 12 | state :multiline_comment do 13 | rule %r([*]/), Comment::Multiline, :pop! 14 | rule %r([^*/]+), Comment::Multiline 15 | rule %r([*/]), Comment::Multiline 16 | end 17 | 18 | state :comments_and_whitespace do 19 | rule /\s+/, Text 20 | rule /