rouge 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/bin/rougify +13 -0
- data/lib/rouge.rb +1 -0
- data/lib/rouge/cli.rb +77 -0
- data/lib/rouge/lexer.rb +58 -6
- data/lib/rouge/lexers/css.rb +3 -2
- data/lib/rouge/lexers/diff.rb +10 -2
- data/lib/rouge/lexers/html.rb +9 -3
- data/lib/rouge/lexers/javascript.rb +66 -2
- data/lib/rouge/lexers/python.rb +8 -2
- data/lib/rouge/lexers/shell.rb +7 -2
- data/lib/rouge/lexers/tcl.rb +9 -1
- data/lib/rouge/lexers/text.rb +2 -0
- data/lib/rouge/text_analyzer.rb +37 -0
- data/lib/rouge/theme.rb +1 -1
- data/lib/rouge/themes/thankful_eyes.rb +1 -5
- data/lib/rouge/version.rb +1 -1
- metadata +24 -4
data/bin/rougify
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
ROOT_DIR = Pathname.new(__FILE__).dirname.parent
|
5
|
+
load ROOT_DIR.join('lib/rouge.rb')
|
6
|
+
load ROOT_DIR.join('lib/rouge/cli.rb')
|
7
|
+
|
8
|
+
begin
|
9
|
+
Rouge::CLI.start
|
10
|
+
rescue => e
|
11
|
+
$stderr.puts e.message
|
12
|
+
exit 1
|
13
|
+
end
|
data/lib/rouge.rb
CHANGED
data/lib/rouge/cli.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# not required by the main lib.
|
2
|
+
# to use this module, require 'rouge/cli'.
|
3
|
+
|
4
|
+
# stdlib
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
# gems
|
8
|
+
require 'thor'
|
9
|
+
|
10
|
+
module Rouge
|
11
|
+
class CLI < Thor
|
12
|
+
default_task :highlight
|
13
|
+
|
14
|
+
def self.start(argv=ARGV, *a)
|
15
|
+
unless %w(highlight style).include?(argv.first)
|
16
|
+
argv.unshift 'highlight'
|
17
|
+
end
|
18
|
+
|
19
|
+
super(argv, *a)
|
20
|
+
end
|
21
|
+
|
22
|
+
desc 'highlight [FILE]', 'highlight some code'
|
23
|
+
option :file, :aliases => '-f', :desc => 'the file to operate on'
|
24
|
+
option :lexer, :aliases => '-l',
|
25
|
+
:desc => ('Which lexer to use. If not provided, rougify will try to ' +
|
26
|
+
'guess based on --mimetype, the filename, and the file ' +
|
27
|
+
'contents.')
|
28
|
+
option :mimetype, :aliases => '-m',
|
29
|
+
:desc => ('a mimetype that Rouge will use to guess the correct lexer. ' +
|
30
|
+
'This is ignored if --lexer is specified.')
|
31
|
+
option :lexer_opts, :aliases => '-L', :type => :hash, :default => {},
|
32
|
+
:desc => ('a hash of options to pass to the lexer.')
|
33
|
+
option :formatter_opts, :aliases => '-F', :type => :hash, :default => {},
|
34
|
+
:desc => ('a hash of options to pass to the formatter.')
|
35
|
+
def highlight(file=nil)
|
36
|
+
filename = options[:file] || file
|
37
|
+
source = filename ? File.read(filename) : $stdin.read
|
38
|
+
|
39
|
+
if options[:lexer].nil?
|
40
|
+
lexer_class = Lexer.guess(
|
41
|
+
:filename => filename,
|
42
|
+
:mimetype => options[:mimetype],
|
43
|
+
:source => source,
|
44
|
+
)
|
45
|
+
else
|
46
|
+
lexer_class = Lexer.find(options[:lexer])
|
47
|
+
raise "unknown lexer: #{options[:lexer]}" unless lexer_class
|
48
|
+
end
|
49
|
+
|
50
|
+
# only HTML is supported for now
|
51
|
+
formatter = Formatters::HTML.new(normalize_hash_keys(options[:formatter_opts]))
|
52
|
+
lexer = lexer_class.new(normalize_hash_keys(options[:lexer_opts]))
|
53
|
+
|
54
|
+
puts Rouge.highlight(source, lexer, formatter)
|
55
|
+
end
|
56
|
+
|
57
|
+
desc 'style THEME', 'render THEME as css'
|
58
|
+
def style(theme_name='thankful_eyes')
|
59
|
+
theme = Theme.find(theme_name)
|
60
|
+
raise "unknown theme: #{theme_name}" unless theme
|
61
|
+
|
62
|
+
puts theme.new(options).render
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
# TODO: does Thor do this for me?
|
67
|
+
def normalize_hash_keys(hash)
|
68
|
+
out = {}
|
69
|
+
hash.each do |k, v|
|
70
|
+
new_key = k.tr('-', '_').to_sym
|
71
|
+
out[new_key] = v
|
72
|
+
end
|
73
|
+
|
74
|
+
out
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/rouge/lexer.rb
CHANGED
@@ -41,6 +41,53 @@ module Rouge
|
|
41
41
|
registry[name.to_s]
|
42
42
|
end
|
43
43
|
|
44
|
+
def guess(info={})
|
45
|
+
by_mimetype = guess_by_mimetype(info[:mimetype]) if info[:mimetype]
|
46
|
+
return by_mimetype if by_mimetype
|
47
|
+
|
48
|
+
by_filename = guess_by_filename(info[:filename]) if info[:filename]
|
49
|
+
return by_filename if by_filename
|
50
|
+
|
51
|
+
by_source = guess_by_source(info[:source]) if info[:source]
|
52
|
+
return by_source if by_source
|
53
|
+
|
54
|
+
# guessing failed, just parse it as text
|
55
|
+
return Lexers::Text
|
56
|
+
end
|
57
|
+
|
58
|
+
def guess_by_mimetype(mt)
|
59
|
+
registry.values.detect do |lexer|
|
60
|
+
lexer.mimetypes.include? mt
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def guess_by_filename(fname)
|
65
|
+
fname = File.basename(fname)
|
66
|
+
registry.values.detect do |lexer|
|
67
|
+
lexer.filenames.any? do |pattern|
|
68
|
+
File.fnmatch?(pattern, fname)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def guess_by_source(source)
|
74
|
+
source = TextAnalyzer.new(source)
|
75
|
+
|
76
|
+
best_result = 0
|
77
|
+
best_match = nil
|
78
|
+
registry.values.each do |lexer|
|
79
|
+
result = lexer.analyze_text(source) || 0
|
80
|
+
return lexer if result == 1
|
81
|
+
|
82
|
+
if result > best_result
|
83
|
+
best_match = lexer
|
84
|
+
best_result = result
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
best_match
|
89
|
+
end
|
90
|
+
|
44
91
|
def register(name, lexer)
|
45
92
|
registry[name.to_s] = lexer
|
46
93
|
end
|
@@ -56,14 +103,12 @@ module Rouge
|
|
56
103
|
args.each { |arg| Lexer.register(arg, self) }
|
57
104
|
end
|
58
105
|
|
59
|
-
def
|
60
|
-
|
61
|
-
Lexer.extension_registry[ext] = self
|
62
|
-
end
|
106
|
+
def filenames(*fnames)
|
107
|
+
(@filenames ||= []).concat(fnames)
|
63
108
|
end
|
64
109
|
|
65
|
-
def
|
66
|
-
@
|
110
|
+
def mimetypes(*mts)
|
111
|
+
(@mimetypes ||= []).concat(mts)
|
67
112
|
end
|
68
113
|
|
69
114
|
private
|
@@ -125,6 +170,13 @@ module Rouge
|
|
125
170
|
def stream_tokens(stream, &b)
|
126
171
|
raise 'abstract'
|
127
172
|
end
|
173
|
+
|
174
|
+
# return a number between 0 and 1 indicating the
|
175
|
+
# likelihood that the text given should be lexed
|
176
|
+
# with this lexer.
|
177
|
+
def self.analyze_text(text)
|
178
|
+
0
|
179
|
+
end
|
128
180
|
end
|
129
181
|
|
130
182
|
class RegexLexer < Lexer
|
data/lib/rouge/lexers/css.rb
CHANGED
data/lib/rouge/lexers/diff.rb
CHANGED
@@ -2,8 +2,16 @@ module Rouge
|
|
2
2
|
module Lexers
|
3
3
|
class Diff < RegexLexer
|
4
4
|
tag 'diff'
|
5
|
-
aliases 'patch'
|
6
|
-
|
5
|
+
aliases 'patch', 'udiff'
|
6
|
+
filenames '*.diff', '*.patch'
|
7
|
+
mimetypes 'text/x-diff', 'text/x-patch'
|
8
|
+
|
9
|
+
def self.analyze_text(text)
|
10
|
+
return 1 if text.start_with?('Index: ')
|
11
|
+
return 1 if text.start_with?('diff ')
|
12
|
+
|
13
|
+
return 0.9 if text =~ /\A---.*?\n\+\+\+/m
|
14
|
+
end
|
7
15
|
|
8
16
|
state :header do
|
9
17
|
rule /^diff .*?\n(?=---|\+\+\+)/m, 'Generic.Heading'
|
data/lib/rouge/lexers/html.rb
CHANGED
@@ -2,7 +2,13 @@ module Rouge
|
|
2
2
|
module Lexers
|
3
3
|
class HTML < RegexLexer
|
4
4
|
tag 'html'
|
5
|
-
|
5
|
+
filenames '*.htm', '*.html', '*.xhtml', '*.xslt'
|
6
|
+
mimetypes 'text/html', 'application/xhtml+xml'
|
7
|
+
|
8
|
+
def self.analyze_text(text)
|
9
|
+
return 1 if text.doctype?(/\bhtml\b/i)
|
10
|
+
return 1 if text =~ /<\s*html\b/
|
11
|
+
end
|
6
12
|
|
7
13
|
state :root do
|
8
14
|
rule /[^<&]+/m, 'Text'
|
@@ -50,14 +56,14 @@ module Rouge
|
|
50
56
|
state :script_content do
|
51
57
|
rule %r(<\s*/\s*script\s*>)m, 'Name.Tag', :pop!
|
52
58
|
rule %r(.*?(?=<\s*/\s*script\s*>))m do
|
53
|
-
delegate
|
59
|
+
delegate Javascript
|
54
60
|
end
|
55
61
|
end
|
56
62
|
|
57
63
|
state :style_content do
|
58
64
|
rule %r(<\s*/\s*style\s*>)m, 'Name.Tag', :pop!
|
59
65
|
rule %r(.*(?=<\s*/\s*style\s*>))m do
|
60
|
-
delegate
|
66
|
+
delegate CSS
|
61
67
|
end
|
62
68
|
end
|
63
69
|
end
|
@@ -1,9 +1,17 @@
|
|
1
1
|
module Rouge
|
2
2
|
module Lexers
|
3
|
-
class
|
3
|
+
class Javascript < RegexLexer
|
4
4
|
tag 'javascript'
|
5
5
|
aliases 'js'
|
6
|
-
|
6
|
+
filenames '*.js'
|
7
|
+
mimetypes 'application/javascript', 'application/x-javascript',
|
8
|
+
'text/javascript', 'text/x-javascript'
|
9
|
+
|
10
|
+
def self.analyze_text(text)
|
11
|
+
return 1 if text.shebang?('node')
|
12
|
+
return 1 if text.shebang?('jsc')
|
13
|
+
# TODO: rhino, spidermonkey, etc
|
14
|
+
end
|
7
15
|
|
8
16
|
state :comments_and_whitespace do
|
9
17
|
rule /\s+/, 'Text'
|
@@ -61,6 +69,7 @@ module Rouge
|
|
61
69
|
).join('|')
|
62
70
|
|
63
71
|
state :root do
|
72
|
+
rule /\A\s*#!.*?\n/m, 'Comment.Preproc'
|
64
73
|
rule %r(^(?=\s|/|<!--)), 'Text', :slash_starts_regex
|
65
74
|
mixin :comments_and_whitespace
|
66
75
|
rule %r(\+\+ | -- | ~ | && | \|\| | \\(?=\n) | << | >>>? | ===
|
@@ -83,5 +92,60 @@ module Rouge
|
|
83
92
|
rule /'(\\\\|\\'|[^'])*'/, 'Literal.String.Single'
|
84
93
|
end
|
85
94
|
end
|
95
|
+
|
96
|
+
class JSON < RegexLexer
|
97
|
+
tag 'json'
|
98
|
+
filenames '*.json'
|
99
|
+
mimetypes 'application/json'
|
100
|
+
|
101
|
+
# TODO: is this too much of a performance hit? JSON is quite simple,
|
102
|
+
# so I'd think this wouldn't be too bad, but for large documents this
|
103
|
+
# could mean doing two full lexes.
|
104
|
+
def self.analyze_text(text)
|
105
|
+
text.lexes_cleanly?(self) ? 0.8 : 0
|
106
|
+
end
|
107
|
+
|
108
|
+
state :root do
|
109
|
+
mixin :whitespace
|
110
|
+
# special case for empty objects
|
111
|
+
rule /(\{)(\s*)(\})/ do
|
112
|
+
group 'Punctuation'
|
113
|
+
group 'Text.Whitespace'
|
114
|
+
group 'Punctuation'
|
115
|
+
end
|
116
|
+
rule /{/, 'Punctuation', :object_key
|
117
|
+
rule /\[/, 'Punctuation', :array
|
118
|
+
rule /-?(?:0|[1-9]\d*)\.\d+(?:e[+-]\d+)?/i, 'Literal.Number.Float'
|
119
|
+
rule /-?(?:0|[1-9]\d*)(?:e[+-]\d+)?/i, 'Literal.Number.Integer'
|
120
|
+
mixin :has_string
|
121
|
+
end
|
122
|
+
|
123
|
+
state :whitespace do
|
124
|
+
rule /\s+/m, 'Text.Whitespace'
|
125
|
+
end
|
126
|
+
|
127
|
+
state :has_string do
|
128
|
+
rule /"(\\.|[^"])*"/, 'Literal.String.Double'
|
129
|
+
end
|
130
|
+
|
131
|
+
state :object_key do
|
132
|
+
mixin :whitespace
|
133
|
+
rule /:/, 'Punctuation', :object_val
|
134
|
+
rule /}/, 'Error', :pop!
|
135
|
+
mixin :has_string
|
136
|
+
end
|
137
|
+
|
138
|
+
state :object_val do
|
139
|
+
rule /,/, 'Punctuation', :pop!
|
140
|
+
rule(/}/) { token 'Punctuation'; pop!; pop! }
|
141
|
+
mixin :root
|
142
|
+
end
|
143
|
+
|
144
|
+
state :array do
|
145
|
+
rule /\]/, 'Punctuation', :pop!
|
146
|
+
rule /,/, 'Punctuation'
|
147
|
+
mixin :root
|
148
|
+
end
|
149
|
+
end
|
86
150
|
end
|
87
151
|
end
|
data/lib/rouge/lexers/python.rb
CHANGED
@@ -3,7 +3,12 @@ module Rouge
|
|
3
3
|
class Python < RegexLexer
|
4
4
|
tag 'python'
|
5
5
|
aliases 'py'
|
6
|
-
|
6
|
+
filenames '*.py', '*.pyw', '*.sc', 'SConstruct', 'SConscript', '*.tac'
|
7
|
+
mimetypes 'text/x-python', 'application/x-python'
|
8
|
+
|
9
|
+
def self.analyze_text(text)
|
10
|
+
return 1 if text.shebang?(/pythonw?(3|2(\.\d)?)?/)
|
11
|
+
end
|
7
12
|
|
8
13
|
keywords = %w(
|
9
14
|
assert break continue del elif else except exec
|
@@ -45,7 +50,8 @@ module Rouge
|
|
45
50
|
dotted_identifier = /[a-z_.][a-z0-9_.]*/i
|
46
51
|
state :root do
|
47
52
|
rule /\n+/m, 'Text'
|
48
|
-
rule /^(\s*)([
|
53
|
+
rule /^(:)(\s*)([ru]{,2}""".*?""")/mi do
|
54
|
+
group 'Punctuation'
|
49
55
|
group 'Text'
|
50
56
|
group 'Literal.String.Doc'
|
51
57
|
end
|
data/lib/rouge/lexers/shell.rb
CHANGED
@@ -1,9 +1,14 @@
|
|
1
1
|
module Rouge
|
2
2
|
module Lexers
|
3
|
-
class
|
3
|
+
class Shell < RegexLexer
|
4
4
|
tag 'shell'
|
5
5
|
aliases 'bash', 'zsh', 'ksh', 'sh'
|
6
|
-
|
6
|
+
filenames '*.sh', '*.bash', '*.zsh', '*.ksh'
|
7
|
+
mimetypes 'application/x-sh', 'application/x-shellscript'
|
8
|
+
|
9
|
+
def self.analyze_text(text)
|
10
|
+
text.shebang?(/(ba|z|k)?sh/) ? 1 : 0
|
11
|
+
end
|
7
12
|
|
8
13
|
KEYWORDS = %w(
|
9
14
|
if fi else while do done for then return function
|
data/lib/rouge/lexers/tcl.rb
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
module Rouge
|
2
2
|
module Lexers
|
3
|
-
class
|
3
|
+
class TCL < RegexLexer
|
4
4
|
tag 'tcl'
|
5
|
+
filenames '*.tcl'
|
6
|
+
mimetypes 'text/x-tcl', 'text/x-script.tcl', 'application/x-tcl'
|
7
|
+
|
8
|
+
def self.analyze_text(text)
|
9
|
+
return 1 if text.shebang? 'tclsh'
|
10
|
+
return 1 if text.shebang? 'wish'
|
11
|
+
return 1 if text.shebang? 'jimsh'
|
12
|
+
end
|
5
13
|
|
6
14
|
KEYWORDS = %w(
|
7
15
|
after apply array break catch continue elseif else error
|
data/lib/rouge/lexers/text.rb
CHANGED
@@ -0,0 +1,37 @@
|
|
1
|
+
module Rouge
|
2
|
+
class TextAnalyzer < String
|
3
|
+
def shebang
|
4
|
+
return @shebang if instance_variable_defined? :@shebang
|
5
|
+
|
6
|
+
self =~ /\A\s*#!(.*)$/
|
7
|
+
@shebang = $1
|
8
|
+
end
|
9
|
+
|
10
|
+
def shebang?(match)
|
11
|
+
match = /\b#{match}(\s|$)/
|
12
|
+
match === shebang
|
13
|
+
end
|
14
|
+
|
15
|
+
def doctype
|
16
|
+
return @doctype if instance_variable_defined? :@doctype
|
17
|
+
|
18
|
+
self =~ %r(\A\s*
|
19
|
+
(?:<\?.*?\?>\s*)? # possible <?xml...?> tag
|
20
|
+
<!DOCTYPE\s+(.+?)>
|
21
|
+
)xm
|
22
|
+
@doctype = $1
|
23
|
+
end
|
24
|
+
|
25
|
+
def doctype?(type)
|
26
|
+
type === doctype
|
27
|
+
end
|
28
|
+
|
29
|
+
def lexes_cleanly?(lexer)
|
30
|
+
lexer.lex(self) do |(tok, _)|
|
31
|
+
return false if tok.name == 'Error'
|
32
|
+
end
|
33
|
+
|
34
|
+
true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/rouge/theme.rb
CHANGED
@@ -22,16 +22,12 @@ module Rouge
|
|
22
22
|
|
23
23
|
style 'Text', :fg => :unicorn, :bg => :krasna
|
24
24
|
|
25
|
-
style 'Comment', :fg => :cool_as_ice
|
25
|
+
style 'Comment', :fg => :cool_as_ice, :italic => true
|
26
26
|
style 'Error',
|
27
27
|
'Generic.Error', :fg => :aluminum1, :bg => :scarletred2
|
28
28
|
style 'Keyword', :fg => :sandy, :bold => true
|
29
29
|
style 'Operator',
|
30
30
|
'Punctuation', :fg => :backlit, :bold => true
|
31
|
-
style 'Comment.Preproc',
|
32
|
-
'Comment.Multiline',
|
33
|
-
'Comment.Single',
|
34
|
-
'Comment.Special', :fg => :cool_as_ice, :italic => true
|
35
31
|
style 'Generic.Deleted', :fg => :scarletred2
|
36
32
|
style 'Generic.Inserted', :fg => :go_get_it
|
37
33
|
style 'Generic.Emph', :italic => true
|
data/lib/rouge/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rouge
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,12 +9,29 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-09-
|
13
|
-
dependencies:
|
12
|
+
date: 2012-09-07 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: thor
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
14
30
|
description: Rouge aims to a be a simple, easy-to-extend drop-in replacement for pygments.
|
15
31
|
email:
|
16
32
|
- jjmadkisson@gmail.com
|
17
|
-
executables:
|
33
|
+
executables:
|
34
|
+
- rougify
|
18
35
|
extensions: []
|
19
36
|
extra_rdoc_files: []
|
20
37
|
files:
|
@@ -33,11 +50,14 @@ files:
|
|
33
50
|
- lib/rouge/themes/base16.rb
|
34
51
|
- lib/rouge/token.rb
|
35
52
|
- lib/rouge/formatters/html.rb
|
53
|
+
- lib/rouge/text_analyzer.rb
|
36
54
|
- lib/rouge/version.rb
|
37
55
|
- lib/rouge/formatter.rb
|
56
|
+
- lib/rouge/cli.rb
|
38
57
|
- lib/rouge/lexer.rb
|
39
58
|
- lib/rouge/theme.rb
|
40
59
|
- lib/rouge.rb
|
60
|
+
- bin/rougify
|
41
61
|
homepage: http://github.com/jayferd/rouge
|
42
62
|
licenses: []
|
43
63
|
post_install_message:
|