Mike Slinn

jekyll_flexible_include

Published 2020-10-03. Last modified 2023-05-18.
Time to read: 5 minutes.

This page is part of the jekyll_plugins collection.

Jekyll's built-in include tag does not support including files outside of the _includes folder. Originally called include_absolute, the tag plugin now does much more than just including absolute file names. The plugin was renamed jekyll_flexible_include, and the tag is called flexible_include. This plugin now supports 4 types of includes:

  1. Filenames relative to the top-level directory of the Jekyll web site. It is unnecessary to preface these paths with ./).
  2. Absolute filenames (first character is /). This feature can be modified or denied where security is a concern by specifying an array of Ruby glob expressions in the FLEXIBLE_INCLUDE_PATHS environment variable.
  3. Filenames relative to the user home directory (first character is ~). This feature can also be modified or denied where security is a concern by specifying an array of Ruby glob expressions in the FLEXIBLE_INCLUDE_PATHS environment variable.
  4. Executable filenames on the PATH (first character is !). This feature can be disabled by defining the DISABLE_FLEXIBLE_INCLUDE environment variable before launching Jekyll.

In addition, filenames that require environment expansion because they contain a $ character are expanded according to the environment variables defined when the jekyll build process was launched.

Installation

Add the following highlighted line to your Jekyll project's Gemfile, within the jekyll_plugins group:

Shell
group :jekyll_plugins do 
  gem 'jekyll_flexible_include'
end 

And then execute:

Shell
$ bundle

Add the following to your Jekyll _config.yml:

Shell
plugins:
  - flexible_include

Syntax

The following are equivalent:

  • {% flexible_include path [ OPTIONS ] %}
  • {% flexible_include 'path' [ OPTIONS ] %}
  • {% flexible_include "path" [ OPTIONS ] %}
  • {% flexible_include file='path' [ OPTIONS ] %}
  • {% flexible_include file="path" [ OPTIONS ] %}

By default, the included file will escape characters <, { and }, unless do_not_escape is specified. Note that the [square brackets] merely indicate optional parameters and are not intended to be written literally.

The file must be specified as a relative or absolute path on the server, or a command to execute. A URL cannot be provided (you cannot write a file name that starts with http: or https:). This capability is under consideration for a possible future release.

Options

  • do_not_escape includes the content without HTML escaping it.
  • pre causes the included file to be wrapped inside a <pre></pre> tag, no label is generated.
  • strip removes leading and trailing whitespace.

The following options imply pre:

  • copyButton draws an icon at the top right of the <pre></pre> area, which causes the included contents to be copied to the clipboard.
  • download uses the name of the file as a label, and displays it above the <pre></pre> tag. Clicking the label causes the file to be downloaded.
  • label specifies that an automatically generated label be placed above the contents. There is no need to specify this option if download or copyButton options are provided.
  • label="blah blah" specifies a label for the contents; this value overrides the default label. The value can be enclosed in single or double quotes. If you want to display a text message other than the file name, use this option.
  • number numbers the lines.

Usage Examples

This example shows a typical set of options. A label is automatically generated from the file name or process output that is included:

Shell
{% flexible_include ~/.mem_settings.yaml download copyButton %}

Here is what the above looks like when rendered by a web browser:

append_file_name: sample.html
output_format: qt

... and without the download option, but still including the copyButton option:

.mem_settings.yaml
append_file_name: sample.html
output_format: qt

... now with just the pre option:

.mem_settings.yaml
append_file_name: sample.html
output_format: qt

... and finally this is the result of using flexible_include without any options. The generated text is outlined in a <pre></pre> tag so it is noticable. The file has a trailing newline, which is apparent below, however flexible_include trims any leading and trailing whitespace when the pre keyword option is specified or implied, or when the strip keyword option is specified. You can disable this behavior by specifying strip=false.

append_file_name: sample.html
output_format: qt



Dark Mode

Normally my website uses light colors, however some content displays better on a dark background. The dark option causes the generated <pre> tag to have the dark class applied. You can define the CSS for the dark and darkLabel classes. The CSS that defines those classes for this web site is here.

.mem_settings.yaml
append_file_name: sample.html
output_format: qt

Home Directory

The included file path can use a tilde (~) to denote the user $HOME directory.

