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 |
|
9 | Utilities for [Hexo].
|
10 |
|
11 | ## Table of contents
|
12 |
|
13 | - [Installation](#installation)
|
14 | - [Usage](#usage)
|
15 | - [CacheStream](#cachestream)
|
16 | - [camelCaseKeys](#camelcasekeysobj-options)
|
17 | - [createSha1Hash](#createsha1hash)
|
18 | - [decodeURL](#decodeurlstr)
|
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 | - [relative_url](#relative_urlfrom-to)
|
32 | - [slugize](#slugizestr-options)
|
33 | - [spawn](#spawncommand-args-options)
|
34 | - [stripHTML](#striphtmlstr)
|
35 | - [wordWrap](#wordwrapstr-options)
|
36 | - [truncate](#truncatestr-options)
|
37 | - [unescapeHTML](#unescapehtmlstr)
|
38 | - [url_for](#url_forpath-option)
|
39 | - [bind(hexo)](#bindhexo)
|
40 |
|
41 | ## Installation
|
42 |
|
43 | ``` bash
|
44 | $ npm install hexo-util --save
|
45 | ```
|
46 |
|
47 | ## Usage
|
48 |
|
49 | ``` js
|
50 | var util = require('hexo-util');
|
51 | ```
|
52 |
|
53 | ### CacheStream()
|
54 |
|
55 | Caches contents piped to the stream.
|
56 |
|
57 | ``` js
|
58 | var stream = new CacheStream();
|
59 |
|
60 | fs.createReadStream('/path/to/file').pipe(stream);
|
61 |
|
62 | stream.on('finish', function(){
|
63 | // Read cache piped to the stream
|
64 | console.log(stream.getCache());
|
65 |
|
66 | // Destroy cache
|
67 | stream.destroy();
|
68 | });
|
69 | ```
|
70 |
|
71 | ### camelCaseKeys(obj, options)
|
72 |
|
73 | Convert object keys to camelCase. Original keys will be converted to getter/setter and sync to the camelCase keys.
|
74 |
|
75 | ``` js
|
76 | camelCaseKeys({
|
77 | foo_bar: 'test'
|
78 | });
|
79 | // { fooBar: 'test', foo_bar: 'test' }
|
80 | ```
|
81 |
|
82 | ### createSha1Hash()
|
83 | return SHA1 hash object.
|
84 | This is the same as calling `createHash('utf8')` in the node.js native module crypto.
|
85 | ``` js
|
86 | const sha1 = createSha1Hash();
|
87 | fs.createReadStream('/path/to/file')
|
88 | .pipe(sha1)
|
89 | .on('finish', () => {
|
90 | console.log(sha1.read());
|
91 | });
|
92 | ```
|
93 |
|
94 | ### decodeURL(str)
|
95 |
|
96 | 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.
|
97 |
|
98 | ``` js
|
99 | decodeURL('http://foo.com/b%C3%A1r')
|
100 | // http://foo.com/bár
|
101 |
|
102 | decodeURL('http://xn--br-mia.com/baz')
|
103 | // http://bár.com/baz
|
104 |
|
105 | decodeURL('/foo/b%C3%A1r/')
|
106 | // /foo/bár/
|
107 |
|
108 | /* Alternatively, Node 10+ offers native API to decode punycoded domain */
|
109 | const {format} = require('url')
|
110 | decodeURI(format(new URL('http://xn--br-mia.com.com/b%C3%A1r'), {unicode: true}))
|
111 | // http://bár.com/báz
|
112 | ```
|
113 |
|
114 | ### encodeURL(str)
|
115 |
|
116 | Encode URL or path into a [safe format](https://en.wikipedia.org/wiki/Percent-encoding).
|
117 |
|
118 | ``` js
|
119 | encodeURL('http://foo.com/bár')
|
120 | // http://foo.com/b%C3%A1r
|
121 |
|
122 | encodeURL('/foo/bár/')
|
123 | // /foo/b%C3%A1r/
|
124 | ```
|
125 |
|
126 | ### escapeDiacritic(str)
|
127 |
|
128 | Escapes diacritic characters in a string.
|
129 |
|
130 | ### escapeHTML(str)
|
131 |
|
132 | Escapes HTML entities in a string.
|
133 |
|
134 | ``` js
|
135 | escapeHTML('<p>Hello "world".</p>')
|
136 | // <p>Hello "world".</p>
|
137 |
|
138 | /* support escaped characters */
|
139 | escapeHTML('<foo>bar</foo>')
|
140 | // <foo>bar</foo>
|
141 | ```
|
142 |
|
143 | ### escapeRegex(str)
|
144 |
|
145 | Escapes special characters in a regular expression.
|
146 |
|
147 | ### full_url_for(path)
|
148 |
|
149 | Returns a url with the config.url prefixed. Output is [encoded](#encodeurlstr) automatically. Requires [`bind(hexo)`](#bindhexo).
|
150 |
|
151 | ``` yml
|
152 | _config.yml
|
153 | url: https://example.com/blog # example
|
154 | ```
|
155 |
|
156 | ``` js
|
157 | full_url_for('/a/path')
|
158 | // https://example.com/blog/a/path
|
159 | ```
|
160 |
|
161 | ### gravatar(str, [options])
|
162 |
|
163 | Returns the gravatar image url from an email.
|
164 |
|
165 | 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.
|
166 |
|
167 | Option | Description | Default
|
168 | --- | --- | ---
|
169 | `s` | Output image size | 80
|
170 | `d` | Default image |
|
171 | `f` | Force default |
|
172 | `r` | Rating |
|
173 |
|
174 | More info: [Gravatar](https://en.gravatar.com/site/implement/images/)
|
175 |
|
176 | ``` js
|
177 | gravatar('a@abc.com')
|
178 | // https://www.gravatar.com/avatar/b9b00e66c6b8a70f88c73cb6bdb06787
|
179 | gravatar('a@abc.com', 40)
|
180 | // https://www.gravatar.com/avatar/b9b00e66c6b8a70f88c73cb6bdb06787?s=40
|
181 | gravatar('a@abc.com' {s: 40, d: 'https://via.placeholder.com/150'})
|
182 | // https://www.gravatar.com/avatar/b9b00e66c6b8a70f88c73cb6bdb06787?s=40&d=https%3A%2F%2Fvia.placeholder.com%2F150
|
183 | ```
|
184 |
|
185 | ### hash(str)
|
186 |
|
187 | Generates SHA1 hash.
|
188 |
|
189 | ``` js
|
190 | hash('123456');
|
191 | // <Buffer 7c 4a 8d 09 ca 37 62 af 61 e5 95 20 94 3d c2 64 94 f8 94 1b>
|
192 | ```
|
193 |
|
194 | ### HashStream()
|
195 | **\[deprecated\]** use [`createSha1Hash()`](#createsha1hash).
|
196 |
|
197 | Generates SHA1 hash with a transform stream.
|
198 |
|
199 | ``` js
|
200 | var stream = new HashStream();
|
201 |
|
202 | fs.createReadStream('/path/to/file')
|
203 | .pipe(stream)
|
204 | .on('finish', function(){
|
205 | console.log(stream.read());
|
206 | });
|
207 | ```
|
208 |
|
209 | ### highlight(str, [options])
|
210 |
|
211 | Syntax highlighting for a code block.
|
212 |
|
213 | Option | Description | Default
|
214 | --- | --- | ---
|
215 | `gutter` | Whether to show line numbers | true
|
216 | `wrap` | Whether to wrap the code block in [`<table>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table) | true
|
217 | `firstLine` | First line number | 1
|
218 | `hljs` | Whether to use the `hljs-*` prefix for CSS classes | false
|
219 | `lang` | Language |
|
220 | `caption` | Caption |
|
221 | `tab`| Replace tabs |
|
222 | `autoDetect` | Detect language automatically | false
|
223 | `mark` | Line highlight specific line(s) |
|
224 |
|
225 | ### htmlTag(tag, attrs, text, escape)
|
226 |
|
227 | Creates a html tag.
|
228 |
|
229 | Option | Description | Default
|
230 | --- | --- | ---
|
231 | `tag` | Tag / element name |
|
232 | `attrs` | Attribute(s) and its value.<br>Value is always [escaped](#escapehtmlstr), URL is always [encoded](#encodeurlstr). |
|
233 | `text` | Text, the value is always escaped<br>_(except for `<style>` tag)_ |
|
234 | `escape` | Whether to escape the text | true
|
235 |
|
236 | ``` js
|
237 | htmlTag('img', {src: 'example.png'})
|
238 | // <img src="example.png">
|
239 |
|
240 | htmlTag('a', {href: 'http://hexo.io/'}, 'Hexo')
|
241 | // <a href="http://hexo.io/">Hexo</a>
|
242 |
|
243 | htmlTag('link', {href: 'http://foo.com/'}, '<a>bar</a>')
|
244 | // <a href="http://foo.com/"><bar></a>
|
245 |
|
246 | htmlTag('a', {href: 'http://foo.com/'}, '<b>bold</b>', false)
|
247 | // <a href="http://foo.com/"><b>bold</b></a>
|
248 |
|
249 | /* text value of <style> won't be escaped, url is still encoded */
|
250 | htmlTag('style', {}, 'p { content: "<"; background: url("bár.jpg"); }')
|
251 | // <style>p { content: "<"; background: url("b%C3%A1r.jpg"); }</style>
|
252 |
|
253 | /* support script tag with async/defer */
|
254 | htmlTag('script', {src: '/foo.js', async: true}, '')
|
255 | // <script src="/foo.js" async></script>
|
256 | ```
|
257 |
|
258 | ### isExternalLink(url, sitehost, [exclude])
|
259 |
|
260 | Option | Description | Default
|
261 | --- | --- | ---
|
262 | `url` | The input URL. |
|
263 | `sitehost` | The hostname / url of website. You can also pass `hexo.config.url`. |
|
264 | `exclude` | Exclude hostnames. Specific subdomain is required when applicable, including www. | `[]`
|
265 |
|
266 | Returns if a given url is external link relative to given `sitehost` and `[exclude]`.
|
267 |
|
268 | ``` js
|
269 | // 'sitehost' can be a domain or url
|
270 | isExternalLink('https://example.com', 'example.com');
|
271 | // false
|
272 | isExternalLink('https://example.com', 'https://example.com');
|
273 | // false
|
274 | isExternalLink('https://example.com', '//example.com/blog/');
|
275 | // false
|
276 | ```
|
277 |
|
278 | ``` js
|
279 | isExternalLink('/archives/foo.html', 'example.com');
|
280 | // false
|
281 | isExternalLink('https://foo.com/', 'example.com');
|
282 | // true
|
283 | ```
|
284 |
|
285 | ``` js
|
286 | isExternalLink('https://foo.com', 'example.com', ['foo.com', 'bar.com']);
|
287 | // false
|
288 | isExternalLink('https://bar.com', 'example.com', ['foo.com', 'bar.com']);
|
289 | // false
|
290 | isExternalLink('https://baz.com/', 'example.com', ['foo.com', 'bar.com']);
|
291 | // true
|
292 | ```
|
293 |
|
294 |
|
295 | ### Pattern(rule)
|
296 |
|
297 | Parses the string and tests if the string matches the rule. `rule` can be a string, a regular expression or a function.
|
298 |
|
299 | ``` js
|
300 | var pattern = new Pattern('posts/:id');
|
301 |
|
302 | pattern.match('posts/89');
|
303 | // {0: 'posts/89', 1: '89', id: '89'}
|
304 | ```
|
305 |
|
306 | ``` js
|
307 | var pattern = new Pattern('posts/*path');
|
308 |
|
309 | pattern.match('posts/2013/hello-world');
|
310 | // {0: 'posts/2013/hello-world', 1: '2013/hello-world', path: '2013/hello-world'}
|
311 | ```
|
312 |
|
313 | ### Permalink(rule, [options])
|
314 |
|
315 | Parses a permalink.
|
316 |
|
317 | Option | Description
|
318 | --- | ---
|
319 | `segments` | Customize the rule of a segment in the permalink
|
320 |
|
321 | ``` js
|
322 | var permalink = new Permalink(':year/:month/:day/:title', {
|
323 | segments: {
|
324 | year: /(\d{4})/,
|
325 | month: /(\d{2})/,
|
326 | day: /(\d{2})/
|
327 | }
|
328 | });
|
329 |
|
330 | permalink.parse('2014/01/31/test');
|
331 | // {year: '2014', month: '01', day: '31', title: 'test'}
|
332 |
|
333 | permalink.test('2014/01/31/test');
|
334 | // true
|
335 |
|
336 | permalink.stringify({year: '2014', month: '01', day: '31', title: 'test'})
|
337 | // 2014/01/31/test
|
338 | ```
|
339 |
|
340 | ### relative_url(from, to)
|
341 |
|
342 | Returns the relative URL from `from` to `to`. Output is [encoded](#encodeurlstr) automatically. Requires [`bind(hexo)`](#bindhexo).
|
343 |
|
344 | ``` js
|
345 | relative_url('foo/bar/', 'css/style.css')
|
346 | // ../../css/style.css
|
347 | ```
|
348 |
|
349 | ### slugize(str, [options])
|
350 |
|
351 | Transforms a string into a clean URL-friendly string.
|
352 |
|
353 | Option | Description | Default
|
354 | --- | --- | ---
|
355 | `separator` | Separator | -
|
356 | `transform` | Transform the string into lower case (`1`) or upper case (`2`) |
|
357 |
|
358 | ``` js
|
359 | slugize('Hello World') = 'Hello-World'
|
360 | slugize('Hellô Wòrld') = 'Hello-World'
|
361 | slugize('Hello World', {separator: '_'}) = 'Hello_World'
|
362 | slugize('Hello World', {transform: 1}) = 'hello-world'
|
363 | slugize('Hello World', {transform: 2}) = 'HELLO-WORLD'
|
364 | ```
|
365 |
|
366 | ### spawn(command, [args], [options])
|
367 |
|
368 | Launches a new process with the given `command`. This method returns a promise.
|
369 |
|
370 | Option | Description | Default
|
371 | --- | --- | ---
|
372 | `cwd` | Current working directory of the child process |
|
373 | `env` | Environment key-value pairs |
|
374 | `stdio` | Child's stdio configuration |
|
375 | `detached` | The child will be a process group leader |
|
376 | `uid` | Sets the user identity of the process |
|
377 | `gid` | Sets the group identity of the process |
|
378 | `verbose` | Display messages on the console | `false`
|
379 | `encoding` | Sets the encoding of the output string | `utf8`
|
380 |
|
381 | ``` js
|
382 | spawn('cat', 'test.txt').then(function(content){
|
383 | console.log(content);
|
384 | });
|
385 | ```
|
386 |
|
387 | ### stripHTML(str)
|
388 |
|
389 | Removes HTML tags in a string.
|
390 |
|
391 | ### wordWrap(str, [options])
|
392 |
|
393 | Wraps the string no longer than line width. This method breaks on the first whitespace character that does not exceed line width.
|
394 |
|
395 | Option | Description | Default
|
396 | --- | --- | ---
|
397 | `width` | Line width | 80
|
398 |
|
399 | ``` js
|
400 | wordWrap('Once upon a time')
|
401 | // Once upon a time
|
402 |
|
403 | 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...')
|
404 | // 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...
|
405 |
|
406 | wordWrap('Once upon a time', {width: 8})
|
407 | // Once\nupon a\ntime
|
408 |
|
409 | wordWrap('Once upon a time', {width: 1})
|
410 | // Once\nupon\na\ntime
|
411 | ```
|
412 |
|
413 | ### truncate(str, [options])
|
414 |
|
415 | 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`.
|
416 |
|
417 | Option | Description | Default
|
418 | --- | --- | ---
|
419 | `length` | Max length of the string | 30
|
420 | `omission` | Omission text | ...
|
421 | `separator` | truncate text at a natural break |
|
422 |
|
423 | ``` js
|
424 | truncate('Once upon a time in a world far far away')
|
425 | // "Once upon a time in a world..."
|
426 |
|
427 | truncate('Once upon a time in a world far far away', {length: 17})
|
428 | // "Once upon a ti..."
|
429 |
|
430 | truncate('Once upon a time in a world far far away', {length: 17, separator: ' '})
|
431 | // "Once upon a..."
|
432 |
|
433 | truncate('And they found that many people were sleeping better.', {length: 25, omission: '... (continued)'})
|
434 | // "And they f... (continued)"
|
435 | ```
|
436 |
|
437 | ### unescapeHTML(str)
|
438 |
|
439 | Unescapes HTML entities in a string.
|
440 |
|
441 | ``` js
|
442 | unescapeHTML('<p>Hello "world".</p>')
|
443 | // <p>Hello "world".</p>
|
444 | ```
|
445 |
|
446 | ### url_for(path, [option])
|
447 |
|
448 | Returns a url with the root path prefixed. Output is [encoded](#encodeurlstr) automatically. Requires [`bind(hexo)`](#bindhexo).
|
449 |
|
450 | Option | Description | Default
|
451 | --- | --- | ---
|
452 | `relative` | Output relative link | Value of `config.relative_link`
|
453 |
|
454 | ``` yml
|
455 | _config.yml
|
456 | root: /blog/ # example
|
457 | ```
|
458 |
|
459 | ``` js
|
460 | url_for('/a/path')
|
461 | // /blog/a/path
|
462 | ```
|
463 |
|
464 | Relative link, follows `relative_link` option by default
|
465 | e.g. post/page path is '/foo/bar/index.html'
|
466 |
|
467 | ``` yml
|
468 | _config.yml
|
469 | relative_link: true
|
470 | ```
|
471 |
|
472 | ``` js
|
473 | url_for('/css/style.css')
|
474 | // ../../css/style.css
|
475 |
|
476 | /* Override option
|
477 | * you could also disable it to output a non-relative link,
|
478 | * even when `relative_link` is enabled and vice versa.
|
479 | */
|
480 | url_for('/css/style.css', {relative: false})
|
481 | // /css/style.css
|
482 | ```
|
483 |
|
484 | ## bind(hexo)
|
485 |
|
486 | Following utilities require `bind(hexo)` / `bind(this)` / `call(hexo, input)` / `call(this, input)` to parse the user config when initializing:
|
487 | - [`full_url_for()`](#full_url_forpath)
|
488 | - [`url_for()`](#url_forpath)
|
489 | - [`relative_url()`](#relative_urlfrom-to)
|
490 |
|
491 | Below examples demonstrate different approaches to creating a [helper](https://hexo.io/api/helper) (each example is separated by `/******/`),
|
492 |
|
493 | ``` js
|
494 | // Single function
|
495 | const url_for = require('hexo-util').url_for.bind(hexo);
|
496 |
|
497 | hexo.extend.helper.register('test_url', (str) => {
|
498 | return url_for(str);
|
499 | })
|
500 |
|
501 |
|
502 | /******/
|
503 | // Multiple functions
|
504 | const url_for = require('hexo-util').url_for.bind(hexo)
|
505 |
|
506 | function testurlHelper(str) {
|
507 | return url_for(str);
|
508 | }
|
509 |
|
510 | hexo.extend.helper.register('test_url', testurlHelper);
|
511 |
|
512 |
|
513 | /******/
|
514 | // Functions separated into different files.
|
515 | // test_url.js
|
516 | module.exports = function(str) {
|
517 | const url_for = require('hexo-util').url_for.bind(this);
|
518 | return url_for(str);
|
519 | }
|
520 |
|
521 | // index.js
|
522 | hexo.extend.helper.register('test_url', require('./test_url'));
|
523 |
|
524 |
|
525 | /******/
|
526 | // Function.call() approach also works
|
527 | const {url_for} = require('hexo-util');
|
528 | module.exports = function(str) {
|
529 | return url_for.call(this, str);
|
530 | }
|
531 |
|
532 | hexo.extend.helper.register('test_url', require('./test_url'));
|
533 |
|
534 |
|
535 | /******/
|
536 | // Separating functions into individual files
|
537 | // Each file has multiple functions
|
538 | // test_url.js
|
539 | function testurlHelper(str) {
|
540 | const url_for = require('hexo-util').url_for.bind(this);
|
541 | return url_for(str);
|
542 | }
|
543 |
|
544 | module.exports = {
|
545 | testurlHelper: testurlHelper
|
546 | }
|
547 |
|
548 | // index.js
|
549 | hexo.extend.helper.register('test_url', require('./test_url').testurlHelper);
|
550 | ```
|
551 |
|
552 | ## License
|
553 |
|
554 | MIT
|
555 |
|
556 | [Hexo]: http://hexo.io/
|