Browse Source

chore: initial commit

e22m4u 2 years ago
commit
0e4e1fded0
22 changed files with 1575 additions and 0 deletions
  1. 9 0
      .c8rc
  2. 5 0
      .commitlintrc
  3. 13 0
      .editorconfig
  4. 20 0
      .eslintrc.cjs
  5. 17 0
      .gitignore
  6. 4 0
      .husky/commit-msg
  7. 9 0
      .husky/pre-commit
  8. 7 0
      .mocharc.cjs
  9. 7 0
      .prettierrc
  10. 21 0
      LICENSE
  11. 122 0
      README.md
  12. 10 0
      mocha.setup.js
  13. 43 0
      package.json
  14. 20 0
      src/array-to-list.js
  15. 256 0
      src/array-to-list.spec.js
  16. 16 0
      src/errorf.js
  17. 17 0
      src/errorf.spec.js
  18. 53 0
      src/format.js
  19. 775 0
      src/format.spec.js
  20. 2 0
      src/index.js
  21. 16 0
      src/value-to-string.js
  22. 133 0
      src/value-to-string.spec.js

+ 9 - 0
.c8rc

@@ -0,0 +1,9 @@
+{
+  "all": true,
+  "include": [
+    "src/**/*.js"
+  ],
+  "exclude": [
+    "src/**/*.spec.js"
+  ]
+}

+ 5 - 0
.commitlintrc

@@ -0,0 +1,5 @@
+{
+  "extends": [
+    "@commitlint/config-conventional"
+  ]
+}

+ 13 - 0
.editorconfig

@@ -0,0 +1,13 @@
+# EditorConfig is awesome: https://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+# Unix-style newlines with a newline ending every file
+[*]
+end_of_line = lf
+insert_final_newline = true
+charset = utf-8
+indent_style = space
+indent_size = 2
+max_line_length = 80

+ 20 - 0
.eslintrc.cjs

@@ -0,0 +1,20 @@
+module.exports = {
+  env: {
+    es2021: true,
+    node: true
+  },
+  parserOptions: {
+    sourceType: 'module',
+    ecmaVersion: 13,
+  },
+  plugins: [
+    'mocha',
+    'chai-expect',
+  ],
+  extends: [
+    'eslint:recommended',
+    'prettier',
+    'plugin:mocha/recommended',
+    'plugin:chai-expect/recommended',
+  ],
+}

+ 17 - 0
.gitignore

@@ -0,0 +1,17 @@
+# OS
+.DS_Store
+
+# IDE
+.idea
+
+# Npm
+node_modules
+npm-debug.log
+package-lock.json
+
+# Yarn
+.yarn/
+.yarn*
+
+# c8
+coverage

+ 4 - 0
.husky/commit-msg

@@ -0,0 +1,4 @@
+#!/bin/sh
+. "$(dirname "$0")/_/husky.sh"
+
+npx --no -- commitlint --edit "${1}"

+ 9 - 0
.husky/pre-commit

@@ -0,0 +1,9 @@
+#!/usr/bin/env sh
+. "$(dirname -- "$0")/_/husky.sh"
+
+npm run lint:fix
+npm run format
+
+npm run test
+
+git add -A

+ 7 - 0
.mocharc.cjs

@@ -0,0 +1,7 @@
+const path = require('path');
+
+module.exports = {
+  extension: ['js'],
+  spec: 'src/**/*.spec.js',
+  require: [path.join(__dirname, 'mocha.setup.js')],
+}

+ 7 - 0
.prettierrc

@@ -0,0 +1,7 @@
+{
+  "bracketSpacing": false,
+  "singleQuote": true,
+  "printWidth": 80,
+  "trailingComma": "all",
+  "arrowParens": "avoid"
+}

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 e22m4u@gmail.com
+
+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.

+ 122 - 0
README.md

@@ -0,0 +1,122 @@
+## @e22m4u/format
+
+Расширенная версия `format` из Node.js модуля `util`
+
+- стандартные спецификаторы `%s`, `%d` и `%j`
+- добавлен `%v` для вывода простых значений или имени конструктора
+- добавлен `%l` для вывода списка через запятую `1, 2, 3`
+- поддержка работы в браузере
+
+дополнительно:
+- встроенный класс `Errorf` с интерполяцией сообщения об ошибке
+
+## Установка
+
+```bash
+npm install @e22m4u/format
+```
+
+## Примеры
+
+Es-импорт
+
+```js
+import {format} from '@e22m4u/format';
+```
+
+CommonJS
+
+```js
+const {format} = require('@e22m4u/format');
+```
+
+### Спецификатор `%v`
+
+Строки оборачиваются в кавычки, остальные примитивы приводятся
+к строке, а для более сложных типов выводится имя конструктора.
+
+```js
+format('> %v', 'foo');        // > "foo"
+format('> %v', '');           // > ""
+format('> %v', 10);           // > 10
+format('> %v', 0);            // > 0
+format('> %v', NaN);          // > NaN
+format('> %v', Infinity);     // > Infinity
+format('> %v', true);         // > true
+format('> %v', false);        // > false
+format('> %v', {foo: 'bar'}); // > Object
+format('> %v', new Date());   // > Date
+format('> %v', new Map());    // > Map
+format('> %v', () => 10);     // > Function
+format('> %v', undefined);    // > undefined
+format('> %v', null);         // > null
+```
+
+Спецификатор `%v` проектировался для вывода значений в сообщениях
+об ошибке, когда важно иметь представление об их типах. При этом,
+вывод содержимого объекта может быть избыточен для такой задачи.
+По этой причине, объекты приводятся к имени конструктора, что
+позволяет относительно точно определить тип выводимого значения.
+
+```js
+class MyClass {}
+
+format('> %v', new MyClass()); // > MyClass
+format('> %v', 'MyClass');     // > "MyClass"
+format('> %v', MyClass);       // > Function
+```
+
+### Спецификатор `%l`
+
+Вывод элементов массива через запятую.
+
+```js
+format('> %l', ['foo', 10, true]); // > "foo", 10, true
+```
+
+Элементы массива приводятся к строке по логике спецификатора `%v`
+
+```js
+format('> %l', ['foo']);        // > "foo"
+format('> %l', ['']);           // > ""
+format('> %l', [10]);           // > 10
+format('> %l', [0]);            // > 0
+format('> %l', [NaN]);          // > NaN
+format('> %l', [Infinity]);     // > Infinity
+format('> %l', [true]);         // > true
+format('> %l', [false]);        // > false
+format('> %l', [{foo: 'bar'}]); // > Object
+format('> %l', [new Date()]);   // > Date
+format('> %l', [new Map()]);    // > Map
+format('> %l', [() => 10]);     // > Function
+format('> %l', [undefined]);    // > undefined
+format('> %l', [null]);         // > null
+```
+
+## Errorf
+
+Конструктор класса `Errorf` передает аргументы функции `format`
+для формирования сообщения об ошибке.
+
+Пример:
+
+```js
+import {Errorf} from '@e22m4u/format';
+
+throw new Errorf(
+  'It requires one of %l, but %v given.',
+  [true, false, 'y', 'n'],
+  new Map(),
+);
+// Error: It requires one of true, false, "y", "n", but Map given.
+```
+
+## Тесты
+
+```bash
+npm run test
+```
+
+## Лицензия
+
+MIT

