Comparing version 0.0.4 to 0.0.5
'use strict'; | ||
const uniq = require('lodash.uniq'); | ||
const uniq = require('lodash/uniqWith'); | ||
const isEqual = require('lodash/isEqual'); | ||
@@ -8,3 +9,3 @@ const SweatMap = class SweatMap { | ||
obj = typeof obj == 'object' ? obj : {}; | ||
//Adhere to CSS class/id rules -> https://www.w3.org/TR/CSS2/syndata.html#characters | ||
@@ -16,6 +17,6 @@ //Does not yet work with escaped characters or ISO 10646 characters as a numeric code | ||
this.fmap = new Map(); | ||
//Map containing obfuscated string to original string values | ||
this.rmap = new Map(); | ||
//Available characters keyed by number of bytes | ||
@@ -135,3 +136,3 @@ this.characters = {}; | ||
}, obj.additional_ranges); | ||
//Add existing strings to map | ||
@@ -151,3 +152,3 @@ if(typeof obj.existing_strings == 'object') { | ||
return; | ||
for(let i = parseInt(Ranges[CR].start, 16); i <= parseInt(Ranges[CR].end, 16); i++) { | ||
@@ -167,3 +168,3 @@ try { | ||
}); | ||
//Removes duplicate chars in the array then freeze to ensure nothing is changed | ||
@@ -174,6 +175,6 @@ Object.keys(this.characters).forEach(C => { | ||
}); | ||
Object.freeze(this.characters); | ||
} | ||
bytes(str) { | ||
@@ -183,3 +184,3 @@ //Determine how many bytes are in a given utf8 string -> https://gist.github.com/mathiasbynens/1010324 | ||
} | ||
cssSafeString(str) { | ||
@@ -190,6 +191,44 @@ //https://www.w3.org/TR/CSS2/syndata.html#characters | ||
return false; | ||
return true; | ||
} | ||
size() { | ||
return this.fmap.size && this.rmap.size; | ||
} | ||
generatePatternForBytes(bytes) { | ||
// Find all the different possible combinations of sums | ||
const calculateSumCombinations = (n) => { | ||
const possibleSums = []; | ||
let subset = []; | ||
// Our subset sum function for finding possible sums combinations for our target value | ||
const subsetSum = (subset, target, partial = []) => { | ||
let s = partial.reduce((a, b) => a + b, 0); | ||
if (s === target) possibleSums.push(partial); | ||
else if (s >= target) return; | ||
subset.forEach(num => subsetSum(subset.slice(1), target, partial.concat(num))); | ||
}; | ||
// Create our subset | ||
for (let i = 1; i <= n; i++) { | ||
// Adding max duplicates of each number to simplify the logic | ||
// It also has the benefit of not needing to calculate permutations separately once we remove duplicates | ||
subset = subset.concat(new Array(n).fill(i)); | ||
} | ||
subsetSum(subset, n); | ||
return possibleSums; | ||
}; | ||
return uniq(calculateSumCombinations(bytes), isEqual) | ||
.map(pattern => pattern | ||
.map(val => this.characters[`${val}`]) | ||
); | ||
} | ||
set(key) { | ||
@@ -199,3 +238,3 @@ //If it's already been done, don't do it again! | ||
return this.fmap.get(key); | ||
//If the key is not a string, throw an error | ||
@@ -205,24 +244,2 @@ if(typeof key != 'string' || key === '') | ||
const getPatterns = () => { | ||
//Hard Coded For Now | ||
if(bytes === 1) { | ||
return [ | ||
[this.characters['1']] //A | ||
]; | ||
} else if(bytes == 2) { | ||
return [ | ||
[this.characters['1'], this.characters['1']], //AA | ||
[this.characters['2']] //B | ||
]; | ||
} else if(bytes == 3) { | ||
return [ | ||
[this.characters['1'], this.characters['1'], this.characters['1']], //AAA | ||
[this.characters['2'], this.characters['1']], //BA | ||
[this.characters['1'], this.characters['2']], //AB | ||
[this.characters['3']] //C | ||
]; | ||
} | ||
// | ||
}; | ||
const isGoodValue = value => { | ||
@@ -234,3 +251,3 @@ if(this.cssSafe) | ||
}; | ||
var bytes = 0, //Determines what patterns to try | ||
@@ -244,4 +261,4 @@ value; //The obfuscated UTF-8 String | ||
//Get all possible patterns | ||
let patterns = getPatterns(); | ||
let patterns = this.generatePatternForBytes(bytes); | ||
//Which pattern are we are currently trying | ||
@@ -253,6 +270,6 @@ let patternsCounter = 0; | ||
let currentPattern = patterns[patternsCounter]; | ||
//Array of counters, one for each charlist | ||
let currentPatternCounters = currentPattern.map(() => 0); | ||
//Last possible match | ||
@@ -270,10 +287,10 @@ let lastMatch = currentPattern.map(charlist => charlist[charlist.length-1]).join(''); | ||
}); | ||
//Increment Counters | ||
for(let i = 0, ln = currentPatternCounters.length; i < ln; i++) { | ||
currentPatternCounters[i]++; | ||
//If the current counter is less than or equal to the total number of chars in this charlist, you don't need to increment anymore | ||
if(currentPatternCounters[i] <= currentPattern[i].length) { | ||
//If it's equal and there are more charlists | ||
@@ -284,3 +301,3 @@ if(currentPatternCounters[i] == currentPattern[i].length && i < ln-1) { | ||
} | ||
break; | ||
@@ -302,6 +319,6 @@ } | ||
} | ||
delete(key) { | ||
const value = this.fmap.get(key); | ||
if(value !== undefined) { | ||
@@ -312,3 +329,3 @@ this.fmap.delete(key); | ||
} | ||
clear() { | ||
@@ -318,15 +335,15 @@ this.fmap.clear(); | ||
} | ||
entries() { | ||
return this.fmap.entries(); | ||
} | ||
get(key) { | ||
return this.fmap.get(key); | ||
} | ||
get_obfuscated(value) { | ||
return this.rmap.get(value); | ||
} | ||
has(key) { | ||
@@ -338,3 +355,3 @@ if(!key) | ||
} | ||
has_obfuscated(value) { | ||
@@ -341,0 +358,0 @@ if(!value) |
{ | ||
"name": "sweatmap", | ||
"description": "SweatMap takes in a series of UTF-8 strings and maps them to UTF-8 strings that are as small as possible while still being unique.", | ||
"version": "0.0.4", | ||
"version": "0.0.5", | ||
"author": { | ||
@@ -34,12 +34,12 @@ "name": "Benjamin Solum", | ||
"dependencies": { | ||
"lodash.uniq": "^4.2.0" | ||
"lodash": "^4.17.4" | ||
}, | ||
"devDependencies": { | ||
"babel-core": "^6.7.4", | ||
"babel-loader": "^6.2.4", | ||
"babel-preset-es2015": "^6.6.0", | ||
"babel-loader": "^6.2.10", | ||
"babel-preset-es2015": "^6.18.0", | ||
"eslint": "^2.5.3", | ||
"mocha": "^2.4.5", | ||
"webpack": "^1.12.14" | ||
"webpack": "^1.14.0" | ||
} | ||
} |
@@ -8,5 +8,26 @@ #SweatMap | ||
##Installation | ||
Coming soon. | ||
`npm install sweatmap --save` | ||
##How to use | ||
Coming soon, but source is pretty simple to read. | ||
*SweatMap uses a few ES6 features that may not be present in your Node/Browser depending on the version.* | ||
These include: `Array.fill`, `Object.assign`, `Object.freeze`, and `Object.keys` | ||
* **Constructor(obj):** | ||
*Takes in an object with up to three optional properties:* | ||
* *cssSafe [default `false`]:* `true` disallows characters that aren't safe for [CSS Identifiers](https://www.w3.org/TR/CSS2/syndata.html#characters). | ||
* *additional_ranges [[`object`](https://github.com/soluml/SweatMap/blob/master/node/sweatmap.js#L27)]:* An object where the key is a "range name" and the value is an object with a start character point and an end character point. You can set null to either start or end to remove a character range. | ||
* *existing_strings [default `{}`]:* Pass in an object of strings that you don't want changed. Key is the original name, value is the name that should be used. | ||
* **bytes(string):** Returns a byte count of the string passed in. | ||
* **size():** Returns number of entries in the map. | ||
* **cssSafeString(string):** Determines if the string is a safe [CSS Identifier](https://www.w3.org/TR/CSS2/syndata.html#characters). | ||
* **set(string):** Returns an obfuscated UTF-8 string that's unique to all strings in the map. | ||
* **delete(key):** Removes the string from the map. | ||
* **clear():** Empties the map. | ||
* **clear():** Empties the map. | ||
* **entries():** Returns a new Iterator object that contains the [key, value] pairs for each element in the Map object in insertion order. | ||
* **get(key):** Returns the value for a given key. | ||
* **get_obfuscated(value):** Returns the key for a given value. | ||
* **has(key):** Returns true/false if a key exists in the map. | ||
* **has_obfuscated(value):** Returns true/false if a value exists in the map. |
195
test/test.js
@@ -11,3 +11,3 @@ 'use strict'; | ||
var myMap = new SweatMap(); | ||
assert.equal(myMap.characters['1'].length, 96); | ||
@@ -17,3 +17,3 @@ assert.equal(myMap.characters['2'].length, 1776); | ||
}); | ||
it('Can pass in existing UTF-8 String key/values:', function() { | ||
@@ -35,3 +35,3 @@ var myMap = new SweatMap({ | ||
}); | ||
it('Can pass in adjusted character ranges:', function() { | ||
@@ -51,3 +51,3 @@ var myMap = new SweatMap({ | ||
}); | ||
it('Character ranges are frozen:', function() { | ||
@@ -61,3 +61,3 @@ var myMap = new SweatMap(); | ||
}); | ||
it('No default entries:', function() { | ||
@@ -69,3 +69,3 @@ var myMap = new SweatMap(); | ||
}); | ||
it('Can set CSS Safe Mode:', function() { | ||
@@ -77,5 +77,5 @@ var myMap = new SweatMap({ cssSafe: true }); | ||
}); | ||
describe('bytes(str)', function () { | ||
it('Returns bytes for a given string:', function () { | ||
@@ -91,7 +91,23 @@ var myMap = new SweatMap(); | ||
}); | ||
}); | ||
describe('size(str)', function () { | ||
it('Returns size of map:', function () { | ||
var myMap = new SweatMap(); | ||
assert.equal(myMap.size(), 0); | ||
myMap.set('aA'); | ||
myMap.set('×'); | ||
myMap.set('×ø×ø!'); | ||
myMap.set('☃'); | ||
myMap.set('�bb☃'); | ||
assert.equal(myMap.size(), 5); | ||
}); | ||
}); | ||
describe('cssSafeString(str)', function () { | ||
it('Determines if a string is a valid css identifier (e.g. class/id):', function () { | ||
@@ -110,9 +126,60 @@ var myMap = new SweatMap(); | ||
}); | ||
}); | ||
describe('generatePatternForBytes(bytes)', function () { | ||
var myMap; | ||
var patternCountTest = function (bytes) { | ||
var expectedPatterns = Math.pow(2, bytes - 1); | ||
var patterns = myMap.generatePatternForBytes(bytes); | ||
var patternCount = 0; | ||
for (var i = 0; i < expectedPatterns; i++) { | ||
try { | ||
if (patterns[i].length > 0) { | ||
patternCount++; | ||
} | ||
} catch (err) { | ||
break; | ||
} | ||
} | ||
assert.equal(patternCount, expectedPatterns); | ||
}; | ||
beforeEach(function() { | ||
myMap = new SweatMap({ | ||
"additional_ranges": { | ||
"A-Z": { start: '41', end: '5A' }, //Add A-Z | ||
"a-z": { start: '61', end: '7A' }, //Add a-z | ||
"Basic Latin": { end: null } //Removes Basic Latin | ||
} | ||
}); | ||
}); | ||
it('Should generate proper number of patterns for 1 bytes', function () { | ||
patternCountTest(1); | ||
}); | ||
it('Should generate proper number of patterns for 2 bytes', function () { | ||
patternCountTest(2); | ||
}); | ||
it('Should generate proper number of patterns for 3 bytes', function () { | ||
patternCountTest(3); | ||
}); | ||
it('Should generate proper number of patterns for 4 bytes', function () { | ||
patternCountTest(4); | ||
}); | ||
it('Should generate proper number of patterns for 5 bytes', function () { | ||
patternCountTest(5); | ||
}); | ||
}); | ||
describe('set(key)', function () { | ||
var myMap; | ||
beforeEach(function() { | ||
@@ -134,3 +201,3 @@ myMap = new SweatMap({ | ||
}); | ||
it('Returns an error if the key is not a string:', function () { | ||
@@ -144,13 +211,13 @@ assert.throws(() => { myMap.set({}); }, Error, '{} is not a string'); | ||
}); | ||
it('Returns an error if the key is an empty string:', function () { | ||
assert.throws(() => { myMap.set(''); }, Error, '"" is an empty string'); | ||
}); | ||
it('Obfuscates the given keys correctly:', function () { | ||
//LONG Timeout -> 1min | ||
this.timeout(60000); | ||
var i, str; | ||
assert.equal(myMap.characters['1'].length, 52); | ||
@@ -162,3 +229,3 @@ assert.equal(myMap.characters['2'].length, 1776); | ||
str = myMap.set('string'+ i); | ||
if(i < 52) { //(52)[A] == 52 | ||
@@ -173,10 +240,10 @@ assert.equal(myMap.bytes(str), 1); | ||
}); | ||
it('Obfuscates the given keys correctly (CSS Safe):', function () { | ||
//LONG Timeout -> 1min | ||
this.timeout(60000); | ||
var i, str, myOtherMap = new SweatMap({ cssSafe: true }); | ||
assert.equal(myOtherMap.characters['1'].length, 96); | ||
@@ -186,6 +253,6 @@ assert.equal(myOtherMap.characters['2'].length, 1776); | ||
assert.equal(myOtherMap.cssSafe, true); | ||
for(i = 0; i < 5300; i++) { | ||
str = myOtherMap.set('string'+ i); | ||
if(i < 53) { // a-z(26) + A-Z(26) + _(1) | ||
@@ -202,11 +269,11 @@ assert.equal(myMap.bytes(str), 1); | ||
}); | ||
describe('delete(key)', function () { | ||
it('Deletes values from both fmap and rmap for a given key:', function () { | ||
var myMap = new SweatMap(); | ||
var astr = myMap.set('A-String'); | ||
var bstr = myMap.set('B-String'); | ||
assert.equal(myMap.fmap.has('A-String'), true); | ||
@@ -216,3 +283,3 @@ assert.equal(myMap.fmap.has('B-String'), true); | ||
assert.equal(myMap.rmap.has(bstr), true); | ||
myMap.delete('B-String'); | ||
@@ -223,3 +290,3 @@ assert.equal(myMap.fmap.has('A-String'), true); | ||
assert.equal(myMap.rmap.has(bstr), false); | ||
myMap.delete('A-String'); | ||
@@ -231,13 +298,13 @@ assert.equal(myMap.fmap.has('A-String'), false); | ||
}); | ||
}); | ||
describe('clear()', function () { | ||
it('Clears values from both fmap and rmap:', function () { | ||
var myMap = new SweatMap(); | ||
var astr = myMap.set('A-String'); | ||
var bstr = myMap.set('B-String'); | ||
assert.equal(myMap.fmap.has('A-String'), true); | ||
@@ -247,3 +314,3 @@ assert.equal(myMap.fmap.has('B-String'), true); | ||
assert.equal(myMap.rmap.has(bstr), true); | ||
myMap.clear(); | ||
@@ -255,10 +322,10 @@ assert.equal(myMap.fmap.has('A-String'), false); | ||
}); | ||
}); | ||
describe('entries()', function () { | ||
it('Returns a new Iterator object that contains an array of [key, value] for each element in the Map object in insertion order:', function () { | ||
var myMap = new SweatMap(); | ||
myMap.set('A-String'); | ||
@@ -269,58 +336,58 @@ myMap.set('B-String'); | ||
}); | ||
}); | ||
describe('get(key)', function () { | ||
it('Get values from the map:', function () { | ||
var myMap = new SweatMap(); | ||
var astr = myMap.set('A-String'); | ||
assert.equal(myMap.get('A-String'), astr); | ||
assert.equal(myMap.get('A-String'), myMap.fmap.get('A-String')); | ||
}); | ||
}); | ||
describe('get_obfuscated(value)', function () { | ||
it('Get keys from the map:', function () { | ||
var myMap = new SweatMap(); | ||
var astr = myMap.set('A-String'); | ||
assert.equal(myMap.get_obfuscated(astr), 'A-String'); | ||
assert.equal(myMap.get_obfuscated(astr), myMap.rmap.get(astr)); | ||
}); | ||
}); | ||
describe('has(key)', function () { | ||
it('Can see if it has a key in the map:', function () { | ||
var myMap = new SweatMap(); | ||
myMap.set('A-String'); | ||
assert.equal(myMap.has('A-String'), true); | ||
myMap.delete('A-String'); | ||
assert.equal(myMap.has('A-String'), false); | ||
}); | ||
}); | ||
describe('has_obfuscated(value)', function () { | ||
it('Can see if it has a value in the map:', function () { | ||
var myMap = new SweatMap(); | ||
var astr = myMap.set('A-String'); | ||
assert.equal(myMap.has_obfuscated(astr), true); | ||
myMap.delete('A-String'); | ||
assert.equal(myMap.has(astr), false); | ||
}); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
161843
1967
33
+ Addedlodash@^4.17.4
+ Addedlodash@4.17.21(transitive)
- Removedlodash.uniq@^4.2.0
- Removedlodash.uniq@4.5.0(transitive)