Skip to content

Commit 1a2041f

Browse files
authored
feat(cache): plain object cache (#162)
* feat(cache): plain object cache * feat(cache): bring up Cache() * refactor(cache): utilize Cache() * feat(cache): add cache.del() * docs(README): add Cache() * refactor(url_for): reduce lines * feat(cache): make null cacheable
1 parent f80d6c6 commit 1a2041f

File tree

9 files changed

+215
-55
lines changed

9 files changed

+215
-55
lines changed

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Utilities for [Hexo].
1212

1313
- [Installation](#installation)
1414
- [Usage](#usage)
15+
- [Cache](#cache)
1516
- [CacheStream](#cachestream)
1617
- [camelCaseKeys](#camelcasekeysobj-options)
1718
- [createSha1Hash](#createsha1hash)
@@ -53,6 +54,47 @@ $ npm install hexo-util --save
5354
var util = require('hexo-util');
5455
```
5556

57+
### Cache()
58+
59+
A simple plain object cache
60+
61+
``` js
62+
const cache = new Cache();
63+
64+
// set(key, value)
65+
cache.set('foo', 'bar');
66+
67+
// get(key) => value
68+
cache.get('foo');
69+
// 'bar'
70+
71+
// has(key) => Boolean
72+
cache.has('foo');
73+
// true
74+
cache.has('bar');
75+
// false
76+
77+
// apply(key. value)
78+
cache.apply('baz', () => 123);
79+
// 123
80+
cache.apply('baz', () => 456);
81+
// 123
82+
cache.apply('qux', 456);
83+
// 456
84+
cache.apply('qux', '789');
85+
// 456
86+
87+
// del(key)
88+
cache.del('baz');
89+
cache.has('baz');
90+
// false
91+
92+
// flush()
93+
cache.flush();
94+
cache.has('foo');
95+
// false
96+
```
97+
5698
### CacheStream()
5799

58100
Caches contents piped to the stream.

lib/cache.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
'use strict';
2+
3+
module.exports = class Cache {
4+
constructor() {
5+
this.cache = {};
6+
}
7+
8+
set(id, value) {
9+
this.cache[id] = value;
10+
}
11+
12+
has(id) {
13+
return typeof this.cache[id] !== 'undefined';
14+
}
15+
16+
get(id) {
17+
return this.cache[id];
18+
}
19+
20+
del(id) {
21+
delete this.cache[id];
22+
}
23+
24+
apply(id, value) {
25+
if (this.has(id)) return this.get(id);
26+
27+
if (typeof value === 'function') value = value();
28+
29+
this.set(id, value);
30+
return value;
31+
}
32+
33+
flush() {
34+
this.cache = {};
35+
}
36+
};

lib/full_url_for.js

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,32 @@ const { parse, URL } = require('url');
44
const encodeURL = require('./encode_url');
55
const prettyUrls = require('./pretty_urls');
66

7+
const Cache = require('./cache');
8+
const cache = new Cache();
9+
710
function fullUrlForHelper(path = '/') {
811
const pathRegex = /^(\/\/|http(s)?:)/;
912
if (pathRegex.test(path)) return path;
1013

1114
const { config } = this;
12-
const sitehost = parse(config.url).hostname || config.url;
13-
const data = new URL(path, `http://${sitehost}`);
14-
15-
// Exit if input is an external link or a data url
16-
if (data.hostname !== sitehost || data.origin === 'null') return path;
17-
18-
path = encodeURL(config.url + `/${path}`.replace(/\/{2,}/g, '/'));
19-
2015
const prettyUrlsOptions = Object.assign({
2116
trailing_index: true,
2217
trailing_html: true
2318
}, config.pretty_urls);
2419

25-
path = prettyUrls(path, prettyUrlsOptions);
20+
// cacheId is designed to works across different hexo.config & options
21+
return cache.apply(`${config.url}-${prettyUrlsOptions.trailing_index}-${prettyUrlsOptions.trailing_html}-${path}`, () => {
22+
const sitehost = parse(config.url).hostname || config.url;
23+
const data = new URL(path, `http://${sitehost}`);
24+
25+
// Exit if input is an external link or a data url
26+
if (data.hostname !== sitehost || data.origin === 'null') return path;
27+
28+
path = encodeURL(config.url + `/${path}`.replace(/\/{2,}/g, '/'));
29+
path = prettyUrls(path, prettyUrlsOptions);
2630

27-
return path;
31+
return path;
32+
});
2833
}
2934

3035
module.exports = fullUrlForHelper;

lib/gravatar.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
const { createHash } = require('crypto');
44
const { stringify } = require('querystring');
55

6+
const Cache = require('./cache');
7+
const cache = new Cache();
8+
69
function md5(str) {
710
return createHash('md5').update(str).digest('hex');
811
}
@@ -12,11 +15,15 @@ function gravatarHelper(email, options) {
1215
options = {s: options};
1316
}
1417

15-
let str = `https://www.gravatar.com/avatar/${md5(email.toLowerCase())}`;
18+
const hash = cache.has(email) ? cache.get(email) : md5(email.toLowerCase());
19+
let str = `https://www.gravatar.com/avatar/${hash}`;
20+
1621
const qs = stringify(options);
1722

1823
if (qs) str += `?${qs}`;
1924

25+
cache.set('email', hash);
26+
2027
return str;
2128
}
2229

lib/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const hash = require('./hash');
44

5+
exports.Cache = require('./cache');
56
exports.CacheStream = require('./cache_stream');
67
exports.camelCaseKeys = require('./camel_case_keys');
78
exports.Color = require('./color');

lib/is_external_link.js

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
const { parse, URL } = require('url');
44

5+
const Cache = require('./cache');
6+
const cache = new Cache();
7+
58
/**
69
* Check whether the link is external
710
* @param {String} input The url to check
@@ -13,27 +16,29 @@ function isExternalLink(input, sitehost, exclude) {
1316

1417
if (!sitehost) return false;
1518

16-
// handle relative url
17-
const data = new URL(input, `http://${sitehost}`);
19+
return cache.apply(`${input}-${sitehost}-${exclude}`, () => {
20+
// handle relative url
21+
const data = new URL(input, `http://${sitehost}`);
1822

19-
// handle mailto: javascript: vbscript: and so on
20-
if (data.origin === 'null') return false;
23+
// handle mailto: javascript: vbscript: and so on
24+
if (data.origin === 'null') return false;
2125

22-
const host = data.hostname;
26+
const host = data.hostname;
2327

24-
if (exclude) {
25-
exclude = Array.isArray(exclude) ? exclude : [exclude];
28+
if (exclude) {
29+
exclude = Array.isArray(exclude) ? exclude : [exclude];
2630

27-
if (exclude && exclude.length) {
28-
for (const i of exclude) {
29-
if (host === i) return false;
31+
if (exclude && exclude.length) {
32+
for (const i of exclude) {
33+
if (host === i) return false;
34+
}
3035
}
3136
}
32-
}
3337

34-
if (host !== sitehost) return true;
38+
if (host !== sitehost) return true;
3539

36-
return false;
40+
return false;
41+
});
3742
}
3843

3944
module.exports = isExternalLink;

lib/relative_url.js

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,35 @@
22

33
const encodeURL = require('./encode_url');
44

5+
const Cache = require('./cache');
6+
const cache = new Cache();
7+
58
function relativeUrlHelper(from = '', to = '') {
6-
const fromParts = from.split('/');
7-
const toParts = to.split('/');
8-
const length = Math.min(fromParts.length, toParts.length);
9-
let i = 0;
9+
return cache.apply(`${from}-${to}`, () => {
10+
const fromParts = from.split('/');
11+
const toParts = to.split('/');
12+
const length = Math.min(fromParts.length, toParts.length);
13+
let i = 0;
1014

11-
for (; i < length; i++) {
12-
if (fromParts[i] !== toParts[i]) break;
13-
}
15+
for (; i < length; i++) {
16+
if (fromParts[i] !== toParts[i]) break;
17+
}
1418

15-
let out = toParts.slice(i);
19+
let out = toParts.slice(i);
1620

17-
for (let j = fromParts.length - i - 1; j > 0; j--) {
18-
out.unshift('..');
19-
}
21+
for (let j = fromParts.length - i - 1; j > 0; j--) {
22+
out.unshift('..');
23+
}
2024

21-
const outLength = out.length;
25+
const outLength = out.length;
2226

23-
// If the last 2 elements of `out` is empty strings, replace them with `index.html`.
24-
if (outLength > 1 && !out[outLength - 1] && !out[outLength - 2]) {
25-
out = out.slice(0, outLength - 2).concat('index.html');
26-
}
27+
// If the last 2 elements of `out` is empty strings, replace them with `index.html`.
28+
if (outLength > 1 && !out[outLength - 1] && !out[outLength - 2]) {
29+
out = out.slice(0, outLength - 2).concat('index.html');
30+
}
2731

28-
return encodeURL(out.join('/').replace(/\/{2,}/g, '/'));
32+
return encodeURL(out.join('/').replace(/\/{2,}/g, '/'));
33+
});
2934
}
3035

3136
module.exports = relativeUrlHelper;

lib/url_for.js

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,11 @@ const encodeURL = require('./encode_url');
55
const relative_url = require('./relative_url');
66
const prettyUrls = require('./pretty_urls');
77

8-
function urlForHelper(path = '/', options) {
9-
const pathRegex = /^(#|\/\/|http(s)?:)/;
10-
if (pathRegex.test(path)) return path;
8+
const Cache = require('./cache');
9+
const cache = new Cache();
1110

11+
function urlForHelper(path = '/', options) {
1212
const { config } = this;
13-
const { root } = config;
14-
const sitehost = parse(config.url).hostname || config.url;
15-
const data = new URL(path, `http://${sitehost}`);
16-
17-
// Exit if input is an external link or a data url
18-
if (data.hostname !== sitehost || data.origin === 'null') return path;
1913

2014
options = Object.assign({
2115
relative: config.relative_link
@@ -26,17 +20,34 @@ function urlForHelper(path = '/', options) {
2620
return relative_url(this.path, path);
2721
}
2822

29-
// Prepend root path
30-
path = encodeURL((root + path).replace(/\/{2,}/g, '/'));
31-
23+
const { root } = config;
3224
const prettyUrlsOptions = Object.assign({
3325
trailing_index: true,
3426
trailing_html: true
3527
}, config.pretty_urls);
3628

37-
path = prettyUrls(path, prettyUrlsOptions);
29+
// cacheId is designed to works across different hexo.config & options
30+
return cache.apply(`${config.url}-${root}-${prettyUrlsOptions.trailing_index}-${prettyUrlsOptions.trailing_html}-${path}`, () => {
31+
const pathRegex = /^(#|\/\/|http(s)?:)/;
32+
if (pathRegex.test(path)) {
33+
return path;
34+
}
35+
36+
const sitehost = parse(config.url).hostname || config.url;
37+
const data = new URL(path, `http://${sitehost}`);
38+
39+
// Exit if input is an external link or a data url
40+
if (data.hostname !== sitehost || data.origin === 'null') {
41+
return path;
42+
}
43+
44+
// Prepend root path
45+
path = encodeURL((root + path).replace(/\/{2,}/g, '/'));
46+
47+
path = prettyUrls(path, prettyUrlsOptions);
3848

39-
return path;
49+
return path;
50+
});
4051
}
4152

4253
module.exports = urlForHelper;

test/cache.spec.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
'use strict';
2+
3+
require('chai').should();
4+
5+
describe('Cache', () => {
6+
const Cache = require('../lib/cache');
7+
const cache = new Cache();
8+
9+
it('get & set', () => {
10+
cache.set('foo', 123);
11+
cache.get('foo').should.eql(123);
12+
});
13+
14+
it('has', () => {
15+
cache.has('foo').should.eql(true);
16+
cache.has('bar').should.eql(false);
17+
});
18+
19+
it('apply - non function', () => {
20+
cache.apply('bar', 123).should.eql(123);
21+
cache.apply('bar', 456).should.eql(123);
22+
23+
cache.apply('foo', 456).should.eql(123);
24+
});
25+
26+
it('apply - function', () => {
27+
cache.apply('baz', () => 123).should.eql(123);
28+
cache.apply('baz', () => 456).should.eql(123);
29+
});
30+
31+
it('del', () => {
32+
cache.del('baz');
33+
cache.has('foo').should.eql(true);
34+
cache.has('baz').should.eql(false);
35+
});
36+
37+
it('flush', () => {
38+
cache.flush();
39+
cache.has('foo').should.eql(false);
40+
cache.has('bar').should.eql(false);
41+
cache.has('baz').should.eql(false);
42+
});
43+
44+
it('cache null', () => {
45+
cache.apply('foo', null);
46+
(cache.apply('foo', 123) === null).should.eql(true);
47+
});
48+
});

0 commit comments

Comments
 (0)