{% flexible_include ~/.mem_settings.yaml %}
append_file_name: sample.html
output_format: qt

Environment variables can be used; they will be expanded according to the environment variables that were current in the process when the Jekyll generator launched.

{% flexible_include '/home/mslinn/.gitconfig' %}
[alias]
  lol=log --graph --decorate --pretty=oneline --abbrev-commit
  lola=log --graph --decorate --pretty=oneline --abbrev-commit --all
  ls=ls-files
  st = status
  ci = commit
  br = branch
  co = checkout
  df = diff
  dc = diff --cached
  dif = diff --word-diff=color --ignore-space-at-eol
  lg = log -p
  ign = ls-files -o -i --exclude-standard
	pwd = !pwd
[branch "master"]
	remote = origin
	merge = refs/heads/master
[core]
	filemode = false
	autocrlf = input
	safecrlf = false
	excludesfile = C:\\Users\\Mike Slinn\\Documents\\gitignore_global.txt
	pager = less -F
[color]
	status = auto
	branch = auto
    ui = auto
[gui]
	trustmtime = true
[push]
  default = matching
  autoSetupRemote = true  
[user]
	name = Mike Slinn
	email = mslinn@mslinn.com
[rebase]
	autostash = true
[diff "exif"]
	textconv = exiftool
[diff]
	compactionHeuristic = true
	renames = 0
[hub]
	protocol = git
[pull]
	rebase = false
[init]
	defaultBranch = master
[log]
	date = local
[creategem]
	githubuser = mslinn
[nugem]
	githubuser = mslinn
	gemserver = ""
[safe]
	directory = /mnt/f/work/ibm
[credential]
	helper = store

Environment Variable Expansion

This example includes the output of running the bash command which jekyll, according to the Ruby environment that was current when the Jekyll generator was launched:

{% flexible_include '!which jekyll' %}
/home/mslinn/.rbenv/versions/3.1.2/bin/jekyll

Strip Leading and Trailing Whitespace

Sometimes a file or process contains leading or trailing whitespace.

{% flexible_include copyButton download .mem_settings.yaml %}

Renders as:



append_file_name: sample.html
output_format: qt



The strip keyword option removes leading and trailing whitespace.

{% flexible_include copyButton download strip .mem_settings.yaml %}

Renders as:

append_file_name: sample.html
output_format: qt

Does This Make Your Brain Hurt?

Ready to have your mind twisted? This invocation:

