Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@flourish/interpreter

Package Overview
Dependencies
Maintainers
10
Versions
33
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@flourish/interpreter - npm Package Compare versions

Comparing version 0.2.1 to 1.0.0

README.md

2

package.json
{
"name": "@flourish/interpreter",
"version": "0.2.1",
"version": "1.0.0",
"description": "Does a best guess at the type of data supplied",

@@ -5,0 +5,0 @@ "main": "src/index.js",

@@ -0,1 +1,9 @@

# 1.0.0
* Made createInterpreter the default export.
* Optionally specify allowed types in interpreter creation.
* Methods for specifying how many failures are allowed and how many different failing values.
* Optionally (on by default) sort by descending failure fraction (with string always last).
* Max number of array elements to interpret now set at method of interpreter.
* Drop coordinate types. Intend to resurrect as properties or methods of number types in the future.
# 0.2.1

@@ -13,2 +21,2 @@ * Fix 1PM format (previously it would render as 01 PM which was not the intended behaviour)

* Improve date handling
* Add longitude and latitude types
* Add coordinate types

@@ -8,3 +8,3 @@ import nodeResolve from "rollup-plugin-node-resolve";

format: "umd",
name: "interpreter"
name: "createInterpreter"
},

@@ -11,0 +11,0 @@ plugins: [

@@ -1,36 +0,111 @@

import { datetime_interpreters } from "./types/datetime";
import { number_interpreters } from "./types/number";
import { string_interpreters } from "./types/string";
import { coordinate_interpreters } from "./types/coordinate";
import { datetime_interpretations } from "./types/datetime";
import { number_interpretations } from "./types/number";
import { string_interpretation } from "./types/string";
function createParserTest(val) { return function(interpreter) { return interpreter.test(val); }; }
var INTERPRETATION_OPTIONS = Object.freeze({
datetime: datetime_interpretations,
number: number_interpretations,
});
function _createAccessorFunction(arr, accessor) {
if (accessor === undefined) return function(index) { return arr[index]; };
if (typeof accessor === "function") return function(index) { return accessor(arr[index], index); };
return function(index) { return arr[index]["" + accessor]; };
var DEFAULT_INTERPRETATIONS_ARRAY = Object.freeze([ "datetime", "number", "string" ]);
var DEFAULT_OPTIONS = Object.freeze({
n_max: 250,
n_failing_values: 0,
failure_fraction: 5 / 100,
sort: true
});
var OPTION_KEYS = Object.freeze(Object.keys(DEFAULT_OPTIONS));
function snakeToCamel(snake_string) {
return snake_string.replace(/_(\w)/g, function(match, capture) {
return capture.toUpperCase();
});
}
function createInterpreter(n_max) {
n_max = n_max > 0 ? n_max : 1000;
function noSort(a, b) { return a.index - b.index; }
function sortBySuccess(a, b) { return b.n_success - a.n_success || noSort(a, b); }
return function(arr, accessor) {
if (!Array.isArray(arr)) return undefined;
if (!arr.length) return string_interpreters.slice();
var n = Math.min(n_max, arr.length);
var getVal = _createAccessorFunction(arr, accessor);
var interpreters = datetime_interpreters.concat(number_interpreters, coordinate_interpreters);
function createAccessorFunction(accessor) {
if (accessor === undefined) return function(value) { return value; };
if (typeof accessor === "function") return function(value, index) { return accessor(value, index); };
return function(value) { return value["" + accessor]; };
}
for (var i = 0; i < n && interpreters.length; i++) {
var val = getVal(i);
if (val) interpreters = interpreters.filter(createParserTest(val));
}
return interpreters.concat(string_interpreters);
function createInterpreter(interpretations_array) {
if (!interpretations_array) interpretations_array = DEFAULT_INTERPRETATIONS_ARRAY;
else if (!Array.isArray(interpretations_array)) interpretations_array = [interpretations_array];
var interpretations = interpretations_array.reduce(function(arr, interp_string) {
var interps = INTERPRETATION_OPTIONS[interp_string];
if (interps) Array.prototype.push.apply(arr, interps);
return arr;
}, []);
var include_string = interpretations_array.indexOf("string" !== -1);
var options = OPTION_KEYS.reduce(function(obj, key) {
obj[key] = DEFAULT_OPTIONS[key];
return obj;
}, {});
var interpreter = function(input_array, accessor) {
accessor = createAccessorFunction(accessor);
var data = input_array.map(accessor).filter(function(x) { return x; });
var n = Math.min(options.n_max, data.length);
var n_max_failure = Math.floor(n * options.failure_fraction);
var n_failing_values = options.n_failing_values;
var sortMethod = options.sort ? sortBySuccess : noSort;
var valid_interpreters = interpretations.slice()
.reduce(function(keep, interp, index) {
var n_fail = i = 0;
var failing_values = [];
var complete_failure = false;
for (var i = 0; i < n; i++) {
var val = data[i];
var is_valid = interp.test(val);
if (is_valid) continue;
if (++n_fail > n_max_failure) complete_failure = true;
else if (failing_values.indexOf(val) === -1) {
failing_values.push(val);
if (failing_values.length > n_failing_values) complete_failure = true;
}
if (complete_failure) break;
}
if (!complete_failure) keep.push({ interp: interp, n_success: n - n_fail, index: index });
return keep;
}, [])
.sort(sortMethod)
.map(function(valid) {
return valid.interp;
});
if (include_string) valid_interpreters.push(string_interpretation);
return valid_interpreters;
};
OPTION_KEYS.forEach(function(option) {
interpreter[snakeToCamel(option)] = function(value) {
if (value === undefined) return options[option];
options[option] = value;
return interpreter;
};
});
return interpreter;
}
export { createInterpreter, _createAccessorFunction };
createInterpreter._createAccessorFunction = createAccessorFunction;
export default createInterpreter;
import { timeFormat, timeParse } from "d3-time-format";
function createDatetimeInterpreter(format_string, secondaryTest) {
function createDatetimeInterpretation(format_string, secondaryTest) {
var parser = timeParse(format_string);

@@ -23,32 +24,33 @@ var formatter = timeFormat(format_string);

var datetime_interpreters = Object.freeze([
createDatetimeInterpreter("%Y-%m-%dT%H:%M:%S.%LZ"),
createDatetimeInterpreter("%Y-%m-%d"),
createDatetimeInterpreter("%m/%d/%Y", function(str) {
var datetime_interpretations = Object.freeze([
createDatetimeInterpretation("%Y-%m-%dT%H:%M:%S.%LZ"),
createDatetimeInterpretation("%Y-%m-%d"),
createDatetimeInterpretation("%m/%d/%Y", function(str) {
var arr = str.split("/").map(parseFloat);
return (arr[0] > 0 && arr[0] <= 12) && (arr[1] > 0 && arr[1] <= 31) && (!isNaN(arr[2]));
}),
createDatetimeInterpreter("%d/%m/%Y", function(str) {
createDatetimeInterpretation("%d/%m/%Y", function(str) {
var arr = str.split("/").map(parseFloat);
return (arr[0] > 0 && arr[0] <= 31) && (arr[1] > 0 && arr[1] <= 12) && (!isNaN(arr[2]));
}),
createDatetimeInterpreter("%d-%b-%y"),
createDatetimeInterpreter("%m/%y"),
createDatetimeInterpreter("%m/%Y"),
createDatetimeInterpreter("%b %Y"),
createDatetimeInterpreter("%B %d"),
createDatetimeInterpreter("%d %b"),
createDatetimeInterpreter("%Y", function(str) {
createDatetimeInterpretation("%d-%b-%y"),
createDatetimeInterpretation("%m/%y"),
createDatetimeInterpretation("%m/%Y"),
createDatetimeInterpretation("%b %Y"),
createDatetimeInterpretation("%B %d"),
createDatetimeInterpretation("%d %b"),
createDatetimeInterpretation("%Y", function(str) {
var val = parseFloat(str);
return val > 1499 && val < 2200;
}),
createDatetimeInterpreter("%B"),
createDatetimeInterpreter("%b"),
createDatetimeInterpreter("%X"),
createDatetimeInterpreter("%I:%M %p"),
createDatetimeInterpreter("%-I%p"),
createDatetimeInterpreter("%H:%M")
createDatetimeInterpretation("%B"),
createDatetimeInterpretation("%b"),
createDatetimeInterpretation("%X"),
createDatetimeInterpretation("%I:%M %p"),
createDatetimeInterpretation("%-I%p"),
createDatetimeInterpretation("%H:%M")
]);
export { datetime_interpreters };
export { datetime_interpretations };

@@ -79,3 +79,3 @@ // https://stackoverflow.com/a/16148273

parse: space_point.parse,
description: "Optional comma separator, point decimal mark",
description: "Optional space separator, point decimal mark",
thousand_separator: ",",

@@ -86,4 +86,5 @@ decimal_mark: "."

var number_interpreters = [
var number_interpretations = Object.freeze([
comma_point,
space_point,
point_comma,

@@ -97,12 +98,10 @@ space_comma,

opt_space_point
];
]);
number_interpreters.forEach(function(interpreter) {
interpreter.type = "number";
Object.freeze(interpreter);
number_interpretations.forEach(function(interp) {
interp.type = "number";
Object.freeze(interp);
});
Object.freeze(number_interpreters);
export { number_interpreters };
export { number_interpretations };

@@ -1,12 +0,10 @@

var string_interpreters = Object.freeze([
Object.freeze({
test: function() { return true; },
parse: function(str) { return str; },
type: "string",
stringToString: function(str) { return str; },
description: "Arbitrary string"
})
]);
var string_interpretation = Object.freeze({
test: function() { return true; },
parse: function(str) { return str; },
type: "string",
stringToString: function(str) { return str; },
description: "Arbitrary string"
});
export { string_interpreters };
export { string_interpretation };
const { expect } = require("chai");
const { createInterpreter, _createAccessorFunction } = require("../interpreter");
const createInterpreter = require("../interpreter");
const _createAccessorFunction = createInterpreter._createAccessorFunction;
const string_data = [ "a", "b", "c", "d"];

@@ -10,85 +12,100 @@ const object_data = [ { letter: "a" }, { letter: "b" }, { letter: "c" }, { letter: "d" }];

describe("_createAccessorFunction", () => {
describe("when an array of strings is the first argument", () => {
describe("and there is no second argument", () => {
it("should return function that can be used to create a copy of the array", () => {
const f = _createAccessorFunction(string_data);
expect(string_data.map((d,i) => f(i))).to.deep.equal(string_data);
});
describe("when no accessor argument is passed in", () => {
it("should return function that can be used to create a copy of the array", () => {
const f = _createAccessorFunction();
expect(string_data.map(f)).to.deep.equal(string_data);
});
});
describe("when an array of objects is the first argument", () => {
describe("and the second argument specifies a key in those objects", () => {
it("should return function that can be used to create an array of values corresponding to that key", () => {
const f = _createAccessorFunction(object_data, "letter");
expect(object_data.map((d,i) => f(i))).to.deep.equal(string_data);
});
describe("when a string accessor is specified", () => {
it("should return function that can be used to create an array of values from an array of objects with that key", () => {
const f = _createAccessorFunction("letter");
expect(object_data.map(f)).to.deep.equal(string_data);
});
});
describe("and the second argument specifies a function that returns a property of each object", () => {
it("should return function that can be used to create an array of values corresponding to that key", () => {
const f = _createAccessorFunction(object_data, d => d.letter);
expect(object_data.map((d,i) => f(i))).to.deep.equal(string_data);
});
describe("when a function accessor is specified", () => {
it("should return function that behaves the same as the accessor function (with two arguments)", () => {
let f = _createAccessorFunction(d => d.letter);
expect(object_data.map(f)).to.deep.equal(string_data);
f = _createAccessorFunction((d, i) => d[i]);
expect(array_data.map(f)).to.deep.equal(string_data);
});
});
describe("and the second argument is not undefined, a function, a string or a number", () => {
it("should do string conversion and proceed as if that string was the accessor", () => {
let f = _createAccessorFunction(object_data, null);
expect(object_data.map((d,i) => f(i))).to.deep.equal([ undefined, undefined, undefined, undefined ]);
var null_object_array = [ { "null": 7 } ]
f = _createAccessorFunction(null_object_array, null);
expect(null_object_array.map((d,i) => f(i))).to.deep.equal([ 7 ]);
});
describe("when an accessor is defined that is not a function or a string", () => {
it("should do string conversion and proceed as if that string was the accessor", () => {
let f = _createAccessorFunction(null);
expect(object_data.map(f)).to.deep.equal([ undefined, undefined, undefined, undefined ]);
f = _createAccessorFunction(null);
expect([ { "null": 7 } ].map(f)).to.deep.equal([ 7 ]);
f = _createAccessorFunction(0);
expect(array_data.map(f)).to.deep.equal([ "a", "a", "a", "a" ]);
});
});
});
describe("when an array of arrays is the first argument", () => {
describe("and the second argument is a number", () => {
it("should return function that can be used to create an array of values corresponding to the value at that index in each array", () => {
const f = _createAccessorFunction(array_data, 0);
expect(array_data.map((d,i) => f(i))).to.deep.equal([ "a", "a", "a", "a" ]);
});
});
describe("and the second argument is a function that uses indexes to access properties", () => {
it("should return function that can be used to create an array of values corresponding to increasing indexes in each array", () => {
const f = _createAccessorFunction(array_data, (d, i) => d[i]);
expect(array_data.map((d,i) => f(i))).to.deep.equal(string_data);
});
});
describe("interpeter", () => {
it("should correctly interpret and format well-formatted datetimes", () => {
const interpret = createInterpreter();
const checkMatch = function(arr, description) {
const interpretation = interpret(arr)[0];
if (interpretation.type !== "datetime" || interpretation.description !== description) return false;
var interpreted = arr.map(interpretation.stringToString);
if (arr.length !== interpreted.length) return false;
return interpreted.every((v, i) => v === arr[i]);
};
const DATE_STRINGS = ["2016-12-03T08:19:56.000Z", "1927-04-03T12:25:32.000Z", "1502-10-21T14:44:28.000Z"];
expect(checkMatch(DATE_STRINGS, "%Y-%m-%dT%H:%M:%S.%LZ")).to.equal(true);
expect(checkMatch(DATE_STRINGS.map(d => d.split("T")[0]), "%Y-%m-%d")).to.equal(true);
expect(checkMatch(["2016-12-03", "1927-04-03", "1502-10-21"], "%Y-%m-%d")).to.equal(true);
expect(checkMatch(["12/03/2016", "04/03/1927", "10/21/1502"], "%m/%d/%Y")).to.equal(true);
expect(checkMatch(["03/12/2016", "03/04/1927", "21/10/1502"], "%d/%m/%Y")).to.equal(true);
expect(checkMatch(["03-Dec-16", "03-Apr-27", "21-Oct-84"], "%d-%b-%y")).to.equal(true);
expect(checkMatch(["12/16", "04/27", "10/84"], "%m/%y")).to.equal(true);
expect(checkMatch(["12/2016", "04/1927", "10/1502"], "%m/%Y")).to.equal(true);
expect(checkMatch(["Dec 2016", "Apr 1927", "Oct 1502"], "%b %Y")).to.equal(true);
expect(checkMatch(["December 03", "April 12", "October 21"], "%B %d")).to.equal(true);
expect(checkMatch(["03 Dec", "03 Apr", "21 Oct"], "%d %b")).to.equal(true);
expect(checkMatch(["2016", "1927", "1502"], "%Y")).to.equal(true);
expect(checkMatch(["December", "April", "October"], "%B")).to.equal(true);
expect(checkMatch(["Dec", "Apr", "Oct"], "%b")).to.equal(true);
expect(checkMatch(["8:19:56 AM", "12:25:32 PM", "2:44:28 PM"], "%X")).to.equal(true);
expect(checkMatch(["08:19 AM", "12:25 PM", "02:44 PM"], "%I:%M %p")).to.equal(true);
expect(checkMatch(["8AM", "12PM", "2PM"], "%-I%p")).to.equal(true);
expect(checkMatch(["08:19", "12:25", "14:44"], "%H:%M")).to.equal(true);
});
it("should correctly interpret consistent numbers", () => {
const interpret = createInterpreter();
const checkMatch = function(arr, description) {
const interpretation = interpret(arr)[0];
return interpretation.type === "number" && interpretation.description === description;
};
expect(checkMatch(["-787", "1,246.83", "203", "1,340,201"], "Comma thousand separator, point decimal mark")).to.equal(true);
expect(checkMatch(["-787", "1 246.83", "203", "1 340 201"], "Space thousand separator, point decimal mark")).to.equal(true);
expect(checkMatch(["-787", "1246.83", "203", "1340201"], "No thousand separator, point decimal mark")).to.equal(true);
expect(checkMatch(["-787", "1.246,83", "203", "1.340.201"], "Point thousand separator, comma decimal mark")).to.equal(true);
expect(checkMatch(["-787", "1 246,83", "203", "1 340 201"], "Space thousand separator, comma decimal mark")).to.equal(true);
expect(checkMatch(["-787", "1246,83", "203", "1340201"], "No thousand separator, comma decimal mark")).to.equal(true);
expect(checkMatch(["-787", "1246.83", "203", "1,340,201"], "Optional comma separator, point decimal mark")).to.equal(true);
expect(checkMatch(["-787", "1246,83", "203", "1.340.201"], "Optional point thousand separator, comma decimal mark")).to.equal(true);
expect(checkMatch(["-787", "1246,83", "203", "1 340 201"], "Optional space thousand separator, comma decimal mark")).to.equal(true);
expect(checkMatch(["-787", "1246.83", "203", "1 340 201"], "Optional space separator, point decimal mark")).to.equal(true);
});
describe("interpeter", () => {
it("should correctly interpret and format well-formatted datetimes", () => {
const interpret = createInterpreter();
const checkMatch = function(arr, description) {
const interpretation = interpret(arr)[0];
if (interpretation.type !== "datetime" || interpretation.description !== description) return false;
var interpreted = arr.map(interpretation.stringToString);
if (arr.length !== interpreted.length) return false;
return interpreted.every((v, i) => v === arr[i]);
};
const DATE_STRINGS = ["2016-12-03T08:19:56.000Z", "1927-04-03T12:25:32.000Z", "1502-10-21T14:44:28.000Z"];
expect(checkMatch(DATE_STRINGS, "%Y-%m-%dT%H:%M:%S.%LZ")).to.equal(true);
expect(checkMatch(DATE_STRINGS.map(d => d.split("T")[0]), "%Y-%m-%d")).to.equal(true);
expect(checkMatch(["2016-12-03", "1927-04-03", "1502-10-21"], "%Y-%m-%d")).to.equal(true);
expect(checkMatch(["12/03/2016", "04/03/1927", "10/21/1502"], "%m/%d/%Y")).to.equal(true);
expect(checkMatch(["03/12/2016", "03/04/1927", "21/10/1502"], "%d/%m/%Y")).to.equal(true);
expect(checkMatch(["03-Dec-16", "03-Apr-27", "21-Oct-84"], "%d-%b-%y")).to.equal(true);
expect(checkMatch(["12/16", "04/27", "10/84"], "%m/%y")).to.equal(true);
expect(checkMatch(["12/2016", "04/1927", "10/1502"], "%m/%Y")).to.equal(true);
expect(checkMatch(["Dec 2016", "Apr 1927", "Oct 1502"], "%b %Y")).to.equal(true);
expect(checkMatch(["December 03", "April 12", "October 21"], "%B %d")).to.equal(true);
expect(checkMatch(["03 Dec", "03 Apr", "21 Oct"], "%d %b")).to.equal(true);
expect(checkMatch(["2016", "1927", "1502"], "%Y")).to.equal(true);
expect(checkMatch(["December", "April", "October"], "%B")).to.equal(true);
expect(checkMatch(["Dec", "Apr", "Oct"], "%b")).to.equal(true);
expect(checkMatch(["8:19:56 AM", "12:25:32 PM", "2:44:28 PM"], "%X")).to.equal(true);
expect(checkMatch(["08:19 AM", "12:25 PM", "02:44 PM"], "%I:%M %p")).to.equal(true);
expect(checkMatch(["8AM", "12PM", "2PM"], "%-I%p")).to.equal(true);
expect(checkMatch(["08:19", "12:25", "14:44"], "%H:%M")).to.equal(true);
});
it("should have option get/set methods with camelCase names", () => {
const interpret = createInterpreter();
const checkMethod = function(prop, default_value, new_value) {
var func = interpret[prop];
if (func() !== default_value) return false;
if (func(new_value) !== interpret) return false;
return func() === new_value;
};
expect(checkMethod("nMax", 250, 100)).to.equal(true);
expect(checkMethod("nFailingValues", 0, 1)).to.equal(true);
expect(checkMethod("failureFraction", 5 / 100, 0.2)).to.equal(true);
expect(checkMethod("sort", true, false)).to.equal(true);
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc