UNPKG

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