Package Exports
- ansie
Readme
Ansie
A library used to render a simplified markdown+html like markup to rich terminal text.
Table of Contents
- Ansie
Quick Example
For example,
<h1 fg="green">Title</h1>
<h2 fg="blue">Subtitle goes here</h2>
<p>
A description using the default text will appear here.
But you can also include <span bold>embedded markup</span>
</p>
<div underline="single" fg="gray">Footnote</div>This is a fully markup-based example. But for simpler output constraints you can use a variation of markdown mixed with ansie markup:
# Title
## [c=blue]Subtitle goes here[/c]
A description use the default text will appear here. But you can
also include **embedded markup**
<span underline="single">Footnote</span>Using the CLI, you can generate rich text by piping in your markup/down to the utility:
echo "# Hello **world**" | ansieInstallation
bun add ansie
or
npm install ansie
Getting Started
If you're integrating the library into a javascript/typescript application, you can get started with the compile function which, at its simplest, takes the markup to translate into ansi codes.
import ansie from 'ansie';
console.log(ansie.compile('<h1 bold italics>Hello there</h1>'));
// Output: ^[[1;3mHello there^[[22;23mThe above will render the string using a default theme that uses some sensible defaults to style the ouptput, but you can override that by passing in your own theme:
import ansie from 'ansie';
const theme = {
h1: {
font: {
color: {
fg: 'red'
},
bold: true,
italics: false,
underline: 'none'
}
}
};
console.log(
ansie.compile({ markup: '<h1 bold italics>Hello there</h1>', theme })
);You can also use template tags to render your output:
import ansie from 'ansie';
console.log(ansie.tpl`<h1 bold italics>Hello ${fella}</h1>`);Finally, you can use console logging replacements to avoid having to add a compile step:
import ansie from 'ansie';
ansie.console.log('# Title\n## Subtitle\nSome content');Ansie Markup
The markup language follows XML rules in that it uses a declarative tag-based system of angle brackets and attributes. The supported tags available today are:
| Name | Attributes | Description |
|---|---|---|
| body | {text attributes} & {spacing attributes} | A semantic tag intended to be a container for multiple other tags - usually at the root |
| div | {text attributes} & {spacing attributes} | Content that injects new lines before and after |
| p | {text attributes} & {spacing attributes} | Content that injects new lines before and after |
| h1 | {text attributes} & {spacing attributes} | A semantic tag intended to represent the headline of a block of text |
| h2 | {text attributes} & {spacing attributes} | A semantic tag intended to represent the sub-headline of a block of text |
| h3 | {text attributes} & {spacing attributes} | A semantic tag intended to represent the tertiary headline of a block of text |
| li | {text attributes} & {spacing attributes} | A semantic tag intended to represent the tertiary headline of a block of text |
| span | {text attributes} | Content that does not have new lines before and after |
| br | {spacing attributes} | Injects a newline in the compiled output |
Text Attributes
| Name | Value | Description |
|---|---|---|
| bold | "false", "true", "yes", "no", undefined | Makes text bold - if bold specified but not value then it will assume true |
| italics | "false", "true", "yes", "no", undefined | Makes text italicized - if italics specified but not value then it will assume true |
| underline | "single", "double", "none", "false", "true", "yes", "no", undefined | Makes text underlined - if underline specified but not value then it will assume single |
| fg | { fg color } | Changes the foreground color of the text |
| bg | { bg color } | Changes the background color of the text |
Tags that accept spacing attributes include:
- h1
- h2
- h3
- body
- p
- div
- span
- li
Spacing Attributes
| Name | Value | Description |
|---|---|---|
| margin | "[number]" | Zero or more. Indicates the number of new lines (vertical spacing) or spaces (horizontal spacing) to inject before and after the content. |
| marginLeft | "[number]" | Zero or more. Indicates the number of spaces to inject before the content. |
| marginRight | "[number]" | Zero or more. Indicates the number of spaces to inject after the content. |
| marginTop | "[number]" | Zero or more. Indicates the number of new lines to inject before the content. |
| marginBottom | "[number]" | Zero or more. Indicates the number of new lines to inject after the content. |
Tags that accept spacing attributes include:
- h1
- h2
- h3
- body
- p
- div
- br
- li
Free (Raw) Text
Additionally you can have regular text that is not enclosed in a tag. For example, you can have:
<h1>Title</h1>
Raw text hereTags can be nested and will render the way you would expect. So, for example,
<body fg="red">
<h1 fg="blue">My Title</h1>
</body>You can mix free text with tags to create more readable strings like this:
This is a quick <span fg="blue">blue</span> testColor Table
| Color Names |
|---|
| black |
| red |
| green |
| yellow |
| blue |
| magenta |
| cyan |
| white |
| gray |
| brightred |
| brightgreen |
| brightyellow |
| brightblue |
| brightmagenta |
| brightcyan |
| brightwhite |
| brightgray |
Emoji
Text can include emoji either through unicode or through Slack style formatting as in 🔥. Supported emoji include:
| Code | Emoji |
|---|---|
❗ |
❗ |
⚠️ |
⚠️ |
⛔ |
⛔ |
✔️ |
✔️ |
❌ |
❌ |
‼️ |
‼️ |
🚩 |
🚩 |
🔥 |
🔥 |
🆘 |
🆘 |
🔒 |
🔒 |
🔑 |
🔑 |
💔 |
💔 |
☠️ |
☠️ |
😀 |
😀 |
😁 |
😁 |
😂 |
😂 |
😍 |
😍 |
😏 |
😏 |
😎 |
😎 |
👍 |
👍 |
👎 |
👎 |
👏 |
👏 |
🙏 |
🙏 |
😢 |
😢 |
😭 |
😭 |
🚀 |
🚀 |
☀️ |
☀️ |
☔ |
☔ |
📷 |
📷 |
📖 |
📖 |
💰 |
💰 |
🎁 |
🎁 |
🔔 |
🔔 |
🔨 |
🔨 |
:thumbsup-skin-tone-1: |
👍🏻 |
:thumbsup-skin-tone-2: |
👍🏻 |
:thumbsup-skin-tone-3: |
👍🏼 |
:thumbsup-skin-tone-4: |
👍🏽 |
:thumbsup-skin-tone-5: |
👍🏾 |
:thumbsup-skin-tone-6: |
👍🏿 |
Markdown
Ansie supports simpler markdown constructs to create more readable input. Support markdown includes:
- h1:
# Headline 1translates to<h1>Headline 1</h1> - h2:
# Headline 2translates to<h2>Headline 2</h2> - h3:
# Headline 3translates to<h3>Headline 3</h3> - bold:
**bold**translates to<span bold>bold</span> - italics:
**italics**translates to<span italics>italics</span> - color:
[c=blue]blue[/c]translates to<span fg="blue">blue</span>
But you can also mix both markdown and markup in the same input. The markdown will first converted to the analogous markup before being compiled to the final output.
API
Once the package is installed, you can quickly get up and running by using the compile function which takes an ansie markup string and returns rich text using ansi codes.
Compilation Function
import ansie from 'ansie';
ansie.compile('<body>Hello, world</body>');Using Template Tags
Ansie supports template tags allowing you to build string templates using tagged templates.
import ansie from 'ansie';
const person = 'world';
// supports markup
console.log(ansie.tpl`<body>Hello</body>, ${person}`);
// support markdown also
console.log(ansie.tpl`# Hello, ${person}!`);
// supports a combination
console.log(
ansie.tpl`# Hello <span fg="blue" underline="single">${person}</span>`
);User Input
The ask family of utilities allows you to ask a question and get a response from the user.
It takes a question and returns the response as a promise.
import ansie from 'ansie';
const response = ansie.ask('What is your name?');
console.log(`Hello, ${response}`);There are different forms of answers that can be provided:
text- a simple text responsepassword- a password responseselect- a selection from a list of optionsconfirm- a yes/no response (with a default)
ask
The ask function asks a question and returns a response. The promise will resolve with the response.
This is the base ask function that can be configured to return a text, password, select, or confirm response.
It might be easier to use the more specific functions below.
| Parameter | Type | Description |
|---|---|---|
| prompt | string | The question to ask the user |
| options | object | Additional options to pass |
import ansie from 'ansie';
const response = await ansie.ask('What is your name?', {
format: 'ansie';
theme: 'default';
default: '';
typeOptions: {
type: 'select';
choices: ['Alice', 'Bob', 'Charlie'];
}
});
console.log(`Hello, ${response}`);askSingleLineText
The askSingleLineText function asks a question and returns a text response.
| Parameter | Type | Description |
|---|---|---|
| prompt | string | The question to ask the user |
| def | string | The default value if nothing is entered (optional) |
import ansie from 'ansie';
const response = await ansie.askText('What is your name?');
console.log(`Hello, ${response}`);askMultiLineText
The askMultiLineText function asks a question and returns a multi-line text response.
If you specify multiline then the user will be presented with a multiline editor to provide a response. Once they save the response, the promise will resolve with the text.
| Parameter | Type | Description |
|---|---|---|
| prompt | string | The question to ask the user |
askSelect
The askSelect function asks a question and returns a selection from a list of options. The options are provided
as an array of strings. The promise will resolve with the selected option.
| Parameter | Type | Description |
|---|---|---|
| question | string | The question to ask the user |
| choices | array | An array of strings representing the options to select from |
| def | string | The default value selected (optional) |
import ansie from 'ansie';
const response = await ansie.askSelect('What is your name?', [
'Alice',
'Bob',
'Charlie'
]);
console.log(`Hello, ${response}`);askPassword
The askPassword function asks a question and returns a password response. The user's input will be hidden as they
type. The promise will resolve with the password.
| Parameter | Type | Description |
|---|---|---|
| prompt | string | The question to ask the user |
| def | string | The default value if the user enters nothing (optional) |
import ansie from 'ansie';
const response = await ansie.askPassword('What is your password?');
console.log(`Password is ${response}`);askConfirm
The askConfirm function asks a question and returns a boolean response. The user can respond with 'y' or 'n' or
press enter to accept the default value. The promise will resolve with the boolean value.
| Parameter | Type | Description |
|---|---|---|
| prompt | string | The question to ask the user |
| options | object | Options to configure the confirmation method |
| def | string | The default value if the user enters nothing (optional) |
The options object can contain the following properties:
| Property | Type | Description |
|---|---|---|
| default | boolean | This should be the same as either the "trueValue" to default to true or the "falseValue" to default to false. For example, if trueValue is "y" and you want to default to true, then set this to "y". |
| isConfirm | boolean | If true then treat this as a confirmation input meaning that any key other than escape will return true |
| trueValue | string | A single character that the user must press to return true, defaults to "y" |
| falseValue | string | A single character that the user must press to return false, defaults to "n" |
import ansie from 'ansie';
const response = await ansie.askConfirm('Are you sure? [Y/n]', {
default: 'y'
});
console.log(`Response is ${response}`);Prompts and Defaults
By default, if you specify a default value, that default value will be displayed after the prompt. If you want to choose where that default value is displayed in the prompt, you can use the [default] placeholder in the prompt text you provide and that will be replaced with the default value text. For example:
import ansie from 'ansie';
const response = await ansie.askSingleLineText('What is your name ([default])? ', 'Alice');
console.log(`Hello, ${response}`);In the above example, the prompt will be displayed as What is your name (Alice)? with the default value displayed inside the question mark.
If you didn't provide a [default] placeholder, the default value would be displayed after the prompt as in What is your name? (Alice)
Themes
You can use themes in Ansie to establish a common set of attributes to associate with each type of tag. A default theme is applied automatically but this can be overridden using the theme property in the compile and compose functions.
A theme is made up of tags each of which has its own style. The styles available are:
| Style Type | Properties | Applicable Tags |
|---|---|---|
| font | color (see color properties), bold, underline [single, double, none, true, false], italics | h1, h2, h3, div, span, body, text, p, li |
| spacing | margin, marginLeft, marginRight, marginTop, marginBottom | h1, h2, h3, body, div, p, li |
| list | bullet (a string like *), indent (number) |
li |
The default theme has the following attributes:
Themes
The theme is made up of the following sections:
| Section | Description |
|---|---|
| h1 | Used the to style the h1 blocks |
| h2 | Used to style the h2 blocks |
| h3 | Used to style the h3 blocks |
| p | Used the style paragraph blocks |
| div | Used to style generic blocks of text |
| list | Used to indicate how lists should be styled |
| span | Used to style generic inline elements of text |
Using the CLI
You can access the functionality in ansie through a CLI as in:
> ansie "<h1 bold>This is bold</h1>"This will output:
This is bold
You can also pipe in the markup to produce the output:
> echo "<h1 bold>This is bold</h1>" | ansieThis is bold
Developing
This package is developed using bun to make the building and packaging of Typescript easier. If you have an interest in making this npm compatible please submit a PR.
To install dependencies:
bun installTo update the parser if you made changes to the grammar:
bun run parser:generateIf you added new tests to test-strings.ts you will need to generate a new fixtures.json file which you can do by running:
bun run test:recordThe library contains three components:
- Parser - this is used to convert a string to an Abstract Syntax Tree. You probably won't need to use this as it represents an incremental state
- Compiler - Converts the abstract syntax tree to renderer terminal text. You can use this if you want to just pass in markup to get your terminal string.
- Composer - A convenient set of methods to build markup through a functional syntax. You can use this if you want a nicer, functional way of building your markup.
Updating the Grammar
The parser code in this context is generated from a grammar file (terminal-markup.peggy) using the peggy library. If you want to update the parser, you would need to modify the grammar file and then re-run the generate.ts script to create a new parser. Here are the steps:
- Navigate to the terminal-markup.peggy file in your project directory.
- Make the necessary changes to the grammar. This could involve adding new rules, modifying existing ones, or fixing bugs.
- Run the generate.ts script to generate a new parser. You can do this by running
bun parser:generate - The updated parser will be written to
generated-parser.js. - Any new grammar that added or fixed remember to add a test to
test/test-markup-strings.json
Testing
Test files are colocated with the files that they are testing using the format <filename>.test.ts. For composition and
markup tests, we automatically generate fixture files from an array of test string and commands.
Many of the tests are built off of fixtures that can be re-recorded at any time using the tests:record script.
test-composer-commands is a file that export an array where each item in the array is a function that runs an compose command.
When you run bun run tests:record each of these functions is executed and the results are stored in the composer-fixtures.json file
which is then run as part of the package's tests.
test-markup-strings is an array of valid markup strings that are used during the bun run tests:record script to
generate the compiler-fixtures.json file which contains the inputs and expected outputs.
Note: You should only rerecord the fixtures if you are confident that they will generate correct output