This commit is contained in:
2026-07-08 18:29:36 +08:00
parent ad51ced86c
commit 3af8ff9b80
1613 changed files with 701644 additions and 2 deletions
+9
View File
@@ -0,0 +1,9 @@
# The MIT License
Copyright 2021 Harminder Virk, contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+95
View File
@@ -0,0 +1,95 @@
# @poppinss/colors
> Wrapper over [kleur](https://www.npmjs.com/package/kleur) with better support for testing
[![gh-workflow-image]][gh-workflow-url] [![typescript-image]][typescript-url] [![npm-image]][npm-url] [![license-image]][license-url]
## Why this package exists?
This package is a wrapper over [kleur](https://www.npmjs.com/package/kleur) with additional implementations to make testing easier and work seamlessly with terminals/stdout streams that do not support colors.
## Usage
Install the package from the npm registry as follows.
```sh
npm i @poppinss/colors
```
And use it as follows. The `ansi` method returns an instance of the [kleur](https://www.npmjs.com/package/kleur) package.
```ts
import useColors from '@poppinss/colors'
const colors = useColors.ansi()
console.log(colors.red('this is an error'))
console.log(colors.cyan('hello world'))
```
Chaining methods
```ts
const colors = useColors.ansi()
console.log(colors.red().bgBlack('this is an error'))
```
### Raw implementation
The raw implementation is ideal for testing. Instead of outputting ANSI escape codes, we wrap the string with transformation names. For example:
```ts
import useColors from '@poppinss/colors'
const colors = useColors.raw()
console.log(colors.red('hello world'))
// OUTPUT: red(hello world)
console.log(colors.bgBlack().red('hello world'))
// OUTPUT: bgBlack(red(hello world))
```
As you can notice, the output is a plain text value, so it is easier to write assertions against it.
```ts
assert.equal(colors.red('hello world'), 'red(hello world)')
```
### Silent output
The silent mode does not perform any transformations on the string and returns the value. This is helpful when the output terminal or stdout stream does not support colors.
```ts
import useColors from '@poppinss/colors'
const colors = useColors.silent()
console.log(colors.red('hello world'))
// OUTPUT: hello world
console.log(colors.bgBlack().red('hello world'))
// OUTPUT: hello world
```
## Pick based on the runtime environment
Ideally, you will use one of the available implementations based on some runtime environment. For example:
```ts
import useColors from '@poppinss/colors'
import supportsColor from 'supports-color'
const isTestEnv = process.env.NODE_ENV === 'test'
const colors = isTestEnv
? useColors.raw() // use raw in test environment
: supportsColor.stdout
? useColors.ansi() // use kleur when stdout has colors
: useColors.silent() // use silent mode
export default colors
```
[gh-workflow-image]: https://img.shields.io/github/actions/workflow/status/poppinss/colors/checks.yml?style=for-the-badge
[gh-workflow-url]: https://github.com/poppinss/colors/actions/workflows/checks.yml "Github action"
[typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript
[typescript-url]: "typescript"
[npm-image]: https://img.shields.io/npm/v/@poppinss/colors.svg?style=for-the-badge&logo=npm
[npm-url]: https://npmjs.org/package/@poppinss/colors 'npm'
[license-image]: https://img.shields.io/npm/l/@poppinss/colors?color=blueviolet&style=for-the-badge
[license-url]: LICENSE.md 'license'
+15
View File
@@ -0,0 +1,15 @@
/**
* @poppinss/colors
*
* (c) Poppinss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { type Colors } from './src/base.ts';
declare const useColors: {
ansi(): Colors;
silent(): Colors;
raw(): Colors;
};
export default useColors;
+139
View File
@@ -0,0 +1,139 @@
import kleur from "kleur";
var Colors = class {
black(text) {
return this.transform("black", text);
}
red(text) {
return this.transform("red", text);
}
green(text) {
return this.transform("green", text);
}
yellow(text) {
return this.transform("yellow", text);
}
blue(text) {
return this.transform("blue", text);
}
magenta(text) {
return this.transform("magenta", text);
}
cyan(text) {
return this.transform("cyan", text);
}
white(text) {
return this.transform("white", text);
}
gray(text) {
return this.transform("gray", text);
}
grey(text) {
return this.transform("grey", text);
}
bgBlack(text) {
return this.transform("bgBlack", text);
}
bgRed(text) {
return this.transform("bgRed", text);
}
bgGreen(text) {
return this.transform("bgGreen", text);
}
bgYellow(text) {
return this.transform("bgYellow", text);
}
bgBlue(text) {
return this.transform("bgBlue", text);
}
bgMagenta(text) {
return this.transform("bgMagenta", text);
}
bgCyan(text) {
return this.transform("bgCyan", text);
}
bgWhite(text) {
return this.transform("bgWhite", text);
}
reset(text) {
return this.transform("reset", text);
}
bold(text) {
return this.transform("bold", text);
}
dim(text) {
return this.transform("dim", text);
}
italic(text) {
return this.transform("italic", text);
}
underline(text) {
return this.transform("underline", text);
}
inverse(text) {
return this.transform("inverse", text);
}
hidden(text) {
return this.transform("hidden", text);
}
strikethrough(text) {
return this.transform("strikethrough", text);
}
};
var Raw = class extends Colors {
#transformations = [];
#dispose(value, callback) {
callback();
return value;
}
transform(transformation, text) {
this.#transformations.push(transformation);
if (text !== void 0) {
const transformations = this.#transformations.concat([text]).join("(");
const closingWrapping = new Array(this.#transformations.length + 1).join(")");
return this.#dispose(`${transformations}${closingWrapping}`, () => {
this.#transformations = [];
});
}
return this;
}
};
var Kleur = class extends Colors {
#chain;
constructor() {
super();
kleur.enabled = true;
}
#dispose(value, callback) {
callback();
return value;
}
transform(transformation, text) {
if (text !== void 0) {
if (this.#chain) return this.#dispose(this.#chain[transformation](text), () => {
this.#chain = void 0;
});
return kleur[transformation](text);
}
if (this.#chain) this.#chain = this.#chain[transformation]();
else this.#chain = kleur[transformation]();
return this;
}
};
var Silent = class extends Colors {
transform(_, text) {
if (text !== void 0) return String(text);
return this;
}
};
var colors_default = {
ansi() {
return new Kleur();
},
silent() {
return new Silent();
},
raw() {
return new Raw();
}
};
export { colors_default as default };
+68
View File
@@ -0,0 +1,68 @@
/**
* @poppinss/colors
*
* (c) Poppinss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Abstract implementation of the colors class.
*/
export declare abstract class Colors {
protected abstract transform(transformation: string): this;
protected abstract transform(transformation: string, text: string | number): string;
protected abstract transform(transformation: string, text?: string | number): this | string;
black(): this;
black(text: string): string;
red(): this;
red(text: string): string;
green(): this;
green(text: string): string;
yellow(): this;
yellow(text: string): string;
blue(): this;
blue(text: string): string;
magenta(): this;
magenta(text: string): string;
cyan(): this;
cyan(text: string): string;
white(): this;
white(text: string): string;
gray(): this;
gray(text: string): string;
grey(): this;
grey(text: string): string;
bgBlack(): this;
bgBlack(text: string): string;
bgRed(): this;
bgRed(text: string): string;
bgGreen(): this;
bgGreen(text: string): string;
bgYellow(): this;
bgYellow(text: string): string;
bgBlue(): this;
bgBlue(text: string): string;
bgMagenta(): this;
bgMagenta(text: string): string;
bgCyan(): this;
bgCyan(text: string): string;
bgWhite(): this;
bgWhite(text: string): string;
reset(): this;
reset(text: string): string;
bold(): this;
bold(text: string): string;
dim(): this;
dim(text: string): string;
italic(): this;
italic(text: string): string;
underline(): this;
underline(text: string): string;
inverse(): this;
inverse(text: string): string;
hidden(): this;
hidden(text: string): string;
strikethrough(): this;
strikethrough(text: string): string;
}
+23
View File
@@ -0,0 +1,23 @@
/**
* @poppinss/colors
*
* (c) Poppinss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { Colors } from './base.ts';
import { type ColorTransformations } from './types.ts';
/**
* Concrete implementation of the Colors class using Kleur
*/
export declare class Kleur extends Colors {
#private;
constructor();
/**
* Perform the given transformation. The abstract Color class calls this
* method
*/
protected transform(transformation: ColorTransformations): this;
protected transform(transformation: ColorTransformations, text: string | number): string;
}
+25
View File
@@ -0,0 +1,25 @@
/**
* @poppinss/colors
*
* (c) Poppinss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { Colors } from './base.ts';
import { type ColorTransformations } from './types.ts';
/**
* Concrete implementation of the Colors class that prefixes the
* applied transformations to the final text as string.
*
* This class is meant to be used at the time of testing
*/
export declare class Raw extends Colors {
#private;
/**
* Perform the given transformation. The base class will
* invoke this method
*/
protected transform(transformation: ColorTransformations): this;
protected transform(transformation: ColorTransformations, text: string | number): string;
}
+22
View File
@@ -0,0 +1,22 @@
/**
* @poppinss/colors
*
* (c) Poppinss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { Colors } from './base.ts';
import { type ColorTransformations } from './types.ts';
/**
* Concrete implementation of the Colors class that returns
* the value as it is.
*/
export declare class Silent extends Colors {
/**
* Perform the given transformation. The abstract Color class calls this
* method
*/
protected transform(transformation: ColorTransformations): this;
protected transform(transformation: ColorTransformations, text: string | number): string;
}
+11
View File
@@ -0,0 +1,11 @@
/**
* @poppinss/colors
*
* (c) Poppinss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { type Colors } from './base.ts';
export type { Colors } from './base.ts';
export type ColorTransformations = keyof Colors;
+1
View File
@@ -0,0 +1 @@
export {};
+118
View File
@@ -0,0 +1,118 @@
{
"name": "@poppinss/colors",
"version": "4.1.6",
"description": "A wrapper on top of kleur with ability to write test against the color functions",
"main": "build/index.js",
"type": "module",
"files": [
"build",
"!build/tests",
"!build/bin"
],
"exports": {
".": "./build/index.js",
"./types": "./build/src/types.js"
},
"scripts": {
"pretest": "npm run lint",
"test": "c8 npm run quick:test",
"lint": "eslint .",
"format": "prettier --write .",
"typecheck": "tsc --noEmit",
"precompile": "npm run lint && npm run clean",
"compile": "tsdown && tsc --emitDeclarationOnly --declaration",
"clean": "del-cli build",
"build": "npm run compile",
"version": "npm run build",
"prepublishOnly": "npm run build",
"release": "release-it",
"quick:test": "node --import=@poppinss/ts-exec --enable-source-maps bin/test.ts"
},
"devDependencies": {
"@adonisjs/eslint-config": "^3.0.0-next.5",
"@adonisjs/prettier-config": "^1.4.5",
"@adonisjs/tsconfig": "^2.0.0-next.3",
"@japa/assert": "^4.1.1",
"@japa/runner": "^4.4.0",
"@poppinss/ts-exec": "^1.4.1",
"@release-it/conventional-changelog": "^10.0.3",
"@types/node": "^25.0.1",
"c8": "^10.1.3",
"del-cli": "^7.0.0",
"eslint": "^9.39.1",
"prettier": "^3.7.4",
"release-it": "^19.1.0",
"tsdown": "^0.17.3",
"typescript": "^5.9.3"
},
"dependencies": {
"kleur": "^4.1.5"
},
"repository": {
"type": "git",
"url": "git+https://github.com/poppinss/colors.git"
},
"bugs": {
"url": "https://github.com/poppinss/colors/issues"
},
"keywords": [
"kleur",
"colors"
],
"author": "virk",
"license": "MIT",
"homepage": "https://github.com/poppinss/colors#readme",
"publishConfig": {
"access": "public",
"provenance": true
},
"tsdown": {
"entry": [
"./index.ts",
"./src/types.ts"
],
"outDir": "./build",
"clean": true,
"format": "esm",
"minify": "dce-only",
"fixedExtension": false,
"dts": false,
"treeshake": false,
"sourcemaps": false,
"target": "esnext"
},
"release-it": {
"git": {
"requireCleanWorkingDir": true,
"requireUpstream": true,
"commitMessage": "chore(release): ${version}",
"tagAnnotation": "v${version}",
"push": true,
"tagName": "v${version}"
},
"github": {
"release": true
},
"npm": {
"publish": true,
"skipChecks": true
},
"plugins": {
"@release-it/conventional-changelog": {
"preset": {
"name": "angular"
}
}
}
},
"c8": {
"reporter": [
"text",
"html"
],
"exclude": [
"tests/**"
]
},
"prettier": "@adonisjs/prettier-config"
}
+9
View File
@@ -0,0 +1,9 @@
# The MIT License
Copyright (c) 2023
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+387
View File
@@ -0,0 +1,387 @@
# @poppinss/dumper
> Pretty print JavaScript data types in the terminal and the browser
<br />
[![gh-workflow-image]][gh-workflow-url] [![npm-image]][npm-url] ![][typescript-image] [![license-image]][license-url]
Dumper is similar to Node.js [util.inspect](https://nodejs.org/api/util.html#utilinspectobject-options), but it provides more control over the output. You can use Dumper to generate [HTML output](#html-formatter), [CLI output](#cli-formatter), or use its [low-level API](#using-parser-directly) to create inspection tokens and render them using a custom formatter.
![](./dumper-example.png)
> [!IMPORTANT]
> Dumper is a low-level utility. You may have to write a wrapper around it for the framework of your choice.
## Installation
Install the package from the npm registry as follows.
```sh
npm i @poppinss/dumper
```
## HTML formatter
You can dump values to HTML output using the `dump` helper from the HTML sub-module. For example:
```ts
import { dump } from '@poppinss/dumper/html'
const values = {
a: 0,
b: 'string',
c: {
nested: 'object',
},
}
const html = dump(values)
```
The HTML output contains a `pre` tag and a `script` tag. The `script` tag invokes a JavaScript function (for collapse/expand behavior) that must be present in the `<HEAD>` element of the HTML document.
You can grab the JavaScript snippet and the required global styles using the `createStyleSheet` and `createScript` helper methods. Following is a complete example of the same.
```ts
import { dump, createStyleSheet, createScript } from '@poppinss/dumper/html'
const values = {
a: 0,
b: 'string',
c: {
nested: 'object',
},
}
const html = dump(values)
const output = `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<style>
${createStyleSheet()}
</style>
<script>
${createScript()}
</script>
</head>
<body>
${html}
</body>
</html>`
```
### Options
You may pass all of the [Parser options](#parser-options) alongside the following options as the second argument to the `dump` method.
- `styles`: The styles property is a key-value pair that contains CSS properties to style HTML elements. You can either define custom styles or use one of the pre-existing themes as a reference.
- `cspNonce`: If your application has CSP enabled, then you must define the [CSP nonce](https://content-security-policy.com/nonce/) for the inline `script` tag output alongside the `pre` tag.
- `expand`: Render the dumped output expanded (instead of collapsed). By default, only the first level is expanded. However, you can expand recursively using the `expand: 'all'` option.
Following is an example of using a pre-existing theme.
```ts
import { dump, themes } from '@poppinss/dumper/html'
dump(value, {
styles: themes.catppuccin,
})
```
To have support for dark and light modes, you must use the `cssVariables` theme and define the colors using CSS variables. For example:
```ts
import { dump, themes } from '@poppinss/dumper/html'
dump(value, {
styles: themes.cssVariables,
})
```
**List of CSS variables**
```css
:root {
--pre-bg-color
--pre-fg-color
--toggle-fg-color
--braces-fg-color
--brackets-fg-color
--dt-number-fg-color
--dt-bigint-fg-color
--dt-boolean-fg-color
--dt-string-fg-color
--dt-null-fg-color
--dt-undefined-fg-color
--prototype-label-fg-color
--dt-symbol-fg-color
--dt-regex-fg-color
--dt-date-fg-color
--dt-buffer-fg-color
--function-label-fg-color
--array-label-fg-color
--object-label-fg-color
--map-label-fg-color
--set-label-fg-color
--object-key-fg-color
--object-key-prefix-fg-color
--class-label-fg-color
--collpase-label-fg-color
--getter-label-fg-color
--circular-label-fg-color
--weakset-label-fg-color
--weakref-label-fg-color
--weakmap-label-fg-color
--observable-label-fg-color
--promise-label-fg-color
--generator-label-fg-color
--blob-label-fg-color
--unknown-label-fg-color
}
```
Following is an example of rendering the expanded output.
```ts
import { dump } from '@poppinss/dumper/html'
dump(value, {
expand: true, // expand first-level
})
dump(value, {
expand: 'all', // expand recursively
})
```
### Creating custom themes
You can also define your own themes as an object. Make sure to consult one of the [existing themes](https://github.com/poppinss/dumper/blob/1.x/formatters/html/themes.ts) to view all the available tokens.
```ts
import { dump } from '@poppinss/dumper/html'
import { HTMLPrinterStyles } from '@poppinss/dumper/html/types'
const myTheme: HTMLPrinterStyles = {
pre: 'background-color: #1e1e2e; color: #94e2d5;',
boolean: 'color: #cba6f7; font-style: italic;'
string: 'color: #a6e3a1;',
symbol: 'color: #f9e2af;',
// ...rest of the styles
}
dump(value, {
styles: myTheme,
})
```
## CLI formatter
You can dump values to the terminal using the `dump` helper from the console sub-module. For example:
```ts
import { dump } from '@poppinss/dumper/console'
const values = {
a: 0,
b: 'string',
c: {
nested: 'object',
},
}
const ansiOutput = dump(values)
console.log(ansiOutput)
```
### Options
You may pass all of the [Parser options](#parser-options) alongside the following options as the second argument to the `dump` method.
- `styles`: The styles property contains a set of functions for different tokens. Each function receives a string input and must return a styled output string.
Following is an example of using a pre-existing theme.
```ts
import { dump, themes } from '@poppinss/dumper/console'
dump(value, {
styles: themes.default,
})
```
You may create a custom theme as follows. Make sure to consult an [existing theme](https://github.com/poppinss/dumper/blob/1.x/formatters/console/themes.ts) to view all the available tokens.
```ts
import { styleText } from 'node:util'
import { dump } from '@poppinss/dumper/console'
import { ConsolePrinterStyles } from '@poppinss/dumper/console/types'
const myTheme: ConsolePrinterStyles = {
number: (value) => styleText('yellow', value),
bigInt: (value) => styleText('yellow', styleText('bold', value)),
boolean: (value) => styleText('yellow', styleText('italic', value)),
// ... styles for the rest of the tokens
}
dump(value, {
styles: myTheme,
})
```
## Supported data types
Following is the list of data types supported by Dumper. All other data types will be converted to their String representation by wrapping them inside the `String` function.
- Object
- Array
- Map
- Set
- Function
- string
- URL
- URLSearchParams
- Error
- FormData
- undefined
- null
- symbol
- number
- boolean
- BigInt
- Date
- RegExp
- Buffer
- WeakSet
- WeakMap
- WeakRef
- Generator
- AsyncGenerator
- GeneratorFunction
- AsyncGeneratorFunction
- AsyncFunction
- Observable
- Blob
- Promise
- NaN
- Int8Array
- Uint8Array
- Int16Array
- Uint16Array
- Int32Array
- Uint32Array
- Float32Array
- Float64Array
- BigInt64Array
- BigUint64Array
## Parser options
Regardless of the output format, you can use one of the following options to tweak the parsing behavior.
```ts
import { dump } from '@poppinss/dumper/console'
dump(values, {
showHidden: false,
depth: 5,
inspectObjectPrototype: false,
inspectArrayPrototype: false,
inspectStaticMembers: false,
maxArrayLength: 100,
maxStringLength: 1000,
})
```
- `showHidden`: When set to true, the non-enumerable properties of an object will be processed. **Default: `false`**.
- `depth`: The depth at which to stop parsing nested values. The depth is shared among all tree-like data structures. For example: Objects, Arrays, Maps, and Sets. **Default: `5`**.
- `inspectObjectPrototype`: Inspect prototype properties of an object. The non-enumerable properties of the prototype are included by default. **Default: `false`**.
- `inspectArrayPrototype`: Inspect prototype properties of an Array. This flag could help inspect prototype properties of extended arrays. **Default: `unless-plain-object`**. The `unless-plain-object` object value will inspect the prototype when the prototype of the value is not the global `Object`.
- `inspectStaticMembers`: Inspect static members of a class. Even though functions and classes are technically the same, this config only applies to functions defined using the `[class]` keyword. **Default: `false`**.
- `maxArrayLength`: Maximum number of members to process for Arrays, Maps, and Sets. **Default: `100`**.
- `maxStringLength`: Maximum number of characters to display for a string. **Default: `1000`**.
## Using Parser directly
For advanced use cases, use the Parser directly and create a custom formatter on top of it. Following is an example of the same. Also, feel free to consult the implementation of the existing formatters.
```ts
import { Parser } from '@poppinss/dumper'
import { ParserConfig } from '@poppinss/dumper/types'
const config: ParserConfig = {}
const parser = new Parser(config)
const values = {
a: 0,
b: 'string',
c: {
nested: 'object',
},
}
parser.parse(values)
const tokens = parser.flush()
console.log(tokens)
```
The `parser.flush` method returns a flat array of [tokens](https://github.com/poppinss/dumper/blob/1.x/src/types.ts#L30), and they must be printed in the same order as they are defined.
The official implementations (shipped with Dumper) use the concept of printers, where we have defined one printer for each token type that is responsible for returning the formatted value.
Following is an oversimplified example of creating custom printers. Once again, feel free to reference the implementation of existing [formatters](https://github.com/poppinss/dumper/blob/1.x/formatters/html/printers/formatter.ts) and [printers](https://github.com/poppinss/dumper/blob/1.x/formatters/html/printers/main.ts).
```ts
const myCustomPrinters: [K in keyof TokensMap]: (
token: TokensMap[K],
) => string = {
'object-start': (token) => {
return `Object {`
},
'object-end': (token) => {
return `}`
},
'object-key': (token) => {
return token.value
},
'object-value-start': (token) => {
return ': '
},
'object-value-end': (token) => {
return ', '
}
}
const tokens = parser.flush()
const output = tokens.map((token) => {
return myCustomPrinters[token.type](token)
}).join('')
```
## Contributing
One of the primary goals of Poppinss is to have a vibrant community of users and contributors who believe in the principles of the framework.
We encourage you to read the [contribution guide](https://github.com/poppinss/.github/blob/main/docs/CONTRIBUTING.md) before contributing to the framework.
## Code of Conduct
In order to ensure that the Poppinss community is welcoming to all, please review and abide by the [Code of Conduct](https://github.com/poppinss/.github/blob/main/docs/CODE_OF_CONDUCT.md).
## License
Poppinss dumper is open-sourced software licensed under the [MIT license](LICENSE.md).
[gh-workflow-image]: https://img.shields.io/github/actions/workflow/status/poppinss/dumper/checks.yml?style=for-the-badge
[gh-workflow-url]: https://github.com/poppinss/dumper/actions/workflows/checks.yml 'Github action'
[typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript
[typescript-url]: "typescript"
[npm-image]: https://img.shields.io/npm/v/@poppinss/dumper.svg?style=for-the-badge&logo=npm
[npm-url]: https://npmjs.org/package/@poppinss/dumper 'npm'
[license-image]: https://img.shields.io/npm/l/@poppinss/dumper?color=blueviolet&style=for-the-badge
[license-url]: LICENSE.md 'license'
+602
View File
@@ -0,0 +1,602 @@
var __defProp = Object.defineProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
// src/helpers.ts
var helpers_exports = {};
__export(helpers_exports, {
htmlEscape: () => htmlEscape,
tokenizeArray: () => tokenizeArray,
tokenizeObject: () => tokenizeObject,
tokenizePrototype: () => tokenizePrototype,
wordWrap: () => wordWrap
});
import is from "@sindresorhus/is";
var ObjectPrototype = Object.prototype;
var ArrayPrototype = Array.prototype;
var ObjectPrototypeKeys = Reflect.ownKeys(ObjectPrototype);
var ArrayPrototypeKeys = Reflect.ownKeys(ArrayPrototype);
function tokenizeObject(value, parser, config) {
parser.context.objectsSeen = parser.context.objectsSeen ?? /* @__PURE__ */ new Set();
parser.context.depth = parser.context.depth ?? 0;
if (parser.context.objectsSeen.has(value)) {
parser.collect({ type: "object-circular-ref" });
return;
}
if (parser.context.depth >= parser.config.depth) {
parser.collect({ type: "object-max-depth-ref" });
return;
}
const showHidden = config.showHidden;
const name = config.constructorName ?? Object.getPrototypeOf(value)?.constructor.name ?? null;
if (config.collapse.includes(name)) {
parser.collect({
type: "collapse",
name,
token: {
type: "object-start",
constructorName: name
}
});
return;
}
const ownKeys = Reflect.ownKeys(value);
const eagerGetters = config.eagerGetters ?? [];
parser.context.depth++;
parser.context.objectsSeen.add(value);
let keys = [];
if (config.membersToIgnore) {
const keysSet = /* @__PURE__ */ new Set([...ownKeys]);
config.membersToIgnore.forEach((m) => keysSet.delete(m));
keys = Array.from(keysSet);
} else {
keys = ownKeys;
}
parser.collect({ type: "object-start", constructorName: name });
for (let key of keys) {
const descriptor = Object.getOwnPropertyDescriptor(value, key);
if (!descriptor) {
continue;
}
if (!descriptor.enumerable && !showHidden) {
continue;
}
const isSymbol = typeof key === "symbol";
const isWritable = !!descriptor.set || !!descriptor.writable;
parser.collect({
type: "object-key",
isSymbol,
isWritable,
value: String(key)
});
if ("get" in descriptor && !eagerGetters.includes(key)) {
parser.collect({ type: "object-value-getter" });
continue;
}
parser.collect({ type: "object-value-start" });
parser.parse(value[key]);
parser.collect({ type: "object-value-end" });
}
if (config.inspectObjectPrototype === true) {
tokenizePrototype(value, parser, {
membersToIgnore: ObjectPrototypeKeys
});
} else if (config.inspectObjectPrototype === "unless-plain-object" && !is.plainObject(value)) {
tokenizePrototype(value, parser, {
membersToIgnore: ObjectPrototypeKeys,
prototypeToIgnore: ObjectPrototype
});
}
parser.collect({ type: "object-end" });
parser.context.depth--;
parser.context.objectsSeen.delete(value);
}
function tokenizePrototype(value, parser, config) {
const prototypeChain = [];
for (let proto = Object.getPrototypeOf(value); proto && (!config.prototypeToIgnore || proto !== config.prototypeToIgnore); proto = Object.getPrototypeOf(proto)) {
let keys = Reflect.ownKeys(proto);
if (config.membersToIgnore) {
const keysSet = /* @__PURE__ */ new Set([...keys]);
config.membersToIgnore.forEach((m) => keysSet.delete(m));
keys = Array.from(keysSet);
}
prototypeChain.push({ proto, keys });
}
if (!prototypeChain.length) {
return;
}
const eagerGetters = config.eagerGetters ?? [];
parser.collect({ type: "prototype-start" });
for (let proto of prototypeChain) {
for (let key of proto.keys) {
if (key === "constructor") {
continue;
}
const descriptor = Object.getOwnPropertyDescriptor(proto.proto, key);
if (!descriptor) {
continue;
}
const isSymbol = typeof key === "symbol";
const isWritable = !!descriptor.set || !!descriptor.writable;
parser.collect({
type: "object-key",
isSymbol,
isWritable,
value: String(key)
});
if ("get" in descriptor && !eagerGetters.includes(key)) {
parser.collect({ type: "object-value-getter" });
continue;
}
parser.collect({ type: "object-value-start" });
parser.parse(value[key]);
parser.collect({ type: "object-value-end" });
}
}
parser.collect({ type: "prototype-end" });
}
function tokenizeArray(values, parser, config) {
parser.context.arraysSeen = parser.context.arraysSeen ?? /* @__PURE__ */ new Set();
parser.context.depth = parser.context.depth ?? 0;
if (parser.context.arraysSeen.has(values)) {
parser.collect({ type: "array-circular-ref" });
return;
}
if (parser.context.depth >= config.depth) {
parser.collect({ type: "array-max-depth-ref" });
return;
}
const limit = config.maxArrayLength;
const size = values.length;
const name = config.name || values.constructor.name;
if (config.collapse.includes(name)) {
parser.collect({
type: "collapse",
name,
token: {
type: "array-start",
name,
size
}
});
return;
}
parser.context.depth++;
parser.context.arraysSeen.add(values);
parser.collect({ type: "array-start", name, size });
for (let index = 0; index < size; index++) {
if (index >= limit) {
parser.collect({ type: "array-max-length-ref", limit, size });
break;
}
const value = values[index];
if (Object.hasOwn(values, index)) {
parser.collect({ type: "array-value-start", index });
parser.parse(value);
parser.collect({ type: "array-value-end", index });
} else {
parser.collect({ type: "array-value-hole", index });
}
}
if (config.inspectArrayPrototype) {
tokenizePrototype(values, parser, {
membersToIgnore: ArrayPrototypeKeys,
prototypeToIgnore: ArrayPrototype
});
}
parser.collect({ type: "array-end", size });
parser.context.depth--;
parser.context.arraysSeen.delete(values);
}
function htmlEscape(value) {
return value.replace(/&/g, "&amp;").replace(/\\"/g, "&bsol;&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
function wordWrap(value, options) {
const width = options.width;
const indent = options.indent;
const newLine = `${options.newLine}${indent}`;
let regexString = ".{1," + width + "}";
regexString += "([\\s\u200B]+|$)|[^\\s\u200B]+?([\\s\u200B]+|$)";
const re = new RegExp(regexString, "g");
const lines = value.match(re) || [];
const result = lines.map(function(line) {
if (line.slice(-1) === "\n") {
line = line.slice(0, line.length - 1);
}
return options.escape ? options.escape(line) : htmlEscape(line);
}).join(newLine);
return result;
}
// src/tokenizers/main.ts
import { inspect } from "util";
import is2 from "@sindresorhus/is";
var tokenizers = {
/**
* Tokenizes an object and its properties.
* - Enable "showHidden" option to parse non-enumerable
* - Enable "inspectObjectPrototype" to parse prototype members
*/
Object: (value, parser) => {
tokenizeObject(value, parser, parser.config);
},
/**
* Tokenizes an array of values
*/
Array: (values, parser) => {
tokenizeArray(values, parser, parser.config);
},
/**
* Tokenizes keys and values inside a map
*/
Map: (values, parser) => {
parser.context.mapsSeen = parser.context.mapsSeen ?? /* @__PURE__ */ new Set();
parser.context.depth = parser.context.depth ?? 0;
if (parser.context.mapsSeen.has(values)) {
parser.collect({ type: "map-circular-ref" });
return;
}
if (parser.context.depth >= parser.config.depth) {
parser.collect({ type: "map-max-depth-ref" });
return;
}
parser.context.depth++;
parser.context.mapsSeen.add(values);
let index = 0;
const size = values.size;
const limit = parser.config.maxArrayLength;
parser.collect({ type: "map-start", size });
for (let [key, value] of values) {
if (index >= limit) {
parser.collect({ type: "map-max-length-ref", limit, size });
break;
}
parser.collect({ type: "map-row-start", index });
parser.collect({ type: "map-key-start", index });
parser.parse(key);
parser.collect({ type: "map-key-end", index });
parser.collect({ type: "map-value-start", index });
parser.parse(value);
parser.collect({ type: "map-value-end", index });
parser.collect({ type: "map-row-end", index });
index++;
}
parser.collect({ type: "map-end", size });
parser.context.depth--;
parser.context.mapsSeen.delete(values);
},
/**
* Tokenizes values inside a set
*/
Set: (values, parser) => {
parser.context.setsSeen = parser.context.setsSeen ?? /* @__PURE__ */ new Set();
parser.context.depth = parser.context.depth ?? 0;
if (parser.context.setsSeen.has(values)) {
parser.collect({ type: "set-circular-ref" });
return;
}
if (parser.context.depth >= parser.config.depth) {
parser.collect({ type: "set-max-depth-ref" });
return;
}
parser.context.depth++;
parser.context.setsSeen.add(values);
let index = 0;
const size = values.size;
const limit = parser.config.maxArrayLength;
parser.collect({ type: "set-start", size });
for (let value of values) {
if (index >= limit) {
parser.collect({ type: "set-max-length-ref", limit, size });
break;
}
parser.collect({ type: "set-value-start", index });
parser.parse(value);
parser.collect({ type: "set-value-end", index });
index++;
}
parser.collect({ type: "set-end", size });
parser.context.depth--;
parser.context.setsSeen.delete(values);
},
/**
* Tokenizes a function. If the function is a class created
* using the [class] keyword, we will process its static
* members when "config.inspectClassConstructor"
* is enabled
*/
Function: (value, parser) => {
const ConstructorName = value.constructor.name;
if (ConstructorName === "GeneratorFunction") {
return tokenizers.GeneratorFunction(value, parser);
}
if (ConstructorName === "AsyncGeneratorFunction") {
return tokenizers.AsyncGeneratorFunction(value, parser);
}
if (ConstructorName === "AsyncFunction") {
return tokenizers.AsyncFunction(value, parser);
}
const isClass = is2.class(value);
parser.collect({
type: "function",
isClass,
isAsync: false,
isGenerator: false,
name: value.name || "anonymous"
});
if (parser.config.inspectStaticMembers && isClass) {
parser.collect({ type: "static-members-start" });
tokenizeObject(value, parser, {
showHidden: true,
depth: parser.config.depth,
inspectObjectPrototype: false,
collapse: parser.config.collapse,
membersToIgnore: ["prototype", "name", "length"]
});
parser.collect({ type: "static-members-end" });
}
},
/**
* Tokenizes a string value and handles max length and
* correct quotes via the "util.inspect" method.
*/
string: (value, parser) => {
const formatted = inspect(value, {
maxStringLength: parser.config.maxStringLength,
customInspect: false
});
parser.collect({ type: "string", value: formatted });
},
/**
* Tokenizes the URL instance as an object
*/
URL: (value, parser) => {
tokenizeObject(
{
hash: value.hash,
host: value.host,
hostname: value.hostname,
href: value.href,
origin: value.origin,
password: value.password,
pathname: value.pathname,
port: value.port,
protocol: value.protocol,
search: value.search,
searchParams: value.searchParams,
username: value.username
},
parser,
{ constructorName: "URL", ...parser.config }
);
},
/**
* Tokenizes the URLSearchParams instance as an object
*/
URLSearchParams: (value, parser) => {
tokenizeObject(Object.fromEntries(value), parser, {
constructorName: "URLSearchParams",
...parser.config
});
},
Error: function(value, parser) {
tokenizeObject(value, parser, {
eagerGetters: ["message", "stack"],
...parser.config,
inspectObjectPrototype: parser.config.inspectObjectPrototype === true ? true : false,
showHidden: true
});
},
FormData: function(value, parser) {
tokenizeObject(Object.fromEntries(value.entries()), parser, {
constructorName: "FormData",
...parser.config
});
},
/**
* Straight forward one's
*/
undefined: (_, parser) => {
parser.collect({ type: "undefined" });
},
null: (_, parser) => {
parser.collect({ type: "null" });
},
symbol: (value, parser) => {
parser.collect({ type: "symbol", value: String(value) });
},
number: (value, parser) => {
parser.collect({ type: "number", value });
},
boolean: (value, parser) => {
parser.collect({ type: "boolean", value });
},
bigint: (value, parser) => {
parser.collect({ type: "bigInt", value: `${value.toString()}n` });
},
Date: (value, parser) => {
parser.collect({ type: "date", value: value.toISOString() });
},
RegExp: (value, parser) => {
parser.collect({ type: "regexp", value: String(value) });
},
Buffer: (value, parser) => {
parser.collect({
type: "buffer",
value: inspect(value)
});
},
WeakSet: (_, parser) => {
parser.collect({ type: "weak-set" });
},
WeakMap: (_, parser) => {
parser.collect({ type: "weak-map" });
},
WeakRef: function(_, parser) {
parser.collect({ type: "weak-ref" });
},
Generator: function(_, parser) {
parser.collect({ type: "generator", isAsync: false });
},
AsyncGenerator: function(_, parser) {
parser.collect({ type: "generator", isAsync: true });
},
GeneratorFunction: function(value, parser) {
parser.collect({
type: "function",
isGenerator: true,
isClass: false,
isAsync: false,
name: value.name || "anonymous"
});
},
AsyncGeneratorFunction: function(value, parser) {
parser.collect({
type: "function",
isGenerator: true,
isClass: false,
isAsync: true,
name: value.name || "anonymous"
});
},
AsyncFunction: function(value, parser) {
parser.collect({
type: "function",
isGenerator: false,
isClass: false,
isAsync: true,
name: value.name || "anonymous"
});
},
Observable: function(_, parser) {
parser.collect({ type: "observable" });
},
Blob: function(value, parser) {
parser.collect({ type: "blob", size: value.size, contentType: value.type });
},
Promise: function(value, parser) {
parser.collect({
type: "promise",
isFulfilled: !inspect(value).includes("pending")
});
},
NaN: function(_, parser) {
parser.collect({ type: "number", value: Number.NaN });
},
Int8Array: function(value, parser) {
tokenizeArray(value, parser, parser.config);
},
Uint8Array: function(value, parser) {
tokenizeArray(value, parser, parser.config);
},
Int16Array: function(value, parser) {
tokenizeArray(value, parser, parser.config);
},
Uint16Array: function(value, parser) {
tokenizeArray(value, parser, parser.config);
},
Int32Array: function(value, parser) {
tokenizeArray(value, parser, parser.config);
},
Uint32Array: function(value, parser) {
tokenizeArray(value, parser, parser.config);
},
Float32Array: function(value, parser) {
tokenizeArray(value, parser, parser.config);
},
Float64Array: function(value, parser) {
tokenizeArray(value, parser, parser.config);
},
BigInt64Array: function(value, parser) {
tokenizeArray(value, parser, parser.config);
},
BigUint64Array: function(value, parser) {
tokenizeArray(value, parser, parser.config);
}
};
// src/parser.ts
import is3 from "@sindresorhus/is";
var Parser = class {
#tokens = [];
/**
* Config shared with tokenizers
*/
config;
/**
* Context maintained through out the parsing phase.
* Each instance of Parser has its own context
* that gets mutated internally.
*/
context;
constructor(config, context) {
this.context = context || {};
this.config = Object.freeze({
showHidden: false,
depth: 5,
inspectObjectPrototype: "unless-plain-object",
inspectArrayPrototype: false,
inspectStaticMembers: false,
maxArrayLength: 100,
maxStringLength: 1e3,
collapse: [],
...config
});
}
/**
* Normalizes the type name of a property using additional
* bit of checks. For example, the "is" module does not
* use instanceOf checks and hence misses out on many
* potentional improvements.
*/
#normalizeTypeName(name, value) {
if (name === "Object" && value instanceof Error) {
return "Error";
}
return name;
}
/**
* Collect a token inside the list of tokens. The order
* of tokens matter during printing therefore you must
* collect tokens in the right order.
*/
collect(token) {
this.#tokens.push(token);
}
/**
* Parses a value using the tokenizers. Under the hood the
* tokenizers will call "parser.collect" to collect
* tokens inside an array.
*
* You can use "parser.flush" method to get the list of
* tokens.
*/
parse(value) {
const typeName = this.#normalizeTypeName(is3.detect(value), value);
const tokenizer = tokenizers[typeName];
if (tokenizer) {
tokenizer(value, this);
} else {
this.collect({ type: "unknown", jsType: typeName, value });
}
}
/**
* Returns collected tokens and resets the internal state.
*/
flush() {
const tokens = this.#tokens;
this.#tokens = [];
this.context = {};
return tokens;
}
};
export {
htmlEscape,
wordWrap,
helpers_exports,
tokenizers,
Parser
};
+57
View File
@@ -0,0 +1,57 @@
import type { Token } from '../../src/types.js';
import type { ConsoleFormatterConfig, ConsolePrinterStyles } from './types.js';
/**
* ConsoleFormatter is used to format a collection of parser
* tokens to CLI output.
*
* @example
* ```ts
* const parser = new Parser()
* parser.parse(value)
*
* const tokens = parser.flush()
*
* const formatter = new ConsoleFormatter()
* const output = formatter.format(tokens)
* ```
*/
export declare class ConsoleFormatter {
/**
* Styles for output elements
*/
readonly styles: ConsolePrinterStyles;
/**
* Context maintained through out the printing
* phase. Each instance has its own context
* that gets mutated internally.
*/
context: Record<string, any>;
/**
* Value for the newline character
*/
readonly newLine = "\n";
/**
* Utility to manage indentation
*/
readonly indentation: {
counter: number;
/**
* Increment the identation by 1 step
*/
increment(): void;
/**
* Decrement the identation by 1 step
*/
decrement(): void;
/**
* Get the identation spaces as per the current
* identation level
*/
getSpaces(): string;
};
constructor(config?: ConsoleFormatterConfig, context?: Record<string, any>);
/**
* Format a collection of tokens to ANSI output
*/
format(tokens: Token[]): string;
}
+29
View File
@@ -0,0 +1,29 @@
import { ConsoleFormatter } from './formatter.js';
import type { ConsoleDumpConfig } from './types.js';
export { ConsoleFormatter };
export { themes } from './themes.js';
export { ConsolePrinters } from './printers/main.js';
/**
* Generate pretty printed HTML output for the provided value. You can
* specify the parser and the formatter options as the 2nd argument.
*
* @example
* ```ts
* const html = dump(someValue)
*
* // With Parser options
* const html = dump(someValue, {
* inspectObjectPrototype: true,
* depth: 10,
* showHidden: true,
* })
*
* // With Formatter options
* const html = dump(someValue, {
* styles: {
* number: 'color: yellow; font-weight: bold;'
* }
* })
* ```
*/
export declare function dump(value: any, config?: ConsoleDumpConfig): string;
+441
View File
@@ -0,0 +1,441 @@
import {
Parser,
wordWrap
} from "../../chunk-26HALFTP.js";
// formatters/console/themes.ts
import useColors from "@poppinss/colors";
import supportsColor from "supports-color";
var colors = supportsColor.stdout ? useColors.ansi() : useColors.silent();
var themes = {
default: {
braces: (value) => colors.yellow(value),
brackets: (value) => colors.yellow(value),
number: (value) => colors.yellow(value),
bigInt: (value) => colors.yellow().bold(value),
boolean: (value) => colors.yellow().italic(value),
string: (value) => colors.green(value),
null: (value) => colors.dim(value),
undefined: (value) => colors.dim(value),
prototypeLabel: (value) => colors.dim(value),
symbol: (value) => colors.magenta(value),
regex: (value) => colors.red(value),
date: (value) => colors.magenta(value),
buffer: (value) => colors.magenta(value),
functionLabel: (value) => colors.cyan().italic(value),
arrayLabel: (value) => colors.cyan(value),
objectLabel: (value) => colors.cyan(value),
mapLabel: (value) => colors.cyan(value),
setLabel: (value) => colors.cyan(value),
objectKey: (value) => colors.blue(value),
objectKeyPrefix: (value) => colors.dim(value),
classLabel: (value) => colors.cyan(value),
weakSetLabel: (value) => colors.cyan(value),
weakRefLabel: (value) => colors.cyan(value),
collapseLabel: (value) => colors.dim(value),
circularLabel: (value) => colors.cyan(value),
getterLabel: (value) => colors.cyan(value),
weakMapLabel: (value) => colors.cyan(value),
observableLabel: (value) => colors.cyan(value),
promiseLabel: (value) => colors.blue(value),
generatorLabel: (value) => colors.cyan(value),
blobLabel: (value) => colors.magenta(value),
unknownLabel: (value) => colors.magenta(value)
}
};
// formatters/console/printers/main.ts
function openingBrace(formatter) {
return formatter.styles.braces("{");
}
function closingBrace(formatter) {
return formatter.styles.braces("}");
}
function openingBrackets(formatter) {
return formatter.styles.brackets("[");
}
function closingBrackets(formatter) {
return formatter.styles.brackets("]");
}
var ConsolePrinters = {
"collapse": (token, formatter) => {
const styles = token.token.type === "object-start" ? formatter.styles.objectLabel : formatter.styles.arrayLabel;
const collpaseStyles = formatter.styles.collapseLabel;
return `${styles(token.name)} ` + (token.token.type === "object-start" ? openingBrace(formatter) : openingBrackets(formatter)) + ` ${collpaseStyles("collpased")} ` + (token.token.type === "object-start" ? closingBrace(formatter) : closingBrackets(formatter));
},
"object-start": (token, formatter) => {
formatter.indentation.increment();
const styles = formatter.styles.objectLabel;
const label = formatter.context.isStaticMember && formatter.context.staticDepth === 0 || token.constructorName === "Object" ? "" : styles(`${token.constructorName || "Object [null]"}`) + " ";
return label + openingBrace(formatter);
},
"object-end": (_, formatter) => {
formatter.indentation.decrement();
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
return indent + closingBrace(formatter);
},
"object-key": (token, formatter) => {
formatter.context.isStack = token.value === "stack";
const styles = formatter.styles.objectKey;
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
let value = token.value;
if (token.isSymbol) {
value = `[${value}]`;
}
let prefix = "";
if (formatter.context.isStaticMember) {
formatter.context.staticDepth++;
if (formatter.context.staticDepth === 1) {
const prefixStyles = formatter.styles.objectKeyPrefix;
prefix = `${prefixStyles("static")} `;
}
}
return indent + prefix + styles(value) + ": ";
},
"object-circular-ref": (_, formatter) => {
const styles = formatter.styles.circularLabel;
return styles("[*Circular]");
},
"object-max-depth-ref": (_, formatter) => {
const styles = formatter.styles.objectLabel;
return styles("[Object]");
},
"object-value-getter": (_, formatter) => {
const styles = formatter.styles.getterLabel;
return styles("[Getter]");
},
"object-value-start": () => {
return "";
},
"object-value-end": (_, formatter) => {
if (formatter.context.isStaticMember) {
formatter.context.staticDepth--;
}
return `,`;
},
"array-start": (token, formatter) => {
formatter.indentation.increment();
const styles = formatter.styles.arrayLabel;
const label = token.name !== "Array" ? styles(`${token.name}`) + " " : "";
return label + openingBrackets(formatter);
},
"array-end": (_, formatter) => {
formatter.indentation.decrement();
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
return indent + closingBrackets(formatter);
},
"array-value-start": (_, formatter) => {
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
return indent;
},
"array-value-hole": (_, formatter) => {
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
const styles = formatter.styles.undefined;
return indent + styles("<hole>") + ",";
},
"array-value-end": () => {
return `,`;
},
"array-circular-ref": (_, formatter) => {
const styles = formatter.styles.circularLabel;
return styles("[*Circular]");
},
"array-max-depth-ref": (_, formatter) => {
const styles = formatter.styles.arrayLabel;
return styles("[Array]");
},
"array-max-length-ref": (token, formatter) => {
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
const styles = formatter.styles.arrayLabel;
const itemsLeft = token.size - token.limit;
if (itemsLeft <= 0) {
return "";
}
const label = itemsLeft === 1 ? `1 more item` : `${itemsLeft} more items`;
return indent + styles(`[...${label}]`);
},
"prototype-start": (_, formatter) => {
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
formatter.indentation.increment();
const styles = formatter.styles.prototypeLabel;
const label = "[[Prototype]] ";
return indent + styles(label) + openingBrace(formatter);
},
"prototype-end": (_, formatter) => {
formatter.indentation.decrement();
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
return indent + closingBrace(formatter);
},
"map-start": (token, formatter) => {
formatter.indentation.increment();
const styles = formatter.styles.mapLabel;
const label = `Map(${token.size}) `;
return styles(label) + openingBrace(formatter);
},
"map-end": (_, formatter) => {
formatter.indentation.decrement();
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
return indent + closingBrace(formatter);
},
"map-row-start": (_, formatter) => {
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
formatter.indentation.increment();
return indent + openingBrackets(formatter);
},
"map-row-end": (_, formatter) => {
formatter.indentation.decrement();
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
return indent + closingBrackets(formatter) + `,`;
},
"map-key-start": (_, formatter) => {
const styles = formatter.styles.objectKey;
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
return indent + styles("key") + ": ";
},
"map-key-end": function() {
return ",";
},
"map-value-start": (_, formatter) => {
const styles = formatter.styles.objectKey;
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
return indent + styles("value") + ": ";
},
"map-value-end": function() {
return ",";
},
"map-circular-ref": (_, formatter) => {
const styles = formatter.styles.circularLabel;
return styles("[*Circular]");
},
"map-max-depth-ref": (_, formatter) => {
const styles = formatter.styles.mapLabel;
return styles("[Map]");
},
"map-max-length-ref": (token, formatter) => {
const styles = formatter.styles.mapLabel;
const itemsLeft = token.size - token.limit;
if (itemsLeft <= 0) {
return "";
}
const label = itemsLeft === 1 ? `1 more item` : `${itemsLeft} more items`;
return styles(`[...${label}]`);
},
"set-start": (token, formatter) => {
formatter.indentation.increment();
const styles = formatter.styles.setLabel;
const label = `Set(${token.size}) `;
return styles(label) + openingBrackets(formatter);
},
"set-end": (_, formatter) => {
formatter.indentation.decrement();
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
return indent + closingBrackets(formatter);
},
"set-value-start": (_, formatter) => {
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
return indent;
},
"set-value-end": () => {
return `,`;
},
"set-circular-ref": (_, formatter) => {
const styles = formatter.styles.circularLabel;
return styles("[*Circular]");
},
"set-max-depth-ref": (_, formatter) => {
const styles = formatter.styles.setLabel;
return styles("[Set]");
},
"set-max-length-ref": (token, formatter) => {
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
const styles = formatter.styles.setLabel;
const itemsLeft = token.size - token.limit;
if (itemsLeft <= 0) {
return "";
}
const label = itemsLeft === 1 ? `1 more item` : `${itemsLeft} more items`;
return indent + styles(`[...${label}]`);
},
"string": (token, formatter) => {
let value = token.value;
const indent = formatter.indentation.getSpaces();
if (formatter.context.isStack) {
value = token.value.split("\n").map((row, index) => {
let rowValue = row.trim();
if (index > 0) {
rowValue = `${indent}${rowValue}`;
}
return rowValue;
}).join("\n");
} else {
value = wordWrap(token.value, {
newLine: formatter.newLine,
indent: formatter.indentation.getSpaces(),
width: 70,
escape: (line) => line
});
}
const styles = formatter.styles.string;
return styles(value);
},
"boolean": (token, formatter) => {
const styles = formatter.styles.boolean;
return styles(String(token.value));
},
"number": (token, formatter) => {
const styles = formatter.styles.number;
return styles(String(token.value));
},
"bigInt": (token, formatter) => {
const styles = formatter.styles.bigInt;
return styles(token.value);
},
"undefined": (_, formatter) => {
const styles = formatter.styles.undefined;
return styles("undefined");
},
"null": (_, formatter) => {
const styles = formatter.styles.null;
return styles("null");
},
"symbol": (token, formatter) => {
const styles = formatter.styles.symbol;
return styles(token.value);
},
"function": (token, formatter) => {
const styles = token.isClass ? formatter.styles.classLabel : formatter.styles.functionLabel;
const async = token.isAsync ? `async ` : "";
const generator = token.isGenerator ? `*` : "";
const label = token.isClass ? `[class ${token.name}]` : `[${async}${generator}function ${token.name}]`;
return styles(label);
},
"date": function(token, formatter) {
const styles = formatter.styles.date;
return styles(token.value);
},
"buffer": function(token, formatter) {
const styles = formatter.styles.buffer;
return styles(token.value);
},
"regexp": function(token, formatter) {
const styles = formatter.styles.regex;
return styles(token.value);
},
"unknown": function(token, formatter) {
const styles = formatter.styles.unknownLabel;
return styles(String(token.value));
},
"weak-set": function(_, formatter) {
const styles = formatter.styles.weakSetLabel;
return styles("[WeakSet]");
},
"weak-ref": function(_, formatter) {
const styles = formatter.styles.weakRefLabel;
return styles("[WeakRef]");
},
"weak-map": function(_, formatter) {
const styles = formatter.styles.weakMapLabel;
return styles("[WeakMap]");
},
"observable": function(_, formatter) {
const styles = formatter.styles.observableLabel;
return styles("[Observable]");
},
"blob": function(token, formatter) {
const styles = formatter.styles.objectLabel;
const sizeProp = formatter.styles.objectKey("size: ");
const sizeValue = formatter.styles.number(`${token.size}`);
const typeProp = token.contentType ? `, ${formatter.styles.objectKey("type: ")}` : "";
const typeValue = token.contentType ? formatter.styles.string(`${token.contentType}`) : "";
return styles("[Blob]") + " " + openingBrace(formatter) + `${sizeProp}${sizeValue}${typeProp}${typeValue}` + closingBrace(formatter);
},
"promise": function(token, formatter) {
const styles = formatter.styles.promiseLabel;
const label = token.isFulfilled ? "resolved" : "pending";
return styles(`[Promise${`<${label}>`}]`);
},
"generator": function(token, formatter) {
const styles = formatter.styles.generatorLabel;
const label = token.isAsync ? "[AsyncGenerator] {}" : "[Generator] {}";
return styles(label);
},
"static-members-start": function(_, formatter) {
formatter.context.isStaticMember = true;
formatter.context.staticDepth = 0;
return " ";
},
"static-members-end": function(_, formatter) {
formatter.context.isStaticMember = false;
formatter.context.staticDepth = 0;
return "";
}
};
// formatters/console/formatter.ts
var ConsoleFormatter = class {
/**
* Styles for output elements
*/
styles;
/**
* Context maintained through out the printing
* phase. Each instance has its own context
* that gets mutated internally.
*/
context;
/**
* Value for the newline character
*/
newLine = "\n";
/**
* Utility to manage indentation
*/
indentation = {
counter: 0,
/**
* Increment the identation by 1 step
*/
increment() {
this.counter++;
},
/**
* Decrement the identation by 1 step
*/
decrement() {
this.counter--;
},
/**
* Get the identation spaces as per the current
* identation level
*/
getSpaces() {
return new Array(this.counter * 2 + 1).join(" ");
}
};
constructor(config, context) {
this.context = context || {};
this.styles = Object.freeze({ ...themes.default, ...config?.styles });
}
/**
* Format a collection of tokens to ANSI output
*/
format(tokens) {
return tokens.map((token) => {
const formatter = ConsolePrinters[token.type];
return formatter(token, this) || "";
}).join("");
}
};
// formatters/console/main.ts
function dump(value, config) {
const parser = new Parser(config);
parser.parse(value);
return new ConsoleFormatter(config).format(parser.flush());
}
export {
ConsoleFormatter,
ConsolePrinters,
dump,
themes
};
@@ -0,0 +1,5 @@
import type { TokenPrinters } from '../types.js';
/**
* Console printers to pretty print parser tokens
*/
export declare const ConsolePrinters: TokenPrinters;
+39
View File
@@ -0,0 +1,39 @@
/**
* Default styles to use for pretty printing to ANSI output
*/
export declare const themes: {
default: {
braces: (value: string) => string;
brackets: (value: string) => string;
number: (value: string) => string;
bigInt: (value: string) => string;
boolean: (value: string) => string;
string: (value: string) => string;
null: (value: string) => string;
undefined: (value: string) => string;
prototypeLabel: (value: string) => string;
symbol: (value: string) => string;
regex: (value: string) => string;
date: (value: string) => string;
buffer: (value: string) => string;
functionLabel: (value: string) => string;
arrayLabel: (value: string) => string;
objectLabel: (value: string) => string;
mapLabel: (value: string) => string;
setLabel: (value: string) => string;
objectKey: (value: string) => string;
objectKeyPrefix: (value: string) => string;
classLabel: (value: string) => string;
weakSetLabel: (value: string) => string;
weakRefLabel: (value: string) => string;
collapseLabel: (value: string) => string;
circularLabel: (value: string) => string;
getterLabel: (value: string) => string;
weakMapLabel: (value: string) => string;
observableLabel: (value: string) => string;
promiseLabel: (value: string) => string;
generatorLabel: (value: string) => string;
blobLabel: (value: string) => string;
unknownLabel: (value: string) => string;
};
};
+114
View File
@@ -0,0 +1,114 @@
import type { ParserConfig, TokensMap } from '../../src/types.js';
import { type ConsoleFormatter } from './formatter.js';
/**
* Styles to use for pretty printing parser tokens. The token
* printer receives a string value and must return back
* a styled string value.
*
* @example
* ```ts
* {
* braces: (value) => chalk.yellow(value)
* }
* ```
*/
export type ConsolePrinterStyles = {
braces: (value: string) => string;
brackets: (value: string) => string;
string: (value: string) => string;
number: (value: string) => string;
boolean: (value: string) => string;
bigInt: (value: string) => string;
undefined: (value: string) => string;
null: (value: string) => string;
symbol: (value: string) => string;
regex: (value: string) => string;
date: (value: string) => string;
buffer: (value: string) => string;
/**
* Styles for displaying the function value with
* its name
*/
functionLabel: (value: string) => string;
/**
* Styles for displaying the class value with
* its name
*/
classLabel: (value: string) => string;
/**
* Styles for the Object label. Use braces setting to define
* the braces color
*/
objectLabel: (value: string) => string;
/**
* Styles for the Object key name
*/
objectKey: (value: string) => string;
/**
* Styles for the Object key prefix name.
*/
objectKeyPrefix: (value: string) => string;
/**
* Styles for the Array label. Use brackets setting to define
* the brackets color
*/
arrayLabel: (value: string) => string;
/**
* Styles for the Map label. Use brackets setting to define
* the brackets color
*/
mapLabel: (value: string) => string;
/**
* Styles for the Set label. Use brackets setting to define
* the brackets color
*/
setLabel: (value: string) => string;
collapseLabel: (value: string) => string;
circularLabel: (value: string) => string;
getterLabel: (value: string) => string;
weakSetLabel: (value: string) => string;
weakRefLabel: (value: string) => string;
weakMapLabel: (value: string) => string;
observableLabel: (value: string) => string;
promiseLabel: (value: string) => string;
generatorLabel: (value: string) => string;
prototypeLabel: (value: string) => string;
/**
* Styles for displaying Blob keyword label. The blob
* properties like the type and size are styled as
* an object
*/
blobLabel: (value: string) => string;
/**
* Styles for displaying values not parsed by the
* parser as first-class citizen but still printed
* by casting them to String
*/
unknownLabel: (value: string) => string;
};
/**
* Printers collection that are used to print parser
* tokens to ANSI output (one at a time)
*/
export type TokenPrinters = {
[K in keyof TokensMap]: (
/**
* Value of the token
*/
token: TokensMap[K],
/**
* Formatter reference
*/
formatter: ConsoleFormatter) => string;
};
/**
* Config accepted by the Console Formatter
*/
export type ConsoleFormatterConfig = {
styles?: Partial<ConsolePrinterStyles>;
};
/**
* Configuration accepted by the "dump" method exported
* by the cli sub-module.
*/
export type ConsoleDumpConfig = ParserConfig & ConsoleFormatterConfig;
View File
+60
View File
@@ -0,0 +1,60 @@
import type { Token } from '../../src/types.js';
import type { HTMLFormatterConfig, HTMLPrinterStyles } from './types.js';
export declare let nanoid: (length?: number) => string;
/**
* HTMLFormatter is used to format a collection of parser
* tokens into HTML output containing pre-tags.
*
* @example
* ```ts
* const parser = new Parser()
* parser.parse(value)
*
* const tokens = parser.flush()
*
* const formatter = new HTMLFormatter()
* const html = formatter.format(tokens)
* ```
*/
export declare class HTMLFormatter {
#private;
/**
* Styles for output elements
*/
readonly styles: HTMLPrinterStyles;
/**
* Context maintained through out the printing
* phase. Each instance has its own context
* that gets mutated internally.
*/
context: Record<string, any>;
/**
* Value for the newline character
*/
readonly newLine = "\n";
/**
* Utility to manage indentation
*/
readonly indentation: {
counter: number;
/**
* Increment the identation by 1 step
*/
increment(): void;
/**
* Decrement the identation by 1 step
*/
decrement(): void;
/**
* Get the identation spaces as per the current
* identation level
*/
getSpaces(): string;
};
constructor(config?: HTMLFormatterConfig, context?: Record<string, any>);
/**
* Format a collection of tokens to HTML output wrapped
* inside the `pre` tag.
*/
format(tokens: Token[]): string;
}
+10
View File
@@ -0,0 +1,10 @@
/**
* Creates the stylesheet content to be injected inside the head
* of the document
*/
export declare function createStyleSheet(): string;
/**
* Returns the script tag contents to be injected inside the head
* of the document
*/
export declare function createScript(): string;
+29
View File
@@ -0,0 +1,29 @@
import type { HTMLDumpConfig } from './types.js';
export * from './head.js';
export { themes } from './themes.js';
export { HTMLFormatter } from './formatter.js';
export { HTMLPrinters } from './printers/main.js';
/**
* Generate pretty printed HTML output for the provided value. You can
* specify the parser and the formatter options as the 2nd argument.
*
* @example
* ```ts
* const html = dump(someValue)
*
* // With Parser options
* const html = dump(someValue, {
* inspectObjectPrototype: true,
* depth: 10,
* showHidden: true,
* })
*
* // With Formatter options
* const html = dump(someValue, {
* styles: {
* number: 'color: yellow; font-weight: bold;'
* }
* })
* ```
*/
export declare function dump(value: any, config?: HTMLDumpConfig): string;
+717
View File
@@ -0,0 +1,717 @@
import {
Parser,
htmlEscape,
wordWrap
} from "../../chunk-26HALFTP.js";
// formatters/html/themes.ts
var themes = {
nightOwl: {
pre: "background-color: #061626; color: #c792ea;",
toggle: "color: #4f5357; background: none; border: none;",
braces: "color: #ffd700;",
brackets: "color: #ffd700;",
number: "color: #f78c6c;",
bigInt: "color: #f78c6c; font-weight: bold;",
boolean: "color: #ff5874; font-style: italic;",
string: "color: #ecc48d;",
null: "color: #637777;",
undefined: "color: #637777;",
prototypeLabel: "color: #637777;",
symbol: "color: #82aaff;",
regex: "color: #ff5874;",
date: "color: #7fdbca;",
buffer: "color: #7fdbca;",
functionLabel: "color: #89b4fa;",
arrayLabel: "color: #82aaff;",
objectLabel: "color: #82aaff;",
mapLabel: "color: #82aaff;",
setLabel: "color: #82aaff;",
objectKey: "color: #c792ea;",
objectKeyPrefix: "color: #637777; font-style: italic; font-weight: bold",
classLabel: "color: #82aaff;",
collapseLabel: "color: #7fdbca; font-style: italic;",
getterLabel: "color: #7fdbca;",
circularLabel: "color: #7fdbca;",
weakSetLabel: "color: #7fdbca;",
weakRefLabel: "color: #7fdbca;",
weakMapLabel: "color: #7fdbca;",
observableLabel: "color: #7fdbca;",
promiseLabel: "color: #7fdbca;",
generatorLabel: "color: #7fdbca;",
blobLabel: "color: #7fdbca;",
unknownLabel: "color: #7fdbca;"
},
minLight: {
pre: "background-color: #fff; color: #212121;",
toggle: "color: #989999; background: none; border: none;",
braces: "color: #0431fa;",
brackets: "color: #0431fa;",
number: "color: #1976d2;",
bigInt: "color: #1976d2; font-weight: bold;",
boolean: "color: #1976d2; font-style: italic;",
string: "color: #22863a;",
null: "color: #9c9c9d;",
undefined: "color: #9c9c9d;",
prototypeLabel: "color: #9c9c9d;",
symbol: "color: #d32f2f;",
regex: "color: #1976d2;",
date: "color: #7b3814;",
buffer: "color: #7b3814;",
functionLabel: "color: #6f42c1;",
arrayLabel: "color: #d32f2f;",
objectLabel: "color: #d32f2f;",
mapLabel: "color: #d32f2f;",
setLabel: "color: #d32f2f;",
objectKey: "color: #212121;",
objectKeyPrefix: "color: #9c9c9d; font-style: italic; font-weight: bold",
classLabel: "color: #6f42c1;",
collapseLabel: "color: #9c9c9d; font-style: italic;",
getterLabel: "color: #7b3814;",
circularLabel: "color: #7b3814;",
weakSetLabel: "color: #7b3814;",
weakRefLabel: "color: #7b3814;",
weakMapLabel: "color: #7b3814;",
observableLabel: "color: #7b3814;",
promiseLabel: "color: #7b3814;",
generatorLabel: "color: #7b3814;",
blobLabel: "color: #7b3814;",
unknownLabel: "color: #7b3814;"
},
catppuccin: {
pre: "background-color: #1e1e2e; color: #94e2d5;",
toggle: "color: #7c7c8c; background: none; border: none;",
braces: "color: #f38ba8;",
brackets: "color: #f38ba8;",
number: "color: #fab387;",
bigInt: "color: #fab387; font-weight: bold;",
boolean: "color: #cba6f7; font-style: italic;",
string: "color: #a6e3a1;",
null: "color: #6c7086;",
undefined: "color: #6c7086;",
prototypeLabel: "color: #6c7086;",
symbol: "color: #f9e2af;",
regex: "color: #cba6f7;",
date: "color: #94e2d5;",
buffer: "color: #94e2d5;",
functionLabel: "color: #cba6f7;",
arrayLabel: "color: #f9e2af;",
objectLabel: "color: #f9e2af;",
mapLabel: "color: #f9e2af;",
setLabel: "color: #f9e2af;",
objectKey: "color: #89b4fa;",
objectKeyPrefix: "color: #6c7086; font-style: italic; font-weight: bold",
classLabel: "color: #cba6f7;",
collapseLabel: "color: #6c7086; font-style: italic;",
getterLabel: "color: #94e2d5;",
circularLabel: "color: #94e2d5;",
weakSetLabel: "color: #94e2d5;",
weakRefLabel: "color: #94e2d5;",
weakMapLabel: "color: #94e2d5;",
observableLabel: "color: #94e2d5;",
promiseLabel: "color: #94e2d5;",
generatorLabel: "color: #94e2d5;",
blobLabel: "color: #94e2d5;",
unknownLabel: "color: #94e2d5;"
},
/**
* Following is the list of defined variables
--pre-bg-color
--pre-fg-color
--toggle-fg-color
--braces-fg-color
--brackets-fg-color
--dt-number-fg-color
--dt-bigint-fg-color
--dt-boolean-fg-color
--dt-string-fg-color
--dt-null-fg-color
--dt-undefined-fg-color
--prototype-label-fg-color
--dt-symbol-fg-color
--dt-regex-fg-color
--dt-date-fg-color
--dt-buffer-fg-color
--function-label-fg-color
--array-label-fg-color
--object-label-fg-color
--map-label-fg-color
--set-label-fg-color
--object-key-fg-color
--object-key-prefix-fg-color
--class-label-fg-color
--collpase-label-fg-color
--getter-label-fg-color
--circular-label-fg-color
--weakset-label-fg-color
--weakref-label-fg-color
--weakmap-label-fg-color
--observable-label-fg-color
--promise-label-fg-color
--generator-label-fg-color
--blob-label-fg-color
--unknown-label-fg-color
*/
cssVariables: {
pre: "background-color: var(--pre-bg-color); color: var(--pre-fg-color);",
toggle: "color: var(--toggle-fg-color); background: none; border: none;",
braces: "color: var(--braces-fg-color);",
brackets: "color: var(--brackets-fg-color);",
number: "color: var(--dt-number-fg-color);",
bigInt: "color: var(--dt-bigint-fg-color); font-weight: bold;",
boolean: "color: var(--dt-boolean-fg-color); font-style: italic;",
string: "color: var(--dt-string-fg-color);",
null: "color: var(--dt-null-fg-color);",
undefined: "color: var(--dt-undefined-fg-color);",
prototypeLabel: "color: var(--prototype-label-fg-color);",
symbol: "color: var(--dt-symbol-fg-color);",
regex: "color: var(--dt-regex-fg-color);",
date: "color: var(--dt-date-fg-color);",
buffer: "color: var(--dt-buffer-fg-color);",
functionLabel: "color: var(--function-label-fg-color);",
arrayLabel: "color: var(--array-label-fg-color);",
objectLabel: "color: var(--object-label-fg-color);",
mapLabel: "color: var(--map-label-fg-color);",
setLabel: "color: var(--set-label-fg-color);",
objectKey: "color: var(--object-key-fg-color);",
objectKeyPrefix: "color: var(--object-key-prefix-fg-color); font-style: italic; font-weight: bold",
classLabel: "color: var(--class-label-fg-color);",
collapseLabel: "color: var(--collpase-label-fg-color); font-style: italic;",
getterLabel: "color: var(--getter-label-fg-color);",
circularLabel: "color: var(--circular-label-fg-color);",
weakSetLabel: "color: var(--weakset-label-fg-color);",
weakRefLabel: "color: var(--weakref-label-fg-color);",
weakMapLabel: "color: var(--weakmap-label-fg-color);",
observableLabel: "color: var(--observable-label-fg-color);",
promiseLabel: "color: var(--promise-label-fg-color);",
generatorLabel: "color: var(--generator-label-fg-color);",
blobLabel: "color: var(--blob-label-fg-color);",
unknownLabel: "color: var(--unknown-label-fg-color);"
}
};
// formatters/html/printers/main.ts
var dropdownIcon = "&#9660;";
function openingBrace(formatter) {
return `<span style="${formatter.styles.braces}">{</span>`;
}
function closingBrace(formatter) {
return `<span style="${formatter.styles.braces}">}</span>`;
}
function openingBrackets(formatter) {
return `<span style="${formatter.styles.brackets}">[</span>`;
}
function closingBrackets(formatter) {
return `<span style="${formatter.styles.brackets}">]</span>`;
}
var HTMLPrinters = {
"collapse": (token, formatter) => {
const styles = token.token.type === "object-start" ? formatter.styles.objectLabel : formatter.styles.arrayLabel;
const collpaseStyles = formatter.styles.collapseLabel;
return `<span style="${styles}">${token.name}</span> ` + (token.token.type === "object-start" ? openingBrace(formatter) : openingBrackets(formatter)) + ` <span style="${collpaseStyles}">collapsed</span> ` + (token.token.type === "object-start" ? closingBrace(formatter) : closingBrackets(formatter));
},
"object-start": (token, formatter) => {
formatter.indentation.increment();
const styles = formatter.styles.objectLabel;
const toggleStyles = formatter.styles.toggle;
const label = formatter.context.isStaticMember && formatter.context.staticDepth === 0 ? " " : `${token.constructorName || "Object [null]"} `;
return `<span class="dumper-group dumper-object-group"><span style="${styles}">${label}</span>` + openingBrace(formatter) + `<button class="dumper-toggle" style="${toggleStyles}"><span>${dropdownIcon}</span></button><samp hidden="true">`;
},
"object-end": (_, formatter) => {
formatter.indentation.decrement();
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
return indent + "</samp>" + closingBrace(formatter) + "</span>";
},
"object-key": (token, formatter) => {
formatter.context.isStack = token.value === "stack";
const styles = formatter.styles.objectKey;
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
let value = token.value;
if (token.isSymbol) {
value = `[${value}]`;
} else if (!/^[a-z$_][$\w]*$/i.test(value)) {
value = `"${htmlEscape(value.replace(/"/g, '\\"'))}"`;
}
let prefix = "";
if (formatter.context.isStaticMember) {
formatter.context.staticDepth++;
if (formatter.context.staticDepth === 1) {
const prefixStyles = formatter.styles.objectKeyPrefix;
prefix = `<span class="dumper-object-prefix" style="${prefixStyles}">static </span>`;
}
}
return indent + prefix + `<span class="dumper-object-key" style="${styles}">${value}</span>: `;
},
"object-circular-ref": (_, formatter) => {
const styles = formatter.styles.circularLabel;
return `<span style="${styles}">[*Circular]</span>`;
},
"object-max-depth-ref": (_, formatter) => {
const styles = formatter.styles.objectLabel;
return `<span style="${styles}">[Object]</span>`;
},
"object-value-getter": (_, formatter) => {
const styles = formatter.styles.getterLabel;
return `<span style="${styles}">[Getter]</span>`;
},
"object-value-start": () => {
return "";
},
"object-value-end": (_, formatter) => {
if (formatter.context.isStaticMember) {
formatter.context.staticDepth--;
}
return `,`;
},
"array-start": (token, formatter) => {
formatter.indentation.increment();
const toggleStyles = formatter.styles.toggle;
const styles = formatter.styles.arrayLabel;
const label = `${token.name}:${token.size} `;
return `<span class="dumper-group dumper-array-group"><span style="${styles}">${label}</span>` + openingBrackets(formatter) + `<button class="dumper-toggle" style="${toggleStyles}"><span>${dropdownIcon}</span></button><samp hidden="true">`;
},
"array-end": (_, formatter) => {
formatter.indentation.decrement();
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
return indent + "</samp>" + closingBrackets(formatter) + "</span>";
},
"array-value-start": (_, formatter) => {
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
return indent;
},
"array-value-hole": (_, formatter) => {
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
const styles = formatter.styles.undefined;
return indent + `<span class="dumper-undefined" style="${styles}">${htmlEscape("<hole>")},</span>`;
},
"array-value-end": () => {
return `,`;
},
"array-circular-ref": (_, formatter) => {
const styles = formatter.styles.circularLabel;
return `<span style="${styles}">[*Circular]</span>`;
},
"array-max-depth-ref": (_, formatter) => {
const styles = formatter.styles.arrayLabel;
return `<span style="${styles}">[Array]</span>`;
},
"array-max-length-ref": (token, formatter) => {
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
const styles = formatter.styles.arrayLabel;
const itemsLeft = token.size - token.limit;
if (itemsLeft <= 0) {
return "";
}
const label = itemsLeft === 1 ? `1 more item` : `${itemsLeft} more items`;
return `${indent}<span style="${styles}">[...${label}]</span>`;
},
"prototype-start": (_, formatter) => {
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
formatter.indentation.increment();
const styles = formatter.styles.prototypeLabel;
const toggleStyles = formatter.styles.toggle;
const label = "[[Prototype]] ";
return indent + `<span class="dumper-group dumper-prototype-group"><span style="${styles}">${label}</span>` + openingBrace(formatter) + `<button class="dumper-toggle" style="${toggleStyles}"><span>${dropdownIcon}</span></button><samp hidden="true">`;
},
"prototype-end": (_, formatter) => {
formatter.indentation.decrement();
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
return indent + "</samp>" + closingBrace(formatter) + "</span>";
},
"map-start": (token, formatter) => {
formatter.indentation.increment();
const toggleStyles = formatter.styles.toggle;
const styles = formatter.styles.mapLabel;
const label = `Map:${token.size} `;
return `<span class="dumper-group dumper-map-group"><span style="${styles}">${label}</span>` + openingBrace(formatter) + `<button class="dumper-toggle" style="${toggleStyles}"><span>${dropdownIcon}</span></button><samp hidden="true">`;
},
"map-end": (_, formatter) => {
formatter.indentation.decrement();
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
return indent + "</samp>" + closingBrace(formatter) + "</span>";
},
"map-row-start": (_, formatter) => {
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
formatter.indentation.increment();
return indent + openingBrackets(formatter);
},
"map-row-end": (_, formatter) => {
formatter.indentation.decrement();
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
return indent + closingBrackets(formatter) + `,`;
},
"map-key-start": (_, formatter) => {
const styles = formatter.styles.objectKey;
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
return indent + `<span style="${styles}">key</span>: `;
},
"map-key-end": function() {
return "";
},
"map-value-start": (_, formatter) => {
const styles = formatter.styles.objectKey;
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
return indent + `<span style="${styles}">value</span>: `;
},
"map-value-end": function() {
return "";
},
"map-circular-ref": (_, formatter) => {
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
const styles = formatter.styles.circularLabel;
return `${indent}<span style="${styles}">[*Circular]</span>`;
},
"map-max-depth-ref": (_, formatter) => {
const styles = formatter.styles.mapLabel;
return `<span style="${styles}">[Map]</span>`;
},
"map-max-length-ref": (token, formatter) => {
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
const styles = formatter.styles.mapLabel;
const itemsLeft = token.size - token.limit;
if (itemsLeft <= 0) {
return "";
}
const label = itemsLeft === 1 ? `1 more item` : `${itemsLeft} more items`;
return `${indent}<span style="${styles}">[...${label}]</span>`;
},
"set-start": (token, formatter) => {
formatter.indentation.increment();
const toggleStyles = formatter.styles.toggle;
const styles = formatter.styles.setLabel;
const label = `Set:${token.size} `;
return `<span class="dumper-group dumper-set-group"><span class="dumper-set-label" style="${styles}">${label}</span>` + openingBrackets(formatter) + `<button class="dumper-toggle" style="${toggleStyles}"><span>${dropdownIcon}</span></button><samp hidden="true">`;
},
"set-end": (_, formatter) => {
formatter.indentation.decrement();
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
return indent + "</samp>" + closingBrackets(formatter) + "</span>";
},
"set-value-start": (_, formatter) => {
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
return indent;
},
"set-value-end": () => {
return `,`;
},
"set-circular-ref": (_, formatter) => {
const styles = formatter.styles.circularLabel;
return `<span style="${styles}">[*Circular]</span>`;
},
"set-max-depth-ref": (_, formatter) => {
const styles = formatter.styles.setLabel;
return `<span style="${styles}">[Set]</span>`;
},
"set-max-length-ref": (token, formatter) => {
const indent = `${formatter.newLine}${formatter.indentation.getSpaces()}`;
const styles = formatter.styles.setLabel;
const itemsLeft = token.size - token.limit;
if (itemsLeft <= 0) {
return "";
}
const label = itemsLeft === 1 ? `1 more item` : `${itemsLeft} more items`;
return `${indent}<span style="${styles}">[...${label}]</span>`;
},
"string": (token, formatter) => {
let value = token.value;
const indent = formatter.indentation.getSpaces();
if (formatter.context.isStack) {
value = token.value.split("\n").map((row, index) => {
let rowValue = `<span>${htmlEscape(row.trim())}</span>`;
if (index > 0) {
rowValue = `${indent}${rowValue}`;
}
return rowValue;
}).join("\n");
} else {
value = wordWrap(token.value, {
newLine: formatter.newLine,
indent: formatter.indentation.getSpaces(),
width: 70
});
}
const styles = formatter.styles.string;
return `<span class="dumper-string" style="${styles}">${value}</span>`;
},
"boolean": (token, formatter) => {
const styles = formatter.styles.boolean;
return `<span class="dumper-boolean" style="${styles}">` + token.value + "</span>";
},
"number": (token, formatter) => {
const styles = formatter.styles.number;
return `<span class="dumper-number" style="${styles}">` + token.value + "</span>";
},
"bigInt": (token, formatter) => {
const styles = formatter.styles.bigInt;
return `<span class="dumper-big-int" style="${styles}">` + token.value + "</span>";
},
"undefined": (_, formatter) => {
const styles = formatter.styles.undefined;
return `<span class="dumper-undefined" style="${styles}">undefined</span>`;
},
"null": (_, formatter) => {
const styles = formatter.styles.null;
return `<span class="dumper-null" style="${styles}">null</span>`;
},
"symbol": (token, formatter) => {
const styles = formatter.styles.symbol;
return `<span class="dumper-symbol" style="${styles}">` + token.value + "</span>";
},
"function": (token, formatter) => {
const className = token.isClass ? "dumper-class" : "dumper-function";
const styles = token.isClass ? formatter.styles.classLabel : formatter.styles.functionLabel;
const async = token.isAsync ? `async ` : "";
const generator = token.isGenerator ? `*` : "";
const label = token.isClass ? `[class ${token.name}]` : `[${async}${generator}function ${token.name}]`;
return `<span class="${className}" style="${styles}">` + label + "</span>";
},
"date": function(token, formatter) {
const styles = formatter.styles.date;
return `<span class="dumper-date" style="${styles}">` + token.value + "</span>";
},
"buffer": function(token, formatter) {
const styles = formatter.styles.buffer;
return `<span class="dumper-buffer" style="${styles}">` + htmlEscape(token.value) + "</span>";
},
"regexp": function(token, formatter) {
const styles = formatter.styles.regex;
return `<span class="dumper-regex" style="${styles}">` + token.value + "</span>";
},
"unknown": function(token, formatter) {
const styles = formatter.styles.unknownLabel;
return `<span class="dumper-value-unknown" style="${styles}">` + String(token.value) + "</span>";
},
"weak-set": function(_, formatter) {
const styles = formatter.styles.weakSetLabel;
return `<span class="dumper-weak-set" style="${styles}">[WeakSet]</span>`;
},
"weak-ref": function(_, formatter) {
const styles = formatter.styles.weakRefLabel;
return `<span class="dumper-weak-ref" style="${styles}">[WeakRef]</span>`;
},
"weak-map": function(_, formatter) {
const styles = formatter.styles.weakMapLabel;
return `<span class="dumper-weak-map" style="${styles}">[WeakMap]</span>`;
},
"observable": function(_, formatter) {
const styles = formatter.styles.observableLabel;
return `<span class="dumper-observable" style="${styles}">[Observable]</span>`;
},
"blob": function(token, formatter) {
const styles = formatter.styles.objectLabel;
const propertiesStart = `<span styles="${formatter.styles.braces}">{ `;
const propertiesEnd = `<span styles="${formatter.styles.braces}"> }</span></span>`;
const sizeProp = `<span styles="${formatter.styles.objectKey}">size: </span>`;
const sizeValue = `<span styles="${formatter.styles.number}">${token.size}</span>,`;
const typeProp = `<span styles="${formatter.styles.objectKey}">type: </span>`;
const typeValue = `<span styles="${formatter.styles.string}">${token.contentType}</span>`;
return `<span class="dumper-blob" style="${styles}">[Blob]` + propertiesStart + `${sizeProp}${sizeValue} ${typeProp}${typeValue}` + propertiesEnd + "</span>";
},
"promise": function(token, formatter) {
const styles = formatter.styles.promiseLabel;
const label = token.isFulfilled ? "resolved" : "pending";
return `<span class="dumper-promise" style="${styles}">[Promise${htmlEscape(`<${label}>`)}]</span>`;
},
"generator": function(token, formatter) {
const styles = formatter.styles.generatorLabel;
const label = token.isAsync ? "[AsyncGenerator] {}" : "[Generator] {}";
return `<span class="dumper-generator" style="${styles}">` + label + "</span>";
},
"static-members-start": function(_, formatter) {
formatter.context.isStaticMember = true;
formatter.context.staticDepth = 0;
return "";
},
"static-members-end": function(_, formatter) {
formatter.context.isStaticMember = false;
formatter.context.staticDepth = 0;
return "";
}
};
// formatters/html/formatter.ts
var seed = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
var nanoid = (length = 15) => {
let output = "";
let random = new Uint8Array(length);
if (globalThis.crypto) {
crypto.getRandomValues(random);
} else {
for (let i = 0; i < length; i++) {
random[i] = Math.floor(Math.random() * 256);
}
}
for (let n = 0; n < length; n++) {
output += seed[63 & random[n]];
}
return output;
};
var HTMLFormatter = class {
#config;
/**
* Styles for output elements
*/
styles;
/**
* Context maintained through out the printing
* phase. Each instance has its own context
* that gets mutated internally.
*/
context;
/**
* Value for the newline character
*/
newLine = "\n";
/**
* Utility to manage indentation
*/
indentation = {
counter: 0,
/**
* Increment the identation by 1 step
*/
increment() {
this.counter++;
},
/**
* Decrement the identation by 1 step
*/
decrement() {
this.counter--;
},
/**
* Get the identation spaces as per the current
* identation level
*/
getSpaces() {
return new Array(this.counter * 2 + 1).join("&nbsp;");
}
};
constructor(config, context) {
this.context = context || {};
this.#config = config || {};
this.styles = Object.freeze({ ...themes.nightOwl, ...config?.styles });
}
/**
* Wraps the final output inside pre tags and add the script
* to activate the frontend iteractions.
*/
#wrapOutput(code) {
const id = `dump-${nanoid()}`;
const expand = this.#config.expand === "all" ? `'all'` : this.#config.expand;
const nonce = this.#config.cspNonce ? ` nonce="${this.#config.cspNonce}"` : "";
return `<div id="${id}" class="dumper-dump"><pre style="${this.styles.pre}"><code>${code}</code></pre><script${nonce}>dumperActivate('${id}', ${expand})</script></div>`;
}
/**
* Format a collection of tokens to HTML output wrapped
* inside the `pre` tag.
*/
format(tokens) {
return this.#wrapOutput(
tokens.map((token) => {
const formatter = HTMLPrinters[token.type];
return formatter(token, this) || "";
}).join("")
);
}
};
// formatters/html/head.ts
function createStyleSheet() {
return `.dumper-dump, .dumper-dump pre, .dumper-dump code, .dumper-dump samp {
font-family: JetBrains Mono, monaspace argon, Menlo, Monaco, Consolas, monospace;
}
.dumper-dump pre {
line-height: 24px;
font-size: 15px;
overflow-x: auto;
position:relative;
z-index:99999;
padding: 10px 15px;
margin: 0;
}
.dumper-dump pre samp {
position: relative;
}
.dumper-dump pre samp[hidden="true"] {
display: none;
}
.dumper-dump .dumper-prototype-group {
opacity: 0.5;
}
.dumper-dump .dumper-toggle {
transform: rotate(270deg);
}
.dumper-dump .dumper-toggle span {
display: inline-block;
position: relative;
top: 1px;
margin: 0 5px;
font-size: 14px;
}
.dumper-dump .dumper-toggle[aria-expanded="true"] {
transform: none;
}`;
}
function createScript() {
return `function expandGroup(group) {
const trigger = group.querySelector('button')
trigger.setAttribute('aria-expanded', 'true')
const samp = group.querySelector('samp')
samp.removeAttribute('hidden')
}
function collapseGroup(group) {
const trigger = group.querySelector('button')
trigger.removeAttribute('aria-expanded', 'true')
const samp = group.querySelector('samp')
samp.setAttribute('hidden', 'true')
}
function dumperActivate(dumpId, expand) {
if (expand === true) {
expandGroup(document.querySelector(\`#\${dumpId} .dumper-group\`))
} else if (expand === 'all') {
document.querySelectorAll(\`#\${dumpId} .dumper-group\`).forEach((c) => expandGroup(c))
}
document.querySelectorAll(\`#\${dumpId} .dumper-toggle\`).forEach((trigger) => {
trigger.addEventListener('click', function (event) {
const target = event.currentTarget
const parent = target.parentElement
const isExpanded = !!target.getAttribute('aria-expanded')
if (isExpanded) {
collapseGroup(parent)
if (event.metaKey) {
parent.querySelectorAll('.dumper-group').forEach((c) => collapseGroup(c))
}
} else {
expandGroup(parent)
if (event.metaKey) {
parent.querySelectorAll('.dumper-group').forEach((c) => expandGroup(c))
}
}
})
})
}`;
}
// formatters/html/main.ts
function dump(value, config) {
const parser = new Parser(config);
parser.parse(value);
return new HTMLFormatter(config).format(parser.flush());
}
export {
HTMLFormatter,
HTMLPrinters,
createScript,
createStyleSheet,
dump,
themes
};
@@ -0,0 +1,5 @@
import type { TokenPrinters } from '../types.js';
/**
* HTML printers to pretty print parser tokens
*/
export declare const HTMLPrinters: TokenPrinters;
+188
View File
@@ -0,0 +1,188 @@
/**
* Default styles to use for pretty printing
* the HTML output.
*/
export declare const themes: {
nightOwl: {
pre: string;
toggle: string;
braces: string;
brackets: string;
number: string;
bigInt: string;
boolean: string;
string: string;
null: string;
undefined: string;
prototypeLabel: string;
symbol: string;
regex: string;
date: string;
buffer: string;
functionLabel: string;
arrayLabel: string;
objectLabel: string;
mapLabel: string;
setLabel: string;
objectKey: string;
objectKeyPrefix: string;
classLabel: string;
collapseLabel: string;
getterLabel: string;
circularLabel: string;
weakSetLabel: string;
weakRefLabel: string;
weakMapLabel: string;
observableLabel: string;
promiseLabel: string;
generatorLabel: string;
blobLabel: string;
unknownLabel: string;
};
minLight: {
pre: string;
toggle: string;
braces: string;
brackets: string;
number: string;
bigInt: string;
boolean: string;
string: string;
null: string;
undefined: string;
prototypeLabel: string;
symbol: string;
regex: string;
date: string;
buffer: string;
functionLabel: string;
arrayLabel: string;
objectLabel: string;
mapLabel: string;
setLabel: string;
objectKey: string;
objectKeyPrefix: string;
classLabel: string;
collapseLabel: string;
getterLabel: string;
circularLabel: string;
weakSetLabel: string;
weakRefLabel: string;
weakMapLabel: string;
observableLabel: string;
promiseLabel: string;
generatorLabel: string;
blobLabel: string;
unknownLabel: string;
};
catppuccin: {
pre: string;
toggle: string;
braces: string;
brackets: string;
number: string;
bigInt: string;
boolean: string;
string: string;
null: string;
undefined: string;
prototypeLabel: string;
symbol: string;
regex: string;
date: string;
buffer: string;
functionLabel: string;
arrayLabel: string;
objectLabel: string;
mapLabel: string;
setLabel: string;
objectKey: string;
objectKeyPrefix: string;
classLabel: string;
collapseLabel: string;
getterLabel: string;
circularLabel: string;
weakSetLabel: string;
weakRefLabel: string;
weakMapLabel: string;
observableLabel: string;
promiseLabel: string;
generatorLabel: string;
blobLabel: string;
unknownLabel: string;
};
/**
* Following is the list of defined variables
--pre-bg-color
--pre-fg-color
--toggle-fg-color
--braces-fg-color
--brackets-fg-color
--dt-number-fg-color
--dt-bigint-fg-color
--dt-boolean-fg-color
--dt-string-fg-color
--dt-null-fg-color
--dt-undefined-fg-color
--prototype-label-fg-color
--dt-symbol-fg-color
--dt-regex-fg-color
--dt-date-fg-color
--dt-buffer-fg-color
--function-label-fg-color
--array-label-fg-color
--object-label-fg-color
--map-label-fg-color
--set-label-fg-color
--object-key-fg-color
--object-key-prefix-fg-color
--class-label-fg-color
--collpase-label-fg-color
--getter-label-fg-color
--circular-label-fg-color
--weakset-label-fg-color
--weakref-label-fg-color
--weakmap-label-fg-color
--observable-label-fg-color
--promise-label-fg-color
--generator-label-fg-color
--blob-label-fg-color
--unknown-label-fg-color
*/
cssVariables: {
pre: string;
toggle: string;
braces: string;
brackets: string;
number: string;
bigInt: string;
boolean: string;
string: string;
null: string;
undefined: string;
prototypeLabel: string;
symbol: string;
regex: string;
date: string;
buffer: string;
functionLabel: string;
arrayLabel: string;
objectLabel: string;
mapLabel: string;
setLabel: string;
objectKey: string;
objectKeyPrefix: string;
classLabel: string;
collapseLabel: string;
getterLabel: string;
circularLabel: string;
weakSetLabel: string;
weakRefLabel: string;
weakMapLabel: string;
observableLabel: string;
promiseLabel: string;
generatorLabel: string;
blobLabel: string;
unknownLabel: string;
};
};
+117
View File
@@ -0,0 +1,117 @@
import type { ParserConfig, TokensMap } from '../../src/types.js';
import type { HTMLFormatter } from './main.js';
/**
* CSS styles to use for pretty printing parser tokens. The values
* should be compatible with the HTML style attribute.
*
* @example
* ```ts
* {
* pre: "background-color: black; color: white;"
* }
* ```
*/
export type HTMLPrinterStyles = {
pre: string;
braces: string;
brackets: string;
toggle: string;
string: string;
number: string;
boolean: string;
bigInt: string;
undefined: string;
null: string;
symbol: string;
regex: string;
date: string;
buffer: string;
/**
* Styles for displaying the function value with
* its name
*/
functionLabel: string;
/**
* Styles for displaying the class value with
* its name
*/
classLabel: string;
/**
* Styles for the Object label. Use braces setting to define
* the braces color
*/
objectLabel: string;
/**
* Styles for the Object key name
*/
objectKey: string;
/**
* Styles for the Object key prefix name.
*/
objectKeyPrefix: string;
/**
* Styles for the Array label. Use brackets setting to define
* the brackets color
*/
arrayLabel: string;
/**
* Styles for the Map label. Use brackets setting to define
* the brackets color
*/
mapLabel: string;
/**
* Styles for the Set label. Use brackets setting to define
* the brackets color
*/
setLabel: string;
collapseLabel: string;
circularLabel: string;
getterLabel: string;
weakSetLabel: string;
weakRefLabel: string;
weakMapLabel: string;
observableLabel: string;
promiseLabel: string;
generatorLabel: string;
prototypeLabel: string;
/**
* Styles for displaying Blob keyword label. The blob
* properties like the type and size are styled as
* an object
*/
blobLabel: string;
/**
* Styles for displaying values not parsed by the
* parser as first-class citizen but still printed
* by casting them to String
*/
unknownLabel: string;
};
/**
* Printers collection that are used to print parser
* tokens to HTML output (one at a time)
*/
export type TokenPrinters = {
[K in keyof TokensMap]: (
/**
* Value of the token
*/
token: TokensMap[K],
/**
* Formatter reference
*/
formatter: HTMLFormatter) => string;
};
/**
* Config accepted by the HTML Formatter
*/
export type HTMLFormatterConfig = {
styles?: Partial<HTMLPrinterStyles>;
cspNonce?: string;
expand?: boolean | 'all';
};
/**
* Configuration accepted by the "dump" method exported
* by the html sub-module.
*/
export type HTMLDumpConfig = ParserConfig & HTMLFormatterConfig;
View File
+3
View File
@@ -0,0 +1,3 @@
export * as helpers from './src/helpers.js';
export { Parser } from './src/parser.js';
export { tokenizers } from './src/tokenizers/main.js';
+10
View File
@@ -0,0 +1,10 @@
import {
Parser,
helpers_exports,
tokenizers
} from "./chunk-26HALFTP.js";
export {
Parser,
helpers_exports as helpers,
tokenizers
};
+47
View File
@@ -0,0 +1,47 @@
import type { Parser } from './parser.js';
/**
* Helper to tokenize an object and its prototype
*/
export declare function tokenizeObject(value: Record<string | symbol, any>, parser: Parser, config: {
depth: number;
showHidden: boolean;
collapse: string[];
inspectObjectPrototype: boolean | 'unless-plain-object';
constructorName?: string;
membersToIgnore?: (string | symbol)[];
eagerGetters?: (string | symbol)[];
}): void;
/**
* Tokenizes the prototype of a value by calling Object.getPrototypeOf
* method on the value.
*/
export declare function tokenizePrototype(value: any, parser: Parser, config: {
prototypeToIgnore?: any;
membersToIgnore?: (string | symbol)[];
eagerGetters?: (string | symbol)[];
}): void;
/**
* Helper to tokenize array like values.
*/
export declare function tokenizeArray(values: Array<any> | Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array | BigInt64Array | BigUint64Array, parser: Parser, config: {
name?: string;
depth: number;
collapse: string[];
inspectArrayPrototype: boolean;
maxArrayLength: number;
}): void;
/**
* HTML escape string values so that they can be nested
* inside the pre-tags.
*/
export declare function htmlEscape(value: string): string;
/**
* Wraps a string value to be on multiple lines after
* a certain characters limit has been hit.
*/
export declare function wordWrap(value: string, options: {
width: number;
indent: string;
newLine: string;
escape?: (value: string) => string;
}): string;
+53
View File
@@ -0,0 +1,53 @@
import type { ParserConfig, Token } from './types.js';
/**
* Parser is used to parse a JavaScript value into a set
* of tokens that can be used to pretty-print the same
* value.
*
* @example
* ```ts
* import { Parser } from '@poppinss/dumper'
*
* const parser = new Parser()
* const value = {
* id: 1,
* username: 'foo',
* }
*
* parser.parse(value)
* parser.flush() // Token[]
* ```
*/
export declare class Parser {
#private;
/**
* Config shared with tokenizers
*/
config: Readonly<Required<ParserConfig>>;
/**
* Context maintained through out the parsing phase.
* Each instance of Parser has its own context
* that gets mutated internally.
*/
context: Record<string, any>;
constructor(config?: ParserConfig, context?: Record<string, any>);
/**
* Collect a token inside the list of tokens. The order
* of tokens matter during printing therefore you must
* collect tokens in the right order.
*/
collect(token: Token): void;
/**
* Parses a value using the tokenizers. Under the hood the
* tokenizers will call "parser.collect" to collect
* tokens inside an array.
*
* You can use "parser.flush" method to get the list of
* tokens.
*/
parse(value: unknown): void;
/**
* Returns collected tokens and resets the internal state.
*/
flush(): Token[];
}
+7
View File
@@ -0,0 +1,7 @@
import { type TypeName } from '@sindresorhus/is';
import type { Tokenizer } from '../types.js';
/**
* Tokenizers are responsible for converting JS data types
* to known dumper tokens.
*/
export declare const tokenizers: Partial<Record<TypeName, Tokenizer>>;
+270
View File
@@ -0,0 +1,270 @@
import type { TypeName } from '@sindresorhus/is';
import type { Parser } from './parser.js';
/**
* Simplifies the complex merged types
*/
export type Simplify<T> = {
[KeyType in keyof T]: T[KeyType];
} & {};
/**
* Representation of a tokenizer function. Tokenizers are
* responsible for converting values to a list of tokens
* and collect them using the "parser.collect" method.
*/
export type Tokenizer = (value: any, parser: Parser) => void;
/**
* Tokens Map represents the list of supported tokens. Tokens
* without any properties acts as a boundary for enclosed
* values.
*/
export type TokensMap = {
/**
* Object tokens
*/
'object-start': {
constructorName: null | string;
};
'object-end': {};
'object-key': {
isWritable: boolean;
isSymbol: boolean;
value: string;
};
'object-value-start': {};
'object-value-end': {};
'object-value-getter': {};
'object-circular-ref': {};
'object-max-depth-ref': {};
/**
* Array tokens
*/
'array-start': {
name: string;
size: number;
};
'array-end': {
size: number;
};
'array-value-start': {
index: number;
};
'array-value-hole': {
index: number;
};
'array-value-end': {
index: number;
};
'array-circular-ref': {};
'array-max-depth-ref': {};
'array-max-length-ref': {
limit: number;
size: number;
};
/**
* Boundary for the prototype properties
*/
'prototype-start': {};
'prototype-end': {};
/**
* Set tokens
*/
'set-start': {
size: number;
};
'set-end': {
size: number;
};
'set-value-start': {
index: number;
};
'set-value-end': {
index: number;
};
'set-circular-ref': {};
'set-max-depth-ref': {};
'set-max-length-ref': {
limit: number;
size: number;
};
/**
* Map tokens
*/
'map-start': {
size: number;
};
'map-end': {
size: number;
};
'map-row-start': {
index: number;
};
'map-row-end': {
index: number;
};
'map-key-start': {
index: number;
};
'map-key-end': {
index: number;
};
'map-value-start': {
index: number;
};
'map-value-end': {
index: number;
};
'map-max-depth-ref': {};
'map-circular-ref': {};
'map-max-length-ref': {
limit: number;
size: number;
};
/**
* Primitives
*/
'string': {
value: string;
};
'number': {
value: number;
};
'bigInt': {
value: string;
};
'boolean': {
value: boolean;
};
'undefined': {};
'null': {};
'symbol': {
value: string;
};
/**
* Others
*/
'function': {
name: string;
isGenerator: boolean;
isAsync: boolean;
isClass: boolean;
};
/**
* Boundary for the static members
*/
'static-members-start': {};
'static-members-end': {};
'date': {
value: string;
};
'buffer': {
value: string;
};
'regexp': {
value: string;
};
'unknown': {
jsType: TypeName;
value: any;
};
'weak-set': {};
'weak-ref': {};
'weak-map': {};
'observable': {};
'blob': {
size: number;
contentType: string;
};
'promise': {
isFulfilled: boolean;
};
'generator': {
isAsync: boolean;
};
/**
* Collapse token represents a value that has been
* collpased and its children are not further
* processed.
*
* Only objects and arrays can be collapsed
*/
'collapse': {
name: string;
token: ({
type: 'object-start';
} & TokensMap['object-start']) | ({
type: 'array-start';
} & TokensMap['array-start']);
};
};
/**
* A union of known tokens. The tokenizer will always
* output only these tokens
*/
export type Token = {
[K in keyof TokensMap]: Simplify<{
type: K;
} & TokensMap[K]>;
}[keyof TokensMap];
/**
* Configuration accepted by the parser
*/
export type ParserConfig = {
/**
* When set to true, the non-enumerable properties of an
* object will be processed.
*
* Defaults to false
*/
showHidden?: boolean;
/**
* The depth at which to stop parsing nested values. The depth
* is shared among all tree like data structures.
*
* Defaults to 5
*/
depth?: number;
/**
* Inspect prototype properties of an object. The non-enumerable properties
* of prototype are included by default.
*
* Defaults to "unless-plain-object"
*
* - The "unless-plain-object" will inspect the prototype of objects
* created with a prototype other than the "Object"
* - True will inspect the prototype for all objects that has a prototype
* - False will not inpsect the prototype
*/
inspectObjectPrototype?: boolean | 'unless-plain-object';
/**
* Inspect prototype properties of an array. The non-enumerable properties
* of prototype are included by default.
*
* Defaults to false
*/
inspectArrayPrototype?: boolean;
/**
* Inspect static members of a class. Even though functions and classes are
* technically same, this config only applies to functions defined using
* the [class] keyword.
*
* Defaults to false
*/
inspectStaticMembers?: boolean;
/**
* Maximum number of members to process for Arrays, Maps and Sets.
*
* Defaults to 100
*/
maxArrayLength?: number;
/**
* Maximum number of characters to display for a string.
*
* Defaults to 1000
*/
maxStringLength?: number;
/**
* An array of values that must be collapsed. The objects and
* arrays constructor names are checked against these values.
*/
collapse?: string[];
};
View File
+122
View File
@@ -0,0 +1,122 @@
{
"name": "@poppinss/dumper",
"version": "0.6.5",
"description": "Pretty print JavaScript data types in the terminal and the browser",
"main": "build/index.js",
"type": "module",
"files": [
"build",
"!build/bin",
"!build/examples",
"!build/tests"
],
"scripts": {
"pretest": "npm run lint",
"test": "npm run quick:test",
"lint": "eslint .",
"format": "prettier --write .",
"clean": "del-cli build",
"typecheck": "tsc --noEmit",
"precompile": "npm run lint && npm run clean",
"compile": "tsup-node && tsc --emitDeclarationOnly --declaration",
"build": "npm run compile",
"version": "npm run build",
"prepublishOnly": "npm run build",
"release": "release-it",
"quick:test": "node --import @poppinss/ts-exec --enable-source-maps bin/test.js"
},
"exports": {
".": "./build/index.js",
"./types": "./build/src/types.js",
"./html": "./build/formatters/html/main.js",
"./html/types": "./build/formatters/html/types.js",
"./console": "./build/formatters/console/main.js",
"./console/types": "./build/formatters/console/types.js"
},
"devDependencies": {
"@adonisjs/eslint-config": "^3.0.0-next.0",
"@adonisjs/prettier-config": "^1.4.5",
"@adonisjs/tsconfig": "^2.0.0-next.0",
"@japa/expect": "^3.0.4",
"@japa/runner": "^4.2.0",
"@japa/snapshot": "^2.0.8",
"@poppinss/ts-exec": "^1.4.0",
"@poppinss/utils": "^6.10.0",
"@release-it/conventional-changelog": "^10.0.1",
"@types/luxon": "^3.6.2",
"del-cli": "^6.0.0",
"eslint": "^9.30.1",
"luxon": "^3.6.1",
"prettier": "^3.6.2",
"release-it": "^19.0.3",
"rxjs": "^7.8.2",
"tsup": "^8.5.0",
"typescript": "^5.8.3"
},
"dependencies": {
"@poppinss/colors": "^4.1.5",
"@sindresorhus/is": "^7.0.2",
"supports-color": "^10.0.0"
},
"homepage": "https://github.com/poppinss/dumper#readme",
"bugs": {
"url": "https://github.com/poppinss/dumper/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/poppinss/dumper.git"
},
"keywords": [
"dumper",
"inspect",
"object-inspect",
"pretty"
],
"author": "Harminder Virk <virk@adonisjs.com>",
"license": "MIT",
"publishConfig": {
"provenance": true,
"access": "public"
},
"tsup": {
"entry": [
"index.ts",
"src/types.ts",
"formatters/html/main.ts",
"formatters/html/types.ts",
"formatters/console/main.ts",
"formatters/console/types.ts"
],
"outDir": "./build",
"clean": true,
"format": "esm",
"dts": false,
"sourcemap": false,
"target": "esnext"
},
"release-it": {
"git": {
"requireCleanWorkingDir": true,
"requireUpstream": true,
"commitMessage": "chore(release): ${version}",
"tagAnnotation": "v${version}",
"push": true,
"tagName": "v${version}"
},
"github": {
"release": true
},
"npm": {
"publish": true,
"skipChecks": true
},
"plugins": {
"@release-it/conventional-changelog": {
"preset": {
"name": "angular"
}
}
}
},
"prettier": "@adonisjs/prettier-config"
}
+9
View File
@@ -0,0 +1,9 @@
# The MIT License
Copyright (c) 2023
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+64
View File
@@ -0,0 +1,64 @@
# @poppinss/exception
> Create custom exceptions with error code, status, and the help description.
<br />
[![gh-workflow-image]][gh-workflow-url] [![npm-image]][npm-url] ![][typescript-image] [![license-image]][license-url]
## Introduction
The `@poppinss/exception` package provides with a base `Exception` class that can be used to create custom errors with support for defining the **error status**, **error code**, and **help description**.
```ts
import { Exception } from '@poppinss/exception'
class ResourceNotFound extends Exception {
static code = 'E_RESOURCE_NOT_FOUND'
static status = 404
static message = 'Unable to find resource'
}
throw new ResourceNotFound()
```
### Anonymous error classes
You can also create an anonymous exception class using the `createError` method. The return value is a class constructor that accepts an array of values to use for message interpolation.
The interpolation of error message is performed using the [`util.format`](https://nodejs.org/api/util.html#utilformatformat-args) method.
```ts
import { createError } from '@poppinss/exception'
const E_RESOURCE_NOT_FOUND = createError<[number]>(
'Unable to find resource with id %d',
'E_RESOURCE_NOT_FOUND'
)
const id = 1
throw new E_RESOURCE_NOT_FOUND([id])
```
## Contributing
One of the primary goals of poppinss is to have a vibrant community of users and contributors who believes in the principles of the framework.
We encourage you to read the [contribution guide](https://github.com/poppinss/.github/blob/main/docs/CONTRIBUTING.md) before contributing to the framework.
## Code of Conduct
In order to ensure that the poppinss community is welcoming to all, please review and abide by the [Code of Conduct](https://github.com/poppinss/.github/blob/main/docs/CODE_OF_CONDUCT.md).
## License
Poppinss exception is open-sourced software licensed under the [MIT license](LICENSE.md).
[gh-workflow-image]: https://img.shields.io/github/actions/workflow/status/poppinss/exception/checks.yml?style=for-the-badge
[gh-workflow-url]: https://github.com/poppinss/exception/actions/workflows/checks.yml 'Github action'
[typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript
[typescript-url]: "typescript"
[npm-image]: https://img.shields.io/npm/v/@poppinss/exception.svg?style=for-the-badge&logo=npm
[npm-url]: https://npmjs.org/package/@poppinss/exception 'npm'
[license-image]: https://img.shields.io/npm/l/@poppinss/exception?color=blueviolet&style=for-the-badge
[license-url]: LICENSE.md 'license'
+1
View File
@@ -0,0 +1 @@
export * from './src/exception.ts';
+44
View File
@@ -0,0 +1,44 @@
import { format } from "node:util";
var Exception = class extends Error {
name;
status;
constructor(message, options) {
super(message, options);
const ErrorConstructor = this.constructor;
this.name = ErrorConstructor.name;
this.message = message || ErrorConstructor.message || "";
this.status = options?.status || ErrorConstructor.status || 500;
const code = options?.code || ErrorConstructor.code;
if (code !== void 0) this.code = code;
const help = ErrorConstructor.help;
if (help !== void 0) this.help = help;
Error.captureStackTrace(this, ErrorConstructor);
}
get [Symbol.toStringTag]() {
return this.constructor.name;
}
toString() {
if (this.code) return `${this.name} [${this.code}]: ${this.message}`;
return `${this.name}: ${this.message}`;
}
};
var InvalidArgumentsException = class extends Exception {
static code = "E_INVALID_ARGUMENTS_EXCEPTION";
static status = 500;
};
var RuntimeException = class extends Exception {
static code = "E_RUNTIME_EXCEPTION";
static status = 500;
};
function createError(message, code, status) {
return class extends Exception {
static message = message;
static code = code;
static status = status;
constructor(args, options) {
super(format(message, ...args || []), options);
this.name = "Exception";
}
};
}
export { Exception, InvalidArgumentsException, RuntimeException, createError };
+76
View File
@@ -0,0 +1,76 @@
/**
* Create a custom error class with the ability to define the error
* code, status, and the help text.
*
* ```js
* export class FileNotFoundException extends Exception {
* static status = 500
* static code = 'E_FILE_NOT_FOUND'
* }
*
* throw new FileNotFoundException(
* `Unable to find file from ${filePath} location`
* )
* ```
*/
export declare class Exception extends Error {
/**
* Define the error metadata as static properties to avoid
* setting them repeatedly on the error instance
*/
static help?: string;
static code?: string;
static status?: number;
static message?: string;
/**
* Name of the class that raised the exception.
*/
name: string;
/**
* Optional help description for the error. You can use it to define additional
* human readable information for the error.
*/
help?: string;
/**
* A machine readable error code. This will allow the error handling logic
* to narrow down exceptions based upon the error code.
*/
code?: string;
/**
* A status code for the error. Usually helpful when converting errors
* to HTTP responses.
*/
status: number;
constructor(message?: string, options?: ErrorOptions & {
code?: string;
status?: number;
});
get [Symbol.toStringTag](): string;
toString(): string;
}
export declare class InvalidArgumentsException extends Exception {
static code: string;
static status: number;
}
export declare class RuntimeException extends Exception {
static code: string;
static status: number;
}
/**
* Helper to create an anonymous error class.
*
* ```ts
*
* const E_RESOURCE_NOT_FOUND = createError<[number]>(
* 'Unable to find resource with id %d',
* 'E_RESOURCE_NOT_FOUND'
* )
* const id = 1
* throw new E_RESOURCE_NOT_FOUND([id])
*```
*/
export declare function createError<T extends any[] = never>(message: string, code: string, status?: number): typeof Exception & T extends never ? {
new (args?: any, options?: ErrorOptions): Exception;
} : {
new (args: T, options?: ErrorOptions): Exception;
};
+109
View File
@@ -0,0 +1,109 @@
{
"name": "@poppinss/exception",
"description": "Utility to create custom exceptions",
"version": "1.2.3",
"type": "module",
"files": [
"build",
"!build/bin",
"!build/tests"
],
"main": "build/index.js",
"exports": {
".": "./build/index.js"
},
"scripts": {
"pretest": "npm run lint",
"test": "c8 npm run quick:test",
"lint": "eslint .",
"format": "prettier --write .",
"typecheck": "tsc --noEmit",
"precompile": "npm run lint",
"compile": "tsdown && tsc --emitDeclarationOnly --declaration",
"build": "npm run compile",
"version": "npm run build",
"prepublishOnly": "npm run build",
"release": "release-it",
"quick:test": "node --import=@poppinss/ts-exec --enable-source-maps bin/test.ts"
},
"devDependencies": {
"@adonisjs/eslint-config": "^3.0.0-next.5",
"@adonisjs/prettier-config": "^1.4.5",
"@adonisjs/tsconfig": "^2.0.0-next.3",
"@japa/expect": "^3.0.6",
"@japa/expect-type": "^2.0.3",
"@japa/runner": "^4.4.0",
"@poppinss/ts-exec": "^1.4.1",
"@release-it/conventional-changelog": "^10.0.3",
"@types/node": "^25.0.1",
"c8": "^10.1.3",
"eslint": "^9.39.1",
"prettier": "^3.7.4",
"release-it": "^19.1.0",
"tsdown": "^0.17.3",
"typescript": "^5.9.3"
},
"homepage": "https://github.com/poppinss/exception#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/poppinss/exception.git"
},
"bugs": {
"url": "https://github.com/poppinss/exception/issues"
},
"keywords": [],
"author": "Harminder Virk <virk@adonisjs.com>",
"license": "MIT",
"publishConfig": {
"access": "public",
"provenance": true
},
"tsdown": {
"entry": [
"index.ts"
],
"outDir": "./build",
"clean": true,
"format": "esm",
"minify": "dce-only",
"fixedExtension": false,
"dts": false,
"treeshake": false,
"sourcemaps": false,
"target": "esnext"
},
"release-it": {
"git": {
"requireCleanWorkingDir": true,
"requireUpstream": true,
"commitMessage": "chore(release): ${version}",
"tagAnnotation": "v${version}",
"push": true,
"tagName": "v${version}"
},
"github": {
"release": true
},
"npm": {
"publish": true,
"skipChecks": true
},
"plugins": {
"@release-it/conventional-changelog": {
"preset": {
"name": "angular"
}
}
}
},
"c8": {
"reporter": [
"text",
"html"
],
"exclude": [
"tests/**"
]
},
"prettier": "@adonisjs/prettier-config"
}