# lines-builder

![Downloads](https://img.shields.io/npm/dw/lines-builder?style=flat-square) ![Version@npm](https://img.shields.io/npm/v/lines-builder?label=version%40npm&style=flat-square) ![Version@git](https://img.shields.io/github/package-json/v/szikszail/lines-builder/main?label=version%40git&style=flat-square) ![CI](https://img.shields.io/github/actions/workflow/status/szikszail/lines-builder/ci.yml?branch=main&label=ci&style=flat-square) ![Docs](https://img.shields.io/github/actions/workflow/status/szikszail/lines-builder/docs.yml?branch=main&label=docs&style=flat-square)

Tool and model to handle string lines and indentation.

## Usage

To create a **new lines-builder** object:

```typescript
import { lines } from "lines-builder";

const l = lines("Hello World\nThis is the 2nd line");
console.log(l);
// Hello World
// This is the 2nd line
```

The `lines` accepts an optional `options` object and any additional string (or lines-builder) as lines.

### Options

| Option                 | Type             | Description                                                                        | Default                 |
| :--------------------- | :--------------- | :--------------------------------------------------------------------------------- | :---------------------- |
| `indent`               | `string\|number` | The indentation to use to indent the lines (exact string or the number of spaces). | `null` (no indentation) |
| `indentEmpty`          | `boolean`        | Whether the empty lines should still be indented.                                  | `false`                 |
| `skipFirstLevelIndent` | `boolean`        | Whether only the nested lines-builders should be indented.                         | `false`                 |
| `skipEmpty`            | `boolean`        | Whether empty lines should be added to the output string.                          | `false`                 |
| `trimLeft`             | `boolean`        | Can trailing white spaces be trimmed from the parsed lines.                        | `true`                  |
| `trimRight`            | `boolean`        | Can leading white spaces be trimmed from the parsed lines.                         | `true`                  |
| `eol`                  | `string`         | The line-break to be used. If not set, it is determined based on the platform.     | `null`                  |

### Append 

You can **append any lines**, including a new line to the end (in place), passing any string (or lines-builder) as arguments to `append` :

```typescript
l.append("3rd line", null, "4th line\nand 5th line");
console.log(l);
// Hello World
// This is the 2nd line
// 3rd line
// 
// 4th line
// 5th line
```

### Prepend

You can **prepend any lines**, including new-line to the beginning (in place), passing any string (or lines-builder) as arguments to `prepend` :

```typescript
l.prepend("This is the title", null, "And the 0th line");
console.log(l);
// This is the title
// 
// And the 0th line
// Hello World
// This is the 2nd line
// 3rd line
// 
// 4th line
// 5th line
```

### Indent

You can set a **global indentation** to the lines added:

```typescript
const l = lines({ indent: '--' }, "Hello World", "2nd line");
console.log(l);
// --Hello World
// --2nd line
```

By default, **no indentation** is set.

You can also set a **number** as the indentation. In this case, the given number of spaces will be used.

### Trim

By default, lines-builder **trims** the leading and trailing **whitespaces**, but you can turn it off:

```typescript
const l = lines({ trimLeft: false, trimRight: false }, "Hello World  ", "  2nd line\n  3rd line");
console.log(l);
// Hello World
//   2nd line
//   3rd line
```

### Nesting

Lines-builder accepts another lines-builder instance instead of any string lines, to be able to nest it:

```typescript
const nested = lines({ indent: '==' }, "1st nested", "2nd nested");
const parent = lines({ indent: '--' }, "1st parent", nested, "2nd parent");
console.log(parent);
// --1st parent
// --==1st nested
// --==2nd nested
// --2nd parent
```

By setting the `skipFirstLevelIndent` option, the lines-builder won't indent the direct lines of the lines-builder, 
only starting from the nested ones:

```typescript
const nested = lines(, "1st nested", "2nd nested");
const parent = lines({ skipFirstLevelIndent: true, indent: '--' }, "1st parent", nested, "2nd parent");
console.log(parent);
// 1st parent
// --1st nested
// --2nd nested
// 2nd parent
```

### Default options

As seen above, options can be set per each lines-builder, but if you want to use one set of options, 
per each lines-builder you will use, you can use the `setDefaultOptions`.

```typescript
import { lines, setDefaultOptions } from "lines-builder";

setDefaultOptions({ indent: "__" });

const nested = lines("1st nested", "2nd nested");
const parent = lines("1st parent", nested, "2nd parent");
console.log(parent);
// __1st parent
// ____1st nested
// ____2nd nested
// __2nd parent
```

Note that the default options can be reset to the [initial ones](src/index.ts#L20) with the `resetDefaultOptions`.

### EOL

To output/format the lines, the lines-builder will determine the appropriate EOL based on the platform used ( `\r\n` for Windows, `\n` for other OS).
If you need to set explicitly what EOL to use, pass it in any options:

```typescript
// IBM Mainframe EOL
const l = lines({ eol: "\025" }, "Hello", "World");
console.log(l);
// Hello\025World
```

### Filtering

The filter method can be used to filter in place - either to keep or remove lines matching a specific pattern.

```typescript
const l = lines("simple line", "# comment line", "other line");
// the following expression removes lines starting with #
l.filter(/^\s*#/, true);
console.log(l);
// simple line
// other line
```

If filtering needs to be done on a copy of the lines-builder, without touching the original one, the 3rd argument must be set to `false`:

```typescript
const l = lines("simple line", "# comment line", "other line");
// the following expression removes lines starting with # and returns the result
const filtered = l.filter(/^\s*#/, true, false);

console.log(filtered);
// simple line
// other line

console.log(l);
// simple line
// # comment line
// other line
```

The filter function accepts either
1. a `string`, which will be converted to a case-insensitive RegExp
1. a `RegExp`, what will be used as it is
1. a `type LineMatcher = (line: string, i: number) => boolean;` function, what should return `true` in case of a match.

The filter function, based on the `reverse` parameter
1. if `false`, it keeps the line if the matcher is `true`; otherwise, it removes it
1. if `true`, it removes the line if the matcher is `true`; otherwise, it removes it

In the case of nested lines-builder, the same matcher and parameter are applied, and the nested one is kept if it contains any line after the filter.

### Mapping

The map method can be used to map the lines in place, updating all the lines with a function.

```typescript
const l = lines("line a", "line b", lines("nested line"));
// the following add level/index to each line
l.map((line: string, i: number, level: number) => `${level}/${i} - ${line}`);
console.log(l);
// 0/0 - line a
// 0/1 - line b
// 1/0 - nested line
```
If mapping needs to be done on a copy of the lines-builder, without touching the original one, the 2nd argument must be set to `false`:

```typescript
const l = lines("line a", "line b", lines("nested line"));
// the following add level/index to each line
const mapped = l.map((line: string, i: number, level: number) => `${level}/${i} - ${line}`, false);

console.log(mapped);
// 0/0 - line a
// 0/1 - line b
// 1/0 - nested line

console.log(l);
// line a
// line b
// nested line
```

### Copy

The copy method can be used to clone the lines-builder (including the nested lines, with the same options):

```typescript
const l = lines("line a", "line b", lines("nested line"));
const copied = l.copy();

console.log(copied);
// line a
// line b
// nested line

console.log(l);
// line a
// line b
// nested line
```

## Other

For detailed documentation see the [TypeDocs documentation](https://szikszail.github.io/lines-builder/).

This package uses [debug](https://www.npmjs.com/package/debug) for logging, use `lines-builder` to see debug logs:

```shell
DEBUG=lines-builder node my-script.js
```
