View on GitHub

authorx

authorx is an extensible, markdown-like language for creating rich, interactive content

authorx is pre 1.0 and it's syntax and API may change before 1.0. You can view the roadmap here.

Table of Contents

Why?

While its nice to write simple documents in markdown, writing anything relatively sophisticated (e.g., blogs, technical documentation, interactive content) is a pain.

For example, consider the red warning panel at the top of this doc that says authorx is pre-1.0. The only way to create something like that in markdown is to embed html into your markdown doc:


<div <!-- ... --> >
  <div>
    <svg
      <!-- ... -->
    >
      <path <!-- ... --> />
    </svg>
  </div>
  <p <!-- ... --> >I don't recommend using authorx for serious projects yet.
  You can view the roadmap to 1.0 <a href="#project-status--roadmap">here</a>.
  </p>
</div>

# Table of Contents

With authorx, you could create a warning info panel like this:

<! {
  authorx is pre 1.0 and it's syntax and API may change before 1.0. 
  You can view the roadmap <a(#project-status--roadmap) { here }.
}

Note that with authorx we can arbitrarily nest transformed content. Here we have a link inside of a warning panel.

Here’s another example: say you wanted to embed a mermaid.js diagram into your doc like the one below. If you’re just using markdown, you’ll have to write your mermaid diagram, run the cli, and then include a reference to the generated image in your markdown. With authorx, you could embed mermaid diagrams like this:

<~ {:
  graph LR
  A[.ax file] -->|parse| B(AST)
  B --> C("authorx-extensions e.g., x-faux-markdown x-mermaid-js")
  C -->D(your extensions)
  D -->E(".html | .md | .*")
:}

I’ve been emphasizing could in these examples because the way authorx documents work is largely up to you. With authorx, syntax and semantics are separate. The <~ in the above example is just a function. You define what the function does in javascript. Maybe it embeds a mermaid diagram into your document. Maybe it floats content to the left side of the page. It’s up to you.

With authorx’s minimal and expressive syntax and your function definitions, you can create markdown-like languages that work for more rich and interactive content. All you have to do is define the functions.

How?

Simple grammar

There’s only two syntactic elements to authorx documents: text and functions that transform text. Hello world looks like this:

<p Hello, world!

You can see the grammar here.

Syntax is separate from semantics

Functions in authorx have no meaning by default. With authorx, semantics are added by defining the text-transformation functions in js and passing them to the compiler:

const { compile } = require("@authorx/compiler");

compile("path-to-ax-file", (target = "html") => ({
  "#": (text) => `<h1>${text}</h1>`
}));

Here’s the high-level flow of how compile transforms text to its final format:

In fact, this markdown README file was generated from a README.ax file, where the functions look like markdown tags:

<# How?

<## Syntax is separate from semantics  

You can see the grammar <a(./grammar.ne) { here }.

<l {
<< We often need to use awkward, ad-hoc extensions to markdown. Take "front-matter" in static site generators, for example.
<< There are some things we just can't do with markdown because it doesn't allow arbitrary nesting of tagged/transformed text. We wind up embedding markdown in html or in some sort of template system.
}

The markdown-like functions that generated this readme are defined in the x-faux-markdown package

Quick Start

npm i @authorx/compiler

Let’s write hello world in a hello.ax file:

<# hello world

Next, we’ll create an empty my-markdown.js file, with details to be filled in later:

touch my-markdown.js

You can use the compiler right away, but all it’ll do is print out hello world:

npx axc hello.ax my-markdown.js
# outputs "hello world"

If you want <# to wrap “hello world” in h1 headings like markdown does, add this to your my-markdown.js file

module.exports = () => ({ 
  '#': (text) => '<h1>' + text + '</h1>' 
})  

Now, when you run axc, you’ll see that hello world is wrapped in h1 tags:

npx axc hello.ax my-markdown.js
# outputs <h1>hello world</h1>

If you don’t want to rewrite markdown-esque functions, you can npm i @authorx/x-faux-markdown and extend those functions trivially:

// my-markdown.js
const xFauxMarkdown = require("@authorx/x-faux-markdown")
module.exports = {
  // Your custom functions here
  ...xFauxMarkdown()  
}

Then just rerun axc and point it to your updated file:

npx axc hello.ax my-markdown.js

Authorx Syntax

Authorx only has two syntactic elements: functions and text. Authorx docs must start with a function invocation. That function may be a no-op, but the current syntax requires it. (This should go away eventually.) Typically, authorx docs start with a no-op function and nest content inside of it like this:

<$ {
  <# Now that i've started the document with a function, the parser is happy.
  
  All of my content goes <* { here }.
}

Text is written exactly how you’d expect: you just write words into your authorx file (by convention, saved with .ax extension).

Functions transform text and/or execute side-effects. They can wrap text in h1 tags, place text inside a warning panel div, or build a mermaid diagram and replace the text with a reference to the generated svg. Functions start with a < and must be followed by an identifer. Here are some examples:

<h1 { wrap me in a heading! }  
<# { wrap me in a heading too !}

Notice that function identifers can contain special characters. This enables you to keep them short and your keep your authorx docs focused on the text. Here’s the full list of supported special characters in identifiers:

Functions that operate on multiple lines of text should surround that text with brackets like the above example. If, however, your function only operates on one line of text, you may omit the brackets like so:

<# Wrap me in a heading!

Whether your function operates on text surrounded by brackets or operates on single line text, the <, {, }, and \ characters must be escaped with a \.

To facilitate the inclusion of code in authorx docs without needing to escape characters, you can also wrap text in colon brackets {: :}. Inside colon brackets, <, {, , and } don’t need to be escaped with \.

Functions can also take arguments. For example, a link function may take the url of the link:

You can find my blog <a(http://philosophicalhacker.com) { here }

Multiple function arguments must be separated by a comma.

Extensions Reference

authorx has some first-party extensions for common use-cases. the functions exposed by those extensions are documented below.

x-faux-markdown

Headings

# wraps text in a h1 tag

## wraps text in a h2 tag

## wraps text in a h3 tag

Lists

l creates an unordered list. Items are added to list using the < function. Example:

<l {
  << item1
  << item2
}

Code

` creates inline monospaced code

``` creates a code block and takes an optional language argument. Example:

<```(js) {:
  console.log("hello world!)
:}

a creates a link and takes an url argument. Example:

You can find my blog <a(http://philosophicalhacker.com) { here }

x-info-panel

! creates an warning panel like the one at the top of this doc.

x-mermaid-js

~ generates an svg mermaid diagram using the text within its brackets. It then replaces the mermaid text with a link to that diagram. It optionally takes an argument specifying the name of generated svg file. Example:

<~(my-diagram.svg) {:
  graph LR
  A -->|parse| B
:}

Project Status & Roadmap

Here’s a roadmap for upcoming features:

If you have any feedback about authorx, feel free to file issues. Would love to hear your feedback. If you have an idea for an extension you think should be in the monorepo, please file an issue so we can discuss it.

FAQs

I’m getting a syntax error about a newline that I didn’t add to my authorx doc. What gives?

The current grammar doesn’t like trailing newlines in authorx docs. This should go away soon, but until you need to watch out for editors that automatically add a newline at the end of file. vim is an offender here.