qmjs (?js)
Run JavaScript inline with text, allowing code sections to echo text into the document. Think <?php ?>
.
qmjs parses text files for <?js ... ?>
blocks, and executes their contents as JavaScript.
The strings echoed by the script in each code section are inserted in-place in the output document.
Examples
Echoing
Running a text file containing:
<?js
echo("qmjs: ");
echoln("because many services don't like names starting with '?'."); // appends a newline character
echo("Unfortunately");
?>
I would have preferred '?js'.
through qmjs
results in an output file containing:
qmjs: because many services don't like names starting with '?'.
Unfortunately.
I would have preferred '?js'.
Variable scope
You can fill the <?js ?>
sections with JavaScript, and have as many sections as you need.
Global variables persist between code sections, but local variables (declared with var
) do not. Use the this
object
to persist over code sections in the current input being processed to avoid polluting the global scope (which is shared
with include()
'ed files).
<?js
globalVariable = "this IS usable in subsequent code sections";
this.variable = "this IS ALSO usable in subsequent code sections";
var localVariable = "this IS NOT usable in subsequent code sections";
?>
<?js
echoln(globalVariable); // OK
echoln(this.variable); // OK
echoln(localVariable); // Error!
?>
Using built-in formatting helpers
The same qmjs
object that you can require()
in your own module is available inside code sections.
There are Markdown formatting helpers you can use like:
<?js
md = qmjs.markdown;
echo(md.h1("Heading 1"));
echo(md.orderedList([ "a", "b", [ "b0", "b1" ], "c" ]);
?>
That result in output like:
# Heading 1
* a
* b
* b0
* b1
* c
See Formatting helpers for lists of what is available.
Including other files verbatim
Using includeText()
like:
<?js
includeText('./README.md');
?>
Echoes the contents of files into the output text.
Asynchronous flow
echo()
can be used in asynchronous callbacks by first calling the wait()
function, and then signalling that the code
section is complete by calling the returned function.
For example:
<?js
var done = wait();
echoln("This is the start of a code section.");
setTimeout(function() {
echoln("This is echoed 1 second later.");
done(); // Don't forget this
}, 1000);
echoln("This is the end of the code section.");
?>
<?js
echoln("This is the next code section.");
?>
Outputs:
This is the start of a code section.
This is the end of the code section.
This is echoed 1 second later.
This is the next code section.
You may use as many wait()
/done()
pairs as required in each code section.
Note: If using qmjs
programatically, you must use the async versions of qmjs()
and qmjs.file()
(see Usage API)
otherwise errors will be reported.
Including other qmjs files
Given a file e.g. LoremIpsum.qmmd
:
# This heading is included from LoremIpsum.qmmd
<?js
echo("Echoed by JS in LoremIpsum.qmmd: ");
echoln("Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
exports.secondSentence = "Donec a diam lectus.";
?>
Synchronously
The return value of the includeSync()
method is the exports
object from LoremIpsum.qmmd
:
<?js
var lorem = includeSync("./LoremIpsum.qmmd");
echoln();
echo("Echoed by JS in main file: ");
echoln(lorem.secondSentence);
?>
Note: If the file being included uses wait()
, you must use the asynchronous include()
.
Asynchronously
The asynchronous include()
optionally takes a callback function that receives an error argument and the exports
object.
The async wait()
and completion signalling is handled within include()
.
<?js
include("./LoremIpsum.qmmd", function(error, lorem) {
echoln();
echo("Echoed by JS in main file: ");
echoln(lorem.secondSentence);
});
?>
Output
# This heading is included from LoremIpsum.qmmd
Echoed by JS in LoremIpsum.qmmd: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Echoed by JS in main file: Donec a diam lectus.
require()
and fs
Node's The current working directory is temporarily changed to that of the text file being processed, so any relative file paths used for reading/writing are relative to that.
The require()
function is available, but is modified to first search relative to the text file currently being processed (for
relative paths), and in the node_modules
folder in the first parent directory of the file being processed that
contains node_modules
.
Usage
To install:
npm install qmjs
CLI
To run the command line executable:
qmjs [INPUT_FILENAME] [options]
If INPUT_FILENAME
is not specified, qmjs will read from stdin
until it is closed.
Options:
- -o OUTPUT_FILENAME -- Path to the desired output file. If not specified, output is sent to
stdout
. - --force -- Set this flag to force continue processing when exceptions are thrown (error messages are instead inserted into the document). Otherwise will halt on exceptions.
- -token.start START -- Token as string, or multiple listed in an array literal, that signal the start of a code section. Defaults to
<?js
. - -token.end END -- Token as string, or multiple listed in an array literal, that signal the end of a code section. Defaults to
?>
.
These command line options, and any other user specified ones, are accessible in code sections in the opts
object.
For example, the opts
object can be echoed into the document with:
<?js
echoln(JSON.stringify(opts, null, 2));
?>
Periods in option keys resolve to object paths, and values can be JSON that contains no spaces. See dot-argv for details.
API
In addition to the command-line program, you can also use qmjs as a module:
var qmjs = require("qmjs");
Synchronous
The string input version:
var outputText = qmjs(inputFileText, opts); // -> string
The version that reads from a file:
var outputText = qmjs.file(inputFileName, opts) // -> string
These synchronous functions can throw errors.
Asynchronous
The string input version:
qmjs(inputFileText, opts, function(error, output) {
// output is string
});
The version that reads from a file:
qmjs.file(inputFileName, opts, function(error, output) {
// output is string
});
With the opts
argument as an object containing the command-line options, that defaults to:
{
"force": false,
"tokens": {
"start": "<js",
"end": "?>"
}
}
Optionally, you can provide:
opts.module
that will be used as the executed code'smodule
object, which expects anexports
property.opts.inputFilename
that will be used for error reporting, and all file read/write will be relative to that path.
And any other variables you need to provide to the code sections through the opts
object.
Formatting helpers
Markdown
qmjs.markdown
provides these methods and variables to aid creating Markdown:
h1(str, padNewlines=true)
h2(str, padNewlines=true)
h3(str, padNewlines=true)
italics(str)
bold(str)
boldItalics(str)
code(str)
- inline codecodeBlock(str)
json(obj)
img(path, [altText, title])
link(url, [text])
orderedList(array, padNewlines=true)
unorderedList(array, padNewlines=true)
encode(str)
- encode HTML entitiesdecode(str)
- decode HTML entitiesendl
- new line characterhr
- horizontal rule
HTML
Implemented and available in qmjs.html
object. Check src/formatting/html.js
for details.
TODO:
wait()
timeout- HTML formatter doc