pygments.rb keeps one Python per Ruby process, and the pipe is the whole design
💎 Ruby wrapper for Pygments syntax highlighter
At a glance
- What is it?
- pygments.rb is a Ruby wrapper around the Pygments syntax highlighter that talks to a long-lived Python child process over a pipe, replacing the older embedded interpreter approach and the github/albino project. It gives you the full Pygments formatter and style catalogue from Ruby.
- Who is it for?
- Adopt pygments.rb if you render syntax highlighting from Ruby and you want the whole Pygments lexer, formatter and style catalogue rather than a hand rolled subset. Do not adopt it if your application cannot run a Python child process, or if you only need to highlight a small fixed set of languages.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 10 days ago.
- What is it written in?
- Mainly Ruby, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A pipe to Python, not an embedded interpreter
pygments.rb is a Ruby wrapper for the Pygments syntax highlighter, and the README states the mechanism in one sentence: it works by talking over a simple pipe to a long-lived Python child process.
That single design choice separates it from the two things it replaced. The README says the library replaces github/albino, as well as an older version of pygments.rb that used an embedded Python interpreter. So the project has been rewritten twice in its life to get away from running Python inside the Ruby process, and this time the answer was a separate child process talking over a pipe.
The reason a pipe and not a socket or a subprocess per call is worth noting: the child stays alive, so the Pygments import and lexer setup cost is paid once rather than on every request.
The process model is per Ruby process, and the README is explicit about it. Each Ruby process that runs has its own personal Python, and the example given is that 4 Unicorn workers will have one Python process each. So the memory cost scales with the worker count, not with the request count. If a Python process dies, a new one is spawned on the next pygments.rb request, which means a crash is a latency spike rather than an outage.
The system requirements are Python 3.8 or newer and Ruby 2.7 or newer. Both are floors, so the project runs on a wide range of runtimes, at the cost of assuming a Python is present on the machine at all.
Installing it as a gem, or into a Gemfile
Installation is the ordinary Ruby way, with a line for the Gemfile and a line for a global install.
gem 'pygments.rb'Then you install the dependencies.
$ bundle installOr you install the gem globally, without Bundler.
$ gem install pygments.rbThe gem is published on RubyGems under the name pygments.rb, and the README's own header points at the gem page and at the PyPI-independent Pygments project it wraps.
The dependency is implicit and easy to miss. The gem installs the Ruby side, but the thing doing the actual highlighting is Pygments, which is a Python package. The README does not give a pip install line in the installation section, so if you are provisioning a container or a CI image, the Python side has to be installed separately, and the version of Pygments you install is what determines which lexers and styles are available to the Ruby API.
That is a real operational consideration for a gem whose whole purpose is to bridge two runtimes. A bundle install does not give you a working highlighter on a machine without Python and Pygments.
Highlighting, and the options hash for everything else
The usage section starts with requiring the module, and the module name is pygments even though the gem is pygments.rb.
require 'pygments'Highlighting a file is a single call with a lexer name.
Pygments.highlight(File.read(__FILE__), lexer: 'ruby')The lexer argument is a Pygments lexer name, so the set of languages you can highlight is the set Pygments knows, not a fixed list in this gem.
Anything else goes through the options hash. Encoding and other lexer or formatter options are passed that way, for example with an encoding option set to utf-8.
Pygments.highlight('code', options: {encoding: 'utf-8'})The formatter is also an option, and pygments.rb uses the HTML formatter by default. You can select a different one by name, and the README shows bbcode and terminal as examples, which is the sign of a wrapper that exposes the underlying library rather than a curated subset of it.
Pygments.highlight('code', formatter: 'bbcode')
Pygments.highlight('code', formatter: 'terminal')For HTML output you also need the stylesheet, and there is a dedicated method for that.
Pygments.css
Pygments.css('.highlight')The first form gives you the CSS for the whole document, the second scopes it to a selector, and a style option selects a specific Pygments style, for example monokai. The style is chosen at the same time as the CSS is generated, which is the detail to remember: the style is a property of the stylesheet, not of a single highlighted fragment, so one document has one palette.
Discovering lexers, and pointing at your own Pygments install
The other high level API methods return arrays describing what is available, and the README names three of them.
Pygments.lexers
Pygments.formatters
Pygments.stylesThis is more useful than it first appears. Because the available lexers and formatters depend on which Pygments version is installed on the machine, these three calls are the way an application discovers what it can actually do, rather than hardcoding a list that will drift from the installed Pygments.
There is also a way to control which Pygments is used, by passing a path to Pygments.start.
Pygments.start("/path/to/pygments")That matters in practice. If the machine has a system Pygments and a vendored one, or if the Pygments version is pinned by your deployment rather than by the gem, this is the hook that decides which one is used.
The repository layout backs up the Python dependency story. There is a vendor/ directory at the top level, alongside lib/, spec/, the gemspec, a Gemfile, a Rakefile, a .rubocop.yml and a CHANGELOG.adoc. A vendor directory in a gem usually means the Python side is being carried along with the Ruby gem, which is the most likely answer to how the Python dependency is satisfied in practice, even though the README does not spell it out.
The README also names the three authors, Ted Nyman, Aman Gupta and Marat Radchenko, which is the kind of attribution that tells you this is a project with a specific history rather than a fresh port.
Timeouts and logging, the two knobs that keep a pipe from hurting you
Two operational controls are documented, and both are about the failure mode that a long-lived child process introduces.
The first is logging. If you want logging, you set the environmental variable MENTOS_LOG to a file path for your logfile. The name is not explained in the README, and neither is the format or the log levels, so treat it as a switch rather than a documented feature.
The second is the timeout, and it is the one that matters. You can apply a timeout to pygments.rb calls by specifying a number of seconds in the MENTOS_TIMEOUT environmental variable, or by passing the timeout argument, and the argument takes precedence over the environment variable.
Pygments.highlight('code', timeout: 4)The reason to care is structural. A pipe to a child process can block. If the Python side hangs on a pathological input, a request in your Ruby process waits with it, and in a web application that is a stuck worker rather than a slow response. A timeout turns that into a failure you can handle.
The precedence rule is worth remembering in deployment. Setting MENTOS_TIMEOUT gives you a global default across the process, and the argument lets an individual call override it, which is the shape you want when one call site is known to be expensive and the rest are not.
The same environment variable mechanism, and the fact that both are named with the same prefix, suggests a small family of settings rather than a single feature.
The benchmark shows what the long-lived process actually buys
The README includes a benchmark section with two runs of a script called bench.rb, and the numbers are worth reading for what they reveal about the design.
pygments popen 0.010000 0.010000 0.020000 ( 0.460370)
pygments popen (process already started) 0.010000 0.000000 0.010000 ( 0.272975)
pygments popen (process already started 2) 0.000000 0.000000 0.000000 ( 0.273589)Each run reports a size and an iteration count, and then three rows for the same operation. The first row is the cold case, where the Python process has to be started. The second and third are the warm cases, where the process is already running.
The pattern is the whole argument for keeping the child alive. The cold real time of 0.46 seconds drops to 0.27 seconds once the process is already started, and the second warm run costs the same again. Process startup is roughly a third of the total time in the small case, and it is the part you avoid by not paying it per request.
The larger run, with 15523 bytes over 10 iterations, shows the same shape with a cold real time of 0.82 seconds against 0.68 seconds warm. The gap narrows in relative terms as the payload grows, which is what you would expect if the fixed cost is process startup.
These are the project's own numbers on its own hardware, and the user, system and total columns are coarse, so treat them as a shape rather than a prediction. What they do establish is that the pipe design is not incidental, and that the difference between a warm and a cold child is measurable in the tens of milliseconds.
Where pygments.rb is the wrong tool
Three cases stand out.
The first is when you cannot run Python. The gem is a wrapper, not a port, and the README's system requirements start with Python 3.8 or newer. A minimal container image, a Lambda style environment, or a platform that ships only Ruby has to add a Python runtime and Pygments, and then it is carrying two language runtimes for a job that some of them do in Ruby.
The second is when you need highlighting in a hot path with a lot of concurrent requests. The process model is one Python per Ruby process, so a multi worker server has multiple Python processes, and each request is a round trip over a pipe. The benchmark shows a warm call at roughly 0.27 seconds of real time in the small case, which is not a figure you would want on a page that renders code on every request. A static highlighting step at build time is a better fit for that case.
The third is when you want a specific, small, fixed set of languages with no surprises. The lexers, formatters and styles are all resolved from the installed Pygments, so what you get depends on a Python package version that the Gemfile does not control. If you need to know that a specific language always highlights the same way, that is a dependency you have to pin on both sides, and the README does not describe how the Python version is pinned.
For everything else, the argument is simple. Pygments has a very large lexer catalogue and a good set of styles, and this library exposes all of it from Ruby for the cost of one pipe.
MIT licensed, with major versions arriving quickly
The licence is MIT, with the README granting free use under those terms and pointing at the LICENSE file for the full text. The copyright line names Ted Nyman, Aman Gupta and Marat Radchenko, 2012 to 2021, which is a long working life for a small wrapper.
The release history is the part to look at carefully. The recent releases are v5.0.0 on 2026-07-22, v4.0.0 on 2025-06-25 and v3.0.0 on 2024-05-14. Three major versions in about two years, and no patch releases listed among them.
A major bump in a library with a small API surface usually means something changed in the contract rather than the internals, and the CHANGELOG.adoc file in the repository is where that is recorded. It is worth reading between versions rather than assuming a minor bump is safe, because the sequence here suggests the maintainers are still settling the API.
On maintenance, the repository is not archived and the last push was on 2026-10-14, so work is landing on master. The development instructions are minimal and conventional: after checking out the repo, run bundle install to install dependencies, then bundle exec rake spec to run the tests. The presence of a .rubocop.yml and a Rakefile means the project uses the standard Ruby lint and test tooling rather than anything unusual.
The irony worth noting is that the repository originally existed to replace github/albino, and it has now had two major rewrites of its own. The pipe based design looks like the arrangement that will last.
Editorial conclusion
Adopt pygments.rb if you render syntax highlighting from Ruby and you want the whole Pygments lexer, formatter and style catalogue rather than a hand rolled subset. Do not adopt it if your application cannot run a Python child process, or if you only need to highlight a small fixed set of languages. Before you depend on it, check that the Python on the target machine satisfies the Python 3.8 or newer requirement, set MENTOS_TIMEOUT so a hung Pygments process cannot stall a request, and read the CHANGELOG.adoc between major versions, since releases go 3.0.0, 4.0.0 and 5.0.0 in fairly quick succession.
Frequently asked questions
How does pygments.rb work?
It talks over a simple pipe to a long-lived Python child process. Each Ruby process has its own personal Python, so 4 Unicorn workers will have one Python process each, and if a Python process dies a new one is spawned on the next pygments.rb request.
What are the system requirements for pygments.rb?
Python 3.8 or newer and Ruby 2.7 or newer. The gem itself is Ruby, but Pygments is a Python package, so a Python runtime with Pygments has to be present on the machine.
How do I set a timeout in pygments.rb?
Set the number of seconds in the MENTOS_TIMEOUT environmental variable, or pass the timeout argument, which takes precedence over the environment variable. Logging is enabled by setting the MENTOS_LOG environmental variable to a file path.
Which formatters and styles can I use in pygments.rb?
The HTML formatter is the default and others are selected by name, with bbcode and terminal given as examples. Pygments.css generates the stylesheet, optionally scoped to a selector, and a style option such as monokai picks the palette. Pygments.lexers, Pygments.formatters and Pygments.styles return the available choices.
Can I use a different Pygments installation with pygments.rb?
Yes. You specify the path to your own Pygments installation with Pygments.start, for example Pygments.start("/path/to/pygments").
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/pygments-pygments-rb)Was this useful?