pygments.rb 0.2.13 → 0.3.0
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/.gitignore +1 -0
- data/README.md +45 -19
- data/Rakefile +21 -11
- data/bench.rb +15 -48
- data/cache-lexers.rb +8 -0
- data/lexers +0 -0
- data/lib/pygments.rb +3 -6
- data/lib/pygments/mentos.py +343 -0
- data/lib/pygments/popen.rb +383 -0
- data/lib/pygments/version.rb +1 -1
- data/pygments.rb.gemspec +5 -4
- data/test/test_data.c +2581 -0
- data/test/test_data.py +514 -0
- data/test/test_data_generated +2582 -0
- data/test/test_pygments.rb +208 -84
- data/vendor/pygments-main/pygments/lexers/_mapping.py +1 -1
- data/vendor/pygments-main/pygments/lexers/shell.py +1 -1
- data/vendor/simplejson/.gitignore +10 -0
- data/vendor/simplejson/.travis.yml +5 -0
- data/vendor/simplejson/CHANGES.txt +291 -0
- data/vendor/simplejson/LICENSE.txt +19 -0
- data/vendor/simplejson/MANIFEST.in +5 -0
- data/vendor/simplejson/README.rst +19 -0
- data/vendor/simplejson/conf.py +179 -0
- data/vendor/simplejson/index.rst +628 -0
- data/vendor/simplejson/scripts/make_docs.py +18 -0
- data/vendor/simplejson/setup.py +104 -0
- data/vendor/simplejson/simplejson/__init__.py +510 -0
- data/vendor/simplejson/simplejson/_speedups.c +2745 -0
- data/vendor/simplejson/simplejson/decoder.py +425 -0
- data/vendor/simplejson/simplejson/encoder.py +567 -0
- data/vendor/simplejson/simplejson/ordered_dict.py +119 -0
- data/vendor/simplejson/simplejson/scanner.py +77 -0
- data/vendor/simplejson/simplejson/tests/__init__.py +67 -0
- data/vendor/simplejson/simplejson/tests/test_bigint_as_string.py +55 -0
- data/vendor/simplejson/simplejson/tests/test_check_circular.py +30 -0
- data/vendor/simplejson/simplejson/tests/test_decimal.py +66 -0
- data/vendor/simplejson/simplejson/tests/test_decode.py +83 -0
- data/vendor/simplejson/simplejson/tests/test_default.py +9 -0
- data/vendor/simplejson/simplejson/tests/test_dump.py +67 -0
- data/vendor/simplejson/simplejson/tests/test_encode_basestring_ascii.py +46 -0
- data/vendor/simplejson/simplejson/tests/test_encode_for_html.py +32 -0
- data/vendor/simplejson/simplejson/tests/test_errors.py +34 -0
- data/vendor/simplejson/simplejson/tests/test_fail.py +91 -0
- data/vendor/simplejson/simplejson/tests/test_float.py +19 -0
- data/vendor/simplejson/simplejson/tests/test_indent.py +86 -0
- data/vendor/simplejson/simplejson/tests/test_item_sort_key.py +20 -0
- data/vendor/simplejson/simplejson/tests/test_namedtuple.py +121 -0
- data/vendor/simplejson/simplejson/tests/test_pass1.py +76 -0
- data/vendor/simplejson/simplejson/tests/test_pass2.py +14 -0
- data/vendor/simplejson/simplejson/tests/test_pass3.py +20 -0
- data/vendor/simplejson/simplejson/tests/test_recursion.py +67 -0
- data/vendor/simplejson/simplejson/tests/test_scanstring.py +117 -0
- data/vendor/simplejson/simplejson/tests/test_separators.py +42 -0
- data/vendor/simplejson/simplejson/tests/test_speedups.py +20 -0
- data/vendor/simplejson/simplejson/tests/test_tuple.py +49 -0
- data/vendor/simplejson/simplejson/tests/test_unicode.py +109 -0
- data/vendor/simplejson/simplejson/tool.py +39 -0
- metadata +80 -22
- data/ext/extconf.rb +0 -14
- data/ext/pygments.c +0 -466
- data/lib/pygments/c.rb +0 -54
- data/lib/pygments/ffi.rb +0 -155
- data/vendor/.gitignore +0 -1
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,14 +1,23 @@
|
|
1
1
|
# pygments.rb
|
2
2
|
|
3
|
-
A
|
3
|
+
A Ruby wrapper for the Python [pygments syntax highlighter](http://pygments.org/).
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
pygments.rb works by talking over a simple pipe to a long-lived
|
6
|
+
Python child process. This library replaces [github/albino](https://github.com/github/albino),
|
7
|
+
as well as a version of pygments.rb that used an embedded Python
|
8
|
+
interpreter.
|
9
|
+
|
10
|
+
Each Ruby process that runs has its own 'personal Python';
|
11
|
+
for example, 4 Unicorn workers will have one Python process each.
|
12
|
+
If a Python process dies, a new one will be spawned on the next
|
13
|
+
pygments.rb request.
|
9
14
|
|
10
15
|
## usage
|
11
16
|
|
17
|
+
``` ruby
|
18
|
+
require 'pygments'
|
19
|
+
```
|
20
|
+
|
12
21
|
``` ruby
|
13
22
|
Pygments.highlight(File.read(__FILE__), :lexer => 'ruby')
|
14
23
|
```
|
@@ -20,29 +29,34 @@ options hash:
|
|
20
29
|
Pygments.highlight('code', :options => {:encoding => 'utf-8'})
|
21
30
|
```
|
22
31
|
|
23
|
-
|
32
|
+
pygments.rb defaults to using an HTML formatter.
|
33
|
+
To use a formatter other than `html`, specify it explicitly
|
34
|
+
like so:
|
24
35
|
|
25
36
|
``` ruby
|
26
37
|
Pygments.highlight('code', :formatter => 'bbcode')
|
27
38
|
Pygments.highlight('code', :formatter => 'terminal')
|
28
39
|
```
|
29
40
|
|
30
|
-
To generate CSS for
|
41
|
+
To generate CSS for HTML formatted code, use the `#css` method:
|
31
42
|
|
32
43
|
``` ruby
|
33
44
|
Pygments.css
|
34
45
|
Pygments.css('.highlight')
|
35
46
|
```
|
36
47
|
|
37
|
-
|
38
|
-
|
48
|
+
Other Pygments high-level API methods are also available.
|
49
|
+
These methods return arrays detailing all the available lexers, formatters,
|
50
|
+
and styles.
|
39
51
|
|
40
52
|
``` ruby
|
41
|
-
|
53
|
+
Pygments.lexers
|
54
|
+
Pygments.formatters
|
55
|
+
Pygments.styles
|
42
56
|
```
|
43
57
|
|
44
58
|
To use a custom pygments installation, specify the path to
|
45
|
-
Pygments
|
59
|
+
`Pygments#start`:
|
46
60
|
|
47
61
|
``` ruby
|
48
62
|
Pygments.start("/path/to/pygments")
|
@@ -50,12 +64,24 @@ Pygments.start("/path/to/pygments")
|
|
50
64
|
|
51
65
|
## benchmarks
|
52
66
|
|
53
|
-
$ ruby -rubygems bench.rb 50
|
54
|
-
user system total real
|
55
|
-
albino 0.050000 0.050000 12.830000 ( 13.180806)
|
56
|
-
pygments::c 1.000000 0.010000 1.010000 ( 1.009348)
|
57
|
-
pygments::ffi + reload 11.350000 1.240000 12.590000 ( 12.692320)
|
58
|
-
pygments::ffi 1.130000 0.010000 1.140000 ( 1.171589)
|
59
67
|
|
60
|
-
|
61
|
-
|
68
|
+
$ ruby bench.rb 50
|
69
|
+
Benchmarking....
|
70
|
+
Size: 698 bytes
|
71
|
+
Iterations: 50
|
72
|
+
user system total real
|
73
|
+
pygments popen 0.010000 0.010000 0.020000 ( 0.460370)
|
74
|
+
pygments popen (process already started) 0.010000 0.000000 0.010000 ( 0.272975)
|
75
|
+
pygments popen (process already started 2) 0.000000 0.000000 0.000000 ( 0.273589)
|
76
|
+
|
77
|
+
$ ruby bench.rb 10
|
78
|
+
Benchmarking....
|
79
|
+
Size: 15523 bytes
|
80
|
+
Iterations: 10
|
81
|
+
user system total real
|
82
|
+
pygments popen 0.000000 0.000000 0.000000 ( 0.819419)
|
83
|
+
pygments popen (process already started) 0.010000 0.000000 0.010000 ( 0.676515)
|
84
|
+
pygments popen (process already started 2) 0.000000 0.010000 0.010000 ( 0.674189)
|
85
|
+
|
86
|
+
|
87
|
+
|
data/Rakefile
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
|
1
4
|
task :default => :test
|
2
5
|
|
3
6
|
# ==========================================================
|
@@ -10,16 +13,6 @@ require 'rake/gempackagetask'
|
|
10
13
|
Rake::GemPackageTask.new(GEMSPEC) do |pkg|
|
11
14
|
end
|
12
15
|
|
13
|
-
# ==========================================================
|
14
|
-
# Ruby Extension
|
15
|
-
# ==========================================================
|
16
|
-
|
17
|
-
require 'rake/extensiontask'
|
18
|
-
Rake::ExtensionTask.new('pygments_ext', GEMSPEC) do |ext|
|
19
|
-
ext.ext_dir = 'ext'
|
20
|
-
end
|
21
|
-
task :build => :compile
|
22
|
-
|
23
16
|
# ==========================================================
|
24
17
|
# Testing
|
25
18
|
# ==========================================================
|
@@ -29,7 +22,24 @@ Rake::TestTask.new 'test' do |t|
|
|
29
22
|
t.test_files = FileList['test/test_*.rb']
|
30
23
|
t.ruby_opts = ['-rubygems']
|
31
24
|
end
|
32
|
-
|
25
|
+
|
26
|
+
# ==========================================================
|
27
|
+
# Benchmarking
|
28
|
+
# ==========================================================
|
29
|
+
|
30
|
+
task :bench do
|
31
|
+
sh "ruby bench.rb"
|
32
|
+
end
|
33
|
+
|
34
|
+
# ==========================================================
|
35
|
+
# Cache lexers
|
36
|
+
# # ==========================================================
|
37
|
+
|
38
|
+
# Write all the lexers to a file for easy lookup
|
39
|
+
task :lexers do
|
40
|
+
sh "ruby cache-lexers.rb"
|
41
|
+
end
|
42
|
+
|
33
43
|
|
34
44
|
# ==========================================================
|
35
45
|
# Vendor
|
data/bench.rb
CHANGED
@@ -1,55 +1,22 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require File.join(File.dirname(__FILE__), '/lib/pygments.rb')
|
3
2
|
require 'benchmark'
|
4
|
-
require 'pygments/c'
|
5
|
-
require 'pygments/ffi'
|
6
|
-
require 'rubygems'
|
7
|
-
require 'albino'
|
8
3
|
|
9
|
-
|
10
|
-
|
4
|
+
include Benchmark
|
5
|
+
# number of iterations
|
6
|
+
num = ARGV[0] ? ARGV[0].to_i : 10
|
11
7
|
|
12
|
-
|
13
|
-
|
14
|
-
Pygments::C.highlight(code, :lexer => 'ruby'),
|
15
|
-
Pygments::FFI.highlight(code, :lexer => 'ruby')
|
8
|
+
# we can also repeat the code itself
|
9
|
+
repeats = ARGV[1] ? ARGV[1].to_i : 1
|
16
10
|
|
17
|
-
|
18
|
-
raise "incompatible implementations (#{albino.size} != #{pygments.size} != #{ffi.size})"
|
19
|
-
end
|
11
|
+
code = File.open('test/test_data.py').read.to_s * repeats
|
20
12
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
Albino.new(code, :ruby, :html).colorize
|
25
|
-
end
|
26
|
-
end
|
27
|
-
x.report('pygments::c') do
|
28
|
-
num.times do
|
29
|
-
Pygments::C.highlight(code, :lexer => 'ruby')
|
30
|
-
end
|
31
|
-
end
|
32
|
-
x.report('pygments::ffi + reload') do
|
33
|
-
num.times do
|
34
|
-
Pygments::FFI.start
|
35
|
-
Pygments::FFI.highlight(code, :lexer => 'ruby')
|
36
|
-
Pygments::FFI.stop
|
37
|
-
end
|
38
|
-
end
|
39
|
-
Pygments::FFI.start
|
40
|
-
x.report('pygments::ffi') do
|
41
|
-
num.times do
|
42
|
-
Pygments::FFI.highlight(code, :lexer => 'ruby')
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
13
|
+
puts "Benchmarking....\n"
|
14
|
+
puts "Size: " + code.bytesize.to_s + " bytes\n"
|
15
|
+
puts "Iterations: " + num.to_s + "\n"
|
46
16
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
pygments::c 1.000000 0.010000 1.010000 ( 1.009348)
|
53
|
-
pygments::ffi + reload 11.350000 1.240000 12.590000 ( 12.692320)
|
54
|
-
pygments::ffi 1.130000 0.010000 1.140000 ( 1.171589)
|
17
|
+
Benchmark.bm(40) do |x|
|
18
|
+
x.report("pygments popen ") { for i in 1..num; Pygments.highlight(code, :lexer => 'python'); end }
|
19
|
+
x.report("pygments popen (process already started) ") { for i in 1..num; Pygments.highlight(code, :lexer => 'python'); end }
|
20
|
+
x.report("pygments popen (process already started 2) ") { for i in 1..num; Pygments.highlight(code, :lexer => 'python'); end }
|
21
|
+
end
|
55
22
|
|
data/cache-lexers.rb
ADDED
data/lexers
ADDED
Binary file
|
data/lib/pygments.rb
CHANGED
@@ -1,11 +1,8 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require File.join(File.dirname(__FILE__), 'pygments/popen')
|
2
|
+
|
3
3
|
|
4
4
|
module Pygments
|
5
|
-
|
6
|
-
include Pygments::FFI
|
5
|
+
extend Pygments::Popen
|
7
6
|
|
8
7
|
autoload :Lexer, 'pygments/lexer'
|
9
|
-
|
10
|
-
extend self
|
11
8
|
end
|
@@ -0,0 +1,343 @@
|
|
1
|
+
#!/usr/bin/python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
import sys, re, os, signal, resource
|
5
|
+
import traceback
|
6
|
+
if 'PYGMENTS_PATH' in os.environ:
|
7
|
+
sys.path.insert(0, os.environ['PYGMENTS_PATH'])
|
8
|
+
|
9
|
+
dirname = os.path.dirname
|
10
|
+
|
11
|
+
base_dir = dirname(dirname(dirname(os.path.abspath(__file__))))
|
12
|
+
sys.path.append(base_dir + "/vendor")
|
13
|
+
sys.path.append(base_dir + "/vendor/pygments-main")
|
14
|
+
sys.path.append(base_dir + "/vendor/simplejson")
|
15
|
+
|
16
|
+
import pygments
|
17
|
+
from pygments import lexers, formatters, styles, filters
|
18
|
+
|
19
|
+
from threading import Lock
|
20
|
+
|
21
|
+
try:
|
22
|
+
import json
|
23
|
+
except ImportError:
|
24
|
+
import simplejson as json
|
25
|
+
|
26
|
+
def _convert_keys(dictionary):
|
27
|
+
if not isinstance(dictionary, dict):
|
28
|
+
return dictionary
|
29
|
+
return dict((str(k), _convert_keys(v))
|
30
|
+
for k, v in dictionary.items())
|
31
|
+
|
32
|
+
def _write_error(error):
|
33
|
+
res = {"error": error}
|
34
|
+
out_header = json.dumps(res).encode('utf-8')
|
35
|
+
bits = _get_fixed_bits_from_header(out_header)
|
36
|
+
sys.stdout.write(bits + "\n")
|
37
|
+
sys.stdout.flush()
|
38
|
+
sys.stdout.write(out_header + "\n")
|
39
|
+
sys.stdout.flush()
|
40
|
+
return
|
41
|
+
|
42
|
+
def _get_fixed_bits_from_header(out_header):
|
43
|
+
size = len(out_header)
|
44
|
+
return "".join(map(lambda y:str((size>>y)&1), range(32-1, -1, -1)))
|
45
|
+
|
46
|
+
def _signal_handler(signal, frame):
|
47
|
+
"""
|
48
|
+
Handle the signal given in the first argument, exiting gracefully
|
49
|
+
"""
|
50
|
+
sys.exit(0)
|
51
|
+
|
52
|
+
class Mentos(object):
|
53
|
+
"""
|
54
|
+
Interacts with pygments.rb to provide access to pygments functionality
|
55
|
+
"""
|
56
|
+
def __init__(self):
|
57
|
+
pass
|
58
|
+
|
59
|
+
def return_lexer(self, lexer, args, inputs, code=None):
|
60
|
+
"""
|
61
|
+
Accepting a variety of possible inputs, return a Lexer object.
|
62
|
+
|
63
|
+
The inputs argument should be a hash with at least one of the following
|
64
|
+
keys:
|
65
|
+
|
66
|
+
- 'lexer' ("python")
|
67
|
+
- 'mimetype' ("text/x-ruby")
|
68
|
+
- 'filename' ("yeaaah.py")
|
69
|
+
- 'code' ("import derp" etc)
|
70
|
+
|
71
|
+
The code guessing method is not especially great. It is advised that
|
72
|
+
clients pass in a literal lexer name whenever possible, which provides
|
73
|
+
the best probability of match (100 percent).
|
74
|
+
"""
|
75
|
+
|
76
|
+
if lexer:
|
77
|
+
if inputs:
|
78
|
+
return lexers.get_lexer_by_name(lexer, **inputs)
|
79
|
+
else:
|
80
|
+
return lexers.get_lexer_by_name(lexer)
|
81
|
+
|
82
|
+
if inputs:
|
83
|
+
if 'lexer' in inputs:
|
84
|
+
return lexers.get_lexer_by_name(inputs['lexer'], **inputs)
|
85
|
+
|
86
|
+
elif 'mimetype' in inputs:
|
87
|
+
return lexers.get_lexer_for_mimetype(inputs['mimetype'], **inputs)
|
88
|
+
|
89
|
+
elif 'filename' in inputs:
|
90
|
+
name = inputs['filename']
|
91
|
+
|
92
|
+
# If we have code and a filename, pygments allows us to guess
|
93
|
+
# with both. This is better than just guessing with code.
|
94
|
+
if code:
|
95
|
+
return lexers.guess_lexer_for_filename(name, code, **inputs)
|
96
|
+
else:
|
97
|
+
return lexers.get_lexer_for_filename(name, **inputs)
|
98
|
+
|
99
|
+
# If all we got is code, try anyway.
|
100
|
+
if code:
|
101
|
+
return lexers.guess_lexer(code, **inputs)
|
102
|
+
|
103
|
+
else:
|
104
|
+
_write_error("No lexer")
|
105
|
+
|
106
|
+
|
107
|
+
def highlight_text(self, code, lexer, formatter_name, args, kwargs):
|
108
|
+
"""
|
109
|
+
Highlight the relevant code, and return a result string.
|
110
|
+
The default formatter is html, but alternate formatters can be passed in via
|
111
|
+
the formatter_name argument. Additional paramters can be passed as args
|
112
|
+
or kwargs.
|
113
|
+
"""
|
114
|
+
# Default to html if we don't have the formatter name.
|
115
|
+
if formatter_name:
|
116
|
+
_format_name = str(formatter_name)
|
117
|
+
else:
|
118
|
+
_format_name = "html"
|
119
|
+
|
120
|
+
# Return a lexer object
|
121
|
+
lexer = self.return_lexer(lexer, args, kwargs, code)
|
122
|
+
|
123
|
+
# Make sure we sucessfuly got a lexer
|
124
|
+
if lexer:
|
125
|
+
formatter = pygments.formatters.get_formatter_by_name(str.lower(_format_name), **kwargs)
|
126
|
+
|
127
|
+
# Do the damn thing.
|
128
|
+
res = pygments.highlight(code, lexer, formatter)
|
129
|
+
|
130
|
+
return res
|
131
|
+
|
132
|
+
else:
|
133
|
+
_write_error("No lexer")
|
134
|
+
|
135
|
+
def get_data(self, method, lexer, args, kwargs, text=None):
|
136
|
+
"""
|
137
|
+
Based on the method argument, determine the action we'd like pygments
|
138
|
+
to do. Then return the data generated from pygments.
|
139
|
+
"""
|
140
|
+
if kwargs:
|
141
|
+
formatter_name = kwargs.get("formatter", None)
|
142
|
+
opts = kwargs.get("options", {})
|
143
|
+
|
144
|
+
# Ensure there's a 'method' key before proceeeding
|
145
|
+
if method:
|
146
|
+
res = None
|
147
|
+
|
148
|
+
# Now check what that method is. For the get methods, pygments
|
149
|
+
# itself returns generators, so we make them lists so we can serialize
|
150
|
+
# easier.
|
151
|
+
if method == 'get_all_styles':
|
152
|
+
res = json.dumps(list(pygments.styles.get_all_styles()))
|
153
|
+
|
154
|
+
elif method == 'get_all_filters':
|
155
|
+
res = json.dumps(list(pygments.filters.get_all_filters()))
|
156
|
+
|
157
|
+
elif method == 'get_all_lexers':
|
158
|
+
res = json.dumps(list(pygments.lexers.get_all_lexers()))
|
159
|
+
|
160
|
+
elif method == 'get_all_formatters':
|
161
|
+
res = [ [ft.__name__, ft.name, ft.aliases] for ft in pygments.formatters.get_all_formatters() ]
|
162
|
+
res = json.dumps(res)
|
163
|
+
|
164
|
+
elif method == 'highlight':
|
165
|
+
try:
|
166
|
+
text = text.decode('utf-8')
|
167
|
+
except UnicodeDecodeError:
|
168
|
+
# The text may already be encoded
|
169
|
+
text = text
|
170
|
+
res = self.highlight_text(text, lexer, formatter_name, args, _convert_keys(opts))
|
171
|
+
|
172
|
+
elif method == 'css':
|
173
|
+
kwargs = _convert_keys(kwargs)
|
174
|
+
fmt = pygments.formatters.get_formatter_by_name(args[0], **kwargs)
|
175
|
+
res = fmt.get_style_defs(args[1])
|
176
|
+
|
177
|
+
elif method == 'lexer_name_for':
|
178
|
+
lexer = self.return_lexer(None, args, kwargs, text)
|
179
|
+
|
180
|
+
if lexer:
|
181
|
+
# We don't want the Lexer itself, just the name.
|
182
|
+
# Take the first alias.
|
183
|
+
res = lexer.aliases[0]
|
184
|
+
|
185
|
+
else:
|
186
|
+
_write_error("No lexer")
|
187
|
+
|
188
|
+
else:
|
189
|
+
_write_error("No lexer")
|
190
|
+
|
191
|
+
return res
|
192
|
+
|
193
|
+
|
194
|
+
def _send_data(self, res, method):
|
195
|
+
|
196
|
+
# Base header. We'll build on this, adding keys as necessary.
|
197
|
+
base_header = {"method": method}
|
198
|
+
|
199
|
+
res_bytes = len(res) + 1
|
200
|
+
base_header["bytes"] = res_bytes
|
201
|
+
|
202
|
+
out_header = json.dumps(base_header).encode('utf-8')
|
203
|
+
|
204
|
+
# Following the protocol, send over a fixed size represenation of the
|
205
|
+
# size of the JSON header
|
206
|
+
bits = _get_fixed_bits_from_header(out_header)
|
207
|
+
|
208
|
+
# Send it to Rubyland
|
209
|
+
sys.stdout.write(bits + "\n")
|
210
|
+
sys.stdout.flush()
|
211
|
+
|
212
|
+
# Send the header.
|
213
|
+
sys.stdout.write(out_header + "\n")
|
214
|
+
sys.stdout.flush()
|
215
|
+
|
216
|
+
# Finally, send the result
|
217
|
+
sys.stdout.write(res + "\n")
|
218
|
+
sys.stdout.flush()
|
219
|
+
|
220
|
+
|
221
|
+
def _get_ids(self, text):
|
222
|
+
start_id = text[:8]
|
223
|
+
end_id = text[-8:]
|
224
|
+
return start_id, end_id
|
225
|
+
|
226
|
+
def _check_and_return_text(self, text, start_id, end_id):
|
227
|
+
|
228
|
+
# Sanity check.
|
229
|
+
id_regex = re.compile('[A-Z]{8}')
|
230
|
+
|
231
|
+
if not id_regex.match(start_id) and not id_regex.match(end_id):
|
232
|
+
_write_error("ID check failed. Not an ID.")
|
233
|
+
|
234
|
+
if not start_id == end_id:
|
235
|
+
_write_error("ID check failed. ID's did not match.")
|
236
|
+
|
237
|
+
# Passed the sanity check. Remove the id's and return
|
238
|
+
text = text[10:-10]
|
239
|
+
return text
|
240
|
+
|
241
|
+
def _parse_header(self, header):
|
242
|
+
method = header["method"]
|
243
|
+
args = header.get("args", [])
|
244
|
+
kwargs = header.get("kwargs", {})
|
245
|
+
lexer = kwargs.get("lexer", None)
|
246
|
+
return (method, args, kwargs, lexer)
|
247
|
+
|
248
|
+
def start(self):
|
249
|
+
"""
|
250
|
+
Main loop, waiting for inputs on stdin. When it gets some data,
|
251
|
+
it goes to work.
|
252
|
+
|
253
|
+
mentos exposes most of the "High-level API" of pygments. It always
|
254
|
+
expects and requires a JSON header of metadata. If there is data to be
|
255
|
+
pygmentized, this header will be followed by the text to be pygmentized.
|
256
|
+
|
257
|
+
The header is of form:
|
258
|
+
{ "method": "highlight", "args": [], "kwargs": {"arg1": "v"}, "bytes": 128, "fd": "8"}
|
259
|
+
"""
|
260
|
+
lock = Lock()
|
261
|
+
|
262
|
+
while True:
|
263
|
+
# The loop begins by reading off a simple 32-arity string
|
264
|
+
# representing an integer of 32 bits. This is the length of
|
265
|
+
# our JSON header.
|
266
|
+
size = sys.stdin.read(32)
|
267
|
+
|
268
|
+
lock.acquire()
|
269
|
+
|
270
|
+
try:
|
271
|
+
# Read from stdin the amount of bytes we were told to expect.
|
272
|
+
header_bytes = int(size, 2)
|
273
|
+
|
274
|
+
# Sanity check the size
|
275
|
+
size_regex = re.compile('[0-1]{32}')
|
276
|
+
if not size_regex.match(size):
|
277
|
+
_write_error("Size received is not valid.")
|
278
|
+
|
279
|
+
line = sys.stdin.read(header_bytes)
|
280
|
+
|
281
|
+
header = json.loads(line)
|
282
|
+
|
283
|
+
method, args, kwargs, lexer = self._parse_header(header)
|
284
|
+
_bytes = 0
|
285
|
+
|
286
|
+
if lexer:
|
287
|
+
lexer = str(lexer)
|
288
|
+
|
289
|
+
# Read more bytes if necessary
|
290
|
+
if kwargs:
|
291
|
+
_bytes = kwargs.get("bytes", 0)
|
292
|
+
|
293
|
+
# Read up to the given number bytes (possibly 0)
|
294
|
+
text = sys.stdin.read(_bytes)
|
295
|
+
|
296
|
+
# Sanity check the return.
|
297
|
+
if method == 'highlight':
|
298
|
+
start_id, end_id = self._get_ids(text)
|
299
|
+
text = self._check_and_return_text(text, start_id, end_id)
|
300
|
+
|
301
|
+
# Get the actual data from pygments.
|
302
|
+
res = self.get_data(method, lexer, args, kwargs, text)
|
303
|
+
|
304
|
+
# Put back the sanity check values.
|
305
|
+
if method == "highlight":
|
306
|
+
res = start_id + " " + res + " " + end_id
|
307
|
+
|
308
|
+
self._send_data(res, method)
|
309
|
+
|
310
|
+
except:
|
311
|
+
tb = traceback.format_exc()
|
312
|
+
_write_error(tb)
|
313
|
+
|
314
|
+
finally:
|
315
|
+
lock.release()
|
316
|
+
|
317
|
+
def main():
|
318
|
+
|
319
|
+
# Signal handlers to trap signals.
|
320
|
+
signal.signal(signal.SIGINT, _signal_handler)
|
321
|
+
signal.signal(signal.SIGTERM, _signal_handler)
|
322
|
+
signal.signal(signal.SIGHUP, _signal_handler)
|
323
|
+
|
324
|
+
mentos = Mentos()
|
325
|
+
|
326
|
+
# close fd's inherited from the ruby parent
|
327
|
+
maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
|
328
|
+
if maxfd == resource.RLIM_INFINITY:
|
329
|
+
maxfd = 65536
|
330
|
+
|
331
|
+
for fd in range(3, maxfd):
|
332
|
+
try:
|
333
|
+
os.close(fd)
|
334
|
+
except:
|
335
|
+
pass
|
336
|
+
|
337
|
+
mentos.start()
|
338
|
+
|
339
|
+
if __name__ == "__main__":
|
340
|
+
main()
|
341
|
+
|
342
|
+
|
343
|
+
|