Skip to content

Commit

Permalink
feat(ansi): add chainable ansi colors module (#128)
Browse files Browse the repository at this point in the history
  • Loading branch information
c4spar committed Dec 29, 2020
1 parent 41a39d0 commit f2d8c93
Show file tree
Hide file tree
Showing 12 changed files with 350 additions and 55 deletions.
142 changes: 92 additions & 50 deletions ansi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,17 @@
<sub>>_ Control cli cursor, erase output, scroll window and more.</sub>
</p>

<p align="center">
<img alt="demo" src="assets/img/demo.gif"/>
</p>

## ❯ Content

- [Install](#-install)
- [Usage](#-usage)
- [Tty](#tty)
- [Ansi](#ansi)
- [Functional](#functional)
- [Tty](#tty)
- [Colors](#colors)
- [Contributing](#-contributing)
- [License](#-license)

Expand All @@ -46,30 +50,85 @@ This module can be imported directly from the repo and from following registries
Deno Registry

```typescript
import { ansi, tty, cursorTo } from "https://deno.land/x/cliffy@<version>/ansi/mod.ts";
import { ansi, colors, tty, cursorTo } from "https://deno.land/x/cliffy@<version>/ansi/mod.ts";
```

Nest Registry

```typescript
import { ansi, tty, cursorTo } from "https://x.nest.land/cliffy@<version>/ansi/mod.ts";
import { ansi, colors, tty, cursorTo } from "https://x.nest.land/cliffy@<version>/ansi/mod.ts";
```

Github

```typescript
import { ansi, tty, cursorTo } from "https://raw.githubusercontent.com/c4spar/deno-cliffy/<version>/ansi/mod.ts";
import { ansi, colors, tty, cursorTo } from "https://raw.githubusercontent.com/c4spar/deno-cliffy/<version>/ansi/mod.ts";
```

## ❯ Usage

The ansi module exports an `ansi` and a `tty` object which have chainable methods and properties for generating and writing ansi escape sequences. `ansi` and `tty` have allmost the same properties and methods.`ansi` generates and returns an ansi string, and `tty` writes the generated ansi escape sequence directly to stdout.
### Ansi

The ansi module exports an `ansi` object with chainable methods and properties for generating ansi escape sequence
strings. The last property must be invoked as a method to generate the ansi string.

```typescript
import { ansi } from "https://deno.land/x/cliffy/ansi/ansi.ts";

console.log(
myAnsi.cursorUp.cursorLeft.eraseDown()
);
```

If a method takes some arguments, you have to invoke the `.toString()` method to generate the ansi string.

```typescript
import { ansi } from "https://deno.land/x/cliffy/ansi/ansi.ts";

console.log(
myAnsi.cursorUp(2).cursorLeft.eraseDown(2).toString()
);
```

Convert to `Uint8Array`:

```typescript
import { ansi } from "https://deno.land/x/cliffy/ansi/ansi.ts";

await Deno.stdout.write(
ansi.cursorUp.cursorLeft.eraseDown.toBuffer()
);
```

You can also directly import the ansi escape methods from the `ansi_escapes.ts` module.

```typescript
import { cursorTo, eraseDown, image, link } from "https://deno.land/x/cliffy/ansi/ansi_escapes.ts";

const response = await fetch("https://deno.land/images/hashrock_simple.png");
const imageBuffer: ArrayBuffer = await response.arrayBuffer();

console.log(
cursorTo(0, 0) +
eraseDown() +
image(imageBuffer, {
width: 29,
preserveAspectRatio: true,
}) +
"\n " +
link("Deno Land", "https://deno.land") +
"\n",
);
```

Both objects can be also invoked as method to create a new instance from itself.
```
$ deno run https://deno.land/x/cliffy/examples/ansi/functional.ts
```

### Tty

Writes generated ansi escape sequences directly to stdout.
The tty module exports a `tty` object which works almost the same way as the `ansi` module. The only difference is, the
`tty` module writes the ansi escape sequences directly to stdout.

```typescript
import { tty } from "https://deno.land/x/cliffy/ansi/tty.ts";
Expand All @@ -96,61 +155,44 @@ myTty.cursorSave
.eraseScreen();
```

### Ansi
### Colors

Returns generated ansi escape sequences.
The colors module is a simple and tiny chainable wrapper around [deno's std colors](https://deno.land/std@0.82.0/fmt/colors.ts)
module and works similar to node's [chalk](https://github.com/chalk/chalk) module.

```typescript
import { ansi } from "https://deno.land/x/cliffy/ansi/ansi.ts";
import { colors } from "https://deno.land/x/cliffy/ansi/colors.ts";

console.log(
myAnsi.cursorUp.cursorLeft.eraseDown()
);

// or:
Deno.stdout.writeSync(
new TextEncoder().encode(
ansi.cursorUp.cursorLeft.eraseDown()
)
);

// or:
Deno.stdout.writeSync(
ansi.cursorUp.cursorLeft.eraseDown.toBuffer()
colors.bold.underline.rgb24("Welcome to Deno.Land!", 0xff3333),
);
```

Create a new instance.

```typescript
import { ansi } from "https://deno.land/x/cliffy/ansi/ansi.ts";

const myAnsi = ansi();

console.log(
myAnsi.cursorUp.cursorLeft.eraseDown()
);
```
$ deno run https://deno.land/x/cliffy/examples/ansi/colors.ts
```

### Functional
Define your own themes:

```typescript
import { cursorTo, eraseDown, image, link } from "https://deno.land/x/cliffy/ansi/ansi_escapes.ts";

const response = await fetch("https://deno.land/images/hashrock_simple.png");
const imageBuffer: ArrayBuffer = await response.arrayBuffer();
// Define theme colors.
const error = colors.bold.red;
const warn = colors.bold.yellow;
const info = colors.bold.blue;

// Use theme colors.
console.log(error("[ERROR]"), "Some error!");
console.log(warn("[WARN]"), "Some warning!");
console.log(info("[INFO]"), "Some information!");

// Override theme colors.
console.log(error.underline("[ERROR]"), "Some error!");
console.log(warn.underline("[WARN]"), "Some warning!");
console.log(info.underline("[INFO]"), "Some information!");
```

console.log(
cursorTo(0, 0) +
eraseDown() +
image(imageBuffer, {
width: 29,
preserveAspectRatio: true,
}) +
"\n " +
link("Deno Land", "https://deno.land") +
"\n",
);
```
$ deno run https://deno.land/x/cliffy/examples/ansi/color_themes.ts
```

## ❯ Contributing
Expand Down
2 changes: 1 addition & 1 deletion ansi/ansi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function factory(): Ansi {

ansi.toString = function (): string {
update();
const str: string = result.join();
const str: string = result.join("");
result = [];
return str;
};
Expand Down
14 changes: 14 additions & 0 deletions ansi/ansi_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { assertEquals, bold, red } from "../dev_deps.ts";
import { underline } from "../prompt/deps.ts";
import { ansi } from "./ansi.ts";
import { colors } from "./colors.ts";

Deno.test({
name: "test colors",
fn() {
assertEquals(
ansi.cursorTo(3, 2).eraseDown.cursorHide(),
"\x1B[2;3H\x1B[0J\x1B[?25l",
);
},
});
Binary file added ansi/assets/img/demo.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 2 additions & 4 deletions ansi/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export interface Chain<T extends Chain<T>> {
* );
* ```
*/
link: T & ((text: string, url: string) => T);
link: (text: string, url: string) => T;
/**
* Create image.
* @param buffer Image buffer.
Expand All @@ -124,7 +124,5 @@ export interface Chain<T extends Chain<T>> {
* );
* ```
*/
image:
& T
& ((buffer: string | ArrayBuffer, options?: ImageOptions) => T);
image: (buffer: string | ArrayBuffer, options?: ImageOptions) => T;
}
75 changes: 75 additions & 0 deletions ansi/colors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import * as stdColors from "https://deno.land/std@0.82.0/fmt/colors.ts";

type ExcludedColorMethods = "setColorEnabled" | "getColorEnabled";
type PropertyNames = keyof typeof stdColors;
type ColorMethod = (str: string, ...args: Array<unknown>) => string;
type ColorMethods = Exclude<PropertyNames, ExcludedColorMethods>;
type Chainable<T, E extends keyof T | null = null> = {
[P in keyof T]: P extends E ? T[P] : Chainable<T, E> & T[P];
};

/** Chainable colors instance returned by all ansi escape properties. */
export type ColorsChain =
& Chainable<typeof stdColors, ExcludedColorMethods>
& { _stack: Array<ColorMethods> };

/** Create new `Colors` instance. */
export type ColorsFactory = () => Colors;

/**
* Chainable colors module.
* If invoked as method, a new `Colors` instance will be returned.
*/
export type Colors = ColorsFactory & ColorsChain;

const proto = Object.create(null);
const methodNames = Object.keys(stdColors) as Array<PropertyNames>;
for (const name of methodNames) {
if (name === "setColorEnabled" || name === "getColorEnabled") {
continue;
}
Object.defineProperty(proto, name, {
get(this: ColorsChain) {
return factory([...this._stack, name]);
},
});
}

export const colors: Colors = factory();

/**
* Chainable colors module.
* ```
* console.log(colors.blue.bgRed.bold('Welcome to Deno.Land!'));
* ```
* If invoked as method, a new Ansi instance will be returned.
* ```
* const myColors: Colors = colors();
* console.log(myColors.blue.bgRed.bold('Welcome to Deno.Land!'));
* ```
*/
function factory(stack: Array<ColorMethods> = []): Colors {
const colors: Colors = function (
this: ColorsChain | undefined,
str?: string,
...args: Array<unknown>
): string | ColorsChain {
if (str) {
const lastIndex = stack.length - 1;
return stack.reduce(
(str: string, name: PropertyNames, index: number) =>
index === lastIndex
? (stdColors[name] as ColorMethod)(str, ...args)
: (stdColors[name] as ColorMethod)(str),
str,
);
}
const tmp = stack.slice();
stack = [];
return factory(tmp);
} as Colors;

Object.setPrototypeOf(colors, proto);
colors._stack = stack;
return colors;
}
28 changes: 28 additions & 0 deletions ansi/colors_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { assertEquals, bold, red } from "../dev_deps.ts";
import { underline } from "../prompt/deps.ts";
import { colors } from "./colors.ts";

Deno.test({
name: "test colors",
fn() {
assertEquals(
colors.red.underline.bold("test"),
bold(underline(red("test"))),
);
},
});

Deno.test({
name: "test color theme",
fn() {
const theme = colors.red.underline;
assertEquals(
theme.bold("test"),
bold(underline(red("test"))),
);
assertEquals(
theme("test"),
underline(red("test")),
);
},
});
1 change: 1 addition & 0 deletions ansi/mod.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from "./ansi.ts";
export * from "./ansi_escapes.ts";
export * from "./chain.ts";
export * from "./colors.ts";
export * from "./cursor_position.ts";
export * from "./tty.ts";
16 changes: 16 additions & 0 deletions examples/ansi/color_themes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { colors } from "../../ansi/colors.ts";

// Define theme colors.
const error = colors.bold.red;
const warn = colors.bold.yellow;
const info = colors.bold.blue;

// Use theme colors.
console.log(error("[ERROR]"), "Some error!");
console.log(warn("[WARN]"), "Some warning!");
console.log(info("[INFO]"), "Some information!");

// Override theme colors.
console.log(error.underline("[ERROR]"), "Some error!");
console.log(warn.underline("[WARN]"), "Some warning!");
console.log(info.underline("[INFO]"), "Some information!");
5 changes: 5 additions & 0 deletions examples/ansi/colors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { colors } from "../../ansi/colors.ts";

console.log(
colors.bold.underline.rgb24("Welcome to Deno.Land!", 0xff3333),
);

0 comments on commit f2d8c93

Please sign in to comment.