npm package: @hexo-testing-monorepo/hexo-util
Description: Utilities for Hexo.
Author: Tommy Chen <tommy351@gmail.com> (http://zespia.tw)
Latest version: 2.6.1

# hexo-util

[![Build Status](https://github.com/hexojs/hexo-util/workflows/Tester/badge.svg?branch=master)](https://github.com/hexojs/hexo-util/actions?query=workflow%3ATester)
[![NPM version](https://badge.fury.io/js/hexo-util.svg)](https://www.npmjs.com/package/hexo-util)
[![Coverage Status](https://coveralls.io/repos/hexojs/hexo-util/badge.svg?branch=master&service=github)](https://coveralls.io/github/hexojs/hexo-util?branch=master)
[![dependencies Status](https://david-dm.org/hexojs/hexo-util/status.svg)](https://david-dm.org/hexojs/hexo-util)
[![devDependencies Status](https://david-dm.org/hexojs/hexo-util/dev-status.svg)](https://david-dm.org/hexojs/hexo-util?type=dev)

Utilities for [Hexo].

## Table of contents

- [Installation](#installation)
- [Usage](#usage)
- [Cache](#cache)
- [CacheStream](#cachestream)
- [camelCaseKeys](#camelcasekeysobj-options)
- [createSha1Hash](#createsha1hash)
- [decodeURL](#decodeurlstr)
- [deepMerge](#deepmergetarget-source)
- [encodeURL](#encodeurlstr)
- [escapeDiacritic](#escapediacriticstr)
- [escapeHTML](#escapehtmlstr)
- [escapeRegex](#escaperegexstr)
- [full_url_for](#full_url_forpath)
- [gravatar](#gravatarstr-options)
- [hash](#hashstr)
- [highlight](#highlightstr-options)
- [htmlTag](#htmltagtag-attrs-text-escape)
- [isExternalLink](#isexternallinkurl-sitehost-exclude)
- [Pattern](#patternrule)
- [Permalink](#permalinkrule-options)
- [prettyUrls](#prettyurlsurl-options)
- [prismHighlight](#prismhighlightstr-options)
- [relative_url](#relative_urlfrom-to)
- [slugize](#slugizestr-options)
- [spawn](#spawncommand-args-options)
- [stripHTML](#striphtmlstr)
- [wordWrap](#wordwrapstr-options)
- [tocObj](#tocobjstr-options)
- [truncate](#truncatestr-options)
- [unescapeHTML](#unescapehtmlstr)
- [url_for](#url_forpath-option)
- [bind(hexo)](#bindhexo)

## Installation

``` bash
$ npm install hexo-util --save
```

## Usage

``` js
var util = require('hexo-util');
```

### Cache()

A simple plain object cache

``` js
const cache = new Cache();

// set(key, value)
cache.set('foo', 'bar');

// get(key) => value
cache.get('foo');
// 'bar'

// has(key) => Boolean
cache.has('foo');
// true
cache.has('bar');
// false

// apply(key. value)
cache.apply('baz', () => 123);
// 123
cache.apply('baz', () => 456);
// 123
cache.apply('qux', 456);
// 456
cache.apply('qux', '789');
// 456

// size()
cache.size();
// 3

// dump()
cache.dump();
/*
{
  foo: 'bar',
  baz: 123,
  qux: 456
}
*/

// del(key)
cache.del('baz');
cache.has('baz');
// false

// flush()
cache.flush();
cache.has('foo');
// false
cache.size();
// 0
```

### CacheStream()

Caches contents piped to the stream.

``` js
var stream = new CacheStream();

fs.createReadStream('/path/to/file').pipe(stream);

stream.on('finish', function(){
  // Read cache piped to the stream
  console.log(stream.getCache());

  // Destroy cache
  stream.destroy();
});
```

### camelCaseKeys(obj, options)

Convert object keys to camelCase. Original keys will be converted to getter/setter and sync to the camelCase keys.

``` js
camelCaseKeys({
  foo_bar: 'test'
});
// { fooBar: 'test', foo_bar: 'test' }
```

### createSha1Hash()
return SHA1 hash object.
 This is the same as calling `createHash('utf8')` in the node.js native module crypto.
 ``` js
const sha1 = createSha1Hash();
 fs.createReadStream('/path/to/file')
  .pipe(sha1)
  .on('finish', () => {
    console.log(sha1.read());
  });
```

### decodeURL(str)

Decode [encoded](https://en.wikipedia.org/wiki/Percent-encoding) URL or path. An alternative to the native [`decodeURI()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURI) function, with added ability to decode [punycoded](https://en.wikipedia.org/wiki/Punycode) domain.

``` js
decodeURL('http://foo.com/b%C3%A1r')
// http://foo.com/bár

decodeURL('http://xn--br-mia.com/baz')
// http://bár.com/baz

decodeURL('/foo/b%C3%A1r/')
// /foo/bár/

/* Alternatively, Node 10+ offers native API to decode punycoded domain */
const {format} = require('url')
decodeURI(format(new URL('http://xn--br-mia.com.com/b%C3%A1r'), {unicode: true}))
// http://bár.com/báz
```

### deepMerge(target, source)

Merges the enumerable properties of two objects deeply. `target` and `source` remain untouched.

``` js
// Merge deeply
const obj1 = {a: {b: 1, c: 1, d: {e: 1, f: 1}}};
const obj2 = {a: {b: 2, d: {f: 'f'} }};

deepMerge(obj1, obj2);
// {a: {b: 2, c: 1, d: {e: 1, f: 'f'} }}
```

``` js
// Arrays will be combined in the same property, similar to lodash.merge
const obj1 = { 'a': [{ 'b': 2 }, { 'd': 4 }] };
const obj2 = { 'a': [{ 'c': 3 }, { 'e': 5 }] };

deepMerge(obj1, obj2);
// { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] };
```

### encodeURL(str)

Encode URL or path into a [safe format](https://en.wikipedia.org/wiki/Percent-encoding).

``` js
encodeURL('http://foo.com/bár')
// http://foo.com/b%C3%A1r

encodeURL('/foo/bár/')
// /foo/b%C3%A1r/
```

### escapeDiacritic(str)

Escapes diacritic characters in a string.

### escapeHTML(str)

Escapes HTML entities in a string.

``` js
escapeHTML('<p>Hello "world".</p>')
// &lt;p&gt;Hello &quot;world&quot;.&lt;&#x2F;p&gt;

/* support escaped characters */
escapeHTML('&lt;foo>bar</foo&gt;')
// &lt;foo&gt;bar&lt;&#x2F;foo&gt;
```

### escapeRegex(str)

Escapes special characters in a regular expression.

### full_url_for(path)

Returns a url with the config.url prefixed. Output is [encoded](#encodeurlstr) automatically. Requires [`bind(hexo)`](#bindhexo).

``` yml
_config.yml
url: https://example.com/blog # example
```

``` js
full_url_for('/a/path')
// https://example.com/blog/a/path
```

### gravatar(str, [options])

Returns the gravatar image url from an email.

If you didn't specify the [options] parameter, the default options will apply. Otherwise, you can set it to a number which will then be passed on as the size parameter to Gravatar. Finally, if you set it to an object, it will be converted into a query string of parameters for Gravatar.

Option | Description | Default
--- | --- | ---
`s` | Output image size | 80
`d` | Default image |
`f` | Force default |
`r` | Rating |

More info: [Gravatar](https://en.gravatar.com/site/implement/images/)

``` js
gravatar('a@abc.com')
// https://www.gravatar.com/avatar/b9b00e66c6b8a70f88c73cb6bdb06787
gravatar('a@abc.com', 40)
// https://www.gravatar.com/avatar/b9b00e66c6b8a70f88c73cb6bdb06787?s=40
gravatar('a@abc.com' {s: 40, d: 'https://via.placeholder.com/150'})
// https://www.gravatar.com/avatar/b9b00e66c6b8a70f88c73cb6bdb06787?s=40&d=https%3A%2F%2Fvia.placeholder.com%2F150
```

### hash(str)

Generates SHA1 hash.

``` js
hash('123456');
// <Buffer 7c 4a 8d 09 ca 37 62 af 61 e5 95 20 94 3d c2 64 94 f8 94 1b>
```

### highlight(str, [options])

Syntax highlighting for a code block.

Option | Description | Default
--- | --- | ---
`gutter` | Whether to show line numbers | true
`wrap` | Whether to wrap the code block in [`<table>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table) | true
`firstLine` | First line number | 1
`hljs` | Whether to use the `hljs-*` prefix for CSS classes | false
`lang` | Language |
`caption` | Caption |
`tab`| Replace tabs |
`autoDetect` | Detect language automatically (warning: slow)<br>_Sublanguage highlight requires `autoDetect` to be enabled and `lang` to be unset_  | false
`mark` | Line highlight specific line(s) |

### htmlTag(tag, attrs, text, escape)

Creates a html tag.

Option | Description | Default
--- | --- | ---
`tag` | Tag / element name |
`attrs` | Attribute(s) and its value.<br>Value is always [escaped](#escapehtmlstr), URL is always [encoded](#encodeurlstr). |
`text` | Text, the value is always escaped<br>_(except for `<style>` tag)_ |
`escape` | Whether to escape the text | true

``` js
htmlTag('img', {src: 'example.png'})
// <img src="example.png">

htmlTag('a', {href: 'http://hexo.io/'}, 'Hexo')
// <a href="http://hexo.io/">Hexo</a>

htmlTag('link', {href: 'http://foo.com/'}, '<a>bar</a>')
// <a href="http://foo.com/">&lt;bar&gt;</a>

htmlTag('a', {href: 'http://foo.com/'}, '<b>bold</b>', false)
// <a href="http://foo.com/"><b>bold</b></a>

/* text value of <style> won't be escaped, url is still encoded */
htmlTag('style', {}, 'p { content: "<"; background: url("bár.jpg"); }')
// <style>p { content: "<"; background: url("b%C3%A1r.jpg"); }</style>

/* support script tag with async/defer */
htmlTag('script', {src: '/foo.js', async: true}, '')
// <script src="/foo.js" async></script>
```

### isExternalLink(url, sitehost, [exclude])

Option | Description | Default
--- | --- | ---
`url` | The input URL. |
`sitehost` | The hostname / url of website. You can also pass `hexo.config.url`. |
`exclude` | Exclude hostnames. Specific subdomain is required when applicable, including www. | `[]`

Returns if a given url is external link relative to given `sitehost` and `[exclude]`.

``` js
// 'sitehost' can be a domain or url
isExternalLink('https://example.com', 'example.com');
// false
isExternalLink('https://example.com', 'https://example.com');
// false
isExternalLink('https://example.com', '//example.com/blog/');
// false
```

``` js
isExternalLink('/archives/foo.html', 'example.com');
// false
isExternalLink('https://foo.com/', 'example.com');
// true
```

``` js
isExternalLink('https://foo.com', 'example.com', ['foo.com', 'bar.com']);
// false
isExternalLink('https://bar.com', 'example.com', ['foo.com', 'bar.com']);
// false
isExternalLink('https://baz.com/', 'example.com', ['foo.com', 'bar.com']);
// true
```


### Pattern(rule)

Parses the string and tests if the string matches the rule. `rule` can be a string, a regular expression or a function.

``` js
var pattern = new Pattern('posts/:id');

pattern.match('posts/89');
// {0: 'posts/89', 1: '89', id: '89'}
```

``` js
var pattern = new Pattern('posts/*path');

pattern.match('posts/2013/hello-world');
// {0: 'posts/2013/hello-world', 1: '2013/hello-world', path: '2013/hello-world'}
```

### Permalink(rule, [options])

Parses a permalink.

Option | Description
--- | ---
`segments` | Customize the rule of a segment in the permalink

``` js
var permalink = new Permalink(':year/:month/:day/:title', {
  segments: {
    year: /(\d{4})/,
    month: /(\d{2})/,
    day: /(\d{2})/
  }
});

permalink.parse('2014/01/31/test');
// {year: '2014', month: '01', day: '31', title: 'test'}

permalink.test('2014/01/31/test');
// true

permalink.stringify({year: '2014', month: '01', day: '31', title: 'test'})
// 2014/01/31/test
```

### prettyUrls(url, [options])

Rewrite urls to pretty URLs.

Option | Description | Default
--- | --- | ---
`trailing_index` | `/about/index.html -> /about/` when `false` | `true`
`trailing_html` | `/about.html -> /about` when `false` | `true`

Note: `trailing_html` ignores any link with a trailing `index.html`. (will not be rewritten to `index`).

``` js
prettyUrls('/foo/bar.html');
// /foo/bar.html
prettyUrls('/foo/bar/index.html');
// /foo/bar/index.html

prettyUrls('/foo/bar.html', { trailing_index: false });
// /foo/bar.html
prettyUrls('/foo/bar/index.html', { trailing_index: false });
// /foo/bar/

prettyUrls('/foo/bar.html', { trailing_html: false });
// /foo/bar
prettyUrls('/foo/bar/index.html', { trailing_html: false });
// /foo/bar/index.html

prettyUrls('/foo/bar.html', { trailing_index: false, trailing_html: false });
// /foo/bar
prettyUrls('/foo/bar/index.html', { trailing_index: false, trailing_html: false });
// /foo/bar/
```

### prismHighlight(str, [options])

Syntax highlighting for a code block using PrismJS.

Option | Description | Default
--- | --- | ---
`lineNumber` | Whether to show line numbers | true
`lang` | Language | `'none'`
`tab`| Replace tabs |
`isPreprocess` | Enable preprocess or not | true
`mark` | Highlight specific line |
`firstLine` | First line number |
`caption` | Caption |

When `isPreprocess` is enabled, `prismHighlight()` will return PrismJS processed HTML snippet. Otherwise `str` will only be escaped and `prismHighlight()` will return the HTML snippet that is suitable for `prism.js` working in the Browser.

`mark` and `firstLine` options will have effect only when `isPreprocess` is disabled.

### relative_url(from, to)

Returns the relative URL from `from` to `to`. Output is [encoded](#encodeurlstr) automatically. Requires [`bind(hexo)`](#bindhexo).

``` js
relative_url('foo/bar/', 'css/style.css')
// ../../css/style.css
```

### slugize(str, [options])

Transforms a string into a clean URL-friendly string.

Option | Description | Default
--- | --- | ---
`separator` | Separator | -
`transform` | Transform the string into lower case (`1`) or upper case (`2`) |

``` js
slugize('Hello World') = 'Hello-World'
slugize('Hellô Wòrld') = 'Hello-World'
slugize('Hello World', {separator: '_'}) = 'Hello_World'
slugize('Hello World', {transform: 1}) = 'hello-world'
slugize('Hello World', {transform: 2}) = 'HELLO-WORLD'
```

### spawn(command, [args], [options])

Launches a new process with the given `command`. This method returns a promise.

Option | Description | Default
--- | --- | ---
`cwd` | Current working directory of the child process |
`env` | Environment key-value pairs |
`stdio` | Child's stdio configuration | `pipe`
`detached` | The child will be a process group leader |
`uid` | Sets the user identity of the process |
`gid` | Sets the group identity of the process |
`verbose` | Display messages on the console | `false`
`encoding` | Sets the encoding of the output string | `utf8`

More info: [`child_process.spawn()`](https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options)

``` js
spawn('cat', 'test.txt').then((content) => {
  console.log(content);
});

// $ cd "/target/folder"
// $ cat "foo.txt" "bar.txt"
spawn('cat', ['foo.txt', 'bar.txt'], { cwd: '/target/folder' }).then((content) => {
  console.log(content);
});
```

### stripHTML(str)

Removes HTML tags in a string.

### stripIndent(str)

Strip leading whitespace from each line in a string. The line with the least number of leading whitespace, ignoring empty lines, determines the number to remove. Useful for removing redundant indentation.

### wordWrap(str, [options])

Wraps the string no longer than line width. This method breaks on the first whitespace character that does not exceed line width.

Option | Description | Default
--- | --- | ---
`width` | Line width | 80

``` js
wordWrap('Once upon a time')
// Once upon a time

wordWrap('Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding a successor to the throne turned out to be more trouble than anyone could have imagined...')
// Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding\na successor to the throne turned out to be more trouble than anyone could have\nimagined...

wordWrap('Once upon a time', {width: 8})
// Once\nupon a\ntime

wordWrap('Once upon a time', {width: 1})
// Once\nupon\na\ntime
```

### tocObj(str, [options])

Generate a table of contents in JSON format based on the given html string. Headings with attribute `data-toc-unnumbered="true"` will be marked as unnumbered.

Option | Description | Default
--- | --- | ---
`min_depth` | The minimum level of TOC | 1
`max_depth` | The maximum level of TOC | 6


``` js
const html = [
  '<h1 id="title_1">Title 1</h1>',
  '<div id="title_1_1"><h2>Title 1.1</h2></div>',
  '<h3 id="title_1_1_1">Title 1.1.1</h3>',
  '<h2 id="title_1_2">Title 1.2</h2>',
  '<h2 id="title_1_3">Title 1.3</h2>',
  '<h3 id="title_1_3_1">Title 1.3.1</h3>',
  '<h1 id="title_2">Title 2</h1>',
  '<h2 id="title_2_1">Title 2.1</h2>'
].join('\n');

tocObj(html);
/*
[
  { text: 'Title 1', id: 'title_1', level: 1 },
  { text: 'Title 1.1', id: 'title_1_1', level: 2 },
  { text: 'Title 1.1.1', id: 'title_1_1_1', level: 3 },
  { text: 'Title 1.2', id: 'title_1_2', level: 2 },
  { text: 'Title 1.3', id: 'title_1_3', level: 2 },
  { text: 'Title 1.3.1', id: 'title_1_3_1', level: 3 },
  { text: 'Title 2', id: 'title_2', level: 1 },
  { text: 'Title 2.1', id: 'title_2_1', level: 2 },
]
*/

tocObj(html, { min_depth: 2 });
/*
[
  { text: 'Title 1.1', id: 'title_1_1', level: 2 },
  { text: 'Title 1.1.1', id: 'title_1_1_1', level: 3 },
  { text: 'Title 1.2', id: 'title_1_2', level: 2 },
  { text: 'Title 1.3', id: 'title_1_3', level: 2 },
  { text: 'Title 1.3.1', id: 'title_1_3_1', level: 3 },
  { text: 'Title 2.1', id: 'title_2_1', level: 2 },
]
*/

tocObj(html, { max_depth: 2 });
/*
[
  { text: 'Title 1', id: 'title_1', level: 1 },
  { text: 'Title 1.1', id: 'title_1_1', level: 2 },
  { text: 'Title 1.2', id: 'title_1_2', level: 2 },
  { text: 'Title 1.3', id: 'title_1_3', level: 2 },
  { text: 'Title 2', id: 'title_2', level: 1 },
  { text: 'Title 2.1', id: 'title_2_1', level: 2 },
]
*/

tocObj('<h1 id="reference" data-toc-unnumbered="true">Reference</h1>')
/*
[
  { text: 'Reference', id: 'reference', level: 1, unnumbered: true }
]
*/
```

### truncate(str, [options])

Truncates a given text after a given `length` if text is longer than `length`. The last characters will be replaced with the `omission` option for a total length not exceeding `length`.

Option | Description | Default
--- | --- | ---
`length` | Max length of the string | 30
`omission` | Omission text | ...
`separator` | truncate text at a natural break |

``` js
truncate('Once upon a time in a world far far away')
// "Once upon a time in a world..."

truncate('Once upon a time in a world far far away', {length: 17})
// "Once upon a ti..."

truncate('Once upon a time in a world far far away', {length: 17, separator: ' '})
// "Once upon a..."

truncate('And they found that many people were sleeping better.', {length: 25, omission: '... (continued)'})
// "And they f... (continued)"
```

### unescapeHTML(str)

Unescapes HTML entities in a string.

``` js
unescapeHTML('&lt;p&gt;Hello &quot;world&quot;.&lt;&#x2F;p&gt;')
// <p>Hello "world".</p>
```

### url_for(path, [option])

Returns a url with the root path prefixed. Output is [encoded](#encodeurlstr) automatically. Requires [`bind(hexo)`](#bindhexo).

Option | Description | Default
--- | --- | ---
`relative` | Output relative link | Value of `config.relative_link`

``` yml
_config.yml
root: /blog/ # example
```

``` js
url_for('/a/path')
// /blog/a/path
```

Relative link, follows `relative_link` option by default
e.g. post/page path is '/foo/bar/index.html'

``` yml
_config.yml
relative_link: true
```

``` js
url_for('/css/style.css')
// ../../css/style.css

/* Override option
 * you could also disable it to output a non-relative link,
 * even when `relative_link` is enabled and vice versa.
 */
url_for('/css/style.css', {relative: false})
// /css/style.css
```

## bind(hexo)

Following utilities require `bind(hexo)` / `bind(this)` / `call(hexo, input)` / `call(this, input)` to parse the user config when initializing:
- [`full_url_for()`](#full_url_forpath)
- [`url_for()`](#url_forpath)
- [`relative_url()`](#relative_urlfrom-to)

Below examples demonstrate different approaches to creating a [helper](https://hexo.io/api/helper) (each example is separated by `/******/`),

``` js
// Single function
const url_for = require('hexo-util').url_for.bind(hexo);

hexo.extend.helper.register('test_url', (str) => {
  return url_for(str);
})


/******/
// Multiple functions
const url_for = require('hexo-util').url_for.bind(hexo)

function testurlHelper(str) {
  return url_for(str);
}

hexo.extend.helper.register('test_url', testurlHelper);


/******/
// Functions separated into different files.
// test_url.js
module.exports = function(str) {
  const url_for = require('hexo-util').url_for.bind(this);
  return url_for(str);
}

// index.js
hexo.extend.helper.register('test_url', require('./test_url'));


/******/
// Function.call() approach also works
const {url_for} = require('hexo-util');
module.exports = function(str) {
  return url_for.call(this, str);
}

hexo.extend.helper.register('test_url', require('./test_url'));


/******/
// Separating functions into individual files
// Each file has multiple functions
// test_url.js
function testurlHelper(str) {
  const url_for = require('hexo-util').url_for.bind(this);
  return url_for(str);
}

module.exports =  {
  testurlHelper: testurlHelper
}

// index.js
hexo.extend.helper.register('test_url', require('./test_url').testurlHelper);
```

## License

MIT

[Hexo]: http://hexo.io/