---
title: nonplain
description: Plaintext files, with metadata
long-description: This is a library for parsing, manipulating, and exporting plaintext files with metadata stored as YAML or JSON frontmatter.
---
Plaintext files are commonly used for notes, code, and documentation. Plaintext files are nondescript by definition: only their content and their filenames describe them. Jekyll popularized YAML frontmatter to enrich plaintext files for static site generation. These "nonplain" files have proven useful in other contexts requiring metadata, such as notetaking.
One drawback of using frontmatter in plaintext files is that there are few general-purpose tools for parsing and operating on these files' metadata and body content separately. The goal of nonplain is to make plaintext files with metadata easier.
- What this library does
- What a nonplain file is
- Parsing nonplain files
- Transforming nonplain file data
- Exporting nonplain file data
- Other useful methods
- Related work
- Contributing
The concept: define the difference between metadata and body content and parse the file accordingly.
Once the file is parsed, the metadata and body can be read, transformed, and exported together or separately to accomplish various goals such as:
- analyzing files according to metadata
- compiling relevant files for pagination
- converting files to some other, less plain format
- whatever else you want to do
In order to get there, we need to:
- define what a nonplain file is
- parse nonplain files
- transform nonplain file data
- export nonplain file data
A "nonplain" file is any plaintext file that contains metadata as frontmatter. It's not really a file format, but rather a way to think about files of any plaintext format that begin with frontmatter.
In the future, this may be more customizable. For our purposes, frontmatter is a "fence" of 3 dashes ---
on the first line of the file, followed by valid JSON or YAML beginning on the next line, followed by a final fence of 3 dashes ---
on the line after the last line of JSON or YAML data.
It looks like this:
---
{
"what is this?": "it's JSON frontmatter"
}
---
or this:
---
syke: now it's YAML
---
The body is everything after the frontmatter.
When the file is put together, it looks like this:
---
{
"what is this?": "it's called JSON"
}
---
This is the body of
the first file
or this:
---
syke: now it's YAML
---
This is the body of
the second file
To parse a nonplain file, load it using the Files
class. If you only want to operate on a single file, you can still use the Files
class or you can use File
instead.
Using Files
:
const Files = require("nonplain").default;
// you can use a glob or a filepath
const files = new Files().load('/path/to/dir/**/*.md');
console.log(files.collect());
// Output:
//
// [
// {
// "body": "This is the body of\nthe first file",
// "metadata": {
// "file": {
// "root": "/",
// "dir": "/path/to/dir",
// "base": "file1.md",
// "ext": ".md",
// "name": "file1"
// },
// "what is this?": "it's JSON frontmatter",
// }
// },
// {
// "body": "This is the body of\nthe second file",
// "metadata": {
// "file": {
// "root": "/",
// "dir": "/path/to/dir",
// "base": "file2.md",
// "ext": ".md",
// "name": "file2"
// },
// "syke": "now it's YAML",
// }
// }
// ]
Using File
:
const { File } = require("nonplain");
const file = new File().load('/path/to/file.md');
console.log(file.getData());
// Output:
//
// {
// "body": "This is the body of\nthe current file",
// "metadata": {
// "file": {
// "root": "/",
// "dir": "/path/to/dir",
// "base": "file.md",
// "ext": ".md",
// "name": "file"
// },
// "course number": "CS231n",
// "description": "Convolutional Neural Networks for Visual Recognition",
// "semester": "Spring 2020"
// }
// }
Notice that the metadata of each file includes a file
property. This property is included by default to denote the original source file. This property can be changed or removed by transforming the data using transform()
.
You may want to transform nonplain file data in place once it's loaded into an instance of File
or Files
. That's what the transform()
method is for.
transform()
receives a callback argument which is called with the current file data (for file.transform()
) or iteratively through each loaded file (files.transform()
). There are two options for this callback argument:
- Traditional callback function:
files.transform((file) => { const { body: oldBody, metadata: oldMetadata } = file; const newBody = oldBody.replace('this', 'that'); const newMetadata = Object.assign(oldMetadata, { newKey: 'My new value for the file called ' + oldMetadata.file.name, }); return { body: newBody, metadata: newMetadata, }; });
- Callback map:
files.transform({ body: (oldBody) => { const newBody = oldBody.replace('this', 'that'); return newBody; }, metadata: (oldMetadata) => { const newMetadata = Object.assign(oldMetadata, { newKey: 'My new value for the file called ' + oldMetadata.file.name, }); return newMetadata; }, });
transform()
works the same way on both File
and Files
. The transform()
method makes these changes "in place", meaning that your instance of File
or Files
will reflect the new file data after the transformation.
Possible uses for transform()
might be converting content from markdown to HTML, calculating and injecting helpful metadata (such as VimWiki backlinks), and more.
Once file data is transformed to your liking, it needs to be exported and used elsewhere. That's where the file.write()
and export2JSON()
methods come in.
Every instance of File
has a write()
method. This is the File.prototype.write()
API:
file.write(file [, options])
file
: string, Buffer, URL, or integer file descriptor - Destination where the file will be written. Using a file descriptor behaves similarly to Node.js'fs.write()
method.- options:
body
(default: true): boolean - Whether to write the body to the destination file.metadata
(default: true): boolean - Whether to write the metadata to the destination file.fmFormat
(default: 'yaml'): 'yaml', 'json', or config object - The format to use when writing the destination file's frontmatter.fmFormat
config object API:format
(default: 'yaml'): 'yaml' or 'json' - The format to use when writing the destination file's frontmatter.space
(default:4
): integer - The indentation to use, in spaces.
transform
: function ((file) => newFile) - A callback function to transform file data before stringification.replace
: function ((content) => newContent) - A callback function to transform file content after stringification and before the new file is written.encoding
(default: 'utf8'): string - The encoding in which to write the destination file. More on this...mode
(default:0o666
): integer - The file mode when writing the destination file. More on this...flag
(default: 'w'): string - The flag used when writing the destination file. More on this...
Files can also be exported to JSON using the export2JSON()
method. The export2JSON()
method exists on instances of both File
and Files
. file.export2JSON()
will export the current file data to JSON and files.export2JSON()
will export an array containing all of the currently loaded files' data to JSON. This is the export2JSON()
API:
files.export2JSON(file [, options])
file.export2JSON(file [, options])
file
: string, Buffer, URL, or integer file descriptor - Destination where the file will be written. Using a file descriptor behaves similarly to Node.js'fs.write()
method.- options:
space
(default:4
): integer - The indentation to use, in spaces.transform
: function ((file) => newFile) - A callback function to transform file data before stringification.encoding
(default: 'utf8'): string - The encoding in which to write the destination file. More on this...mode
(default:0o666
): integer - The file mode when writing the destination file. More on this...flag
(default: 'w'): string - The flag used when writing the destination file. More on this...
Clears all currently loaded files from the Files
instance.
function filter(file) {
if (file.metadata.public) {
return true;
}
return false;
}
files.filter(filter);
Filters file instances in place using a filter callback. If the filter callback returns true
for a given item, the item is kept. If the filter callback returns false
for a given item, the item is discarded from the Files
instance collection.
There is no return value; this operation is executed in place on the existing collection.
function compare(a, b) {
return b.metadata.date - a.metadata.date;
}
files.sort(compare);
Sorts file instances in place using a comparison callback. If the comparison callback returns a negative number, a
is sorted before b
. If the comparison callback returns a positive number, b
is sorted before a
. If the comparison callback returns 0
, the original order is kept.
There is no return value; this operation is executed in place on the existing collection.
Returns all currently loaded files as an array of file data:
const files = new Files().load('/path/to/dir/**/*.md');
console.log(files.collect());
// Output:
//
// [
// {
// "body": "This is the body of\nthe first file",
// "metadata": {
// "file": {
// "root": "/",
// "dir": "/path/to/dir",
// "base": "file1.md",
// "ext": ".md",
// "name": "file1"
// },
// "what is this?": "it's JSON frontmatter",
// }
// },
// {
// "body": "This is the body of\nthe second file",
// "metadata": {
// "file": {
// "root": "/",
// "dir": "/path/to/dir",
// "base": "file2.md",
// "ext": ".md",
// "name": "file2"
// },
// "syke": "now it's yaml",
// }
// }
// ]
Returns all currently loaded files as an array of File
instances. Primarily used to iteratively call File
methods, such as file.write()
.
Returns the currently loaded file data:
const file = new File().load('/path/to/file1.md');
console.log(file.getData());
// Output:
//
// {
// "body": "This is the body of\nthe current file",
// "metadata": {
// "file": {
// "root": "/",
// "dir": "/path/to/dir",
// "base": "file.md",
// "ext": ".md",
// "name": "file"
// },
// "course number": "CS231n",
// "description": "Convolutional Neural Networks for Visual Recognition",
// "semester": "Spring 2020"
// }
// }
- Tools for markdown links in nonplain files: nonplain-md-link.js
- Generate backlinks for nonplain files: nonplain-md-backlinker.js
Other libraries providing simple, composable tools for working with stuff like VimWiki notes are in the works. Stay tuned for more.
Nothing is set in stone right now; this concept is very much a work in progress. Please feel free to contact me with suggestions or ideas. Thanks!