+ 10 - 0
mocha.setup.js

@@ -0,0 +1,10 @@
+import chai from 'chai';
+import chaiSpies from 'chai-spies';
+import chaiSubset from 'chai-subset';
+import chaiAsPromised from 'chai-as-promised';
+
+process.env['NODE_ENV'] = 'test';
+
+chai.use(chaiSpies);
+chai.use(chaiSubset);
+chai.use(chaiAsPromised);

+ 43 - 0
package.json

@@ -0,0 +1,43 @@
+{
+  "name": "@e22m4u/format",
+  "version": "0.0.1",
+  "description": "Расширенная версия format из Node.js модуля util",
+  "type": "module",
+  "main": "src/index.js",
+  "scripts": {
+    "lint": "eslint .",
+    "lint:fix": "eslint . --fix",
+    "format": "prettier --write \"./src/**/*.js\"",
+    "test": "eslint . && c8 --reporter=text-summary mocha",
+    "test:coverage": "eslint . && c8 --reporter=text mocha",
+    "prepare": "npx husky install"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/e22m4u/format.git"
+  },
+  "keywords": [
+    "format",
+    "printf",
+    "errorf"
+  ],
+  "author": "e22m4u <e22m4u@gmail.com>",
+  "license": "MIT",
+  "homepage": "https://github.com/e22m4u/format",
+  "devDependencies": {
+    "@commitlint/cli": "^17.7.1",
+    "@commitlint/config-conventional": "^17.7.0",
+    "c8": "^8.0.1",
+    "chai": "^4.3.7",
+    "chai-as-promised": "^7.1.1",
+    "chai-spies": "^1.0.0",
+    "chai-subset": "^1.6.0",
+    "eslint": "^8.47.0",
+    "eslint-config-prettier": "^9.0.0",
+    "eslint-plugin-chai-expect": "^3.0.0",
+    "eslint-plugin-mocha": "^10.1.0",
+    "husky": "^8.0.3",
+    "mocha": "^10.2.0",
+    "prettier": "^3.0.1"
+  }
+}

+ 20 - 0
src/array-to-list.js

@@ -0,0 +1,20 @@
+import {valueToString} from './value-to-string.js';
+
+/**
+ * Separator.
+ *
+ * @type {string}
+ */
+const SEPARATOR = ', ';
+
+/**
+ * Array to list.
+ *
+ * @param {any} input
+ * @return {string}
+ */
+export function arrayToList(input) {
+  if (Array.isArray(input) && input.length)
+    return input.map(valueToString).join(SEPARATOR);
+  return valueToString(input);
+}

+ 256 - 0
src/array-to-list.spec.js

@@ -0,0 +1,256 @@
+import {expect} from 'chai';
+import {arrayToList} from './array-to-list.js';
+
+describe('arrayToList', function () {
+  describe('non-array values', function () {
+    it('returns a string representation of the given string', function () {
+      const res = arrayToList('foo');
+      expect(res).to.be.eq('"foo"');
+    });
+
+    it('returns a string representation of the given empty string', function () {
+      const res = arrayToList('');
+      expect(res).to.be.eq('""');
+    });
+
+    it('returns a string representation of the given number', function () {
+      const res = arrayToList(10);
+      expect(res).to.be.eq('10');
+    });
+
+    it('returns a string representation of the given zero', function () {
+      const res = arrayToList(0);
+      expect(res).to.be.eq('0');
+    });
+
+    it('returns a string representation of the given NaN', function () {
+      const res = arrayToList(NaN);
+      expect(res).to.be.eq('NaN');
+    });
+
+    it('returns a string representation of the given Infinity', function () {
+      const res = arrayToList(Infinity);
+      expect(res).to.be.eq('Infinity');
+    });
+
+    it('returns a string representation of the given true', function () {
+      const res = arrayToList(true);
+      expect(res).to.be.eq('true');
+    });
+
+    it('returns a string representation of the given false', function () {
+      const res = arrayToList(false);
+      expect(res).to.be.eq('false');
+    });
+
+    it('returns a string representation of the given key-value object', function () {
+      const res = arrayToList({foo: 'bar'});
+      expect(res).to.be.eq('Object');
+    });
+
+    it('returns a string representation of the given object without keys', function () {
+      const res = arrayToList({});
+      expect(res).to.be.eq('Object');
+    });
+
+    it('returns a string representation of the given object without prototype', function () {
+      const res = arrayToList(Object.create(null));
+      expect(res).to.be.eq('Object');
+    });
+
+    it('returns a string representation of the given date instance', function () {
+      const res = arrayToList(new Date());
+      expect(res).to.be.eq('Date');
+    });
+
+    it('returns a string representation of the given map instance', function () {
+      const res = arrayToList(new Map());
+      expect(res).to.be.eq('Map');
+    });
+
+    it('returns a string representation of the given class instance', function () {
+      class MyClass {}
+      const res = arrayToList(new MyClass());
+      expect(res).to.be.eq('MyClass');
+    });
+
+    it('returns a string representation of the given function', function () {
+      const res = arrayToList(function () {});
+      expect(res).to.be.eq('Function');
+    });
+
+    it('returns a string representation of the given shorthand function', function () {
+      const res = arrayToList(() => undefined);
+      expect(res).to.be.eq('Function');
+    });
+
+    it('returns a string representation of the given named function', function () {
+      function foo() {}
+      const res = arrayToList(foo);
+      expect(res).to.be.eq('Function');
+    });
+
+    it('returns a string representation of the given class', function () {
+      class MyClass {}
+      const res = arrayToList(MyClass);
+      expect(res).to.be.eq('Function');
+    });
+
+    it('returns a string representation of the given class constructor', function () {
+      class MyClass {}
+      const res = arrayToList(MyClass.constructor);
+      expect(res).to.be.eq('Function');
+    });
+
+    it('returns a string representation of the given symbol', function () {
+      const res = arrayToList(Symbol());
+      expect(res).to.be.eq('Symbol');
+    });
+
+    it('returns a string representation of the given named symbol', function () {
+      const res = arrayToList(Symbol('foo'));
+      expect(res).to.be.eq('Symbol');
+    });
+
+    it('returns a string representation of the given undefined', function () {
+      const res = arrayToList(undefined);
+      expect(res).to.be.eq('undefined');
+    });
+
+    it('returns a string representation of the given null', function () {
+      const res = arrayToList(null);
+      expect(res).to.be.eq('null');
+    });
+  });
+
+  describe('array values', function () {
+    it('adds a separator between the given elements', function () {
+      const res = arrayToList(['foo', 1, true]);
+      expect(res).to.be.eq('"foo", 1, true');
+    });
+
+    it('returns a string representation of the given empty array', function () {
+      const res = arrayToList([]);
+      expect(res).to.be.eq('Array');
+    });
+
+    it('returns an element representation of the given string', function () {
+      const res = arrayToList(['foo']);
+      expect(res).to.be.eq('"foo"');
+    });
+
+    it('returns an element representation of the given empty string', function () {
+      const res = arrayToList(['']);
+      expect(res).to.be.eq('""');
+    });
+
+    it('returns an element representation of the given number', function () {
+      const res = arrayToList([10]);
+      expect(res).to.be.eq('10');
+    });
+
+    it('returns an element representation of the given zero', function () {
+      const res = arrayToList([0]);
+      expect(res).to.be.eq('0');
+    });
+
+    it('returns an element representation of the given NaN', function () {
+      const res = arrayToList([NaN]);
+      expect(res).to.be.eq('NaN');
+    });
+
+    it('returns an element representation of the given Infinity', function () {
+      const res = arrayToList([Infinity]);
+      expect(res).to.be.eq('Infinity');
+    });
+
+    it('returns an element representation of the given true', function () {
+      const res = arrayToList([true]);
+      expect(res).to.be.eq('true');
+    });
+
+    it('returns an element representation of the given false', function () {
+      const res = arrayToList([false]);
+      expect(res).to.be.eq('false');
+    });
+
+    it('returns an element representation of the given key-value object', function () {
+      const res = arrayToList([{foo: 'bar'}]);
+      expect(res).to.be.eq('Object');
+    });
+
+    it('returns an element representation of the given object without keys', function () {
+      const res = arrayToList([{}]);
+      expect(res).to.be.eq('Object');
+    });
+
+    it('returns an element representation of the given object without prototype', function () {
+      const res = arrayToList([Object.create(null)]);
+      expect(res).to.be.eq('Object');
+    });
+
+    it('returns an element representation of the given date instance', function () {
+      const res = arrayToList([new Date()]);
+      expect(res).to.be.eq('Date');
+    });
+
+    it('returns an element representation of the given map instance', function () {
+      const res = arrayToList([new Map()]);
+      expect(res).to.be.eq('Map');
+    });
+
+    it('returns an element representation of the given class instance', function () {
+      class MyClass {}
+      const res = arrayToList([new MyClass()]);
+      expect(res).to.be.eq('MyClass');
+    });
+
+    it('returns an element representation of the given function', function () {
+      const res = arrayToList([function () {}]);
+      expect(res).to.be.eq('Function');
+    });
+
+    it('returns an element representation of the given shorthand function', function () {
+      const res = arrayToList([() => undefined]);
+      expect(res).to.be.eq('Function');
+    });
+
+    it('returns an element representation of the given named function', function () {
+      function foo() {}
+      const res = arrayToList([foo]);
+      expect(res).to.be.eq('Function');
+    });
+
+    it('returns an element representation of the given class', function () {
+      class MyClass {}
+      const res = arrayToList([MyClass]);
+      expect(res).to.be.eq('Function');
+    });
+
+    it('returns an element representation of the given class constructor', function () {
+      class MyClass {}
+      const res = arrayToList([MyClass.constructor]);
+      expect(res).to.be.eq('Function');
+    });
+
+    it('returns an element representation of the given symbol', function () {
+      const res = arrayToList([Symbol()]);
+      expect(res).to.be.eq('Symbol');
+    });
+
+    it('returns an element representation of the given named symbol', function () {
+      const res = arrayToList([Symbol('foo')]);
+      expect(res).to.be.eq('Symbol');
+    });
+
+    it('returns an element representation of the given undefined', function () {
+      const res = arrayToList([undefined]);
+      expect(res).to.be.eq('undefined');
+    });
+
+    it('returns an element representation of the given null', function () {
+      const res = arrayToList([null]);
+      expect(res).to.be.eq('null');
+    });
+  });
+});

+ 16 - 0
src/errorf.js

@@ -0,0 +1,16 @@
+import {format} from './format.js';
+
+/**
+ * Errorf.
+ */
+export class Errorf extends Error {
+  /**
+   * Constructor.
+   *
+   * @param {string} pattern
+   * @param {any} args
+   */
+  constructor(pattern, ...args) {
+    super(format(pattern, ...args));
+  }
+}

