mini-preproc
Version:
A simple, light, and fast text preprocessor implemented as a node transform stream.
226 lines (206 loc) • 7.57 kB
Markdown
copyright 2020, craigphicks, ISC license
MiniPreproc
----
# Outline
- A simple, light, and fast text preprocessor implemented as a node transform stream.
- Speed is ensured by only pre-processing upto a `STOP` command. After the `STOP` command, the MiniPreproc transform stream stops parsing lines and simply passes each "chunk" to output with no processing. That is as fast a transform stream can possibly work.
- The available commands are `IF`, `ELSE`, `ENDIF`, and `STOP`. Only one level of IF condition is allowed.
- An `IF` command is followed by a property: `IF{{<key>}}`, with true/false looked up in dictionary passed in by the caller.
- Each command must be prefixed by `//--` from the start of the line, with no spaces before the command. (Currently the choice of prefix is fixed.)
- There is a progmatic interface `createPreprocStream`. (Currently there is no CLI interface provided). See API section for details.
- The `strip` option allows all directives to be removed, so the output looks clean.
- When `strip` is false, the preproccer output can be run through the preprocessor again with any `defines` and yield the correct result (\*).
- *\*Yes, it is possible to write input which confuses the preprocessor (e.g. extra `//--`), however that is only possible before the first `STOP` command, so it is not a problem in practice.*
# Install
`mini-preproc` would most likely be used as a dev tool, so installation as a dev depency is demonstrated:
```
npm install mini-preproc --save-dev
```
# Example
Suppose an input file `./demo-test.txt` with content:
```text
...before
//--IF{{RELEASE}}
//--const someModule=require('some-module');
//--const RELEASE_MODE=true;
//--ELSE
const someModule=require('./some-module.js');
const RELEASE_MODE=false;
//--ENDIF
//--STOP
...after
```
The following example CLI program `demo-cli.js` pipes `stdin` through the stream created by `createPreprocStream`, to `stdout`, while also passing CLI parameters to that stream.
```js
'use strict';
const {createPreprocStream}=require('mini-preproc');
async function mpp(defines,strip){
process.stdin.pipe(
createPreprocStream(
defines,{strip:strip}))
.on('error',(e)=>{
console.error(e.message);
process.exitCode=1;
})
.pipe(process.stdout);
}
var defines=JSON.parse(process.argv[2]);
var strip=!!JSON.parse(process.argv[3]);
mpp(defines,strip);
```
Some runs of the program:
```
$ node demo-cli.js '{"RELEASE":true}' true < ./demo-test.txt
...before
const someModule=require('some-module');
const RELEASE_MODE=true;
...after
```
```
$ node demo-cli.js '{"RELEASE":false}' true < ./demo-test.txt
...before
const someModule=require('./some-module.js');
const RELEASE_MODE=true;
...after
```
```
$ node demo-cli.js '{"RELEASE":true}' false < ./demo-test.txt
...before
//--IF{{RELEASE}}
const someModule=require('some-module');
const RELEASE_MODE=true;
//--ELSE
//--const someModule=require('./some-module.js');
//--const RELEASE_MODE=true;
//--ENDIF
//--STOP
...after
```
```
$ node demo-cli.js '{"RELEASE":false}' false < ./demo-test.txt
...before
//--IF{{RELEASE}}
//--const someModule=require('some-module');
//--const RELEASE_MODE=true;
//--ELSE
const someModule=require('./some-module.js');
const RELEASE_MODE=true;
//--ENDIF
//--STOP
...after
```
A typescript version of the program is [listed in the appendix](#typescript-version-of-the-example-program)
*WARNING: [If using vscode debugger, output to process.stdout is NOT normally shown in the 'DEBUG CONSOLE' window.](#printing-to-processstdout-from-vscode-in-debug-mode)*
# API
- import
- `const miniPreproc=require('mini-preproc');`
- `miniPreproc.createPreprocStream(defines,options)`
- return value is a node transform stream, suitable for piping
- `defines` is an object containing zero or more property-value pairs which are used to evaluate the preprocessor `IF` conditions in the stream's input text. An absent property evaluates to `false`. Other values are converted to `true` or `false` according to normal javascript rules
- See [Preprocessor syntax](#preprocessor-syntax) for details about preprocesser directives.
- `options` has a single valid property: `strip`. When `strip` is true all the command lines are removed from output. Otherwise they remain.
- the returned stream may throw an error on illegal preprocessor syntax. The error is of class `MiniPreprocError`, derived from type `Error`.
## Preprocessor syntax
- All directives are on a single line, `//--<directive>` with no spaces before `//--`, and `<directive>` in [`IF`,`ELSE`,`ENDIF`,`STOP`].
- Any text outside of an `IF-ELSE-ENDIF` block of lines is passed through.
- Any text after the first `STOP` directive line is passed through.
Consider:
```
//--IF{{<key>}}
<text-block-true>
//--ELSE
<text-block-false>
//--ENDIF
```
- If `(<key> in defines)` evaluates to true
- If `options.strip` evaluates to true
- Output
```
<<text-block-true>> with any `//--` line prefixes removed
```
- else if `options.strip` evaluates to false
- Output
```
//--IF{{<key>}}
<text-block-true> with '//--' line prefixes removed
//--ELSE
<text-block-false> with '//--' line prefixes required
//--ENDIF
```
- else if `(<key> in defines)` evaluates to false
- If `options.strip` evaluates to true
- Output
```
<<text-block-false>> with any `//--` line prefixes removed
```
- else if `options.strip` evaluates to false
- Output
```
//--IF{{<key>}}
<text-block-true> with '//--' line prefixes required
//--ELSE
<text-block-false> with '//--' line prefixes removed
//--ENDIF
```
- examples of `<text-block-*> with '//--' line prefixes removed`
- Case: Has `//--` prefix
```
//--xxxx
```
becomes
```
xxx
```
- Case: Already doesn't have `//--` prefix
```
yyyy
```
stays as
```
yyyy
```
- examples of `<text-block-false> with '//--' line prefixes required`
- Case: Already has `//--` prefix
```
//--xxxx
```
stays as
```
//--xxxx
```
- Case: Doesn't have `//--` prefix
```
yyyy
```
becomes
```
//--yyyy
```
# Typescript
The typescript declaration are bundled with the package. (They are not provided seperately under npm ).
An example typescript program using the `mini-preproc` typescript delared argument types is [listed in the appendix](#typescript-version-of-the-example-program).
# version changes
## 1.1.0
- typescript definitions added
- supports "node": ">=10.13.0"
# Appendix
## Printing to process.stdout from vscode in debug mode.
When a program is started by the `vscode` debugger, output to `process.stdout` is NOT shown in the 'DEBUG CONSOLE' window. See 'outputCapture' at https://code.visualstudio.com/docs/nodejs/nodejs-debugging . However, the easiest solution is to start the program from outside the debugger with `node --inspect-brk my-prog.js` and then attach that process from vscode - the output to `process.stdout` will then show correctly in the terminal from which node was invoked.
## Typescript version of the example program
```typescript
'use strict'
import mpp=require('mini-preproc');
async function mppCli(defines:mpp.Defs,strip:boolean){
process.stdin.pipe(
mpp.createPreprocStream(
defines,{strip:strip}))
.on('error',(e)=>{
console.error(e.message);
process.exitCode=1;
})
.pipe(process.stdout);
}
var defines=JSON.parse(process.argv[2]);
var strip=!!JSON.parse(process.argv[3]);
mppCli(defines,strip);
```