Shell
{% flexible_include '~/.mem_settings.yaml' download copyButton
  label='&lcub;% flexible_include download copyButton
    ~/.mem_settings.yaml %&rcub;' %}

Renders like this:

append_file_name: sample.html
output_format: qt

Hint: &lcub; and &rcub; are HTML entities for { and }, respectively.

Highlighting Text

A regular expression can be passed to the highlight option. This causes text that matches the regex pattern to be wrapped within a <span class="bg_yellow"></span> tag.

The following highlights filenames in a directory listing that contain django-admin that might contain dots, underscores, dashes and forward slashes:

Shell
{% flexible_include
  highlight="[\w./\-_]*django-admin[\w.\-_]*"
  label="ls ~/venv/aw/bin/*"
  file="!ls ~/venv/aw/bin/*"
%}

Renders as:

ls ~/venv/aw/bin/*
/home/mslinn/venv/aw/bin/activate
/home/mslinn/venv/aw/bin/activate.csh
/home/mslinn/venv/aw/bin/activate.fish
/home/mslinn/venv/aw/bin/activate.ps1
/home/mslinn/venv/aw/bin/activate.xsh
/home/mslinn/venv/aw/bin/activate_this.py
/home/mslinn/venv/aw/bin/django-admin
/home/mslinn/venv/aw/bin/django-admin.py
/home/mslinn/venv/aw/bin/easy_install
/home/mslinn/venv/aw/bin/easy_install-3.8
/home/mslinn/venv/aw/bin/easy_install3
/home/mslinn/venv/aw/bin/faker
/home/mslinn/venv/aw/bin/pip
/home/mslinn/venv/aw/bin/pip3
/home/mslinn/venv/aw/bin/pip3.10
/home/mslinn/venv/aw/bin/pip3.11
/home/mslinn/venv/aw/bin/pybabel
/home/mslinn/venv/aw/bin/python
/home/mslinn/venv/aw/bin/python3
/home/mslinn/venv/aw/bin/python3.8
/home/mslinn/venv/aw/bin/sqlformat
/home/mslinn/venv/aw/bin/wheel
/home/mslinn/venv/aw/bin/wheel-3.8
/home/mslinn/venv/aw/bin/wheel3

/home/mslinn/venv/aw/bin/__pycache__:
django-admin.cpython-39.pyc

Numbering Lines

Shell
{% flexible_include
  label="ls ~/venv/aw/*"
  file="!ls ~/venv/aw/*"
  number
%}

Renders as (notice that the numbers are unselectable):

ls ~/venv/aw/*
  1: /home/mslinn/venv/aw/pyvenv.cfg
  2: 
  3: /home/mslinn/venv/aw/bin:
  4: __pycache__
  5: activate
  6: activate.csh
  7: activate.fish
  8: activate.ps1
  9: activate.xsh
 10: activate_this.py
 11: django-admin
 12: django-admin.py
 13: easy_install
 14: easy_install-3.8
 15: easy_install3
 16: faker
 17: pip
 18: pip3
 19: pip3.10
 20: pip3.11
 21: pybabel
 22: python
 23: python3
 24: python3.8
 25: sqlformat
 26: wheel
 27: wheel-3.8
 28: wheel3
 29: 
 30: /home/mslinn/venv/aw/lib:
 31: python3.10
 32: python3.11
 33: python3.8
 34: python3.9

CSS

Below are the CSS declarations that I defined for the flexible_include tag that produced the above output. This CSS is the same as used by pre.

.clear {
  clear: both;
}

ol li .codeLabel {
  padding-left: 1.75em;
}

.error {
  color: white;
  background-color: darkred;
  padding: 2px;
}

#main-content li > div.jekyll_pre, li > div.jekyll_pre {
  margin-top: 20px;
}

.jekyll_pre + div,
.jekyll_pre + p,
.jekyll_pre + ul,
.jekyll_pre + ol,
.jekyll_pre + dl {
  margin-top: 20px;
}

.jekyll_pre + .jekyll_pre {
  margin-top: 2em;
}

.pre_tag {
  margin-bottom: 1em;
}

.tree {
  line-height: 1;
}

Restricting Directory Access

By default, flexible_include can read from all directories according to the permissions of the user account that launched the jekyll process. For security-conscience environments, the accessible paths can be restricted.

Defining an environment variable called FLEXIBLE_INCLUDE_PATHS prior to launching Jekyll will restrict the paths that flexible_include will be able to read from. This environment variable consists of a colon-delimited set of regular expressions. For example, the following restricts access to only the files within:

  1. The ~/my_dir directory tree of the account of the user that launched Jekyll.
  2. The directory tree rooted at /var/files.
  3. The directory tree rooted at the expanded value of the $work environment variable.
Shell
$ export FLEXIBLE_INCLUDE_PATHS='~/my_dir/.*:/var/files/.*:$work/.*'

If a reference to an unauthorized file is intercepted, a big red message will appear on the generated web page that says something like

Access to #{path} denied by FLEXIBLE_INCLUDE_PATHS value.

... and an error message will be logged on the console that looks something like:

ERROR FlexibleInclude: _posts/2020/2020-10-03-jekyll-plugins.html - Access to #{path} denied by FLEXIBLE_INCLUDE_PATHS value.

Restricting Arbitrary Processes

By default, flexible_include can execute any command. You can disable that by setting the environment variable DISABLE_FLEXIBLE_INCLUDE to any non-empty value.

Shell
$ export DISABLE_FLEXIBLE_INCLUDE=true

If a potential command execution is intercepted, a big red message will appear on the generated web page that says:

Arbitrary command execution denied by DISABLE_FLEXIBLE_INCLUDE value.

... and an error message will be logged on the console that looks something like:

ERROR FlexibleInclude: #{path} - Arbitrary command execution denied by DISABLE_FLEXIBLE_INCLUDE value.



* indicates a required field.

Please select the following to receive Mike Slinn’s newsletter:

You can unsubscribe at any time by clicking the link in the footer of emails.

Mike Slinn uses Mailchimp as his marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp’s privacy practices.