+ 17 - 0
src/errorf.spec.js

@@ -0,0 +1,17 @@
+import {expect} from 'chai';
+import {Errorf} from './errorf.js';
+
+describe('Errorf', function () {
+  it('interpolates the given arguments', function () {
+    const throwable = () => {
+      throw new Errorf(
+        'It requires one of %l, but %v given.',
+        [true, false, 'y', 'n'],
+        new Map(),
+      );
+    };
+    expect(throwable).to.throw(
+      'It requires one of true, false, "y", "n", but Map given.',
+    );
+  });
+});

+ 53 - 0
src/format.js

@@ -0,0 +1,53 @@
+import {arrayToList} from './array-to-list.js';
+import {valueToString} from './value-to-string.js';
+
+/**
+ * Format - extended version of https://github.com/tmpfs/format-util
+ *
+ * native:
+ * s - string
+ * d - digits
+ * j - json
+ *
+ * extras:
+ * v - value (valueToString.js)
+ * l - list (arrayToList.js)
+ *
+ * @param {any} fmt
+ * @return {string}
+ */
+export function format(fmt) {
+  const re = /(%?)(%([sdjvl]))/g;
+  const args = Array.prototype.slice.call(arguments, 1);
+  if (args.length) {
+    fmt = fmt.replace(re, function (match, escaped, ptn, flag) {
+      let arg = args.shift();
+      switch (flag) {
+        // eslint-disable-next-line no-fallthrough
+        case 's':
+          arg = String(arg);
+          break;
+        case 'd':
+          arg = Number(arg);
+          break;
+        case 'j':
+          arg = JSON.stringify(arg);
+          break;
+        case 'v':
+          arg = valueToString(arg);
+          break;
+        case 'l':
+          arg = arrayToList(arg);
+          break;
+      }
+      if (!escaped) return arg;
+      args.unshift(arg);
+      return match;
+    });
+  }
+  // arguments remain after formatting
+  if (args.length) fmt += ' ' + args.join(' ');
+  // update escaped %% values
+  fmt = fmt.replace(/%{2,2}/g, '%');
+  return '' + fmt;
+}

+ 775 - 0
src/format.spec.js

