UNPKG

20.2 kBMarkdownView Raw
1# hexo-util
2
3[![Build Status](https://github.com/hexojs/hexo-util/workflows/Tester/badge.svg?branch=master)](https://github.com/hexojs/hexo-util/actions?query=workflow%3ATester)
4[![NPM version](https://badge.fury.io/js/hexo-util.svg)](https://www.npmjs.com/package/hexo-util)
5[![Coverage Status](https://coveralls.io/repos/hexojs/hexo-util/badge.svg?branch=master&service=github)](https://coveralls.io/github/hexojs/hexo-util?branch=master)
6
7Utilities for [Hexo].
8
9## Table of contents
10
11- [Installation](#installation)
12- [Usage](#usage)
13- [Cache](#cache)
14- [CacheStream](#cachestream)
15- [camelCaseKeys](#camelcasekeysobj-options)
16- [createSha1Hash](#createsha1hash)
17- [decodeURL](#decodeurlstr)
18- [deepMerge](#deepmergetarget-source)
19- [encodeURL](#encodeurlstr)
20- [escapeDiacritic](#escapediacriticstr)
21- [escapeHTML](#escapehtmlstr)
22- [escapeRegex](#escaperegexstr)
23- [full_url_for](#full_url_forpath)
24- [gravatar](#gravatarstr-options)
25- [hash](#hashstr)
26- [highlight](#highlightstr-options)
27- [htmlTag](#htmltagtag-attrs-text-escape)
28- [isExternalLink](#isexternallinkurl-sitehost-exclude)
29- [Pattern](#patternrule)
30- [Permalink](#permalinkrule-options)
31- [prettyUrls](#prettyurlsurl-options)
32- [prismHighlight](#prismhighlightstr-options)
33- [relative_url](#relative_urlfrom-to)
34- [slugize](#slugizestr-options)
35- [spawn](#spawncommand-args-options)
36- [stripHTML](#striphtmlstr)
37- [wordWrap](#wordwrapstr-options)
38- [tocObj](#tocobjstr-options)
39- [truncate](#truncatestr-options)
40- [unescapeHTML](#unescapehtmlstr)
41- [url_for](#url_forpath-option)
42- [bind(hexo)](#bindhexo)
43
44## Installation
45
46``` bash
47$ npm install hexo-util --save
48```
49
50## Usage
51
52``` js
53var util = require('hexo-util');
54```
55
56### Cache()
57
58A simple plain object cache
59
60``` js
61const cache = new Cache();
62
63// set(key, value)
64cache.set('foo', 'bar');
65
66// get(key) => value
67cache.get('foo');
68// 'bar'
69
70// has(key) => Boolean
71cache.has('foo');
72// true
73cache.has('bar');
74// false
75
76// apply(key. value)
77cache.apply('baz', () => 123);
78// 123
79cache.apply('baz', () => 456);
80// 123
81cache.apply('qux', 456);
82// 456
83cache.apply('qux', '789');
84// 456
85
86// size()
87cache.size();
88// 3
89
90// dump()
91cache.dump();
92/*
93{
94 foo: 'bar',
95 baz: 123,
96 qux: 456
97}
98*/
99
100// del(key)
101cache.del('baz');
102cache.has('baz');
103// false
104
105// flush()
106cache.flush();
107cache.has('foo');
108// false
109cache.size();
110// 0
111```
112
113### CacheStream()
114
115Caches contents piped to the stream.
116
117``` js
118var stream = new CacheStream();
119
120fs.createReadStream('/path/to/file').pipe(stream);
121
122stream.on('finish', function(){
123 // Read cache piped to the stream
124 console.log(stream.getCache());
125
126 // Destroy cache
127 stream.destroy();
128});
129```
130
131### camelCaseKeys(obj, options)
132
133Convert object keys to camelCase. Original keys will be converted to getter/setter and sync to the camelCase keys.
134
135``` js
136camelCaseKeys({
137 foo_bar: 'test'
138});
139// { fooBar: 'test', foo_bar: 'test' }
140```
141
142### createSha1Hash()
143return SHA1 hash object.
144 This is the same as calling `createHash('utf8')` in the node.js native module crypto.
145 ``` js
146const sha1 = createSha1Hash();
147 fs.createReadStream('/path/to/file')
148 .pipe(sha1)
149 .on('finish', () => {
150 console.log(sha1.read());
151 });
152```
153
154### decodeURL(str)
155
156Decode [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.
157
158``` js
159decodeURL('http://foo.com/b%C3%A1r')
160// http://foo.com/bár
161
162decodeURL('http://xn--br-mia.com/baz')
163// http://bár.com/baz
164
165decodeURL('/foo/b%C3%A1r/')
166// /foo/bár/
167
168/* Alternatively, Node 10+ offers native API to decode punycoded domain */
169const {format} = require('url')
170decodeURI(format(new URL('http://xn--br-mia.com.com/b%C3%A1r'), {unicode: true}))
171// http://bár.com/báz
172```
173
174### deepMerge(target, source)
175
176Merges the enumerable properties of two objects deeply. `target` and `source` remain untouched.
177
178``` js
179// Merge deeply
180const obj1 = {a: {b: 1, c: 1, d: {e: 1, f: 1}}};
181const obj2 = {a: {b: 2, d: {f: 'f'} }};
182
183deepMerge(obj1, obj2);
184// {a: {b: 2, c: 1, d: {e: 1, f: 'f'} }}
185```
186
187``` js
188// Arrays will be combined in the same property, similar to lodash.merge
189const obj1 = { 'a': [{ 'b': 2 }, { 'd': 4 }] };
190const obj2 = { 'a': [{ 'c': 3 }, { 'e': 5 }] };
191
192deepMerge(obj1, obj2);
193// { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] };
194```
195
196### encodeURL(str)
197
198Encode URL or path into a [safe format](https://en.wikipedia.org/wiki/Percent-encoding).
199
200``` js
201encodeURL('http://foo.com/bár')
202// http://foo.com/b%C3%A1r
203
204encodeURL('/foo/bár/')
205// /foo/b%C3%A1r/
206```
207
208### escapeDiacritic(str)
209
210Escapes diacritic characters in a string.
211
212### escapeHTML(str)
213
214Escapes HTML entities in a string.
215
216``` js
217escapeHTML('<p>Hello "world".</p>')
218// &lt;p&gt;Hello &quot;world&quot;.&lt;&#x2F;p&gt;
219
220/* support escaped characters */
221escapeHTML('&lt;foo>bar</foo&gt;')
222// &lt;foo&gt;bar&lt;&#x2F;foo&gt;
223```
224
225### escapeRegex(str)
226
227Escapes special characters in a regular expression.
228
229### full_url_for(path)
230
231Returns a url with the config.url prefixed. Output is [encoded](#encodeurlstr) automatically. Requires [`bind(hexo)`](#bindhexo).
232
233``` yml
234_config.yml
235url: https://example.com/blog # example
236```
237
238``` js
239full_url_for('/a/path')
240// https://example.com/blog/a/path
241```
242
243### gravatar(str, [options])
244
245Returns the gravatar image url from an email.
246
247If 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.
248
249Option | Description | Default
250--- | --- | ---
251`s` | Output image size | 80
252`d` | Default image |
253`f` | Force default |
254`r` | Rating |
255
256More info: [Gravatar](https://en.gravatar.com/site/implement/images/)
257
258``` js
259gravatar('a@abc.com')
260// https://www.gravatar.com/avatar/b9b00e66c6b8a70f88c73cb6bdb06787
261gravatar('a@abc.com', 40)
262// https://www.gravatar.com/avatar/b9b00e66c6b8a70f88c73cb6bdb06787?s=40
263gravatar('a@abc.com' {s: 40, d: 'https://via.placeholder.com/150'})
264// https://www.gravatar.com/avatar/b9b00e66c6b8a70f88c73cb6bdb06787?s=40&d=https%3A%2F%2Fvia.placeholder.com%2F150
265```
266
267### hash(str)
268
269Generates SHA1 hash.
270
271``` js
272hash('123456');
273// <Buffer 7c 4a 8d 09 ca 37 62 af 61 e5 95 20 94 3d c2 64 94 f8 94 1b>
274```
275
276### highlight(str, [options])
277
278Syntax highlighting for a code block.
279
280Option | Description | Default
281--- | --- | ---
282`gutter` | Whether to show line numbers | true
283`wrap` | Whether to wrap the code block in [`<table>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table) | true
284`firstLine` | First line number | 1
285`hljs` | Whether to use the `hljs-*` prefix for CSS classes | false
286`lang` | Language |
287`caption` | Caption |
288`tab`| Replace tabs |
289`autoDetect` | Detect language automatically (warning: slow)<br>_Sublanguage highlight requires `autoDetect` to be enabled and `lang` to be unset_ | false
290`mark` | Line highlight specific line(s) |
291`languageAttr` | Output code language into `data-language` attr | false
292
293### htmlTag(tag, attrs, text, escape)
294
295Creates a html tag.
296
297Option | Description | Default
298--- | --- | ---
299`tag` | Tag / element name |
300`attrs` | Attribute(s) and its value.<br>Value is always [escaped](#escapehtmlstr), URL is always [encoded](#encodeurlstr). |
301`text` | Text, the value is always escaped<br>_(except for `<style>` tag)_ |
302`escape` | Whether to escape the text | true
303
304``` js
305htmlTag('img', {src: 'example.png'})
306// <img src="example.png">
307
308htmlTag('a', {href: 'http://hexo.io/'}, 'Hexo')
309// <a href="http://hexo.io/">Hexo</a>
310
311htmlTag('link', {href: 'http://foo.com/'}, '<a>bar</a>')
312// <a href="http://foo.com/">&lt;bar&gt;</a>
313
314htmlTag('a', {href: 'http://foo.com/'}, '<b>bold</b>', false)
315// <a href="http://foo.com/"><b>bold</b></a>
316
317/* text value of <style> won't be escaped, url is still encoded */
318htmlTag('style', {}, 'p { content: "<"; background: url("bár.jpg"); }')
319// <style>p { content: "<"; background: url("b%C3%A1r.jpg"); }</style>
320
321/* support script tag with async/defer */
322htmlTag('script', {src: '/foo.js', async: true}, '')
323// <script src="/foo.js" async></script>
324```
325
326### isExternalLink(url, sitehost, [exclude])
327
328Option | Description | Default
329--- | --- | ---
330`url` | The input URL. |
331`sitehost` | The hostname / url of website. You can also pass `hexo.config.url`. |
332`exclude` | Exclude hostnames. Specific subdomain is required when applicable, including www. | `[]`
333
334Returns if a given url is external link relative to given `sitehost` and `[exclude]`.
335
336``` js
337// 'sitehost' can be a domain or url
338isExternalLink('https://example.com', 'example.com');
339// false
340isExternalLink('https://example.com', 'https://example.com');
341// false
342isExternalLink('https://example.com', '//example.com/blog/');
343// false
344```
345
346``` js
347isExternalLink('/archives/foo.html', 'example.com');
348// false
349isExternalLink('https://foo.com/', 'example.com');
350// true
351```
352
353``` js
354isExternalLink('https://foo.com', 'example.com', ['foo.com', 'bar.com']);
355// false
356isExternalLink('https://bar.com', 'example.com', ['foo.com', 'bar.com']);
357// false
358isExternalLink('https://baz.com/', 'example.com', ['foo.com', 'bar.com']);
359// true
360```
361
362
363### Pattern(rule)
364
365Parses the string and tests if the string matches the rule. `rule` can be a string, a regular expression or a function.
366
367``` js
368var pattern = new Pattern('posts/:id');
369
370pattern.match('posts/89');
371// {0: 'posts/89', 1: '89', id: '89'}
372```
373
374``` js
375var pattern = new Pattern('posts/*path');
376
377pattern.match('posts/2013/hello-world');
378// {0: 'posts/2013/hello-world', 1: '2013/hello-world', path: '2013/hello-world'}
379```
380
381### Permalink(rule, [options])
382
383Parses a permalink.
384
385Option | Description
386--- | ---
387`segments` | Customize the rule of a segment in the permalink
388
389``` js
390var permalink = new Permalink(':year/:month/:day/:title', {
391 segments: {
392 year: /(\d{4})/,
393 month: /(\d{2})/,
394 day: /(\d{2})/
395 }
396});
397
398permalink.parse('2014/01/31/test');
399// {year: '2014', month: '01', day: '31', title: 'test'}
400
401permalink.test('2014/01/31/test');
402// true
403
404permalink.stringify({year: '2014', month: '01', day: '31', title: 'test'})
405// 2014/01/31/test
406```
407
408### prettyUrls(url, [options])
409
410Rewrite urls to pretty URLs.
411
412Option | Description | Default
413--- | --- | ---
414`trailing_index` | `/about/index.html -> /about/` when `false` | `true`
415`trailing_html` | `/about.html -> /about` when `false` | `true`
416
417Note: `trailing_html` ignores any link with a trailing `index.html`. (will not be rewritten to `index`).
418
419``` js
420prettyUrls('/foo/bar.html');
421// /foo/bar.html
422prettyUrls('/foo/bar/index.html');
423// /foo/bar/index.html
424
425prettyUrls('/foo/bar.html', { trailing_index: false });
426// /foo/bar.html
427prettyUrls('/foo/bar/index.html', { trailing_index: false });
428// /foo/bar/
429
430prettyUrls('/foo/bar.html', { trailing_html: false });
431// /foo/bar
432prettyUrls('/foo/bar/index.html', { trailing_html: false });
433// /foo/bar/index.html
434
435prettyUrls('/foo/bar.html', { trailing_index: false, trailing_html: false });
436// /foo/bar
437prettyUrls('/foo/bar/index.html', { trailing_index: false, trailing_html: false });
438// /foo/bar/
439```
440
441### prismHighlight(str, [options])
442
443Syntax highlighting for a code block using PrismJS.
444
445Option | Description | Default
446--- | --- | ---
447`lineNumber` | Whether to show line numbers | true
448`lang` | Language | `'none'`
449`tab`| Replace tabs |
450`isPreprocess` | Enable preprocess or not | true
451`mark` | Highlight specific line |
452`firstLine` | First line number |
453`caption` | Caption |
454
455When `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.
456
457`mark` and `firstLine` options will have effect only when `isPreprocess` is disabled.
458
459### relative_url(from, to)
460
461Returns the relative URL from `from` to `to`. Output is [encoded](#encodeurlstr) automatically. Requires [`bind(hexo)`](#bindhexo).
462
463``` js
464relative_url('foo/bar/', 'css/style.css')
465// ../../css/style.css
466```
467
468### slugize(str, [options])
469
470Transforms a string into a clean URL-friendly string.
471
472Option | Description | Default
473--- | --- | ---
474`separator` | Separator | -
475`transform` | Transform the string into lower case (`1`) or upper case (`2`) |
476
477``` js
478slugize('Hello World') = 'Hello-World'
479slugize('Hellô Wòrld') = 'Hello-World'
480slugize('Hello World', {separator: '_'}) = 'Hello_World'
481slugize('Hello World', {transform: 1}) = 'hello-world'
482slugize('Hello World', {transform: 2}) = 'HELLO-WORLD'
483```
484
485### spawn(command, [args], [options])
486
487Launches a new process with the given `command`. This method returns a promise.
488
489Option | Description | Default
490--- | --- | ---
491`cwd` | Current working directory of the child process |
492`env` | Environment key-value pairs |
493`stdio` | Child's stdio configuration | `pipe`
494`detached` | The child will be a process group leader |
495`uid` | Sets the user identity of the process |
496`gid` | Sets the group identity of the process |
497`verbose` | Display messages on the console | `false`
498`encoding` | Sets the encoding of the output string | `utf8`
499
500More info: [`child_process.spawn()`](https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options)
501
502``` js
503spawn('cat', 'test.txt').then((content) => {
504 console.log(content);
505});
506
507// $ cd "/target/folder"
508// $ cat "foo.txt" "bar.txt"
509spawn('cat', ['foo.txt', 'bar.txt'], { cwd: '/target/folder' }).then((content) => {
510 console.log(content);
511});
512```
513
514### stripHTML(str)
515
516Removes HTML tags in a string.
517
518### stripIndent(str)
519
520Strip 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.
521
522### wordWrap(str, [options])
523
524Wraps the string no longer than line width. This method breaks on the first whitespace character that does not exceed line width.
525
526Option | Description | Default
527--- | --- | ---
528`width` | Line width | 80
529
530``` js
531wordWrap('Once upon a time')
532// Once upon a time
533
534wordWrap('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...')
535// 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...
536
537wordWrap('Once upon a time', {width: 8})
538// Once\nupon a\ntime
539
540wordWrap('Once upon a time', {width: 1})
541// Once\nupon\na\ntime
542```
543
544### tocObj(str, [options])
545
546Generate 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.
547
548Option | Description | Default
549--- | --- | ---
550`min_depth` | The minimum level of TOC | 1
551`max_depth` | The maximum level of TOC | 6
552
553
554``` js
555const html = [
556 '<h1 id="title_1">Title 1</h1>',
557 '<div id="title_1_1"><h2>Title 1.1</h2></div>',
558 '<h3 id="title_1_1_1">Title 1.1.1</h3>',
559 '<h2 id="title_1_2">Title 1.2</h2>',
560 '<h2 id="title_1_3">Title 1.3</h2>',
561 '<h3 id="title_1_3_1">Title 1.3.1</h3>',
562 '<h1 id="title_2">Title 2</h1>',
563 '<h2 id="title_2_1">Title 2.1</h2>'
564].join('\n');
565
566tocObj(html);
567/*
568[
569 { text: 'Title 1', id: 'title_1', level: 1 },
570 { text: 'Title 1.1', id: 'title_1_1', level: 2 },
571 { text: 'Title 1.1.1', id: 'title_1_1_1', level: 3 },
572 { text: 'Title 1.2', id: 'title_1_2', level: 2 },
573 { text: 'Title 1.3', id: 'title_1_3', level: 2 },
574 { text: 'Title 1.3.1', id: 'title_1_3_1', level: 3 },
575 { text: 'Title 2', id: 'title_2', level: 1 },
576 { text: 'Title 2.1', id: 'title_2_1', level: 2 },
577]
578*/
579
580tocObj(html, { min_depth: 2 });
581/*
582[
583 { text: 'Title 1.1', id: 'title_1_1', level: 2 },
584 { text: 'Title 1.1.1', id: 'title_1_1_1', level: 3 },
585 { text: 'Title 1.2', id: 'title_1_2', level: 2 },
586 { text: 'Title 1.3', id: 'title_1_3', level: 2 },
587 { text: 'Title 1.3.1', id: 'title_1_3_1', level: 3 },
588 { text: 'Title 2.1', id: 'title_2_1', level: 2 },
589]
590*/
591
592tocObj(html, { max_depth: 2 });
593/*
594[
595 { text: 'Title 1', id: 'title_1', level: 1 },
596 { text: 'Title 1.1', id: 'title_1_1', level: 2 },
597 { text: 'Title 1.2', id: 'title_1_2', level: 2 },
598 { text: 'Title 1.3', id: 'title_1_3', level: 2 },
599 { text: 'Title 2', id: 'title_2', level: 1 },
600 { text: 'Title 2.1', id: 'title_2_1', level: 2 },
601]
602*/
603
604tocObj('<h1 id="reference" data-toc-unnumbered="true">Reference</h1>')
605/*
606[
607 { text: 'Reference', id: 'reference', level: 1, unnumbered: true }
608]
609*/
610```
611
612### truncate(str, [options])
613
614Truncates 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`.
615
616Option | Description | Default
617--- | --- | ---
618`length` | Max length of the string | 30
619`omission` | Omission text | ...
620`separator` | truncate text at a natural break |
621
622``` js
623truncate('Once upon a time in a world far far away')
624// "Once upon a time in a world..."
625
626truncate('Once upon a time in a world far far away', {length: 17})
627// "Once upon a ti..."
628
629truncate('Once upon a time in a world far far away', {length: 17, separator: ' '})
630// "Once upon a..."
631
632truncate('And they found that many people were sleeping better.', {length: 25, omission: '... (continued)'})
633// "And they f... (continued)"
634```
635
636### unescapeHTML(str)
637
638Unescapes HTML entities in a string.
639
640``` js
641unescapeHTML('&lt;p&gt;Hello &quot;world&quot;.&lt;&#x2F;p&gt;')
642// <p>Hello "world".</p>
643```
644
645### url_for(path, [option])
646
647Returns a url with the root path prefixed. Output is [encoded](#encodeurlstr) automatically. Requires [`bind(hexo)`](#bindhexo).
648
649Option | Description | Default
650--- | --- | ---
651`relative` | Output relative link | Value of `config.relative_link`
652
653``` yml
654_config.yml
655root: /blog/ # example
656```
657
658``` js
659url_for('/a/path')
660// /blog/a/path
661```
662
663Relative link, follows `relative_link` option by default
664e.g. post/page path is '/foo/bar/index.html'
665
666``` yml
667_config.yml
668relative_link: true
669```
670
671``` js
672url_for('/css/style.css')
673// ../../css/style.css
674
675/* Override option
676 * you could also disable it to output a non-relative link,
677 * even when `relative_link` is enabled and vice versa.
678 */
679url_for('/css/style.css', {relative: false})
680// /css/style.css
681```
682
683## bind(hexo)
684
685Following utilities require `bind(hexo)` / `bind(this)` / `call(hexo, input)` / `call(this, input)` to parse the user config when initializing:
686- [`full_url_for()`](#full_url_forpath)
687- [`url_for()`](#url_forpath)
688- [`relative_url()`](#relative_urlfrom-to)
689
690Below examples demonstrate different approaches to creating a [helper](https://hexo.io/api/helper) (each example is separated by `/******/`),
691
692``` js
693// Single function
694const url_for = require('hexo-util').url_for.bind(hexo);
695
696hexo.extend.helper.register('test_url', (str) => {
697 return url_for(str);
698})
699
700
701/******/
702// Multiple functions
703const url_for = require('hexo-util').url_for.bind(hexo)
704
705function testurlHelper(str) {
706 return url_for(str);
707}
708
709hexo.extend.helper.register('test_url', testurlHelper);
710
711
712/******/
713// Functions separated into different files.
714// test_url.js
715module.exports = function(str) {
716 const url_for = require('hexo-util').url_for.bind(this);
717 return url_for(str);
718}
719
720// index.js
721hexo.extend.helper.register('test_url', require('./test_url'));
722
723
724/******/
725// Function.call() approach also works
726const {url_for} = require('hexo-util');
727module.exports = function(str) {
728 return url_for.call(this, str);
729}
730
731hexo.extend.helper.register('test_url', require('./test_url'));
732
733
734/******/
735// Separating functions into individual files
736// Each file has multiple functions
737// test_url.js
738function testurlHelper(str) {
739 const url_for = require('hexo-util').url_for.bind(this);
740 return url_for(str);
741}
742
743module.exports = {
744 testurlHelper: testurlHelper
745}
746
747// index.js
748hexo.extend.helper.register('test_url', require('./test_url').testurlHelper);
749```
750
751## License
752
753MIT
754
755[Hexo]: http://hexo.io/