# `evalmd` [![github actions](https://img.shields.io/github/check-runs/reggi/evalmd/master)](https://github.com/reggi/evalmd/actions) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://github.com/feross/standard) Write javascript in your markdown & execute it. I wanted a way of making sure the javscript that I write in markdown was valid and worked, not only for my own sake, but to ensure the examples and code provided was valid for others to reliably refer to. - [Usage](#usage) - [Testing](#testing) - [Install](#install) - [Current Module Definition](#current-module-definition) - [Preventing Eval](#preventing-eval) - [Prepend Flag](#prepend-flag) - [Inspiration](#inspiration) - [Todo](#todo) ## Usage `evalmd` will scan a markdown file searching for a javascript code declaration, all of them are gathered then the string sent to [`eval`](https://github.com/pierrec/node-eval). ```javascript ``` or ```js ``` ## Options ``` evalmd - Evaluate the javascript in markdown files Options: -i, --include Includes prevented blocks [default: false] -P, --prevent Prevent code from being evaluated [default: false] -b, --block Change the scope to block level [default: false] -o, --output Output js [choices: false, true, "preserve", "concat", "preserveAlter", "concatAlter"] [default: false] -n, --nonstop Runs all files regardless if error [default: false] -s, --silent Silence `evalmd` logging [default: false] -u, --uniform Does not use absolute urls when error logging [default: false] -D, --debug Debug Output [default: false] -e, --eval Comma-separated list of block kinds to evaluate (e.g. js, sh) [default: "js"] --sloppy Evaluate code in sloppy (non-strict) mode [default: false] --eslint Derive parser settings (ecmaVersion, sourceType, parser) from your eslint config per block [default: false] -h, --help Show help [boolean] --path Prefix local module with path [default: "./"] --package Th path of a package.json [default: "./package.json"] --version Show version number [boolean] -d, --delimeter [default: false] Examples: evalmd Evaluate file(s) evalmd -n Evaluate file(s) uninterrupted evalmd -b Evaluate block(s) evalmd -bn Evaluate block(s) uninterrupted evalmd -Po Outputs js file(s) evalmd -Pio Outputs js file(s) with all block(s) (for linting) evalmd -Pob Outputs block(s) evalmd -Piob Outputs all blocks(s) (for linting) evalmd --eval=js,sh Evaluate js and sh block(s) ``` ## Block kinds By default `evalmd` evaluates `js` (and `javascript`) blocks. Use `--eval` to pick which fenced block kinds are evaluated, given as a comma-separated list - or by repeating the flag. Each kind is dispatched to its own evaluator, so more kinds can be added over time. evalmd ./readme.md --eval=js,sh evalmd ./readme.md --eval=js --eval=sh ### `sh` blocks An `sh` block is one or more prompts and their expected output. A prompt line begins with `$`, `%`, or `>`; every line after it, up to the next prompt, is the command's expected combined `stdout`/`stderr` output. Each command must exit `0` and its output must match, otherwise `evalmd` exits non-zero. Multiple prompts per block are supported. ```sh > echo hello hello > node -e "console.log('a'); console.error('b')" a b ``` ## Sloppy mode Blocks are evaluated in strict mode by default. Pass `--sloppy` to evaluate in sloppy (non-strict) mode instead - for example, to use `with`. Sloppy blocks use CommonJS (`require`); ES `import`/`export` syntax is only available in the default strict mode. ## eslint parser config Dependency detection parses each block with a fixed `ecmaVersion` by default, so modern syntax (object spread, etc.) can fail to parse. Pass `--eslint` to instead derive the parser settings from your own eslint config, per block. For each block, evalmd resolves the config for the virtual path `.md/.js` (where `` is the block number), so eslint `overrides` (eslintrc) or `files` (flat config) that target `**/*.md/*.js` apply — the same convention as [`eslint-plugin-markdown`](https://github.com/eslint/eslint-plugin-markdown). This is how each block (or block kind) gets its own settings. eslint 8, 9, and 10 are supported, in both eslintrc and flat config. `--eslint` honors `ecmaVersion`, `sourceType`, and a configured `parser` (such as `@typescript-eslint/parser`). It requires `eslint` to be installed — evalmd does not depend on it, so nothing changes for users who don't pass `--eslint`. ## Testing Here is a bit of javascript that has an assertion at the end of it. The assertion will throw an error if the result of the `.equal` is invalid. This file is used as a test to see if `evalmd` is in working order. ```javascript var assert = require('assert') var helloWorld = 'foo' assert.equal(helloWorld, 'foo') ``` Here's another one: ```js var assert = require('assert') var helloWorld = ['foo', 'bar'] assert.deepEqual(helloWorld, ['foo', 'bar']) ``` If you run this file using `test-markdown` it will exit with a status code of `0`, meaning no exceptions where thrown. This overall provides a different way of sharing and explaining code, because it's much more formal then a complicated test file. ## Install Try it yourself by executing the command: ```bash npm install evalmd -g evalmd ./readme.md ``` ## Current Module Definition If the command is ran within a node module with a `package.main` and a `package.name` then that reference will be replaced throughout your code. For instance the following passes. ```javascript var evalmd = require('evalmd') assert.equal(typeof evalmd, 'function') ``` ## Prevent Eval Declaration The `preventEval` declaration allows you to prevent a code block from being evaluated. There are two different ways of declaring a code block. One is to use an `anchor`. Here's an example: [](#preventEval) ```js module.exports = 'alpha-path' ``` When adding a `preventEval` declaration in this way the name of the file is the `text` content of the anchor. Another way to declare a block as a file is using a comment. Here's an example: ```js // preventEval module.exports = 'alpha-path' ``` When the first line of a code block is a comment with the word `preventEval` in front the string after will be interpreted as the file. > Note. The match patterns for `prevent eval` and `preventEval` are case-insensitive. So `pReVenTeVaL` works just as well. # File Eval Declaration The `fileEval` declaration allows you to define a code block as a file. There are two different ways of declaring a code block. One is to use an `anchor` tag with the `href` set to `#fileEval`. Here's an example: This is the file [./alpha.js](#fileEval). ```js module.exports = 'alpha-path' ``` When adding a `fileEval` declaration in this way the name of the file is the `text` content of the anchor. Another way to declare a block as a file is using a comment. Here's an example: ```js // fileEval ./alpha.js module.exports = 'alpha-path' ``` When the first line of a code block is a comment with the word `fileEval` in front the string after will be interpreted as the file. > Note. If any of the code blocks in a file contain a `fileEval` declaration then the entire file will be run as `blockScope`. > Note. The match patterns for `file eval` and `fileEval` are case-insensitive. So `fILeEvAl` works just as well. ## Prepend Flag If you want to run code from `docs`, and your javscript files are in the root directory, you can use the `--prepend` flag to prepend every local module reference with the value. Let's say you run the command: ```bash evalmd ./docs/my-document.md --prepend='..' ``` And you have `my-document.md` with the conents: ```javascript var alpha = require('./alpha.js') ``` The prepend command will transform this code to this before it executes it. ```javascript var alpha = require('../alpha.js') ``` > Note: it's a prepend `path.join()` string, and not a concatenated prepend. ## Inspiration I wanted a way of writing unit tests in markdown. I've been playing around with things like [`yamadapc/jsdoctest`](https://github.com/yamadapc/jsdoctest) which parses `JSDoc` declarations looking for `@example` keywords in source code and creates a test based on them. ## Todo * Add ability for custom linting support (<3 [`standard`](https://github.com/feross/standard#standardlinttexttext-opts-callback))