@@ -0,0 +1,775 @@
+import {expect} from 'chai';
+import {format} from './format.js';
+
+describe('format', function () {
+  describe('%s', function () {
+    it('returns a string representation of the non-pattern string', function () {
+      const res = format('foo');
+      expect(res).to.be.eq('foo');
+    });
+
+    it('returns a string representation of the escaped pattern', function () {
+      const res = format('%%s', 'foo');
+      expect(res).to.be.eq('%s foo');
+    });
+
+    it('returns a string representation of the multiple strings', function () {
+      const res = format('%s:%s', 'foo', 'bar');
+      expect(res).to.be.eq('foo:bar');
+    });
+
+    it('returns a string representation of the given string', function () {
+      const res = format('%s', 'foo');
+      expect(res).to.be.eq('foo');
+    });
+
+    it('returns a string representation of the given empty string', function () {
+      const res = format('%s', '');
+      expect(res).to.be.eq('');
+    });
+
+    it('returns a string representation of the given number', function () {
+      const res = format('%s', 10);
+      expect(res).to.be.eq('10');
+    });
+
+    it('returns a string representation of the given zero', function () {
+      const res = format('%s', 0);
+      expect(res).to.be.eq('0');
+    });
+
+    it('returns a string representation of the given NaN', function () {
+      const res = format('%s', NaN);
+      expect(res).to.be.eq('NaN');
+    });
+
+    it('returns a string representation of the given Infinity', function () {
+      const res = format('%s', Infinity);
+      expect(res).to.be.eq('Infinity');
+    });
+
+    it('returns a string representation of the given true', function () {
+      const res = format('%s', true);
+      expect(res).to.be.eq('true');
+    });
+
+    it('returns a string representation of the given false', function () {
+      const res = format('%s', false);
+      expect(res).to.be.eq('false');
+    });
+
+    it('returns a string representation of the given array', function () {
+      const res = format('%s', [1, 2, 3]);
+      expect(res).to.be.eq('1,2,3');
+    });
+
+    it('returns a string representation of the given empty array', function () {
+      const res = format('%s', []);
+      expect(res).to.be.eq('');
+    });
+
+    it('returns a string representation of the given key-value object', function () {
+      const res = format('%s', {foo: 'bar'});
+      expect(res).to.be.eq('[object Object]');
+    });
+
+    it('returns a string representation of the given object without keys', function () {
+      const res = format('%s', {});
+      expect(res).to.be.eq('[object Object]');
+    });
+
+    it('returns a string representation of the given date instance', function () {
+      const date = new Date();
+      const res = format('%s', date);
+      expect(res).to.be.eq(date.toString());
+    });
+
+    it('returns a string representation of the given map instance', function () {
+      const res = format('%s', new Map());
+      expect(res).to.be.eq('[object Map]');
+    });
+
+    it('returns a string representation of the given class instance', function () {
+      class MyClass {}
+      const res = format('%s', new MyClass());
+      expect(res).to.be.eq('[object Object]');
+    });
+
+    it('returns a string representation of the given function', function () {
+      const res = format('%s', function () {});
+      expect(res).to.be.eq('function () {}');
+    });
+
+    it('returns a string representation of the given shorthand function', function () {
+      const res = format('%s', () => undefined);
+      expect(res).to.be.eq('() => undefined');
+    });
+
+    it('returns a string representation of the given named function', function () {
+      function foo() {}
+      const res = format('%s', foo);
+      expect(res).to.be.eq('function foo() {}');
+    });
+
+    it('returns a string representation of the given class', function () {
+      class MyClass {}
+      const res = format('%s', MyClass);
+      expect(res).to.be.eq('class MyClass {}');
+    });
+
+    it('returns a string representation of the given class constructor', function () {
+      class MyClass {}
+      const res = format('%s', MyClass.constructor);
+      expect(res).to.be.eq('function Function() { [native code] }');
+    });
+
+    it('returns a string representation of the given symbol', function () {
+      const res = format('%s', Symbol());
+      expect(res).to.be.eq('Symbol()');
+    });
+
+    it('returns a string representation of the given named symbol', function () {
+      const res = format('%s', Symbol('foo'));
+      expect(res).to.be.eq('Symbol(foo)');
+    });
+
+    it('returns a string representation of the given undefined', function () {
+      const res = format('%s', undefined);
+      expect(res).to.be.eq('undefined');
+    });
+
+    it('returns a string representation of the given null', function () {
+      const res = format('%s', null);
+      expect(res).to.be.eq('null');
+    });
+  });
+
+  describe('%d', function () {
+    it('returns a string representation of the given string', function () {
+      const res = format('%d', 'foo');
+      expect(res).to.be.eq('NaN');
+    });
+
+    it('returns a string representation of the given empty string', function () {
+      const res = format('%d', '');
+      expect(res).to.be.eq('0');
+    });
+
+    it('returns a string representation of the given number', function () {
+      const res = format('%d', 10);
+      expect(res).to.be.eq('10');
+    });
+
+    it('returns a string representation of the given zero', function () {
+      const res = format('%d', 0);
+      expect(res).to.be.eq('0');
+    });
+
+    it('returns a string representation of the given NaN', function () {
+      const res = format('%d', NaN);
+      expect(res).to.be.eq('NaN');
+    });
+
+    it('returns a string representation of the given Infinity', function () {
+      const res = format('%d', Infinity);
+      expect(res).to.be.eq('Infinity');
+    });
+
+    it('returns a string representation of the given true', function () {
+      const res = format('%d', true);
+      expect(res).to.be.eq('1');
+    });
+
+    it('returns a string representation of the given false', function () {
+      const res = format('%d', false);
+      expect(res).to.be.eq('0');
+    });
+
+    it('returns a string representation of the given array', function () {
+      const res = format('%d', [1, 2, 3]);
+      expect(res).to.be.eq('NaN');
+    });
+
+    it('returns a string representation of the given empty array', function () {
+      const res = format('%d', []);
+      expect(res).to.be.eq('0');
+    });
+
+    it('returns a string representation of the given key-value object', function () {
+      const res = format('%d', {foo: 'bar'});
+      expect(res).to.be.eq('NaN');
+    });
+
+    it('returns a string representation of the given object without keys', function () {
+      const res = format('%d', {});
+      expect(res).to.be.eq('NaN');
+    });
+
+    it('returns a string representation of the given date instance', function () {
+      const date = new Date();
+      const res = format('%d', date);
+      expect(res).to.be.eq(String(date.getTime()));
+    });
+
+    it('returns a string representation of the given map instance', function () {
+      const res = format('%d', new Map());
+      expect(res).to.be.eq('NaN');
+    });
+
+    it('returns a string representation of the given class instance', function () {
+      class MyClass {}
+      const res = format('%d', new MyClass());
+      expect(res).to.be.eq('NaN');
+    });
+
+    it('returns a string representation of the given function', function () {
+      const res = format('%d', function () {});
+      expect(res).to.be.eq('NaN');
+    });
+
+    it('returns a string representation of the given shorthand function', function () {
+      const res = format('%d', () => undefined);
+      expect(res).to.be.eq('NaN');
+    });
+
+    it('returns a string representation of the given named function', function () {
+      function foo() {}
+      const res = format('%d', foo);
+      expect(res).to.be.eq('NaN');
+    });
+
+    it('returns a string representation of the given class', function () {
+      class MyClass {}
+      const res = format('%d', MyClass);
+      expect(res).to.be.eq('NaN');
+    });
+
+    it('returns a string representation of the given class constructor', function () {
+      class MyClass {}
+      const res = format('%d', MyClass.constructor);
+      expect(res).to.be.eq('NaN');
+    });
+
+    it('returns a string representation of the given undefined', function () {
+      const res = format('%d', undefined);
+      expect(res).to.be.eq('NaN');
+    });
+
+    it('returns a string representation of the given null', function () {
+      const res = format('%d', null);
+      expect(res).to.be.eq('0');
+    });
+  });
+
+  describe('%j', function () {
+    it('returns a string representation of the given string', function () {
+      const res = format('%j', 'foo');
+      expect(res).to.be.eq('"foo"');
+    });
+
+    it('returns a string representation of the given empty string', function () {
+      const res = format('%j', '');
+      expect(res).to.be.eq('""');
+    });
+
+    it('returns a string representation of the given number', function () {
+      const res = format('%j', 10);
+      expect(res).to.be.eq('10');
+    });
+
+    it('returns a string representation of the given zero', function () {
+      const res = format('%j', 0);
+      expect(res).to.be.eq('0');
+    });
+
+    it('returns a string representation of the given NaN', function () {
+      const res = format('%j', NaN);
+      expect(res).to.be.eq('null');
+    });
+
+    it('returns a string representation of the given Infinity', function () {
+      const res = format('%j', Infinity);
+      expect(res).to.be.eq('null');
+    });
+
+    it('returns a string representation of the given true', function () {
+      const res = format('%j', true);
+      expect(res).to.be.eq('true');
+    });
+
+    it('returns a string representation of the given false', function () {
+      const res = format('%j', false);
+      expect(res).to.be.eq('false');
+    });
+
+    it('returns a string representation of the given array', function () {
+      const res = format('%j', [1, 2, 3]);
+      expect(res).to.be.eq('[1,2,3]');
+    });
+
+    it('returns a string representation of the given empty array', function () {
+      const res = format('%j', []);
+      expect(res).to.be.eq('[]');
+    });
+
+    it('returns a string representation of the given key-value object', function () {
+      const res = format('%j', {foo: 'bar'});
+      expect(res).to.be.eq('{"foo":"bar"}');
+    });
+
+    it('returns a string representation of the given object without keys', function () {
+      const res = format('%j', {});
+      expect(res).to.be.eq('{}');
+    });
+
+    it('returns a string representation of the given date instance', function () {
+      const date = new Date();
+      const res = format('%j', date);
+      expect(res).to.be.eq(`"${date.toISOString()}"`);
+    });
+
+    it('returns a string representation of the given map instance', function () {
+      const res = format('%j', new Map());
+      expect(res).to.be.eq('{}');
+    });
+
+    it('returns a string representation of the given class instance', function () {
+      class MyClass {}
+      const res = format('%j', new MyClass());
+      expect(res).to.be.eq('{}');
+    });
+
+    it('returns a string representation of the given function', function () {
+      const res = format('%j', function () {});
+      expect(res).to.be.eq('undefined');
+    });
+
+    it('returns a string representation of the given shorthand function', function () {
+      const res = format('%j', () => undefined);
+      expect(res).to.be.eq('undefined');
+    });
+
+    it('returns a string representation of the given named function', function () {
+      function foo() {}
+      const res = format('%j', foo);
+      expect(res).to.be.eq('undefined');
+    });
+
+    it('returns a string representation of the given class', function () {
+      class MyClass {}
+      const res = format('%j', MyClass);
+      expect(res).to.be.eq('undefined');
+    });
+
+    it('returns a string representation of the given class constructor', function () {
+      class MyClass {}
+      const res = format('%j', MyClass.constructor);
+      expect(res).to.be.eq('undefined');
+    });
+
+    it('returns a string representation of the given symbol', function () {
+      const res = format('%j', Symbol());
+      expect(res).to.be.eq('undefined');
+    });
+
+    it('returns a string representation of the given named symbol', function () {
+      const res = format('%j', Symbol('foo'));
+      expect(res).to.be.eq('undefined');
+    });
+
+    it('returns a string representation of the given undefined', function () {
+      const res = format('%j', undefined);
+      expect(res).to.be.eq('undefined');
+    });
+
+    it('returns a string representation of the given null', function () {
+      const res = format('%j', null);
+      expect(res).to.be.eq('null');
+    });
+  });
+
+  describe('%v', function () {
+    it('returns a string representation of the given string', function () {
+      const res = format('%v', 'foo');
+      expect(res).to.be.eq('"foo"');
+    });
+
+    it('returns a string representation of the given empty string', function () {
+      const res = format('%v', '');
+      expect(res).to.be.eq('""');
+    });
+
+    it('returns a string representation of the given number', function () {
+      const res = format('%v', 10);
+      expect(res).to.be.eq('10');
+    });
+
+    it('returns a string representation of the given zero', function () {
+      const res = format('%v', 0);
+      expect(res).to.be.eq('0');
+    });
+
+    it('returns a string representation of the given NaN', function () {
+      const res = format('%v', NaN);
+      expect(res).to.be.eq('NaN');
+    });
+
+    it('returns a string representation of the given Infinity', function () {
+      const res = format('%v', Infinity);
+      expect(res).to.be.eq('Infinity');
+    });
+
+    it('returns a string representation of the given true', function () {
+      const res = format('%v', true);
+      expect(res).to.be.eq('true');
+    });
+
+    it('returns a string representation of the given false', function () {
+      const res = format('%v', false);
+      expect(res).to.be.eq('false');
+    });
+
+    it('returns a string representation of the given array', function () {
+      const res = format('%v', [1, 2, 3]);
+      expect(res).to.be.eq('Array');
+    });
+
+    it('returns a string representation of the given empty array', function () {
+      const res = format('%v', []);
+      expect(res).to.be.eq('Array');
+    });
+
+    it('returns a string representation of the given key-value object', function () {
+      const res = format('%v', {foo: 'bar'});
+      expect(res).to.be.eq('Object');
+    });
+
+    it('returns a string representation of the given object without keys', function () {
+      const res = format('%v', {});
+      expect(res).to.be.eq('Object');
+    });
+
+    it('returns a string representation of the given object without prototype', function () {
+      const res = format('%v', Object.create(null));
+      expect(res).to.be.eq('Object');
+    });
+
+    it('returns a string representation of the given date instance', function () {
+      const res = format('%v', new Date());
+      expect(res).to.be.eq('Date');
+    });
+
+    it('returns a string representation of the given map instance', function () {
+      const res = format('%v', new Map());
+      expect(res).to.be.eq('Map');
+    });
+
+    it('returns a string representation of the given class instance', function () {
+      class MyClass {}
+      const res = format('%v', new MyClass());
+      expect(res).to.be.eq('MyClass');
+    });
+
+    it('returns a string representation of the given function', function () {
+      const res = format('%v', function () {});
+      expect(res).to.be.eq('Function');
+    });
+
+    it('returns a string representation of the given shorthand function', function () {
+      const res = format('%v', () => undefined);
+      expect(res).to.be.eq('Function');
+    });
+
+    it('returns a string representation of the given named function', function () {
+      function foo() {}
+      const res = format('%v', foo);
+      expect(res).to.be.eq('Function');
+    });
+
+    it('returns a string representation of the given class', function () {
+      class MyClass {}
+      const res = format('%v', MyClass);
+      expect(res).to.be.eq('Function');
+    });
+
+    it('returns a string representation of the given class constructor', function () {
+      class MyClass {}
+      const res = format('%v', MyClass.constructor);
+      expect(res).to.be.eq('Function');
+    });
+
+    it('returns a string representation of the given symbol', function () {
+      const res = format('%v', Symbol());
+      expect(res).to.be.eq('Symbol');
+    });
+
+    it('returns a string representation of the given named symbol', function () {
+      const res = format('%v', Symbol('foo'));
+      expect(res).to.be.eq('Symbol');
+    });
+
+    it('returns a string representation of the given undefined', function () {
+      const res = format('%v', undefined);
+      expect(res).to.be.eq('undefined');
+    });
+
+    it('returns a string representation of the given null', function () {
+      const res = format('%v', null);
+      expect(res).to.be.eq('null');
+    });
+  });
+
+  describe('%l', function () {
+    describe('non-array values', function () {
+      it('returns a string representation of the given string', function () {
+        const res = format('%l', 'foo');
+        expect(res).to.be.eq('"foo"');
+      });
+
+      it('returns a string representation of the given empty string', function () {
+        const res = format('%l', '');
+        expect(res).to.be.eq('""');
+      });
+
+      it('returns a string representation of the given number', function () {
+        const res = format('%l', 10);
+        expect(res).to.be.eq('10');
+      });
+
+      it('returns a string representation of the given zero', function () {
+        const res = format('%l', 0);
+        expect(res).to.be.eq('0');
+      });
+
+      it('returns a string representation of the given NaN', function () {
+        const res = format('%l', NaN);
+        expect(res).to.be.eq('NaN');
+      });
+
+      it('returns a string representation of the given Infinity', function () {
+        const res = format('%l', Infinity);
+        expect(res).to.be.eq('Infinity');
+      });
+
+      it('returns a string representation of the given true', function () {
+        const res = format('%l', true);
+        expect(res).to.be.eq('true');
+      });
+
+      it('returns a string representation of the given false', function () {
+        const res = format('%l', false);
+        expect(res).to.be.eq('false');
+      });
+
+      it('returns a string representation of the given key-value object', function () {
+        const res = format('%l', {foo: 'bar'});
+        expect(res).to.be.eq('Object');
+      });
+
+      it('returns a string representation of the given object without keys', function () {
+        const res = format('%l', {});
+        expect(res).to.be.eq('Object');
+      });
+
+      it('returns a string representation of the given object without prototype', function () {
+        const res = format('%l', Object.create(null));
+        expect(res).to.be.eq('Object');
+      });
+
+      it('returns a string representation of the given date instance', function () {
+        const res = format('%l', new Date());
+        expect(res).to.be.eq('Date');
+      });
+
+      it('returns a string representation of the given map instance', function () {
+        const res = format('%l', new Map());
+        expect(res).to.be.eq('Map');
+      });
+
+      it('returns a string representation of the given class instance', function () {
+        class MyClass {}
+        const res = format('%l', new MyClass());
+        expect(res).to.be.eq('MyClass');
+      });
+
+      it('returns a string representation of the given function', function () {
+        const res = format('%l', function () {});
+        expect(res).to.be.eq('Function');
+      });
+
+      it('returns a string representation of the given shorthand function', function () {
+        const res = format('%l', () => undefined);
+        expect(res).to.be.eq('Function');
+      });
+
+      it('returns a string representation of the given named function', function () {
+        function foo() {}
+        const res = format('%l', foo);
+        expect(res).to.be.eq('Function');
+      });
+
+      it('returns a string representation of the given class', function () {
+        class MyClass {}
+        const res = format('%l', MyClass);
+        expect(res).to.be.eq('Function');
+      });
+
+      it('returns a string representation of the given class constructor', function () {
+        class MyClass {}
+        const res = format('%l', MyClass.constructor);
+        expect(res).to.be.eq('Function');
+      });
+
+      it('returns a string representation of the given symbol', function () {
+        const res = format('%l', Symbol());
+        expect(res).to.be.eq('Symbol');
+      });
+
+      it('returns a string representation of the given named symbol', function () {
+        const res = format('%l', Symbol('foo'));
+        expect(res).to.be.eq('Symbol');
+      });
+
+      it('returns a string representation of the given undefined', function () {
+        const res = format('%l', undefined);
+        expect(res).to.be.eq('undefined');
+      });
+
+      it('returns a string representation of the given null', function () {
+        const res = format('%l', null);
+        expect(res).to.be.eq('null');
+      });
+    });
+
+    describe('array values', function () {
+      it('adds a separator between the given elements', function () {
+        const res = format('%l', ['foo', 1, true]);
+        expect(res).to.be.eq('"foo", 1, true');
+      });
+
+      it('returns a string representation of the given empty array', function () {
+        const res = format('%l', []);
+        expect(res).to.be.eq('Array');
+      });
+
+      it('returns an element representation of the given string', function () {
+        const res = format('%l', ['foo']);
+        expect(res).to.be.eq('"foo"');
+      });
+
+      it('returns an element representation of the given empty string', function () {
+        const res = format('%l', ['']);
+        expect(res).to.be.eq('""');
+      });
+
+      it('returns an element representation of the given number', function () {
+        const res = format('%l', [10]);
+        expect(res).to.be.eq('10');
+      });
+
+      it('returns an element representation of the given zero', function () {
+        const res = format('%l', [0]);
+        expect(res).to.be.eq('0');
+      });
+
+      it('returns an element representation of the given NaN', function () {
+        const res = format('%l', [NaN]);
+        expect(res).to.be.eq('NaN');
+      });
+
+      it('returns an element representation of the given Infinity', function () {
+        const res = format('%l', [Infinity]);
+        expect(res).to.be.eq('Infinity');
+      });
+
+      it('returns an element representation of the given true', function () {
+        const res = format('%l', [true]);
+        expect(res).to.be.eq('true');
+      });
+
+      it('returns an element representation of the given false', function () {
+        const res = format('%l', [false]);
+        expect(res).to.be.eq('false');
+      });
+
+      it('returns an element representation of the given key-value object', function () {
+        const res = format('%l', [{foo: 'bar'}]);
+        expect(res).to.be.eq('Object');
+      });
+
+      it('returns an element representation of the given object without keys', function () {
+        const res = format('%l', [{}]);
+        expect(res).to.be.eq('Object');
+      });
+
+      it('returns an element representation of the given object without prototype', function () {
+        const res = format('%l', [Object.create(null)]);
+        expect(res).to.be.eq('Object');
+      });
+
+      it('returns an element representation of the given date instance', function () {
+        const res = format('%l', [new Date()]);
+        expect(res).to.be.eq('Date');
+      });
+
+      it('returns an element representation of the given map instance', function () {
+        const res = format('%l', [new Map()]);
+        expect(res).to.be.eq('Map');
+      });
+
+      it('returns an element representation of the given class instance', function () {
+        class MyClass {}
+        const res = format('%l', [new MyClass()]);
+        expect(res).to.be.eq('MyClass');
+      });
+
+      it('returns an element representation of the given function', function () {
+        const res = format('%l', [function () {}]);
+        expect(res).to.be.eq('Function');
+      });
+
+      it('returns an element representation of the given shorthand function', function () {
+        const res = format('%l', [() => undefined]);
+        expect(res).to.be.eq('Function');
+      });
+
+      it('returns an element representation of the given named function', function () {
+        function foo() {}
+        const res = format('%l', [foo]);
+        expect(res).to.be.eq('Function');
+      });
+
+      it('returns an element representation of the given class', function () {
+        class MyClass {}
+        const res = format('%l', [MyClass]);
+        expect(res).to.be.eq('Function');
+      });
+
+      it('returns an element representation of the given class constructor', function () {
+        class MyClass {}
+        const res = format('%l', [MyClass.constructor]);
+        expect(res).to.be.eq('Function');
+      });
+
+      it('returns an element representation of the given symbol', function () {
+        const res = format('%l', [Symbol()]);
+        expect(res).to.be.eq('Symbol');
+      });
+
+      it('returns an element representation of the given named symbol', function () {
+        const res = format('%l', [Symbol('foo')]);
+        expect(res).to.be.eq('Symbol');
+      });
+
+      it('returns an element representation of the given undefined', function () {
+        const res = format('%l', [undefined]);
+        expect(res).to.be.eq('undefined');
+      });
+
+      it('returns an element representation of the given null', function () {
+        const res = format('%l', [null]);
+        expect(res).to.be.eq('null');
+      });
+    });
+  });
+});

