-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Some unit-tests
- Loading branch information
Showing
19 changed files
with
446 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,67 @@ | |||
/** | |||
* A simple time-based cache storage: stores items and removes them when they | |||
* expire. Unlike LRU, items in this cache will always expire, no matter how | |||
* often you access them. | |||
*/ | |||
'use strict'; | |||
|
|||
const extend = require('xtend'); | |||
|
|||
const defaultOptions = { | |||
maxAge: 10000 | |||
}; | |||
|
|||
class CacheMap extends Map { | |||
constructor(options) { | |||
super(); | |||
this.options = extend(defaultOptions, options || {}); | |||
this._times = new Map(); | |||
} | |||
|
|||
set(key, value) { | |||
super.set(key, value); | |||
this._times.set(key, Date.now()); | |||
} | |||
|
|||
get(key) { | |||
removeExpired(this, this._times, this.options.maxAge); | |||
return super.get(key); | |||
} | |||
|
|||
delete(key) { | |||
this._times.delete(key); | |||
return super.delete(key); | |||
} | |||
|
|||
clear() { | |||
this._times.clear(); | |||
return super.clear(); | |||
} | |||
|
|||
keys() { | |||
removeExpired(this, this._times, this.options.maxAge); | |||
return super.keys(); | |||
} | |||
|
|||
values() { | |||
removeExpired(this, this._times, this.options.maxAge); | |||
return super.values(); | |||
} | |||
|
|||
entries() { | |||
removeExpired(this, this._times, this.options.maxAge); | |||
return super.entries(); | |||
} | |||
}; | |||
|
|||
module.exports = CacheMap; | |||
|
|||
function removeExpired(storage, times, maxAge) { | |||
var expTime = Date.now() - maxAge; | |||
times.forEach((v, k) => { | |||
if (v < expTime) { | |||
storage.delete(k); | |||
times.delete(k); | |||
} | |||
}); | |||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,131 @@ | |||
/** | |||
* A project config reader for LiveStyle, mostly for fetching global deps. | |||
* Reads data from `livestyle.json` file found in one of the top folder of given | |||
* file. | |||
*/ | |||
'use strict'; | |||
|
|||
const fs = require('fs'); | |||
const path = require('path'); | |||
const glob = require('glob'); | |||
const extend = require('xtend'); | |||
const CacheMap = require('./cache-map'); | |||
|
|||
const configFiles = ['livestyle.json', '.livestyle.json']; | |||
const configCache = new CacheMap({maxAge: 5000}); | |||
const lookupCache = new CacheMap({maxAge: 30000}); | |||
|
|||
module.exports = function(file) { | |||
let dir = path.dirname(file); | |||
if (!lookupCache.get(dir)) { | |||
lookupCache.set(dir, findConfigFile(dir)); | |||
} | |||
|
|||
return lookupCache.get(dir) | |||
.then(configFile => { | |||
if (!configCache.get(configFile)) { | |||
configCache.set(configFile, readConfig(configFile)); | |||
} | |||
return configCache.get(configFile); | |||
}) | |||
}; | |||
|
|||
// for unit-tesing | |||
module.exports._caches = () => ({configCache, lookupCache}); | |||
|
|||
/** | |||
* Find config for given `dir`, moving upward the file structure | |||
* @param {String} dir Initial dir to start searching | |||
* @return {Promise} | |||
*/ | |||
function findConfigFile(dir) { | |||
return new Promise((resolve, reject) => { | |||
let next = (d) => { | |||
if (!d) { | |||
return reject(error('ENOCONFIG', `No config found for ${dir} path`)); | |||
} | |||
|
|||
fs.readdir(d, (err, items) => { | |||
if (err) { | |||
return reject(err); | |||
} | |||
|
|||
let found = configFiles.filter(f => items.indexOf(f) !== -1); | |||
if (found.length) { | |||
return resolve(path.resolve(d, found[0])); | |||
} | |||
|
|||
let nextDir = path.dirname(d); | |||
next(nextDir && nextDir !== d ? nextDir : null); | |||
}); | |||
}; | |||
|
|||
next(dir); | |||
}); | |||
} | |||
|
|||
/** | |||
* Reads given config file into a final config data | |||
* @param {String} file Path to config file | |||
* @return {Promise} | |||
*/ | |||
function readConfig(file) { | |||
return new Promise((resolve, reject) => { | |||
fs.readFile(file, 'utf8', (err, content) => { | |||
if (err) { | |||
return reject(error(err.code, `Unable to read ${file}: ${err.message}`)); | |||
} | |||
|
|||
try { | |||
content = JSON.parse(content) || {}; | |||
} catch(e) { | |||
return reject(error('EINVALIDJSON', `Unable to parse ${file}: ${e.message}`)); | |||
} | |||
|
|||
readDeps(file, content).then(resolve, reject); | |||
}); | |||
}); | |||
} | |||
|
|||
function readDeps(file, config) { | |||
var globals = config.globals; | |||
if (!config.globals) { | |||
return Promise.resolve(extend(config, {globals: []})); | |||
} | |||
|
|||
return globAllDeps(globals, path.dirname(file)) | |||
.then(globals => extend(config, {globals})); | |||
} | |||
|
|||
function globDeps(pattern, cwd) { | |||
return new Promise((resolve, reject) => { | |||
glob(pattern, {cwd}, (err, items) => resolve(items || [])); | |||
}); | |||
} | |||
|
|||
function globAllDeps(patters, cwd) { | |||
if (typeof patters === 'string') { | |||
return globDeps(patters, cwd); | |||
} | |||
|
|||
if (Array.isArray(patters)) { | |||
return Promise.all(patters.map(p => globDeps(p, cwd))) | |||
.then(values => values | |||
.reduce((r, v) => r.concat(v), []) | |||
.filter(unique) | |||
.map(v => path.resolve(cwd, v)) | |||
); | |||
} | |||
|
|||
return Promise.reject(new Error('Unknown pattern type for global dependencies')); | |||
} | |||
|
|||
function error(code, message) { | |||
let err = new Error(message || code); | |||
err.code = code; | |||
return err; | |||
} | |||
|
|||
function unique(value, i, array) { | |||
return array.indexOf(value) === i; | |||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,64 @@ | |||
'use strict'; | |||
|
|||
const assert = require('assert'); | |||
const CacheMap = require('../lib/cache-map'); | |||
|
|||
describe('Cache Map', () => { | |||
it('create & auto-remove', done => { | |||
let cache = new CacheMap({maxAge: 100}); | |||
|
|||
cache.set('k1', 'v1'); | |||
cache.set('k2', 'v2'); | |||
|
|||
assert.equal(cache.get('k1'), 'v1'); | |||
assert.equal(cache.get('k2'), 'v2'); | |||
assert.deepEqual(getKeys(cache), ['k1', 'k2']); | |||
|
|||
setTimeout(() => { | |||
cache.set('k2', 'v2-2'); | |||
cache.set('k3', 'v3'); | |||
assert.deepEqual(getKeys(cache), ['k1', 'k2', 'k3']); | |||
}, 50); | |||
|
|||
setTimeout(() => { | |||
// `k1` should be removed here | |||
assert.deepEqual(getKeys(cache), ['k2', 'k3']); | |||
assert.equal(cache.get('k1'), undefined); | |||
assert.equal(cache.get('k2'), 'v2-2'); | |||
assert.equal(cache.get('k3'), 'v3'); | |||
}, 110); | |||
|
|||
setTimeout(() => { | |||
assert.deepEqual(getKeys(cache), []); | |||
assert.equal(cache.get('k1'), undefined); | |||
assert.equal(cache.get('k2'), undefined); | |||
assert.equal(cache.get('k3'), undefined); | |||
|
|||
done(); | |||
}, 200); | |||
}); | |||
|
|||
it('sync times and values', () => { | |||
let cache = new CacheMap({maxAge: 100}); | |||
cache.set('k1', 'v1'); | |||
cache.set('k2', 'v2'); | |||
|
|||
assert.deepEqual(getKeys(cache), ['k1', 'k2']); | |||
|
|||
cache.delete('k1'); | |||
assert.deepEqual(getKeys(cache), ['k2']); | |||
assert.deepEqual(getKeys(cache._times), ['k2']); | |||
|
|||
cache.clear(); | |||
assert.deepEqual(getKeys(cache), []); | |||
assert.deepEqual(getKeys(cache._times), []); | |||
}); | |||
}); | |||
|
|||
function getKeys(map) { | |||
let keys = []; | |||
for (let k of map.keys()) { | |||
keys.push(k); | |||
} | |||
return keys; | |||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1 @@ | |||
.foo {padding: 10px;} |
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,3 @@ | |||
{ | |||
"globals": ["d1.scss", "deps/*.scss"] | |||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,3 @@ | |||
{ | |||
"globals": ["d1.scss", "deps/*.scss"] | |||
} |
Empty file.
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,3 @@ | |||
{ | |||
globals: ["d1.scss", "deps/*.scss"] | |||
} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,3 @@ | |||
{ | |||
"globals": ["d1.scss", "deps/*.scss"] | |||
} |
Oops, something went wrong.