# 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('

Hello "world".

') // <p>Hello "world".</p> /* support escaped characters */ escapeHTML('<foo>bar ``` ### 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 [``](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)
_Sublanguage highlight requires `autoDetect` to be enabled and `lang` to be unset_ | false `mark` | Line highlight specific line(s) | `languageAttr` | Output code language into `data-language` attr | false ### htmlTag(tag, attrs, text, escape) Creates a html tag. Option | Description | Default --- | --- | --- `tag` | Tag / element name | `attrs` | Attribute(s) and its value.
Value is always [escaped](#escapehtmlstr), URL is always [encoded](#encodeurlstr). | `text` | Text, the value is always escaped
_(except for ` /* support script tag with async/defer */ htmlTag('script', {src: '/foo.js', async: true}, '') // ``` ### 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 = [ '

Title 1

', '

Title 1.1

', '

Title 1.1.1

', '

Title 1.2

', '

Title 1.3

', '

Title 1.3.1

', '

Title 2

', '

Title 2.1

' ].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('

Reference

') /* [ { 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('<p>Hello "world".</p>') //

Hello "world".

``` ### 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/