Class: Pygments::Popen

Inherits:
Object
  • Object
show all
Defined in:
lib/pygments/popen.rb

Instance Method Summary collapse

Instance Method Details

#alive?Boolean

Check for a @pid variable, and then hit ‘kill -0` with the pid to check if the pid is still in the process table. If this function gives us an ENOENT or ESRCH, we can also safely return false (no process to worry about). Defensively, if EPERM is raised, in a odd/rare dying process situation (e.g., mentos is checking on the pid of a dead process and the pid has already been re-used) we’ll want to raise that as a more informative Mentos exception.

Returns true if the child is alive.

Returns:

  • (Boolean)


117
118
119
120
121
122
123
124
# File 'lib/pygments/popen.rb', line 117

def alive?
  return true if defined?(@pid) && @pid && Process.kill(0, @pid)
  false
rescue Errno::ENOENT, Errno::ESRCH
  false
rescue Errno::EPERM
  raise MentosError, "EPERM checking if child process is alive."
end

#css(klass = '', opts = {}) ⇒ Object

Public: Return css for highlighted code



191
192
193
194
195
196
197
# File 'lib/pygments/popen.rb', line 191

def css(klass='', opts={})
  if klass.is_a?(Hash)
    opts = klass
    klass = ''
  end
  mentos(:css, ['html', klass], opts)
end

#filtersObject

Public: Return an array of all available filters



181
182
183
# File 'lib/pygments/popen.rb', line 181

def filters
  mentos(:get_all_filters)
end

#find_python_binaryObject

Detect a suitable Python binary to use. Or return $PYGMENTS_RB_PYTHON if it’s exists.



62
63
64
65
66
67
68
69
# File 'lib/pygments/popen.rb', line 62

def find_python_binary
  if ENV['PYGMENTS_RB_PYTHON']
    return which(ENV['PYGMENTS_RB_PYTHON'])
  elsif windows? && which('py')
    return 'py -2'
  end
  return which('python2') || which('python')
end

#formattersObject

Public: Get an array of available Pygments formatters

Returns an array of formatters.



129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/pygments/popen.rb', line 129

def formatters
  mentos(:get_all_formatters).inject(Hash.new) do | hash, (name, desc, aliases) |
    # Remove the long-winded and repetitive 'Formatter' suffix
    name.sub!(/Formatter$/, '')
    hash[name] = {
      :name => name,
      :description => desc,
      :aliases => aliases
    }
    hash
  end
end

#highlight(code, opts = {}) ⇒ Object

Public: Highlight code.

Takes a first-position argument of the code to be highlighted, and a second-position hash of various arguments specifying highlighting properties.

Returns the highlighted string or nil when the request to the Python process timed out.



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/pygments/popen.rb', line 224

def highlight(code, opts={})
  # If the caller didn't give us any code, we have nothing to do,
  # so return right away.
  return code if code.nil? || code.empty?

  # Callers pass along options in the hash
  opts[:options] ||= {}

  # Default to utf-8 for the output encoding, if not given.
  opts[:options][:outencoding] ||= 'utf-8'

  # Get back the string from mentos and force encoding if we can
  str = mentos(:highlight, nil, opts, code)
  str.force_encoding(opts[:options][:outencoding]) if str.respond_to?(:force_encoding)
  str
end

#lexer_name_for(*args) ⇒ Object

Public: Return the name of a lexer.



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/pygments/popen.rb', line 200

def lexer_name_for(*args)
  # Pop off the last arg if it's a hash, which becomes our opts
  if args.last.is_a?(Hash)
    opts = args.pop
  else
    opts = {}
  end

  if args.last.is_a?(String)
    code = args.pop
  else
    code = nil
  end

  mentos(:lexer_name_for, args, opts, code)
end

#lexersObject

Public: Get all lexers from a serialized array. This avoids needing to spawn mentos when it’s not really needed (e.g., one-off jobs, loading the Rails env, etc).

Should be preferred to #lexers!

Returns an array of lexers.



149
150
151
152
153
154
155
156
157
# File 'lib/pygments/popen.rb', line 149

def lexers
  begin
    lexer_file = File.expand_path('../../../lexers', __FILE__)
    raw = File.open(lexer_file, "rb").read
    Marshal.load(raw)
  rescue Errno::ENOENT
    raise MentosError, "Error loading lexer file. Was it created and vendored?"
  end
end

#lexers!Object

Public: Get back all available lexers from mentos itself

Returns an array of lexers.



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/pygments/popen.rb', line 162

def lexers!
  mentos(:get_all_lexers).inject(Hash.new) do |hash, lxr|
    name = lxr[0]
    hash[name] = {
      :name => name,
      :aliases => lxr[1],
      :filenames => lxr[2],
      :mimetypes => lxr[3]
    }
    hash["dasm16"] = {:name=>"dasm16", :aliases=>["DASM16"], :filenames=>["*.dasm16", "*.dasm"], :mimetypes=>['text/x-dasm16']}
    hash["Puppet"] = {:name=>"Puppet", :aliases=>["puppet"], :filenames=>["*.pp"], :mimetypes=>[]}
    hash["Augeas"] = {:name=>"Augeas", :aliases=>["augeas"], :filenames=>["*.aug"], :mimetypes=>[]}
    hash["TOML"]   = {:name=>"TOML",   :aliases=>["toml"],   :filenames=>["*.toml"], :mimetypes=>[]}
    hash["Slash"]  = {:name=>"Slash",  :aliases=>["slash"],  :filenames=>["*.sl"], :mimetypes=>[]}
    hash
  end
end

#popen4(cmd) ⇒ Object



19
20
21
22
# File 'lib/pygments/popen.rb', line 19

def popen4(cmd)
  stdin, stdout, stderr, wait_thr = Open3.popen3(cmd)
  [wait_thr[:pid], stdin, stdout, stderr]
end

#python_binaryObject



52
53
54
# File 'lib/pygments/popen.rb', line 52

def python_binary
  @python_binary ||= find_python_binary
end

#python_binary=(python_bin) ⇒ Object



56
57
58
# File 'lib/pygments/popen.rb', line 56

def python_binary=(python_bin)
  @python_bin = python_bin
end

#start(pygments_path = File.expand_path('../../../vendor/pygments-main/', __FILE__)) ⇒ Object

Get things started by opening a pipe to mentos (the freshmaker), a Python process that talks to the Pygments library. We’ll talk back and forth across this pipe.



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/pygments/popen.rb', line 27

def start(pygments_path = File.expand_path('../../../vendor/pygments-main/', __FILE__))
  begin
    @log = Logger.new(ENV['MENTOS_LOG'] ||= File::NULL)
    @log.level = Logger::INFO
    @log.datetime_format = "%Y-%m-%d %H:%M "
  rescue
    @log = Logger.new(File::NULL)
  end

  ENV['PYGMENTS_PATH'] = pygments_path

  # Make sure we kill off the child when we're done
  at_exit { stop "Exiting" }

  # A pipe to the mentos python process. #popen4 gives us
  # the pid and three IO objects to write and read.
  script = "#{python_binary} #{File.expand_path('../mentos.py', __FILE__)}"
  @pid, @in, @out, @err = popen4(script)
  @log.info "Starting pid #{@pid} with fd #{@out.to_i} and python #{python_binary}."
end

#stop(reason) ⇒ Object

Stop the child process by issuing a kill -9.

We then call waitpid() with the pid, which waits for that particular child and reaps it.

kill() can set errno to ESRCH if, for some reason, the file is gone; regardless the final outcome of this method will be to set our @pid variable to nil.

Technically, kill() can also fail with EPERM or EINVAL (wherein the signal isn’t sent); but we have permissions, and we’re not doing anything invalid here.



96
97
98
99
100
101
102
103
104
105
106
# File 'lib/pygments/popen.rb', line 96

def stop(reason)
  if @pid
    begin
      Process.kill('KILL', @pid)
      Process.waitpid(@pid)
    rescue Errno::ESRCH, Errno::ECHILD
    end
  end
  @log.info "Killing pid: #{@pid.to_s}. Reason: #{reason}"
  @pid = nil
end

#stylesObject

Public: Return an array of all available styles



186
187
188
# File 'lib/pygments/popen.rb', line 186

def styles
  mentos(:get_all_styles)
end

#which(command) ⇒ Object

Cross platform which command from stackoverflow.com/a/5471032/284795



73
74
75
76
77
78
79
80
81
82
# File 'lib/pygments/popen.rb', line 73

def which(command)
  exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
  ENV['PATH'].split(File::PATH_SEPARATOR).each do |dir|
    exts.each { |ext|
      path = File.join(dir, "#{command}#{ext}")
      return path if File.executable?(path) && !File.directory?(path)
    }
  end
  return nil
end

#windows?Boolean

Returns:

  • (Boolean)


48
49
50
# File 'lib/pygments/popen.rb', line 48

def windows?
  RUBY_PLATFORM =~ /mswin|mingw/
end