Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cache): plain object cache #162

Merged
merged 7 commits into from
Dec 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Utilities for [Hexo].

- [Installation](#installation)
- [Usage](#usage)
- [Cache](#cache)
- [CacheStream](#cachestream)
- [camelCaseKeys](#camelcasekeysobj-options)
- [createSha1Hash](#createsha1hash)
Expand Down Expand Up @@ -52,6 +53,47 @@ $ npm install hexo-util --save
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

// del(key)
cache.del('baz');
cache.has('baz');
// false

// flush()
cache.flush();
cache.has('foo');
// false
```

### CacheStream()

Caches contents piped to the stream.
Expand Down
36 changes: 36 additions & 0 deletions lib/cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use strict';

module.exports = class Cache {
constructor() {
this.cache = {};
}

set(id, value) {
this.cache[id] = value;
}

has(id) {
return typeof this.cache[id] !== 'undefined';
}

get(id) {
return this.cache[id];
}

del(id) {
delete this.cache[id];
}

apply(id, value) {
if (this.has(id)) return this.get(id);

if (typeof value === 'function') value = value();

this.set(id, value);
return value;
}

flush() {
this.cache = {};
}
};
25 changes: 15 additions & 10 deletions lib/full_url_for.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,32 @@ const { parse, URL } = require('url');
const encodeURL = require('./encode_url');
const prettyUrls = require('./pretty_urls');

const Cache = require('./cache');
const cache = new Cache();

function fullUrlForHelper(path = '/') {
const pathRegex = /^(\/\/|http(s)?:)/;
if (pathRegex.test(path)) return path;

const { config } = this;
const sitehost = parse(config.url).hostname || config.url;
const data = new URL(path, `http://${sitehost}`);

// Exit if input is an external link or a data url
if (data.hostname !== sitehost || data.origin === 'null') return path;

path = encodeURL(config.url + `/${path}`.replace(/\/{2,}/g, '/'));

const prettyUrlsOptions = Object.assign({
trailing_index: true,
trailing_html: true
}, config.pretty_urls);

path = prettyUrls(path, prettyUrlsOptions);
// cacheId is designed to works across different hexo.config & options
return cache.apply(`${config.url}-${prettyUrlsOptions.trailing_index}-${prettyUrlsOptions.trailing_html}-${path}`, () => {
const sitehost = parse(config.url).hostname || config.url;
const data = new URL(path, `http://${sitehost}`);

// Exit if input is an external link or a data url
if (data.hostname !== sitehost || data.origin === 'null') return path;

path = encodeURL(config.url + `/${path}`.replace(/\/{2,}/g, '/'));
path = prettyUrls(path, prettyUrlsOptions);

return path;
return path;
});
}

module.exports = fullUrlForHelper;
9 changes: 8 additions & 1 deletion lib/gravatar.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
const { createHash } = require('crypto');
const { stringify } = require('querystring');

const Cache = require('./cache');
const cache = new Cache();

function md5(str) {
return createHash('md5').update(str).digest('hex');
}
Expand All @@ -12,11 +15,15 @@ function gravatarHelper(email, options) {
options = {s: options};
}

let str = `https://www.gravatar.com/avatar/${md5(email.toLowerCase())}`;
const hash = cache.has(email) ? cache.get(email) : md5(email.toLowerCase());
let str = `https://www.gravatar.com/avatar/${hash}`;

const qs = stringify(options);

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

cache.set('email', hash);

return str;
}

Expand Down
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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

exports.Cache = require('./cache');
exports.CacheStream = require('./cache_stream');
exports.camelCaseKeys = require('./camel_case_keys');
exports.Color = require('./color');
Expand Down
31 changes: 18 additions & 13 deletions lib/is_external_link.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

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

const Cache = require('./cache');
const cache = new Cache();

/**
* Check whether the link is external
* @param {String} input The url to check
Expand All @@ -13,27 +16,29 @@ function isExternalLink(input, sitehost, exclude) {

if (!sitehost) return false;

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

// handle mailto: javascript: vbscript: and so on
if (data.origin === 'null') return false;
// handle mailto: javascript: vbscript: and so on
if (data.origin === 'null') return false;

const host = data.hostname;
const host = data.hostname;

if (exclude) {
exclude = Array.isArray(exclude) ? exclude : [exclude];
if (exclude) {
exclude = Array.isArray(exclude) ? exclude : [exclude];

if (exclude && exclude.length) {
for (const i of exclude) {
if (host === i) return false;
if (exclude && exclude.length) {
for (const i of exclude) {
if (host === i) return false;
}
}
}
}

if (host !== sitehost) return true;
if (host !== sitehost) return true;

return false;
return false;
});
}

module.exports = isExternalLink;
39 changes: 22 additions & 17 deletions lib/relative_url.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,35 @@

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

const Cache = require('./cache');
const cache = new Cache();

function relativeUrlHelper(from = '', to = '') {
const fromParts = from.split('/');
const toParts = to.split('/');
const length = Math.min(fromParts.length, toParts.length);
let i = 0;
return cache.apply(`${from}-${to}`, () => {
const fromParts = from.split('/');
const toParts = to.split('/');
const length = Math.min(fromParts.length, toParts.length);
let i = 0;

for (; i < length; i++) {
if (fromParts[i] !== toParts[i]) break;
}
for (; i < length; i++) {
if (fromParts[i] !== toParts[i]) break;
}

let out = toParts.slice(i);
let out = toParts.slice(i);

for (let j = fromParts.length - i - 1; j > 0; j--) {
out.unshift('..');
}
for (let j = fromParts.length - i - 1; j > 0; j--) {
out.unshift('..');
}

const outLength = out.length;
const outLength = out.length;

// If the last 2 elements of `out` is empty strings, replace them with `index.html`.
if (outLength > 1 && !out[outLength - 1] && !out[outLength - 2]) {
out = out.slice(0, outLength - 2).concat('index.html');
}
// If the last 2 elements of `out` is empty strings, replace them with `index.html`.
if (outLength > 1 && !out[outLength - 1] && !out[outLength - 2]) {
out = out.slice(0, outLength - 2).concat('index.html');
}

return encodeURL(out.join('/').replace(/\/{2,}/g, '/'));
return encodeURL(out.join('/').replace(/\/{2,}/g, '/'));
});
}

module.exports = relativeUrlHelper;
39 changes: 25 additions & 14 deletions lib/url_for.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,11 @@ const encodeURL = require('./encode_url');
const relative_url = require('./relative_url');
const prettyUrls = require('./pretty_urls');

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

function urlForHelper(path = '/', options) {
const { config } = this;
const { root } = config;
const sitehost = parse(config.url).hostname || config.url;
const data = new URL(path, `http://${sitehost}`);

// Exit if input is an external link or a data url
if (data.hostname !== sitehost || data.origin === 'null') return path;

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

// Prepend root path
path = encodeURL((root + path).replace(/\/{2,}/g, '/'));

const { root } = config;
const prettyUrlsOptions = Object.assign({
trailing_index: true,
trailing_html: true
}, config.pretty_urls);

path = prettyUrls(path, prettyUrlsOptions);
// cacheId is designed to works across different hexo.config & options
return cache.apply(`${config.url}-${root}-${prettyUrlsOptions.trailing_index}-${prettyUrlsOptions.trailing_html}-${path}`, () => {
const pathRegex = /^(#|\/\/|http(s)?:)/;
if (pathRegex.test(path)) {
return path;
}

const sitehost = parse(config.url).hostname || config.url;
const data = new URL(path, `http://${sitehost}`);

// Exit if input is an external link or a data url
if (data.hostname !== sitehost || data.origin === 'null') {
return path;
}

// Prepend root path
path = encodeURL((root + path).replace(/\/{2,}/g, '/'));

path = prettyUrls(path, prettyUrlsOptions);

return path;
return path;
});
}

module.exports = urlForHelper;
48 changes: 48 additions & 0 deletions test/cache.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use strict';

require('chai').should();

describe('Cache', () => {
const Cache = require('../lib/cache');
const cache = new Cache();

it('get & set', () => {
cache.set('foo', 123);
cache.get('foo').should.eql(123);
});

it('has', () => {
cache.has('foo').should.eql(true);
cache.has('bar').should.eql(false);
});

it('apply - non function', () => {
cache.apply('bar', 123).should.eql(123);
cache.apply('bar', 456).should.eql(123);

cache.apply('foo', 456).should.eql(123);
});

it('apply - function', () => {
cache.apply('baz', () => 123).should.eql(123);
cache.apply('baz', () => 456).should.eql(123);
});

it('del', () => {
cache.del('baz');
cache.has('foo').should.eql(true);
cache.has('baz').should.eql(false);
});

it('flush', () => {
cache.flush();
cache.has('foo').should.eql(false);
cache.has('bar').should.eql(false);
cache.has('baz').should.eql(false);
});

it('cache null', () => {
cache.apply('foo', null);
(cache.apply('foo', 123) === null).should.eql(true);
});
});