lite-utils-md_code
Extracts runnable code inside MD fences,
tagged with an identifier, so you can eval
them from your test suite. The main use case:
keeping documentation examples honest by actually
running them as specs.
Writing the documentation
Write runnable code in the MD file and mark it with a tag and a unique identifier. The fenced block looks somewhat like this in raw MD:
```ruby test rspec_example
expect(true).to be(true)
```
The info string after the opening triple-backtick follows a fixed
grammar: <language> <tag> <key>, space-separated.
language– the language identifier used for syntax highlighting (e.g.ruby). Not interpreted by this library.tag– groups snippets so they can be filtered together. Matches thetag:keyword passed toMdCode.instance.key– an identifier for this specific snippet, used later to reference it viaSPECS.snippet!(:key). Must be unique in a tag.
So ```ruby test unique_identifier declares a Ruby snippet, tagged
test, retrievable under the key unique_identifier.
Writing the tests
Load the snippets somewhere in your specs. The positional
argument is the path to the file containing code snippets,
optional tag keyword specifies the tag used to filter
them – defaults to :test. Another
optional keyword is dir, which can be used to pass over
__dir__ from the caller's location.
SPECS = MdCode.instance('../../../README.md', dir: __dir__)
Configure RSpec so that it ensures all snippets have been consumed
RSpec.configure do |config|
config.add_setting :readme_spec, default: false
config.(file_path: %r{spec/utils/md_code}) do ||
config.readme_spec = true
end
config.after(:suite) do
next unless config.readme_spec
SPECS.ensure_consumed!
end
end
You may need a more complex configuration depending on how you structure your specs; however, the setup shown above works even with complex readme specs spreading across multiple files.
Eval the snippets inside your specs – either as a part of the context or inside your examples. Note the bang at the end of the method name – this method tracks usage of individual keys and raises if it was called twice with the same key. This makes it easier to avoid mistakes where your test evals some key by mistake and omits another, making sure your whole documentation is tested as intended.
it 'is true' do
eval(SPECS.snippet!(:rspec_example))
end
Obviously the bang variant is meant for snippets that run
exactly once across the whole suite. When the snippet
represents shared setup – like a memoized helper body –
it must be allowed to evaluate repeatedly. In such
case use SPECS.snippet without the bang. It still tracks
usage but doesn't raise when the key is accessed multiple times.
A note on eval: This library works by evaluating snippet contents
as Ruby source. That's inherent to the pattern – the whole point is
to run your documentation's example code.
Snippets only ever come from files that already live in your own repository, and they're only evaluated inside your own test suite. You're not pulling snippets from user input, a network request, or any other untrusted source – just from docs you wrote yourself.
License
This library is published under the MIT license