Jekyll: Include partial snippets of code
The Jekyll include tag is useful when including files or templates on a page. Combined with the highlight tag, it makes including code snippets easy. However, it will include the complete file, and often it is desirable to include only a few lines, or maybe a method. That could of course be done by simply copy/pasting the code in question into the article, but then the code gets out of sync if the example file is changed.
The basic usecase is something like this:
{% highlight java %}
{% include src/HelloWorld.java %}
{% endhighlight %}
Ruby based plugin
A Jekyll tag to include only a section of a file would be great. As far as I can tell, that does not exist yet, so I started writing one. Unfortunately, Github Pages does not allow custom plugin for security reasons. There are work-arounds for that, but it also makes the deployment more complex, and loses the convenience of being able to edit the articles and code directly on github.com.
Sans error handling or caching, a simple implementation could look like this. It works outside Github Pages, so it's a start.
module Jekyll
class IncludeLines < Liquid::Tag
Syntax = /(#{Liquid::QuotedFragment}+)\s(\d+)\s(\d+)\s\z/o
def initialize(tag_name, markup, options)
super
if markup =~ Syntax
@file = $1
@startline = $2.to_i
@endline = $3.to_i
else
raise "Syntax error in includelines: " + markup
end
end
def render(context)
lines = IO.readlines(context.evaluate(@file))
part = lines.drop(@startline)
part.take(@endline - @startline)
end
end
end
Liquid::Template.register_tag('includelines', Jekyll::IncludeLines)
Liquid slice and split
Using the Liquid capture block, it's possible to read a file and store it as a string variable. It can then be processed by Liquid instead of the plugin, and works fine with Github pages. The Liquid syntax is certainly verbose, but it gets the job done.
An initial implementation cutting the file content as a single string looked like this. However, it is far from ideal, since the character index and count will shift with any source code modifications on the included file.
{% capture filecontent %}
{% include src/HelloWorld.java %}
{% endcapture %}
{% highlight java %}
{{ filecontent | slice: 132, 57 }}
{% endhighlight %}
A slightly better solution uses the same idea, but operates on line numbers instead. It is almost as fragile when it comes to changes, but at least usable.
{% capture filecontent %}
{% include src/HelloWorld.java %}
{% endcapture %}
{% assign lines = filecontent | newline_to_br | split: '<br />' %}
{% highlight java %}
{% for line in lines offset:10 limit:5 %}{{ line }}{% endfor %}
{% endhighlight %}
A helper include file implementing this idea can be found here. It can be used like this:
{% include includelines filename='src/HelloWorld.java' start=10 count=5 %}
Include method
Ideally, it would be possible to mark the start of a line to include, and then indicate how much should be included. Improving on the line based iterator above, this helper file does that. Usage goes like this:
{% highlight java %}
{% include includemethod filename='src/HelloWorld.java' method='test()' before=5 after=1 %}
{% endhighlight %}
It also adds options to include lines before and after the specified method, for example for comment blocks or further methods below the first. There are of course some extensions which could be made, e.g. to include multiple split sections; support other non-C like languages, etc. The linked code is under the GPL 3 license, so feel free to improve.