commit
message
[Utilities] Breaking out Utils.ts into class files
author
Ben Vogt <[email protected]>
date
2017-04-30 17:46:12
stats
16 file(s) changed,
1072 insertions(+),
1009 deletions(-)
files
src/Formulas/Date.ts
src/Formulas/Engineering.ts
src/Formulas/Financial.ts
src/Formulas/Logical.ts
src/Formulas/Math.ts
src/Formulas/Statistical.ts
src/Formulas/Text.ts
src/Formulas/Utils.ts
src/Utilities/ArgsChecker.ts
src/Utilities/CriteriaFunctionFactory.ts
src/Utilities/DateRegExBuilder.ts
src/Utilities/Filter.ts
src/Utilities/Serializer.ts
src/Utilities/TypeCaster.ts
tests/Formulas/DateFormulasTest.ts
tests/Formulas/EngineeringTest.ts
1diff --git a/src/Formulas/Date.ts b/src/Formulas/Date.ts
2index 37e2940..684fa43 100644
3--- a/src/Formulas/Date.ts
4+++ b/src/Formulas/Date.ts
5@@ -1,9 +1,11 @@
6 /// <reference path="../../node_modules/moment/moment.d.ts"/>
7 import * as moment from "moment";
8 import {
9- ArgsChecker,
10+ ArgsChecker
11+} from "../Utilities/ArgsChecker";
12+import {
13 TypeCaster
14-} from "./Utils";
15+} from "../Utilities/TypeCaster";
16 import {
17 NumError,
18 ValueError,
19diff --git a/src/Formulas/Engineering.ts b/src/Formulas/Engineering.ts
20index 72bbea5..59edbf7 100644
21--- a/src/Formulas/Engineering.ts
22+++ b/src/Formulas/Engineering.ts
23@@ -1,7 +1,9 @@
24 import {
25- ArgsChecker,
26+ ArgsChecker
27+} from "../Utilities/ArgsChecker";
28+import {
29 TypeCaster
30-} from "./Utils";
31+} from "../Utilities/TypeCaster";
32 import {
33 ValueError,
34 NumError
35diff --git a/src/Formulas/Financial.ts b/src/Formulas/Financial.ts
36index 4e1f5c2..1affc70 100644
37--- a/src/Formulas/Financial.ts
38+++ b/src/Formulas/Financial.ts
39@@ -1,8 +1,10 @@
40 import {
41- ArgsChecker,
42+ ArgsChecker
43+} from "../Utilities/ArgsChecker";
44+import {
45 TypeCaster,
46 checkForDevideByZero
47-} from "./Utils";
48+} from "../Utilities/TypeCaster";
49 import {
50 NumError,
51 DivZeroError
52diff --git a/src/Formulas/Logical.ts b/src/Formulas/Logical.ts
53index 747acf8..289121e 100644
54--- a/src/Formulas/Logical.ts
55+++ b/src/Formulas/Logical.ts
56@@ -1,5 +1,13 @@
57-import { ArgsChecker, TypeCaster } from "./Utils"
58-import { ValueError, RefError } from "../Errors"
59+import {
60+ ArgsChecker
61+} from "../Utilities/ArgsChecker";
62+import {
63+ TypeCaster
64+} from "../Utilities/TypeCaster";
65+import {
66+ ValueError,
67+ RefError
68+} from "../Errors";
69
70 /**
71 * Returns true if all of the provided arguments are logically true, and false if any of the provided arguments are logically false.
72diff --git a/src/Formulas/Math.ts b/src/Formulas/Math.ts
73index 335bcc3..1a0baf4 100644
74--- a/src/Formulas/Math.ts
75+++ b/src/Formulas/Math.ts
76@@ -1,10 +1,18 @@
77 import {
78- ArgsChecker,
79- CriteriaFunctionFactory,
80- Filter,
81- Serializer,
82+ ArgsChecker
83+} from "../Utilities/ArgsChecker";
84+import {
85 TypeCaster
86-} from "./Utils";
87+} from "../Utilities/TypeCaster";
88+import {
89+ Filter
90+} from "../Utilities/Filter";
91+import {
92+ Serializer
93+} from "../Utilities/Serializer";
94+import {
95+ CriteriaFunctionFactory
96+} from "../Utilities/CriteriaFunctionFactory";
97 import {
98 NumError,
99 DivZeroError,
100@@ -933,7 +941,7 @@ var SUMX2MY2 = function (...values) : number {
101 var COUNTUNIQUE = function (...values) : number {
102 ArgsChecker.checkAtLeastLength(values, 1);
103
104- // Private function that will recursively generate an array of the unique primatives
105+ // Private function that will recursively generate an array of the unique primitives
106 var countUniquePrivate = function (values: Array<any>) : Object {
107 var uniques = {};
108 for (var i = 0; i < values.length; i++) {
109diff --git a/src/Formulas/Statistical.ts b/src/Formulas/Statistical.ts
110index 70d7f15..f57a71a 100644
111--- a/src/Formulas/Statistical.ts
112+++ b/src/Formulas/Statistical.ts
113@@ -1,9 +1,15 @@
114 import {
115- ArgsChecker,
116- CriteriaFunctionFactory,
117- Filter,
118+ ArgsChecker
119+} from "../Utilities/ArgsChecker";
120+import {
121+ CriteriaFunctionFactory
122+} from "../Utilities/CriteriaFunctionFactory";
123+import {
124+ Filter
125+} from "../Utilities/Filter";
126+import {
127 TypeCaster
128-} from "./Utils";
129+} from "../Utilities/TypeCaster";
130 import {
131 RefError, NumError, DivZeroError, NAError
132 } from "../Errors";
133diff --git a/src/Formulas/Text.ts b/src/Formulas/Text.ts
134index dec14b8..6a9704e 100644
135--- a/src/Formulas/Text.ts
136+++ b/src/Formulas/Text.ts
137@@ -1,7 +1,9 @@
138 import {
139- ArgsChecker,
140+ ArgsChecker
141+} from "../Utilities/ArgsChecker";
142+import {
143 TypeCaster
144-} from "./Utils";
145+} from "../Utilities/TypeCaster";
146 import {
147 ValueError,
148 NumError,
149diff --git a/src/Utilities/ArgsChecker.ts b/src/Utilities/ArgsChecker.ts
150new file mode 100644
151index 0000000..be90c76
152--- /dev/null
153+++ b/src/Utilities/ArgsChecker.ts
154@@ -0,0 +1,46 @@
155+import {
156+ NAError
157+} from "../Errors";
158+
159+/**
160+ * Static class to check argument length within expected ranges when calling functions.
161+ */
162+class ArgsChecker {
163+ /**
164+ * Checks to see if the arguments are of the correct length.
165+ * @param args to check length of
166+ * @param length expected length
167+ */
168+ static checkLength(args: Array<any> | IArguments, length: number) {
169+ if (args.length !== length) {
170+ throw new NAError("Wrong number of arguments to ___. Expected 1 arguments, but got " + args.length + " arguments.");
171+ }
172+ }
173+
174+ /**
175+ * Checks to see if the arguments are at least a certain length.
176+ * @param args to check length of
177+ * @param length expected length
178+ */
179+ static checkAtLeastLength(args: any, length: number) {
180+ if (args.length < length) {
181+ throw new NAError("Wrong number of arguments to ___. Expected 1 arguments, but got " + args.length + " arguments.");
182+ }
183+ }
184+
185+ /**
186+ * Checks to see if the arguments are within a max and min, inclusively
187+ * @param args to check length of
188+ * @param low least number of arguments
189+ * @param high max number of arguments
190+ */
191+ static checkLengthWithin(args: any, low: number, high: number) {
192+ if (args.length > high || args.length < low) {
193+ throw new NAError("Wrong number of arguments to ___. Expected 1 arguments, but got " + args.length + " arguments.");
194+ }
195+ }
196+}
197+
198+export {
199+ ArgsChecker
200+}
201\ No newline at end of file
202diff --git a/src/Utilities/CriteriaFunctionFactory.ts b/src/Utilities/CriteriaFunctionFactory.ts
203new file mode 100644
204index 0000000..bee94e3
205--- /dev/null
206+++ b/src/Utilities/CriteriaFunctionFactory.ts
207@@ -0,0 +1,91 @@
208+/**
209+ * Converts wild-card style expressions (in which * matches zero or more characters, and ? matches exactly one character)
210+ * to regular expressions. * and ? can be escaped by prefixing ~
211+ * @param c input
212+ * @returns {RegExp} resulting regex
213+ */
214+function wildCardRegex(c: string) {
215+ var a = c.split("~?");
216+ for (var i = 0; i < a.length; i++) {
217+ a[i] = a[i].split("?").join(".{1}");
218+ }
219+ var b = a.join("\\\?");
220+ var d = b.split("~*");
221+ for (var i = 0; i < d.length; i++) {
222+ d[i] = d[i].split("*").join(".*");
223+ }
224+ return new RegExp("^"+d.join(".*")+"$", "g");
225+}
226+
227+
228+
229+
230+/**
231+ * Creates a criteria function to evaluate elements in a range in an *IF function.
232+ */
233+class CriteriaFunctionFactory {
234+ /**
235+ * If the criteria is a number, use strict equality checking.
236+ * If the criteria is a string, check to see if it is a comparator.
237+ * If the criteria is a string, and it is not a comparator, check for regex.
238+ * If the criteria is a string and has not matched the above, finally use strict equality checking as a fallback.
239+ * If the criteria has not been set, default to false-returning criteria function.
240+ * @param criteria
241+ * @returns {(x:any)=>boolean}
242+ */
243+ static createCriteriaFunction(criteria: string) : Function {
244+ // Default criteria does nothing
245+ var criteriaEvaluation = function (x) : boolean {
246+ return false;
247+ };
248+
249+ if (typeof criteria === "number" || typeof criteria === "boolean") {
250+ criteriaEvaluation = function (x) : boolean {
251+ return x === criteria;
252+ };
253+ } else if (typeof criteria === "string") {
254+ var comparisonMatches = criteria.match(/^\s*(<=|>=|=|<>|>|<)\s*(-)?\s*(\$)?\s*([0-9]+([,.][0-9]+)?)\s*$/);
255+ if (comparisonMatches !== null && comparisonMatches.length >= 6 && comparisonMatches[4] !== undefined) {
256+ criteriaEvaluation = function (x) : boolean {
257+ return eval(x + comparisonMatches[1] + (comparisonMatches[2] === undefined ? "" : "-") + comparisonMatches[4]);
258+ };
259+ if (comparisonMatches[1] === "=") {
260+ criteriaEvaluation = function (x) : boolean {
261+ return eval(x + "===" + (comparisonMatches[2] === undefined ? "" : "-") + comparisonMatches[4]);
262+ };
263+ }
264+ if (comparisonMatches[1] === "<>") {
265+ criteriaEvaluation = function (x) : boolean {
266+ return eval(x + "!==" + (comparisonMatches[2] === undefined ? "" : "-") + comparisonMatches[4]);
267+ };
268+ }
269+ } else if (criteria.match(/\*|\~\*|\?|\~\?/) !== null) {
270+ // Regular string
271+ var matches = criteria.match(/\*|\~\*|\?|\~\?/);
272+ if (matches !== null) {
273+ criteriaEvaluation = function (x) : boolean {
274+ try {
275+ // http://stackoverflow.com/questions/26246601/wildcard-string-comparison-in-javascript
276+ return wildCardRegex(criteria).test(x);
277+ } catch (e) {
278+ return false;
279+ }
280+ };
281+ } else {
282+ criteriaEvaluation = function (x) : boolean {
283+ return x === criteria;
284+ };
285+ }
286+ } else {
287+ criteriaEvaluation = function (x) : boolean {
288+ return x === criteria;
289+ };
290+ }
291+ }
292+ return criteriaEvaluation;
293+ }
294+}
295+
296+export {
297+ CriteriaFunctionFactory
298+}
299\ No newline at end of file
300diff --git a/src/Utilities/DateRegExBuilder.ts b/src/Utilities/DateRegExBuilder.ts
301new file mode 100644
302index 0000000..a25cb27
303--- /dev/null
304+++ b/src/Utilities/DateRegExBuilder.ts
305@@ -0,0 +1,198 @@
306+/**
307+ * Build a regular expression step by step, to make it easier to build and read the resulting regular expressions.
308+ */
309+class DateRegExBuilder {
310+ private regexString = "";
311+ private static ZERO_OR_MORE_SPACES = "\\s*";
312+
313+ static DateRegExBuilder() : DateRegExBuilder {
314+ return new DateRegExBuilder();
315+ }
316+
317+ /**
318+ * Start the regular expression builder by matching the start of a line and zero or more spaces.
319+ * @returns {DateRegExBuilder} builder
320+ */
321+ start() : DateRegExBuilder {
322+ this.regexString += "^" + DateRegExBuilder.ZERO_OR_MORE_SPACES;
323+ return this;
324+ }
325+
326+ /**
327+ * End the regular expression builder by matching the end of the line.
328+ * @returns {DateRegExBuilder} builder
329+ */
330+ end(): DateRegExBuilder {
331+ this.regexString += "$";
332+ return this;
333+ }
334+
335+ /**
336+ * Capture all month full name and short names to the regular expression.
337+ * @returns {DateRegExBuilder} builder
338+ */
339+ MONTHNAME() : DateRegExBuilder {
340+ this.regexString += "(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|oct|nov|dec)";
341+ return this;
342+ }
343+
344+ /**
345+ * Capture all month full name and short names to the regular expression, in addition to any followed by one or more
346+ * spaces.
347+ * @returns {DateRegExBuilder} builder
348+ * @constructor
349+ */
350+ MONTHNAME_W_SPACE() : DateRegExBuilder {
351+ this.regexString += "(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|oct|nov|dec|january\\s+|february\\s+|march\\s+|april\\s+|may\\s+|june\\s+|july\\s+|august\\s+|september\\s+|october\\s+|november\\s+|december\\s+|jan\\s+|feb\\s+|mar\\s+|apr\\s+|jun\\s+|jul\\s+|aug\\s+|sep\\s+|oct\\s+|nov\\s+|dec\\s+)";
352+ return this;
353+ }
354+
355+ /**
356+ * Add capture group for optionally capturing day names.
357+ * @returns {DateRegExBuilder} builder
358+ * @constructor
359+ */
360+ OPTIONAL_DAYNAME() : DateRegExBuilder {
361+ this.regexString += "(sunday|monday|tuesday|wednesday|thursday|friday|saturday|sun|mon|tue|wed|thu|fri|sat)?";
362+ return this;
363+ }
364+
365+ /**
366+ * Add capture group for optionally capturing a comma followed by one or more spaces.
367+ * @returns {DateRegExBuilder} builder
368+ * @constructor
369+ */
370+ OPTIONAL_COMMA() : DateRegExBuilder {
371+ this.regexString += "(,?\\s+)?";
372+ return this;
373+ }
374+
375+ /**
376+ * Add capture group for capturing month digits between 01 and 12, inclusively.
377+ * @returns {DateRegExBuilder} builder
378+ * @constructor
379+ */
380+ MM() : DateRegExBuilder {
381+ this.regexString += "([1-9]|0[1-9]|1[0-2])";
382+ return this;
383+ }
384+
385+ /**
386+ * Add capture group for capturing month digits between 01 and 12, inclusively, in addition to any followed by one or
387+ * more spaces.
388+ * @returns {DateRegExBuilder} builder
389+ * @constructor
390+ */
391+ MM_W_SPACE() : DateRegExBuilder {
392+ this.regexString += "([1-9]|0[1-9]|1[0-2]|[1-9]\\s+|0[1-9]\\s+|1[0-2]\\s+)";
393+ return this;
394+ }
395+
396+ /**
397+ * Add capture group for capturing day digits between 01 and 31, inclusively.
398+ * @returns {DateRegExBuilder} builder
399+ * @constructor
400+ */
401+ DD() : DateRegExBuilder {
402+ this.regexString += "(0?[0-9]|1[0-9]|2[0-9]|3[0-1])";
403+ return this;
404+ }
405+
406+ /**
407+ * Add capture group for capturing day digits between 01 and 31, inclusively, in addition to any followed by one or
408+ * more spaces.
409+ * @returns {DateRegExBuilder} builder
410+ * @constructor
411+ */
412+ DD_W_SPACE() : DateRegExBuilder {
413+ this.regexString += "(0?[0-9]|1[0-9]|2[0-9]|3[0-1]|0?[0-9]\\s+|1[0-9]\\s+|2[0-9]\\s+|3[0-1]\\s+)";
414+ return this;
415+ }
416+
417+ /**
418+ * Add capture group for capturing 4 digits or 3 digits starting with 0-9.
419+ * @returns {DateRegExBuilder} builder
420+ * @constructor
421+ */
422+ YYYY() : DateRegExBuilder {
423+ this.regexString += "([0-9]{4}|[1-9][0-9][0-9])";
424+ return this;
425+ }
426+
427+ /**
428+ * Add capture group for capturing 1 through 4 digits.
429+ * @returns {DateRegExBuilder} builder
430+ * @constructor
431+ */
432+ YYYY14() : DateRegExBuilder {
433+ this.regexString += "([0-9]{1,4})";
434+ return this;
435+ }
436+
437+ /**
438+ * Add capture group for capturing 1 through 4 digits, in addition to any followed by one or more spaces.
439+ * @returns {DateRegExBuilder} builder
440+ * @constructor
441+ */
442+ YYYY14_W_SPACE() : DateRegExBuilder {
443+ this.regexString += "([0-9]{1,4}|[0-9]{1,4}\\s+)";
444+ return this;
445+ }
446+
447+ YYYY2_OR_4_W_SPACE() : DateRegExBuilder {
448+ this.regexString += "([0-9]{2}|[0-9]{4}|[0-9]{2}\\s+|[0-9]{4}\\s+)";
449+ return this;
450+ }
451+
452+ /**
453+ * Add capture group for a flexible delimiter, including ", ", " ", ". ", "\", "-".
454+ * @returns {DateRegExBuilder} builder
455+ * @constructor
456+ */
457+ FLEX_DELIMITER() : DateRegExBuilder {
458+ // this.regexString += "(,?\\s+|\\s*-?\\.?-?\\/?\\s+)";// close to being right
459+ this.regexString += "(,?\\s+|\\s*\\.\\s+|\\s*-\\s*|\\s*\\/\\s*)";
460+ return this;
461+ }
462+
463+ /**
464+ * Add capture group for a flexible delimiter, including ", ", " ", ".", "\", "-". Different from FLEX_DELIMITER
465+ * in that it will match periods with zero or more spaces on either side.
466+ * For reference: https://regex101.com/r/q1fp1z/1/
467+ * @returns {DateRegExBuilder} builder
468+ * @constructor
469+ */
470+ FLEX_DELIMITER_LOOSEDOT() : DateRegExBuilder {
471+ // this.regexString += "(,?\\s+|\\s*-?\\.?-?\\/?\\s+)";// close to being right
472+ this.regexString += "(,?\\s+|\\s*\\.\\s*|\\s*-\\s*|\\s*\\/\\s*)";
473+ return this;
474+ }
475+
476+ /**
477+ * Add a capture group for capturing timestamps including: "10am", "10:10", "10:10pm", "10:10:10", "10:10:10am", along
478+ * with zero or more spaces after semi colons, AM or PM, and unlimited number of digits per unit.
479+ * @returns {DateRegExBuilder} builder
480+ * @constructor
481+ */
482+ OPTIONAL_TIMESTAMP_CAPTURE_GROUP() : DateRegExBuilder {
483+ this.regexString += "((\\s+[0-9]+\\s*am\\s*$|[0-9]+\\s*pm\\s*$)|(\\s+[0-9]+:\\s*[0-9]+\\s*$)|(\\s+[0-9]+:\\s*[0-9]+\\s*am\\s*$|\\s+[0-9]+:\\s*[0-9]+\\s*pm\\s*$)|(\\s+[0-9]+:\\s*[0-9]+:\\s*[0-9]+\\s*$)|(\\s+[0-9]+:\\s*[0-9]+:\\s*[0-9]+\\s*am\\s*$|[0-9]+:\\s*[0-9]+:\\s*[0-9]+\\s*pm\\s*$))?";
484+ return this;
485+ }
486+
487+ TIMESTAMP_UNITS_CAPTURE_GROUP() : DateRegExBuilder {
488+ this.regexString += "(\\s*([0-9]+)()()\\s*(am|pm)\\s*$)|(\\s*([0-9]+):\\s*([0-9]+)()()\\s*$)|(\\s*(([0-9]+):\\s*([0-9]+)()\\s*(am|pm))\\s*$)|(\\s*(([0-9]+):\\s*([0-9]+):\\s*([0-9]+)())\\s*$)|(\\s*(([0-9]+):\\s*([0-9]+):\\s*([0-9]+)\\s*(am|pm))\\s*$)";
489+ return this;
490+ }
491+
492+ /**
493+ * Build the regular expression and ignore case.
494+ * @returns {RegExp}
495+ */
496+ build() : RegExp {
497+ return new RegExp(this.regexString, 'i');
498+ }
499+}
500+
501+export {
502+ DateRegExBuilder
503+}
504\ No newline at end of file
505diff --git a/src/Utilities/Filter.ts b/src/Utilities/Filter.ts
506new file mode 100644
507index 0000000..f7d070b
508--- /dev/null
509+++ b/src/Utilities/Filter.ts
510@@ -0,0 +1,84 @@
511+import {
512+ RefError
513+} from "../Errors";
514+
515+/**
516+ * Static class to help filter down Arrays
517+ */
518+class Filter {
519+ /**
520+ * Converts string values in array to 0
521+ * @param arr to convert
522+ * @returns {Array} array in which all string values have been converted to 0.
523+ */
524+ static stringValuesToZeros(arr: Array<any>) : Array<any> {
525+ var toReturn = [];
526+ for (var i = 0; i < arr.length; i++) {
527+ if (typeof arr[i] !== "string") {
528+ toReturn.push(arr[i]);
529+ } else {
530+ toReturn.push(0);
531+ }
532+ }
533+ return toReturn;
534+ }
535+
536+ /**
537+ * Flatten an array of arrays of ...etc.
538+ * @param values array of values
539+ * @returns {Array} flattened array
540+ */
541+ static flatten(values: Array<any>) : Array<any> {
542+ return values.reduce(function (flat, toFlatten) {
543+ return flat.concat(Array.isArray(toFlatten) ? Filter.flatten(toFlatten) : toFlatten);
544+ }, []);
545+ }
546+
547+ /**
548+ * Flatten an array of arrays of... etc, but throw an error if any are empty references.
549+ * @param values array of values
550+ * @returns {Array} flattened array
551+ */
552+ static flattenAndThrow(values: Array<any>) : Array<any> {
553+ return values.reduce(function (flat, toFlatten) {
554+ if (Array.isArray(toFlatten) && toFlatten.length === 0) {
555+ throw new RefError("Reference does not exist.");
556+ }
557+ return flat.concat(Array.isArray(toFlatten) ? Filter.flattenAndThrow(toFlatten) : toFlatten);
558+ }, []);
559+ }
560+
561+ /**
562+ * Filter out all strings from an array.
563+ * @param arr to filter
564+ * @returns {Array} filtered array
565+ */
566+ static filterOutStringValues(arr: Array<any>) : Array<any> {
567+ var toReturn = [];
568+ for (var i = 0; i < arr.length; i++) {
569+ if (typeof arr[i] !== "string") {
570+ toReturn.push(arr[i]);
571+ }
572+ }
573+ return toReturn;
574+ }
575+
576+ /**
577+ * Filters out non number values.
578+ * @param arr to filter
579+ * @returns {Array} filtered array
580+ */
581+ static filterOutNonNumberValues(arr: Array<any>) : Array<any> {
582+ var toReturn = [];
583+ for (var i = 0; i < arr.length; i++) {
584+ if (typeof arr[i] !== "string" && typeof arr[i] !== "boolean") {
585+ toReturn.push(arr[i]);
586+ }
587+ }
588+ return toReturn;
589+ }
590+}
591+
592+export {
593+ Filter
594+}
595\ No newline at end of file
596diff --git a/src/Utilities/Serializer.ts b/src/Utilities/Serializer.ts
597new file mode 100644
598index 0000000..95e6c38
599--- /dev/null
600+++ b/src/Utilities/Serializer.ts
601@@ -0,0 +1,13 @@
602+/**
603+ * Class to hold static methods for serialization.
604+ */
605+class Serializer {
606+ static serialize(value: any) : string {
607+ var t = typeof value;
608+ return "<" + t + ": " + value + ">";
609+ }
610+}
611+
612+export {
613+ Serializer
614+}
615\ No newline at end of file
616diff --git a/src/Formulas/Utils.ts b/src/Utilities/TypeCaster.ts
617similarity index 60%
618rename from src/Formulas/Utils.ts
619rename to src/Utilities/TypeCaster.ts
620index cd32b8e..119c6b8 100644
621--- a/src/Formulas/Utils.ts
622+++ b/src/Utilities/TypeCaster.ts
623@@ -1,221 +1,15 @@
624 /// <reference path="../../node_modules/moment/moment.d.ts"/>
625 import * as moment from "moment";
626-import { ValueError, RefError, NAError, DivZeroError } from "../Errors"
627-import { ExcelDate } from "../ExcelDate";
628-
629-/**
630- * Converts wild-card style expressions (in which * matches zero or more characters, and ? matches exactly one character)
631- * to regular expressions. * and ? can be escaped by prefixing ~
632- * @param c input
633- * @returns {RegExp} resulting regex
634- */
635-function wildCardRegex(c: string) {
636- var a = c.split("~?");
637- for (var i = 0; i < a.length; i++) {
638- a[i] = a[i].split("?").join(".{1}");
639- }
640- var b = a.join("\\\?");
641- var d = b.split("~*");
642- for (var i = 0; i < d.length; i++) {
643- d[i] = d[i].split("*").join(".*");
644- }
645- return new RegExp("^"+d.join(".*")+"$", "g");
646-}
647-
648-/**
649- * Build a regular expression step by step, to make it easier to build and read the resulting regular expressions.
650- */
651-class DateRegExBuilder {
652- private regexString = "";
653- private static ZERO_OR_MORE_SPACES = "\\s*";
654-
655- static DateRegExBuilder() : DateRegExBuilder {
656- return new DateRegExBuilder();
657- }
658-
659- /**
660- * Start the regular expression builder by matching the start of a line and zero or more spaces.
661- * @returns {DateRegExBuilder} builder
662- */
663- start() : DateRegExBuilder {
664- this.regexString += "^" + DateRegExBuilder.ZERO_OR_MORE_SPACES;
665- return this;
666- }
667-
668- /**
669- * End the regular expression builder by matching the end of the line.
670- * @returns {DateRegExBuilder} builder
671- */
672- end(): DateRegExBuilder {
673- this.regexString += "$";
674- return this;
675- }
676-
677- /**
678- * Capture all month full name and short names to the regular expression.
679- * @returns {DateRegExBuilder} builder
680- */
681- MONTHNAME() : DateRegExBuilder {
682- this.regexString += "(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|oct|nov|dec)";
683- return this;
684- }
685-
686- /**
687- * Capture all month full name and short names to the regular expression, in addition to any followed by one or more
688- * spaces.
689- * @returns {DateRegExBuilder} builder
690- * @constructor
691- */
692- MONTHNAME_W_SPACE() : DateRegExBuilder {
693- this.regexString += "(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|oct|nov|dec|january\\s+|february\\s+|march\\s+|april\\s+|may\\s+|june\\s+|july\\s+|august\\s+|september\\s+|october\\s+|november\\s+|december\\s+|jan\\s+|feb\\s+|mar\\s+|apr\\s+|jun\\s+|jul\\s+|aug\\s+|sep\\s+|oct\\s+|nov\\s+|dec\\s+)";
694- return this;
695- }
696-
697- /**
698- * Add capture group for optionally capturing day names.
699- * @returns {DateRegExBuilder} builder
700- * @constructor
701- */
702- OPTIONAL_DAYNAME() : DateRegExBuilder {
703- this.regexString += "(sunday|monday|tuesday|wednesday|thursday|friday|saturday|sun|mon|tue|wed|thu|fri|sat)?";
704- return this;
705- }
706-
707- /**
708- * Add capture group for optionally capturing a comma followed by one or more spaces.
709- * @returns {DateRegExBuilder} builder
710- * @constructor
711- */
712- OPTIONAL_COMMA() : DateRegExBuilder {
713- this.regexString += "(,?\\s+)?";
714- return this;
715- }
716-
717- /**
718- * Add capture group for capturing month digits between 01 and 12, inclusively.
719- * @returns {DateRegExBuilder} builder
720- * @constructor
721- */
722- MM() : DateRegExBuilder {
723- this.regexString += "([1-9]|0[1-9]|1[0-2])";
724- return this;
725- }
726-
727- /**
728- * Add capture group for capturing month digits between 01 and 12, inclusively, in addition to any followed by one or
729- * more spaces.
730- * @returns {DateRegExBuilder} builder
731- * @constructor
732- */
733- MM_W_SPACE() : DateRegExBuilder {
734- this.regexString += "([1-9]|0[1-9]|1[0-2]|[1-9]\\s+|0[1-9]\\s+|1[0-2]\\s+)";
735- return this;
736- }
737-
738- /**
739- * Add capture group for capturing day digits between 01 and 31, inclusively.
740- * @returns {DateRegExBuilder} builder
741- * @constructor
742- */
743- DD() : DateRegExBuilder {
744- this.regexString += "(0?[0-9]|1[0-9]|2[0-9]|3[0-1])";
745- return this;
746- }
747-
748- /**
749- * Add capture group for capturing day digits between 01 and 31, inclusively, in addition to any followed by one or
750- * more spaces.
751- * @returns {DateRegExBuilder} builder
752- * @constructor
753- */
754- DD_W_SPACE() : DateRegExBuilder {
755- this.regexString += "(0?[0-9]|1[0-9]|2[0-9]|3[0-1]|0?[0-9]\\s+|1[0-9]\\s+|2[0-9]\\s+|3[0-1]\\s+)";
756- return this;
757- }
758-
759- /**
760- * Add capture group for capturing 4 digits or 3 digits starting with 0-9.
761- * @returns {DateRegExBuilder} builder
762- * @constructor
763- */
764- YYYY() : DateRegExBuilder {
765- this.regexString += "([0-9]{4}|[1-9][0-9][0-9])";
766- return this;
767- }
768-
769- /**
770- * Add capture group for capturing 1 through 4 digits.
771- * @returns {DateRegExBuilder} builder
772- * @constructor
773- */
774- YYYY14() : DateRegExBuilder {
775- this.regexString += "([0-9]{1,4})";
776- return this;
777- }
778-
779- /**
780- * Add capture group for capturing 1 through 4 digits, in addition to any followed by one or more spaces.
781- * @returns {DateRegExBuilder} builder
782- * @constructor
783- */
784- YYYY14_W_SPACE() : DateRegExBuilder {
785- this.regexString += "([0-9]{1,4}|[0-9]{1,4}\\s+)";
786- return this;
787- }
788-
789- YYYY2_OR_4_W_SPACE() : DateRegExBuilder {
790- this.regexString += "([0-9]{2}|[0-9]{4}|[0-9]{2}\\s+|[0-9]{4}\\s+)";
791- return this;
792- }
793-
794- /**
795- * Add capture group for a flexible delimiter, including ", ", " ", ". ", "\", "-".
796- * @returns {DateRegExBuilder} builder
797- * @constructor
798- */
799- FLEX_DELIMITER() : DateRegExBuilder {
800- // this.regexString += "(,?\\s+|\\s*-?\\.?-?\\/?\\s+)";// close to being right
801- this.regexString += "(,?\\s+|\\s*\\.\\s+|\\s*-\\s*|\\s*\\/\\s*)";
802- return this;
803- }
804-
805- /**
806- * Add capture group for a flexible delimiter, including ", ", " ", ".", "\", "-". Different from FLEX_DELIMITER
807- * in that it will match periods with zero or more spaces on either side.
808- * For reference: https://regex101.com/r/q1fp1z/1/
809- * @returns {DateRegExBuilder} builder
810- * @constructor
811- */
812- FLEX_DELIMITER_LOOSEDOT() : DateRegExBuilder {
813- // this.regexString += "(,?\\s+|\\s*-?\\.?-?\\/?\\s+)";// close to being right
814- this.regexString += "(,?\\s+|\\s*\\.\\s*|\\s*-\\s*|\\s*\\/\\s*)";
815- return this;
816- }
817-
818- /**
819- * Add a capture group for capturing timestamps including: "10am", "10:10", "10:10pm", "10:10:10", "10:10:10am", along
820- * with zero or more spaces after semi colons, AM or PM, and unlimited number of digits per unit.
821- * @returns {DateRegExBuilder} builder
822- * @constructor
823- */
824- OPTIONAL_TIMESTAMP_CAPTURE_GROUP() : DateRegExBuilder {
825- this.regexString += "((\\s+[0-9]+\\s*am\\s*$|[0-9]+\\s*pm\\s*$)|(\\s+[0-9]+:\\s*[0-9]+\\s*$)|(\\s+[0-9]+:\\s*[0-9]+\\s*am\\s*$|\\s+[0-9]+:\\s*[0-9]+\\s*pm\\s*$)|(\\s+[0-9]+:\\s*[0-9]+:\\s*[0-9]+\\s*$)|(\\s+[0-9]+:\\s*[0-9]+:\\s*[0-9]+\\s*am\\s*$|[0-9]+:\\s*[0-9]+:\\s*[0-9]+\\s*pm\\s*$))?";
826- return this;
827- }
828-
829- TIMESTAMP_UNITS_CAPTURE_GROUP() : DateRegExBuilder {
830- this.regexString += "(\\s*([0-9]+)()()\\s*(am|pm)\\s*$)|(\\s*([0-9]+):\\s*([0-9]+)()()\\s*$)|(\\s*(([0-9]+):\\s*([0-9]+)()\\s*(am|pm))\\s*$)|(\\s*(([0-9]+):\\s*([0-9]+):\\s*([0-9]+)())\\s*$)|(\\s*(([0-9]+):\\s*([0-9]+):\\s*([0-9]+)\\s*(am|pm))\\s*$)";
831- return this;
832- }
833-
834- /**
835- * Build the regular expression and ignore case.
836- * @returns {RegExp}
837- */
838- build() : RegExp {
839- return new RegExp(this.regexString, 'i');
840- }
841-}
842+import {
843+ RefError,
844+ ValueError, DivZeroError
845+} from "../Errors";
846+import {
847+ ExcelDate
848+} from "../ExcelDate";
849+import {
850+ DateRegExBuilder
851+} from "./DateRegExBuilder";
852
853 const YEAR_MONTHDIG_DAY = DateRegExBuilder.DateRegExBuilder()
854 .start()
855@@ -268,74 +62,6 @@ const FIRST_YEAR = 1900;
856 // The year 2000.
857 const Y2K_YEAR = 2000;
858
859-
860-/**
861- * Creates a criteria function to evaluate elements in a range in an *IF function.
862- */
863-class CriteriaFunctionFactory {
864- /**
865- * If the criteria is a number, use strict equality checking.
866- * If the criteria is a string, check to see if it is a comparator.
867- * If the criteria is a string, and it is not a comparator, check for regex.
868- * If the criteria is a string and has not matched the above, finally use strict equality checking as a fallback.
869- * If the criteria has not been set, default to false-returning criteria function.
870- * @param criteria
871- * @returns {(x:any)=>boolean}
872- */
873- static createCriteriaFunction(criteria: string) : Function {
874- // Default criteria does nothing
875- var criteriaEvaluation = function (x) : boolean {
876- return false;
877- };
878-
879- if (typeof criteria === "number" || typeof criteria === "boolean") {
880- criteriaEvaluation = function (x) : boolean {
881- return x === criteria;
882- };
883- } else if (typeof criteria === "string") {
884- var comparisonMatches = criteria.match(/^\s*(<=|>=|=|<>|>|<)\s*(-)?\s*(\$)?\s*([0-9]+([,.][0-9]+)?)\s*$/);
885- if (comparisonMatches !== null && comparisonMatches.length >= 6 && comparisonMatches[4] !== undefined) {
886- criteriaEvaluation = function (x) : boolean {
887- return eval(x + comparisonMatches[1] + (comparisonMatches[2] === undefined ? "" : "-") + comparisonMatches[4]);
888- };
889- if (comparisonMatches[1] === "=") {
890- criteriaEvaluation = function (x) : boolean {
891- return eval(x + "===" + (comparisonMatches[2] === undefined ? "" : "-") + comparisonMatches[4]);
892- };
893- }
894- if (comparisonMatches[1] === "<>") {
895- criteriaEvaluation = function (x) : boolean {
896- return eval(x + "!==" + (comparisonMatches[2] === undefined ? "" : "-") + comparisonMatches[4]);
897- };
898- }
899- } else if (criteria.match(/\*|\~\*|\?|\~\?/) !== null) {
900- // Regular string
901- var matches = criteria.match(/\*|\~\*|\?|\~\?/);
902- if (matches !== null) {
903- criteriaEvaluation = function (x) : boolean {
904- try {
905- // http://stackoverflow.com/questions/26246601/wildcard-string-comparison-in-javascript
906- return wildCardRegex(criteria).test(x);
907- } catch (e) {
908- return false;
909- }
910- };
911- } else {
912- criteriaEvaluation = function (x) : boolean {
913- return x === criteria;
914- };
915- }
916- } else {
917- criteriaEvaluation = function (x) : boolean {
918- return x === criteria;
919- };
920- }
921- }
922- return criteriaEvaluation;
923- }
924-}
925-
926-
927 /**
928 * Matches a timestamp string, adding the units to the moment passed in.
929 * @param timestampString to parse. ok formats: "10am", "10:10", "10:10am", "10:10:10", "10:10:10am", etc.
930@@ -398,7 +124,6 @@ function matchTimestampAndMutateMoment(timestampString : string, momentToMutate:
931 return momentToMutate;
932 }
933
934-
935 /**
936 * Static class of helpers used to cast various types to each other.
937 */
938@@ -841,132 +566,6 @@ class TypeCaster {
939 }
940 }
941
942-/**
943- * Static class to help filter down Arrays
944- */
945-class Filter {
946- /**
947- * Converts string values in array to 0
948- * @param arr to convert
949- * @returns {Array} array in which all string values have been converted to 0.
950- */
951- static stringValuesToZeros(arr: Array<any>) : Array<any> {
952- var toReturn = [];
953- for (var i = 0; i < arr.length; i++) {
954- if (typeof arr[i] !== "string") {
955- toReturn.push(arr[i]);
956- } else {
957- toReturn.push(0);
958- }
959- }
960- return toReturn;
961- }
962-
963- /**
964- * Flatten an array of arrays of ...etc.
965- * @param values array of values
966- * @returns {Array} flattened array
967- */
968- static flatten(values: Array<any>) : Array<any> {
969- return values.reduce(function (flat, toFlatten) {
970- return flat.concat(Array.isArray(toFlatten) ? Filter.flatten(toFlatten) : toFlatten);
971- }, []);
972- }
973-
974- /**
975- * Flatten an array of arrays of... etc, but throw an error if any are empty references.
976- * @param values array of values
977- * @returns {Array} flattened array
978- */
979- static flattenAndThrow(values: Array<any>) : Array<any> {
980- return values.reduce(function (flat, toFlatten) {
981- if (Array.isArray(toFlatten) && toFlatten.length === 0) {
982- throw new RefError("Reference does not exist.");
983- }
984- return flat.concat(Array.isArray(toFlatten) ? Filter.flattenAndThrow(toFlatten) : toFlatten);
985- }, []);
986- }
987-
988- /**
989- * Filter out all strings from an array.
990- * @param arr to filter
991- * @returns {Array} filtered array
992- */
993- static filterOutStringValues(arr: Array<any>) : Array<any> {
994- var toReturn = [];
995- for (var i = 0; i < arr.length; i++) {
996- if (typeof arr[i] !== "string") {
997- toReturn.push(arr[i]);
998- }
999- }
1000- return toReturn;
1001- }
1002-
1003- /**
1004- * Filters out non number values.
1005- * @param arr to filter
1006- * @returns {Array} filtered array
1007- */
1008- static filterOutNonNumberValues(arr: Array<any>) : Array<any> {
1009- var toReturn = [];
1010- for (var i = 0; i < arr.length; i++) {
1011- if (typeof arr[i] !== "string" && typeof arr[i] !== "boolean") {
1012- toReturn.push(arr[i]);
1013- }
1014- }
1015- return toReturn;
1016- }
1017-}
1018-
1019-/**
1020- * Static class to check argument length within expected ranges when calling functions.
1021- */
1022-class ArgsChecker {
1023- /**
1024- * Checks to see if the arguments are of the correct length.
1025- * @param args to check length of
1026- * @param length expected length
1027- */
1028- static checkLength(args: Array<any> | IArguments, length: number) {
1029- if (args.length !== length) {
1030- throw new NAError("Wrong number of arguments to ___. Expected 1 arguments, but got " + args.length + " arguments.");
1031- }
1032- }
1033-
1034- /**
1035- * Checks to see if the arguments are at least a certain length.
1036- * @param args to check length of
1037- * @param length expected length
1038- */
1039- static checkAtLeastLength(args: any, length: number) {
1040- if (args.length < length) {
1041- throw new NAError("Wrong number of arguments to ___. Expected 1 arguments, but got " + args.length + " arguments.");
1042- }
1043- }
1044-
1045- /**
1046- * Checks to see if the arguments are within a max and min, inclusively
1047- * @param args to check length of
1048- * @param low least number of arguments
1049- * @param high max number of arguments
1050- */
1051- static checkLengthWithin(args: any, low: number, high: number) {
1052- if (args.length > high || args.length < low) {
1053- throw new NAError("Wrong number of arguments to ___. Expected 1 arguments, but got " + args.length + " arguments.");
1054- }
1055- }
1056-}
1057-
1058-/**
1059- * Class to hold static methods for serialization.
1060- */
1061-class Serializer {
1062- static serialize(value: any) : string {
1063- var t = typeof value;
1064- return "<" + t + ": " + value + ">";
1065- }
1066-}
1067-
1068 /**
1069 * Catches divide by zero situations and throws them as errors
1070 * @param n number to check
1071@@ -980,16 +579,7 @@ var checkForDevideByZero = function(n : number) : number {
1072 return n;
1073 };
1074
1075-var divideAndCheck
1076-
1077-
1078-
1079 export {
1080- ArgsChecker,
1081- CriteriaFunctionFactory,
1082- DateRegExBuilder,
1083- Filter,
1084- Serializer,
1085 TypeCaster,
1086 checkForDevideByZero
1087 }
1088\ No newline at end of file
1089diff --git a/tests/Formulas/DateFormulasTest.ts b/tests/Formulas/DateFormulasTest.ts
1090index 7877a48..b66bf0e 100644
1091--- a/tests/Formulas/DateFormulasTest.ts
1092+++ b/tests/Formulas/DateFormulasTest.ts
1093@@ -1,4 +1,3 @@
1094-
1095 import {
1096 DATE,
1097 DATEVALUE,
1098diff --git a/tests/Formulas/EngineeringTest.ts b/tests/Formulas/EngineeringTest.ts
1099index 132b5cd..138d6d5 100644
1100--- a/tests/Formulas/EngineeringTest.ts
1101+++ b/tests/Formulas/EngineeringTest.ts
1102@@ -11,7 +11,7 @@ import * as ERRORS from "../../src/Errors";
1103 import {
1104 assertEquals,
1105 catchAndAssertEquals
1106-} from "../utils/Asserts"
1107+} from "../utils/Asserts";
1108
1109
1110 // Test BIN2DEC