Asciidoctor Server
This is an attempt to have asciidoctor run as a server. This is useful for programs that use asciidoctor’s cli, like static site generators (that aren’t written in ruby). Spawning a ruby process for every file is very wasteful and squanders asciidoctor’s speed.
Warning
|
THIS IS ALPHA SOFTWARE, USE AT YOUR OWN RISK. |
Installation
Tip
|
Use direnv (or many similar tools) to create an environment for your website. Install the client within this environment. |
Make sure the client and server version agree. Check the list of git tags. We are at semver 0 and so regularly make breaking changes.
Client
The client is availible as a cargo package and as a nix package
cargo install asciidoctor-client
{
description = "A Nix Environment for running asciidoctor-client";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05";
inputs.flake-utils.url = "github:numtide/flake-utils";
inputs.asciidoctor-server = {
url = "github:hybras/asciidoctor-server";
inputs.nixpkgs.follows = "nixpkgs";
inputs.flake-utils.follows = "flake-utils";
};
outputs = { self, nixpkgs, flake-utils, asciidoctor-server }:
flake-utils.lib.eachDefaultSystem (system:
let pkgs = import nixpkgs { inherit system; }; in
{
devShells = {
default = pkgs.mkShell {
buildInputs = [
asciidoctor-server.packages.${system}.asciidoctor-client
];
};
};
});
}
Server
Use one of the following options. Feel free to also add specific versions of asciidoctor and its extensions / optional dependencies. The server will load them on request.
-
bundle add asciidoctor-server
-
Add the following to your Gemfile
gem "asciidoctor-server", "$VERSION"
-
gem install asciidoctor-server
Shim Script
Add the following shim to your path under the name asciidoctor
Note
|
I hard code the address of the server, but feel free to leave it as an environment variable. |
#!/bin/bash
exec asciidoctor-client --address $ADDRESS "${@:1:$#-1}" yeet
Usage
Warning
|
I haven’t yet setup an easy way to stop/start the server or have the client discover the server. Also there’s no security so don’t use in prod. |
Caution
|
Extensions cannot be loaded / unloaded on demand. Once loaded, they are enabled for all subsequent requests. This is not a problem for hugo, which passes the same cli args (ie, extensions) on every invocation. It might be a problem for other setups |
Pick an address for the server to bind to, maybe something like $PROJECT_ROOT/.asciidoctor-server.sock
-
Start the server with
bundle exec
, iebundle exec asciidoctor-server -addr $ADDRESS
. You’ll probably need to use job control or a separate shell to have this run in the background. -
Build your website (that’s
hugo
for me). If your shim was setup correctly, your static site generator will use it. You can test this by adding print statement to the shim script.
Implementation
This project was written with the constraint that hugo and asciidoctor should run unmodified.
The client and server communicate using grpc, with the client mapping its cli arguments to the asciidoctor api. Only a small subset of asciidoctor’s arguments are supported.
Normally, hugo spawns a converter process per file. The program that is spawned corresponds to the content format. Markdown support is built into hugo, so it spawns a goroutine for markdown files.
Asciidoctor is written in ruby, and is a very large program with many optional features. While its conversion speed is excellent, its startup time is not.
This project consists of a lightweight client (there are go and rust clients in tree, though it could have been any compiled language). The client starts quickly and makes a grpc request to the server. We only pay the startup cost once.
Unfortunately, this makes the conversion process significantly more complicated. There is now a server and client sitting between hugo and asciidoctor. Both are poorly written, exposing only a fraction of asciidoctor’s functionality. If unsupported args are passed, the client should error.
Development
You’ll need the protobuf compiler, protoc
, version 3. This is included in the nix flake for this repo.
Client
-
Install the go protobuf compiler plugins
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28 $ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
-
Update your PATH so that the protoc compiler can find the plugins. This is not necessary if you’re using direnv.
export PATH="$PATH:$(go env GOPATH)/bin"
Server
Do the following under server
After checking out the repo, run bin/setup
to install dependencies. Then, run rake test
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and the created tag, and push the .gem
file to [rubygems.org](https://rubygems.org).
Future
Given how hacky this is, this is not a long term solution. Long term solutions include:
-
an implementation of asciidoc with a shorter startup time (perhaps in a compiled language?)
-
If a go implementation existed, it could be included in hugo. The author has expressed support for this idea given a suitable go library.
-
Asciidoctor’s startup time might improve, but this is a difficult undertaking
-
-
The basic principle of this (a single process / goroutine that does all conversions, and communication occurs over message passing) is merged into hugo. This is far more feasible than the other options, but would require a rearchitecture of how hugo handles external converters. It wouldn’t make sense to do this solely for asciidoc, unfortunately.