+ 2 - 0
src/index.js

@@ -0,0 +1,2 @@
+export * from './format.js';
+export * from './errorf.js';

+ 16 - 0
src/value-to-string.js

@@ -0,0 +1,16 @@
+/**
+ * Value to string.
+ *
+ * @param {any} input
+ * @return {string}
+ */
+export function valueToString(input) {
+  if (input == null) return String(input);
+  if (typeof input === 'string') return `"${input}"`;
+  if (typeof input === 'number' || typeof input === 'boolean')
+    return String(input);
+  if (input.constructor && input.constructor.name)
+    return input.constructor.name;
+  if (typeof input === 'object' && input.constructor == null) return 'Object';
+  return String(input);
+}

+ 133 - 0
src/value-to-string.spec.js

@@ -0,0 +1,133 @@
+import {expect} from 'chai';
+import {valueToString} from './value-to-string.js';
+
+describe('valueToString', function () {
+  it('returns a string representation of the given string', function () {
+    const res = valueToString('foo');
+    expect(res).to.be.eq('"foo"');
+  });
+
+  it('returns a string representation of the given empty string', function () {
+    const res = valueToString('');
+    expect(res).to.be.eq('""');
+  });
+
+  it('returns a string representation of the given number', function () {
+    const res = valueToString(10);
+    expect(res).to.be.eq('10');
+  });
+
+  it('returns a string representation of the given zero', function () {
+    const res = valueToString(0);
+    expect(res).to.be.eq('0');
+  });
+
+  it('returns a string representation of the given NaN', function () {
+    const res = valueToString(NaN);
+    expect(res).to.be.eq('NaN');
+  });
+
+  it('returns a string representation of the given Infinity', function () {
+    const res = valueToString(Infinity);
+    expect(res).to.be.eq('Infinity');
+  });
+
+  it('returns a string representation of the given true', function () {
+    const res = valueToString(true);
+    expect(res).to.be.eq('true');
+  });
+
+  it('returns a string representation of the given false', function () {
+    const res = valueToString(false);
+    expect(res).to.be.eq('false');
+  });
+
+  it('returns a string representation of the given array', function () {
+    const res = valueToString([1, 2, 3]);
+    expect(res).to.be.eq('Array');
+  });
+
+  it('returns a string representation of the given empty array', function () {
+    const res = valueToString([]);
+    expect(res).to.be.eq('Array');
+  });
+
+  it('returns a string representation of the given key-value object', function () {
+    const res = valueToString({foo: 'bar'});
+    expect(res).to.be.eq('Object');
+  });
+
+  it('returns a string representation of the given object without keys', function () {
+    const res = valueToString({});
+    expect(res).to.be.eq('Object');
+  });
+
+  it('returns a string representation of the given object without prototype', function () {
+    const res = valueToString(Object.create(null));
+    expect(res).to.be.eq('Object');
+  });
+
+  it('returns a string representation of the given date instance', function () {
+    const res = valueToString(new Date());
+    expect(res).to.be.eq('Date');
+  });
+
+  it('returns a string representation of the given map instance', function () {
+    const res = valueToString(new Map());
+    expect(res).to.be.eq('Map');
+  });
+
+  it('returns a string representation of the given class instance', function () {
+    class MyClass {}
+    const res = valueToString(new MyClass());
+    expect(res).to.be.eq('MyClass');
+  });
+
+  it('returns a string representation of the given function', function () {
+    const res = valueToString(function () {});
+    expect(res).to.be.eq('Function');
+  });
+
+  it('returns a string representation of the given shorthand function', function () {
+    const res = valueToString(() => undefined);
+    expect(res).to.be.eq('Function');
+  });
+
+  it('returns a string representation of the given named function', function () {
+    function foo() {}
+    const res = valueToString(foo);
+    expect(res).to.be.eq('Function');
+  });
+
+  it('returns a string representation of the given class', function () {
+    class MyClass {}
+    const res = valueToString(MyClass);
+    expect(res).to.be.eq('Function');
+  });
+
+  it('returns a string representation of the given class constructor', function () {
+    class MyClass {}
+    const res = valueToString(MyClass.constructor);
+    expect(res).to.be.eq('Function');
+  });
+
+  it('returns a string representation of the given symbol', function () {
+    const res = valueToString(Symbol());
+    expect(res).to.be.eq('Symbol');
+  });
+
+  it('returns a string representation of the given named symbol', function () {
+    const res = valueToString(Symbol('foo'));
+    expect(res).to.be.eq('Symbol');
+  });
+
+  it('returns a string representation of the given undefined', function () {
+    const res = valueToString(undefined);
+    expect(res).to.be.eq('undefined');
+  });
+
+  it('returns a string representation of the given null', function () {
+    const res = valueToString(null);
+    expect(res).to.be.eq('null');
+  });
+});